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

com.mxgraph.layout.mxGraphLayout Maven / Gradle / Ivy

There is a newer version: 2.2.18
Show newest version
/**
 * $Id: mxGraphLayout.java,v 1.27 2012-05-10 16:20:58 david Exp $
 * Copyright (c) 2008-2009, JGraph Ltd
 */
package com.mxgraph.layout;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.mxgraph.model.mxGeometry;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxGraph.mxICellVisitor;
import com.mxgraph.view.mxGraphView;

/**
 * Abstract bass class for layouts
 */
public abstract class mxGraphLayout implements mxIGraphLayout
{

	/**
	 * Holds the enclosing graph.
	 */
	protected mxGraph graph;

	/**
	 * The parent cell of the layout, if any
	 */
	protected Object parent;

	/**
	 * Boolean indicating if the bounding box of the label should be used if
	 * its available. Default is true.
	 */
	protected boolean useBoundingBox = true;

	/**
	 * Constructs a new fast organic layout for the specified graph.
	 */
	public mxGraphLayout(mxGraph graph)
	{
		this.graph = graph;
	}

	public void execute(Object parent)
	{
		this.parent = parent;
	}

	/* (non-Javadoc)
	 * @see com.mxgraph.layout.mxIGraphLayout#move(java.lang.Object, double, double)
	 */
	public void moveCell(Object cell, double x, double y)
	{
		// TODO: Map the position to a child index for
		// the cell to be placed closest to the position
	}

	/**
	 * Returns the associated graph.
	 */
	public mxGraph getGraph()
	{
		return graph;
	}

	/**
	 * Returns the constraint for the given key and cell. This implementation
	 * always returns the value for the given key in the style of the given
	 * cell.
	 * 
	 * @param key Key of the constraint to be returned.
	 * @param cell Cell whose constraint should be returned.
	 */
	public Object getConstraint(Object key, Object cell)
	{
		return getConstraint(key, cell, null, false);
	}

	/**
	 * Returns the constraint for the given key and cell. The optional edge and
	 * source arguments are used to return inbound and outgoing routing-
	 * constraints for the given edge and vertex. This implementation always
	 * returns the value for the given key in the style of the given cell.
	 * 
	 * @param key Key of the constraint to be returned.
	 * @param cell Cell whose constraint should be returned.
	 * @param edge Optional cell that represents the connection whose constraint
	 * should be returned. Default is null.
	 * @param source Optional boolean that specifies if the connection is incoming
	 * or outgoing. Default is false.
	 */
	public Object getConstraint(Object key, Object cell, Object edge,
			boolean source)
	{
		mxCellState state = graph.getView().getState(cell);
		Map style = (state != null) ? state.getStyle() : graph
				.getCellStyle(cell);

		return (style != null) ? style.get(key) : null;
	}

	/**
	 * Traverses the (directed) graph invoking the given function for each
	 * visited vertex and edge. The function is invoked with the current vertex
	 * and the incoming edge as a parameter. This implementation makes sure
	 * each vertex is only visited once. The function may return false if the
	 * traversal should stop at the given vertex.
	 * 
	 * @param vertex  that represents the vertex where the traversal starts.
	 * @param directed Optional boolean indicating if edges should only be traversed
	 * from source to target. Default is true.
	 * @param visitor Visitor that takes the current vertex and the incoming edge.
	 * The traversal stops if the function returns false.
	 * @param edge Optional  that represents the incoming edge. This is
	 * null for the first step of the traversal.
	 * @param visited Optional array of cell paths for the visited cells.
	 */
	public void traverse(Object vertex, boolean directed,
			mxICellVisitor visitor, Object edge, Set visited)
	{
		mxGraphView view = graph.getView();
		mxIGraphModel model = graph.getModel();
		
		if (vertex != null && visitor != null)
		{
			if (visited == null)
			{
				visited = new HashSet();
			}

			if (!visited.contains(vertex))
			{
				visited.add(vertex);

				if (visitor.visit(vertex, edge))
				{
					int edgeCount = model.getEdgeCount(vertex);

					if (edgeCount > 0)
					{
						for (int i = 0; i < edgeCount; i++)
						{
							Object e = model.getEdgeAt(vertex, i);
							boolean isSource = model.getTerminal(e, true) == vertex;

							if (!directed || isSource)
							{
								Object next = view.getVisibleTerminal(e, !isSource);
								traverse(next, directed, visitor, e, visited);
							}
						}
					}
				}
			}
		}
	}

