All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.cmu.tetradapp.editor.SemImEditor Maven / Gradle / Ivy

The newest version!
///////////////////////////////////////////////////////////////////////////////
// For information as to what this class does, see the Javadoc, below.       //
// Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,       //
// 2007, 2008, 2009, 2010, 2014, 2015, 2022 by Peter Spirtes, Richard        //
// Scheines, Joseph Ramsey, and Clark Glymour.                               //
//                                                                           //
// This program is free software; you can redistribute it and/or modify      //
// it under the terms of the GNU General Public License as published by      //
// the Free Software Foundation; either version 2 of the License, or         //
// (at your option) any later version.                                       //
//                                                                           //
// This program is distributed in the hope that it will be useful,           //
// but WITHOUT ANY WARRANTY; without even the implied warranty of            //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             //
// GNU General Public License for more details.                              //
//                                                                           //
// You should have received a copy of the GNU General Public License         //
// along with this program; if not, write to the Free Software               //
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA //
///////////////////////////////////////////////////////////////////////////////
package edu.cmu.tetradapp.editor;

import edu.cmu.tetrad.data.Knowledge;
import edu.cmu.tetrad.graph.*;
import edu.cmu.tetrad.sem.*;
import edu.cmu.tetrad.util.Matrix;
import edu.cmu.tetrad.util.NumberFormatUtil;
import edu.cmu.tetrad.util.ProbUtils;
import edu.cmu.tetradapp.model.EditorUtils;
import edu.cmu.tetradapp.model.SemEstimatorWrapper;
import edu.cmu.tetradapp.model.SemImWrapper;
import edu.cmu.tetradapp.util.DoubleTextField;
import edu.cmu.tetradapp.util.LayoutEditable;
import edu.cmu.tetradapp.workbench.DisplayNode;
import edu.cmu.tetradapp.workbench.GraphNodeMeasured;
import edu.cmu.tetradapp.workbench.GraphWorkbench;
import edu.cmu.tetradapp.workbench.LayoutMenu;
import nu.xom.Document;
import nu.xom.Element;
import nu.xom.Serializer;
import org.apache.commons.math3.util.FastMath;

import javax.swing.*;
import javax.swing.border.TitledBorder;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableModel;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Serial;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Edits a SEM instantiated model.
 *
 * @author Donald Crimbchin
 * @author josephramsey
 * @version $Id: $Id
 */
public final class SemImEditor extends JPanel implements LayoutEditable, DoNotScroll {

    @Serial
    private static final long serialVersionUID = -1856607070184945405L;

    /**
     * The panel in which the graphical editor is displayed.
     */
    private final JPanel targetPanel;

    /**
     * The graphical editor for the SEM IM.
     */
    private OneEditor oneEditorPanel;

    /**
     * The SEM IM being edited.
     */
    private SemImWrapper wrapper;

    /**
     * Constructs a new SemImEditor from the given OldSemEstimateAdapter.
     *
     * @param semImWrapper a {@link edu.cmu.tetradapp.model.SemImWrapper} object
     */
    public SemImEditor(SemImWrapper semImWrapper) {
        this(semImWrapper, "Graphical Editor", "Tabular Editor", TabbedPaneDefault.GRAPHICAL);
    }

    /**
     * Constructs a new SemImEditor from the given OldSemEstimateAdapter.
     *
     * @param semEstWrapper a {@link edu.cmu.tetradapp.model.SemEstimatorWrapper} object
     */
    public SemImEditor(SemEstimatorWrapper semEstWrapper) {
        this(new SemImWrapper(semEstWrapper.getSemEstimator().getEstimatedSem()));
    }

