// PathVisio, // a tool for data visualization and analysis using Biological Pathways // Copyright 2006-2011 BiGCaT Bioinformatics // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // package org.pathvisio.core.model; import java.awt.Shape; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.List; import org.pathvisio.core.model.ConnectorShape.WayPoint; import org.pathvisio.core.model.GraphLink.GraphIdContainer; import org.pathvisio.core.util.Utils; /** * MLine - basically a PathwayElement, but overrides some methods * to calculate coordinates dynamically. For example getMCenterX(). * * For other shapes, the centerX coordinate is stored in GPML. Lines however * store the end-points, the center can be calculated based on that. */ public class MLine extends PathwayElement implements ConnectorRestrictions { ConnectorShape shape; public MLine() { super(ObjectType.LINE); } /** * the Connector Shape for this line - the connector shape * can calculate a Shape based on the connector type (straight, elbow or curved) * and possibly way points */ public ConnectorShape getConnectorShape() { String type = getConnectorType().getName(); //Recreate the ConnectorShape when it's null or when the type //doesn't match the implementing class if(shape == null || !shape.getClass().equals(ConnectorShapeFactory.getImplementingClass(type))) { shape = ConnectorShapeFactory.createConnectorShape(getConnectorType().getName()); shape.recalculateShape(this); } return shape; } /** * returns the center x coordinate of the bounding box around (start, end) */ public double getMCenterX() { double start = getMStart().getX(); double end = getMEnd().getX(); return start + (end - start) / 2; } /** * returns the center y coordinate of the bounding box around (start, end) */ public double getMCenterY() { double start = getMStart().getY(); double end = getMEnd().getY(); return start + (end - start) / 2; } /** * returns the left x coordinate of the bounding box around (start, end) */ public double getMLeft() { double start = getMStart().getX(); double end = getMEnd().getX(); return Math.min(start, end); } /** * returns the width of the bounding box around (start, end) */ public double getMWidth() { double start = getMStart().getX(); double end = getMEnd().getX(); return Math.abs(start-end); } /** * returns the height of the bounding box around (start, end) */ public double getMHeight() { double start = getMStart().getY(); double end = getMEnd().getY(); return Math.abs(start-end); } /** * returns the top y coordinate of the bounding box around (start, end) */ public double getMTop() { double start = getMStart().getY(); double end = getMEnd().getY(); return Math.min(start, end); } /** * Sets the position of the top side * of the rectangular bounds of the line */ public void setMTop(double v) { if(getDirectionY() > 0) { setMStartY(v); } else { setMEndY(v); } } /** * Sets the position of the left side * of the rectangular bounds of the line */ public void setMLeft(double v) { if(getDirectionX() > 0) { setMStartX(v); } else { setMEndX(v); } } /** * Sets the x position of the center of the line. This makes * the line move as a whole */ public void setMCenterX(double v) { double dx = v - getMCenterX(); setMStartX(getMStartX() + dx); setMEndX(getMEndX() + dx); } /** * Sets the y position of the center of the line. This makes the line * move as a whole. */ public void setMCenterY(double v) { double dy = v - getMCenterY(); setMStartY(getMStartY() + dy); setMEndY(getMEndY() + dy); } /** returns the sign of end.x - start.x */ private int getDirectionX() { return (int)Math.signum(getMEndX() - getMStartX()); } /** returns the sign of end.y - start.y */ private int getDirectionY() { return (int)Math.signum(getMEndY() - getMStartY()); } /** converts end point from MPoint to Point2D */ public Point2D getEndPoint() { return getMEnd().toPoint2D(); } /** converts start point from MPoint to Point2D */ public Point2D getStartPoint() { return getMStart().toPoint2D(); } /** converts all points from MPoint to Point2D */ public List getPoints() { List pts = new ArrayList(); for(MPoint p : getMPoints()) { pts.add(p.toPoint2D()); } return pts; } /** * Returns the element that the start of this line is connected to. Returns null if there isn't any. */ private GraphIdContainer getStartElement() { Pathway parent = getParent(); if(parent != null) { return parent.getGraphIdContainer(getStartGraphRef()); } return null; } /** * Returns the element that the end of this line is connected to. Returns null if there isn't any. */ private GraphIdContainer getEndElement() { Pathway parent = getParent(); if(parent != null) { return parent.getGraphIdContainer(getEndGraphRef()); } return null; } /** * Calculate on which side of a PathwayElement (SIDE_NORTH, SIDE_EAST, SIDE_SOUTH or SIDE_WEST) * the start of this line is connected to. * * If the start is not connected to anything, returns SIDE_WEST */ public int getStartSide() { int side = SIDE_WEST; GraphIdContainer e = getStartElement(); if(e != null) { if(e instanceof PathwayElement) { side = getSide(getMStart().getRelX(), getMStart().getRelY()); } else if(e instanceof MAnchor) { side= getAttachedLineDirection((MAnchor)e); } } return side; } /** * Calculate on which side of a PathwayElement (SIDE_NORTH, SIDE_EAST, SIDE_SOUTH or SIDE_WEST) * the end of this line is connected to. * * If the end is not connected to anything, returns SIDE_EAST */ public int getEndSide() { int side = SIDE_EAST; GraphIdContainer e = getEndElement(); if(e != null) { if(e instanceof PathwayElement) { side = getSide(getMEnd().getRelX(), getMEnd().getRelY()); } else if(e instanceof MAnchor) { side= getAttachedLineDirection((MAnchor)e); } } return side; } private int getAttachedLineDirection(MAnchor anchor) { int side; double pos = anchor.getPosition(); MLine attLine = ((MLine)anchor.getParent()); if (attLine.getConnectorShape() instanceof ElbowConnectorShape) { ConnectorShape.Segment attSeg = findAnchorSegment(attLine, pos); int orientationX = Utils.getDirectionX(attSeg.getMStart(), attSeg.getMEnd()); int orientationY = Utils.getDirectionY(attSeg.getMStart(), attSeg.getMEnd()); side = getSide(orientationY, orientationX); } else { side = getOppositeSide(getSide(getMEndX(), getMEndY(), getMStartX(), getMStartY())); if (attLine.almostPerfectAlignment(side)) { side = getClockwisePerpendicularSide(side); } } return side; } private ConnectorShape.Segment findAnchorSegment(MLine attLine, double pos) { ConnectorShape.Segment[] segments = attLine.getConnectorShape().getSegments(); Double totLength = 0.0; ConnectorShape.Segment attSeg = null; for (ConnectorShape.Segment segment:segments) { totLength = totLength + segment.getMLength(); } Double currPos; Double segSum = 0.0; for (ConnectorShape.Segment segment:segments) { segSum = segSum + segment.getMLength(); currPos = segSum / totLength; attSeg = segment; if (currPos > pos) { break; } } return attSeg; } /** * Check if either the line segment has less than or equal to * 10 degree alignment with the side passed * @param startLine * @param endLine * @return true if <= 10 degree alignment * else false */ private boolean almostPerfectAlignment(int side){ int MAXOFFSET = 30; /* cutoff point where we see a shallow angle still as either horizontal or vertical */ // X axis if ((side == SIDE_EAST) || (side == SIDE_WEST)) { double angleDegree = (180/Math.PI)*Math.atan2(Math.abs(getStartPoint().getY() - getEndPoint().getY()), Math.abs(getStartPoint().getX() - getEndPoint().getX())); if (angleDegree <= MAXOFFSET) return true; } else {//north south or Y axis double angleDegree = (180/Math.PI)*Math.atan2(Math.abs(getStartPoint().getX() - getEndPoint().getX()), Math.abs(getStartPoint().getY() - getEndPoint().getY())); if (angleDegree <= MAXOFFSET) return true; } return false; } /** * Returns the Perpendicular for a SIDE_* constant (e.g. SIDE_EAST <-> SIDE_WEST) */ private int getClockwisePerpendicularSide(int side) { switch(side) { case SIDE_EAST: return SIDE_SOUTH; case SIDE_WEST: return SIDE_NORTH; case SIDE_NORTH: return SIDE_EAST; case SIDE_SOUTH: return SIDE_WEST; } return -1; } public void adjustWayPointPreferences(WayPoint[] waypoints) { List mpoints = getMPoints(); for(int i = 0; i < waypoints.length; i++) { WayPoint wp = waypoints[i]; MPoint mp = mpoints.get(i + 1); if(mp.getX() != wp.getX() || mp.getY() != wp.getY()) { dontFireEvents(1); mp.moveTo(wp.getX(), wp.getY()); } } } public void resetWayPointPreferences() { List mps = getMPoints(); while(mps.size() > 2) { mps.remove(mps.size() - 2); } } /** * Get the preferred waypoints, to which the connector must draw * it's path. The waypoints returned by this method are preferences * and the connector shape may decide not to use them if they are invalid. */ public WayPoint[] getWayPointPreferences() { List pts = getMPoints(); WayPoint[] wps = new WayPoint[pts.size() - 2]; for(int i = 0; i < wps.length; i++) { wps[i] = new WayPoint(pts.get(i + 1).toPoint2D()); } return wps; } /** * Get the side of the given pathway element to which * the x and y coordinates connect * @param x The x coordinate * @param y The y coordinate * @param e The element to find the side of * @return One of the SIDE_* constants */ private static int getSide(double x, double y, double cx, double cy) { return getSide(x - cx, y - cy); } private static int getSide(double relX, double relY) { int direction = 0; if(Math.abs(relX) > Math.abs(relY)) { if(relX > 0) { direction = SIDE_EAST; } else { direction = SIDE_WEST; } } else { if(relY > 0) { direction = SIDE_SOUTH; } else { direction = SIDE_NORTH; } } return direction; } /** * Returns the opposite for a SIDE_* constant (e.g. SIDE_EAST <-> SIDE_WEST) */ private int getOppositeSide(int side) { switch(side) { case SIDE_EAST: return SIDE_WEST; case SIDE_WEST: return SIDE_EAST; case SIDE_NORTH: return SIDE_SOUTH; case SIDE_SOUTH: return SIDE_NORTH; } return -1; } /** * Check if the connector may cross this point * Optionally, returns a shape that defines the boundaries of the area around this * point that the connector may not cross. * This method can be used for advanced connectors that route along other objects * on the drawing * @return A shape that defines the boundaries of the area around this point that * the connector may not cross. Returning null is allowed for implementing classes. */ public Shape mayCross(Point2D point) { Pathway parent = getParent(); Rectangle2D rect = null; if(parent != null) { for(PathwayElement e : parent.getDataObjects()) { ObjectType ot = e.getObjectType(); if(ot == ObjectType.SHAPE || ot == ObjectType.DATANODE || ot == ObjectType.LABEL) { Rectangle2D b = e.getMBounds(); if(b.contains(point)) { if(rect == null) rect = b; else rect.add(b); } } } } return rect; } }