edu.cmu.tetradapp.editor.TimeLagGraphEditor Maven / Gradle / Ivy
///////////////////////////////////////////////////////////////////////////////
// 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.search.IndependenceTest;
import edu.cmu.tetrad.search.test.MsepTest;
import edu.cmu.tetrad.util.TetradSerializable;
import edu.cmu.tetradapp.model.IndTestProducer;
import edu.cmu.tetradapp.model.TimeLagGraphWrapper;
import edu.cmu.tetradapp.ui.PaddingPanel;
import edu.cmu.tetradapp.util.DesktopController;
import edu.cmu.tetradapp.util.LayoutEditable;
import edu.cmu.tetradapp.workbench.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.io.Serial;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* Displays a workbench editing workbench area together with a toolbench for editing tetrad-style graphs.
*
* @author Aaron Powers
* @author josephramsey
* @author Zhou Yuan
* @version $Id: $Id
*/
public final class TimeLagGraphEditor extends JPanel
implements GraphEditable, LayoutEditable, IndTestProducer {
@Serial
private static final long serialVersionUID = -2425361202348129265L;
/**
* The layout editable.
*/
private final LayoutEditable layoutEditable;
/**
* The graph editor scroll.
*/
private final JScrollPane graphEditorScroll = new JScrollPane();
/**
* The edge type table.
*/
private final EdgeTypeTable edgeTypeTable;
/**
* The workbench.
*/
private TimeLagGraphWorkbench workbench;
//===========================CONSTRUCTOR========================//
/**
* Constructor for TimeLagGraphEditor.
*
* @param timeLagGraphWrapper a {@link edu.cmu.tetradapp.model.TimeLagGraphWrapper} object
*/
public TimeLagGraphEditor(TimeLagGraphWrapper timeLagGraphWrapper) {
setLayout(new BorderLayout());
this.layoutEditable = this;
this.edgeTypeTable = new EdgeTypeTable();
initUI(timeLagGraphWrapper);
}
//===========================PUBLIC METHODS========================//
/**
* {@inheritDoc}
*
* Sets the name of this editor.
*/
@Override
public void setName(String name) {
String oldName = getName();
super.setName(name);
firePropertyChange("name", oldName, getName());
}
/**
* {@inheritDoc}
*/
@Override
public List getSelectedModelComponents() {
List selectedComponents
= getWorkbench().getSelectedComponents();
List selectedModelComponents
= new ArrayList<>();
selectedComponents.forEach(comp -> {
if (comp instanceof DisplayNode) {
selectedModelComponents.add(((DisplayNode) comp).getModelNode());
} else if (comp instanceof DisplayEdge) {
selectedModelComponents.add(((DisplayEdge) comp).getModelEdge());
}
});
return selectedModelComponents;
}
/**
* {@inheritDoc}
*
* Pastes list of session elements into the workbench.
*/
@Override
public void pasteSubsession(List sessionElements, Point upperLeft) {
getWorkbench().pasteSubgraph(sessionElements, upperLeft);
getWorkbench().deselectAll();
for (Object o : sessionElements) {
if (o instanceof GraphNode) {
Node modelNode = (Node) o;
getWorkbench().selectNode(modelNode);
}
}
getWorkbench().selectConnectingEdges();
}
/**
* {@inheritDoc}
*/
@Override
public GraphWorkbench getWorkbench() {
return this.workbench;
}
/**
* {@inheritDoc}
*/
@Override
public Graph getGraph() {
return getWorkbench().getGraph();
}
/**
* {@inheritDoc}
*/
@Override
public void setGraph(Graph graph) {
getWorkbench().setGraph(graph);
}
/**
* {@inheritDoc}
*/
@Override
public Map getModelEdgesToDisplay() {
return getWorkbench().getModelEdgesToDisplay();
}
/**
* {@inheritDoc}
*/
@Override
public Map getModelNodesToDisplay() {
return getWorkbench().getModelNodesToDisplay();
}
/**
* {@inheritDoc}
*/
@Override
public Knowledge getKnowledge() {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public Graph getSourceGraph() {
return getWorkbench().getGraph();
}
/**
* {@inheritDoc}
*/
@Override
public void layoutByGraph(Graph graph) {
getWorkbench().layoutByGraph(graph);
}
/**
* {@inheritDoc}
*/
@Override
public void layoutByKnowledge() {
// Does nothing.
}
/**
* {@inheritDoc}
*/
@Override
public Rectangle getVisibleRect() {
return getWorkbench().getVisibleRect();
}
//===========================PRIVATE METHODS======================//
private void initUI(TimeLagGraphWrapper timeLagGraphWrapper) {
TimeLagGraph graph = (TimeLagGraph) timeLagGraphWrapper.getGraph();
this.workbench = new TimeLagGraphWorkbench(graph);
this.workbench.addPropertyChangeListener((PropertyChangeEvent evt) -> {
String propertyName = evt.getPropertyName();
// Update the bootstrap table if there's changes to the edges or node renaming
String[] events = {"graph", "edgeAdded", "edgeRemoved"};
if (Arrays.asList(events).contains(propertyName)) {
if (getWorkbench() != null) {
TimeLagGraph targetGraph = (TimeLagGraph) getWorkbench().getGraph();
// Update the timeLagGraphWrapper
timeLagGraphWrapper.setGraph(targetGraph);
// Also need to update the UI
// updateBootstrapTable(targetGraph);
}
} else if ("modelChanged".equals(propertyName)) {
firePropertyChange("modelChanged", null, null);
}
});
// Graph menu at the very top of the window
JMenuBar menuBar = createGraphMenuBar();
// topBox Left side toolbar
DagGraphToolbar graphToolbar = new DagGraphToolbar(getWorkbench());
graphToolbar.setMaximumSize(new Dimension(140, 450));
// topBox right side graph editor
this.graphEditorScroll.setPreferredSize(new Dimension(760, 450));
this.graphEditorScroll.setViewportView(this.workbench);
// topBox contains the topGraphBox and the instructionBox underneath
Box topBox = Box.createVerticalBox();
topBox.setPreferredSize(new Dimension(820, 400));
// topGraphBox contains the vertical graph toolbar and graph editor
Box topGraphBox = Box.createHorizontalBox();
topGraphBox.add(graphToolbar);
topGraphBox.add(this.graphEditorScroll);
// Instruction with info button
Box instructionBox = Box.createHorizontalBox();
instructionBox.setMaximumSize(new Dimension(820, 40));
JLabel label = new JLabel("Double click variable/node to change name.");
label.setFont(new Font("SansSerif", Font.PLAIN, 12));
// // Info button added by Zhou to show edge types
// JButton infoBtn = new JButton(new ImageIcon(ImageUtils.getImage(this, "info.png")));
// infoBtn.setBorder(new EmptyBorder(0, 0, 0, 0));
//
// // Clock info button to show edge types instructions - Zhou
// infoBtn.addActionListener(new ActionListener() {
// @Override
// public void actionPerformed(ActionEvent e) {
// // Initialize helpSet
// final String helpHS = "/docs/javahelp/TetradHelp.hs";
//
// try {
// URL url = this.getClass().getResource(helpHS);
// HelpSet helpSet = new HelpSet(null, url);
//
// helpSet.setHomeID("graph_edge_types");
// HelpBroker broker = helpSet.createHelpBroker();
// ActionListener listener = new CSH.DisplayHelpFromSource(broker);
// listener.actionPerformed(e);
// } catch (Exception ee) {
// System.out.println("HelpSet " + ee.getMessage());
// System.out.println("HelpSet " + helpHS + " not found");
// throw new IllegalArgumentException();
// }
// }
// });
//
instructionBox.add(label);
instructionBox.add(Box.createHorizontalStrut(2));
// instructionBox.add(infoBtn);
// Add to topBox
topBox.add(topGraphBox);
topBox.add(instructionBox);
this.edgeTypeTable.setPreferredSize(new Dimension(820, 150));
JTabbedPane tabbedPane = new JTabbedPane(SwingConstants.BOTTOM);
tabbedPane.addTab("Graph", new PaddingPanel(topBox));
tabbedPane.addTab("Edges", this.edgeTypeTable);
// Add to parent container
add(menuBar, BorderLayout.NORTH);
add(tabbedPane, BorderLayout.CENTER);
this.edgeTypeTable.update(graph);
// Performs relayout.
// It means invalid content is asked for all the sizes and
// all the subcomponents' sizes are set to proper values by LayoutManager.
validate();
}
/**
* Updates bootstrap table on adding/removing edges or graph changes
*/
private void updateBootstrapTable(Graph graph) {
this.edgeTypeTable.update(graph);
validate();
}
private JMenuBar createGraphMenuBar() {
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new GraphFileMenu(this, getWorkbench(), false);
JMenu editMenu = createNumLagsMenu();
// JMenu graphMenu = createGraphMenu();
menuBar.add(fileMenu);
menuBar.add(editMenu);
// menuBar.add(graphMenu);
menuBar.add(new LayoutMenu(this));
return menuBar;
}
/**
* Creates the "file" menu, which allows the user to load, save, and post workbench models.
*
* @return this menu.
*/
private JMenu createNumLagsMenu() {
TimeLagGraph graph = (TimeLagGraph) getLayoutEditable().getGraph();
JMenu edit = new JMenu("Number-of-Lags = " + graph.getMaxLag());
JMenuItem configuration = new JMenuItem("Configuration...");
edit.add(configuration);
configuration.addActionListener(e -> {
// final TimeLagGraph graph = (TimeLagGraph) getLayoutEditable().getGraph();
class ConfigurationEditor extends JPanel {
private static final long serialVersionUID = 1878934284195587094L;
private int maxLag;
private int numInitialLags;
public ConfigurationEditor(TimeLagGraph graph) {
this.maxLag = graph.getMaxLag();
this.numInitialLags = graph.getNumInitialLags();
SpinnerModel maxLagSpinnerModel = new SpinnerNumberModel(graph.getMaxLag(), 0, 300, 1);
JSpinner maxLagSpinner = new JSpinner(maxLagSpinnerModel);
maxLagSpinner.addChangeListener(e -> {
JSpinner spinner = (JSpinner) e.getSource();
SpinnerNumberModel model = (SpinnerNumberModel) spinner.getModel();
int value = (Integer) model.getValue();
setMaxLag(value);
});
SpinnerModel initialLagsSpinnerModel = new SpinnerNumberModel(graph.getNumInitialLags(), 1, 300, 1);
JSpinner initialLagsSpinner = new JSpinner(initialLagsSpinnerModel);
initialLagsSpinner.addChangeListener(e -> {
JSpinner spinner = (JSpinner) e.getSource();
SpinnerNumberModel model = (SpinnerNumberModel) spinner.getModel();
int value = (Integer) model.getValue();
setNumInitialLags(value);
});
setLayout(new BorderLayout());
Box box = Box.createVerticalBox();
Box b1 = Box.createHorizontalBox();
b1.add(new JLabel("Time lag graph configuration:"));
b1.add(Box.createHorizontalGlue());
box.add(b1);
Box b2 = Box.createHorizontalBox();
b2.add(new JLabel("Maximum Lag = "));
b2.add(Box.createHorizontalGlue());
b2.add(maxLagSpinner);
box.add(b2);
box.setBorder(new EmptyBorder(10, 10, 10, 10));
add(box, BorderLayout.CENTER);
}
public int getMaxLag() {
return this.maxLag;
}
public void setMaxLag(int maxLag) {
this.maxLag = maxLag;
edit.setText("Number-of-Lags = " + maxLag);
}
public int getNumInitialLags() {
return this.numInitialLags;
}
public void setNumInitialLags(int numInitialLags) {
this.numInitialLags = numInitialLags;
}
}
ConfigurationEditor editor = new ConfigurationEditor((TimeLagGraph) getGraph());
EditorWindow editorWindow
= new EditorWindow(editor, "Configuration...", "Save", true, this);
DesktopController.getInstance().addEditorWindow(editorWindow, JLayeredPane.PALETTE_LAYER);
editorWindow.pack();
editorWindow.setVisible(true);
editorWindow.addInternalFrameListener(new InternalFrameAdapter() {
@Override
public void internalFrameClosed(InternalFrameEvent e) {
EditorWindow window = (EditorWindow) e.getSource();
if (window.isCanceled()) {
return;
}
graph.setMaxLag(editor.getMaxLag());
graph.setNumInitialLags(editor.getNumInitialLags());
LayoutUtils.lastLayout(getLayoutEditable());
}
});
});
return edit;
}
/**
* {@inheritDoc}
*/
@Override
public IndependenceTest getIndependenceTest() {
Graph graph = getWorkbench().getGraph();
EdgeListGraph listGraph = new EdgeListGraph(graph);
return new MsepTest(listGraph);
}
private LayoutEditable getLayoutEditable() {
return this.layoutEditable;
}
}