package org.codemap.callhierarchy.vizualization; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import org.codemap.util.geom.CubicCurve2D; import org.codemap.util.geom.Line2D; import org.codemap.util.geom.PathIterator; import org.codemap.util.geom.Point2D; import org.codemap.util.geom.Shape; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Path; import org.eclipse.swt.graphics.PathData; import edu.stanford.hci.flowmap.cluster.Vector2D; import edu.stanford.hci.flowmap.prefuse.render.BezierSpline; import edu.stanford.hci.flowmap.prefuse.render.FlowScale; import edu.stanford.hci.flowmap.structure.Edge; import edu.stanford.hci.flowmap.structure.Graph; import edu.stanford.hci.flowmap.structure.Node; import edu.stanford.hci.flowmap.utils.GraphicsGems; public class EdgeRenderer { private static final float ARROW_SIZE = 8; private boolean m_additiveEdges = true; private FlowScale scale; protected Point2D grandParent, parent, child, grandChild; protected Point2D fourPoints[]; public EdgeRenderer(FlowScale scale) { this.scale = scale; fourPoints = new Point2D[4]; } public void renderEdge(GC gc, Edge edge) { double displayWidth; Node n1 = edge.getFirstNode(); Node n2 = edge.getSecondNode(); if (((n1.getPrevControlPoint() == null) && (n1.getRoutingParent() == null) && (n2.getPrevControlPoint() == null) && (n2.getRoutingParent() == null))) { displayWidth = computeStraightEdge(edge); } else { displayWidth = computeEdge(edge); } Shape shape = edge.getShape(); gc.setLineWidth((int) displayWidth); renderShape(gc, shape); } public void initializeRenderTree(Graph graph) { LinkedList<Node> nodeQeue = new LinkedList<Node>(); nodeQeue.add(graph.getRootNode()); while(nodeQeue.size() > 0) { Node nodeItem = nodeQeue.removeFirst(); for(Edge each: nodeItem.getOutEdges()) { computeEdge(each); nodeQeue.add(each.getSecondNode()); } } } protected double computeStraightEdge(Edge edge) { double displayWidth = scale.getDisplayWidth(edge.getWeight()); Node n1 = edge.getFirstNode(); Node n2 = edge.getSecondNode(); Shape shape = edge.getShape(); if ((shape == null) || !(shape instanceof Line2D)) { shape = new Line2D.Double(n1.getLocation(), n2.getLocation()); edge.setShape(shape); } else { Line2D line = (Line2D)shape; line.setLine(n1.getLocation(), n2.getLocation()); //System.out.println(n1.getLocation() + ", " + n2.getLocation()); } return displayWidth; } protected double computeEdge(Edge edge) { Node n1; Node n2; //used as temporary node storage // Compute the display width here double edgeWeight = edge.getWeight(); double displayWidth = scale.getDisplayWidth(edge.getWeight()); displayWidth = Math.round(displayWidth); //System.out.println("SimpleEdgeRenderer edgeWeight " + // edgeItem.getWeight() + " displayWidth " + displayWidth ); n1 = edge.getFirstNode(); // 1. set the grandParent catmull-rom point. This is the parent of n1 n2 = n1.getRoutingParent(); if (n2 == null) { //System.out.println("n2 is null"); Point2D n1Pt = n1.getLocation(); // if the grandParent doesn't exist (this is the root), // then we want to construct a grandParent point that is suitable. // we do this by taking the average position of all the children // constructing the vector between the node and those children // flipping the vector, and moving it away from the parent point Iterator gpIter = n1.getOutEdges().iterator(); //System.out.println("n1.outEdges: " + n1.getOutEdges().size()); double avgX, avgY; int count = 0; avgX = avgY = 0; while (gpIter.hasNext()) { Edge e2 = (Edge) gpIter.next(); Point2D e2Pt = e2.getSecondNode() .getLocation(); //System.out.println("averaging points: " + e2Pt); avgX += e2Pt.getX(); avgY += e2Pt.getY(); count++; } avgX /= count; avgY /= count; Vector2D gpVec = new Vector2D(n1Pt, new Point2D.Double(avgX, avgY)); Point2D gpVecNorm = gpVec.getNormalized(); gpVecNorm.setLocation(-1 * gpVecNorm.getX(), -1 * gpVecNorm.getY()); grandParent = new Point2D.Double(n1Pt.getX() + 10 * gpVecNorm.getX(), n1Pt.getY() + 10 * gpVecNorm.getY()); } else { //System.out.println("n2 is not null"); grandParent = n1.getPrevControlPoint(); //System.out.println("Prev Control Point for " + // ((FlowNode)n1.getEntity()).toStringId() + " has prev control // point: " + grandParent); } //System.out.println("grandparent: " + grandParent); // 2. the parent catmull-rom point is n1, since we want it to // go through this point parent = n1.getLocation(); Point2D shiftPoint = null; // additive edge code // only shift edges if we are not at the root // relies on the fact that there only 2 edges per node if (m_additiveEdges && (n1.getRoutingParent() != null)) { n2 = n1.getRoutingParent(); //redundant, but who cares // get the other edge that starts from n1 Edge otherEdgeItem = null; //System.out.println("n1.getOutEdges().size() " + n1.getOutEdges().size()); for (Edge other: n1.getOutEdges()) { if ((otherEdgeItem = other) != edge) break; } assert(otherEdgeItem != null); // find the parent edge from n2 to n1 Edge parentEdgeItem = null; for (Edge parent: n2.getOutEdges()) { parentEdgeItem = parent; if (parentEdgeItem.isIncident(n1) && (parentEdgeItem.isIncident(n2))) { break; } } assert(parentEdgeItem != null); Node item1; Node item2; // get vectors for both edges item1 = edge.getFirstNode(); item2 = edge.getSecondNode(); Vector2D thisEdgeVec = new Vector2D(item1.getLocation(), item2.getLocation()); item1 = otherEdgeItem.getFirstNode(); item2 = otherEdgeItem.getSecondNode(); Vector2D otherEdgeVec = new Vector2D(item1.getLocation(), item2.getLocation()); // now do the cross product of thisEdgeVec // do not use Vector3d but own implementation that gets rid of this dependency // Vector3d thisEdgeVector = new Vector3d(thisEdgeVec.getNormalized().getX(), -1*thisEdgeVec.getNormalized().getY(), 0); // Vector3d otherEdgeVector = new Vector3d(otherEdgeVec.getNormalized().getX(), -1*otherEdgeVec.getNormalized().getY(), 0); // // Vector3d result = new Vector3d(); // // result.cross(thisEdgeVector, otherEdgeVector); double[] cross = computeCrossProduct(thisEdgeVec.getNormalized().getX(), -1*thisEdgeVec.getNormalized().getY(), otherEdgeVec.getNormalized().getX(), -1*otherEdgeVec.getNormalized().getY()); //System.out.println("edges: " + parentEdgeItem + ", " + edgeItem + " , " + otherEdgeItem); //System.out.println(result); // compute the perpendicular vector, the direction to shift // we will get a vector that points to the right Vector2D grandToParent = new Vector2D(grandParent, parent); Point2D shiftDir = new Point2D.Double(-grandToParent.getNormalized().getY(), -grandToParent.getNormalized().getX()); // if (result.z < 0) { if (cross[2] < 0 ) { // if z > 0 then we know that thisEdge is to the right of otherEdge // so we push this in the direction of shiftDir // else we know that thisEdge is to the left of otherEdge // so we push this up, or in the negative direction of shiftDir shiftDir.setLocation(shiftDir.getX()*-1, shiftDir.getY()*-1); } double parentWidth = scale.getDisplayWidth(parentEdgeItem.getWeight()); double otherWidth = scale.getDisplayWidth(otherEdgeItem.getWeight()); parentWidth = Math.round(parentWidth); otherWidth = Math.round(otherWidth); /* System.out.println("edges: par: " + parentEdgeItem + ", edge:" + edgeItem + " , other:" + otherEdgeItem); System.out.println("parentActual: " + parentEdgeItem.getWeight(currFlowType) + " dispActual: " + edgeItem.getWeight(currFlowType) + " otherActual: " + otherEdgeItem.getWeight(currFlowType)); System.out.println("parentWidth: " + parentWidth + " dispWidth: " + displayWidth + " otherWidth: " + otherWidth); */ assert(parentWidth >= displayWidth); // compute the distance to shift this edge. It should be: // parentDisplayWidth/2 - displayWidth/2 double shiftDist = (parentWidth/2) - (displayWidth/2); shiftPoint = new Point2D.Double(shiftDist*shiftDir.getX(), -1*shiftDist*shiftDir.getY()); } // 6. Set the child catmull-rom pont to be e1.to child = edge.getSecondNode().getLocation(); // 7. Compute the grand child catmull-rom point. // It is either the position of the heavier child, // or if there are no more child nodes, it is in a straight // line with parent and child, just further out Collection<Edge> grandChildEdges = edge.getSecondNode().getOutEdges(); if (grandChildEdges.size() != 0) { double gcWeight, gcX, gcY; gcX = gcY = gcWeight = -1; // find the heaviest child for(Edge gcEdge: grandChildEdges) { if (gcEdge.getWeight() > gcWeight) { gcWeight = gcEdge.getWeight(); gcX = gcEdge.getSecondNode().getLocation().getX(); gcY = gcEdge.getSecondNode().getLocation().getY(); //System.out.println("x: " + gcX + " y:" + gcY); } } assert(gcWeight != -1); grandChild = new Point2D.Double(gcX, gcY); GraphicsGems.checkNaN(grandChild); } else { /* System.out.println(); System.out.println(grandParent); System.out.println(parent); System.out.println(child); */ Vector2D parentToChild = new Vector2D(parent, child); Point2D pToCDir = parentToChild.getNormalized(); // System.out.println(pToCDir); Vector2D grandParentToParent = new Vector2D(grandParent, parent); Point2D gpToPDir = grandParentToParent.getNormalized(); //System.out.println(gpToPDir); double angleBetween = parentToChild .absAngleBetween(grandParentToParent); //System.out.println(edgeItem + " angleBetween is " + (180*angleBetween/Math.PI)); // now rotate the parentToChild normalized vector by -angleBetween // assuming rotations are CCW double x = pToCDir.getX() * Math.cos(-angleBetween) - pToCDir.getY() * Math.sin(-angleBetween); double y = pToCDir.getX() * Math.sin(-angleBetween) + pToCDir.getY() * Math.cos(-angleBetween); //System.out.println("grandChild case 2: " + x + "," + y); grandChild = new Point2D.Double(child.getX() + 100 * x, child .getY() + 100 * y); GraphicsGems.checkNaN(grandChild); } // 8. Now that we know where all the catmull-rom objects are, // construct a BasicStroke object with e1's thickness // haha, it's not even read ... // BasicStroke bs = new BasicStroke((float) displayWidth, // BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND); // 9. construct a CubicCurve object with the given control points // by passing the four points object to the BezierSpline.computeSplines // function fourPoints[0] = grandParent; fourPoints[1] = parent; fourPoints[2] = child; fourPoints[3] = grandChild; Shape shape = edge.getShape(); CubicCurve2D myCurve; if (shape == null) { myCurve = new CubicCurve2D.Double(); edge.setShape(myCurve); } else { myCurve = (CubicCurve2D)shape; } BezierSpline.computeOneSpline(grandParent, parent, child, grandChild, myCurve); // 11. Now do some tweaking of the control points to ensure continuity. // need to tweak the first control point to be collinear with the parent // and the grandparent and have the same distance as the // parent-grandparent double parentGrandDist = grandParent.distance(parent); Vector2D grandToParent = new Vector2D(grandParent, parent); //System.out.println(grandToParent); //now take the grandToParent vector, scale by parentGrandDistance, add // it to the parent vector Point2D collinShift = new Point2D.Double(parentGrandDist * grandToParent.getNormalized().getX() + parent.getX(), parentGrandDist * grandToParent.getNormalized().getY() + parent.getY()); //System.out.println("before: " + myCurve.getP1() + ", " // +myCurve.getCtrlP1() + ", " + myCurve.getCtrlP2() + ", " + // myCurve.getP2() ); myCurve.setCurve(myCurve.getP1(), collinShift, myCurve.getCtrlP2(), myCurve.getP2()); GraphicsGems.checkNaN(myCurve.getP1()); GraphicsGems.checkNaN(myCurve.getP2()); GraphicsGems.checkNaN(myCurve.getCtrlP1()); GraphicsGems.checkNaN(myCurve.getCtrlP2()); //System.out.println("after: " + myCurve.getP1() + ", " // +myCurve.getCtrlP1() + ", " + myCurve.getCtrlP2() + ", " + // myCurve.getP2() ); //System.out.println("CollinShift dist: " + // collinShift.distance(parent) + " and p-GP dist: " + parentGrandDist); // 10. Need to set the previous control point for the child node Node otherItem = edge.getSecondNode(); otherItem.setPrevControlPoint(myCurve.getCtrlP2()); //System.out.println("Prev Control Point for " + // ((FlowNode)otherItem.getEntity()).toStringId() + " was set to prev // control point: " + grandParent); if ((m_additiveEdges) && (shiftPoint != null)) { // 12. Now shift the bezier points to line up properly with the horizontal translation // that takes into account the thickness of the curve. We do this by shifting // the first two points of every bezier to get it to line up in the appropriate place Point2D grandParentShift = new Point2D.Double(myCurve.getP1().getX()+shiftPoint.getX(), myCurve.getP1().getY()+shiftPoint.getY()); Point2D parentShift = new Point2D.Double(myCurve.getCtrlP1().getX()+shiftPoint.getX(), myCurve.getCtrlP1().getY()+shiftPoint.getY()); myCurve.setCurve(grandParentShift, parentShift, myCurve.getCtrlP2(), myCurve.getP2()); } GraphicsGems.checkNaN(myCurve.getP1()); GraphicsGems.checkNaN(myCurve.getP2()); GraphicsGems.checkNaN(myCurve.getCtrlP1()); GraphicsGems.checkNaN(myCurve.getCtrlP2()); return displayWidth; } /* * JFreeChart : a free chart library for the Java(tm) platform * * * (C) Copyright 2000-2009, by Object Refinery Limited and Contributors. * * Project Info: http://www.jfree.org/jfreechart/index.html * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. * * [Java is a trademark or registered trademark of Sun Microsystems, Inc. * in the United States and other countries.] * * ------------------ * SWTGraphics2D.java * ------------------ * (C) Copyright 2006-2008, by Henry Proudhon and Contributors. * * Original Author: Henry Proudhon (henry.proudhon AT ensmp.fr); * Contributor(s): Cedric Chabanois (cchabanois AT no-log.org); * David Gilbert (for Object Refinery Limited); * Ronnie Duan (see bug report 2583891); * */ /** * Converts an AWT <code>Shape</code> into a SWT <code>Path</code>. * copied from: http://www.java2s.com/Code/Java/SWT-JFace-Eclipse/DrawGraphics2Dstuffonaswtcomposite.htm */ private void renderShape(GC gc, Shape shape) { int type; float[] coords = new float[6]; Path path = new Path(gc.getDevice()); PathIterator pit = shape.getPathIterator(null); while (!pit.isDone()) { type = pit.currentSegment(coords); switch (type) { case (PathIterator.SEG_MOVETO): path.moveTo(coords[0], coords[1]); break; case (PathIterator.SEG_LINETO): path.lineTo(coords[0], coords[1]); break; case (PathIterator.SEG_QUADTO): path.quadTo(coords[0], coords[1], coords[2], coords[3]); break; case (PathIterator.SEG_CUBICTO): path.cubicTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); break; case (PathIterator.SEG_CLOSE): path.close(); break; default: break; } pit.next(); } gc.drawPath(path); rederArrowHeads(gc, path.getPathData()); } private void rederArrowHeads(GC gc, PathData pathData) { float[] points = pathData.points; int length = points.length; if (length < 4) return; float endx = points[length-2], endy = points[length-1]; float cx = points[length-4], cy = points[length-3]; // Control point of the curve float adjSize = (float)(ARROW_SIZE/Math.sqrt(2)); float ex = endx - cx; float ey = endy - cy; float abs_e = (float)Math.sqrt(ex*ex + ey*ey); ex /= abs_e; ey /= abs_e; // Creating arrowhead Path arrowHead = new Path(gc.getDevice()); arrowHead.moveTo(endx, endy); arrowHead.lineTo(endx + (ey-ex)*adjSize, endy - (ex + ey)*adjSize); arrowHead.moveTo(endx, endy); arrowHead.lineTo(endx - (ey + ex)*adjSize, endy + (ex - ey)*adjSize); gc.drawPath(arrowHead); } private double[] computeCrossProduct (double x1, double y1, double x2, double y2) { double v0[] = {x1, y1, 0}; double v1[] = {x2, y2, 0}; double crossProduct[] = new double[3]; crossProduct[0] = v0[1] * v1[2] - v0[2] * v1[1]; crossProduct[1] = v0[2] * v1[0] - v0[0] * v1[2]; crossProduct[2] = v0[0] * v1[1] - v0[1] * v1[0]; return crossProduct; } }