	/**
	 * @return the useBoundingBox
	 */
	public boolean isUseBoundingBox()
	{
		return useBoundingBox;
	}

	/**
	 * @param useBoundingBox the useBoundingBox to set
	 */
	public void setUseBoundingBox(boolean useBoundingBox)
	{
		this.useBoundingBox = useBoundingBox;
	}

	/**
	 * Returns true if the given vertex may be moved by the layout.
	 * 
	 * @param vertex Object that represents the vertex to be tested.
	 * @return Returns true if the vertex can be moved.
	 */
	public boolean isVertexMovable(Object vertex)
	{
		return graph.isCellMovable(vertex);
	}

	/**
	 * Returns true if the given vertex has no connected edges.
	 * 
	 * @param vertex Object that represents the vertex to be tested.
	 * @return Returns true if the vertex should be ignored.
	 */
	public boolean isVertexIgnored(Object vertex)
	{
		return !graph.getModel().isVertex(vertex)
				|| !graph.isCellVisible(vertex);
	}

	/**
	 * Returns true if the given edge has no source or target terminal.
	 * 
	 * @param edge Object that represents the edge to be tested.
	 * @return Returns true if the edge should be ignored.
	 */
	public boolean isEdgeIgnored(Object edge)
	{
		mxIGraphModel model = graph.getModel();

		return !model.isEdge(edge) || !graph.isCellVisible(edge)
				|| model.getTerminal(edge, true) == null
				|| model.getTerminal(edge, false) == null;
	}

	/**
	 * Disables or enables the edge style of the given edge.
	 */
	public void setEdgeStyleEnabled(Object edge, boolean value)
	{
		graph.setCellStyles(mxConstants.STYLE_NOEDGESTYLE, (value) ? "0" : "1",
				new Object[] { edge });
	}

	/**
	 * Disables or enables orthogonal end segments of the given edge
	 */
	public void setOrthogonalEdge(Object edge, boolean value)
	{
		graph.setCellStyles(mxConstants.STYLE_ORTHOGONAL, (value) ? "1" : "0",
				new Object[] { edge });
	}

	public mxPoint getParentOffset(Object parent)
	{
		mxPoint result = new mxPoint();

		if (parent != null && parent != this.parent)
		{
			mxIGraphModel model = graph.getModel();

			if (model.isAncestor(this.parent, parent))
			{
				mxGeometry parentGeo = model.getGeometry(parent);

				while (parent != this.parent)
				{
					result.setX(result.getX() + parentGeo.getX());
					result.setY(result.getY() + parentGeo.getY());

					parent = model.getParent(parent);;
					parentGeo = model.getGeometry(parent);
				}
			}
		}

		return result;
	}

	/**
	 * Sets the control points of the given edge to the given
	 * list of mxPoints. Set the points to null to remove all
	 * existing points for an edge.
	 */
	public void setEdgePoints(Object edge, List points)
	{
		mxIGraphModel model = graph.getModel();
		mxGeometry geometry = model.getGeometry(edge);

		if (geometry == null)
		{
			geometry = new mxGeometry();
			geometry.setRelative(true);
		}
		else
		{
			geometry = (mxGeometry) geometry.clone();
		}

		if (this.parent != null && points != null)
		{
			Object parent = graph.getModel().getParent(edge);

				mxPoint parentOffset = getParentOffset(parent);

				for (mxPoint point : points)
				{
					point.setX(point.getX() - parentOffset.getX());
					point.setY(point.getY() - parentOffset.getY());
				}

		}

		geometry.setPoints(points);
		model.setGeometry(edge, geometry);
	}

