14.0.1 release
This commit is contained in:
parent
479e5a1701
commit
414e2d2f62
248 changed files with 82910 additions and 979 deletions
|
@ -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
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
14.0.0
|
||||
14.0.1
|
|
@ -164,7 +164,7 @@
|
|||
<file name="Init.js" />
|
||||
</sources>
|
||||
|
||||
<sources dir="${basedir}/../mxgraph">
|
||||
<sources dir="${war.dir}/mxgraph">
|
||||
<file name="mxClient.js" />
|
||||
</sources>
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
12
src/main/java/com/mxgraph/analysis/StructuralException.java
Normal file
12
src/main/java/com/mxgraph/analysis/StructuralException.java
Normal 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);
|
||||
}
|
||||
};
|
224
src/main/java/com/mxgraph/analysis/mxAnalysisGraph.java
Normal file
224
src/main/java/com/mxgraph/analysis/mxAnalysisGraph.java
Normal 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;
|
||||
};
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
607
src/main/java/com/mxgraph/analysis/mxFibonacciHeap.java
Normal file
607
src/main/java/com/mxgraph/analysis/mxFibonacciHeap.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
463
src/main/java/com/mxgraph/analysis/mxGraphAnalysis.java
Normal file
463
src/main/java/com/mxgraph/analysis/mxGraphAnalysis.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
1551
src/main/java/com/mxgraph/analysis/mxGraphGenerator.java
Normal file
1551
src/main/java/com/mxgraph/analysis/mxGraphGenerator.java
Normal file
File diff suppressed because it is too large
Load diff
129
src/main/java/com/mxgraph/analysis/mxGraphProperties.java
Normal file
129
src/main/java/com/mxgraph/analysis/mxGraphProperties.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
1038
src/main/java/com/mxgraph/analysis/mxGraphStructure.java
Normal file
1038
src/main/java/com/mxgraph/analysis/mxGraphStructure.java
Normal file
File diff suppressed because it is too large
Load diff
26
src/main/java/com/mxgraph/analysis/mxICostFunction.java
Normal file
26
src/main/java/com/mxgraph/analysis/mxICostFunction.java
Normal 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);
|
||||
|
||||
}
|
558
src/main/java/com/mxgraph/analysis/mxTraversal.java
Normal file
558
src/main/java/com/mxgraph/analysis/mxTraversal.java
Normal 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;
|
||||
}
|
||||
};
|
156
src/main/java/com/mxgraph/analysis/mxUnionFind.java
Normal file
156
src/main/java/com/mxgraph/analysis/mxUnionFind.java
Normal 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 >= 1 | A(i, floor(m/n)) > log n} for m >= n >= 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;
|
||||
}
|
||||
}
|
||||
}
|
6
src/main/java/com/mxgraph/analysis/package.html
Normal file
6
src/main/java/com/mxgraph/analysis/package.html
Normal 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>
|
161
src/main/java/com/mxgraph/canvas/mxBasicCanvas.java
Normal file
161
src/main/java/com/mxgraph/canvas/mxBasicCanvas.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
626
src/main/java/com/mxgraph/canvas/mxGraphics2DCanvas.java
Normal file
626
src/main/java/com/mxgraph/canvas/mxGraphics2DCanvas.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
1857
src/main/java/com/mxgraph/canvas/mxGraphicsCanvas2D.java
Normal file
1857
src/main/java/com/mxgraph/canvas/mxGraphicsCanvas2D.java
Normal file
File diff suppressed because it is too large
Load diff
348
src/main/java/com/mxgraph/canvas/mxHtmlCanvas.java
Normal file
348
src/main/java/com/mxgraph/canvas/mxHtmlCanvas.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
55
src/main/java/com/mxgraph/canvas/mxICanvas.java
Normal file
55
src/main/java/com/mxgraph/canvas/mxICanvas.java
Normal 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);
|
||||
|
||||
}
|
368
src/main/java/com/mxgraph/canvas/mxICanvas2D.java
Normal file
368
src/main/java/com/mxgraph/canvas/mxICanvas2D.java
Normal 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();
|
||||
|
||||
}
|
151
src/main/java/com/mxgraph/canvas/mxImageCanvas.java
Normal file
151
src/main/java/com/mxgraph/canvas/mxImageCanvas.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
1487
src/main/java/com/mxgraph/canvas/mxSvgCanvas.java
Normal file
1487
src/main/java/com/mxgraph/canvas/mxSvgCanvas.java
Normal file
File diff suppressed because it is too large
Load diff
599
src/main/java/com/mxgraph/canvas/mxVmlCanvas.java
Normal file
599
src/main/java/com/mxgraph/canvas/mxVmlCanvas.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
6
src/main/java/com/mxgraph/canvas/package.html
Normal file
6
src/main/java/com/mxgraph/canvas/package.html
Normal 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>
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.mxgraph.costfunction;
|
||||
|
||||
import com.mxgraph.analysis.mxICostFunction;
|
||||
|
||||
public abstract class mxCostFunction implements mxICostFunction
|
||||
{
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
};
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -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
|
||||
{
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
};
|
|
@ -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;
|
||||
};
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
@ -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);
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
267
src/main/java/com/mxgraph/layout/mxCircleLayout.java
Normal file
267
src/main/java/com/mxgraph/layout/mxCircleLayout.java
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
1214
src/main/java/com/mxgraph/layout/mxCompactTreeLayout.java
Normal file
1214
src/main/java/com/mxgraph/layout/mxCompactTreeLayout.java
Normal file
File diff suppressed because it is too large
Load diff
149
src/main/java/com/mxgraph/layout/mxEdgeLabelLayout.java
Normal file
149
src/main/java/com/mxgraph/layout/mxEdgeLabelLayout.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
681
src/main/java/com/mxgraph/layout/mxFastOrganicLayout.java
Normal file
681
src/main/java/com/mxgraph/layout/mxFastOrganicLayout.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
415
src/main/java/com/mxgraph/layout/mxGraphLayout.java
Normal file
415
src/main/java/com/mxgraph/layout/mxGraphLayout.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
31
src/main/java/com/mxgraph/layout/mxIGraphLayout.java
Normal file
31
src/main/java/com/mxgraph/layout/mxIGraphLayout.java
Normal 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);
|
||||
|
||||
}
|
1854
src/main/java/com/mxgraph/layout/mxOrganicLayout.java
Normal file
1854
src/main/java/com/mxgraph/layout/mxOrganicLayout.java
Normal file
File diff suppressed because it is too large
Load diff
197
src/main/java/com/mxgraph/layout/mxParallelEdgeLayout.java
Normal file
197
src/main/java/com/mxgraph/layout/mxParallelEdgeLayout.java
Normal 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) }));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
235
src/main/java/com/mxgraph/layout/mxPartitionLayout.java
Normal file
235
src/main/java/com/mxgraph/layout/mxPartitionLayout.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
330
src/main/java/com/mxgraph/layout/mxStackLayout.java
Normal file
330
src/main/java/com/mxgraph/layout/mxStackLayout.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* Copyright (c) 2008, Gaudenz Alder
|
||||
*/
|
||||
package com.mxgraph.layout.orthogonal.model;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class mxPointPair
|
||||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* Copyright (c) 2008, Gaudenz Alder
|
||||
*/
|
||||
package com.mxgraph.layout.orthogonal.model;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class mxPointSequence
|
||||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* Copyright (c) 2008, Gaudenz Alder
|
||||
*/
|
||||
package com.mxgraph.layout.orthogonal.model;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class mxSegment
|
||||
{
|
||||
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
5
src/main/java/com/mxgraph/layout/package.html
Normal file
5
src/main/java/com/mxgraph/layout/package.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
<HTML>
|
||||
<BODY>
|
||||
This package contains various graph layouts.
|
||||
</BODY>
|
||||
</HTML>
|
641
src/main/java/com/mxgraph/model/mxCell.java
Normal file
641
src/main/java/com/mxgraph/model/mxCell.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
144
src/main/java/com/mxgraph/model/mxCellPath.java
Normal file
144
src/main/java/com/mxgraph/model/mxCellPath.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
373
src/main/java/com/mxgraph/model/mxGeometry.java
Normal file
373
src/main/java/com/mxgraph/model/mxGeometry.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
2642
src/main/java/com/mxgraph/model/mxGraphModel.java
Normal file
2642
src/main/java/com/mxgraph/model/mxGraphModel.java
Normal file
File diff suppressed because it is too large
Load diff
273
src/main/java/com/mxgraph/model/mxICell.java
Normal file
273
src/main/java/com/mxgraph/model/mxICell.java
Normal 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;
|
||||
|
||||
}
|
336
src/main/java/com/mxgraph/model/mxIGraphModel.java
Normal file
336
src/main/java/com/mxgraph/model/mxIGraphModel.java
Normal 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);
|
||||
|
||||
}
|
5
src/main/java/com/mxgraph/model/package.html
Normal file
5
src/main/java/com/mxgraph/model/package.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
<HTML>
|
||||
<BODY>
|
||||
This package contains the classes that define a graph model.
|
||||
</BODY>
|
||||
</HTML>
|
511
src/main/java/com/mxgraph/reader/mxDomOutputParser.java
Normal file
511
src/main/java/com/mxgraph/reader/mxDomOutputParser.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
290
src/main/java/com/mxgraph/reader/mxGraphViewImageReader.java
Normal file
290
src/main/java/com/mxgraph/reader/mxGraphViewImageReader.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
227
src/main/java/com/mxgraph/reader/mxGraphViewReader.java
Normal file
227
src/main/java/com/mxgraph/reader/mxGraphViewReader.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
482
src/main/java/com/mxgraph/reader/mxSaxOutputHandler.java
Normal file
482
src/main/java/com/mxgraph/reader/mxSaxOutputHandler.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
6
src/main/java/com/mxgraph/reader/package.html
Normal file
6
src/main/java/com/mxgraph/reader/package.html
Normal 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>
|
4
src/main/java/com/mxgraph/resources/graph.properties
Normal file
4
src/main/java/com/mxgraph/resources/graph.properties
Normal file
|
@ -0,0 +1,4 @@
|
|||
alreadyConnected=Nodes already connected
|
||||
containsValidationErrors=Contains validation errors
|
||||
collapse-expand=Collapse/Expand
|
||||
doubleClickOrientation=Doubleclick to change orientation
|
4
src/main/java/com/mxgraph/resources/graph_de.properties
Normal file
4
src/main/java/com/mxgraph/resources/graph_de.properties
Normal file
|
@ -0,0 +1,4 @@
|
|||
alreadyConnected=Knoten schon verbunden
|
||||
containsValidationErrors=Enthält Validierungsfehler
|
||||
collapse-expand=Zusammenklappen/Auseinanderfalten
|
||||
doubleClickOrientation=Doppelklicken um Orientierung zu ändern
|
4
src/main/java/com/mxgraph/resources/graph_es.properties
Normal file
4
src/main/java/com/mxgraph/resources/graph_es.properties
Normal 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
|
4
src/main/java/com/mxgraph/resources/graph_fr.properties
Normal file
4
src/main/java/com/mxgraph/resources/graph_fr.properties
Normal 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
|
4
src/main/java/com/mxgraph/resources/graph_it.properties
Normal file
4
src/main/java/com/mxgraph/resources/graph_it.properties
Normal file
|
@ -0,0 +1,4 @@
|
|||
alreadyConnected=Nodi già connessi
|
||||
containsValidationErrors=Contiene errori di convalida
|
||||
collapse-expand=Riduci/Espandi
|
||||
doubleClickOrientation=Doppio click per cambiare orientamento
|
4
src/main/java/com/mxgraph/resources/graph_ja.properties
Normal file
4
src/main/java/com/mxgraph/resources/graph_ja.properties
Normal 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
|
4
src/main/java/com/mxgraph/resources/graph_nl.properties
Normal file
4
src/main/java/com/mxgraph/resources/graph_nl.properties
Normal file
|
@ -0,0 +1,4 @@
|
|||
alreadyConnected=Knooppunten reeds verbonden
|
||||
containsValidationErrors=Bevat validatiefouten
|
||||
collapse-expand=Verkleinen/Vergroten
|
||||
doubleClickOrientation=Dubbelklik om richting te veranderen
|
4
src/main/java/com/mxgraph/resources/graph_pt.properties
Normal file
4
src/main/java/com/mxgraph/resources/graph_pt.properties
Normal 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
|
4
src/main/java/com/mxgraph/resources/graph_ru.properties
Normal file
4
src/main/java/com/mxgraph/resources/graph_ru.properties
Normal 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"
|
4
src/main/java/com/mxgraph/resources/graph_zh.properties
Normal file
4
src/main/java/com/mxgraph/resources/graph_zh.properties
Normal 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
|
40
src/main/java/com/mxgraph/shape/mxActorShape.java
Normal file
40
src/main/java/com/mxgraph/shape/mxActorShape.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
68
src/main/java/com/mxgraph/shape/mxArrowShape.java
Normal file
68
src/main/java/com/mxgraph/shape/mxArrowShape.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
139
src/main/java/com/mxgraph/shape/mxBasicShape.java
Normal file
139
src/main/java/com/mxgraph/shape/mxBasicShape.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
48
src/main/java/com/mxgraph/shape/mxCloudShape.java
Normal file
48
src/main/java/com/mxgraph/shape/mxCloudShape.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
196
src/main/java/com/mxgraph/shape/mxConnectorShape.java
Normal file
196
src/main/java/com/mxgraph/shape/mxConnectorShape.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
699
src/main/java/com/mxgraph/shape/mxCurveLabelShape.java
Normal file
699
src/main/java/com/mxgraph/shape/mxCurveLabelShape.java
Normal 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;
|
||||
}
|
||||
}
|
132
src/main/java/com/mxgraph/shape/mxCurveShape.java
Normal file
132
src/main/java/com/mxgraph/shape/mxCurveShape.java
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
49
src/main/java/com/mxgraph/shape/mxCylinderShape.java
Normal file
49
src/main/java/com/mxgraph/shape/mxCylinderShape.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
140
src/main/java/com/mxgraph/shape/mxDefaultTextShape.java
Normal file
140
src/main/java/com/mxgraph/shape/mxDefaultTextShape.java
Normal 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){}
|
||||
}
|
33
src/main/java/com/mxgraph/shape/mxDoubleEllipseShape.java
Normal file
33
src/main/java/com/mxgraph/shape/mxDoubleEllipseShape.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
33
src/main/java/com/mxgraph/shape/mxDoubleRectangleShape.java
Normal file
33
src/main/java/com/mxgraph/shape/mxDoubleRectangleShape.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
23
src/main/java/com/mxgraph/shape/mxEllipseShape.java
Normal file
23
src/main/java/com/mxgraph/shape/mxEllipseShape.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
52
src/main/java/com/mxgraph/shape/mxHexagonShape.java
Normal file
52
src/main/java/com/mxgraph/shape/mxHexagonShape.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
134
src/main/java/com/mxgraph/shape/mxHtmlTextShape.java
Normal file
134
src/main/java/com/mxgraph/shape/mxHtmlTextShape.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
15
src/main/java/com/mxgraph/shape/mxIMarker.java
Normal file
15
src/main/java/com/mxgraph/shape/mxIMarker.java
Normal 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);
|
||||
|
||||
}
|
13
src/main/java/com/mxgraph/shape/mxIShape.java
Normal file
13
src/main/java/com/mxgraph/shape/mxIShape.java
Normal 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);
|
||||
|
||||
}
|
19
src/main/java/com/mxgraph/shape/mxITextShape.java
Normal file
19
src/main/java/com/mxgraph/shape/mxITextShape.java
Normal 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);
|
||||
|
||||
}
|
80
src/main/java/com/mxgraph/shape/mxImageShape.java
Normal file
80
src/main/java/com/mxgraph/shape/mxImageShape.java
Normal 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
Loading…
Reference in a new issue