summaryrefslogtreecommitdiff
path: root/src/de/lmu/ifi/dbs/elki/visualization/svg
diff options
context:
space:
mode:
Diffstat (limited to 'src/de/lmu/ifi/dbs/elki/visualization/svg')
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/svg/SVGButton.java165
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/svg/SVGCheckbox.java187
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/svg/SVGCloneVisible.java60
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/svg/SVGEffects.java147
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/svg/SVGHyperCube.java22
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/svg/SVGHyperSphere.java137
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPath.java211
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPlot.java179
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/svg/SVGScoreBar.java118
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/svg/SVGSimpleLinearAxis.java148
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/svg/SVGUtil.java92
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/svg/UpdateRunner.java42
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/svg/UpdateSynchronizer.java2
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/svg/VoronoiDraw.java152
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/svg/package-info.java2
15 files changed, 1423 insertions, 241 deletions
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGButton.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGButton.java
new file mode 100644
index 00000000..9d71c3d6
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGButton.java
@@ -0,0 +1,165 @@
+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 <http://www.gnu.org/licenses/>.
+ */
+import org.apache.batik.util.SVGConstants;
+import org.w3c.dom.Element;
+
+import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
+
+/**
+ * Class to draw a button as SVG.
+ *
+ * @author Erich Schubert
+ */
+public class SVGButton {
+ /**
+ * Default button color
+ */
+ public static final String DEFAULT_BUTTON_COLOR = SVGConstants.CSS_LIGHTGRAY_VALUE;
+
+ /**
+ * Default text color
+ */
+ public static final String DEFAULT_TEXT_COLOR = SVGConstants.CSS_BLACK_VALUE;
+
+ /**
+ * X position
+ */
+ private double x;
+
+ /**
+ * Y position
+ */
+ private double y;
+
+ /**
+ * Width
+ */
+ private double w;
+
+ /**
+ * Height
+ */
+ private double h;
+
+ /**
+ * Corner rounding factor. NaN = no rounding
+ */
+ private double r = Double.NaN;
+
+ /**
+ * Class for the buttons main CSS
+ */
+ private CSSClass butcss;
+
+ /**
+ * Button title, optional
+ */
+ private String title = null;
+
+ /**
+ * Title styling
+ */
+ private CSSClass titlecss = null;
+
+ /**
+ * Constructor.
+ *
+ * @param x Position X
+ * @param y Position Y
+ * @param w Width
+ * @param h Height
+ * @param r Rounded radius
+ */
+ public SVGButton(double x, double y, double w, double h, double r) {
+ super();
+ this.x = x;
+ this.y = y;
+ this.w = w;
+ this.h = h;
+ this.r = r;
+ this.butcss = new CSSClass(this, "button");
+ butcss.setStatement(SVGConstants.CSS_FILL_PROPERTY, DEFAULT_BUTTON_COLOR);
+ butcss.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_BLACK_VALUE);
+ butcss.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, ".01");
+ }
+
+ /**
+ * Produce the actual SVG elements for the button.
+ *
+ * @param svgp Plot to draw to
+ * @return Button wrapper element
+ */
+ public Element render(SVGPlot svgp) {
+ Element tag = svgp.svgElement(SVGConstants.SVG_G_TAG);
+ Element button = svgp.svgRect(x, y, w, h);
+ if(!Double.isNaN(r)) {
+ SVGUtil.setAtt(button, SVGConstants.SVG_RX_ATTRIBUTE, r);
+ SVGUtil.setAtt(button, SVGConstants.SVG_RY_ATTRIBUTE, r);
+ }
+ SVGUtil.setAtt(button, SVGConstants.SVG_STYLE_ATTRIBUTE, butcss.inlineCSS());
+ tag.appendChild(button);
+ // Add light effect:
+ if (svgp.getIdElement(SVGEffects.LIGHT_GRADIENT_ID) != null) {
+ Element light = svgp.svgRect(x, y, w, h);
+ if(!Double.isNaN(r)) {
+ SVGUtil.setAtt(light, SVGConstants.SVG_RX_ATTRIBUTE, r);
+ SVGUtil.setAtt(light, SVGConstants.SVG_RY_ATTRIBUTE, r);
+ }
+ SVGUtil.setAtt(light, SVGConstants.SVG_STYLE_ATTRIBUTE, "fill:url(#"+SVGEffects.LIGHT_GRADIENT_ID+");fill-opacity:.5");
+ tag.appendChild(light);
+ }
+
+ // Add shadow effect:
+ if(svgp.getIdElement(SVGEffects.SHADOW_ID) != null) {
+ //Element shadow = svgp.svgRect(x + (w * .05), y + (h * .05), w, h);
+ //SVGUtil.setAtt(button, SVGConstants.SVG_STYLE_ATTRIBUTE, SVGConstants.CSS_FILL_PROPERTY + ":" + SVGConstants.CSS_BLACK_VALUE);
+ button.setAttribute(SVGConstants.SVG_FILTER_ATTRIBUTE, "url(#" + SVGEffects.SHADOW_ID + ")");
+ //tag.appendChild(shadow);
+ }
+
+ if(title != null) {
+ Element label = svgp.svgText(x + w * .5, y + h * .7, title);
+ label.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, titlecss.inlineCSS());
+ tag.appendChild(label);
+ }
+ return tag;
+ }
+
+ /**
+ * Set the button title
+ *
+ * @param title Button title
+ * @param textcolor Color
+ */
+ public void setTitle(String title, String textcolor) {
+ this.title = title;
+ if(titlecss == null) {
+ titlecss = new CSSClass(this, "text");
+ titlecss.setStatement(SVGConstants.CSS_TEXT_ANCHOR_PROPERTY, SVGConstants.CSS_MIDDLE_VALUE);
+ titlecss.setStatement(SVGConstants.CSS_FILL_PROPERTY, textcolor);
+ titlecss.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, .6 * h);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGCheckbox.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGCheckbox.java
new file mode 100644
index 00000000..b6cf1165
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGCheckbox.java
@@ -0,0 +1,187 @@
+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 <http://www.gnu.org/licenses/>.
+ */
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.EventListenerList;
+
+import org.apache.batik.util.SVGConstants;
+import org.w3c.dom.Element;
+import org.w3c.dom.events.Event;
+import org.w3c.dom.events.EventListener;
+import org.w3c.dom.events.EventTarget;
+
+/**
+ * SVG checkbox.
+ *
+ * @author Sascha Goldhofer
+ */
+public class SVGCheckbox {
+ /**
+ * Status flag
+ */
+ protected boolean checked;
+
+ /**
+ * Event listeners
+ */
+ protected EventListenerList listenerList = new EventListenerList();
+
+ /**
+ * Checkbox label
+ */
+ protected String label = null;
+
+ /**
+ * Constructor, without label
+ *
+ * @param checked Checked status
+ */
+ public SVGCheckbox(boolean checked) {
+ this(checked, null);
+ }
+
+ /**
+ * Constructor, with label
+ *
+ * @param checked Checked status
+ * @param label Label
+ */
+ public SVGCheckbox(boolean checked, String label) {
+ this.checked = checked;
+ this.label = label;
+ }
+
+ /**
+ * Render the SVG checkbox to a plot
+ *
+ * @param svgp Plot to draw to
+ * @param x X offset
+ * @param y Y offset
+ * @param size Size factor
+ * @return Container element
+ */
+ public Element renderCheckBox(SVGPlot svgp, double x, double y, double size) {
+ // create check
+ final Element checkmark = SVGEffects.makeCheckmark(svgp);
+ checkmark.setAttribute(SVGConstants.SVG_TRANSFORM_ATTRIBUTE, "scale(" + (size / 11) + ") translate(" + x + " " + y + ")");
+ if(!checked) {
+ checkmark.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, SVGConstants.CSS_DISPLAY_PROPERTY + ":" + SVGConstants.CSS_NONE_VALUE);
+ }
+
+ // create box
+ Element checkbox_box = SVGUtil.svgRect(svgp.getDocument(), x, y, size, size);
+ checkbox_box.setAttribute(SVGConstants.SVG_FILL_ATTRIBUTE, "#d4e4f1");
+ checkbox_box.setAttribute(SVGConstants.SVG_STROKE_ATTRIBUTE, "#a0a0a0");
+ checkbox_box.setAttribute(SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE, "0.5");
+
+ // create checkbox
+ final Element checkbox = svgp.svgElement(SVGConstants.SVG_G_TAG);
+ checkbox.appendChild(checkbox_box);
+ checkbox.appendChild(checkmark);
+
+ // create Label
+ if(label != null) {
+ Element labele = svgp.svgText(x + 2 * size, y + size, label);
+ // TODO: font size!
+ checkbox.appendChild(labele);
+ }
+
+ // add click event listener
+ EventTarget targ = (EventTarget) checkbox;
+ targ.addEventListener(SVGConstants.SVG_CLICK_EVENT_TYPE, new EventListener() {
+ /**
+ * Set the checkmark, and fire the event.
+ */
+ protected void check() {
+ checkmark.removeAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE);
+ checked = true;
+ fireSwitchEvent(new ChangeEvent(SVGCheckbox.this));
+ }
+
+ /**
+ * Remove the checkmark and fire the event.
+ */
+ protected void uncheck() {
+ checkmark.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, SVGConstants.CSS_DISPLAY_PROPERTY + ":" + SVGConstants.CSS_NONE_VALUE);
+ checked = false;
+ fireSwitchEvent(new ChangeEvent(SVGCheckbox.this));
+ }
+
+ @Override
+ public void handleEvent(Event evt) {
+ if(checked) {
+ uncheck();
+ }
+ else {
+ check();
+ }
+ }
+ }, false);
+
+ return checkbox;
+ }
+
+ /**
+ * Register a listener for this checkbox.
+ *
+ * @param listener Listener to add
+ */
+ public void addCheckBoxListener(ChangeListener listener) {
+ listenerList.add(ChangeListener.class, listener);
+ }
+
+ /**
+ * Remove a listener for this checkbox.
+ *
+ * @param listener Listener to remove
+ */
+ public void removeCheckBoxListener(ChangeListener listener) {
+ listenerList.remove(ChangeListener.class, listener);
+ }
+
+ /**
+ * Return the checkmark status.
+ *
+ * @return true, when checked
+ */
+ public boolean isChecked() {
+ return checked;
+ }
+
+ /**
+ * Fire the event to listeners
+ *
+ * @param evt Event to fire
+ */
+ protected void fireSwitchEvent(ChangeEvent evt) {
+ Object[] listeners = listenerList.getListenerList();
+ for(int i = 0; i < listeners.length; i += 2) {
+ if(listeners[i] == ChangeListener.class) {
+ ((ChangeListener) listeners[i + 1]).stateChanged(evt);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGCloneVisible.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGCloneVisible.java
new file mode 100644
index 00000000..749720ee
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGCloneVisible.java
@@ -0,0 +1,60 @@
+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 <http://www.gnu.org/licenses/>.
+ */
+
+import org.apache.batik.util.SVGConstants;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import de.lmu.ifi.dbs.elki.utilities.xml.DOMCloner;
+
+/**
+ * Clone visible parts of an SVG document.
+ *
+ * @author Erich Schubert
+ */
+public class SVGCloneVisible extends DOMCloner {
+ @Override
+ public Node cloneNode(Document doc, Node eold) {
+ // Skip elements with visibility=hidden
+ if(eold instanceof Element) {
+ Element eeold = (Element) eold;
+ String vis = eeold.getAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY);
+ if(SVGConstants.CSS_HIDDEN_VALUE.equals(vis)) {
+ return null;
+ }
+ }
+ // Perform clone flat
+ Node enew = doc.importNode(eold, false);
+ // Recurse:
+ for(Node n = eold.getFirstChild(); n != null; n = n.getNextSibling()) {
+ final Node clone = cloneNode(doc, n);
+ if (clone != null) {
+ enew.appendChild(clone);
+ }
+ }
+ return enew;
+ }
+}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGEffects.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGEffects.java
new file mode 100644
index 00000000..79565902
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGEffects.java
@@ -0,0 +1,147 @@
+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 <http://www.gnu.org/licenses/>.
+ */
+import org.apache.batik.util.SVGConstants;
+import org.w3c.dom.Element;
+
+/**
+ * Class containing some popular SVG effects.
+ *
+ * @author Erich Schubert
+ */
+public class SVGEffects {
+ /**
+ * ID for the drop shadow effect
+ */
+ public static final String SHADOW_ID = "shadow-effect";
+
+ /**
+ * ID for the light gradient fill
+ */
+ public static final String LIGHT_GRADIENT_ID = "light-gradient";
+
+ /**
+ * Static method to prepare a SVG document for drop shadow effects.
+ *
+ * Invoke this from an appropriate update thread!
+ *
+ * @param svgp Plot to prepare
+ */
+ public static void addShadowFilter(SVGPlot svgp) {
+ Element shadow = svgp.getIdElement(SHADOW_ID);
+ if(shadow == null) {
+ shadow = svgp.svgElement(SVGConstants.SVG_FILTER_TAG);
+ shadow.setAttribute(SVGConstants.SVG_ID_ATTRIBUTE, SHADOW_ID);
+ shadow.setAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE, "140%");
+ shadow.setAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE, "140%");
+
+ Element offset = svgp.svgElement(SVGConstants.SVG_FE_OFFSET_TAG);
+ offset.setAttribute(SVGConstants.SVG_IN_ATTRIBUTE, SVGConstants.SVG_SOURCE_ALPHA_VALUE);
+ offset.setAttribute(SVGConstants.SVG_RESULT_ATTRIBUTE, "off");
+ offset.setAttribute(SVGConstants.SVG_DX_ATTRIBUTE, "0.1");
+ offset.setAttribute(SVGConstants.SVG_DY_ATTRIBUTE, "0.1");
+ shadow.appendChild(offset);
+
+ Element gauss = svgp.svgElement(SVGConstants.SVG_FE_GAUSSIAN_BLUR_TAG);
+ gauss.setAttribute(SVGConstants.SVG_IN_ATTRIBUTE, "off");
+ gauss.setAttribute(SVGConstants.SVG_RESULT_ATTRIBUTE, "blur");
+ gauss.setAttribute(SVGConstants.SVG_STD_DEVIATION_ATTRIBUTE, "0.1");
+ shadow.appendChild(gauss);
+
+ Element blend = svgp.svgElement(SVGConstants.SVG_FE_BLEND_TAG);
+ blend.setAttribute(SVGConstants.SVG_IN_ATTRIBUTE, SVGConstants.SVG_SOURCE_GRAPHIC_VALUE);
+ blend.setAttribute(SVGConstants.SVG_IN2_ATTRIBUTE, "blur");
+ blend.setAttribute(SVGConstants.SVG_MODE_ATTRIBUTE, SVGConstants.SVG_NORMAL_VALUE);
+ shadow.appendChild(blend);
+
+ svgp.getDefs().appendChild(shadow);
+ svgp.putIdElement(SHADOW_ID, shadow);
+ }
+ }
+
+ /**
+ * Static method to prepare a SVG document for light gradient effects.
+ *
+ * Invoke this from an appropriate update thread!
+ *
+ * @param svgp Plot to prepare
+ */
+ public static void addLightGradient(SVGPlot svgp) {
+ Element gradient = svgp.getIdElement(LIGHT_GRADIENT_ID);
+ if(gradient == null) {
+ gradient = svgp.svgElement(SVGConstants.SVG_LINEAR_GRADIENT_TAG);
+ gradient.setAttribute(SVGConstants.SVG_ID_ATTRIBUTE, LIGHT_GRADIENT_ID);
+ gradient.setAttribute(SVGConstants.SVG_X1_ATTRIBUTE, "0");
+ gradient.setAttribute(SVGConstants.SVG_Y1_ATTRIBUTE, "0");
+ gradient.setAttribute(SVGConstants.SVG_X2_ATTRIBUTE, "0");
+ gradient.setAttribute(SVGConstants.SVG_Y2_ATTRIBUTE, "1");
+
+ Element stop0 = svgp.svgElement(SVGConstants.SVG_STOP_TAG);
+ stop0.setAttribute(SVGConstants.SVG_STOP_COLOR_ATTRIBUTE, "white");
+ stop0.setAttribute(SVGConstants.SVG_STOP_OPACITY_ATTRIBUTE, "1");
+ stop0.setAttribute(SVGConstants.SVG_OFFSET_ATTRIBUTE, "0");
+ gradient.appendChild(stop0);
+
+ Element stop04 = svgp.svgElement(SVGConstants.SVG_STOP_TAG);
+ stop04.setAttribute(SVGConstants.SVG_STOP_COLOR_ATTRIBUTE, "white");
+ stop04.setAttribute(SVGConstants.SVG_STOP_OPACITY_ATTRIBUTE, "0");
+ stop04.setAttribute(SVGConstants.SVG_OFFSET_ATTRIBUTE, ".4");
+ gradient.appendChild(stop04);
+
+ Element stop06 = svgp.svgElement(SVGConstants.SVG_STOP_TAG);
+ stop06.setAttribute(SVGConstants.SVG_STOP_COLOR_ATTRIBUTE, "black");
+ stop06.setAttribute(SVGConstants.SVG_STOP_OPACITY_ATTRIBUTE, "0");
+ stop06.setAttribute(SVGConstants.SVG_OFFSET_ATTRIBUTE, ".6");
+ gradient.appendChild(stop06);
+
+ Element stop1 = svgp.svgElement(SVGConstants.SVG_STOP_TAG);
+ stop1.setAttribute(SVGConstants.SVG_STOP_COLOR_ATTRIBUTE, "black");
+ stop1.setAttribute(SVGConstants.SVG_STOP_OPACITY_ATTRIBUTE, ".5");
+ stop1.setAttribute(SVGConstants.SVG_OFFSET_ATTRIBUTE, "1");
+ gradient.appendChild(stop1);
+
+ svgp.getDefs().appendChild(gradient);
+ svgp.putIdElement(LIGHT_GRADIENT_ID, gradient);
+ }
+ }
+
+ /**
+ * Checkmark path, sized approx. 15x15
+ */
+ public static final String SVG_CHECKMARK_PATH = "M0 6.458 L2.047 4.426 5.442 7.721 12.795 0 15 2.117 5.66 11.922 Z";
+
+ /**
+ * Creates a 15x15 big checkmark
+ *
+ * @param svgp Plot to create the element for
+ * @return Element
+ */
+ public static Element makeCheckmark(SVGPlot svgp) {
+ Element checkmark = svgp.svgElement(SVGConstants.SVG_PATH_TAG);
+ checkmark.setAttribute(SVGConstants.SVG_D_ATTRIBUTE, SVG_CHECKMARK_PATH);
+ checkmark.setAttribute(SVGConstants.SVG_FILL_ATTRIBUTE, SVGConstants.CSS_BLACK_VALUE);
+ checkmark.setAttribute(SVGConstants.SVG_STROKE_ATTRIBUTE, SVGConstants.CSS_NONE_VALUE);
+ return checkmark;
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGHyperCube.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGHyperCube.java
index 18bbeb06..723f13df 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGHyperCube.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGHyperCube.java
@@ -4,7 +4,7 @@ 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) 2011
+ Copyright (C) 2012
Ludwig-Maximilians-Universität München
Lehr- und Forschungseinheit für Datenbanksysteme
ELKI Development Team
@@ -31,7 +31,7 @@ import org.apache.batik.util.SVGConstants;
import org.w3c.dom.Element;
import de.lmu.ifi.dbs.elki.data.NumberVector;
-import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector;
+import de.lmu.ifi.dbs.elki.math.linearalgebra.VMath;
import de.lmu.ifi.dbs.elki.visualization.projections.Projection2D;
/**
@@ -52,7 +52,7 @@ public class SVGHyperCube {
* @param max Opposite corner
* @return path element
*/
- public static Element drawFrame(SVGPlot svgp, Projection2D proj, Vector min, Vector max) {
+ public static Element drawFrame(SVGPlot svgp, Projection2D proj, double[] min, double[] max) {
SVGPath path = new SVGPath();
ArrayList<double[]> edges = getVisibleEdges(proj, min, max);
double[] rv_min = proj.fastProjectDataToRenderSpace(min);
@@ -72,7 +72,7 @@ public class SVGHyperCube {
*/
public static <V extends NumberVector<V, ?>> Element drawFrame(SVGPlot svgp, Projection2D proj, V min, V max) {
SVGPath path = new SVGPath();
- ArrayList<double[]> edges = getVisibleEdges(proj, min.getColumnVector(), max.getColumnVector());
+ ArrayList<double[]> edges = getVisibleEdges(proj, min.getColumnVector().getArrayRef(), max.getColumnVector().getArrayRef());
double[] rv_min = proj.fastProjectDataToRenderSpace(min);
recDrawEdges(path, rv_min, edges, 0, new BitSet());
return path.makeElement(svgp);
@@ -88,7 +88,7 @@ public class SVGHyperCube {
* @param max Opposite corner
* @return group element
*/
- public static Element drawFilled(SVGPlot svgp, String cls, Projection2D proj, Vector min, Vector max) {
+ public static Element drawFilled(SVGPlot svgp, String cls, Projection2D proj, double[] min, double[] max) {
Element group = svgp.svgElement(SVGConstants.SVG_G_TAG);
ArrayList<double[]> edges = getVisibleEdges(proj, min, max);
double[] rv_min = proj.fastProjectDataToRenderSpace(min);
@@ -109,7 +109,7 @@ public class SVGHyperCube {
*/
public static <V extends NumberVector<V, ?>> Element drawFilled(SVGPlot svgp, String cls, Projection2D proj, V min, V max) {
Element group = svgp.svgElement(SVGConstants.SVG_G_TAG);
- ArrayList<double[]> edges = getVisibleEdges(proj, min.getColumnVector(), max.getColumnVector());
+ ArrayList<double[]> edges = getVisibleEdges(proj, min.getColumnVector().getArrayRef(), max.getColumnVector().getArrayRef());
double[] rv_min = proj.fastProjectDataToRenderSpace(min);
recDrawSides(svgp, group, cls, rv_min, edges, 0, new BitSet());
return group;
@@ -123,12 +123,12 @@ public class SVGHyperCube {
* @param s_max Maximum value (in scaled space)
* @return Edge list
*/
- private static ArrayList<double[]> getVisibleEdges(Projection2D proj, Vector s_min, Vector s_max) {
- Vector s_deltas = s_max.minus(s_min);
+ private static ArrayList<double[]> getVisibleEdges(Projection2D proj, double[] s_min, double[] s_max) {
+ double[] s_deltas = VMath.minus(s_max, s_min);
ArrayList<double[]> r_edges = new ArrayList<double[]>();
- for(int i = 0; i < s_min.getDimensionality(); i++) {
- Vector delta = new Vector(s_min.getDimensionality());
- delta.set(i, s_deltas.get(i));
+ for(int i = 0; i < s_min.length; i++) {
+ double[] delta = new double[s_min.length];
+ delta[i] = s_deltas[i];
double[] deltas = proj.fastProjectRelativeDataToRenderSpace(delta);
if(deltas[0] != 0 || deltas[1] != 0) {
r_edges.add(deltas);
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGHyperSphere.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGHyperSphere.java
index 169c8698..29b437f9 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGHyperSphere.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGHyperSphere.java
@@ -4,7 +4,7 @@ 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) 2011
+ Copyright (C) 2012
Ludwig-Maximilians-Universität München
Lehr- und Forschungseinheit für Datenbanksysteme
ELKI Development Team
@@ -29,7 +29,6 @@ import org.w3c.dom.Element;
import de.lmu.ifi.dbs.elki.data.NumberVector;
import de.lmu.ifi.dbs.elki.distance.distancevalue.NumberDistance;
-import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector;
import de.lmu.ifi.dbs.elki.visualization.projections.Projection2D;
/**
@@ -51,7 +50,6 @@ public class SVGHyperSphere {
/**
* Wireframe "manhattan" hypersphere
*
- * @param <V> vector type
* @param <D> radius
* @param svgp SVG Plot
* @param proj Visualization projection
@@ -59,26 +57,27 @@ public class SVGHyperSphere {
* @param rad radius
* @return path element
*/
- public static <V extends NumberVector<V, ?>, D extends NumberDistance<?, ?>> Element drawManhattan(SVGPlot svgp, Projection2D proj, V mid, D rad) {
- Vector v_mid = mid.getColumnVector();
- BitSet dims = proj.getVisibleDimensions2D();
+ public static <D extends NumberDistance<?, ?>> Element drawManhattan(SVGPlot svgp, Projection2D proj, NumberVector<?, ?> mid, D rad) {
+ final double radius = rad.doubleValue();
+ final double[] v_mid = mid.getColumnVector().getArrayRef(); // a copy
+ final BitSet dims = proj.getVisibleDimensions2D();
SVGPath path = new SVGPath();
for(int dim = dims.nextSetBit(0); dim >= 0; dim = dims.nextSetBit(dim + 1)) {
- Vector v1 = v_mid.copy();
- v1.set(dim, v1.get(dim) + rad.doubleValue());
- Vector v2 = v_mid.copy();
- v2.set(dim, v2.get(dim) - rad.doubleValue());
- double[] p1 = proj.fastProjectDataToRenderSpace(v1);
- double[] p2 = proj.fastProjectDataToRenderSpace(v2);
+ v_mid[dim] += radius;
+ double[] p1 = proj.fastProjectDataToRenderSpace(v_mid);
+ v_mid[dim] -= radius;
+ v_mid[dim] -= radius;
+ double[] p2 = proj.fastProjectDataToRenderSpace(v_mid);
+ v_mid[dim] += radius;
for(int dim2 = dims.nextSetBit(0); dim2 >= 0; dim2 = dims.nextSetBit(dim2 + 1)) {
if(dim < dim2) {
- Vector v3 = v_mid.copy();
- v3.set(dim2, v3.get(dim2) + rad.doubleValue());
- Vector v4 = v_mid.copy();
- v4.set(dim2, v4.get(dim2) - rad.doubleValue());
- double[] p3 = proj.fastProjectDataToRenderSpace(v3);
- double[] p4 = proj.fastProjectDataToRenderSpace(v4);
+ v_mid[dim2] += radius;
+ double[] p3 = proj.fastProjectDataToRenderSpace(v_mid);
+ v_mid[dim2] -= radius;
+ v_mid[dim2] -= radius;
+ double[] p4 = proj.fastProjectDataToRenderSpace(v_mid);
+ v_mid[dim2] += radius;
path.moveTo(p1[0], p1[1]);
path.drawTo(p3[0], p3[1]);
@@ -98,7 +97,6 @@ public class SVGHyperSphere {
/**
* Wireframe "euclidean" hypersphere
*
- * @param <V> vector type
* @param <D> radius
* @param svgp SVG Plot
* @param proj Visualization projection
@@ -106,33 +104,34 @@ public class SVGHyperSphere {
* @param rad radius
* @return path element
*/
- public static <V extends NumberVector<V, ?>, D extends NumberDistance<?, ?>> Element drawEuclidean(SVGPlot svgp, Projection2D proj, V mid, D rad) {
- Vector v_mid = mid.getColumnVector();
+ public static <D extends NumberDistance<?, ?>> Element drawEuclidean(SVGPlot svgp, Projection2D proj, NumberVector<?, ?> mid, D rad) {
+ final double radius = rad.doubleValue();
+ double[] v_mid = mid.getColumnVector().getArrayRef(); // a copy
BitSet dims = proj.getVisibleDimensions2D();
SVGPath path = new SVGPath();
for(int dim = dims.nextSetBit(0); dim >= 0; dim = dims.nextSetBit(dim + 1)) {
- Vector v1 = v_mid.copy();
- v1.set(dim, v1.get(dim) + rad.doubleValue());
- Vector v2 = v_mid.copy();
- v2.set(dim, v2.get(dim) - rad.doubleValue());
- double[] p1 = proj.fastProjectDataToRenderSpace(v1);
- double[] p2 = proj.fastProjectDataToRenderSpace(v2);
+ v_mid[dim] += radius;
+ double[] p1 = proj.fastProjectDataToRenderSpace(v_mid);
+ v_mid[dim] -= radius;
+ v_mid[dim] -= radius;
+ double[] p2 = proj.fastProjectDataToRenderSpace(v_mid);
+ v_mid[dim] += radius;
// delta vector
- Vector dt1 = new Vector(v1.getDimensionality());
- dt1.set(dim, rad.doubleValue());
+ double[] dt1 = new double[v_mid.length];
+ dt1[dim] = radius;
double[] d1 = proj.fastProjectRelativeDataToRenderSpace(dt1);
for(int dim2 = dims.nextSetBit(0); dim2 >= 0; dim2 = dims.nextSetBit(dim2 + 1)) {
if(dim < dim2) {
- Vector v3 = v_mid.copy();
- v3.set(dim2, v3.get(dim2) + rad.doubleValue());
- Vector v4 = v_mid.copy();
- v4.set(dim2, v4.get(dim2) - rad.doubleValue());
- double[] p3 = proj.fastProjectDataToRenderSpace(v3);
- double[] p4 = proj.fastProjectDataToRenderSpace(v4);
+ v_mid[dim2] += radius;
+ double[] p3 = proj.fastProjectDataToRenderSpace(v_mid);
+ v_mid[dim2] -= radius;
+ v_mid[dim2] -= radius;
+ double[] p4 = proj.fastProjectDataToRenderSpace(v_mid);
+ v_mid[dim2] += radius;
// delta vector
- Vector dt2 = new Vector(v2.getDimensionality());
- dt2.set(dim2, rad.doubleValue());
+ double[] dt2 = new double[v_mid.length];
+ dt2[dim2] = radius;
double[] d2 = proj.fastProjectRelativeDataToRenderSpace(dt2);
path.moveTo(p1[0], p1[1]);
@@ -150,7 +149,6 @@ public class SVGHyperSphere {
/**
* Wireframe "Lp" hypersphere
*
- * @param <V> vector type
* @param <D> radius
* @param svgp SVG Plot
* @param proj Visualization projection
@@ -159,18 +157,19 @@ public class SVGHyperSphere {
* @param p L_p value
* @return path element
*/
- public static <V extends NumberVector<V, ?>, D extends NumberDistance<?, ?>> Element drawLp(SVGPlot svgp, Projection2D proj, V mid, D rad, double p) {
- Vector v_mid = mid.getColumnVector();
- BitSet dims = proj.getVisibleDimensions2D();
+ public static <D extends NumberDistance<?, ?>> Element drawLp(SVGPlot svgp, Projection2D proj, NumberVector<?, ?> mid, D rad, double p) {
+ final double radius = rad.doubleValue();
+ final double[] v_mid = mid.getColumnVector().getArrayRef();
+ final BitSet dims = proj.getVisibleDimensions2D();
final double kappax, kappay;
if(p > 1.) {
- double kappal = Math.pow(0.5, 1. / p);
+ final double kappal = Math.pow(0.5, 1. / p);
kappax = Math.min(1.3, 4. * (2 * kappal - 1) / 3.);
kappay = 0;
}
else if(p < 1.) {
- double kappal = 1 - Math.pow(0.5, 1. / p);
+ final double kappal = 1 - Math.pow(0.5, 1. / p);
kappax = 0;
kappay = Math.min(1.3, 4. * (2 * kappal - 1) / 3.);
}
@@ -182,27 +181,27 @@ public class SVGHyperSphere {
SVGPath path = new SVGPath();
for(int dim = dims.nextSetBit(0); dim >= 0; dim = dims.nextSetBit(dim + 1)) {
- Vector vp0 = v_mid.copy();
- vp0.set(dim, vp0.get(dim) + rad.doubleValue());
- Vector vm0 = v_mid.copy();
- vm0.set(dim, vm0.get(dim) - rad.doubleValue());
- double[] pvp0 = proj.fastProjectDataToRenderSpace(vp0);
- double[] pvm0 = proj.fastProjectDataToRenderSpace(vm0);
+ v_mid[dim] += radius;
+ double[] pvp0 = proj.fastProjectDataToRenderSpace(v_mid);
+ v_mid[dim] -= radius;
+ v_mid[dim] -= radius;
+ double[] pvm0 = proj.fastProjectDataToRenderSpace(v_mid);
+ v_mid[dim] += radius;
// delta vector
- Vector tvd0 = new Vector(vp0.getDimensionality());
- tvd0.set(dim, rad.doubleValue());
+ double[] tvd0 = new double[v_mid.length];
+ tvd0[dim] = radius;
double[] vd0 = proj.fastProjectRelativeDataToRenderSpace(tvd0);
for(int dim2 = dims.nextSetBit(0); dim2 >= 0; dim2 = dims.nextSetBit(dim2 + 1)) {
if(dim < dim2) {
- Vector v0p = v_mid.copy();
- v0p.set(dim2, v0p.get(dim2) + rad.doubleValue());
- Vector v0m = v_mid.copy();
- v0m.set(dim2, v0m.get(dim2) - rad.doubleValue());
- double[] pv0p = proj.fastProjectDataToRenderSpace(v0p);
- double[] pv0m = proj.fastProjectDataToRenderSpace(v0m);
+ v_mid[dim2] += radius;
+ double[] pv0p = proj.fastProjectDataToRenderSpace(v_mid);
+ v_mid[dim2] -= radius;
+ v_mid[dim2] -= radius;
+ double[] pv0m = proj.fastProjectDataToRenderSpace(v_mid);
+ v_mid[dim2] += radius;
// delta vector
- Vector tv0d = new Vector(vm0.getDimensionality());
- tv0d.set(dim2, rad.doubleValue());
+ double[] tv0d = new double[v_mid.length];
+ tv0d[dim2] = radius;
double[] v0d = proj.fastProjectRelativeDataToRenderSpace(tv0d);
if(p > 1) {
@@ -271,7 +270,6 @@ public class SVGHyperSphere {
/**
* Wireframe "cross" hypersphere
*
- * @param <V> vector type
* @param <D> radius
* @param svgp SVG Plot
* @param proj Visualization projection
@@ -279,19 +277,20 @@ public class SVGHyperSphere {
* @param rad radius
* @return path element
*/
- public static <V extends NumberVector<V, ?>, D extends NumberDistance<?, ?>> Element drawCross(SVGPlot svgp, Projection2D proj, V mid, D rad) {
- Vector v_mid = mid.getColumnVector();
- BitSet dims = proj.getVisibleDimensions2D();
+ public static <D extends NumberDistance<?, ?>> Element drawCross(SVGPlot svgp, Projection2D proj, NumberVector<?, ?> mid, D rad) {
+ final double radius = rad.doubleValue();
+ final double[] v_mid = mid.getColumnVector().getArrayRef();
+ final BitSet dims = proj.getVisibleDimensions2D();
SVGPath path = new SVGPath();
for(int dim = dims.nextSetBit(0); dim >= 0; dim = dims.nextSetBit(dim + 1)) {
- Vector v1 = v_mid.copy();
- v1.set(dim, v1.get(dim) + rad.doubleValue());
- Vector v2 = v_mid.copy();
- v2.set(dim, v2.get(dim) - rad.doubleValue());
- double[] p1 = proj.fastProjectDataToRenderSpace(v1);
- double[] p2 = proj.fastProjectDataToRenderSpace(v2);
+ v_mid[dim] += radius;
+ double[] p1 = proj.fastProjectDataToRenderSpace(v_mid);
+ v_mid[dim] -= radius;
path.moveTo(p1[0], p1[1]);
+ v_mid[dim] -= radius;
+ double[] p2 = proj.fastProjectDataToRenderSpace(v_mid);
+ v_mid[dim] += radius;
path.drawTo(p2[0], p2[1]);
path.close();
}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPath.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPath.java
index 2be331ca..2b86870b 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPath.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPath.java
@@ -4,7 +4,7 @@ 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) 2011
+ Copyright (C) 2012
Ludwig-Maximilians-Universität München
Lehr- und Forschungseinheit für Datenbanksysteme
ELKI Development Team
@@ -118,8 +118,18 @@ public class SVGPath {
}
/**
+ * 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<Vector> vectors) {
@@ -159,6 +169,25 @@ public class SVGPath {
* @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);
@@ -196,6 +225,17 @@ public class SVGPath {
* @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;
@@ -219,6 +259,17 @@ public class SVGPath {
* @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;
@@ -286,6 +337,17 @@ public class SVGPath {
* @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;
@@ -309,6 +371,17 @@ public class SVGPath {
* @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;
@@ -338,6 +411,19 @@ public class SVGPath {
* @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;
@@ -367,6 +453,19 @@ public class SVGPath {
* @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;
@@ -393,6 +492,18 @@ public class SVGPath {
* @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;
@@ -419,6 +530,18 @@ public class SVGPath {
* @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;
@@ -445,6 +568,18 @@ public class SVGPath {
* @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;
@@ -471,6 +606,18 @@ public class SVGPath {
* @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;
@@ -494,6 +641,17 @@ public class SVGPath {
* @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;
@@ -517,6 +675,17 @@ public class SVGPath {
* @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;
@@ -528,7 +697,7 @@ public class SVGPath {
* @param rx x radius
* @param ry y radius
* @param ar x-axis-rotation
- * @param la large arc flag, if angle >= 180°
+ * @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
@@ -541,9 +710,24 @@ public class SVGPath {
/**
* 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°
+ * @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
*/
@@ -558,7 +742,7 @@ public class SVGPath {
* @param rx x radius
* @param ry y radius
* @param ar x-axis-rotation
- * @param la large arc flag, if angle >= 180°
+ * @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
@@ -571,9 +755,24 @@ public class SVGPath {
/**
* 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°
+ * @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
*/
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPlot.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPlot.java
index 14889fb0..81299b10 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPlot.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPlot.java
@@ -4,7 +4,7 @@ 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) 2011
+ Copyright (C) 2012
Ludwig-Maximilians-Universität München
Lehr- und Forschungseinheit für Datenbanksysteme
ELKI Development Team
@@ -23,15 +23,12 @@ package de.lmu.ifi.dbs.elki.visualization.svg;
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-import java.io.ByteArrayOutputStream;
+import java.awt.image.BufferedImage;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
-import java.net.URI;
-import java.net.URISyntaxException;
import java.util.Collection;
import java.util.HashMap;
@@ -44,31 +41,25 @@ import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.batik.dom.svg.SVGDOMImplementation;
-import org.apache.batik.dom.util.DOMUtilities;
-import org.apache.batik.svggen.SVGSyntax;
import org.apache.batik.transcoder.Transcoder;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
+import org.apache.batik.transcoder.XMLAbstractTranscoder;
import org.apache.batik.transcoder.image.JPEGTranscoder;
import org.apache.batik.transcoder.image.PNGTranscoder;
-import org.apache.batik.util.Base64EncoderStream;
import org.apache.batik.util.SVGConstants;
-import org.apache.fop.render.ps.EPSTranscoder;
-import org.apache.fop.render.ps.PSTranscoder;
-import org.apache.fop.svg.PDFTranscoder;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
import org.w3c.dom.events.Event;
import org.w3c.dom.svg.SVGDocument;
import org.w3c.dom.svg.SVGPoint;
import de.lmu.ifi.dbs.elki.logging.LoggingUtil;
import de.lmu.ifi.dbs.elki.utilities.FileUtil;
-import de.lmu.ifi.dbs.elki.utilities.xml.XMLNodeListIterator;
+import de.lmu.ifi.dbs.elki.visualization.batikutil.CloneInlineImages;
+import de.lmu.ifi.dbs.elki.visualization.batikutil.ThumbnailTranscoder;
import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
import de.lmu.ifi.dbs.elki.visualization.css.CSSClassManager;
import de.lmu.ifi.dbs.elki.visualization.css.CSSClassManager.CSSNamingConflict;
@@ -165,12 +156,12 @@ public class SVGPlot {
// create a CSS class manager.
cssman = new CSSClassManager();
}
-
+
/**
* Clean up the plot.
*/
public void dispose() {
- getUpdateRunner().clear();
+ runner.clear();
}
/**
@@ -296,6 +287,7 @@ public class SVGPlot {
/**
* Convenience method to add a CSS class or log an error.
+ *
* @param cls CSS class to add.
*/
public void addCSSClassOrLogError(CSSClass cls) {
@@ -303,7 +295,7 @@ public class SVGPlot {
cssman.addClass(cls);
}
catch(CSSNamingConflict e) {
- de.lmu.ifi.dbs.elki.logging.LoggingUtil.exception(e);
+ //de.lmu.ifi.dbs.elki.logging.LoggingUtil.exception(e);
}
}
@@ -313,8 +305,7 @@ public class SVGPlot {
*/
public void updateStyleElement() {
// TODO: this should be sufficient - why does Batik occasionally not pick up
- // the
- // changes unless we actually replace the style element itself?
+ // the changes unless we actually replace the style element itself?
// cssman.updateStyleElement(document, style);
Element newstyle = cssman.makeStyleElement(document);
style.getParentNode().replaceChild(newstyle, style);
@@ -336,40 +327,7 @@ public class SVGPlot {
OutputStream out = new FileOutputStream(file);
// TODO embed linked images.
javax.xml.transform.Result result = new StreamResult(out);
- // deep clone document
- SVGDocument doc = (SVGDocument) DOMUtilities.deepCloneDocument(getDocument(), getDocument().getImplementation());
- NodeList imgs = doc.getElementsByTagNameNS(SVGConstants.SVG_NAMESPACE_URI, SVGConstants.SVG_IMAGE_TAG);
- final String tmpurl = new File(System.getProperty("java.io.tmpdir") + File.separator).toURI().toString();
- for(Node img : new XMLNodeListIterator(imgs)) {
- if(img instanceof Element) {
- try {
- Element i = (Element) img;
- String href = i.getAttributeNS(SVGConstants.XLINK_NAMESPACE_URI, SVGConstants.XLINK_HREF_ATTRIBUTE);
- if(href.startsWith(tmpurl) && href.endsWith(".png")) {
- // need to convert the image into an inline image.
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- Base64EncoderStream encoder = new Base64EncoderStream(os);
- File in = new File(new URI(href));
- FileInputStream instream = new FileInputStream(in);
- byte[] buf = new byte[4096];
- while(true) {
- int read = instream.read(buf, 0, buf.length);
- if(read <= 0) {
- break;
- }
- encoder.write(buf, 0, read);
- }
- instream.close();
- encoder.close();
- // replace HREF with inlined image data.
- i.setAttributeNS(SVGConstants.XLINK_NAMESPACE_URI, SVGConstants.XLINK_HREF_ATTRIBUTE, SVGSyntax.DATA_PROTOCOL_PNG_PREFIX + os.toString());
- }
- }
- catch(URISyntaxException e) {
- LoggingUtil.warning("Error in embedding PNG image.");
- }
- }
- }
+ SVGDocument doc = cloneDocument();
// Use a transformer for pretty printing
Transformer xformer = TransformerFactory.newInstance().newTransformer();
xformer.setOutputProperty(OutputKeys.INDENT, "yes");
@@ -387,11 +345,9 @@ public class SVGPlot {
* @throws TranscoderException On input/parsing errors
*/
protected void transcode(File file, Transcoder transcoder) throws IOException, TranscoderException {
- // Since the Transcoder is Batik-based, it will replace the rendering tree,
- // which would then break display. Thus we need to deep clone the document
- // first.
- // -- found by Simon.
- SVGDocument doc = (SVGDocument) DOMUtilities.deepCloneDocument(getDocument(), getDocument().getImplementation());
+ // Disable validation, performance is more important here (thumbnails!)
+ transcoder.addTranscodingHint(XMLAbstractTranscoder.KEY_XML_PARSER_VALIDATING, Boolean.FALSE);
+ SVGDocument doc = cloneDocument();
TranscoderInput input = new TranscoderInput(doc);
OutputStream out = new FileOutputStream(file);
TranscoderOutput output = new TranscoderOutput(out);
@@ -401,14 +357,37 @@ public class SVGPlot {
}
/**
+ * Clone the SVGPlot document for transcoding.
+ *
+ * This will usually be necessary for exporting the SVG document if it is
+ * currently being displayed: otherwise, we break the Batik rendering trees.
+ * (Discovered by Simon).
+ *
+ * @return cloned document
+ */
+ protected SVGDocument cloneDocument() {
+ return (SVGDocument) new CloneInlineImages().cloneDocument(SVGDOMImplementation.getDOMImplementation(), document);
+ }
+
+ /**
* Transcode file to PDF.
*
* @param file Output filename
* @throws IOException On write errors
* @throws TranscoderException On input/parsing errors.
+ * @throws ClassNotFoundException PDF transcoder not installed
*/
- public void saveAsPDF(File file) throws IOException, TranscoderException {
- transcode(file, new PDFTranscoder());
+ public void saveAsPDF(File file) throws IOException, TranscoderException, ClassNotFoundException {
+ try {
+ Object t = Class.forName("org.apache.fop.svg.PDFTranscoder").newInstance();
+ transcode(file, (Transcoder) t);
+ }
+ catch(InstantiationException e) {
+ throw new ClassNotFoundException("Could not instantiate PDF transcoder - is Apache FOP installed?", e);
+ }
+ catch(IllegalAccessException e) {
+ throw new ClassNotFoundException("Could not instantiate PDF transcoder - is Apache FOP installed?", e);
+ }
}
/**
@@ -417,9 +396,19 @@ public class SVGPlot {
* @param file Output filename
* @throws IOException On write errors
* @throws TranscoderException On input/parsing errors.
+ * @throws ClassNotFoundException PS transcoder not installed
*/
- public void saveAsPS(File file) throws IOException, TranscoderException {
- transcode(file, new PSTranscoder());
+ public void saveAsPS(File file) throws IOException, TranscoderException, ClassNotFoundException {
+ try {
+ Object t = Class.forName("org.apache.fop.render.ps.PSTranscoder").newInstance();
+ transcode(file, (Transcoder) t);
+ }
+ catch(InstantiationException e) {
+ throw new ClassNotFoundException("Could not instantiate PS transcoder - is Apache FOP installed?", e);
+ }
+ catch(IllegalAccessException e) {
+ throw new ClassNotFoundException("Could not instantiate PS transcoder - is Apache FOP installed?", e);
+ }
}
/**
@@ -428,9 +417,36 @@ public class SVGPlot {
* @param file Output filename
* @throws IOException On write errors
* @throws TranscoderException On input/parsing errors.
+ * @throws ClassNotFoundException EPS transcoder not installed
*/
- public void saveAsEPS(File file) throws IOException, TranscoderException {
- transcode(file, new EPSTranscoder());
+ public void saveAsEPS(File file) throws IOException, TranscoderException, ClassNotFoundException {
+ try {
+ Object t = Class.forName("org.apache.fop.render.ps.EPSTranscoder").newInstance();
+ transcode(file, (Transcoder) t);
+ }
+ catch(InstantiationException e) {
+ throw new ClassNotFoundException("Could not instantiate EPS transcoder - is Apache FOP installed?", e);
+ }
+ catch(IllegalAccessException e) {
+ throw new ClassNotFoundException("Could not instantiate EPS transcoder - is Apache FOP installed?", e);
+ }
+ }
+
+ /**
+ * Test whether FOP were installed (for PDF, PS and EPS output support).
+ *
+ * @return true when FOP is available.
+ */
+ public static boolean hasFOPInstalled() {
+ try {
+ Class<?> c1 = Class.forName("org.apache.fop.svg.PDFTranscoder");
+ Class<?> c2 = Class.forName("org.apache.fop.render.ps.PSTranscoder");
+ Class<?> c3 = Class.forName("org.apache.fop.render.ps.EPSTranscoder");
+ return (c1 != null) && (c2 != null) && (c3 != null);
+ }
+ catch(ClassNotFoundException e) {
+ return false;
+ }
}
/**
@@ -491,8 +507,9 @@ public class SVGPlot {
* @throws TranscoderException on transcoding errors
* @throws TransformerFactoryConfigurationError on transcoding errors
* @throws TransformerException on transcoding errors
+ * @throws ClassNotFoundException when the transcoder was not installed
*/
- public void saveAsANY(File file, int width, int height, double quality) throws IOException, TranscoderException, TransformerFactoryConfigurationError, TransformerException {
+ public void saveAsANY(File file, int width, int height, double quality) throws IOException, TranscoderException, TransformerFactoryConfigurationError, TransformerException, ClassNotFoundException {
String extension = FileUtil.getFilenameExtension(file);
if(extension.equals("svg")) {
saveAsSVG(file);
@@ -518,6 +535,23 @@ public class SVGPlot {
}
/**
+ * Convert the SVG to a thumbnail image.
+ *
+ * @param width Width of thumbnail
+ * @param height Height of thumbnail
+ * @return Buffered image
+ */
+ public BufferedImage makeAWTImage(int width, int height) throws TranscoderException {
+ ThumbnailTranscoder t = new ThumbnailTranscoder();
+ t.addTranscodingHint(PNGTranscoder.KEY_WIDTH, new Float(width));
+ t.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, new Float(height));
+ // Don't clone. Assume this is used safely.
+ TranscoderInput input = new TranscoderInput(document);
+ t.transcode(input, null);
+ return t.getLastImage();
+ }
+
+ /**
* Dump the SVG plot to a debug file.
*/
public void dumpDebugFile() {
@@ -563,21 +597,12 @@ public class SVGPlot {
}
/**
- * Get the plots update runner.
- *
- * @return update runner
- */
- private UpdateRunner getUpdateRunner() {
- return runner;
- }
-
- /**
* Schedule an update.
*
* @param runnable Runnable to schedule
*/
public void scheduleUpdate(Runnable runnable) {
- getUpdateRunner().invokeLater(runnable);
+ runner.invokeLater(runnable);
}
/**
@@ -586,7 +611,7 @@ public class SVGPlot {
* @param sync Update synchronizer
*/
public void synchronizeWith(UpdateSynchronizer sync) {
- getUpdateRunner().synchronizeWith(sync);
+ runner.synchronizeWith(sync);
}
/**
@@ -595,7 +620,7 @@ public class SVGPlot {
* @param sync Update synchronizer to detach from.
*/
public void unsynchronizeWith(UpdateSynchronizer sync) {
- getUpdateRunner().unsynchronizeWith(sync);
+ runner.unsynchronizeWith(sync);
}
/**
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGScoreBar.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGScoreBar.java
new file mode 100644
index 00000000..185a6061
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGScoreBar.java
@@ -0,0 +1,118 @@
+package de.lmu.ifi.dbs.elki.visualization.svg;
+
+import java.text.NumberFormat;
+
+import org.apache.batik.util.SVGConstants;
+import org.w3c.dom.Element;
+
+import de.lmu.ifi.dbs.elki.utilities.FormatUtil;
+
+/**
+ * Draw a score bar. Essentially like a progress bar, left-to-right, displaying
+ * a relative score.
+ *
+ * @author Sascha Goldhofer
+ */
+// TODO: refactor to get a progress bar?
+public class SVGScoreBar {
+ /**
+ * Fill value
+ */
+ protected double fill = 0.0;
+
+ /**
+ * Total size
+ */
+ protected double size = 1.0;
+
+ /**
+ * Label (on the right)
+ */
+ protected String label = null;
+
+ /**
+ * Number format, set to print the actual score
+ */
+ private NumberFormat format = null;
+
+ /**
+ * Constructor.
+ */
+ public SVGScoreBar() {
+ // Nothing to do here.
+ }
+
+ /**
+ * Set the fill of the score bar.
+ *
+ * @param fill Fill value
+ * @param size Total size
+ */
+ public void setFill(double fill, double size) {
+ this.fill = fill;
+ this.size = size;
+ }
+
+ /**
+ * Set label (right of the bar)
+ *
+ * @param text Label text
+ */
+ public void addLabel(String text) {
+ this.label = text;
+ }
+
+ /**
+ * To show score values, set a number format
+ *
+ * @param format Number format
+ */
+ public void showValues(NumberFormat format) {
+ this.format = format;
+ }
+
+ /**
+ * Build the actual element
+ *
+ * @param svgp Plot to draw to
+ * @param x X coordinate
+ * @param y Y coordinate
+ * @param width Width
+ * @param height Height
+ * @return new element
+ */
+ public Element build(SVGPlot svgp, double x, double y, double width, double height) {
+ Element barchart = svgp.svgElement(SVGConstants.SVG_G_TAG);
+
+ // TODO: use style library for colors!
+ Element bar = svgp.svgRect(x, y, width, height);
+ bar.setAttribute(SVGConstants.SVG_FILL_ATTRIBUTE, "#a0a0a0");
+ bar.setAttribute(SVGConstants.SVG_STROKE_ATTRIBUTE, "#a0a0a0");
+ bar.setAttribute(SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE, "" + height * 0.01);
+
+ double fpos = (fill / size) * (width - (0.04 * height));
+ Element chart = svgp.svgRect(x + 0.02 * height, y + 0.02 * height, fpos, height - 0.04 * height);
+ chart.setAttribute(SVGConstants.SVG_FILL_ATTRIBUTE, "#d4e4f1");
+ chart.setAttribute(SVGConstants.SVG_STROKE_ATTRIBUTE, "#a0a0a0");
+ chart.setAttribute(SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE, "" + height * 0.01);
+
+ barchart = svgp.svgElement(SVGConstants.SVG_G_TAG);
+ barchart.appendChild(bar);
+ barchart.appendChild(chart);
+
+ // Draw the values:
+ if(format != null) {
+ Element lbl = svgp.svgText(x + 0.05 * width, y + 0.75 * height, FormatUtil.format(fill, format));
+ lbl.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, "font-size: " + 0.75 * height + "; font-weight: bold");
+ barchart.appendChild(lbl);
+ }
+
+ // Draw the label
+ if(label != null) {
+ Element lbl = svgp.svgText(x + 1.05 * width, y + 0.75 * height, label);
+ lbl.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, "font-size: " + 0.75 * height + "; font-weight: normal");
+ barchart.appendChild(lbl);
+ }
+ return barchart;
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGSimpleLinearAxis.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGSimpleLinearAxis.java
index c32fef04..9a676c38 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGSimpleLinearAxis.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGSimpleLinearAxis.java
@@ -4,7 +4,7 @@ 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) 2011
+ Copyright (C) 2012
Ludwig-Maximilians-Universität München
Lehr- und Forschungseinheit für Datenbanksysteme
ELKI Development Team
@@ -26,10 +26,10 @@ package de.lmu.ifi.dbs.elki.visualization.svg;
import org.apache.batik.util.SVGConstants;
import org.w3c.dom.Element;
+import de.lmu.ifi.dbs.elki.math.scales.LinearScale;
import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
import de.lmu.ifi.dbs.elki.visualization.css.CSSClassManager;
import de.lmu.ifi.dbs.elki.visualization.css.CSSClassManager.CSSNamingConflict;
-import de.lmu.ifi.dbs.elki.visualization.scales.LinearScale;
import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
/**
@@ -41,6 +41,7 @@ import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
* @apiviz.uses CSSClassManager
* @apiviz.uses LinearScale
* @apiviz.uses StyleLibrary
+ * @apiviz.uses STYLE
* @apiviz.uses Element oneway - - «create»
*/
public class SVGSimpleLinearAxis {
@@ -55,6 +56,15 @@ public class SVGSimpleLinearAxis {
}
/**
+ * Labeling style: left-handed, right-handed, no ticks, labels at ends
+ *
+ * @author Erich Schubert
+ */
+ public enum LabelStyle {
+ LEFTHAND, RIGHTHAND, NOLABELS, NOTHING, ENDLABEL
+ }
+
+ /**
* CSS class name for the axes
*/
private final static String CSS_AXIS = "axis";
@@ -93,8 +103,6 @@ public class SVGSimpleLinearAxis {
CSSClass label = new CSSClass(owner, CSS_AXIS_LABEL);
label.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getTextColor(StyleLibrary.AXIS_LABEL));
label.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, style.getFontFamily(StyleLibrary.AXIS_LABEL));
- // label.setStatement(SVGConstants.SVG_TEXT_RENDERING_ATTRIBUTE,
- // SVGConstants.SVG_OPTIMIZE_LEGIBILITY_VALUE);
label.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, style.getTextSize(StyleLibrary.AXIS_LABEL));
manager.addClass(label);
}
@@ -110,13 +118,11 @@ public class SVGSimpleLinearAxis {
* @param y1 starting coordinate
* @param x2 ending coordinate
* @param y2 ending coordinate
- * @param labels control whether labels are printed.
- * @param righthanded control whether to print labels on the right hand side
- * or left hand side
+ * @param labelstyle Style for placing the labels
* @param style Style library
* @throws CSSNamingConflict when a conflict occurs in CSS
*/
- public static void drawAxis(SVGPlot plot, Element parent, LinearScale scale, double x1, double y1, double x2, double y2, boolean labels, boolean righthanded, StyleLibrary style) throws CSSNamingConflict {
+ public static void drawAxis(SVGPlot plot, Element parent, LinearScale scale, double x1, double y1, double x2, double y2, LabelStyle labelstyle, StyleLibrary style) throws CSSNamingConflict {
assert (parent != null);
Element line = plot.svgLine(x1, y1, x2, y2);
SVGUtil.setCSSClass(line, CSS_AXIS);
@@ -129,75 +135,109 @@ public class SVGSimpleLinearAxis {
double th = -tx * 0.01;
// choose where to print labels.
+ final boolean labels, ticks;
+ switch(labelstyle){
+ case LEFTHAND:
+ case RIGHTHAND:
+ labels = true;
+ ticks = true;
+ break;
+ case NOLABELS:
+ labels = false;
+ ticks = true;
+ break;
+ case ENDLABEL: // end labels are handle specially
+ case NOTHING:
+ default:
+ labels = false;
+ ticks = false;
+ }
ALIGNMENT pos = ALIGNMENT.LL;
if(labels) {
double angle = Math.atan2(ty, tx);
// System.err.println(tx + " " + (-ty) + " " + angle);
if(angle > 2.6) { // pi .. 2.6 = 180 .. 150
- pos = righthanded ? ALIGNMENT.RC : ALIGNMENT.LC;
+ pos = labelstyle == LabelStyle.RIGHTHAND ? ALIGNMENT.RC : ALIGNMENT.LC;
}
else if(angle > 0.5) { // 2.3 .. 0.7 = 130 .. 40
- pos = righthanded ? ALIGNMENT.RR : ALIGNMENT.LL;
+ pos = labelstyle == LabelStyle.RIGHTHAND ? ALIGNMENT.RR : ALIGNMENT.LL;
}
else if(angle > -0.5) { // 0.5 .. -0.5 = 30 .. -30
- pos = righthanded ? ALIGNMENT.RC : ALIGNMENT.LC;
+ pos = labelstyle == LabelStyle.RIGHTHAND ? ALIGNMENT.RC : ALIGNMENT.LC;
}
else if(angle > -2.6) { // -0.5 .. -2.6 = -30 .. -150
- pos = righthanded ? ALIGNMENT.RL : ALIGNMENT.LR;
+ pos = labelstyle == LabelStyle.RIGHTHAND ? ALIGNMENT.RL : ALIGNMENT.LR;
}
else { // -2.6 .. -pi = -150 .. -180
- pos = righthanded ? ALIGNMENT.RC : ALIGNMENT.LC;
+ pos = labelstyle == LabelStyle.RIGHTHAND ? ALIGNMENT.RC : ALIGNMENT.LC;
}
}
// vertical text offset; align approximately with middle instead of
// baseline.
- double textvoff = 0.015;
+ double textvoff = style.getTextSize(StyleLibrary.AXIS_LABEL) * .35;
// draw ticks on x axis
- for(double tick = scale.getMin(); tick <= scale.getMax() + scale.getRes() / 10; tick += scale.getRes()) {
- double x = x1 + tx * scale.getScaled(tick);
- double y = y1 + ty * scale.getScaled(tick);
- // This is correct. Vectors: (vec - tvec) to (vec + tvec)
- Element tickline = plot.svgLine(x - tw, y - th, x + tw, y + th);
- SVGUtil.setAtt(tickline, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_AXIS_TICK);
- parent.appendChild(tickline);
- // draw labels
- if(labels) {
- double tex = x;
- double tey = y;
- switch(pos){
- case LL:
- case LC:
- case LR:
- tex = x + tw * 2.5;
- tey = y + th * 2.5 + textvoff;
- break;
- case RL:
- case RC:
- case RR:
- tex = x - tw * 2.5;
- tey = y - th * 2.5 + textvoff;
+ if(ticks || labels) {
+ for(double tick = scale.getMin(); tick <= scale.getMax() + scale.getRes() / 10; tick += scale.getRes()) {
+ double x = x1 + tx * scale.getScaled(tick);
+ double y = y1 + ty * scale.getScaled(tick);
+ if(ticks) {
+ // This is correct. Vectors: (vec - tvec) to (vec + tvec)
+ Element tickline = plot.svgLine(x - tw, y - th, x + tw, y + th);
+ SVGUtil.setAtt(tickline, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_AXIS_TICK);
+ parent.appendChild(tickline);
}
- Element text = plot.svgText(tex, tey, scale.formatValue(tick));
- SVGUtil.setAtt(text, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_AXIS_LABEL);
- switch(pos){
- case LL:
- case RL:
- SVGUtil.setAtt(text, SVGConstants.SVG_TEXT_ANCHOR_ATTRIBUTE, SVGConstants.SVG_START_VALUE);
- break;
- case LC:
- case RC:
- SVGUtil.setAtt(text, SVGConstants.SVG_TEXT_ANCHOR_ATTRIBUTE, SVGConstants.SVG_MIDDLE_VALUE);
- break;
- case LR:
- case RR:
- SVGUtil.setAtt(text, SVGConstants.SVG_TEXT_ANCHOR_ATTRIBUTE, SVGConstants.SVG_END_VALUE);
- break;
+ // draw labels
+ if(labels) {
+ double tex = x;
+ double tey = y;
+ switch(pos){
+ case LL:
+ case LC:
+ case LR:
+ tex = x + tw * 2.5;
+ tey = y + th * 2.5 + textvoff;
+ break;
+ case RL:
+ case RC:
+ case RR:
+ tex = x - tw * 2.5;
+ tey = y - th * 2.5 + textvoff;
+ }
+ Element text = plot.svgText(tex, tey, scale.formatValue(tick));
+ text.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_AXIS_LABEL);
+ switch(pos){
+ case LL:
+ case RL:
+ text.setAttribute(SVGConstants.SVG_TEXT_ANCHOR_ATTRIBUTE, SVGConstants.SVG_START_VALUE);
+ break;
+ case LC:
+ case RC:
+ text.setAttribute(SVGConstants.SVG_TEXT_ANCHOR_ATTRIBUTE, SVGConstants.SVG_MIDDLE_VALUE);
+ break;
+ case LR:
+ case RR:
+ text.setAttribute(SVGConstants.SVG_TEXT_ANCHOR_ATTRIBUTE, SVGConstants.SVG_END_VALUE);
+ break;
+ }
+ parent.appendChild(text);
}
+ }
+ }
+ if(labelstyle == LabelStyle.ENDLABEL) {
+ {
+ Element text = plot.svgText(x1 - tx * 0.02, y1 - ty * 0.02 + textvoff, scale.formatValue(scale.getMin()));
+ text.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_AXIS_LABEL);
+ text.setAttribute(SVGConstants.SVG_TEXT_ANCHOR_ATTRIBUTE, SVGConstants.SVG_MIDDLE_VALUE);
+ parent.appendChild(text);
+ }
+ {
+ Element text = plot.svgText(x2 + tx * 0.02, y2 + ty * 0.02 + textvoff, scale.formatValue(scale.getMax()));
+ text.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_AXIS_LABEL);
+ text.setAttribute(SVGConstants.SVG_TEXT_ANCHOR_ATTRIBUTE, SVGConstants.SVG_MIDDLE_VALUE);
parent.appendChild(text);
}
}
setupCSSClasses(plot, plot.getCSSClassManager(), style);
}
-
-}
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGUtil.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGUtil.java
index b4e59fda..4e40d1b0 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGUtil.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGUtil.java
@@ -4,7 +4,7 @@ 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) 2011
+ Copyright (C) 2012
Ludwig-Maximilians-Universität München
Lehr- und Forschungseinheit für Datenbanksysteme
ELKI Development Team
@@ -23,9 +23,10 @@ package de.lmu.ifi.dbs.elki.visualization.svg;
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+import gnu.trove.map.hash.TObjectIntHashMap;
+
import java.awt.Color;
import java.text.NumberFormat;
-import java.util.HashMap;
import java.util.Locale;
import javax.swing.text.html.StyleSheet;
@@ -77,9 +78,16 @@ public final class SVGUtil {
/**
* SVG color names conversion.
*/
- final private static HashMap<String, Integer> SVG_COLOR_NAMES = new HashMap<String, Integer>();
+ final private static TObjectIntHashMap<String> SVG_COLOR_NAMES;
+
+ /**
+ * Key not found value. Not a reasonable color, fully transparent!
+ */
+ final private static int NO_VALUE = 0x00123456;
static {
+ // Build a reasonably sized hashmap. Use 0
+ SVG_COLOR_NAMES = new TObjectIntHashMap<String>(90, .8f, NO_VALUE);
// List taken from SVG specification:
// http://www.w3.org/TR/SVG/types.html#ColorKeywords
SVG_COLOR_NAMES.put("aliceblue", 0xFFF0F8FF);
@@ -229,6 +237,8 @@ public final class SVGUtil {
SVG_COLOR_NAMES.put("whitesmoke", 0xFFF5F5F5);
SVG_COLOR_NAMES.put("yellow", 0xFFFFFF00);
SVG_COLOR_NAMES.put("yellowgreen", 0xFF9ACD32);
+ // Nonstandard:
+ SVG_COLOR_NAMES.put("transparent", 0xFFFFFFFF);
}
/**
@@ -468,8 +478,8 @@ public final class SVGUtil {
* @return Color value
*/
public static Color stringToColor(String str) {
- Integer icol = SVG_COLOR_NAMES.get(str.toLowerCase());
- if(icol != null) {
+ int icol = SVG_COLOR_NAMES.get(str.toLowerCase());
+ if(icol != NO_VALUE) {
return new Color(icol, false);
}
return colorLookupStylesheet.stringToColor(str);
@@ -478,6 +488,8 @@ public final class SVGUtil {
/**
* Convert a color name from an AWT color object to CSS syntax
*
+ * Note: currently only RGB (from ARGB order) are supported.
+ *
* @param col Color value
* @return Color string
*/
@@ -486,6 +498,18 @@ public final class SVGUtil {
}
/**
+ * Convert a color name from an AWT color object to CSS syntax
+ *
+ * Note: currently only RGB (from ARGB order) are supported.
+ *
+ * @param col Color value
+ * @return Color string
+ */
+ public static String colorToString(int col) {
+ return String.format("#%02x%02x%02x", (col >>> 16) & 0xFF, (col >>> 8) & 0xFF, col & 0xFF);
+ }
+
+ /**
* Make a transform string to add margins
*
* @param owidth Width of outer (embedding) canvas
@@ -572,4 +596,62 @@ public final class SVGUtil {
tag.removeChild(last);
}
}
+
+ /**
+ * Remove an element from its parent, if defined.
+ *
+ * @param elem Element to remove
+ */
+ public static void removeFromParent(Element elem) {
+ if(elem != null) {
+ if(elem.getParentNode() != null) {
+ elem.getParentNode().removeChild(elem);
+ }
+ }
+ }
+
+ /**
+ * Create a circle segment.
+ *
+ * @param svgp Plot to draw to
+ * @param centerx Center X position
+ * @param centery Center Y position
+ * @param angleStart Starting angle
+ * @param angleDelta Angle delta
+ * @param innerRadius inner radius
+ * @param outerRadius outer radius
+ * @return SVG element representing this circle segment
+ */
+ public static Element svgCircleSegment(SVGPlot svgp, double centerx, double centery, double angleStart, double angleDelta, double innerRadius, double outerRadius) {
+ double sin1st = Math.sin(angleStart);
+ double cos1st = Math.cos(angleStart);
+
+ double sin2nd = Math.sin(angleStart + angleDelta);
+ double cos2nd = Math.cos(angleStart + angleDelta);
+
+ double inner1stx = centerx + (innerRadius * sin1st);
+ double inner1sty = centery - (innerRadius * cos1st);
+ double outer1stx = centerx + (outerRadius * sin1st);
+ double outer1sty = centery - (outerRadius * cos1st);
+
+ double inner2ndx = centerx + (innerRadius * sin2nd);
+ double inner2ndy = centery - (innerRadius * cos2nd);
+ double outer2ndx = centerx + (outerRadius * sin2nd);
+ double outer2ndy = centery - (outerRadius * cos2nd);
+
+ double largeArc = 0;
+ if(angleDelta >= Math.PI) {
+ largeArc = 1;
+ }
+
+ SVGPath path = new SVGPath(inner1stx, inner1sty);
+ path.lineTo(outer1stx, outer1sty);
+ path.ellipticalArc(outerRadius, outerRadius, 0, largeArc, 1, outer2ndx, outer2ndy);
+ path.lineTo(inner2ndx, inner2ndy);
+ if (innerRadius > 0) {
+ path.ellipticalArc(innerRadius, innerRadius, 0, largeArc, 0, inner1stx, inner1sty);
+ }
+
+ return path.makeElement(svgp);
+ }
} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/UpdateRunner.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/UpdateRunner.java
index 03bc9d74..463c7c63 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/svg/UpdateRunner.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/UpdateRunner.java
@@ -4,7 +4,7 @@ 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) 2011
+ Copyright (C) 2012
Ludwig-Maximilians-Universität München
Lehr- und Forschungseinheit für Datenbanksysteme
ELKI Development Team
@@ -23,7 +23,6 @@ package de.lmu.ifi.dbs.elki.visualization.svg;
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-import java.util.Collection;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -52,7 +51,7 @@ public class UpdateRunner {
/**
* Synchronizer that can block events from being executed right away.
*/
- private Collection<UpdateSynchronizer> synchronizer = new java.util.Vector<UpdateSynchronizer>();
+ private UpdateSynchronizer synchronizer = null;
/**
* Construct a new update handler
@@ -70,13 +69,11 @@ public class UpdateRunner {
*/
public void invokeLater(Runnable r) {
queue.add(r);
- if(synchronizer.size() > 0) {
- for(UpdateSynchronizer s : synchronizer) {
- s.activate();
- }
+ if(synchronizer == null) {
+ runQueue();
}
else {
- runQueue();
+ synchronizer.activate();
}
}
@@ -128,14 +125,18 @@ public class UpdateRunner {
* @param newsync Update synchronizer
*/
public synchronized void synchronizeWith(UpdateSynchronizer newsync) {
- //LoggingUtil.warning("Synchronizing: " + sync + " " + newsync);
- if(synchronizer.contains(newsync)) {
+ // LoggingUtil.warning("Synchronizing: " + sync + " " + newsync);
+ if(synchronizer == newsync) {
LoggingUtil.warning("Double-synced to the same plot!");
+ return;
}
- else {
- synchronizer.add(newsync);
- newsync.addUpdateRunner(this);
+ if(synchronizer != null) {
+ LoggingUtil.warning("Attempting to synchronize to more than one synchronizer.");
+ return;
}
+ synchronizer = newsync;
+ newsync.addUpdateRunner(this);
+
}
/**
@@ -144,10 +145,17 @@ public class UpdateRunner {
* @param oldsync Update synchronizer to remove
*/
public synchronized void unsynchronizeWith(UpdateSynchronizer oldsync) {
- //LoggingUtil.warning("Unsynchronizing: " + sync + " " + oldsync);
- synchronizer.remove(oldsync);
- if(synchronizer.size() == 0) {
- runQueue();
+ if(synchronizer == null) {
+ LoggingUtil.warning("Warning: was not synchronized.");
+ }
+ else {
+ if(synchronizer != oldsync) {
+ LoggingUtil.warning("Warning: was synchronized differently!");
+ return;
+ }
}
+ // LoggingUtil.warning("Unsynchronizing: " + sync + " " + oldsync);
+ synchronizer = null;
+ runQueue();
}
} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/UpdateSynchronizer.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/UpdateSynchronizer.java
index c65ae719..3f249871 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/svg/UpdateSynchronizer.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/UpdateSynchronizer.java
@@ -4,7 +4,7 @@ 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) 2011
+ Copyright (C) 2012
Ludwig-Maximilians-Universität München
Lehr- und Forschungseinheit für Datenbanksysteme
ELKI Development Team
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/VoronoiDraw.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/VoronoiDraw.java
new file mode 100644
index 00000000..317a44ae
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/VoronoiDraw.java
@@ -0,0 +1,152 @@
+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 <http://www.gnu.org/licenses/>.
+ */
+
+import java.util.List;
+
+import de.lmu.ifi.dbs.elki.math.geometry.SweepHullDelaunay2D;
+import de.lmu.ifi.dbs.elki.math.geometry.SweepHullDelaunay2D.Triangle;
+import de.lmu.ifi.dbs.elki.math.linearalgebra.VMath;
+import de.lmu.ifi.dbs.elki.visualization.projections.CanvasSize;
+import de.lmu.ifi.dbs.elki.visualization.projections.Projection2D;
+
+/**
+ * Draw the Voronoi cells
+ *
+ * @author Robert Rödler
+ * @author Erich Schubert
+ */
+public class VoronoiDraw {
+ /**
+ * Draw the Delaunay triangulation.
+ *
+ * @param proj Projection
+ * @param delaunay Triangulation
+ * @param means Means
+ * @return Path
+ */
+ public static SVGPath drawDelaunay(Projection2D proj, List<SweepHullDelaunay2D.Triangle> delaunay, List<double[]> means) {
+ final SVGPath path = new SVGPath();
+ for(SweepHullDelaunay2D.Triangle del : delaunay) {
+ path.moveTo(proj.fastProjectDataToRenderSpace(means.get(del.a)));
+ path.drawTo(proj.fastProjectDataToRenderSpace(means.get(del.b)));
+ path.drawTo(proj.fastProjectDataToRenderSpace(means.get(del.c)));
+ path.close();
+ }
+ return path;
+ }
+
+ /**
+ * Draw a Voronoi diagram
+ *
+ * @param proj Projection
+ * @param delaunay Delaunay triangulation
+ * @param means Cluster means
+ * @return SVG path
+ */
+ public static SVGPath drawVoronoi(Projection2D proj, List<SweepHullDelaunay2D.Triangle> delaunay, List<double[]> means) {
+ final SVGPath path = new SVGPath();
+ CanvasSize viewport = proj.estimateViewport();
+ for(int i = 0; i < delaunay.size(); i++) {
+ SweepHullDelaunay2D.Triangle del = delaunay.get(i);
+ final double[] projcx = proj.fastProjectDataToRenderSpace(del.m.getArrayRef());
+ if(del.ab > i) {
+ Triangle oth = delaunay.get(del.ab);
+ path.moveTo(projcx);
+ path.drawTo(proj.fastProjectDataToRenderSpace(oth.m.getArrayRef()));
+ }
+ else if(del.ab < 0) {
+ double[] dirv = VMath.minus(means.get(del.a), means.get(del.b));
+ VMath.rotate90Equals(dirv);
+ double[] dir = proj.fastProjectRelativeDataToRenderSpace(dirv);
+ final double factor = viewport.continueToMargin(projcx, dir);
+ if(factor > 0) {
+ path.moveTo(projcx);
+ path.relativeLineTo(factor * dir[0], factor * dir[1]);
+ }
+ }
+
+ if(del.bc > i) {
+ Triangle oth = delaunay.get(del.bc);
+ path.moveTo(projcx);
+ path.drawTo(proj.fastProjectDataToRenderSpace(oth.m.getArrayRef()));
+ }
+ else if(del.bc < 0) {
+ double[] dirv = VMath.minus(means.get(del.b), means.get(del.c));
+ VMath.rotate90Equals(dirv);
+ double[] dir = proj.fastProjectRelativeDataToRenderSpace(dirv);
+ final double factor = viewport.continueToMargin(projcx, dir);
+ if(factor > 0) {
+ path.moveTo(projcx);
+ path.relativeLineTo(factor * dir[0], factor * dir[1]);
+ }
+ }
+
+ if(del.ca > i) {
+ Triangle oth = delaunay.get(del.ca);
+ path.moveTo(projcx);
+ path.drawTo(proj.fastProjectDataToRenderSpace(oth.m.getArrayRef()));
+ }
+ else if(del.ca < 0) {
+ double[] dirv = VMath.minus(means.get(del.c), means.get(del.a));
+ VMath.rotate90Equals(dirv);
+ double[] dir = proj.fastProjectRelativeDataToRenderSpace(dirv);
+ final double factor = viewport.continueToMargin(projcx, dir);
+ if(factor > 0) {
+ path.moveTo(projcx);
+ path.relativeLineTo(factor * dir[0], factor * dir[1]);
+ }
+ }
+ }
+ return path;
+ }
+
+ /**
+ * Fake Voronoi diagram. For two means only
+ *
+ * @param proj Projection
+ * @param means Mean vectors
+ * @return SVG path
+ */
+ public static SVGPath drawFakeVoronoi(Projection2D proj, List<double[]> means) {
+ CanvasSize viewport = proj.estimateViewport();
+ final SVGPath path = new SVGPath();
+ // Difference
+ final double[] dirv = VMath.minus(means.get(1), means.get(0));
+ VMath.rotate90Equals(dirv);
+ double[] dir = proj.fastProjectRelativeDataToRenderSpace(dirv);
+ // Mean
+ final double[] mean = VMath.plus(means.get(0), means.get(1));
+ VMath.timesEquals(mean, 0.5);
+ double[] projmean = proj.fastProjectDataToRenderSpace(mean);
+
+ double factor = viewport.continueToMargin(projmean, dir);
+ path.moveTo(projmean[0] + factor * dir[0], projmean[1] + factor * dir[1]);
+ // Inverse direction:
+ dir[0] *= -1;
+ dir[1] *= -1;
+ factor = viewport.continueToMargin(projmean, dir);
+ path.drawTo(projmean[0] + factor * dir[0], projmean[1] + factor * dir[1]);
+ return path;
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/package-info.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/package-info.java
index 7c9ba1f9..7811b493 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/svg/package-info.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/package-info.java
@@ -6,7 +6,7 @@
This file is part of ELKI:
Environment for Developing KDD-Applications Supported by Index-Structures
-Copyright (C) 2011
+Copyright (C) 2012
Ludwig-Maximilians-Universität München
Lehr- und Forschungseinheit für Datenbanksysteme
ELKI Development Team