	/**
	 * Returns an  that defines the bounds of the given cell
	 * or the bounding box if  is true.
	 */
	public mxRectangle getVertexBounds(Object vertex)
	{
		mxGeometry geo = graph.getModel().getGeometry(vertex);
		// ==============start==============
		if (geo.isRelative()) {
			geo = graph.getModel().getGeometry(graph.getModel().getParent(vertex));
		}
		// ==============end================
		return new mxRectangle(geo);
	}

	/**
	 * Sets the new position of the given cell taking into account the size of
	 * the bounding box if  is true. The change is only carried
	 * out if the new location is not equal to the existing location, otherwise
	 * the geometry is not replaced with an updated instance. The new or old
	 * bounds are returned (including overlapping labels).
	 * 
	 * Parameters:
	 * 
	 * cell -  whose geometry is to be set.
	 * x - Integer that defines the x-coordinate of the new location.
	 * y - Integer that defines the y-coordinate of the new location.
	 */
	public mxRectangle setVertexLocation(Object vertex, double x, double y)
	{
		mxIGraphModel model = graph.getModel();
		mxGeometry geometry = model.getGeometry(vertex);
		mxRectangle result = null;

		if (geometry != null)
		{
			if (geometry.isRelative()) {
				result = model.getGeometry(model.getParent(vertex));
			} else {
				result = new mxRectangle(x, y, geometry.getWidth(), geometry
						.getHeight());
			}
			mxGraphView graphView = graph.getView();

			// Checks for oversize labels and offset the result
			if (useBoundingBox)
			{
				mxCellState state = graphView.getState(vertex);

				if (state != null)
				{
					double scale = graph.getView().getScale();
					mxRectangle box = state.getBoundingBox();

					if (state.getBoundingBox().getX() < state.getX())
					{
						x += (state.getX() - box.getX()) / scale;
						result.setWidth(box.getWidth());
					}
					if (state.getBoundingBox().getY() < state.getY())
					{
						y += (state.getY() - box.getY()) / scale;
						result.setHeight(box.getHeight());
					}
				}
			}

			if (this.parent != null)
			{
				Object parent = model.getParent(vertex);

				if (parent != null && parent != this.parent)
				{
					mxPoint parentOffset = getParentOffset(parent);

					x = x - parentOffset.getX();
					y = y - parentOffset.getY();
				}
			}

			if (!geometry.isRelative() && (geometry.getX() != x || geometry.getY() != y))
			{
				geometry = (mxGeometry) geometry.clone();
				geometry.setX(x);
				geometry.setY(y);

				model.setGeometry(vertex, geometry);
			}
		}

		return result;
	}
	
	/**
	 * Updates the bounds of the given groups to include all children. Call
	 * this with the groups in parent to child order, top-most group first, eg.
	 * 
	 * arrangeGroups(graph, mxUtils.sortCells(Arrays.asList(
	 *   new Object[] { v1, v3 }), true).toArray(), 10);
	 * @param groups the groups to adjust
	 * @param border the border applied to the adjusted groups
	 */
	public void arrangeGroups(Object[] groups, int border)
	{
		graph.getModel().beginUpdate();
		try
		{
			for (int i = groups.length - 1; i >= 0; i--)
			{
				Object group = groups[i];
				Object[] children = graph.getChildVertices(group);
				mxRectangle bounds = graph.getBoundingBoxFromGeometry(children);

				mxGeometry geometry = graph.getCellGeometry(group);
				double left = 0;
				double top = 0;
				
				// Adds the size of the title area for swimlanes
				if (this.graph.isSwimlane(group))
				{
					mxRectangle size = graph.getStartSize(group);
					left = size.getWidth();
					top = size.getHeight();
				}
				
				if (bounds != null && geometry != null)
				{
					geometry = (mxGeometry) geometry.clone();
					geometry.setX(geometry.getX() + bounds.getX() - border - left);
					geometry.setY(geometry.getY() + bounds.getY() - border - top);
					geometry.setWidth(bounds.getWidth() + 2 * border + left);
					geometry.setHeight(bounds.getHeight() + 2 * border + top);
					graph.getModel().setGeometry(group, geometry);
					graph.moveCells(children, border + left - bounds.getX(),
							border + top - bounds.getY());
				}
			}
		}
		finally
		{
			graph.getModel().endUpdate();
		}
	}
}