14.0.1 release

This commit is contained in:
David Benson [draw.io] 2020-12-10 22:45:22 +00:00
parent 479e5a1701
commit 414e2d2f62
248 changed files with 82910 additions and 979 deletions

View file

@ -1,3 +1,8 @@
10-DEC-2020: 14.0.1
- Uses minimal UI on iPad Mini and smaller
- Adds clear.html for clearing caches
09-DEC-2020: 14.0.0
- Internal refactoring of source code

View file

@ -1 +1 @@
14.0.0
14.0.1

View file

@ -164,7 +164,7 @@
<file name="Init.js" />
</sources>
<sources dir="${basedir}/../mxgraph">
<sources dir="${war.dir}/mxgraph">
<file name="mxClient.js" />
</sources>

View file

@ -1190,7 +1190,7 @@ Graph.foreignObjectWarningText = 'Viewer does not support full SVG 1.1';
/**
* Link for foreign object warning.
*/
Graph.foreignObjectWarningLink = 'https://desk.draw.io/support/solutions/articles/16000042487';
Graph.foreignObjectWarningLink = 'https://www.diagrams.net/doc/faq/svg-export-text-problems';
/**
* Minimum height for table rows.

View file

@ -0,0 +1,12 @@
package com.mxgraph.analysis;
public class StructuralException extends Exception {
/**
* A custom exception for irregular graph structure for certain algorithms
*/
private static final long serialVersionUID = -468633497832330356L;
public StructuralException(String message) {
super(message);
}
};

View file

@ -0,0 +1,224 @@
/**
* Copyright (c) 2012-2017, JGraph Ltd
*/
package com.mxgraph.analysis;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.mxgraph.costfunction.mxDoubleValCostFunction;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.view.mxGraph;
/**
* Implements a collection of utility methods abstracting the graph structure
* taking into account graph properties such as visible/non-visible traversal
*/
public class mxAnalysisGraph
{
// contains various filters, like visibility and direction
protected Map<String, Object> properties = new HashMap<String, Object>();
// contains various data that is used for graph generation and analysis
protected mxGraphGenerator generator;
protected mxGraph graph;
/**
* Returns the incoming and/or outgoing edges for the given cell.
* If the optional parent argument is specified, then only edges are returned
* where the opposite is in the given parent cell.
*
* @param cell Cell whose edges should be returned.
* @param parent Optional parent. If specified the opposite end of any edge
* must be a child of that parent in order for the edge to be returned. The
* recurse parameter specifies whether or not it must be the direct child
* or the parent just be an ancestral parent.
* @param incoming Specifies if incoming edges should be included in the
* result.
* @param outgoing Specifies if outgoing edges should be included in the
* result.
* @param includeLoops Specifies if loops should be included in the result.
* @param recurse Specifies if the parent specified only need be an ancestral
* parent, <code>true</code>, or the direct parent, <code>false</code>
* @return Returns the edges connected to the given cell.
*/
public Object[] getEdges(Object cell, Object parent, boolean incoming, boolean outgoing, boolean includeLoops, boolean recurse)
{
if (!mxGraphProperties.isTraverseVisible(properties, mxGraphProperties.DEFAULT_TRAVERSE_VISIBLE))
{
return graph.getEdges(cell, parent, incoming, outgoing, includeLoops, recurse);
}
else
{
Object[] edges = graph.getEdges(cell, parent, incoming, outgoing, includeLoops, recurse);
List<Object> result = new ArrayList<Object>(edges.length);
mxIGraphModel model = graph.getModel();
for (int i = 0; i < edges.length; i++)
{
Object source = model.getTerminal(edges[i], true);
Object target = model.getTerminal(edges[i], false);
if (((includeLoops && source == target) || ((source != target) && ((incoming && target == cell) || (outgoing && source == cell))))
&& model.isVisible(edges[i]))
{
result.add(edges[i]);
}
}
return result.toArray();
}
};
/**
* Returns the incoming and/or outgoing edges for the given cell.
* If the optional parent argument is specified, then only edges are returned
* where the opposite is in the given parent cell.
*
* @param cell Cell whose edges should be returned.
* @param parent Optional parent. If specified the opposite end of any edge
* must be a child of that parent in order for the edge to be returned. The
* recurse parameter specifies whether or not it must be the direct child
* or the parent just be an ancestral parent.
* @param includeLoops Specifies if loops should be included in the result.
* @param recurse Specifies if the parent specified only need be an ancestral
* parent, <code>true</code>, or the direct parent, <code>false</code>
* @return Returns the edges connected to the given cell.
*/
public Object[] getEdges(Object cell, Object parent, boolean includeLoops, boolean recurse)
{
if (mxGraphProperties.isDirected(properties, mxGraphProperties.DEFAULT_DIRECTED))
{
return getEdges(cell, parent, false, true, includeLoops, recurse);
}
else
{
return getEdges(cell, parent, true, true, includeLoops, recurse);
}
};
/**
*
* @param parent the cell whose children will be return
* @return all vertices of the given <b>parent</b>
*/
public Object[] getChildVertices(Object parent)
{
return graph.getChildVertices(parent);
};
/**
*
* @param parent the cell whose child edges will be return
* @return all edges of the given <b>parent</b>
*/
public Object[] getChildEdges(Object parent)
{
return graph.getChildEdges(parent);
};
/**
*
* @param edge the whose terminal is being sought
* @param isSource whether the source terminal is being sought
* @return the terminal as specified
*/
public Object getTerminal(Object edge, boolean isSource)
{
return graph.getModel().getTerminal(edge, isSource);
};
/**
*
* @param parent
* @param vertices
* @param edges
* @return
*/
public Object[] getChildCells(Object parent, boolean vertices, boolean edges)
{
return graph.getChildCells(parent, vertices, edges);
}
/**
* Returns all distinct opposite cells for the specified terminal
* on the given edges.
*
* @param edges Edges whose opposite terminals should be returned.
* @param terminal Terminal that specifies the end whose opposite should be
* returned.
* @param sources Specifies if source terminals should be included in the
* result.
* @param targets Specifies if target terminals should be included in the
* result.
* @return Returns the cells at the opposite ends of the given edges.
*/
public Object[] getOpposites(Object[] edges, Object terminal, boolean sources, boolean targets)
{
// TODO needs non-visible graph version
return graph.getOpposites(edges, terminal, sources, targets);
};
/**
* Returns all distinct opposite cells for the specified terminal
* on the given edges.
*
* @param edges Edges whose opposite terminals should be returned.
* @param terminal Terminal that specifies the end whose opposite should be
* returned.
* @return Returns the cells at the opposite ends of the given edges.
*/
public Object[] getOpposites(Object[] edges, Object terminal)
{
if (mxGraphProperties.isDirected(properties, mxGraphProperties.DEFAULT_DIRECTED))
{
return getOpposites(edges, terminal, false, true);
}
else
{
return getOpposites(edges, terminal, true, true);
}
};
public Map<String, Object> getProperties()
{
return properties;
};
public void setProperties(Map<String, Object> properties)
{
this.properties = properties;
};
public mxGraph getGraph()
{
return graph;
};
public void setGraph(mxGraph graph)
{
this.graph = graph;
}
public mxGraphGenerator getGenerator()
{
if (generator != null)
{
return generator;
}
else
{
return new mxGraphGenerator(null, new mxDoubleValCostFunction());
}
}
public void setGenerator(mxGraphGenerator generator)
{
this.generator = generator;
};
};

View file

@ -0,0 +1,37 @@
/**
* Copyright (c) 2007-2017, Gaudenz Alder
* Copyright (c) 2007-2017, JGraph Ltd
*/
package com.mxgraph.analysis;
import com.mxgraph.view.mxCellState;
/**
* Implements a cost function for a constant cost per traversed cell.
*/
public class mxConstantCostFunction implements mxICostFunction
{
/**
*
*/
protected double cost = 0;
/**
*
* @param cost the cost value for this function
*/
public mxConstantCostFunction(double cost)
{
this.cost = cost;
}
/**
*
*/
public double getCost(mxCellState state)
{
return cost;
}
}

View file

@ -0,0 +1,38 @@
/**
* Copyright (c) 2007-2017, JGraph Ltd
*/
package com.mxgraph.analysis;
import com.mxgraph.util.mxPoint;
import com.mxgraph.view.mxCellState;
/**
* Implements a cost function for the Euclidean length of an edge.
*/
public class mxDistanceCostFunction implements mxICostFunction
{
/**
* Returns the Euclidean length of the edge defined by the absolute
* points in the given state or 0 if no points are defined.
*/
public double getCost(mxCellState state)
{
double cost = 0;
int pointCount = state.getAbsolutePointCount();
if (pointCount > 0)
{
mxPoint last = state.getAbsolutePoint(0);
for (int i = 1; i < pointCount; i++)
{
mxPoint point = state.getAbsolutePoint(i);
cost += point.getPoint().distance(last.getPoint());
last = point;
}
}
return cost;
}
}

View file

@ -0,0 +1,607 @@
/**
* Copyright (c) 2007-2017, Gaudenz Alder
* Copyright (c) 2007-2017, JGraph Ltd
*/
package com.mxgraph.analysis;
import java.util.Hashtable;
import java.util.Map;
/**
* This class implements a priority queue.
*/
public class mxFibonacciHeap
{
/**
* Maps from elements to nodes
*/
protected Map<Object, Node> nodes = new Hashtable<Object, Node>();
/**
*
*/
protected Node min;
/**
*
*/
protected int size;
/**
* Returns the node that represents element.
* @param element the element whose node to find
* @param create whether to create
* @return the node representing the specified element
*/
public Node getNode(Object element, boolean create)
{
Node node = nodes.get(element);
if (node == null && create)
{
node = new Node(element, Double.MAX_VALUE);
nodes.put(element, node);
insert(node, node.getKey());
}
return node;
}
/**
* Returns true if the queue is empty.
* @return whether the queue is empty
*/
public boolean isEmpty()
{
return min == null;
}
/**
* Decreases the key value for a heap node, given the new value to take on.
* The structure of the heap may be changed and will not be consolidated.
*
* <p>
* Running time: O(1) amortized
* </p>
*
* @param x Node whose value should be decreased.
* @param k New key value for node x.
*
* @exception IllegalArgumentException
* Thrown if k is larger than x.key value.
*/
public void decreaseKey(Node x, double k)
{
if (k > x.key)
{
throw new IllegalArgumentException(
"decreaseKey() got larger key value");
}
x.key = k;
Node y = x.parent;
if ((y != null) && (x.key < y.key))
{
cut(x, y);
cascadingCut(y);
}
if (min == null || x.key < min.key)
{
min = x;
}
}
/**
* Deletes a node from the heap given the reference to the node. The trees
* in the heap will be consolidated, if necessary. This operation may fail
* to remove the correct element if there are nodes with key value
* -Infinity.
*
* <p>
* Running time: O(log n) amortized
* </p>
*
* @param x The node to remove from the heap.
*/
public void delete(Node x)
{
// make x as small as possible
decreaseKey(x, Double.NEGATIVE_INFINITY);
// remove the smallest, which decreases n also
removeMin();
}
/**
* Inserts a new data element into the heap. No heap consolidation is
* performed at this time, the new node is simply inserted into the root
* list of this heap.
*
* <p>
* Running time: O(1) actual
* </p>
*
* @param node
* new node to insert into heap
* @param key
* key value associated with data object
*/
public void insert(Node node, double key)
{
node.key = key;
// concatenate node into min list
if (min != null)
{
node.left = min;
node.right = min.right;
min.right = node;
node.right.left = node;
if (key < min.key)
{
min = node;
}
}
else
{
min = node;
}
size++;
}
/**
* Returns the smallest element in the heap. This smallest element is the
* one with the minimum key value.
*
* <p>
* Running time: O(1) actual
* </p>
*
* @return Returns the heap node with the smallest key.
*/
public Node min()
{
return min;
}
/**
* Removes the smallest element from the heap. This will cause the trees in
* the heap to be consolidated, if necessary.
* Does not remove the data node so that the current key remains stored.
*
* <p>
* Running time: O(log n) amortized
* </p>
*
* @return Returns the node with the smallest key.
*/
public Node removeMin()
{
Node z = min;
if (z != null)
{
int numKids = z.degree;
Node x = z.child;
Node tempRight;
// for each child of z do...
while (numKids > 0)
{
tempRight = x.right;
// remove x from child list
x.left.right = x.right;
x.right.left = x.left;
// add x to root list of heap
x.left = min;
x.right = min.right;
min.right = x;
x.right.left = x;
// set parent[x] to null
x.parent = null;
x = tempRight;
numKids--;
}
// remove z from root list of heap
z.left.right = z.right;
z.right.left = z.left;
if (z == z.right)
{
min = null;
}
else
{
min = z.right;
consolidate();
}
// decrement size of heap
size--;
}
return z;
}
/**
* Returns the size of the heap which is measured in the number of elements
* contained in the heap.
*
* <p>
* Running time: O(1) actual
* </p>
*
* @return Returns the number of elements in the heap.
*/
public int size()
{
return size;
}
/**
* Joins two Fibonacci heaps into a new one. No heap consolidation is
* performed at this time. The two root lists are simply joined together.
*
* <p>
* Running time: O(1) actual
* </p>
*
* @param h1 The first heap.
* @param h2 The second heap.
* @return Returns a new heap containing h1 and h2.
*/
public static mxFibonacciHeap union(mxFibonacciHeap h1, mxFibonacciHeap h2)
{
mxFibonacciHeap h = new mxFibonacciHeap();
if ((h1 != null) && (h2 != null))
{
h.min = h1.min;
if (h.min != null)
{
if (h2.min != null)
{
h.min.right.left = h2.min.left;
h2.min.left.right = h.min.right;
h.min.right = h2.min;
h2.min.left = h.min;
if (h2.min.key < h1.min.key)
{
h.min = h2.min;
}
}
}
else
{
h.min = h2.min;
}
h.size = h1.size + h2.size;
}
return h;
}
/**
* Performs a cascading cut operation. This cuts y from its parent and then
* does the same for its parent, and so on up the tree.
*
* <p>
* Running time: O(log n); O(1) excluding the recursion
* </p>
*
* @param y The node to perform cascading cut on.
*/
protected void cascadingCut(Node y)
{
Node z = y.parent;
// if there's a parent...
if (z != null)
{
// if y is unmarked, set it marked
if (!y.mark)
{
y.mark = true;
}
else
{
// it's marked, cut it from parent
cut(y, z);
// cut its parent as well
cascadingCut(z);
}
}
}
/**
* Consolidates the trees in the heap by joining trees of equal degree until
* there are no more trees of equal degree in the root list.
*
* <p>
* Running time: O(log n) amortized
* </p>
*/
protected void consolidate()
{
int arraySize = size + 1;
Node[] array = new Node[arraySize];
// Initialize degree array
for (int i = 0; i < arraySize; i++)
{
array[i] = null;
}
// Find the number of root nodes.
int numRoots = 0;
Node x = min;
if (x != null)
{
numRoots++;
x = x.right;
while (x != min)
{
numRoots++;
x = x.right;
}
}
// For each node in root list do...
while (numRoots > 0)
{
// Access this node's degree..
int d = x.degree;
Node next = x.right;
// ..and see if there's another of the same degree.
while (array[d] != null)
{
// There is, make one of the nodes a child of the other.
Node y = array[d];
// Do this based on the key value.
if (x.key > y.key)
{
Node temp = y;
y = x;
x = temp;
}
// Node y disappears from root list.
link(y, x);
// We've handled this degree, go to next one.
array[d] = null;
d++;
}
// Save this node for later when we might encounter another
// of the same degree.
array[d] = x;
// Move forward through list.
x = next;
numRoots--;
}
// Set min to null (effectively losing the root list) and
// reconstruct the root list from the array entries in array[].
min = null;
for (int i = 0; i < arraySize; i++)
{
if (array[i] != null)
{
// We've got a live one, add it to root list.
if (min != null)
{
// First remove node from root list.
array[i].left.right = array[i].right;
array[i].right.left = array[i].left;
// Now add to root list, again.
array[i].left = min;
array[i].right = min.right;
min.right = array[i];
array[i].right.left = array[i];
// Check if this is a new min.
if (array[i].key < min.key)
{
min = array[i];
}
}
else
{
min = array[i];
}
}
}
}
/**
* The reverse of the link operation: removes x from the child list of y.
* This method assumes that min is non-null.
*
* <p>
* Running time: O(1)
* </p>
*
* @param x The child of y to be removed from y's child list.
* @param y The parent of x about to lose a child.
*/
protected void cut(Node x, Node y)
{
// remove x from childlist of y and decrement degree[y]
x.left.right = x.right;
x.right.left = x.left;
y.degree--;
// reset y.child if necessary
if (y.child == x)
{
y.child = x.right;
}
if (y.degree == 0)
{
y.child = null;
}
// add x to root list of heap
x.left = min;
x.right = min.right;
min.right = x;
x.right.left = x;
// set parent[x] to nil
x.parent = null;
// set mark[x] to false
x.mark = false;
}
/**
* Make node y a child of node x.
*
* <p>
* Running time: O(1) actual
* </p>
*
* @param y The node to become child.
* @param x The node to become parent.
*/
protected void link(Node y, Node x)
{
// remove y from root list of heap
y.left.right = y.right;
y.right.left = y.left;
// make y a child of x
y.parent = x;
if (x.child == null)
{
x.child = y;
y.right = y;
y.left = y;
}
else
{
y.left = x.child;
y.right = x.child.right;
x.child.right = y;
y.right.left = y;
}
// increase degree[x]
x.degree++;
// set mark[y] false
y.mark = false;
}
/**
* Implements a node of the Fibonacci heap. It holds the information
* necessary for maintaining the structure of the heap. It also holds the
* reference to the key value (which is used to determine the heap
* structure). Additional Node data should be stored in a subclass.
*/
public static class Node
{
Object userObject;
/**
* first child node
*/
Node child;
/**
* left sibling node
*/
Node left;
/**
* parent node
*/
Node parent;
/**
* right sibling node
*/
Node right;
/**
* true if this node has had a child removed since this node was added
* to its parent
*/
boolean mark;
/**
* key value for this node
*/
double key;
/**
* number of children of this node (does not count grandchildren)
*/
int degree;
/**
* Default constructor. Initializes the right and left pointers, making
* this a circular doubly-linked list.
*
* @param key The initial key for node.
*/
public Node(Object userObject, double key)
{
this.userObject = userObject;
right = this;
left = this;
this.key = key;
}
/**
* Obtain the key for this node.
*
* @return the key
*/
public final double getKey()
{
return key;
}
/**
* @return Returns the userObject.
*/
public Object getUserObject()
{
return userObject;
}
/**
* @param userObject The userObject to set.
*/
public void setUserObject(Object userObject)
{
this.userObject = userObject;
}
}
}

View file

@ -0,0 +1,463 @@
/*
* Copyright (c) 2001-2005, Gaudenz Alder
*
* All rights reserved.
*
* This file is licensed under the JGraph software license, a copy of which
* will have been provided to you in the file LICENSE at the root of your
* installation directory. If you are unable to locate this file please
* contact JGraph sales for another copy.
*/
package com.mxgraph.analysis;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.List;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxGraphView;
/**
* A singleton class that provides algorithms for graphs. Assume these
* variables for the following examples:<br>
* <code>
* mxICostFunction cf = mxDistanceCostFunction();
* Object[] v = graph.getChildVertices(graph.getDefaultParent());
* Object[] e = graph.getChildEdges(graph.getDefaultParent());
* mxGraphAnalysis mga = mxGraphAnalysis.getInstance();
* </code>
*
* <h3>Shortest Path (Dijkstra)</h3>
*
* For example, to find the shortest path between the first and the second
* selected cell in a graph use the following code: <br>
* <br>
* <code>Object[] path = mga.getShortestPath(graph, from, to, cf, v.length, true);</code>
*
* <h3>Minimum Spanning Tree</h3>
*
* This algorithm finds the set of edges with the minimal length that connect
* all vertices. This algorithm can be used as follows:
* <h5>Prim</h5>
* <code>mga.getMinimumSpanningTree(graph, v, cf, true))</code>
* <h5>Kruskal</h5>
* <code>mga.getMinimumSpanningTree(graph, v, e, cf))</code>
*
* <h3>Connection Components</h3>
*
* The union find may be used as follows to determine whether two cells are
* connected: <code>boolean connected = uf.differ(vertex1, vertex2)</code>.
*
* @see mxICostFunction
*/
public class mxGraphAnalysis
{
/**
* Holds the shared instance of this class.
*/
protected static mxGraphAnalysis instance = new mxGraphAnalysis();
/**
*
*/
protected mxGraphAnalysis()
{
// empty
}
/**
* @return Returns the sharedInstance.
*/
public static mxGraphAnalysis getInstance()
{
return instance;
}
/**
* Sets the shared instance of this class.
*
* @param instance The instance to set.
*/
public static void setInstance(mxGraphAnalysis instance)
{
mxGraphAnalysis.instance = instance;
}
/**
* Returns the shortest path between two cells or their descendants
* represented as an array of edges in order of traversal. <br>
* This implementation is based on the Dijkstra algorithm.
*
* @param graph The object that defines the graph structure
* @param from The source cell.
* @param to The target cell (aka sink).
* @param cf The cost function that defines the edge length.
* @param steps The maximum number of edges to traverse.
* @param directed If edge directions should be taken into account.
* @return Returns the shortest path as an alternating array of vertices
* and edges, starting with <code>from</code> and ending with
* <code>to</code>.
*
* @see #createPriorityQueue()
*/
public Object[] getShortestPath(mxGraph graph, Object from, Object to,
mxICostFunction cf, int steps, boolean directed)
{
// Sets up a pqueue and a hashtable to store the predecessor for each
// cell in tha graph traversal. The pqueue is initialized
// with the from element at prio 0.
mxGraphView view = graph.getView();
mxFibonacciHeap q = createPriorityQueue();
Hashtable<Object, Object> pred = new Hashtable<Object, Object>();
q.decreaseKey(q.getNode(from, true), 0); // Inserts automatically
// The main loop of the dijkstra algorithm is based on the pqueue being
// updated with the actual shortest distance to the source vertex.
for (int j = 0; j < steps; j++)
{
mxFibonacciHeap.Node node = q.removeMin();
double prio = node.getKey();
Object obj = node.getUserObject();
// Exits the loop if the target node or vertex has been reached
if (obj == to)
{
break;
}
// Gets all outgoing edges of the closest cell to the source
Object[] e = (directed) ? graph.getOutgoingEdges(obj) : graph
.getConnections(obj);
if (e != null)
{
for (int i = 0; i < e.length; i++)
{
Object[] opp = graph.getOpposites(new Object[] { e[i] },
obj);
if (opp != null && opp.length > 0)
{
Object neighbour = opp[0];
// Updates the priority in the pqueue for the opposite node
// to be the distance of this step plus the cost to
// traverese the edge to the neighbour. Note that the
// priority queue will make sure that in the next step the
// node with the smallest prio will be traversed.
if (neighbour != null && neighbour != obj
&& neighbour != from)
{
double newPrio = prio
+ ((cf != null) ? cf.getCost(view
.getState(e[i])) : 1);
node = q.getNode(neighbour, true);
double oldPrio = node.getKey();
if (newPrio < oldPrio)
{
pred.put(neighbour, e[i]);
q.decreaseKey(node, newPrio);
}
}
}
}
}
if (q.isEmpty())
{
break;
}
}
// Constructs a path array by walking backwards through the predessecor
// map and filling up a list of edges, which is subsequently returned.
ArrayList<Object> list = new ArrayList<Object>(2 * steps);
Object obj = to;
Object edge = pred.get(obj);
if (edge != null)
{
list.add(obj);
while (edge != null)
{
list.add(0, edge);
mxCellState state = view.getState(edge);
Object source = (state != null) ? state
.getVisibleTerminal(true) : view.getVisibleTerminal(
edge, true);
boolean isSource = source == obj;
obj = (state != null) ? state.getVisibleTerminal(!isSource)
: view.getVisibleTerminal(edge, !isSource);
list.add(0, obj);
edge = pred.get(obj);
}
}
return list.toArray();
}
/**
* Returns the minimum spanning tree (MST) for the graph defined by G=(E,V).
* The MST is defined as the set of all vertices with minimal lengths that
* forms no cycles in G.<br>
* This implementation is based on the algorihm by Prim-Jarnik. It uses
* O(|E|+|V|log|V|) time when used with a Fibonacci heap and a graph whith a
* double linked-list datastructure, as is the case with the default
* implementation.
*
* @param graph
* the object that describes the graph
* @param v
* the vertices of the graph
* @param cf
* the cost function that defines the edge length
*
* @return Returns the MST as an array of edges
*
* @see #createPriorityQueue()
*/
public Object[] getMinimumSpanningTree(mxGraph graph, Object[] v,
mxICostFunction cf, boolean directed)
{
ArrayList<Object> mst = new ArrayList<Object>(v.length);
// Sets up a pqueue and a hashtable to store the predecessor for each
// cell in tha graph traversal. The pqueue is initialized
// with the from element at prio 0.
mxFibonacciHeap q = createPriorityQueue();
Hashtable<Object, Object> pred = new Hashtable<Object, Object>();
Object u = v[0];
q.decreaseKey(q.getNode(u, true), 0);
for (int i = 1; i < v.length; i++)
{
q.getNode(v[i], true);
}
// The main loop of the dijkstra algorithm is based on the pqueue being
// updated with the actual shortest distance to the source vertex.
while (!q.isEmpty())
{
mxFibonacciHeap.Node node = q.removeMin();
u = node.getUserObject();
Object edge = pred.get(u);
if (edge != null)
{
mst.add(edge);
}
// Gets all outgoing edges of the closest cell to the source
Object[] e = (directed) ? graph.getOutgoingEdges(u) : graph
.getConnections(u);
Object[] opp = graph.getOpposites(e, u);
if (e != null)
{
for (int i = 0; i < e.length; i++)
{
Object neighbour = opp[i];
// Updates the priority in the pqueue for the opposite node
// to be the distance of this step plus the cost to
// traverese the edge to the neighbour. Note that the
// priority queue will make sure that in the next step the
// node with the smallest prio will be traversed.
if (neighbour != null && neighbour != u)
{
node = q.getNode(neighbour, false);
if (node != null)
{
double newPrio = cf.getCost(graph.getView()
.getState(e[i]));
double oldPrio = node.getKey();
if (newPrio < oldPrio)
{
pred.put(neighbour, e[i]);
q.decreaseKey(node, newPrio);
}
}
}
}
}
}
return mst.toArray();
}
/**
* Returns the minimum spanning tree (MST) for the graph defined by G=(E,V).
* The MST is defined as the set of all vertices with minimal lenths that
* forms no cycles in G.<br>
* This implementation is based on the algorihm by Kruskal. It uses
* O(|E|log|E|)=O(|E|log|V|) time for sorting the edges, O(|V|) create sets,
* O(|E|) find and O(|V|) union calls on the union find structure, thus
* yielding no more than O(|E|log|V|) steps. For a faster implementatin
*
* @see #getMinimumSpanningTree(mxGraph, Object[], mxICostFunction,
* boolean)
*
* @param graph The object that contains the graph.
* @param v The vertices of the graph.
* @param e The edges of the graph.
* @param cf The cost function that defines the edge length.
*
* @return Returns the MST as an array of edges.
*
* @see #createUnionFind(Object[])
*/
public Object[] getMinimumSpanningTree(mxGraph graph, Object[] v,
Object[] e, mxICostFunction cf)
{
// Sorts all edges according to their lengths, then creates a union
// find structure for all vertices. Then walks through all edges by
// increasing length and tries adding to the MST. Only edges are added
// that do not form cycles in the graph, that is, where the source
// and target are in different sets in the union find structure.
// Whenever an edge is added to the MST, the two different sets are
// unified.
mxGraphView view = graph.getView();
mxUnionFind uf = createUnionFind(v);
ArrayList<Object> result = new ArrayList<Object>(e.length);
mxCellState[] edgeStates = sort(view.getCellStates(e), cf);
for (int i = 0; i < edgeStates.length; i++)
{
Object source = edgeStates[i].getVisibleTerminal(true);
Object target = edgeStates[i].getVisibleTerminal(false);
mxUnionFind.Node setA = uf.find(uf.getNode(source));
mxUnionFind.Node setB = uf.find(uf.getNode(target));
if (setA == null || setB == null || setA != setB)
{
uf.union(setA, setB);
result.add(edgeStates[i].getCell());
}
}
return result.toArray();
}
/**
* Returns a union find structure representing the connection components of
* G=(E,V).
*
* @param graph The object that contains the graph.
* @param v The vertices of the graph.
* @param e The edges of the graph.
* @return Returns the connection components in G=(E,V)
*
* @see #createUnionFind(Object[])
*/
public mxUnionFind getConnectionComponents(mxGraph graph, Object[] v,
Object[] e)
{
mxGraphView view = graph.getView();
mxUnionFind uf = createUnionFind(v);
for (int i = 0; i < e.length; i++)
{
mxCellState state = view.getState(e[i]);
Object source = (state != null) ? state.getVisibleTerminal(true)
: view.getVisibleTerminal(e[i], true);
Object target = (state != null) ? state.getVisibleTerminal(false)
: view.getVisibleTerminal(e[i], false);
uf.union(uf.find(uf.getNode(source)), uf.find(uf.getNode(target)));
}
return uf;
}
/**
* Returns a sorted set for <code>cells</code> with respect to
* <code>cf</code>.
*
* @param states
* the cell states to sort
* @param cf
* the cost function that defines the order
*
* @return Returns an ordered set of <code>cells</code> wrt.
* <code>cf</code>
*/
public mxCellState[] sort(mxCellState[] states, final mxICostFunction cf)
{
List<mxCellState> result = Arrays.asList(states);
Collections.sort(result, new Comparator<mxCellState>()
{
/**
*
*/
public int compare(mxCellState o1, mxCellState o2)
{
Double d1 = new Double(cf.getCost(o1));
Double d2 = new Double(cf.getCost(o2));
return d1.compareTo(d2);
}
});
return (mxCellState[]) result.toArray();
}
/**
* Returns the sum of all cost for <code>cells</code> with respect to
* <code>cf</code>.
*
* @param states
* the cell states to use for the sum
* @param cf
* the cost function that defines the costs
*
* @return Returns the sum of all cell cost
*/
public double sum(mxCellState[] states, mxICostFunction cf)
{
double sum = 0;
for (int i = 0; i < states.length; i++)
{
sum += cf.getCost(states[i]);
}
return sum;
}
/**
* Hook for subclassers to provide a custom union find structure.
*
* @param v
* the array of all elements
*
* @return Returns a union find structure for <code>v</code>
*/
protected mxUnionFind createUnionFind(Object[] v)
{
return new mxUnionFind(v);
}
/**
* Hook for subclassers to provide a custom fibonacci heap.
*/
protected mxFibonacciHeap createPriorityQueue()
{
return new mxFibonacciHeap();
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,129 @@
package com.mxgraph.analysis;
import java.util.Map;
import com.mxgraph.util.mxUtils;
/**
* Constants for graph structure properties
*/
public class mxGraphProperties
{
public enum GraphType
{
FULLY_CONNECTED,
RANDOM_CONNECTED,
TREE,
FLOW,
NULL,
COMPLETE,
NREGULAR,
GRID,
BIPARTITE,
COMPLETE_BIPARTITE,
BASIC_TREE,
SIMPLE_RANDOM,
BFS_DIR,
BFS_UNDIR,
DFS_DIR,
DFS_UNDIR,
DIJKSTRA,
MAKE_TREE_DIRECTED,
SIMPLE_RANDOM_TREE,
KNIGHT_TOUR,
KNIGHT,
GET_ADJ_MATRIX,
FROM_ADJ_MATRIX,
PETERSEN,
WHEEL,
STAR,
PATH,
FRIENDSHIP_WINDMILL,
FULL_WINDMILL,
INDEGREE,
OUTDEGREE,
IS_CUT_VERTEX,
IS_CUT_EDGE,
RESET_STYLE,
KING,
BELLMAN_FORD
}
/**
* Whether or not to navigate the graph raw graph structure or
* the visible structure. The value associated with this key
* should evaluate as a string to <code>1</code> or
* <code>0</code>
*/
public static String TRAVERSE_VISIBLE = "traverseVisible";
public static boolean DEFAULT_TRAVERSE_VISIBLE = false;
/**
* Whether or not to take into account the direction on edges.
* The value associated with this key should evaluate as a
* string to <code>1</code> or <code>0</code>
*/
public static String DIRECTED = "directed";
public static boolean DEFAULT_DIRECTED = false;
/**
* @param properties
* @param defaultValue
* @return
*/
public static boolean isTraverseVisible(Map<String, Object> properties, boolean defaultValue)
{
if (properties != null)
{
return mxUtils.isTrue(properties, TRAVERSE_VISIBLE, defaultValue);
}
return false;
}
/**
*
* @param properties
* @param isTraverseVisible
*/
public static void setTraverseVisible(Map<String, Object> properties,
boolean isTraverseVisible)
{
if (properties != null)
{
properties.put(TRAVERSE_VISIBLE, isTraverseVisible);
}
}
/**
*
* @param properties
* @return
*/
public static boolean isDirected(Map<String, Object> properties, boolean defaultValue)
{
if (properties != null)
{
return mxUtils.isTrue(properties, DIRECTED, defaultValue);
}
return false;
}
/**
*
* @param properties
* @param isTraverseVisible
*/
public static void setDirected(Map<String, Object> properties,
boolean isDirected)
{
if (properties != null)
{
properties.put(DIRECTED, isDirected);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
/**
* Copyright (c) 2007, Gaudenz Alder
*/
package com.mxgraph.analysis;
import com.mxgraph.view.mxCellState;
/**
* The cost function takes a cell and returns it's cost as a double. Two typical
* examples of cost functions are the euclidian length of edges or a constant
* number for each edge. To use one of the built-in cost functions, use either
* <code>new mxDistanceCostFunction(graph)</code> or
* <code>new mxConstantCostFunction(1)</code>.
*/
public interface mxICostFunction
{
/**
* Evaluates the cost of the given cell state.
*
* @param state The cell state to be evaluated
* @return Returns the cost to traverse the given cell state.
*/
double getCost(mxCellState state);
}

View file

@ -0,0 +1,558 @@
/**
* Copyright (c) 2011-2012, JGraph Ltd
*/
package com.mxgraph.analysis;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.mxgraph.costfunction.mxCostFunction;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxGraph.mxICellVisitor;
import com.mxgraph.view.mxGraphView;
/**
* Implements a collection of utility methods for traversing the
* graph structure. This does not include tree traversal methods.
*/
public class mxTraversal
{
/**
* Implements a recursive depth first search starting from the specified
* cell. Process on the cell is performing by the visitor class passed in.
* The visitor has access to the current cell and the edge traversed to
* find this cell. Every cell is processed once only.
* <pre>
* mxTraversal.bfs(analysisGraph, startVertex, new mxICellVisitor()
* {
* public boolean visit(Object vertex, Object edge)
* {
* // perform your processing on each cell here
* return false;
* }
* });
* </pre>
* @param aGraph the graph
* @param startVertex
* @param visitor
*/
public static void dfs(mxAnalysisGraph aGraph, Object startVertex, mxICellVisitor visitor)
{
dfsRec(aGraph, startVertex, null, new HashSet<Object>(), visitor);
}
/**
* Core recursive DFS - for internal use
* @param aGraph
* @param cell
* @param edge
* @param seen
* @param visitor
*/
private static void dfsRec(mxAnalysisGraph aGraph, Object cell, Object edge, Set<Object> seen, mxICellVisitor visitor)
{
if (cell != null)
{
if (!seen.contains(cell))
{
visitor.visit(cell, edge);
seen.add(cell);
final Object[] edges = aGraph.getEdges(cell, null, false, true);
final Object[] opposites = aGraph.getOpposites(edges, cell);
for (int i = 0; i < opposites.length; i++)
{
dfsRec(aGraph, opposites[i], edges[i], seen, visitor);
}
}
}
}
/**
* Implements a recursive breadth first search starting from the specified
* cell. Process on the cell is performing by the visitor class passed in.
* The visitor has access to the current cell and the edge traversed to
* find this cell. Every cell is processed once only.
* <pre>
* mxTraversal.bfs(analysisGraph, startVertex, new mxICellVisitor()
* {
* public boolean visit(Object vertex, Object edge)
* {
* // perform your processing on each cell here
* return false;
* }
* });
* </pre>
* @param aGraph the graph
* @param startVertex
* @param visitor
*/
public static void bfs(mxAnalysisGraph aGraph, Object startVertex, mxICellVisitor visitor)
{
if (aGraph != null && startVertex != null && visitor != null)
{
Set<Object> queued = new HashSet<Object>();
LinkedList<Object[]> queue = new LinkedList<Object[]>();
Object[] q = { startVertex, null };
queue.addLast(q);
queued.add(startVertex);
bfsRec(aGraph, queued, queue, visitor);
}
};
/**
* Core recursive BFS - for internal use
* @param aGraph
* @param queued
* @param queue
* @param visitor
*/
private static void bfsRec(mxAnalysisGraph aGraph, Set<Object> queued, LinkedList<Object[]> queue, mxICellVisitor visitor)
{
if (queue.size() > 0)
{
Object[] q = queue.removeFirst();
Object cell = q[0];
Object incomingEdge = q[1];
visitor.visit(cell, incomingEdge);
final Object[] edges = aGraph.getEdges(cell, null, false, false);
for (int i = 0; i < edges.length; i++)
{
Object[] currEdge = { edges[i] };
Object opposite = aGraph.getOpposites(currEdge, cell)[0];
if (!queued.contains(opposite))
{
Object[] current = { opposite, edges[i] };
queue.addLast(current);
queued.add(opposite);
}
}
bfsRec(aGraph, queued, queue, visitor);
}
};
/**
* Implements the Dijkstra's shortest path from startVertex to endVertex.
* Process on the cell is performing by the visitor class passed in.
* The visitor has access to the current cell and the edge traversed to
* find this cell. Every cell is processed once only.
* <pre>
* mxTraversal.dijkstra(analysisGraph, startVertex, endVertex, new mxICellVisitor()
* {
* public boolean visit(Object vertex, Object edge)
* {
* // perform your processing on each cell here
* return false;
* }
* });
* </pre>
*
* @param aGraph
* @param startVertex
* @param endVertex
* @param visitor
* @throws StructuralException - The current Dijkstra algorithm only works for connected graphs
*/
public static void dijkstra(mxAnalysisGraph aGraph, Object startVertex, Object endVertex, mxICellVisitor visitor)
throws StructuralException
{
if (!mxGraphStructure.isConnected(aGraph))
{
throw new StructuralException("The current Dijkstra algorithm only works for connected graphs and this graph isn't connected");
}
Object parent = aGraph.getGraph().getDefaultParent();
Object[] vertexes = aGraph.getChildVertices(parent);
int vertexCount = vertexes.length;
double[] distances = new double[vertexCount];
// parents[][0] is the traveled vertex
// parents[][1] is the traveled outgoing edge
Object[][] parents = new Object[vertexCount][2];
ArrayList<Object> vertexList = new ArrayList<Object>();
ArrayList<Object> vertexListStatic = new ArrayList<Object>();
for (int i = 0; i < vertexCount; i++)
{
distances[i] = Integer.MAX_VALUE;
vertexList.add((Object) vertexes[i]);
vertexListStatic.add((Object) vertexes[i]);
}
distances[vertexListStatic.indexOf(startVertex)] = 0;
mxCostFunction costFunction = aGraph.getGenerator().getCostFunction();
mxGraphView view = aGraph.getGraph().getView();
while (vertexList.size() > 0)
{
//find closest vertex
double minDistance;
Object currVertex;
Object closestVertex;
currVertex = vertexList.get(0);
int currIndex = vertexListStatic.indexOf(currVertex);
double currDistance = distances[currIndex];
minDistance = currDistance;
closestVertex = currVertex;
if (vertexList.size() > 1)
{
for (int i = 1; i < vertexList.size(); i++)
{
currVertex = vertexList.get(i);
currIndex = vertexListStatic.indexOf(currVertex);
currDistance = distances[currIndex];
if (currDistance < minDistance)
{
minDistance = currDistance;
closestVertex = currVertex;
}
}
}
// we found the closest vertex
vertexList.remove(closestVertex);
Object currEdge = new Object();
Object[] neighborVertices = aGraph.getOpposites(aGraph.getEdges(closestVertex, null, true, true, false, true), closestVertex,
true, true);
for (int j = 0; j < neighborVertices.length; j++)
{
Object currNeighbor = neighborVertices[j];
if (vertexList.contains(currNeighbor))
{
//find edge that connects to the current vertex
Object[] neighborEdges = aGraph.getEdges(currNeighbor, null, true, true, false, true);
Object connectingEdge = null;
for (int k = 0; k < neighborEdges.length; k++)
{
currEdge = neighborEdges[k];
if (aGraph.getTerminal(currEdge, true).equals(closestVertex)
|| aGraph.getTerminal(currEdge, false).equals(closestVertex))
{
connectingEdge = currEdge;
}
}
// check for new distance
int neighborIndex = vertexListStatic.indexOf(currNeighbor);
double oldDistance = distances[neighborIndex];
double currEdgeWeight;
currEdgeWeight = costFunction.getCost(new mxCellState(view, connectingEdge, null));
double newDistance = minDistance + currEdgeWeight;
//final part - updating the structure
if (newDistance < oldDistance)
{
distances[neighborIndex] = newDistance;
parents[neighborIndex][0] = closestVertex;
parents[neighborIndex][1] = connectingEdge;
}
}
}
}
ArrayList<Object[]> resultList = new ArrayList<Object[]>();
Object currVertex = endVertex;
while (currVertex != startVertex)
{
int currIndex = vertexListStatic.indexOf(currVertex);
currVertex = parents[currIndex][0];
resultList.add(0, parents[currIndex]);
}
resultList.add(resultList.size(), new Object[] { endVertex, null });
for (int i = 0; i < resultList.size(); i++)
{
visitor.visit(resultList.get(i)[0], resultList.get(i)[1]);
}
};
/**
* Implements the Bellman-Ford shortest path from startVertex to all vertices.
*
* @param aGraph
* @param startVertex
* @return a List where List(0) is the distance map and List(1) is the parent map. See the example in GraphConfigDialog.java
* @throws StructuralException - The Bellman-Ford algorithm only works for graphs without negative cycles
*/
public static List<Map<Object, Object>> bellmanFord(mxAnalysisGraph aGraph, Object startVertex) throws StructuralException
{
mxGraph graph = aGraph.getGraph();
Object[] vertices = aGraph.getChildVertices(graph.getDefaultParent());
Object[] edges = aGraph.getChildEdges(graph.getDefaultParent());
int vertexNum = vertices.length;
int edgeNum = edges.length;
Map<Object, Object> distanceMap = new HashMap<Object, Object>();
Map<Object, Object> parentMap = new HashMap<Object, Object>();
mxCostFunction costFunction = aGraph.getGenerator().getCostFunction();
mxGraphView view = graph.getView();
for (int i = 0; i < vertexNum; i++)
{
Object currVertex = vertices[i];
distanceMap.put(currVertex, Double.MAX_VALUE);
}
distanceMap.put(startVertex, 0.0);
parentMap.put(startVertex, startVertex);
for (int i = 0; i < vertexNum; i++)
{
for (int j = 0; j < edgeNum; j++)
{
Object currEdge = edges[j];
Object source = aGraph.getTerminal(currEdge, true);
Object target = aGraph.getTerminal(currEdge, false);
double dist = (Double) distanceMap.get(source) + costFunction.getCost(new mxCellState(view, currEdge, null));
if (dist < (Double) distanceMap.get(target))
{
distanceMap.put(target, dist);
parentMap.put(target, source);
}
//for undirected graphs, check the reverse direction too
if (!mxGraphProperties.isDirected(aGraph.getProperties(), mxGraphProperties.DEFAULT_DIRECTED))
{
dist = (Double) distanceMap.get(target) + costFunction.getCost(new mxCellState(view, currEdge, null));
if (dist < (Double) distanceMap.get(source))
{
distanceMap.put(source, dist);
parentMap.put(source, target);
}
}
}
}
for (int i = 0; i < edgeNum; i++)
{
Object currEdge = edges[i];
Object source = aGraph.getTerminal(currEdge, true);
Object target = aGraph.getTerminal(currEdge, false);
double dist = (Double) distanceMap.get(source) + costFunction.getCost(new mxCellState(view, currEdge, null));
if (dist < (Double) distanceMap.get(target))
{
throw new StructuralException("The graph contains a negative cycle, so Bellman-Ford can't be completed.");
}
}
List<Map<Object, Object>> result = new ArrayList<Map<Object, Object>>();
result.add(distanceMap);
result.add(parentMap);
return result;
};
/**
* Implements the Floyd-Roy-Warshall (aka WFI) shortest path algorithm between all vertices.
*
* @param aGraph
* @return an ArrayList where ArrayList(0) is the distance map and List(1) is the path map. See the example in GraphConfigDialog.java
* @throws StructuralException - The Floyd-Roy-Warshall algorithm only works for graphs without negative cycles
*/
public static ArrayList<Object[][]> floydRoyWarshall(mxAnalysisGraph aGraph) throws StructuralException
{
Object[] vertices = aGraph.getChildVertices(aGraph.getGraph().getDefaultParent());
Double[][] dist = new Double[vertices.length][vertices.length];
Object[][] paths = new Object[vertices.length][vertices.length];
Map<Object, Integer> indexMap = new HashMap<Object, Integer>();
for (int i = 0; i < vertices.length; i++)
{
indexMap.put(vertices[i], i);
}
Object[] edges = aGraph.getChildEdges(aGraph.getGraph().getDefaultParent());
dist = initializeWeight(aGraph, vertices, edges, indexMap);
for (int k = 0; k < vertices.length; k++)
{
for (int i = 0; i < vertices.length; i++)
{
for (int j = 0; j < vertices.length; j++)
{
if (dist[i][j] > dist[i][k] + dist[k][j])
{
paths[i][j] = mxGraphStructure.getVertexWithValue(aGraph, k);
dist[i][j] = dist[i][k] + dist[k][j];
}
}
}
}
for (int i = 0; i < dist[0].length; i++)
{
if ((Double) dist[i][i] < 0)
{
throw new StructuralException("The graph has negative cycles");
}
}
ArrayList<Object[][]> result = new ArrayList<Object[][]>();
result.add(dist);
result.add(paths);
return result;
};
/**
* A helper function for the Floyd-Roy-Warshall algorithm - for internal use
* @param aGraph
* @param nodes
* @param edges
* @param indexMap
* @return
*/
private static Double[][] initializeWeight(mxAnalysisGraph aGraph, Object[] nodes, Object[] edges, Map<Object, Integer> indexMap)
{
Double[][] weight = new Double[nodes.length][nodes.length];
for (int i = 0; i < nodes.length; i++)
{
Arrays.fill(weight[i], Double.MAX_VALUE);
}
boolean isDirected = mxGraphProperties.isDirected(aGraph.getProperties(), mxGraphProperties.DEFAULT_DIRECTED);
mxCostFunction costFunction = aGraph.getGenerator().getCostFunction();
mxGraphView view = aGraph.getGraph().getView();
for (Object currEdge : edges)
{
Object source = aGraph.getTerminal(currEdge, true);
Object target = aGraph.getTerminal(currEdge, false);
weight[indexMap.get(source)][indexMap.get(target)] = costFunction.getCost(view.getState(currEdge));
if (!isDirected)
{
weight[indexMap.get(target)][indexMap.get(source)] = costFunction.getCost(view.getState(currEdge));
}
}
for (int i = 0; i < nodes.length; i++)
{
weight[i][i] = 0.0;
}
return weight;
};
/**
* This method helps the user to get the desired data from the result of the Floyd-Roy-Warshall algorithm.
* @param aGraph
* @param FWIresult - the result of the Floyd-Roy-Warhall algorithm
* @param startVertex
* @param targetVertex
* @return returns the shortest path from <b>startVertex</b> to <b>endVertex</b>
* @throws StructuralException - The Floyd-Roy-Warshall algorithm only works for graphs without negative cycles
*/
public static Object[] getWFIPath(mxAnalysisGraph aGraph, ArrayList<Object[][]> FWIresult, Object startVertex, Object targetVertex)
throws StructuralException
{
Object[][] dist = FWIresult.get(0);
Object[][] paths = FWIresult.get(1);
ArrayList<Object> result = null;
if (aGraph == null || paths == null || startVertex == null || targetVertex == null)
{
throw new IllegalArgumentException();
}
for (int i = 0; i < dist[0].length; i++)
{
if ((Double) dist[i][i] < 0)
{
throw new StructuralException("The graph has negative cycles");
}
}
if (startVertex != targetVertex)
{
mxCostFunction cf = aGraph.getGenerator().getCostFunction();
mxGraphView view = aGraph.getGraph().getView();
ArrayList<Object> currPath = new ArrayList<Object>();
currPath.add(startVertex);
while (startVertex != targetVertex)
{
result = getWFIPathRec(aGraph, paths, startVertex, targetVertex, currPath, cf, view);
startVertex = result.get(result.size() - 1);
}
}
if (result == null)
{
result = new ArrayList<Object>();
}
return result.toArray();
};
/**
* Helper method for getWFIPath - for internal use
* @param aGraph
* @param paths
* @param startVertex
* @param targetVertex
* @param currPath
* @param cf
* @param view
* @return
* @throws StructuralException
*/
private static ArrayList<Object> getWFIPathRec(mxAnalysisGraph aGraph, Object[][] paths, Object startVertex, Object targetVertex,
ArrayList<Object> currPath, mxCostFunction cf, mxGraphView view) throws StructuralException
{
Double sourceIndexD = (Double) cf.getCost(view.getState(startVertex));
Object[] parents = paths[sourceIndexD.intValue()];
Double targetIndexD = (Double) cf.getCost(view.getState(targetVertex));
int tIndex = targetIndexD.intValue();
if (parents[tIndex] != null)
{
currPath = getWFIPathRec(aGraph, paths, startVertex, parents[tIndex], currPath, cf, view);
}
else
{
if (mxGraphStructure.areConnected(aGraph, startVertex, targetVertex) || startVertex == targetVertex)
{
currPath.add(targetVertex);
}
else
{
throw new StructuralException("The two vertices aren't connected");
}
}
return currPath;
}
};

View file

@ -0,0 +1,156 @@
/**
* Copyright (c) 2007, Gaudenz Alder
*/
package com.mxgraph.analysis;
import java.util.Hashtable;
import java.util.Map;
/**
* Implements a union find structure that uses union by rank and path
* compression. The union by rank guarantees worst case find time of O(log N),
* while Tarjan shows that in combination with path compression (halving) the
* average time for an arbitrary sequence of m >= n operations is
* O(m*alpha(m,n)), where alpha is the inverse of the Ackermann function,
* defined as follows:
* <code>alpha(m,n) = min{i &gt;= 1 | A(i, floor(m/n)) &gt; log n} for m &gt;= n &gt;= 1</code>
* Which yields almost constant time for each individual operation.
*/
public class mxUnionFind
{
/**
* Maps from elements to nodes
*/
protected Map<Object, Node> nodes = new Hashtable<Object, Node>();
/**
* Constructs a union find structure and initializes it with the specified
* elements.
*
* @param elements
*/
public mxUnionFind(Object[] elements)
{
for (int i = 0; i < elements.length; i++)
{
nodes.put(elements[i], new Node());
}
}
/**
* Returns the node that represents element.
*/
public Node getNode(Object element)
{
return nodes.get(element);
}
/**
* Returns the set that contains <code>node</code>. This implementation
* provides path compression by halving.
*/
public Node find(Node node)
{
while (node.getParent().getParent() != node.getParent())
{
Node t = node.getParent().getParent();
node.setParent(t);
node = t;
}
return node.getParent();
}
/**
* Unifies the sets <code>a</code> and <code>b</code> in constant time
* using a union by rank on the tree size.
*/
public void union(Node a, Node b)
{
Node set1 = find(a);
Node set2 = find(b);
if (set1 != set2)
{
// Limits the worst case runtime of a find to O(log N)
if (set1.getSize() < set2.getSize())
{
set2.setParent(set1);
set1.setSize(set1.getSize() + set2.getSize());
}
else
{
set1.setParent(set2);
set2.setSize(set1.getSize() + set2.getSize());
}
}
}
/**
* Returns true if element a and element b are not in the same set. This
* uses getNode and then find to determine the elements set.
*
* @param a The first element to compare.
* @param b The second element to compare.
* @return Returns true if a and b are in the same set.
*
* @see #getNode(Object)
*/
public boolean differ(Object a, Object b)
{
Node set1 = find(getNode(a));
Node set2 = find(getNode(b));
return set1 != set2;
}
/**
* A class that defines the identity of a set.
*/
public class Node
{
/**
* Reference to the parent node. Root nodes point to themselves.
*/
protected Node parent = this;
/**
* The size of the tree. Initial value is 1.
*/
protected int size = 1;
/**
* @return Returns the parent node
*/
public Node getParent()
{
return parent;
}
/**
* @param parent The parent node to set.
*/
public void setParent(Node parent)
{
this.parent = parent;
}
/**
* @return Returns the size.
*/
public int getSize()
{
return size;
}
/**
* @param size The size to set.
*/
public void setSize(int size)
{
this.size = size;
}
}
}

View file

@ -0,0 +1,6 @@
<HTML>
<BODY>
This package provides various algorithms for graph analysis, such as
shortest path and minimum spanning tree.
</BODY>
</HTML>

View file

@ -0,0 +1,161 @@
package com.mxgraph.canvas;
import java.awt.image.BufferedImage;
import java.util.Hashtable;
import java.util.Map;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxUtils;
public abstract class mxBasicCanvas implements mxICanvas
{
/**
* Specifies if image aspect should be preserved in drawImage. Default is true.
*/
public static boolean PRESERVE_IMAGE_ASPECT = true;
/**
* Defines the default value for the imageBasePath in all GDI canvases.
* Default is an empty string.
*/
public static String DEFAULT_IMAGEBASEPATH = "";
/**
* Defines the base path for images with relative paths. Trailing slash
* is required. Default value is DEFAULT_IMAGEBASEPATH.
*/
protected String imageBasePath = DEFAULT_IMAGEBASEPATH;
/**
* Specifies the current translation. Default is (0,0).
*/
protected mxPoint translate = new mxPoint();
/**
* Specifies the current scale. Default is 1.
*/
protected double scale = 1;
/**
* Specifies whether labels should be painted. Default is true.
*/
protected boolean drawLabels = true;
/**
* Cache for images.
*/
protected Hashtable<String, BufferedImage> imageCache = new Hashtable<String, BufferedImage>();
/**
* Sets the current translate.
*/
public void setTranslate(double dx, double dy)
{
translate = new mxPoint(dx, dy);
}
/**
* Returns the current translate.
*/
public mxPoint getTranslate()
{
return translate;
}
/**
*
*/
public void setScale(double scale)
{
this.scale = scale;
}
/**
*
*/
public double getScale()
{
return scale;
}
/**
*
*/
public void setDrawLabels(boolean drawLabels)
{
this.drawLabels = drawLabels;
}
/**
*
*/
public String getImageBasePath()
{
return imageBasePath;
}
/**
*
*/
public void setImageBasePath(String imageBasePath)
{
this.imageBasePath = imageBasePath;
}
/**
*
*/
public boolean isDrawLabels()
{
return drawLabels;
}
/**
* Returns an image instance for the given URL. If the URL has
* been loaded before than an instance of the same instance is
* returned as in the previous call.
*/
public BufferedImage loadImage(String image)
{
BufferedImage img = imageCache.get(image);
if (img == null)
{
img = mxUtils.loadImage(image);
if (img != null)
{
imageCache.put(image, img);
}
}
return img;
}
/**
*
*/
public void flushImageCache()
{
imageCache.clear();
}
/**
* Gets the image path from the given style. If the path is relative (does
* not start with a slash) then it is appended to the imageBasePath.
*/
public String getImageForStyle(Map<String, Object> style)
{
String filename = mxUtils.getString(style, mxConstants.STYLE_IMAGE);
if (filename != null && !filename.startsWith("/") && !filename.startsWith("file:/"))
{
filename = imageBasePath + filename;
}
return filename;
}
}

View file

@ -0,0 +1,626 @@
/**
* Copyright (c) 2007-2012, JGraph Ltd
*/
package com.mxgraph.canvas;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.CellRendererPane;
import com.mxgraph.shape.mxActorShape;
import com.mxgraph.shape.mxArrowShape;
import com.mxgraph.shape.mxCloudShape;
import com.mxgraph.shape.mxConnectorShape;
import com.mxgraph.shape.mxCurveShape;
import com.mxgraph.shape.mxCylinderShape;
import com.mxgraph.shape.mxDefaultTextShape;
import com.mxgraph.shape.mxDoubleEllipseShape;
import com.mxgraph.shape.mxDoubleRectangleShape;
import com.mxgraph.shape.mxEllipseShape;
import com.mxgraph.shape.mxHexagonShape;
import com.mxgraph.shape.mxHtmlTextShape;
import com.mxgraph.shape.mxIShape;
import com.mxgraph.shape.mxITextShape;
import com.mxgraph.shape.mxImageShape;
import com.mxgraph.shape.mxLabelShape;
import com.mxgraph.shape.mxLineShape;
import com.mxgraph.shape.mxRectangleShape;
import com.mxgraph.shape.mxRhombusShape;
import com.mxgraph.shape.mxStencilRegistry;
import com.mxgraph.shape.mxSwimlaneShape;
import com.mxgraph.shape.mxTriangleShape;
import com.mxgraph.swing.util.mxSwingConstants;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
/**
* An implementation of a canvas that uses Graphics2D for painting.
*/
public class mxGraphics2DCanvas extends mxBasicCanvas
{
private static final Logger log = Logger.getLogger(mxGraphics2DCanvas.class.getName());
/**
*
*/
public static final String TEXT_SHAPE_DEFAULT = "default";
/**
*
*/
public static final String TEXT_SHAPE_HTML = "html";
/**
* Specifies the image scaling quality. Default is Image.SCALE_SMOOTH.
*/
public static int IMAGE_SCALING = Image.SCALE_SMOOTH;
/**
* Maps from names to mxIVertexShape instances.
*/
protected static Map<String, mxIShape> shapes = new HashMap<String, mxIShape>();
/**
* Maps from names to mxITextShape instances. There are currently three different
* hardcoded text shapes available here: default, html and wrapped.
*/
protected static Map<String, mxITextShape> textShapes = new HashMap<String, mxITextShape>();
/**
* Static initializer.
*/
static
{
putShape(mxConstants.SHAPE_ACTOR, new mxActorShape());
putShape(mxConstants.SHAPE_ARROW, new mxArrowShape());
putShape(mxConstants.SHAPE_CLOUD, new mxCloudShape());
putShape(mxConstants.SHAPE_CONNECTOR, new mxConnectorShape());
putShape(mxConstants.SHAPE_CYLINDER, new mxCylinderShape());
putShape(mxConstants.SHAPE_CURVE, new mxCurveShape());
putShape(mxConstants.SHAPE_DOUBLE_RECTANGLE, new mxDoubleRectangleShape());
putShape(mxConstants.SHAPE_DOUBLE_ELLIPSE, new mxDoubleEllipseShape());
putShape(mxConstants.SHAPE_ELLIPSE, new mxEllipseShape());
putShape(mxConstants.SHAPE_HEXAGON, new mxHexagonShape());
putShape(mxConstants.SHAPE_IMAGE, new mxImageShape());
putShape(mxConstants.SHAPE_LABEL, new mxLabelShape());
putShape(mxConstants.SHAPE_LINE, new mxLineShape());
putShape(mxConstants.SHAPE_RECTANGLE, new mxRectangleShape());
putShape(mxConstants.SHAPE_RHOMBUS, new mxRhombusShape());
putShape(mxConstants.SHAPE_SWIMLANE, new mxSwimlaneShape());
putShape(mxConstants.SHAPE_TRIANGLE, new mxTriangleShape());
putTextShape(TEXT_SHAPE_DEFAULT, new mxDefaultTextShape());
putTextShape(TEXT_SHAPE_HTML, new mxHtmlTextShape());
}
/**
* Optional renderer pane to be used for HTML label rendering.
*/
protected CellRendererPane rendererPane;
/**
* Global graphics handle to the image.
*/
protected Graphics2D g;
/**
* Constructs a new graphics canvas with an empty graphics object.
*/
public mxGraphics2DCanvas()
{
this(null);
}
/**
* Constructs a new graphics canvas for the given graphics object.
*/
public mxGraphics2DCanvas(Graphics2D g)
{
this.g = g;
// Initializes the cell renderer pane for drawing HTML markup
try
{
rendererPane = new CellRendererPane();
}
catch (Exception e)
{
log.log(Level.WARNING, "Failed to initialize renderer pane", e);
}
}
/**
*
*/
public static void putShape(String name, mxIShape shape)
{
shapes.put(name, shape);
}
/**
*
*/
public mxIShape getShape(Map<String, Object> style)
{
String name = mxUtils.getString(style, mxConstants.STYLE_SHAPE, null);
mxIShape shape = shapes.get(name);
if (shape == null && name != null)
{
shape = mxStencilRegistry.getStencil(name);
}
return shape;
}
/**
*
*/
public static void putTextShape(String name, mxITextShape shape)
{
textShapes.put(name, shape);
}
/**
*
*/
public mxITextShape getTextShape(Map<String, Object> style, boolean html)
{
String name;
if (html)
{
name = TEXT_SHAPE_HTML;
}
else
{
name = TEXT_SHAPE_DEFAULT;
}
return textShapes.get(name);
}
/**
*
*/
public CellRendererPane getRendererPane()
{
return rendererPane;
}
/**
* Returns the graphics object for this canvas.
*/
public Graphics2D getGraphics()
{
return g;
}
/**
* Sets the graphics object for this canvas.
*/
public void setGraphics(Graphics2D g)
{
this.g = g;
}
/*
* (non-Javadoc)
* @see com.mxgraph.canvas.mxICanvas#drawCell()
*/
public Object drawCell(mxCellState state)
{
Map<String, Object> style = state.getStyle();
mxIShape shape = getShape(style);
if (g != null && shape != null)
{
// Creates a temporary graphics instance for drawing this shape
float opacity = mxUtils.getFloat(style, mxConstants.STYLE_OPACITY,
100);
Graphics2D previousGraphics = g;
g = createTemporaryGraphics(style, opacity, state);
// Paints the shape and restores the graphics object
shape.paintShape(this, state);
g.dispose();
g = previousGraphics;
}
return shape;
}
/*
* (non-Javadoc)
* @see com.mxgraph.canvas.mxICanvas#drawLabel()
*/
public Object drawLabel(String text, mxCellState state, boolean html)
{
Map<String, Object> style = state.getStyle();
mxITextShape shape = getTextShape(style, html);
if (g != null && shape != null && drawLabels && text != null
&& text.length() > 0)
{
// Creates a temporary graphics instance for drawing this shape
float opacity = mxUtils.getFloat(style,
mxConstants.STYLE_TEXT_OPACITY, 100);
Graphics2D previousGraphics = g;
g = createTemporaryGraphics(style, opacity, null);
// Draws the label background and border
Color bg = mxUtils.getColor(style,
mxConstants.STYLE_LABEL_BACKGROUNDCOLOR);
Color border = mxUtils.getColor(style,
mxConstants.STYLE_LABEL_BORDERCOLOR);
paintRectangle(state.getLabelBounds().getRectangle(), bg, border);
// Paints the label and restores the graphics object
shape.paintShape(this, text, state, style);
g.dispose();
g = previousGraphics;
}
return shape;
}
/**
*
*/
public void drawImage(Rectangle bounds, String imageUrl)
{
drawImage(bounds, imageUrl, PRESERVE_IMAGE_ASPECT, false, false);
}
/**
*
*/
public void drawImage(Rectangle bounds, String imageUrl,
boolean preserveAspect, boolean flipH, boolean flipV)
{
if (imageUrl != null && bounds.getWidth() > 0 && bounds.getHeight() > 0)
{
Image img = loadImage(imageUrl);
if (img != null)
{
int w, h;
int x = bounds.x;
int y = bounds.y;
Dimension size = getImageSize(img);
if (preserveAspect)
{
double s = Math.min(bounds.width / (double) size.width,
bounds.height / (double) size.height);
w = (int) (size.width * s);
h = (int) (size.height * s);
x += (bounds.width - w) / 2;
y += (bounds.height - h) / 2;
}
else
{
w = bounds.width;
h = bounds.height;
}
Image scaledImage = (w == size.width && h == size.height) ? img
: img.getScaledInstance(w, h, IMAGE_SCALING);
if (scaledImage != null)
{
AffineTransform af = null;
if (flipH || flipV)
{
af = g.getTransform();
int sx = 1;
int sy = 1;
int dx = 0;
int dy = 0;
if (flipH)
{
sx = -1;
dx = -w - 2 * x;
}
if (flipV)
{
sy = -1;
dy = -h - 2 * y;
}
g.scale(sx, sy);
g.translate(dx, dy);
}
drawImageImpl(scaledImage, x, y);
// Restores the previous transform
if (af != null)
{
g.setTransform(af);
}
}
}
}
}
/**
* Implements the actual graphics call.
*/
protected void drawImageImpl(Image image, int x, int y)
{
g.drawImage(image, x, y, null);
}
/**
* Returns the size for the given image.
*/
protected Dimension getImageSize(Image image)
{
return new Dimension(image.getWidth(null), image.getHeight(null));
}
/**
*
*/
public void paintPolyline(mxPoint[] points, boolean rounded)
{
if (points != null && points.length > 1)
{
mxPoint pt = points[0];
mxPoint pe = points[points.length - 1];
double arcSize = mxConstants.LINE_ARCSIZE * scale;
GeneralPath path = new GeneralPath();
path.moveTo((float) pt.getX(), (float) pt.getY());
// Draws the line segments
for (int i = 1; i < points.length - 1; i++)
{
mxPoint tmp = points[i];
double dx = pt.getX() - tmp.getX();
double dy = pt.getY() - tmp.getY();
if ((rounded && i < points.length - 1) && (dx != 0 || dy != 0))
{
// Draws a line from the last point to the current
// point with a spacing of size off the current point
// into direction of the last point
double dist = Math.sqrt(dx * dx + dy * dy);
double nx1 = dx * Math.min(arcSize, dist / 2) / dist;
double ny1 = dy * Math.min(arcSize, dist / 2) / dist;
double x1 = tmp.getX() + nx1;
double y1 = tmp.getY() + ny1;
path.lineTo((float) x1, (float) y1);
// Draws a curve from the last point to the current
// point with a spacing of size off the current point
// into direction of the next point
mxPoint next = points[i + 1];
// Uses next non-overlapping point
while (i < points.length - 2 && Math.round(next.getX() - tmp.getX()) == 0 && Math.round(next.getY() - tmp.getY()) == 0)
{
next = points[i + 2];
i++;
}
dx = next.getX() - tmp.getX();
dy = next.getY() - tmp.getY();
dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
double nx2 = dx * Math.min(arcSize, dist / 2) / dist;
double ny2 = dy * Math.min(arcSize, dist / 2) / dist;
double x2 = tmp.getX() + nx2;
double y2 = tmp.getY() + ny2;
path.quadTo((float) tmp.getX(), (float) tmp.getY(),
(float) x2, (float) y2);
tmp = new mxPoint(x2, y2);
}
else
{
path.lineTo((float) tmp.getX(), (float) tmp.getY());
}
pt = tmp;
}
path.lineTo((float) pe.getX(), (float) pe.getY());
g.draw(path);
}
}
/**
*
*/
public void paintRectangle(Rectangle bounds, Color background, Color border)
{
if (background != null)
{
g.setColor(background);
fillShape(bounds);
}
if (border != null)
{
g.setColor(border);
g.draw(bounds);
}
}
/**
*
*/
public void fillShape(Shape shape)
{
fillShape(shape, false);
}
/**
*
*/
public void fillShape(Shape shape, boolean shadow)
{
int shadowOffsetX = (shadow) ? mxConstants.SHADOW_OFFSETX : 0;
int shadowOffsetY = (shadow) ? mxConstants.SHADOW_OFFSETY : 0;
if (shadow)
{
// Saves the state and configures the graphics object
Paint p = g.getPaint();
Color previousColor = g.getColor();
g.setColor(mxSwingConstants.SHADOW_COLOR);
g.translate(shadowOffsetX, shadowOffsetY);
// Paints the shadow
fillShape(shape, false);
// Restores the state of the graphics object
g.translate(-shadowOffsetX, -shadowOffsetY);
g.setColor(previousColor);
g.setPaint(p);
}
g.fill(shape);
}
/**
*
*/
public Stroke createStroke(Map<String, Object> style)
{
double width = mxUtils
.getFloat(style, mxConstants.STYLE_STROKEWIDTH, 1) * scale;
boolean dashed = mxUtils.isTrue(style, mxConstants.STYLE_DASHED);
if (dashed)
{
float[] dashPattern = mxUtils.getFloatArray(style,
mxConstants.STYLE_DASH_PATTERN,
mxConstants.DEFAULT_DASHED_PATTERN, " ");
float[] scaledDashPattern = new float[dashPattern.length];
for (int i = 0; i < dashPattern.length; i++)
{
scaledDashPattern[i] = (float) (dashPattern[i] * scale * width);
}
return new BasicStroke((float) width, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER, 10.0f, scaledDashPattern, 0.0f);
}
else
{
return new BasicStroke((float) width);
}
}
/**
*
*/
public Paint createFillPaint(mxRectangle bounds, Map<String, Object> style)
{
Color fillColor = mxUtils.getColor(style, mxConstants.STYLE_FILLCOLOR);
Paint fillPaint = null;
if (fillColor != null)
{
Color gradientColor = mxUtils.getColor(style,
mxConstants.STYLE_GRADIENTCOLOR);
if (gradientColor != null)
{
String gradientDirection = mxUtils.getString(style,
mxConstants.STYLE_GRADIENT_DIRECTION);
float x1 = (float) bounds.getX();
float y1 = (float) bounds.getY();
float x2 = (float) bounds.getX();
float y2 = (float) bounds.getY();
if (gradientDirection == null
|| gradientDirection
.equals(mxConstants.DIRECTION_SOUTH))
{
y2 = (float) (bounds.getY() + bounds.getHeight());
}
else if (gradientDirection.equals(mxConstants.DIRECTION_EAST))
{
x2 = (float) (bounds.getX() + bounds.getWidth());
}
else if (gradientDirection.equals(mxConstants.DIRECTION_NORTH))
{
y1 = (float) (bounds.getY() + bounds.getHeight());
}
else if (gradientDirection.equals(mxConstants.DIRECTION_WEST))
{
x1 = (float) (bounds.getX() + bounds.getWidth());
}
fillPaint = new GradientPaint(x1, y1, fillColor, x2, y2,
gradientColor, true);
}
}
return fillPaint;
}
/**
*
*/
public Graphics2D createTemporaryGraphics(Map<String, Object> style,
float opacity, mxRectangle bounds)
{
Graphics2D temporaryGraphics = (Graphics2D) g.create();
// Applies the default translate
temporaryGraphics.translate(translate.getX(), translate.getY());
// Applies the rotation on the graphics object
if (bounds != null)
{
double rotation = mxUtils.getDouble(style,
mxConstants.STYLE_ROTATION, 0);
if (rotation != 0)
{
temporaryGraphics.rotate(Math.toRadians(rotation),
bounds.getCenterX(), bounds.getCenterY());
}
}
// Applies the opacity to the graphics object
if (opacity != 100)
{
temporaryGraphics.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, opacity / 100));
}
return temporaryGraphics;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,348 @@
/**
* Copyright (c) 2007, Gaudenz Alder
*/
package com.mxgraph.canvas;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
/**
* An implementation of a canvas that uses HTML for painting.
*/
public class mxHtmlCanvas extends mxBasicCanvas
{
/**
* Holds the HTML document that represents the canvas.
*/
protected Document document;
/**
* Constructs a new HTML canvas for the specified dimension and scale.
*/
public mxHtmlCanvas()
{
this(null);
}
/**
* Constructs a new HTML canvas for the specified bounds, scale and
* background color.
*/
public mxHtmlCanvas(Document document)
{
setDocument(document);
}
/**
*
*/
public void appendHtmlElement(Element node)
{
if (document != null)
{
Node body = document.getDocumentElement().getFirstChild()
.getNextSibling();
if (body != null)
{
body.appendChild(node);
}
}
}
/**
*
*/
public void setDocument(Document document)
{
this.document = document;
}
/**
* Returns a reference to the document that represents the canvas.
*
* @return Returns the document.
*/
public Document getDocument()
{
return document;
}
/*
* (non-Javadoc)
* @see com.mxgraph.canvas.mxICanvas#drawCell()
*/
public Object drawCell(mxCellState state)
{
Map<String, Object> style = state.getStyle();
if (state.getAbsolutePointCount() > 1)
{
List<mxPoint> pts = state.getAbsolutePoints();
// Transpose all points by cloning into a new array
pts = mxUtils.translatePoints(pts, translate.getX(), translate.getY());
drawLine(pts, style);
}
else
{
int x = (int) (state.getX() + translate.getX());
int y = (int) (state.getY() + translate.getY());
int w = (int) state.getWidth();
int h = (int) state.getHeight();
if (!mxUtils.getString(style, mxConstants.STYLE_SHAPE, "").equals(
mxConstants.SHAPE_SWIMLANE))
{
drawShape(x, y, w, h, style);
}
else
{
int start = (int) Math.round(mxUtils.getInt(style,
mxConstants.STYLE_STARTSIZE,
mxConstants.DEFAULT_STARTSIZE)
* scale);
// Removes some styles to draw the content area
Map<String, Object> cloned = new Hashtable<String, Object>(
style);
cloned.remove(mxConstants.STYLE_FILLCOLOR);
cloned.remove(mxConstants.STYLE_ROUNDED);
if (mxUtils.isTrue(style, mxConstants.STYLE_HORIZONTAL, true))
{
drawShape(x, y, w, start, style);
drawShape(x, y + start, w, h - start, cloned);
}
else
{
drawShape(x, y, start, h, style);
drawShape(x + start, y, w - start, h, cloned);
}
}
}
return null;
}
/*
* (non-Javadoc)
* @see com.mxgraph.canvas.mxICanvas#drawLabel()
*/
public Object drawLabel(String label, mxCellState state, boolean html)
{
mxRectangle bounds = state.getLabelBounds();
if (drawLabels && bounds != null)
{
int x = (int) (bounds.getX() + translate.getY());
int y = (int) (bounds.getY() + translate.getY());
int w = (int) bounds.getWidth();
int h = (int) bounds.getHeight();
Map<String, Object> style = state.getStyle();
return drawText(label, x, y, w, h, style);
}
return null;
}
/**
* Draws the shape specified with the STYLE_SHAPE key in the given style.
*
* @param x X-coordinate of the shape.
* @param y Y-coordinate of the shape.
* @param w Width of the shape.
* @param h Height of the shape.
* @param style Style of the the shape.
*/
public Element drawShape(int x, int y, int w, int h,
Map<String, Object> style)
{
String fillColor = mxUtils
.getString(style, mxConstants.STYLE_FILLCOLOR);
String strokeColor = mxUtils.getString(style,
mxConstants.STYLE_STROKECOLOR);
float strokeWidth = (float) (mxUtils.getFloat(style,
mxConstants.STYLE_STROKEWIDTH, 1) * scale);
// Draws the shape
String shape = mxUtils.getString(style, mxConstants.STYLE_SHAPE);
Element elem = document.createElement("div");
if (shape.equals(mxConstants.SHAPE_LINE))
{
String direction = mxUtils.getString(style,
mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);
if (direction.equals(mxConstants.DIRECTION_EAST)
|| direction.equals(mxConstants.DIRECTION_WEST))
{
y = Math.round(y + h / 2);
h = 1;
}
else
{
x = Math.round(y + w / 2);
w = 1;
}
}
if (mxUtils.isTrue(style, mxConstants.STYLE_SHADOW, false)
&& fillColor != null)
{
Element shadow = (Element) elem.cloneNode(true);
String s = "overflow:hidden;position:absolute;" + "left:"
+ String.valueOf(x + mxConstants.SHADOW_OFFSETX) + "px;"
+ "top:" + String.valueOf(y + mxConstants.SHADOW_OFFSETY)
+ "px;" + "width:" + String.valueOf(w) + "px;" + "height:"
+ String.valueOf(h) + "px;background:"
+ mxConstants.W3C_SHADOWCOLOR
+ ";border-style:solid;border-color:"
+ mxConstants.W3C_SHADOWCOLOR + ";border-width:"
+ String.valueOf(Math.round(strokeWidth)) + ";";
shadow.setAttribute("style", s);
appendHtmlElement(shadow);
}
if (shape.equals(mxConstants.SHAPE_IMAGE))
{
String img = getImageForStyle(style);
if (img != null)
{
elem = document.createElement("img");
elem.setAttribute("border", "0");
elem.setAttribute("src", img);
}
}
// TODO: Draw other shapes. eg. SHAPE_LINE here
String s = "overflow:hidden;position:absolute;" + "left:"
+ String.valueOf(x) + "px;" + "top:" + String.valueOf(y)
+ "px;" + "width:" + String.valueOf(w) + "px;" + "height:"
+ String.valueOf(h) + "px;background:" + fillColor + ";"
+ ";border-style:solid;border-color:" + strokeColor
+ ";border-width:" + String.valueOf(Math.round(strokeWidth))
+ ";";
elem.setAttribute("style", s);
appendHtmlElement(elem);
return elem;
}
/**
* Draws the given lines as segments between all points of the given list
* of mxPoints.
*
* @param pts List of points that define the line.
* @param style Style to be used for painting the line.
*/
public void drawLine(List<mxPoint> pts, Map<String, Object> style)
{
String strokeColor = mxUtils.getString(style,
mxConstants.STYLE_STROKECOLOR);
int strokeWidth = (int) (mxUtils.getInt(style,
mxConstants.STYLE_STROKEWIDTH, 1) * scale);
if (strokeColor != null && strokeWidth > 0)
{
mxPoint last = pts.get(0);
for (int i = 1; i < pts.size(); i++)
{
mxPoint pt = pts.get(i);
drawSegment((int) last.getX(), (int) last.getY(), (int) pt
.getX(), (int) pt.getY(), strokeColor, strokeWidth);
last = pt;
}
}
}
/**
* Draws the specified segment of a line.
*
* @param x0 X-coordinate of the start point.
* @param y0 Y-coordinate of the start point.
* @param x1 X-coordinate of the end point.
* @param y1 Y-coordinate of the end point.
* @param strokeColor Color of the stroke to be painted.
* @param strokeWidth Width of the stroke to be painted.
*/
protected void drawSegment(int x0, int y0, int x1, int y1,
String strokeColor, int strokeWidth)
{
int tmpX = Math.min(x0, x1);
int tmpY = Math.min(y0, y1);
int width = Math.max(x0, x1) - tmpX;
int height = Math.max(y0, y1) - tmpY;
x0 = tmpX;
y0 = tmpY;
if (width == 0 || height == 0)
{
String s = "overflow:hidden;position:absolute;" + "left:"
+ String.valueOf(x0) + "px;" + "top:" + String.valueOf(y0)
+ "px;" + "width:" + String.valueOf(width) + "px;"
+ "height:" + String.valueOf(height) + "px;"
+ "border-color:" + strokeColor + ";"
+ "border-style:solid;" + "border-width:1 1 0 0px;";
Element elem = document.createElement("div");
elem.setAttribute("style", s);
appendHtmlElement(elem);
}
else
{
int x = x0 + (x1 - x0) / 2;
drawSegment(x0, y0, x, y0, strokeColor, strokeWidth);
drawSegment(x, y0, x, y1, strokeColor, strokeWidth);
drawSegment(x, y1, x1, y1, strokeColor, strokeWidth);
}
}
/**
* Draws the specified text either using drawHtmlString or using drawString.
*
* @param text Text to be painted.
* @param x X-coordinate of the text.
* @param y Y-coordinate of the text.
* @param w Width of the text.
* @param h Height of the text.
* @param style Style to be used for painting the text.
*/
public Element drawText(String text, int x, int y, int w, int h,
Map<String, Object> style)
{
Element table = mxUtils.createTable(document, text, x, y, w, h, scale,
style);
appendHtmlElement(table);
return table;
}
}

View file

@ -0,0 +1,55 @@
/**
* Copyright (c) 2007-2010, Gaudenz Alder, David Benson
*/
package com.mxgraph.canvas;
import com.mxgraph.util.mxPoint;
import com.mxgraph.view.mxCellState;
/**
* Defines the requirements for a canvas that paints the vertices and edges of
* a graph.
*/
public interface mxICanvas
{
/**
* Sets the translation for the following drawing requests.
*/
void setTranslate(double x, double y);
/**
* Returns the current translation.
*
* @return Returns the current translation.
*/
mxPoint getTranslate();
/**
* Sets the scale for the following drawing requests.
*/
void setScale(double scale);
/**
* Returns the scale.
*/
double getScale();
/**
* Draws the given cell.
*
* @param state State of the cell to be painted.
* @return Object that represents the cell.
*/
Object drawCell(mxCellState state);
/**
* Draws the given label.
*
* @param text String that represents the label.
* @param state State of the cell whose label is to be painted.
* @param html Specifies if the label contains HTML markup.
* @return Object that represents the label.
*/
Object drawLabel(String text, mxCellState state, boolean html);
}

View file

@ -0,0 +1,368 @@
package com.mxgraph.canvas;
import com.mxgraph.util.mxConstants;
/**
* Requirements for implementing technologies:
*
* - Path rendering (move, line, quad, curve, arc)
* - Images, flip v/h, aspect, alpha (PNG, JPG, GIF)
* - Linear gradients (in all four directions)
* - Transparency, fill and stroke
* - Rotation, flip v/h
* - Font rendering
* - Dash patterns
* - Clipping by path (not just rectangle)
* - Alpha gradients (for glass effect)
* - Encode result as image (PNG, JPG)
*/
public interface mxICanvas2D
{
/**
* Saves the current state of the canvas.
*/
void save();
/**
* Restores the previous state of the canvas.
*/
void restore();
/**
* Uniformaly scales the canvas by the given amount.
*
* @param value The new scale value.
*/
void scale(double value);
/**
* Translates the canvas by the given amount.
*
* @param dx X-coordinate of the translation.
* @param dy Y-coordinate of the translation.
*/
void translate(double dx, double dy);
/**
* Rotates the canvas by the given angle around the given center. This
* method may add rendering overhead and should be used with care.
*
* @param theta Rotation angle in degrees (0 - 360).
* @param flipH Specifies if drawing should be flipped horizontally.
* @param flipV Specifies if drawing should be flipped vertically.
* @param cx X-coordinate of the center point.
* @param cy Y-coordinate of the center point.
*/
void rotate(double theta, boolean flipH, boolean flipV, double cx, double cy);
/**
* Sets the stroke width. This should default to 1 if unset.
*
* @param value Width of the stroke. The value should be multiplied by the
* current scale.
*/
void setStrokeWidth(double value);
/**
* Sets the stroke color. This should default to {@link mxConstants#NONE}
* if unset.
*
* @param value Hex representation of the color or {@link mxConstants#NONE}.
*/
void setStrokeColor(String value);
/**
* Sets the dashed state. This should default to false if unset.
*
* @param value Boolean representing the dashed state.
*/
void setDashed(boolean value);
/**
* Sets the dashed state. This should default to false if unset.
*
* @param value Boolean representing the dashed state.
*/
void setDashed(boolean value, boolean fixDash);
/**
* Sets the dash pattern. This should default to "3 3" if unset.
*
* @param value Space separated list of floats representing the dash
* pattern. The value should be multiplied by the current scale.
*/
void setDashPattern(String value);
/**
* Sets the linecap. This should default to "flat" if unset.
*
* @param value "flat", "square" or "round".
*/
void setLineCap(String value);
/**
* Sets the linejoin. This should default to "miter" if unset.
*
* @param value "miter", "round" or "bevel".
*/
void setLineJoin(String value);
/**
* Sets the miterlimit. This should default to 10 if unset.
*
* @param value
*/
void setMiterLimit(double value);
/**
* Default value {@link mxConstants#DEFAULT_FONTSIZE}.
*
* @param value
*/
void setFontSize(double value);
/**
* Default value "#000000".
*
* @param value Hex representation of the color or {@link mxConstants#NONE}.
*/
void setFontColor(String value);
/**
* Default value {@link mxConstants#DEFAULT_FONTFAMILY}.
*
* @param value
*/
void setFontFamily(String value);
/**
* Default value 0. See {@link mxConstants#STYLE_FONTSTYLE}.
*
* @param value
*/
void setFontStyle(int value);
/**
* Default value "#000000".
*
* @param value Hex representation of the color or {@link mxConstants#NONE}.
*/
void setFontBackgroundColor(String value);
/**
* Default value "#000000".
*
* @param value Hex representation of the color or {@link mxConstants#NONE}.
*/
void setFontBorderColor(String value);
/**
* Default value 1. This method may add rendering overhead and should be
* used with care.
*
* @param value
*/
void setAlpha(double value);
/**
* Default value 1. This method may add rendering overhead and should be
* used with care.
*
* @param value
*/
void setFillAlpha(double value);
/**
* Default value 1. This method may add rendering overhead and should be
* used with care.
*
* @param value
*/
void setStrokeAlpha(double value);
/**
* Default value {@link mxConstants#NONE}.
*
* @param value Hex representation of the color or {@link mxConstants#NONE}.
*/
void setFillColor(String value);
/**
* Prepares the canvas to draw a gradient.
*
* @param color1
* @param color2
* @param x
* @param y
* @param w
* @param h
* @param direction Direction may be null. Use default value
* {@link mxConstants#DIRECTION_SOUTH}.
*/
void setGradient(String color1, String color2, double x, double y,
double w, double h, String direction, double alpha1, double alpha2);
/**
* Enables or disables the painting of shadows.
*
* @param enabled Whether the shadow should be enabled.
*/
void setShadow(boolean enabled);
/**
* Default value {@link mxConstants#NONE}.
*
* @param value Hex representation of the color or {@link mxConstants#NONE}.
*/
void setShadowColor(String value);
/**
* Default value {@link mxConstants#NONE}.
*
* @param value Hex representation of the color or {@link mxConstants#NONE}.
*/
void setShadowAlpha(double value);
/**
* Default value {@link mxConstants#NONE}.
*
* @param value Hex representation of the color or {@link mxConstants#NONE}.
*/
void setShadowOffset(double dx, double dy);
/**
* Next fill or stroke should draw a rectangle.
*
* @param x
* @param y
* @param w
* @param h
*/
void rect(double x, double y, double w, double h);
/**
*
* Next fill or stroke should draw a round rectangle.
*
* @param x
* @param y
* @param w
* @param h
* @param dx
* @param dy
*/
void roundrect(double x, double y, double w, double h, double dx, double dy);
/**
*
* Next fill or stroke should draw an ellipse.
*
* @param x
* @param y
* @param w
* @param h
*/
void ellipse(double x, double y, double w, double h);
/**
* Draws the given image.
*
* @param x
* @param y
* @param w
* @param h
* @param src
* @param aspect
* @param flipH
* @param flipV
*/
void image(double x, double y, double w, double h, String src,
boolean aspect, boolean flipH, boolean flipV);
/**
* Draws the given string. Possible values for format are empty string for
* plain text and html for HTML markup.
*
* @param x
* @param y
* @param w
* @param h
* @param str
* @param align
* @param valign
* @param wrap
* @param format
* @param overflow
* @param clip
* @param rotation
* @param dir
*/
void text(double x, double y, double w, double h, String str, String align,
String valign, boolean wrap, String format, String overflow,
boolean clip, double rotation, String dir);
/**
* Begins a new path.
*/
void begin();
/**
* Moves to the given path.
*
* @param x
* @param y
*/
void moveTo(double x, double y);
/**
* Draws a line to the given path.
*
* @param x
* @param y
*/
void lineTo(double x, double y);
/**
* Draws a quadratic curve to the given point.
*
* @param x1
* @param y1
* @param x2
* @param y2
*/
void quadTo(double x1, double y1, double x2, double y2);
/**
* Draws a bezier curve to the given point.
*
* @param x1
* @param y1
* @param x2
* @param y2
* @param x3
* @param y3
*/
void curveTo(double x1, double y1, double x2, double y2, double x3,
double y3);
/**
* Closes the current path.
*/
void close();
/**
* Paints the outline of the current path.
*/
void stroke();
/**
* Fills the current path.
*/
void fill();
/**
* Fills and paints the outline of the current path.
*/
void fillAndStroke();
}

View file

@ -0,0 +1,151 @@
/**
* Copyright (c) 2007-2010, Gaudenz Alder, David Benson
*/
package com.mxgraph.canvas;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
/**
* An implementation of a canvas that uses Graphics2D for painting. To use an
* image canvas for an existing graphics canvas and create an image the
* following code is used:
*
* <code>BufferedImage image = mxCellRenderer.createBufferedImage(graph, cells, 1, Color.white, true, null, canvas);</code>
*/
public class mxImageCanvas implements mxICanvas
{
/**
*
*/
protected mxGraphics2DCanvas canvas;
/**
*
*/
protected Graphics2D previousGraphics;
/**
*
*/
protected BufferedImage image;
/**
*
*/
public mxImageCanvas(mxGraphics2DCanvas canvas, int width, int height,
Color background, boolean antiAlias)
{
this(canvas, width, height, background, antiAlias, true);
}
/**
*
*/
public mxImageCanvas(mxGraphics2DCanvas canvas, int width, int height,
Color background, boolean antiAlias, boolean textAntiAlias)
{
this.canvas = canvas;
previousGraphics = canvas.getGraphics();
image = mxUtils.createBufferedImage(width, height, background);
if (image != null)
{
Graphics2D g = image.createGraphics();
mxUtils.setAntiAlias(g, antiAlias, textAntiAlias);
canvas.setGraphics(g);
}
}
/**
*
*/
public mxGraphics2DCanvas getGraphicsCanvas()
{
return canvas;
}
/**
*
*/
public BufferedImage getImage()
{
return image;
}
/**
*
*/
public Object drawCell(mxCellState state)
{
return canvas.drawCell(state);
}
/**
*
*/
public Object drawLabel(String label, mxCellState state, boolean html)
{
return canvas.drawLabel(label, state, html);
}
/**
*
*/
public double getScale()
{
return canvas.getScale();
}
/**
*
*/
public mxPoint getTranslate()
{
return canvas.getTranslate();
}
/**
*
*/
public void setScale(double scale)
{
canvas.setScale(scale);
}
/**
*
*/
public void setTranslate(double dx, double dy)
{
canvas.setTranslate(dx, dy);
}
/**
*
*/
public BufferedImage destroy()
{
BufferedImage tmp = image;
if (canvas.getGraphics() != null)
{
canvas.getGraphics().dispose();
}
canvas.setGraphics(previousGraphics);
previousGraphics = null;
canvas = null;
image = null;
return tmp;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,599 @@
/**
* Copyright (c) 2007, Gaudenz Alder
*/
package com.mxgraph.canvas;
import java.awt.Rectangle;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
/**
* An implementation of a canvas that uses VML for painting.
*/
public class mxVmlCanvas extends mxBasicCanvas
{
/**
* Holds the HTML document that represents the canvas.
*/
protected Document document;
/**
* Constructs a new VML canvas for the specified dimension and scale.
*/
public mxVmlCanvas()
{
this(null);
}
/**
* Constructs a new VML canvas for the specified bounds, scale and
* background color.
*/
public mxVmlCanvas(Document document)
{
setDocument(document);
}
/**
*
*/
public void setDocument(Document document)
{
this.document = document;
}
/**
* Returns a reference to the document that represents the canvas.
*
* @return Returns the document.
*/
public Document getDocument()
{
return document;
}
/**
*
*/
public void appendVmlElement(Element node)
{
if (document != null)
{
Node body = document.getDocumentElement().getFirstChild()
.getNextSibling();
if (body != null)
{
body.appendChild(node);
}
}
}
/* (non-Javadoc)
* @see com.mxgraph.canvas.mxICanvas#drawCell()
*/
public Object drawCell(mxCellState state)
{
Map<String, Object> style = state.getStyle();
Element elem = null;
if (state.getAbsolutePointCount() > 1)
{
List<mxPoint> pts = state.getAbsolutePoints();
// Transpose all points by cloning into a new array
pts = mxUtils.translatePoints(pts, translate.getX(), translate.getY());
// Draws the line
elem = drawLine(pts, style);
Element strokeNode = document.createElement("v:stroke");
// Draws the markers
String start = mxUtils.getString(style,
mxConstants.STYLE_STARTARROW);
String end = mxUtils.getString(style, mxConstants.STYLE_ENDARROW);
if (start != null || end != null)
{
if (start != null)
{
strokeNode.setAttribute("startarrow", start);
String startWidth = "medium";
String startLength = "medium";
double startSize = mxUtils.getFloat(style,
mxConstants.STYLE_STARTSIZE,
mxConstants.DEFAULT_MARKERSIZE)
* scale;
if (startSize < 6)
{
startWidth = "narrow";
startLength = "short";
}
else if (startSize > 10)
{
startWidth = "wide";
startLength = "long";
}
strokeNode.setAttribute("startarrowwidth", startWidth);
strokeNode.setAttribute("startarrowlength", startLength);
}
if (end != null)
{
strokeNode.setAttribute("endarrow", end);
String endWidth = "medium";
String endLength = "medium";
double endSize = mxUtils.getFloat(style,
mxConstants.STYLE_ENDSIZE,
mxConstants.DEFAULT_MARKERSIZE)
* scale;
if (endSize < 6)
{
endWidth = "narrow";
endLength = "short";
}
else if (endSize > 10)
{
endWidth = "wide";
endLength = "long";
}
strokeNode.setAttribute("endarrowwidth", endWidth);
strokeNode.setAttribute("endarrowlength", endLength);
}
}
if (mxUtils.isTrue(style, mxConstants.STYLE_DASHED))
{
strokeNode.setAttribute("dashstyle", "2 2");
}
elem.appendChild(strokeNode);
}
else
{
int x = (int) (state.getX() + translate.getX());
int y = (int) (state.getY() + translate.getY());
int w = (int) state.getWidth();
int h = (int) state.getHeight();
if (!mxUtils.getString(style, mxConstants.STYLE_SHAPE, "").equals(
mxConstants.SHAPE_SWIMLANE))
{
elem = drawShape(x, y, w, h, style);
if (mxUtils.isTrue(style, mxConstants.STYLE_DASHED))
{
Element strokeNode = document.createElement("v:stroke");
strokeNode.setAttribute("dashstyle", "2 2");
elem.appendChild(strokeNode);
}
}
else
{
int start = (int) Math.round(mxUtils.getInt(style,
mxConstants.STYLE_STARTSIZE,
mxConstants.DEFAULT_STARTSIZE)
* scale);
// Removes some styles to draw the content area
Map<String, Object> cloned = new Hashtable<String, Object>(
style);
cloned.remove(mxConstants.STYLE_FILLCOLOR);
cloned.remove(mxConstants.STYLE_ROUNDED);
if (mxUtils.isTrue(style, mxConstants.STYLE_HORIZONTAL, true))
{
elem = drawShape(x, y, w, start, style);
drawShape(x, y + start, w, h - start, cloned);
}
else
{
elem = drawShape(x, y, start, h, style);
drawShape(x + start, y, w - start, h, cloned);
}
}
}
return elem;
}
/*
* (non-Javadoc)
* @see com.mxgraph.canvas.mxICanvas#drawLabel()
*/
public Object drawLabel(String label, mxCellState state, boolean html)
{
mxRectangle bounds = state.getLabelBounds();
if (drawLabels && bounds != null)
{
int x = (int) (bounds.getX() + translate.getX());
int y = (int) (bounds.getY() + translate.getY());
int w = (int) bounds.getWidth();
int h = (int) bounds.getHeight();
Map<String, Object> style = state.getStyle();
return drawText(label, x, y, w, h, style);
}
return null;
}
/**
* Draws the shape specified with the STYLE_SHAPE key in the given style.
*
* @param x X-coordinate of the shape.
* @param y Y-coordinate of the shape.
* @param w Width of the shape.
* @param h Height of the shape.
* @param style Style of the the shape.
*/
public Element drawShape(int x, int y, int w, int h,
Map<String, Object> style)
{
String fillColor = mxUtils
.getString(style, mxConstants.STYLE_FILLCOLOR);
String strokeColor = mxUtils.getString(style,
mxConstants.STYLE_STROKECOLOR);
float strokeWidth = (float) (mxUtils.getFloat(style,
mxConstants.STYLE_STROKEWIDTH, 1) * scale);
// Draws the shape
String shape = mxUtils.getString(style, mxConstants.STYLE_SHAPE);
Element elem = null;
if (shape.equals(mxConstants.SHAPE_IMAGE))
{
String img = getImageForStyle(style);
if (img != null)
{
elem = document.createElement("v:img");
elem.setAttribute("src", img);
}
}
else if (shape.equals(mxConstants.SHAPE_LINE))
{
String direction = mxUtils.getString(style,
mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);
String points = null;
if (direction.equals(mxConstants.DIRECTION_EAST)
|| direction.equals(mxConstants.DIRECTION_WEST))
{
int mid = Math.round(h / 2);
points = "m 0 " + mid + " l " + w + " " + mid;
}
else
{
int mid = Math.round(w / 2);
points = "m " + mid + " 0 L " + mid + " " + h;
}
elem = document.createElement("v:shape");
elem.setAttribute("coordsize", w + " " + h);
elem.setAttribute("path", points + " x e");
}
else if (shape.equals(mxConstants.SHAPE_ELLIPSE))
{
elem = document.createElement("v:oval");
}
else if (shape.equals(mxConstants.SHAPE_DOUBLE_ELLIPSE))
{
elem = document.createElement("v:shape");
elem.setAttribute("coordsize", w + " " + h);
int inset = (int) ((3 + strokeWidth) * scale);
String points = "ar 0 0 " + w + " " + h + " 0 " + (h / 2) + " "
+ (w / 2) + " " + (h / 2) + " e ar " + inset + " " + inset
+ " " + (w - inset) + " " + (h - inset) + " 0 " + (h / 2)
+ " " + (w / 2) + " " + (h / 2);
elem.setAttribute("path", points + " x e");
}
else if (shape.equals(mxConstants.SHAPE_RHOMBUS))
{
elem = document.createElement("v:shape");
elem.setAttribute("coordsize", w + " " + h);
String points = "m " + (w / 2) + " 0 l " + w + " " + (h / 2)
+ " l " + (w / 2) + " " + h + " l 0 " + (h / 2);
elem.setAttribute("path", points + " x e");
}
else if (shape.equals(mxConstants.SHAPE_TRIANGLE))
{
elem = document.createElement("v:shape");
elem.setAttribute("coordsize", w + " " + h);
String direction = mxUtils.getString(style,
mxConstants.STYLE_DIRECTION, "");
String points = null;
if (direction.equals(mxConstants.DIRECTION_NORTH))
{
points = "m 0 " + h + " l " + (w / 2) + " 0 " + " l " + w + " "
+ h;
}
else if (direction.equals(mxConstants.DIRECTION_SOUTH))
{
points = "m 0 0 l " + (w / 2) + " " + h + " l " + w + " 0";
}
else if (direction.equals(mxConstants.DIRECTION_WEST))
{
points = "m " + w + " 0 l " + w + " " + (h / 2) + " l " + w
+ " " + h;
}
else
// east
{
points = "m 0 0 l " + w + " " + (h / 2) + " l 0 " + h;
}
elem.setAttribute("path", points + " x e");
}
else if (shape.equals(mxConstants.SHAPE_HEXAGON))
{
elem = document.createElement("v:shape");
elem.setAttribute("coordsize", w + " " + h);
String direction = mxUtils.getString(style,
mxConstants.STYLE_DIRECTION, "");
String points = null;
if (direction.equals(mxConstants.DIRECTION_NORTH)
|| direction.equals(mxConstants.DIRECTION_SOUTH))
{
points = "m " + (int) (0.5 * w) + " 0 l " + w + " "
+ (int) (0.25 * h) + " l " + w + " " + (int) (0.75 * h)
+ " l " + (int) (0.5 * w) + " " + h + " l 0 "
+ (int) (0.75 * h) + " l 0 " + (int) (0.25 * h);
}
else
{
points = "m " + (int) (0.25 * w) + " 0 l " + (int) (0.75 * w)
+ " 0 l " + w + " " + (int) (0.5 * h) + " l "
+ (int) (0.75 * w) + " " + h + " l " + (int) (0.25 * w)
+ " " + h + " l 0 " + (int) (0.5 * h);
}
elem.setAttribute("path", points + " x e");
}
else if (shape.equals(mxConstants.SHAPE_CLOUD))
{
elem = document.createElement("v:shape");
elem.setAttribute("coordsize", w + " " + h);
String points = "m " + (int) (0.25 * w) + " " + (int) (0.25 * h)
+ " c " + (int) (0.05 * w) + " " + (int) (0.25 * h) + " 0 "
+ (int) (0.5 * h) + " " + (int) (0.16 * w) + " "
+ (int) (0.55 * h) + " c 0 " + (int) (0.66 * h) + " "
+ (int) (0.18 * w) + " " + (int) (0.9 * h) + " "
+ (int) (0.31 * w) + " " + (int) (0.8 * h) + " c "
+ (int) (0.4 * w) + " " + (h) + " " + (int) (0.7 * w) + " "
+ (h) + " " + (int) (0.8 * w) + " " + (int) (0.8 * h)
+ " c " + (w) + " " + (int) (0.8 * h) + " " + (w) + " "
+ (int) (0.6 * h) + " " + (int) (0.875 * w) + " "
+ (int) (0.5 * h) + " c " + (w) + " " + (int) (0.3 * h)
+ " " + (int) (0.8 * w) + " " + (int) (0.1 * h) + " "
+ (int) (0.625 * w) + " " + (int) (0.2 * h) + " c "
+ (int) (0.5 * w) + " " + (int) (0.05 * h) + " "
+ (int) (0.3 * w) + " " + (int) (0.05 * h) + " "
+ (int) (0.25 * w) + " " + (int) (0.25 * h);
elem.setAttribute("path", points + " x e");
}
else if (shape.equals(mxConstants.SHAPE_ACTOR))
{
elem = document.createElement("v:shape");
elem.setAttribute("coordsize", w + " " + h);
double width3 = w / 3;
String points = "m 0 " + (h) + " C 0 " + (3 * h / 5) + " 0 "
+ (2 * h / 5) + " " + (w / 2) + " " + (2 * h / 5) + " c "
+ (int) (w / 2 - width3) + " " + (2 * h / 5) + " "
+ (int) (w / 2 - width3) + " 0 " + (w / 2) + " 0 c "
+ (int) (w / 2 + width3) + " 0 " + (int) (w / 2 + width3)
+ " " + (2 * h / 5) + " " + (w / 2) + " " + (2 * h / 5)
+ " c " + (w) + " " + (2 * h / 5) + " " + (w) + " "
+ (3 * h / 5) + " " + (w) + " " + (h);
elem.setAttribute("path", points + " x e");
}
else if (shape.equals(mxConstants.SHAPE_CYLINDER))
{
elem = document.createElement("v:shape");
elem.setAttribute("coordsize", w + " " + h);
double dy = Math.min(40, Math.floor(h / 5));
String points = "m 0 " + (int) (dy) + " C 0 " + (int) (dy / 3)
+ " " + (w) + " " + (int) (dy / 3) + " " + (w) + " "
+ (int) (dy) + " L " + (w) + " " + (int) (h - dy) + " C "
+ (w) + " " + (int) (h + dy / 3) + " 0 "
+ (int) (h + dy / 3) + " 0 " + (int) (h - dy) + " x e"
+ " m 0 " + (int) (dy) + " C 0 " + (int) (2 * dy) + " "
+ (w) + " " + (int) (2 * dy) + " " + (w) + " " + (int) (dy);
elem.setAttribute("path", points + " e");
}
else
{
if (mxUtils.isTrue(style, mxConstants.STYLE_ROUNDED, false))
{
elem = document.createElement("v:roundrect");
elem.setAttribute("arcsize",
(mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) + "%");
}
else
{
elem = document.createElement("v:rect");
}
}
String s = "position:absolute;left:" + String.valueOf(x) + "px;top:"
+ String.valueOf(y) + "px;width:" + String.valueOf(w)
+ "px;height:" + String.valueOf(h) + "px;";
// Applies rotation
double rotation = mxUtils.getDouble(style, mxConstants.STYLE_ROTATION);
if (rotation != 0)
{
s += "rotation:" + rotation + ";";
}
elem.setAttribute("style", s);
// Adds the shadow element
if (mxUtils.isTrue(style, mxConstants.STYLE_SHADOW, false)
&& fillColor != null)
{
Element shadow = document.createElement("v:shadow");
shadow.setAttribute("on", "true");
shadow.setAttribute("color", mxConstants.W3C_SHADOWCOLOR);
elem.appendChild(shadow);
}
float opacity = mxUtils.getFloat(style, mxConstants.STYLE_OPACITY, 100);
float fillOpacity = mxUtils.getFloat(style, mxConstants.STYLE_FILL_OPACITY, 100);
float strokeOpacity = mxUtils.getFloat(style, mxConstants.STYLE_STROKE_OPACITY, 100);
// Applies opacity to fill
if (fillColor != null)
{
Element fill = document.createElement("v:fill");
fill.setAttribute("color", fillColor);
if (opacity != 100 || fillOpacity != 100)
{
fill.setAttribute("opacity", String.valueOf(opacity * fillOpacity / 10000));
}
elem.appendChild(fill);
}
else
{
elem.setAttribute("filled", "false");
}
// Applies opacity to stroke
if (strokeColor != null)
{
elem.setAttribute("strokecolor", strokeColor);
Element stroke = document.createElement("v:stroke");
if (opacity != 100 || strokeOpacity != 100)
{
stroke.setAttribute("opacity", String.valueOf(opacity * strokeOpacity / 10000));
}
elem.appendChild(stroke);
}
else
{
elem.setAttribute("stroked", "false");
}
elem.setAttribute("strokeweight", String.valueOf(strokeWidth) + "px");
appendVmlElement(elem);
return elem;
}
/**
* Draws the given lines as segments between all points of the given list
* of mxPoints.
*
* @param pts List of points that define the line.
* @param style Style to be used for painting the line.
*/
public Element drawLine(List<mxPoint> pts, Map<String, Object> style)
{
String strokeColor = mxUtils.getString(style,
mxConstants.STYLE_STROKECOLOR);
float strokeWidth = (float) (mxUtils.getFloat(style,
mxConstants.STYLE_STROKEWIDTH, 1) * scale);
Element elem = document.createElement("v:shape");
if (strokeColor != null && strokeWidth > 0)
{
mxPoint pt = pts.get(0);
Rectangle r = new Rectangle(pt.getPoint());
StringBuilder buf = new StringBuilder("m " + Math.round(pt.getX())
+ " " + Math.round(pt.getY()));
for (int i = 1; i < pts.size(); i++)
{
pt = pts.get(i);
buf.append(" l " + Math.round(pt.getX()) + " "
+ Math.round(pt.getY()));
r = r.union(new Rectangle(pt.getPoint()));
}
String d = buf.toString();
elem.setAttribute("path", d);
elem.setAttribute("filled", "false");
elem.setAttribute("strokecolor", strokeColor);
elem.setAttribute("strokeweight", String.valueOf(strokeWidth)
+ "px");
String s = "position:absolute;" + "left:" + String.valueOf(r.x)
+ "px;" + "top:" + String.valueOf(r.y) + "px;" + "width:"
+ String.valueOf(r.width) + "px;" + "height:"
+ String.valueOf(r.height) + "px;";
elem.setAttribute("style", s);
elem.setAttribute("coordorigin",
String.valueOf(r.x) + " " + String.valueOf(r.y));
elem.setAttribute("coordsize", String.valueOf(r.width) + " "
+ String.valueOf(r.height));
}
appendVmlElement(elem);
return elem;
}
/**
* Draws the specified text either using drawHtmlString or using drawString.
*
* @param text Text to be painted.
* @param x X-coordinate of the text.
* @param y Y-coordinate of the text.
* @param w Width of the text.
* @param h Height of the text.
* @param style Style to be used for painting the text.
*/
public Element drawText(String text, int x, int y, int w, int h,
Map<String, Object> style)
{
Element table = mxUtils.createTable(document, text, x, y, w, h, scale,
style);
appendVmlElement(table);
return table;
}
}

View file

@ -0,0 +1,6 @@
<HTML>
<BODY>
This package contains various implementations for painting a graph using
different technologies, such as Graphics2D, HTML, SVG or VML.
</BODY>
</HTML>

View file

@ -0,0 +1,22 @@
package com.mxgraph.costfunction;
import com.mxgraph.view.mxCellState;
/**
* @author Mate
* A constant cost function that returns <b>const</b> regardless of edge value
*/
public class mxConstCostFunction extends mxCostFunction
{
private double cost;
public mxConstCostFunction(double cost)
{
this.cost = cost;
};
public double getCost(mxCellState state)
{
return cost;
};
}

View file

@ -0,0 +1,7 @@
package com.mxgraph.costfunction;
import com.mxgraph.analysis.mxICostFunction;
public abstract class mxCostFunction implements mxICostFunction
{
}

View file

@ -0,0 +1,43 @@
/**
* Copyright (c) 2012, JGraph Ltd
* Returns the value of a cell, which is assumed a Double
*/
package com.mxgraph.costfunction;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;
/**
* A cost function that assumes that edge value is of type "double" or "String" and returns that value. Default edge weight is 1.0 (if no double value can be retrieved)
*/
public class mxDoubleValCostFunction extends mxCostFunction
{
public double getCost(mxCellState state)
{
//assumed future parameters
if (state == null || state.getView() == null || state.getView().getGraph() == null)
{
return 1.0;
}
mxGraph graph = state.getView().getGraph();
Object cell = state.getCell();
Double edgeWeight = null;
if(graph.getModel().getValue(cell) == null || graph.getModel().getValue(cell) == "")
{
return 1.0;
}
else if (graph.getModel().getValue(cell) instanceof String)
{
edgeWeight = Double.parseDouble((String) graph.getModel().getValue(cell));
}
else
{
edgeWeight = (Double) graph.getModel().getValue(cell);
}
return edgeWeight;
};
};

View file

@ -0,0 +1,23 @@
package com.mxgraph.generatorfunction;
import com.mxgraph.view.mxCellState;
/**
* @author Mate
* A constant cost function that can be used during graph generation
* All generated edges will have the weight <b>cost</b>
*/
public class mxGeneratorConstFunction extends mxGeneratorFunction
{
private double cost;
public mxGeneratorConstFunction(double cost)
{
this.cost = cost;
};
public double getCost(mxCellState state)
{
return cost;
};
}

View file

@ -0,0 +1,11 @@
package com.mxgraph.generatorfunction;
import com.mxgraph.analysis.mxICostFunction;
/**
* @author Mate
* A parent class for all generator cost functions that are used for generating edge weights during graph generation
*/
public abstract class mxGeneratorFunction implements mxICostFunction
{
}

View file

@ -0,0 +1,59 @@
package com.mxgraph.generatorfunction;
import com.mxgraph.view.mxCellState;
/**
* @author Mate
* A generator random cost function
* It will generate random (type "double") edge weights in the range of (<b>minWeight</b>, <b>maxWeight</b>) and rounds the values to <b>roundToDecimals</b>
*/
public class mxGeneratorRandomFunction extends mxGeneratorFunction
{
private double maxWeight = 1;
private double minWeight = 0;
private int roundToDecimals = 2;
public mxGeneratorRandomFunction(double minWeight, double maxWeight, int roundToDecimals)
{
setWeightRange(minWeight, maxWeight);
setRoundToDecimals(roundToDecimals);
};
public double getCost(mxCellState state)
{
Double edgeWeight = null;
edgeWeight = Math.random() * (maxWeight - minWeight) + minWeight;
edgeWeight = (double) Math.round(edgeWeight * Math.pow(10, getRoundToDecimals())) / Math.pow(10, getRoundToDecimals());
return edgeWeight;
};
public double getMaxWeight()
{
return maxWeight;
};
public void setWeightRange(double minWeight, double maxWeight)
{
this.maxWeight = Math.max(minWeight, maxWeight);
this.minWeight = Math.min(minWeight, maxWeight);
};
public double getMinWeight()
{
return minWeight;
};
public int getRoundToDecimals()
{
return roundToDecimals;
};
public void setRoundToDecimals(int roundToDecimals)
{
this.roundToDecimals = roundToDecimals;
};
};

View file

@ -0,0 +1,51 @@
package com.mxgraph.generatorfunction;
import com.mxgraph.view.mxCellState;
/**
* @author Mate
* A generator random cost function
* It will generate random integer edge weights in the range of (<b>minWeight</b>, <b>maxWeight</b>) and rounds the values to <b>roundToDecimals</b>
*/
public class mxGeneratorRandomIntFunction extends mxGeneratorFunction
{
private double maxWeight = 10;
private double minWeight = 0;
public mxGeneratorRandomIntFunction(double minWeight, double maxWeight)
{
setWeightRange(minWeight, maxWeight);
};
public double getCost(mxCellState state)
{
//assumed future parameters
// mxGraph graph = state.getView().getGraph();
// Object cell = state.getCell();
if (minWeight == maxWeight)
{
return minWeight;
}
double currValue = minWeight + Math.round((Math.random() * (maxWeight - minWeight)));
return currValue;
};
public double getMaxWeight()
{
return maxWeight;
};
public void setWeightRange(double minWeight, double maxWeight)
{
this.maxWeight = Math.round(Math.max(minWeight, maxWeight));
this.minWeight = Math.round(Math.min(minWeight, maxWeight));
};
public double getMinWeight()
{
return minWeight;
};
};

View file

@ -0,0 +1,168 @@
/**
* Copyright (c) 2005-2010, David Benson, Gaudenz Alder
*/
package com.mxgraph.layout.hierarchical.model;
import java.util.List;
/**
* An abstraction of an internal hierarchy node or edge
*/
public abstract class mxGraphAbstractHierarchyCell
{
/**
* The maximum rank this cell occupies
*/
public int maxRank = -1;
/**
* The minimum rank this cell occupies
*/
public int minRank = -1;
/**
* The x position of this cell for each layer it occupies
*/
public double x[] = new double[1];
/**
* The y position of this cell for each layer it occupies
*/
public double y[] = new double[1];
/**
* The width of this cell
*/
public double width = 0.0;
/**
* The height of this cell
*/
public double height = 0.0;
/**
* A cached version of the cells this cell connects to on the next layer up
*/
protected List<mxGraphAbstractHierarchyCell>[] nextLayerConnectedCells = null;
/**
* A cached version of the cells this cell connects to on the next layer down
*/
protected List<mxGraphAbstractHierarchyCell>[] previousLayerConnectedCells = null;
/**
* Temporary variable for general use. Generally, try to avoid
* carrying information between stages. Currently, the longest
* path layering sets temp to the rank position in fixRanks()
* and the crossing reduction uses this. This meant temp couldn't
* be used for hashing the nodes in the model dfs and so hashCode
* was created
*/
public int[] temp = new int[1];
/**
* Returns the cells this cell connects to on the next layer up
* @param layer the layer this cell is on
* @return the cells this cell connects to on the next layer up
*/
public abstract List<mxGraphAbstractHierarchyCell> getNextLayerConnectedCells(int layer);
/**
* Returns the cells this cell connects to on the next layer down
* @param layer the layer this cell is on
* @return the cells this cell connects to on the next layer down
*/
public abstract List<mxGraphAbstractHierarchyCell> getPreviousLayerConnectedCells(int layer);
/**
*
* @return whether or not this cell is an edge
*/
public abstract boolean isEdge();
/**
*
* @return whether or not this cell is a node
*/
public abstract boolean isVertex();
/**
* Gets the value of temp for the specified layer
*
* @param layer
* the layer relating to a specific entry into temp
* @return the value for that layer
*/
public abstract int getGeneralPurposeVariable(int layer);
/**
* Set the value of temp for the specified layer
*
* @param layer
* the layer relating to a specific entry into temp
* @param value
* the value for that layer
*/
public abstract void setGeneralPurposeVariable(int layer, int value);
/**
* Set the value of x for the specified layer
*
* @param layer
* the layer relating to a specific entry into x[]
* @param value
* the x value for that layer
*/
public void setX(int layer, double value)
{
if (isVertex())
{
x[0] = value;
}
else if (isEdge())
{
x[layer - minRank - 1] = value;
}
}
/**
* Gets the value of x on the specified layer
* @param layer the layer to obtain x for
* @return the value of x on the specified layer
*/
public double getX(int layer)
{
if (isVertex())
{
return x[0];
}
else if (isEdge())
{
return x[layer - minRank - 1];
}
return 0.0;
}
/**
* Set the value of y for the specified layer
*
* @param layer
* the layer relating to a specific entry into y[]
* @param value
* the y value for that layer
*/
public void setY(int layer, double value)
{
if (isVertex())
{
y[0] = value;
}
else if (isEdge())
{
y[layer - minRank - 1] = value;
}
}
}

View file

@ -0,0 +1,183 @@
/*
* Copyright (c) 2005, David Benson
*
* All rights reserved.
*
* This file is licensed under the JGraph software license, a copy of which
* will have been provided to you in the file LICENSE at the root of your
* installation directory. If you are unable to locate this file please
* contact JGraph sales for another copy.
*/
package com.mxgraph.layout.hierarchical.model;
import java.util.ArrayList;
import java.util.List;
/**
* An abstraction of a hierarchical edge for the hierarchy layout
*/
public class mxGraphHierarchyEdge extends mxGraphAbstractHierarchyCell
{
/**
* The graph edge(s) this object represents. Parallel edges are all grouped
* together within one hierarchy edge.
*/
public List<Object> edges;
/**
* The node this edge is sourced at
*/
public mxGraphHierarchyNode source;
/**
* The node this edge targets
*/
public mxGraphHierarchyNode target;
/**
* Whether or not the direction of this edge has been reversed
* internally to create a DAG for the hierarchical layout
*/
protected boolean isReversed = false;
/**
* Constructs a hierarchy edge
* @param edges a list of real graph edges this abstraction represents
*/
public mxGraphHierarchyEdge(List<Object> edges)
{
this.edges = edges;
}
/**
* Inverts the direction of this internal edge(s)
*/
public void invert()
{
mxGraphHierarchyNode temp = source;
source = target;
target = temp;
isReversed = !isReversed;
}
/**
* @return Returns the isReversed.
*/
public boolean isReversed()
{
return isReversed;
}
/**
* @param isReversed The isReversed to set.
*/
public void setReversed(boolean isReversed)
{
this.isReversed = isReversed;
}
/**
* Returns the cells this cell connects to on the next layer up
* @param layer the layer this cell is on
* @return the cells this cell connects to on the next layer up
*/
@SuppressWarnings("unchecked")
public List<mxGraphAbstractHierarchyCell> getNextLayerConnectedCells(int layer)
{
if (nextLayerConnectedCells == null)
{
nextLayerConnectedCells = new ArrayList[temp.length];
for (int i = 0; i < nextLayerConnectedCells.length; i++)
{
nextLayerConnectedCells[i] = new ArrayList<mxGraphAbstractHierarchyCell>(2);
if (i == nextLayerConnectedCells.length - 1)
{
nextLayerConnectedCells[i].add(source);
}
else
{
nextLayerConnectedCells[i].add(this);
}
}
}
return nextLayerConnectedCells[layer - minRank - 1];
}
/**
* Returns the cells this cell connects to on the next layer down
* @param layer the layer this cell is on
* @return the cells this cell connects to on the next layer down
*/
@SuppressWarnings("unchecked")
public List<mxGraphAbstractHierarchyCell> getPreviousLayerConnectedCells(int layer)
{
if (previousLayerConnectedCells == null)
{
previousLayerConnectedCells = new ArrayList[temp.length];
for (int i = 0; i < previousLayerConnectedCells.length; i++)
{
previousLayerConnectedCells[i] = new ArrayList<mxGraphAbstractHierarchyCell>(2);
if (i == 0)
{
previousLayerConnectedCells[i].add(target);
}
else
{
previousLayerConnectedCells[i].add(this);
}
}
}
return previousLayerConnectedCells[layer - minRank - 1];
}
/**
*
* @return whether or not this cell is an edge
*/
public boolean isEdge()
{
return true;
}
/**
*
* @return whether or not this cell is a node
*/
public boolean isVertex()
{
return false;
}
/**
* Gets the value of temp for the specified layer
*
* @param layer
* the layer relating to a specific entry into temp
* @return the value for that layer
*/
public int getGeneralPurposeVariable(int layer)
{
return temp[layer - minRank - 1];
}
/**
* Set the value of temp for the specified layer
*
* @param layer
* the layer relating to a specific entry into temp
* @param value
* the value for that layer
*/
public void setGeneralPurposeVariable(int layer, int value)
{
temp[layer - minRank - 1] = value;
}
}

View file

@ -0,0 +1,787 @@
/*
* Copyright (c) 2005-2012, JGraph Ltd
*/
package com.mxgraph.layout.hierarchical.model;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.mxgraph.layout.hierarchical.mxHierarchicalLayout;
import com.mxgraph.view.mxGraph;
/**
* Internal model of a hierarchical graph. This model stores nodes and edges
* equivalent to the real graph nodes and edges, but also stores the rank of the
* cells, the order within the ranks and the new candidate locations of cells.
* The internal model also reverses edge direction were appropriate , ignores
* self-loop and groups parallels together under one edge object.
*/
public class mxGraphHierarchyModel
{
/**
* Stores the largest rank number allocated
*/
public int maxRank;
/**
* Map from graph vertices to internal model nodes
*/
protected Map<Object, mxGraphHierarchyNode> vertexMapper = null;
/**
* Map from graph edges to internal model edges
*/
protected Map<Object, mxGraphHierarchyEdge> edgeMapper = null;
/**
* Mapping from rank number to actual rank
*/
public Map<Integer, mxGraphHierarchyRank> ranks = null;
/**
* Store of roots of this hierarchy model, these are real graph cells, not
* internal cells
*/
public List<Object> roots;
/**
* The parent cell whose children are being laid out
*/
public Object parent = null;
/**
* Count of the number of times the ancestor dfs has been used
*/
protected int dfsCount = 0;
/** High value to start source layering scan rank value from */
private final int SOURCESCANSTARTRANK = 100000000;
/**
* Creates an internal ordered graph model using the vertices passed in. If
* there are any, leftward edge need to be inverted in the internal model
*
* @param layout
* the enclosing layout object
* @param vertices
* the vertices for this hierarchy
*/
public mxGraphHierarchyModel(mxHierarchicalLayout layout,
Object[] vertices, List<Object> roots, Object parent)
{
mxGraph graph = layout.getGraph();
this.roots = roots;
this.parent = parent;
if (vertices == null)
{
vertices = graph.getChildVertices(parent);
}
// map of cells to internal cell needed for second run through
// to setup the sink of edges correctly. Guess size by number
// of edges is roughly same as number of vertices.
vertexMapper = new Hashtable<Object, mxGraphHierarchyNode>(
vertices.length);
edgeMapper = new Hashtable<Object, mxGraphHierarchyEdge>(
vertices.length);
maxRank = SOURCESCANSTARTRANK;
mxGraphHierarchyNode[] internalVertices = new mxGraphHierarchyNode[vertices.length];
createInternalCells(layout, vertices, internalVertices);
// Go through edges set their sink values. Also check the
// ordering if and invert edges if necessary
for (int i = 0; i < vertices.length; i++)
{
Collection<mxGraphHierarchyEdge> edges = internalVertices[i].connectsAsSource;
Iterator<mxGraphHierarchyEdge> iter = edges.iterator();
while (iter.hasNext())
{
mxGraphHierarchyEdge internalEdge = iter.next();
Collection<Object> realEdges = internalEdge.edges;
Iterator<Object> iter2 = realEdges.iterator();
// Only need to process the first real edge, since
// all the edges connect to the same other vertex
if (iter2.hasNext())
{
Object realEdge = iter2.next();
Object targetCell = graph.getView().getVisibleTerminal(
realEdge, false);
mxGraphHierarchyNode internalTargetCell = vertexMapper
.get(targetCell);
if (internalVertices[i] == internalTargetCell)
{
// The real edge is reversed relative to the internal edge
targetCell = graph.getView().getVisibleTerminal(
realEdge, true);
internalTargetCell = vertexMapper.get(targetCell);
}
if (internalTargetCell != null
&& internalVertices[i] != internalTargetCell)
{
internalEdge.target = internalTargetCell;
if (internalTargetCell.connectsAsTarget.size() == 0)
{
internalTargetCell.connectsAsTarget = new LinkedHashSet<mxGraphHierarchyEdge>(
4);
}
internalTargetCell.connectsAsTarget.add(internalEdge);
}
}
}
// Use the temp variable in the internal nodes to mark this
// internal vertex as having been visited.
internalVertices[i].temp[0] = 1;
}
}
/**
* Creates all edges in the internal model
*
* @param layout
* reference to the layout algorithm
* @param vertices
* the vertices whom are to have an internal representation
* created
* @param internalVertices
* the blank internal vertices to have their information filled
* in using the real vertices
*/
protected void createInternalCells(mxHierarchicalLayout layout,
Object[] vertices, mxGraphHierarchyNode[] internalVertices)
{
mxGraph graph = layout.getGraph();
// Create internal edges
for (int i = 0; i < vertices.length; i++)
{
internalVertices[i] = new mxGraphHierarchyNode(vertices[i]);
vertexMapper.put(vertices[i], internalVertices[i]);
// If the layout is deterministic, order the cells
Object[] conns = layout.getEdges(vertices[i]);
List<Object> outgoingCells = Arrays.asList(graph.getOpposites(
conns, vertices[i]));
internalVertices[i].connectsAsSource = new LinkedHashSet<mxGraphHierarchyEdge>(
outgoingCells.size());
// Create internal edges, but don't do any rank assignment yet
// First use the information from the greedy cycle remover to
// invert the leftward edges internally
Iterator<Object> iter = outgoingCells.iterator();
while (iter.hasNext())
{
// Don't add self-loops
Object cell = iter.next();
if (cell != vertices[i] && graph.getModel().isVertex(cell)
&& !layout.isVertexIgnored(cell))
{
// We process all edge between this source and its targets
// If there are edges going both ways, we need to collect
// them all into one internal edges to avoid looping problems
// later. We assume this direction (source -> target) is the
// natural direction if at least half the edges are going in
// that direction.
// The check below for edgeMapper.get(edges[0]) == null is
// in case we've processed this the other way around
// (target -> source) and the number of edges in each direction
// are the same. All the graph edges will have been assigned to
// an internal edge going the other way, so we don't want to
// process them again
Object[] undirectEdges = graph.getEdgesBetween(vertices[i],
cell, false);
Object[] directedEdges = graph.getEdgesBetween(vertices[i],
cell, true);
if (undirectEdges != null
&& undirectEdges.length > 0
&& (edgeMapper.get(undirectEdges[0]) == null)
&& (directedEdges.length * 2 >= undirectEdges.length))
{
ArrayList<Object> listEdges = new ArrayList<Object>(
undirectEdges.length);
for (int j = 0; j < undirectEdges.length; j++)
{
listEdges.add(undirectEdges[j]);
}
mxGraphHierarchyEdge internalEdge = new mxGraphHierarchyEdge(
listEdges);
Iterator<Object> iter2 = listEdges.iterator();
while (iter2.hasNext())
{
Object edge = iter2.next();
edgeMapper.put(edge, internalEdge);
// Resets all point on the edge and disables the edge style
// without deleting it from the cell style
graph.resetEdge(edge);
if (layout.isDisableEdgeStyle())
{
layout.setEdgeStyleEnabled(edge, false);
layout.setOrthogonalEdge(edge, true);
}
}
internalEdge.source = internalVertices[i];
internalVertices[i].connectsAsSource.add(internalEdge);
}
}
}
// Ensure temp variable is cleared from any previous use
internalVertices[i].temp[0] = 0;
}
}
/**
* Basic determination of minimum layer ranking by working from from sources
* or sinks and working through each node in the relevant edge direction.
* Starting at the sinks is basically a longest path layering algorithm.
*/
public void initialRank()
{
Collection<mxGraphHierarchyNode> internalNodes = vertexMapper.values();
LinkedList<mxGraphHierarchyNode> startNodes = new LinkedList<mxGraphHierarchyNode>();
if (roots != null)
{
Iterator<Object> iter = roots.iterator();
while (iter.hasNext())
{
mxGraphHierarchyNode internalNode = vertexMapper.get(iter
.next());
if (internalNode != null)
{
startNodes.add(internalNode);
}
}
}
Iterator<mxGraphHierarchyNode> iter = internalNodes.iterator();
while (iter.hasNext())
{
mxGraphHierarchyNode internalNode = iter.next();
// Mark the node as not having had a layer assigned
internalNode.temp[0] = -1;
}
List<mxGraphHierarchyNode> startNodesCopy = new ArrayList<mxGraphHierarchyNode>(
startNodes);
while (!startNodes.isEmpty())
{
mxGraphHierarchyNode internalNode = startNodes.getFirst();
Collection<mxGraphHierarchyEdge> layerDeterminingEdges;
Collection<mxGraphHierarchyEdge> edgesToBeMarked;
layerDeterminingEdges = internalNode.connectsAsTarget;
edgesToBeMarked = internalNode.connectsAsSource;
// flag to keep track of whether or not all layer determining
// edges have been scanned
boolean allEdgesScanned = true;
// Work out the layer of this node from the layer determining
// edges
Iterator<mxGraphHierarchyEdge> iter2 = layerDeterminingEdges
.iterator();
// The minimum layer number of any node connected by one of
// the layer determining edges variable. If we are starting
// from sources, need to start at some huge value and
// normalise down afterwards
int minimumLayer = SOURCESCANSTARTRANK;
while (allEdgesScanned && iter2.hasNext())
{
mxGraphHierarchyEdge internalEdge = iter2.next();
if (internalEdge.temp[0] == 5270620)
{
// This edge has been scanned, get the layer of the
// node on the other end
mxGraphHierarchyNode otherNode = internalEdge.source;
minimumLayer = Math.min(minimumLayer,
otherNode.temp[0] - 1);
}
else
{
allEdgesScanned = false;
}
}
// If all edge have been scanned, assign the layer, mark all
// edges in the other direction and remove from the nodes list
if (allEdgesScanned)
{
internalNode.temp[0] = minimumLayer;
maxRank = Math.min(maxRank, minimumLayer);
if (edgesToBeMarked != null)
{
Iterator<mxGraphHierarchyEdge> iter3 = edgesToBeMarked
.iterator();
while (iter3.hasNext())
{
mxGraphHierarchyEdge internalEdge = iter3.next();
// Assign unique stamp ( y/m/d/h )
internalEdge.temp[0] = 5270620;
// Add node on other end of edge to LinkedList of
// nodes to be analysed
mxGraphHierarchyNode otherNode = internalEdge.target;
// Only add node if it hasn't been assigned a layer
if (otherNode.temp[0] == -1)
{
startNodes.addLast(otherNode);
// Mark this other node as neither being
// unassigned nor assigned so it isn't
// added to this list again, but it's
// layer isn't used in any calculation.
otherNode.temp[0] = -2;
}
}
}
startNodes.removeFirst();
}
else
{
// Not all the edges have been scanned, get to the back of
// the class and put the dunces cap on
Object removedCell = startNodes.removeFirst();
startNodes.addLast(internalNode);
if (removedCell == internalNode && startNodes.size() == 1)
{
// This is an error condition, we can't get out of
// this loop. It could happen for more than one node
// but that's a lot harder to detect. Log the error
// TODO make log comment
break;
}
}
}
// Normalize the ranks down from their large starting value to place
// at least 1 sink on layer 0
iter = internalNodes.iterator();
while (iter.hasNext())
{
mxGraphHierarchyNode internalNode = iter.next();
// Mark the node as not having had a layer assigned
internalNode.temp[0] -= maxRank;
}
// Tighten the roots as far as possible
for (int i = 0; i < startNodesCopy.size(); i++)
{
mxGraphHierarchyNode internalNode = startNodesCopy.get(i);
int currentMaxLayer = 0;
Iterator<mxGraphHierarchyEdge> iter2 = internalNode.connectsAsSource
.iterator();
while (iter2.hasNext())
{
mxGraphHierarchyEdge internalEdge = iter2.next();
mxGraphHierarchyNode otherNode = internalEdge.target;
internalNode.temp[0] = Math.max(currentMaxLayer,
otherNode.temp[0] + 1);
currentMaxLayer = internalNode.temp[0];
}
}
// Reset the maxRank to that which would be expected for a from-sink
// scan
maxRank = SOURCESCANSTARTRANK - maxRank;
}
/**
* Fixes the layer assignments to the values stored in the nodes. Also needs
* to create dummy nodes for edges that cross layers.
*/
public void fixRanks()
{
final mxGraphHierarchyRank[] rankList = new mxGraphHierarchyRank[maxRank + 1];
ranks = new LinkedHashMap<Integer, mxGraphHierarchyRank>(maxRank + 1);
for (int i = 0; i < maxRank + 1; i++)
{
rankList[i] = new mxGraphHierarchyRank();
ranks.put(new Integer(i), rankList[i]);
}
// Perform a DFS to obtain an initial ordering for each rank.
// Without doing this you would end up having to process
// crossings for a standard tree.
mxGraphHierarchyNode[] rootsArray = null;
if (roots != null)
{
Object[] oldRootsArray = roots.toArray();
rootsArray = new mxGraphHierarchyNode[oldRootsArray.length];
for (int i = 0; i < oldRootsArray.length; i++)
{
Object node = oldRootsArray[i];
mxGraphHierarchyNode internalNode = vertexMapper.get(node);
rootsArray[i] = internalNode;
}
}
visit(new mxGraphHierarchyModel.CellVisitor()
{
public void visit(mxGraphHierarchyNode parent,
mxGraphHierarchyNode cell,
mxGraphHierarchyEdge connectingEdge, int layer, int seen)
{
mxGraphHierarchyNode node = cell;
if (seen == 0 && node.maxRank < 0 && node.minRank < 0)
{
rankList[node.temp[0]].add(cell);
node.maxRank = node.temp[0];
node.minRank = node.temp[0];
// Set temp[0] to the nodes position in the rank
node.temp[0] = rankList[node.maxRank].size() - 1;
}
if (parent != null && connectingEdge != null)
{
int parentToCellRankDifference = (parent).maxRank
- node.maxRank;
if (parentToCellRankDifference > 1)
{
// There are ranks in between the parent and current cell
mxGraphHierarchyEdge edge = connectingEdge;
edge.maxRank = (parent).maxRank;
edge.minRank = (cell).maxRank;
edge.temp = new int[parentToCellRankDifference - 1];
edge.x = new double[parentToCellRankDifference - 1];
edge.y = new double[parentToCellRankDifference - 1];
for (int i = edge.minRank + 1; i < edge.maxRank; i++)
{
// The connecting edge must be added to the
// appropriate ranks
rankList[i].add(edge);
edge.setGeneralPurposeVariable(i,
rankList[i].size() - 1);
}
}
}
}
}, rootsArray, false, null);
}
/**
* A depth first search through the internal hierarchy model
*
* @param visitor
* the visitor pattern to be called for each node
* @param trackAncestors
* whether or not the search is to keep track all nodes directly
* above this one in the search path
*/
public void visit(CellVisitor visitor, mxGraphHierarchyNode[] dfsRoots,
boolean trackAncestors, Set<mxGraphHierarchyNode> seenNodes)
{
// Run dfs through on all roots
if (dfsRoots != null)
{
for (int i = 0; i < dfsRoots.length; i++)
{
mxGraphHierarchyNode internalNode = dfsRoots[i];
if (internalNode != null)
{
if (seenNodes == null)
{
seenNodes = new HashSet<mxGraphHierarchyNode>();
}
if (trackAncestors)
{
// Set up hash code for root
internalNode.hashCode = new int[2];
internalNode.hashCode[0] = dfsCount;
internalNode.hashCode[1] = i;
dfs(null, internalNode, null, visitor, seenNodes,
internalNode.hashCode, i, 0);
}
else
{
dfs(null, internalNode, null, visitor, seenNodes, 0);
}
}
}
dfsCount++;
}
}
/**
* Performs a depth first search on the internal hierarchy model
*
* @param parent
* the parent internal node of the current internal node
* @param root
* the current internal node
* @param connectingEdge
* the internal edge connecting the internal node and the parent
* internal node, if any
* @param visitor
* the visitor pattern to be called for each node
* @param seen
* a set of all nodes seen by this dfs a set of all of the
* ancestor node of the current node
* @param layer
* the layer on the dfs tree ( not the same as the model ranks )
*/
public void dfs(mxGraphHierarchyNode parent, mxGraphHierarchyNode root,
mxGraphHierarchyEdge connectingEdge, CellVisitor visitor,
Set<mxGraphHierarchyNode> seen, int layer)
{
if (root != null)
{
if (!seen.contains(root))
{
visitor.visit(parent, root, connectingEdge, layer, 0);
seen.add(root);
// Copy the connects as source list so that visitors
// can change the original for edge direction inversions
final Object[] outgoingEdges = root.connectsAsSource.toArray();
for (int i = 0; i < outgoingEdges.length; i++)
{
mxGraphHierarchyEdge internalEdge = (mxGraphHierarchyEdge) outgoingEdges[i];
mxGraphHierarchyNode targetNode = internalEdge.target;
// Root check is O(|roots|)
dfs(root, targetNode, internalEdge, visitor, seen,
layer + 1);
}
}
else
{
// Use the int field to indicate this node has been seen
visitor.visit(parent, root, connectingEdge, layer, 1);
}
}
}
/**
* Performs a depth first search on the internal hierarchy model. This dfs
* extends the default version by keeping track of cells ancestors, but it
* should be only used when necessary because of it can be computationally
* intensive for deep searches.
*
* @param parent
* the parent internal node of the current internal node
* @param root
* the current internal node
* @param connectingEdge
* the internal edge connecting the internal node and the parent
* internal node, if any
* @param visitor
* the visitor pattern to be called for each node
* @param seen
* a set of all nodes seen by this dfs
* @param ancestors
* the parent hash code
* @param childHash
* the new hash code for this node
* @param layer
* the layer on the dfs tree ( not the same as the model ranks )
*/
public void dfs(mxGraphHierarchyNode parent, mxGraphHierarchyNode root,
mxGraphHierarchyEdge connectingEdge, CellVisitor visitor,
Set<mxGraphHierarchyNode> seen, int[] ancestors, int childHash,
int layer)
{
// Explanation of custom hash set. Previously, the ancestors variable
// was passed through the dfs as a HashSet. The ancestors were copied
// into a new HashSet and when the new child was processed it was also
// added to the set. If the current node was in its ancestor list it
// meant there is a cycle in the graph and this information is passed
// to the visitor.visit() in the seen parameter. The HashSet clone was
// very expensive on CPU so a custom hash was developed using primitive
// types. temp[] couldn't be used so hashCode[] was added to each node.
// Each new child adds another int to the array, copying the prefix
// from its parent. Child of the same parent add different ints (the
// limit is therefore 2^32 children per parent...). If a node has a
// child with the hashCode already set then the child code is compared
// to the same portion of the current nodes array. If they match there
// is a loop.
// Note that the basic mechanism would only allow for 1 use of this
// functionality, so the root nodes have two ints. The second int is
// incremented through each node root and the first is incremented
// through each run of the dfs algorithm (therefore the dfs is not
// thread safe). The hash code of each node is set if not already set,
// or if the first int does not match that of the current run.
if (root != null)
{
if (parent != null)
{
// Form this nodes hash code if necessary, that is, if the
// hashCode variable has not been initialized or if the
// start of the parent hash code does not equal the start of
// this nodes hash code, indicating the code was set on a
// previous run of this dfs.
if (root.hashCode == null
|| root.hashCode[0] != parent.hashCode[0])
{
int hashCodeLength = parent.hashCode.length + 1;
root.hashCode = new int[hashCodeLength];
System.arraycopy(parent.hashCode, 0, root.hashCode, 0,
parent.hashCode.length);
root.hashCode[hashCodeLength - 1] = childHash;
}
}
if (!seen.contains(root))
{
visitor.visit(parent, root, connectingEdge, layer, 0);
seen.add(root);
// Copy the connects as source list so that visitors
// can change the original for edge direction inversions
final Object[] outgoingEdges = root.connectsAsSource.toArray();
for (int i = 0; i < outgoingEdges.length; i++)
{
mxGraphHierarchyEdge internalEdge = (mxGraphHierarchyEdge) outgoingEdges[i];
mxGraphHierarchyNode targetNode = internalEdge.target;
// Root check is O(|roots|)
dfs(root, targetNode, internalEdge, visitor, seen,
root.hashCode, i, layer + 1);
}
}
else
{
// Use the int field to indicate this node has been seen
visitor.visit(parent, root, connectingEdge, layer, 1);
}
}
}
/**
* Defines the interface that visitors use to perform operations upon the
* graph information during depth first search (dfs) or other tree-traversal
* strategies implemented by subclassers.
*/
public interface CellVisitor
{
/**
* The method within which the visitor will perform operations upon the
* graph model
*
* @param parent
* the parent cell the current cell
* @param cell
* the current cell visited
* @param connectingEdge
* the edge that led the last cell visited to this cell
* @param layer
* the current layer of the tree
* @param seen
* an int indicating whether this cell has been seen
* previously
*/
public void visit(mxGraphHierarchyNode parent,
mxGraphHierarchyNode cell, mxGraphHierarchyEdge connectingEdge,
int layer, int seen);
}
/**
* @return Returns the vertexMapping.
*/
public Map<Object, mxGraphHierarchyNode> getVertexMapper()
{
if (vertexMapper == null)
{
vertexMapper = new Hashtable<Object, mxGraphHierarchyNode>();
}
return vertexMapper;
}
/**
* @param vertexMapping
* The vertexMapping to set.
*/
public void setVertexMapper(Map<Object, mxGraphHierarchyNode> vertexMapping)
{
this.vertexMapper = vertexMapping;
}
/**
* @return Returns the edgeMapper.
*/
public Map<Object, mxGraphHierarchyEdge> getEdgeMapper()
{
return edgeMapper;
}
/**
* @param edgeMapper
* The edgeMapper to set.
*/
public void setEdgeMapper(Map<Object, mxGraphHierarchyEdge> edgeMapper)
{
this.edgeMapper = edgeMapper;
}
/**
* @return Returns the dfsCount.
*/
public int getDfsCount()
{
return dfsCount;
}
/**
* @param dfsCount
* The dfsCount to set.
*/
public void setDfsCount(int dfsCount)
{
this.dfsCount = dfsCount;
}
}

View file

@ -0,0 +1,216 @@
/*
* Copyright (c) 2005, David Benson
*
* All rights reserved.
*
* This file is licensed under the JGraph software license, a copy of which
* will have been provided to you in the file LICENSE at the root of your
* installation directory. If you are unable to locate this file please
* contact JGraph sales for another copy.
*/
package com.mxgraph.layout.hierarchical.model;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
/**
* An abstraction of an internal node in the hierarchy layout
*/
public class mxGraphHierarchyNode extends mxGraphAbstractHierarchyCell
{
/**
* Shared empty connection map to return instead of null in applyMap.
*/
public static Collection<mxGraphHierarchyEdge> emptyConnectionMap = new ArrayList<mxGraphHierarchyEdge>(0);
/**
* The graph cell this object represents.
*/
public Object cell = null;
/**
* Collection of hierarchy edges that have this node as a target
*/
public Collection<mxGraphHierarchyEdge> connectsAsTarget = emptyConnectionMap;
/**
* Collection of hierarchy edges that have this node as a source
*/
public Collection<mxGraphHierarchyEdge> connectsAsSource = emptyConnectionMap;
/**
* Assigns a unique hashcode for each node. Used by the model dfs instead
* of copying HashSets
*/
public int[] hashCode;
/**
* Constructs an internal node to represent the specified real graph cell
* @param cell the real graph cell this node represents
*/
public mxGraphHierarchyNode(Object cell)
{
this.cell = cell;
}
/**
* Returns the integer value of the layer that this node resides in
* @return the integer value of the layer that this node resides in
*/
public int getRankValue()
{
return maxRank;
}
/**
* Returns the cells this cell connects to on the next layer up
* @param layer the layer this cell is on
* @return the cells this cell connects to on the next layer up
*/
@SuppressWarnings("unchecked")
public List<mxGraphAbstractHierarchyCell> getNextLayerConnectedCells(int layer)
{
if (nextLayerConnectedCells == null)
{
nextLayerConnectedCells = new ArrayList[1];
nextLayerConnectedCells[0] = new ArrayList<mxGraphAbstractHierarchyCell>(connectsAsTarget.size());
Iterator<mxGraphHierarchyEdge> iter = connectsAsTarget.iterator();
while (iter.hasNext())
{
mxGraphHierarchyEdge edge = iter.next();
if (edge.maxRank == -1 || edge.maxRank == layer + 1)
{
// Either edge is not in any rank or
// no dummy nodes in edge, add node of other side of edge
nextLayerConnectedCells[0].add(edge.source);
}
else
{
// Edge spans at least two layers, add edge
nextLayerConnectedCells[0].add(edge);
}
}
}
return nextLayerConnectedCells[0];
}
/**
* Returns the cells this cell connects to on the next layer down
* @param layer the layer this cell is on
* @return the cells this cell connects to on the next layer down
*/
@SuppressWarnings("unchecked")
public List<mxGraphAbstractHierarchyCell> getPreviousLayerConnectedCells(int layer)
{
if (previousLayerConnectedCells == null)
{
previousLayerConnectedCells = new ArrayList[1];
previousLayerConnectedCells[0] = new ArrayList<mxGraphAbstractHierarchyCell>(connectsAsSource
.size());
Iterator<mxGraphHierarchyEdge> iter = connectsAsSource.iterator();
while (iter.hasNext())
{
mxGraphHierarchyEdge edge = iter.next();
if (edge.minRank == -1 || edge.minRank == layer - 1)
{
// No dummy nodes in edge, add node of other side of edge
previousLayerConnectedCells[0].add(edge.target);
}
else
{
// Edge spans at least two layers, add edge
previousLayerConnectedCells[0].add(edge);
}
}
}
return previousLayerConnectedCells[0];
}
/**
*
* @return whether or not this cell is an edge
*/
public boolean isEdge()
{
return false;
}
/**
*
* @return whether or not this cell is a node
*/
public boolean isVertex()
{
return true;
}
/**
* Gets the value of temp for the specified layer
*
* @param layer
* the layer relating to a specific entry into temp
* @return the value for that layer
*/
public int getGeneralPurposeVariable(int layer)
{
return temp[0];
}
/**
* Set the value of temp for the specified layer
*
* @param layer
* the layer relating to a specific entry into temp
* @param value
* the value for that layer
*/
public void setGeneralPurposeVariable(int layer, int value)
{
temp[0] = value;
}
public boolean isAncestor(mxGraphHierarchyNode otherNode)
{
// Firstly, the hash code of this node needs to be shorter than the
// other node
if (otherNode != null && hashCode != null && otherNode.hashCode != null
&& hashCode.length < otherNode.hashCode.length)
{
if (hashCode == otherNode.hashCode)
{
return true;
}
if (hashCode == null)
{
return false;
}
// Secondly, this hash code must match the start of the other
// node's hash code. Arrays.equals cannot be used here since
// the arrays are different length, and we do not want to
// perform another array copy.
for (int i = 0; i < hashCode.length; i++)
{
if (hashCode[i] != otherNode.hashCode[i])
{
return false;
}
}
return true;
}
return false;
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2005, David Benson
*
* All rights reserved.
*
* This file is licensed under the JGraph software license, a copy of which
* will have been provided to you in the file LICENSE at the root of your
* installation directory. If you are unable to locate this file please
* contact JGraph sales for another copy.
*/
package com.mxgraph.layout.hierarchical.model;
import java.util.LinkedHashSet;
/**
* An abstraction of a rank in the hierarchy layout. Should be ordered, perform
* remove in constant time and contains in constant time
*/
public class mxGraphHierarchyRank extends LinkedHashSet<mxGraphAbstractHierarchyCell>
{
/**
*
*/
private static final long serialVersionUID = -2781491210687143878L;
}

View file

@ -0,0 +1,709 @@
/*
* Copyright (c) 2005-2012, JGraph Ltd
*/
package com.mxgraph.layout.hierarchical;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.swing.SwingConstants;
import com.mxgraph.layout.mxGraphLayout;
import com.mxgraph.layout.hierarchical.model.mxGraphHierarchyModel;
import com.mxgraph.layout.hierarchical.stage.mxCoordinateAssignment;
import com.mxgraph.layout.hierarchical.stage.mxHierarchicalLayoutStage;
import com.mxgraph.layout.hierarchical.stage.mxMedianHybridCrossingReduction;
import com.mxgraph.layout.hierarchical.stage.mxMinimumCycleRemover;
import com.mxgraph.model.mxGraphModel;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxGraphView;
/**
* The top level compound layout of the hierarchical layout. The individual
* elements of the layout are called in sequence.
*/
public class mxHierarchicalLayout extends mxGraphLayout/*,
JGraphLayout.Stoppable*/
{
/** The root nodes of the layout */
protected List<Object> roots = null;
/**
* Specifies if the parent should be resized after the layout so that it
* contains all the child cells. Default is false. @See parentBorder.
*/
protected boolean resizeParent = true;
/**
* Specifies if the parnent should be moved if resizeParent is enabled.
* Default is false. @See resizeParent.
*/
protected boolean moveParent = false;
/**
* The border to be added around the children if the parent is to be
* resized using resizeParent. Default is 0. @See resizeParent.
*/
protected int parentBorder = 0;
/**
* The spacing buffer added between cells on the same layer
*/
protected double intraCellSpacing = 30.0;
/**
* The spacing buffer added between cell on adjacent layers
*/
protected double interRankCellSpacing = 50.0;
/**
* The spacing buffer between unconnected hierarchies
*/
protected double interHierarchySpacing = 60.0;
/**
* The distance between each parallel edge on each ranks for long edges
*/
protected double parallelEdgeSpacing = 10.0;
/**
* The position of the root node(s) relative to the laid out graph in.
* Default is <code>SwingConstants.NORTH</code>, i.e. top-down.
*/
protected int orientation = SwingConstants.NORTH;
/**
* Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
* modified by the result. Default is true.
*/
protected boolean disableEdgeStyle = true;
/**
* Whether or not to perform local optimisations and iterate multiple times
* through the algorithm
*/
protected boolean fineTuning = true;
/**
* Whether or not to navigate edges whose terminal vertices
* have different parents but are in the same ancestry chain
*/
protected boolean traverseAncestors = true;
/**
* The internal model formed of the layout
*/
protected mxGraphHierarchyModel model = null;
/**
* The layout progress bar
*/
//protected JGraphLayoutProgress progress = new JGraphLayoutProgress();
/**
* Constructs a hierarchical layout
* @param graph the graph to lay out
*
*/
public mxHierarchicalLayout(mxGraph graph)
{
this(graph, SwingConstants.NORTH);
}
/**
* Constructs a hierarchical layout
* @param graph the graph to lay out
* @param orientation <code>SwingConstants.NORTH, SwingConstants.EAST, SwingConstants.SOUTH</code> or <code> SwingConstants.WEST</code>
*
*/
public mxHierarchicalLayout(mxGraph graph, int orientation)
{
super(graph);
this.orientation = orientation;
}
/**
* Returns the model for this layout algorithm.
*/
public mxGraphHierarchyModel getModel()
{
return model;
}
/**
* Executes the layout for the children of the specified parent.
*
* @param parent Parent cell that contains the children to be laid out.
*/
public void execute(Object parent)
{
execute(parent, null);
}
/**
* Executes the layout for the children of the specified parent.
*
* @param parent Parent cell that contains the children to be laid out.
* @param roots the starting roots of the layout
*/
public void execute(Object parent, List<Object> roots)
{
super.execute(parent);
mxIGraphModel model = graph.getModel();
// If the roots are set and the parent is set, only
// use the roots that are some dependent of the that
// parent.
// If just the root are set, use them as-is
// If just the parent is set use it's immediate
// children as the initial set
if (roots == null && parent == null)
{
// TODO indicate the problem
return;
}
if (roots != null && parent != null)
{
for (Object root : roots)
{
if (!model.isAncestor(parent, root))
{
roots.remove(root);
}
}
}
this.roots = roots;
model.beginUpdate();
try
{
run(parent);
if (isResizeParent() && !graph.isCellCollapsed(parent))
{
graph.updateGroupBounds(new Object[] { parent },
getParentBorder(), isMoveParent());
}
}
finally
{
model.endUpdate();
}
}
/**
* Returns all visible children in the given parent which do not have
* incoming edges. If the result is empty then the children with the
* maximum difference between incoming and outgoing edges are returned.
* This takes into account edges that are being promoted to the given
* root due to invisible children or collapsed cells.
*
* @param parent Cell whose children should be checked.
* @return List of tree roots in parent.
*/
public List<Object> findRoots(Object parent, Set<Object> vertices)
{
List<Object> roots = new ArrayList<Object>();
Object best = null;
int maxDiff = -100000;
mxIGraphModel model = graph.getModel();
for (Object vertex : vertices)
{
if (model.isVertex(vertex) && graph.isCellVisible(vertex))
{
Object[] conns = this.getEdges(vertex);
int fanOut = 0;
int fanIn = 0;
for (int k = 0; k < conns.length; k++)
{
Object src = graph.getView().getVisibleTerminal(conns[k],
true);
if (src == vertex)
{
fanOut++;
}
else
{
fanIn++;
}
}
if (fanIn == 0 && fanOut > 0)
{
roots.add(vertex);
}
int diff = fanOut - fanIn;
if (diff > maxDiff)
{
maxDiff = diff;
best = vertex;
}
}
}
if (roots.isEmpty() && best != null)
{
roots.add(best);
}
return roots;
}
/**
*
* @param cell
* @return
*/
public Object[] getEdges(Object cell)
{
mxIGraphModel model = graph.getModel();
boolean isCollapsed = graph.isCellCollapsed(cell);
List<Object> edges = new ArrayList<Object>();
int childCount = model.getChildCount(cell);
for (int i = 0; i < childCount; i++)
{
Object child = model.getChildAt(cell, i);
if (isCollapsed || !graph.isCellVisible(child))
{
edges.addAll(Arrays.asList(mxGraphModel.getEdges(model, child,
true, true, false)));
}
}
edges.addAll(Arrays.asList(mxGraphModel.getEdges(model, cell, true,
true, false)));
List<Object> result = new ArrayList<Object>(edges.size());
Iterator<Object> it = edges.iterator();
while (it.hasNext())
{
Object edge = it.next();
mxCellState state = graph.getView().getState(edge);
Object source = (state != null) ? state.getVisibleTerminal(true)
: graph.getView().getVisibleTerminal(edge, true);
Object target = (state != null) ? state.getVisibleTerminal(false)
: graph.getView().getVisibleTerminal(edge, false);
if (((source != target) && ((target == cell && (parent == null || graph
.isValidAncestor(source, parent, traverseAncestors))) || (source == cell && (parent == null || graph
.isValidAncestor(target, parent, traverseAncestors))))))
{
result.add(edge);
}
}
return result.toArray();
}
/**
* The API method used to exercise the layout upon the graph description
* and produce a separate description of the vertex position and edge
* routing changes made.
*/
public void run(Object parent)
{
// Separate out unconnected hierarchies
List<Set<Object>> hierarchyVertices = new ArrayList<Set<Object>>();
Set<Object> allVertexSet = new LinkedHashSet<Object>();
if (this.roots == null && parent != null)
{
Set<Object> filledVertexSet = filterDescendants(parent);
this.roots = new ArrayList<Object>();
while (!filledVertexSet.isEmpty())
{
List<Object> candidateRoots = findRoots(parent, filledVertexSet);
for (Object root : candidateRoots)
{
Set<Object> vertexSet = new LinkedHashSet<Object>();
hierarchyVertices.add(vertexSet);
traverse(root, true, null, allVertexSet, vertexSet,
hierarchyVertices, filledVertexSet);
}
this.roots.addAll(candidateRoots);
}
}
else
{
// Find vertex set as directed traversal from roots
for (int i = 0; i < roots.size(); i++)
{
Set<Object> vertexSet = new LinkedHashSet<Object>();
hierarchyVertices.add(vertexSet);
traverse(roots.get(i), true, null, allVertexSet, vertexSet,
hierarchyVertices, null);
}
}
// Iterate through the result removing parents who have children in this layout
// Perform a layout for each separate hierarchy
// Track initial coordinate x-positioning
double initialX = 0;
Iterator<Set<Object>> iter = hierarchyVertices.iterator();
while (iter.hasNext())
{
Set<Object> vertexSet = iter.next();
this.model = new mxGraphHierarchyModel(this, vertexSet.toArray(),
roots, parent);
cycleStage(parent);
layeringStage();
crossingStage(parent);
initialX = placementStage(initialX, parent);
}
}
/**
* Creates a set of descendant cells
* @param cell The cell whose descendants are to be calculated
* @return the descendants of the cell (not the cell)
*/
public Set<Object> filterDescendants(Object cell)
{
mxIGraphModel model = graph.getModel();
Set<Object> result = new LinkedHashSet<Object>();
if (model.isVertex(cell) && cell != this.parent && graph.isCellVisible(cell))
{
result.add(cell);
}
if (this.traverseAncestors || cell == this.parent
&& graph.isCellVisible(cell))
{
int childCount = model.getChildCount(cell);
for (int i = 0; i < childCount; i++)
{
Object child = model.getChildAt(cell, i);
result.addAll(filterDescendants(child));
}
}
return result;
}
/**
* 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 <mxCell> 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 edge Optional <mxCell> that represents the incoming edge. This is
* null for the first step of the traversal.
* @param allVertices Array of cell paths for the visited cells.
*/
protected void traverse(Object vertex, boolean directed, Object edge,
Set<Object> allVertices, Set<Object> currentComp,
List<Set<Object>> hierarchyVertices, Set<Object> filledVertexSet)
{
mxGraphView view = graph.getView();
mxIGraphModel model = graph.getModel();
if (vertex != null && allVertices != null)
{
// Has this vertex been seen before in any traversal
// And if the filled vertex set is populated, only
// process vertices in that it contains
if (!allVertices.contains(vertex)
&& (filledVertexSet == null ? true : filledVertexSet
.contains(vertex)))
{
currentComp.add(vertex);
allVertices.add(vertex);
if (filledVertexSet != null)
{
filledVertexSet.remove(vertex);
}
int edgeCount = model.getEdgeCount(vertex);
if (edgeCount > 0)
{
for (int i = 0; i < edgeCount; i++)
{
Object e = model.getEdgeAt(vertex, i);
boolean isSource = view.getVisibleTerminal(e, true) == vertex;
if (!directed || isSource)
{
Object next = view.getVisibleTerminal(e, !isSource);
traverse(next, directed, e, allVertices,
currentComp, hierarchyVertices,
filledVertexSet);
}
}
}
}
else
{
if (!currentComp.contains(vertex))
{
// We've seen this vertex before, but not in the current component
// This component and the one it's in need to be merged
Set<Object> matchComp = null;
for (Set<Object> comp : hierarchyVertices)
{
if (comp.contains(vertex))
{
currentComp.addAll(comp);
matchComp = comp;
break;
}
}
if (matchComp != null)
{
hierarchyVertices.remove(matchComp);
}
}
}
}
}
/**
* Executes the cycle stage. This implementation uses the
* mxMinimumCycleRemover.
*/
public void cycleStage(Object parent)
{
mxHierarchicalLayoutStage cycleStage = new mxMinimumCycleRemover(this);
cycleStage.execute(parent);
}
/**
* Implements first stage of a Sugiyama layout.
*/
public void layeringStage()
{
model.initialRank();
model.fixRanks();
}
/**
* Executes the crossing stage using mxMedianHybridCrossingReduction.
*/
public void crossingStage(Object parent)
{
mxHierarchicalLayoutStage crossingStage = new mxMedianHybridCrossingReduction(
this);
crossingStage.execute(parent);
}
/**
* Executes the placement stage using mxCoordinateAssignment.
*/
public double placementStage(double initialX, Object parent)
{
mxCoordinateAssignment placementStage = new mxCoordinateAssignment(
this, intraCellSpacing, interRankCellSpacing, orientation,
initialX, parallelEdgeSpacing);
placementStage.setFineTuning(fineTuning);
placementStage.execute(parent);
return placementStage.getLimitX() + interHierarchySpacing;
}
/**
* Returns the resizeParent flag.
*/
public boolean isResizeParent()
{
return resizeParent;
}
/**
* Sets the resizeParent flag.
*/
public void setResizeParent(boolean value)
{
resizeParent = value;
}
/**
* Returns the moveParent flag.
*/
public boolean isMoveParent()
{
return moveParent;
}
/**
* Sets the moveParent flag.
*/
public void setMoveParent(boolean value)
{
moveParent = value;
}
/**
* Returns parentBorder.
*/
public int getParentBorder()
{
return parentBorder;
}
/**
* Sets parentBorder.
*/
public void setParentBorder(int value)
{
parentBorder = value;
}
/**
* @return Returns the intraCellSpacing.
*/
public double getIntraCellSpacing()
{
return intraCellSpacing;
}
/**
* @param intraCellSpacing
* The intraCellSpacing to set.
*/
public void setIntraCellSpacing(double intraCellSpacing)
{
this.intraCellSpacing = intraCellSpacing;
}
/**
* @return Returns the interRankCellSpacing.
*/
public double getInterRankCellSpacing()
{
return interRankCellSpacing;
}
/**
* @param interRankCellSpacing
* The interRankCellSpacing to set.
*/
public void setInterRankCellSpacing(double interRankCellSpacing)
{
this.interRankCellSpacing = interRankCellSpacing;
}
/**
* @return Returns the orientation.
*/
public int getOrientation()
{
return orientation;
}
/**
* @param orientation
* The orientation to set.
*/
public void setOrientation(int orientation)
{
this.orientation = orientation;
}
/**
* @return Returns the interHierarchySpacing.
*/
public double getInterHierarchySpacing()
{
return interHierarchySpacing;
}
/**
* @param interHierarchySpacing
* The interHierarchySpacing to set.
*/
public void setInterHierarchySpacing(double interHierarchySpacing)
{
this.interHierarchySpacing = interHierarchySpacing;
}
public double getParallelEdgeSpacing()
{
return parallelEdgeSpacing;
}
public void setParallelEdgeSpacing(double parallelEdgeSpacing)
{
this.parallelEdgeSpacing = parallelEdgeSpacing;
}
/**
* @return Returns the fineTuning.
*/
public boolean isFineTuning()
{
return fineTuning;
}
/**
* @param fineTuning
* The fineTuning to set.
*/
public void setFineTuning(boolean fineTuning)
{
this.fineTuning = fineTuning;
}
/**
*
*/
public boolean isDisableEdgeStyle()
{
return disableEdgeStyle;
}
/**
*
* @param disableEdgeStyle
*/
public void setDisableEdgeStyle(boolean disableEdgeStyle)
{
this.disableEdgeStyle = disableEdgeStyle;
}
/**
* Returns <code>Hierarchical</code>, the name of this algorithm.
*/
public String toString()
{
return "Hierarchical";
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2005, David Benson
*
* All rights reserved.
*
* This file is licensed under the JGraph software license, a copy of which
* will have been provided to you in the file LICENSE at the root of your
* installation directory. If you are unable to locate this file please
* contact JGraph sales for another copy.
*/
package com.mxgraph.layout.hierarchical.stage;
/**
* The specific layout interface for hierarchical layouts. It adds a
* <code>run</code> method with a parameter for the hierarchical layout model
* that is shared between the layout stages.
*/
public interface mxHierarchicalLayoutStage
{
/**
* Takes the graph detail and configuration information within the facade
* and creates the resulting laid out graph within that facade for further
* use.
*/
public void execute(Object parent);
}

View file

@ -0,0 +1,655 @@
/*
* Copyright (c) 2005-2009, JGraph Ltd
*
* All rights reserved.
*
* This file is licensed under the JGraph software license, a copy of which
* will have been provided to you in the file LICENSE at the root of your
* installation directory. If you are unable to locate this file please
* contact JGraph sales for another copy.
*/
package com.mxgraph.layout.hierarchical.stage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.mxgraph.layout.hierarchical.mxHierarchicalLayout;
import com.mxgraph.layout.hierarchical.model.mxGraphAbstractHierarchyCell;
import com.mxgraph.layout.hierarchical.model.mxGraphHierarchyModel;
import com.mxgraph.layout.hierarchical.model.mxGraphHierarchyRank;
/**
* Performs a vertex ordering within ranks as described by Gansner et al 1993
*/
public class mxMedianHybridCrossingReduction implements
mxHierarchicalLayoutStage/*, JGraphLayout.Stoppable*/
{
/**
* Reference to the enclosing layout algorithm
*/
protected mxHierarchicalLayout layout;
/**
* The maximum number of iterations to perform whilst reducing edge
* crossings
*/
protected int maxIterations = 24;
/**
* Stores each rank as a collection of cells in the best order found for
* each layer so far
*/
protected mxGraphAbstractHierarchyCell[][] nestedBestRanks = null;
/**
* The total number of crossings found in the best configuration so far
*/
protected int currentBestCrossings = 0;
protected int iterationsWithoutImprovement = 0;
protected int maxNoImprovementIterations = 2;
/**
* Constructor that has the roots specified
*/
public mxMedianHybridCrossingReduction(mxHierarchicalLayout layout)
{
this.layout = layout;
}
/**
* Performs a vertex ordering within ranks as described by Gansner et al
* 1993
*/
public void execute(Object parent)
{
mxGraphHierarchyModel model = layout.getModel();
// Stores initial ordering as being the best one found so far
nestedBestRanks = new mxGraphAbstractHierarchyCell[model.ranks.size()][];
for (int i = 0; i < nestedBestRanks.length; i++)
{
mxGraphHierarchyRank rank = model.ranks.get(new Integer(i));
nestedBestRanks[i] = new mxGraphAbstractHierarchyCell[rank.size()];
rank.toArray(nestedBestRanks[i]);
}
iterationsWithoutImprovement = 0;
currentBestCrossings = calculateCrossings(model);
for (int i = 0; i < maxIterations
&& iterationsWithoutImprovement < maxNoImprovementIterations; i++)
{
weightedMedian(i, model);
transpose(i, model);
int candidateCrossings = calculateCrossings(model);
if (candidateCrossings < currentBestCrossings)
{
currentBestCrossings = candidateCrossings;
iterationsWithoutImprovement = 0;
// Store the current rankings as the best ones
for (int j = 0; j < nestedBestRanks.length; j++)
{
mxGraphHierarchyRank rank = model.ranks.get(new Integer(j));
Iterator<mxGraphAbstractHierarchyCell> iter = rank
.iterator();
for (int k = 0; k < rank.size(); k++)
{
mxGraphAbstractHierarchyCell cell = iter
.next();
nestedBestRanks[j][cell.getGeneralPurposeVariable(j)] = cell;
}
}
}
else
{
// Increase count of iterations where we haven't improved the
// layout
iterationsWithoutImprovement++;
// Restore the best values to the cells
for (int j = 0; j < nestedBestRanks.length; j++)
{
mxGraphHierarchyRank rank = model.ranks.get(new Integer(j));
Iterator<mxGraphAbstractHierarchyCell> iter = rank
.iterator();
for (int k = 0; k < rank.size(); k++)
{
mxGraphAbstractHierarchyCell cell = iter
.next();
cell.setGeneralPurposeVariable(j, k);
}
}
}
if (currentBestCrossings == 0)
{
// Do nothing further
break;
}
}
// Store the best rankings but in the model
Map<Integer, mxGraphHierarchyRank> ranks = new LinkedHashMap<Integer, mxGraphHierarchyRank>(
model.maxRank + 1);
mxGraphHierarchyRank[] rankList = new mxGraphHierarchyRank[model.maxRank + 1];
for (int i = 0; i < model.maxRank + 1; i++)
{
rankList[i] = new mxGraphHierarchyRank();
ranks.put(new Integer(i), rankList[i]);
}
for (int i = 0; i < nestedBestRanks.length; i++)
{
for (int j = 0; j < nestedBestRanks[i].length; j++)
{
rankList[i].add(nestedBestRanks[i][j]);
}
}
model.ranks = ranks;
}
/**
* Calculates the total number of edge crossing in the current graph
*
* @param model
* the internal model describing the hierarchy
* @return the current number of edge crossings in the hierarchy graph model
* in the current candidate layout
*/
private int calculateCrossings(mxGraphHierarchyModel model)
{
// The intra-rank order of cells are stored within the temp variables
// on cells
int numRanks = model.ranks.size();
int totalCrossings = 0;
for (int i = 1; i < numRanks; i++)
{
totalCrossings += calculateRankCrossing(i, model);
}
return totalCrossings;
}
/**
* Calculates the number of edges crossings between the specified rank and
* the rank below it
*
* @param i
* the topmost rank of the pair ( higher rank value )
* @param model
* the internal hierarchy model of the graph
* @return the number of edges crossings with the rank beneath
*/
protected int calculateRankCrossing(int i, mxGraphHierarchyModel model)
{
int totalCrossings = 0;
mxGraphHierarchyRank rank = model.ranks.get(new Integer(i));
mxGraphHierarchyRank previousRank = model.ranks.get(new Integer(i - 1));
// Create an array of connections between these two levels
int currentRankSize = rank.size();
int previousRankSize = previousRank.size();
int[][] connections = new int[currentRankSize][previousRankSize];
// Iterate over the top rank and fill in the connection information
Iterator<mxGraphAbstractHierarchyCell> iter = rank.iterator();
while (iter.hasNext())
{
mxGraphAbstractHierarchyCell cell = iter.next();
int rankPosition = cell.getGeneralPurposeVariable(i);
Collection<mxGraphAbstractHierarchyCell> connectedCells = cell
.getPreviousLayerConnectedCells(i);
Iterator<mxGraphAbstractHierarchyCell> iter2 = connectedCells
.iterator();
while (iter2.hasNext())
{
mxGraphAbstractHierarchyCell connectedCell = iter2.next();
int otherCellRankPosition = connectedCell
.getGeneralPurposeVariable(i - 1);
connections[rankPosition][otherCellRankPosition] = 201207;
}
}
// Iterate through the connection matrix, crossing edges are
// indicated by other connected edges with a greater rank position
// on one rank and lower position on the other
for (int j = 0; j < currentRankSize; j++)
{
for (int k = 0; k < previousRankSize; k++)
{
if (connections[j][k] == 201207)
{
// Draw a grid of connections, crossings are top right
// and lower left from this crossing pair
for (int j2 = j + 1; j2 < currentRankSize; j2++)
{
for (int k2 = 0; k2 < k; k2++)
{
if (connections[j2][k2] == 201207)
{
totalCrossings++;
}
}
}
for (int j2 = 0; j2 < j; j2++)
{
for (int k2 = k + 1; k2 < previousRankSize; k2++)
{
if (connections[j2][k2] == 201207)
{
totalCrossings++;
}
}
}
}
}
}
return totalCrossings / 2;
}
/**
* Takes each possible adjacent cell pair on each rank and checks if
* swapping them around reduces the number of crossing
*
* @param mainLoopIteration
* the iteration number of the main loop
* @param model
* the internal model describing the hierarchy
*/
private void transpose(int mainLoopIteration, mxGraphHierarchyModel model)
{
boolean improved = true;
// Track the number of iterations in case of looping
int count = 0;
int maxCount = 10;
while (improved && count++ < maxCount)
{
// On certain iterations allow allow swapping of cell pairs with
// equal edge crossings switched or not switched. This help to
// nudge a stuck layout into a lower crossing total.
boolean nudge = mainLoopIteration % 2 == 1 && count % 2 == 1;
improved = false;
for (int i = 0; i < model.ranks.size(); i++)
{
mxGraphHierarchyRank rank = model.ranks.get(new Integer(i));
mxGraphAbstractHierarchyCell[] orderedCells = new mxGraphAbstractHierarchyCell[rank
.size()];
Iterator<mxGraphAbstractHierarchyCell> iter = rank.iterator();
for (int j = 0; j < orderedCells.length; j++)
{
mxGraphAbstractHierarchyCell cell = iter
.next();
orderedCells[cell.getGeneralPurposeVariable(i)] = cell;
}
List<mxGraphAbstractHierarchyCell> leftCellAboveConnections = null;
List<mxGraphAbstractHierarchyCell> leftCellBelowConnections = null;
List<mxGraphAbstractHierarchyCell> rightCellAboveConnections = null;
List<mxGraphAbstractHierarchyCell> rightCellBelowConnections = null;
int[] leftAbovePositions = null;
int[] leftBelowPositions = null;
int[] rightAbovePositions = null;
int[] rightBelowPositions = null;
mxGraphAbstractHierarchyCell leftCell = null;
mxGraphAbstractHierarchyCell rightCell = null;
for (int j = 0; j < (rank.size() - 1); j++)
{
// For each intra-rank adjacent pair of cells
// see if swapping them around would reduce the
// number of edges crossing they cause in total
// On every cell pair except the first on each rank, we
// can save processing using the previous values for the
// right cell on the new left cell
if (j == 0)
{
leftCell = orderedCells[j];
leftCellAboveConnections = leftCell
.getNextLayerConnectedCells(i);
leftCellBelowConnections = leftCell
.getPreviousLayerConnectedCells(i);
leftAbovePositions = new int[leftCellAboveConnections
.size()];
leftBelowPositions = new int[leftCellBelowConnections
.size()];
for (int k = 0; k < leftAbovePositions.length; k++)
{
leftAbovePositions[k] = leftCellAboveConnections
.get(k).getGeneralPurposeVariable(i + 1);
}
for (int k = 0; k < leftBelowPositions.length; k++)
{
leftBelowPositions[k] = (leftCellBelowConnections
.get(k)).getGeneralPurposeVariable(i - 1);
}
}
else
{
leftCellAboveConnections = rightCellAboveConnections;
leftCellBelowConnections = rightCellBelowConnections;
leftAbovePositions = rightAbovePositions;
leftBelowPositions = rightBelowPositions;
leftCell = rightCell;
}
rightCell = orderedCells[j + 1];
rightCellAboveConnections = rightCell
.getNextLayerConnectedCells(i);
rightCellBelowConnections = rightCell
.getPreviousLayerConnectedCells(i);
rightAbovePositions = new int[rightCellAboveConnections
.size()];
rightBelowPositions = new int[rightCellBelowConnections
.size()];
for (int k = 0; k < rightAbovePositions.length; k++)
{
rightAbovePositions[k] = (rightCellAboveConnections
.get(k)).getGeneralPurposeVariable(i + 1);
}
for (int k = 0; k < rightBelowPositions.length; k++)
{
rightBelowPositions[k] = (rightCellBelowConnections
.get(k)).getGeneralPurposeVariable(i - 1);
}
int totalCurrentCrossings = 0;
int totalSwitchedCrossings = 0;
for (int k = 0; k < leftAbovePositions.length; k++)
{
for (int ik = 0; ik < rightAbovePositions.length; ik++)
{
if (leftAbovePositions[k] > rightAbovePositions[ik])
{
totalCurrentCrossings++;
}
if (leftAbovePositions[k] < rightAbovePositions[ik])
{
totalSwitchedCrossings++;
}
}
}
for (int k = 0; k < leftBelowPositions.length; k++)
{
for (int ik = 0; ik < rightBelowPositions.length; ik++)
{
if (leftBelowPositions[k] > rightBelowPositions[ik])
{
totalCurrentCrossings++;
}
if (leftBelowPositions[k] < rightBelowPositions[ik])
{
totalSwitchedCrossings++;
}
}
}
if ((totalSwitchedCrossings < totalCurrentCrossings)
|| (totalSwitchedCrossings == totalCurrentCrossings && nudge))
{
int temp = leftCell.getGeneralPurposeVariable(i);
leftCell.setGeneralPurposeVariable(i, rightCell
.getGeneralPurposeVariable(i));
rightCell.setGeneralPurposeVariable(i, temp);
// With this pair exchanged we have to switch all of
// values for the left cell to the right cell so the
// next iteration for this rank uses it as the left
// cell again
rightCellAboveConnections = leftCellAboveConnections;
rightCellBelowConnections = leftCellBelowConnections;
rightAbovePositions = leftAbovePositions;
rightBelowPositions = leftBelowPositions;
rightCell = leftCell;
if (!nudge)
{
// Don't count nudges as improvement or we'll end
// up stuck in two combinations and not finishing
// as early as we should
improved = true;
}
}
}
}
}
}
/**
* Sweeps up or down the layout attempting to minimise the median placement
* of connected cells on adjacent ranks
*
* @param iteration
* the iteration number of the main loop
* @param model
* the internal model describing the hierarchy
*/
private void weightedMedian(int iteration, mxGraphHierarchyModel model)
{
// Reverse sweep direction each time through this method
boolean downwardSweep = (iteration % 2 == 0);
if (downwardSweep)
{
for (int j = model.maxRank - 1; j >= 0; j--)
{
medianRank(j, downwardSweep);
}
}
else
{
for (int j = 1; j < model.maxRank; j++)
{
medianRank(j, downwardSweep);
}
}
}
/**
* Attempts to minimise the median placement of connected cells on this rank
* and one of the adjacent ranks
*
* @param rankValue
* the layer number of this rank
* @param downwardSweep
* whether or not this is a downward sweep through the graph
*/
private void medianRank(int rankValue, boolean downwardSweep)
{
int numCellsForRank = nestedBestRanks[rankValue].length;
ArrayList<MedianCellSorter> medianValues = new ArrayList<MedianCellSorter>(numCellsForRank);
boolean[] reservedPositions = new boolean[numCellsForRank];
for (int i = 0; i < numCellsForRank; i++)
{
mxGraphAbstractHierarchyCell cell = nestedBestRanks[rankValue][i];
MedianCellSorter sorterEntry = new MedianCellSorter();
sorterEntry.cell = cell;
// Flip whether or not equal medians are flipped on up and down
// sweeps
// todo reimplement some kind of nudging depending on sweep
//nudge = !downwardSweep;
Collection<mxGraphAbstractHierarchyCell> nextLevelConnectedCells;
if (downwardSweep)
{
nextLevelConnectedCells = cell
.getNextLayerConnectedCells(rankValue);
}
else
{
nextLevelConnectedCells = cell
.getPreviousLayerConnectedCells(rankValue);
}
int nextRankValue;
if (downwardSweep)
{
nextRankValue = rankValue + 1;
}
else
{
nextRankValue = rankValue - 1;
}
if (nextLevelConnectedCells != null
&& nextLevelConnectedCells.size() != 0)
{
sorterEntry.medianValue = medianValue(
nextLevelConnectedCells, nextRankValue);
medianValues.add(sorterEntry);
}
else
{
// Nodes with no adjacent vertices are flagged in the reserved array
// to indicate they should be left in their current position.
reservedPositions[cell.getGeneralPurposeVariable(rankValue)] = true;
}
}
MedianCellSorter[] medianArray = medianValues.toArray(new MedianCellSorter[medianValues.size()]);
Arrays.sort(medianArray);
// Set the new position of each node within the rank using
// its temp variable
int index = 0;
for (int i = 0; i < numCellsForRank; i++)
{
if (!reservedPositions[i])
{
MedianCellSorter wrapper = medianArray[index++];
wrapper.cell.setGeneralPurposeVariable(rankValue, i);
}
}
}
/**
* Calculates the median rank order positioning for the specified cell using
* the connected cells on the specified rank
*
* @param connectedCells
* the cells on the specified rank connected to the specified
* cell
* @param rankValue
* the rank that the connected cell lie upon
* @return the median rank ordering value of the connected cells
*/
private double medianValue(
Collection<mxGraphAbstractHierarchyCell> connectedCells,
int rankValue)
{
double[] medianValues = new double[connectedCells.size()];
int arrayCount = 0;
Iterator<mxGraphAbstractHierarchyCell> iter = connectedCells.iterator();
while (iter.hasNext())
{
medianValues[arrayCount++] = (iter
.next()).getGeneralPurposeVariable(rankValue);
}
Arrays.sort(medianValues);
if (arrayCount % 2 == 1)
{
// For odd numbers of adjacent vertices return the median
return medianValues[arrayCount / 2];
}
else if (arrayCount == 2)
{
return ((medianValues[0] + medianValues[1]) / 2.0);
}
else
{
int medianPoint = arrayCount / 2;
double leftMedian = medianValues[medianPoint - 1] - medianValues[0];
double rightMedian = medianValues[arrayCount - 1]
- medianValues[medianPoint];
return (medianValues[medianPoint - 1] * rightMedian + medianValues[medianPoint]
* leftMedian)
/ (leftMedian + rightMedian);
}
}
/**
* A utility class used to track cells whilst sorting occurs on the median
* values. Does not violate (x.compareTo(y)==0) == (x.equals(y))
*/
protected class MedianCellSorter implements Comparable<Object>
{
/**
* The median value of the cell stored
*/
public double medianValue = 0.0;
/**
* The cell whose median value is being calculated
*/
mxGraphAbstractHierarchyCell cell = null;
/**
* comparator on the medianValue
*
* @param arg0
* the object to be compared to
* @return the standard return you would expect when comparing two
* double
*/
public int compareTo(Object arg0)
{
if (arg0 instanceof MedianCellSorter)
{
if (medianValue < ((MedianCellSorter) arg0).medianValue)
{
return -1;
}
else if (medianValue > ((MedianCellSorter) arg0).medianValue)
{
return 1;
}
}
return 0;
}
}
}

View file

@ -0,0 +1,155 @@
/*
* Copyright (c) 2005, David Benson
*
* All rights reserved.
*
* This file is licensed under the JGraph software license, a copy of which
* will have been provided to you in the file LICENSE at the root of your
* installation directory. If you are unable to locate this file please
* contact JGraph sales for another copy.
*/
package com.mxgraph.layout.hierarchical.stage;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import com.mxgraph.layout.hierarchical.mxHierarchicalLayout;
import com.mxgraph.layout.hierarchical.model.mxGraphHierarchyEdge;
import com.mxgraph.layout.hierarchical.model.mxGraphHierarchyModel;
import com.mxgraph.layout.hierarchical.model.mxGraphHierarchyNode;
import com.mxgraph.view.mxGraph;
/**
* An implementation of the first stage of the Sugiyama layout. Straightforward
* longest path calculation of layer assignment
*/
public class mxMinimumCycleRemover implements mxHierarchicalLayoutStage
{
/**
* Reference to the enclosing layout algorithm
*/
protected mxHierarchicalLayout layout;
/**
* Constructor that has the roots specified
*/
public mxMinimumCycleRemover(mxHierarchicalLayout layout)
{
this.layout = layout;
}
/**
* Produces the layer assignmment using the graph information specified
*/
public void execute(Object parent)
{
mxGraphHierarchyModel model = layout.getModel();
final Set<mxGraphHierarchyNode> seenNodes = new HashSet<mxGraphHierarchyNode>();
final Set<mxGraphHierarchyNode> unseenNodes = new HashSet<mxGraphHierarchyNode>(
model.getVertexMapper().values());
// Perform a dfs through the internal model. If a cycle is found,
// reverse it.
mxGraphHierarchyNode[] rootsArray = null;
if (model.roots != null)
{
Object[] modelRoots = model.roots.toArray();
rootsArray = new mxGraphHierarchyNode[modelRoots.length];
for (int i = 0; i < modelRoots.length; i++)
{
Object node = modelRoots[i];
mxGraphHierarchyNode internalNode = model
.getVertexMapper().get(node);
rootsArray[i] = internalNode;
}
}
model.visit(new mxGraphHierarchyModel.CellVisitor()
{
public void visit(mxGraphHierarchyNode parent,
mxGraphHierarchyNode cell,
mxGraphHierarchyEdge connectingEdge, int layer, int seen)
{
// Check if the cell is in it's own ancestor list, if so
// invert the connecting edge and reverse the target/source
// relationship to that edge in the parent and the cell
if ((cell)
.isAncestor(parent))
{
connectingEdge.invert();
parent.connectsAsSource.remove(connectingEdge);
parent.connectsAsTarget.add(connectingEdge);
cell.connectsAsTarget.remove(connectingEdge);
cell.connectsAsSource.add(connectingEdge);
}
seenNodes.add(cell);
unseenNodes.remove(cell);
}
}, rootsArray, true, null);
Set<Object> possibleNewRoots = null;
if (unseenNodes.size() > 0)
{
possibleNewRoots = new HashSet<Object>(unseenNodes);
}
// If there are any nodes that should be nodes that the dfs can miss
// these need to be processed with the dfs and the roots assigned
// correctly to form a correct internal model
Set<mxGraphHierarchyNode> seenNodesCopy = new HashSet<mxGraphHierarchyNode>(
seenNodes);
// Pick a random cell and dfs from it
mxGraphHierarchyNode[] unseenNodesArray = new mxGraphHierarchyNode[1];
unseenNodes.toArray(unseenNodesArray);
model.visit(new mxGraphHierarchyModel.CellVisitor()
{
public void visit(mxGraphHierarchyNode parent,
mxGraphHierarchyNode cell,
mxGraphHierarchyEdge connectingEdge, int layer, int seen)
{
// Check if the cell is in it's own ancestor list, if so
// invert the connecting edge and reverse the target/source
// relationship to that edge in the parent and the cell
if ((cell)
.isAncestor(parent))
{
connectingEdge.invert();
parent.connectsAsSource.remove(connectingEdge);
parent.connectsAsTarget.add(connectingEdge);
cell.connectsAsTarget.remove(connectingEdge);
cell.connectsAsSource.add(connectingEdge);
}
seenNodes.add(cell);
unseenNodes.remove(cell);
}
}, unseenNodesArray, true, seenNodesCopy);
mxGraph graph = layout.getGraph();
if (possibleNewRoots != null && possibleNewRoots.size() > 0)
{
Iterator<Object> iter = possibleNewRoots.iterator();
List<Object> roots = model.roots;
while (iter.hasNext())
{
mxGraphHierarchyNode node = (mxGraphHierarchyNode) iter.next();
Object realNode = node.cell;
int numIncomingEdges = graph.getIncomingEdges(realNode).length;
if (numIncomingEdges == 0)
{
roots.add(realNode);
}
}
}
}
}

View file

@ -0,0 +1,267 @@
package com.mxgraph.layout;
import java.util.ArrayList;
import java.util.List;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.view.mxGraph;
public class mxCircleLayout extends mxGraphLayout
{
/**
* Integer specifying the size of the radius. Default is 100.
*/
protected double radius;
/**
* Boolean specifying if the circle should be moved to the top,
* left corner specified by x0 and y0. Default is false.
*/
protected boolean moveCircle = true;
/**
* Integer specifying the left coordinate of the circle.
* Default is 0.
*/
protected double x0 = 0;
/**
* Integer specifying the top coordinate of the circle.
* Default is 0.
*/
protected double y0 = 0;
/**
* Specifies if all edge points of traversed edges should be removed.
* Default is true.
*/
protected boolean resetEdges = false;
/**
* Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
* modified by the result. Default is true.
*/
protected boolean disableEdgeStyle = true;
/**
* Constructs a new stack layout layout for the specified graph,
* spacing, orientation and offset.
*/
public mxCircleLayout(mxGraph graph)
{
this(graph, 100);
}
/**
* Constructs a new stack layout layout for the specified graph,
* spacing, orientation and offset.
*/
public mxCircleLayout(mxGraph graph, double radius)
{
super(graph);
this.radius = radius;
}
/**
* @return the radius
*/
public double getRadius()
{
return radius;
}
/**
* @param radius the radius to set
*/
public void setRadius(double radius)
{
this.radius = radius;
}
/**
* @return the moveCircle
*/
public boolean isMoveCircle()
{
return moveCircle;
}
/**
* @param moveCircle the moveCircle to set
*/
public void setMoveCircle(boolean moveCircle)
{
this.moveCircle = moveCircle;
}
/**
* @return the x0
*/
public double getX0()
{
return x0;
}
/**
* @param x0 the x0 to set
*/
public void setX0(double x0)
{
this.x0 = x0;
}
/**
* @return the y0
*/
public double getY0()
{
return y0;
}
/**
* @param y0 the y0 to set
*/
public void setY0(double y0)
{
this.y0 = y0;
}
/**
* @return the resetEdges
*/
public boolean isResetEdges()
{
return resetEdges;
}
/**
* @param resetEdges the resetEdges to set
*/
public void setResetEdges(boolean resetEdges)
{
this.resetEdges = resetEdges;
}
/**
* @return the disableEdgeStyle
*/
public boolean isDisableEdgeStyle()
{
return disableEdgeStyle;
}
/**
* @param disableEdgeStyle the disableEdgeStyle to set
*/
public void setDisableEdgeStyle(boolean disableEdgeStyle)
{
this.disableEdgeStyle = disableEdgeStyle;
}
/*
* (non-Javadoc)
* @see com.mxgraph.layout.mxIGraphLayout#execute(java.lang.Object)
*/
public void execute(Object parent)
{
mxIGraphModel model = graph.getModel();
// Moves the vertices to build a circle. Makes sure the
// radius is large enough for the vertices to not
// overlap
model.beginUpdate();
try
{
// Gets all vertices inside the parent and finds
// the maximum dimension of the largest vertex
double max = 0;
Double top = null;
Double left = null;
List<Object> vertices = new ArrayList<Object>();
int childCount = model.getChildCount(parent);
for (int i = 0; i < childCount; i++)
{
Object cell = model.getChildAt(parent, i);
if (!isVertexIgnored(cell))
{
vertices.add(cell);
mxRectangle bounds = getVertexBounds(cell);
if (top == null)
{
top = bounds.getY();
}
else
{
top = Math.min(top, bounds.getY());
}
if (left == null)
{
left = bounds.getX();
}
else
{
left = Math.min(left, bounds.getX());
}
max = Math.max(max, Math.max(bounds.getWidth(), bounds
.getHeight()));
}
else if (!isEdgeIgnored(cell))
{
if (isResetEdges())
{
graph.resetEdge(cell);
}
if (isDisableEdgeStyle())
{
setEdgeStyleEnabled(cell, false);
}
}
}
int vertexCount = vertices.size();
double r = Math.max(vertexCount * max / Math.PI, radius);
// Moves the circle to the specified origin
if (moveCircle)
{
left = x0;
top = y0;
}
circle(vertices.toArray(), r, left.doubleValue(), top.doubleValue());
}
finally
{
model.endUpdate();
}
}
/**
* Executes the circular layout for the specified array
* of vertices and the given radius.
*/
public void circle(Object[] vertices, double r, double left, double top)
{
int vertexCount = vertices.length;
double phi = 2 * Math.PI / vertexCount;
for (int i = 0; i < vertexCount; i++)
{
if (isVertexMovable(vertices[i]))
{
setVertexLocation(vertices[i],
left + r + r * Math.sin(i * phi), top + r + r
* Math.cos(i * phi));
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,149 @@
package com.mxgraph.layout;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.util.mxPoint;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxGraphView;
public class mxEdgeLabelLayout extends mxGraphLayout
{
/**
* Constructs a new stack layout layout for the specified graph,
* spacing, orientation and offset.
*/
public mxEdgeLabelLayout(mxGraph graph)
{
super(graph);
}
/*
* (non-Javadoc)
* @see com.mxgraph.layout.mxIGraphLayout#execute(java.lang.Object)
*/
public void execute(Object parent)
{
mxGraphView view = graph.getView();
mxIGraphModel model = graph.getModel();
// Gets all vertices and edges inside the parent
List<Object> edges = new ArrayList<Object>();
List<Object> vertices = new ArrayList<Object>();
int childCount = model.getChildCount(parent);
for (int i = 0; i < childCount; i++)
{
Object cell = model.getChildAt(parent, i);
mxCellState state = view.getState(cell);
if (state != null)
{
if (!isVertexIgnored(cell))
{
vertices.add(state);
}
else if (!isEdgeIgnored(cell))
{
edges.add(state);
}
}
}
placeLabels(vertices.toArray(), edges.toArray());
}
/**
*
*/
protected void placeLabels(Object[] v, Object[] e)
{
mxIGraphModel model = graph.getModel();
// Moves the vertices to build a circle. Makes sure the
// radius is large enough for the vertices to not
// overlap
model.beginUpdate();
try
{
for (int i = 0; i < e.length; i++)
{
mxCellState edge = (mxCellState) e[i];
if (edge != null && edge.getLabelBounds() != null)
{
for (int j = 0; j < v.length; j++)
{
mxCellState vertex = (mxCellState) v[j];
if (vertex != null)
{
avoid(edge, vertex);
}
}
}
}
}
finally
{
model.endUpdate();
}
}
/**
*
*/
protected void avoid(mxCellState edge, mxCellState vertex)
{
mxIGraphModel model = graph.getModel();
Rectangle labRect = edge.getLabelBounds().getRectangle();
Rectangle vRect = vertex.getRectangle();
if (labRect.intersects(vRect))
{
int dy1 = -labRect.y - labRect.height + vRect.y;
int dy2 = -labRect.y + vRect.y + vRect.height;
int dy = (Math.abs(dy1) < Math.abs(dy2)) ? dy1 : dy2;
int dx1 = -labRect.x - labRect.width + vRect.x;
int dx2 = -labRect.x + vRect.x + vRect.width;
int dx = (Math.abs(dx1) < Math.abs(dx2)) ? dx1 : dx2;
if (Math.abs(dx) < Math.abs(dy))
{
dy = 0;
}
else
{
dx = 0;
}
mxGeometry g = model.getGeometry(edge.getCell());
if (g != null)
{
g = (mxGeometry) g.clone();
if (g.getOffset() != null)
{
g.getOffset().setX(g.getOffset().getX() + dx);
g.getOffset().setY(g.getOffset().getY() + dy);
}
else
{
g.setOffset(new mxPoint(dx, dy));
}
model.setGeometry(edge.getCell(), g);
}
}
}
}

View file

@ -0,0 +1,681 @@
/**
* Copyright (c) 2007, Gaudenz Alder
*/
package com.mxgraph.layout;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.view.mxGraph;
/**
* Fast organic layout algorithm.
*/
public class mxFastOrganicLayout extends mxGraphLayout
{
/**
* Specifies if the top left corner of the input cells should be the origin
* of the layout result. Default is true.
*/
protected boolean useInputOrigin = true;
/**
* Specifies if all edge points of traversed edges should be removed.
* Default is true.
*/
protected boolean resetEdges = true;
/**
* Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
* modified by the result. Default is true.
*/
protected boolean disableEdgeStyle = true;
/**
* The force constant by which the attractive forces are divided and the
* replusive forces are multiple by the square of. The value equates to the
* average radius there is of free space around each node. Default is 50.
*/
protected double forceConstant = 50;
/**
* Cache of <forceConstant>^2 for performance.
*/
protected double forceConstantSquared = 0;
/**
* Minimal distance limit. Default is 2. Prevents of
* dividing by zero.
*/
protected double minDistanceLimit = 2;
/**
* Cached version of <minDistanceLimit> squared.
*/
protected double minDistanceLimitSquared = 0;
/**
* The maximum distance between vertices, beyond which their
* repulsion no longer has an effect
*/
protected double maxDistanceLimit = 500;
/**
* Start value of temperature. Default is 200.
*/
protected double initialTemp = 200;
/**
* Temperature to limit displacement at later stages of layout.
*/
protected double temperature = 0;
/**
* Total number of iterations to run the layout though.
*/
protected double maxIterations = 0;
/**
* Current iteration count.
*/
protected double iteration = 0;
/**
* An array of all vertices to be laid out.
*/
protected Object[] vertexArray;
/**
* An array of locally stored X co-ordinate displacements for the vertices.
*/
protected double[] dispX;
/**
* An array of locally stored Y co-ordinate displacements for the vertices.
*/
protected double[] dispY;
/**
* An array of locally stored co-ordinate positions for the vertices.
*/
protected double[][] cellLocation;
/**
* The approximate radius of each cell, nodes only.
*/
protected double[] radius;
/**
* The approximate radius squared of each cell, nodes only.
*/
protected double[] radiusSquared;
/**
* Array of booleans representing the movable states of the vertices.
*/
protected boolean[] isMoveable;
/**
* Local copy of cell neighbours.
*/
protected int[][] neighbours;
/**
* Boolean flag that specifies if the layout is allowed to run. If this is
* set to false, then the layout exits in the following iteration.
*/
protected boolean allowedToRun = true;
/**
* Maps from vertices to indices.
*/
protected Hashtable<Object, Integer> indices = new Hashtable<Object, Integer>();
/**
* Constructs a new fast organic layout for the specified graph.
*/
public mxFastOrganicLayout(mxGraph graph)
{
super(graph);
}
/**
* Returns a boolean indicating if the given <mxCell> should be ignored as a
* vertex. This returns true if the cell has no connections.
*
* @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 super.isVertexIgnored(vertex)
|| graph.getConnections(vertex).length == 0;
}
/**
*
*/
public boolean isUseInputOrigin()
{
return useInputOrigin;
}
/**
*
* @param value
*/
public void setUseInputOrigin(boolean value)
{
useInputOrigin = value;
}
/**
*
*/
public boolean isResetEdges()
{
return resetEdges;
}
/**
*
* @param value
*/
public void setResetEdges(boolean value)
{
resetEdges = value;
}
/**
*
*/
public boolean isDisableEdgeStyle()
{
return disableEdgeStyle;
}
/**
*
* @param value
*/
public void setDisableEdgeStyle(boolean value)
{
disableEdgeStyle = value;
}
/**
*
*/
public double getMaxIterations()
{
return maxIterations;
}
/**
*
* @param value
*/
public void setMaxIterations(double value)
{
maxIterations = value;
}
/**
*
*/
public double getForceConstant()
{
return forceConstant;
}
/**
*
* @param value
*/
public void setForceConstant(double value)
{
forceConstant = value;
}
/**
*
*/
public double getMinDistanceLimit()
{
return minDistanceLimit;
}
/**
*
* @param value
*/
public void setMinDistanceLimit(double value)
{
minDistanceLimit = value;
}
/**
* @return the maxDistanceLimit
*/
public double getMaxDistanceLimit()
{
return maxDistanceLimit;
}
/**
* @param maxDistanceLimit the maxDistanceLimit to set
*/
public void setMaxDistanceLimit(double maxDistanceLimit)
{
this.maxDistanceLimit = maxDistanceLimit;
}
/**
*
*/
public double getInitialTemp()
{
return initialTemp;
}
/**
*
* @param value
*/
public void setInitialTemp(double value)
{
initialTemp = value;
}
/**
* Reduces the temperature of the layout from an initial setting in a linear
* fashion to zero.
*/
protected void reduceTemperature()
{
temperature = initialTemp * (1.0 - iteration / maxIterations);
}
/* (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
}
/* (non-Javadoc)
* @see com.mxgraph.layout.mxIGraphLayout#execute(java.lang.Object)
*/
public void execute(Object parent)
{
mxIGraphModel model = graph.getModel();
// Finds the relevant vertices for the layout
Object[] vertices = graph.getChildVertices(parent);
List<Object> tmp = new ArrayList<Object>(vertices.length);
for (int i = 0; i < vertices.length; i++)
{
if (!isVertexIgnored(vertices[i]))
{
tmp.add(vertices[i]);
}
}
vertexArray = tmp.toArray();
mxRectangle initialBounds = (useInputOrigin) ? graph.getBoundsForCells(
vertexArray, false, false, true) : null;
int n = vertexArray.length;
dispX = new double[n];
dispY = new double[n];
cellLocation = new double[n][];
isMoveable = new boolean[n];
neighbours = new int[n][];
radius = new double[n];
radiusSquared = new double[n];
minDistanceLimitSquared = minDistanceLimit * minDistanceLimit;
if (forceConstant < 0.001)
{
forceConstant = 0.001;
}
forceConstantSquared = forceConstant * forceConstant;
// Create a map of vertices first. This is required for the array of
// arrays called neighbours which holds, for each vertex, a list of
// ints which represents the neighbours cells to that vertex as
// the indices into vertexArray
for (int i = 0; i < vertexArray.length; i++)
{
Object vertex = vertexArray[i];
cellLocation[i] = new double[2];
// Set up the mapping from array indices to cells
indices.put(vertex, new Integer(i));
mxRectangle bounds = getVertexBounds(vertex);
// Set the X,Y value of the internal version of the cell to
// the center point of the vertex for better positioning
double width = bounds.getWidth();
double height = bounds.getHeight();
// Randomize (0, 0) locations
double x = bounds.getX();
double y = bounds.getY();
cellLocation[i][0] = x + width / 2.0;
cellLocation[i][1] = y + height / 2.0;
radius[i] = Math.min(width, height);
radiusSquared[i] = radius[i] * radius[i];
}
// Moves cell location back to top-left from center locations used in
// algorithm, resetting the edge points is part of the transaction
model.beginUpdate();
try
{
for (int i = 0; i < n; i++)
{
dispX[i] = 0;
dispY[i] = 0;
isMoveable[i] = isVertexMovable(vertexArray[i]);
// Get lists of neighbours to all vertices, translate the cells
// obtained in indices into vertexArray and store as an array
// against the original cell index
Object[] edges = graph.getConnections(vertexArray[i], parent);
for (int k = 0; k < edges.length; k++)
{
if (isResetEdges())
{
graph.resetEdge(edges[k]);
}
if (isDisableEdgeStyle())
{
setEdgeStyleEnabled(edges[k], false);
}
}
Object[] cells = graph.getOpposites(edges, vertexArray[i]);
neighbours[i] = new int[cells.length];
for (int j = 0; j < cells.length; j++)
{
Integer index = indices.get(cells[j]);
// Check the connected cell in part of the vertex list to be
// acted on by this layout
if (index != null)
{
neighbours[i][j] = index.intValue();
}
// Else if index of the other cell doesn't correspond to
// any cell listed to be acted upon in this layout. Set
// the index to the value of this vertex (a dummy self-loop)
// so the attraction force of the edge is not calculated
else
{
neighbours[i][j] = i;
}
}
}
temperature = initialTemp;
// If max number of iterations has not been set, guess it
if (maxIterations == 0)
{
maxIterations = 20.0 * Math.sqrt(n);
}
// Main iteration loop
for (iteration = 0; iteration < maxIterations; iteration++)
{
if (!allowedToRun)
{
return;
}
// Calculate repulsive forces on all vertices
calcRepulsion();
// Calculate attractive forces through edges
calcAttraction();
calcPositions();
reduceTemperature();
}
Double minx = null;
Double miny = null;
for (int i = 0; i < vertexArray.length; i++)
{
Object vertex = vertexArray[i];
mxGeometry geo = model.getGeometry(vertex);
if (geo != null)
{
cellLocation[i][0] -= geo.getWidth() / 2.0;
cellLocation[i][1] -= geo.getHeight() / 2.0;
double x = graph.snap(cellLocation[i][0]);
double y = graph.snap(cellLocation[i][1]);
setVertexLocation(vertex, x, y);
if (minx == null)
{
minx = new Double(x);
}
else
{
minx = new Double(Math.min(minx.doubleValue(), x));
}
if (miny == null)
{
miny = new Double(y);
}
else
{
miny = new Double(Math.min(miny.doubleValue(), y));
}
}
}
// Modifies the cloned geometries in-place. Not needed
// to clone the geometries again as we're in the same
// undoable change.
double dx = (minx != null) ? -minx.doubleValue() - 1 : 0;
double dy = (miny != null) ? -miny.doubleValue() - 1 : 0;
if (initialBounds != null)
{
dx += initialBounds.getX();
dy += initialBounds.getY();
}
graph.moveCells(vertexArray, dx, dy);
}
finally
{
model.endUpdate();
}
}
/**
* Takes the displacements calculated for each cell and applies them to the
* local cache of cell positions. Limits the displacement to the current
* temperature.
*/
protected void calcPositions()
{
for (int index = 0; index < vertexArray.length; index++)
{
if (isMoveable[index])
{
// Get the distance of displacement for this node for this
// iteration
double deltaLength = Math.sqrt(dispX[index] * dispX[index]
+ dispY[index] * dispY[index]);
if (deltaLength < 0.001)
{
deltaLength = 0.001;
}
// Scale down by the current temperature if less than the
// displacement distance
double newXDisp = dispX[index] / deltaLength
* Math.min(deltaLength, temperature);
double newYDisp = dispY[index] / deltaLength
* Math.min(deltaLength, temperature);
// reset displacements
dispX[index] = 0;
dispY[index] = 0;
// Update the cached cell locations
cellLocation[index][0] += newXDisp;
cellLocation[index][1] += newYDisp;
}
}
}
/**
* Calculates the attractive forces between all laid out nodes linked by
* edges
*/
protected void calcAttraction()
{
// Check the neighbours of each vertex and calculate the attractive
// force of the edge connecting them
for (int i = 0; i < vertexArray.length; i++)
{
for (int k = 0; k < neighbours[i].length; k++)
{
// Get the index of the othe cell in the vertex array
int j = neighbours[i][k];
// Do not proceed self-loops
if (i != j)
{
double xDelta = cellLocation[i][0] - cellLocation[j][0];
double yDelta = cellLocation[i][1] - cellLocation[j][1];
// The distance between the nodes
double deltaLengthSquared = xDelta * xDelta + yDelta
* yDelta - radiusSquared[i] - radiusSquared[j];
if (deltaLengthSquared < minDistanceLimitSquared)
{
deltaLengthSquared = minDistanceLimitSquared;
}
double deltaLength = Math.sqrt(deltaLengthSquared);
double force = (deltaLengthSquared) / forceConstant;
double displacementX = (xDelta / deltaLength) * force;
double displacementY = (yDelta / deltaLength) * force;
if (isMoveable[i])
{
this.dispX[i] -= displacementX;
this.dispY[i] -= displacementY;
}
if (isMoveable[j])
{
dispX[j] += displacementX;
dispY[j] += displacementY;
}
}
}
}
}
/**
* Calculates the repulsive forces between all laid out nodes
*/
protected void calcRepulsion()
{
int vertexCount = vertexArray.length;
for (int i = 0; i < vertexCount; i++)
{
for (int j = i; j < vertexCount; j++)
{
// Exits if the layout is no longer allowed to run
if (!allowedToRun)
{
return;
}
if (j != i)
{
double xDelta = cellLocation[i][0] - cellLocation[j][0];
double yDelta = cellLocation[i][1] - cellLocation[j][1];
if (xDelta == 0)
{
xDelta = 0.01 + Math.random();
}
if (yDelta == 0)
{
yDelta = 0.01 + Math.random();
}
// Distance between nodes
double deltaLength = Math.sqrt((xDelta * xDelta)
+ (yDelta * yDelta));
double deltaLengthWithRadius = deltaLength - radius[i]
- radius[j];
if (deltaLengthWithRadius > maxDistanceLimit)
{
// Ignore vertices too far apart
continue;
}
if (deltaLengthWithRadius < minDistanceLimit)
{
deltaLengthWithRadius = minDistanceLimit;
}
double force = forceConstantSquared / deltaLengthWithRadius;
double displacementX = (xDelta / deltaLength) * force;
double displacementY = (yDelta / deltaLength) * force;
if (isMoveable[i])
{
dispX[i] += displacementX;
dispY[i] += displacementY;
}
if (isMoveable[j])
{
dispX[j] -= displacementX;
dispY[j] -= displacementY;
}
}
}
}
}
}

View file

@ -0,0 +1,415 @@
/**
* Copyright (c) 2008-2009, JGraph Ltd
*/
package com.mxgraph.layout;
import java.util.List;
import java.util.Map;
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.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<String, Object> style = (state != null) ? state.getStyle() : graph
.getCellStyle(cell);
return (style != null) ? style.get(key) : null;
}
/**
* @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<mxPoint> 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 <mxRectangle> that defines the bounds of the given cell
* or the bounding box if <useBoundingBox> is true.
*/
public mxRectangle getVertexBounds(Object vertex)
{
mxRectangle geo = graph.getModel().getGeometry(vertex);
// Checks for oversize label bounding box and corrects
// the return value accordingly
if (useBoundingBox)
{
mxCellState state = graph.getView().getState(vertex);
if (state != null)
{
double scale = graph.getView().getScale();
mxRectangle tmp = state.getBoundingBox();
double dx0 = (tmp.getX() - state.getX()) / scale;
double dy0 = (tmp.getY() - state.getY()) / scale;
double dx1 = (tmp.getX() + tmp.getWidth() - state.getX() - state
.getWidth()) / scale;
double dy1 = (tmp.getY() + tmp.getHeight() - state.getY() - state
.getHeight()) / scale;
geo = new mxRectangle(geo.getX() + dx0, geo.getY() + dy0,
geo.getWidth() - dx0 + dx1, geo.getHeight() + -dy0
+ dy1);
}
}
if (this.parent != null)
{
Object parent = graph.getModel().getParent(vertex);
geo = (mxRectangle) geo.clone();
if (parent != null && parent != this.parent)
{
mxPoint parentOffset = getParentOffset(parent);
geo.setX(geo.getX() + parentOffset.getX());
geo.setY(geo.getY() + parentOffset.getY());
}
}
return new mxRectangle(geo);
}
/**
* Sets the new position of the given cell taking into account the size of
* the bounding box if <useBoundingBox> 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 - <mxCell> 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)
{
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.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();
}
}
}

View file

@ -0,0 +1,31 @@
/**
* Copyright (c) 2007, Gaudenz Alder
*/
package com.mxgraph.layout;
/**
* Defines the requirements for an object that implements a graph layout.
*/
public interface mxIGraphLayout
{
/**
* Executes the layout for the children of the specified parent.
*
* @param parent Parent cell that contains the children to be layed out.
*/
void execute(Object parent);
/**
* Notified when a cell is being moved in a parent that has automatic
* layout to update the cell state (eg. index) so that the outcome of the
* layout will position the vertex as close to the point (x, y) as
* possible.
*
* @param cell Cell which is being moved.
* @param x X-coordinate of the new cell location.
* @param y Y-coordinate of the new cell location.
*/
void moveCell(Object cell, double x, double y);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,197 @@
package com.mxgraph.layout;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.mxgraph.model.mxCellPath;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.model.mxICell;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.util.mxPoint;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxGraphView;
public class mxParallelEdgeLayout extends mxGraphLayout
{
/**
* Specifies the spacing between the edges. Default is 20.
*/
protected int spacing;
/**
* Constructs a new stack layout layout for the specified graph,
* spacing, orientation and offset.
*/
public mxParallelEdgeLayout(mxGraph graph)
{
this(graph, 20);
}
/**
* Constructs a new stack layout layout for the specified graph,
* spacing, orientation and offset.
*/
public mxParallelEdgeLayout(mxGraph graph, int spacing)
{
super(graph);
this.spacing = spacing;
}
/*
* (non-Javadoc)
* @see com.mxgraph.layout.mxIGraphLayout#execute(java.lang.Object)
*/
public void execute(Object parent)
{
Map<String, List<Object>> lookup = findParallels(parent);
graph.getModel().beginUpdate();
try
{
Iterator<List<Object>> it = lookup.values().iterator();
while (it.hasNext())
{
List<Object> parallels = it.next();
if (parallels.size() > 1)
{
layout(parallels);
}
}
}
finally
{
graph.getModel().endUpdate();
}
}
/**
*
*/
protected Map<String, List<Object>> findParallels(Object parent)
{
Map<String, List<Object>> lookup = new Hashtable<String, List<Object>>();
mxIGraphModel model = graph.getModel();
int childCount = model.getChildCount(parent);
for (int i = 0; i < childCount; i++)
{
Object child = model.getChildAt(parent, i);
if (!isEdgeIgnored(child))
{
String id = getEdgeId(child);
if (id != null)
{
if (!lookup.containsKey(id))
{
lookup.put(id, new ArrayList<Object>());
}
lookup.get(id).add(child);
}
}
}
return lookup;
}
/**
*
*/
protected String getEdgeId(Object edge)
{
mxGraphView view = graph.getView();
mxCellState state = view.getState(edge);
Object src = (state != null) ? state.getVisibleTerminal(true) : view
.getVisibleTerminal(edge, true);
Object trg = (state != null) ? state.getVisibleTerminal(false) : view
.getVisibleTerminal(edge, false);
if (src instanceof mxICell && trg instanceof mxICell)
{
String srcId = mxCellPath.create((mxICell) src);
String trgId = mxCellPath.create((mxICell) trg);
return (srcId.compareTo(trgId) > 0) ? trgId + "-" + srcId : srcId
+ "-" + trgId;
}
return null;
}
/**
*
*/
protected void layout(List<Object> parallels)
{
Object edge = parallels.get(0);
mxIGraphModel model = graph.getModel();
mxGeometry src = model.getGeometry(model.getTerminal(edge, true));
mxGeometry trg = model.getGeometry(model.getTerminal(edge, false));
// Routes multiple loops
if (src == trg)
{
double x0 = src.getX() + src.getWidth() + this.spacing;
double y0 = src.getY() + src.getHeight() / 2;
for (int i = 0; i < parallels.size(); i++)
{
route(parallels.get(i), x0, y0);
x0 += spacing;
}
}
else if (src != null && trg != null)
{
// Routes parallel edges
double scx = src.getX() + src.getWidth() / 2;
double scy = src.getY() + src.getHeight() / 2;
double tcx = trg.getX() + trg.getWidth() / 2;
double tcy = trg.getY() + trg.getHeight() / 2;
double dx = tcx - scx;
double dy = tcy - scy;
double len = Math.sqrt(dx * dx + dy * dy);
double x0 = scx + dx / 2;
double y0 = scy + dy / 2;
double nx = dy * spacing / len;
double ny = dx * spacing / len;
x0 += nx * (parallels.size() - 1) / 2;
y0 -= ny * (parallels.size() - 1) / 2;
for (int i = 0; i < parallels.size(); i++)
{
route(parallels.get(i), x0, y0);
x0 -= nx;
y0 += ny;
}
}
}
/**
*
*/
protected void route(Object edge, double x, double y)
{
if (graph.isCellMovable(edge))
{
setEdgePoints(edge,
Arrays.asList(new mxPoint[] { new mxPoint(x, y) }));
}
}
}

View file

@ -0,0 +1,235 @@
package com.mxgraph.layout;
import java.util.ArrayList;
import java.util.List;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.model.mxICell;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.view.mxGraph;
public class mxPartitionLayout extends mxGraphLayout
{
/**
* Boolean indicating the direction in which the space is partitioned.
* Default is true.
*/
protected boolean horizontal;
/**
* Integer that specifies the absolute spacing in pixels between the
* children. Default is 0.
*/
protected int spacing;
/**
* Integer that specifies the absolute inset in pixels for the parent that
* contains the children. Default is 0.
*/
protected int border;
/**
* Boolean that specifies if vertices should be resized. Default is true.
*/
protected boolean resizeVertices = true;
/**
* Constructs a new stack layout layout for the specified graph,
* spacing, orientation and offset.
*/
public mxPartitionLayout(mxGraph graph)
{
this(graph, true);
}
/**
* Constructs a new stack layout layout for the specified graph,
* spacing, orientation and offset.
*/
public mxPartitionLayout(mxGraph graph, boolean horizontal)
{
this(graph, horizontal, 0);
}
/**
* Constructs a new stack layout layout for the specified graph,
* spacing, orientation and offset.
*/
public mxPartitionLayout(mxGraph graph, boolean horizontal, int spacing)
{
this(graph, horizontal, spacing, 0);
}
/**
* Constructs a new stack layout layout for the specified graph,
* spacing, orientation and offset.
*/
public mxPartitionLayout(mxGraph graph, boolean horizontal, int spacing,
int border)
{
super(graph);
this.horizontal = horizontal;
this.spacing = spacing;
this.border = border;
}
/*
* (non-Javadoc)
* @see com.mxgraph.layout.mxGraphLayout#move(java.lang.Object, double, double)
*/
public void moveCell(Object cell, double x, double y)
{
mxIGraphModel model = graph.getModel();
Object parent = model.getParent(cell);
if (cell instanceof mxICell && parent instanceof mxICell)
{
int i = 0;
double last = 0;
int childCount = model.getChildCount(parent);
// Finds index of the closest swimlane
// TODO: Take into account the orientation
for (i = 0; i < childCount; i++)
{
Object child = model.getChildAt(parent, i);
mxRectangle bounds = getVertexBounds(child);
if (bounds != null)
{
double tmp = bounds.getX() + bounds.getWidth() / 2;
if (last < x && tmp > x)
{
break;
}
last = tmp;
}
}
// Changes child order in parent
int idx = ((mxICell) parent).getIndex((mxICell) cell);
idx = Math.max(0, i - ((i > idx) ? 1 : 0));
model.add(parent, cell, idx);
}
}
/**
* Hook for subclassers to return the container size.
*/
public mxRectangle getContainerSize()
{
return new mxRectangle();
}
/*
* (non-Javadoc)
* @see com.mxgraph.layout.mxIGraphLayout#execute(java.lang.Object)
*/
public void execute(Object parent)
{
mxIGraphModel model = graph.getModel();
mxGeometry pgeo = model.getGeometry(parent);
// Handles special case where the parent is either a layer with no
// geometry or the current root of the view in which case the size
// of the graph's container will be used.
if (pgeo == null && model.getParent(parent) == model.getRoot()
|| parent == graph.getView().getCurrentRoot())
{
mxRectangle tmp = getContainerSize();
pgeo = new mxGeometry(0, 0, tmp.getWidth(), tmp.getHeight());
}
if (pgeo != null)
{
int childCount = model.getChildCount(parent);
List<Object> children = new ArrayList<Object>(childCount);
for (int i = 0; i < childCount; i++)
{
Object child = model.getChildAt(parent, i);
if (!isVertexIgnored(child) && isVertexMovable(child))
{
children.add(child);
}
}
int n = children.size();
if (n > 0)
{
double x0 = border;
double y0 = border;
double other = (horizontal) ? pgeo.getHeight() : pgeo
.getWidth();
other -= 2 * border;
mxRectangle size = graph.getStartSize(parent);
other -= (horizontal) ? size.getHeight() : size.getWidth();
x0 = x0 + size.getWidth();
y0 = y0 + size.getHeight();
double tmp = border + (n - 1) * spacing;
double value = (horizontal) ? ((pgeo.getWidth() - x0 - tmp) / n)
: ((pgeo.getHeight() - y0 - tmp) / n);
// Avoids negative values, that is values where the sum of the
// spacing plus the border is larger then the available space
if (value > 0)
{
model.beginUpdate();
try
{
for (int i = 0; i < n; i++)
{
Object child = children.get(i);
mxGeometry geo = model.getGeometry(child);
if (geo != null)
{
geo = (mxGeometry) geo.clone();
geo.setX(x0);
geo.setY(y0);
if (horizontal)
{
if (resizeVertices)
{
geo.setWidth(value);
geo.setHeight(other);
}
x0 += value + spacing;
}
else
{
if (resizeVertices)
{
geo.setHeight(value);
geo.setWidth(other);
}
y0 += value + spacing;
}
model.setGeometry(child, geo);
}
}
}
finally
{
model.endUpdate();
}
}
}
}
}
}

View file

@ -0,0 +1,330 @@
package com.mxgraph.layout;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.model.mxICell;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;
public class mxStackLayout extends mxGraphLayout
{
/**
* Specifies the orientation of the layout. Default is true.
*/
protected boolean horizontal;
/**
* Specifies the spacing between the cells. Default is 0.
*/
protected int spacing;
/**
* Specifies the horizontal origin of the layout. Default is 0.
*/
protected int x0;
/**
* Specifies the vertical origin of the layout. Default is 0.
*/
protected int y0;
/**
* Border to be added if fill is true. Default is 0.
*/
protected int border;
/**
* Boolean indicating if dimension should be changed to fill out the parent
* cell. Default is false.
*/
protected boolean fill = false;
/**
* If the parent should be resized to match the width/height of the
* stack. Default is false.
*/
protected boolean resizeParent = false;
/**
* Value at which a new column or row should be created. Default is 0.
*/
protected int wrap = 0;
/**
* Constructs a new stack layout layout for the specified graph,
* spacing, orientation and offset.
*/
public mxStackLayout(mxGraph graph)
{
this(graph, true);
}
/**
* Constructs a new stack layout layout for the specified graph,
* spacing, orientation and offset.
*/
public mxStackLayout(mxGraph graph, boolean horizontal)
{
this(graph, horizontal, 0);
}
/**
* Constructs a new stack layout layout for the specified graph,
* spacing, orientation and offset.
*/
public mxStackLayout(mxGraph graph, boolean horizontal, int spacing)
{
this(graph, horizontal, spacing, 0, 0, 0);
}
/**
* Constructs a new stack layout layout for the specified graph,
* spacing, orientation and offset.
*/
public mxStackLayout(mxGraph graph, boolean horizontal, int spacing,
int x0, int y0, int border)
{
super(graph);
this.horizontal = horizontal;
this.spacing = spacing;
this.x0 = x0;
this.y0 = y0;
this.border = border;
}
/**
*
*/
public boolean isHorizontal()
{
return horizontal;
}
/*
* (non-Javadoc)
* @see com.mxgraph.layout.mxGraphLayout#move(java.lang.Object, double, double)
*/
public void moveCell(Object cell, double x, double y)
{
mxIGraphModel model = graph.getModel();
Object parent = model.getParent(cell);
boolean horizontal = isHorizontal();
if (cell instanceof mxICell && parent instanceof mxICell)
{
int i = 0;
double last = 0;
int childCount = model.getChildCount(parent);
double value = (horizontal) ? x : y;
mxCellState pstate = graph.getView().getState(parent);
if (pstate != null)
{
value -= (horizontal) ? pstate.getX() : pstate.getY();
}
for (i = 0; i < childCount; i++)
{
Object child = model.getChildAt(parent, i);
if (child != cell)
{
mxGeometry bounds = model.getGeometry(child);
if (bounds != null)
{
double tmp = (horizontal) ? bounds.getX()
+ bounds.getWidth() / 2 : bounds.getY()
+ bounds.getHeight() / 2;
if (last < value && tmp > value)
{
break;
}
last = tmp;
}
}
}
// Changes child order in parent
int idx = ((mxICell) parent).getIndex((mxICell) cell);
idx = Math.max(0, i - ((i > idx) ? 1 : 0));
model.add(parent, cell, idx);
}
}
/**
* Hook for subclassers to return the container size.
*/
public mxRectangle getContainerSize()
{
return new mxRectangle();
}
/*
* (non-Javadoc)
* @see com.mxgraph.layout.mxIGraphLayout#execute(java.lang.Object)
*/
public void execute(Object parent)
{
if (parent != null)
{
boolean horizontal = isHorizontal();
mxIGraphModel model = graph.getModel();
mxGeometry pgeo = model.getGeometry(parent);
// Handles special case where the parent is either a layer with no
// geometry or the current root of the view in which case the size
// of the graph's container will be used.
if (pgeo == null && model.getParent(parent) == model.getRoot()
|| parent == graph.getView().getCurrentRoot())
{
mxRectangle tmp = getContainerSize();
pgeo = new mxGeometry(0, 0, tmp.getWidth(), tmp.getHeight());
}
double fillValue = 0;
if (pgeo != null)
{
fillValue = (horizontal) ? pgeo.getHeight() : pgeo.getWidth();
}
fillValue -= 2 * spacing + 2 * border;
// Handles swimlane start size
mxRectangle size = graph.getStartSize(parent);
fillValue -= (horizontal) ? size.getHeight() : size.getWidth();
double x0 = this.x0 + size.getWidth() + border;
double y0 = this.y0 + size.getHeight() + border;
model.beginUpdate();
try
{
double tmp = 0;
mxGeometry last = null;
int childCount = model.getChildCount(parent);
for (int i = 0; i < childCount; i++)
{
Object child = model.getChildAt(parent, i);
if (!isVertexIgnored(child) && isVertexMovable(child))
{
mxGeometry geo = model.getGeometry(child);
if (geo != null)
{
geo = (mxGeometry) geo.clone();
if (wrap != 0 && last != null)
{
if ((horizontal && last.getX()
+ last.getWidth() + geo.getWidth() + 2
* spacing > wrap)
|| (!horizontal && last.getY()
+ last.getHeight()
+ geo.getHeight() + 2 * spacing > wrap))
{
last = null;
if (horizontal)
{
y0 += tmp + spacing;
}
else
{
x0 += tmp + spacing;
}
tmp = 0;
}
}
tmp = Math.max(tmp, (horizontal) ? geo
.getHeight() : geo.getWidth());
if (last != null)
{
if (horizontal)
{
geo.setX(last.getX() + last.getWidth()
+ spacing);
}
else
{
geo.setY(last.getY() + last.getHeight()
+ spacing);
}
}
else
{
if (horizontal)
{
geo.setX(x0);
}
else
{
geo.setY(y0);
}
}
if (horizontal)
{
geo.setY(y0);
}
else
{
geo.setX(x0);
}
if (fill && fillValue > 0)
{
if (horizontal)
{
geo.setHeight(fillValue);
}
else
{
geo.setWidth(fillValue);
}
}
model.setGeometry(child, geo);
last = geo;
}
}
}
if (resizeParent && pgeo != null && last != null
&& !graph.isCellCollapsed(parent))
{
pgeo = (mxGeometry) pgeo.clone();
if (horizontal)
{
pgeo.setWidth(last.getX() + last.getWidth() + spacing);
}
else
{
pgeo
.setHeight(last.getY() + last.getHeight()
+ spacing);
}
model.setGeometry(parent, pgeo);
}
}
finally
{
model.endUpdate();
}
}
}
}

View file

@ -0,0 +1,17 @@
/**
* Copyright (c) 2008-2009, JGraph Ltd
*/
package com.mxgraph.layout.orthogonal.model;
import com.mxgraph.view.mxGraph;
/**
* A custom graph model
*/
public class mxOrthogonalModel
{
public mxOrthogonalModel(mxGraph graph)
{
//
}
}

View file

@ -0,0 +1,12 @@
/**
* Copyright (c) 2008, Gaudenz Alder
*/
package com.mxgraph.layout.orthogonal.model;
/**
*
*/
public class mxPointPair
{
}

View file

@ -0,0 +1,12 @@
/**
* Copyright (c) 2008, Gaudenz Alder
*/
package com.mxgraph.layout.orthogonal.model;
/**
*
*/
public class mxPointSequence
{
}

View file

@ -0,0 +1,12 @@
/**
* Copyright (c) 2008, Gaudenz Alder
*/
package com.mxgraph.layout.orthogonal.model;
/**
*
*/
public class mxSegment
{
}

View file

@ -0,0 +1,48 @@
/**
* Copyright (c) 2008-2009, JGraph Ltd
*/
package com.mxgraph.layout.orthogonal;
import com.mxgraph.layout.mxGraphLayout;
import com.mxgraph.layout.orthogonal.model.mxOrthogonalModel;
import com.mxgraph.view.mxGraph;
/**
*
*/
/**
*
*/
public class mxOrthogonalLayout extends mxGraphLayout
{
/**
*
*/
protected mxOrthogonalModel orthModel;
/**
* Whether or not to route the edges along grid lines only, if the grid
* is enabled. Default is false
*/
protected boolean routeToGrid = false;
/**
*
*/
public mxOrthogonalLayout(mxGraph graph)
{
super(graph);
orthModel = new mxOrthogonalModel(graph);
}
/**
*
*/
public void execute(Object parent)
{
// Create the rectangulation
}
}

View file

@ -0,0 +1,5 @@
<HTML>
<BODY>
This package contains various graph layouts.
</BODY>
</HTML>

View file

@ -0,0 +1,641 @@
/**
* Copyright (c) 2007, Gaudenz Alder
*/
package com.mxgraph.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* Cells are the elements of the graph model. They represent the state
* of the groups, vertices and edges in a graph.
*
* <h4>Edge Labels</h4>
*
* Using the x- and y-coordinates of a cell's geometry it is
* possible to position the label on edges on a specific location
* on the actual edge shape as it appears on the screen. The
* x-coordinate of an edge's geometry is used to describe the
* distance from the center of the edge from -1 to 1 with 0
* being the center of the edge and the default value. The
* y-coordinate of an edge's geometry is used to describe
* the absolute, orthogonal distance in pixels from that
* point. In addition, the mxGeometry.offset is used
* as a absolute offset vector from the resulting point.
*
* The width and height of an edge geometry are ignored.
*
* To add more than one edge label, add a child vertex with
* a relative geometry. The x- and y-coordinates of that
* geometry will have the same semantiv as the above for
* edge labels.
*/
public class mxCell implements mxICell, Cloneable, Serializable
{
/**
*
*/
private static final long serialVersionUID = 910211337632342672L;
/**
* Holds the Id. Default is null.
*/
protected String id;
/**
* Holds the user object. Default is null.
*/
protected Object value;
/**
* Holds the geometry. Default is null.
*/
protected mxGeometry geometry;
/**
* Holds the style as a string of the form
* stylename[;key=value]. Default is null.
*/
protected String style;
/**
* Specifies whether the cell is a vertex or edge and whether it is
* connectable, visible and collapsed. Default values are false, false,
* true, true and false respectively.
*/
protected boolean vertex = false, edge = false, connectable = true,
visible = true, collapsed = false;
/**
* Reference to the parent cell and source and target terminals for edges.
*/
protected mxICell parent, source, target;
/**
* Holds the child cells and connected edges.
*/
protected List<Object> children, edges;
/**
* Constructs a new cell with an empty user object.
*/
public mxCell()
{
this(null);
}
/**
* Constructs a new cell for the given user object.
*
* @param value
* Object that represents the value of the cell.
*/
public mxCell(Object value)
{
this(value, null, null);
}
/**
* Constructs a new cell for the given parameters.
*
* @param value Object that represents the value of the cell.
* @param geometry Specifies the geometry of the cell.
* @param style Specifies the style as a formatted string.
*/
public mxCell(Object value, mxGeometry geometry, String style)
{
setValue(value);
setGeometry(geometry);
setStyle(style);
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#getId()
*/
public String getId()
{
return id;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#setId(String)
*/
public void setId(String id)
{
this.id = id;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#getValue()
*/
public Object getValue()
{
return value;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#setValue(Object)
*/
public void setValue(Object value)
{
this.value = value;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#getGeometry()
*/
public mxGeometry getGeometry()
{
return geometry;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#setGeometry(com.mxgraph.model.mxGeometry)
*/
public void setGeometry(mxGeometry geometry)
{
this.geometry = geometry;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#getStyle()
*/
public String getStyle()
{
return style;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#setStyle(String)
*/
public void setStyle(String style)
{
this.style = style;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#isVertex()
*/
public boolean isVertex()
{
return vertex;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#setVertex(boolean)
*/
public void setVertex(boolean vertex)
{
this.vertex = vertex;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#isEdge()
*/
public boolean isEdge()
{
return edge;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#setEdge(boolean)
*/
public void setEdge(boolean edge)
{
this.edge = edge;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#isConnectable()
*/
public boolean isConnectable()
{
return connectable;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#setConnectable(boolean)
*/
public void setConnectable(boolean connectable)
{
this.connectable = connectable;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#isVisible()
*/
public boolean isVisible()
{
return visible;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#setVisible(boolean)
*/
public void setVisible(boolean visible)
{
this.visible = visible;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#isCollapsed()
*/
public boolean isCollapsed()
{
return collapsed;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#setCollapsed(boolean)
*/
public void setCollapsed(boolean collapsed)
{
this.collapsed = collapsed;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#getParent()
*/
public mxICell getParent()
{
return parent;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#setParent(com.mxgraph.model.mxICell)
*/
public void setParent(mxICell parent)
{
this.parent = parent;
}
/**
* Returns the source terminal.
*/
public mxICell getSource()
{
return source;
}
/**
* Sets the source terminal.
*
* @param source Cell that represents the new source terminal.
*/
public void setSource(mxICell source)
{
this.source = source;
}
/**
* Returns the target terminal.
*/
public mxICell getTarget()
{
return target;
}
/**
* Sets the target terminal.
*
* @param target Cell that represents the new target terminal.
*/
public void setTarget(mxICell target)
{
this.target = target;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#getTerminal(boolean)
*/
public mxICell getTerminal(boolean source)
{
return (source) ? getSource() : getTarget();
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#setTerminal(com.mxgraph.model.mxICell, boolean)
*/
public mxICell setTerminal(mxICell terminal, boolean isSource)
{
if (isSource)
{
setSource(terminal);
}
else
{
setTarget(terminal);
}
return terminal;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#getChildCount()
*/
public int getChildCount()
{
return (children != null) ? children.size() : 0;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#getIndex(com.mxgraph.model.mxICell)
*/
public int getIndex(mxICell child)
{
return (children != null) ? children.indexOf(child) : -1;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#getChildAt(int)
*/
public mxICell getChildAt(int index)
{
return (children != null) ? (mxICell) children.get(index) : null;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#insert(com.mxgraph.model.mxICell)
*/
public mxICell insert(mxICell child)
{
int index = getChildCount();
if (child.getParent() == this)
{
index--;
}
return insert(child, index);
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#insert(com.mxgraph.model.mxICell, int)
*/
public mxICell insert(mxICell child, int index)
{
if (child != null)
{
child.removeFromParent();
child.setParent(this);
if (children == null)
{
children = new ArrayList<Object>();
children.add(child);
}
else
{
children.add(index, child);
}
}
return child;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#remove(int)
*/
public mxICell remove(int index)
{
mxICell child = null;
if (children != null && index >= 0)
{
child = getChildAt(index);
remove(child);
}
return child;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#remove(com.mxgraph.model.mxICell)
*/
public mxICell remove(mxICell child)
{
if (child != null && children != null)
{
children.remove(child);
child.setParent(null);
}
return child;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#removeFromParent()
*/
public void removeFromParent()
{
if (parent != null)
{
parent.remove(this);
}
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#getEdgeCount()
*/
public int getEdgeCount()
{
return (edges != null) ? edges.size() : 0;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#getEdgeIndex(com.mxgraph.model.mxICell)
*/
public int getEdgeIndex(mxICell edge)
{
return (edges != null) ? edges.indexOf(edge) : -1;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#getEdgeAt(int)
*/
public mxICell getEdgeAt(int index)
{
return (edges != null) ? (mxICell) edges.get(index) : null;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#insertEdge(com.mxgraph.model.mxICell, boolean)
*/
public mxICell insertEdge(mxICell edge, boolean isOutgoing)
{
if (edge != null)
{
edge.removeFromTerminal(isOutgoing);
edge.setTerminal(this, isOutgoing);
if (edges == null || edge.getTerminal(!isOutgoing) != this
|| !edges.contains(edge))
{
if (edges == null)
{
edges = new ArrayList<Object>();
}
edges.add(edge);
}
}
return edge;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#removeEdge(com.mxgraph.model.mxICell, boolean)
*/
public mxICell removeEdge(mxICell edge, boolean isOutgoing)
{
if (edge != null)
{
if (edge.getTerminal(!isOutgoing) != this && edges != null)
{
edges.remove(edge);
}
edge.setTerminal(null, isOutgoing);
}
return edge;
}
/* (non-Javadoc)
* @see com.mxgraph.model.mxICell#removeFromTerminal(boolean)
*/
public void removeFromTerminal(boolean isSource)
{
mxICell terminal = getTerminal(isSource);
if (terminal != null)
{
terminal.removeEdge(this, isSource);
}
}
/**
* Returns the specified attribute from the user object if it is an XML
* node.
*
* @param name Name of the attribute whose value should be returned.
* @return Returns the value of the given attribute or null.
*/
public String getAttribute(String name)
{
return getAttribute(name, null);
}
/**
* Returns the specified attribute from the user object if it is an XML
* node.
*
* @param name Name of the attribute whose value should be returned.
* @param defaultValue Default value to use if the attribute has no value.
* @return Returns the value of the given attribute or defaultValue.
*/
public String getAttribute(String name, String defaultValue)
{
Object userObject = getValue();
String val = null;
if (userObject instanceof Element)
{
Element element = (Element) userObject;
val = element.getAttribute(name);
}
if (val == null)
{
val = defaultValue;
}
return val;
}
/**
* Sets the specified attribute on the user object if it is an XML node.
*
* @param name Name of the attribute whose value should be set.
* @param value New value of the attribute.
*/
public void setAttribute(String name, String value)
{
Object userObject = getValue();
if (userObject instanceof Element)
{
Element element = (Element) userObject;
element.setAttribute(name, value);
}
}
/**
* Returns a clone of the cell.
*/
public Object clone() throws CloneNotSupportedException
{
mxCell clone = (mxCell) super.clone();
clone.setValue(cloneValue());
clone.setStyle(getStyle());
clone.setCollapsed(isCollapsed());
clone.setConnectable(isConnectable());
clone.setEdge(isEdge());
clone.setVertex(isVertex());
clone.setVisible(isVisible());
clone.setParent(null);
clone.setSource(null);
clone.setTarget(null);
clone.children = null;
clone.edges = null;
mxGeometry geometry = getGeometry();
if (geometry != null)
{
clone.setGeometry((mxGeometry) geometry.clone());
}
return clone;
}
/**
* Returns a clone of the user object. This implementation clones any XML
* nodes or otherwise returns the same user object instance.
*/
protected Object cloneValue()
{
Object value = getValue();
if (value instanceof Node)
{
value = ((Node) value).cloneNode(true);
}
return value;
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder(64);
builder.append(getClass().getSimpleName());
builder.append(" [");
builder.append("id=");
builder.append(id);
builder.append(", value=");
builder.append(value);
builder.append(", geometry=");
builder.append(geometry);
builder.append("]");
return builder.toString();
}
}

View file

@ -0,0 +1,144 @@
/**
* Copyright (c) 2007, Gaudenz Alder
*/
package com.mxgraph.model;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
/**
* Implements a mechanism for temporary cell Ids.
*/
public class mxCellPath
{
/**
* Defines the separator between the path components. Default is
* <code>.</code>.
*/
public static String PATH_SEPARATOR = ".";
/**
* Creates the cell path for the given cell. The cell path is a
* concatenation of the indices of all cells on the (finite) path to
* the root, eg. "0.0.0.1".
*
* @param cell Cell whose path should be returned.
* @return Returns the string that represents the path.
*/
public static String create(mxICell cell)
{
String result = "";
if (cell != null)
{
mxICell parent = cell.getParent();
while (parent != null)
{
int index = parent.getIndex(cell);
result = index + mxCellPath.PATH_SEPARATOR + result;
cell = parent;
parent = cell.getParent();
}
}
return (result.length() > 1) ? result.substring(0, result.length() - 1)
: "";
}
/**
* Returns the path for the parent of the cell represented by the given
* path. Returns null if the given path has no parent.
*
* @param path Path whose parent path should be returned.
*/
public static String getParentPath(String path)
{
if (path != null)
{
int index = path.lastIndexOf(mxCellPath.PATH_SEPARATOR);
if (index >= 0)
{
return path.substring(0, index);
}
else if (path.length() > 0)
{
return "";
}
}
return null;
}
/**
* Returns the cell for the specified cell path using the given root as the
* root of the path.
*
* @param root Root cell of the path to be resolved.
* @param path String that defines the path.
* @return Returns the cell that is defined by the path.
*/
public static mxICell resolve(mxICell root, String path)
{
mxICell parent = root;
String[] tokens = path.split(Pattern.quote(PATH_SEPARATOR));
for (int i = 0; i < tokens.length; i++)
{
parent = parent.getChildAt(Integer.parseInt(tokens[i]));
}
return parent;
}
/**
* Compares the given cell paths and returns -1 if cp1 is smaller, 0 if
* cp1 is equal and 1 if cp1 is greater than cp2.
*/
public static int compare(String cp1, String cp2)
{
StringTokenizer p1 = new StringTokenizer(cp1, mxCellPath.PATH_SEPARATOR);
StringTokenizer p2 = new StringTokenizer(cp2, mxCellPath.PATH_SEPARATOR);
int comp = 0;
while (p1.hasMoreTokens() &&
p2.hasMoreTokens())
{
String t1 = p1.nextToken();
String t2 = p2.nextToken();
if (!t1.equals(t2))
{
if (t1.length() == 0 ||
t2.length() == 0)
{
comp = t1.compareTo(t2);
}
else
{
comp = Integer.valueOf(t1).compareTo(Integer.valueOf(t2));
}
break;
}
}
// Compares path length if both paths are equal to this point
if (comp == 0)
{
int t1 = p1.countTokens();
int t2 = p2.countTokens();
if (t1 != t2)
{
comp = (t1 > t2) ? 1 : -1;
}
}
return comp;
}
}

View file

@ -0,0 +1,373 @@
/**
* Copyright (c) 2007, Gaudenz Alder
*/
package com.mxgraph.model;
import java.util.ArrayList;
import java.util.List;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle;
/**
* Represents the geometry of a cell. For vertices, the geometry consists
* of the x- and y-location, as well as the width and height. For edges,
* the geometry either defines the source- and target-terminal, or it
* defines the respective terminal points.
*
* For edges, if the geometry is relative (default), then the x-coordinate
* is used to describe the distance from the center of the edge from -1 to 1
* with 0 being the center of the edge and the default value, and the
* y-coordinate is used to describe the absolute, orthogonal distance in
* pixels from that point. In addition, the offset is used as an absolute
* offset vector from the resulting point.
*/
public class mxGeometry extends mxRectangle
{
/**
*
*/
private static final long serialVersionUID = 2649828026610336589L;
/**
* Global switch to translate the points in translate. Default is true.
*/
public static transient boolean TRANSLATE_CONTROL_POINTS = true;
/**
* Stores alternate values for x, y, width and height in a rectangle.
* Default is null.
*/
protected mxRectangle alternateBounds;
/**
* Defines the source- and target-point of the edge. This is used if the
* corresponding edge does not have a source vertex. Otherwise it is
* ignored. Default is null.
*/
protected mxPoint sourcePoint, targetPoint;
/**
* List of mxPoints which specifies the control points along the edge.
* These points are the intermediate points on the edge, for the endpoints
* use targetPoint and sourcePoint or set the terminals of the edge to
* a non-null value. Default is null.
*/
protected List<mxPoint> points;
/**
* Holds the offset of the label for edges. This is the absolute vector
* between the center of the edge and the top, left point of the label.
* Default is null.
*/
protected mxPoint offset;
/**
* Specifies if the coordinates in the geometry are to be interpreted as
* relative coordinates. Default is false. This is used to mark a geometry
* with an x- and y-coordinate that is used to describe an edge label
* position, or a relative location with respect to a parent cell's
* width and height.
*/
protected boolean relative = false;
/**
* Constructs a new geometry at (0, 0) with the width and height set to 0.
*/
public mxGeometry()
{
this(0, 0, 0, 0);
}
/**
* Constructs a geometry using the given parameters.
*
* @param x X-coordinate of the new geometry.
* @param y Y-coordinate of the new geometry.
* @param width Width of the new geometry.
* @param height Height of the new geometry.
*/
public mxGeometry(double x, double y, double width, double height)
{
super(x, y, width, height);
}
/**
* Returns the alternate bounds.
*/
public mxRectangle getAlternateBounds()
{
return alternateBounds;
}
/**
* Sets the alternate bounds to the given rectangle.
*
* @param rect Rectangle to be used for the alternate bounds.
*/
public void setAlternateBounds(mxRectangle rect)
{
alternateBounds = rect;
}
/**
* Returns the source point.
*
* @return Returns the source point.
*/
public mxPoint getSourcePoint()
{
return sourcePoint;
}
/**
* Sets the source point.
*
* @param sourcePoint Source point to be used.
*/
public void setSourcePoint(mxPoint sourcePoint)
{
this.sourcePoint = sourcePoint;
}
/**
* Returns the target point.
*
* @return Returns the target point.
*/
public mxPoint getTargetPoint()
{
return targetPoint;
}
/**
* Sets the target point.
*
* @param targetPoint Target point to be used.
*/
public void setTargetPoint(mxPoint targetPoint)
{
this.targetPoint = targetPoint;
}
/**
* Returns the list of control points.
*/
public List<mxPoint> getPoints()
{
return points;
}
/**
* Sets the list of control points to the given list.
*
* @param value List that contains the new control points.
*/
public void setPoints(List<mxPoint> value)
{
points = value;
}
/**
* Returns the offset.
*/
public mxPoint getOffset()
{
return offset;
}
/**
* Sets the offset to the given point.
*
* @param offset Point to be used for the offset.
*/
public void setOffset(mxPoint offset)
{
this.offset = offset;
}
/**
* Returns true of the geometry is relative.
*/
public boolean isRelative()
{
return relative;
}
/**
* Sets the relative state of the geometry.
*
* @param value Boolean value to be used as the new relative state.
*/
public void setRelative(boolean value)
{
relative = value;
}
/**
* Swaps the x, y, width and height with the values stored in
* alternateBounds and puts the previous values into alternateBounds as
* a rectangle. This operation is carried-out in-place, that is, using the
* existing geometry instance. If this operation is called during a graph
* model transactional change, then the geometry should be cloned before
* calling this method and setting the geometry of the cell using
* mxGraphModel.setGeometry.
*/
public void swap()
{
if (alternateBounds != null)
{
mxRectangle old = new mxRectangle(getX(), getY(), getWidth(),
getHeight());
x = alternateBounds.getX();
y = alternateBounds.getY();
width = alternateBounds.getWidth();
height = alternateBounds.getHeight();
alternateBounds = old;
}
}
/**
* Returns the point representing the source or target point of this edge.
* This is only used if the edge has no source or target vertex.
*
* @param isSource Boolean that specifies if the source or target point
* should be returned.
* @return Returns the source or target point.
*/
public mxPoint getTerminalPoint(boolean isSource)
{
return (isSource) ? sourcePoint : targetPoint;
}
/**
* Sets the sourcePoint or targetPoint to the given point and returns the
* new point.
*
* @param point Point to be used as the new source or target point.
* @param isSource Boolean that specifies if the source or target point
* should be set.
* @return Returns the new point.
*/
public mxPoint setTerminalPoint(mxPoint point, boolean isSource)
{
if (isSource)
{
sourcePoint = point;
}
else
{
targetPoint = point;
}
return point;
}
/**
* Translates the geometry by the specified amount. That is, x and y of the
* geometry, the sourcePoint, targetPoint and all elements of points are
* translated by the given amount. X and y are only translated if the
* geometry is not relative. If TRANSLATE_CONTROL_POINTS is false, then
* are not modified by this function.
*
* @param dx Integer that specifies the x-coordinate of the translation.
* @param dy Integer that specifies the y-coordinate of the translation.
*/
public void translate(double dx, double dy)
{
// Translates the geometry
if (!isRelative())
{
x += dx;
y += dy;
}
// Translates the source point
if (sourcePoint != null)
{
sourcePoint.setX(sourcePoint.getX() + dx);
sourcePoint.setY(sourcePoint.getY() + dy);
}
// Translates the target point
if (targetPoint != null)
{
targetPoint.setX(targetPoint.getX() + dx);
targetPoint.setY(targetPoint.getY() + dy);
}
// Translate the control points
if (TRANSLATE_CONTROL_POINTS && points != null)
{
int count = points.size();
for (int i = 0; i < count; i++)
{
mxPoint pt = points.get(i);
pt.setX(pt.getX() + dx);
pt.setY(pt.getY() + dy);
}
}
}
/**
* Returns a clone of the cell.
*/
public Object clone()
{
mxGeometry clone = (mxGeometry) super.clone();
clone.setX(getX());
clone.setY(getY());
clone.setWidth(getWidth());
clone.setHeight(getHeight());
clone.setRelative(isRelative());
List<mxPoint> pts = getPoints();
if (pts != null)
{
clone.points = new ArrayList<mxPoint>(pts.size());
for (int i = 0; i < pts.size(); i++)
{
clone.points.add((mxPoint) pts.get(i).clone());
}
}
mxPoint tp = getTargetPoint();
if (tp != null)
{
clone.setTargetPoint((mxPoint) tp.clone());
}
mxPoint sp = getSourcePoint();
if (sp != null)
{
setSourcePoint((mxPoint) sp.clone());
}
mxPoint off = getOffset();
if (off != null)
{
clone.setOffset((mxPoint) off.clone());
}
mxRectangle alt = getAlternateBounds();
if (alt != null)
{
setAlternateBounds((mxRectangle) alt.clone());
}
return clone;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,273 @@
/**
* Copyright (c) 2007, Gaudenz Alder
*/
package com.mxgraph.model;
/**
* Defines the requirements for a cell that can be used in an mxGraphModel.
*/
public interface mxICell
{
/**
* Returns the Id of the cell as a string.
*
* @return Returns the Id.
*/
String getId();
/**
* Sets the Id of the cell to the given string.
*
* @param id String that represents the new Id.
*/
void setId(String id);
/**
* Returns the user object of the cell.
*
* @return Returns the user object.
*/
Object getValue();
/**
* Sets the user object of the cell.
*
* @param value Object that represents the new value.
*/
void setValue(Object value);
/**
* Returns the object that describes the geometry.
*
* @return Returns the cell geometry.
*/
mxGeometry getGeometry();
/**
* Sets the object to be used as the geometry.
*/
void setGeometry(mxGeometry geometry);
/**
* Returns the string that describes the style.
*
* @return Returns the cell style.
*/
String getStyle();
/**
* Sets the string to be used as the style.
*/
void setStyle(String style);
/**
* Returns true if the cell is a vertex.
*
* @return Returns true if the cell is a vertex.
*/
boolean isVertex();
/**
* Returns true if the cell is an edge.
*
* @return Returns true if the cell is an edge.
*/
boolean isEdge();
/**
* Returns true if the cell is connectable.
*
* @return Returns the connectable state.
*/
boolean isConnectable();
/**
* Returns true if the cell is visibile.
*
* @return Returns the visible state.
*/
boolean isVisible();
/**
* Specifies if the cell is visible.
*
* @param visible Boolean that specifies the new visible state.
*/
void setVisible(boolean visible);
/**
* Returns true if the cell is collapsed.
*
* @return Returns the collapsed state.
*/
boolean isCollapsed();
/**
* Sets the collapsed state.
*
* @param collapsed Boolean that specifies the new collapsed state.
*/
void setCollapsed(boolean collapsed);
/**
* Returns the cell's parent.
*
* @return Returns the parent cell.
*/
mxICell getParent();
/**
* Sets the parent cell.
*
* @param parent Cell that represents the new parent.
*/
void setParent(mxICell parent);
/**
* Returns the source or target terminal.
*
* @param source Boolean that specifies if the source terminal should be
* returned.
* @return Returns the source or target terminal.
*/
mxICell getTerminal(boolean source);
/**
* Sets the source or target terminal and returns the new terminal.
*
* @param terminal Cell that represents the new source or target terminal.
* @param isSource Boolean that specifies if the source or target terminal
* should be set.
* @return Returns the new terminal.
*/
mxICell setTerminal(mxICell terminal, boolean isSource);
/**
* Returns the number of child cells.
*
* @return Returns the number of children.
*/
int getChildCount();
/**
* Returns the index of the specified child in the child array.
*
* @param child Child whose index should be returned.
* @return Returns the index of the given child.
*/
int getIndex(mxICell child);
/**
* Returns the child at the specified index.
*
* @param index Integer that specifies the child to be returned.
* @return Returns the child at the given index.
*/
mxICell getChildAt(int index);
/**
* Appends the specified child into the child array and updates the parent
* reference of the child. Returns the appended child.
*
* @param child Cell to be appended to the child array.
* @return Returns the new child.
*/
mxICell insert(mxICell child);
/**
* Inserts the specified child into the child array at the specified index
* and updates the parent reference of the child. Returns the inserted child.
*
* @param child Cell to be inserted into the child array.
* @param index Integer that specifies the index at which the child should
* be inserted into the child array.
* @return Returns the new child.
*/
mxICell insert(mxICell child, int index);
/**
* Removes the child at the specified index from the child array and
* returns the child that was removed. Will remove the parent reference of
* the child.
*
* @param index Integer that specifies the index of the child to be
* removed.
* @return Returns the child that was removed.
*/
mxICell remove(int index);
/**
* Removes the given child from the child array and returns it. Will remove
* the parent reference of the child.
*
* @param child Cell that represents the child to be removed.
* @return Returns the child that was removed.
*/
mxICell remove(mxICell child);
/**
* Removes the cell from its parent.
*/
void removeFromParent();
/**
* Returns the number of edges in the edge array.
*
* @return Returns the number of edges.
*/
int getEdgeCount();
/**
* Returns the index of the specified edge in the edge array.
*
* @param edge Cell whose index should be returned.
* @return Returns the index of the given edge.
*/
int getEdgeIndex(mxICell edge);
/**
* Returns the edge at the specified index in the edge array.
*
* @param index Integer that specifies the index of the edge to be
* returned.
* @return Returns the edge at the given index.
*/
mxICell getEdgeAt(int index);
/**
* Inserts the specified edge into the edge array and returns the edge.
* Will update the respective terminal reference of the edge.
*
* @param edge Cell to be inserted into the edge array.
* @param isOutgoing Boolean that specifies if the edge is outgoing.
* @return Returns the new edge.
*/
mxICell insertEdge(mxICell edge, boolean isOutgoing);
/**
* Removes the specified edge from the edge array and returns the edge.
* Will remove the respective terminal reference from the edge.
*
* @param edge Cell to be removed from the edge array.
* @param isOutgoing Boolean that specifies if the edge is outgoing.
* @return Returns the edge that was removed.
*/
mxICell removeEdge(mxICell edge, boolean isOutgoing);
/**
* Removes the edge from its source or target terminal.
*
* @param isSource Boolean that specifies if the edge should be removed
* from its source or target terminal.
*/
void removeFromTerminal(boolean isSource);
/**
* Returns a clone of this cell.
*
* @return Returns a clone of this cell.
*/
Object clone() throws CloneNotSupportedException;
}

View file

@ -0,0 +1,336 @@
/**
* Copyright (c) 2007, Gaudenz Alder
*/
package com.mxgraph.model;
import com.mxgraph.util.mxEventSource.mxIEventListener;
import com.mxgraph.util.mxUndoableEdit.mxUndoableChange;
/**
* Defines the requirements for a graph model to be used with mxGraph.
*/
public interface mxIGraphModel
{
/**
* Defines the interface for an atomic change of the graph model.
*/
public abstract class mxAtomicGraphModelChange implements mxUndoableChange
{
/**
* Holds the model where the change happened.
*/
protected mxIGraphModel model;
/**
* Constructs an empty atomic graph model change.
*/
public mxAtomicGraphModelChange()
{
this(null);
}
/**
* Constructs an atomic graph model change for the given model.
*/
public mxAtomicGraphModelChange(mxIGraphModel model)
{
this.model = model;
}
/**
* Returns the model where the change happened.
*/
public mxIGraphModel getModel()
{
return model;
}
/**
* Sets the model where the change is to be carried out.
*/
public void setModel(mxIGraphModel model)
{
this.model = model;
}
/**
* Executes the change on the model.
*/
public abstract void execute();
}
/**
* Returns the root of the model or the topmost parent of the given cell.
*
* @return Returns the root cell.
*/
Object getRoot();
/**
* Sets the root of the model and resets all structures.
*
* @param root Cell that specifies the new root.
*/
Object setRoot(Object root);
/**
* Returns an array of clones for the given array of cells.
* Depending on the value of includeChildren, a deep clone is created for
* each cell. Connections are restored based if the corresponding
* cell is contained in the passed in array.
*
* @param cells Array of cells to be cloned.
* @param includeChildren Boolean indicating if the cells should be cloned
* with all descendants.
* @return Returns a cloned array of cells.
*/
Object[] cloneCells(Object[] cells, boolean includeChildren);
/**
* Returns true if the given parent is an ancestor of the given child.
*
* @param parent Cell that specifies the parent.
* @param child Cell that specifies the child.
* @return Returns true if child is an ancestor of parent.
*/
boolean isAncestor(Object parent, Object child);
/**
* Returns true if the model contains the given cell.
*
* @param cell Cell to be checked.
* @return Returns true if the cell is in the model.
*/
boolean contains(Object cell);
/**
* Returns the parent of the given cell.
*
* @param child Cell whose parent should be returned.
* @return Returns the parent of the given cell.
*/
Object getParent(Object child);
/**
* Adds the specified child to the parent at the given index. If no index
* is specified then the child is appended to the parent's array of
* children.
*
* @param parent Cell that specifies the parent to contain the child.
* @param child Cell that specifies the child to be inserted.
* @param index Integer that specifies the index of the child.
* @return Returns the inserted child.
*/
Object add(Object parent, Object child, int index);
/**
* Removes the specified cell from the model. This operation will remove
* the cell and all of its children from the model.
*
* @param cell Cell that should be removed.
* @return Returns the removed cell.
*/
Object remove(Object cell);
/**
* Returns the number of children in the given cell.
*
* @param cell Cell whose number of children should be returned.
* @return Returns the number of children in the given cell.
*/
int getChildCount(Object cell);
/**
* Returns the child of the given parent at the given index.
*
* @param parent Cell that represents the parent.
* @param index Integer that specifies the index of the child to be
* returned.
* @return Returns the child at index in parent.
*/
Object getChildAt(Object parent, int index);
/**
* Returns the source or target terminal of the given edge depending on the
* value of the boolean parameter.
*
* @param edge Cell that specifies the edge.
* @param isSource Boolean indicating which end of the edge should be
* returned.
* @return Returns the source or target of the given edge.
*/
Object getTerminal(Object edge, boolean isSource);
/**
* Sets the source or target terminal of the given edge using.
*
* @param edge Cell that specifies the edge.
* @param terminal Cell that specifies the new terminal.
* @param isSource Boolean indicating if the terminal is the new source or
* target terminal of the edge.
* @return Returns the new terminal.
*/
Object setTerminal(Object edge, Object terminal, boolean isSource);
/**
* Returns the number of distinct edges connected to the given cell.
*
* @param cell Cell that represents the vertex.
* @return Returns the number of edges connected to cell.
*/
int getEdgeCount(Object cell);
/**
* Returns the edge of cell at the given index.
*
* @param cell Cell that specifies the vertex.
* @param index Integer that specifies the index of the edge to return.
* @return Returns the edge at the given index.
*/
Object getEdgeAt(Object cell, int index);
/**
* Returns true if the given cell is a vertex.
*
* @param cell Cell that represents the possible vertex.
* @return Returns true if the given cell is a vertex.
*/
boolean isVertex(Object cell);
/**
* Returns true if the given cell is an edge.
*
* @param cell Cell that represents the possible edge.
* @return Returns true if the given cell is an edge.
*/
boolean isEdge(Object cell);
/**
* Returns true if the given cell is connectable.
*
* @param cell Cell whose connectable state should be returned.
* @return Returns the connectable state of the given cell.
*/
boolean isConnectable(Object cell);
/**
* Returns the user object of the given cell.
*
* @param cell Cell whose user object should be returned.
* @return Returns the user object of the given cell.
*/
Object getValue(Object cell);
/**
* Sets the user object of then given cell.
*
* @param cell Cell whose user object should be changed.
* @param value Object that defines the new user object.
* @return Returns the new value.
*/
Object setValue(Object cell, Object value);
/**
* Returns the geometry of the given cell.
*
* @param cell Cell whose geometry should be returned.
* @return Returns the geometry of the given cell.
*/
mxGeometry getGeometry(Object cell);
/**
* Sets the geometry of the given cell.
*
* @param cell Cell whose geometry should be changed.
* @param geometry Object that defines the new geometry.
* @return Returns the new geometry.
*/
mxGeometry setGeometry(Object cell, mxGeometry geometry);
/**
* Returns the style of the given cell.
*
* @param cell Cell whose style should be returned.
* @return Returns the style of the given cell.
*/
String getStyle(Object cell);
/**
* Sets the style of the given cell.
*
* @param cell Cell whose style should be changed.
* @param style String of the form stylename[;key=value] to specify
* the new cell style.
* @return Returns the new style.
*/
String setStyle(Object cell, String style);
/**
* Returns true if the given cell is collapsed.
*
* @param cell Cell whose collapsed state should be returned.
* @return Returns the collapsed state of the given cell.
*/
boolean isCollapsed(Object cell);
/**
* Sets the collapsed state of the given cell.
*
* @param cell Cell whose collapsed state should be changed.
* @param collapsed Boolean that specifies the new collpased state.
* @return Returns the new collapsed state.
*/
boolean setCollapsed(Object cell, boolean collapsed);
/**
* Returns true if the given cell is visible.
*
* @param cell Cell whose visible state should be returned.
* @return Returns the visible state of the given cell.
*/
boolean isVisible(Object cell);
/**
* Sets the visible state of the given cell.
*
* @param cell Cell whose visible state should be changed.
* @param visible Boolean that specifies the new visible state.
* @return Returns the new visible state.
*/
boolean setVisible(Object cell, boolean visible);
/**
* Increments the updateLevel by one. The event notification is queued
* until updateLevel reaches 0 by use of endUpdate.
*/
void beginUpdate();
/**
* Decrements the updateLevel by one and fires a notification event if the
* updateLevel reaches 0.
*/
void endUpdate();
/**
* Binds the specified function to the given event name. If no event name
* is given, then the listener is registered for all events.
*/
void addListener(String eventName, mxIEventListener listener);
/**
* Function: removeListener
*
* Removes the given listener from the list of listeners.
*/
void removeListener(mxIEventListener listener);
/**
* Function: removeListener
*
* Removes the given listener from the list of listeners.
*/
void removeListener(mxIEventListener listener, String eventName);
}

View file

@ -0,0 +1,5 @@
<HTML>
<BODY>
This package contains the classes that define a graph model.
</BODY>
</HTML>

View file

@ -0,0 +1,511 @@
package com.mxgraph.reader;
import java.util.Hashtable;
import java.util.Map;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import com.mxgraph.canvas.mxICanvas2D;
/**
*
public static void main(String[] args)
{
try
{
String filename = Test.class.getResource(
"/com/mxgraph/online/exported.xml").getPath();
String xml = mxUtils.readFile(filename);
System.out.println("xml=" + xml);
Document doc = mxUtils.parseXml(xml);
Element root = doc.getDocumentElement();
int width = Integer.parseInt(root.getAttribute("width"));
int height = Integer.parseInt(root.getAttribute("height"));
System.out.println("width=" + width + " height=" + height);
BufferedImage img = mxUtils.createBufferedImage(width, height,
Color.WHITE);
Graphics2D g2 = img.createGraphics();
mxUtils.setAntiAlias(g2, true, true);
mxDomOutputParser reader = new mxDomOutputParser(
new mxGraphicsExportCanvas(g2));
reader.read((Element) root.getFirstChild().getNextSibling());
ImageIO.write(img, "PNG", new File(
"C:\\Users\\Gaudenz\\Desktop\\test.png"));
}
catch (Exception e)
{
e.printStackTrace();
}
}
// -------------
Document doc = mxUtils.parseXml(xml);
Element root = doc.getDocumentElement();
mxDomOutputParser reader = new mxDomOutputParser(canvas);
reader.read(root.getFirstChild());
*/
public class mxDomOutputParser
{
/**
*
*/
protected mxICanvas2D canvas;
/**
*
*/
protected transient Map<String, IElementHandler> handlers = new Hashtable<String, IElementHandler>();
/**
*
*/
public mxDomOutputParser(mxICanvas2D canvas)
{
this.canvas = canvas;
initHandlers();
}
/**
*
*/
public void read(Node node)
{
while (node != null)
{
if (node instanceof Element)
{
Element elt = (Element) node;
IElementHandler handler = handlers.get(elt.getNodeName());
if (handler != null)
{
handler.parseElement(elt);
}
}
node = node.getNextSibling();
}
}
/**
*
*/
protected void initHandlers()
{
handlers.put("save", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.save();
}
});
handlers.put("restore", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.restore();
}
});
handlers.put("scale", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.scale(Double.parseDouble(elt.getAttribute("scale")));
}
});
handlers.put("translate", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.translate(Double.parseDouble(elt.getAttribute("dx")),
Double.parseDouble(elt.getAttribute("dy")));
}
});
handlers.put("rotate", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.rotate(Double.parseDouble(elt.getAttribute("theta")),
elt.getAttribute("flipH").equals("1"), elt
.getAttribute("flipV").equals("1"), Double
.parseDouble(elt.getAttribute("cx")), Double
.parseDouble(elt.getAttribute("cy")));
}
});
handlers.put("strokewidth", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.setStrokeWidth(Double.parseDouble(elt
.getAttribute("width")));
}
});
handlers.put("strokecolor", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.setStrokeColor(elt.getAttribute("color"));
}
});
handlers.put("dashed", new IElementHandler()
{
public void parseElement(Element elt)
{
String temp = elt.getAttribute("fixDash");
boolean fixDash = temp != null && temp.equals("1");
canvas.setDashed(elt.getAttribute("dashed").equals("1"), fixDash);
}
});
handlers.put("dashpattern", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.setDashPattern(elt.getAttribute("pattern"));
}
});
handlers.put("linecap", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.setLineCap(elt.getAttribute("cap"));
}
});
handlers.put("linejoin", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.setLineJoin(elt.getAttribute("join"));
}
});
handlers.put("miterlimit", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.setMiterLimit(Double.parseDouble(elt
.getAttribute("limit")));
}
});
handlers.put("fontsize", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.setFontSize(Double.parseDouble(elt.getAttribute("size")));
}
});
handlers.put("fontcolor", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.setFontColor(elt.getAttribute("color"));
}
});
handlers.put("fontbackgroundcolor", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.setFontBackgroundColor(elt.getAttribute("color"));
}
});
handlers.put("fontbordercolor", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.setFontBorderColor(elt.getAttribute("color"));
}
});
handlers.put("fontfamily", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.setFontFamily(elt.getAttribute("family"));
}
});
handlers.put("fontstyle", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.setFontStyle(Integer.parseInt(elt.getAttribute("style")));
}
});
handlers.put("alpha", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.setAlpha(Double.parseDouble(elt.getAttribute("alpha")));
}
});
handlers.put("fillalpha", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.setFillAlpha(Double.parseDouble(elt.getAttribute("alpha")));
}
});
handlers.put("strokealpha", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.setStrokeAlpha(Double.parseDouble(elt.getAttribute("alpha")));
}
});
handlers.put("fillcolor", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.setFillColor(elt.getAttribute("color"));
}
});
handlers.put("shadowcolor", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.setShadowColor(elt.getAttribute("color"));
}
});
handlers.put("shadowalpha", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.setShadowAlpha(Double.parseDouble(elt.getAttribute("alpha")));
}
});
handlers.put("shadowoffset", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.setShadowOffset(Double.parseDouble(elt.getAttribute("dx")),
Double.parseDouble(elt.getAttribute("dy")));
}
});
handlers.put("shadow", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.setShadow(elt.getAttribute("enabled").equals("1"));
}
});
handlers.put("gradient", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.setGradient(elt.getAttribute("c1"),
elt.getAttribute("c2"),
Double.parseDouble(elt.getAttribute("x")),
Double.parseDouble(elt.getAttribute("y")),
Double.parseDouble(elt.getAttribute("w")),
Double.parseDouble(elt.getAttribute("h")),
elt.getAttribute("direction"),
Double.parseDouble(getValue(elt, "alpha1", "1")),
Double.parseDouble(getValue(elt, "alpha2", "1")));
}
});
handlers.put("rect", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.rect(Double.parseDouble(elt.getAttribute("x")),
Double.parseDouble(elt.getAttribute("y")),
Double.parseDouble(elt.getAttribute("w")),
Double.parseDouble(elt.getAttribute("h")));
}
});
handlers.put("roundrect", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.roundrect(Double.parseDouble(elt.getAttribute("x")),
Double.parseDouble(elt.getAttribute("y")),
Double.parseDouble(elt.getAttribute("w")),
Double.parseDouble(elt.getAttribute("h")),
Double.parseDouble(elt.getAttribute("dx")),
Double.parseDouble(elt.getAttribute("dy")));
}
});
handlers.put("ellipse", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.ellipse(Double.parseDouble(elt.getAttribute("x")),
Double.parseDouble(elt.getAttribute("y")),
Double.parseDouble(elt.getAttribute("w")),
Double.parseDouble(elt.getAttribute("h")));
}
});
handlers.put("image", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.image(Double.parseDouble(elt.getAttribute("x")), Double
.parseDouble(elt.getAttribute("y")), Double
.parseDouble(elt.getAttribute("w")), Double
.parseDouble(elt.getAttribute("h")), elt
.getAttribute("src"), elt.getAttribute("aspect")
.equals("1"), elt.getAttribute("flipH").equals("1"),
elt.getAttribute("flipV").equals("1"));
}
});
handlers.put("text", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.text(Double.parseDouble(elt.getAttribute("x")),
Double.parseDouble(elt.getAttribute("y")),
Double.parseDouble(elt.getAttribute("w")),
Double.parseDouble(elt.getAttribute("h")),
elt.getAttribute("str"),
elt.getAttribute("align"),
elt.getAttribute("valign"),
getValue(elt, "wrap", "").equals("1"),
elt.getAttribute("format"),
elt.getAttribute("overflow"),
getValue(elt, "clip", "").equals("1"),
Double.parseDouble(getValue(elt, "rotation", "0")),
elt.getAttribute("dir"));
}
});
handlers.put("begin", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.begin();
}
});
handlers.put("move", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.moveTo(Double.parseDouble(elt.getAttribute("x")),
Double.parseDouble(elt.getAttribute("y")));
}
});
handlers.put("line", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.lineTo(Double.parseDouble(elt.getAttribute("x")),
Double.parseDouble(elt.getAttribute("y")));
}
});
handlers.put("quad", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.quadTo(Double.parseDouble(elt.getAttribute("x1")),
Double.parseDouble(elt.getAttribute("y1")),
Double.parseDouble(elt.getAttribute("x2")),
Double.parseDouble(elt.getAttribute("y2")));
}
});
handlers.put("curve", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.curveTo(Double.parseDouble(elt.getAttribute("x1")),
Double.parseDouble(elt.getAttribute("y1")),
Double.parseDouble(elt.getAttribute("x2")),
Double.parseDouble(elt.getAttribute("y2")),
Double.parseDouble(elt.getAttribute("x3")),
Double.parseDouble(elt.getAttribute("y3")));
}
});
handlers.put("close", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.close();
}
});
handlers.put("stroke", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.stroke();
}
});
handlers.put("fill", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.fill();
}
});
handlers.put("fillstroke", new IElementHandler()
{
public void parseElement(Element elt)
{
canvas.fillAndStroke();
}
});
}
/**
* Returns the given attribute value or an empty string.
*/
protected String getValue(Element elt, String name, String defaultValue)
{
String value = elt.getAttribute(name);
if (value == null)
{
value = defaultValue;
}
return value;
};
/**
*
*/
protected interface IElementHandler
{
void parseElement(Element elt);
}
}

View file

@ -0,0 +1,290 @@
/**
* Copyright (c) 2007, Gaudenz Alder
*/
package com.mxgraph.reader;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.canvas.mxICanvas;
import com.mxgraph.canvas.mxImageCanvas;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxUtils;
/**
* A converter that renders display XML data onto a graphics canvas. This
* reader can only be used to generate images for encoded graph views.
*/
public class mxGraphViewImageReader extends mxGraphViewReader
{
/**
* Specifies the background color. Default is null.
*/
protected Color background;
/**
* Specifies if the image should be anti-aliased. Default is true.
*/
protected boolean antiAlias;
/**
* Specifies the border which is added to the size of the graph. Default is
* 0.
*/
protected int border;
/**
* Specifies the border which is added to the size of the graph. Default is
* true.
*/
protected boolean cropping;
/**
* Defines the clip to be drawn. Default is null.
*/
protected mxRectangle clip;
/**
* Constructs a new reader with a transparent background.
*/
public mxGraphViewImageReader()
{
this(null);
}
/**
* Constructs a new reader with the given background color.
*/
public mxGraphViewImageReader(Color background)
{
this(background, 0);
}
/**
* Constructs a new reader with a transparent background.
*/
public mxGraphViewImageReader(Color background, int border)
{
this(background, border, true);
}
/**
* Constructs a new reader with a transparent background.
*/
public mxGraphViewImageReader(Color background, int border,
boolean antiAlias)
{
this(background, border, antiAlias, true);
}
/**
* Constructs a new reader with a transparent background.
*/
public mxGraphViewImageReader(Color background, int border,
boolean antiAlias, boolean cropping)
{
setBackground(background);
setBorder(border);
setAntiAlias(antiAlias);
setCropping(cropping);
}
/**
*
*/
public Color getBackground()
{
return background;
}
/**
*
*/
public void setBackground(Color background)
{
this.background = background;
}
/**
*
*/
public int getBorder()
{
return border;
}
/**
*
*/
public void setBorder(int border)
{
this.border = border;
}
/**
*
*/
public boolean isAntiAlias()
{
return antiAlias;
}
/**
*
*/
public void setAntiAlias(boolean antiAlias)
{
this.antiAlias = antiAlias;
}
/**
* Specifies the optional clipping rectangle.
*/
public boolean isCropping()
{
return cropping;
}
/**
*
*/
public void setCropping(boolean value)
{
this.cropping = value;
}
/**
*
*/
public mxRectangle getClip()
{
return clip;
}
/**
*
*/
public void setClip(mxRectangle value)
{
this.clip = value;
}
/*
* (non-Javadoc)
*
* @see
* com.mxgraph.reader.mxGraphViewReader#createCanvas(java.util.Hashtable)
*/
public mxICanvas createCanvas(Map<String, Object> attrs)
{
int width = 0;
int height = 0;
int dx = 0;
int dy = 0;
mxRectangle tmp = getClip();
if (tmp != null)
{
dx -= (int) tmp.getX();
dy -= (int) tmp.getY();
width = (int) tmp.getWidth();
height = (int) tmp.getHeight();
}
else
{
int x = (int) Math.round(mxUtils.getDouble(attrs, "x"));
int y = (int) Math.round(mxUtils.getDouble(attrs, "y"));
width = (int) (Math.round(mxUtils.getDouble(attrs, "width")))
+ border + 3;
height = (int) (Math.round(mxUtils.getDouble(attrs, "height")))
+ border + 3;
if (isCropping())
{
dx = -x + 3;
dy = -y + 3;
}
else
{
width += x;
height += y;
}
}
mxImageCanvas canvas = new mxImageCanvas(createGraphicsCanvas(), width,
height, getBackground(), isAntiAlias());
canvas.setTranslate(dx, dy);
return canvas;
}
/**
* Hook that creates the graphics canvas.
*/
protected mxGraphics2DCanvas createGraphicsCanvas()
{
return new mxGraphics2DCanvas();
}
/**
* Creates the image for the given display XML file. (Note: The XML file is
* an encoded mxGraphView, not mxGraphModel.)
*
* @param filename
* Filename of the display XML file.
* @return Returns an image representing the display XML file.
*/
public static BufferedImage convert(String filename,
mxGraphViewImageReader viewReader)
throws ParserConfigurationException, SAXException, IOException
{
return convert(new InputSource(new FileInputStream(filename)),
viewReader);
}
/**
* Creates the image for the given display XML input source. (Note: The XML
* is an encoded mxGraphView, not mxGraphModel.)
*
* @param inputSource
* Input source that contains the display XML.
* @return Returns an image representing the display XML input source.
*/
public static BufferedImage convert(InputSource inputSource,
mxGraphViewImageReader viewReader)
throws ParserConfigurationException, SAXException, IOException
{
BufferedImage result = null;
XMLReader reader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
reader.setContentHandler(viewReader);
reader.parse(inputSource);
if (viewReader.getCanvas() instanceof mxImageCanvas)
{
result = ((mxImageCanvas) viewReader.getCanvas()).destroy();
}
return result;
}
}

View file

@ -0,0 +1,227 @@
/**
* Copyright (c) 2007, Gaudenz Alder
*/
package com.mxgraph.reader;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import com.mxgraph.canvas.mxICanvas;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
/**
* An abstract converter that renders display XML data onto a canvas.
*/
public abstract class mxGraphViewReader extends DefaultHandler
{
/**
* Holds the canvas to be used for rendering the graph.
*/
protected mxICanvas canvas;
/**
* Holds the global scale of the graph. This is set just before
* createCanvas is called.
*/
protected double scale = 1;
/**
* Specifies if labels should be rendered as HTML markup.
*/
protected boolean htmlLabels = false;
/**
* Sets the htmlLabels switch.
*/
public void setHtmlLabels(boolean value)
{
htmlLabels = value;
}
/**
* Returns the htmlLabels switch.
*/
public boolean isHtmlLabels()
{
return htmlLabels;
}
/**
* Returns the canvas to be used for rendering.
*
* @param attrs Specifies the attributes of the new canvas.
* @return Returns a new canvas.
*/
public abstract mxICanvas createCanvas(Map<String, Object> attrs);
/**
* Returns the canvas that is used for rendering the graph.
*
* @return Returns the canvas.
*/
public mxICanvas getCanvas()
{
return canvas;
}
/* (non-Javadoc)
* @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
*/
public void startElement(String uri, String localName, String qName,
Attributes atts) throws SAXException
{
String tagName = qName.toUpperCase();
Map<String, Object> attrs = new Hashtable<String, Object>();
for (int i = 0; i < atts.getLength(); i++)
{
String name = atts.getQName(i);
// Workaround for possible null name
if (name == null || name.length() == 0)
{
name = atts.getLocalName(i);
}
attrs.put(name, atts.getValue(i));
}
parseElement(tagName, attrs);
}
/**
* Parses the given element and paints it onto the canvas.
*
* @param tagName Name of the node to be parsed.
* @param attrs Attributes of the node to be parsed.
*/
public void parseElement(String tagName, Map<String, Object> attrs)
{
if (canvas == null && tagName.equalsIgnoreCase("graph"))
{
scale = mxUtils.getDouble(attrs, "scale", 1);
canvas = createCanvas(attrs);
if (canvas != null)
{
canvas.setScale(scale);
}
}
else if (canvas != null)
{
boolean edge = tagName.equalsIgnoreCase("edge");
boolean group = tagName.equalsIgnoreCase("group");
boolean vertex = tagName.equalsIgnoreCase("vertex");
if ((edge && attrs.containsKey("points"))
|| ((vertex || group) && attrs.containsKey("x")
&& attrs.containsKey("y")
&& attrs.containsKey("width") && attrs
.containsKey("height")))
{
mxCellState state = new mxCellState(null, null, attrs);
String label = parseState(state, edge);
canvas.drawCell(state);
canvas.drawLabel(label, state, isHtmlLabels());
}
}
}
/**
* Parses the bounds, absolute points and label information from the style
* of the state into its respective fields and returns the label of the
* cell.
*/
public String parseState(mxCellState state, boolean edge)
{
Map<String, Object> style = state.getStyle();
// Parses the bounds
state.setX(mxUtils.getDouble(style, "x"));
state.setY(mxUtils.getDouble(style, "y"));
state.setWidth(mxUtils.getDouble(style, "width"));
state.setHeight(mxUtils.getDouble(style, "height"));
// Parses the absolute points list
List<mxPoint> pts = parsePoints(mxUtils.getString(style, "points"));
if (pts.size() > 0)
{
state.setAbsolutePoints(pts);
}
// Parses the label and label bounds
String label = mxUtils.getString(style, "label");
if (label != null && label.length() > 0)
{
mxPoint offset = new mxPoint(mxUtils.getDouble(style, "dx"),
mxUtils.getDouble(style, "dy"));
mxRectangle vertexBounds = (!edge) ? state : null;
state.setLabelBounds(mxUtils.getLabelPaintBounds(label, state
.getStyle(), mxUtils.isTrue(style, "html", false), offset,
vertexBounds, scale));
}
return label;
}
/**
* Parses the list of points into an object-oriented representation.
*
* @param pts String containing a list of points.
* @return Returns the points as a list of mxPoints.
*/
public static List<mxPoint> parsePoints(String pts)
{
List<mxPoint> result = new ArrayList<mxPoint>();
if (pts != null)
{
int len = pts.length();
String tmp = "";
String x = null;
for (int i = 0; i < len; i++)
{
char c = pts.charAt(i);
if (c == ',' || c == ' ')
{
if (x == null)
{
x = tmp;
}
else
{
result.add(new mxPoint(Double.parseDouble(x), Double
.parseDouble(tmp)));
x = null;
}
tmp = "";
}
else
{
tmp += c;
}
}
result.add(new mxPoint(Double.parseDouble(x), Double
.parseDouble(tmp)));
}
return result;
}
}

View file

@ -0,0 +1,482 @@
package com.mxgraph.reader;
import java.util.Hashtable;
import java.util.Map;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import com.mxgraph.canvas.mxICanvas2D;
/**
XMLReader reader = SAXParserFactory.newInstance().newSAXParser()
.getXMLReader();
reader.setContentHandler(new mxSaxExportHandler(
new mxGraphicsExportCanvas(g2)));
reader.parse(new InputSource(new StringReader(xml)));
*/
public class mxSaxOutputHandler extends DefaultHandler
{
/**
*
*/
protected mxICanvas2D canvas;
/**
*
*/
protected transient Map<String, IElementHandler> handlers = new Hashtable<String, IElementHandler>();
/**
*
*/
public mxSaxOutputHandler(mxICanvas2D canvas)
{
setCanvas(canvas);
initHandlers();
}
/**
* Sets the canvas for rendering.
*/
public void setCanvas(mxICanvas2D value)
{
canvas = value;
}
/**
* Returns the canvas for rendering.
*/
public mxICanvas2D getCanvas()
{
return canvas;
}
/**
*
*/
public void startElement(String uri, String localName, String qName,
Attributes atts) throws SAXException
{
IElementHandler handler = handlers.get(qName.toLowerCase());
if (handler != null)
{
handler.parseElement(atts);
}
}
/**
*
*/
protected void initHandlers()
{
handlers.put("save", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.save();
}
});
handlers.put("restore", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.restore();
}
});
handlers.put("scale", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.scale(Double.parseDouble(atts.getValue("scale")));
}
});
handlers.put("translate", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.translate(Double.parseDouble(atts.getValue("dx")),
Double.parseDouble(atts.getValue("dy")));
}
});
handlers.put("rotate", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.rotate(Double.parseDouble(atts.getValue("theta")), atts
.getValue("flipH").equals("1"), atts.getValue("flipV")
.equals("1"), Double.parseDouble(atts.getValue("cx")),
Double.parseDouble(atts.getValue("cy")));
}
});
handlers.put("strokewidth", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.setStrokeWidth(Double.parseDouble(atts.getValue("width")));
}
});
handlers.put("strokecolor", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.setStrokeColor(atts.getValue("color"));
}
});
handlers.put("dashed", new IElementHandler()
{
public void parseElement(Attributes atts)
{
String temp = atts.getValue("fixDash");
boolean fixDash = temp != null && temp.equals("1");
canvas.setDashed(atts.getValue("dashed").equals("1"), fixDash);
}
});
handlers.put("dashpattern", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.setDashPattern(atts.getValue("pattern"));
}
});
handlers.put("linecap", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.setLineCap(atts.getValue("cap"));
}
});
handlers.put("linejoin", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.setLineJoin(atts.getValue("join"));
}
});
handlers.put("miterlimit", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.setMiterLimit(Double.parseDouble(atts.getValue("limit")));
}
});
handlers.put("fontsize", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.setFontSize(Double.parseDouble(atts.getValue("size")));
}
});
handlers.put("fontcolor", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.setFontColor(atts.getValue("color"));
}
});
handlers.put("fontbackgroundcolor", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.setFontBackgroundColor(atts.getValue("color"));
}
});
handlers.put("fontbordercolor", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.setFontBorderColor(atts.getValue("color"));
}
});
handlers.put("fontfamily", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.setFontFamily(atts.getValue("family"));
}
});
handlers.put("fontstyle", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.setFontStyle(Integer.parseInt(atts.getValue("style")));
}
});
handlers.put("alpha", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.setAlpha(Double.parseDouble(atts.getValue("alpha")));
}
});
handlers.put("fillalpha", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.setFillAlpha(Double.parseDouble(atts.getValue("alpha")));
}
});
handlers.put("strokealpha", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.setStrokeAlpha(Double.parseDouble(atts.getValue("alpha")));
}
});
handlers.put("fillcolor", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.setFillColor(atts.getValue("color"));
}
});
handlers.put("shadowcolor", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.setShadowColor(atts.getValue("color"));
}
});
handlers.put("shadowalpha", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.setShadowAlpha(Double.parseDouble(atts.getValue("alpha")));
}
});
handlers.put("shadowoffset", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.setShadowOffset(Double.parseDouble(atts.getValue("dx")),
Double.parseDouble(atts.getValue("dy")));
}
});
handlers.put("shadow", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.setShadow(getValue(atts, "enabled", "1").equals("1"));
}
});
handlers.put("gradient", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.setGradient(atts.getValue("c1"), atts.getValue("c2"),
Double.parseDouble(atts.getValue("x")),
Double.parseDouble(atts.getValue("y")),
Double.parseDouble(atts.getValue("w")),
Double.parseDouble(atts.getValue("h")),
atts.getValue("direction"),
Double.parseDouble(getValue(atts, "alpha1", "1")),
Double.parseDouble(getValue(atts, "alpha2", "1")));
}
});
handlers.put("rect", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.rect(Double.parseDouble(atts.getValue("x")),
Double.parseDouble(atts.getValue("y")),
Double.parseDouble(atts.getValue("w")),
Double.parseDouble(atts.getValue("h")));
}
});
handlers.put("roundrect", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.roundrect(Double.parseDouble(atts.getValue("x")),
Double.parseDouble(atts.getValue("y")),
Double.parseDouble(atts.getValue("w")),
Double.parseDouble(atts.getValue("h")),
Double.parseDouble(atts.getValue("dx")),
Double.parseDouble(atts.getValue("dy")));
}
});
handlers.put("ellipse", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.ellipse(Double.parseDouble(atts.getValue("x")),
Double.parseDouble(atts.getValue("y")),
Double.parseDouble(atts.getValue("w")),
Double.parseDouble(atts.getValue("h")));
}
});
handlers.put("image", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.image(Double.parseDouble(atts.getValue("x")),
Double.parseDouble(atts.getValue("y")),
Double.parseDouble(atts.getValue("w")),
Double.parseDouble(atts.getValue("h")),
atts.getValue("src"),
atts.getValue("aspect").equals("1"),
atts.getValue("flipH").equals("1"),
atts.getValue("flipV").equals("1"));
}
});
handlers.put("text", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.text(Double.parseDouble(atts.getValue("x")),
Double.parseDouble(atts.getValue("y")),
Double.parseDouble(atts.getValue("w")),
Double.parseDouble(atts.getValue("h")),
atts.getValue("str"),
atts.getValue("align"),
atts.getValue("valign"),
getValue(atts, "wrap", "").equals("1"),
atts.getValue("format"),
atts.getValue("overflow"),
getValue(atts, "clip", "").equals("1"),
Double.parseDouble(getValue(atts, "rotation", "0")),
getValue(atts, "dir", null));
}
});
handlers.put("begin", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.begin();
}
});
handlers.put("move", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.moveTo(Double.parseDouble(atts.getValue("x")),
Double.parseDouble(atts.getValue("y")));
}
});
handlers.put("line", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.lineTo(Double.parseDouble(atts.getValue("x")),
Double.parseDouble(atts.getValue("y")));
}
});
handlers.put("quad", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.quadTo(Double.parseDouble(atts.getValue("x1")),
Double.parseDouble(atts.getValue("y1")),
Double.parseDouble(atts.getValue("x2")),
Double.parseDouble(atts.getValue("y2")));
}
});
handlers.put("curve", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.curveTo(Double.parseDouble(atts.getValue("x1")),
Double.parseDouble(atts.getValue("y1")),
Double.parseDouble(atts.getValue("x2")),
Double.parseDouble(atts.getValue("y2")),
Double.parseDouble(atts.getValue("x3")),
Double.parseDouble(atts.getValue("y3")));
}
});
handlers.put("close", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.close();
}
});
handlers.put("stroke", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.stroke();
}
});
handlers.put("fill", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.fill();
}
});
handlers.put("fillstroke", new IElementHandler()
{
public void parseElement(Attributes atts)
{
canvas.fillAndStroke();
}
});
}
/**
* Returns the given attribute value or an empty string.
*/
protected String getValue(Attributes atts, String name, String defaultValue)
{
String value = atts.getValue(name);
if (value == null)
{
value = defaultValue;
}
return value;
};
/**
*
*/
protected interface IElementHandler
{
void parseElement(Attributes atts);
}
}

View file

@ -0,0 +1,6 @@
<HTML>
<BODY>
This package contains the classes required to turn an encoded mxGraphView
into an image using SAX and without having to create a graph model.
</BODY>
</HTML>

View file

@ -0,0 +1,4 @@
alreadyConnected=Nodes already connected
containsValidationErrors=Contains validation errors
collapse-expand=Collapse/Expand
doubleClickOrientation=Doubleclick to change orientation

View file

@ -0,0 +1,4 @@
alreadyConnected=Knoten schon verbunden
containsValidationErrors=Enthält Validierungsfehler
collapse-expand=Zusammenklappen/Auseinanderfalten
doubleClickOrientation=Doppelklicken um Orientierung zu ändern

View file

@ -0,0 +1,4 @@
alreadyConnected=Los nodos ya están conectados
containsValidationErrors=Contiene errores de validación
collapse-expand=Minimizar/Maximizar
doubleClickOrientation=Doble clic para cambiar la orientación

View file

@ -0,0 +1,4 @@
alreadyConnected=N\u0153uds déjà connectés
containsValidationErrors=Contient des erreurs de validation
collapse-expand=Réduire/Développer
doubleClickOrientation=Double-cliquez pour modifier l\u2019orientation

View file

@ -0,0 +1,4 @@
alreadyConnected=Nodi già connessi
containsValidationErrors=Contiene errori di convalida
collapse-expand=Riduci/Espandi
doubleClickOrientation=Doppio click per cambiare orientamento

View file

@ -0,0 +1,4 @@
alreadyConnected=\u30ce\u30fc\u30c9\u304c\u3059\u3067\u306b\u63a5\u7d9a\u3055\u308c\u3066\u3044\u307e\u3059
containsValidationErrors=\u691c\u8a3c\u30a8\u30e9\u30fc\u304c\u3042\u308a\u307e\u3059
collapse-expand=\u7e2e\u5c0f/\u62e1\u5927
doubleClickOrientation=\u30c0\u30d6\u30eb\u30af\u30ea\u30c3\u30af\u3057\u3066\u5411\u304d\u3092\u5909\u66f4

View file

@ -0,0 +1,4 @@
alreadyConnected=Knooppunten reeds verbonden
containsValidationErrors=Bevat validatiefouten
collapse-expand=Verkleinen/Vergroten
doubleClickOrientation=Dubbelklik om richting te veranderen

View file

@ -0,0 +1,4 @@
alreadyConnected=Nodos já conectados 
containsValidationErrors=Contém erros de validação 
collapse-expand=Recolher/Expandir 
doubleClickOrientation=Clique duas vezes para mudar de orientação

View file

@ -0,0 +1,4 @@
alreadyConnected=\u0423\u0437\u043b\u044b \u0443\u0436\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u044b
containsValidationErrors=\u0421\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043e\u0448\u0438\u0431\u043a\u0438 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438
collapse-expand=\u0421\u0432\u0435\u0440\u043d\u0443\u0442\u044c/\u0420\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442\u044c
doubleClickOrientation="\u0414\u0432\u0430\u0436\u0434\u044b \u0449\u0435\u043b\u043a\u043d\u0438\u0442\u0435 \u043c\u044b\u0448\u044c\u044e, \u0447\u0442\u043e\u0431\u044b \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043e\u0440\u0438\u0435\u043d\u0442\u0430\u0446\u0438\u044e"

View file

@ -0,0 +1,4 @@
alreadyConnected=\u5df2\u8fde\u63a5\u8282\u70b9
containsValidationErrors=\u5305\u542b\u9a8c\u8bc1\u9519\u8bef
collapse-expand=\u6298\u53e0/\u5c55\u5f00
doubleClickOrientation=\u53cc\u51fb\u6539\u53d8\u65b9\u5411

View file

@ -0,0 +1,40 @@
package com.mxgraph.shape;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.GeneralPath;
import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.view.mxCellState;
public class mxActorShape extends mxBasicShape
{
/**
*
*/
public Shape createShape(mxGraphics2DCanvas canvas, mxCellState state)
{
Rectangle temp = state.getRectangle();
int x = temp.x;
int y = temp.y;
int w = temp.width;
int h = temp.height;
float width = w * 2 / 6;
GeneralPath path = new GeneralPath();
path.moveTo(x, y + h);
path.curveTo(x, y + 3 * h / 5, x, y + 2 * h / 5, x + w / 2, y + 2 * h
/ 5);
path.curveTo(x + w / 2 - width, y + 2 * h / 5, x + w / 2 - width, y, x
+ w / 2, y);
path.curveTo(x + w / 2 + width, y, x + w / 2 + width, y + 2 * h / 5, x
+ w / 2, y + 2 * h / 5);
path.curveTo(x + w, y + 2 * h / 5, x + w, y + 3 * h / 5, x + w, y + h);
path.closePath();
return path;
}
}

View file

@ -0,0 +1,68 @@
package com.mxgraph.shape;
import java.awt.Polygon;
import java.awt.Shape;
import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxPoint;
import com.mxgraph.view.mxCellState;
public class mxArrowShape extends mxBasicShape
{
/**
*
*/
public Shape createShape(mxGraphics2DCanvas canvas, mxCellState state)
{
double scale = canvas.getScale();
mxPoint p0 = state.getAbsolutePoint(0);
mxPoint pe = state.getAbsolutePoint(state.getAbsolutePointCount() - 1);
// Geometry of arrow
double spacing = mxConstants.ARROW_SPACING * scale;
double width = mxConstants.ARROW_WIDTH * scale;
double arrow = mxConstants.ARROW_SIZE * scale;
double dx = pe.getX() - p0.getX();
double dy = pe.getY() - p0.getY();
double dist = Math.sqrt(dx * dx + dy * dy);
double length = dist - 2 * spacing - arrow;
// Computes the norm and the inverse norm
double nx = dx / dist;
double ny = dy / dist;
double basex = length * nx;
double basey = length * ny;
double floorx = width * ny / 3;
double floory = -width * nx / 3;
// Computes points
double p0x = p0.getX() - floorx / 2 + spacing * nx;
double p0y = p0.getY() - floory / 2 + spacing * ny;
double p1x = p0x + floorx;
double p1y = p0y + floory;
double p2x = p1x + basex;
double p2y = p1y + basey;
double p3x = p2x + floorx;
double p3y = p2y + floory;
// p4 not required
double p5x = p3x - 3 * floorx;
double p5y = p3y - 3 * floory;
Polygon poly = new Polygon();
poly.addPoint((int) Math.round(p0x), (int) Math.round(p0y));
poly.addPoint((int) Math.round(p1x), (int) Math.round(p1y));
poly.addPoint((int) Math.round(p2x), (int) Math.round(p2y));
poly.addPoint((int) Math.round(p3x), (int) Math.round(p3y));
poly.addPoint((int) Math.round(pe.getX() - spacing * nx), (int) Math
.round(pe.getY() - spacing * ny));
poly.addPoint((int) Math.round(p5x), (int) Math.round(p5y));
poly.addPoint((int) Math.round(p5x + floorx), (int) Math.round(p5y
+ floory));
return poly;
}
}

View file

@ -0,0 +1,139 @@
/**
* Copyright (c) 2010, Gaudenz Alder, David Benson
*/
package com.mxgraph.shape;
import java.awt.Color;
import java.awt.Paint;
import java.awt.Shape;
import java.util.Map;
import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
public class mxBasicShape implements mxIShape
{
/**
*
*/
public void paintShape(mxGraphics2DCanvas canvas, mxCellState state)
{
Shape shape = createShape(canvas, state);
if (shape != null)
{
// Paints the background
if (configureGraphics(canvas, state, true))
{
canvas.fillShape(shape, hasShadow(canvas, state));
}
// Paints the foreground
if (configureGraphics(canvas, state, false))
{
canvas.getGraphics().draw(shape);
}
}
}
/**
*
*/
public Shape createShape(mxGraphics2DCanvas canvas, mxCellState state)
{
return null;
}
/**
* Configures the graphics object ready to paint.
* @param canvas the canvas to be painted to
* @param state the state of cell to be painted
* @param background whether or not this is the background stage of
* the shape paint
* @return whether or not the shape is ready to be drawn
*/
protected boolean configureGraphics(mxGraphics2DCanvas canvas,
mxCellState state, boolean background)
{
Map<String, Object> style = state.getStyle();
if (background)
{
// Paints the background of the shape
Paint fillPaint = hasGradient(canvas, state) ? canvas
.createFillPaint(getGradientBounds(canvas, state), style)
: null;
if (fillPaint != null)
{
canvas.getGraphics().setPaint(fillPaint);
return true;
}
else
{
Color color = getFillColor(canvas, state);
canvas.getGraphics().setColor(color);
return color != null;
}
}
else
{
canvas.getGraphics().setPaint(null);
Color color = getStrokeColor(canvas, state);
canvas.getGraphics().setColor(color);
canvas.getGraphics().setStroke(canvas.createStroke(style));
return color != null;
}
}
/**
*
*/
protected mxRectangle getGradientBounds(mxGraphics2DCanvas canvas,
mxCellState state)
{
return state;
}
/**
*
*/
public boolean hasGradient(mxGraphics2DCanvas canvas, mxCellState state)
{
return true;
}
/**
*
*/
public boolean hasShadow(mxGraphics2DCanvas canvas, mxCellState state)
{
return mxUtils
.isTrue(state.getStyle(), mxConstants.STYLE_SHADOW, false);
}
/**
*
*/
public Color getFillColor(mxGraphics2DCanvas canvas, mxCellState state)
{
return mxUtils.getColor(state.getStyle(), mxConstants.STYLE_FILLCOLOR);
}
/**
*
*/
public Color getStrokeColor(mxGraphics2DCanvas canvas, mxCellState state)
{
return mxUtils
.getColor(state.getStyle(), mxConstants.STYLE_STROKECOLOR);
}
}

View file

@ -0,0 +1,48 @@
package com.mxgraph.shape;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.GeneralPath;
import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.view.mxCellState;
public class mxCloudShape extends mxBasicShape
{
/**
*
*/
public Shape createShape(mxGraphics2DCanvas canvas, mxCellState state)
{
Rectangle temp = state.getRectangle();
int x = temp.x;
int y = temp.y;
int w = temp.width;
int h = temp.height;
GeneralPath path = new GeneralPath();
path.moveTo((float) (x + 0.25 * w), (float) (y + 0.25 * h));
path.curveTo((float) (x + 0.05 * w), (float) (y + 0.25 * h), x,
(float) (y + 0.5 * h), (float) (x + 0.16 * w),
(float) (y + 0.55 * h));
path.curveTo(x, (float) (y + 0.66 * h), (float) (x + 0.18 * w),
(float) (y + 0.9 * h), (float) (x + 0.31 * w),
(float) (y + 0.8 * h));
path.curveTo((float) (x + 0.4 * w), (y + h), (float) (x + 0.7 * w),
(y + h), (float) (x + 0.8 * w), (float) (y + 0.8 * h));
path.curveTo((x + w), (float) (y + 0.8 * h), (x + w),
(float) (y + 0.6 * h), (float) (x + 0.875 * w),
(float) (y + 0.5 * h));
path.curveTo((x + w), (float) (y + 0.3 * h), (float) (x + 0.8 * w),
(float) (y + 0.1 * h), (float) (x + 0.625 * w),
(float) (y + 0.2 * h));
path.curveTo((float) (x + 0.5 * w), (float) (y + 0.05 * h),
(float) (x + 0.3 * w), (float) (y + 0.05 * h),
(float) (x + 0.25 * w), (float) (y + 0.25 * h));
path.closePath();
return path;
}
}

View file

@ -0,0 +1,196 @@
/**
* Copyright (c) 2010, Gaudenz Alder, David Benson
*/
package com.mxgraph.shape;
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxLine;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
public class mxConnectorShape extends mxBasicShape
{
/**
*
*/
public void paintShape(mxGraphics2DCanvas canvas, mxCellState state)
{
if (state.getAbsolutePointCount() > 1
&& configureGraphics(canvas, state, false))
{
List<mxPoint> pts = new ArrayList<mxPoint>(
state.getAbsolutePoints());
Map<String, Object> style = state.getStyle();
// Paints the markers and updates the points
// Switch off any dash pattern for markers
boolean dashed = mxUtils.isTrue(style, mxConstants.STYLE_DASHED);
Object dashedValue = style.get(mxConstants.STYLE_DASHED);
if (dashed)
{
style.remove(mxConstants.STYLE_DASHED);
canvas.getGraphics().setStroke(canvas.createStroke(style));
}
translatePoint(pts, 0,
paintMarker(canvas, state, true));
translatePoint(
pts,
pts.size() - 1,
paintMarker(canvas, state, false));
if (dashed)
{
// Replace the dash pattern
style.put(mxConstants.STYLE_DASHED, dashedValue);
canvas.getGraphics().setStroke(canvas.createStroke(style));
}
paintPolyline(canvas, pts, state.getStyle());
}
}
/**
*
*/
protected void paintPolyline(mxGraphics2DCanvas canvas,
List<mxPoint> points, Map<String, Object> style)
{
boolean rounded = isRounded(style)
&& canvas.getScale() > mxConstants.MIN_SCALE_FOR_ROUNDED_LINES;
canvas.paintPolyline(points.toArray(new mxPoint[points.size()]),
rounded);
}
/**
*
*/
public boolean isRounded(Map<String, Object> style)
{
return mxUtils.isTrue(style, mxConstants.STYLE_ROUNDED, false);
}
/**
*
*/
private void translatePoint(List<mxPoint> points, int index, mxPoint offset)
{
if (offset != null)
{
mxPoint pt = (mxPoint) points.get(index).clone();
pt.setX(pt.getX() + offset.getX());
pt.setY(pt.getY() + offset.getY());
points.set(index, pt);
}
}
/**
* Draws the marker for the given edge.
*
* @return the offset of the marker from the end of the line
*/
public mxPoint paintMarker(mxGraphics2DCanvas canvas, mxCellState state, boolean source)
{
Map<String, Object> style = state.getStyle();
float strokeWidth = (float) (mxUtils.getFloat(style,
mxConstants.STYLE_STROKEWIDTH, 1) * canvas.getScale());
String type = mxUtils.getString(style,
(source) ? mxConstants.STYLE_STARTARROW
: mxConstants.STYLE_ENDARROW, "");
float size = (mxUtils.getFloat(style,
(source) ? mxConstants.STYLE_STARTSIZE
: mxConstants.STYLE_ENDSIZE,
mxConstants.DEFAULT_MARKERSIZE));
Color color = mxUtils.getColor(style, mxConstants.STYLE_STROKECOLOR);
canvas.getGraphics().setColor(color);
double absSize = size * canvas.getScale();
List<mxPoint> points = state.getAbsolutePoints();
mxLine markerVector = getMarkerVector(points, source, absSize);
mxPoint p0 = new mxPoint(markerVector.getX(), markerVector.getY());
mxPoint pe = markerVector.getEndPoint();
mxPoint offset = null;
// Computes the norm and the inverse norm
double dx = pe.getX() - p0.getX();
double dy = pe.getY() - p0.getY();
double dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
double unitX = dx / dist;
double unitY = dy / dist;
double nx = unitX * absSize;
double ny = unitY * absSize;
// Allow for stroke width in the end point used and the
// orthogonal vectors describing the direction of the
// marker
double strokeX = unitX * strokeWidth;
double strokeY = unitY * strokeWidth;
pe = (mxPoint) pe.clone();
pe.setX(pe.getX() - strokeX / 2.0);
pe.setY(pe.getY() - strokeY / 2.0);
mxIMarker marker = mxMarkerRegistry.getMarker(type);
if (marker != null)
{
offset = marker.paintMarker(canvas, state, type, pe, nx, ny, absSize, source);
if (offset != null)
{
offset.setX(offset.getX() - strokeX / 2.0);
offset.setY(offset.getY() - strokeY / 2.0);
}
}
else
{
// Offset for the strokewidth
nx = dx * strokeWidth / dist;
ny = dy * strokeWidth / dist;
offset = new mxPoint(-strokeX / 2.0, -strokeY / 2.0);
}
return offset;
}
/**
* Hook to override creation of the vector that the marker is drawn along
* since it may not be the same as the vector between any two control
* points
* @param points the guide points of the connector
* @param source whether the marker is at the source end
* @param markerSize the scaled maximum length of the marker
* @return a line describing the vector the marker should be drawn along
*/
protected mxLine getMarkerVector(List<mxPoint> points, boolean source,
double markerSize)
{
int n = points.size();
mxPoint p0 = (source) ? points.get(1) : points.get(n - 2);
mxPoint pe = (source) ? points.get(0) : points.get(n - 1);
int count = 1;
// Uses next non-overlapping point
while (count < n - 1 && Math.round(p0.getX() - pe.getX()) == 0 && Math.round(p0.getY() - pe.getY()) == 0)
{
p0 = (source) ? points.get(1 + count) : points.get(n - 2 - count);
count++;
}
return new mxLine(p0, pe);
}
}

View file

@ -0,0 +1,699 @@
/**
* Copyright (c) 2010, David Benson, Gaudenz Alder
*/
package com.mxgraph.shape;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.text.Bidi;
import java.text.BreakIterator;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxCurve;
import com.mxgraph.util.mxLine;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
/**
* Draws the edge label along a curve derived from the curve describing
* the edge's path
*/
public class mxCurveLabelShape implements mxITextShape
{
/**
* Cache of the label text
*/
protected String lastValue;
/**
* Cache of the label font
*/
protected Font lastFont;
/**
* Cache of the last set of guide points that this label was calculated for
*/
protected List<mxPoint> lastPoints;
/**
* Cache of the points between which drawing straight lines views as a
* curve
*/
protected mxCurve curve;
/**
* Cache the state associated with this shape
*/
protected mxCellState state;
/**
* Cache of information describing characteristics relating to drawing
* each glyph of this label
*/
protected LabelGlyphCache[] labelGlyphs;
/**
* Cache of the total length of the branch label
*/
protected double labelSize;
/**
* Cache of the bounds of the label
*/
protected mxRectangle labelBounds;
/**
* ADT to encapsulate label positioning information
*/
protected LabelPosition labelPosition = new LabelPosition();
/**
* Buffer at both ends of the label
*/
public static double LABEL_BUFFER = 30;
/**
* Factor by which text on the inside of curve is stretched
*/
public static double CURVE_TEXT_STRETCH_FACTOR = 20.0;
/**
* Indicates that a glyph does not have valid drawing bounds, usually
* because it is not visible
*/
public static mxRectangle INVALID_GLYPH_BOUNDS = new mxRectangle(0, 0, 0, 0);
/**
* The index of the central glyph of the label that is visible
*/
public int centerVisibleIndex = 0;
/**
* Specifies if image aspect should be preserved in drawImage. Default is true.
*/
public static Object FONT_FRACTIONALMETRICS = RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT;
/**
* Cache of BIDI glyph vectors
*/
public GlyphVector[] rtlGlyphVectors;
/**
* Shared FRC for font size calculations
*/
public static FontRenderContext frc = new FontRenderContext(null, false,
false);
/**
*
*/
protected boolean rotationEnabled = true;
public mxCurveLabelShape(mxCellState state, mxCurve value)
{
this.state = state;
this.curve = value;
}
/**
*
*/
public boolean getRotationEnabled()
{
return rotationEnabled;
}
/**
*
*/
public void setRotationEnabled(boolean value)
{
rotationEnabled = value;
}
/**
*
*/
public void paintShape(mxGraphics2DCanvas canvas, String text,
mxCellState state, Map<String, Object> style)
{
Rectangle rect = state.getLabelBounds().getRectangle();
Graphics2D g = canvas.getGraphics();
if (labelGlyphs == null)
{
updateLabelBounds(text, style);
}
if (labelGlyphs != null
&& (g.getClipBounds() == null || g.getClipBounds().intersects(
rect)))
{
// Creates a temporary graphics instance for drawing this shape
float opacity = mxUtils.getFloat(style, mxConstants.STYLE_OPACITY,
100);
Graphics2D previousGraphics = g;
g = canvas.createTemporaryGraphics(style, opacity, state);
Font font = mxUtils.getFont(style, canvas.getScale());
g.setFont(font);
Color fontColor = mxUtils.getColor(style,
mxConstants.STYLE_FONTCOLOR, Color.black);
g.setColor(fontColor);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
FONT_FRACTIONALMETRICS);
for (int j = 0; j < labelGlyphs.length; j++)
{
mxLine parallel = labelGlyphs[j].glyphGeometry;
if (labelGlyphs[j].visible && parallel != null
&& parallel != mxCurve.INVALID_POSITION)
{
mxPoint parallelEnd = parallel.getEndPoint();
double x = parallelEnd.getX();
double rotation = (Math.atan(parallelEnd.getY() / x));
if (x < 0)
{
// atan only ranges from -PI/2 to PI/2, have to offset
// for negative x values
rotation += Math.PI;
}
final AffineTransform old = g.getTransform();
g.translate(parallel.getX(), parallel.getY());
g.rotate(rotation);
Shape letter = labelGlyphs[j].glyphShape;
g.fill(letter);
g.setTransform(old);
}
}
g.dispose();
g = previousGraphics;
}
}
/**
* Updates the cached position and size of each glyph in the edge label.
* @param label the entire string of the label.
* @param style the edge style
*/
public mxRectangle updateLabelBounds(String label, Map<String, Object> style)
{
double scale = state.getView().getScale();
Font font = mxUtils.getFont(style, scale);
FontMetrics fm = mxUtils.getFontMetrics(font);
int descent = 0;
int ascent = 0;
if (fm != null)
{
descent = fm.getDescent();
ascent = fm.getAscent();
}
// Check that the size of the widths array matches
// that of the label size
if (labelGlyphs == null || (!label.equals(lastValue)))
{
labelGlyphs = new LabelGlyphCache[label.length()];
}
if (!label.equals(lastValue) || !font.equals(lastFont))
{
char[] labelChars = label.toCharArray();
ArrayList<LabelGlyphCache> glyphList = new ArrayList<LabelGlyphCache>();
boolean bidiRequired = Bidi.requiresBidi(labelChars, 0,
labelChars.length);
labelSize = 0;
if (bidiRequired)
{
Bidi bidi = new Bidi(label,
Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT);
int runCount = bidi.getRunCount();
if (rtlGlyphVectors == null
|| rtlGlyphVectors.length != runCount)
{
rtlGlyphVectors = new GlyphVector[runCount];
}
for (int i = 0; i < bidi.getRunCount(); i++)
{
final String labelSection = label.substring(
bidi.getRunStart(i), bidi.getRunLimit(i));
rtlGlyphVectors[i] = font
.layoutGlyphVector(mxCurveLabelShape.frc,
labelSection.toCharArray(), 0,
labelSection.length(),
Font.LAYOUT_RIGHT_TO_LEFT);
}
int charCount = 0;
for (GlyphVector gv : rtlGlyphVectors)
{
float vectorOffset = 0.0f;
for (int j = 0; j < gv.getNumGlyphs(); j++)
{
Shape shape = gv.getGlyphOutline(j, -vectorOffset, 0);
LabelGlyphCache qlyph = new LabelGlyphCache();
glyphList.add(qlyph);
qlyph.glyphShape = shape;
mxRectangle size = new mxRectangle(gv.getGlyphLogicalBounds(j).getBounds2D());
qlyph.labelGlyphBounds = size;
labelSize += size.getWidth();
vectorOffset += size.getWidth();
charCount++;
}
}
}
else
{
rtlGlyphVectors = null;
//String locale = System.getProperty("user.language");
// Character iterator required where character is split over
// string elements
BreakIterator it = BreakIterator.getCharacterInstance(Locale.getDefault());
it.setText(label);
for (int i = 0; i < label.length();)
{
int next = it.next();
int characterLen = 1;
if (next != BreakIterator.DONE)
{
characterLen = next - i;
}
String glyph = label.substring(i, i + characterLen);
LabelGlyphCache labelGlyph = new LabelGlyphCache();
glyphList.add(labelGlyph);
labelGlyph.glyph = glyph;
GlyphVector vector = font.createGlyphVector(frc, glyph);
labelGlyph.glyphShape = vector.getOutline();
if (fm == null)
{
mxRectangle size = new mxRectangle(
font.getStringBounds(glyph,
mxCurveLabelShape.frc));
labelGlyph.labelGlyphBounds = size;
labelSize += size.getWidth();
}
else
{
double width = fm.stringWidth(glyph);
labelGlyph.labelGlyphBounds = new mxRectangle(0, 0,
width, ascent);
labelSize += width;
}
i += characterLen;
}
}
// Update values used to determine whether or not the label cache
// is valid or not
lastValue = label;
lastFont = font;
lastPoints = curve.getGuidePoints();
this.labelGlyphs = glyphList.toArray(new LabelGlyphCache[glyphList.size()]);
}
// Store the start/end buffers that pad out the ends of the branch so the label is
// visible. We work initially as the start section being at the start of the
// branch and the end at the end of the branch. Note that the actual label curve
// might be reversed, so we allow for this after completing the buffer calculations,
// otherwise they'd need to be constant isReversed() checks throughout
labelPosition.startBuffer = LABEL_BUFFER * scale;
labelPosition.endBuffer = LABEL_BUFFER * scale;
calculationLabelPosition(style, label);
if (curve.isLabelReversed())
{
double temp = labelPosition.startBuffer;
labelPosition.startBuffer = labelPosition.endBuffer;
labelPosition.endBuffer = temp;
}
double curveLength = curve.getCurveLength(mxCurve.LABEL_CURVE);
double currentPos = labelPosition.startBuffer / curveLength;
double endPos = 1.0 - (labelPosition.endBuffer / curveLength);
mxRectangle overallLabelBounds = null;
centerVisibleIndex = 0;
double currentCurveDelta = 0.0;
double curveDeltaSignificant = 0.3;
double curveDeltaMax = 0.5;
mxLine nextParallel = null;
// TODO on translation just move the points, don't recalculate
// Might be better than the curve is the only thing updated and
// the curve shapes listen to curve events
// !lastPoints.equals(curve.getGuidePoints())
for (int j = 0; j < labelGlyphs.length; j++)
{
if (currentPos > endPos)
{
labelGlyphs[j].visible = false;
continue;
}
mxLine parallel = nextParallel;
if (currentCurveDelta > curveDeltaSignificant
|| nextParallel == null)
{
parallel = curve.getCurveParallel(mxCurve.LABEL_CURVE,
currentPos);
currentCurveDelta = 0.0;
nextParallel = null;
}
labelGlyphs[j].glyphGeometry = parallel;
if (parallel == mxCurve.INVALID_POSITION)
{
continue;
}
// Get the four corners of the rotated rectangle bounding the glyph
// The drawing bounds of the glyph is the unrotated rect that
// just bounds those four corners
final double w = labelGlyphs[j].labelGlyphBounds.getWidth();
final double h = labelGlyphs[j].labelGlyphBounds.getHeight();
final double x = parallel.getEndPoint().getX();
final double y = parallel.getEndPoint().getY();
// Bottom left
double p1X = parallel.getX() - (descent * y);
double minX = p1X, maxX = p1X;
double p1Y = parallel.getY() + (descent * x);
double minY = p1Y, maxY = p1Y;
// Top left
double p2X = p1X + ((h + descent) * y);
double p2Y = p1Y - ((h + descent) * x);
minX = Math.min(minX, p2X);
maxX = Math.max(maxX, p2X);
minY = Math.min(minY, p2Y);
maxY = Math.max(maxY, p2Y);
// Bottom right
double p3X = p1X + (w * x);
double p3Y = p1Y + (w * y);
minX = Math.min(minX, p3X);
maxX = Math.max(maxX, p3X);
minY = Math.min(minY, p3Y);
maxY = Math.max(maxY, p3Y);
// Top right
double p4X = p2X + (w * x);
double p4Y = p2Y + (w * y);
minX = Math.min(minX, p4X);
maxX = Math.max(maxX, p4X);
minY = Math.min(minY, p4Y);
maxY = Math.max(maxY, p4Y);
minX -= 2 * scale;
minY -= 2 * scale;
maxX += 2 * scale;
maxY += 2 * scale;
// Hook for sub-classers
postprocessGlyph(curve, label, j, currentPos);
// Need to allow for text on inside of curve bends. Need to get the
// parallel for the next section, if there is an excessive
// inner curve, advance the current position accordingly
double currentPosCandidate = currentPos
+ (labelGlyphs[j].labelGlyphBounds.getWidth() + labelPosition.defaultInterGlyphSpace)
/ curveLength;
nextParallel = curve.getCurveParallel(mxCurve.LABEL_CURVE,
currentPosCandidate);
currentPos = currentPosCandidate;
mxPoint nextVector = nextParallel.getEndPoint();
double end2X = nextVector.getX();
double end2Y = nextVector.getY();
if (nextParallel != mxCurve.INVALID_POSITION
&& j + 1 < label.length())
{
// Extend the current parallel line in its direction
// by the length of the next parallel. Use the approximate
// deviation to work out the angle change
double deltaX = Math.abs(x - end2X);
double deltaY = Math.abs(y - end2Y);
// The difference as a proportion of the length of the next
// vector. 1 means a variation of 60 degrees.
currentCurveDelta = Math
.sqrt(deltaX * deltaX + deltaY * deltaY);
}
if (currentCurveDelta > curveDeltaSignificant)
{
// Work out which direction the curve is going in
int ccw = Line2D.relativeCCW(0, 0, x, y, end2X, end2Y);
if (ccw == 1)
{
// Text is on inside of curve
if (currentCurveDelta > curveDeltaMax)
{
// Don't worry about excessive deltas, if they
// are big the label curve will be screwed anyway
currentCurveDelta = curveDeltaMax;
}
double textBuffer = currentCurveDelta
* CURVE_TEXT_STRETCH_FACTOR / curveLength;
currentPos += textBuffer;
endPos += textBuffer;
}
}
if (labelGlyphs[j].drawingBounds != null)
{
labelGlyphs[j].drawingBounds.setRect(minX, minY, maxX - minX,
maxY - minY);
}
else
{
labelGlyphs[j].drawingBounds = new mxRectangle(minX, minY, maxX
- minX, maxY - minY);
}
if (overallLabelBounds == null)
{
overallLabelBounds = (mxRectangle) labelGlyphs[j].drawingBounds
.clone();
}
else
{
overallLabelBounds.add(labelGlyphs[j].drawingBounds);
}
labelGlyphs[j].visible = true;
centerVisibleIndex++;
}
centerVisibleIndex /= 2;
if (overallLabelBounds == null)
{
// Return a small rectangle in the center of the label curve
// Null label bounds causes NPE when editing
mxLine labelCenter = curve.getCurveParallel(mxCurve.LABEL_CURVE,
0.5);
overallLabelBounds = new mxRectangle(labelCenter.getX(),
labelCenter.getY(), 1, 1);
}
this.labelBounds = overallLabelBounds;
return overallLabelBounds;
}
/**
* Hook for sub-classers to perform additional processing on
* each glyph
* @param curve The curve object holding the label curve
* @param label the text label of the curve
* @param j the index of the label
* @param currentPos the distance along the label curve the glyph is
*/
protected void postprocessGlyph(mxCurve curve, String label, int j,
double currentPos)
{
}
/**
* Returns whether or not the rectangle passed in hits any part of this
* curve.
* @param rect the rectangle to detect for a hit
* @return whether or not the rectangle hits this curve
*/
public boolean intersectsRect(Rectangle rect)
{
// To save CPU, we can test if the rectangle intersects the entire
// bounds of this label
if ( (labelBounds != null
&& (!labelBounds.getRectangle().intersects(rect)) )
|| labelGlyphs == null )
{
return false;
}
for (int i = 0; i < labelGlyphs.length; i++)
{
if (labelGlyphs[i].visible
&& rect.intersects(labelGlyphs[i].drawingBounds
.getRectangle()))
{
return true;
}
}
return false;
}
/**
* Hook method to override how the label is positioned on the curve
* @param style the style of the curve
* @param label the string label to be displayed on the curve
*/
protected void calculationLabelPosition(Map<String, Object> style,
String label)
{
double curveLength = curve.getCurveLength(mxCurve.LABEL_CURVE);
double availableLabelSpace = curveLength - labelPosition.startBuffer
- labelPosition.endBuffer;
labelPosition.startBuffer = Math.max(labelPosition.startBuffer,
labelPosition.startBuffer + availableLabelSpace / 2 - labelSize
/ 2);
labelPosition.endBuffer = Math.max(labelPosition.endBuffer,
labelPosition.endBuffer + availableLabelSpace / 2 - labelSize
/ 2);
}
/**
* @return the curve
*/
public mxCurve getCurve()
{
return curve;
}
/**
* @param curve the curve to set
*/
public void setCurve(mxCurve curve)
{
this.curve = curve;
}
/**
* Utility class to describe the characteristics of each glyph of a branch
* branch label. Each instance represents one glyph
*
*/
public class LabelGlyphCache
{
/**
* Cache of the bounds of the individual element of the label of this
* edge. Note that these are the unrotated values used to determine the
* width of each glyph.
*/
public mxRectangle labelGlyphBounds;
/**
* The un-rotated rectangle that just bounds this character
*/
public mxRectangle drawingBounds;
/**
* The glyph being drawn
*/
public String glyph;
/**
* A line parallel to the curve segment at which the element is to be
* drawn
*/
public mxLine glyphGeometry;
/**
* The cached shape of the glyph
*/
public Shape glyphShape;
/**
* Whether or not the glyph should be drawn
*/
public boolean visible;
}
/**
* Utility class that stores details of how the label is positioned
* on the curve
*/
public class LabelPosition
{
public double startBuffer = LABEL_BUFFER;
public double endBuffer = LABEL_BUFFER;
public double defaultInterGlyphSpace = 0;;
}
public mxRectangle getLabelBounds()
{
return labelBounds;
}
/**
* Returns the drawing bounds of the central indexed visible glyph
* @return the centerVisibleIndex
*/
public mxRectangle getCenterVisiblePosition()
{
return labelGlyphs[centerVisibleIndex].drawingBounds;
}
}

View file

@ -0,0 +1,132 @@
/**
* Copyright (c) 2009-2010, David Benson, Gaudenz Alder
*/
package com.mxgraph.shape;
import java.awt.RenderingHints;
import java.util.List;
import java.util.Map;
import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxCurve;
import com.mxgraph.util.mxLine;
import com.mxgraph.util.mxPoint;
import com.mxgraph.view.mxCellState;
public class mxCurveShape extends mxConnectorShape
{
/**
* Cache of the points between which drawing straight lines views as a
* curve
*/
protected mxCurve curve;
/**
*
*/
public mxCurveShape()
{
this(new mxCurve());
}
/**
*
*/
public mxCurveShape(mxCurve curve)
{
this.curve = curve;
}
/**
*
*/
public mxCurve getCurve()
{
return curve;
}
/**
*
*/
public void paintShape(mxGraphics2DCanvas canvas, mxCellState state)
{
Object keyStrokeHint = canvas.getGraphics().getRenderingHint(
RenderingHints.KEY_STROKE_CONTROL);
canvas.getGraphics().setRenderingHint(
RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
super.paintShape(canvas, state);
canvas.getGraphics().setRenderingHint(
RenderingHints.KEY_STROKE_CONTROL, keyStrokeHint);
}
/**
*
*/
protected void paintPolyline(mxGraphics2DCanvas canvas,
List<mxPoint> points, Map<String, Object> style)
{
double scale = canvas.getScale();
validateCurve(points, scale, style);
canvas.paintPolyline(curve.getCurvePoints(mxCurve.CORE_CURVE), false);
}
/**
* Forces underlying curve to a valid state
* @param points
*/
public void validateCurve(List<mxPoint> points, double scale,
Map<String, Object> style)
{
if (curve == null)
{
curve = new mxCurve(points);
}
else
{
curve.updateCurve(points);
}
curve.setLabelBuffer(scale * mxConstants.DEFAULT_LABEL_BUFFER);
}
/**
* Hook to override creation of the vector that the marker is drawn along
* since it may not be the same as the vector between any two control
* points
* @param points the guide points of the connector
* @param source whether the marker is at the source end
* @param markerSize the scaled maximum length of the marker
* @return a line describing the vector the marker should be drawn along
*/
protected mxLine getMarkerVector(List<mxPoint> points, boolean source,
double markerSize)
{
double curveLength = curve.getCurveLength(mxCurve.CORE_CURVE);
double markerRatio = markerSize / curveLength;
if (markerRatio >= 1.0)
{
markerRatio = 1.0;
}
if (source)
{
mxLine sourceVector = curve.getCurveParallel(mxCurve.CORE_CURVE,
markerRatio);
return new mxLine(sourceVector.getX(), sourceVector.getY(),
points.get(0));
}
else
{
mxLine targetVector = curve.getCurveParallel(mxCurve.CORE_CURVE,
1.0 - markerRatio);
int pointCount = points.size();
return new mxLine(targetVector.getX(), targetVector.getY(),
points.get(pointCount - 1));
}
}
}

View file

@ -0,0 +1,49 @@
package com.mxgraph.shape;
import java.awt.Rectangle;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.view.mxCellState;
public class mxCylinderShape extends mxBasicShape
{
/**
* Draws a cylinder for the given parameters.
*/
public void paintShape(mxGraphics2DCanvas canvas, mxCellState state)
{
Rectangle rect = state.getRectangle();
int x = rect.x;
int y = rect.y;
int w = rect.width;
int h = rect.height;
int h4 = h / 4;
int h2 = h4 / 2;
int r = w;
// Paints the background
if (configureGraphics(canvas, state, true))
{
Area area = new Area(new Rectangle(x, y + h4 / 2, r, h - h4));
area.add(new Area(new Rectangle(x, y + h4 / 2, r, h - h4)));
area.add(new Area(new Ellipse2D.Float(x, y, r, h4)));
area.add(new Area(new Ellipse2D.Float(x, y + h - h4, r, h4)));
canvas.fillShape(area, hasShadow(canvas, state));
}
// Paints the foreground
if (configureGraphics(canvas, state, false))
{
canvas.getGraphics().drawOval(x, y, r, h4);
canvas.getGraphics().drawLine(x, y + h2, x, y + h - h2);
canvas.getGraphics().drawLine(x + w, y + h2, x + w, y + h - h2);
// TODO: Use QuadCurve2D.Float() for painting the arc
canvas.getGraphics().drawArc(x, y + h - h4, r, h4, 0, -180);
}
}
}

View file

@ -0,0 +1,140 @@
/**
* Copyright (c) 2010, Gaudenz Alder, David Benson
*/
package com.mxgraph.shape;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.Map;
import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
public class mxDefaultTextShape implements mxITextShape
{
/**
*
*/
public void paintShape(mxGraphics2DCanvas canvas, String text,
mxCellState state, Map<String, Object> style)
{
Rectangle rect = state.getLabelBounds().getRectangle();
Graphics2D g = canvas.getGraphics();
if (g.getClipBounds() == null || g.getClipBounds().intersects(rect))
{
boolean horizontal = mxUtils.isTrue(style,
mxConstants.STYLE_HORIZONTAL, true);
double scale = canvas.getScale();
int x = rect.x;
int y = rect.y;
int w = rect.width;
int h = rect.height;
if (!horizontal)
{
g.rotate(-Math.PI / 2, x + w / 2, y + h / 2);
g.translate(w / 2 - h / 2, h / 2 - w / 2);
}
Color fontColor = mxUtils.getColor(style,
mxConstants.STYLE_FONTCOLOR, Color.black);
g.setColor(fontColor);
// Shifts the y-coordinate down by the ascent plus a workaround
// for the line not starting at the exact vertical location
Font scaledFont = mxUtils.getFont(style, scale);
g.setFont(scaledFont);
int fontSize = mxUtils.getInt(style, mxConstants.STYLE_FONTSIZE,
mxConstants.DEFAULT_FONTSIZE);
FontMetrics fm = g.getFontMetrics();
int scaledFontSize = scaledFont.getSize();
double fontScaleFactor = ((double) scaledFontSize)
/ ((double) fontSize);
// This factor is the amount by which the font is smaller/
// larger than we expect for the given scale. 1 means it's
// correct, 0.8 means the font is 0.8 the size we expected
// when scaled, etc.
double fontScaleRatio = fontScaleFactor / scale;
// The y position has to be moved by (1 - ratio) * height / 2
y += 2 * fm.getMaxAscent() - fm.getHeight()
+ mxConstants.LABEL_INSET * scale;
Object vertAlign = mxUtils.getString(style,
mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE);
double vertAlignProportion = 0.5;
if (vertAlign.equals(mxConstants.ALIGN_TOP))
{
vertAlignProportion = 0;
}
else if (vertAlign.equals(mxConstants.ALIGN_BOTTOM))
{
vertAlignProportion = 1.0;
}
y += (1.0 - fontScaleRatio) * h * vertAlignProportion;
// Gets the alignment settings
Object align = mxUtils.getString(style, mxConstants.STYLE_ALIGN,
mxConstants.ALIGN_CENTER);
if (align.equals(mxConstants.ALIGN_LEFT))
{
x += mxConstants.LABEL_INSET * scale;
}
else if (align.equals(mxConstants.ALIGN_RIGHT))
{
x -= mxConstants.LABEL_INSET * scale;
}
// Draws the text line by line
String[] lines = text.split("\n");
for (int i = 0; i < lines.length; i++)
{
int dx = 0;
if (align.equals(mxConstants.ALIGN_CENTER))
{
int sw = fm.stringWidth(lines[i]);
if (horizontal)
{
dx = (w - sw) / 2;
}
else
{
dx = (h - sw) / 2;
}
}
else if (align.equals(mxConstants.ALIGN_RIGHT))
{
int sw = fm.stringWidth(lines[i]);
dx = ((horizontal) ? w : h) - sw;
}
g.drawString(lines[i], x + dx, y);
postProcessLine(text, lines[i], fm, canvas, x + dx, y);
y += fm.getHeight() + mxConstants.LINESPACING;
}
}
}
/**
* Hook to add functionality after a line has been drawn
* @param text the entire label text
* @param line the line at the specified location
* @param fm the text font metrics
* @param canvas the canvas object currently being painted to
* @param x the x co-ord of the baseline of the text line
* @param y the y co-ord of the baseline of the text line
*/
protected void postProcessLine(String text, String line, FontMetrics fm, mxGraphics2DCanvas canvas, int x, int y){}
}

View file

@ -0,0 +1,33 @@
package com.mxgraph.shape;
import java.awt.Rectangle;
import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
public class mxDoubleEllipseShape extends mxEllipseShape
{
/**
*
*/
public void paintShape(mxGraphics2DCanvas canvas, mxCellState state)
{
super.paintShape(canvas, state);
int inset = (int) Math.round((mxUtils.getFloat(state.getStyle(),
mxConstants.STYLE_STROKEWIDTH, 1) + 3)
* canvas.getScale());
Rectangle rect = state.getRectangle();
int x = rect.x + inset;
int y = rect.y + inset;
int w = rect.width - 2 * inset;
int h = rect.height - 2 * inset;
canvas.getGraphics().drawOval(x, y, w, h);
}
}

View file

@ -0,0 +1,33 @@
package com.mxgraph.shape;
import java.awt.Rectangle;
import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
public class mxDoubleRectangleShape extends mxRectangleShape
{
/**
*
*/
public void paintShape(mxGraphics2DCanvas canvas, mxCellState state)
{
super.paintShape(canvas, state);
int inset = (int) Math.round((mxUtils.getFloat(state.getStyle(),
mxConstants.STYLE_STROKEWIDTH, 1) + 3)
* canvas.getScale());
Rectangle rect = state.getRectangle();
int x = rect.x + inset;
int y = rect.y + inset;
int w = rect.width - 2 * inset;
int h = rect.height - 2 * inset;
canvas.getGraphics().drawRect(x, y, w, h);
}
}

View file

@ -0,0 +1,23 @@
package com.mxgraph.shape;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.view.mxCellState;
public class mxEllipseShape extends mxBasicShape
{
/**
*
*/
public Shape createShape(mxGraphics2DCanvas canvas, mxCellState state)
{
Rectangle temp = state.getRectangle();
return new Ellipse2D.Float(temp.x, temp.y, temp.width, temp.height);
}
}

View file

@ -0,0 +1,52 @@
package com.mxgraph.shape;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Shape;
import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
public class mxHexagonShape extends mxBasicShape
{
/**
*
*/
public Shape createShape(mxGraphics2DCanvas canvas, mxCellState state)
{
Rectangle temp = state.getRectangle();
int x = temp.x;
int y = temp.y;
int w = temp.width;
int h = temp.height;
String direction = mxUtils.getString(state.getStyle(),
mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);
Polygon hexagon = new Polygon();
if (direction.equals(mxConstants.DIRECTION_NORTH)
|| direction.equals(mxConstants.DIRECTION_SOUTH))
{
hexagon.addPoint(x + (int) (0.5 * w), y);
hexagon.addPoint(x + w, y + (int) (0.25 * h));
hexagon.addPoint(x + w, y + (int) (0.75 * h));
hexagon.addPoint(x + (int) (0.5 * w), y + h);
hexagon.addPoint(x, y + (int) (0.75 * h));
hexagon.addPoint(x, y + (int) (0.25 * h));
}
else
{
hexagon.addPoint(x + (int) (0.25 * w), y);
hexagon.addPoint(x + (int) (0.75 * w), y);
hexagon.addPoint(x + w, y + (int) (0.5 * h));
hexagon.addPoint(x + (int) (0.75 * w), y + h);
hexagon.addPoint(x + (int) (0.25 * w), y + h);
hexagon.addPoint(x, y + (int) (0.5 * h));
}
return hexagon;
}
}

View file

@ -0,0 +1,134 @@
/**
* Copyright (c) 2010, Gaudenz Alder, David Benson
*/
package com.mxgraph.shape;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.Map;
import javax.swing.CellRendererPane;
import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxLightweightLabel;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
/**
* To set global CSS for all HTML labels, use the following code:
*
* <pre>
* mxGraphics2DCanvas.putTextShape(mxGraphics2DCanvas.TEXT_SHAPE_HTML,
* new mxHtmlTextShape()
* {
* protected String createHtmlDocument(Map<String, Object> style, String text)
* {
* return mxUtils.createHtmlDocument(style, text, 1, 0,
* "<style type=\"text/css\">.selectRef { " +
* "font-size:9px;font-weight:normal; }</style>");
* }
* }
* );
* </pre>
*/
public class mxHtmlTextShape implements mxITextShape
{
/**
* Specifies if linefeeds should be replaced with breaks in HTML markup.
* Default is true.
*/
protected boolean replaceHtmlLinefeeds = true;
/**
* Returns replaceHtmlLinefeeds
*/
public boolean isReplaceHtmlLinefeeds()
{
return replaceHtmlLinefeeds;
}
/**
* Returns replaceHtmlLinefeeds
*/
public void setReplaceHtmlLinefeeds(boolean value)
{
replaceHtmlLinefeeds = value;
}
/**
*
*/
protected String createHtmlDocument(Map<String, Object> style, String text,
int w, int h)
{
String overflow = mxUtils.getString(style, mxConstants.STYLE_OVERFLOW, "");
if (overflow.equals("fill"))
{
return mxUtils.createHtmlDocument(style, text, 1, w, null, "height:" + h + "pt;");
}
else if (overflow.equals("width"))
{
return mxUtils.createHtmlDocument(style, text, 1, w);
}
else
{
return mxUtils.createHtmlDocument(style, text);
}
}
/**
*
*/
public void paintShape(mxGraphics2DCanvas canvas, String text,
mxCellState state, Map<String, Object> style)
{
mxLightweightLabel textRenderer = mxLightweightLabel
.getSharedInstance();
CellRendererPane rendererPane = canvas.getRendererPane();
Rectangle rect = state.getLabelBounds().getRectangle();
Graphics2D g = canvas.getGraphics();
if (textRenderer != null
&& rendererPane != null
&& (g.getClipBounds() == null || g.getClipBounds().intersects(
rect)))
{
double scale = canvas.getScale();
int x = rect.x;
int y = rect.y;
int w = rect.width;
int h = rect.height;
if (!mxUtils.isTrue(style, mxConstants.STYLE_HORIZONTAL, true))
{
g.rotate(-Math.PI / 2, x + w / 2, y + h / 2);
g.translate(w / 2 - h / 2, h / 2 - w / 2);
int tmp = w;
w = h;
h = tmp;
}
// Replaces the linefeeds with BR tags
if (isReplaceHtmlLinefeeds())
{
text = text.replaceAll("\n", "<br>");
}
// Renders the scaled text
textRenderer.setText(createHtmlDocument(style, text,
(int) Math.round(w / state.getView().getScale()),
(int) Math.round(h / state.getView().getScale())));
textRenderer.setFont(mxUtils.getFont(style, canvas.getScale()));
g.scale(scale, scale);
rendererPane.paintComponent(g, textRenderer, rendererPane,
(int) (x / scale) + mxConstants.LABEL_INSET,
(int) (y / scale) + mxConstants.LABEL_INSET,
(int) (w / scale), (int) (h / scale), true);
}
}
}

View file

@ -0,0 +1,15 @@
package com.mxgraph.shape;
import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.util.mxPoint;
import com.mxgraph.view.mxCellState;
public interface mxIMarker
{
/**
*
*/
mxPoint paintMarker(mxGraphics2DCanvas canvas, mxCellState state, String type,
mxPoint pe, double nx, double ny, double size, boolean source);
}

View file

@ -0,0 +1,13 @@
package com.mxgraph.shape;
import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.view.mxCellState;
public interface mxIShape
{
/**
*
*/
void paintShape(mxGraphics2DCanvas canvas, mxCellState state);
}

View file

@ -0,0 +1,19 @@
/**
* Copyright (c) 2010, Gaudenz Alder, David Benson
*/
package com.mxgraph.shape;
import java.util.Map;
import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.view.mxCellState;
public interface mxITextShape
{
/**
*
*/
void paintShape(mxGraphics2DCanvas canvas, String text, mxCellState state,
Map<String, Object> style);
}

View file

@ -0,0 +1,80 @@
/**
* Copyright (c) 2007-2010, Gaudenz Alder, David Benson
*/
package com.mxgraph.shape;
import java.awt.Color;
import java.awt.Rectangle;
import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
/**
* A rectangular shape that contains a single image. See mxImageBundle for
* creating a lookup table with images which can then be referenced by key.
*/
public class mxImageShape extends mxRectangleShape
{
/**
*
*/
public void paintShape(mxGraphics2DCanvas canvas, mxCellState state)
{
super.paintShape(canvas, state);
boolean flipH = mxUtils.isTrue(state.getStyle(),
mxConstants.STYLE_IMAGE_FLIPH, false);
boolean flipV = mxUtils.isTrue(state.getStyle(),
mxConstants.STYLE_IMAGE_FLIPV, false);
canvas.drawImage(getImageBounds(canvas, state),
getImageForStyle(canvas, state),
mxGraphics2DCanvas.PRESERVE_IMAGE_ASPECT, flipH, flipV);
}
/**
*
*/
public Rectangle getImageBounds(mxGraphics2DCanvas canvas, mxCellState state)
{
return state.getRectangle();
}
/**
*
*/
public boolean hasGradient(mxGraphics2DCanvas canvas, mxCellState state)
{
return false;
}
/**
*
*/
public String getImageForStyle(mxGraphics2DCanvas canvas, mxCellState state)
{
return canvas.getImageForStyle(state.getStyle());
}
/**
*
*/
public Color getFillColor(mxGraphics2DCanvas canvas, mxCellState state)
{
return mxUtils.getColor(state.getStyle(),
mxConstants.STYLE_IMAGE_BACKGROUND);
}
/**
*
*/
public Color getStrokeColor(mxGraphics2DCanvas canvas, mxCellState state)
{
return mxUtils.getColor(state.getStyle(),
mxConstants.STYLE_IMAGE_BORDER);
}
}

Some files were not shown because too many files have changed in this diff Show more