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

edu.cmu.tetradapp.workbench.LagWorkbench Maven / Gradle / Ivy

There is a newer version: 7.6.6
Show 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.workbench;

import edu.cmu.tetrad.graph.*;
import edu.cmu.tetradapp.model.EditorUtils;

import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;


/**
 * Extends AbstractWorkbench in the ways needed to display tetrad-style graphs.
 *
 * @author josephramsey
 * @author Willie Wheeler
 * @see edu.cmu.tetradapp.workbench.AbstractWorkbench
 */
public class LagWorkbench extends AbstractWorkbench {

    //=================PUBLIC STATIC FINAL FIELDS=========================//
    private static final int MEASURED_NODE = 0;
    private static final int LATENT_NODE = 1;
    private static final int DIRECTED_EDGE = 0;
    private static final int NONDIRECTED_EDGE = 2;
    private static final int PARTIALLY_ORIENTED_EDGE = 3;
    private static final int BIDIRECTED_EDGE = 4;

    //====================PRIVATE FIELDS=================================//
    private int nodeType = LagWorkbench.MEASURED_NODE;
    private int edgeMode = LagWorkbench.DIRECTED_EDGE;

    //========================CONSTRUCTORS===============================//

    /**
     * Constructs a new workbench with an empty graph; useful if another graph will be set later.
     */
    public LagWorkbench() {
        this(new EdgeListGraph());
    }

    /**
     * Constructs a new workbench workbench for the given workbench model.
     */
    private LagWorkbench(Graph graph) {
        super(graph);
        setRightClickPopupAllowed(true);
    }

    //========================PUBLIC METHODS==============================//

    private static boolean containsName(List nodes, String name) {
        for (Node node : nodes) {
            if (name.equals(node.getName())) {
                return true;
            }
        }
        return false;
    }

    /**
     * The type of edge to be drawn next.
     *
     * @return the type of edge to be drawn.
     * @see #DIRECTED_EDGE
     * @see #NONDIRECTED_EDGE
     * @see #PARTIALLY_ORIENTED_EDGE
     * @see #BIDIRECTED_EDGE
     */
    public int getEdgeMode() {
        return this.edgeMode;
    }

    /**
     * Sets the edge mode to the given mode.
     */
    public void setEdgeMode(int edgeMode) {
        switch (edgeMode) {
            case LagWorkbench.DIRECTED_EDGE:
                // Falls through!
            case LagWorkbench.NONDIRECTED_EDGE:
                // Falls through!
            case LagWorkbench.PARTIALLY_ORIENTED_EDGE:
                // Falls through!
            case LagWorkbench.BIDIRECTED_EDGE:
                this.edgeMode = edgeMode;
                break;
            default:
                throw new IllegalArgumentException();
        }
    }

    /**
     * Creates a new model node for the workbench.
     */
    public Node getNewModelNode() {

        // select a name and create the model node
        String name;
        Node modelNode;

        switch (this.nodeType) {
            case LagWorkbench.MEASURED_NODE:
                name = nextVariableName("X");
                modelNode = new GraphNode(name);
                modelNode.setNodeType(NodeType.MEASURED);
                break;

            case LagWorkbench.LATENT_NODE:
                name = nextVariableName("L");
                modelNode = new GraphNode(name);
                modelNode.setNodeType(NodeType.LATENT);
                break;

            default:
                throw new IllegalStateException();
        }

        return modelNode;
    }

