diff options
author | Andrej Shadura <andrewsh@debian.org> | 2019-03-09 22:30:32 +0000 |
---|---|---|
committer | Andrej Shadura <andrewsh@debian.org> | 2019-03-09 22:30:32 +0000 |
commit | c36aa2a8fd31ca5e225ff30278e910070cd2c8c1 (patch) | |
tree | bdfe1a5ccb57999d4d664a2a44121a78c88b19d4 /src/de/lmu/ifi/dbs/elki/visualization/visualizers | |
parent | 89aa1958dbaf9052da0c24706308a2ef8cefa96e (diff) |
Import Upstream version 0.5.0~beta2
Diffstat (limited to 'src/de/lmu/ifi/dbs/elki/visualization/visualizers')
81 files changed, 5566 insertions, 1594 deletions
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/AbstractVisFactory.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/AbstractVisFactory.java index 4b017fac..40ae5cdf 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/AbstractVisFactory.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/AbstractVisFactory.java @@ -4,7 +4,7 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers; This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -35,6 +35,11 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualizati */ public abstract class AbstractVisFactory implements VisFactory { /** + * Mask for redrawing thumbnails + */ + protected int thumbmask = 0; + + /** * Constructor. */ protected AbstractVisFactory() { @@ -46,7 +51,7 @@ public abstract class AbstractVisFactory implements VisFactory { // Is this a thumbnail request? Boolean isthumb = task.get(VisualizationTask.THUMBNAIL, Boolean.class); if (isthumb != null && isthumb.booleanValue() && allowThumbnails(task)) { - return new ThumbnailVisualization(this, task, 0); + return new ThumbnailVisualization(this, task, thumbmask); } return makeVisualization(task); } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/AbstractVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/AbstractVisualization.java index dab1c6dd..bbff0117 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/AbstractVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/AbstractVisualization.java @@ -4,7 +4,7 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers; This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -25,21 +25,20 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers; import org.w3c.dom.Element; +import de.lmu.ifi.dbs.elki.database.datastore.DataStoreEvent; +import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener; import de.lmu.ifi.dbs.elki.result.Result; import de.lmu.ifi.dbs.elki.result.ResultListener; import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; import de.lmu.ifi.dbs.elki.visualization.VisualizerContext; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; -import de.lmu.ifi.dbs.elki.visualization.visualizers.events.ContextChangeListener; -import de.lmu.ifi.dbs.elki.visualization.visualizers.events.ContextChangedEvent; -import de.lmu.ifi.dbs.elki.visualization.visualizers.events.ResizedEvent; /** * Abstract base class for visualizations. * * @author Erich Schubert */ -public abstract class AbstractVisualization implements Visualization, ContextChangeListener, ResultListener { +public abstract class AbstractVisualization implements Visualization, ResultListener { /** * The visualization task we do. */ @@ -76,12 +75,18 @@ public abstract class AbstractVisualization implements Visualization, ContextCha this.context = task.getContext(); this.svgp = task.getPlot(); this.layer = null; + // Note: we do not auto-add listeners, as we don't know what kind of + // listeners a visualizer needs, and the visualizer might need to do some initialization first } @Override public void destroy() { - context.removeContextChangeListener(this); + // Always unregister listeners, as this is easy to forget otherwise + // TODO: remove destroy() overrides that are redundant? context.removeResultListener(this); + if (this instanceof DataStoreListener) { + context.removeDataStoreListener((DataStoreListener) this); + } } @Override @@ -110,26 +115,6 @@ public abstract class AbstractVisualization implements Visualization, ContextCha return task.getHeight(); } - @Override - public void contextChanged(ContextChangedEvent e) { - if(testRedraw(e)) { - synchronizedRedraw(); - } - } - - /** - * Override this method to add additional redraw triggers! - * - * @param e Event - * @return Test result - */ - protected boolean testRedraw(ContextChangedEvent e) { - if(e instanceof ResizedEvent) { - return true; - } - return false; - } - /** * Trigger a redraw, but avoid excessive redraws. */ @@ -186,4 +171,16 @@ public abstract class AbstractVisualization implements Visualization, ContextCha public void resultRemoved(Result child, Result parent) { // Ignore by default. } + + /** + * Default implementation for + * {@link de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener#contentChanged} + * + * Not enabled or used by default, but saves redundant code. + * + * @param e Data store event + */ + public void contentChanged(DataStoreEvent e) { + synchronizedRedraw(); + } }
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/StaticVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/StaticVisualization.java index cfc653ef..3a4be45b 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/StaticVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/StaticVisualization.java @@ -4,7 +4,7 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers; This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/VisFactory.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/VisFactory.java index b89d1ae0..eb675951 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/VisFactory.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/VisFactory.java @@ -4,7 +4,7 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers; This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/Visualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/Visualization.java index d2dfb687..d2d1e5fe 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/Visualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/Visualization.java @@ -4,7 +4,7 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers; This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/VisualizerUtil.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/VisualizerUtil.java index 14984a5d..a3b0e3f4 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/VisualizerUtil.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/VisualizerUtil.java @@ -4,7 +4,7 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers; This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -29,6 +29,7 @@ import de.lmu.ifi.dbs.elki.data.NumberVector; import de.lmu.ifi.dbs.elki.data.type.SimpleTypeInformation; import de.lmu.ifi.dbs.elki.data.type.VectorFieldTypeInformation; import de.lmu.ifi.dbs.elki.database.relation.Relation; +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; @@ -53,9 +54,10 @@ public final class VisualizerUtil { */ public static VisualizerContext getContext(HierarchicalResult baseResult) { IterableIterator<VisualizerContext> iter = ResultUtil.filteredResults(baseResult, VisualizerContext.class); - if (iter.hasNext()) { + if(iter.hasNext()) { return iter.next(); - } else { + } + else { return null; } } @@ -79,6 +81,44 @@ public final class VisualizerUtil { } /** + * Utility function to change Visualizer visibility. + * + * @param task Visualization task + * @param visibility Visibility value + */ + public static void setVisible(VisualizationTask task, boolean visibility) { + VisualizerContext context = task.getContext(); + if(context != null) { + setVisible(context, task, visibility); + } + else { + LoggingUtil.warning("setVisible called without context in task.", new Throwable()); + } + } + + /** + * Utility function to change Visualizer visibility. + * + * @param context Visualization context + * @param task Visualization task + * @param visibility Visibility value + */ + public static void setVisible(VisualizerContext context, VisualizationTask task, boolean visibility) { + // Hide other tools + if(visibility && VisualizerUtil.isTool(task)) { + final Iterable<VisualizationTask> visualizers = ResultUtil.filteredResults(context.getResult(), VisualizationTask.class); + for(VisualizationTask other : visualizers) { + if(other != task && VisualizerUtil.isTool(other) && VisualizerUtil.isVisible(other)) { + other.put(VisualizationTask.META_VISIBLE, false); + context.getHierarchy().resultChanged(other); + } + } + } + task.put(VisualizationTask.META_VISIBLE, visibility); + context.getHierarchy().resultChanged(task); + } + + /** * Utility function to test for a visualizer being a "tool". * * @param vis Visualizer to test @@ -91,34 +131,68 @@ public final class VisualizerUtil { } /** + * Utility function to test for a visualizer having options. + * + * @param vis Visualizer to test + * @return true when it has options + */ + public static boolean hasOptions(VisualizationTask vis) { + // Currently enabled? + Boolean hasoptions = vis.getGenerics(VisualizationTask.META_HAS_OPTIONS, Boolean.class); + return (hasoptions != null) && hasoptions; + } + + /** * Filter for number vector field representations * * @param result Result to filter * @return Iterator over suitable representations */ // TODO: move to DatabaseUtil? - public static Iterator<Relation<? extends NumberVector<?, ?>>> iterateVectorFieldRepresentations(final Result result) { - final Iterator<Relation<?>> parent = ResultUtil.filteredResults(result, Relation.class); - return new AbstractFilteredIterator<Relation<?>, Relation<? extends NumberVector<?, ?>>>() { - @Override - protected Iterator<Relation<?>> getParentIterator() { - return parent; - } + public static IterableIterator<Relation<? extends NumberVector<?, ?>>> iterateVectorFieldRepresentations(final Result result) { + Iterator<Relation<?>> parent = ResultUtil.filteredResults(result, Relation.class); + return new VectorspaceIterator(parent); + } + + /** + * Iterate over vectorspace + * + * @author Erich Schubert + * + * @apiviz.exclude + */ + private static class VectorspaceIterator extends AbstractFilteredIterator<Relation<?>, Relation<? extends NumberVector<?, ?>>> implements IterableIterator<Relation<? extends NumberVector<?, ?>>> { + /** Parent iterator */ + private Iterator<Relation<?>> parent; - @SuppressWarnings("unchecked") - @Override - protected Relation<? extends NumberVector<?, ?>> testFilter(Relation<?> nextobj) { - final SimpleTypeInformation<?> type = nextobj.getDataTypeInformation(); - if(!NumberVector.class.isAssignableFrom(type.getRestrictionClass())) { - return null; - } - if(!(type instanceof VectorFieldTypeInformation)) { - return null; - } - return (Relation<? extends NumberVector<?, ?>>) nextobj; + public VectorspaceIterator(Iterator<Relation<?>> parent) { + super(); + this.parent = parent; + } + + @Override + protected Iterator<Relation<?>> getParentIterator() { + return parent; + } + + @SuppressWarnings("unchecked") + @Override + protected Relation<? extends NumberVector<?, ?>> testFilter(Relation<?> nextobj) { + final SimpleTypeInformation<?> type = nextobj.getDataTypeInformation(); + if(!NumberVector.class.isAssignableFrom(type.getRestrictionClass())) { + return null; } - }; - } + if(!(type instanceof VectorFieldTypeInformation)) { + return null; + } + return (Relation<? extends NumberVector<?, ?>>) nextobj; + } + + @Override + public Iterator<Relation<? extends NumberVector<?, ?>>> iterator() { + return this; + } + }; /** * Test whether a thumbnail is enabled for this visualizer. diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis1d/P1DVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/histogram/AbstractHistogramVisualization.java index 721d3432..95df9533 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis1d/P1DVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/histogram/AbstractHistogramVisualization.java @@ -1,10 +1,10 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis1d; +package de.lmu.ifi.dbs.elki.visualization.visualizers.histogram; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -23,6 +23,7 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.vis1d; along with this program. If not, see <http://www.gnu.org/licenses/>. */ +import de.lmu.ifi.dbs.elki.result.Result; import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; import de.lmu.ifi.dbs.elki.visualization.projections.Projection1D; import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisualization; @@ -35,7 +36,7 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisualization; * @apiviz.landmark * @apiviz.has Projection1D */ -public abstract class P1DVisualization extends AbstractVisualization { +public abstract class AbstractHistogramVisualization extends AbstractVisualization { /** * The current projection */ @@ -46,8 +47,16 @@ public abstract class P1DVisualization extends AbstractVisualization { * * @param task Visualization task */ - public P1DVisualization(VisualizationTask task) { + public AbstractHistogramVisualization(VisualizationTask task) { super(task); this.proj = task.getProj(); } + + @Override + public void resultChanged(Result current) { + super.resultChanged(current); + if(current == proj) { + synchronizedRedraw(); + } + } }
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis1d/P1DHistogramVisualizer.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/histogram/ColoredHistogramVisualizer.java index 7aee8108..d518d0cf 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis1d/P1DHistogramVisualizer.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/histogram/ColoredHistogramVisualizer.java @@ -1,10 +1,10 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis1d; +package de.lmu.ifi.dbs.elki.visualization.visualizers.histogram; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -23,50 +23,49 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.vis1d; along with this program. If not, see <http://www.gnu.org/licenses/>. */ -import java.util.ArrayList; 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.NumberVector; -import de.lmu.ifi.dbs.elki.data.model.Model; import de.lmu.ifi.dbs.elki.database.ids.DBID; import de.lmu.ifi.dbs.elki.database.relation.Relation; import de.lmu.ifi.dbs.elki.logging.LoggingUtil; -import de.lmu.ifi.dbs.elki.math.AggregatingHistogram; import de.lmu.ifi.dbs.elki.math.DoubleMinMax; +import de.lmu.ifi.dbs.elki.math.histograms.AggregatingHistogram; import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector; +import de.lmu.ifi.dbs.elki.math.scales.LinearScale; 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.DatabaseUtil; import de.lmu.ifi.dbs.elki.utilities.exceptions.ObjectNotFoundException; -import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil; +import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator; import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer; import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID; import de.lmu.ifi.dbs.elki.utilities.optionhandling.constraints.GreaterEqualConstraint; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Flag; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.IntParameter; -import de.lmu.ifi.dbs.elki.utilities.pairs.Pair; +import de.lmu.ifi.dbs.elki.utilities.pairs.DoubleObjPair; import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; import de.lmu.ifi.dbs.elki.visualization.colors.ColorLibrary; import de.lmu.ifi.dbs.elki.visualization.css.CSSClass; import de.lmu.ifi.dbs.elki.visualization.css.CSSClassManager.CSSNamingConflict; import de.lmu.ifi.dbs.elki.visualization.projections.Projection; import de.lmu.ifi.dbs.elki.visualization.projector.HistogramProjector; -import de.lmu.ifi.dbs.elki.visualization.scales.LinearScale; +import de.lmu.ifi.dbs.elki.visualization.style.ClassStylingPolicy; import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; +import de.lmu.ifi.dbs.elki.visualization.style.StyleResult; +import de.lmu.ifi.dbs.elki.visualization.style.StylingPolicy; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPath; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; 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; +import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualization; /** * Generates a SVG-Element containing a histogram representing the distribution @@ -80,16 +79,11 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; */ // FIXME: make non-static, react to database changes! // FIXME: cache histogram instead of recomputing it. -public class P1DHistogramVisualizer<NV extends NumberVector<NV, ?>> extends P1DVisualization { +public class ColoredHistogramVisualizer<NV extends NumberVector<NV, ?>> extends AbstractHistogramVisualization { /** * Name for this visualizer. */ - private static final String NAME = "Histogram"; - - /** - * Name for this visualizer. - */ - private static final String CNAME = "Cluster Histograms"; + private static final String CNAME = "Histograms"; /** * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc. @@ -112,9 +106,9 @@ public class P1DHistogramVisualizer<NV extends NumberVector<NV, ?>> extends P1DV private Relation<NV> relation; /** - * The clustering we visualize + * The style policy */ - private Clustering<Model> clustering; + private StyleResult style; /** * Constructor. @@ -123,12 +117,19 @@ public class P1DHistogramVisualizer<NV extends NumberVector<NV, ?>> extends P1DV * @param curves Curves flag * @param bins Number of bins */ - public P1DHistogramVisualizer(VisualizationTask task, boolean curves, int bins) { + public ColoredHistogramVisualizer(VisualizationTask task, boolean curves, int bins) { super(task); this.curves = curves; this.bins = bins; this.relation = task.getRelation(); - this.clustering = task.getResult(); + this.style = task.getResult(); + context.addResultListener(this); + } + + @Override + public void destroy() { + context.removeResultListener(this); + super.destroy(); } @Override @@ -141,14 +142,23 @@ public class P1DHistogramVisualizer<NV extends NumberVector<NV, ?>> extends P1DV final String transform = SVGUtil.makeMarginTransform(task.getWidth(), task.getHeight(), xsize, ysize, margin); SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); - final List<Cluster<Model>> allClusters = clustering != null ? clustering.getAllClusters() : null; - final int numc = allClusters != null ? allClusters.size() : 0; - - setupCSS(svgp, numc); + // Styling policy + final StylingPolicy spol = style.getStylingPolicy(); + final ClassStylingPolicy cspol; + if(spol instanceof ClassStylingPolicy) { + cspol = (ClassStylingPolicy) spol; + } + else { + cspol = null; + } + // TODO also use min style? + setupCSS(svgp, (cspol != null) ? cspol.getMaxStyle() : 0); - // Creating histograms + // Create histograms + final int off = (cspol != null) ? cspol.getMinStyle() : 0; + final int numc = (cspol != null) ? (cspol.getMaxStyle() - cspol.getMinStyle()) : 0; DoubleMinMax minmax = new DoubleMinMax(); - final double frac = 1. / relation.size(); + final double frac = 1. / relation.size(); // TODO: sampling? final int cols = numc + 1; AggregatingHistogram<double[], double[]> histogram = new AggregatingHistogram<double[], double[]>(bins, -.5, .5, new AggregatingHistogram.Adapter<double[], double[]>() { @Override @@ -165,12 +175,13 @@ public class P1DHistogramVisualizer<NV extends NumberVector<NV, ?>> extends P1DV } }); - if(allClusters != null) { - int clusterID = 0; - for(Cluster<Model> cluster : allClusters) { + if(cspol != null) { + for(int snum = 0; snum < numc; snum++) { double[] inc = new double[cols]; - inc[clusterID + 1] = frac; - for(DBID id : cluster.getIDs()) { + inc[0] = frac; + inc[snum + 1] = frac; + for(Iterator<DBID> iter = cspol.iterateClass(snum + off); iter.hasNext();) { + DBID id = iter.next(); try { double pos = proj.fastProjectDataToRenderSpace(relation.get(id)) / Projection.SCALE; histogram.aggregate(pos, inc); @@ -179,18 +190,19 @@ public class P1DHistogramVisualizer<NV extends NumberVector<NV, ?>> extends P1DV // Ignore. The object was probably deleted from the database } } - clusterID += 1; } } - // Actual data distribution. - double[] inc = new double[cols]; - inc[0] = frac; - for(DBID id : relation.iterDBIDs()) { - double pos = proj.fastProjectDataToRenderSpace(relation.get(id)) / Projection.SCALE; - histogram.aggregate(pos, inc); + else { + // Actual data distribution. + double[] inc = new double[cols]; + inc[0] = frac; + for(DBID id : relation.iterDBIDs()) { + double pos = proj.fastProjectDataToRenderSpace(relation.get(id)) / Projection.SCALE; + histogram.aggregate(pos, inc); + } } // for scaling, get the maximum occurring value in the bins: - for(Pair<Double, double[]> bin : histogram) { + for(DoubleObjPair<double[]> bin : histogram) { for(double val : bin.second) { minmax.put(val); } @@ -199,9 +211,9 @@ public class P1DHistogramVisualizer<NV extends NumberVector<NV, ?>> extends P1DV LinearScale yscale = new LinearScale(0, minmax.getMax()); LinearScale xscale = new LinearScale(histogram.getCoverMinimum(), histogram.getCoverMaximum()); - // Axis. TODO: Use AxisVisualizer for this? + // Axis. TODO: Add an AxisVisualizer for this? try { - SVGSimpleLinearAxis.drawAxis(svgp, layer, yscale, 0, ysize, 0, 0, true, false, context.getStyleLibrary()); + SVGSimpleLinearAxis.drawAxis(svgp, layer, yscale, 0, ysize, 0, 0, SVGSimpleLinearAxis.LabelStyle.LEFTHAND, context.getStyleLibrary()); // draw axes that are non-trivial final int dimensionality = DatabaseUtil.dimensionality(relation); @@ -214,7 +226,7 @@ public class P1DHistogramVisualizer<NV extends NumberVector<NV, ?>> extends P1DV if(ax != orig) { final double left = (orig / Projection.SCALE + 0.5) * xsize; final double right = (ax / Projection.SCALE + 0.5) * xsize; - SVGSimpleLinearAxis.drawAxis(svgp, layer, proj.getScale(d), left, ysize, right, ysize, true, true, context.getStyleLibrary()); + SVGSimpleLinearAxis.drawAxis(svgp, layer, proj.getScale(d), left, ysize, right, ysize, SVGSimpleLinearAxis.LabelStyle.RIGHTHAND, context.getStyleLibrary()); } } } @@ -225,16 +237,16 @@ public class P1DHistogramVisualizer<NV extends NumberVector<NV, ?>> extends P1DV double binwidth = histogram.getBinsize(); // Visualizing if(!curves) { - for(Pair<Double, double[]> bin : histogram) { - double lpos = xscale.getScaled(bin.getFirst() - binwidth / 2); - double rpos = xscale.getScaled(bin.getFirst() + binwidth / 2); + for(DoubleObjPair<double[]> bin : histogram) { + double lpos = xscale.getScaled(bin.first - binwidth / 2); + double rpos = xscale.getScaled(bin.first + binwidth / 2); double stack = 0.0; final int start = numc > 0 ? 1 : 0; for(int key = start; key < cols; key++) { double val = yscale.getScaled(bin.getSecond()[key]); Element row = SVGUtil.svgRect(svgp.getDocument(), xsize * lpos, ysize * (1 - (val + stack)), xsize * (rpos - lpos), ysize * val); stack = stack + val; - SVGUtil.addCSSClass(row, BIN + (key - 1)); + SVGUtil.addCSSClass(row, BIN + (off + key - 1)); layer.appendChild(row); } } @@ -251,9 +263,9 @@ public class P1DHistogramVisualizer<NV extends NumberVector<NV, ?>> extends P1DV } // draw histogram lines - for(Pair<Double, double[]> bin : histogram) { - left = xscale.getScaled(bin.getFirst() - binwidth / 2); - right = xscale.getScaled(bin.getFirst() + binwidth / 2); + for(DoubleObjPair<double[]> bin : histogram) { + left = xscale.getScaled(bin.first - binwidth / 2); + right = xscale.getScaled(bin.first + binwidth / 2); for(int i = 0; i < cols; i++) { double val = yscale.getScaled(bin.getSecond()[i]); if(lasty[i] != val) { @@ -271,7 +283,7 @@ public class P1DHistogramVisualizer<NV extends NumberVector<NV, ?>> extends P1DV } paths[i].lineTo(xsize * right, ysize * 1); Element elem = paths[i].makeElement(svgp); - SVGUtil.addCSSClass(elem, BIN + (i - 1)); + SVGUtil.addCSSClass(elem, BIN + (off + i - 1)); layer.appendChild(elem); } } @@ -368,20 +380,21 @@ public class P1DHistogramVisualizer<NV extends NumberVector<NV, ?>> extends P1DV super(); this.curves = curves; this.bins = bins; + thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_STYLE; } @Override public Visualization makeVisualization(VisualizationTask task) { - return new P1DHistogramVisualizer<NV>(task, curves, bins); + return new ColoredHistogramVisualizer<NV>(task, curves, bins); } @Override public void processNewResult(HierarchicalResult baseResult, Result result) { - // Cluster histograms - ArrayList<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class); - for(Clustering<?> c : clusterings) { - Iterator<HistogramProjector<?>> ps = ResultUtil.filteredResults(baseResult, HistogramProjector.class); - for(HistogramProjector<?> p : IterableUtil.fromIterator(ps)) { + // Find a style result to visualize: + IterableIterator<StyleResult> styleres = ResultUtil.filteredResults(result, StyleResult.class); + for(StyleResult c : styleres) { + IterableIterator<HistogramProjector<?>> ps = ResultUtil.filteredResults(baseResult, HistogramProjector.class); + for(HistogramProjector<?> p : ps) { // register self final VisualizationTask task = new VisualizationTask(CNAME, c, p.getRelation(), this); task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA); @@ -389,20 +402,6 @@ public class P1DHistogramVisualizer<NV extends NumberVector<NV, ?>> extends P1DV baseResult.getHierarchy().add(p, task); } } - // General data distribution - { - Iterator<HistogramProjector<?>> ps = ResultUtil.filteredResults(result, HistogramProjector.class); - for(HistogramProjector<?> p : IterableUtil.fromIterator(ps)) { - // register self - final VisualizationTask task = new VisualizationTask(NAME, null, p.getRelation(), this); - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA); - if(clusterings.size() > 0) { - task.put(VisualizationTask.META_VISIBLE_DEFAULT, false); - } - // baseResult.getHierarchy().add(p.getRelation(), task); - baseResult.getHierarchy().add(p, task); - } - } } @Override diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis1d/package-info.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/histogram/package-info.java index 36dae86c..4489904f 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis1d/package-info.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/histogram/package-info.java @@ -1,12 +1,12 @@ /** - * <p>Visualizers based on 1D projections.</p> + * <p>Visualizers based on 1D projected histograms.</p> * */ /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures -Copyright (C) 2011 +Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -24,4 +24,4 @@ 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.vis1d;
\ No newline at end of file +package de.lmu.ifi.dbs.elki.visualization.visualizers.histogram;
\ No newline at end of file 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 index bcf25a84..a04a4f7f 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/AbstractOPTICSVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/AbstractOPTICSVisualization.java @@ -4,7 +4,7 @@ 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 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSClusterVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSClusterVisualization.java index 542dcea7..f69bdc59 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSClusterVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSClusterVisualization.java @@ -4,7 +4,7 @@ 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 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -38,7 +38,7 @@ 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.utilities.iterator.IterableIterator; 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; @@ -190,12 +190,12 @@ public class OPTICSClusterVisualization<D extends Distance<D>> extends AbstractO @Override public void processNewResult(HierarchicalResult baseResult, Result result) { - Iterator<OPTICSProjector<?>> ops = ResultUtil.filteredResults(result, OPTICSProjector.class); - for(OPTICSProjector<?> p : IterableUtil.fromIterator(ops)) { + IterableIterator<OPTICSProjector<?>> ops = ResultUtil.filteredResults(result, OPTICSProjector.class); + for(OPTICSProjector<?> p : 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(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA); task.put(CLUSTERING, ocl); baseResult.getHierarchy().add(p, task); } 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 index 08ca7504..58cd9af3 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotCutVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotCutVisualization.java @@ -23,8 +23,6 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.optics; 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;
@@ -40,7 +38,7 @@ 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.utilities.iterator.IterableIterator;
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;
@@ -229,7 +227,7 @@ public class OPTICSPlotCutVisualization<D extends Distance<D>> extends AbstractO Clustering<Model> cl = OPTICSCut.makeOPTICSCut(order, optics.getOPTICSPlot(context).getDistanceAdapter(), epsilon);
order.addChildResult(cl);
}
- context.resultChanged(this.task);
+ context.getHierarchy().resultChanged(this.task);
// synchronizedRedraw();
return true;
}
@@ -277,8 +275,8 @@ public class OPTICSPlotCutVisualization<D extends Distance<D>> extends AbstractO @Override
public void processNewResult(HierarchicalResult baseResult, Result result) {
- Iterator<OPTICSProjector<?>> ops = ResultUtil.filteredResults(result, OPTICSProjector.class);
- for(OPTICSProjector<?> p : IterableUtil.fromIterator(ops)) {
+ IterableIterator<OPTICSProjector<?>> ops = ResultUtil.filteredResults(result, OPTICSProjector.class);
+ for(OPTICSProjector<?> p : ops) {
final VisualizationTask task = new VisualizationTask(NAME, p, null, this);
task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE);
baseResult.getHierarchy().add(p, task);
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 index 69bc781e..f8f30317 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotSelectionVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotSelectionVisualization.java @@ -23,7 +23,6 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.optics; 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;
@@ -45,7 +44,7 @@ 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.utilities.iterator.IterableIterator;
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;
@@ -350,8 +349,8 @@ public class OPTICSPlotSelectionVisualization<D extends Distance<D>> extends Abs @Override
public void processNewResult(HierarchicalResult baseResult, Result result) {
- Iterator<OPTICSProjector<?>> ops = ResultUtil.filteredResults(result, OPTICSProjector.class);
- for(OPTICSProjector<?> p : IterableUtil.fromIterator(ops)) {
+ IterableIterator<OPTICSProjector<?>> ops = ResultUtil.filteredResults(result, OPTICSProjector.class);
+ for(OPTICSProjector<?> p : ops) {
final VisualizationTask task = new VisualizationTask(NAME, p, null, this);
task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE);
baseResult.getHierarchy().add(p, task);
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 index 7f4e3248..da287252 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotVisualizer.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotVisualizer.java @@ -4,7 +4,7 @@ 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 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -23,10 +23,6 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.optics; 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; @@ -36,7 +32,7 @@ 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.utilities.iterator.IterableIterator; 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; @@ -74,13 +70,7 @@ public class OPTICSPlotVisualizer<D extends Distance<D>> extends AbstractOPTICSV // 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); - } + String ploturi = opticsplot.getSVGPlotURI(); Element itag = svgp.svgElement(SVGConstants.SVG_IMAGE_TAG); SVGUtil.setAtt(itag, SVGConstants.SVG_IMAGE_RENDERING_ATTRIBUTE, SVGConstants.SVG_OPTIMIZE_SPEED_VALUE); @@ -88,13 +78,13 @@ public class OPTICSPlotVisualizer<D extends Distance<D>> extends AbstractOPTICSV 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()); + itag.setAttributeNS(SVGConstants.XLINK_NAMESPACE_URI, SVGConstants.XLINK_HREF_QNAME, ploturi); 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()); + SVGSimpleLinearAxis.drawAxis(svgp, layer, opticsplot.getScale(), 0, plotheight, 0, 0, SVGSimpleLinearAxis.LabelStyle.LEFTHAND, context.getStyleLibrary()); + SVGSimpleLinearAxis.drawAxis(svgp, layer, opticsplot.getScale(), plotwidth, plotheight, plotwidth, 0, SVGSimpleLinearAxis.LabelStyle.RIGHTHAND, context.getStyleLibrary()); } catch(CSSNamingConflict e) { LoggingUtil.exception("CSS naming conflict for axes on OPTICS plot", e); @@ -120,11 +110,11 @@ public class OPTICSPlotVisualizer<D extends Distance<D>> extends AbstractOPTICSV @Override public void processNewResult(HierarchicalResult baseResult, Result result) { - Iterator<OPTICSProjector<?>> ops = ResultUtil.filteredResults(result, OPTICSProjector.class); - for(OPTICSProjector<?> p : IterableUtil.fromIterator(ops)) { + IterableIterator<OPTICSProjector<?>> ops = ResultUtil.filteredResults(result, OPTICSProjector.class); + for(OPTICSProjector<?> p : ops) { // Add plots, attach visualizer final VisualizationTask task = new VisualizationTask(NAME, p, null, this); - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_STATIC); + task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA); baseResult.getHierarchy().add(p, task); } } 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 index b082836f..95b3d53b 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSSteepAreaVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSSteepAreaVisualization.java @@ -4,7 +4,7 @@ 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 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -24,7 +24,6 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.optics; */ import java.awt.Color; -import java.util.Iterator; import java.util.List; import org.apache.batik.util.SVGConstants; @@ -34,19 +33,19 @@ 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.math.scales.LinearScale; 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.utilities.iterator.IterableIterator; 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; @@ -196,12 +195,12 @@ public class OPTICSSteepAreaVisualization<D extends Distance<D>> extends Abstrac @Override public void processNewResult(HierarchicalResult baseResult, Result result) { - Iterator<OPTICSProjector<?>> ops = ResultUtil.filteredResults(result, OPTICSProjector.class); - for(OPTICSProjector<?> p : IterableUtil.fromIterator(ops)) { + IterableIterator<OPTICSProjector<?>> ops = ResultUtil.filteredResults(result, OPTICSProjector.class); + for(OPTICSProjector<?> p : 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_LEVEL, VisualizationTask.LEVEL_DATA + 1); task.put(VisualizationTask.META_VISIBLE_DEFAULT, false); baseResult.getHierarchy().add(p, task); baseResult.getHierarchy().add(steep, task); 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 index 05b02142..642f817c 100644 --- 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 @@ -5,7 +5,7 @@ This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures -Copyright (C) 2011 +Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/package-info.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/package-info.java index 985988c6..208d8cb7 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/package-info.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/package-info.java @@ -8,7 +8,7 @@ This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures -Copyright (C) 2011 +Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/pairsegments/CircleSegmentsVisualizer.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/pairsegments/CircleSegmentsVisualizer.java new file mode 100644 index 00000000..360197ab --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/pairsegments/CircleSegmentsVisualizer.java @@ -0,0 +1,708 @@ +package de.lmu.ifi.dbs.elki.visualization.visualizers.pairsegments; + +/* + This file is part of ELKI: + Environment for Developing KDD-Applications Supported by Index-Structures + + Copyright (C) 2012 + Ludwig-Maximilians-Universität München + Lehr- und Forschungseinheit für Datenbanksysteme + ELKI Development Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +import java.awt.Color; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.batik.util.SVGConstants; +import org.w3c.dom.Element; +import org.w3c.dom.events.Event; +import org.w3c.dom.events.EventListener; +import org.w3c.dom.events.EventTarget; +import org.w3c.dom.events.MouseEvent; + +import de.lmu.ifi.dbs.elki.evaluation.clustering.pairsegments.Segment; +import de.lmu.ifi.dbs.elki.evaluation.clustering.pairsegments.Segments; +import de.lmu.ifi.dbs.elki.logging.Logging; +import de.lmu.ifi.dbs.elki.math.MathUtil; +import de.lmu.ifi.dbs.elki.result.HierarchicalResult; +import de.lmu.ifi.dbs.elki.result.Result; +import de.lmu.ifi.dbs.elki.result.ResultListener; +import de.lmu.ifi.dbs.elki.result.ResultUtil; +import de.lmu.ifi.dbs.elki.utilities.documentation.Reference; +import de.lmu.ifi.dbs.elki.utilities.exceptions.AbortException; +import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; +import de.lmu.ifi.dbs.elki.visualization.css.CSSClass; +import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; +import de.lmu.ifi.dbs.elki.visualization.svg.SVGCheckbox; +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.AbstractVisualization; +import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; + +/** + * Visualizer to draw circle segments of clusterings and enable interactive + * selection of segments. For "empty" segments, all related segments are + * selected instead, to visualize the differences. + * + * <p> + * Reference:<br /> + * Evaluation of Clusterings – Metrics and Visual Support<br /> + * Elke Achtert, Sascha Goldhofer, Hans-Peter Kriegel, Erich Schubert, Arthur + * Zimek<br /> + * In: Proc. 28th International Conference on Data Engineering (ICDE) 2012 + * </p> + * + * @author Sascha Goldhofer + * @author Erich Schubert + */ +@Reference(title = "Evaluation of Clusterings – Metrics and Visual Support", authors = "Elke Achtert, Sascha Goldhofer, Hans-Peter Kriegel, Erich Schubert, Arthur Zimek", booktitle = "Proc. 28th International Conference on Data Engineering (ICDE) 2012", url = "http://elki.dbs.ifi.lmu.de/wiki/PairSegments") +public class CircleSegmentsVisualizer extends AbstractVisualization implements ResultListener { + /** + * Class logger + */ + private static final Logging logger = Logging.getLogger(CircleSegmentsVisualizer.class); + + /** + * CircleSegments visualizer name + */ + private static final String NAME = "CircleSegments"; + + /** Minimum width (radian) of Segment */ + private final static double SEGMENT_MIN_ANGLE = 0.01; + + /** Gap (radian) between segments */ + private final static double SEGMENT_MIN_SEP_ANGLE = 0.005; + + /** Offset from center to first ring */ + private final static double RADIUS_INNER = 0.04 * StyleLibrary.SCALE; + + /** Margin between two rings */ + private final static double RADIUS_DISTANCE = 0.01 * StyleLibrary.SCALE; + + /** Radius of whole CircleSegments except selection border */ + private final static double RADIUS_OUTER = 0.47 * StyleLibrary.SCALE; + + /** Radius of highlight selection (outer ring) */ + private final static double RADIUS_SELECTION = 0.02 * StyleLibrary.SCALE; + + /** + * CSS class name for the clusterings. + */ + private static final String CLR_CLUSTER_CLASS_PREFIX = "clusterSegment"; + + /** + * CSS border class of a cluster + */ + public static final String CLR_BORDER_CLASS = "clusterBorder"; + + /** + * CSS hover class for clusters of hovered segment + */ + public static final String CLR_UNPAIRED_CLASS = "clusterUnpaired"; + + /** + * CSS hover class of a segment cluster + */ + public static final String CLR_HOVER_CLASS = "clusterHover"; + + /** + * CSS class of selected Segment + */ + public static final String SEG_UNPAIRED_SELECTED_CLASS = "unpairedSegmentSelected"; + + /** + * Style prefix + */ + public static final String STYLE = "segments"; + + /** + * Style for border lines + */ + public static final String STYLE_BORDER = STYLE + ".border"; + + /** + * Style for hover effect + */ + public static final String STYLE_HOVER = STYLE + ".hover"; + + /** + * First color for producing segment-cluster colors + */ + public static final String STYLE_GRADIENT_FIRST = STYLE + ".cluster.first"; + + /** + * Second color for producing segment-cluster colors + */ + public static final String STYLE_GRADIENT_SECOND = STYLE + ".cluster.second"; + + /** + * Segmentation of Clusterings + */ + protected final Segments segments; + + /** + * The two main layers + */ + private Element visLayer, ctrlLayer; + + /** + * Map to connect segments to their visual elements + */ + public Map<Segment, List<Element>> segmentToElements = new HashMap<Segment, List<Element>>(); + + /** + * Show unclustered Pairs in CircleSegments + */ + boolean showUnclusteredPairs = false; + + /** + * Styling policy + */ + protected final SegmentsStylingPolicy policy; + + /** + * Flag to disallow an incremental redraw + */ + private boolean noIncrementalRedraw = true; + + /** + * Constructor + */ + public CircleSegmentsVisualizer(VisualizationTask task) { + super(task); + segments = task.getResult(); + policy = new SegmentsStylingPolicy(segments, context.getStyleLibrary()); + // Listen for result changes (Selection changed) + context.addResultListener(this); + } + + public void toggleUnclusteredPairs(boolean show) { + noIncrementalRedraw = true; + showUnclusteredPairs = show; + synchronizedRedraw(); + } + + @Override + public void resultChanged(Result current) { + super.resultChanged(current); + // Redraw on style result changes. + if(current == context.getStyleResult()) { + // When switching to a different policy, unhighlight segments. + if(context.getStyleResult().getStylingPolicy() != policy) { + policy.deselectAllSegments(); + } + synchronizedRedraw(); + } + } + + @Override + protected void incrementalRedraw() { + if(noIncrementalRedraw) { + super.incrementalRedraw(); + } + else { + redrawSelection(); + } + } + + @Override + public void redraw() { + logger.debug("Full redraw"); + noIncrementalRedraw = false; // Done that. + + // initialize css (needs clusterSize!) + addCSSClasses(segments.getHighestClusterCount()); + + layer = svgp.svgElement(SVGConstants.SVG_G_TAG); + visLayer = svgp.svgElement(SVGConstants.SVG_G_TAG); + // Setup scaling for canvas: 0 to StyleLibrary.SCALE (usually 100 to avoid a + // Java drawing bug!) + String transform = SVGUtil.makeMarginTransform(task.width, task.height, StyleLibrary.SCALE, StyleLibrary.SCALE, 0) + " translate(" + (StyleLibrary.SCALE * .5) + " " + (StyleLibrary.SCALE * .5) + ")"; + visLayer.setAttribute(SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); + ctrlLayer = svgp.svgElement(SVGConstants.SVG_G_TAG); + + // and create svg elements + drawSegments(); + + // + // Build Interface + // + SVGCheckbox checkbox = new SVGCheckbox(showUnclusteredPairs, "Show unclustered pairs"); + checkbox.addCheckBoxListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + toggleUnclusteredPairs(((SVGCheckbox) e.getSource()).isChecked()); + } + }); + + // Add ring:clustering info + Element clrInfo = drawClusteringInfo(); + Element c = checkbox.renderCheckBox(svgp, 1, 5 + Double.parseDouble(clrInfo.getAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE)), 11); + ctrlLayer.appendChild(clrInfo); + ctrlLayer.appendChild(c); + + ctrlLayer.setAttribute(SVGConstants.SVG_TRANSFORM_ATTRIBUTE, "scale(" + (0.25 / StyleLibrary.SCALE) + ")"); + + layer.appendChild(visLayer); + layer.appendChild(ctrlLayer); + } + + /** + * Define and add required CSS classes + */ + protected void addCSSClasses(int maxClusterSize) { + StyleLibrary style = context.getStyleLibrary(); + + // Cluster separation lines + CSSClass cssReferenceBorder = new CSSClass(this.getClass(), CLR_BORDER_CLASS); + cssReferenceBorder.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, style.getColor(STYLE_BORDER)); + svgp.addCSSClassOrLogError(cssReferenceBorder); + + // Hover effect for clusters + CSSClass cluster_hover = new CSSClass(this.getClass(), CLR_HOVER_CLASS); + // Note: !important is needed to override the regular color assignment + cluster_hover.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, style.getColor(STYLE_HOVER) + " !important"); + cluster_hover.setStatement(SVGConstants.SVG_CURSOR_TAG, SVGConstants.SVG_POINTER_VALUE); + svgp.addCSSClassOrLogError(cluster_hover); + + // Unpaired cluster segment + CSSClass cluster_unpaired = new CSSClass(this.getClass(), CLR_UNPAIRED_CLASS); + cluster_unpaired.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, style.getBackgroundColor(STYLE)); + cluster_unpaired.setStatement(SVGConstants.SVG_STROKE_ATTRIBUTE, SVGConstants.CSS_NONE_VALUE); + svgp.addCSSClassOrLogError(cluster_unpaired); + + // Selected unpaired cluster segment + CSSClass cluster_unpaired_s = new CSSClass(this.getClass(), SEG_UNPAIRED_SELECTED_CLASS); + cluster_unpaired_s.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, style.getColor(STYLE_HOVER) + " !important"); + svgp.addCSSClassOrLogError(cluster_unpaired_s); + + // create Color shades for clusters + String firstcol = style.getColor(STYLE_GRADIENT_FIRST); + String secondcol = style.getColor(STYLE_GRADIENT_SECOND); + String[] clusterColorShades = makeGradient(maxClusterSize, new String[] { firstcol, secondcol }); + + for(int i = 0; i < maxClusterSize; i++) { + CSSClass clusterClasses = new CSSClass(CircleSegmentsVisualizer.class, CLR_CLUSTER_CLASS_PREFIX + "_" + i); + clusterClasses.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, clusterColorShades[i]); + clusterClasses.setStatement(SVGConstants.SVG_STROKE_ATTRIBUTE, SVGConstants.SVG_NONE_VALUE); + svgp.addCSSClassOrLogError(clusterClasses); + } + } + + /** + * Create the segments + */ + private void drawSegments() { + final int clusterings = segments.getClusterings(); + + // Reinitialize + this.segmentToElements.clear(); + + double angle_pair = (MathUtil.TWOPI - (SEGMENT_MIN_SEP_ANGLE * segments.size())) / segments.getPairCount(showUnclusteredPairs); + final int pair_min_count = (int) Math.ceil(SEGMENT_MIN_ANGLE / angle_pair); + + // number of segments needed to be resized + int cluster_min_count = 0; + for(Segment segment : segments) { + if(segment.getPairCount() <= pair_min_count) { + cluster_min_count++; + } + } + + // update width of a pair + angle_pair = (MathUtil.TWOPI - (SEGMENT_MIN_SEP_ANGLE * segments.size() + cluster_min_count * SEGMENT_MIN_ANGLE)) / (segments.getPairCount(showUnclusteredPairs) - cluster_min_count); + double radius_delta = (RADIUS_OUTER - RADIUS_INNER - clusterings * RADIUS_DISTANCE) / clusterings; + double border_width = SEGMENT_MIN_SEP_ANGLE; + + int refClustering = 0; + int refSegment = Segment.UNCLUSTERED; + double offsetAngle = 0.0; + + for(final Segment segment : segments) { + long currentPairCount = segment.getPairCount(); + + // resize small segments if below minimum + double alpha = SEGMENT_MIN_ANGLE; + if(currentPairCount > pair_min_count) { + alpha = angle_pair * currentPairCount; + } + + // ITERATE OVER ALL SEGMENT-CLUSTERS + + ArrayList<Element> elems = new ArrayList<Element>(clusterings); + segmentToElements.put(segment, elems); + // draw segment for every clustering + + for(int i = 0; i < clusterings; i++) { + double currentRadius = i * (radius_delta + RADIUS_DISTANCE) + RADIUS_INNER; + + // Add border if the next segment is a different cluster in the + // reference clustering + if((refSegment != segment.get(refClustering)) && refClustering == i) { + Element border = SVGUtil.svgCircleSegment(svgp, 0, 0, offsetAngle - SEGMENT_MIN_SEP_ANGLE, border_width, currentRadius, RADIUS_OUTER - RADIUS_DISTANCE); + border.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, CLR_BORDER_CLASS); + visLayer.appendChild(border); + + if(segment.get(refClustering) == Segment.UNCLUSTERED) { + refClustering = Math.min(refClustering + 1, clusterings - 1); + } + refSegment = segment.get(refClustering); + } + + int cluster = segment.get(i); + + // create ring segment + Element segelement = SVGUtil.svgCircleSegment(svgp, 0, 0, offsetAngle, alpha, currentRadius, currentRadius + radius_delta); + elems.add(segelement); + + // MouseEvents on segment cluster + EventListener listener = new SegmentListenerProxy(segment, i); + EventTarget targ = (EventTarget) segelement; + targ.addEventListener(SVGConstants.SVG_MOUSEOVER_EVENT_TYPE, listener, false); + targ.addEventListener(SVGConstants.SVG_MOUSEOUT_EVENT_TYPE, listener, false); + targ.addEventListener(SVGConstants.SVG_CLICK_EVENT_TYPE, listener, false); + + // Coloring based on clusterID + if(cluster >= 0) { + segelement.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, CLR_CLUSTER_CLASS_PREFIX + "_" + cluster); + } + // if its an unpaired cluster set color to white + else { + segelement.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, CLR_UNPAIRED_CLASS); + } + + visLayer.appendChild(segelement); + } + + // + // Add a extended strip for each segment to emphasis selection + // (easier to track thin segments and their color coding and + // differentiates them from cluster border lines) + // + + double currentRadius = clusterings * (radius_delta + RADIUS_DISTANCE) + RADIUS_INNER; + Element extension = SVGUtil.svgCircleSegment(svgp, 0, 0, offsetAngle, alpha, currentRadius, currentRadius + RADIUS_SELECTION); + extension.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, CLR_UNPAIRED_CLASS); + elems.add(extension); + + if(segment.isUnpaired()) { + if(policy.isSelected(segment)) { + SVGUtil.addCSSClass(extension, SEG_UNPAIRED_SELECTED_CLASS); + } + else { + // Remove highlight + SVGUtil.removeCSSClass(extension, SEG_UNPAIRED_SELECTED_CLASS); + } + } + else { + int idx = policy.indexOfSegment(segment); + if(idx >= 0) { + String color = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT).getColor(idx); + extension.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, SVGConstants.CSS_FILL_PROPERTY + ":" + color); + } + else { + // Remove styling + extension.removeAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE); + } + } + + visLayer.appendChild(extension); + + // calculate angle for next segment + offsetAngle += alpha + SEGMENT_MIN_SEP_ANGLE; + } + } + + private void redrawSelection() { + logger.debug("Updating selection only."); + for(Entry<Segment, List<Element>> entry : segmentToElements.entrySet()) { + Segment segment = entry.getKey(); + // The selection marker is the extra element in the list + Element extension = entry.getValue().get(segments.getClusterings()); + if(segment.isUnpaired()) { + if(policy.isSelected(segment)) { + SVGUtil.addCSSClass(extension, SEG_UNPAIRED_SELECTED_CLASS); + } + else { + // Remove highlight + SVGUtil.removeCSSClass(extension, SEG_UNPAIRED_SELECTED_CLASS); + } + } + else { + int idx = policy.indexOfSegment(segment); + if(idx >= 0) { + String color = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT).getColor(idx); + extension.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, SVGConstants.CSS_FILL_PROPERTY + ":" + color); + } + else { + // Remove styling + extension.removeAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE); + } + } + } + } + + /** + * Creates a gradient over a set of colors + * + * @param shades number of colors in the gradient + * @param colors colors for the gradient + * @return array of colors for CSS + */ + protected static String[] makeGradient(int shades, String[] colors) { + if(shades <= colors.length) { + return colors; + } + + // Convert SVG colors into AWT colors for math + Color[] cols = new Color[colors.length]; + for(int i = 0; i < colors.length; i++) { + cols[i] = SVGUtil.stringToColor(colors[i]); + if(cols[i] == null) { + throw new AbortException("Error parsing color: " + colors[i]); + } + } + + // Step size + double increment = (cols.length - 1.) / shades; + + String[] colorShades = new String[shades]; + + for(int s = 0; s < shades; s++) { + final int ppos = Math.min((int) Math.floor(increment * s), cols.length); + final int npos = Math.min((int) Math.ceil(increment * s), cols.length); + if(ppos == npos) { + colorShades[s] = colors[ppos]; + } + else { + Color prev = cols[ppos]; + Color next = cols[npos]; + final double mix = (increment * s - ppos) / (npos - ppos); + final int r = (int) ((1 - mix) * prev.getRed() + mix * next.getRed()); + final int g = (int) ((1 - mix) * prev.getGreen() + mix * next.getGreen()); + final int b = (int) ((1 - mix) * prev.getBlue() + mix * next.getBlue()); + colorShades[s] = SVGUtil.colorToString(((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF)); + } + } + + return colorShades; + } + + protected Element drawClusteringInfo() { + Element thumbnail = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG); + + // build thumbnail + int startRadius = 4; + int singleHeight = 12; + int margin = 4; + int radius = segments.getClusterings() * (singleHeight + margin) + startRadius; + + SVGUtil.setAtt(thumbnail, SVGConstants.SVG_HEIGHT_ATTRIBUTE, radius); + + for(int i = 0; i < segments.getClusterings(); i++) { + double innerRadius = i * singleHeight + margin * i + startRadius; + Element clr = SVGUtil.svgCircleSegment(svgp, radius - startRadius, radius - startRadius, Math.PI * 1.5, Math.PI * 0.5, innerRadius, innerRadius + singleHeight); + // FIXME: Use StyleLibrary + clr.setAttribute(SVGConstants.SVG_FILL_ATTRIBUTE, "#d4e4f1"); + clr.setAttribute(SVGConstants.SVG_STROKE_ATTRIBUTE, "#a0a0a0"); + clr.setAttribute(SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE, "1.0"); + + String labelText = segments.getClusteringDescription(i); + Element label = svgp.svgText(radius + startRadius, radius - innerRadius - startRadius, labelText); + thumbnail.appendChild(label); + + thumbnail.appendChild(clr); + } + + return thumbnail; + } + + protected void segmentHover(Segment segment, int ringid, boolean active) { + if(active) { + // abort if this are the unclustered pairs + if(segment.isNone()) { + return; + } + if(logger.isDebugging()) { + logger.debug("Hover on segment: " + segment + " unpaired: " + segment.isUnpaired()); + } + + if(!segment.isUnpaired()) { + // + // STANDARD CLUSTER SEGMENT + // highlight all ring segments in this clustering and this cluster + // + // highlight all corresponding ring Segments + for(Entry<Segment, List<Element>> entry : segmentToElements.entrySet()) { + Segment other = entry.getKey(); + // Same cluster in same clustering? + if(other.get(ringid) != segment.get(ringid)) { + continue; + } + Element ringSegment = entry.getValue().get(ringid); + SVGUtil.addCSSClass(ringSegment, CLR_HOVER_CLASS); + } + } + else { + // + // UNPAIRED SEGMENT + // highlight all ring segments in this clustering responsible for + // unpaired + // segment + // + // get the paired segments corresponding to the unpaired segment + List<Segment> paired = segments.getPairedSegments(segment); + + for(Segment other : paired) { + Element ringSegment = segmentToElements.get(other).get(ringid); + SVGUtil.addCSSClass(ringSegment, CLR_HOVER_CLASS); + } + } + } + else { + for(List<Element> elems : segmentToElements.values()) { + for(Element current : elems) { + SVGUtil.removeCSSClass(current, CLR_HOVER_CLASS); + } + } + } + } + + protected void segmentClick(Segment segment, Event evt, boolean dblClick) { + MouseEvent mouse = (MouseEvent) evt; + + // CTRL (add) pressed? + boolean ctrl = false; + if(mouse.getCtrlKey()) { + ctrl = true; + } + + // Unselect others on double click + if(dblClick) { + policy.deselectAllSegments(); + } + policy.select(segment, ctrl); + // update stylePolicy + context.getStyleResult().setStylingPolicy(policy); + // fire changed event to trigger redraw + context.getHierarchy().resultChanged(context.getStyleResult()); + } + + /** + * Proxy element to connect signals. + * + * @author Erich Schubert + */ + private class SegmentListenerProxy implements EventListener { + /** + * Mouse double click time window in milliseconds + * + * TODO: does Batik have double click events? + */ + public static final int EVT_DBLCLICK_DELAY = 350; + + /** + * Segment we are attached to + */ + private Segment id; + + /** + * Segment ring we are + */ + private int ringid; + + /** + * For detecting double clicks. + */ + private long lastClick = 0; + + /** + * Constructor. + * + * @param id Segment id + * @param ringid Ring id + */ + public SegmentListenerProxy(Segment id, int ringid) { + super(); + this.id = id; + this.ringid = ringid; + } + + @Override + public void handleEvent(Event evt) { + if(SVGConstants.SVG_MOUSEOVER_EVENT_TYPE.equals(evt.getType())) { + segmentHover(id, ringid, true); + } + if(SVGConstants.SVG_MOUSEOUT_EVENT_TYPE.equals(evt.getType())) { + segmentHover(id, ringid, false); + } + if(SVGConstants.SVG_CLICK_EVENT_TYPE.equals(evt.getType())) { + // Check Double Click + boolean dblClick = false; + long time = java.util.Calendar.getInstance().getTimeInMillis(); + if(time - lastClick <= EVT_DBLCLICK_DELAY) { + dblClick = true; + } + lastClick = time; + + segmentClick(id, evt, dblClick); + } + } + } + + /** + * Factory for visualizers for a circle segment + * + * @author Sascha Goldhofer + * + * @apiviz.stereotype factory + * @apiviz.uses CircleSegmentsVisualizer oneway - - «create» + */ + public static class Factory extends AbstractVisFactory { + /** + * Constructor + */ + public Factory() { + super(); + } + + @Override + public Visualization makeVisualization(VisualizationTask task) { + return new CircleSegmentsVisualizer(task); + } + + @Override + public void processNewResult(HierarchicalResult baseResult, Result result) { + // If no comparison result found abort + List<Segments> segments = ResultUtil.filterResults(result, Segments.class); + for(Segments segmentResult : segments) { + // create task for visualization + final VisualizationTask task = new VisualizationTask(NAME, segmentResult, null, this); + task.width = 2.0; + task.height = 2.0; + task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE); + baseResult.getHierarchy().add(segmentResult, task); + } + } + }; +}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/pairsegments/SegmentsStylingPolicy.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/pairsegments/SegmentsStylingPolicy.java new file mode 100644 index 00000000..99ecf081 --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/pairsegments/SegmentsStylingPolicy.java @@ -0,0 +1,287 @@ +package de.lmu.ifi.dbs.elki.visualization.visualizers.pairsegments; + +/* + This file is part of ELKI: + Environment for Developing KDD-Applications Supported by Index-Structures + + Copyright (C) 2012 + Ludwig-Maximilians-Universität München + Lehr- und Forschungseinheit für Datenbanksysteme + ELKI Development Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.TreeMap; + +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.ModifiableDBIDs; +import de.lmu.ifi.dbs.elki.evaluation.clustering.pairsegments.Segment; +import de.lmu.ifi.dbs.elki.evaluation.clustering.pairsegments.Segments; +import de.lmu.ifi.dbs.elki.visualization.colors.ColorLibrary; +import de.lmu.ifi.dbs.elki.visualization.style.ClassStylingPolicy; +import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; +import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil; + +/** + * Styling policy to communicate the segment selection to other visualizers. + * + * @author Sascha Goldhofer + * @author Erich Schubert + */ +public class SegmentsStylingPolicy implements ClassStylingPolicy { + /** + * The segments we use for visualization + */ + protected final Segments segments; + + /** + * Selected segments + */ + protected ArrayList<Segment> selectedSegments = new ArrayList<Segment>(); + + /** + * Segments indirectly selected + */ + protected TreeMap<Segment, Segment> indirectSelections = new TreeMap<Segment, Segment>(); + + /** + * Not selected IDs that will be drawn in default colors. + */ + protected ModifiableDBIDs unselectedObjects = DBIDUtil.newHashSet(); + + /** + * Color library (only used in compatibility mode) + */ + // TODO: move to abstract super class? + ColorLibrary colorset; + + /** + * Constructor. + * + * @param segments Segments + */ + public SegmentsStylingPolicy(Segments segments, StyleLibrary style) { + super(); + this.segments = segments; + this.colorset = style.getColorSet(StyleLibrary.PLOT); + + // get all selectable segments + for(Segment segment : segments) { + // store segmentID + if(!segment.isUnpaired()) { + // and store their get all objects + if(segment.getDBIDs() != null) { + unselectedObjects.addDBIDs(segment.getDBIDs()); + } + } + } + } + + /** + * Test whether a segment is selected. + * + * @param segment Segment to test + * @return true when selected + */ + public boolean isSelected(Segment segment) { + return selectedSegments.contains(segment) || indirectSelections.containsValue(segment); + } + + @Override + public int getStyleForDBID(DBID id) { + Iterator<Segment> s = selectedSegments.iterator(); + for(int i = 0; s.hasNext(); i++) { + Segment seg = s.next(); + DBIDs ids = seg.getDBIDs(); + if(ids != null && ids.contains(id)) { + return i; + } + } + return -2; + } + + @Override + public int getColorForDBID(DBID id) { + int style = getStyleForDBID(id); + // FIXME: slow + return SVGUtil.stringToColor(colorset.getColor(style)).getRGB(); + } + + @Override + // -2=grau, -1=schwarz, 0+=farben + public int getMinStyle() { + return -2; + } + + @Override + public int getMaxStyle() { + return selectedSegments.size(); + } + + @Override + public Iterator<DBID> iterateClass(int cnum) { + // unselected + if(cnum == -2) { + return unselectedObjects.iterator(); + } + else if(cnum == -1) { + return DBIDUtil.EMPTYDBIDS.iterator(); + } + // colors + DBIDs ids = selectedSegments.get(cnum).getDBIDs(); + return (ids != null) ? ids.iterator() : DBIDUtil.EMPTYDBIDS.iterator(); + } + + /** + * Adds or removes the given segment to the selection. Depending on the + * clustering and cluster selected and the addToSelection option given, the + * current selection will be modified. This method is called by clicking on a + * segment and ring and the CTRL-button status. + * + * Adding selections does only work on the same clustering and cluster, else a + * new selection will be added. + * + * @param segment the selected element representing a segment ring (specific + * clustering) + * @param addToSelection flag for adding segment to current selection + */ + public void select(Segment segment, boolean addToSelection) { + // abort if segment represents pairs inNone. Would select all segments... + if(segment.isNone()) { + return; + } + if(!addToSelection) { + deselectAllSegments(); + } + + // get selected segments + if(segment.isUnpaired()) { + // check if all segments are selected + if(addToSelection) { + boolean allSegmentsSelected = true; + for(Segment other : segments.getPairedSegments(segment)) { + if(!isSelected(other)) { + allSegmentsSelected = false; + break; + } + } + + // if all are selected, deselect all + if(allSegmentsSelected) { + deselectSegment(segment); + return; + } + } + if(isSelected(segment)) { + deselectSegment(segment); + } + else { + selectSegment(segment); + } + } + else { + // an object segment was selected + if(isSelected(segment)) { + deselectSegment(segment); + } + else { + selectSegment(segment); + } + } + } + + /** + * Deselect all currently selected segments + */ + public void deselectAllSegments() { + while(selectedSegments.size() > 0) { + deselectSegment(selectedSegments.get(selectedSegments.size() - 1)); + } + } + + /** + * Deselect a segment + * + * @param segment Segment to deselect + */ + protected void deselectSegment(Segment segment) { + if(segment.isUnpaired()) { + ArrayList<Segment> remove = new ArrayList<Segment>(); + // remove all object segments associated with unpaired segment from + // selection list + for(Entry<Segment, Segment> entry : indirectSelections.entrySet()) { + if(entry.getValue() == segment) { + remove.add(entry.getKey()); + } + } + + for(Segment other : remove) { + indirectSelections.remove(other); + deselectSegment(other); + } + } + else { + // check if deselected object Segment has a unpaired segment highlighted + Segment unpaired = indirectSelections.get(segment); + if(unpaired != null) { + // remove highlight + deselectSegment(unpaired); + } + if(selectedSegments.remove(segment)) { + if(segment.getDBIDs() != null) { + unselectedObjects.addDBIDs(segment.getDBIDs()); + } + } + } + } + + /** + * Select a segment + * + * @param segment Segment to select + */ + protected void selectSegment(Segment segment) { + if(segment.isUnpaired()) { + // remember selected unpaired segment + for(Segment other : segments.getPairedSegments(segment)) { + indirectSelections.put(other, segment); + selectSegment(other); + } + } + else { + if(!selectedSegments.contains(segment)) { + selectedSegments.add(segment); + if(segment.getDBIDs() != null) { + unselectedObjects.removeDBIDs(segment.getDBIDs()); + } + } + } + } + + /** + * Get the index of a selected segment. + * + * @param segment Segment to find + * @return Index, or -1 + */ + public int indexOfSegment(Segment segment) { + return selectedSegments.indexOf(segment); + } +}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/events/ContextChangeListener.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/pairsegments/package-info.java index 2d7d3e91..c0c71dd3 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/events/ContextChangeListener.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/pairsegments/package-info.java @@ -1,10 +1,11 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.events; - +/** + * <p>Visualizers for inspecting cluster differences using pair counting segments.</p> + */ /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -22,22 +23,4 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.events; 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.EventListener; - -/** - * Listener for context changes. - * - * @author Erich Schubert - * - * @apiviz.uses de.lmu.ifi.dbs.elki.visualization.visualizers.events.ContextChangedEvent oneway - - listens - */ -public interface ContextChangeListener extends EventListener { - /** - * Method called on context changes (e.g. projection changes). - * Usually, this should trigger a redraw! - * - * @param e Change event - */ - public void contextChanged(ContextChangedEvent e); -}
\ No newline at end of file +package de.lmu.ifi.dbs.elki.visualization.visualizers.pairsegments;
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/AbstractParallelVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/AbstractParallelVisualization.java new file mode 100644 index 00000000..babcb864 --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/AbstractParallelVisualization.java @@ -0,0 +1,163 @@ +package de.lmu.ifi.dbs.elki.visualization.visualizers.parallel;
+
+/*
+ This file is part of ELKI:
+ Environment for Developing KDD-Applications Supported by Index-Structures
+
+ Copyright (C) 2012
+ Ludwig-Maximilians-Universität München
+ Lehr- und Forschungseinheit für Datenbanksysteme
+ ELKI Development Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import org.apache.batik.util.SVGConstants;
+import org.w3c.dom.Element;
+
+import de.lmu.ifi.dbs.elki.data.NumberVector;
+import de.lmu.ifi.dbs.elki.database.relation.Relation;
+import de.lmu.ifi.dbs.elki.result.Result;
+import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
+import de.lmu.ifi.dbs.elki.visualization.projections.ProjectionParallel;
+import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisualization;
+
+/**
+ * Abstract base class for parallel visualizations.
+ *
+ * @author Robert Rödler
+ * @author Erich Schubert
+ *
+ * @param <NV> Vector type in relation
+ */
+public abstract class AbstractParallelVisualization<NV extends NumberVector<?, ?>> extends AbstractVisualization {
+ /**
+ * The current projection
+ */
+ final protected ProjectionParallel proj;
+
+ /**
+ * The representation we visualize
+ */
+ final protected Relation<NV> relation;
+
+ /**
+ * margin
+ */
+ final double[] margins;
+
+ /**
+ * Space between two axes
+ */
+ protected double axsep;
+
+ /**
+ * viewbox size
+ */
+ final double[] size;
+
+ /**
+ * Constructor.
+ *
+ * @param task Visualization task
+ */
+ public AbstractParallelVisualization(VisualizationTask task) {
+ super(task);
+ this.proj = task.getProj();
+ this.relation = task.getRelation();
+
+ double ratio = task.width / task.height;
+
+ margins = new double[] { 0.05 * StyleLibrary.SCALE, 0.1 * StyleLibrary.SCALE, 0.05 * StyleLibrary.SCALE, 0.4 * StyleLibrary.SCALE };
+ size = new double[] { ratio * StyleLibrary.SCALE, StyleLibrary.SCALE };
+ recalcAxisPositions();
+
+ this.layer = setupCanvas(svgp, proj, task.getWidth(), task.getHeight());
+ }
+
+ /**
+ * Utility function to setup a canvas element for the visualization.
+ *
+ * @param svgp Plot element
+ * @param proj Projection to use
+ * @param width Width
+ * @param height Height
+ * @return wrapper element with appropriate view box.
+ */
+ public Element setupCanvas(SVGPlot svgp, ProjectionParallel proj, double width, double height) {
+ Element layer = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG);
+ final String transform = SVGUtil.makeMarginTransform(width, height, size[0], size[1], margins[0], margins[1], margins[2], margins[3]);
+ SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform);
+ return layer;
+ }
+
+ /**
+ * Get width of main canvas.
+ *
+ * @return Width
+ */
+ protected double getSizeX() {
+ return size[0];
+ }
+
+ protected double getSizeY() {
+ return size[1];
+ }
+
+ protected double getMarginLeft() {
+ return margins[0];
+ }
+
+ protected double getMarginTop() {
+ return margins[1];
+ }
+
+ /**
+ * Distance between axes.
+ *
+ * @return Axis separation
+ */
+ protected double getAxisSep() {
+ return axsep;
+ }
+
+ /**
+ * Recalculate axis positions, in particular after projection changes.
+ */
+ private void recalcAxisPositions() {
+ axsep = size[0] / (proj.getVisibleDimensions() - 1.);
+ }
+
+ /**
+ * Get the position of visible axis d
+ *
+ * @param d Visible axis number
+ * @return Position
+ */
+ protected double getVisibleAxisX(double d) {
+ return d * axsep;
+ }
+
+ @Override
+ public void resultChanged(Result current) {
+ super.resultChanged(current);
+ if(current == proj) {
+ recalcAxisPositions();
+ synchronizedRedraw();
+ }
+ }
+}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/LineVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/LineVisualization.java new file mode 100644 index 00000000..3a2c7c1d --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/LineVisualization.java @@ -0,0 +1,218 @@ +package de.lmu.ifi.dbs.elki.visualization.visualizers.parallel;
+
+/*
+ This file is part of ELKI:
+ Environment for Developing KDD-Applications Supported by Index-Structures
+
+ Copyright (C) 2012
+ Ludwig-Maximilians-Universität München
+ Lehr- und Forschungseinheit für Datenbanksysteme
+ ELKI Development Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import java.util.Iterator;
+
+import org.apache.batik.util.SVGConstants;
+import org.w3c.dom.Element;
+
+import de.lmu.ifi.dbs.elki.data.NumberVector;
+import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener;
+import de.lmu.ifi.dbs.elki.database.ids.DBID;
+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.SamplingResult;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator;
+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.ParallelPlotProjector;
+import de.lmu.ifi.dbs.elki.visualization.style.ClassStylingPolicy;
+import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
+import de.lmu.ifi.dbs.elki.visualization.style.StylingPolicy;
+import de.lmu.ifi.dbs.elki.visualization.style.lines.LineStyleLibrary;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPath;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot;
+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;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualization;
+
+/**
+ * Generates data lines.
+ *
+ * @author Robert Rödler
+ */
+public class LineVisualization extends AbstractParallelVisualization<NumberVector<?, ?>> implements DataStoreListener {
+ /**
+ * Generic tags to indicate the type of element. Used in IDs, CSS-Classes etc.
+ */
+ public static final String DATALINE = "Dataline";
+
+ /**
+ * Sample we visualize.
+ */
+ private SamplingResult sample;
+
+ /**
+ * Constructor.
+ *
+ * @param task VisualizationTask
+ */
+ public LineVisualization(VisualizationTask task) {
+ super(task);
+ this.sample = ResultUtil.getSamplingResult(relation);
+ context.addResultListener(this);
+ context.addDataStoreListener(this);
+ incrementalRedraw();
+ }
+
+ @Override
+ public void destroy() {
+ context.removeDataStoreListener(this);
+ context.removeResultListener(this);
+ super.destroy();
+ }
+
+ @Override
+ public void resultChanged(Result current) {
+ super.resultChanged(current);
+ if(current == sample || current == context.getStyleResult()) {
+ synchronizedRedraw();
+ }
+ }
+
+ @Override
+ protected void redraw() {
+ StylingPolicy sp = context.getStyleResult().getStylingPolicy();
+ addCSSClasses(svgp, sp);
+
+ Iterator<DBID> ids = sample.getSample().iterator();
+ if(ids == null || !ids.hasNext()) {
+ ids = relation.iterDBIDs();
+ }
+ if(sp instanceof ClassStylingPolicy) {
+ ClassStylingPolicy csp = (ClassStylingPolicy) sp;
+ for(int c = csp.getMinStyle(); c < csp.getMaxStyle(); c++) {
+ String key = DATALINE + "_" + c;
+ for(Iterator<DBID> iter = csp.iterateClass(c); iter.hasNext();) {
+ DBID id = iter.next();
+ if(!sample.getSample().contains(id)) {
+ continue; // TODO: can we test more efficiently than this?
+ }
+ SVGPath path = new SVGPath();
+ double[] yPos = proj.fastProjectDataToRenderSpace(relation.get(id));
+ for(int i = 0; i < yPos.length; i++) {
+ path.drawTo(getVisibleAxisX(i), yPos[i]);
+ }
+ Element line = path.makeElement(svgp);
+ SVGUtil.addCSSClass(line, key);
+ layer.appendChild(line);
+ }
+ }
+ }
+ else {
+ while(ids.hasNext()) {
+ DBID id = ids.next();
+ SVGPath path = new SVGPath();
+ double[] yPos = proj.fastProjectDataToRenderSpace(relation.get(id));
+ for(int i = 0; i < yPos.length; i++) {
+ path.drawTo(getVisibleAxisX(i), yPos[i]);
+ }
+ Element line = path.makeElement(svgp);
+ SVGUtil.addCSSClass(line, DATALINE);
+ // assign color
+ line.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, SVGConstants.CSS_STROKE_PROPERTY + ":" + SVGUtil.colorToString(sp.getColorForDBID(id)));
+ layer.appendChild(line);
+ }
+ }
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp, StylingPolicy sp) {
+ final StyleLibrary style = context.getStyleLibrary();
+ final LineStyleLibrary lines = style.lines();
+ final double width = .5 * style.getLineWidth(StyleLibrary.PLOT);
+ if(sp instanceof ClassStylingPolicy) {
+ ClassStylingPolicy csp = (ClassStylingPolicy) sp;
+ for(int i = csp.getMinStyle(); i < csp.getMaxStyle(); i++) {
+ String key = DATALINE + "_" + i;
+ if(!svgp.getCSSClassManager().contains(key)) {
+ CSSClass cls = new CSSClass(this, key);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ lines.formatCSSClass(cls, i, width);
+ svgp.addCSSClassOrLogError(cls);
+ }
+ }
+ }
+ else {
+ // Class for the distance function
+ if(!svgp.getCSSClassManager().contains(DATALINE)) {
+ CSSClass cls = new CSSClass(this, DATALINE);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ lines.formatCSSClass(cls, -1, width);
+ svgp.addCSSClassOrLogError(cls);
+ }
+ }
+ svgp.updateStyleElement();
+ }
+
+ /**
+ * Factory for axis visualizations
+ *
+ * @author Robert Rödler
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses LineVisualization oneway - - «create»
+ */
+ public static class Factory extends AbstractVisFactory {
+ /**
+ * A short name characterizing this Visualizer.
+ */
+ private static final String NAME = "Data lines";
+
+ /**
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ */
+ public Factory() {
+ super();
+ thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_STYLE;
+ }
+
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new LineVisualization(task);
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ IterableIterator<ParallelPlotProjector<?>> ps = ResultUtil.filteredResults(result, ParallelPlotProjector.class);
+ for(ParallelPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, p.getRelation(), p.getRelation(), this);
+ task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ }
+}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/ParallelAxisVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/ParallelAxisVisualization.java new file mode 100644 index 00000000..a47ea734 --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/ParallelAxisVisualization.java @@ -0,0 +1,196 @@ +package de.lmu.ifi.dbs.elki.visualization.visualizers.parallel;
+
+/*
+ This file is part of ELKI:
+ Environment for Developing KDD-Applications Supported by Index-Structures
+
+ Copyright (C) 2012
+ Ludwig-Maximilians-Universität München
+ Lehr- und Forschungseinheit für Datenbanksysteme
+ ELKI Development Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import java.util.Iterator;
+
+import org.apache.batik.util.SVGConstants;
+import org.w3c.dom.Element;
+import org.w3c.dom.events.Event;
+import org.w3c.dom.events.EventListener;
+import org.w3c.dom.events.EventTarget;
+
+import de.lmu.ifi.dbs.elki.data.NumberVector;
+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.DatabaseUtil;
+import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
+import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
+import de.lmu.ifi.dbs.elki.visualization.css.CSSClassManager.CSSNamingConflict;
+import de.lmu.ifi.dbs.elki.visualization.projector.ParallelPlotProjector;
+import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot;
+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;
+
+/**
+ * Generates a SVG-Element containing axes, including labeling.
+ *
+ * @author Robert Rödler
+ *
+ * @apiviz.uses SVGSimpleLinearAxis
+ */
+// TODO: split into interactive / non-interactive parts?
+public class ParallelAxisVisualization extends AbstractParallelVisualization<NumberVector<?, ?>> {
+ /**
+ * Axis label class
+ */
+ public final static String AXIS_LABEL = "paxis-label";
+
+ /**
+ * Clickable area for the axis.
+ */
+ public final static String INVERTEDAXIS = "paxis-button";
+
+ /**
+ * Constructor.
+ *
+ * @param task VisualizationTask
+ */
+ public ParallelAxisVisualization(VisualizationTask task) {
+ super(task);
+ incrementalRedraw();
+ context.addResultListener(this);
+ }
+
+ @Override
+ protected void redraw() {
+ addCSSClasses(svgp);
+ final int dim = proj.getVisibleDimensions();
+ try {
+ for(int i = 0; i < dim; i++) {
+ final int truedim = proj.getDimForVisibleAxis(i);
+ final double axisX = getVisibleAxisX(i);
+ if(!proj.isAxisInverted(i)) {
+ SVGSimpleLinearAxis.drawAxis(svgp, layer, proj.getAxisScale(i), axisX, getSizeY(), axisX, 0, SVGSimpleLinearAxis.LabelStyle.ENDLABEL, context.getStyleLibrary());
+ }
+ else {
+ SVGSimpleLinearAxis.drawAxis(svgp, layer, proj.getAxisScale(i), axisX, 0, axisX, getSizeY(), SVGSimpleLinearAxis.LabelStyle.ENDLABEL, context.getStyleLibrary());
+ }
+ // Get axis label
+ final String label = DatabaseUtil.getColumnLabel(relation, truedim + 1);
+ // Add axis label
+ Element text = svgp.svgText(axisX, -.7 * getMarginTop(), label);
+ SVGUtil.setCSSClass(text, AXIS_LABEL);
+ layer.appendChild(text);
+ // TODO: Split into background + clickable layer.
+ Element button = svgp.svgRect(axisX - getAxisSep() * .475, -getMarginTop(), .95 * getAxisSep(), .5 * getMarginTop());
+ SVGUtil.setCSSClass(button, INVERTEDAXIS);
+ addEventListener(button, truedim);
+ layer.appendChild(button);
+ }
+ }
+ catch(CSSNamingConflict e) {
+ throw new RuntimeException("Conflict in CSS naming for axes.", e);
+ }
+ }
+
+ /**
+ * Add the main CSS classes.
+ *
+ * @param svgp Plot to draw to
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ final StyleLibrary style = context.getStyleLibrary();
+ if(!svgp.getCSSClassManager().contains(AXIS_LABEL)) {
+ CSSClass cls = new CSSClass(this, AXIS_LABEL);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getTextColor(StyleLibrary.AXIS_LABEL));
+ cls.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, style.getFontFamily(StyleLibrary.AXIS_LABEL));
+ cls.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, style.getTextSize(StyleLibrary.AXIS_LABEL));
+ cls.setStatement(SVGConstants.CSS_TEXT_ANCHOR_PROPERTY, SVGConstants.SVG_MIDDLE_VALUE);
+ svgp.addCSSClassOrLogError(cls);
+ }
+ if(!svgp.getCSSClassManager().contains(INVERTEDAXIS)) {
+ CSSClass cls = new CSSClass(this, INVERTEDAXIS);
+ cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, 0.1);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_GREY_VALUE);
+ svgp.addCSSClassOrLogError(cls);
+ }
+ }
+
+ /**
+ * Add an event listener to the Element
+ *
+ * @param tag Element to add the listener
+ * @param i Tool number for the Element
+ */
+ private void addEventListener(final Element tag, final int i) {
+ EventTarget targ = (EventTarget) tag;
+ targ.addEventListener(SVGConstants.SVG_EVENT_CLICK, new EventListener() {
+ @Override
+ public void handleEvent(Event evt) {
+ proj.toggleDimInverted(i);
+ context.getHierarchy().resultChanged(proj);
+ }
+ }, false);
+ }
+
+ /**
+ * Factory for axis visualizations
+ *
+ * @author Robert Rödler
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses ParallelAxisVisualization oneway - - «create»
+ */
+ public static class Factory extends AbstractVisFactory {
+ /**
+ * A short name characterizing this Visualizer.
+ */
+ private static final String NAME = "Parallel Axes";
+
+ /**
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ */
+ public Factory() {
+ super();
+ }
+
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new ParallelAxisVisualization(task);
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Iterator<ParallelPlotProjector<?>> ps = ResultUtil.filteredResults(result, ParallelPlotProjector.class);
+ while(ps.hasNext()) {
+ ParallelPlotProjector<?> p = ps.next();
+ final VisualizationTask task = new VisualizationTask(NAME, p, p.getRelation(), this);
+ task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_BACKGROUND);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+
+ @Override
+ public boolean allowThumbnails(VisualizationTask task) {
+ // Don't use thumbnails
+ return true;
+ }
+ }
+}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/cluster/ClusterOutlineVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/cluster/ClusterOutlineVisualization.java new file mode 100644 index 00000000..821ca2a5 --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/cluster/ClusterOutlineVisualization.java @@ -0,0 +1,259 @@ +package de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.cluster;
+
+/*
+ This file is part of ELKI:
+ Environment for Developing KDD-Applications Supported by Index-Structures
+
+ Copyright (C) 2012
+ Ludwig-Maximilians-Universität München
+ Lehr- und Forschungseinheit für Datenbanksysteme
+ ELKI Development Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import java.util.Iterator;
+
+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.NumberVector;
+import de.lmu.ifi.dbs.elki.data.model.Model;
+import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener;
+import de.lmu.ifi.dbs.elki.database.ids.DBID;
+import de.lmu.ifi.dbs.elki.math.DoubleMinMax;
+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.IterableIterator;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID;
+import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
+import de.lmu.ifi.dbs.elki.visualization.colors.ColorLibrary;
+import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
+import de.lmu.ifi.dbs.elki.visualization.projector.ParallelPlotProjector;
+import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPath;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot;
+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;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.AbstractParallelVisualization;
+
+/**
+ * Generates a SVG-Element that visualizes the area covered by a cluster.
+ *
+ * @author Robert Rödler
+ * @author Erich Schubert
+ */
+public class ClusterOutlineVisualization extends AbstractParallelVisualization<NumberVector<?, ?>> implements DataStoreListener {
+ /**
+ * Generic tags to indicate the type of element. Used in IDs, CSS-Classes etc.
+ */
+ public static final String CLUSTERAREA = "Clusteroutline";
+
+ /**
+ * The result we visualize
+ */
+ private Clustering<Model> clustering;
+
+ /**
+ * Flag for using rounded shapes
+ */
+ boolean rounded = true;
+
+ /**
+ * Constructor.
+ *
+ * @param task VisualizationTask
+ */
+ public ClusterOutlineVisualization(VisualizationTask task, boolean rounded) {
+ super(task);
+ this.clustering = task.getResult();
+ this.rounded = rounded;
+ context.addDataStoreListener(this);
+ context.addResultListener(this);
+ incrementalRedraw();
+ }
+
+ @Override
+ public void destroy() {
+ context.removeDataStoreListener(this);
+ context.removeResultListener(this);
+ super.destroy();
+ }
+
+ @Override
+ protected void redraw() {
+ addCSSClasses(svgp);
+ int dim = proj.getVisibleDimensions();
+
+ DoubleMinMax[] mms = DoubleMinMax.newArray(dim);
+ DoubleMinMax[] midmm = DoubleMinMax.newArray(dim - 1);
+
+ Iterator<Cluster<Model>> ci = clustering.getAllClusters().iterator();
+ for(int cnum = 0; cnum < clustering.getAllClusters().size(); cnum++) {
+ Cluster<?> clus = ci.next();
+ for(int i = 0; i < dim; i++) {
+ mms[i].reset();
+ if(i < dim - 1) {
+ midmm[i].reset();
+ }
+ }
+
+ // Process points
+ // TODO: do this just once, cache the result somewhere appropriately?
+ for(DBID objId : clus.getIDs()) {
+ double[] yPos = proj.fastProjectDataToRenderSpace(relation.get(objId));
+ for(int i = 0; i < dim; i++) {
+ mms[i].put(yPos[i]);
+ if(i > 0) {
+ midmm[i - 1].put((yPos[i] + yPos[i - 1]) / 2.);
+ }
+ }
+ }
+
+ SVGPath path = new SVGPath();
+ if(!rounded) {
+ // Straight lines
+ for(int i = 0; i < dim; i++) {
+ path.drawTo(getVisibleAxisX(i), mms[i].getMax());
+ if(i < dim - 1) {
+ path.drawTo(getVisibleAxisX(i + .5), midmm[i].getMax());
+ }
+ }
+ for(int i = dim - 1; i >= 0; i--) {
+ if(i < dim - 1) {
+ path.drawTo(getVisibleAxisX(i + .5), midmm[i].getMin());
+ }
+ path.drawTo(getVisibleAxisX(i), mms[i].getMin());
+ }
+ path.close();
+ }
+ else {
+ // Maxima
+ path.drawTo(getVisibleAxisX(0), mms[0].getMax());
+ for(int i = 1; i < dim; i++) {
+ path.quadTo(getVisibleAxisX(i - .5), midmm[i - 1].getMax(), getVisibleAxisX(i), mms[i].getMax());
+ }
+ // Minima
+ path.drawTo(getVisibleAxisX(dim - 1), mms[dim - 1].getMin());
+ for(int i = dim - 1; i > 0; i--) {
+ path.quadTo(getVisibleAxisX(i - .5), midmm[i - 1].getMin(), getVisibleAxisX(i - 1), mms[i - 1].getMin());
+ }
+ path.close();
+ }
+
+ Element intervals = path.makeElement(svgp);
+ SVGUtil.addCSSClass(intervals, CLUSTERAREA + cnum);
+ layer.appendChild(intervals);
+ }
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ if(!svgp.getCSSClassManager().contains(CLUSTERAREA)) {
+ ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT);
+ int clusterID = 0;
+
+ for(@SuppressWarnings("unused")
+ Cluster<?> cluster : clustering.getAllClusters()) {
+ CSSClass cls = new CSSClass(this, CLUSTERAREA + clusterID);
+ // cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY,
+ // context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT) / 2.0);
+ cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, 0.5);
+ final String color;
+ if(clustering.getAllClusters().size() == 1) {
+ color = SVGConstants.CSS_BLACK_VALUE;
+ }
+ else {
+ color = colors.getColor(clusterID);
+ }
+ // cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, color);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, color);
+
+ svgp.addCSSClassOrLogError(cls);
+ clusterID++;
+ }
+ }
+ }
+
+ /**
+ * Factory for axis visualizations
+ *
+ * @author Robert Rödler
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses ClusterOutlineVisualization oneway - - «create»
+ */
+ public static class Factory extends AbstractVisFactory {
+ /**
+ * A short name characterizing this Visualizer.
+ */
+ private static final String NAME = "Cluster Outline";
+
+ /**
+ * Currently unused option to enable/disable rounding
+ */
+ public static final OptionID ROUNDED_ID = OptionID.getOrCreateOptionID("parallel.clusteroutline.rounded", "Draw lines rounded");
+
+ /**
+ * Currently, always enabled.
+ */
+ private boolean rounded = true;
+
+ /**
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ */
+ public Factory() {
+ super();
+ }
+
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new ClusterOutlineVisualization(task, rounded);
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ // Find clusterings we can visualize:
+ Iterator<Clustering<?>> clusterings = ResultUtil.filteredResults(result, Clustering.class);
+ while(clusterings.hasNext()) {
+ Clustering<?> c = clusterings.next();
+ if(c.getAllClusters().size() > 0) {
+ IterableIterator<ParallelPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ParallelPlotProjector.class);
+ for(ParallelPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
+ task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA - 1);
+ task.put(VisualizationTask.META_VISIBLE_DEFAULT, false);
+ baseResult.getHierarchy().add(c, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean allowThumbnails(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/parallel/cluster/ClusterParallelMeanVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/cluster/ClusterParallelMeanVisualization.java new file mode 100644 index 00000000..df731ac2 --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/cluster/ClusterParallelMeanVisualization.java @@ -0,0 +1,215 @@ +package de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.cluster;
+
+/*
+ This file is part of ELKI:
+ Environment for Developing KDD-Applications Supported by Index-Structures
+
+ Copyright (C) 2012
+ Ludwig-Maximilians-Universität München
+ Lehr- und Forschungseinheit für Datenbanksysteme
+ ELKI Development Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import java.util.Iterator;
+
+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.NumberVector;
+import de.lmu.ifi.dbs.elki.data.model.MeanModel;
+import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener;
+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.visualization.VisualizationTask;
+import de.lmu.ifi.dbs.elki.visualization.colors.ColorLibrary;
+import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
+import de.lmu.ifi.dbs.elki.visualization.projector.ParallelPlotProjector;
+import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPath;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot;
+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;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.AbstractParallelVisualization;
+
+/**
+ * Generates a SVG-Element that visualizes cluster means.
+ *
+ * @author Robert Rödler
+ */
+public class ClusterParallelMeanVisualization extends AbstractParallelVisualization<NumberVector<?, ?>> implements DataStoreListener {
+ /**
+ * Generic tags to indicate the type of element. Used in IDs, CSS-Classes etc.
+ */
+ public static final String CLUSTERMEAN = "Clustermean";
+
+ /**
+ * The result we visualize
+ */
+ private Clustering<MeanModel<? extends NumberVector<?, ?>>> clustering;
+
+ /**
+ * Constructor.
+ *
+ * @param task VisualizationTask
+ */
+ public ClusterParallelMeanVisualization(VisualizationTask task) {
+ super(task);
+ this.clustering = task.getResult();
+ context.addDataStoreListener(this);
+ context.addResultListener(this);
+ incrementalRedraw();
+ }
+
+ @Override
+ public void destroy() {
+ context.removeDataStoreListener(this);
+ context.removeResultListener(this);
+ super.destroy();
+ }
+
+ @Override
+ protected void redraw() {
+ addCSSClasses(svgp);
+
+ Iterator<Cluster<MeanModel<? extends NumberVector<?, ?>>>> ci = clustering.getAllClusters().iterator();
+ for(int cnum = 0; cnum < clustering.getAllClusters().size(); cnum++) {
+ Cluster<MeanModel<? extends NumberVector<?, ?>>> clus = ci.next();
+ NumberVector<?, ?> mean = clus.getModel().getMean();
+ if(mean == null) {
+ continue;
+ }
+
+ double[] pmean = proj.fastProjectDataToRenderSpace(mean);
+
+ SVGPath path = new SVGPath();
+ for(int i = 0; i < pmean.length; i++) {
+ path.drawTo(getVisibleAxisX(i), pmean[i]);
+ }
+
+ Element meanline = path.makeElement(svgp);
+ SVGUtil.addCSSClass(meanline, CLUSTERMEAN + cnum);
+ layer.appendChild(meanline);
+ }
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ if(!svgp.getCSSClassManager().contains(CLUSTERMEAN)) {
+ ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT);
+ int clusterID = 0;
+
+ for(@SuppressWarnings("unused")
+ Cluster<?> cluster : clustering.getAllClusters()) {
+ CSSClass cls = new CSSClass(this, CLUSTERMEAN + clusterID);
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT) * 2);
+
+ final String color;
+ if(clustering.getAllClusters().size() == 1) {
+ color = SVGConstants.CSS_BLACK_VALUE;
+ }
+ else {
+ color = colors.getColor(clusterID);
+ }
+
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, color);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+
+ svgp.addCSSClassOrLogError(cls);
+ clusterID++;
+ }
+ }
+ }
+
+ /**
+ * Factory for axis visualizations
+ *
+ * @author Robert Rödler
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses ClusterParallelMeanVisualization oneway - - «create»
+ *
+ */
+ public static class Factory extends AbstractVisFactory {
+ /**
+ * A short name characterizing this Visualizer.
+ */
+ private static final String NAME = "Cluster Means";
+
+ /**
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ */
+ public Factory() {
+ super();
+ }
+
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new ClusterParallelMeanVisualization(task);
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ // Find clusterings we can visualize:
+ Iterator<Clustering<?>> clusterings = ResultUtil.filteredResults(result, Clustering.class);
+ while(clusterings.hasNext()) {
+ Clustering<?> c = clusterings.next();
+ if(c.getAllClusters().size() > 0) {
+ // Does the cluster have a model with cluster means?
+ Clustering<MeanModel<? extends NumberVector<?, ?>>> mcls = findMeanModel(c);
+ if(mcls != null) {
+ Iterator<ParallelPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ParallelPlotProjector.class);
+ while(ps.hasNext()) {
+ ParallelPlotProjector<?> p = ps.next();
+ final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
+ task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA + 1);
+ baseResult.getHierarchy().add(c, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Test if the given clustering has a mean model.
+ *
+ * @param c Clustering to inspect
+ * @return the clustering cast to return a mean model, null otherwise.
+ */
+ @SuppressWarnings("unchecked")
+ private static Clustering<MeanModel<? extends NumberVector<?, ?>>> findMeanModel(Clustering<?> c) {
+ if(c.getAllClusters().get(0).getModel() instanceof MeanModel<?>) {
+ return (Clustering<MeanModel<? extends NumberVector<?, ?>>>) c;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean allowThumbnails(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/events/ResizedEvent.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/cluster/package-info.java index 6eb6cad3..7977da0c 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/events/ResizedEvent.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/cluster/package-info.java @@ -1,10 +1,11 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.events; - +/** + * <p>Visualizers for clustering results based on parallel coordinates.</p> + */ /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -22,28 +23,4 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.events; 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 de.lmu.ifi.dbs.elki.visualization.VisualizerContext; - -/** - * Event triggered when the contexts view was resized. - * - * @author Erich Schubert - * - * @apiviz.stereotype event - */ -public class ResizedEvent extends ContextChangedEvent { - /** - * Serial version - */ - private static final long serialVersionUID = 1L; - - /** - * Constructor. - * - * @param source Visualization context - */ - public ResizedEvent(VisualizerContext source) { - super(source); - } -}
\ No newline at end of file +package de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.cluster;
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/index/RTreeParallelVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/index/RTreeParallelVisualization.java new file mode 100644 index 00000000..c36dff0c --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/index/RTreeParallelVisualization.java @@ -0,0 +1,267 @@ +package de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.index;
+
+/*This file is part of ELKI:
+ Environment for Developing KDD-Applications Supported by Index-Structures
+
+ Copyright (C) 2012
+ Ludwig-Maximilians-Universität München
+ Lehr- und Forschungseinheit für Datenbanksysteme
+ ELKI Development Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import java.util.ArrayList;
+
+import org.apache.batik.util.SVGConstants;
+import org.w3c.dom.Element;
+
+import de.lmu.ifi.dbs.elki.data.NumberVector;
+import de.lmu.ifi.dbs.elki.data.spatial.SpatialUtil;
+import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener;
+import de.lmu.ifi.dbs.elki.index.tree.spatial.SpatialEntry;
+import de.lmu.ifi.dbs.elki.index.tree.spatial.rstarvariants.AbstractRStarTree;
+import de.lmu.ifi.dbs.elki.index.tree.spatial.rstarvariants.AbstractRStarTreeNode;
+import de.lmu.ifi.dbs.elki.index.tree.spatial.rstarvariants.rstar.RStarTreeNode;
+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.IterableIterator;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Flag;
+import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
+import de.lmu.ifi.dbs.elki.visualization.colors.ColorLibrary;
+import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
+import de.lmu.ifi.dbs.elki.visualization.projections.ProjectionParallel;
+import de.lmu.ifi.dbs.elki.visualization.projector.ParallelPlotProjector;
+import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPath;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot;
+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;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.AbstractParallelVisualization;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.index.TreeMBRVisualization;
+
+/**
+ * Visualize the of an R-Tree based index.
+ *
+ * @author Robert Rödler
+ *
+ * @apiviz.has AbstractRStarTree oneway - - visualizes
+ *
+ * @param <N> Tree node type
+ * @param <E> Tree entry type
+ */
+// TODO: listen for tree changes instead of data changes?
+public class RTreeParallelVisualization<N extends AbstractRStarTreeNode<N, E>, E extends SpatialEntry> extends AbstractParallelVisualization<NumberVector<?, ?>> implements DataStoreListener {
+ /**
+ * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc.
+ */
+ public static final String INDEX = "parallelrtree";
+
+ /**
+ * A short name characterizing this Visualizer.
+ */
+ public static final String NAME = "R-Tree Index MBRs";
+
+ /**
+ * Fill parameter.
+ */
+ protected boolean fill = true;
+
+ /**
+ * The tree we visualize
+ */
+ protected AbstractRStarTree<N, E> tree;
+
+ /**
+ * Constructor.
+ *
+ * @param task Visualization task
+ * @param fill Fill flag
+ */
+ @SuppressWarnings("unchecked")
+ public RTreeParallelVisualization(VisualizationTask task, boolean fill) {
+ super(task);
+ this.tree = AbstractRStarTree.class.cast(task.getResult());
+ this.fill = fill;
+ context.addDataStoreListener(this);
+ context.addResultListener(this);
+ incrementalRedraw();
+ }
+
+ @Override
+ public void destroy() {
+ context.removeDataStoreListener(this);
+ context.removeResultListener(this);
+ super.destroy();
+ }
+
+ @Override
+ protected void redraw() {
+ if(tree != null) {
+ addCSSClasses(svgp);
+ E root = tree.getRootEntry();
+ visualizeRTreeEntry(svgp, layer, proj, tree, root, 0, 0);
+ }
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT);
+
+ for(int i = 0; i < tree.getHeight(); i++) {
+
+ if(!svgp.getCSSClassManager().contains(INDEX + i)) {
+ CSSClass cls = new CSSClass(this, INDEX + i);
+
+ // Relative depth of this level. 1.0 = toplevel
+ final double relDepth = 1. - (((double) i) / tree.getHeight());
+ if(fill) {
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(i));
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, relDepth * context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT));
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, colors.getColor(i));
+ cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 0.2);
+ }
+ else {
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(i));
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, relDepth * context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT));
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ }
+ cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ svgp.addCSSClassOrLogError(cls);
+ }
+ }
+ svgp.updateStyleElement();
+ }
+
+ /**
+ * Recursively draw the MBR rectangles.
+ *
+ * @param svgp SVG Plot
+ * @param layer Layer
+ * @param proj Projection
+ * @param rtree Rtree to visualize
+ * @param entry Current entry
+ * @param depth Current depth
+ */
+ private void visualizeRTreeEntry(SVGPlot svgp, Element layer, ProjectionParallel proj, AbstractRStarTree<? extends N, E> rtree, E entry, int depth, int step) {
+ final int dim = proj.getVisibleDimensions();
+ double[] min = proj.fastProjectDataToRenderSpace(SpatialUtil.getMin(entry));
+ double[] max = proj.fastProjectDataToRenderSpace(SpatialUtil.getMax(entry));
+ assert (min.length == dim && max.length == dim);
+ SVGPath path = new SVGPath();
+ for(int i = 0; i < dim; i++) {
+ path.drawTo(getVisibleAxisX(i), Math.max(min[i], max[i]));
+ }
+ for(int i = dim - 1; i >= 0; i--) {
+ path.drawTo(getVisibleAxisX(i), Math.min(min[i], max[i]));
+ }
+ path.close();
+
+ Element intervals = path.makeElement(svgp);
+
+ SVGUtil.addCSSClass(intervals, INDEX + depth);
+ layer.appendChild(intervals);
+
+ if(!entry.isLeafEntry()) {
+ N node = rtree.getNode(entry);
+ for(int i = 0; i < node.getNumEntries(); i++) {
+ E child = node.getEntry(i);
+ if(!child.isLeafEntry()) {
+ visualizeRTreeEntry(svgp, layer, proj, rtree, child, depth + 1, ++step);
+ }
+ }
+ }
+ }
+
+ /**
+ * Factory
+ *
+ * @author Robert Rödler
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses RTreeParallelVisualization oneway - - «create»
+ */
+ public static class Factory extends AbstractVisFactory {
+ /**
+ * Fill parameter.
+ */
+ protected boolean fill = true;
+
+ /**
+ * Constructor.
+ *
+ * @param fill
+ */
+ public Factory(boolean fill) {
+ super();
+ this.fill = fill;
+ }
+
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new RTreeParallelVisualization<RStarTreeNode, SpatialEntry>(task, fill);
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ ArrayList<AbstractRStarTree<RStarTreeNode, SpatialEntry>> trees = ResultUtil.filterResults(result, AbstractRStarTree.class);
+ for(AbstractRStarTree<RStarTreeNode, SpatialEntry> tree : trees) {
+ if(tree instanceof Result) {
+ IterableIterator<ParallelPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ParallelPlotProjector.class);
+ for(ParallelPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, (Result) tree, p.getRelation(), this);
+ task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_BACKGROUND + 2);
+ baseResult.getHierarchy().add((Result) tree, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ }
+ }
+
+ /**
+ * Parameterization class.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ public static class Parameterizer extends AbstractParameterizer {
+ protected boolean fill = true;
+
+ @Override
+ protected void makeOptions(Parameterization config) {
+ super.makeOptions(config);
+ Flag fillF = new Flag(TreeMBRVisualization.Factory.FILL_ID);
+ fillF.setDefaultValue(true);
+ if(config.grab(fillF)) {
+ fill = fillF.getValue();
+ }
+ }
+
+ @Override
+ protected Factory makeInstance() {
+ return new Factory(fill);
+ }
+ }
+ }
+}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/events/ContextChangedEvent.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/index/package-info.java index 6d3aa24a..1cefd6f0 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/events/ContextChangedEvent.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/index/package-info.java @@ -1,10 +1,11 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.events; - +/** + * <p>Visualizers for index structure based on parallel coordinates.</p> + */ /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -22,30 +23,4 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.events; 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.EventObject; - -import de.lmu.ifi.dbs.elki.visualization.VisualizerContext; - -/** - * Event produced when the visualizer context has changed. - * - * @author Erich Schubert - * - * @apiviz.stereotype event - */ -public abstract class ContextChangedEvent extends EventObject { - /** - * Serial version - */ - private static final long serialVersionUID = 1L; - - /** - * Visualization context changed. - * - * @param source context that has changed - */ - public ContextChangedEvent(VisualizerContext source) { - super(source); - } -}
\ No newline at end of file +package de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.index;
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/package-info.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/package-info.java index f01505c7..0ab7d565 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/package-info.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/package-info.java @@ -6,7 +6,7 @@ This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures -Copyright (C) 2011 +Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionAxisRangeVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionAxisRangeVisualization.java new file mode 100644 index 00000000..cb8ee645 --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionAxisRangeVisualization.java @@ -0,0 +1,184 @@ +package de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.selection;
+
+/*
+ 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.ArrayList;
+
+import org.apache.batik.util.SVGConstants;
+import org.w3c.dom.Element;
+
+import de.lmu.ifi.dbs.elki.data.NumberVector;
+import de.lmu.ifi.dbs.elki.result.DBIDSelection;
+import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
+import de.lmu.ifi.dbs.elki.result.RangeSelection;
+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.utilities.iterator.IterableIterator;
+import de.lmu.ifi.dbs.elki.utilities.pairs.DoubleDoublePair;
+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.ParallelPlotProjector;
+import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot;
+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;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.AbstractParallelVisualization;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualization;
+
+/**
+ * Visualizer for generating an SVG-Element representing the selected range for
+ * each dimension
+ *
+ * @author Robert Rödler
+ *
+ * @apiviz.has SelectionResult oneway - - visualizes
+ * @apiviz.has RangeSelection oneway - - visualizes
+ */
+public class SelectionAxisRangeVisualization extends AbstractParallelVisualization<NumberVector<?, ?>> {
+ /**
+ * A short name characterizing this Visualizer.
+ */
+ private static final String NAME = "Selection Axis Range";
+
+ /**
+ * CSS Class for the range marker
+ */
+ public static final String MARKER = "selectionAxisRange";
+
+ /**
+ * Constructor.
+ *
+ * @param task Visualization task
+ */
+ public SelectionAxisRangeVisualization(VisualizationTask task) {
+ super(task);
+ addCSSClasses(svgp);
+ context.addResultListener(this);
+ incrementalRedraw();
+ }
+
+ @Override
+ public void destroy() {
+ context.removeResultListener(this);
+ super.destroy();
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ final StyleLibrary style = context.getStyleLibrary();
+ // Class for the cube
+ if(!svgp.getCSSClassManager().contains(MARKER)) {
+ CSSClass cls = new CSSClass(this, MARKER);
+ cls.setStatement(SVGConstants.CSS_STROKE_VALUE, style.getColor(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_STROKE_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT));
+ cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
+
+ svgp.addCSSClassOrLogError(cls);
+ }
+ }
+
+ @Override
+ protected void redraw() {
+ DBIDSelection selContext = context.getSelection();
+ if(selContext == null || !(selContext instanceof RangeSelection)) {
+ return;
+ }
+ DoubleDoublePair[] ranges = ((RangeSelection) selContext).getRanges();
+ if(ranges == null) {
+ return;
+ }
+
+ // Project:
+ double[] min = new double[ranges.length];
+ double[] max = new double[ranges.length];
+ for(int d = 0; d < ranges.length; d++) {
+ if(ranges[d] != null) {
+ min[d] = ranges[d].first;
+ max[d] = ranges[d].second;
+ }
+ }
+ min = proj.fastProjectDataToRenderSpace(min);
+ max = proj.fastProjectDataToRenderSpace(max);
+
+ int dim = proj.getVisibleDimensions();
+ for(int d = 0; d < dim; d++) {
+ if(ranges[proj.getDimForVisibleAxis(d)] != null) {
+ double amin = Math.min(min[d], max[d]);
+ double amax = Math.max(min[d], max[d]);
+ Element rect = svgp.svgRect(getVisibleAxisX(d) - (0.01 * StyleLibrary.SCALE), amin, 0.02 * StyleLibrary.SCALE, amax - amin);
+ SVGUtil.addCSSClass(rect, MARKER);
+ layer.appendChild(rect);
+ }
+ }
+ }
+
+ /**
+ * Factory for visualizers to generate an SVG-Element containing a cube as
+ * marker representing the selected range for each dimension
+ *
+ * @author Robert Rödler
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses SelectionAxisRangeVisualization oneway - - «create»
+ */
+ public static class Factory extends AbstractVisFactory {
+ /**
+ * Constructor.
+ */
+ public Factory() {
+ super();
+ thumbmask |= ThumbnailVisualization.ON_SELECTION;
+ }
+
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new SelectionAxisRangeVisualization(task);
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ final ArrayList<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
+ for(SelectionResult selres : selectionResults) {
+ IterableIterator<ParallelPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ParallelPlotProjector.class);
+ for(ParallelPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
+ task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA - 1);
+ baseResult.getHierarchy().add(selres, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionLineVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionLineVisualization.java new file mode 100644 index 00000000..f6de4133 --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionLineVisualization.java @@ -0,0 +1,167 @@ +package de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.selection;
+
+/*
+ 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.ArrayList;
+
+import org.apache.batik.util.SVGConstants;
+import org.w3c.dom.Element;
+
+import de.lmu.ifi.dbs.elki.data.NumberVector;
+import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener;
+import de.lmu.ifi.dbs.elki.database.ids.DBID;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDs;
+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.utilities.iterator.IterableIterator;
+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.ParallelPlotProjector;
+import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPath;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot;
+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;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.AbstractParallelVisualization;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualization;
+
+/**
+ * Visualizer for generating SVG-Elements representing the selected objects
+ *
+ * @author Robert Rödler
+ *
+ * @apiviz.has SelectionResult oneway - - visualizes
+ */
+public class SelectionLineVisualization extends AbstractParallelVisualization<NumberVector<?, ?>> implements DataStoreListener {
+ /**
+ * A short name characterizing this Visualizer.
+ */
+ private static final String NAME = "Selection Line";
+
+ /**
+ * CSS Class for the range marker
+ */
+ public static final String MARKER = "SelectionLine";
+
+ /**
+ * Constructor.
+ *
+ * @param task Visualization task
+ */
+ public SelectionLineVisualization(VisualizationTask task) {
+ super(task);
+ addCSSClasses(svgp);
+ context.addDataStoreListener(this);
+ context.addResultListener(this);
+ incrementalRedraw();
+ }
+
+ @Override
+ public void destroy() {
+ context.removeDataStoreListener(this);
+ context.removeResultListener(this);
+ super.destroy();
+ }
+
+ @Override
+ protected void redraw() {
+ DBIDSelection selContext = context.getSelection();
+ if(selContext != null) {
+ DBIDs selection = selContext.getSelectedIds();
+
+ for(DBID objId : selection) {
+ double[] yPos = proj.fastProjectDataToRenderSpace(relation.get(objId));
+
+ SVGPath path = new SVGPath();
+ for(int i = 0; i < proj.getVisibleDimensions(); i++) {
+ path.drawTo(getVisibleAxisX(i), yPos[i]);
+ }
+ Element marker = path.makeElement(svgp);
+ SVGUtil.addCSSClass(marker, MARKER);
+ layer.appendChild(marker);
+ }
+ }
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ final StyleLibrary style = context.getStyleLibrary();
+ // Class for the cube
+ if(!svgp.getCSSClassManager().contains(MARKER)) {
+ CSSClass cls = new CSSClass(this, MARKER);
+ cls.setStatement(SVGConstants.CSS_STROKE_VALUE, style.getColor(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_STROKE_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT) * 2.);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ svgp.addCSSClassOrLogError(cls);
+ }
+ }
+
+ /**
+ * Factory for visualizers to generate an SVG-Element containing a cube as
+ * marker representing the selected range for each dimension
+ *
+ * @author Robert Rödler
+ *
+ * @apiviz.stereotype factory
+ */
+ public static class Factory extends AbstractVisFactory {
+ /**
+ * Constructor.
+ */
+ public Factory() {
+ super();
+ thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION;
+ }
+
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new SelectionLineVisualization(task);
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ final ArrayList<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
+ for(SelectionResult selres : selectionResults) {
+ IterableIterator<ParallelPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ParallelPlotProjector.class);
+ for(ParallelPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
+ task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA -1);
+ baseResult.getHierarchy().add(selres, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionToolAxisRangeVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionToolAxisRangeVisualization.java new file mode 100644 index 00000000..ead8e5ce --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionToolAxisRangeVisualization.java @@ -0,0 +1,316 @@ +package de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.selection;
+
+/*
+ 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.ArrayList;
+
+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.NumberVector;
+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.ModifiableDBIDs;
+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.RangeSelection;
+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.utilities.iterator.IterableIterator;
+import de.lmu.ifi.dbs.elki.utilities.pairs.DoubleDoublePair;
+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.projections.Projection;
+import de.lmu.ifi.dbs.elki.visualization.projector.ParallelPlotProjector;
+import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot;
+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;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.AbstractParallelVisualization;
+
+/**
+ * Tool-Visualization for the tool to select axis ranges
+ *
+ * @author Robert Rödler
+ *
+ * @apiviz.has SelectionResult oneway - - updates
+ * @apiviz.has RangeSelection oneway - - updates
+ */
+public class SelectionToolAxisRangeVisualization extends AbstractParallelVisualization<NumberVector<?, ?>> implements DragableArea.DragListener {
+ /**
+ * The logger for this class.
+ */
+ protected static final Logging logger = Logging.getLogger(SelectionToolAxisRangeVisualization.class);
+
+ /**
+ * A short name characterizing this Visualizer.
+ */
+ private static final String NAME = "Axis Range Selection";
+
+ /**
+ * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc.
+ */
+ private static final String CSS_RANGEMARKER = "selectionAxisRangeMarker";
+
+ /**
+ * Element for selection rectangle
+ */
+ private Element rtag;
+
+ /**
+ * Element for the rectangle to add listeners
+ */
+ private Element etag;
+
+ /**
+ * Constructor.
+ *
+ * @param task Task
+ */
+ public SelectionToolAxisRangeVisualization(VisualizationTask task) {
+ super(task);
+ incrementalRedraw();
+ }
+
+ @Override
+ public void destroy() {
+ super.destroy();
+ }
+
+ @Override
+ protected void redraw() {
+ addCSSClasses(svgp);
+
+ // rtag: tag for the selected rect
+ rtag = svgp.svgElement(SVGConstants.SVG_G_TAG);
+ SVGUtil.addCSSClass(rtag, CSS_RANGEMARKER);
+ layer.appendChild(rtag);
+
+ // etag: sensitive area
+ DragableArea drag = new DragableArea(svgp, -.1 * getMarginLeft(), -.1 * getMarginTop(), getSizeX() + getMarginLeft() * .2, getSizeY() + getMarginTop() * .2, this);
+ etag = drag.getElement();
+ layer.appendChild(etag);
+ }
+
+ /**
+ * Delete the children of the element
+ *
+ * @param container SVG-Element
+ */
+ private void deleteChildren(Element container) {
+ while(container.hasChildNodes()) {
+ container.removeChild(container.getLastChild());
+ }
+ }
+
+ /**
+ * Set the selected ranges and the mask for the actual dimensions in the
+ * context
+ *
+ * @param x1 min x-value
+ * @param x2 max x-value
+ * @param y1 min y-value
+ * @param y2 max y-value
+ */
+ private void updateSelectionRectKoordinates(double x1, double x2, double y1, double y2, DoubleDoublePair[] ranges) {
+ final int dim = proj.getVisibleDimensions();
+ int minaxis = dim + 1;
+ int maxaxis = -1;
+ {
+ int i = 0;
+ while(i < dim) {
+ double axx = getVisibleAxisX(i);
+ if(x1 < axx || x2 < axx) {
+ minaxis = i;
+ break;
+ }
+ i++;
+ }
+ while(i <= dim) {
+ double axx = getVisibleAxisX(i);
+ if(x2 < axx && x1 < axx) {
+ maxaxis = i;
+ break;
+ }
+ i++;
+ }
+ }
+ double z1 = Math.max(Math.min(y1, y2), 0);
+ double z2 = Math.min(Math.max(y1, y2), getSizeY());
+ for(int i = minaxis; i < maxaxis; i++) {
+ double v1 = proj.fastProjectRenderToDataSpace(z1, i);
+ double v2 = proj.fastProjectRenderToDataSpace(z2, i);
+ if (logger.isDebugging()) {
+ logger.debug("Axis "+i+" dimension "+proj.getDimForVisibleAxis(i)+" "+v1+" to "+v2);
+ }
+ ranges[proj.getDimForVisibleAxis(i)] = new DoubleDoublePair(Math.min(v1, v2), Math.max(v1, v2));
+ }
+ }
+
+ @Override
+ public boolean startDrag(SVGPoint startPoint, Event evt) {
+ return true;
+ }
+
+ @Override
+ public boolean duringDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
+ deleteChildren(rtag);
+ double x = Math.min(startPoint.getX(), dragPoint.getX());
+ double y = Math.min(startPoint.getY(), dragPoint.getY());
+ double width = Math.abs(startPoint.getX() - dragPoint.getX());
+ double height = Math.abs(startPoint.getY() - dragPoint.getY());
+ rtag.appendChild(svgp.svgRect(x, y, width, height));
+ return true;
+ }
+
+ @Override
+ public boolean endDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
+ deleteChildren(rtag);
+ if(startPoint.getX() != dragPoint.getX() || startPoint.getY() != dragPoint.getY()) {
+ updateSelection(proj, startPoint, dragPoint);
+ }
+ return true;
+ }
+
+ /**
+ * Update the selection in the context.
+ *
+ * @param proj The projection
+ * @param p1 First Point of the selected rectangle
+ * @param p2 Second Point of the selected rectangle
+ */
+ private void updateSelection(Projection proj, SVGPoint p1, SVGPoint p2) {
+ DBIDSelection selContext = context.getSelection();
+ ModifiableDBIDs selection;
+ if(selContext != null) {
+ selection = DBIDUtil.newHashSet(selContext.getSelectedIds());
+ }
+ else {
+ selection = DBIDUtil.newHashSet();
+ }
+ DoubleDoublePair[] ranges;
+
+ if(p1 == null || p2 == null) {
+ logger.warning("no rect selected: p1: " + p1 + " p2: " + p2);
+ }
+ else {
+ double x1 = Math.min(p1.getX(), p2.getX());
+ double x2 = Math.max(p1.getX(), p2.getX());
+ double y1 = Math.max(p1.getY(), p2.getY());
+ double y2 = Math.min(p1.getY(), p2.getY());
+
+ int dim = proj.getInputDimensionality();
+ if(selContext instanceof RangeSelection) {
+ ranges = ((RangeSelection) selContext).getRanges();
+ }
+ else {
+ ranges = new DoubleDoublePair[dim];
+ }
+ updateSelectionRectKoordinates(x1, x2, y1, y2, ranges);
+
+ selection.clear();
+ boolean idIn = true;
+ for(DBID id : relation.iterDBIDs()) {
+ NumberVector<?, ?> dbTupel = relation.get(id);
+ idIn = true;
+ for(int i = 0; i < dim; i++) {
+ if(ranges != null && ranges[i] != null) {
+ if(dbTupel.doubleValue(i + 1) < ranges[i].first || dbTupel.doubleValue(i + 1) > ranges[i].second) {
+ idIn = false;
+ break;
+ }
+ }
+ }
+ if(idIn == true) {
+ selection.add(id);
+ }
+ }
+ context.setSelection(new RangeSelection(selection, ranges));
+ }
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ protected void addCSSClasses(SVGPlot svgp) {
+ // Class for the range marking
+ if(!svgp.getCSSClassManager().contains(CSS_RANGEMARKER)) {
+ final CSSClass rcls = new CSSClass(this, CSS_RANGEMARKER);
+ final StyleLibrary style = context.getStyleLibrary();
+ rcls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION_ACTIVE));
+ rcls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION_ACTIVE));
+ svgp.addCSSClassOrLogError(rcls);
+ }
+ }
+
+ /**
+ * Factory for tool visualizations for selecting ranges and the inclosed
+ * objects
+ *
+ * @author Robert Rödler
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses SelectionToolAxisRangeVisualization 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 Visualization makeVisualization(VisualizationTask task) {
+ return new SelectionToolAxisRangeVisualization(task);
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ final ArrayList<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
+ for(SelectionResult selres : selectionResults) {
+ IterableIterator<ParallelPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ParallelPlotProjector.class);
+ for(ParallelPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
+ task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE);
+ task.put(VisualizationTask.META_TOOL, true);
+ task.put(VisualizationTask.META_NOTHUMB, true);
+ task.put(VisualizationTask.META_NOEXPORT, true);
+ task.put(VisualizationTask.META_VISIBLE_DEFAULT, false);
+ baseResult.getHierarchy().add(selres, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ }
+ }
+}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionToolLineVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionToolLineVisualization.java new file mode 100644 index 00000000..966e754a --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionToolLineVisualization.java @@ -0,0 +1,331 @@ +package de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.selection;
+
+/*
+ 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.geom.Line2D;
+import java.util.ArrayList;
+
+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.data.NumberVector;
+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.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.utilities.iterator.IterableIterator;
+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.ParallelPlotProjector;
+import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot;
+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;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.AbstractParallelVisualization;
+
+/**
+ * Tool-Visualization for the tool to select objects
+ *
+ * @author Robert Rödler
+ *
+ * @apiviz.has SelectionResult oneway - - updates
+ * @apiviz.has DBIDSelection oneway - - updates
+ */
+public class SelectionToolLineVisualization extends AbstractParallelVisualization<NumberVector<?, ?>> implements DragableArea.DragListener {
+ /**
+ * A short name characterizing this Visualizer.
+ */
+ private static final String NAME = "Object Selection";
+
+ /**
+ * CSS class of the selection rectangle while selecting.
+ */
+ private static final String CSS_RANGEMARKER = "selectionRangeMarker";
+
+ /**
+ * Input modes
+ *
+ * @apiviz.exclude
+ */
+ private enum Mode {
+ REPLACE, ADD, INVERT
+ }
+
+ /**
+ * Element for selection rectangle
+ */
+ Element rtag;
+
+ /**
+ * Element for the rectangle to add listeners
+ */
+ Element etag;
+
+ /**
+ * Constructor.
+ *
+ * @param task Task
+ */
+ public SelectionToolLineVisualization(VisualizationTask task) {
+ super(task);
+ incrementalRedraw();
+ }
+
+ @Override
+ protected void redraw() {
+ addCSSClasses(svgp);
+
+ rtag = svgp.svgElement(SVGConstants.SVG_G_TAG);
+ SVGUtil.addCSSClass(rtag, CSS_RANGEMARKER);
+ layer.appendChild(rtag);
+
+ // etag: sensitive area
+ DragableArea drag = new DragableArea(svgp, -.1 * getMarginLeft(), -.5 * getMarginTop(), getSizeX() + .2 * getMarginLeft(), getMarginTop() * 1.5 + getSizeY(), this);
+ etag = drag.getElement();
+ layer.appendChild(etag);
+ }
+
+ /**
+ * Delete the children of the element
+ *
+ * @param container SVG-Element
+ */
+ private void deleteChildren(Element container) {
+ while(container.hasChildNodes()) {
+ container.removeChild(container.getLastChild());
+ }
+ }
+
+ @Override
+ public boolean startDrag(SVGPoint startPoint, Event evt) {
+ return true;
+ }
+
+ @Override
+ public boolean duringDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
+ deleteChildren(rtag);
+ double x = Math.min(startPoint.getX(), dragPoint.getX());
+ double y = Math.min(startPoint.getY(), dragPoint.getY());
+ double width = Math.abs(startPoint.getX() - dragPoint.getX());
+ double height = Math.abs(startPoint.getY() - dragPoint.getY());
+ rtag.appendChild(svgp.svgRect(x, y, width, height));
+ return true;
+ }
+
+ @Override
+ public boolean endDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
+ Mode mode = getInputMode(evt);
+ deleteChildren(rtag);
+ if(startPoint.getX() != dragPoint.getX() || startPoint.getY() != dragPoint.getY()) {
+ updateSelection(mode, startPoint, dragPoint);
+ }
+ return true;
+ }
+
+ /**
+ * Get the current input mode, on each mouse event.
+ *
+ * @param evt Mouse event.
+ * @return current 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;
+ }
+
+ /**
+ * Updates the selection in the context.<br>
+ *
+ * @param mode Input mode
+ * @param p1 first point of the selected rectangle
+ * @param p2 second point of the selected rectangle
+ */
+ private void updateSelection(Mode mode, SVGPoint p1, SVGPoint p2) {
+ DBIDSelection selContext = context.getSelection();
+ // Note: we rely on SET semantics below!
+ final HashSetModifiableDBIDs selection;
+ if(selContext == null || mode == Mode.REPLACE) {
+ selection = DBIDUtil.newHashSet();
+ }
+ else {
+ selection = DBIDUtil.newHashSet(selContext.getSelectedIds());
+ }
+ int[] axisrange = getAxisRange(Math.min(p1.getX(), p2.getX()), Math.max(p1.getX(), p2.getX()));
+ DBIDs objIds = ResultUtil.getSamplingResult(relation).getSample();
+ for(DBID objId : objIds){
+ double[] yPos = proj.fastProjectDataToRenderSpace(relation.get(objId));
+ if(checkSelected(axisrange, yPos, Math.max(p1.getX(), p2.getX()), Math.min(p1.getX(), p2.getX()), Math.max(p1.getY(), p2.getY()), Math.min(p1.getY(), p2.getY()))) {
+ if(mode == Mode.INVERT) {
+ if(!selection.contains(objId)) {
+ selection.add(objId);
+ }
+ else {
+ selection.remove(objId);
+ }
+ }
+ 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(objId);
+ }
+ }
+ }
+ context.setSelection(new DBIDSelection(selection));
+ }
+
+ private int[] getAxisRange(double x1, double x2) {
+ final int dim = proj.getVisibleDimensions();
+ int minaxis = 0;
+ int maxaxis = 0;
+ boolean minx = true;
+ boolean maxx = false;
+ int count = -1;
+ for(int i = 0; i < dim; i++) {
+ if(minx && getVisibleAxisX(i) > x1) {
+ minaxis = count;
+ minx = false;
+ maxx = true;
+ }
+ if(maxx && (getVisibleAxisX(i) > x2 || i == dim - 1)) {
+ maxaxis = count + 1;
+ if(i == dim - 1 && getVisibleAxisX(i) <= x2) {
+ maxaxis++;
+ }
+ break;
+ }
+ count = i;
+ }
+ return new int[] { minaxis, maxaxis };
+ }
+
+ private boolean checkSelected(int[] ar, double[] yPos, double x1, double x2, double y1, double y2) {
+ final int dim = proj.getVisibleDimensions();
+ if (ar[0] < 0) {
+ ar[0] = 0;
+ }
+ if(ar[1] >= dim) {
+ ar[1] = dim - 1;
+ }
+ for(int i = ar[0] + 1; i <= ar[1] - 1; i++) {
+ if(yPos[i] <= y1 && yPos[i] >= y2) {
+ return true;
+ }
+ }
+ Line2D.Double idline1 = new Line2D.Double(getVisibleAxisX(ar[0]), yPos[ar[0]], getVisibleAxisX(ar[0] + 1), yPos[ar[0] + 1]);
+ Line2D.Double idline2 = new Line2D.Double(getVisibleAxisX(ar[1] - 1), yPos[ar[1] - 1], getVisibleAxisX(ar[1]), yPos[ar[1]]);
+ Line2D.Double rectline1 = new Line2D.Double(x2, y1, x1, y1);
+ Line2D.Double rectline2 = new Line2D.Double(x2, y1, x2, y2);
+ Line2D.Double rectline3 = new Line2D.Double(x2, y2, x1, y2);
+ if(idline1.intersectsLine(rectline1) || idline1.intersectsLine(rectline2) || idline1.intersectsLine(rectline3)) {
+ return true;
+ }
+ Line2D.Double rectline4 = new Line2D.Double(x1, y1, x1, y2);
+ if(idline2.intersectsLine(rectline1) || idline2.intersectsLine(rectline4) || idline2.intersectsLine(rectline3)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ protected void addCSSClasses(SVGPlot svgp) {
+ // Class for the range marking
+ if(!svgp.getCSSClassManager().contains(CSS_RANGEMARKER)) {
+ final CSSClass rcls = new CSSClass(this, CSS_RANGEMARKER);
+ final StyleLibrary style = context.getStyleLibrary();
+ rcls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION_ACTIVE));
+ rcls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION_ACTIVE));
+ svgp.addCSSClassOrLogError(rcls);
+ }
+ }
+
+ /**
+ * Factory for tool visualizations for selecting objects
+ *
+ * @author Robert Rödler
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses SelectionToolLineVisualization - - «create»
+ *
+ */
+ public static class Factory extends AbstractVisFactory {
+ /**
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ */
+ public Factory() {
+ super();
+ }
+
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new SelectionToolLineVisualization(task);
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ final ArrayList<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
+ for(SelectionResult selres : selectionResults) {
+ IterableIterator<ParallelPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ParallelPlotProjector.class);
+ for(ParallelPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
+ task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE);
+ task.put(VisualizationTask.META_TOOL, true);
+ task.put(VisualizationTask.META_NOTHUMB, true);
+ task.put(VisualizationTask.META_NOEXPORT, true);
+ task.put(VisualizationTask.META_VISIBLE_DEFAULT, false);
+ baseResult.getHierarchy().add(selres, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ }
+ }
+}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/package-info.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/package-info.java new file mode 100644 index 00000000..e4589b05 --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/package-info.java @@ -0,0 +1,26 @@ +/** + * <p>Visualizers for object selection based on parallel projections.</p> + */ +/* + This file is part of ELKI: + Environment for Developing KDD-Applications Supported by Index-Structures + + Copyright (C) 2012 + Ludwig-Maximilians-Universität München + Lehr- und Forschungseinheit für Datenbanksysteme + ELKI Development Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.selection;
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/P2DVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractScatterplotVisualization.java index b6b4b418..cce9ea5d 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/P2DVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractScatterplotVisualization.java @@ -1,10 +1,10 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -28,7 +28,11 @@ import org.w3c.dom.Element; import de.lmu.ifi.dbs.elki.data.NumberVector; import de.lmu.ifi.dbs.elki.database.relation.Relation; +import de.lmu.ifi.dbs.elki.result.Result; +import de.lmu.ifi.dbs.elki.result.ResultUtil; +import de.lmu.ifi.dbs.elki.result.SamplingResult; import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; +import de.lmu.ifi.dbs.elki.visualization.projections.CanvasSize; import de.lmu.ifi.dbs.elki.visualization.projections.Projection2D; import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; @@ -43,7 +47,7 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisualization; * @apiviz.landmark * @apiviz.has Projection2D */ -public abstract class P2DVisualization<NV extends NumberVector<?, ?>> extends AbstractVisualization { +public abstract class AbstractScatterplotVisualization extends AbstractVisualization { /** * The current projection */ @@ -52,17 +56,23 @@ public abstract class P2DVisualization<NV extends NumberVector<?, ?>> extends Ab /** * The representation we visualize */ - final protected Relation<NV> rel; + final protected Relation<? extends NumberVector<?, ?>> rel; + + /** + * The DBID sample + */ + final protected SamplingResult sample; /** * Constructor. * * @param task Visualization task */ - public P2DVisualization(VisualizationTask task) { + public AbstractScatterplotVisualization(VisualizationTask task) { super(task); this.proj = task.getProj(); this.rel = task.getRelation(); + this.sample = ResultUtil.getSamplingResult(rel); final double margin = context.getStyleLibrary().getSize(StyleLibrary.MARGIN); this.layer = setupCanvas(svgp, proj, margin, task.getWidth(), task.getHeight()); } @@ -78,8 +88,21 @@ public abstract class P2DVisualization<NV extends NumberVector<?, ?>> extends Ab * @return wrapper element with appropriate view box. */ public static Element setupCanvas(SVGPlot svgp, Projection2D proj, double margin, double width, double height) { - Element layer = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG); - SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, proj.estimateTransformString(margin, width, height)); + final CanvasSize canvas = proj.estimateViewport(); + final double sizex = canvas.getDiffX(); + final double sizey = canvas.getDiffY(); + String transform = SVGUtil.makeMarginTransform(width, height, sizex, sizey, margin) + " translate(" + SVGUtil.fmt(sizex / 2) + " " + SVGUtil.fmt(sizey / 2) + ")"; + + final Element layer = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG); + SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); return layer; } + + @Override + public void resultChanged(Result current) { + super.resultChanged(current); + if(current == proj) { + synchronizedRedraw(); + } + } }
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/AbstractTooltipVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractTooltipVisualization.java index 4e0171ae..8ce6ad67 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/AbstractTooltipVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractTooltipVisualization.java @@ -1,10 +1,10 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -30,11 +30,10 @@ import org.w3c.dom.events.Event; import org.w3c.dom.events.EventListener; import org.w3c.dom.events.EventTarget; -import de.lmu.ifi.dbs.elki.data.NumberVector; -import de.lmu.ifi.dbs.elki.database.datastore.DataStoreEvent; import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener; import de.lmu.ifi.dbs.elki.database.ids.DBID; import de.lmu.ifi.dbs.elki.logging.LoggingUtil; +import de.lmu.ifi.dbs.elki.result.Result; import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; @@ -44,11 +43,9 @@ import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil; * General base class for a tooltip visualizer. * * @author Erich Schubert - * - * @param <NV> Number Vector */ // TODO: can we improve performance by not adding as many hovers? -public abstract class AbstractTooltipVisualization<NV extends NumberVector<NV, ?>> extends P2DVisualization<NV> implements DataStoreListener { +public abstract class AbstractTooltipVisualization extends AbstractScatterplotVisualization implements DataStoreListener { /** * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc. */ @@ -69,6 +66,21 @@ public abstract class AbstractTooltipVisualization<NV extends NumberVector<NV, ? */ public static final String TOOLTIP_AREA = "tooltip_area"; + /** + * Our event listener. + */ + EventListener hoverer = new EventListener() { + @Override + public void handleEvent(Event evt) { + handleHoverEvent(evt); + } + }; + + /** + * Constructor. + * + * @param task Visualization task + */ public AbstractTooltipVisualization(VisualizationTask task) { super(task); context.addDataStoreListener(this); @@ -84,22 +96,15 @@ public abstract class AbstractTooltipVisualization<NV extends NumberVector<NV, ? public void redraw() { setupCSS(svgp); - double dotsize = 2 * context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT); + double dotsize = context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT); - EventListener hoverer = new EventListener() { - @Override - public void handleEvent(Event evt) { - handleHoverEvent(evt); - } - }; - - for(DBID id : rel.iterDBIDs()) { + for(DBID id : sample.getSample()) { double[] v = proj.fastProjectDataToRenderSpace(rel.get(id)); Element tooltip = makeTooltip(id, v[0], v[1], dotsize); SVGUtil.addCSSClass(tooltip, TOOLTIP_HIDDEN); // sensitive area. - Element area = svgp.svgCircle(v[0], v[1], dotsize); + Element area = svgp.svgRect(v[0] - dotsize, v[1] - dotsize, 2 * dotsize, 2 * dotsize); SVGUtil.addCSSClass(area, TOOLTIP_AREA); EventTarget targ = (EventTarget) area; @@ -172,7 +177,9 @@ public abstract class AbstractTooltipVisualization<NV extends NumberVector<NV, ? abstract protected void setupCSS(SVGPlot svgp); @Override - public void contentChanged(DataStoreEvent e) { - synchronizedRedraw(); + public void resultChanged(Result current) { + if(sample == current) { + synchronizedRedraw(); + } } }
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/AxisVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AxisVisualization.java index 4dc7e820..a8f7fdca 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/AxisVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AxisVisualization.java @@ -1,10 +1,10 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -23,18 +23,14 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; along with this program. If not, see <http://www.gnu.org/licenses/>. */ -import java.util.Iterator; - import org.apache.batik.util.SVGConstants; import org.w3c.dom.Element; -import de.lmu.ifi.dbs.elki.data.NumberVector; -import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector; import de.lmu.ifi.dbs.elki.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.DatabaseUtil; -import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil; +import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator; import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; import de.lmu.ifi.dbs.elki.visualization.css.CSSClass; import de.lmu.ifi.dbs.elki.visualization.css.CSSClassManager.CSSNamingConflict; @@ -52,10 +48,8 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * @author Remigius Wojdanowski * * @apiviz.uses SVGSimpleLinearAxis - * - * @param <NV> Type of the DatabaseObject being visualized. */ -public class AxisVisualization<NV extends NumberVector<NV, ?>> extends P2DVisualization<NV> { +public class AxisVisualization extends AbstractScatterplotVisualization { /** * Constructor. * @@ -71,28 +65,28 @@ public class AxisVisualization<NV extends NumberVector<NV, ?>> extends P2DVisual int dim = DatabaseUtil.dimensionality(rel); // origin - double[] orig = proj.fastProjectScaledToRender(new Vector(dim)); + double[] orig = proj.fastProjectScaledToRenderSpace(new double[dim]); // diagonal point opposite to origin double[] diag = new double[dim]; for(int d2 = 0; d2 < dim; d2++) { diag[d2] = 1; } - diag = proj.fastProjectScaledToRender(new Vector(diag)); + diag = proj.fastProjectScaledToRenderSpace(diag); // compute angle to diagonal line, used for axis labeling. double diaga = Math.atan2(diag[1] - orig[1], diag[0] - orig[0]); - double alfontsize = 1.2 * context.getStyleLibrary().getTextSize(StyleLibrary.AXIS_LABEL); - CSSClass alcls = new CSSClass(svgp, "unmanaged"); + double alfontsize = 1.1 * context.getStyleLibrary().getTextSize(StyleLibrary.AXIS_LABEL); + CSSClass alcls = new CSSClass(AxisVisualization.class, "unmanaged"); alcls.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, SVGUtil.fmt(alfontsize)); alcls.setStatement(SVGConstants.CSS_FILL_PROPERTY, context.getStyleLibrary().getTextColor(StyleLibrary.AXIS_LABEL)); alcls.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, context.getStyleLibrary().getFontFamily(StyleLibrary.AXIS_LABEL)); // draw axes for(int d = 0; d < dim; d++) { - Vector v = new Vector(dim); - v.set(d, 1); + double[] v = new double[dim]; + v[d] = 1; // projected endpoint of axis - double[] ax = proj.fastProjectScaledToRender(v); + double[] ax = proj.fastProjectScaledToRenderSpace(v); boolean righthand = false; double axa = Math.atan2(ax[1] - orig[1], ax[0] - orig[0]); if(axa > diaga || (diaga > 0 && axa > diaga + Math.PI)) { @@ -102,7 +96,7 @@ public class AxisVisualization<NV extends NumberVector<NV, ?>> extends P2DVisual // " "+(axa*180/Math.PI)+" "+(diaga*180/Math.PI)); if(ax[0] != orig[0] || ax[1] != orig[1]) { try { - SVGSimpleLinearAxis.drawAxis(svgp, layer, proj.getScale(d), orig[0], orig[1], ax[0], ax[1], true, righthand, context.getStyleLibrary()); + SVGSimpleLinearAxis.drawAxis(svgp, layer, proj.getScale(d), orig[0], orig[1], ax[0], ax[1], righthand ? SVGSimpleLinearAxis.LabelStyle.RIGHTHAND : SVGSimpleLinearAxis.LabelStyle.LEFTHAND, context.getStyleLibrary()); // TODO: move axis labeling into drawAxis function. double offx = (righthand ? 1 : -1) * 0.02 * Projection.SCALE; double offy = (righthand ? 1 : -1) * 0.02 * Projection.SCALE; @@ -125,10 +119,8 @@ public class AxisVisualization<NV extends NumberVector<NV, ?>> extends P2DVisual * * @apiviz.stereotype factory * @apiviz.uses AxisVisualization oneway - - «create» - * - * @param <NV> */ - public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory { + public static class Factory extends AbstractVisFactory { /** * A short name characterizing this Visualizer. */ @@ -144,13 +136,13 @@ public class AxisVisualization<NV extends NumberVector<NV, ?>> extends P2DVisual @Override public Visualization makeVisualization(VisualizationTask task) { - return new AxisVisualization<NV>(task); + return new AxisVisualization(task); } @Override public void processNewResult(HierarchicalResult baseResult, Result result) { - Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(result, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) { + IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(result, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { final VisualizationTask task = new VisualizationTask(NAME, p, p.getRelation(), this); task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_BACKGROUND); baseResult.getHierarchy().add(p, task); diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/MarkerVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/MarkerVisualization.java new file mode 100644 index 00000000..7672ee93 --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/MarkerVisualization.java @@ -0,0 +1,175 @@ +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot; + +/* + This file is part of ELKI: + Environment for Developing KDD-Applications Supported by Index-Structures + + Copyright (C) 2012 + Ludwig-Maximilians-Universität München + Lehr- und Forschungseinheit für Datenbanksysteme + ELKI Development Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +import java.util.Iterator; + +import org.apache.batik.util.SVGConstants; +import org.w3c.dom.Element; + +import de.lmu.ifi.dbs.elki.data.NumberVector; +import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener; +import de.lmu.ifi.dbs.elki.database.ids.DBID; +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.exceptions.ObjectNotFoundException; +import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator; +import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; +import de.lmu.ifi.dbs.elki.visualization.projector.ScatterPlotProjector; +import de.lmu.ifi.dbs.elki.visualization.style.ClassStylingPolicy; +import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; +import de.lmu.ifi.dbs.elki.visualization.style.StyleResult; +import de.lmu.ifi.dbs.elki.visualization.style.StylingPolicy; +import de.lmu.ifi.dbs.elki.visualization.style.marker.MarkerLibrary; +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; +import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualization; + +/** + * Visualize e.g. a clustering using different markers for different clusters. + * This visualizer is not constraint to clusters. It can in fact visualize any + * kind of result we have a style source for. + * + * @author Erich Schubert + * + * @apiviz.uses StyleResult + */ +public class MarkerVisualization extends AbstractScatterplotVisualization implements DataStoreListener { + /** + * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc. + */ + public static final String DOTMARKER = "dot"; + + /** + * The result we visualize + */ + private StyleResult style; + + /** + * Constructor. + * + * @param task Visualization task + */ + public MarkerVisualization(VisualizationTask task) { + super(task); + this.style = task.getResult(); + context.addDataStoreListener(this); + context.addResultListener(this); + incrementalRedraw(); + } + + @Override + public void destroy() { + super.destroy(); + context.removeDataStoreListener(this); + context.removeResultListener(this); + } + + @Override + public void redraw() { + final MarkerLibrary ml = context.getStyleLibrary().markers(); + final double marker_size = context.getStyleLibrary().getSize(StyleLibrary.MARKERPLOT); + final StylingPolicy spol = style.getStylingPolicy(); + + if(spol instanceof ClassStylingPolicy) { + ClassStylingPolicy cspol = (ClassStylingPolicy) spol; + for(int cnum = cspol.getMinStyle(); cnum < cspol.getMaxStyle(); cnum++) { + for(Iterator<DBID> iter = cspol.iterateClass(cnum); iter.hasNext();) { + DBID cur = iter.next(); + try { + final NumberVector<?, ?> vec = rel.get(cur); + double[] v = proj.fastProjectDataToRenderSpace(vec); + ml.useMarker(svgp, layer, v[0], v[1], cnum, marker_size); + } + catch(ObjectNotFoundException e) { + // ignore. + } + } + } + } + else { + final String FILL = SVGConstants.CSS_FILL_PROPERTY + ":"; + // Color-based styling. Fall back to dots + for(DBID id : sample.getSample()) { + try { + double[] v = proj.fastProjectDataToRenderSpace(rel.get(id)); + Element dot = svgp.svgCircle(v[0], v[1], marker_size); + SVGUtil.addCSSClass(dot, DOTMARKER); + int col = spol.getColorForDBID(id); + SVGUtil.setAtt(dot, SVGConstants.SVG_STYLE_ATTRIBUTE, FILL + SVGUtil.colorToString(col)); + layer.appendChild(dot); + } + catch(ObjectNotFoundException e) { + // ignore. + } + } + } + } + + /** + * Visualization factory + * + * @author Erich Schubert + * + * @apiviz.stereotype factory + * @apiviz.uses MarkerVisualization oneway - - «create» + */ + public static class Factory extends AbstractVisFactory { + /** + * A short name characterizing this Visualizer. + */ + private static final String NAME = "Markers"; + + /** + * Constructor, adhering to + * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} + */ + public Factory() { + super(); + thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_STYLE; + } + + @Override + public Visualization makeVisualization(VisualizationTask task) { + return new MarkerVisualization(task); + } + + @Override + public void processNewResult(HierarchicalResult baseResult, Result result) { + // Find a style result to visualize: + IterableIterator<StyleResult> styleres = ResultUtil.filteredResults(result, StyleResult.class); + for(StyleResult c : styleres) { + IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { + final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this); + task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA); + baseResult.getHierarchy().add(c, task); + baseResult.getHierarchy().add(p, task); + } + } + } + } +}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/PolygonVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/PolygonVisualization.java index b0f18666..4dc78ecf 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/PolygonVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/PolygonVisualization.java @@ -1,10 +1,10 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -24,17 +24,13 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; */ import java.util.ArrayList; -import java.util.Iterator; import org.apache.batik.util.SVGConstants; import org.w3c.dom.Element; -import de.lmu.ifi.dbs.elki.data.DoubleVector; -import de.lmu.ifi.dbs.elki.data.NumberVector; import de.lmu.ifi.dbs.elki.data.spatial.Polygon; import de.lmu.ifi.dbs.elki.data.spatial.PolygonsObject; import de.lmu.ifi.dbs.elki.data.type.TypeUtil; -import de.lmu.ifi.dbs.elki.database.datastore.DataStoreEvent; import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener; import de.lmu.ifi.dbs.elki.database.ids.DBID; import de.lmu.ifi.dbs.elki.database.relation.Relation; @@ -44,7 +40,7 @@ import de.lmu.ifi.dbs.elki.result.Result; import de.lmu.ifi.dbs.elki.result.ResultUtil; import de.lmu.ifi.dbs.elki.utilities.DatabaseUtil; import de.lmu.ifi.dbs.elki.utilities.exceptions.ObjectNotFoundException; -import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil; +import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator; import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; import de.lmu.ifi.dbs.elki.visualization.css.CSSClass; import de.lmu.ifi.dbs.elki.visualization.projections.Projection2D; @@ -62,7 +58,7 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * * @apiviz.has PolygonsObject - - visualizes */ -public class PolygonVisualization<V extends NumberVector<?, ?>> extends P2DVisualization<V> implements DataStoreListener { +public class PolygonVisualization extends AbstractScatterplotVisualization implements DataStoreListener { /** * A short name characterizing this Visualizer. */ @@ -122,13 +118,13 @@ public class PolygonVisualization<V extends NumberVector<?, ?>> extends P2DVisua SVGPath path = new SVGPath(); for(Polygon ppoly : poly.getPolygons()) { Vector first = ppoly.get(0); - double[] f = proj.fastProjectDataToRenderSpace(first); + double[] f = proj.fastProjectDataToRenderSpace(first.getArrayRef()); path.moveTo(f[0], f[1]); for(Vector v : ppoly) { if(v == first) { continue; } - double[] p = proj.fastProjectDataToRenderSpace(v); + double[] p = proj.fastProjectDataToRenderSpace(v.getArrayRef()); path.drawTo(p[0], p[1]); } // close path. @@ -144,11 +140,6 @@ public class PolygonVisualization<V extends NumberVector<?, ?>> extends P2DVisua } } - @Override - public void contentChanged(DataStoreEvent e) { - synchronizedRedraw(); - } - /** * The visualization factory * @@ -167,7 +158,7 @@ public class PolygonVisualization<V extends NumberVector<?, ?>> extends P2DVisua @Override public Visualization makeVisualization(VisualizationTask task) { - return new PolygonVisualization<DoubleVector>(task); + return new PolygonVisualization(task); } @Override @@ -176,8 +167,8 @@ public class PolygonVisualization<V extends NumberVector<?, ?>> extends P2DVisua for(Relation<?> rel : results) { if(TypeUtil.POLYGON_TYPE.isAssignableFromType(rel.getDataTypeInformation())) { // Assume that a 2d projector is using the same coordinates as the polygons. - Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) { + IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { if(DatabaseUtil.dimensionality(p.getRelation()) == 2) { final VisualizationTask task = new VisualizationTask(NAME, rel, p.getRelation(), this); task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA - 10); diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/ReferencePointsVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ReferencePointsVisualization.java index df1aac20..644fc96f 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/ReferencePointsVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ReferencePointsVisualization.java @@ -1,10 +1,10 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -34,7 +34,7 @@ import de.lmu.ifi.dbs.elki.result.HierarchicalResult; import de.lmu.ifi.dbs.elki.result.ReferencePointsResult; 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.utilities.iterator.IterableIterator; 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.ScatterPlotProjector; @@ -52,7 +52,7 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * @apiviz.has ReferencePointsResult oneway - - visualizes */ // TODO: add a result listener for the reference points. -public class ReferencePointsVisualization<NV extends NumberVector<NV, ?>> extends P2DVisualization<NV> { +public class ReferencePointsVisualization extends AbstractScatterplotVisualization { /** * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc. */ @@ -66,7 +66,7 @@ public class ReferencePointsVisualization<NV extends NumberVector<NV, ?>> extend /** * Serves reference points. */ - protected ReferencePointsResult<NV> result; + protected ReferencePointsResult<? extends NumberVector<?, ?>> result; /** * Constructor. @@ -82,11 +82,11 @@ public class ReferencePointsVisualization<NV extends NumberVector<NV, ?>> extend @Override public void redraw() { setupCSS(svgp); - Iterator<NV> iter = result.iterator(); + Iterator<? extends NumberVector<?, ?>> iter = result.iterator(); final double dotsize = context.getStyleLibrary().getSize(StyleLibrary.REFERENCE_POINTS); while(iter.hasNext()) { - NV v = iter.next(); + NumberVector<?, ?> v = iter.next(); double[] projected = proj.fastProjectDataToRenderSpace(v); Element dot = svgp.svgCircle(projected[0], projected[1], dotsize); SVGUtil.addCSSClass(dot, REFPOINT); @@ -112,10 +112,8 @@ public class ReferencePointsVisualization<NV extends NumberVector<NV, ?>> extend * * @apiviz.stereotype factory * @apiviz.uses ReferencePointsVisualization oneway - - «create» - * - * @param <NV> Type of the DatabaseObject being visualized. */ - public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory { + public static class Factory extends AbstractVisFactory { /** * Constructor, adhering to * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} @@ -126,10 +124,10 @@ public class ReferencePointsVisualization<NV extends NumberVector<NV, ?>> extend @Override public void processNewResult(HierarchicalResult baseResult, Result result) { - Collection<ReferencePointsResult<NV>> rps = ResultUtil.filterResults(result, ReferencePointsResult.class); - for(ReferencePointsResult<NV> rp : rps) { - Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) { + Collection<ReferencePointsResult<?>> rps = ResultUtil.filterResults(result, ReferencePointsResult.class); + for(ReferencePointsResult<?> rp : rps) { + IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { final VisualizationTask task = new VisualizationTask(NAME, rp, p.getRelation(), this); task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA); baseResult.getHierarchy().add(rp, task); @@ -140,7 +138,7 @@ public class ReferencePointsVisualization<NV extends NumberVector<NV, ?>> extend @Override public Visualization makeVisualization(VisualizationTask task) { - return new ReferencePointsVisualization<NV>(task); + return new ReferencePointsVisualization(task); } } }
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/ToolBox2DVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ToolBox2DVisualization.java index c2339fe2..7ec5f1ac 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/ToolBox2DVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ToolBox2DVisualization.java @@ -1,4 +1,4 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d;
+package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot;
/*
This file is part of ELKI:
@@ -24,7 +24,6 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; */
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
import org.apache.batik.util.SVGConstants;
@@ -33,17 +32,15 @@ import org.w3c.dom.events.Event; import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;
-import de.lmu.ifi.dbs.elki.data.NumberVector;
import de.lmu.ifi.dbs.elki.logging.Logging;
-import de.lmu.ifi.dbs.elki.math.DoubleMinMax;
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.FormatUtil;
-import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil;
-import de.lmu.ifi.dbs.elki.utilities.pairs.Pair;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator;
import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
+import de.lmu.ifi.dbs.elki.visualization.projections.CanvasSize;
import de.lmu.ifi.dbs.elki.visualization.projector.ScatterPlotProjector;
import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot;
@@ -51,7 +48,6 @@ 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;
import de.lmu.ifi.dbs.elki.visualization.visualizers.VisualizerUtil;
-import de.lmu.ifi.dbs.elki.visualization.visualizers.events.ContextChangedEvent;
/**
* Renders a tool box on the left of the 2D visualization
@@ -59,10 +55,8 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.events.ContextChangedEvent; * @author Heidi Kolb
*
* @apiviz.has VisualizationTask oneway - - visualizes
- *
- * @param <NV> Type of the NumberVector being visualized.
*/
-public class ToolBox2DVisualization<NV extends NumberVector<NV, ?>> extends P2DVisualization<NV> {
+public class ToolBox2DVisualization extends AbstractScatterplotVisualization {
/**
* A short name characterizing this Visualizer.
*/
@@ -101,17 +95,11 @@ public class ToolBox2DVisualization<NV extends NumberVector<NV, ?>> extends P2DV public ToolBox2DVisualization(VisualizationTask task) {
super(task);
// TODO: which result do we best attach to?
- context.addContextChangeListener(this);
context.addResultListener(this);
incrementalRedraw();
}
@Override
- public void contextChanged(ContextChangedEvent e) {
- synchronizedRedraw();
- }
-
- @Override
protected void redraw() {
addCSSClasses(svgp);
container = svgp.svgElement(SVGConstants.SVG_G_TAG);
@@ -146,12 +134,12 @@ public class ToolBox2DVisualization<NV extends NumberVector<NV, ?>> extends P2DV }
// calculate the position of the first tool
- Pair<DoubleMinMax, DoubleMinMax> pt = proj.estimateViewport();
- double x = pt.getFirst().getMin() - 0.17 * scale;
+ CanvasSize viewport = proj.estimateViewport();
+ double x = viewport.getMinX() - 0.17 * scale;
double width = 0.07 * scale;
double height = 0.06 * scale;
- double miny = pt.getSecond().getMin();
- double maxy = pt.getSecond().getMax();
+ double miny = viewport.getMinY();
+ double maxy = viewport.getMaxY();
double y = (miny + maxy) / 2 - (vis.size() * height * 1.4) / 2;
if(y < miny) {
logger.warning("Too many Tools");
@@ -248,7 +236,7 @@ public class ToolBox2DVisualization<NV extends NumberVector<NV, ?>> extends P2DV if(VisualizerUtil.isVisible(tool)) {
context.setSelection(null);
}
- context.setVisualizationVisibility(tool, true);
+ VisualizerUtil.setVisible(context, tool, true);
}
@Override
@@ -288,10 +276,8 @@ public class ToolBox2DVisualization<NV extends NumberVector<NV, ?>> extends P2DV *
* @apiviz.stereotype factory
* @apiviz.uses ToolBox2DVisualization oneway - - «create»
- *
- * @param <NV> Type of the NumberVector being visualized.
*/
- public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory {
+ public static class Factory extends AbstractVisFactory {
/**
* Constructor
*/
@@ -301,17 +287,18 @@ public class ToolBox2DVisualization<NV extends NumberVector<NV, ?>> extends P2DV @Override
public Visualization makeVisualization(VisualizationTask task) {
- return new ToolBox2DVisualization<NV>(task);
+ return new ToolBox2DVisualization(task);
}
@Override
public void processNewResult(HierarchicalResult baseResult, Result result) {
- Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(result, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) {
+ IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(result, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
final VisualizationTask task = new VisualizationTask(NAME, p, p.getRelation(), this);
task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE);
task.put(VisualizationTask.META_NOTHUMB, true);
task.put(VisualizationTask.META_NOEXPORT, true);
+ task.put(VisualizationTask.META_NOEMBED, true);
baseResult.getHierarchy().add(p, task);
}
}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/TooltipScoreVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipScoreVisualization.java index 309cc9c7..838390d0 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/TooltipScoreVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipScoreVisualization.java @@ -1,10 +1,10 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -24,21 +24,20 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; */ import java.text.NumberFormat; -import java.util.Iterator; import java.util.List; import java.util.Locale; import org.apache.batik.util.SVGConstants; import org.w3c.dom.Element; -import de.lmu.ifi.dbs.elki.data.NumberVector; +import de.lmu.ifi.dbs.elki.data.type.TypeUtil; import de.lmu.ifi.dbs.elki.database.ids.DBID; import de.lmu.ifi.dbs.elki.database.relation.Relation; 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.outlier.OutlierResult; -import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil; +import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator; import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer; import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID; import de.lmu.ifi.dbs.elki.utilities.optionhandling.constraints.GreaterEqualConstraint; @@ -59,13 +58,18 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * * @author Remigius Wojdanowski */ -public class TooltipScoreVisualization<NV extends NumberVector<NV, ?>> extends AbstractTooltipVisualization<NV> { +public class TooltipScoreVisualization extends AbstractTooltipVisualization { /** * A short name characterizing this Visualizer. */ public static final String NAME = "Outlier Score Tooltips"; /** + * A short name characterizing this Visualizer. + */ + public static final String NAME_GEN = "Score Tooltips"; + + /** * Number format. */ NumberFormat nf; @@ -142,10 +146,8 @@ public class TooltipScoreVisualization<NV extends NumberVector<NV, ?>> extends A * * @apiviz.stereotype factory * @apiviz.uses TooltipScoreVisualization oneway - - «create» - * - * @param <NV> Data type visualized. */ - public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory { + public static class Factory extends AbstractVisFactory { /** * Parameter for the gamma-correction. * @@ -178,7 +180,7 @@ public class TooltipScoreVisualization<NV extends NumberVector<NV, ?>> extends A @Override public Visualization makeVisualization(VisualizationTask task) { - return new TooltipScoreVisualization<NV>(task, nf); + return new TooltipScoreVisualization(task, nf); } @Override @@ -186,14 +188,39 @@ public class TooltipScoreVisualization<NV extends NumberVector<NV, ?>> extends A // TODO: we can also visualize other scores! List<OutlierResult> ors = ResultUtil.filterResults(result, OutlierResult.class); for(OutlierResult o : ors) { - Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) { + IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { final VisualizationTask task = new VisualizationTask(NAME, o.getScores(), p.getRelation(), this); task.put(VisualizationTask.META_TOOL, true); + task.put(VisualizationTask.META_VISIBLE_DEFAULT, false); baseResult.getHierarchy().add(o.getScores(), task); baseResult.getHierarchy().add(p, task); } } + List<Relation<?>> rrs = ResultUtil.filterResults(result, Relation.class); + for(Relation<?> r : rrs) { + if(!TypeUtil.DOUBLE.isAssignableFromType(r.getDataTypeInformation())) { + continue; + } + // Skip if we already considered it above + boolean add = true; + for(Result p : baseResult.getHierarchy().getChildren(r)) { + if(p instanceof VisualizationTask && ((VisualizationTask) p).getFactory() instanceof Factory) { + add = false; + break; + } + } + if(add) { + IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { + final VisualizationTask task = new VisualizationTask(NAME_GEN, r, p.getRelation(), this); + task.put(VisualizationTask.META_TOOL, true); + task.put(VisualizationTask.META_VISIBLE_DEFAULT, false); + baseResult.getHierarchy().add(r, task); + baseResult.getHierarchy().add(p, task); + } + } + } } /** @@ -203,7 +230,7 @@ public class TooltipScoreVisualization<NV extends NumberVector<NV, ?>> extends A * * @apiviz.exclude */ - public static class Parameterizer<NV extends NumberVector<NV, ?>> extends AbstractParameterizer { + public static class Parameterizer extends AbstractParameterizer { protected int digits = 4; @Override @@ -217,8 +244,8 @@ public class TooltipScoreVisualization<NV extends NumberVector<NV, ?>> extends A } @Override - protected Factory<NV> makeInstance() { - return new Factory<NV>(digits); + protected Factory makeInstance() { + return new Factory(digits); } } } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/TooltipStringVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipStringVisualization.java index 044dc66f..9baea926 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/TooltipStringVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipStringVisualization.java @@ -1,10 +1,10 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -24,7 +24,6 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; */ import java.util.ArrayList; -import java.util.Iterator; import org.apache.batik.util.SVGConstants; import org.w3c.dom.Element; @@ -32,13 +31,12 @@ import org.w3c.dom.Element; import de.lmu.ifi.dbs.elki.data.ClassLabel; import de.lmu.ifi.dbs.elki.data.ExternalID; import de.lmu.ifi.dbs.elki.data.LabelList; -import de.lmu.ifi.dbs.elki.data.NumberVector; import de.lmu.ifi.dbs.elki.database.ids.DBID; import de.lmu.ifi.dbs.elki.database.relation.Relation; 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.utilities.iterator.IterableIterator; 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.ScatterPlotProjector; @@ -56,10 +54,8 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * @author Erich Schubert * * @apiviz.has Relation oneway - - visualizes - * - * @param <NV> Data type visualized. */ -public class TooltipStringVisualization<NV extends NumberVector<NV, ?>> extends AbstractTooltipVisualization<NV> { +public class TooltipStringVisualization extends AbstractTooltipVisualization { /** * A short name characterizing this Visualizer. */ @@ -161,10 +157,8 @@ public class TooltipStringVisualization<NV extends NumberVector<NV, ?>> extends * * @apiviz.stereotype factory * @apiviz.uses TooltipStringVisualization oneway - - «create» - * - * @param <NV> */ - public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory { + public static class Factory extends AbstractVisFactory { /** * Constructor, adhering to * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} @@ -175,7 +169,7 @@ public class TooltipStringVisualization<NV extends NumberVector<NV, ?>> extends @Override public Visualization makeVisualization(VisualizationTask task) { - return new TooltipStringVisualization<NV>(task); + return new TooltipStringVisualization(task); } @Override @@ -183,37 +177,41 @@ public class TooltipStringVisualization<NV extends NumberVector<NV, ?>> extends ArrayList<Relation<?>> reps = ResultUtil.filterResults(result, Relation.class); for(Relation<?> rep : reps) { if(DBID.class.isAssignableFrom(rep.getDataTypeInformation().getRestrictionClass())) { - Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) { + IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { final VisualizationTask task = new VisualizationTask(NAME_ID, rep, p.getRelation(), this); task.put(VisualizationTask.META_TOOL, true); + task.put(VisualizationTask.META_VISIBLE_DEFAULT, false); baseResult.getHierarchy().add(rep, task); baseResult.getHierarchy().add(p, task); } } if(ClassLabel.class.isAssignableFrom(rep.getDataTypeInformation().getRestrictionClass())) { - Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) { + IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { final VisualizationTask task = new VisualizationTask(NAME_CLASS, rep, p.getRelation(), this); task.put(VisualizationTask.META_TOOL, true); + task.put(VisualizationTask.META_VISIBLE_DEFAULT, false); baseResult.getHierarchy().add(rep, task); baseResult.getHierarchy().add(p, task); } } if(LabelList.class.isAssignableFrom(rep.getDataTypeInformation().getRestrictionClass())) { - Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) { + IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { final VisualizationTask task = new VisualizationTask(NAME_LABEL, rep, p.getRelation(), this); task.put(VisualizationTask.META_TOOL, true); + task.put(VisualizationTask.META_VISIBLE_DEFAULT, false); baseResult.getHierarchy().add(rep, task); baseResult.getHierarchy().add(p, task); } } if(ExternalID.class.isAssignableFrom(rep.getDataTypeInformation().getRestrictionClass())) { - Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) { + IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { final VisualizationTask task = new VisualizationTask(NAME_EID, rep, p.getRelation(), this); task.put(VisualizationTask.META_TOOL, true); + task.put(VisualizationTask.META_VISIBLE_DEFAULT, false); baseResult.getHierarchy().add(rep, task); baseResult.getHierarchy().add(p, task); } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterHullVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterHullVisualization.java new file mode 100644 index 00000000..430ab194 --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterHullVisualization.java @@ -0,0 +1,276 @@ +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.cluster;
+
+/*
+ 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.ArrayList;
+import java.util.Collection;
+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.Model;
+import de.lmu.ifi.dbs.elki.data.spatial.Polygon;
+import de.lmu.ifi.dbs.elki.data.spatial.SpatialUtil;
+import de.lmu.ifi.dbs.elki.database.ids.DBID;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDs;
+import de.lmu.ifi.dbs.elki.math.geometry.AlphaShape;
+import de.lmu.ifi.dbs.elki.math.geometry.GrahamScanConvexHull2D;
+import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector;
+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.IterableIterator;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.DoubleParameter;
+import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
+import de.lmu.ifi.dbs.elki.visualization.colors.ColorLibrary;
+import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
+import de.lmu.ifi.dbs.elki.visualization.projections.CanvasSize;
+import de.lmu.ifi.dbs.elki.visualization.projections.Projection;
+import de.lmu.ifi.dbs.elki.visualization.projector.ScatterPlotProjector;
+import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPath;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot;
+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;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatterplotVisualization;
+
+/**
+ * Visualizer for generating an SVG-Element containing the convex hull / alpha
+ * shape of each cluster.
+ *
+ * @author Robert Rödler
+ * @author Erich Schubert
+ *
+ * @apiviz.has Clustering oneway - - visualizes
+ * @apiviz.uses ConvexHull2D
+ * @apiviz.uses AlphaShape
+ */
+public class ClusterHullVisualization extends AbstractScatterplotVisualization {
+ /**
+ * A short name characterizing this Visualizer.
+ */
+ private static final String NAME = "Cluster Hull Visualization";
+
+ /**
+ * Generic tags to indicate the type of element. Used in IDs, CSS-Classes etc.
+ */
+ public static final String CLUSTERHULL = "cluster-hull";
+
+ /**
+ * The result we work on
+ */
+ Clustering<Model> clustering;
+
+ /**
+ * Alpha value
+ */
+ double alpha = Double.POSITIVE_INFINITY;
+
+ /**
+ * Constructor
+ *
+ * @param task VisualizationTask
+ * @param alpha Alpha value
+ */
+ public ClusterHullVisualization(VisualizationTask task, double alpha) {
+ super(task);
+ this.clustering = task.getResult();
+ this.alpha = alpha;
+ incrementalRedraw();
+ }
+
+ @Override
+ protected void redraw() {
+ // Viewport size, for "relative size" computations
+ final CanvasSize viewp = proj.estimateViewport();
+ double projarea = viewp.getDiffX() * viewp.getDiffY();
+
+ double opacity = 0.25;
+
+ Iterator<Cluster<Model>> ci = clustering.getAllClusters().iterator();
+
+ for(int cnum = 0; cnum < clustering.getAllClusters().size(); cnum++) {
+ Cluster<?> clus = ci.next();
+ final DBIDs ids = clus.getIDs();
+
+ if(alpha >= Double.POSITIVE_INFINITY) {
+ GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D();
+
+ for(DBID clpnum : ids) {
+ double[] projP = proj.fastProjectDataToRenderSpace(rel.get(clpnum));
+ hull.add(new Vector(projP));
+ }
+ Polygon chres = hull.getHull();
+
+ // Plot the convex hull:
+ if(chres != null && chres.size() > 1) {
+ SVGPath path = new SVGPath(chres);
+ // Approximate area (using bounding box)
+ double hullarea = SpatialUtil.volume(chres);
+ final double relativeArea = (projarea - hullarea) / projarea;
+ final double relativeSize = (double) ids.size() / rel.size();
+ opacity = Math.sqrt(relativeSize * relativeArea);
+
+ Element hulls = path.makeElement(svgp);
+ addCSSClasses(svgp, cnum, opacity);
+ SVGUtil.addCSSClass(hulls, CLUSTERHULL + cnum);
+ layer.appendChild(hulls);
+ }
+ }
+ else {
+ ArrayList<Vector> ps = new ArrayList<Vector>(ids.size());
+ for(DBID clpnum : ids) {
+ double[] projP = proj.fastProjectDataToRenderSpace(rel.get(clpnum));
+ ps.add(new Vector(projP));
+ }
+ List<Polygon> polys = (new AlphaShape(ps, alpha * Projection.SCALE)).compute();
+ for(Polygon p : polys) {
+ SVGPath path = new SVGPath(p);
+ Element hulls = path.makeElement(svgp);
+ addCSSClasses(svgp, cnum, 0.5);
+ SVGUtil.addCSSClass(hulls, CLUSTERHULL + cnum);
+ layer.appendChild(hulls);
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp, int clusterID, double opac) {
+ ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT);
+
+ CSSClass cls = new CSSClass(this, CLUSTERHULL + clusterID);
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT));
+
+ final String color;
+ if(clustering.getAllClusters().size() == 1) {
+ color = "black";
+ }
+ else {
+ color = colors.getColor(clusterID);
+ }
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, color);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, color);
+ cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, opac);
+
+ svgp.addCSSClassOrLogError(cls);
+ }
+
+ /**
+ * Factory for visualizers to generate an SVG-Element containing the convex
+ * hull or alpha shape of a cluster.
+ *
+ * @author Robert Rödler
+ * @author Erich Schubert
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses ClusterHullVisualization oneway - - «create»
+ */
+ public static class Factory extends AbstractVisFactory {
+ /**
+ * Alpha value
+ */
+ double alpha = Double.POSITIVE_INFINITY;
+
+ /**
+ * Constructor.
+ *
+ * @param alpha Alpha value
+ */
+ public Factory(double alpha) {
+ super();
+ this.alpha = alpha;
+ }
+
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new ClusterHullVisualization(task, alpha);
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ // Find clusterings we can visualize:
+ Collection<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class);
+ for(Clustering<?> c : clusterings) {
+ IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
+ task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA - 1);
+ task.put(VisualizationTask.META_VISIBLE_DEFAULT, false);
+ baseResult.getHierarchy().add(c, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ }
+
+ /**
+ * Parameterization class.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ public static class Parameterizer extends AbstractParameterizer {
+ /**
+ * Alpha-Value for alpha-shapes
+ *
+ * <p>
+ * Key: {@code -hull.alpha}
+ * </p>
+ */
+ public static final OptionID ALPHA_ID = OptionID.getOrCreateOptionID("hull.alpha", "Alpha value for hull drawing (in projected space!).");
+
+ /**
+ * Alpha value
+ */
+ double alpha = Double.POSITIVE_INFINITY;
+
+ @Override
+ protected void makeOptions(Parameterization config) {
+ super.makeOptions(config);
+ DoubleParameter alphaP = new DoubleParameter(ALPHA_ID, Double.POSITIVE_INFINITY);
+ if(config.grab(alphaP)) {
+ alpha = alphaP.getValue();
+ }
+ }
+
+ @Override
+ protected Factory makeInstance() {
+ return new Factory(alpha);
+ }
+ }
+ }
+}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/ClusterMeanVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterMeanVisualization.java index d1f1712d..c9443a9f 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/ClusterMeanVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterMeanVisualization.java @@ -1,4 +1,4 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d;
+package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.cluster;
/*
This file is part of ELKI:
@@ -23,7 +23,6 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-import java.util.Collection;
import java.util.Iterator;
import org.apache.batik.util.SVGConstants;
@@ -33,19 +32,26 @@ import de.lmu.ifi.dbs.elki.data.Cluster; import de.lmu.ifi.dbs.elki.data.Clustering;
import de.lmu.ifi.dbs.elki.data.NumberVector;
import de.lmu.ifi.dbs.elki.data.model.MeanModel;
+import de.lmu.ifi.dbs.elki.database.ids.DBID;
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.utilities.optionhandling.AbstractParameterizer;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Flag;
import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
+import de.lmu.ifi.dbs.elki.visualization.colors.ColorLibrary;
import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
import de.lmu.ifi.dbs.elki.visualization.projector.ScatterPlotProjector;
import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
import de.lmu.ifi.dbs.elki.visualization.style.marker.MarkerLibrary;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPath;
import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot;
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;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatterplotVisualization;
/**
* Visualize the mean of a KMeans-Clustering
@@ -53,10 +59,8 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * @author Heidi Kolb
*
* @apiviz.has MeanModel oneway - - visualizes
- *
- * @param <NV> Type of the DatabaseObject being visualized.
*/
-public class ClusterMeanVisualization<NV extends NumberVector<NV, ?>> extends P2DVisualization<NV> {
+public class ClusterMeanVisualization extends AbstractScatterplotVisualization {
/**
* A short name characterizing this Visualizer.
*/
@@ -73,14 +77,30 @@ public class ClusterMeanVisualization<NV extends NumberVector<NV, ?>> extends P2 private final static String CSS_MEAN = "mean-marker";
/**
+ * CSS class name for center of the means
+ */
+ private final static String CSS_MEAN_STAR = "mean-star";
+
+ /**
* Clustering to visualize.
*/
- Clustering<MeanModel<NV>> clustering;
+ Clustering<MeanModel<? extends NumberVector<?, ?>>> clustering;
- public ClusterMeanVisualization(VisualizationTask task) {
+ /**
+ * Draw stars
+ */
+ boolean stars;
+
+ /**
+ * Constructor.
+ *
+ * @param task Visualization task
+ * @param stars Draw stars
+ */
+ public ClusterMeanVisualization(VisualizationTask task, boolean stars) {
super(task);
this.clustering = task.getResult();
- context.addContextChangeListener(this);
+ this.stars = stars;
incrementalRedraw();
}
@@ -91,9 +111,9 @@ public class ClusterMeanVisualization<NV extends NumberVector<NV, ?>> extends P2 MarkerLibrary ml = context.getStyleLibrary().markers();
double marker_size = context.getStyleLibrary().getSize(StyleLibrary.MARKERPLOT);
- Iterator<Cluster<MeanModel<NV>>> ci = clustering.getAllClusters().iterator();
- for(int cnum = 0; cnum < clustering.getAllClusters().size(); cnum++) {
- Cluster<MeanModel<NV>> clus = ci.next();
+ Iterator<Cluster<MeanModel<? extends NumberVector<?, ?>>>> ci = clustering.getAllClusters().iterator();
+ for(int cnum = 0; ci.hasNext(); cnum++) {
+ Cluster<MeanModel<? extends NumberVector<?, ?>>> clus = ci.next();
double[] mean = proj.fastProjectDataToRenderSpace(clus.getModel().getMean());
// add a greater Marker for the mean
@@ -108,6 +128,18 @@ public class ClusterMeanVisualization<NV extends NumberVector<NV, ?>> extends P2 layer.appendChild(meanMarkerCenter);
layer.appendChild(meanMarkerCenter2);
+
+ if(stars) {
+ SVGPath star = new SVGPath();
+ for(DBID id : clus.getIDs()) {
+ double[] obj = proj.fastProjectDataToRenderSpace(rel.get(id));
+ star.moveTo(mean);
+ star.drawTo(obj);
+ }
+ Element stare = star.makeElement(svgp);
+ SVGUtil.setCSSClass(stare, CSS_MEAN_STAR + "_" + cnum);
+ layer.appendChild(stare);
+ }
}
}
@@ -118,16 +150,31 @@ public class ClusterMeanVisualization<NV extends NumberVector<NV, ?>> extends P2 */
private void addCSSClasses(SVGPlot svgp) {
if(!svgp.getCSSClassManager().contains(CSS_MEAN_CENTER)) {
- CSSClass center = new CSSClass(svgp, CSS_MEAN_CENTER);
+ CSSClass center = new CSSClass(this, CSS_MEAN_CENTER);
center.setStatement(SVGConstants.CSS_STROKE_PROPERTY, context.getStyleLibrary().getTextColor(StyleLibrary.DEFAULT));
center.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.AXIS_TICK) / 2);
svgp.addCSSClassOrLogError(center);
}
if(!svgp.getCSSClassManager().contains(CSS_MEAN)) {
- CSSClass center = new CSSClass(svgp, CSS_MEAN);
+ CSSClass center = new CSSClass(this, CSS_MEAN);
center.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, "0.7");
svgp.addCSSClassOrLogError(center);
}
+ if(stars) {
+ ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT);
+
+ Iterator<Cluster<MeanModel<? extends NumberVector<?, ?>>>> ci = clustering.getAllClusters().iterator();
+ for(int cnum = 0; ci.hasNext(); cnum++) {
+ ci.next();
+ if(!svgp.getCSSClassManager().contains(CSS_MEAN_STAR + "_" + cnum)) {
+ CSSClass center = new CSSClass(this, CSS_MEAN_STAR + "_" + cnum);
+ center.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(cnum));
+ center.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT));
+ center.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, "0.7");
+ svgp.addCSSClassOrLogError(center);
+ }
+ }
+ }
}
/**
@@ -138,33 +185,50 @@ public class ClusterMeanVisualization<NV extends NumberVector<NV, ?>> extends P2 *
* @apiviz.stereotype factory
* @apiviz.uses ClusterMeanVisualization oneway - - «create»
- *
- * @param <NV> Type of the NumberVector being visualized.
*/
- public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory {
+ public static class Factory extends AbstractVisFactory {
/**
- * Constructor
+ * Option ID for visualization of cluster means.
+ *
+ * <pre>
+ * -cluster.stars
+ * </pre>
+ */
+ public static final OptionID STARS_ID = OptionID.getOrCreateOptionID("cluster.stars", "Visualize mean-based clusters using stars.");
+
+ /**
+ * Draw stars
+ */
+ private boolean stars;
+
+ /**
+ * Constructor.
+ *
+ * @param stars Draw stars
*/
- public Factory() {
+ public Factory(boolean stars) {
super();
+ this.stars = stars;
}
@Override
public Visualization makeVisualization(VisualizationTask task) {
- return new ClusterMeanVisualization<NV>(task);
+ return new ClusterMeanVisualization(task, stars);
}
@Override
public void processNewResult(HierarchicalResult baseResult, Result result) {
// Find clusterings we can visualize:
- Collection<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class);
- for(Clustering<?> c : clusterings) {
+ Iterator<Clustering<?>> clusterings = ResultUtil.filteredResults(result, Clustering.class);
+ while(clusterings.hasNext()) {
+ Clustering<?> c = clusterings.next();
if(c.getAllClusters().size() > 0) {
// Does the cluster have a model with cluster means?
- Clustering<MeanModel<NV>> mcls = findMeanModel(c);
+ Clustering<MeanModel<? extends NumberVector<?, ?>>> mcls = findMeanModel(c);
if(mcls != null) {
Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) {
+ while(ps.hasNext()) {
+ ScatterPlotProjector<?> p = ps.next();
final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA + 1);
baseResult.getHierarchy().add(c, task);
@@ -178,16 +242,40 @@ public class ClusterMeanVisualization<NV extends NumberVector<NV, ?>> extends P2 /**
* Test if the given clustering has a mean model.
*
- * @param <NV> Vector type
* @param c Clustering to inspect
* @return the clustering cast to return a mean model, null otherwise.
*/
@SuppressWarnings("unchecked")
- private static <NV extends NumberVector<NV, ?>> Clustering<MeanModel<NV>> findMeanModel(Clustering<?> c) {
+ private static Clustering<MeanModel<? extends NumberVector<?, ?>>> findMeanModel(Clustering<?> c) {
if(c.getAllClusters().get(0).getModel() instanceof MeanModel<?>) {
- return (Clustering<MeanModel<NV>>) c;
+ return (Clustering<MeanModel<? extends NumberVector<?, ?>>>) c;
}
return null;
}
+
+ /**
+ * Parameterization class.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ public static class Parameterizer extends AbstractParameterizer {
+ protected boolean stars = false;
+
+ @Override
+ protected void makeOptions(Parameterization config) {
+ super.makeOptions(config);
+ Flag starsF = new Flag(STARS_ID);
+ if(config.grab(starsF)) {
+ stars = starsF.getValue();
+ }
+ }
+
+ @Override
+ protected Factory makeInstance() {
+ return new Factory(stars);
+ }
+ }
}
}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/ClusterOrderVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterOrderVisualization.java index f758ff99..1ac55851 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/ClusterOrderVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterOrderVisualization.java @@ -1,10 +1,10 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.cluster; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -24,12 +24,9 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; */ import java.util.Collection; -import java.util.Iterator; import org.w3c.dom.Element; -import de.lmu.ifi.dbs.elki.data.NumberVector; -import de.lmu.ifi.dbs.elki.database.datastore.DataStoreEvent; import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener; import de.lmu.ifi.dbs.elki.database.ids.DBID; import de.lmu.ifi.dbs.elki.distance.distancevalue.DoubleDistance; @@ -38,7 +35,7 @@ import de.lmu.ifi.dbs.elki.result.Result; import de.lmu.ifi.dbs.elki.result.ResultUtil; 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.utilities.iterator.IterableIterator; 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.ScatterPlotProjector; @@ -46,6 +43,7 @@ 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; +import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatterplotVisualization; /** * Cluster order visualizer. @@ -55,7 +53,7 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * @apiviz.has ClusterOrderResult oneway - - visualizes */ // TODO: listen for CLUSTER ORDER changes. -public class ClusterOrderVisualization<NV extends NumberVector<NV, ?>> extends P2DVisualization<NV> implements DataStoreListener { +public class ClusterOrderVisualization extends AbstractScatterplotVisualization implements DataStoreListener { /** * A short name characterizing this Visualizer. */ @@ -99,6 +97,10 @@ public class ClusterOrderVisualization<NV extends NumberVector<NV, ?>> extends P } double[] thisVec = proj.fastProjectDataToRenderSpace(rel.get(thisId)); double[] prevVec = proj.fastProjectDataToRenderSpace(rel.get(prevId)); + + // FIXME: DO NOT COMMIT + thisVec[0] = thisVec[0] * 0.95 + prevVec[0] * 0.05; + thisVec[1] = thisVec[1] * 0.95 + prevVec[1] * 0.05; Element arrow = svgp.svgLine(prevVec[0], prevVec[1], thisVec[0], thisVec[1]); SVGUtil.setCSSClass(arrow, cls.getName()); @@ -107,11 +109,6 @@ public class ClusterOrderVisualization<NV extends NumberVector<NV, ?>> extends P } } - @Override - public void contentChanged(DataStoreEvent e) { - synchronizedRedraw(); - } - /** * Visualize an OPTICS cluster order by drawing connection lines. * @@ -119,10 +116,8 @@ public class ClusterOrderVisualization<NV extends NumberVector<NV, ?>> extends P * * @apiviz.stereotype factory * @apiviz.uses ClusterOrderVisualization oneway - - «create» - * - * @param <NV> object type */ - public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory { + public static class Factory extends AbstractVisFactory { /** * Constructor, adhering to * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} @@ -133,15 +128,15 @@ public class ClusterOrderVisualization<NV extends NumberVector<NV, ?>> extends P @Override public Visualization makeVisualization(VisualizationTask task) { - return new ClusterOrderVisualization<NV>(task); + return new ClusterOrderVisualization(task); } @Override public void processNewResult(HierarchicalResult baseResult, Result result) { Collection<ClusterOrderResult<DoubleDistance>> cos = ResultUtil.filterResults(result, ClusterOrderResult.class); for(ClusterOrderResult<DoubleDistance> co : cos) { - Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) { + IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { final VisualizationTask task = new VisualizationTask(NAME, co, p.getRelation(), this); task.put(VisualizationTask.META_VISIBLE_DEFAULT, false); task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA - 1); diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/EMClusterVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/EMClusterVisualization.java index f62ff1a6..6b2f43a3 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/EMClusterVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/EMClusterVisualization.java @@ -1,10 +1,10 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.cluster; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -39,8 +39,8 @@ import de.lmu.ifi.dbs.elki.data.model.Model; import de.lmu.ifi.dbs.elki.data.spatial.Polygon; import de.lmu.ifi.dbs.elki.database.ids.DBIDs; import de.lmu.ifi.dbs.elki.logging.LoggingUtil; -import de.lmu.ifi.dbs.elki.math.ConvexHull2D; import de.lmu.ifi.dbs.elki.math.MathUtil; +import de.lmu.ifi.dbs.elki.math.geometry.GrahamScanConvexHull2D; import de.lmu.ifi.dbs.elki.math.linearalgebra.EigenPair; import de.lmu.ifi.dbs.elki.math.linearalgebra.Matrix; import de.lmu.ifi.dbs.elki.math.linearalgebra.SortedEigenPairs; @@ -50,7 +50,7 @@ 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.ClassGenericsUtil; -import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil; +import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.EmptyParameterization; import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; import de.lmu.ifi.dbs.elki.visualization.colors.ColorLibrary; @@ -62,6 +62,7 @@ import de.lmu.ifi.dbs.elki.visualization.svg.SVGPath; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; 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.scatterplot.AbstractScatterplotVisualization; /** * Visualizer for generating SVG-Elements containing ellipses for first, second @@ -76,7 +77,7 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisFactory; */ // TODO: nicer stacking of n-fold hulls // TODO: can we find a proper sphere for 3+ dimensions? -public class EMClusterVisualization<NV extends NumberVector<NV, ?>> extends P2DVisualization<NV> { +public class EMClusterVisualization<NV extends NumberVector<NV, ?>> extends AbstractScatterplotVisualization { /** * A short name characterizing this Visualizer. */ @@ -115,7 +116,6 @@ public class EMClusterVisualization<NV extends NumberVector<NV, ?>> extends P2DV public EMClusterVisualization(VisualizationTask task) { super(task); this.clustering = task.getResult(); - context.addContextChangeListener(this); incrementalRedraw(); } @@ -144,7 +144,7 @@ public class EMClusterVisualization<NV extends NumberVector<NV, ?>> extends P2DV for(int i = 0; i < eps.size(); i++) { EigenPair ep = eps.getEigenPair(i); Vector sev = ep.getEigenvector().times(Math.sqrt(ep.getEigenvalue())); - pc[i] = new Vector(proj.fastProjectRelativeDataToRenderSpace(sev)); + pc[i] = new Vector(proj.fastProjectRelativeDataToRenderSpace(sev.getArrayRef())); } if(drawStyle != 0 || eps.size() == 2) { drawSphere2D(cnum, cent, pc); @@ -193,27 +193,29 @@ public class EMClusterVisualization<NV extends NumberVector<NV, ?>> extends P2DV } protected void drawHullLines(int cnum, Vector cent, Polygon chres) { - for(int i = 1; i <= times; i++) { - SVGPath path = new SVGPath(); - for(int p = 0; p < chres.size(); p++) { - Vector cur = cent.plusTimes(chres.get(p), i); - path.drawTo(cur); - } - path.close(); - Element ellipse = path.makeElement(svgp); - SVGUtil.addCSSClass(ellipse, EMBORDER + cnum); - if(opacStyle == 1) { - CSSClass cls = new CSSClass(null, "temp"); - double s = (i >= 1 && i <= sigma.length) ? sigma[i - 1] : 0.0; - cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, s); - SVGUtil.setAtt(ellipse, SVGConstants.SVG_STYLE_ATTRIBUTE, cls.inlineCSS()); + if(chres.size() > 1) { + for(int i = 1; i <= times; i++) { + SVGPath path = new SVGPath(); + for(int p = 0; p < chres.size(); p++) { + Vector cur = cent.plusTimes(chres.get(p), i); + path.drawTo(cur); + } + path.close(); + Element ellipse = path.makeElement(svgp); + SVGUtil.addCSSClass(ellipse, EMBORDER + cnum); + if(opacStyle == 1) { + CSSClass cls = new CSSClass(null, "temp"); + double s = (i >= 1 && i <= sigma.length) ? sigma[i - 1] : 0.0; + cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, s); + SVGUtil.setAtt(ellipse, SVGConstants.SVG_STYLE_ATTRIBUTE, cls.inlineCSS()); + } + layer.appendChild(ellipse); } - layer.appendChild(ellipse); } } protected Polygon makeHull(Vector[] pc) { - ConvexHull2D hull = new ConvexHull2D(); + GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D(); Vector diag = new Vector(0, 0); for(int j = 0; j < pc.length; j++) { @@ -239,7 +241,7 @@ public class EMClusterVisualization<NV extends NumberVector<NV, ?>> extends P2DV } protected Polygon makeHullComplex(Vector[] pc) { - ConvexHull2D hull = new ConvexHull2D(); + GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D(); Vector diag = new Vector(0, 0); for(int j = 0; j < pc.length; j++) { @@ -440,8 +442,8 @@ public class EMClusterVisualization<NV extends NumberVector<NV, ?>> extends P2DV // Does the cluster have a model with cluster means? Clustering<MeanModel<NV>> mcls = findMeanModel(c); if(mcls != null) { - Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) { + IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this); task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA + 3); baseResult.getHierarchy().add(c, task); diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/VoronoiVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/VoronoiVisualization.java new file mode 100644 index 00000000..37f125d4 --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/VoronoiVisualization.java @@ -0,0 +1,298 @@ +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.cluster;
+/* + This file is part of ELKI: + Environment for Developing KDD-Applications Supported by Index-Structures + + Copyright (C) 2012 + Ludwig-Maximilians-Universität München + Lehr- und Forschungseinheit für Datenbanksysteme + ELKI Development Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +
+import java.util.ArrayList;
+import java.util.Collection;
+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.NumberVector;
+import de.lmu.ifi.dbs.elki.data.model.EMModel;
+import de.lmu.ifi.dbs.elki.data.model.MeanModel;
+import de.lmu.ifi.dbs.elki.data.model.Model;
+import de.lmu.ifi.dbs.elki.math.geometry.SweepHullDelaunay2D;
+import de.lmu.ifi.dbs.elki.math.geometry.SweepHullDelaunay2D.Triangle;
+import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector;
+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.DatabaseUtil;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.EnumParameter;
+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.ScatterPlotProjector;
+import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPath;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil;
+import de.lmu.ifi.dbs.elki.visualization.svg.VoronoiDraw;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisFactory;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatterplotVisualization;
+
+/**
+ * Visualizer drawing Voronoi cells for k-means clusterings.
+ *
+ * See also: {@link de.lmu.ifi.dbs.elki.algorithm.clustering.kmeans.KMeansLloyd KMeans
+ * clustering}
+ *
+ * @author Robert Rödler
+ * @author Erich Schubert
+ *
+ * @apiviz.has MeanModel oneway - - visualizes
+ */
+public class VoronoiVisualization extends AbstractScatterplotVisualization {
+ /**
+ * A short name characterizing this Visualizer.
+ */
+ private static final String NAME = "k-means Voronoi cells";
+
+ /**
+ * Generic tags to indicate the type of element. Used in IDs, CSS-Classes etc.
+ */
+ private static final String KMEANSBORDER = "kmeans-border";
+
+ /**
+ * Visualization mode.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ public static enum Mode {
+ VORONOI, DELAUNAY, V_AND_D
+ }
+
+ /**
+ * The result we work on
+ */
+ Clustering<MeanModel<? extends NumberVector<?, ?>>> clustering;
+
+ /**
+ * The Voronoi diagram
+ */
+ Element voronoi;
+
+ /**
+ * Active drawing mode.
+ */
+ private Mode mode;
+
+ /**
+ * Constructor
+ *
+ * @param task VisualizationTask
+ * @param mode Drawing mode
+ */
+ public VoronoiVisualization(VisualizationTask task, Mode mode) {
+ super(task);
+ this.clustering = task.getResult();
+ this.mode = mode;
+ incrementalRedraw();
+ }
+
+ @Override
+ protected void redraw() {
+ addCSSClasses(svgp);
+ final List<Cluster<MeanModel<? extends NumberVector<?, ?>>>> clusters = clustering.getAllClusters();
+
+ if(clusters.size() < 2) {
+ return;
+ }
+
+ // Collect cluster means
+ if(clusters.size() == 2) {
+ ArrayList<double[]> means = new ArrayList<double[]>(clusters.size());
+ {
+ for(Cluster<MeanModel<? extends NumberVector<?, ?>>> clus : clusters) {
+ means.add(clus.getModel().getMean().getColumnVector().getArrayRef());
+ }
+ }
+ if(mode == Mode.VORONOI || mode == Mode.V_AND_D) {
+ Element path = VoronoiDraw.drawFakeVoronoi(proj, means).makeElement(svgp);
+ SVGUtil.addCSSClass(path, KMEANSBORDER);
+ layer.appendChild(path);
+ }
+ if(mode == Mode.DELAUNAY || mode == Mode.V_AND_D) {
+ Element path = new SVGPath(proj.fastProjectDataToRenderSpace(means.get(0))).drawTo(proj.fastProjectDataToRenderSpace(means.get(1))).makeElement(svgp);
+ SVGUtil.addCSSClass(path, KMEANSBORDER);
+ layer.appendChild(path);
+ }
+ }
+ else {
+ ArrayList<Vector> vmeans = new ArrayList<Vector>(clusters.size());
+ ArrayList<double[]> means = new ArrayList<double[]>(clusters.size());
+ {
+ for(Cluster<MeanModel<? extends NumberVector<?, ?>>> clus : clusters) {
+ Vector v = clus.getModel().getMean().getColumnVector();
+ vmeans.add(v);
+ means.add(v.getArrayRef());
+ }
+ }
+ // Compute Delaunay Triangulation
+ ArrayList<Triangle> delaunay = new SweepHullDelaunay2D(vmeans).getDelaunay();
+ if(mode == Mode.VORONOI || mode == Mode.V_AND_D) {
+ Element path = VoronoiDraw.drawVoronoi(proj, delaunay, means).makeElement(svgp);
+ SVGUtil.addCSSClass(path, KMEANSBORDER);
+ layer.appendChild(path);
+ }
+ if(mode == Mode.DELAUNAY || mode == Mode.V_AND_D) {
+ Element path = VoronoiDraw.drawDelaunay(proj, delaunay, means).makeElement(svgp);
+ SVGUtil.addCSSClass(path, KMEANSBORDER);
+ layer.appendChild(path);
+ }
+ }
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ // Class for the distance markers
+ if(!svgp.getCSSClassManager().contains(KMEANSBORDER)) {
+ CSSClass cls = new CSSClass(this, KMEANSBORDER);
+ cls = new CSSClass(this, KMEANSBORDER);
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_BLACK_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT) * .5);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ svgp.addCSSClassOrLogError(cls);
+ }
+ }
+
+ /**
+ * Factory for visualizers to generate an SVG-Element containing the lines
+ * between kMeans clusters
+ *
+ * @author Robert Rödler
+ * @author Erich Schubert
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses VoronoiVisualisation oneway - - «create»
+ */
+ public static class Factory extends AbstractVisFactory {
+ /**
+ * Mode for drawing: Voronoi, Delaunay, both
+ *
+ * <p>
+ * Key: {@code -voronoi.mode}
+ * </p>
+ */
+ public static final OptionID MODE_ID = OptionID.getOrCreateOptionID("voronoi.mode", "Mode for drawing the voronoi cells (and/or delaunay triangulation)");
+
+ /**
+ * Drawing mode
+ */
+ private Mode mode;
+
+ /**
+ * Constructor
+ *
+ * @param mode Drawing mode
+ */
+ public Factory(Mode mode) {
+ super();
+ this.mode = mode;
+ }
+
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new VoronoiVisualization(task, mode);
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ // Find clusterings we can visualize:
+ Collection<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class);
+ for(Clustering<?> c : clusterings) {
+ if(c.getAllClusters().size() > 0) {
+ // Does the cluster have a model with cluster means?
+ Clustering<MeanModel<? extends NumberVector<?, ?>>> mcls = findMeanModel(c);
+ if(mcls != null) {
+ IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ if(DatabaseUtil.dimensionality(p.getRelation()) == 2) {
+ final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
+ task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA + 3);
+ baseResult.getHierarchy().add(p, task);
+ baseResult.getHierarchy().add(c, task);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Test if the given clustering has a mean model.
+ *
+ * @param c Clustering to inspect
+ * @return the clustering cast to return a mean model, null otherwise.
+ */
+ @SuppressWarnings("unchecked")
+ private static Clustering<MeanModel<? extends NumberVector<?, ?>>> findMeanModel(Clustering<?> c) {
+ final Model firstModel = c.getAllClusters().get(0).getModel();
+ if(firstModel instanceof MeanModel<?> && !(firstModel instanceof EMModel<?>)) {
+ return (Clustering<MeanModel<? extends NumberVector<?, ?>>>) c;
+ }
+ return null;
+ }
+
+ /**
+ * Parameterization class.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ public static class Parameterizer extends AbstractParameterizer {
+ protected Mode mode;
+
+ @Override
+ protected void makeOptions(Parameterization config) {
+ super.makeOptions(config);
+ EnumParameter<Mode> modeP = new EnumParameter<Mode>(MODE_ID, Mode.class, Mode.VORONOI);
+ if(config.grab(modeP)) {
+ mode = modeP.getValue();
+ }
+ }
+
+ @Override
+ protected Factory makeInstance() {
+ return new Factory(mode);
+ }
+ }
+ }
+}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/package-info.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/package-info.java new file mode 100644 index 00000000..0f232498 --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/package-info.java @@ -0,0 +1,26 @@ +/** + * <p>Visualizers for clustering results based on 2D projections.</p> + */ +/* + This file is part of ELKI: + Environment for Developing KDD-Applications Supported by Index-Structures + + Copyright (C) 2012 + Ludwig-Maximilians-Universität München + Lehr- und Forschungseinheit für Datenbanksysteme + ELKI Development Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.cluster;
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/density/DensityEstimationOverlay.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/density/DensityEstimationOverlay.java new file mode 100644 index 00000000..28e4da32 --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/density/DensityEstimationOverlay.java @@ -0,0 +1,236 @@ +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.density; + +/* + This file is part of ELKI: + Environment for Developing KDD-Applications Supported by Index-Structures + + Copyright (C) 2012 + Ludwig-Maximilians-Universität München + Lehr- und Forschungseinheit für Datenbanksysteme + ELKI Development Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +import java.awt.image.BufferedImage; +import java.util.Arrays; +import java.util.Comparator; + +import org.apache.batik.util.SVGConstants; +import org.w3c.dom.Element; + +import de.lmu.ifi.dbs.elki.database.ids.DBID; +import de.lmu.ifi.dbs.elki.math.MathUtil; +import de.lmu.ifi.dbs.elki.math.MeanVariance; +import de.lmu.ifi.dbs.elki.result.HierarchicalResult; +import de.lmu.ifi.dbs.elki.result.KMLOutputHandler; +import de.lmu.ifi.dbs.elki.result.Result; +import de.lmu.ifi.dbs.elki.result.ResultUtil; +import de.lmu.ifi.dbs.elki.utilities.documentation.Reference; +import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator; +import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; +import de.lmu.ifi.dbs.elki.visualization.batikutil.ThumbnailRegistryEntry; +import de.lmu.ifi.dbs.elki.visualization.projections.CanvasSize; +import de.lmu.ifi.dbs.elki.visualization.projector.ScatterPlotProjector; +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; +import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatterplotVisualization; + +/** + * A simple density estimation visualization, based on a simple kernel-density + * <em>in the projection, not the actual data!</em> + * + * @author Erich Schubert + */ +// TODO: make parameterizable, in particular color map, kernel bandwidth and +// kernel function +public class DensityEstimationOverlay extends AbstractScatterplotVisualization { + /** + * A short name characterizing this Visualizer. + */ + private static final String NAME = "Density estimation overlay"; + + /** + * Density map resolution + */ + private int resolution = 500; + + /** + * The actual image + */ + private BufferedImage img = null; + + /** + * Constructor. + * + * @param task Task + */ + public DensityEstimationOverlay(VisualizationTask task) { + super(task); + incrementalRedraw(); + } + + @Override + protected void redraw() { + if(img == null) { + renderImage(); + } + + CanvasSize canvas = proj.estimateViewport(); + String imguri = ThumbnailRegistryEntry.INTERNAL_PREFIX + ThumbnailRegistryEntry.registerImage(img); + 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, canvas.minx); + SVGUtil.setAtt(itag, SVGConstants.SVG_Y_ATTRIBUTE, canvas.miny); + SVGUtil.setAtt(itag, SVGConstants.SVG_WIDTH_ATTRIBUTE, canvas.maxx - canvas.minx); + SVGUtil.setAtt(itag, SVGConstants.SVG_HEIGHT_ATTRIBUTE, canvas.maxy - canvas.miny); + SVGUtil.setAtt(itag, SVGConstants.SVG_STYLE_ATTRIBUTE, SVGConstants.CSS_OPACITY_PROPERTY + ": .5"); + itag.setAttributeNS(SVGConstants.XLINK_NAMESPACE_URI, SVGConstants.XLINK_HREF_QNAME, imguri); + + layer.appendChild(itag); + } + + @Reference(authors = "D. W. Scott", title = "Multivariate density estimation", booktitle = "Multivariate Density Estimation: Theory, Practice, and Visualization", url = "http://dx.doi.org/10.1002/9780470316849.fmatter") + private double[] initializeBandwidth(double[][] data) { + MeanVariance mv0 = new MeanVariance(); + MeanVariance mv1 = new MeanVariance(); + // For Kernel bandwidth. + for(double[] projected : data) { + mv0.put(projected[0]); + mv1.put(projected[1]); + } + // Set bandwidths according to Scott's rule: + // Note: in projected space, d=2. + double[] bandwidth = new double[2]; + bandwidth[0] = MathUtil.SQRT5 * mv0.getSampleStddev() * Math.pow(rel.size(), -1 / 6.); + bandwidth[1] = MathUtil.SQRT5 * mv1.getSampleStddev() * Math.pow(rel.size(), -1 / 6.); + return bandwidth; + } + + private void renderImage() { + // TODO: SAMPLE? Do region queries? + // Project the data just once, keep a copy. + double[][] data = new double[rel.size()][]; + { + int i = 0; + for(DBID id : rel.iterDBIDs()) { + data[i] = proj.fastProjectDataToRenderSpace(rel.get(id)); + i++; + } + } + double[] bandwidth = initializeBandwidth(data); + // Compare by first component + Comparator<double[]> comp0 = new Comparator<double[]>() { + @Override + public int compare(double[] o1, double[] o2) { + return Double.compare(o1[0], o2[0]); + } + }; + // Compare by second component + Comparator<double[]> comp1 = new Comparator<double[]>() { + @Override + public int compare(double[] o1, double[] o2) { + return Double.compare(o1[1], o2[1]); + } + }; + // TODO: choose comparator order based on smaller bandwidth? + Arrays.sort(data, comp0); + + CanvasSize canvas = proj.estimateViewport(); + double min0 = canvas.minx, max0 = canvas.maxx, ste0 = (max0 - min0) / resolution; + double min1 = canvas.miny, max1 = canvas.maxy, ste1 = (max1 - min1) / resolution; + + double kernf = 9. / (16 * bandwidth[0] * bandwidth[1]); + double maxdens = 0.0; + double[][] dens = new double[resolution][resolution]; + { + // TODO: incrementally update the loff/roff values? + for(int x = 0; x < resolution; x++) { + double xlow = min0 + ste0 * x, xhig = xlow + ste0; + int loff = unflip(Arrays.binarySearch(data, new double[] { xlow - bandwidth[0] }, comp0)); + int roff = unflip(Arrays.binarySearch(data, new double[] { xhig + bandwidth[0] }, comp0)); + // Resort by second component + Arrays.sort(data, loff, roff, comp1); + for(int y = 0; y < resolution; y++) { + double ylow = min1 + ste1 * y, yhig = ylow + ste1; + int boff = unflip(Arrays.binarySearch(data, loff, roff, new double[] { 0, ylow - bandwidth[1] }, comp1)); + int toff = unflip(Arrays.binarySearch(data, loff, roff, new double[] { 0, yhig + bandwidth[1] }, comp1)); + for(int pos = boff; pos < toff; pos++) { + double[] val = data[pos]; + double d0 = (val[0] < xlow) ? (xlow - val[0]) : (val[0] > xhig) ? (val[0] - xhig) : 0; + double d1 = (val[1] < ylow) ? (ylow - val[1]) : (val[1] > yhig) ? (val[1] - yhig) : 0; + d0 = d0 / bandwidth[0]; + d1 = d1 / bandwidth[1]; + dens[x][y] += kernf * (1 - d0 * d0) * (1 - d1 * d1); + } + maxdens = Math.max(maxdens, dens[x][y]); + } + // Restore original sorting, as the intervals overlap + Arrays.sort(data, loff, roff, comp0); + } + } + img = new BufferedImage(resolution, resolution, BufferedImage.TYPE_INT_ARGB); + { + for(int x = 0; x < resolution; x++) { + for(int y = 0; y < resolution; y++) { + int rgb = KMLOutputHandler.getColorForValue(dens[x][y] / maxdens).getRGB(); + img.setRGB(x, y, rgb); + } + } + } + } + + private int unflip(int binarySearch) { + if(binarySearch < 0) { + return (-binarySearch) - 1; + } + else { + return binarySearch; + } + } + + /** + * The visualization factory + * + * @author Erich Schubert + * + * @apiviz.stereotype factory + * @apiviz.uses DensityEstimation2DVisualization 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 Visualization makeVisualization(VisualizationTask task) { + return new DensityEstimationOverlay(task); + } + + @Override + public void processNewResult(HierarchicalResult baseResult, Result result) { + IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(result, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { + final VisualizationTask task = new VisualizationTask(NAME, p.getRelation(), p.getRelation(), this); + task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA + 1); + task.put(VisualizationTask.META_VISIBLE_DEFAULT, false); + baseResult.getHierarchy().add(p, task); + } + } + } +}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/events/package-info.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/density/package-info.java index 6c10d496..b9771aed 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/events/package-info.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/density/package-info.java @@ -1,11 +1,11 @@ /** - * <p>Events occuring in visualization contexts</p> + * <p>Visualizers for data set density in a scatterplot projection.</p> */ /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures -Copyright (C) 2011 +Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -23,4 +23,4 @@ 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.events;
\ No newline at end of file +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.density;
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/TreeMBRVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeMBRVisualization.java index cc3dfbe8..0b4bee2f 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/TreeMBRVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeMBRVisualization.java @@ -1,10 +1,10 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.index; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -24,25 +24,21 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; */ import java.util.ArrayList; -import java.util.Iterator; import org.apache.batik.util.SVGConstants; import org.w3c.dom.Element; -import de.lmu.ifi.dbs.elki.data.NumberVector; import de.lmu.ifi.dbs.elki.data.spatial.SpatialComparable; import de.lmu.ifi.dbs.elki.data.spatial.SpatialUtil; -import de.lmu.ifi.dbs.elki.database.datastore.DataStoreEvent; import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener; import de.lmu.ifi.dbs.elki.index.tree.spatial.SpatialEntry; import de.lmu.ifi.dbs.elki.index.tree.spatial.rstarvariants.AbstractRStarTree; import de.lmu.ifi.dbs.elki.index.tree.spatial.rstarvariants.AbstractRStarTreeNode; import de.lmu.ifi.dbs.elki.index.tree.spatial.rstarvariants.rstar.RStarTreeNode; -import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector; 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.utilities.iterator.IterableIterator; import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer; import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization; @@ -58,6 +54,7 @@ import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; 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; +import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatterplotVisualization; /** * Visualize the bounding rectangles of an R-Tree based index. @@ -67,12 +64,11 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * @apiviz.has AbstractRStarTree oneway - - visualizes * @apiviz.uses SVGHyperCube * - * @param <NV> Type of the DatabaseObject being visualized. * @param <N> Tree node type * @param <E> Tree entry type */ // TODO: listen for tree changes instead of data changes? -public class TreeMBRVisualization<NV extends NumberVector<NV, ?>, N extends AbstractRStarTreeNode<N, E>, E extends SpatialEntry> extends P2DVisualization<NV> implements DataStoreListener { +public class TreeMBRVisualization<N extends AbstractRStarTreeNode<N, E>, E extends SpatialEntry> extends AbstractScatterplotVisualization implements DataStoreListener { /** * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc. */ @@ -152,11 +148,11 @@ public class TreeMBRVisualization<NV extends NumberVector<NV, ?>, N extends Abst SpatialComparable mbr = entry; if(fill) { - Element r = SVGHyperCube.drawFilled(svgp, INDEX + depth, proj, new Vector(SpatialUtil.getMin(mbr)), new Vector(SpatialUtil.getMax(mbr))); + Element r = SVGHyperCube.drawFilled(svgp, INDEX + depth, proj, SpatialUtil.getMin(mbr), SpatialUtil.getMax(mbr)); layer.appendChild(r); } else { - Element r = SVGHyperCube.drawFrame(svgp, proj, new Vector(SpatialUtil.getMin(mbr)), new Vector(SpatialUtil.getMax(mbr))); + Element r = SVGHyperCube.drawFrame(svgp, proj, SpatialUtil.getMin(mbr), SpatialUtil.getMax(mbr)); SVGUtil.setCSSClass(r, INDEX + depth); layer.appendChild(r); } @@ -178,11 +174,6 @@ public class TreeMBRVisualization<NV extends NumberVector<NV, ?>, N extends Abst context.removeDataStoreListener(this); } - @Override - public void contentChanged(DataStoreEvent e) { - synchronizedRedraw(); - } - /** * Factory * @@ -190,10 +181,8 @@ public class TreeMBRVisualization<NV extends NumberVector<NV, ?>, N extends Abst * * @apiviz.stereotype factory * @apiviz.uses TreeMBRVisualization oneway - - «create» - * - * @param <NV> vector type */ - public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory { + public static class Factory extends AbstractVisFactory { /** * Flag for half-transparent filling of bubbles. * @@ -220,7 +209,7 @@ public class TreeMBRVisualization<NV extends NumberVector<NV, ?>, N extends Abst @Override public Visualization makeVisualization(VisualizationTask task) { - return new TreeMBRVisualization<NV, RStarTreeNode, SpatialEntry>(task, fill); + return new TreeMBRVisualization<RStarTreeNode, SpatialEntry>(task, fill); } @Override @@ -228,8 +217,8 @@ public class TreeMBRVisualization<NV extends NumberVector<NV, ?>, N extends Abst ArrayList<AbstractRStarTree<RStarTreeNode, SpatialEntry>> trees = ResultUtil.filterResults(result, AbstractRStarTree.class); for(AbstractRStarTree<RStarTreeNode, SpatialEntry> tree : trees) { if(tree instanceof Result) { - Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) { + IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { final VisualizationTask task = new VisualizationTask(NAME, (Result) tree, p.getRelation(), this); task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_BACKGROUND + 1); baseResult.getHierarchy().add((Result) tree, task); @@ -246,7 +235,7 @@ public class TreeMBRVisualization<NV extends NumberVector<NV, ?>, N extends Abst * * @apiviz.exclude */ - public static class Parameterizer<NV extends NumberVector<NV, ?>> extends AbstractParameterizer { + public static class Parameterizer extends AbstractParameterizer { protected boolean fill = false; @Override @@ -259,8 +248,8 @@ public class TreeMBRVisualization<NV extends NumberVector<NV, ?>, N extends Abst } @Override - protected Factory<NV> makeInstance() { - return new Factory<NV>(fill); + protected Factory makeInstance() { + return new Factory(fill); } } } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/TreeSphereVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeSphereVisualization.java index 73a02ddb..85429eb1 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/TreeSphereVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeSphereVisualization.java @@ -1,10 +1,10 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.index; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -24,13 +24,11 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; */ import java.util.ArrayList; -import java.util.Iterator; import org.apache.batik.util.SVGConstants; import org.w3c.dom.Element; import de.lmu.ifi.dbs.elki.data.NumberVector; -import de.lmu.ifi.dbs.elki.database.datastore.DataStoreEvent; import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener; import de.lmu.ifi.dbs.elki.database.ids.DBID; import de.lmu.ifi.dbs.elki.distance.distancefunction.DistanceFunction; @@ -46,7 +44,7 @@ import de.lmu.ifi.dbs.elki.index.tree.metrical.mtreevariants.mtree.MTreeNode; 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.utilities.iterator.IterableIterator; import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Flag; @@ -61,6 +59,7 @@ import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; 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; +import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatterplotVisualization; /** * Visualize the bounding sphere of a metric index. @@ -70,12 +69,11 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * @apiviz.has AbstractMTree oneway - - visualizes * @apiviz.uses SVGHyperSphere * - * @param <NV> Type of the DatabaseObject being visualized. * @param <N> Tree node type * @param <E> Tree entry type */ // TODO: listen for tree changes! -public class TreeSphereVisualization<NV extends NumberVector<NV, ?>, D extends NumberDistance<D, ?>, N extends AbstractMTreeNode<NV, D, N, E>, E extends MTreeEntry<D>> extends P2DVisualization<NV> implements DataStoreListener { +public class TreeSphereVisualization<D extends NumberDistance<D, ?>, N extends AbstractMTreeNode<?, D, N, E>, E extends MTreeEntry<D>> extends AbstractScatterplotVisualization implements DataStoreListener { /** * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc. */ @@ -105,7 +103,7 @@ public class TreeSphereVisualization<NV extends NumberVector<NV, ?>, D extends N /** * The tree we visualize */ - protected AbstractMTree<NV, D, N, E> tree; + protected AbstractMTree<?, D, N, E> tree; /** * Fill parameter. @@ -208,10 +206,10 @@ public class TreeSphereVisualization<NV extends NumberVector<NV, ?>, D extends N * @param entry Current entry * @param depth Current depth */ - private void visualizeMTreeEntry(SVGPlot svgp, Element layer, Projection2D proj, AbstractMTree<NV, D, N, E> mtree, E entry, int depth) { + private void visualizeMTreeEntry(SVGPlot svgp, Element layer, Projection2D proj, AbstractMTree<?, D, N, E> mtree, E entry, int depth) { DBID roid = entry.getRoutingObjectID(); if(roid != null) { - NV ro = rel.get(roid); + NumberVector<?, ?> ro = rel.get(roid); D rad = entry.getCoveringRadius(); final Element r; @@ -247,11 +245,6 @@ public class TreeSphereVisualization<NV extends NumberVector<NV, ?>, D extends N context.removeDataStoreListener(this); } - @Override - public void contentChanged(DataStoreEvent e) { - synchronizedRedraw(); - } - /** * Factory * @@ -259,10 +252,8 @@ public class TreeSphereVisualization<NV extends NumberVector<NV, ?>, D extends N * * @apiviz.stereotype factory * @apiviz.uses TreeSphereVisualization oneway - - «create» - * - * @param <NV> */ - public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory { + public static class Factory extends AbstractVisFactory { /** * Fill parameter. */ @@ -280,10 +271,10 @@ public class TreeSphereVisualization<NV extends NumberVector<NV, ?>, D extends N @Override public void processNewResult(HierarchicalResult baseResult, Result result) { - Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) { - ArrayList<AbstractMTree<NV, DoubleDistance, ?, ?>> trees = ResultUtil.filterResults(result, AbstractMTree.class); - for(AbstractMTree<NV, DoubleDistance, ?, ?> tree : trees) { + IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { + ArrayList<AbstractMTree<?, DoubleDistance, ?, ?>> trees = ResultUtil.filterResults(result, AbstractMTree.class); + for(AbstractMTree<?, DoubleDistance, ?, ?> tree : trees) { if(canVisualize(tree) && tree instanceof Result) { final VisualizationTask task = new VisualizationTask(NAME, (Result) tree, p.getRelation(), this); task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_BACKGROUND + 1); @@ -296,7 +287,7 @@ public class TreeSphereVisualization<NV extends NumberVector<NV, ?>, D extends N @Override public Visualization makeVisualization(VisualizationTask task) { - return new TreeSphereVisualization<NV, DoubleDistance, MTreeNode<NV, DoubleDistance>, MTreeEntry<DoubleDistance>>(task, fill); + return new TreeSphereVisualization<DoubleDistance, MTreeNode<Object, DoubleDistance>, MTreeEntry<DoubleDistance>>(task, fill); } /** @@ -306,7 +297,7 @@ public class TreeSphereVisualization<NV extends NumberVector<NV, ?>, D extends N * * @apiviz.exclude */ - public static class Parameterizer<NV extends NumberVector<NV, ?>> extends AbstractParameterizer { + public static class Parameterizer extends AbstractParameterizer { protected boolean fill = false; @Override @@ -319,8 +310,8 @@ public class TreeSphereVisualization<NV extends NumberVector<NV, ?>, D extends N } @Override - protected Factory<NV> makeInstance() { - return new Factory<NV>(fill); + protected Factory makeInstance() { + return new Factory(fill); } } } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/package-info.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/package-info.java new file mode 100644 index 00000000..a4b3c0c9 --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/package-info.java @@ -0,0 +1,26 @@ +/** + * <p>Visualizers for index structures based on 2D projections.</p> + */ +/* + This file is part of ELKI: + Environment for Developing KDD-Applications Supported by Index-Structures + + Copyright (C) 2012 + Ludwig-Maximilians-Universität München + Lehr- und Forschungseinheit für Datenbanksysteme + ELKI Development Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.index;
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/BubbleVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/outlier/BubbleVisualization.java index 3d183a88..7c40c1cb 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/BubbleVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/outlier/BubbleVisualization.java @@ -1,10 +1,10 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.outlier; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -23,17 +23,12 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; 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.NumberVector; -import de.lmu.ifi.dbs.elki.data.model.Model; -import de.lmu.ifi.dbs.elki.database.datastore.DataStoreEvent; import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener; import de.lmu.ifi.dbs.elki.database.ids.DBID; import de.lmu.ifi.dbs.elki.result.HierarchicalResult; @@ -41,7 +36,7 @@ import de.lmu.ifi.dbs.elki.result.Result; import de.lmu.ifi.dbs.elki.result.ResultUtil; import de.lmu.ifi.dbs.elki.result.outlier.OutlierResult; import de.lmu.ifi.dbs.elki.utilities.documentation.Reference; -import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil; +import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator; import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer; import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization; @@ -53,11 +48,14 @@ import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; import de.lmu.ifi.dbs.elki.visualization.colors.ColorLibrary; import de.lmu.ifi.dbs.elki.visualization.css.CSSClass; import de.lmu.ifi.dbs.elki.visualization.projector.ScatterPlotProjector; +import de.lmu.ifi.dbs.elki.visualization.style.ClassStylingPolicy; import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; +import de.lmu.ifi.dbs.elki.visualization.style.StylingPolicy; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; 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; +import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatterplotVisualization; /** * Generates a SVG-Element containing bubbles. A Bubble is a circle visualizing @@ -68,11 +66,9 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * @author Erich Schubert * * @apiviz.has OutlierResult oneway - - visualizes - * - * @param <NV> Type of the DatabaseObject being visualized. */ -@Reference(authors = "E. Achtert, H.-P. Kriegel, L. Reichert, E. Schubert, R. Wojdanowski, A. Zimek", title = "Visual Evaluation of Outlier Detection Models", booktitle = "Proceedings of the 15th International Conference on Database Systems for Advanced Applications (DASFAA), Tsukuba, Japan, 2010", url = "http://dx.doi.org/10.1007%2F978-3-642-12098-5_34") -public class BubbleVisualization<NV extends NumberVector<NV, ?>> extends P2DVisualization<NV> implements DataStoreListener { +@Reference(authors = "E. Achtert, H.-P. Kriegel, L. Reichert, E. Schubert, R. Wojdanowski, A. Zimek", title = "Visual Evaluation of Outlier Detection Models", booktitle = "Proceedings of the 15th International Conference on Database Systems for Advanced Applications (DASFAA), Tsukuba, Japan, 2010", url = "http://dx.doi.org/10.1007/978-3-642-12098-5_34") +public class BubbleVisualization extends AbstractScatterplotVisualization implements DataStoreListener { /** * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc. */ @@ -120,22 +116,46 @@ public class BubbleVisualization<NV extends NumberVector<NV, ?>> extends P2DVisu @Override public void redraw() { - Clustering<Model> clustering = context.getOrCreateDefaultClustering(); - setupCSS(svgp, clustering); + StylingPolicy stylepolicy = context.getStyleResult().getStylingPolicy(); // bubble size - double bubble_size = context.getStyleLibrary().getSize(StyleLibrary.BUBBLEPLOT); - // draw data - Iterator<Cluster<Model>> ci = clustering.getAllClusters().iterator(); - for(int cnum = 0; cnum < clustering.getAllClusters().size(); cnum++) { - Cluster<?> clus = ci.next(); - for(DBID objId : clus.getIDs()) { + final double bubble_size = context.getStyleLibrary().getSize(StyleLibrary.BUBBLEPLOT); + if(stylepolicy instanceof ClassStylingPolicy) { + ClassStylingPolicy colors = (ClassStylingPolicy) stylepolicy; + setupCSS(svgp, colors); + // draw data + for(DBID objId : sample.getSample()) { + final Double radius = getScaledForId(objId); + if(radius > 0.01 && !Double.isInfinite(radius)) { + final NumberVector<?, ?> vec = rel.get(objId); + if(vec != null) { + double[] v = proj.fastProjectDataToRenderSpace(vec); + Element circle = svgp.svgCircle(v[0], v[1], radius * bubble_size); + SVGUtil.addCSSClass(circle, BUBBLE + colors.getStyleForDBID(objId)); + layer.appendChild(circle); + } + } + } + } + else { + // draw data + for(DBID objId : sample.getSample()) { final Double radius = getScaledForId(objId); - if(radius > 0.01) { - final NV vec = rel.get(objId); + if(radius > 0.01 && !Double.isInfinite(radius)) { + final NumberVector<?, ?> vec = rel.get(objId); if(vec != null) { double[] v = proj.fastProjectDataToRenderSpace(vec); Element circle = svgp.svgCircle(v[0], v[1], radius * bubble_size); - SVGUtil.addCSSClass(circle, BUBBLE + cnum); + int color = stylepolicy.getColorForDBID(objId); + final StringBuffer style = new StringBuffer(); + if(fill) { + style.append(SVGConstants.CSS_FILL_PROPERTY + ":").append(SVGUtil.colorToString(color)); + style.append(SVGConstants.CSS_FILL_OPACITY_PROPERTY + ":0.5"); + } + else { + style.append(SVGConstants.CSS_STROKE_VALUE + ":").append(SVGUtil.colorToString(color)); + style.append(SVGConstants.CSS_FILL_PROPERTY + ":" + SVGConstants.CSS_NONE_VALUE); + } + SVGUtil.setAtt(circle, SVGConstants.SVG_STYLE_ATTRIBUTE, style.toString()); layer.appendChild(circle); } } @@ -144,35 +164,27 @@ public class BubbleVisualization<NV extends NumberVector<NV, ?>> extends P2DVisu } @Override - public void contentChanged(DataStoreEvent e) { - synchronizedRedraw(); + public void resultChanged(Result current) { + if(sample == current) { + synchronizedRedraw(); + } } /** * Registers the Bubble-CSS-Class at a SVGPlot. * * @param svgp the SVGPlot to register the Tooltip-CSS-Class. - * @param clustering Clustering to use + * @param policy Clustering to use */ - private void setupCSS(SVGPlot svgp, Clustering<? extends Model> clustering) { + private void setupCSS(SVGPlot svgp, ClassStylingPolicy policy) { ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT); // creating IDs manually because cluster often return a null-ID. - int clusterID = 0; - - for(@SuppressWarnings("unused") - Cluster<?> cluster : clustering.getAllClusters()) { + for(int clusterID = policy.getMinStyle(); clusterID < policy.getMaxStyle(); clusterID++) { CSSClass bubble = new CSSClass(svgp, BUBBLE + clusterID); bubble.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT)); - String color; - - if(clustering.getAllClusters().size() == 1) { - color = "black"; - } - else { - color = colors.getColor(clusterID); - } + String color = colors.getColor(clusterID); if(fill) { bubble.setStatement(SVGConstants.CSS_FILL_PROPERTY, color); @@ -185,7 +197,6 @@ public class BubbleVisualization<NV extends NumberVector<NV, ?>> extends P2DVisu } svgp.addCSSClassOrLogError(bubble); - clusterID += 1; } } @@ -196,9 +207,9 @@ public class BubbleVisualization<NV extends NumberVector<NV, ?>> extends P2DVisu * @return a Double representing a outlierness-score, after it has modified by * the given scales. */ - protected Double getScaledForId(DBID id) { - Double d = result.getScores().get(id).doubleValue(); - if(d == null) { + protected double getScaledForId(DBID id) { + double d = result.getScores().get(id).doubleValue(); + if(Double.isNaN(d) || Double.isInfinite(d)) { return 0.0; } if(scaling == null) { @@ -216,10 +227,8 @@ public class BubbleVisualization<NV extends NumberVector<NV, ?>> extends P2DVisu * * @apiviz.stereotype factory * @apiviz.uses BubbleVisualization oneway - - «create» - * - * @param <NV> Type of the DatabaseObject being visualized. */ - public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory { + public static class Factory extends AbstractVisFactory { /** * Flag for half-transparent filling of bubbles. * @@ -266,14 +275,14 @@ public class BubbleVisualization<NV extends NumberVector<NV, ?>> extends P2DVisu final OutlierResult outlierResult = task.getResult(); ((OutlierScalingFunction) this.scaling).prepare(outlierResult); } - return new BubbleVisualization<NV>(task, scaling); + return new BubbleVisualization(task, scaling); } @Override public void processNewResult(HierarchicalResult baseResult, Result result) { List<OutlierResult> ors = ResultUtil.filterResults(result, OutlierResult.class); for(OutlierResult o : ors) { - Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); + IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); boolean vis = true; // Quick and dirty hack: hide if parent result is also an outlier result // Since that probably is already visible and we're redundant. @@ -283,7 +292,7 @@ public class BubbleVisualization<NV extends NumberVector<NV, ?>> extends P2DVisu break; } } - for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) { + for(ScatterPlotProjector<?> p : ps) { final VisualizationTask task = new VisualizationTask(NAME, o, p.getRelation(), this); task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA); if(!vis) { @@ -302,7 +311,7 @@ public class BubbleVisualization<NV extends NumberVector<NV, ?>> extends P2DVisu * * @apiviz.exclude */ - public static class Parameterizer<NV extends NumberVector<NV, ?>> extends AbstractParameterizer { + public static class Parameterizer extends AbstractParameterizer { /** * Fill parameter. */ @@ -328,8 +337,8 @@ public class BubbleVisualization<NV extends NumberVector<NV, ?>> extends P2DVisu } @Override - protected Factory<NV> makeInstance() { - return new Factory<NV>(fill, scaling); + protected Factory makeInstance() { + return new Factory(fill, scaling); } } } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/outlier/package-info.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/outlier/package-info.java new file mode 100644 index 00000000..7332fcb0 --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/outlier/package-info.java @@ -0,0 +1,26 @@ +/** + * <p>Visualizers for outlier scores based on 2D projections.</p> + */ +/* + This file is part of ELKI: + Environment for Developing KDD-Applications Supported by Index-Structures + + Copyright (C) 2012 + Ludwig-Maximilians-Universität München + Lehr- und Forschungseinheit für Datenbanksysteme + ELKI Development Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.outlier;
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/package-info.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/package-info.java index 6a0fa750..805379d7 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/package-info.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/package-info.java @@ -1,9 +1,8 @@ /** - * <p>Visualizers based on 2D projections.</p> + * <p>Visualizers based on scatterplots.</p> * * @apiviz.exclude de.lmu.ifi.dbs.elki.visualization.batikutil.* * @apiviz.exclude de.lmu.ifi.dbs.elki.visualization.svg.* - * @apiviz.exclude de.lmu.ifi.dbs.elki.visualization.visualizers.events.* * @apiviz.exclude de.lmu.ifi.dbs.elki.result.* * @apiviz.exclude de.lmu.ifi.dbs.elki.index.* * @apiviz.exclude de.lmu.ifi.dbs.elki.data.* @@ -13,7 +12,7 @@ This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures -Copyright (C) 2011 +Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -31,4 +30,4 @@ 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.vis2d;
\ No newline at end of file +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot;
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/MoveObjectsToolVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/MoveObjectsToolVisualization.java index 6d798247..fb9de7d5 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/MoveObjectsToolVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/MoveObjectsToolVisualization.java @@ -1,4 +1,4 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d;
+package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.selection;
/*
This file is part of ELKI:
@@ -30,7 +30,6 @@ import org.w3c.dom.Element; import org.w3c.dom.events.Event;
import org.w3c.dom.svg.SVGPoint;
-import de.lmu.ifi.dbs.elki.data.NumberVector;
import de.lmu.ifi.dbs.elki.database.UpdatableDatabase;
import de.lmu.ifi.dbs.elki.database.ids.DBIDs;
import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector;
@@ -38,7 +37,7 @@ 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.exceptions.AbortException;
-import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator;
import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
import de.lmu.ifi.dbs.elki.visualization.batikutil.DragableArea;
import de.lmu.ifi.dbs.elki.visualization.batikutil.DragableArea.DragListener;
@@ -49,7 +48,7 @@ import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; 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;
-import de.lmu.ifi.dbs.elki.visualization.visualizers.events.ContextChangedEvent;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatterplotVisualization;
/**
* Tool to move the currently selected objects.
@@ -58,10 +57,8 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.events.ContextChangedEvent; * @author Erich Schubert
*
* @apiviz.has NumberVector oneway - - edits
- *
- * @param <NV> Type of the NumberVector being visualized.
*/
-public class MoveObjectsToolVisualization<NV extends NumberVector<NV, ?>> extends P2DVisualization<NV> implements DragListener {
+public class MoveObjectsToolVisualization extends AbstractScatterplotVisualization implements DragListener {
/**
* A short name characterizing this Visualizer.
*/
@@ -84,19 +81,14 @@ public class MoveObjectsToolVisualization<NV extends NumberVector<NV, ?>> extend public MoveObjectsToolVisualization(VisualizationTask task) {
super(task);
- context.addContextChangeListener(this);
incrementalRedraw();
}
@Override
- public void destroy() {
- super.destroy();
- context.removeContextChangeListener(this);
- }
-
- @Override
- public void contextChanged(ContextChangedEvent e) {
- synchronizedRedraw();
+ public void resultChanged(Result current) {
+ if(sample == current) {
+ synchronizedRedraw();
+ }
}
@Override
@@ -200,10 +192,8 @@ public class MoveObjectsToolVisualization<NV extends NumberVector<NV, ?>> extend *
* @apiviz.stereotype factory
* @apiviz.uses MoveObjectsToolVisualization oneway - - «create»
- *
- * @param <NV> Type of the NumberVector being visualized.
*/
- public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory {
+ public static class Factory extends AbstractVisFactory {
/**
* Constructor
*/
@@ -213,7 +203,7 @@ public class MoveObjectsToolVisualization<NV extends NumberVector<NV, ?>> extend @Override
public Visualization makeVisualization(VisualizationTask task) {
- return new MoveObjectsToolVisualization<NV>(task);
+ return new MoveObjectsToolVisualization(task);
}
@Override
@@ -222,13 +212,14 @@ public class MoveObjectsToolVisualization<NV extends NumberVector<NV, ?>> extend if(!dbs.hasNext()) {
return;
}
- Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) {
+ IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
final VisualizationTask task = new VisualizationTask(NAME, p.getRelation(), p.getRelation(), this);
task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE);
task.put(VisualizationTask.META_TOOL, true);
task.put(VisualizationTask.META_NOTHUMB, true);
task.put(VisualizationTask.META_NOEXPORT, true);
+ task.put(VisualizationTask.META_VISIBLE_DEFAULT, false);
// baseResult.getHierarchy().add(p.getRelation(), task);
baseResult.getHierarchy().add(p, task);
}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/SelectionConvexHullVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionConvexHullVisualization.java index fde2c00d..5702800d 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/SelectionConvexHullVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionConvexHullVisualization.java @@ -1,4 +1,4 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d;
+package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.selection;
/*
This file is part of ELKI:
@@ -24,18 +24,15 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; */
import java.util.ArrayList;
-import java.util.Iterator;
import org.apache.batik.util.SVGConstants;
import org.w3c.dom.Element;
-import de.lmu.ifi.dbs.elki.data.NumberVector;
import de.lmu.ifi.dbs.elki.data.spatial.Polygon;
-import de.lmu.ifi.dbs.elki.database.datastore.DataStoreEvent;
import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener;
import de.lmu.ifi.dbs.elki.database.ids.DBID;
import de.lmu.ifi.dbs.elki.database.ids.DBIDs;
-import de.lmu.ifi.dbs.elki.math.ConvexHull2D;
+import de.lmu.ifi.dbs.elki.math.geometry.GrahamScanConvexHull2D;
import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector;
import de.lmu.ifi.dbs.elki.result.DBIDSelection;
import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
@@ -43,7 +40,7 @@ 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.utilities.exceptions.ObjectNotFoundException;
-import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator;
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.ScatterPlotProjector;
@@ -53,7 +50,7 @@ import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; 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;
-import de.lmu.ifi.dbs.elki.visualization.visualizers.events.ContextChangeListener;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatterplotVisualization;
import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualization;
/**
@@ -65,10 +62,8 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualizati * @apiviz.has SelectionResult oneway - - visualizes
* @apiviz.has DBIDSelection oneway - - visualizes
* @apiviz.uses ConvexHull2D
- *
- * @param <NV> Type of the NumberVector being visualized.
*/
-public class SelectionConvexHullVisualization<NV extends NumberVector<NV, ?>> extends P2DVisualization<NV> implements ContextChangeListener, DataStoreListener {
+public class SelectionConvexHullVisualization extends AbstractScatterplotVisualization implements DataStoreListener {
/**
* A short name characterizing this Visualizer.
*/
@@ -86,7 +81,6 @@ public class SelectionConvexHullVisualization<NV extends NumberVector<NV, ?>> ex */
public SelectionConvexHullVisualization(VisualizationTask task) {
super(task);
- context.addContextChangeListener(this);
context.addResultListener(this);
context.addDataStoreListener(this);
incrementalRedraw();
@@ -98,7 +92,7 @@ public class SelectionConvexHullVisualization<NV extends NumberVector<NV, ?>> ex DBIDSelection selContext = context.getSelection();
if(selContext != null) {
DBIDs selection = selContext.getSelectedIds();
- ConvexHull2D hull = new ConvexHull2D();
+ GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D();
for(DBID i : selection) {
try {
hull.add(new Vector(proj.fastProjectDataToRenderSpace(rel.get(i))));
@@ -139,11 +133,6 @@ public class SelectionConvexHullVisualization<NV extends NumberVector<NV, ?>> ex }
}
- @Override
- public void contentChanged(DataStoreEvent e) {
- synchronizedRedraw();
- }
-
/**
* Factory for visualizers to generate an SVG-Element containing the convex
* hull of the selected points
@@ -152,33 +141,27 @@ public class SelectionConvexHullVisualization<NV extends NumberVector<NV, ?>> ex *
* @apiviz.stereotype factory
* @apiviz.uses SelectionConvexHullVisualization oneway - - «create»
- *
- * @param <NV> Type of the NumberVector being visualized.
*/
- public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory {
+ public static class Factory extends AbstractVisFactory {
/**
* Constructor
*/
public Factory() {
super();
+ thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION;
}
@Override
public Visualization makeVisualization(VisualizationTask task) {
- return new SelectionConvexHullVisualization<NV>(task);
- }
-
- @Override
- public Visualization makeVisualizationOrThumbnail(VisualizationTask task) {
- return new ThumbnailVisualization(this, task, ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION);
+ return new SelectionConvexHullVisualization(task);
}
@Override
public void processNewResult(HierarchicalResult baseResult, Result result) {
final ArrayList<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
for(SelectionResult selres : selectionResults) {
- Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) {
+ IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA - 2);
baseResult.getHierarchy().add(selres, task);
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/SelectionCubeVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionCubeVisualization.java index 087ec6af..9fd24b43 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/SelectionCubeVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionCubeVisualization.java @@ -1,10 +1,10 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d;
+package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.selection;
/*
This file is part of ELKI:
Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011
+ Copyright (C) 2012
Ludwig-Maximilians-Universität München
Lehr- und Forschungseinheit für Datenbanksysteme
ELKI Development Team @@ -24,13 +24,10 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; */
import java.util.ArrayList;
-import java.util.Iterator;
import org.apache.batik.util.SVGConstants;
import org.w3c.dom.Element;
-import de.lmu.ifi.dbs.elki.data.NumberVector;
-import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector;
import de.lmu.ifi.dbs.elki.result.DBIDSelection;
import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
import de.lmu.ifi.dbs.elki.result.RangeSelection;
@@ -38,7 +35,7 @@ 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.utilities.DatabaseUtil;
-import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization;
@@ -54,7 +51,7 @@ import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; 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;
-import de.lmu.ifi.dbs.elki.visualization.visualizers.events.ContextChangeListener;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatterplotVisualization;
import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualization;
/**
@@ -66,10 +63,8 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualizati * @apiviz.has SelectionResult oneway - - visualizes
* @apiviz.has RangeSelection oneway - - visualizes
* @apiviz.uses SVGHyperCube
- *
- * @param <NV> Type of the NumberVector being visualized.
*/
-public class SelectionCubeVisualization<NV extends NumberVector<NV, ?>> extends P2DVisualization<NV> implements ContextChangeListener {
+public class SelectionCubeVisualization extends AbstractScatterplotVisualization {
/**
* A short name characterizing this Visualizer.
*/
@@ -99,11 +94,16 @@ public class SelectionCubeVisualization<NV extends NumberVector<NV, ?>> extends super(task);
this.nofill = nofill;
addCSSClasses(svgp);
- context.addContextChangeListener(this);
context.addResultListener(this);
incrementalRedraw();
}
+ @Override
+ public void destroy() {
+ context.removeResultListener(this);
+ super.destroy();
+ }
+
/**
* Adds the required CSS-Classes
*
@@ -165,12 +165,12 @@ public class SelectionCubeVisualization<NV extends NumberVector<NV, ?>> extends }
}
if(nofill) {
- Element r = SVGHyperCube.drawFrame(svgp, proj, new Vector(min), new Vector(max));
+ Element r = SVGHyperCube.drawFrame(svgp, proj, min, max);
SVGUtil.setCSSClass(r, CSS_CUBEFRAME);
layer.appendChild(r);
}
else {
- Element r = SVGHyperCube.drawFilled(svgp, CSS_CUBE, proj, new Vector(min), new Vector(max));
+ Element r = SVGHyperCube.drawFilled(svgp, CSS_CUBE, proj, min, max);
layer.appendChild(r);
}
@@ -193,10 +193,8 @@ public class SelectionCubeVisualization<NV extends NumberVector<NV, ?>> extends *
* @apiviz.stereotype factory
* @apiviz.uses SelectionCubeVisualization oneway - - «create»
- *
- * @param <NV> vector type
*/
- public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory {
+ public static class Factory extends AbstractVisFactory {
/**
* Flag for half-transparent filling of selection cubes.
*
@@ -219,19 +217,20 @@ public class SelectionCubeVisualization<NV extends NumberVector<NV, ?>> extends public Factory(boolean nofill) {
super();
this.nofill = nofill;
+ thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION;
}
@Override
public Visualization makeVisualization(VisualizationTask task) {
- return new SelectionCubeVisualization<NV>(task, nofill);
+ return new SelectionCubeVisualization(task, nofill);
}
@Override
public void processNewResult(HierarchicalResult baseResult, Result result) {
final ArrayList<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
for(SelectionResult selres : selectionResults) {
- Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) {
+ IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA - 2);
baseResult.getHierarchy().add(selres, task);
@@ -240,11 +239,6 @@ public class SelectionCubeVisualization<NV extends NumberVector<NV, ?>> extends }
}
- @Override
- public Visualization makeVisualizationOrThumbnail(VisualizationTask task) {
- return new ThumbnailVisualization(this, task, ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION);
- }
-
/**
* Parameterization class.
*
@@ -252,7 +246,7 @@ public class SelectionCubeVisualization<NV extends NumberVector<NV, ?>> extends *
* @apiviz.exclude
*/
- public static class Parameterizer<NV extends NumberVector<NV, ?>> extends AbstractParameterizer {
+ public static class Parameterizer extends AbstractParameterizer {
protected boolean nofill;
@Override
@@ -265,8 +259,8 @@ public class SelectionCubeVisualization<NV extends NumberVector<NV, ?>> extends }
@Override
- protected Factory<NV> makeInstance() {
- return new Factory<NV>(nofill);
+ protected Factory makeInstance() {
+ return new Factory(nofill);
}
}
}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/SelectionDotVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionDotVisualization.java index 06167532..79d1afe5 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/SelectionDotVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionDotVisualization.java @@ -1,10 +1,10 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d;
+package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.selection;
/*
This file is part of ELKI:
Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011
+ Copyright (C) 2012
Ludwig-Maximilians-Universität München
Lehr- und Forschungseinheit für Datenbanksysteme
ELKI Development Team @@ -24,13 +24,10 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; */
import java.util.ArrayList;
-import java.util.Iterator;
import org.apache.batik.util.SVGConstants;
import org.w3c.dom.Element;
-import de.lmu.ifi.dbs.elki.data.NumberVector;
-import de.lmu.ifi.dbs.elki.database.datastore.DataStoreEvent;
import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener;
import de.lmu.ifi.dbs.elki.database.ids.DBID;
import de.lmu.ifi.dbs.elki.database.ids.DBIDs;
@@ -40,7 +37,7 @@ 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.utilities.exceptions.ObjectNotFoundException;
-import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator;
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.ScatterPlotProjector;
@@ -49,7 +46,7 @@ import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; 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;
-import de.lmu.ifi.dbs.elki.visualization.visualizers.events.ContextChangeListener;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatterplotVisualization;
import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualization;
/**
@@ -60,10 +57,8 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualizati *
* @apiviz.has SelectionResult oneway - - visualizes
* @apiviz.has DBIDSelection oneway - - visualizes
- *
- * @param <NV> Type of the NumberVector being visualized.
*/
-public class SelectionDotVisualization<NV extends NumberVector<NV, ?>> extends P2DVisualization<NV> implements ContextChangeListener, DataStoreListener {
+public class SelectionDotVisualization extends AbstractScatterplotVisualization implements DataStoreListener {
/**
* A short name characterizing this Visualizer.
*/
@@ -81,13 +76,18 @@ public class SelectionDotVisualization<NV extends NumberVector<NV, ?>> extends P */
public SelectionDotVisualization(VisualizationTask task) {
super(task);
- context.addContextChangeListener(this);
context.addResultListener(this);
context.addDataStoreListener(this);
incrementalRedraw();
}
@Override
+ public void destroy() {
+ context.removeResultListener(this);
+ super.destroy();
+ }
+
+ @Override
protected void redraw() {
addCSSClasses(svgp);
final double size = context.getStyleLibrary().getSize(StyleLibrary.SELECTION);
@@ -124,11 +124,6 @@ public class SelectionDotVisualization<NV extends NumberVector<NV, ?>> extends P }
}
- @Override
- public void contentChanged(DataStoreEvent e) {
- synchronizedRedraw();
- }
-
/**
* Factory for visualizers to generate an SVG-Element containing dots as
* markers representing the selected Database's objects.
@@ -137,33 +132,27 @@ public class SelectionDotVisualization<NV extends NumberVector<NV, ?>> extends P *
* @apiviz.stereotype factory
* @apiviz.uses SelectionDotVisualization oneway - - «create»
- *
- * @param <NV> Type of the NumberVector being visualized.
*/
- public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory {
+ public static class Factory extends AbstractVisFactory {
/**
* Constructor
*/
public Factory() {
super();
+ thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION;
}
@Override
public Visualization makeVisualization(VisualizationTask task) {
- return new SelectionDotVisualization<NV>(task);
- }
-
- @Override
- public Visualization makeVisualizationOrThumbnail(VisualizationTask task) {
- return new ThumbnailVisualization(this, task, ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION);
+ return new SelectionDotVisualization(task);
}
@Override
public void processNewResult(HierarchicalResult baseResult, Result result) {
final ArrayList<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
for(SelectionResult selres : selectionResults) {
- Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) {
+ IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA - 1);
baseResult.getHierarchy().add(selres, task);
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/SelectionToolCubeVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolCubeVisualization.java index ed0b456d..8e86f920 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/SelectionToolCubeVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolCubeVisualization.java @@ -1,4 +1,4 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d;
+package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.selection;
/*
This file is part of ELKI:
@@ -25,7 +25,6 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; import java.util.ArrayList;
import java.util.BitSet;
-import java.util.Iterator;
import org.apache.batik.util.SVGConstants;
import org.w3c.dom.Element;
@@ -37,7 +36,6 @@ 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.ModifiableDBIDs;
import de.lmu.ifi.dbs.elki.logging.Logging;
-import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector;
import de.lmu.ifi.dbs.elki.result.DBIDSelection;
import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
import de.lmu.ifi.dbs.elki.result.RangeSelection;
@@ -45,7 +43,7 @@ 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.utilities.DatabaseUtil;
-import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator;
import de.lmu.ifi.dbs.elki.utilities.pairs.DoubleDoublePair;
import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
import de.lmu.ifi.dbs.elki.visualization.batikutil.DragableArea;
@@ -57,7 +55,7 @@ import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; 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;
-import de.lmu.ifi.dbs.elki.visualization.visualizers.events.ContextChangedEvent;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatterplotVisualization;
/**
* Tool-Visualization for the tool to select ranges
@@ -66,10 +64,8 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.events.ContextChangedEvent; *
* @apiviz.has SelectionResult oneway - - updates
* @apiviz.has RangeSelection oneway - - updates
- *
- * @param <NV> Type of the NumberVector being visualized.
*/
-public class SelectionToolCubeVisualization<NV extends NumberVector<NV, ?>> extends P2DVisualization<NV> implements DragableArea.DragListener {
+public class SelectionToolCubeVisualization extends AbstractScatterplotVisualization implements DragableArea.DragListener {
/**
* The logger for this class.
*/
@@ -108,22 +104,10 @@ public class SelectionToolCubeVisualization<NV extends NumberVector<NV, ?>> exte public SelectionToolCubeVisualization(VisualizationTask task) {
super(task);
this.dim = DatabaseUtil.dimensionality(rel);
- context.addContextChangeListener(this);
incrementalRedraw();
}
@Override
- public void destroy() {
- super.destroy();
- context.removeContextChangeListener(this);
- }
-
- @Override
- public void contextChanged(ContextChangedEvent e) {
- synchronizedRedraw();
- }
-
- @Override
protected void redraw() {
addCSSClasses(svgp);
@@ -160,20 +144,18 @@ public class SelectionToolCubeVisualization<NV extends NumberVector<NV, ?>> exte */
private void updateSelectionRectKoordinates(double x1, double x2, double y1, double y2, DoubleDoublePair[] ranges) {
BitSet actDim = proj.getVisibleDimensions2D();
- Vector v1 = new Vector(dim);
- Vector v2 = new Vector(dim);
- v1.set(0, x1);
- v1.set(1, y1);
- v2.set(0, x2);
- v2.set(1, y2);
+ double[] v1 = new double[dim];
+ double[] v2 = new double[dim];
+ v1[0] = x1;
+ v1[1] = y1;
+ v2[0] = x2;
+ v2[1] = y2;
- NV factory = DatabaseUtil.assumeVectorField(rel).getFactory();
-
- NV nv1 = proj.projectRenderToDataSpace(v1, factory);
- NV nv2 = proj.projectRenderToDataSpace(v2, factory);
+ double[] nv1 = proj.fastProjectRenderToDataSpace(v1);
+ double[] nv2 = proj.fastProjectRenderToDataSpace(v2);
for(int d = actDim.nextSetBit(0); d >= 0; d = actDim.nextSetBit(d + 1)) {
- ranges[d] = new DoubleDoublePair(Math.min(nv1.doubleValue(d + 1), nv2.doubleValue(d + 1)), Math.max(nv1.doubleValue(d + 1), nv2.doubleValue(d + 1)));
+ ranges[d] = new DoubleDoublePair(Math.min(nv1[d], nv2[d]), Math.max(nv1[d], nv2[d]));
}
}
@@ -240,7 +222,7 @@ public class SelectionToolCubeVisualization<NV extends NumberVector<NV, ?>> exte selection.clear();
boolean idIn = true;
for(DBID id : rel.iterDBIDs()) {
- NV dbTupel = rel.get(id);
+ NumberVector<?, ?> dbTupel = rel.get(id);
idIn = true;
for(int i = 0; i < dim; i++) {
if(ranges != null && ranges[i] != null) {
@@ -282,10 +264,8 @@ public class SelectionToolCubeVisualization<NV extends NumberVector<NV, ?>> exte *
* @apiviz.stereotype factory
* @apiviz.uses SelectionToolCubeVisualization oneway - - «create»
- *
- * @param <NV> Type of the NumberVector being visualized.
*/
- public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory {
+ public static class Factory extends AbstractVisFactory {
/**
* Constructor, adhering to
* {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
@@ -296,20 +276,21 @@ public class SelectionToolCubeVisualization<NV extends NumberVector<NV, ?>> exte @Override
public Visualization makeVisualization(VisualizationTask task) {
- return new SelectionToolCubeVisualization<NV>(task);
+ return new SelectionToolCubeVisualization(task);
}
@Override
public void processNewResult(HierarchicalResult baseResult, Result result) {
final ArrayList<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
for(SelectionResult selres : selectionResults) {
- Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) {
+ IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE);
task.put(VisualizationTask.META_TOOL, true);
task.put(VisualizationTask.META_NOTHUMB, true);
task.put(VisualizationTask.META_NOEXPORT, true);
+ task.put(VisualizationTask.META_VISIBLE_DEFAULT, false);
baseResult.getHierarchy().add(selres, task);
baseResult.getHierarchy().add(p, task);
}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/SelectionToolDotVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolDotVisualization.java index 7593dac5..877833f7 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/SelectionToolDotVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolDotVisualization.java @@ -1,4 +1,4 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d;
+package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.selection;
/*
This file is part of ELKI:
@@ -24,7 +24,6 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; */
import java.util.ArrayList;
-import java.util.Iterator;
import org.apache.batik.dom.events.DOMMouseEvent;
import org.apache.batik.util.SVGConstants;
@@ -32,7 +31,6 @@ import org.w3c.dom.Element; import org.w3c.dom.events.Event;
import org.w3c.dom.svg.SVGPoint;
-import de.lmu.ifi.dbs.elki.data.NumberVector;
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.HashSetModifiableDBIDs;
@@ -41,7 +39,7 @@ 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.utilities.iterator.IterableUtil;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator;
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;
@@ -52,7 +50,7 @@ import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; 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;
-import de.lmu.ifi.dbs.elki.visualization.visualizers.events.ContextChangedEvent;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatterplotVisualization;
/**
* Tool-Visualization for the tool to select objects
@@ -61,10 +59,8 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.events.ContextChangedEvent; *
* @apiviz.has SelectionResult oneway - - updates
* @apiviz.has DBIDSelection oneway - - updates
- *
- * @param <NV> vector type
*/
-public class SelectionToolDotVisualization<NV extends NumberVector<NV, ?>> extends P2DVisualization<NV> implements DragableArea.DragListener {
+public class SelectionToolDotVisualization extends AbstractScatterplotVisualization implements DragableArea.DragListener {
/**
* A short name characterizing this Visualizer.
*/
@@ -101,22 +97,10 @@ public class SelectionToolDotVisualization<NV extends NumberVector<NV, ?>> exten */
public SelectionToolDotVisualization(VisualizationTask task) {
super(task);
- context.addContextChangeListener(this);
incrementalRedraw();
}
@Override
- public void destroy() {
- super.destroy();
- context.removeContextChangeListener(this);
- }
-
- @Override
- public void contextChanged(ContextChangedEvent e) {
- synchronizedRedraw();
- }
-
- @Override
protected void redraw() {
addCSSClasses(svgp);
@@ -255,10 +239,8 @@ public class SelectionToolDotVisualization<NV extends NumberVector<NV, ?>> exten *
* @apiviz.stereotype factory
* @apiviz.uses SelectionToolDotVisualization - - «create»
- *
- * @param <NV> Type of the NumberVector being visualized.
*/
- public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory {
+ public static class Factory extends AbstractVisFactory {
/**
* Constructor, adhering to
* {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
@@ -269,20 +251,21 @@ public class SelectionToolDotVisualization<NV extends NumberVector<NV, ?>> exten @Override
public Visualization makeVisualization(VisualizationTask task) {
- return new SelectionToolDotVisualization<NV>(task);
+ return new SelectionToolDotVisualization(task);
}
@Override
public void processNewResult(HierarchicalResult baseResult, Result result) {
final ArrayList<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
for(SelectionResult selres : selectionResults) {
- Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) {
+ IterableIterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE);
task.put(VisualizationTask.META_TOOL, true);
task.put(VisualizationTask.META_NOTHUMB, true);
task.put(VisualizationTask.META_NOEXPORT, true);
+ task.put(VisualizationTask.META_VISIBLE_DEFAULT, false);
baseResult.getHierarchy().add(selres, task);
baseResult.getHierarchy().add(p, task);
}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/package-info.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/package-info.java new file mode 100644 index 00000000..3710de5c --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/package-info.java @@ -0,0 +1,26 @@ +/** + * <p>Visualizers for object selection based on 2D projections.</p> + */ +/* + This file is part of ELKI: + Environment for Developing KDD-Applications Supported by Index-Structures + + Copyright (C) 2012 + Ludwig-Maximilians-Universität München + Lehr- und Forschungseinheit für Datenbanksysteme + ELKI Development Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.selection;
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/thumbs/ThumbnailThread.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/thumbs/ThumbnailThread.java index 5d79a986..2c9425de 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/thumbs/ThumbnailThread.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/thumbs/ThumbnailThread.java @@ -4,7 +4,7 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs; This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -26,7 +26,6 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; - /** * Thread to render thumbnails in the background. * @@ -47,11 +46,6 @@ public class ThumbnailThread extends Thread { private boolean shutdown = false; /** - * Thumbnailer to use. - */ - private Thumbnailer t = new Thumbnailer(); - - /** * The static thumbnail thread. */ private static ThumbnailThread THREAD = null; @@ -113,7 +107,7 @@ public class ThumbnailThread extends Thread { * @param ti Visualization task */ private void generateThumbnail(Task ti) { - ti.callback.doThumbnail(t); + ti.callback.doThumbnail(); } @Override @@ -163,9 +157,7 @@ public class ThumbnailThread extends Thread { public interface Listener { /** * Callback when to (re-)compute the thumbnail. - * - * @param t Thumbnailer to use */ - public void doThumbnail(Thumbnailer t); + public void doThumbnail(); } }
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/thumbs/ThumbnailVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/thumbs/ThumbnailVisualization.java index 25f55d41..9ea016a5 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/thumbs/ThumbnailVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/thumbs/ThumbnailVisualization.java @@ -4,7 +4,7 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs; This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -23,32 +23,30 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs; along with this program. If not, see <http://www.gnu.org/licenses/>. */ -import java.io.File; +import java.awt.image.BufferedImage; import org.apache.batik.util.SVGConstants; import org.w3c.dom.Element; -import de.lmu.ifi.dbs.elki.database.datastore.DataStoreEvent; import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener; import de.lmu.ifi.dbs.elki.logging.Logging; import de.lmu.ifi.dbs.elki.logging.LoggingUtil; import de.lmu.ifi.dbs.elki.result.Result; import de.lmu.ifi.dbs.elki.result.SelectionResult; import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; +import de.lmu.ifi.dbs.elki.visualization.batikutil.ThumbnailRegistryEntry; +import de.lmu.ifi.dbs.elki.visualization.style.StyleResult; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil; import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisualization; import de.lmu.ifi.dbs.elki.visualization.visualizers.VisFactory; import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; -import de.lmu.ifi.dbs.elki.visualization.visualizers.events.ContextChangedEvent; -import de.lmu.ifi.dbs.elki.visualization.visualizers.events.ResizedEvent; /** * Thumbnail visualization. * * @author Erich Schubert * - * @apiviz.uses Thumbnailer * @apiviz.uses ThumbnailThread */ public class ThumbnailVisualization extends AbstractVisualization implements ThumbnailThread.Listener, DataStoreListener { @@ -63,14 +61,24 @@ public class ThumbnailVisualization extends AbstractVisualization implements Thu public static final int ON_SELECTION = 2; /** + * Constant to listen for style result changes + */ + public static final int ON_STYLE = 4; + + /** + * Constant to <em>not</em> listen for projection changes + */ + public static final int NO_PROJECTION = 8; + + /** * Visualizer factory */ protected final VisFactory visFactory; /** - * The thumbnail file. + * The thumbnail id. */ - protected File thumb = null; + protected int thumbid = -1; /** * Pending redraw @@ -83,11 +91,16 @@ public class ThumbnailVisualization extends AbstractVisualization implements Thu protected int tresolution; /** - * The event mask. See {@link #ON_DATA}, {@link #ON_SELECTION} + * The event mask. See {@link #ON_DATA}, {@link #ON_SELECTION}, {@link #ON_STYLE}, {@link #NO_PROJECTION} */ private int mask; /** + * Our thumbnail (keep a reference to prevent garbage collection!) + */ + private BufferedImage thumb; + + /** * Constructor. * * @param visFactory Visualizer Factory to use @@ -100,14 +113,13 @@ public class ThumbnailVisualization extends AbstractVisualization implements Thu Integer tres = task.getGenerics(VisualizationTask.THUMBNAIL_RESOLUTION, Integer.class); this.tresolution = tres; this.layer = task.getPlot().svgElement(SVGConstants.SVG_G_TAG); + this.thumbid = -1; this.thumb = null; this.mask = mask; // Listen for database events only when needed. if((mask & ON_DATA) == ON_DATA) { context.addDataStoreListener(this); } - // Always listen for context changes, in particular resize. - context.addContextChangeListener(this); // Listen for result changes, including the one we monitor context.addResultListener(this); } @@ -117,45 +129,19 @@ public class ThumbnailVisualization extends AbstractVisualization implements Thu if(pendingThumbnail != null) { ThumbnailThread.UNQUEUE(pendingThumbnail); } + // TODO: remove image from registry? context.removeResultListener(this); - context.removeContextChangeListener(this); context.removeDataStoreListener(this); } @Override public Element getLayer() { - if(thumb == null) { + if(thumbid < 0) { synchronizedRedraw(); } return layer; } - @Override - public void contextChanged(ContextChangedEvent e) { - if(testRedraw(e)) { - refreshThumbnail(); - } - } - - /** - * Override this method to add additional redraw triggers! - * - * @param e Event - * @return Test result - */ - @Override - protected boolean testRedraw(ContextChangedEvent e) { - if(e instanceof ResizedEvent) { - return true; - } - return false; - } - - @Override - public void contentChanged(DataStoreEvent e) { - refreshThumbnail(); - } - /** * Redraw the visualization (maybe incremental). * @@ -182,7 +168,7 @@ public class ThumbnailVisualization extends AbstractVisualization implements Thu */ @Override protected void redraw() { - if(thumb == null) { + if(thumbid < 0) { // LoggingUtil.warning("Generating new thumbnail " + this); layer.appendChild(SVGUtil.svgWaitIcon(task.getPlot().getDocument(), 0, 0, task.getWidth(), task.getHeight())); if(pendingThumbnail == null) { @@ -196,13 +182,13 @@ public class ThumbnailVisualization extends AbstractVisualization implements Thu SVGUtil.setAtt(i, SVGConstants.SVG_Y_ATTRIBUTE, 0); SVGUtil.setAtt(i, SVGConstants.SVG_WIDTH_ATTRIBUTE, task.getWidth()); SVGUtil.setAtt(i, SVGConstants.SVG_HEIGHT_ATTRIBUTE, task.getHeight()); - i.setAttributeNS(SVGConstants.XLINK_NAMESPACE_URI, SVGConstants.XLINK_HREF_QNAME, thumb.toURI().toString()); + i.setAttributeNS(SVGConstants.XLINK_NAMESPACE_URI, SVGConstants.XLINK_HREF_QNAME, ThumbnailRegistryEntry.INTERNAL_PROTOCOL + ":" + thumbid); layer.appendChild(i); } } @Override - public synchronized void doThumbnail(Thumbnailer t) { + public synchronized void doThumbnail() { pendingThumbnail = null; try { SVGPlot plot = new SVGPlot(); @@ -217,7 +203,8 @@ public class ThumbnailVisualization extends AbstractVisualization implements Thu plot.updateStyleElement(); final int tw = (int) (task.getWidth() * tresolution); final int th = (int) (task.getHeight() * tresolution); - thumb = t.thumbnail(plot, tw, th); + thumb = plot.makeAWTImage(tw, th); + thumbid = ThumbnailRegistryEntry.registerImage(thumb); // The visualization will not be used anymore. vis.destroy(); synchronizedRedraw(); @@ -236,7 +223,9 @@ public class ThumbnailVisualization extends AbstractVisualization implements Thu protected void refreshThumbnail() { // Discard an existing thumbnail + thumbid = -1; thumb = null; + // TODO: also purge from ThumbnailRegistryEntry? synchronizedRedraw(); } @@ -246,6 +235,14 @@ public class ThumbnailVisualization extends AbstractVisualization implements Thu refreshThumbnail(); return; } + if((mask & ON_STYLE) == ON_STYLE && current instanceof StyleResult) { + refreshThumbnail(); + return; + } + if (task.getProj() != null && (mask & NO_PROJECTION) != NO_PROJECTION && current == task.getProj()) { + refreshThumbnail(); + return; + } super.resultChanged(current); } }
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/thumbs/Thumbnailer.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/thumbs/Thumbnailer.java deleted file mode 100644 index 1909821a..00000000 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/thumbs/Thumbnailer.java +++ /dev/null @@ -1,96 +0,0 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs; - -/* - 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 de.lmu.ifi.dbs.elki.logging.LoggingUtil; -import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; - -/** - * Class that will render a {@link SVGPlot} into a {@link File} as thumbnail. - * - * Note: this does not happen in the background - call it from your own Thread if you need that! - * - * @author Erich Schubert - * - * @apiviz.uses SVGPlot oneway - - renders - * @apiviz.has File oneway - - «create» - */ -public class Thumbnailer { - /** - * Default prefix - */ - private static final String DEFAULT_PREFIX = "elki-"; - - /** - * Prefix storage. - */ - private String prefix; - - /** - * Constructor - * @param prefix Filename prefix to avoid collisions (e.g "elki-") - */ - public Thumbnailer(String prefix) { - this.prefix = prefix; - } - - /** - * Constructor - */ - public Thumbnailer() { - this(DEFAULT_PREFIX); - } - - /** - * Generate a thumbnail for a given plot. - * - * @param plot Plot to use - * @param thumbwidth Width of the thumbnail - * @param thumbheight height of the thumbnail - * @return File object of the thumbnail, which has deleteOnExit set. - */ - public synchronized File thumbnail(SVGPlot plot, int thumbwidth, int thumbheight) { - File temp = null; - try { - temp = File.createTempFile(prefix, ".png"); - temp.deleteOnExit(); - plot.saveAsPNG(temp, thumbwidth, thumbheight); - } - catch(org.apache.batik.bridge.BridgeException e) { - plot.dumpDebugFile(); - LoggingUtil.exception("Exception rendering thumbnail: ", e); - } - catch(org.apache.batik.transcoder.TranscoderException e) { - plot.dumpDebugFile(); - LoggingUtil.exception("Exception rendering thumbnail: ", e); - } - catch(IOException e) { - LoggingUtil.exception(e); - } - return temp; - } -}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/thumbs/package-info.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/thumbs/package-info.java index 3006f52b..64676bb3 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/thumbs/package-info.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/thumbs/package-info.java @@ -6,7 +6,7 @@ This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures -Copyright (C) 2011 +Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/ClusterConvexHullVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/ClusterConvexHullVisualization.java deleted file mode 100644 index 0b97986b..00000000 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/ClusterConvexHullVisualization.java +++ /dev/null @@ -1,207 +0,0 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d;
-
-/*
- 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.Collection;
-import java.util.Iterator;
-
-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.NumberVector;
-import de.lmu.ifi.dbs.elki.data.model.Model;
-import de.lmu.ifi.dbs.elki.data.spatial.Polygon;
-import de.lmu.ifi.dbs.elki.data.spatial.SpatialUtil;
-import de.lmu.ifi.dbs.elki.database.ids.DBID;
-import de.lmu.ifi.dbs.elki.database.ids.DBIDs;
-import de.lmu.ifi.dbs.elki.math.ConvexHull2D;
-import de.lmu.ifi.dbs.elki.math.DoubleMinMax;
-import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector;
-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.utilities.pairs.Pair;
-import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
-import de.lmu.ifi.dbs.elki.visualization.colors.ColorLibrary;
-import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
-import de.lmu.ifi.dbs.elki.visualization.projector.ScatterPlotProjector;
-import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
-import de.lmu.ifi.dbs.elki.visualization.svg.SVGPath;
-import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot;
-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;
-
-/**
- * Visualizer for generating an SVG-Element containing the convex hull of each
- * cluster.
- *
- * @author Robert Rödler
- *
- * @apiviz.has Clustering oneway - - visualizes
- * @apiviz.uses ConvexHull2D
- *
- * @param <NV> Type of the NumberVector being visualized.
- */
-public class ClusterConvexHullVisualization<NV extends NumberVector<NV, ?>> extends P2DVisualization<NV> {
- /**
- * A short name characterizing this Visualizer.
- */
- private static final String NAME = "Cluster Convex Hull Visualization";
-
- /**
- * Generic tags to indicate the type of element. Used in IDs, CSS-Classes etc.
- */
- public static final String CONVEXHULL = "convexHull";
-
- /**
- * The result we work on
- */
- Clustering<Model> clustering;
-
- /**
- * The hulls
- */
- Element hulls;
-
- /**
- * Constructor
- *
- * @param task VisualizationTask
- */
- public ClusterConvexHullVisualization(VisualizationTask task) {
- super(task);
- this.clustering = task.getResult();
- context.addContextChangeListener(this);
- incrementalRedraw();
- }
-
- @Override
- protected void redraw() {
- // Viewport size, for "relative size" computations
- final Pair<DoubleMinMax, DoubleMinMax> viewp = proj.estimateViewport();
- double projarea = (viewp.getFirst().getDiff()) * (viewp.getSecond().getDiff());
-
- double opacity = 0.25;
-
- Iterator<Cluster<Model>> ci = clustering.getAllClusters().iterator();
-
- for(int cnum = 0; cnum < clustering.getAllClusters().size(); cnum++) {
- Cluster<?> clus = ci.next();
-
- final DBIDs ids = clus.getIDs();
- ConvexHull2D hull = new ConvexHull2D();
-
- for(DBID clpnum : ids) {
- double[] projP = proj.fastProjectDataToRenderSpace(rel.get(clpnum).getColumnVector());
- hull.add(new Vector(projP));
- }
- Polygon chres = hull.getHull();
-
- // Plot the convex hull:
- if(chres != null) {
- SVGPath path = new SVGPath(chres);
- // Approximate area (using bounding box)
- double hullarea = SpatialUtil.volume(chres);
- final double relativeArea = (projarea - hullarea) / projarea;
- final double relativeSize = (double) ids.size() / rel.size();
- opacity = Math.sqrt(relativeSize * relativeArea);
-
- hulls = path.makeElement(svgp);
- addCSSClasses(svgp, cnum, opacity);
- SVGUtil.addCSSClass(hulls, CONVEXHULL + cnum);
- layer.appendChild(hulls);
- }
- }
- }
-
- /**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- private void addCSSClasses(SVGPlot svgp, int clusterID, double opac) {
- ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT);
-
- CSSClass cls = new CSSClass(this, CONVEXHULL + clusterID);
- cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT));
-
- final String color;
- if(clustering.getAllClusters().size() == 1) {
- color = "black";
- }
- else {
- color = colors.getColor(clusterID);
- }
- cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, color);
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, color);
- cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, opac);
-
- svgp.addCSSClassOrLogError(cls);
- }
-
- /**
- * Factory for visualizers to generate an SVG-Element containing the convex
- * hull of a cluster.
- *
- * @author Robert Rödler
- *
- * @apiviz.stereotype factory
- * @apiviz.uses ClusterConvexHullVisualization oneway - - «create»
- *
- * @param <NV> Type of the NumberVector being visualized.
- */
- public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory {
- /**
- * Constructor
- */
- public Factory() {
- super();
- }
-
- @Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new ClusterConvexHullVisualization<NV>(task);
- }
-
- @Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- // Find clusterings we can visualize:
- Collection<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class);
- for(Clustering<?> c : clusterings) {
- Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) {
- final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA - 1);
- task.put(VisualizationTask.META_VISIBLE_DEFAULT, false);
- baseResult.getHierarchy().add(c, task);
- baseResult.getHierarchy().add(p, task);
- }
- }
- }
- }
-}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/ClusteringVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/ClusteringVisualization.java deleted file mode 100644 index 4bef6e11..00000000 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/ClusteringVisualization.java +++ /dev/null @@ -1,154 +0,0 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; - -/* - 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.Collection; -import java.util.Iterator; - -import de.lmu.ifi.dbs.elki.data.Cluster; -import de.lmu.ifi.dbs.elki.data.Clustering; -import de.lmu.ifi.dbs.elki.data.NumberVector; -import de.lmu.ifi.dbs.elki.data.model.Model; -import de.lmu.ifi.dbs.elki.database.datastore.DataStoreEvent; -import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener; -import de.lmu.ifi.dbs.elki.database.ids.DBID; -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.exceptions.ObjectNotFoundException; -import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil; -import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; -import de.lmu.ifi.dbs.elki.visualization.projector.ScatterPlotProjector; -import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; -import de.lmu.ifi.dbs.elki.visualization.style.marker.MarkerLibrary; -import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisFactory; -import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; - -/** - * Visualize a clustering using different markers for different clusters. - * - * @author Erich Schubert - * - * @apiviz.has Clustering oneway - - visualizes - * - * @param <NV> Type of the DatabaseObject being visualized. - */ -// TODO: ensure we have the right database for this -public class ClusteringVisualization<NV extends NumberVector<NV, ?>> extends P2DVisualization<NV> implements DataStoreListener { - /** - * The result we visualize - */ - private Clustering<Model> clustering; - - /** - * Constructor. - * - * @param task Visualization task - */ - public ClusteringVisualization(VisualizationTask task) { - super(task); - this.clustering = task.getResult(); - context.addDataStoreListener(this); - incrementalRedraw(); - } - - @Override - public void destroy() { - super.destroy(); - context.removeDataStoreListener(this); - } - - @Override - public void redraw() { - MarkerLibrary ml = context.getStyleLibrary().markers(); - double marker_size = context.getStyleLibrary().getSize(StyleLibrary.MARKERPLOT); - // draw data - Iterator<Cluster<Model>> ci = clustering.getAllClusters().iterator(); - for(int cnum = 0; cnum < clustering.getAllClusters().size(); cnum++) { - Cluster<?> clus = ci.next(); - for(DBID objId : clus.getIDs()) { - try { - final NV vec = rel.get(objId); - double[] v = proj.fastProjectDataToRenderSpace(vec); - ml.useMarker(svgp, layer, v[0], v[1], cnum, marker_size); - } - catch(ObjectNotFoundException e) { - // ignore. - } - } - } - } - - @Override - public void contentChanged(DataStoreEvent e) { - synchronizedRedraw(); - } - - /** - * Visualization factory - * - * @author Erich Schubert - * - * @apiviz.stereotype factory - * @apiviz.uses ClusteringVisualization oneway - - «create» - * - * @param <NV> Type of the DatabaseObject being visualized. - */ - public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory { - /** - * A short name characterizing this Visualizer. - */ - private static final String NAME = "Cluster Markers"; - - /** - * Constructor, adhering to - * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} - */ - public Factory() { - super(); - } - - @Override - public Visualization makeVisualization(VisualizationTask task) { - return new ClusteringVisualization<NV>(task); - } - - @Override - public void processNewResult(HierarchicalResult baseResult, Result result) { - // Find clusterings we can visualize: - Collection<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class); - for(Clustering<?> c : clusterings) { - if(c.getAllClusters().size() > 0) { - Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) { - final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this); - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA); - baseResult.getHierarchy().add(c, task); - baseResult.getHierarchy().add(p, task); - } - } - } - } - } -}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/DotVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/DotVisualization.java deleted file mode 100644 index 3cfd5a48..00000000 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/vis2d/DotVisualization.java +++ /dev/null @@ -1,150 +0,0 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.vis2d; - -/* - 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.ArrayList; -import java.util.Iterator; - -import org.w3c.dom.Element; - -import de.lmu.ifi.dbs.elki.data.Clustering; -import de.lmu.ifi.dbs.elki.data.NumberVector; -import de.lmu.ifi.dbs.elki.database.datastore.DataStoreEvent; -import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener; -import de.lmu.ifi.dbs.elki.database.ids.DBID; -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.exceptions.ObjectNotFoundException; -import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil; -import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; -import de.lmu.ifi.dbs.elki.visualization.projector.ScatterPlotProjector; -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; - -/** - * Generates a SVG-Element containing "dots" as markers representing the - * Database's objects. - * - * @author Remigius Wojdanowski - * - * @apiviz.has NumberVector - - visualizes - * - * @param <NV> Type of the NumberVector being visualized. - */ -public class DotVisualization<NV extends NumberVector<NV, ?>> extends P2DVisualization<NV> implements DataStoreListener { - /** - * A short name characterizing this Visualizer. - */ - private static final String NAME = "Data Dots"; - - /** - * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc. - */ - public static final String MARKER = "marker"; - - /** - * Constructor. - * - * @param task Task to visualize - */ - public DotVisualization(VisualizationTask task) { - super(task); - context.addDataStoreListener(this); - incrementalRedraw(); - } - - @Override - public void destroy() { - super.destroy(); - context.removeDataStoreListener(this); - } - - @Override - public void redraw() { - // draw data - double dot_size = context.getStyleLibrary().getSize(StyleLibrary.DOTPLOT); - for(DBID id : rel.iterDBIDs()) { - try { - double[] v = proj.fastProjectDataToRenderSpace(rel.get(id)); - Element dot = svgp.svgCircle(v[0], v[1], dot_size); - SVGUtil.addCSSClass(dot, MARKER); - layer.appendChild(dot); - } - catch(ObjectNotFoundException e) { - // ignore. - } - - } - } - - @Override - public void contentChanged(DataStoreEvent e) { - synchronizedRedraw(); - } - - /** - * The visualization factory - * - * @author Erich Schubert - * - * @apiviz.stereotype factory - * @apiviz.uses DotVisualization oneway - - «create» - * - * @param <NV> Type of the NumberVector being visualized. - */ - public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory { - /** - * Constructor, adhering to - * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} - */ - public Factory() { - super(); - } - - @Override - public Visualization makeVisualization(VisualizationTask task) { - return new DotVisualization<NV>(task); - } - - @Override - public void processNewResult(HierarchicalResult baseResult, Result result) { - ArrayList<Clustering<?>> cs = ResultUtil.filterResults(result, Clustering.class); - boolean hasClustering = (cs.size() > 0); - - Iterator<ScatterPlotProjector<?>> ps = ResultUtil.filteredResults(result, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : IterableUtil.fromIterator(ps)) { - final VisualizationTask task = new VisualizationTask(NAME, p.getRelation(), p.getRelation(), this); - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA + 1); - if(hasClustering) { - task.put(VisualizationTask.META_VISIBLE_DEFAULT, false); - } - // baseResult.getHierarchy().add(p.getRelation(), task); - baseResult.getHierarchy().add(p, task); - } - } - } -}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/ClusterEvaluationVisFactory.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/ClusterEvaluationVisFactory.java index 405ba673..05d9038c 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/ClusterEvaluationVisFactory.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/ClusterEvaluationVisFactory.java @@ -4,7 +4,7 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.visunproj; This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -24,12 +24,20 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.visunproj; */ import java.util.ArrayList; +import java.util.List; import org.apache.batik.util.SVGConstants; import org.w3c.dom.Element; -import de.lmu.ifi.dbs.elki.evaluation.paircounting.EvaluatePairCountingFMeasure; -import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector; +import de.lmu.ifi.dbs.elki.data.Clustering; +import de.lmu.ifi.dbs.elki.evaluation.clustering.BCubed; +import de.lmu.ifi.dbs.elki.evaluation.clustering.ClusterContingencyTable; +import de.lmu.ifi.dbs.elki.evaluation.clustering.EditDistance; +import de.lmu.ifi.dbs.elki.evaluation.clustering.Entropy; +import de.lmu.ifi.dbs.elki.evaluation.clustering.EvaluateClustering; +import de.lmu.ifi.dbs.elki.evaluation.clustering.PairCounting; +import de.lmu.ifi.dbs.elki.evaluation.clustering.SetMatchingPurity; +import de.lmu.ifi.dbs.elki.math.MeanVariance; import de.lmu.ifi.dbs.elki.result.HierarchicalResult; import de.lmu.ifi.dbs.elki.result.Result; import de.lmu.ifi.dbs.elki.result.ResultUtil; @@ -37,6 +45,7 @@ import de.lmu.ifi.dbs.elki.utilities.FormatUtil; import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; +import de.lmu.ifi.dbs.elki.visualization.svg.SVGScoreBar; 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.StaticVisualization; @@ -46,10 +55,11 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * Pseudo-Visualizer, that lists the cluster evaluation results found. * * @author Erich Schubert + * @author Sascha Goldhofer * * @apiviz.stereotype factory * @apiviz.uses StaticVisualization oneway - - «create» - * @apiviz.has de.lmu.ifi.dbs.elki.evaluation.paircounting.EvaluatePairCountingFMeasure.ScoreResult oneway - - visualizes + * @apiviz.has EvaluateClustering.ScoreResult oneway - - visualizes */ public class ClusterEvaluationVisFactory extends AbstractVisFactory { /** @@ -58,68 +68,124 @@ public class ClusterEvaluationVisFactory extends AbstractVisFactory { private static final String NAME = "Cluster Evaluation"; /** + * Constant: width of score bars + */ + private static final double BARLENGTH = 5; + + /** + * Constant: height of score bars + */ + private static final double BARHEIGHT = 0.7; + + /** * Constructor. */ public ClusterEvaluationVisFactory() { super(); } - + @Override public void processNewResult(HierarchicalResult baseResult, Result newResult) { - final ArrayList<EvaluatePairCountingFMeasure.ScoreResult> srs = ResultUtil.filterResults(newResult, EvaluatePairCountingFMeasure.ScoreResult.class); - for(EvaluatePairCountingFMeasure.ScoreResult sr : srs) { + final ArrayList<EvaluateClustering.ScoreResult> srs = ResultUtil.filterResults(newResult, EvaluateClustering.ScoreResult.class); + for(EvaluateClustering.ScoreResult sr : srs) { final VisualizationTask task = new VisualizationTask(NAME, sr, null, this); - task.width = 1.0; - task.height = 0.5; + task.width = .5; + task.height = 2.0; task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_STATIC); baseResult.getHierarchy().add(sr, task); } } + private double addBarChart(SVGPlot svgp, Element parent, double ypos, String label, double maxValue, double value) { + SVGScoreBar barchart = new SVGScoreBar(); + barchart.setFill(value, maxValue); + barchart.showValues(FormatUtil.NF4); + barchart.addLabel(label); + parent.appendChild(barchart.build(svgp, 0.0, ypos, BARLENGTH, BARHEIGHT)); + ypos += 1; + return ypos; + } + + private double addHeader(SVGPlot svgp, Element parent, double ypos, String text) { + ypos += .5; + Element object = svgp.svgText(0, ypos + BARHEIGHT * 0.5, text); + object.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, "font-size: 0.6; font-weight: bold"); + parent.appendChild(object); + ypos += 1; + return ypos; + } + @Override public Visualization makeVisualization(VisualizationTask task) { - SVGPlot svgp = task.getPlot(); - Element layer = svgp.svgElement(SVGConstants.SVG_G_TAG); - EvaluatePairCountingFMeasure.ScoreResult sr = task.getResult(); + // TODO: make a utility class to wrap SVGPlot + parent layer + ypos. - // TODO: use CSSClass and StyleLibrary - int i = 0; - { - Element object = svgp.svgText(0, i + 0.7, "Same-cluster object pairs"); - object.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, "font-size: 0.6; font-weight: bold"); - layer.appendChild(object); - i++; - } - { - Element object = svgp.svgText(0, i + 0.7, "F1-Measure, Precision and Recall:"); - object.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, "font-size: 0.6; font-weight: bold"); - layer.appendChild(object); - i++; - } - for(Vector vec : sr) { - StringBuffer buf = new StringBuffer(); - double fmeasure = vec.get(0); - double inboth = vec.get(1); - double infirst = vec.get(2); - double insecond = vec.get(3); - buf.append(FormatUtil.format(fmeasure, FormatUtil.NF6)); - buf.append(" / "); - buf.append(FormatUtil.format(inboth / (inboth + infirst), FormatUtil.NF6)); - buf.append(" / "); - buf.append(FormatUtil.format(inboth / (inboth + insecond), FormatUtil.NF6)); - Element object = svgp.svgText(0, i + 0.7, buf.toString()); - object.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, "font-size: 0.6"); - layer.appendChild(object); - i++; + double ypos = -.5; // Skip space before first header + SVGPlot svgp = task.getPlot(); + Element parent = svgp.svgElement(SVGConstants.SVG_G_TAG); + EvaluateClustering.ScoreResult sr = task.getResult(); + ClusterContingencyTable cont = sr.getContingencyTable(); + + List<Result> parents = task.getContext().getHierarchy().getParents(sr); + + for(Result r : parents) { + if(r instanceof Clustering) { + ypos = addHeader(svgp, parent, ypos, r.getLongName()); + } } + // TODO: use CSSClass and StyleLibrary + + ypos = addHeader(svgp, parent, ypos, "Pair counting measures"); + + PairCounting paircount = cont.getPaircount(); + ypos = addBarChart(svgp, parent, ypos, "Jaccard", 1, paircount.jaccard()); + ypos = addBarChart(svgp, parent, ypos, "F1-Measure", 1, paircount.f1Measure()); + ypos = addBarChart(svgp, parent, ypos, "Precision", 1, paircount.precision()); + ypos = addBarChart(svgp, parent, ypos, "Recall", 1, paircount.recall()); + ypos = addBarChart(svgp, parent, ypos, "Rand", 1, paircount.randIndex()); + ypos = addBarChart(svgp, parent, ypos, "ARI", 1, paircount.adjustedRandIndex()); + ypos = addBarChart(svgp, parent, ypos, "FowlkesMallows", 1, paircount.fowlkesMallows()); + + ypos = addHeader(svgp, parent, ypos, "Entropy based measures"); + + Entropy entropy = cont.getEntropy(); + ypos = addBarChart(svgp, parent, ypos, "NMI Joint", 1, entropy.entropyNMIJoint()); + ypos = addBarChart(svgp, parent, ypos, "NMI Sqrt", 1, entropy.entropyNMISqrt()); + + ypos = addHeader(svgp, parent, ypos, "BCubed-based measures"); + + BCubed bcubed = cont.getBCubed(); + ypos = addBarChart(svgp, parent, ypos, "F1-Measure", 1, bcubed.f1Measure()); + ypos = addBarChart(svgp, parent, ypos, "Recall", 1, bcubed.recall()); + ypos = addBarChart(svgp, parent, ypos, "Precision", 1, bcubed.precision()); + + ypos = addHeader(svgp, parent, ypos, "Set-Matching-based measures"); + + SetMatchingPurity setm = cont.getSetMatching(); + ypos = addBarChart(svgp, parent, ypos, "F1-Measure", 1, setm.f1Measure()); + ypos = addBarChart(svgp, parent, ypos, "Purity", 1, setm.purity()); + ypos = addBarChart(svgp, parent, ypos, "Inverse Purity", 1, setm.inversePurity()); + + ypos = addHeader(svgp, parent, ypos, "Editing-distance measures"); + + EditDistance edit = cont.getEdit(); + ypos = addBarChart(svgp, parent, ypos, "F1-Measure", 1, edit.f1Measure()); + ypos = addBarChart(svgp, parent, ypos, "Precision", 1, edit.editDistanceFirst()); + ypos = addBarChart(svgp, parent, ypos, "Recall", 1, edit.editDistanceSecond()); + + ypos = addHeader(svgp, parent, ypos, "Gini measures"); + + final MeanVariance gini = cont.averageSymmetricGini(); + ypos = addBarChart(svgp, parent, ypos, "Mean +-" + FormatUtil.format(gini.getSampleStddev(), FormatUtil.NF4), 1, gini.getMean()); - int cols = Math.max(10, (int) (i * task.getHeight() / task.getWidth())); - int rows = i; + // scale vis + double cols = 10; // Math.max(10, (int) (i * task.getHeight() / + // task.getWidth())); + double rows = ypos; final double margin = task.getContext().getStyleLibrary().getSize(StyleLibrary.MARGIN); final String transform = SVGUtil.makeMarginTransform(task.getWidth(), task.getHeight(), cols, rows, margin / StyleLibrary.SCALE); - SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); + SVGUtil.setAtt(parent, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); - return new StaticVisualization(task, layer); + return new StaticVisualization(task, parent); } @Override diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/CurveVisFactory.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/CurveVisFactory.java index 4d0e3dc0..e12a392a 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/CurveVisFactory.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/CurveVisFactory.java @@ -4,7 +4,7 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.visunproj; This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -33,6 +33,7 @@ import de.lmu.ifi.dbs.elki.evaluation.roc.ComputeROCCurve; import de.lmu.ifi.dbs.elki.evaluation.roc.ComputeROCCurve.ROCResult; import de.lmu.ifi.dbs.elki.logging.LoggingUtil; import de.lmu.ifi.dbs.elki.math.DoubleMinMax; +import de.lmu.ifi.dbs.elki.math.scales.LinearScale; import de.lmu.ifi.dbs.elki.result.HierarchicalResult; import de.lmu.ifi.dbs.elki.result.IterableResult; import de.lmu.ifi.dbs.elki.result.Result; @@ -45,7 +46,6 @@ import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; import de.lmu.ifi.dbs.elki.visualization.VisualizerContext; import de.lmu.ifi.dbs.elki.visualization.css.CSSClass; import de.lmu.ifi.dbs.elki.visualization.css.CSSClassManager.CSSNamingConflict; -import de.lmu.ifi.dbs.elki.visualization.scales.LinearScale; import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPath; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; @@ -118,8 +118,8 @@ public class CurveVisFactory extends AbstractVisFactory { // add axes try { - SVGSimpleLinearAxis.drawAxis(svgp, layer, scalex, 0, sizey, sizex, sizey, true, true, context.getStyleLibrary()); - SVGSimpleLinearAxis.drawAxis(svgp, layer, scaley, 0, sizey, 0, 0, true, false, context.getStyleLibrary()); + SVGSimpleLinearAxis.drawAxis(svgp, layer, scaley, 0, sizey, 0, 0, SVGSimpleLinearAxis.LabelStyle.LEFTHAND, context.getStyleLibrary()); + SVGSimpleLinearAxis.drawAxis(svgp, layer, scalex, 0, sizey, sizex, sizey, SVGSimpleLinearAxis.LabelStyle.RIGHTHAND, context.getStyleLibrary()); } catch(CSSNamingConflict e) { LoggingUtil.exception(e); diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/HistogramVisFactory.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/HistogramVisFactory.java index 81a77c13..117c3c2c 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/HistogramVisFactory.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/HistogramVisFactory.java @@ -4,7 +4,7 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.visunproj; This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -31,6 +31,7 @@ import org.w3c.dom.Element; import de.lmu.ifi.dbs.elki.data.NumberVector; import de.lmu.ifi.dbs.elki.logging.LoggingUtil; import de.lmu.ifi.dbs.elki.math.DoubleMinMax; +import de.lmu.ifi.dbs.elki.math.scales.LinearScale; import de.lmu.ifi.dbs.elki.result.HierarchicalResult; import de.lmu.ifi.dbs.elki.result.HistogramResult; import de.lmu.ifi.dbs.elki.result.Result; @@ -40,7 +41,6 @@ import de.lmu.ifi.dbs.elki.visualization.VisualizerContext; import de.lmu.ifi.dbs.elki.visualization.colors.ColorLibrary; import de.lmu.ifi.dbs.elki.visualization.css.CSSClass; import de.lmu.ifi.dbs.elki.visualization.css.CSSClassManager.CSSNamingConflict; -import de.lmu.ifi.dbs.elki.visualization.scales.LinearScale; import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPath; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; @@ -141,8 +141,8 @@ public class HistogramVisFactory extends AbstractVisFactory { // add axes try { - SVGSimpleLinearAxis.drawAxis(svgp, layer, xscale, 0, sizey, sizex, sizey, true, true, context.getStyleLibrary()); - SVGSimpleLinearAxis.drawAxis(svgp, layer, yscale, 0, sizey, 0, 0, true, false, context.getStyleLibrary()); + SVGSimpleLinearAxis.drawAxis(svgp, layer, yscale, 0, sizey, 0, 0, SVGSimpleLinearAxis.LabelStyle.LEFTHAND, context.getStyleLibrary()); + SVGSimpleLinearAxis.drawAxis(svgp, layer, xscale, 0, sizey, sizex, sizey, SVGSimpleLinearAxis.LabelStyle.RIGHTHAND, context.getStyleLibrary()); } catch(CSSNamingConflict e) { LoggingUtil.exception(e); diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/KeyVisFactory.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/KeyVisFactory.java deleted file mode 100644 index e3a5dfd4..00000000 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/KeyVisFactory.java +++ /dev/null @@ -1,122 +0,0 @@ -package de.lmu.ifi.dbs.elki.visualization.visualizers.visunproj; - -/* - 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.Collection; -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.Model; -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.visualization.VisualizationTask; -import de.lmu.ifi.dbs.elki.visualization.VisualizerContext; -import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; -import de.lmu.ifi.dbs.elki.visualization.style.marker.MarkerLibrary; -import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; -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.StaticVisualization; -import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; - -/** - * Pseudo-Visualizer, that gives the key for a clustering. - * - * @author Erich Schubert - * - * @apiviz.stereotype factory - * @apiviz.uses StaticVisualization oneway - - «create» - * @apiviz.has Clustering oneway - - visualizes - */ -public class KeyVisFactory extends AbstractVisFactory { - /** - * Name for this visualizer. - */ - private static final String NAME = "Cluster Key"; - - /** - * Constructor, adhering to - * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} - */ - public KeyVisFactory() { - super(); - } - - @Override - public Visualization makeVisualization(VisualizationTask task) { - Clustering<Model> clustering = task.getResult(); - SVGPlot svgp = task.getPlot(); - VisualizerContext context = task.getContext(); - final List<Cluster<Model>> allcs = clustering.getAllClusters(); - int numc = allcs.size(); - - // FIXME: Use CSS and style library. - - Element layer = svgp.svgElement(SVGConstants.SVG_G_TAG); - - MarkerLibrary ml = context.getStyleLibrary().markers(); - - int i = 0; - for(Cluster<Model> c : allcs) { - ml.useMarker(svgp, layer, 0.3, i + 0.5, i, 0.3); - Element label = svgp.svgText(0.7, i + 0.7, c.getNameAutomatic()); - label.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, "font-size: 0.6"); - layer.appendChild(label); - i++; - } - - int cols = Math.max(6, (int) (numc * task.getHeight() / task.getWidth())); - int rows = numc; - final double margin = context.getStyleLibrary().getSize(StyleLibrary.MARGIN); - final String transform = SVGUtil.makeMarginTransform(task.getWidth(), task.getHeight(), cols, rows, margin / StyleLibrary.SCALE); - SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); - - return new StaticVisualization(task, layer); - } - - @Override - public void processNewResult(HierarchicalResult baseResult, Result newResult) { - // Find clusterings we can visualize: - Collection<Clustering<?>> clusterings = ResultUtil.filterResults(newResult, Clustering.class); - for(Clustering<?> c : clusterings) { - if(c.getAllClusters().size() > 0) { - final VisualizationTask task = new VisualizationTask(NAME, c, null, this); - task.width = 1.0; - task.height = 1.0; - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_STATIC); - baseResult.getHierarchy().add(c, task); - } - } - } - - @Override - public boolean allowThumbnails(VisualizationTask task) { - return false; - } -}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/KeyVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/KeyVisualization.java new file mode 100644 index 00000000..15c50a39 --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/KeyVisualization.java @@ -0,0 +1,198 @@ +package de.lmu.ifi.dbs.elki.visualization.visualizers.visunproj; + +/* + This file is part of ELKI: + Environment for Developing KDD-Applications Supported by Index-Structures + + Copyright (C) 2012 + Ludwig-Maximilians-Universität München + Lehr- und Forschungseinheit für Datenbanksysteme + ELKI Development Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +import java.util.Iterator; +import java.util.List; + +import org.apache.batik.util.SVGConstants; +import org.w3c.dom.Element; +import org.w3c.dom.events.Event; +import org.w3c.dom.events.EventListener; +import org.w3c.dom.events.EventTarget; + +import de.lmu.ifi.dbs.elki.data.Cluster; +import de.lmu.ifi.dbs.elki.data.Clustering; +import de.lmu.ifi.dbs.elki.data.model.Model; +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.visualization.VisualizationTask; +import de.lmu.ifi.dbs.elki.visualization.style.ClusterStylingPolicy; +import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; +import de.lmu.ifi.dbs.elki.visualization.style.StylingPolicy; +import de.lmu.ifi.dbs.elki.visualization.style.marker.MarkerLibrary; +import de.lmu.ifi.dbs.elki.visualization.svg.SVGButton; +import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; +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.AbstractVisualization; +import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; + +/** + * Visualizer, displaying the key for a clustering. + * + * @author Erich Schubert + * + * @apiviz.has Clustering oneway - - visualizes + */ +public class KeyVisualization extends AbstractVisualization { + /** + * Name for this visualizer. + */ + private static final String NAME = "Cluster Key"; + + /** + * Clustering to display + */ + private Clustering<Model> clustering; + + /** + * Constructor. + * + * @param task Visualization task + */ + public KeyVisualization(VisualizationTask task) { + super(task); + this.clustering = task.getResult(); + context.addResultListener(this); + } + + @Override + public void destroy() { + context.removeResultListener(this); + super.destroy(); + } + + @Override + public void resultChanged(Result current) { + super.resultChanged(current); + if(current == context.getStyleResult()) { + incrementalRedraw(); + } + } + + @Override + protected void redraw() { + SVGPlot svgp = task.getPlot(); + final List<Cluster<Model>> allcs = clustering.getAllClusters(); + + MarkerLibrary ml = context.getStyleLibrary().markers(); + layer = svgp.svgElement(SVGConstants.SVG_G_TAG); + + // Add a label for the clustering. + { + Element label = svgp.svgText(0.1, 0.7, clustering.getLongName()); + label.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, "font-size: 0.4"); + layer.appendChild(label); + } + + // TODO: multi-column layout! + int i = 0; + for(Cluster<Model> c : allcs) { + ml.useMarker(svgp, layer, 0.3, i + 1.5, i, 0.3); + Element label = svgp.svgText(0.7, i + 1.7, c.getNameAutomatic()); + label.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, "font-size: 0.6"); + layer.appendChild(label); + i++; + } + + // Add a button to set style policy + { + StylingPolicy sp = context.getStyleResult().getStylingPolicy(); + if(sp instanceof ClusterStylingPolicy && ((ClusterStylingPolicy) sp).getClustering() == clustering) { + // Don't show the button when active. May confuse people more than the disappearing button + + // SVGButton button = new SVGButton(.1, i + 1.1, 3.8, .7, .2); + // button.setTitle("Active style", "darkgray"); + // layer.appendChild(button.render(svgp)); + } + else { + SVGButton button = new SVGButton(.1, i + 1.1, 3.8, .7, .2); + button.setTitle("Set style", "black"); + Element elem = button.render(svgp); + // Attach listener + EventTarget etr = (EventTarget) elem; + etr.addEventListener(SVGConstants.SVG_CLICK_EVENT_TYPE, new EventListener() { + @Override + public void handleEvent(Event evt) { + setStylePolicy(); + } + }, false); + layer.appendChild(elem); + } + } + + int rows = i + 2; + int cols = Math.max(6, (int) (rows * task.getHeight() / task.getWidth())); + final double margin = context.getStyleLibrary().getSize(StyleLibrary.MARGIN); + final String transform = SVGUtil.makeMarginTransform(task.getWidth(), task.getHeight(), cols, rows, margin / StyleLibrary.SCALE); + SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); + } + + /** + * Trigger a style change. + */ + protected void setStylePolicy() { + context.getStyleResult().setStylingPolicy(new ClusterStylingPolicy(clustering, context.getStyleLibrary())); + context.getHierarchy().resultChanged(context.getStyleResult()); + } + + /** + * Visualization factory + * + * @author Erich Schubert + * + * @apiviz.stereotype factory + * @apiviz.uses KeyVisualization oneway - - «create» + */ + public static class Factory extends AbstractVisFactory { + @Override + public void processNewResult(HierarchicalResult baseResult, Result newResult) { + // Find clusterings we can visualize: + Iterator<Clustering<?>> clusterings = ResultUtil.filteredResults(newResult, Clustering.class); + while(clusterings.hasNext()) { + Clustering<?> c = clusterings.next(); + if(c.getAllClusters().size() > 0) { + final VisualizationTask task = new VisualizationTask(NAME, c, null, this); + task.width = 1.0; + task.height = 1.0; + task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_STATIC); + task.put(VisualizationTask.META_NODETAIL, true); + baseResult.getHierarchy().add(c, task); + } + } + } + + @Override + public Visualization makeVisualization(VisualizationTask task) { + return new KeyVisualization(task); + } + + @Override + public boolean allowThumbnails(VisualizationTask task) { + return false; + } + } +}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/LabelVisFactory.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/LabelVisFactory.java index 6145b7a8..b6eb4549 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/LabelVisFactory.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/LabelVisFactory.java @@ -4,7 +4,7 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.visunproj; This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/PixmapVisualizer.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/PixmapVisualizer.java index 825d64b6..9269f404 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/PixmapVisualizer.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/PixmapVisualizer.java @@ -4,7 +4,7 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.visunproj; This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/SettingsVisFactory.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/SettingsVisFactory.java index 5d33fede..5bbc698c 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/SettingsVisFactory.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/SettingsVisFactory.java @@ -4,7 +4,7 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.visunproj; This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/SimilarityMatrixVisualizer.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/SimilarityMatrixVisualizer.java index 17424519..c53a722a 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/SimilarityMatrixVisualizer.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/SimilarityMatrixVisualizer.java @@ -4,7 +4,7 @@ package de.lmu.ifi.dbs.elki.visualization.visualizers.visunproj; This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/package-info.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/package-info.java index 252bffc5..30042923 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/package-info.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/package-info.java @@ -6,7 +6,7 @@ This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures -Copyright (C) 2011 +Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team |