diff options
Diffstat (limited to 'src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics')
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 |