    /**
     * Creates a new display node for the workbench based on the given model node.
     *
     * @param modelNode the model node.
     * @return the new display node.
     */
    public DisplayNode getNewDisplayNode(Node modelNode) {
        DisplayNode displayNode;

        if (modelNode.getNodeType() == NodeType.MEASURED) {
            GraphNodeMeasured nodeMeasured = new GraphNodeMeasured(modelNode);
            nodeMeasured.setEditExitingMeasuredVarsAllowed(isEditExistingMeasuredVarsAllowed());
            displayNode = nodeMeasured;
        } else if (modelNode.getNodeType() == NodeType.LATENT) {
            displayNode = new GraphNodeLatent(modelNode);
        } else if (modelNode.getNodeType() == NodeType.ERROR) {
            displayNode = new GraphNodeError(modelNode);
        } else {
            throw new IllegalStateException();
        }

        displayNode.addPropertyChangeListener(new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                if ("resetGraph".equals(evt.getPropertyName())) {
                    setGraph(getGraph());
                } else if ("editingValueChanged".equals(evt.getPropertyName())) {
                    firePropertyChange("modelChanged", null, null);
                }
            }
        });

        return displayNode;
    }

    /**
     * Creates a new display edge for the workbench based on the given model edge.
     *
     * @param modelEdge the model edge.
     * @return the new display edge.
     */
    public IDisplayEdge getNewDisplayEdge(Edge modelEdge) {
        Node node1 = modelEdge.getNode1();
        Node node2 = modelEdge.getNode2();

        if (node1 == node2) {
            throw new IllegalArgumentException("Edges to self not supported.");
        }

        DisplayNode displayNodeA = (DisplayNode) getModelNodesToDisplay().get(node1);
        DisplayNode displayNodeB = (DisplayNode) getModelNodesToDisplay().get(node2);

        if ((displayNodeA == null) || (displayNodeB == null)) {
            return null;
        }

        return new DisplayEdge(modelEdge, displayNodeA, displayNodeB);
    }

    /**
     * Creates a new model edge for the workbench connecting the two given model nodes and using the edge type from
     * #getEdgeType().
     *
     * @param node1 the one model node.
     * @param node2 the other model node.
     * @return the new model edge.
     */
    public Edge getNewModelEdge(Node node1, Node node2) {
        switch (this.edgeMode) {
            case LagWorkbench.DIRECTED_EDGE:
                return Edges.directedEdge(node1, node2);

            case LagWorkbench.NONDIRECTED_EDGE:
                return Edges.nondirectedEdge(node1, node2);

            case LagWorkbench.PARTIALLY_ORIENTED_EDGE:
                return Edges.partiallyOrientedEdge(node1, node2);

            case LagWorkbench.BIDIRECTED_EDGE:
                return Edges.bidirectedEdge(node1, node2);

            default:
                throw new IllegalStateException();
        }
    }

    /**
     * Gets a new "tracking edge"--that is, an edge which is anchored at one end to a node but tracks the mouse at the
     * other end.  Used for drawing new edges.
     *
     * @param node     the node to anchor to.
     * @param mouseLoc the location of the mouse.
     * @return the new tracking edge (a display edge).
     */
    public IDisplayEdge getNewTrackingEdge(DisplayNode node, Point mouseLoc) {
        switch (this.edgeMode) {
            case LagWorkbench.DIRECTED_EDGE:
                return new DisplayEdge(node, mouseLoc, DisplayEdge.DIRECTED);

            case LagWorkbench.NONDIRECTED_EDGE:
                return new DisplayEdge(node, mouseLoc, DisplayEdge.NONDIRECTED);

            case LagWorkbench.PARTIALLY_ORIENTED_EDGE:
                return new DisplayEdge(node, mouseLoc,
                        DisplayEdge.PARTIALLY_ORIENTED);

            case LagWorkbench.BIDIRECTED_EDGE:
                return new DisplayEdge(node, mouseLoc, DisplayEdge.BIDIRECTED);

            default:
                throw new IllegalStateException();
        }
    }

    /**
     * Determines whether the next node to be constructed will be measured or latent.
     *
     * @return MEASURED_NODE or LATENT_NODE
     * @see #MEASURED_NODE
     * @see #LATENT_NODE
     */
    public int getNodeMode() {
        return this.nodeType;
    }

    /**
     * Given base b (a String), returns the first node in the sequence "b1", "b2", "b3", etc., which is not already the
     * name of a node in the workbench.
     *
     * @param base the base string.
     * @return the first string in the sequence not already being used.
     */
    private String nextVariableName(String base) {

        // Variable names should start with "1."
        int i = 0;

        loop:
        while (true) {
            String name = base + (++i);

            for (Node node1 : getGraph().getNodes()) {

                if (node1.getName().equals(name)) {
                    continue loop;
                }
            }

            break;
        }

        return base + i;
    }

    /**
     * Sets the type of this node to the given type.
     */
    public void setNodeType(int nodeType) {
        if (nodeType == LagWorkbench.MEASURED_NODE || nodeType == LagWorkbench.LATENT_NODE) {
            this.nodeType = nodeType;
        } else {
            throw new IllegalArgumentException("The type of the node must be " +
                    "MEASURED_NODE or LATENT_NODE.");
        }
    }

    //===========================PRIVATE METHODS==========================//

    /**
     * Pastes a list of session elements (SessionNodeWrappers and SessionEdges) into the workbench.
     */
    public void pasteSubgraph(List graphElements, Point upperLeft) {

        // Extract the SessionNodes from the SessionNodeWrappers
        // and pass the list of them to the Session.  Choose a unique
        // name for each of the session wrappers.
        Point oldUpperLeft = EditorUtils.getTopLeftPoint(graphElements);
        int deltaX = upperLeft.x - oldUpperLeft.x;
        int deltaY = upperLeft.y - oldUpperLeft.y;

        for (Object graphElement : graphElements) {

            if (graphElement instanceof Node) {
                Node node = (Node) graphElement;
                adjustNameAndPosition(node, deltaX, deltaY);
                getWorkbench().getGraph().addNode(node);
            } else if (graphElement instanceof Edge) {
                getWorkbench().getGraph().addEdge((Edge) graphElement);
            } else {
                throw new IllegalArgumentException("The list of session " +
                        "elements should contain only SessionNodeWrappers " +
                        "and SessionEdges: " + graphElement);
            }
        }
    }

    /**
     * Adjusts the name to avoid name conflicts in the new session and, if the name is adjusted, adjusts the position so
     * the user can see the two nodes.
     *
     * @param node   The node which is being adjusted
     * @param deltaX the shift in x
     * @param deltaY the shift in y.
     */
    private void adjustNameAndPosition(Node node, int deltaX,
                                       int deltaY) {
        String originalName = node.getName();
        //String base = extractBase(originalName);
        String uniqueName = nextUniqueName(originalName);

        if (!uniqueName.equals(originalName)) {
            node.setName(uniqueName);
            node.setCenterX(node.getCenterX() + deltaX);
            node.setCenterY(node.getCenterY() + deltaY);
        }
    }

    /**
     * @param base the string base of the name--for example, "Graph".
     * @return the next string in the sequence--for example, "Graph1".
     */
    private String nextUniqueName(String base) {
        if (base == null) {
            throw new NullPointerException("Base name must be non-null.");
        }
        List currentNodes = this.getWorkbench().getGraph().getNodes();
        if (!LagWorkbench.containsName(currentNodes, base)) {
            return base;
        }
        // otherwise fine new unique name.
        base += "_";
        int i = 1;
        while (LagWorkbench.containsName(currentNodes, base + i)) {
            i++;
        }

        return base + i;
    }
}






© 2015 - 2025 Weber Informatics LLC | Privacy Policy