    /**
     * Constructs an editor for the given SemIm.
     *
     * @param wrapper              a {@link edu.cmu.tetradapp.model.SemImWrapper} object
     * @param graphicalEditorTitle a {@link java.lang.String} object
     * @param tabularEditorTitle   a {@link java.lang.String} object
     * @param tabbedPaneDefault    a {@link edu.cmu.tetradapp.editor.SemImEditor.TabbedPaneDefault} object
     */
    public SemImEditor(SemImWrapper wrapper, String graphicalEditorTitle,
                       String tabularEditorTitle, TabbedPaneDefault tabbedPaneDefault) {

        if (wrapper == null) {
            throw new NullPointerException("The SEM IM wrapper has not been specified.");
        }

        setLayout(new BorderLayout());
        this.targetPanel = new JPanel();
        this.targetPanel.setLayout(new BorderLayout());
        add(this.targetPanel, BorderLayout.CENTER);

        this.wrapper = wrapper;

        if (wrapper.getNumModels() > 1) {
            JComboBox comp = new JComboBox<>();

            for (int i = 0; i < wrapper.getNumModels(); i++) {
                comp.addItem(i + 1);
            }

            comp.setSelectedIndex(wrapper.getModelIndex());

            comp.addActionListener((e) -> {
                Object selectedItem = comp.getSelectedItem();
                if (selectedItem instanceof Integer) {
                    wrapper.setModelIndex(((Integer) selectedItem) - 1);
                    this.oneEditorPanel = new OneEditor(wrapper, graphicalEditorTitle, tabularEditorTitle, tabbedPaneDefault);
                    this.targetPanel.add(this.oneEditorPanel, BorderLayout.CENTER);
                    validate();
                }
            });

            Box b = Box.createHorizontalBox();
            b.add(new JLabel("Using model"));
            b.add(comp);
            b.add(new JLabel("from "));
            b.add(new JLabel(wrapper.getModelSourceName()));
            b.add(Box.createHorizontalGlue());

            add(b, BorderLayout.NORTH);
        }

        this.oneEditorPanel = new OneEditor(wrapper, graphicalEditorTitle, tabularEditorTitle, tabbedPaneDefault);
        this.targetPanel.add(this.oneEditorPanel, BorderLayout.CENTER);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Graph getGraph() {
        return this.oneEditorPanel.getGraph();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Map getModelEdgesToDisplay() {
        return this.oneEditorPanel.getModelEdgesToDisplay();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Map getModelNodesToDisplay() {
        return this.oneEditorPanel.getModelNodesToDisplay();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Knowledge getKnowledge() {
        return this.oneEditorPanel.getKnowledge();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Graph getSourceGraph() {
        return this.oneEditorPanel.getSourceGraph();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void layoutByGraph(Graph graph) {
        this.oneEditorPanel.layoutByGraph(graph);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void layoutByKnowledge() {

    }

    /**
     * 

setEditable.

* * @param editable a boolean */ public void setEditable(boolean editable) { this.oneEditorPanel.setEditable(editable); } /** *

getTabSelectionIndex.

* * @return a int */ public int getTabSelectionIndex() { return this.oneEditorPanel.getTabSelectionIndex(); } /** *

getMatrixSelection.

* * @return a int */ public int getMatrixSelection() { return 0; } /** *

getWorkbench.

* * @return a {@link edu.cmu.tetradapp.workbench.GraphWorkbench} object */ public GraphWorkbench getWorkbench() { return this.oneEditorPanel.getWorkbench(); } /** *

displaySemIm.

* * @param updatedSem a {@link edu.cmu.tetrad.sem.SemIm} object * @param tabSelectionIndex a int * @param matrixSelection a int */ public void displaySemIm(SemIm updatedSem, int tabSelectionIndex, int matrixSelection) { this.oneEditorPanel.displaySemIm(updatedSem, tabSelectionIndex, matrixSelection); } private Component getComp() { EditorWindow editorWindow = (EditorWindow) SwingUtilities.getAncestorOfClass( EditorWindow.class, this); if (editorWindow != null) { return editorWindow.getRootPane().getContentPane(); } else { return null; } } /** * An enum to say which tab is being displayed. */ public enum TabbedPaneDefault { /** * GRAPHICAL. */ GRAPHICAL, /** * TABULAR. */ TABULAR, /** * COVMATRIX. */ COVMATRIX, /** * Default. */ tabbedPanedDefault, /** * STATS. */ STATS } /** * Dispays the implied covariance and correlation matrices for the given SemIm. */ static class ImpliedMatricesPanel extends JPanel { private static final long serialVersionUID = 2462316724126834072L; private final SemImWrapper wrapper; private JTable impliedJTable; private JComboBox selector; public ImpliedMatricesPanel(SemImWrapper wrapper, int matrixSelection) { this.wrapper = wrapper; setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); add(selector()); add(Box.createVerticalStrut(10)); add(new JScrollPane(impliedJTable())); add(Box.createVerticalGlue()); setBorder(new TitledBorder("Select Implied Matrix to View")); setMatrixSelection(matrixSelection); } /** * @return the matrix in tab delimited form. */ public String getMatrixInTabDelimitedForm() { StringBuilder builder = new StringBuilder(); TableModel model = impliedJTable().getModel(); for (int row = 0; row < model.getRowCount(); row++) { for (int col = 0; col < model.getColumnCount(); col++) { Object o = model.getValueAt(row, col); if (o != null) { builder.append(o); } builder.append('\t'); } builder.append('\n'); } return builder.toString(); } private JTable impliedJTable() { if (this.impliedJTable == null) { this.impliedJTable = new JTable(); this.impliedJTable.setTableHeader(null); } return this.impliedJTable; } private JComboBox selector() { if (this.selector == null) { this.selector = new JComboBox<>(); List selections = getImpliedSelections(); for (String selection : selections) { this.selector.addItem(selection); } this.selector.addItemListener((e) -> { String item = (String) e.getItem(); setMatrixSelection(getImpliedSelections().indexOf(item)); }); } return this.selector; } public void setMatrixSelection(int index) { selector().setSelectedIndex(index); switchView(index); } private void switchView(int index) { if (index < 0 || index > 3) { throw new IllegalArgumentException( "Matrix selection must be 0, 1, 2, or 3."); } switch (index) { case 0: switchView(false, false); break; case 1: switchView(true, false); break; case 2: switchView(false, true); break; case 3: switchView(true, true); break; } } private void switchView(boolean a, boolean b) { impliedJTable().setModel(new ImpliedCovTable(this.wrapper, a, b)); // impliedJTable().getTableHeader().setReorderingAllowed(false); impliedJTable().setAutoResizeMode(JTable.AUTO_RESIZE_OFF); impliedJTable().setRowSelectionAllowed(false); impliedJTable().setCellSelectionEnabled(false); impliedJTable().doLayout(); } private List getImpliedSelections() { List list = new ArrayList<>(); list.add("Implied covariance matrix (all variables)"); list.add("Implied covariance matrix (measured variables only)"); list.add("Implied correlation matrix (all variables)"); list.add("Implied correlation matrix (measured variables only)"); return list; } } /** * Presents a covariance matrix as a table model for the SemImEditor. * * @author Donald Crimbchin */ static final class ImpliedCovTable extends AbstractTableModel { private static final long serialVersionUID = -8269181589527893805L; /** * True iff the matrices for the observed variables ony should be displayed. */ private final boolean measured; /** * True iff correlations (rather than covariances) should be displayed. */ private final boolean correlations; /** * Formats numbers so that they have 4 digits after the decimal place. */ private final NumberFormat nf; private final SemImWrapper wrapper; /** * The matrix being displayed. (This varies.) */ private final double[][] matrix; /** * Constructs a new table for the given covariance matrix, the nodes for which are as specified (in the order * they appear in the matrix). */ public ImpliedCovTable(SemImWrapper wrapper, boolean measured, boolean correlations) { this.wrapper = wrapper; this.measured = measured; this.correlations = correlations; this.nf = NumberFormatUtil.getInstance().getNumberFormat(); if (measured() && covariances()) { this.matrix = getSemIm().getImplCovarMeas().toArray(); } else if (measured() ) { this.matrix = corr(getSemIm().getImplCovarMeas().toArray()); } else if (covariances()) { Matrix implCovarC = getSemIm().getImplCovar(false); this.matrix = implCovarC.toArray(); } else { Matrix implCovarC = getSemIm().getImplCovar(false); this.matrix = corr(implCovarC.toArray()); } } /** * @return the number of rows being displayed--one more than the size of the matrix, which may be different * depending on whether only the observed variables are being displayed or all the variables are being * displayed. */ @Override public int getRowCount() { if (measured()) { return this.getSemIm().getMeasuredNodes().size() + 1; } else { return this.getSemIm().getVariableNodes().size() + 1; } } /** * @return the number of columns displayed--one more than the size of the matrix, which may be different * depending on whether only the observed variables are being displayed or all the variables are being * displayed. */ @Override public int getColumnCount() { if (measured()) { return this.getSemIm().getMeasuredNodes().size() + 1; } else { return this.getSemIm().getVariableNodes().size() + 1; } } /** * @return the name of the column at columnIndex, which is "" for column 0 and the name of the variable for the * other columns. */ @Override public String getColumnName(int columnIndex) { if (columnIndex == 0) { return ""; } else { if (measured()) { List nodes = getSemIm().getMeasuredNodes(); Node node = (nodes.get(columnIndex - 1)); return node.getName(); } else { List nodes = getSemIm().getVariableNodes(); Node node = (nodes.get(columnIndex - 1)); return node.getName(); } } } /** * @return the value being displayed in a cell, either a variable name or a Double. */ @Override public Object getValueAt(int rowIndex, int columnIndex) { if (rowIndex == 0) { return getColumnName(columnIndex); } if (columnIndex == 0) { return getColumnName(rowIndex); } else if (rowIndex < columnIndex) { return null; } else { return this.nf.format(this.matrix[rowIndex - 1][columnIndex - 1]); } } private boolean covariances() { return !correlations(); } private double[][] corr(double[][] implCovar) { int length = implCovar.length; double[][] corr = new double[length][length]; for (int i = 1; i < length; i++) { for (int j = 0; j < i; j++) { double d1 = implCovar[i][j]; double d2 = implCovar[i][i]; double d3 = implCovar[j][j]; double d4 = d1 / FastMath.pow(d2 * d3, 0.5); if (d4 <= 1.0 || Double.isNaN(d4)) { corr[i][j] = d4; } else { throw new IllegalArgumentException( "Off-diagonal element at (" + i + ", " + j + ") cannot be converted to correlation: " + d1 + " <= FastMath.pow(" + d2 + " * " + d3 + ", 0.5)"); } } } for (int i = 0; i < length; i++) { corr[i][i] = 1.0; } return corr; } /** * @return true iff only observed variables are displayed. */ private boolean measured() { return this.measured; } /** * @return true iff correlations (rather than covariances) are displayed. */ private boolean correlations() { return this.correlations; } private ISemIm getSemIm() { return this.wrapper.getSemIm(); } } static final class ModelStatisticsPanel extends JTextArea { private static final long serialVersionUID = -9096723049787232471L; private final NumberFormat nf = NumberFormatUtil.getInstance().getNumberFormat(); private final SemImWrapper wrapper; public ModelStatisticsPanel(SemImWrapper wrapper) { this.wrapper = wrapper; reset(); addComponentListener(new ComponentAdapter() { @Override public void componentShown(ComponentEvent e) { reset(); } }); } private void reset() { setText(""); setLineWrap(true); setWrapStyleWord(true); double modelChiSquare; double modelDof; double modelPValue; SemPm semPm = semIm().getSemPm(); List variables = semPm.getVariableNodes(); boolean containsLatent = false; for (Node node : variables) { if (node.getNodeType() == NodeType.LATENT) { containsLatent = true; break; } } try { modelChiSquare = semIm().getChiSquare(); modelDof = semIm().getSemPm().getDof(); modelPValue = semIm().getPValue(); } catch (Exception exception) { append("Model statistics not available."); return; } if (containsLatent) { append("\nEstimated degrees of Freedom = " + (int) modelDof); } else { append("\nDegrees of Freedom = " + (int) modelDof); } // append("\n(If the model is latent, this is the estimated degrees of freedom.)"); append("\nChi Square = " + this.nf.format(modelChiSquare)); if (modelDof >= 0) { String pValueString = modelPValue > 0.001 ? this.nf.format(modelPValue) : new DecimalFormat("0.0000E0").format(modelPValue); append("\nP Value = " + (Double.isNaN(modelPValue) || modelDof == 0 ? "undefined" : pValueString)); append("\nBIC Score = " + this.nf.format(semIm().getBicScore())); append("\nCFI = " + this.nf.format(semIm().getCfi())); append("\nRMSEA = " + this.nf.format(semIm().getRmsea())); } else { int numToFix = (int) FastMath.abs(modelDof); append("\n\nA SEM with negative degrees of freedom is underidentified, " + "\nand other model statistics are meaningless. Please increase " + "\nthe degrees of freedom to 0 or above by fixing at least " + numToFix + " parameter" + (numToFix == 1 ? "." : "s.")); } append("\n\nThe above chi square test assumes that the maximum " + "likelihood function over the measured variables has been " + "minimized. Under that assumption, the null hypothesis for " + "the test is that the population covariance matrix over all " + "of the measured variables is equal to the estimated covariance " + "matrix over all of the measured variables written as a function " + "of the free model parameters--that is, the unfixed parameters " + "for each directed edge (the linear coefficient for that edge), " + "each exogenous variable (the variance for the error term for " + "that variable), and each bidirected edge (the covariance for " + "the exogenous variables it connects). The model is explained " + "in Bollen, Structural Equations with Latent Variable, 110. " + "Degrees of freedom are calculated as m (m + 1) / 2 - d, where d " + "is the number of linear coefficients, variance terms, and error " + "covariance terms that are not fixed in the model. For latent models, " + "the degrees of freedom are termed 'estimated' since extra contraints " + "(e.g. pentad constraints) are not taken into account."); } private ISemIm semIm() { return this.wrapper.getSemIm(); } } private class OneEditor extends JPanel implements LayoutEditable { private static final long serialVersionUID = 6622060253747442717L; private final SemImWrapper semImWrapper; /** * Maximum number of free parameters for which statistics will be calculated. (Calculating standard errors is * high complexity.) Set this to zero to turn off statistics calculations (which can be problematic sometimes). */ private final int maxFreeParamsForStatistics = 1000; private final String graphicalEditorTitle; private final String tabularEditorTitle; /** * The graphical editor for the SemIm. */ private SemImGraphicalEditor semImGraphicalEditor; /** * Edits the parameters in a simple list. */ private SemImTabularEditor semImTabularEditor; /** * Displays one of four possible implied covariance matrices. */ private ImpliedMatricesPanel impliedMatricesPanel; /** * Displays the model statistics. */ private ModelStatisticsPanel modelStatisticsPanel; /** * True iff covariance parameters are edited as correlations. */ private boolean editCovariancesAsCorrelations; /** * True iff covariance parameters are edited as correlations. */ private boolean editIntercepts; private JTabbedPane tabbedPane; private boolean editable = true; private int matrixSelection; private JCheckBoxMenuItem meansItem; private JCheckBoxMenuItem interceptsItem; private JMenuItem errorTerms; public OneEditor(SemImWrapper wrapper, String graphicalEditorTitle, String tabularEditorTitle, TabbedPaneDefault tabbedPaneDefault) { this.semImWrapper = wrapper; this.graphicalEditorTitle = graphicalEditorTitle; this.tabularEditorTitle = tabularEditorTitle; displaySemIm(graphicalEditorTitle, tabularEditorTitle, tabbedPaneDefault); } private void displaySemIm(String graphicalEditorTitle, String tabularEditorTitle, TabbedPaneDefault tabbedPaneDefault) { this.tabbedPane = new JTabbedPane(); this.tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); setLayout(new BorderLayout()); if (tabbedPaneDefault == TabbedPaneDefault.GRAPHICAL) { this.tabbedPane.add(graphicalEditorTitle, graphicalEditor()); this.tabbedPane.add(tabularEditorTitle, tabularEditor()); this.tabbedPane.add("Implied Matrices", impliedMatricesPanel()); this.tabbedPane.add("Model Statistics", modelStatisticsPanel()); } else if (tabbedPaneDefault == TabbedPaneDefault.TABULAR) { this.tabbedPane.add(tabularEditorTitle, tabularEditor()); this.tabbedPane.add(graphicalEditorTitle, graphicalEditor()); this.tabbedPane.add("Implied Matrices", impliedMatricesPanel()); this.tabbedPane.add("Model Statistics", modelStatisticsPanel()); } else if (tabbedPaneDefault == TabbedPaneDefault.COVMATRIX) { this.tabbedPane.add("Implied Matrices", impliedMatricesPanel()); this.tabbedPane.add("Model Statistics", modelStatisticsPanel()); this.tabbedPane.add(graphicalEditorTitle, graphicalEditor()); this.tabbedPane.add(tabularEditorTitle, tabularEditor()); } else if (tabbedPaneDefault == TabbedPaneDefault.STATS) { this.tabbedPane.add("Model Statistics", modelStatisticsPanel()); this.tabbedPane.add(graphicalEditorTitle, graphicalEditor()); this.tabbedPane.add(tabularEditorTitle, tabularEditor()); this.tabbedPane.add("Implied Matrices", impliedMatricesPanel()); } SemImEditor.this.targetPanel.add(this.tabbedPane, BorderLayout.CENTER); JMenuBar menuBar = new JMenuBar(); JMenu file = new JMenu("File"); menuBar.add(file); file.add(new SaveComponentImage(this.semImGraphicalEditor.getWorkbench(), "Save Graph Image...")); file.add(this.getCopyMatrixMenuItem()); file.add(this.getCoefMatrixMenuItem()); file.add(this.getCopyErrCovarMenuItem()); JMenuItem saveSemAsXml = new JMenuItem("Save SEM as XML"); file.add(saveSemAsXml); saveSemAsXml.addActionListener(e -> { try { File outfile = EditorUtils.getSaveFile("semIm", "xml", getComp(), false, "Save SEM IM as XML..."); SemIm semIm = (SemIm) SemImEditor.this.oneEditorPanel.getSemIm(); FileOutputStream out = new FileOutputStream(outfile); Element element = SemXmlRenderer.getElement(semIm); Document document = new Document(element); Serializer serializer = new Serializer(out); serializer.setLineSeparator("\n"); serializer.setIndent(2); serializer.write(document); out.close(); } catch (IOException ioException) { ioException.printStackTrace(); } }); JCheckBoxMenuItem covariances = new JCheckBoxMenuItem("Show standard deviations"); JCheckBoxMenuItem correlations = new JCheckBoxMenuItem("Show correlations"); ButtonGroup correlationGroup = new ButtonGroup(); correlationGroup.add(covariances); correlationGroup.add(correlations); covariances.setSelected(true); covariances.addActionListener((e) -> setEditCovariancesAsCorrelations(false)); correlations.addActionListener((e) -> setEditCovariancesAsCorrelations(true)); this.errorTerms = new JMenuItem(); // By default, hide the error terms. if (getSemGraph().isShowErrorTerms()) { this.errorTerms.setText("Hide Error Terms"); } else { this.errorTerms.setText("Show Error Terms"); } this.errorTerms.addActionListener((e) -> { JMenuItem menuItem = (JMenuItem) e.getSource(); if ("Hide Error Terms".equals(menuItem.getText())) { menuItem.setText("Show Error Terms"); getSemGraph().setShowErrorTerms(false); graphicalEditor().resetLabels(); } else if ("Show Error Terms".equals(menuItem.getText())) { menuItem.setText("Hide Error Terms"); getSemGraph().setShowErrorTerms(true); graphicalEditor().resetLabels(); } }); this.meansItem = new JCheckBoxMenuItem("Show means"); this.interceptsItem = new JCheckBoxMenuItem("Show intercepts"); ButtonGroup meansGroup = new ButtonGroup(); meansGroup.add(this.meansItem); meansGroup.add(this.interceptsItem); this.meansItem.setSelected(true); this.meansItem.addActionListener((e) -> { if (this.meansItem.isSelected()) { setEditIntercepts(false); } }); this.interceptsItem.addActionListener((e) -> { if (this.interceptsItem.isSelected()) { setEditIntercepts(true); } }); JMenu params = new JMenu("Parameters"); params.add(this.errorTerms); params.addSeparator(); params.add(covariances); params.add(correlations); params.addSeparator(); if (!SemImEditor.this.wrapper.getSemIm().isCyclic()) { params.add(this.meansItem); params.add(this.interceptsItem); } menuBar.add(params); menuBar.add(new LayoutMenu(this)); SemImEditor.this.targetPanel.add(menuBar, BorderLayout.NORTH); add(this.tabbedPane, BorderLayout.CENTER); } @Override public Graph getGraph() { return this.semImGraphicalEditor.getWorkbench().getGraph(); } @Override public Map getModelEdgesToDisplay() { return getWorkbench().getModelEdgesToDisplay(); } @Override public Map getModelNodesToDisplay() { return getWorkbench().getModelNodesToDisplay(); } @Override public Knowledge getKnowledge() { return this.semImGraphicalEditor.getWorkbench().getKnowledge(); } @Override public Graph getSourceGraph() { return this.semImGraphicalEditor.getWorkbench().getSourceGraph(); } @Override public void layoutByGraph(Graph graph) { SemGraph _graph = (SemGraph) this.semImGraphicalEditor.getWorkbench().getGraph(); _graph.setShowErrorTerms(false); this.semImGraphicalEditor.getWorkbench().layoutByGraph(graph); _graph.resetErrorPositions(); // semImGraphicalEditor.getWorkbench().setGraph(_graph); this.errorTerms.setText("Show Error Terms"); } @Override public void layoutByKnowledge() { SemGraph _graph = (SemGraph) this.semImGraphicalEditor.getWorkbench().getGraph(); _graph.setShowErrorTerms(false); this.semImGraphicalEditor.getWorkbench().layoutByKnowledge(); _graph.resetErrorPositions(); // semImGraphicalEditor.getWorkbench().setGraph(_graph); this.errorTerms.setText("Show Error Terms"); } private SemGraph getSemGraph() { return getSemIm().getSemPm().getGraph(); } /** * @return the index of the currently selected tab. Used to construct a new SemImEditor in the same state as a * previous one. */ public int getTabSelectionIndex() { return this.tabbedPane.getSelectedIndex(); } /** * Sets a new SemIm to edit. */ public void displaySemIm(SemIm semIm, int tabSelectionIndex, int matrixSelection) { if (semIm == null) { throw new NullPointerException(); } if (tabSelectionIndex < 0 || tabSelectionIndex >= 4) { throw new IllegalArgumentException( "Tab selection must be 0, 1, 2, or 3: " + tabSelectionIndex); } if (matrixSelection < 0 || matrixSelection >= 4) { throw new IllegalArgumentException( "Matrix selection must be 0, 1, 2, or 3: " + matrixSelection); } SemImEditor.this.wrapper = new SemImWrapper(semIm); Graph oldGraph = getSemIm().getSemPm().getGraph(); LayoutUtil.arrangeBySourceGraph(getSemIm().getSemPm().getGraph(), oldGraph); this.matrixSelection = matrixSelection; impliedMatricesPanel().setMatrixSelection(matrixSelection); this.semImGraphicalEditor = null; this.semImTabularEditor = null; this.impliedMatricesPanel = null; this.modelStatisticsPanel = null; this.tabbedPane.removeAll(); this.tabbedPane.add(getGraphicalEditorTitle(), graphicalEditor()); this.tabbedPane.add(getTabularEditorTitle(), tabularEditor()); this.tabbedPane.add("Implied Matrices", impliedMatricesPanel()); this.tabbedPane.add("Model Statistics", modelStatisticsPanel()); this.tabbedPane.setSelectedIndex(tabSelectionIndex); this.tabbedPane.validate(); } public GraphWorkbench getWorkbench() { return this.semImGraphicalEditor.getWorkbench(); } //========================PRIVATE METHODS===========================// private JMenuItem getCopyMatrixMenuItem() { JMenuItem item = new JMenuItem("Copy Implied Covariance Matrix"); item.addActionListener((e) -> { String s = this.impliedMatricesPanel.getMatrixInTabDelimitedForm(); Clipboard board = Toolkit.getDefaultToolkit().getSystemClipboard(); StringSelection selection = new StringSelection(s); board.setContents(selection, selection); }); return item; } private JMenuItem getCoefMatrixMenuItem() { JMenuItem item = new JMenuItem("Copy Coefficient Matrix"); item.addActionListener((e) -> { if (oneEditorPanel == null) { throw new IllegalStateException("Not estimated"); } SemIm semIm = (SemIm) SemImEditor.this.oneEditorPanel.getSemIm(); if (semIm == null) throw new IllegalStateException("SemIm is null"); Matrix edgeCoef = semIm.getEdgeCoef(); Clipboard board = Toolkit.getDefaultToolkit().getSystemClipboard(); StringSelection selection = new StringSelection(edgeCoef.toString()); board.setContents(selection, selection); }); return item; } private JMenuItem getCopyErrCovarMenuItem() { JMenuItem item = new JMenuItem("Copy Error Covariance Matrix"); item.addActionListener((e) -> { if (oneEditorPanel == null) { throw new IllegalStateException("Not estimated"); } SemIm semIm = (SemIm) SemImEditor.this.oneEditorPanel.getSemIm(); if (semIm == null) throw new IllegalStateException("SemIm is null"); Matrix edgeCoef = semIm.getErrCovar(); Clipboard board = Toolkit.getDefaultToolkit().getSystemClipboard(); StringSelection selection = new StringSelection(edgeCoef.toString()); board.setContents(selection, selection); }); return item; } private ISemIm getSemIm() { return this.semImWrapper.getSemIm(); } private SemImGraphicalEditor graphicalEditor() { if (this.semImGraphicalEditor == null) { this.semImGraphicalEditor = new SemImGraphicalEditor(SemImEditor.this.wrapper, this, this.maxFreeParamsForStatistics); this.semImGraphicalEditor.addPropertyChangeListener((evt) -> SemImEditor.this.firePropertyChange(evt.getPropertyName(), null, null)); } return this.semImGraphicalEditor; } private SemImTabularEditor tabularEditor() { if (this.semImTabularEditor == null) { this.semImTabularEditor = new SemImTabularEditor(SemImEditor.this.wrapper, this, this.maxFreeParamsForStatistics); } this.semImTabularEditor.addPropertyChangeListener((evt) -> SemImEditor.this.firePropertyChange(evt.getPropertyName(), null, null)); return this.semImTabularEditor; } private ImpliedMatricesPanel impliedMatricesPanel() { if (this.impliedMatricesPanel == null) { this.impliedMatricesPanel = new ImpliedMatricesPanel(SemImEditor.this.wrapper, this.matrixSelection); } return this.impliedMatricesPanel; } private ModelStatisticsPanel modelStatisticsPanel() { if (this.modelStatisticsPanel == null) { this.modelStatisticsPanel = new ModelStatisticsPanel(SemImEditor.this.wrapper); } return this.modelStatisticsPanel; } public boolean isEditCovariancesAsCorrelations() { return this.editCovariancesAsCorrelations; } private void setEditCovariancesAsCorrelations( boolean editCovariancesAsCorrelations) { this.editCovariancesAsCorrelations = editCovariancesAsCorrelations; graphicalEditor().resetLabels(); tabularEditor().getTableModel().fireTableDataChanged(); } public boolean isEditIntercepts() { return this.editIntercepts; } private void setEditIntercepts(boolean editIntercepts) { this.editIntercepts = editIntercepts; graphicalEditor().resetLabels(); tabularEditor().getTableModel().fireTableDataChanged(); this.meansItem.setSelected(!editIntercepts); this.interceptsItem.setSelected(editIntercepts); } private String getGraphicalEditorTitle() { return this.graphicalEditorTitle; } private String getTabularEditorTitle() { return this.tabularEditorTitle; } public boolean isEditable() { return this.editable; } public void setEditable(boolean editable) { graphicalEditor().setEditable(editable); tabularEditor().setEditable(editable); this.editable = editable; } } /** * Edits the parameters of the SemIm using a graph workbench. */ final class SemImGraphicalEditor extends JPanel { private static final long serialVersionUID = 6469399368858967087L; /** * Font size for parameter values in the graph. */ private final Font SMALL_FONT = new Font("Dialog", Font.PLAIN, 10); private final SemImWrapper wrapper; /** * The editor that sits inside the SemImEditor that allows the user to edit the SemIm graphically. */ private final OneEditor editor; /** * Maximum number of free parameters for which model statistics will be calculated. The algorithm for * calculating these is expensive. */ private final int maxFreeParamsForStatistics; /** * w Workbench for the graphical editor. */ private GraphWorkbench workbench; /** * Stores the last active edge so that it can be reset properly. */ private Object lastEditedObject; /** * This delay needs to be restored when the component is hidden. */ private int savedTooltipDelay; /** * True iff this graphical display is editable. */ private boolean editable = true; private Container dialog; /** * Constructs a SemIm graphical editor for the given SemIm. */ public SemImGraphicalEditor(SemImWrapper semImWrapper, OneEditor editor, int maxFreeParamsForStatistics) { this.wrapper = semImWrapper; this.editor = editor; this.maxFreeParamsForStatistics = maxFreeParamsForStatistics; setLayout(new BorderLayout()); JScrollPane scroll = new JScrollPane(workbench()); add(scroll, BorderLayout.CENTER); setBorder(new TitledBorder("Click parameter values to edit")); ToolTipManager toolTipManager = ToolTipManager.sharedInstance(); setSavedTooltipDelay(toolTipManager.getInitialDelay()); // Laborious code that follows is intended to make sure tooltips come // almost immediately within the sem im editor but more slowly outside. // Ugh. workbench().addComponentListener(new ComponentAdapter() { @Override public void componentShown(ComponentEvent e) { resetLabels(); ToolTipManager toolTipManager = ToolTipManager.sharedInstance(); toolTipManager.setInitialDelay(100); } @Override public void componentHidden(ComponentEvent e) { ToolTipManager toolTipManager = ToolTipManager.sharedInstance(); toolTipManager.setInitialDelay(getSavedTooltipDelay()); } }); workbench().addMouseListener(new MouseAdapter() { @Override public void mouseEntered(MouseEvent e) { if (workbench().contains(e.getPoint())) { // Commenting out the resetLabels, since it seems to make // people confused when they can't move the mouse away // from the text field they are editing without the // textfield disappearing. jdramsey 3/16/2005. // resetLabels(); ToolTipManager toolTipManager = ToolTipManager.sharedInstance(); toolTipManager.setInitialDelay(100); } } @Override public void mouseExited(MouseEvent e) { if (!workbench().contains(e.getPoint())) { ToolTipManager toolTipManager = ToolTipManager.sharedInstance(); toolTipManager.setInitialDelay(getSavedTooltipDelay()); } } }); // Make sure the graphical editor reflects changes made to parameters // in other editors. addComponentListener(new ComponentAdapter() { @Override public void componentShown(ComponentEvent e) { resetLabels(); } }); } //========================PRIVATE METHODS===========================// private void beginEdgeEdit(Edge edge) { finishEdit(); if (!isEditable()) { return; } Parameter parameter = getEdgeParameter(edge); double d = semIm().getParamValue(parameter); if (this.editor.isEditCovariancesAsCorrelations() && parameter.getType() == ParamType.COVAR) { Node nodeA = parameter.getNodeA(); Node nodeB = parameter.getNodeB(); double varA = semIm().getParamValue(nodeA, nodeA); double varB = semIm().getParamValue(nodeB, nodeB); d /= FastMath.sqrt(varA * varB); } DoubleTextField field = new DoubleTextField(d, 10, NumberFormatUtil.getInstance().getNumberFormat()); field.setFilter((value, oldValue) -> { try { setEdgeValue(edge, Double.toString(value)); return value; } catch (IllegalArgumentException e) { return oldValue; } }); Box box = Box.createHorizontalBox(); box.add(Box.createHorizontalGlue()); box.add(new JLabel("New value: ")); box.add(field); box.add(Box.createHorizontalGlue()); field.addAncestorListener(new AncestorListener() { @Override public void ancestorMoved(AncestorEvent ancestorEvent) { } @Override public void ancestorRemoved(AncestorEvent ancestorEvent) { } @Override public void ancestorAdded(AncestorEvent ancestorEvent) { Container ancestor = ancestorEvent.getAncestor(); if (ancestor instanceof JDialog) { SemImGraphicalEditor.this.dialog = ancestor; } field.selectAll(); field.grabFocus(); } }); field.addActionListener((e) -> { if (this.dialog != null) { this.dialog.setVisible(false); } }); JOptionPane.showMessageDialog(this.workbench.getComponent(edge), box, "Coefficient for " + edge, JOptionPane.PLAIN_MESSAGE); } private void beginNodeEdit(Node node) { finishEdit(); if (!isEditable()) { return; } Parameter parameter = getNodeParameter(node); if (this.editor.isEditCovariancesAsCorrelations() && parameter.getType() == ParamType.VAR) { return; } double d; if (parameter.getType() == ParamType.MEAN) { if (this.editor.isEditIntercepts()) { d = semIm().getIntercept(node); } else { d = semIm().getMean(node); } } else { d = FastMath.sqrt(semIm().getParamValue(parameter)); } DoubleTextField field = new DoubleTextField(d, 10, NumberFormatUtil.getInstance().getNumberFormat()); field.setFilter((value, oldValue) -> { try { setNodeValue(node, Double.toString(value)); return value; } catch (IllegalArgumentException e) { return oldValue; } }); Box box = Box.createHorizontalBox(); box.add(Box.createHorizontalGlue()); box.add(new JLabel("New value: ")); box.add(field); box.add(Box.createHorizontalGlue()); field.addAncestorListener(new AncestorListener() { @Override public void ancestorMoved(AncestorEvent ancestorEvent) { } @Override public void ancestorRemoved(AncestorEvent ancestorEvent) { } @Override public void ancestorAdded(AncestorEvent ancestorEvent) { Container ancestor = ancestorEvent.getAncestor(); if (ancestor instanceof JDialog) { SemImGraphicalEditor.this.dialog = ancestor; } } }); field.addActionListener((e) -> { if (this.dialog != null) { this.dialog.setVisible(false); } }); String s; if (parameter.getType() == ParamType.MEAN) { if (this.editor.isEditIntercepts()) { s = "Intercept for " + node; } else { s = "Mean for " + node; } } else { s = "Standard Deviation for " + node; } JOptionPane.showMessageDialog(this.workbench.getComponent(node), box, s, JOptionPane.PLAIN_MESSAGE); } private void finishEdit() { if (lastEditedObject() != null) { resetLabels(); } } private ISemIm semIm() { return this.wrapper.getSemIm(); } private Graph graph() { SemIm semIm = this.wrapper.getSemIm(); return semIm.getSemPm().getGraph(); } private GraphWorkbench workbench() { if (this.getWorkbench() == null) { this.workbench = new GraphWorkbench(graph()); this.workbench.setEnableEditing(false); this.getWorkbench().setAllowDoubleClickActions(false); this.getWorkbench().addPropertyChangeListener((evt) -> { if ("BackgroundClicked".equals( evt.getPropertyName())) { finishEdit(); } }); resetLabels(); addMouseListenerToGraphNodesMeasured(); } return getWorkbench(); } private void setLastEditedObject() { this.lastEditedObject = null; } private Object lastEditedObject() { return this.lastEditedObject; } public void resetLabels() { Matrix implCovar = semIm().getImplCovar(false); for (Edge o : graph().getEdges()) { resetEdgeLabel(o, implCovar); } List nodes = graph().getNodes(); for (Node node : nodes) { resetNodeLabel(node, implCovar); } workbench().repaint(); } private void resetEdgeLabel(Edge edge, Matrix implCovar) { Parameter parameter = getEdgeParameter(edge); if (parameter != null) { double val = semIm().getParamValue(parameter); double standardError; try { standardError = semIm().getStandardError(parameter, this.maxFreeParamsForStatistics); } catch (Exception exception) { standardError = Double.NaN; } double tValue; try { tValue = semIm().getTValue(parameter, this.maxFreeParamsForStatistics); } catch (Exception exception) { tValue = Double.NaN; } double pValue; try { pValue = semIm().getPValue(parameter, this.maxFreeParamsForStatistics); } catch (Exception exception) { pValue = Double.NaN; } if (this.editor.isEditCovariancesAsCorrelations() && parameter.getType() == ParamType.COVAR) { Node nodeA = edge.getNode1(); Node nodeB = edge.getNode2(); double varA = semIm().getVariance(nodeA, implCovar); double varB = semIm().getVariance(nodeB, implCovar); val /= FastMath.sqrt(varA * varB); } JLabel label = new JLabel(); if (parameter.getType() == ParamType.COVAR) { label.setForeground(Color.GREEN.darker().darker()); } if (parameter.isFixed()) { label.setForeground(Color.RED); } label.setBackground(Color.white); label.setOpaque(true); label.setFont(this.SMALL_FONT); label.setText(" " + asString(val) + " "); label.setToolTipText(parameter.getName() + " = " + asString(val)); label.addMouseListener(new EdgeMouseListener(edge, this)); if (!Double.isNaN(standardError) && semIm().isEstimated()) { label.setToolTipText("SE=" + asString(standardError) + ", T=" + asString(tValue) + ", P=" + asString(pValue)); } workbench().setEdgeLabel(edge, label); } else { workbench().setEdgeLabel(edge, null); } } private void resetNodeLabel(Node node, Matrix implCovar) { if (!semIm().getSemPm().getGraph().isParameterizable(node)) { return; } Parameter parameter = semIm().getSemPm().getVarianceParameter(node); double meanOrIntercept = Double.NaN; JLabel label = new JLabel(); label.setBackground(Color.WHITE); label.addMouseListener(new NodeMouseListener(node, this)); label.setFont(this.SMALL_FONT); String tooltip = ""; NodeType nodeType = node.getNodeType(); if (nodeType == NodeType.MEASURED || nodeType == NodeType.LATENT) { if (this.editor.isEditIntercepts()) { meanOrIntercept = semIm().getIntercept(node); } else { meanOrIntercept = semIm().getMean(node); } } double stdDev = semIm().getStdDev(node, implCovar); if (this.editor.isEditCovariancesAsCorrelations() && !Double.isNaN(stdDev)) { stdDev = 1.0; } if (parameter != null) { double standardError = semIm().getStandardError(parameter, this.maxFreeParamsForStatistics); double tValue = semIm().getTValue(parameter, this.maxFreeParamsForStatistics); double pValue = semIm().getPValue(parameter, this.maxFreeParamsForStatistics); tooltip = "SE=" + asString(standardError) + ", T=" + asString(tValue) + ", P=" + asString(pValue); } if (!Double.isNaN(meanOrIntercept)) { label.setForeground(Color.GREEN.darker()); label.setText(asString(meanOrIntercept)); if (this.editor.isEditIntercepts()) { tooltip = "" + "B0_" + node.getName() + " = " + asString(meanOrIntercept) + ""; } else { tooltip = "" + "Mean(" + node.getName() + ") = " + asString(meanOrIntercept) + ""; } } else if (!this.editor.isEditCovariancesAsCorrelations() && !Double.isNaN(stdDev)) { label.setForeground(Color.BLUE); label.setText(asString(stdDev)); tooltip = "" + node.getName() + " ~ N(0," + asString(stdDev) + ")" + "

" + tooltip + ""; } else if (this.editor.isEditCovariancesAsCorrelations()) { label.setForeground(Color.GRAY); label.setText(asString(stdDev)); } if (parameter != null && parameter.isFixed()) { label.setForeground(Color.RED); } label.setToolTipText(tooltip); // Offset the nodes slightly differently depending on whether // they're error nodes or not. if (nodeType == NodeType.ERROR) { label.setOpaque(false); workbench().setNodeLabel(node, label, -10, -10); } else { label.setOpaque(false); workbench().setNodeLabel(node, label, 0, 0); } } private Parameter getNodeParameter(Node node) { Parameter parameter = semIm().getSemPm().getMeanParameter(node); if (parameter == null) { parameter = semIm().getSemPm().getVarianceParameter(node); } return parameter; } /** * @return the parameter for the given edge, or null if the edge does not have a parameter associated with it in * the model. The edge must be either directed or bidirected, since it has to come from a SemGraph. For directed * edges, this method automatically adjusts if the user has changed the endpoints of an edge X1 --> X2 to X1 * <-- X2 and returns the correct parameter. @throws IllegalArgumentException if the edge is neither directed * nor bidirected. */ private Parameter getEdgeParameter(Edge edge) { if (Edges.isDirectedEdge(edge)) { return semIm().getSemPm().getCoefficientParameter(edge.getNode1(), edge.getNode2()); } else if (Edges.isBidirectedEdge(edge)) { return semIm().getSemPm().getCovarianceParameter(edge.getNode1(), edge.getNode2()); } throw new IllegalArgumentException( "This is not a directed or bidirected edge: " + edge); } private void setEdgeValue(Edge edge, String text) { try { Parameter parameter = getEdgeParameter(edge); double d = Double.parseDouble(text); if (this.editor.isEditCovariancesAsCorrelations() && parameter.getType() == ParamType.COVAR) { Node nodeA = edge.getNode1(); Node nodeB = edge.getNode2(); Matrix implCovar = semIm().getImplCovar(false); double varA = semIm().getVariance(nodeA, implCovar); double varB = semIm().getVariance(nodeB, implCovar); d *= FastMath.sqrt(varA * varB); semIm().setParamValue(parameter, d); this.firePropertyChange("modelChanged", null, null); } else if (!this.editor.isEditCovariancesAsCorrelations() && parameter.getType() == ParamType.COVAR) { semIm().setParamValue(parameter, d); this.firePropertyChange("modelChanged", null, null); } else if (parameter.getType() == ParamType.COEF) { Node x = parameter.getNodeA(); Node y = parameter.getNodeB(); semIm().setEdgeCoef(x, y, d); if (this.editor.isEditIntercepts()) { double intercept = semIm().getIntercept(y); semIm().setIntercept(y, intercept); } this.firePropertyChange("modelChanged", null, null); } } catch (NumberFormatException e) { // Let the old value be reinstated. } resetLabels(); workbench().repaint(); setLastEditedObject(); } private void setNodeValue(Node node, String text) { try { Parameter parameter = getNodeParameter(node); double d = Double.parseDouble(text); if (parameter.getType() == ParamType.VAR && d >= 0) { semIm().setParamValue(node, node, d * d); this.firePropertyChange("modelChanged", null, null); } else if (parameter.getType() == ParamType.MEAN) { if (this.editor.isEditIntercepts()) { semIm().setIntercept(node, d); } else { semIm().setMean(node, d); } this.firePropertyChange("modelChanged", null, null); } } catch (Exception exception) { exception.printStackTrace(System.err); // Let the old value be reinstated. } resetLabels(); workbench().repaint(); setLastEditedObject(); } private int getSavedTooltipDelay() { return this.savedTooltipDelay; } private void setSavedTooltipDelay(int savedTooltipDelay) { if (this.savedTooltipDelay == 0) { this.savedTooltipDelay = savedTooltipDelay; } } private String asString(double value) { NumberFormat nf = NumberFormatUtil.getInstance().getNumberFormat(); if (Double.isNaN(value)) { return " * "; } else { return nf.format(value); } } private void addMouseListenerToGraphNodesMeasured() { List nodes = graph().getNodes(); for (Node node : nodes) { Object displayNode = workbench().getModelNodesToDisplay().get(node); if (displayNode instanceof GraphNodeMeasured) { DisplayNode _displayNode = (DisplayNode) displayNode; _displayNode.setToolTipText( getEquationOfNode(_displayNode.getModelNode()) ); } } } private String getEquationOfNode(Node node) { StringBuilder eqn = new StringBuilder(node.getName() + " = B0_" + node.getName()); SemGraph semGraph = semIm().getSemPm().getGraph(); List parentNodes = semGraph.getParents(node); for (Node parentNodeObj : parentNodes) { Edge directedEdge = semGraph.getDirectedEdge(parentNodeObj, node); if (directedEdge == null) continue; Parameter edgeParam = getEdgeParameter(directedEdge); if (edgeParam != null) { eqn.append(" + ").append(edgeParam.getName()).append("*").append(parentNodeObj); } } eqn.append(" + ").append(semIm().getSemPm().getGraph().getExogenous(node)); return eqn.toString(); } public GraphWorkbench getWorkbench() { return this.workbench; } private boolean isEditable() { return this.editable; } public void setEditable(boolean editable) { workbench().setAllowEdgeReorientations(editable); workbench().setAllowDoubleClickActions(editable); workbench().setAllowNodeEdgeSelection(editable); this.editable = editable; } final class EdgeMouseListener extends MouseAdapter { private final Edge edge; private final SemImGraphicalEditor editor; public EdgeMouseListener(Edge edge, SemImGraphicalEditor editor) { this.edge = edge; this.editor = editor; } private Edge getEdge() { return this.edge; } private SemImGraphicalEditor getEditor() { return this.editor; } public void mouseClicked(MouseEvent e) { getEditor().beginEdgeEdit(getEdge()); } } final class NodeMouseListener extends MouseAdapter { private final Node node; private final SemImGraphicalEditor editor; public NodeMouseListener(Node node, SemImGraphicalEditor editor) { this.node = node; this.editor = editor; } private Node getNode() { return this.node; } private SemImGraphicalEditor getEditor() { return this.editor; } @Override public void mouseClicked(MouseEvent e) { getEditor().beginNodeEdit(getNode()); } } } /** * Edits parameter values for a SemIm as a simple list. */ final class SemImTabularEditor extends JPanel { private static final long serialVersionUID = -3652030288654100645L; private final ParamTableModel tableModel; private final SemImWrapper wrapper; private boolean editable = true; public SemImTabularEditor(SemImWrapper wrapper, OneEditor editor, int maxFreeParamsForStatistics) { this.wrapper = wrapper; setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); // setBorder(new TitledBorder("Click parameter values to edit")); if (semIm().isEstimated()) { setBorder(new TitledBorder("Null hypothesis for T and P is that the parameter is zero")); } else { setBorder(new TitledBorder("Click parameter values to edit")); } JTable table = new JTable() { private static final long serialVersionUID = -530774590911763214L; @Override public TableCellEditor getCellEditor(int row, int col) { return new DataCellEditor(); } }; this.tableModel = new ParamTableModel(wrapper, editor, maxFreeParamsForStatistics); table.setModel(getTableModel()); this.tableModel.addTableModelListener((e) -> this.firePropertyChange("modelChanged", null, null)); add(new JScrollPane(table), BorderLayout.CENTER); } private ISemIm semIm() { return this.wrapper.getSemIm(); } public ParamTableModel getTableModel() { return this.tableModel; } public boolean isEditable() { return this.editable; } public void setEditable(boolean editable) { this.tableModel.setEditable(editable); this.editable = editable; this.tableModel.fireTableStructureChanged(); } } final class ParamTableModel extends AbstractTableModel { private static final long serialVersionUID = 2210883212769846304L; private final NumberFormat nf = NumberFormatUtil.getInstance().getNumberFormat(); private final SemImWrapper wrapper; private final OneEditor editor; private final int maxFreeParamsForStatistics; private boolean editable = true; public ParamTableModel(SemImWrapper wrapper, OneEditor editor, int maxFreeParamsForStatistics) { this.wrapper = wrapper; if (maxFreeParamsForStatistics < 0) { throw new IllegalArgumentException(); } this.maxFreeParamsForStatistics = maxFreeParamsForStatistics; this.editor = editor; } @Override public int getRowCount() { int numNodes = semIm().getVariableNodes().size(); return semIm().getNumFreeParams() + semIm().getFixedParameters().size() + numNodes; } @Override public int getColumnCount() { return 7; } @Override public String getColumnName(int column) { switch (column) { case 0: return "From"; case 1: return "To"; case 2: return "Type"; case 3: return "Value"; case 4: return "SE"; case 5: return "T"; case 6: return "P"; } return null; } @Override public Object getValueAt(int row, int column) { List nodes = semIm().getVariableNodes(); List parameters = new ArrayList<>(semIm().getFreeParameters()); parameters.addAll(semIm().getFixedParameters()); int numParams = semIm().getNumFreeParams() + semIm().getFixedParameters().size(); if (row < numParams) { Parameter parameter = (parameters.get(row)); switch (column) { case 0: return parameter.getNodeA(); case 1: return parameter.getNodeB(); case 2: return typeString(parameter); case 3: return asString(paramValue(parameter)); case 4: if (parameter.isFixed()) { return "*"; } else { return asString(semIm().getStandardError(parameter, this.maxFreeParamsForStatistics)); } case 5: if (parameter.isFixed()) { return "*"; } else { return asString(semIm().getTValue(parameter, this.maxFreeParamsForStatistics)); } case 6: if (parameter.isFixed()) { return "*"; } else { return asString(semIm().getPValue(parameter, this.maxFreeParamsForStatistics)); } } } else if (row < numParams + nodes.size()) { int index = row - numParams; Node node = semIm().getVariableNodes().get(index); int n = semIm().getSampleSize(); int df = n - 1; double mean = semIm().getMean(node); double stdDev = semIm().getMeanStdDev(node); double stdErr = stdDev / FastMath.sqrt(n); // double tValue = mean * FastMath.sqrt(n - 1) / stdDev; double tValue = mean / stdErr; double p = 2.0 * (1.0 - ProbUtils.tCdf(FastMath.abs(tValue), df)); switch (column) { case 0: case 1: return nodes.get(index); case 2: if (this.editor.isEditIntercepts()) { return "Intercept"; } else { return "Mean"; } case 3: if (this.editor.isEditIntercepts()) { double intercept = semIm().getIntercept(node); return asString(intercept); } else { return asString(mean); } case 4: return asString(stdErr); case 5: return asString(tValue); case 6: return asString(p); } } return null; } private double paramValue(Parameter parameter) { double paramValue = semIm().getParamValue(parameter); if (this.editor.isEditCovariancesAsCorrelations()) { if (parameter.getType() == ParamType.VAR) { paramValue = 1.0; } if (parameter.getType() == ParamType.COVAR) { Node nodeA = parameter.getNodeA(); Node nodeB = parameter.getNodeB(); double varA = semIm().getParamValue(nodeA, nodeA); double varB = semIm().getParamValue(nodeB, nodeB); paramValue *= FastMath.sqrt(varA * varB); } } else { if (parameter.getType() == ParamType.VAR) { paramValue = FastMath.sqrt(paramValue); } } return paramValue; } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return isEditable() && columnIndex == 3; } private boolean isEditable() { return this.editable; } public void setEditable(boolean editable) { this.editable = editable; } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { if (columnIndex == 3) { try { double value = Double.parseDouble((String) aValue); if (rowIndex < semIm().getNumFreeParams()) { Parameter parameter = semIm().getFreeParameters().get(rowIndex); if (parameter.getType() == ParamType.VAR) { value = value * value; semIm().setErrVar(parameter.getNodeA(), value); } else if (parameter.getType() == ParamType.COEF) { Node x = parameter.getNodeA(); Node y = parameter.getNodeB(); semIm().setEdgeCoef(x, y, value); double intercept = semIm().getIntercept(y); if (this.editor.isEditIntercepts()) { semIm().setIntercept(y, intercept); } } this.editor.firePropertyChange("modelChanged", 0, 0); } else { int index = rowIndex - semIm().getNumFreeParams(); Node node = semIm().getVariableNodes().get(index); if (semIm().getMean(semIm().getVariableNodes().get(index)) != value) { if (this.editor.isEditIntercepts()) { semIm().setIntercept(node, value); } else { semIm().setMean(node, value); } this.editor.firePropertyChange("modelChanged", 0, 0); } } } catch (Exception exception) { // The old value will be reinstated automatically. } fireTableDataChanged(); } } private String asString(double value) { if (Double.isNaN(value)) { return " * "; } else { return this.nf.format(value); } } private String typeString(Parameter parameter) { ParamType type = parameter.getType(); if (type == ParamType.COEF) { return "Edge Coef."; } if (this.editor.isEditCovariancesAsCorrelations()) { if (type == ParamType.VAR) { return "Correlation"; } if (type == ParamType.COVAR) { return "Correlation"; } } if (type == ParamType.VAR) { //return "Variance"; return "Std. Dev."; } if (type == ParamType.COVAR) { return "Covariance"; } throw new IllegalStateException("Unknown param type."); } private ISemIm semIm() { return this.wrapper.getSemIm(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy