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