// 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.GeneralPath; import java.awt.geom.Point2D; /** * Implements a curved connector that draws curved lines between * the segments. * @author thomas * */ public class CurvedConnectorShape extends ElbowConnectorShape { Segment[] elbowSegments; //The original segments of the elbow connector CurvedSegment[] curvedSegments; //The elbow segment with bezier points //Higher resolution approximation of the curve //Used for calculating the anchor position Segment[] curveHigh; //Lower resolution approximation of the curve //Used for calculating the arrow heads Segment[] curveLow; protected Shape calculateShape() { return calculateShape(elbowSegments); } public Shape calculateShape(Segment[] segments) { GeneralPath path = new GeneralPath(); CurvedSegment[] curvedSegments = calculateCurvedSegments(segments); path.moveTo( (float)curvedSegments[0].getMStart().getX(), (float)curvedSegments[0].getMStart().getY() ); Segment first = segments[0]; path.moveTo( (float)first.getMStart().getX(), (float)first.getMStart().getY() ); for(int i = 0; i < curvedSegments.length; i++) { CurvedSegment cs = curvedSegments[i]; path.curveTo( (float)cs.getC1().getX(), (float)cs.getC1().getY(), (float)cs.getC2().getX(), (float)cs.getC2().getY(), (float)cs.getMEnd().getX(), (float)cs.getMEnd().getY() ); } // //Custom curve calculation (for testing) // Segment[] curve = calculateCurve(NRSTEP_LOW); // path.moveTo(curve[0].getMStart().getX(), curve[0].getMStart().getY()); // for(int i = 0; i < curve.length; i++) { // path.lineTo(curve[i].getMEnd().getX(), curve[i].getMEnd().getY()); // } return path; } /** * Also calculates curvedSegments and curve * @see calculateCurvedSegments * @see calculateCurve */ protected Segment[] calculateSegments(ConnectorRestrictions restrictions, WayPoint[] waypoints) { elbowSegments = super.calculateSegments(restrictions, waypoints); //Also calculate curved segments curvedSegments = calculateCurvedSegments(elbowSegments); curveHigh = calculateCurve(NRSTEP_HIGH); curveLow = calculateCurve(NRSTEP_LOW); return curveLow; } /** * Based on the given elbow segments, calculate a new segment * and control points for each bezier curve. */ protected CurvedSegment[] calculateCurvedSegments(Segment[] segments) { CurvedSegment[] curvedSegments = new CurvedSegment[segments.length - 1]; Segment first = segments[0]; Segment last = segments[segments.length - 1]; Point2D prev = first.getMStart(); for(int i = 1; i < segments.length - 1; i++) { Segment s = segments[i]; Point2D center = s.getMCenter(); Point2D start = s.getMStart(); curvedSegments[i - 1] = new CurvedSegment( prev, center, prev, start ); prev = s.getMCenter(); } curvedSegments[curvedSegments.length - 1] = new CurvedSegment( prev, last.getMEnd(), last.getMStart(), last.getMEnd() ); return curvedSegments; } static final int NRSTEP_LOW = 3; //Number of steps for lowres curve static final int NRSTEP_HIGH = 20; //Number of steps for highres curve /** * Calculates the bezier curve, using NRSTEP segments for each * curvedSegment. * @see calculateCurvedSegments * @return An array with the curve broken down into small segments */ protected Segment[] calculateCurve(int nrStep) { Segment[] curve = new Segment[nrStep * curvedSegments.length]; for(int i = 0; i < curvedSegments.length; i++) { CurvedSegment cs = curvedSegments[i]; Point2D prev = cs.getMStart(); for(int j = 0; j < nrStep; j++) { double t = (j + 1) * 1.0/nrStep; double xe = bezier( cs.getMStart().getX(), cs.getC1().getX(), cs.getC2().getX(), cs.getMEnd().getX(), t ); double ye = bezier( cs.getMStart().getY(), cs.getC1().getY(), cs.getC2().getY(), cs.getMEnd().getY(), t ); curve[i * nrStep + j] = new Segment(prev, new Point2D.Double(xe, ye)); prev = new Point2D.Double(xe, ye); } } return curve; } /** * Function for the cubic bezier curve * @param p0 The start coordinate * @param p1 The first helper coordinate * @param p2 The second helper coordinate * @param p3 The end coordinate * @param t The relative position in the curve (0 <= t <= 1) * @return The coordinate on the curve for t */ private double bezier(double p0, double p1, double p2, double p3, double t) { return (pow((1-t), 3))*p0 + 3*t*pow((1-t),2)*p1 + 3*pow(t, 2)*(1-t)*p2 + pow(t, 3)*p3; } private double pow(double a, double b) { return Math.pow(a, b); } /** * Segment for curved connector, also stores bezier control points * @author thomas */ private class CurvedSegment extends Segment { private Point2D c1; private Point2D c2; public CurvedSegment(Point2D start, Point2D end, Point2D c1, Point2D c2) { super(start, end); this.c1 = c1; this.c2 = c2; } public Point2D getC1() { return c1; } public Point2D getC2() { return c2; } } public Point2D fromLineCoordinate(double l) { return super.fromLineCoordinate(l, curveHigh); } protected WayPoint[] wayPointsToCenter(WayPoint[] waypoints, Segment[] segments) { return super.wayPointsToCenter(waypoints, elbowSegments); } }