/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package com.trickl.graph.ext; import com.jgraph.components.labels.CellConstants; import com.jgraph.components.labels.MultiLineVertexRenderer; import com.trickl.graph.edges.DirectedEdge; import com.trickl.graph.planar.AbstractPlanarFaceTraversalVisitor; import com.trickl.graph.planar.BreadthFirstPlanarFaceTraversal; import com.trickl.graph.planar.CanonicalPlanarFaceTraversal; import com.trickl.graph.planar.PlanarFaceTraversal; import com.trickl.graph.planar.PlanarGraph; import com.trickl.graph.planar.PlanarGraphs; import java.awt.*; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.text.ParseException; import java.util.*; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.BorderFactory; import org.jgraph.event.GraphModelEvent; import org.jgraph.event.GraphModelEvent.GraphModelChange; import org.jgraph.event.GraphModelListener; import org.jgraph.graph.*; import org.jgrapht.Graph; import org.jgrapht.ext.ComponentAttributeProvider; import org.jgrapht.ext.EdgeNameProvider; import org.jgrapht.ext.JGraphModelAdapter; import org.jgrapht.ext.VertexNameProvider; /** * * @author tgee */ public class JGraphModelAdapterExt<V, E> extends JGraphModelAdapter { private PlanarGraph<V, E> graph; private VertexNameProvider<V> vertexLabelProvider; private EdgeNameProvider<E> edgeLabelProvider; private FaceNameProvider<V> faceLabelProvider; private ComponentAttributeProvider<V> vertexAttributeProvider; private ComponentAttributeProvider<E> edgeAttributeProvider; private ComponentAttributeProvider<DirectedEdge<V>> faceAttributeProvider; private AttributeMap defaultFaceAttributes; private final Map<DirectedEdge<V>, GraphCell> faceToCell = new HashMap<DirectedEdge<V>, GraphCell>(); private final Map<GraphCell, DirectedEdge<V>> cellToFace = new HashMap<GraphCell, DirectedEdge<V>>(); final private Pattern pointPattern = Pattern.compile("([-+]?[0-9]*\\.?[0-9]+)\\s*,\\s*([-+]?[0-9]*\\.?[0-9]+)[!]?"); final private Pattern dimensionPattern = Pattern.compile("([0-9]+)\\s*,\\s*([0-9]+)"); final private Pattern colorPattern = Pattern.compile("#([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])?"); public JGraphModelAdapterExt(PlanarGraph<V, E> graph, VertexNameProvider<V> vertexLabelProvider, EdgeNameProvider<E> edgeLabelProvider, ComponentAttributeProvider<V> vertexAttributeProvider, ComponentAttributeProvider<E> edgeAttributeProvider) { this(graph, vertexLabelProvider, edgeLabelProvider, null, vertexAttributeProvider, edgeAttributeProvider, null); } public JGraphModelAdapterExt(PlanarGraph<V, E> graph, VertexNameProvider<V> vertexLabelProvider, EdgeNameProvider<E> edgeLabelProvider, FaceNameProvider<V> faceLabelProvider, ComponentAttributeProvider<V> vertexAttributeProvider, ComponentAttributeProvider<E> edgeAttributeProvider, ComponentAttributeProvider<DirectedEdge<V>> faceAttributeProvider) { super(graph); this.graph = graph; this.vertexLabelProvider = vertexLabelProvider; this.edgeLabelProvider = edgeLabelProvider; this.faceLabelProvider = faceLabelProvider; this.vertexAttributeProvider = vertexAttributeProvider; this.edgeAttributeProvider = edgeAttributeProvider; this.faceAttributeProvider = faceAttributeProvider; defaultFaceAttributes = createDefaultFaceAttributes(); // A nasty hack to use a custom listener so we can filter the face vertices // If only "internalAddVertex" was protected not private... for (GraphModelListener listener : getGraphModelListeners()) { this.removeGraphModelListener(listener); } // We're unable to set these on vertex creation correctly, due to visibility // issues. So we'll update them now. Note, this workaround means that the correct // dynamic labels and attributes are not set correctly for modified graphs. updateVertexLabels(); updateEdgeLabels(); updateVertexAttributes(); updateEdgeAttributes(); // Traverse all faces and add face cells PlanarFaceTraversal<V, E> planarFaceTraversal = new BreadthFirstPlanarFaceTraversal<V, E>(graph); planarFaceTraversal.traverse(new AbstractPlanarFaceTraversalVisitor<V, E>() { @Override public void beginFace(V source, V target) { handleJGraphTAddedFace(new DirectedEdge<V>(source, target)); } }); } private void updateEdgeLabels() { for (E edge : graph.edgeSet()) { DefaultEdge cell = getEdgeCell(edge); String label = edgeLabelProvider == null ? "" : edgeLabelProvider.getEdgeName(edge); cell.setUserObject(label == null ? "" : label); } } private void updateVertexLabels() { for (V vertex : graph.vertexSet()) { DefaultGraphCell cell = getVertexCell(vertex); String label = vertexLabelProvider == null ? "" : vertexLabelProvider.getVertexName(vertex); cell.setUserObject(label == null ? "" : label); } } private void updateVertexAttributes() { for (V vertex : graph.vertexSet()) { DefaultGraphCell cell = getVertexCell(vertex); AttributeMap cellAttributes = cell.getAttributes(); cellAttributes.cloneEntries(getVertexAttributeMap(vertex, cell)); } } private void updateEdgeAttributes() { for (E edge : graph.edgeSet()) { DefaultEdge cell = getEdgeCell(edge); AttributeMap cellAttributes = cell.getAttributes(); cellAttributes.cloneEntries(getEdgeAttributeMap(edge, cell)); } } private Point2D.Double parsePoint(String pointString) throws ParseException { Matcher matcher = pointPattern.matcher(pointString); if (matcher.matches()) { return new Point2D.Double(Double.parseDouble(matcher.group(1)), Double.parseDouble(matcher.group(2))); } else { throw new ParseException("Could not parse point '" + pointString + "'", 0); } } private Dimension parseDimension(String dimensionString) throws ParseException { Matcher matcher = dimensionPattern.matcher(dimensionString); if (matcher.matches()) { return new Dimension(Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2))); } else { throw new ParseException("Could not parse dimension '" + dimensionString + "'", 0); } } private int parseMultiLineVertexRendererShape(String shapeString) throws ParseException { shapeString = shapeString.toLowerCase(); if ("circle".equals(shapeString)) { return MultiLineVertexRenderer.SHAPE_CIRCLE; } else if ("cylinder".equals(shapeString)) { return MultiLineVertexRenderer.SHAPE_CYLINDER; } else if ("diamond".equals(shapeString)) { return MultiLineVertexRenderer.SHAPE_DIAMOND; } else if ("box".equals(shapeString)) { return MultiLineVertexRenderer.SHAPE_RECTANGLE; } else if ("rounded".equals(shapeString)) { return MultiLineVertexRenderer.SHAPE_ROUNDED; } else if ("triangle".equals(shapeString)) { return MultiLineVertexRenderer.SHAPE_TRIANGLE; } else { throw new ParseException("Could not parse shape '" + shapeString + "'", 0); } } /** * Creates and returns a map of attributes to be used as defaults for face * * @return a map of attributes to be used as defaults for vertex attributes. */ public static AttributeMap createDefaultFaceAttributes() { AttributeMap map = new AttributeMap(); Color c = Color.decode("#DDDDDD"); GraphConstants.setBackground(map, c); GraphConstants.setForeground(map, Color.white); GraphConstants.setFont( map, GraphConstants.DEFAULTFONT.deriveFont(Font.BOLD, 12)); GraphConstants.setOpaque(map, true); return map; } public AttributeMap getDefaultFaceAttributes() { return defaultFaceAttributes; } protected AttributeMap getVertexAttributeMap(V vertex, DefaultGraphCell cell) { AttributeMap attrs = cell.getAttributes(); attrs.putAll(attrs.cloneEntries(getDefaultVertexAttributes())); if (vertexAttributeProvider != null) { Map<String, String> vertexAttributes = vertexAttributeProvider.getComponentAttributes(vertex); for (String attributeKey : vertexAttributes.keySet()) { Object attributeValue = vertexAttributes.get(attributeKey); try { if ("size".equals(attributeKey)) { Dimension dimension = parseDimension(attributeValue.toString()); Rectangle2D bounds = GraphConstants.getBounds(attrs); GraphConstants.setBounds(attrs, new Rectangle((int) bounds.getX(), (int) bounds.getY(), dimension.width, dimension.height)); } else if ("width".equals(attributeKey)) { int vertexWidth = Integer.parseInt(attributeValue.toString()); Rectangle2D bounds = GraphConstants.getBounds(attrs); GraphConstants.setBounds(attrs, new Rectangle((int) bounds.getX(), (int) bounds.getY(), vertexWidth, (int) bounds.getHeight())); } else if ("height".equals(attributeKey)) { int vertexHeight = Integer.parseInt(attributeValue.toString()); Rectangle2D bounds = GraphConstants.getBounds(attrs); GraphConstants.setBounds(attrs, new Rectangle((int) bounds.getX(), (int) bounds.getY(), (int) bounds.getWidth(), vertexHeight)); } else if ("pos".equals(attributeKey)) { Point2D.Double point = parsePoint(attributeValue.toString()); Rectangle2D bounds = GraphConstants.getBounds(attrs); GraphConstants.setBounds(attrs, new Rectangle((int) point.x, (int) point.y, (int) bounds.getWidth(), (int) bounds.getHeight())); } else if ("color".equals(attributeKey)) { attrs.applyValue(GraphConstants.BORDERCOLOR, Color.decode(attributeValue.toString())); } else if ("fillcolor".equals(attributeKey)) { attrs.applyValue(GraphConstants.BACKGROUND, Color.decode(attributeValue.toString())); } else if ("shape".equals(attributeKey)) { attrs.applyValue(CellConstants.VERTEXSHAPE, parseMultiLineVertexRendererShape(attributeValue.toString())); } } catch (ParseException ex) { // Silently ignore? } } } return attrs; } protected AttributeMap getEdgeAttributeMap(E edge, DefaultGraphCell cell) { AttributeMap attrs = cell.getAttributes(); attrs.putAll(attrs.cloneEntries(getDefaultEdgeAttributes())); if (edgeAttributeProvider != null) { Map<String, String> edgeAttributes = edgeAttributeProvider.getComponentAttributes(edge); for (String attributeKey : edgeAttributes.keySet()) { Object attributeValue = edgeAttributes.get(attributeKey); if ("color".equals(attributeKey)) { attrs.applyValue(GraphConstants.BACKGROUND, Color.decode(attributeValue.toString())); } } } return attrs; } protected AttributeMap getFaceAttributeMap(DirectedEdge<V> face, DefaultGraphCell cell) { AttributeMap attrs = cell.getAttributes(); attrs.putAll(attrs.cloneEntries(getDefaultFaceAttributes())); if (faceAttributeProvider != null) { Map<String, String> faceAttributes = faceAttributeProvider.getComponentAttributes(face); for (String attributeKey : faceAttributes.keySet()) { Object attributeValue = faceAttributes.get(attributeKey); if ("color".equals(attributeKey)) { attrs.applyValue(GraphConstants.BORDERCOLOR, Color.decode(attributeValue.toString())); } else if ("fillcolor".equals(attributeKey)) { attrs.applyValue(GraphConstants.BACKGROUND, Color.decode(attributeValue.toString())); } } } return attrs; } public DefaultGraphCell getFaceCell(DirectedEdge<V> face) { return (DefaultGraphCell) faceToCell.get(face); } void handleJGraphTAddedFace(DirectedEdge<V> jtFace) { if (graph.isBoundary(jtFace.getSource(), jtFace.getTarget())) return; List<V> vertices = PlanarGraphs.getVerticesOnFace((PlanarGraph<V, E>) graph, jtFace.getSource(), jtFace.getTarget()); List<Point> boundary = new LinkedList<Point>(); for (V vertex : vertices) { DefaultGraphCell vertexCell = getVertexCell(vertex); AttributeMap vertexAttrs = vertexCell.getAttributes(); Rectangle2D vertexBounds = GraphConstants.getBounds(vertexAttrs); boundary.add(new Point((int) vertexBounds.getCenterX(), (int) vertexBounds.getCenterY())); } DefaultGraphCell faceCell = new FaceCell(jtFace, boundary); faceToCell.put(jtFace, faceCell); cellToFace.put(faceCell, jtFace); AttributeMap attributeMap = new AttributeMap(); attributeMap.put(faceCell, getFaceAttributeMap(jtFace, faceCell).clone()); insert(new Object[]{faceCell}, attributeMap, null, null, null); } void handleJGraphTRemoveFace(DirectedEdge<V> jtFace) { DefaultGraphCell faceCell = (DefaultGraphCell) faceToCell.remove(jtFace); cellToFace.remove(faceCell); remove(new Object[]{faceCell}); } }