summaryrefslogtreecommitdiff
path: root/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics
diff options
context:
space:
mode:
Diffstat (limited to 'src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics')
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/AbstractOPTICSVisualization.java91
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSClusterVisualization.java218
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotCutVisualization.java302
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotSelectionVisualization.java374
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotVisualizer.java146
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSSteepAreaVisualization.java223
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/package-info.java26
7 files changed, 1380 insertions, 0 deletions
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/AbstractOPTICSVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/AbstractOPTICSVisualization.java
new file mode 100644
index 00000000..9cb95c23
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/AbstractOPTICSVisualization.java
@@ -0,0 +1,91 @@
+package de.lmu.ifi.dbs.elki.visualization.visualizers.optics;
+
+/*
+ This file is part of ELKI:
+ Environment for Developing KDD-Applications Supported by Index-Structures
+
+ Copyright (C) 2011
+ 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 org.apache.batik.util.SVGConstants;
+
+import de.lmu.ifi.dbs.elki.distance.distancevalue.Distance;
+import de.lmu.ifi.dbs.elki.result.optics.ClusterOrderEntry;
+import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
+import de.lmu.ifi.dbs.elki.visualization.projector.OPTICSProjector;
+import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisualization;
+
+/**
+ * Abstract base class for OPTICS visualizer
+ *
+ * @author Erich Schubert
+ *
+ * @param <D>
+ */
+public abstract class AbstractOPTICSVisualization<D extends Distance<D>> extends AbstractVisualization {
+ /**
+ * The plot
+ */
+ final protected OPTICSProjector<D> optics;
+
+ /**
+ * Width of plot (in display units)
+ */
+ protected double plotwidth;
+
+ /**
+ * Height of plot (in display units)
+ */
+ protected double plotheight;
+
+ /**
+ * Constructor.
+ *
+ * @param task Visualization task.
+ */
+ public AbstractOPTICSVisualization(VisualizationTask task) {
+ super(task);
+ this.optics = task.getResult();
+ }
+
+ /**
+ * Produce a new layer element.
+ */
+ protected void makeLayerElement() {
+ plotwidth = StyleLibrary.SCALE;
+ plotheight = StyleLibrary.SCALE / optics.getOPTICSPlot(context).getRatio();
+ final double margin = context.getStyleLibrary().getSize(StyleLibrary.MARGIN);
+ layer = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG);
+ final String transform = SVGUtil.makeMarginTransform(task.getWidth(), task.getHeight(), plotwidth, plotheight, margin / 2);
+ SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform);
+ }
+
+ /**
+ * Access the raw cluster order
+ *
+ * @return Cluster order
+ */
+ protected List<ClusterOrderEntry<D>> getClusterOrder() {
+ return optics.getResult().getClusterOrder();
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSClusterVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSClusterVisualization.java
new file mode 100644
index 00000000..0898d9d0
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSClusterVisualization.java
@@ -0,0 +1,218 @@
+package de.lmu.ifi.dbs.elki.visualization.visualizers.optics;
+
+/*
+ This file is part of ELKI:
+ Environment for Developing KDD-Applications Supported by Index-Structures
+
+ Copyright (C) 2011
+ 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.Iterator;
+import java.util.List;
+
+import org.apache.batik.util.SVGConstants;
+import org.w3c.dom.Element;
+
+import de.lmu.ifi.dbs.elki.data.Cluster;
+import de.lmu.ifi.dbs.elki.data.Clustering;
+import de.lmu.ifi.dbs.elki.data.model.OPTICSModel;
+import de.lmu.ifi.dbs.elki.distance.distancevalue.Distance;
+import de.lmu.ifi.dbs.elki.distance.distancevalue.DoubleDistance;
+import de.lmu.ifi.dbs.elki.logging.Logging;
+import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
+import de.lmu.ifi.dbs.elki.result.Result;
+import de.lmu.ifi.dbs.elki.result.ResultUtil;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil;
+import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
+import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
+import de.lmu.ifi.dbs.elki.visualization.projector.OPTICSProjector;
+import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisFactory;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;
+
+/**
+ * Visualize the clusters and cluster hierarchy found by OPTICS on the OPTICS
+ * Plot.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.uses ClusterOrderResult
+ * @apiviz.uses OPTICSPlot
+ *
+ * @param <D> Distance type (actually unused)
+ */
+public class OPTICSClusterVisualization<D extends Distance<D>> extends AbstractOPTICSVisualization<D> {
+ /**
+ * The logger for this class.
+ */
+ private static final Logging logger = Logging.getLogger(OPTICSClusterVisualization.class);
+
+ /**
+ * A short name characterizing this Visualizer.
+ */
+ private static final String NAME = "OPTICS Cluster Ranges";
+
+ /**
+ * CSS class for markers
+ */
+ protected static final String CSS_BRACKET = "opticsBracket";
+
+ /**
+ * Optics clustering we visualize
+ */
+ public static final String CLUSTERING = "OPTICSClustering";
+
+ /**
+ * Our clustering
+ */
+ Clustering<OPTICSModel> clus;
+
+ /**
+ * Constructor.
+ *
+ * @param task Visualization task
+ */
+ public OPTICSClusterVisualization(VisualizationTask task) {
+ super(task);
+ this.clus = task.getGenerics(CLUSTERING, Clustering.class);
+ context.addResultListener(this);
+ incrementalRedraw();
+ }
+
+ /**
+ * Find the first OPTICS clustering child of a result.
+ *
+ * @param result Result to start searching at
+ * @return OPTICS clustering
+ */
+ @SuppressWarnings("unchecked")
+ protected static Clustering<OPTICSModel> findOPTICSClustering(Result result) {
+ Iterator<Clustering<?>> cs = ResultUtil.filteredResults(result, Clustering.class);
+ while (cs.hasNext()) {
+ Clustering<?> clus = cs.next();
+ if(clus.getToplevelClusters().size() == 0) {
+ continue;
+ }
+ try {
+ Cluster<?> firstcluster = clus.getToplevelClusters().iterator().next();
+ if(firstcluster.getModel() instanceof OPTICSModel) {
+ return (Clustering<OPTICSModel>) clus;
+ }
+ } catch(Exception e) {
+ // Empty clustering? Shouldn't happen.
+ logger.warning("Clustering with no cluster detected.", e);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void redraw() {
+ makeLayerElement();
+ addCSSClasses();
+ drawClusters(clus.getToplevelClusters(), 1);
+ }
+
+ /**
+ * Recursively draw clusters
+ *
+ * @param clusters Current set of clusters
+ * @param depth Recursion depth
+ */
+ private void drawClusters(List<Cluster<OPTICSModel>> clusters, int depth) {
+ final double scale = StyleLibrary.SCALE;
+ for(Cluster<OPTICSModel> cluster : clusters) {
+ try {
+ OPTICSModel model = cluster.getModel();
+ final double x1 = plotwidth * ((model.getStartIndex() + .25) / this.optics.getResult().getClusterOrder().size());
+ final double x2 = plotwidth * ((model.getEndIndex() + .75) / this.optics.getResult().getClusterOrder().size());
+ final double y = plotheight + depth * scale * 0.01;
+ Element e = svgp.svgLine(x1, y, x2, y);
+ SVGUtil.addCSSClass(e, CSS_BRACKET);
+ layer.appendChild(e);
+ }
+ catch(ClassCastException e) {
+ logger.warning("Expected OPTICSModel, got: " + cluster.getModel().getClass().getSimpleName());
+ }
+ // Descend
+ final List<Cluster<OPTICSModel>> children = cluster.getChildren();
+ if(children != null) {
+ drawClusters(children, depth + 1);
+ }
+ }
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ */
+ private void addCSSClasses() {
+ // Class for the markers
+ if(!svgp.getCSSClassManager().contains(CSS_BRACKET)) {
+ final CSSClass cls = new CSSClass(this, CSS_BRACKET);
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, context.getStyleLibrary().getColor(StyleLibrary.PLOT));
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT));
+ svgp.addCSSClassOrLogError(cls);
+ }
+ }
+
+ /**
+ * Factory class for OPTICS plot selections.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses OPTICSPlotSelectionVisualization oneway - - «create»
+ */
+ public static class Factory extends AbstractVisFactory {
+ /**
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ */
+ public Factory() {
+ super();
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Iterator<OPTICSProjector<?>> ops = ResultUtil.filteredResults(result, OPTICSProjector.class);
+ for(OPTICSProjector<?> p : IterableUtil.fromIterator(ops)) {
+ final Clustering<OPTICSModel> ocl = findOPTICSClustering(baseResult);
+ if(ocl != null) {
+ final VisualizationTask task = new VisualizationTask(NAME, p, null, this);
+ task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE);
+ task.put(CLUSTERING, ocl);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ // TODO: also run when a new clustering is added, instead of just new projections?
+ }
+
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new OPTICSClusterVisualization<DoubleDistance>(task);
+ }
+
+ @Override
+ public boolean allowThumbnails(@SuppressWarnings("unused") VisualizationTask task) {
+ // Don't use thumbnails
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotCutVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotCutVisualization.java
new file mode 100644
index 00000000..9f2a5734
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotCutVisualization.java
@@ -0,0 +1,302 @@
+package de.lmu.ifi.dbs.elki.visualization.visualizers.optics;
+
+/*
+ This file is part of ELKI:
+ Environment for Developing KDD-Applications Supported by Index-Structures
+
+ Copyright (C) 2011
+ 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.Iterator;
+
+import org.apache.batik.util.SVG12Constants;
+import org.apache.batik.util.SVGConstants;
+import org.w3c.dom.Element;
+import org.w3c.dom.events.Event;
+import org.w3c.dom.svg.SVGPoint;
+
+import de.lmu.ifi.dbs.elki.data.Clustering;
+import de.lmu.ifi.dbs.elki.data.model.Model;
+import de.lmu.ifi.dbs.elki.distance.distancevalue.Distance;
+import de.lmu.ifi.dbs.elki.distance.distancevalue.DoubleDistance;
+import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
+import de.lmu.ifi.dbs.elki.result.Result;
+import de.lmu.ifi.dbs.elki.result.ResultUtil;
+import de.lmu.ifi.dbs.elki.result.optics.ClusterOrderResult;
+import de.lmu.ifi.dbs.elki.utilities.FormatUtil;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil;
+import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
+import de.lmu.ifi.dbs.elki.visualization.batikutil.DragableArea;
+import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
+import de.lmu.ifi.dbs.elki.visualization.opticsplot.OPTICSCut;
+import de.lmu.ifi.dbs.elki.visualization.projector.OPTICSProjector;
+import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisFactory;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;
+
+/**
+ * Visualizes a cut in an OPTICS Plot to select an Epsilon value and generate a
+ * new clustering result
+ *
+ * @author Heidi Kolb
+ *
+ * @apiviz.uses ClusterOrderResult oneway - 1 visualizes
+ * @apiviz.uses OPTICSPlot oneway - 1 visualizes
+ *
+ * @param <D> distance type
+ */
+public class OPTICSPlotCutVisualization<D extends Distance<D>> extends AbstractOPTICSVisualization<D> implements DragableArea.DragListener {
+ /**
+ * A short name characterizing this Visualizer.
+ */
+ private static final String NAME = "OPTICS Cut";
+
+ /**
+ * CSS-Styles
+ */
+ protected static final String CSS_LINE = "opticsPlotLine";
+
+ /**
+ * CSS-Styles
+ */
+ protected final static String CSS_EPSILON = "opticsPlotEpsilonValue";
+
+ /**
+ * The current epsilon value.
+ */
+ private double epsilon = 0.0;
+
+ /**
+ * Sensitive (clickable) area
+ */
+ private DragableArea eventarea = null;
+
+ /**
+ * The label element
+ */
+ private Element elemText = null;
+
+ /**
+ * The line element
+ */
+ private Element elementLine = null;
+
+ /**
+ * The drag handle element
+ */
+ private Element elementPoint = null;
+
+ /**
+ * Constructor.
+ *
+ * @param task Task
+ */
+ public OPTICSPlotCutVisualization(VisualizationTask task) {
+ super(task);
+ }
+
+ @Override
+ protected void redraw() {
+ incrementalRedraw();
+ }
+
+ @Override
+ protected void incrementalRedraw() {
+ if(layer == null) {
+ makeLayerElement();
+ addCSSClasses();
+ }
+
+ // TODO make the number of digits configurable
+ final String label = (epsilon != 0.0) ? FormatUtil.format(epsilon, 4) : "";
+ // compute absolute y-value of bar
+ final double yAct = plotheight - getYFromEpsilon(epsilon);
+
+ if(elemText == null) {
+ elemText = svgp.svgText(StyleLibrary.SCALE * 1.05, yAct, label);
+ SVGUtil.setAtt(elemText, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_EPSILON);
+ layer.appendChild(elemText);
+ }
+ else {
+ elemText.setTextContent(label);
+ SVGUtil.setAtt(elemText, SVGConstants.SVG_Y_ATTRIBUTE, yAct);
+ }
+
+ // line and handle
+ if(elementLine == null) {
+ elementLine = svgp.svgLine(0, yAct, StyleLibrary.SCALE * 1.04, yAct);
+ SVGUtil.addCSSClass(elementLine, CSS_LINE);
+ layer.appendChild(elementLine);
+ }
+ else {
+ SVGUtil.setAtt(elementLine, SVG12Constants.SVG_Y1_ATTRIBUTE, yAct);
+ SVGUtil.setAtt(elementLine, SVG12Constants.SVG_Y2_ATTRIBUTE, yAct);
+ }
+ if(elementPoint == null) {
+ elementPoint = svgp.svgCircle(StyleLibrary.SCALE * 1.04, yAct, StyleLibrary.SCALE * 0.004);
+ SVGUtil.addCSSClass(elementPoint, CSS_LINE);
+ layer.appendChild(elementPoint);
+ }
+ else {
+ SVGUtil.setAtt(elementPoint, SVG12Constants.SVG_CY_ATTRIBUTE, yAct);
+ }
+
+ if(eventarea == null) {
+ eventarea = new DragableArea(svgp, StyleLibrary.SCALE, 0, StyleLibrary.SCALE * 0.1, plotheight, this);
+ layer.appendChild(eventarea.getElement());
+ }
+ }
+
+ @Override
+ public void destroy() {
+ super.destroy();
+ eventarea.destroy();
+ }
+
+ /**
+ * Get epsilon from y-value
+ *
+ * @param y y-Value
+ * @return epsilon
+ */
+ protected double getEpsilonFromY(double y) {
+ if(y < 0) {
+ y = 0;
+ }
+ if(y > plotheight) {
+ y = plotheight;
+ }
+ return optics.getOPTICSPlot(context).getScale().getUnscaled(y / plotheight);
+ }
+
+ /**
+ * Get y-value from epsilon
+ *
+ * @param epsilon epsilon
+ * @return y-Value
+ */
+ protected double getYFromEpsilon(double epsilon) {
+ double y = optics.getOPTICSPlot(context).getScale().getScaled(epsilon) * plotheight;
+ if(y < 0) {
+ y = 0;
+ }
+ if(y > plotheight) {
+ y = plotheight;
+ }
+ return y;
+ }
+
+ @Override
+ public boolean startDrag(SVGPoint start, @SuppressWarnings("unused") Event evt) {
+ epsilon = getEpsilonFromY(plotheight - start.getY());
+ // opvis.unsetEpsilonExcept(this);
+ synchronizedRedraw();
+ return true;
+ }
+
+ @Override
+ public boolean duringDrag(@SuppressWarnings("unused") SVGPoint start, SVGPoint end, @SuppressWarnings("unused") Event evt, boolean inside) {
+ if(inside) {
+ epsilon = getEpsilonFromY(plotheight - end.getY());
+ }
+ // opvis.unsetEpsilonExcept(this);
+ synchronizedRedraw();
+ return true;
+ }
+
+ @Override
+ public boolean endDrag(@SuppressWarnings("unused") SVGPoint start, SVGPoint end, @SuppressWarnings("unused") Event evt, boolean inside) {
+ if(inside) {
+ epsilon = getEpsilonFromY(plotheight - end.getY());
+ // opvis.unsetEpsilonExcept(this);
+
+ // FIXME: replace an existing optics cut result!
+ final ClusterOrderResult<D> order = optics.getResult();
+ Clustering<Model> cl = OPTICSCut.makeOPTICSCut(order, optics.getOPTICSPlot(context).getDistanceAdapter(), epsilon);
+ order.addChildResult(cl);
+ }
+ context.resultChanged(this.task);
+ // synchronizedRedraw();
+ return true;
+ }
+
+ /**
+ * Reset the epsilon value.
+ */
+ public void unsetEpsilon() {
+ epsilon = 0.0;
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ */
+ private void addCSSClasses() {
+ // Class for the epsilon-value
+ if(!svgp.getCSSClassManager().contains(CSS_EPSILON)) {
+ final CSSClass label = new CSSClass(svgp, CSS_EPSILON);
+ label.setStatement(SVGConstants.CSS_FILL_PROPERTY, context.getStyleLibrary().getTextColor(StyleLibrary.AXIS_LABEL));
+ label.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, context.getStyleLibrary().getFontFamily(StyleLibrary.AXIS_LABEL));
+ label.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, context.getStyleLibrary().getTextSize(StyleLibrary.AXIS_LABEL));
+ svgp.addCSSClassOrLogError(label);
+ }
+ // Class for the epsilon cut line
+ if(!svgp.getCSSClassManager().contains(CSS_LINE)) {
+ final CSSClass lcls = new CSSClass(svgp, CSS_LINE);
+ lcls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, context.getStyleLibrary().getColor(StyleLibrary.PLOT));
+ lcls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, 0.5 * context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT));
+ svgp.addCSSClassOrLogError(lcls);
+ }
+ }
+
+ /**
+ * Factory class
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses OPTICSPlotCutVisualization oneway - - «create»
+ */
+ public static class Factory extends AbstractVisFactory {
+ public Factory() {
+ super();
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Iterator<OPTICSProjector<?>> ops = ResultUtil.filteredResults(result, OPTICSProjector.class);
+ for(OPTICSProjector<?> p : IterableUtil.fromIterator(ops)) {
+ final VisualizationTask task = new VisualizationTask(NAME, p, null, this);
+ task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new OPTICSPlotCutVisualization<DoubleDistance>(task);
+ }
+
+ @Override
+ public boolean allowThumbnails(@SuppressWarnings("unused") VisualizationTask task) {
+ // Don't use thumbnails
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotSelectionVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotSelectionVisualization.java
new file mode 100644
index 00000000..b75bd3b5
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotSelectionVisualization.java
@@ -0,0 +1,374 @@
+package de.lmu.ifi.dbs.elki.visualization.visualizers.optics;
+
+/*
+ This file is part of ELKI:
+ Environment for Developing KDD-Applications Supported by Index-Structures
+
+ Copyright (C) 2011
+ 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.Iterator;
+import java.util.List;
+
+import org.apache.batik.dom.events.DOMMouseEvent;
+import org.apache.batik.util.SVGConstants;
+import org.w3c.dom.Element;
+import org.w3c.dom.events.Event;
+import org.w3c.dom.svg.SVGPoint;
+
+import de.lmu.ifi.dbs.elki.database.ids.DBID;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDUtil;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDs;
+import de.lmu.ifi.dbs.elki.database.ids.HashSetModifiableDBIDs;
+import de.lmu.ifi.dbs.elki.distance.distancevalue.Distance;
+import de.lmu.ifi.dbs.elki.distance.distancevalue.DoubleDistance;
+import de.lmu.ifi.dbs.elki.logging.Logging;
+import de.lmu.ifi.dbs.elki.result.DBIDSelection;
+import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
+import de.lmu.ifi.dbs.elki.result.Result;
+import de.lmu.ifi.dbs.elki.result.ResultUtil;
+import de.lmu.ifi.dbs.elki.result.SelectionResult;
+import de.lmu.ifi.dbs.elki.result.optics.ClusterOrderEntry;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil;
+import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
+import de.lmu.ifi.dbs.elki.visualization.batikutil.DragableArea;
+import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
+import de.lmu.ifi.dbs.elki.visualization.projector.OPTICSProjector;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisFactory;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;
+
+/**
+ * Handle the marker in an OPTICS plot.
+ *
+ * @author Heidi Kolb
+ *
+ * @apiviz.uses ClusterOrderResult oneway - 1
+ * @apiviz.uses OPTICSPlot oneway - 1
+ * @apiviz.uses DBIDSelection oneway - 1 visualizes
+ *
+ * @param <D> distance type
+ */
+public class OPTICSPlotSelectionVisualization<D extends Distance<D>> extends AbstractOPTICSVisualization<D> implements DragableArea.DragListener {
+ /**
+ * The logger for this class.
+ */
+ private static final Logging logger = Logging.getLogger(OPTICSPlotSelectionVisualization.class);
+
+ /**
+ * A short name characterizing this Visualizer.
+ */
+ private static final String NAME = "OPTICS Selection";
+
+ /**
+ * CSS class for markers
+ */
+ protected static final String CSS_MARKER = "opticsPlotMarker";
+
+ /**
+ * CSS class for markers
+ */
+ protected static final String CSS_RANGEMARKER = "opticsPlotRangeMarker";
+
+ /**
+ * Input modes
+ *
+ * @apiviz.exclude
+ */
+ // TODO: Refactor all Mode copies into a shared class?
+ private enum Mode {
+ REPLACE, ADD, INVERT
+ }
+
+ /**
+ * Element for the events
+ */
+ private Element etag;
+
+ /**
+ * Element for the marker
+ */
+ private Element mtag;
+
+ /**
+ * Constructor.
+ *
+ * @param task Visualization task
+ */
+ public OPTICSPlotSelectionVisualization(VisualizationTask task) {
+ super(task);
+ context.addResultListener(this);
+ incrementalRedraw();
+ }
+
+ @Override
+ protected void redraw() {
+ makeLayerElement();
+ addCSSClasses();
+
+ mtag = svgp.svgElement(SVGConstants.SVG_G_TAG);
+ addMarker();
+
+ DragableArea drag = new DragableArea(svgp, 0 - plotwidth * 0.1, 0, plotwidth * 1.1, plotheight, this);
+ etag = drag.getElement();
+ // mtag first, etag must be the top Element
+ layer.appendChild(mtag);
+ layer.appendChild(etag);
+ }
+
+ /**
+ * Add marker for the selected IDs to mtag
+ */
+ public void addMarker() {
+ List<ClusterOrderEntry<D>> order = getClusterOrder();
+ // TODO: replace mtag!
+ DBIDSelection selContext = context.getSelection();
+ if(selContext != null) {
+ DBIDs selection = DBIDUtil.ensureSet(selContext.getSelectedIds());
+
+ final double width = plotwidth / order.size();
+ int begin = -1;
+ for(int j = 0; j < order.size(); j++) {
+ DBID id = order.get(j).getID();
+ if(selection.contains(id)) {
+ if(begin == -1) {
+ begin = j;
+ }
+ }
+ else {
+ if(begin != -1) {
+ Element marker = addMarkerRect(begin * width, (j - begin) * width);
+ SVGUtil.addCSSClass(marker, CSS_MARKER);
+ mtag.appendChild(marker);
+ begin = -1;
+ }
+ }
+ }
+ // tail
+ if(begin != -1) {
+ Element marker = addMarkerRect(begin * width, (order.size() - begin) * width);
+ SVGUtil.addCSSClass(marker, CSS_MARKER);
+ mtag.appendChild(marker);
+ }
+ }
+ }
+
+ /**
+ * Create a rectangle as marker (Marker higher than plot!)
+ *
+ * @param x1 X-Value for the marker
+ * @param width Width of an entry
+ * @return SVG-Element svg-rectangle
+ */
+ public Element addMarkerRect(double x1, double width) {
+ return svgp.svgRect(x1, 0, width, plotheight);
+ }
+
+ @Override
+ public boolean startDrag(SVGPoint startPoint, @SuppressWarnings("unused") Event evt) {
+ List<ClusterOrderEntry<D>> order = getClusterOrder();
+ int mouseActIndex = getSelectedIndex(order, startPoint);
+ if(mouseActIndex >= 0 && mouseActIndex < order.size()) {
+ double width = plotwidth / order.size();
+ double x1 = mouseActIndex * width;
+ Element marker = addMarkerRect(x1, width);
+ SVGUtil.setCSSClass(marker, CSS_RANGEMARKER);
+ mtag.appendChild(marker);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean duringDrag(SVGPoint startPoint, SVGPoint dragPoint, @SuppressWarnings("unused") Event evt, @SuppressWarnings("unused") boolean inside) {
+ List<ClusterOrderEntry<D>> order = getClusterOrder();
+ int mouseDownIndex = getSelectedIndex(order, startPoint);
+ int mouseActIndex = getSelectedIndex(order, dragPoint);
+ final int begin = Math.max(Math.min(mouseDownIndex, mouseActIndex), 0);
+ final int end = Math.min(Math.max(mouseDownIndex, mouseActIndex), order.size());
+ double width = plotwidth / order.size();
+ double x1 = begin * width;
+ double x2 = (end * width) + width;
+ mtag.removeChild(mtag.getLastChild());
+ Element marker = addMarkerRect(x1, x2 - x1);
+ SVGUtil.setCSSClass(marker, CSS_RANGEMARKER);
+ mtag.appendChild(marker);
+ return true;
+ }
+
+ @Override
+ public boolean endDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, @SuppressWarnings("unused") boolean inside) {
+ List<ClusterOrderEntry<D>> order = getClusterOrder();
+ int mouseDownIndex = getSelectedIndex(order, startPoint);
+ int mouseActIndex = getSelectedIndex(order, dragPoint);
+ Mode mode = getInputMode(evt);
+ final int begin = Math.max(Math.min(mouseDownIndex, mouseActIndex), 0);
+ final int end = Math.min(Math.max(mouseDownIndex, mouseActIndex), order.size());
+ updateSelection(mode, begin, end);
+ return true;
+ }
+
+ /**
+ * Get the current input mode, on each mouse event.
+ *
+ * @param evt Mouse event.
+ * @return Input mode
+ */
+ private Mode getInputMode(Event evt) {
+ if(evt instanceof DOMMouseEvent) {
+ DOMMouseEvent domme = (DOMMouseEvent) evt;
+ // TODO: visual indication of mode possible?
+ if(domme.getShiftKey()) {
+ return Mode.ADD;
+ }
+ else if(domme.getCtrlKey()) {
+ return Mode.INVERT;
+ }
+ else {
+ return Mode.REPLACE;
+ }
+ }
+ // Default mode is replace.
+ return Mode.REPLACE;
+ }
+
+ /**
+ * Gets the Index of the ClusterOrderEntry where the event occurred
+ *
+ * @param order List of ClusterOrderEntries
+ * @param cPt clicked point
+ * @return Index of the object
+ */
+ private int getSelectedIndex(List<ClusterOrderEntry<D>> order, SVGPoint cPt) {
+ int mouseActIndex = (int) ((cPt.getX() / plotwidth) * order.size());
+ return mouseActIndex;
+ }
+
+ /**
+ * Updates the selection for the given ClusterOrderEntry.
+ *
+ * @param mode Input mode
+ * @param begin first index to select
+ * @param end last index to select
+ */
+ protected void updateSelection(Mode mode, int begin, int end) {
+ List<ClusterOrderEntry<D>> order = getClusterOrder();
+ if(begin < 0 || begin > end || end >= order.size()) {
+ logger.warning("Invalid range in updateSelection: " + begin + " .. " + end);
+ return;
+ }
+
+ DBIDSelection selContext = context.getSelection();
+ HashSetModifiableDBIDs selection;
+ if(selContext == null || mode == Mode.REPLACE) {
+ selection = DBIDUtil.newHashSet();
+ }
+ else {
+ selection = DBIDUtil.newHashSet(selContext.getSelectedIds());
+ }
+
+ for(int i = begin; i <= end; i++) {
+ DBID id = order.get(i).getID();
+ if(mode == Mode.INVERT) {
+ if(!selection.contains(id)) {
+ selection.add(id);
+ }
+ else {
+ selection.remove(id);
+ }
+ }
+ else {
+ // In REPLACE and ADD, add objects.
+ // The difference was done before by not re-using the selection.
+ // Since we are using a set, we can just add in any case.
+ selection.add(id);
+ }
+ }
+ context.setSelection(new DBIDSelection(selection));
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ */
+ private void addCSSClasses() {
+ // Class for the markers
+ if(!svgp.getCSSClassManager().contains(CSS_MARKER)) {
+ final CSSClass cls = new CSSClass(this, CSS_MARKER);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_BLUE_VALUE);
+ cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, "0.2");
+ svgp.addCSSClassOrLogError(cls);
+ }
+
+ // Class for the range marking
+ if(!svgp.getCSSClassManager().contains(CSS_RANGEMARKER)) {
+ final CSSClass rcls = new CSSClass(this, CSS_RANGEMARKER);
+ rcls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_RED_VALUE);
+ rcls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, "0.2");
+ svgp.addCSSClassOrLogError(rcls);
+ }
+ }
+
+ @Override
+ public void resultChanged(Result current) {
+ if(current instanceof SelectionResult) {
+ synchronizedRedraw();
+ return;
+ }
+ super.resultChanged(current);
+ }
+
+ /**
+ * Factory class for OPTICS plot selections.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses OPTICSPlotSelectionVisualization oneway - - «create»
+ */
+ public static class Factory extends AbstractVisFactory {
+ /**
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ */
+ public Factory() {
+ super();
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Iterator<OPTICSProjector<?>> ops = ResultUtil.filteredResults(result, OPTICSProjector.class);
+ for(OPTICSProjector<?> p : IterableUtil.fromIterator(ops)) {
+ final VisualizationTask task = new VisualizationTask(NAME, p, null, this);
+ task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new OPTICSPlotSelectionVisualization<DoubleDistance>(task);
+ }
+
+ @Override
+ public boolean allowThumbnails(@SuppressWarnings("unused") VisualizationTask task) {
+ // Don't use thumbnails
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotVisualizer.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotVisualizer.java
new file mode 100644
index 00000000..4af5d466
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotVisualizer.java
@@ -0,0 +1,146 @@
+package de.lmu.ifi.dbs.elki.visualization.visualizers.optics;
+
+/*
+ This file is part of ELKI:
+ Environment for Developing KDD-Applications Supported by Index-Structures
+
+ Copyright (C) 2011
+ 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.io.File;
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.apache.batik.util.SVGConstants;
+import org.w3c.dom.Element;
+
+import de.lmu.ifi.dbs.elki.distance.distancevalue.Distance;
+import de.lmu.ifi.dbs.elki.distance.distancevalue.DoubleDistance;
+import de.lmu.ifi.dbs.elki.logging.LoggingUtil;
+import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
+import de.lmu.ifi.dbs.elki.result.Result;
+import de.lmu.ifi.dbs.elki.result.ResultUtil;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil;
+import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
+import de.lmu.ifi.dbs.elki.visualization.css.CSSClassManager.CSSNamingConflict;
+import de.lmu.ifi.dbs.elki.visualization.opticsplot.OPTICSPlot;
+import de.lmu.ifi.dbs.elki.visualization.projector.OPTICSProjector;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGSimpleLinearAxis;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisFactory;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;
+
+/**
+ * Visualize an OPTICS result by constructing an OPTICS plot for it.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.has OPTICSPlot oneway - 1 visualizes
+ * @apiviz.has ClusterOrderResult oneway - 1 visualizes
+ *
+ * @param <D> Distance type
+ */
+public class OPTICSPlotVisualizer<D extends Distance<D>> extends AbstractOPTICSVisualization<D> {
+ /**
+ * Name for this visualizer.
+ */
+ private static final String NAME = "OPTICS Plot";
+
+ /**
+ * Constructor.
+ *
+ * @param task Visualization task
+ */
+ public OPTICSPlotVisualizer(VisualizationTask task) {
+ super(task);
+ }
+
+ @Override
+ protected void redraw() {
+ makeLayerElement();
+ // addCSSClasses();
+
+ OPTICSPlot<D> opticsplot = optics.getOPTICSPlot(context);
+ File imgfile = null;
+ try {
+ imgfile = opticsplot.getAsTempFile();
+ }
+ catch(IOException e) {
+ LoggingUtil.exception("Could not generate OPTICS plot.", e);
+ }
+
+ Element itag = svgp.svgElement(SVGConstants.SVG_IMAGE_TAG);
+ SVGUtil.setAtt(itag, SVGConstants.SVG_IMAGE_RENDERING_ATTRIBUTE, SVGConstants.SVG_OPTIMIZE_SPEED_VALUE);
+ SVGUtil.setAtt(itag, SVGConstants.SVG_X_ATTRIBUTE, 0);
+ SVGUtil.setAtt(itag, SVGConstants.SVG_Y_ATTRIBUTE, 0);
+ SVGUtil.setAtt(itag, SVGConstants.SVG_WIDTH_ATTRIBUTE, plotwidth);
+ SVGUtil.setAtt(itag, SVGConstants.SVG_HEIGHT_ATTRIBUTE, plotheight);
+ itag.setAttributeNS(SVGConstants.XLINK_NAMESPACE_URI, SVGConstants.XLINK_HREF_QNAME, imgfile.toURI().toString());
+
+ layer.appendChild(itag);
+
+ try {
+ SVGSimpleLinearAxis.drawAxis(svgp, layer, opticsplot.getScale(), 0, plotheight, 0, 0, true, false, context.getStyleLibrary());
+ SVGSimpleLinearAxis.drawAxis(svgp, layer, opticsplot.getScale(), plotwidth, plotheight, plotwidth, 0, true, true, context.getStyleLibrary());
+ }
+ catch(CSSNamingConflict e) {
+ LoggingUtil.exception("CSS naming conflict for axes on OPTICS plot", e);
+ }
+ }
+
+ /**
+ * Factory class for OPTICS plot.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses OPTICSPlotVisualizer oneway - - «create»
+ */
+ public static class Factory extends AbstractVisFactory {
+ /**
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ */
+ public Factory() {
+ super();
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Iterator<OPTICSProjector<?>> ops = ResultUtil.filteredResults(result, OPTICSProjector.class);
+ for(OPTICSProjector<?> p : IterableUtil.fromIterator(ops)) {
+ // Add plots, attach visualizer
+ final VisualizationTask task = new VisualizationTask(NAME, p, null, this);
+ task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_STATIC);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new OPTICSPlotVisualizer<DoubleDistance>(task);
+ }
+
+ @Override
+ public boolean allowThumbnails(@SuppressWarnings("unused") VisualizationTask task) {
+ // Don't use thumbnails
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSSteepAreaVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSSteepAreaVisualization.java
new file mode 100644
index 00000000..f9430145
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSSteepAreaVisualization.java
@@ -0,0 +1,223 @@
+package de.lmu.ifi.dbs.elki.visualization.visualizers.optics;
+
+/*
+ This file is part of ELKI:
+ Environment for Developing KDD-Applications Supported by Index-Structures
+
+ Copyright (C) 2011
+ 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.awt.Color;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.batik.util.SVGConstants;
+import org.w3c.dom.Element;
+
+import de.lmu.ifi.dbs.elki.algorithm.clustering.OPTICSXi;
+import de.lmu.ifi.dbs.elki.algorithm.clustering.OPTICSXi.SteepAreaResult;
+import de.lmu.ifi.dbs.elki.distance.distancevalue.Distance;
+import de.lmu.ifi.dbs.elki.distance.distancevalue.DoubleDistance;
+import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
+import de.lmu.ifi.dbs.elki.result.Result;
+import de.lmu.ifi.dbs.elki.result.ResultUtil;
+import de.lmu.ifi.dbs.elki.result.SelectionResult;
+import de.lmu.ifi.dbs.elki.result.optics.ClusterOrderEntry;
+import de.lmu.ifi.dbs.elki.result.optics.ClusterOrderResult;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil;
+import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
+import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
+import de.lmu.ifi.dbs.elki.visualization.opticsplot.OPTICSDistanceAdapter;
+import de.lmu.ifi.dbs.elki.visualization.opticsplot.OPTICSPlot;
+import de.lmu.ifi.dbs.elki.visualization.projector.OPTICSProjector;
+import de.lmu.ifi.dbs.elki.visualization.scales.LinearScale;
+import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisFactory;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;
+
+/**
+ * Visualize the steep areas found in an OPTICS plot
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.uses
+ * de.lmu.ifi.dbs.elki.algorithm.clustering.OPTICSXi.SteepAreaResult
+ */
+public class OPTICSSteepAreaVisualization<D extends Distance<D>> extends AbstractOPTICSVisualization<D> {
+ /**
+ * A short name characterizing this Visualizer.
+ */
+ private static final String NAME = "OPTICS Steep Areas";
+
+ /**
+ * CSS class for markers
+ */
+ protected static final String CSS_STEEP_UP = "opticsSteepUp";
+
+ /**
+ * CSS class for markers
+ */
+ protected static final String CSS_STEEP_DOWN = "opticsSteepDown";
+
+ /**
+ * Our clustering
+ */
+ OPTICSXi.SteepAreaResult areas;
+
+ /**
+ * Constructor.
+ *
+ * @param task Visualization task
+ */
+ public OPTICSSteepAreaVisualization(VisualizationTask task) {
+ super(task);
+ this.areas = findSteepAreaResult(this.optics.getResult());
+ context.addResultListener(this);
+ incrementalRedraw();
+ }
+
+ /**
+ * Find the OPTICS clustering child of a cluster order.
+ *
+ * @param co Cluster order
+ * @return OPTICS clustering
+ */
+ protected static OPTICSXi.SteepAreaResult findSteepAreaResult(ClusterOrderResult<?> co) {
+ for(Result r : co.getHierarchy().getChildren(co)) {
+ if(OPTICSXi.SteepAreaResult.class.isInstance(r)) {
+ return (OPTICSXi.SteepAreaResult) r;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void redraw() {
+ makeLayerElement();
+ addCSSClasses();
+
+ final OPTICSPlot<D> opticsplot = optics.getOPTICSPlot(context);
+ final List<ClusterOrderEntry<D>> co = getClusterOrder();
+ final OPTICSDistanceAdapter<D> adapter = opticsplot.getDistanceAdapter();
+ final LinearScale scale = opticsplot.getScale();
+
+ for(OPTICSXi.SteepArea area : areas) {
+ final int st = area.getStartIndex();
+ final int en = area.getEndIndex();
+ // Note: make sure we are using doubles!
+ final double x1 = (st + .25) / co.size();
+ final double x2 = (en + .75) / co.size();
+ final double d1 = adapter.getDoubleForEntry(co.get(st));
+ final double d2 = adapter.getDoubleForEntry(co.get(en));
+ final double y1 = (!Double.isInfinite(d1) && !Double.isNaN(d1)) ? (1. - scale.getScaled(d1)) : 0.;
+ final double y2 = (!Double.isInfinite(d2) && !Double.isNaN(d2)) ? (1. - scale.getScaled(d2)) : 0.;
+ Element e = svgp.svgLine(plotwidth * x1, plotheight * y1, plotwidth * x2, plotheight * y2);
+ if(area instanceof OPTICSXi.SteepDownArea) {
+ SVGUtil.addCSSClass(e, CSS_STEEP_DOWN);
+ }
+ else {
+ SVGUtil.addCSSClass(e, CSS_STEEP_UP);
+ }
+ layer.appendChild(e);
+ }
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ */
+ private void addCSSClasses() {
+ // Class for the markers
+ if(!svgp.getCSSClassManager().contains(CSS_STEEP_DOWN)) {
+ final CSSClass cls = new CSSClass(this, CSS_STEEP_DOWN);
+ Color color = SVGUtil.stringToColor(context.getStyleLibrary().getColor(StyleLibrary.PLOT));
+ if(color == null) {
+ color = Color.BLACK;
+ }
+ color = new Color((int) (color.getRed() * 0.8), (int) (color.getGreen() * 0.8 + 0.2 * 256), (int) (color.getBlue() * 0.8));
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGUtil.colorToString(color));
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT));
+ svgp.addCSSClassOrLogError(cls);
+ }
+ if(!svgp.getCSSClassManager().contains(CSS_STEEP_UP)) {
+ final CSSClass cls = new CSSClass(this, CSS_STEEP_UP);
+ Color color = SVGUtil.stringToColor(context.getStyleLibrary().getColor(StyleLibrary.PLOT));
+ if(color == null) {
+ color = Color.BLACK;
+ }
+ color = new Color((int) (color.getRed() * 0.8 + 0.2 * 256), (int) (color.getGreen() * 0.8), (int) (color.getBlue() * 0.8));
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGUtil.colorToString(color));
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT));
+ svgp.addCSSClassOrLogError(cls);
+ }
+ }
+
+ @Override
+ public void resultChanged(Result current) {
+ if(current instanceof SelectionResult) {
+ synchronizedRedraw();
+ return;
+ }
+ super.resultChanged(current);
+ }
+
+ /**
+ * Factory class for OPTICS plot selections.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses OPTICSPlotSelectionVisualization oneway - - «create»
+ */
+ public static class Factory extends AbstractVisFactory {
+ /**
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ */
+ public Factory() {
+ super();
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Iterator<OPTICSProjector<?>> ops = ResultUtil.filteredResults(result, OPTICSProjector.class);
+ for(OPTICSProjector<?> p : IterableUtil.fromIterator(ops)) {
+ final SteepAreaResult steep = findSteepAreaResult(p.getResult());
+ if(steep != null) {
+ final VisualizationTask task = new VisualizationTask(NAME, p, null, this);
+ task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE);
+ task.put(VisualizationTask.META_VISIBLE_DEFAULT, false);
+ baseResult.getHierarchy().add(p, task);
+ baseResult.getHierarchy().add(steep, task);
+ }
+ }
+ }
+
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new OPTICSSteepAreaVisualization<DoubleDistance>(task);
+ }
+
+ @Override
+ public boolean allowThumbnails(@SuppressWarnings("unused") VisualizationTask task) {
+ // Don't use thumbnails
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/package-info.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/package-info.java
new file mode 100644
index 00000000..05b02142
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/package-info.java
@@ -0,0 +1,26 @@
+/**
+ * <p>Visualizers that do work on OPTICS plots</p>
+ */
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+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/>.
+*/
+package de.lmu.ifi.dbs.elki.visualization.visualizers.optics; \ No newline at end of file