package de.lmu.ifi.dbs.elki.visualization.svg; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import org.apache.batik.util.SVGConstants; import org.w3c.dom.Document; import org.w3c.dom.Element; import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector; /** * Element used for building an SVG path using a string buffer. * * @author Erich Schubert * * @apiviz.uses Element oneway - - «create» */ public class SVGPath { /** * String buffer for building the path. */ private StringBuilder buf = new StringBuilder(); /** * The last action we did, to not add unnecessary commands */ private String lastaction = null; /** * The absolute "smooth cubic to" SVG path command (missing from * SVGConstants). */ public static final String PATH_SMOOTH_CUBIC_TO = "S"; /** * The lower case version (relative) line to command. */ public static final String PATH_LINE_TO_RELATIVE = SVGConstants.PATH_LINE_TO.toLowerCase(); /** * The lower case version (relative) move command. */ public static final String PATH_MOVE_RELATIVE = SVGConstants.PATH_MOVE.toLowerCase(); /** * The lower case version (relative) horizontal line to command. */ public static final String PATH_HORIZONTAL_LINE_TO_RELATIVE = SVGConstants.PATH_HORIZONTAL_LINE_TO.toLowerCase(); /** * The lower case version (relative) vertical line to command. */ public static final String PATH_VERTICAL_LINE_TO_RELATIVE = SVGConstants.PATH_VERTICAL_LINE_TO.toLowerCase(); /** * The lower case version (relative) cubic line to command. */ public static final String PATH_CUBIC_TO_RELATIVE = SVGConstants.PATH_CUBIC_TO.toLowerCase(); /** * The lower case version (relative) smooth cubic to command. */ public static final String PATH_SMOOTH_CUBIC_TO_RELATIVE = PATH_SMOOTH_CUBIC_TO.toLowerCase(); /** * The lower case version (relative) quadratic interpolation to command. */ public static final String PATH_QUAD_TO_RELATIVE = SVGConstants.PATH_QUAD_TO.toLowerCase(); /** * The lower case version (relative) smooth quadratic interpolation to * command. */ public static final String PATH_SMOOTH_QUAD_TO_RELATIVE = SVGConstants.PATH_SMOOTH_QUAD_TO.toLowerCase(); /** * The lower case version (relative) path arc command. */ public static final String PATH_ARC_RELATIVE = SVGConstants.PATH_ARC.toLowerCase(); /** * Empty path constructor. */ public SVGPath() { // Nothing to do. } /** * Constructor with initial point. * * @param x initial coordinates * @param y initial coordinates */ public SVGPath(double x, double y) { this(); this.moveTo(x, y); } /** * Constructor with initial point. * * @param xy initial coordinates */ public SVGPath(double[] xy) { this(); this.moveTo(xy); } /** * Constructor from a vector collection (e.g. a polygon) * * @param vectors vectors */ public SVGPath(Iterable vectors) { this(); for(Vector vec : vectors) { this.drawTo(vec.get(0), vec.get(1)); } this.close(); } /** * Draw a line given a series of coordinates. * * Helper function that will use "move" for the first point, "lineto" for the * remaining. * * @param x new coordinates * @param y new coordinates * @return path object, for compact syntax. */ public SVGPath drawTo(double x, double y) { if(!isStarted()) { moveTo(x, y); } else { lineTo(x, y); } return this; } /** * Draw a line given a series of coordinates. * * Helper function that will use "move" for the first point, "lineto" for the * remaining. * * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath drawTo(double[] xy) { if(!isStarted()) { moveTo(xy); } else { lineTo(xy); } return this; } /** * Draw a line given a series of coordinates. * * Helper function that will use "move" for the first point, "lineto" for the * remaining. * * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath drawTo(Vector xy) { if(!isStarted()) { moveTo(xy); } else { lineTo(xy); } return this; } /** * Test whether the path drawing has already started. * * @return Path freshness */ public boolean isStarted() { return lastaction != null; } /** * Draw a line to the given coordinates. * * @param x new coordinates * @param y new coordinates * @return path object, for compact syntax. */ public SVGPath lineTo(double x, double y) { append(SVGConstants.PATH_LINE_TO, x, y); return this; } /** * Draw a line to the given coordinates. * * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath lineTo(double[] xy) { append(SVGConstants.PATH_LINE_TO, xy[0], xy[1]); return this; } /** * Draw a line to the given coordinates. * * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath lineTo(Vector xy) { append(SVGConstants.PATH_LINE_TO, xy.get(0), xy.get(1)); return this; } /** * Draw a line to the given relative coordinates. * * @param x relative coordinates * @param y relative coordinates * @return path object, for compact syntax. */ public SVGPath relativeLineTo(double x, double y) { append(PATH_LINE_TO_RELATIVE, x, y); return this; } /** * Draw a line to the given relative coordinates. * * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath relativeLineTo(double[] xy) { append(PATH_LINE_TO_RELATIVE, xy[0], xy[1]); return this; } /** * Draw a line to the given relative coordinates. * * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath relativeLineTo(Vector xy) { append(PATH_LINE_TO_RELATIVE, xy.get(0), xy.get(1)); return this; } /** * Draw a horizontal line to the given x coordinate. * * @param x new coordinates * @return path object, for compact syntax. */ public SVGPath horizontalLineTo(double x) { append(SVGConstants.PATH_HORIZONTAL_LINE_TO, x); return this; } /** * Draw a horizontal line to the given relative x coordinate. * * @param x new coordinates * @return path object, for compact syntax. */ public SVGPath relativeHorizontalLineTo(double x) { append(PATH_HORIZONTAL_LINE_TO_RELATIVE, x); return this; } /** * Draw a vertical line to the given y coordinate. * * @param y new coordinate * @return path object, for compact syntax. */ public SVGPath verticalLineTo(double y) { append(SVGConstants.PATH_VERTICAL_LINE_TO, y); return this; } /** * Draw a vertical line to the given relative y coordinate. * * @param y new coordinate * @return path object, for compact syntax. */ public SVGPath relativeVerticalLineTo(double y) { append(PATH_VERTICAL_LINE_TO_RELATIVE, y); return this; } /** * Move to the given coordinates. * * @param x new coordinates * @param y new coordinates * @return path object, for compact syntax. */ public SVGPath moveTo(double x, double y) { append(SVGConstants.PATH_MOVE, x, y); return this; } /** * Move to the given coordinates. * * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath moveTo(double[] xy) { append(SVGConstants.PATH_MOVE, xy[0], xy[1]); return this; } /** * Move to the given coordinates. * * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath moveTo(Vector xy) { append(SVGConstants.PATH_MOVE, xy.get(0), xy.get(1)); return this; } /** * Move to the given relative coordinates. * * @param x new coordinates * @param y new coordinates * @return path object, for compact syntax. */ public SVGPath relativeMoveTo(double x, double y) { append(PATH_MOVE_RELATIVE, x, y); return this; } /** * Move to the given relative coordinates. * * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath relativeMoveTo(double[] xy) { append(PATH_MOVE_RELATIVE, xy[0], xy[1]); return this; } /** * Move to the given relative coordinates. * * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath relativeMoveTo(Vector xy) { append(PATH_MOVE_RELATIVE, xy.get(0), xy.get(1)); return this; } /** * Cubic Bezier line to the given coordinates. * * @param c1x first control point x * @param c1y first control point y * @param c2x second control point x * @param c2y second control point y * @param x new coordinates * @param y new coordinates * @return path object, for compact syntax. */ public SVGPath cubicTo(double c1x, double c1y, double c2x, double c2y, double x, double y) { append(SVGConstants.PATH_CUBIC_TO, c1x, c1y, c2x, c2y, x, y); return this; } /** * Cubic Bezier line to the given coordinates. * * @param c1xy first control point * @param c2xy second control point * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath cubicTo(double[] c1xy, double[] c2xy, double[] xy) { append(SVGConstants.PATH_CUBIC_TO, c1xy[0], c1xy[1], c2xy[0], c2xy[1], xy[0], xy[1]); return this; } /** * Cubic Bezier line to the given coordinates. * * @param c1xy first control point * @param c2xy second control point * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath cubicTo(Vector c1xy, Vector c2xy, Vector xy) { append(SVGConstants.PATH_CUBIC_TO, c1xy.get(0), c1xy.get(1), c2xy.get(0), c2xy.get(1), xy.get(0), xy.get(1)); return this; } /** * Cubic Bezier line to the given relative coordinates. * * @param c1x first control point x * @param c1y first control point y * @param c2x second control point x * @param c2y second control point y * @param x new coordinates * @param y new coordinates * @return path object, for compact syntax. */ public SVGPath relativeCubicTo(double c1x, double c1y, double c2x, double c2y, double x, double y) { append(PATH_CUBIC_TO_RELATIVE, c1x, c1y, c2x, c2y, x, y); return this; } /** * Cubic Bezier line to the given relative coordinates. * * @param c1xy first control point * @param c2xy second control point * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath relativeCubicTo(double[] c1xy, double[] c2xy, double[] xy) { append(PATH_CUBIC_TO_RELATIVE, c1xy[0], c1xy[1], c2xy[0], c2xy[1], xy[0], xy[1]); return this; } /** * Cubic Bezier line to the given relative coordinates. * * @param c1xy first control point * @param c2xy second control point * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath relativeCubicTo(Vector c1xy, Vector c2xy, Vector xy) { append(PATH_CUBIC_TO_RELATIVE, c1xy.get(0), c1xy.get(1), c2xy.get(0), c2xy.get(1), xy.get(0), xy.get(1)); return this; } /** * Smooth Cubic Bezier line to the given coordinates. * * @param c2x second control point x * @param c2y second control point y * @param x new coordinates * @param y new coordinates * @return path object, for compact syntax. */ public SVGPath smoothCubicTo(double c2x, double c2y, double x, double y) { append(PATH_SMOOTH_CUBIC_TO, c2x, c2y, x, y); return this; } /** * Smooth Cubic Bezier line to the given coordinates. * * @param c2xy second control point * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath smoothCubicTo(double[] c2xy, double[] xy) { append(PATH_SMOOTH_CUBIC_TO, c2xy[0], c2xy[1], xy[0], xy[1]); return this; } /** * Smooth Cubic Bezier line to the given coordinates. * * @param c2xy second control point * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath smoothCubicTo(Vector c2xy, Vector xy) { append(PATH_SMOOTH_CUBIC_TO, c2xy.get(0), c2xy.get(1), xy.get(0), xy.get(1)); return this; } /** * Smooth Cubic Bezier line to the given relative coordinates. * * @param c2x second control point x * @param c2y second control point y * @param x new coordinates * @param y new coordinates * @return path object, for compact syntax. */ public SVGPath relativeSmoothCubicTo(double c2x, double c2y, double x, double y) { append(PATH_SMOOTH_CUBIC_TO_RELATIVE, c2x, c2y, x, y); return this; } /** * Smooth Cubic Bezier line to the given relative coordinates. * * @param c2xy second control point * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath relativeSmoothCubicTo(double[] c2xy, double[] xy) { append(PATH_SMOOTH_CUBIC_TO_RELATIVE, c2xy[0], c2xy[1], xy[0], xy[1]); return this; } /** * Smooth Cubic Bezier line to the given relative coordinates. * * @param c2xy second control point * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath relativeSmoothCubicTo(Vector c2xy, Vector xy) { append(PATH_SMOOTH_CUBIC_TO_RELATIVE, c2xy.get(0), c2xy.get(1), xy.get(0), xy.get(1)); return this; } /** * Quadratic Bezier line to the given coordinates. * * @param c1x first control point x * @param c1y first control point y * @param x new coordinates * @param y new coordinates * @return path object, for compact syntax. */ public SVGPath quadTo(double c1x, double c1y, double x, double y) { append(SVGConstants.PATH_QUAD_TO, c1x, c1y, x, y); return this; } /** * Quadratic Bezier line to the given coordinates. * * @param c1xy first control point * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath quadTo(double[] c1xy, double[] xy) { append(SVGConstants.PATH_QUAD_TO, c1xy[0], c1xy[1], xy[0], xy[1]); return this; } /** * Quadratic Bezier line to the given coordinates. * * @param c1xy first control point * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath quadTo(Vector c1xy, Vector xy) { append(SVGConstants.PATH_QUAD_TO, c1xy.get(0), c1xy.get(1), xy.get(0), xy.get(1)); return this; } /** * Quadratic Bezier line to the given relative coordinates. * * @param c1x first control point x * @param c1y first control point y * @param x new coordinates * @param y new coordinates * @return path object, for compact syntax. */ public SVGPath relativeQuadTo(double c1x, double c1y, double x, double y) { append(PATH_QUAD_TO_RELATIVE, c1x, c1y, x, y); return this; } /** * Quadratic Bezier line to the given relative coordinates. * * @param c1xy first control point * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath relativeQuadTo(double[] c1xy, double[] xy) { append(PATH_QUAD_TO_RELATIVE, c1xy[0], c1xy[1], xy[0], xy[1]); return this; } /** * Quadratic Bezier line to the given relative coordinates. * * @param c1xy first control point * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath relativeQuadTo(Vector c1xy, Vector xy) { append(PATH_QUAD_TO_RELATIVE, c1xy.get(0), c1xy.get(1), xy.get(0), xy.get(1)); return this; } /** * Smooth quadratic Bezier line to the given coordinates. * * @param x new coordinates * @param y new coordinates * @return path object, for compact syntax. */ public SVGPath smoothQuadTo(double x, double y) { append(SVGConstants.PATH_SMOOTH_QUAD_TO, x, y); return this; } /** * Smooth quadratic Bezier line to the given coordinates. * * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath smoothQuadTo(double[] xy) { append(SVGConstants.PATH_SMOOTH_QUAD_TO, xy[0], xy[1]); return this; } /** * Smooth quadratic Bezier line to the given coordinates. * * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath smoothQuadTo(Vector xy) { append(SVGConstants.PATH_SMOOTH_QUAD_TO, xy.get(0), xy.get(1)); return this; } /** * Smooth quadratic Bezier line to the given relative coordinates. * * @param x new coordinates * @param y new coordinates * @return path object, for compact syntax. */ public SVGPath relativeSmoothQuadTo(double x, double y) { append(PATH_SMOOTH_QUAD_TO_RELATIVE, x, y); return this; } /** * Smooth quadratic Bezier line to the given relative coordinates. * * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath relativeSmoothQuadTo(double[] xy) { append(PATH_SMOOTH_QUAD_TO_RELATIVE, xy[0], xy[1]); return this; } /** * Smooth quadratic Bezier line to the given relative coordinates. * * @param xy new coordinates * @return path object, for compact syntax. */ public SVGPath relativeSmoothQuadTo(Vector xy) { append(PATH_SMOOTH_QUAD_TO_RELATIVE, xy.get(0), xy.get(1)); return this; } /** * Elliptical arc curve to the given coordinates. * * @param rx x radius * @param ry y radius * @param ar x-axis-rotation * @param la large arc flag, if angle >= 180 deg * @param sp sweep flag, if arc will be drawn in positive-angle direction * @param x new coordinates * @param y new coordinates */ public SVGPath ellipticalArc(double rx, double ry, double ar, double la, double sp, double x, double y) { append(SVGConstants.PATH_ARC, rx, ry, ar, la, sp, x, y); return this; } /** * Elliptical arc curve to the given coordinates. * * @param rx x radius * @param ry y radius * @param ar x-axis-rotation * @param la large arc flag, if angle >= 180 deg * @param sp sweep flag, if arc will be drawn in positive-angle direction * @param xy new coordinates */ public SVGPath ellipticalArc(double rx, double ry, double ar, double la, double sp, double[] xy) { append(SVGConstants.PATH_ARC, rx, ry, ar, la, sp, xy[0], xy[1]); return this; } /** * Elliptical arc curve to the given coordinates. * * @param rxy radius * @param ar x-axis-rotation * @param la large arc flag, if angle >= 180 deg * @param sp sweep flag, if arc will be drawn in positive-angle direction * @param xy new coordinates */ public SVGPath ellipticalArc(Vector rxy, double ar, double la, double sp, Vector xy) { append(SVGConstants.PATH_ARC, rxy.get(0), rxy.get(1), ar, la, sp, xy.get(0), xy.get(1)); return this; } /** * Elliptical arc curve to the given relative coordinates. * * @param rx x radius * @param ry y radius * @param ar x-axis-rotation * @param la large arc flag, if angle >= 180 deg * @param sp sweep flag, if arc will be drawn in positive-angle direction * @param x new coordinates * @param y new coordinates */ public SVGPath relativeEllipticalArc(double rx, double ry, double ar, double la, double sp, double x, double y) { append(PATH_ARC_RELATIVE, rx, ry, ar, la, sp, x, y); return this; } /** * Elliptical arc curve to the given relative coordinates. * * @param rx x radius * @param ry y radius * @param ar x-axis-rotation * @param la large arc flag, if angle >= 180 deg * @param sp sweep flag, if arc will be drawn in positive-angle direction * @param xy new coordinates */ public SVGPath relativeEllipticalArc(double rx, double ry, double ar, double la, double sp, double[] xy) { append(PATH_ARC_RELATIVE, rx, ry, ar, la, sp, xy[0], xy[1]); return this; } /** * Elliptical arc curve to the given relative coordinates. * * @param rxy radius * @param ar x-axis-rotation * @param la large arc flag, if angle >= 180 deg * @param sp sweep flag, if arc will be drawn in positive-angle direction * @param xy new coordinates */ public SVGPath relativeEllipticalArc(Vector rxy, double ar, double la, double sp, Vector xy) { append(PATH_ARC_RELATIVE, rxy.get(0), rxy.get(1), ar, la, sp, xy.get(0), xy.get(1)); return this; } /** * Append an action to the current path. * * @param action Current action * @param ds coordinates. */ private void append(String action, double... ds) { if(lastaction != action) { buf.append(action); lastaction = action; } for(double d : ds) { buf.append(SVGUtil.FMT.format(d)); buf.append(' '); } } /** * Close the path. * * @return path object, for compact syntax. */ public SVGPath close() { if(lastaction != SVGConstants.PATH_CLOSE) { buf.append(SVGConstants.PATH_CLOSE); lastaction = SVGConstants.PATH_CLOSE; } return this; } /** * Turn the path buffer into an SVG element. * * @param document Document context (= element factory) * @return SVG Element */ public Element makeElement(Document document) { Element elem = SVGUtil.svgElement(document, SVGConstants.SVG_PATH_TAG); elem.setAttribute(SVGConstants.SVG_D_ATTRIBUTE, buf.toString()); return elem; } /** * Turn the path buffer into an SVG element. * * @param plot Plot context (= element factory) * @return SVG Element */ public Element makeElement(SVGPlot plot) { Element elem = plot.svgElement(SVGConstants.SVG_PATH_TAG); elem.setAttribute(SVGConstants.SVG_D_ATTRIBUTE, buf.toString()); return elem; } /** * Return the SVG serialization of the path. */ @Override public String toString() { return buf.toString(); } }