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) })); } } }