summaryrefslogtreecommitdiff
path: root/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot
diff options
context:
space:
mode:
Diffstat (limited to 'src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot')
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractScatterplotVisualization.java6
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractTooltipVisualization.java30
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AxisVisualization.java175
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/MarkerVisualization.java167
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/PolygonVisualization.java170
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ReferencePointsVisualization.java127
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ToolBox2DVisualization.java393
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipScoreVisualization.java281
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipStringVisualization.java243
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterHullVisualization.java306
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterMeanVisualization.java345
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterOrderVisualization.java126
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/EMClusterVisualization.java659
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/VoronoiVisualization.java358
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/density/DensityEstimationOverlay.java298
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeMBRVisualization.java258
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeSphereVisualization.java333
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/outlier/BubbleVisualization.java374
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/outlier/COPVectorVisualization.java190
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/DistanceFunctionVisualization.java370
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/MoveObjectsToolVisualization.java270
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionConvexHullVisualization.java161
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionCubeVisualization.java293
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionDotVisualization.java143
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolCubeVisualization.java373
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolDotVisualization.java330
26 files changed, 3656 insertions, 3123 deletions
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractScatterplotVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractScatterplotVisualization.java
index cce9ea5d..8cedcf0b 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractScatterplotVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractScatterplotVisualization.java
@@ -56,7 +56,7 @@ public abstract class AbstractScatterplotVisualization extends AbstractVisualiza
/**
* The representation we visualize
*/
- final protected Relation<? extends NumberVector<?, ?>> rel;
+ final protected Relation<? extends NumberVector<?>> rel;
/**
* The DBID sample
@@ -73,7 +73,7 @@ public abstract class AbstractScatterplotVisualization extends AbstractVisualiza
this.proj = task.getProj();
this.rel = task.getRelation();
this.sample = ResultUtil.getSamplingResult(rel);
- final double margin = context.getStyleLibrary().getSize(StyleLibrary.MARGIN);
+ final double margin = context.getStyleResult().getStyleLibrary().getSize(StyleLibrary.MARGIN);
this.layer = setupCanvas(svgp, proj, margin, task.getWidth(), task.getHeight());
}
@@ -91,7 +91,7 @@ public abstract class AbstractScatterplotVisualization extends AbstractVisualiza
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) + ")";
+ String transform = SVGUtil.makeMarginTransform(width, height, sizex, sizey, margin) + " translate(" + SVGUtil.fmt(sizex * .5) + " " + SVGUtil.fmt(sizey * .5) + ")";
final Element layer = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG);
SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform);
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractTooltipVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractTooltipVisualization.java
index 8ce6ad67..107af095 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractTooltipVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractTooltipVisualization.java
@@ -31,7 +31,8 @@ import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;
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.DBIDIter;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDRef;
import de.lmu.ifi.dbs.elki.logging.LoggingUtil;
import de.lmu.ifi.dbs.elki.result.Result;
import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
@@ -87,18 +88,12 @@ public abstract class AbstractTooltipVisualization extends AbstractScatterplotVi
}
@Override
- public void destroy() {
- super.destroy();
- context.removeDataStoreListener(this);
- }
-
- @Override
public void redraw() {
setupCSS(svgp);
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ double dotsize = style.getLineWidth(StyleLibrary.PLOT);
- double dotsize = context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT);
-
- for(DBID id : sample.getSample()) {
+ for(DBIDIter id = sample.getSample().iter(); id.valid(); id.advance()) {
double[] v = proj.fastProjectDataToRenderSpace(rel.get(id));
Element tooltip = makeTooltip(id, v[0], v[1], dotsize);
SVGUtil.addCSSClass(tooltip, TOOLTIP_HIDDEN);
@@ -118,7 +113,16 @@ public abstract class AbstractTooltipVisualization extends AbstractScatterplotVi
}
}
- abstract protected Element makeTooltip(DBID id, double x, double y, double dotsize);
+ /**
+ * Make a tooltip Element for this id.
+ *
+ * @param id Id to make a tooltip for
+ * @param x X position
+ * @param y Y position
+ * @param dotsize Size of a dot
+ * @return Element
+ */
+ protected abstract Element makeTooltip(DBIDRef id, double x, double y, double dotsize);
/**
* Handle the hover events.
@@ -129,7 +133,7 @@ public abstract class AbstractTooltipVisualization extends AbstractScatterplotVi
if(evt.getTarget() instanceof Element) {
Element e = (Element) evt.getTarget();
Node next = e.getNextSibling();
- if(next != null && next instanceof Element) {
+ if(next instanceof Element) {
toggleTooltip((Element) next, evt.getType());
}
else {
@@ -174,7 +178,7 @@ public abstract class AbstractTooltipVisualization extends AbstractScatterplotVi
*
* @param svgp the SVGPlot to register the Tooltip-CSS-Class.
*/
- abstract protected void setupCSS(SVGPlot svgp);
+ protected abstract void setupCSS(SVGPlot svgp);
@Override
public void resultChanged(Result current) {
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AxisVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AxisVisualization.java
index 4173084d..f08c4b2d 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AxisVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AxisVisualization.java
@@ -28,10 +28,10 @@ import java.util.Collection;
import org.apache.batik.util.SVGConstants;
import org.w3c.dom.Element;
+import de.lmu.ifi.dbs.elki.database.relation.RelationUtil;
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;
@@ -47,113 +47,116 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;
* Generates a SVG-Element containing axes, including labeling.
*
* @author Remigius Wojdanowski
+ * @author Erich Schubert
*
- * @apiviz.uses SVGSimpleLinearAxis
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class AxisVisualization extends AbstractScatterplotVisualization {
+public class AxisVisualization extends AbstractVisFactory {
+ /**
+ * A short name characterizing this Visualizer.
+ */
+ private static final String NAME = "Axes";
+
/**
* Constructor.
- *
- * @param task VisualizationTask
*/
- public AxisVisualization(VisualizationTask task) {
- super(task);
- incrementalRedraw();
+ public AxisVisualization() {
+ super();
}
@Override
- protected void redraw() {
- int dim = DatabaseUtil.dimensionality(rel);
-
- // origin
- 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.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.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++) {
- double[] v = new double[dim];
- v[d] = 1;
- // projected endpoint of axis
- 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)) {
- righthand = true;
- }
- // System.err.println(ax.get(0) + " "+ ax.get(1)+
- // " "+(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], 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;
- Element label = svgp.svgText(ax[0] + offx, ax[1] + offy, DatabaseUtil.getColumnLabel(rel, d + 1));
- SVGUtil.setAtt(label, SVGConstants.SVG_STYLE_ATTRIBUTE, alcls.inlineCSS());
- SVGUtil.setAtt(label, SVGConstants.SVG_TEXT_ANCHOR_ATTRIBUTE, righthand ? SVGConstants.SVG_START_VALUE : SVGConstants.SVG_END_VALUE);
- layer.appendChild(label);
- }
- catch(CSSNamingConflict e) {
- throw new RuntimeException("Conflict in CSS naming for axes.", e);
- }
- }
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(result, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, p, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_BACKGROUND;
+ baseResult.getHierarchy().add(p, task);
}
}
+ @Override
+ public boolean allowThumbnails(VisualizationTask task) {
+ // Don't use thumbnails
+ return false;
+ }
+
/**
- * Factory for axis visualizations
+ * Instance.
*
* @author Erich Schubert
+ * @author Remigius Wojdanowski
+ *
+ * @apiviz.uses SVGSimpleLinearAxis
*
- * @apiviz.stereotype factory
- * @apiviz.uses AxisVisualization oneway - - «create»
*/
- public static class Factory extends AbstractVisFactory {
+ public class Instance extends AbstractScatterplotVisualization {
/**
- * A short name characterizing this Visualizer.
+ * Constructor.
+ *
+ * @param task VisualizationTask
*/
- private static final String NAME = "Axes";
-
- /**
- * Constructor, adhering to
- * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
- */
- public Factory() {
- super();
- }
-
- @Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new AxisVisualization(task);
+ public Instance(VisualizationTask task) {
+ super(task);
+ incrementalRedraw();
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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);
+ protected void redraw() {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ final int dim = RelationUtil.dimensionality(rel);
+
+ // origin
+ 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.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.1 * style.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, style.getTextColor(StyleLibrary.AXIS_LABEL));
+ alcls.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, style.getFontFamily(StyleLibrary.AXIS_LABEL));
+
+ // draw axes
+ for(int d = 0; d < dim; d++) {
+ double[] v = new double[dim];
+ v[d] = 1;
+ // projected endpoint of axis
+ 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)) {
+ righthand = true;
+ }
+ // System.err.println(ax.get(0) + " "+ ax.get(1)+
+ // " "+(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], righthand ? SVGSimpleLinearAxis.LabelStyle.RIGHTHAND : SVGSimpleLinearAxis.LabelStyle.LEFTHAND, style);
+ // 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;
+ Element label = svgp.svgText(ax[0] + offx, ax[1] + offy, RelationUtil.getColumnLabel(rel, d));
+ SVGUtil.setAtt(label, SVGConstants.SVG_STYLE_ATTRIBUTE, alcls.inlineCSS());
+ SVGUtil.setAtt(label, SVGConstants.SVG_TEXT_ANCHOR_ATTRIBUTE, righthand ? SVGConstants.SVG_START_VALUE : SVGConstants.SVG_END_VALUE);
+ layer.appendChild(label);
+ }
+ catch(CSSNamingConflict e) {
+ throw new RuntimeException("Conflict in CSS naming for axes.", e);
+ }
+ }
}
- }
-
- @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/scatterplot/MarkerVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/MarkerVisualization.java
index c98ca83b..f2140502 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/MarkerVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/MarkerVisualization.java
@@ -54,121 +54,122 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualizati
*
* @author Erich Schubert
*
- * @apiviz.uses StyleResult
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class MarkerVisualization extends AbstractScatterplotVisualization implements DataStoreListener {
+public class MarkerVisualization extends AbstractVisFactory {
/**
- * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc.
+ * A short name characterizing this Visualizer.
*/
- public static final String DOTMARKER = "dot";
+ private static final String NAME = "Markers";
/**
- * The result we visualize
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
*/
- 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();
+ public MarkerVisualization() {
+ super();
+ thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_STYLE;
}
@Override
- public void destroy() {
- super.destroy();
- context.removeDataStoreListener(this);
- context.removeResultListener(this);
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
@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(DBIDIter iter = cspol.iterateClass(cnum); iter.valid(); iter.advance()) {
- if(!sample.getSample().contains(iter)) {
- continue; // TODO: can we test more efficiently than this?
- }
- try {
- final NumberVector<?, ?> vec = rel.get(iter);
- 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(DBIDIter iter = sample.getSample().iter(); iter.valid(); iter.advance()) {
- try {
- double[] v = proj.fastProjectDataToRenderSpace(rel.get(iter));
- Element dot = svgp.svgCircle(v[0], v[1], marker_size);
- SVGUtil.addCSSClass(dot, DOTMARKER);
- int col = spol.getColorForDBID(iter);
- SVGUtil.setAtt(dot, SVGConstants.SVG_STYLE_ATTRIBUTE, FILL + SVGUtil.colorToString(col));
- layer.appendChild(dot);
- }
- catch(ObjectNotFoundException e) {
- // ignore.
- }
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ // Find a style result to visualize:
+ Collection<StyleResult> styleres = ResultUtil.filterResults(result, StyleResult.class);
+ for(StyleResult c : styleres) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA;
+ baseResult.getHierarchy().add(c, task);
+ baseResult.getHierarchy().add(p, task);
}
}
}
/**
- * Visualization factory
+ * Instance.
*
* @author Erich Schubert
*
- * @apiviz.stereotype factory
- * @apiviz.uses MarkerVisualization oneway - - «create»
+ * @apiviz.uses StyleResult
*/
- public static class Factory extends AbstractVisFactory {
+ public class Instance extends AbstractScatterplotVisualization implements DataStoreListener {
/**
- * A short name characterizing this Visualizer.
+ * Generic tag to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
*/
- private static final String NAME = "Markers";
+ public static final String DOTMARKER = "dot";
/**
- * Constructor, adhering to
- * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ * The result we visualize
*/
- public Factory() {
- super();
- thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_STYLE;
+ private StyleResult style;
+
+ /**
+ * Constructor.
+ *
+ * @param task Visualization task
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.style = task.getResult();
+ context.addDataStoreListener(this);
+ context.addResultListener(this);
+ incrementalRedraw();
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new MarkerVisualization(task);
+ public void destroy() {
+ super.destroy();
+ context.removeDataStoreListener(this);
+ context.removeResultListener(this);
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- // Find a style result to visualize:
- Collection<StyleResult> styleres = ResultUtil.filterResults(result, StyleResult.class);
- for(StyleResult c : styleres) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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);
+ public void redraw() {
+ final MarkerLibrary ml = style.getStyleLibrary().markers();
+ final double marker_size = style.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(DBIDIter iter = cspol.iterateClass(cnum); iter.valid(); iter.advance()) {
+ if(!sample.getSample().contains(iter)) {
+ continue; // TODO: can we test more efficiently than this?
+ }
+ try {
+ final NumberVector<?> vec = rel.get(iter);
+ 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(DBIDIter iter = sample.getSample().iter(); iter.valid(); iter.advance()) {
+ try {
+ double[] v = proj.fastProjectDataToRenderSpace(rel.get(iter));
+ Element dot = svgp.svgCircle(v[0], v[1], marker_size);
+ SVGUtil.addCSSClass(dot, DOTMARKER);
+ int col = spol.getColorForDBID(iter);
+ SVGUtil.setAtt(dot, SVGConstants.SVG_STYLE_ATTRIBUTE, FILL + SVGUtil.colorToString(col));
+ layer.appendChild(dot);
+ }
+ catch(ObjectNotFoundException e) {
+ // ignore.
+ }
}
}
}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/PolygonVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/PolygonVisualization.java
index 5b7c13b4..0fc99bbc 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/PolygonVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/PolygonVisualization.java
@@ -34,15 +34,14 @@ import de.lmu.ifi.dbs.elki.data.type.TypeUtil;
import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener;
import de.lmu.ifi.dbs.elki.database.ids.DBIDIter;
import de.lmu.ifi.dbs.elki.database.relation.Relation;
+import de.lmu.ifi.dbs.elki.database.relation.RelationUtil;
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.exceptions.ObjectNotFoundException;
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;
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;
@@ -55,126 +54,123 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;
*
* @author Erich Schubert
*
- * @apiviz.has PolygonsObject - - visualizes
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class PolygonVisualization extends AbstractScatterplotVisualization implements DataStoreListener {
+public class PolygonVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
private static final String NAME = "Polygons";
/**
- * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc.
+ * Constructor
*/
- public static final String POLYS = "polys";
-
- /**
- * The current projection
- */
- final protected Projection2D proj;
-
- /**
- * The representation we visualize
- */
- final protected Relation<PolygonsObject> rep;
-
- /**
- * Constructor.
- *
- * @param task Task to visualize
- */
- public PolygonVisualization(VisualizationTask task) {
- super(task);
- this.proj = task.getProj();
- this.rep = task.getResult(); // Note: relation was used for projection
- context.addDataStoreListener(this);
- incrementalRedraw();
+ public PolygonVisualization() {
+ super();
}
@Override
- public void destroy() {
- super.destroy();
- context.removeDataStoreListener(this);
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
@Override
- public void redraw() {
- CSSClass css = new CSSClass(svgp, POLYS);
- // TODO: separate fill and line colors?
- css.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.POLYGONS));
- css.setStatement(SVGConstants.CSS_STROKE_PROPERTY, context.getStyleLibrary().getColor(StyleLibrary.POLYGONS));
- css.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
- svgp.addCSSClassOrLogError(css);
- svgp.updateStyleElement();
-
- // draw data
- for(DBIDIter iditer = rep.iterDBIDs(); iditer.valid(); iditer.advance()) {
- try {
- PolygonsObject poly = rep.get(iditer);
- if(poly == null) {
- continue;
- }
- SVGPath path = new SVGPath();
- for(Polygon ppoly : poly.getPolygons()) {
- Vector first = ppoly.get(0);
- 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.getArrayRef());
- path.drawTo(p[0], p[1]);
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<Relation<?>> results = ResultUtil.filterResults(result, Relation.class);
+ for(Relation<?> rel : results) {
+ if(TypeUtil.POLYGON_TYPE.isAssignableFromType(rel.getDataTypeInformation())) {
+ // Assume that a 2d projector is using the same coordinates as the
+ // polygons.
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ if(RelationUtil.dimensionality(p.getRelation()) == 2) {
+ final VisualizationTask task = new VisualizationTask(NAME, rel, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA - 10;
+ baseResult.getHierarchy().add(rel, task);
+ baseResult.getHierarchy().add(p, task);
}
- // close path.
- path.drawTo(f[0], f[1]);
}
- Element e = path.makeElement(svgp);
- SVGUtil.addCSSClass(e, POLYS);
- layer.appendChild(e);
- }
- catch(ObjectNotFoundException e) {
- // ignore.
}
}
}
/**
- * The visualization factory
+ * Instance
*
* @author Erich Schubert
*
- * @apiviz.stereotype factory
- * @apiviz.uses PolygonVisualization oneway - - «create»
+ * @apiviz.has PolygonsObject - - visualizes
*/
- public static class Factory extends AbstractVisFactory {
+ public class Instance extends AbstractScatterplotVisualization implements DataStoreListener {
+ /**
+ * Generic tag to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
+ */
+ public static final String POLYS = "polys";
+
/**
- * Constructor
+ * The representation we visualize
*/
- public Factory() {
- super();
+ final protected Relation<PolygonsObject> rep;
+
+ /**
+ * Constructor.
+ *
+ * @param task Task to visualize
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.rep = task.getResult(); // Note: relation was used for projection
+ context.addDataStoreListener(this);
+ incrementalRedraw();
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new PolygonVisualization(task);
+ public void destroy() {
+ super.destroy();
+ context.removeDataStoreListener(this);
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<Relation<?>> results = ResultUtil.filterResults(result, Relation.class);
- for(Relation<?> rel : results) {
- if(TypeUtil.POLYGON_TYPE.isAssignableFromType(rel.getDataTypeInformation())) {
- // Assume that a 2d projector is using the same coordinates as the polygons.
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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);
- baseResult.getHierarchy().add(rel, task);
- baseResult.getHierarchy().add(p, task);
+ public void redraw() {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ CSSClass css = new CSSClass(svgp, POLYS);
+ // TODO: separate fill and line colors?
+ css.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.POLYGONS));
+ css.setStatement(SVGConstants.CSS_STROKE_PROPERTY, style.getColor(StyleLibrary.POLYGONS));
+ css.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ svgp.addCSSClassOrLogError(css);
+ svgp.updateStyleElement();
+
+ // draw data
+ for(DBIDIter iditer = rep.iterDBIDs(); iditer.valid(); iditer.advance()) {
+ try {
+ PolygonsObject poly = rep.get(iditer);
+ if(poly == null) {
+ continue;
+ }
+ SVGPath path = new SVGPath();
+ for(Polygon ppoly : poly.getPolygons()) {
+ Vector first = ppoly.get(0);
+ 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.getArrayRef());
+ path.drawTo(p[0], p[1]);
}
+ // close path.
+ path.drawTo(f[0], f[1]);
}
+ Element e = path.makeElement(svgp);
+ SVGUtil.addCSSClass(e, POLYS);
+ layer.appendChild(e);
+ }
+ catch(ObjectNotFoundException e) {
+ // ignore.
}
}
}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ReferencePointsVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ReferencePointsVisualization.java
index 056b788b..94c1c8d2 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ReferencePointsVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ReferencePointsVisualization.java
@@ -46,98 +46,103 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;
/**
* The actual visualization instance, for a single projection
*
+ * @author Remigius Wojdanowski
* @author Erich Schubert
*
- * @apiviz.has ReferencePointsResult oneway - - visualizes
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-// TODO: add a result listener for the reference points.
-public class ReferencePointsVisualization extends AbstractScatterplotVisualization {
- /**
- * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc.
- */
- public static final String REFPOINT = "refpoint";
-
+public class ReferencePointsVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
private static final String NAME = "Reference Points";
/**
- * Serves reference points.
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
*/
- protected ReferencePointsResult<? extends NumberVector<?, ?>> result;
-
- /**
- * Constructor.
- *
- * @param task Visualization task
- */
- public ReferencePointsVisualization(VisualizationTask task) {
- super(task);
- this.result = task.getResult();
- incrementalRedraw();
+ public ReferencePointsVisualization() {
+ super();
}
@Override
- public void redraw() {
- setupCSS(svgp);
- Iterator<? extends NumberVector<?, ?>> iter = result.iterator();
-
- final double dotsize = context.getStyleLibrary().getSize(StyleLibrary.REFERENCE_POINTS);
- while(iter.hasNext()) {
- NumberVector<?, ?> v = iter.next();
- double[] projected = proj.fastProjectDataToRenderSpace(v);
- Element dot = svgp.svgCircle(projected[0], projected[1], dotsize);
- SVGUtil.addCSSClass(dot, REFPOINT);
- layer.appendChild(dot);
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<ReferencePointsResult<?>> rps = ResultUtil.filterResults(result, ReferencePointsResult.class);
+ for(ReferencePointsResult<?> rp : rps) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, rp, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA;
+ baseResult.getHierarchy().add(rp, task);
+ baseResult.getHierarchy().add(p, task);
+ }
}
}
- /**
- * Registers the Reference-Point-CSS-Class at a SVGPlot.
- *
- * @param svgp the SVGPlot to register the -CSS-Class.
- */
- private void setupCSS(SVGPlot svgp) {
- CSSClass refpoint = new CSSClass(svgp, REFPOINT);
- refpoint.setStatement(SVGConstants.CSS_FILL_PROPERTY, context.getStyleLibrary().getColor(StyleLibrary.REFERENCE_POINTS));
- svgp.addCSSClassOrLogError(refpoint);
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
/**
- * Generates a SVG-Element visualizing reference points.
+ * Instance.
*
* @author Remigius Wojdanowski
+ * @author Erich Schubert
*
- * @apiviz.stereotype factory
- * @apiviz.uses ReferencePointsVisualization oneway - - «create»
+ * @apiviz.has ReferencePointsResult oneway - - visualizes
*/
- public static class Factory extends AbstractVisFactory {
+ // TODO: add a result listener for the reference points.
+ public class Instance extends AbstractScatterplotVisualization {
+ /**
+ * Generic tag to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
+ */
+ public static final String REFPOINT = "refpoint";
+
+ /**
+ * Serves reference points.
+ */
+ protected ReferencePointsResult<? extends NumberVector<?>> result;
+
/**
- * Constructor, adhering to
- * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ * Constructor.
+ *
+ * @param task Visualization task
*/
- public Factory() {
- super();
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.result = task.getResult();
+ incrementalRedraw();
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<ReferencePointsResult<?>> rps = ResultUtil.filterResults(result, ReferencePointsResult.class);
- for(ReferencePointsResult<?> rp : rps) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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);
- baseResult.getHierarchy().add(p, task);
- }
+ public void redraw() {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ setupCSS(svgp);
+ Iterator<? extends NumberVector<?>> iter = result.iterator();
+
+ final double dotsize = style.getSize(StyleLibrary.REFERENCE_POINTS);
+ while(iter.hasNext()) {
+ NumberVector<?> v = iter.next();
+ double[] projected = proj.fastProjectDataToRenderSpace(v);
+ Element dot = svgp.svgCircle(projected[0], projected[1], dotsize);
+ SVGUtil.addCSSClass(dot, REFPOINT);
+ layer.appendChild(dot);
}
}
- @Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new ReferencePointsVisualization(task);
+ /**
+ * Registers the Reference-Point-CSS-Class at a SVGPlot.
+ *
+ * @param svgp the SVGPlot to register the -CSS-Class.
+ */
+ private void setupCSS(SVGPlot svgp) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ CSSClass refpoint = new CSSClass(svgp, REFPOINT);
+ refpoint.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.REFERENCE_POINTS));
+ svgp.addCSSClassOrLogError(refpoint);
}
}
} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ToolBox2DVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ToolBox2DVisualization.java
index d0c05cc7..352e79de 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ToolBox2DVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ToolBox2DVisualization.java
@@ -54,9 +54,10 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.VisualizerUtil;
*
* @author Heidi Kolb
*
- * @apiviz.has VisualizationTask oneway - - visualizes
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class ToolBox2DVisualization extends AbstractScatterplotVisualization {
+public class ToolBox2DVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
@@ -65,241 +66,241 @@ public class ToolBox2DVisualization extends AbstractScatterplotVisualization {
/**
* The logger for this class.
*/
- private static final Logging logger = Logging.getLogger(Factory.class);
+ private static final Logging LOG = Logging.getLogger(ToolBox2DVisualization.class);
/**
- * CSS class for a tool button
+ * Constructor
*/
- public static final String CSS_TOOL_BUTTON = "toolButton";
-
- /**
- * CSS class for the tool button caption
- */
- public static final String CSS_TOOL_CAPTION = "toolCaption";
-
- /**
- * CSS class for a tool button
- */
- public static final String CSS_TOOL_BUTTON_SELECTED = "toolButtonSelected";
-
- /**
- * The container
- */
- private Element container;
-
- /**
- * Constructor.
- *
- * @param task Task
- */
- public ToolBox2DVisualization(VisualizationTask task) {
- super(task);
- // TODO: which result do we best attach to?
- context.addResultListener(this);
- incrementalRedraw();
+ public ToolBox2DVisualization() {
+ super();
}
@Override
- protected void redraw() {
- addCSSClasses(svgp);
- container = svgp.svgElement(SVGConstants.SVG_G_TAG);
- buildToolBox();
- layer.appendChild(container);
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
- /**
- * Deletes the children of the container
- *
- * @param container Element to delete children
- */
- private void deleteChildren(Element container) {
- while(container.hasChildNodes()) {
- container.removeChild(container.getLastChild());
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(result, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, p, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_INTERACTIVE;
+ task.thumbnail = false;
+ task.noexport = true;
+ task.noembed = true;
+ baseResult.getHierarchy().add(p, task);
}
}
/**
- * Build the toolbox
+ * Instance.
+ *
+ * @author Heidi Kolb
+ *
+ * @apiviz.has VisualizationTask oneway - - visualizes
*/
- private void buildToolBox() {
- double scale = StyleLibrary.SCALE;
- deleteChildren(container);
-
- ArrayList<VisualizationTask> vis = new ArrayList<VisualizationTask>();
- Collection<VisualizationTask> visualizers = ResultUtil.filterResults(task.getResult(), VisualizationTask.class);
- for(VisualizationTask task : visualizers) {
- if(VisualizerUtil.isTool(task) && !vis.contains(task)) {
- vis.add(task);
- }
- }
+ public class Instance extends AbstractScatterplotVisualization {
+ /**
+ * CSS class for a tool button
+ */
+ public static final String CSS_TOOL_BUTTON = "toolButton";
- // calculate the position of the first tool
- CanvasSize viewport = proj.estimateViewport();
- double x = viewport.getMinX() - 0.17 * scale;
- double width = 0.07 * scale;
- double height = 0.06 * scale;
- 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");
- }
+ /**
+ * CSS class for the tool button caption
+ */
+ public static final String CSS_TOOL_CAPTION = "toolCaption";
- // add tools
- Element[] toolTags = new Element[vis.size()];
- for(int i = 0; i < vis.size(); i++) {
- VisualizationTask v = vis.get(i);
- toolTags[i] = svgp.svgRect(x, y, width, height);
- String name = v.getLongName();
- // Split
- List<String> lines = FormatUtil.splitAtLastBlank(name, 8);
- // Generate label objects.
- for(int l = 0; l < lines.size(); l++) {
- Element selectRangeText = svgp.svgText(x + 0.01 * scale, y + (0.02 + 0.05 * l / lines.size()) * scale, lines.get(l));
- SVGUtil.setAtt(selectRangeText, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_TOOL_CAPTION);
- container.appendChild(selectRangeText);
- }
+ /**
+ * CSS class for a tool button
+ */
+ public static final String CSS_TOOL_BUTTON_SELECTED = "toolButtonSelected";
- if(VisualizerUtil.isVisible(v)) {
- SVGUtil.addCSSClass(toolTags[i], CSS_TOOL_BUTTON_SELECTED);
- }
- else {
- SVGUtil.addCSSClass(toolTags[i], CSS_TOOL_BUTTON);
- }
- addEventListener(toolTags[i], v);
+ /**
+ * The container
+ */
+ private Element container;
- container.appendChild(toolTags[i]);
- y = y + 0.1 * scale;
+ /**
+ * Constructor.
+ *
+ * @param task Task
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ // TODO: which result do we best attach to?
+ context.addResultListener(this);
+ incrementalRedraw();
}
- }
- /**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- private void addCSSClasses(SVGPlot svgp) {
- // Class for the not selected tool
- if(!svgp.getCSSClassManager().contains(CSS_TOOL_BUTTON)) {
- final CSSClass modeCls = new CSSClass(this, CSS_TOOL_BUTTON);
- modeCls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, 0.4);
- modeCls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_GREEN_VALUE);
- modeCls.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE);
-
- svgp.addCSSClassOrLogError(modeCls);
- }
- // Class for the selected tool
- if(!svgp.getCSSClassManager().contains(CSS_TOOL_BUTTON_SELECTED)) {
- final CSSClass modeCls = new CSSClass(this, CSS_TOOL_BUTTON_SELECTED);
- modeCls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, 0.4);
- modeCls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_BLUE_VALUE);
- modeCls.setStatement(SVGConstants.CSS_STROKE_OPACITY_PROPERTY, 0.4);
- modeCls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_BLUE_VALUE);
- modeCls.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE);
-
- svgp.addCSSClassOrLogError(modeCls);
+ @Override
+ protected void redraw() {
+ addCSSClasses(svgp);
+ container = svgp.svgElement(SVGConstants.SVG_G_TAG);
+ buildToolBox();
+ layer.appendChild(container);
}
- // Class for the text of the tools
- if(!svgp.getCSSClassManager().contains(CSS_TOOL_CAPTION)) {
- final CSSClass label = new CSSClass(svgp, CSS_TOOL_CAPTION);
- label.setStatement(SVGConstants.CSS_FILL_PROPERTY, context.getStyleLibrary().getTextColor(StyleLibrary.AXIS_LABEL));
- label.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, context.getStyleLibrary().getFontFamily(StyleLibrary.AXIS_LABEL));
- label.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, context.getStyleLibrary().getTextSize(StyleLibrary.AXIS_LABEL) * .8);
-
- svgp.addCSSClassOrLogError(label);
+
+ /**
+ * Deletes the children of the container
+ *
+ * @param container Element to delete children
+ */
+ private void deleteChildren(Element container) {
+ while(container.hasChildNodes()) {
+ container.removeChild(container.getLastChild());
+ }
}
- }
- /**
- * Add an event listener to the Element
- *
- * @param tag Element to add the listener
- * @param tool Tool represented by the Element
- */
- private void addEventListener(final Element tag, final VisualizationTask tool) {
- EventTarget targ = (EventTarget) tag;
- targ.addEventListener(SVGConstants.SVG_EVENT_CLICK, new EventListener() {
- @Override
- public void handleEvent(Event evt) {
- handleMouseClick(tool);
+ /**
+ * Build the toolbox
+ */
+ private void buildToolBox() {
+ double scale = StyleLibrary.SCALE;
+ deleteChildren(container);
+
+ ArrayList<VisualizationTask> vis = new ArrayList<VisualizationTask>();
+ Collection<VisualizationTask> visualizers = ResultUtil.filterResults(task.getResult(), VisualizationTask.class);
+ for(VisualizationTask task : visualizers) {
+ if(task.tool && !vis.contains(task)) {
+ vis.add(task);
+ }
}
- }, false);
- }
- /**
- * Handle the mouseClick - change the selected tool in the context
- *
- * @param tool Selected Tool
- */
- protected void handleMouseClick(VisualizationTask tool) {
- // TODO: Move this to the selected tool instead?
- if(VisualizerUtil.isVisible(tool)) {
- context.setSelection(null);
- }
- VisualizerUtil.setVisible(context, tool, true);
- }
+ // calculate the position of the first tool
+ CanvasSize viewport = proj.estimateViewport();
+ double x = viewport.getMinX() - 0.17 * scale;
+ double width = 0.07 * scale;
+ double height = 0.06 * scale;
+ double miny = viewport.getMinY();
+ double maxy = viewport.getMaxY();
+ double y = (miny + maxy - vis.size() * height * 1.4) * .5;
+ if(y < miny) {
+ LOG.warning("Too many Tools");
+ }
- @Override
- public void resultAdded(Result child, Result parent) {
- if(child instanceof VisualizationTask) {
- VisualizationTask task = (VisualizationTask) child;
- if(VisualizerUtil.isTool(task)) {
- synchronizedRedraw();
+ // add tools
+ Element[] toolTags = new Element[vis.size()];
+ for(int i = 0; i < vis.size(); i++) {
+ VisualizationTask v = vis.get(i);
+ toolTags[i] = svgp.svgRect(x, y, width, height);
+ String name = v.getLongName();
+ // Split
+ List<String> lines = FormatUtil.splitAtLastBlank(name, 8);
+ // Generate label objects.
+ for(int l = 0; l < lines.size(); l++) {
+ Element selectRangeText = svgp.svgText(x + 0.01 * scale, y + (0.02 + 0.05 * l / lines.size()) * scale, lines.get(l));
+ SVGUtil.setAtt(selectRangeText, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_TOOL_CAPTION);
+ container.appendChild(selectRangeText);
+ }
+
+ if(v.visible) {
+ SVGUtil.addCSSClass(toolTags[i], CSS_TOOL_BUTTON_SELECTED);
+ }
+ else {
+ SVGUtil.addCSSClass(toolTags[i], CSS_TOOL_BUTTON);
+ }
+ addEventListener(toolTags[i], v);
+
+ container.appendChild(toolTags[i]);
+ y = y + 0.1 * scale;
}
}
- }
- @Override
- public void resultRemoved(Result child, Result parent) {
- if(child instanceof VisualizationTask) {
- VisualizationTask task = (VisualizationTask) child;
- if(VisualizerUtil.isTool(task)) {
- synchronizedRedraw();
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ // Class for the not selected tool
+ if(!svgp.getCSSClassManager().contains(CSS_TOOL_BUTTON)) {
+ final CSSClass modeCls = new CSSClass(this, CSS_TOOL_BUTTON);
+ modeCls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, 0.4);
+ modeCls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_GREEN_VALUE);
+ modeCls.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE);
+
+ svgp.addCSSClassOrLogError(modeCls);
+ }
+ // Class for the selected tool
+ if(!svgp.getCSSClassManager().contains(CSS_TOOL_BUTTON_SELECTED)) {
+ final CSSClass modeCls = new CSSClass(this, CSS_TOOL_BUTTON_SELECTED);
+ modeCls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, 0.4);
+ modeCls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_BLUE_VALUE);
+ modeCls.setStatement(SVGConstants.CSS_STROKE_OPACITY_PROPERTY, 0.4);
+ modeCls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_BLUE_VALUE);
+ modeCls.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE);
+
+ svgp.addCSSClassOrLogError(modeCls);
+ }
+ // Class for the text of the tools
+ if(!svgp.getCSSClassManager().contains(CSS_TOOL_CAPTION)) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ final CSSClass label = new CSSClass(svgp, CSS_TOOL_CAPTION);
+ label.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getTextColor(StyleLibrary.AXIS_LABEL));
+ label.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, style.getFontFamily(StyleLibrary.AXIS_LABEL));
+ label.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, style.getTextSize(StyleLibrary.AXIS_LABEL) * .8);
+
+ svgp.addCSSClassOrLogError(label);
}
}
- }
- @Override
- public void resultChanged(Result current) {
- if(current instanceof VisualizationTask) {
- VisualizationTask task = (VisualizationTask) current;
- if(VisualizerUtil.isTool(task)) {
- synchronizedRedraw();
- }
+ /**
+ * Add an event listener to the Element
+ *
+ * @param tag Element to add the listener
+ * @param tool Tool represented by the Element
+ */
+ private void addEventListener(final Element tag, final VisualizationTask tool) {
+ EventTarget targ = (EventTarget) tag;
+ targ.addEventListener(SVGConstants.SVG_EVENT_CLICK, new EventListener() {
+ @Override
+ public void handleEvent(Event evt) {
+ handleMouseClick(tool);
+ }
+ }, false);
}
- }
- /**
- * Factory for visualizers for a toolbox
- *
- * @author Heidi Kolb
- *
- * @apiviz.stereotype factory
- * @apiviz.uses ToolBox2DVisualization oneway - - «create»
- */
- public static class Factory extends AbstractVisFactory {
/**
- * Constructor
+ * Handle the mouseClick - change the selected tool in the context
+ *
+ * @param tool Selected Tool
*/
- public Factory() {
- super();
+ protected void handleMouseClick(VisualizationTask tool) {
+ // TODO: Move this to the selected tool instead?
+ if(tool.visible) {
+ context.setSelection(null);
+ }
+ VisualizerUtil.setVisible(context, tool, true);
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new ToolBox2DVisualization(task);
+ public void resultAdded(Result child, Result parent) {
+ if(child instanceof VisualizationTask) {
+ VisualizationTask task = (VisualizationTask) child;
+ if(task.tool) {
+ synchronizedRedraw();
+ }
+ }
+ }
+
+ @Override
+ public void resultRemoved(Result child, Result parent) {
+ if(child instanceof VisualizationTask) {
+ VisualizationTask task = (VisualizationTask) child;
+ if(task.tool) {
+ synchronizedRedraw();
+ }
+ }
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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);
+ public void resultChanged(Result current) {
+ if(current instanceof VisualizationTask) {
+ VisualizationTask task = (VisualizationTask) current;
+ if(task.tool) {
+ synchronizedRedraw();
+ }
}
}
}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipScoreVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipScoreVisualization.java
index 139fa1ed..f3f35002 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipScoreVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipScoreVisualization.java
@@ -31,7 +31,7 @@ import org.apache.batik.util.SVGConstants;
import org.w3c.dom.Element;
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.ids.DBIDRef;
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;
@@ -56,8 +56,12 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;
* as the cursor lingers on the marker.
*
* @author Remigius Wojdanowski
+ * @author Erich Schubert
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class TooltipScoreVisualization extends AbstractTooltipVisualization {
+public class TooltipScoreVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
@@ -69,185 +73,182 @@ public class TooltipScoreVisualization extends AbstractTooltipVisualization {
public static final String NAME_GEN = " Tooltips";
/**
- * Number format.
+ * Settings
*/
- NumberFormat nf;
+ protected Parameterizer settings;
/**
- * Number value to visualize
- */
- private Relation<? extends Number> result;
-
- /**
- * Font size to use.
- */
- private double fontsize;
-
- /**
- * Constructor
+ * Constructor.
*
- * @param task Task
- * @param nf Number Format
+ * @param settings Settings
*/
- public TooltipScoreVisualization(VisualizationTask task, NumberFormat nf) {
- super(task);
- this.result = task.getResult();
- this.nf = nf;
- this.fontsize = 3 * context.getStyleLibrary().getTextSize(StyleLibrary.PLOT);
- synchronizedRedraw();
+ public TooltipScoreVisualization(Parameterizer settings) {
+ super();
+ this.settings = settings;
}
@Override
- protected Element makeTooltip(DBID id, double x, double y, double dotsize) {
- return svgp.svgText(x + dotsize, y + fontsize * 0.07, nf.format(result.get(id).doubleValue()));
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
- /**
- * Registers the Tooltip-CSS-Class at a SVGPlot.
- *
- * @param svgp the SVGPlot to register the Tooltip-CSS-Class.
- */
@Override
- protected void setupCSS(SVGPlot svgp) {
- final StyleLibrary style = context.getStyleLibrary();
- final double fontsize = style.getTextSize(StyleLibrary.PLOT);
- final String fontfamily = style.getFontFamily(StyleLibrary.PLOT);
-
- CSSClass tooltiphidden = new CSSClass(svgp, TOOLTIP_HIDDEN);
- tooltiphidden.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize);
- tooltiphidden.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily);
- tooltiphidden.setStatement(SVGConstants.CSS_DISPLAY_PROPERTY, SVGConstants.CSS_NONE_VALUE);
- svgp.addCSSClassOrLogError(tooltiphidden);
-
- CSSClass tooltipvisible = new CSSClass(svgp, TOOLTIP_VISIBLE);
- tooltipvisible.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize);
- tooltipvisible.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily);
- svgp.addCSSClassOrLogError(tooltipvisible);
-
- CSSClass tooltipsticky = new CSSClass(svgp, TOOLTIP_STICKY);
- tooltipsticky.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize);
- tooltipsticky.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily);
- svgp.addCSSClassOrLogError(tooltipsticky);
-
- // invisible but sensitive area for the tooltip activator
- CSSClass tooltiparea = new CSSClass(svgp, TOOLTIP_AREA);
- tooltiparea.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_RED_VALUE);
- tooltiparea.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_NONE_VALUE);
- tooltiparea.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, "0");
- tooltiparea.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE);
- svgp.addCSSClassOrLogError(tooltiparea);
-
- svgp.updateStyleElement();
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ // TODO: we can also visualize other scores!
+ Collection<OutlierResult> ors = ResultUtil.filterResults(result, OutlierResult.class);
+ for(OutlierResult o : ors) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, o.getScores(), p.getRelation(), this);
+ task.tool = true;
+ task.initDefaultVisibility(false);
+ baseResult.getHierarchy().add(o.getScores(), task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ Collection<Relation<?>> rrs = ResultUtil.filterResults(result, Relation.class);
+ for(Relation<?> r : rrs) {
+ if(!TypeUtil.DOUBLE.isAssignableFromType(r.getDataTypeInformation()) && !TypeUtil.INTEGER.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 TooltipScoreVisualization) {
+ add = false;
+ break;
+ }
+ }
+ if(add) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(r.getLongName() + NAME_GEN, r, p.getRelation(), this);
+ task.tool = true;
+ task.initDefaultVisibility(false);
+ baseResult.getHierarchy().add(r, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ }
}
/**
- * Factory for tooltip visualizers
+ * Instance
*
+ * @author Remigius Wojdanowski
* @author Erich Schubert
- *
- * @apiviz.stereotype factory
- * @apiviz.uses TooltipScoreVisualization oneway - - «create»
*/
- public static class Factory extends AbstractVisFactory {
+ public class Instance extends AbstractTooltipVisualization {
/**
- * Parameter for the gamma-correction.
- *
- * <p>
- * Key: {@code -tooltip.digits}
- * </p>
- *
- * <p>
- * Default value: 4
- * </p>
+ * Number value to visualize
*/
- public static final OptionID DIGITS_ID = OptionID.getOrCreateOptionID("tooltip.digits", "Number of digits to show (e.g. when visualizing outlier scores)");
+ private Relation<? extends Number> result;
/**
- * Number formatter used for visualization
+ * Font size to use.
*/
- NumberFormat nf = null;
+ private double fontsize;
/**
- * Constructor.
+ * Constructor
*
- * @param digits number of digits
+ * @param task Task
*/
- public Factory(int digits) {
- super();
- nf = NumberFormat.getInstance(Locale.ROOT);
- nf.setGroupingUsed(false);
- nf.setMaximumFractionDigits(digits);
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.result = task.getResult();
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ this.fontsize = 3 * style.getTextSize(StyleLibrary.PLOT);
+ synchronizedRedraw();
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new TooltipScoreVisualization(task, nf);
+ protected Element makeTooltip(DBIDRef id, double x, double y, double dotsize) {
+ return svgp.svgText(x + dotsize, y + fontsize * 0.07, settings.nf.format(result.get(id).doubleValue()));
}
+ /**
+ * Registers the Tooltip-CSS-Class at a SVGPlot.
+ *
+ * @param svgp the SVGPlot to register the Tooltip-CSS-Class.
+ */
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- // TODO: we can also visualize other scores!
- Collection<OutlierResult> ors = ResultUtil.filterResults(result, OutlierResult.class);
- for(OutlierResult o : ors) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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);
- }
- }
- Collection<Relation<?>> rrs = ResultUtil.filterResults(result, Relation.class);
- for(Relation<?> r : rrs) {
- if(!TypeUtil.DOUBLE.isAssignableFromType(r.getDataTypeInformation()) && !TypeUtil.INTEGER.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) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : ps) {
- final VisualizationTask task = new VisualizationTask(r.getLongName() + 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);
- }
- }
- }
+ protected void setupCSS(SVGPlot svgp) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ final double fontsize = style.getTextSize(StyleLibrary.PLOT);
+ final String fontfamily = style.getFontFamily(StyleLibrary.PLOT);
+
+ CSSClass tooltiphidden = new CSSClass(svgp, TOOLTIP_HIDDEN);
+ tooltiphidden.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize);
+ tooltiphidden.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily);
+ tooltiphidden.setStatement(SVGConstants.CSS_DISPLAY_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ svgp.addCSSClassOrLogError(tooltiphidden);
+
+ CSSClass tooltipvisible = new CSSClass(svgp, TOOLTIP_VISIBLE);
+ tooltipvisible.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize);
+ tooltipvisible.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily);
+ svgp.addCSSClassOrLogError(tooltipvisible);
+
+ CSSClass tooltipsticky = new CSSClass(svgp, TOOLTIP_STICKY);
+ tooltipsticky.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize);
+ tooltipsticky.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily);
+ svgp.addCSSClassOrLogError(tooltipsticky);
+
+ // invisible but sensitive area for the tooltip activator
+ CSSClass tooltiparea = new CSSClass(svgp, TOOLTIP_AREA);
+ tooltiparea.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_RED_VALUE);
+ tooltiparea.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ tooltiparea.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, "0");
+ tooltiparea.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE);
+ svgp.addCSSClassOrLogError(tooltiparea);
+
+ svgp.updateStyleElement();
}
+ }
+
+ /**
+ * Parameterization class.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ public static class Parameterizer extends AbstractParameterizer {
+ /**
+ * Number formatter used for visualization
+ */
+ NumberFormat nf = null;
/**
- * Parameterization class.
+ * Parameter for the gamma-correction.
*
- * @author Erich Schubert
+ * <p>
+ * Key: {@code -tooltip.digits}
+ * </p>
*
- * @apiviz.exclude
+ * <p>
+ * Default value: 4
+ * </p>
*/
- public static class Parameterizer extends AbstractParameterizer {
- protected int digits = 4;
+ public static final OptionID DIGITS_ID = new OptionID("tooltip.digits", "Number of digits to show (e.g. when visualizing outlier scores)");
- @Override
- protected void makeOptions(Parameterization config) {
- super.makeOptions(config);
- IntParameter DIGITS_PARAM = new IntParameter(DIGITS_ID, new GreaterEqualConstraint(0), 4);
-
- if(config.grab(DIGITS_PARAM)) {
- digits = DIGITS_PARAM.getValue();
- }
+ @Override
+ protected void makeOptions(Parameterization config) {
+ super.makeOptions(config);
+ IntParameter digitsP = new IntParameter(DIGITS_ID, 4);
+ digitsP.addConstraint(new GreaterEqualConstraint(0));
+
+ if(config.grab(digitsP)) {
+ int digits = digitsP.intValue();
+ nf = NumberFormat.getInstance(Locale.ROOT);
+ nf.setGroupingUsed(false);
+ nf.setMaximumFractionDigits(digits);
}
+ }
- @Override
- protected Factory makeInstance() {
- return new Factory(digits);
- }
+ @Override
+ protected TooltipScoreVisualization makeInstance() {
+ return new TooltipScoreVisualization(this);
}
}
} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipStringVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipStringVisualization.java
index d015793c..979ce905 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipStringVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipStringVisualization.java
@@ -32,6 +32,7 @@ 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.database.ids.DBID;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDRef;
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;
@@ -52,9 +53,10 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;
* @author Remigius Wojdanowski
* @author Erich Schubert
*
- * @apiviz.has Relation oneway - - visualizes
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class TooltipStringVisualization extends AbstractTooltipVisualization {
+public class TooltipStringVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
@@ -76,146 +78,147 @@ public class TooltipStringVisualization extends AbstractTooltipVisualization {
public static final String NAME_EID = "External ID Tooltips";
/**
- * Number value to visualize
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
*/
- private Relation<?> result;
-
- /**
- * Font size to use.
- */
- private double fontsize;
-
- /**
- * Constructor.
- *
- * @param task Task
- */
- public TooltipStringVisualization(VisualizationTask task) {
- super(task);
- this.result = task.getResult();
- this.fontsize = 3 * context.getStyleLibrary().getTextSize(StyleLibrary.PLOT);
- synchronizedRedraw();
+ public TooltipStringVisualization() {
+ super();
}
@Override
- protected Element makeTooltip(DBID id, double x, double y, double dotsize) {
- final Object data = result.get(id);
- String label;
- if(data == null) {
- label = "null";
- }
- else {
- label = data.toString();
- }
- if(label == "" || label == null) {
- label = "null";
- }
- return svgp.svgText(x + dotsize, y + fontsize * 0.07, label);
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
- /**
- * Registers the Tooltip-CSS-Class at a SVGPlot.
- *
- * @param svgp the SVGPlot to register the Tooltip-CSS-Class.
- */
@Override
- protected void setupCSS(SVGPlot svgp) {
- final StyleLibrary style = context.getStyleLibrary();
- final double fontsize = style.getTextSize(StyleLibrary.PLOT);
- final String fontfamily = style.getFontFamily(StyleLibrary.PLOT);
-
- CSSClass tooltiphidden = new CSSClass(svgp, TOOLTIP_HIDDEN);
- tooltiphidden.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize);
- tooltiphidden.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily);
- tooltiphidden.setStatement(SVGConstants.CSS_DISPLAY_PROPERTY, SVGConstants.CSS_NONE_VALUE);
- svgp.addCSSClassOrLogError(tooltiphidden);
-
- CSSClass tooltipvisible = new CSSClass(svgp, TOOLTIP_VISIBLE);
- tooltipvisible.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize);
- tooltipvisible.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily);
- svgp.addCSSClassOrLogError(tooltipvisible);
-
- CSSClass tooltipsticky = new CSSClass(svgp, TOOLTIP_STICKY);
- tooltipsticky.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize);
- tooltipsticky.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily);
- svgp.addCSSClassOrLogError(tooltipsticky);
-
- // invisible but sensitive area for the tooltip activator
- CSSClass tooltiparea = new CSSClass(svgp, TOOLTIP_AREA);
- tooltiparea.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_RED_VALUE);
- tooltiparea.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_NONE_VALUE);
- tooltiparea.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, "0");
- tooltiparea.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE);
- svgp.addCSSClassOrLogError(tooltiparea);
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<Relation<?>> reps = ResultUtil.filterResults(result, Relation.class);
+ for(Relation<?> rep : reps) {
+ if(DBID.class.isAssignableFrom(rep.getDataTypeInformation().getRestrictionClass())) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME_ID, rep, p.getRelation(), this);
+ task.tool = true;
+ task.initDefaultVisibility(false);
+ baseResult.getHierarchy().add(rep, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ if(ClassLabel.class.isAssignableFrom(rep.getDataTypeInformation().getRestrictionClass())) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME_CLASS, rep, p.getRelation(), this);
+ task.tool = true;
+ task.initDefaultVisibility(false);
+ baseResult.getHierarchy().add(rep, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ if(LabelList.class.isAssignableFrom(rep.getDataTypeInformation().getRestrictionClass())) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME_LABEL, rep, p.getRelation(), this);
+ task.tool = true;
+ task.initDefaultVisibility(false);
+ baseResult.getHierarchy().add(rep, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ if(ExternalID.class.isAssignableFrom(rep.getDataTypeInformation().getRestrictionClass())) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME_EID, rep, p.getRelation(), this);
+ task.tool = true;
+ task.initDefaultVisibility(false);
+ baseResult.getHierarchy().add(rep, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ }
}
/**
- * Factory
+ * Instance
*
+ * @author Remigius Wojdanowski
* @author Erich Schubert
*
- * @apiviz.stereotype factory
- * @apiviz.uses TooltipStringVisualization oneway - - «create»
+ * @apiviz.has Relation oneway - - visualizes
*/
- public static class Factory extends AbstractVisFactory {
+ public class Instance extends AbstractTooltipVisualization {
+ /**
+ * Number value to visualize
+ */
+ private Relation<?> result;
+
/**
- * Constructor, adhering to
- * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ * Font size to use.
*/
- public Factory() {
- super();
+ private double fontsize;
+
+ /**
+ * Constructor.
+ *
+ * @param task Task
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.result = task.getResult();
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ this.fontsize = 3 * style.getTextSize(StyleLibrary.PLOT);
+ synchronizedRedraw();
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new TooltipStringVisualization(task);
+ protected Element makeTooltip(DBIDRef id, double x, double y, double dotsize) {
+ final Object data = result.get(id);
+ String label;
+ if(data == null) {
+ label = "null";
+ }
+ else {
+ label = data.toString();
+ }
+ if(label == "" || label == null) {
+ label = "null";
+ }
+ return svgp.svgText(x + dotsize, y + fontsize * 0.07, label);
}
+ /**
+ * Registers the Tooltip-CSS-Class at a SVGPlot.
+ *
+ * @param svgp the SVGPlot to register the Tooltip-CSS-Class.
+ */
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<Relation<?>> reps = ResultUtil.filterResults(result, Relation.class);
- for(Relation<?> rep : reps) {
- if(DBID.class.isAssignableFrom(rep.getDataTypeInformation().getRestrictionClass())) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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())) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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())) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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())) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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);
- }
- }
- }
+ protected void setupCSS(SVGPlot svgp) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ final double fontsize = style.getTextSize(StyleLibrary.PLOT);
+ final String fontfamily = style.getFontFamily(StyleLibrary.PLOT);
+
+ CSSClass tooltiphidden = new CSSClass(svgp, TOOLTIP_HIDDEN);
+ tooltiphidden.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize);
+ tooltiphidden.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily);
+ tooltiphidden.setStatement(SVGConstants.CSS_DISPLAY_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ svgp.addCSSClassOrLogError(tooltiphidden);
+
+ CSSClass tooltipvisible = new CSSClass(svgp, TOOLTIP_VISIBLE);
+ tooltipvisible.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize);
+ tooltipvisible.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily);
+ svgp.addCSSClassOrLogError(tooltipvisible);
+
+ CSSClass tooltipsticky = new CSSClass(svgp, TOOLTIP_STICKY);
+ tooltipsticky.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize);
+ tooltipsticky.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily);
+ svgp.addCSSClassOrLogError(tooltipsticky);
+
+ // invisible but sensitive area for the tooltip activator
+ CSSClass tooltiparea = new CSSClass(svgp, TOOLTIP_AREA);
+ tooltiparea.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_RED_VALUE);
+ tooltiparea.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ tooltiparea.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, "0");
+ tooltiparea.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE);
+ svgp.addCSSClassOrLogError(tooltiparea);
}
}
} \ No newline at end of file
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
index e1817b1e..ebaf9eb9 100644
--- 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
@@ -69,207 +69,201 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatter
* @author Robert Rödler
* @author Erich Schubert
*
- * @apiviz.has Clustering oneway - - visualizes
- * @apiviz.uses GrahamScanConvexHull2D
- * @apiviz.uses AlphaShape
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class ClusterHullVisualization extends AbstractScatterplotVisualization {
+public class ClusterHullVisualization extends AbstractVisFactory {
/**
* 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.
+ * Settings
*/
- public static final String CLUSTERHULL = "cluster-hull";
+ Parameterizer settings;
/**
- * The result we work on
- */
- Clustering<Model> clustering;
-
- /**
- * Alpha value
- */
- double alpha = Double.POSITIVE_INFINITY;
-
- /**
- * Constructor
+ * Constructor.
*
- * @param task VisualizationTask
- * @param alpha Alpha value
+ * @param settings Settings
*/
- public ClusterHullVisualization(VisualizationTask task, double alpha) {
- super(task);
- this.clustering = task.getResult();
- this.alpha = alpha;
- incrementalRedraw();
+ public ClusterHullVisualization(Parameterizer settings) {
+ super();
+ this.settings = settings;
}
@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(DBIDIter iter = ids.iter(); iter.valid(); iter.advance()) {
- double[] projP = proj.fastProjectDataToRenderSpace(rel.get(iter));
- 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(DBIDIter iter = ids.iter(); iter.valid(); iter.advance()) {
- double[] projP = proj.fastProjectDataToRenderSpace(rel.get(iter));
- 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);
- }
- }
- }
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
- /**
- * 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);
+ @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) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA - 1;
+ task.initDefaultVisibility(false);
+ baseResult.getHierarchy().add(c, task);
+ baseResult.getHierarchy().add(p, task);
+ }
}
- 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.
+ * Instance.
*
* @author Robert Rödler
* @author Erich Schubert
*
- * @apiviz.stereotype factory
- * @apiviz.uses ClusterHullVisualization oneway - - «create»
+ * @apiviz.has Clustering oneway - - visualizes
+ * @apiviz.uses GrahamScanConvexHull2D
+ * @apiviz.uses AlphaShape
*/
- public static class Factory extends AbstractVisFactory {
+ public class Instance extends AbstractScatterplotVisualization {
/**
- * Alpha value
+ * Generic tags to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
*/
- double alpha = Double.POSITIVE_INFINITY;
+ public static final String CLUSTERHULL = "cluster-hull";
/**
- * Constructor.
- *
- * @param alpha Alpha value
+ * The result we work on
*/
- public Factory(double alpha) {
- super();
- this.alpha = alpha;
- }
+ Clustering<Model> clustering;
- @Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new ClusterHullVisualization(task, alpha);
+ /**
+ * Constructor
+ *
+ * @param task VisualizationTask
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.clustering = task.getResult();
+ incrementalRedraw();
}
@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) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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);
+ 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(settings.alpha >= Double.POSITIVE_INFINITY) {
+ GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D();
+
+ for(DBIDIter iter = ids.iter(); iter.valid(); iter.advance()) {
+ double[] projP = proj.fastProjectDataToRenderSpace(rel.get(iter));
+ 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(DBIDIter iter = ids.iter(); iter.valid(); iter.advance()) {
+ double[] projP = proj.fastProjectDataToRenderSpace(rel.get(iter));
+ ps.add(new Vector(projP));
+ }
+ List<Polygon> polys = (new AlphaShape(ps, settings.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);
+ }
}
}
}
/**
- * Parameterization class.
- *
- * @author Erich Schubert
+ * Adds the required CSS-Classes
*
- * @apiviz.exclude
+ * @param svgp SVG-Plot
*/
- 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();
- }
+ private void addCSSClasses(SVGPlot svgp, int clusterID, double opac) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ ColorLibrary colors = style.getColorSet(StyleLibrary.PLOT);
+
+ CSSClass cls = new CSSClass(this, CLUSTERHULL + clusterID);
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.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);
+ }
+ }
+
+ /**
+ * 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 = new OptionID("hull.alpha", "Alpha value for hull drawing (in projected space!).");
+
+ /**
+ * Alpha value
+ */
+ double alpha = Double.POSITIVE_INFINITY;
- @Override
- protected Factory makeInstance() {
- return new Factory(alpha);
+ @Override
+ protected void makeOptions(Parameterization config) {
+ super.makeOptions(config);
+ DoubleParameter alphaP = new DoubleParameter(ALPHA_ID, Double.POSITIVE_INFINITY);
+ if(config.grab(alphaP)) {
+ alpha = alphaP.doubleValue();
}
}
+
+ @Override
+ protected ClusterHullVisualization makeInstance() {
+ return new ClusterHullVisualization(this);
+ }
}
} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterMeanVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterMeanVisualization.java
index 4f14f4ef..c07e5dca 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterMeanVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterMeanVisualization.java
@@ -35,7 +35,7 @@ import de.lmu.ifi.dbs.elki.data.NumberVector;
import de.lmu.ifi.dbs.elki.data.model.MeanModel;
import de.lmu.ifi.dbs.elki.data.model.MedoidModel;
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.ids.DBIDIter;
import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
import de.lmu.ifi.dbs.elki.result.Result;
import de.lmu.ifi.dbs.elki.result.ResultUtil;
@@ -61,238 +61,235 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatter
*
* @author Heidi Kolb
*
- * @apiviz.has MeanModel oneway - - visualizes
- * @apiviz.has MedoidModel oneway - - visualizes
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class ClusterMeanVisualization extends AbstractScatterplotVisualization {
+public class ClusterMeanVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
private static final String NAME = "Cluster Means";
/**
- * CSS class name for center of the means
+ * Settings
*/
- private final static String CSS_MEAN_CENTER = "mean-center";
-
- /**
- * CSS class name for center of the means
- */
- 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<Model> clustering;
-
- /**
- * Draw stars
- */
- boolean stars;
+ protected Parameterizer settings;
/**
* Constructor.
*
- * @param task Visualization task
- * @param stars Draw stars
+ * @param settings Settings
*/
- public ClusterMeanVisualization(VisualizationTask task, boolean stars) {
- super(task);
- this.clustering = task.getResult();
- this.stars = stars;
- incrementalRedraw();
+ public ClusterMeanVisualization(Parameterizer settings) {
+ super();
+ this.settings = settings;
}
@Override
- protected void redraw() {
- addCSSClasses(svgp);
-
- MarkerLibrary ml = context.getStyleLibrary().markers();
- double marker_size = context.getStyleLibrary().getSize(StyleLibrary.MARKERPLOT);
-
- Iterator<Cluster<Model>> ci = clustering.getAllClusters().iterator();
- for(int cnum = 0; ci.hasNext(); cnum++) {
- Cluster<Model> clus = ci.next();
- Model model = clus.getModel();
- double[] mean;
- if(model instanceof MeanModel) {
- @SuppressWarnings("unchecked")
- MeanModel<? extends NumberVector<?, ?>> mmodel = (MeanModel<? extends NumberVector<?, ?>>) model;
- mean = proj.fastProjectDataToRenderSpace(mmodel.getMean());
- }
- else if(model instanceof MedoidModel) {
- MedoidModel mmodel = (MedoidModel) model;
- mean = proj.fastProjectDataToRenderSpace(rel.get(mmodel.getMedoid()));
- }
- else {
- continue;
- }
-
- // add a greater Marker for the mean
- Element meanMarker = ml.useMarker(svgp, layer, mean[0], mean[1], cnum, marker_size * 3);
- SVGUtil.setAtt(meanMarker, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_MEAN);
-
- // Add a fine cross to mark the exact location of the mean.
- Element meanMarkerCenter = svgp.svgLine(mean[0] - .7, mean[1], mean[0] + .7, mean[1]);
- SVGUtil.setAtt(meanMarkerCenter, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_MEAN_CENTER);
- Element meanMarkerCenter2 = svgp.svgLine(mean[0], mean[1] - .7, mean[0], mean[1] + .7);
- SVGUtil.setAtt(meanMarkerCenter2, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_MEAN_CENTER);
-
- 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);
- }
- }
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
- /**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- private void addCSSClasses(SVGPlot svgp) {
- if(!svgp.getCSSClassManager().contains(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(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<Model>> 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);
+ @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?
+ if(testMeanModel(c)) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA + 1;
+ baseResult.getHierarchy().add(c, task);
+ baseResult.getHierarchy().add(p, task);
+ }
}
}
}
}
/**
- * Factory for visualizers to generate an SVG-Element containing a marker for
- * the mean in a KMeans-Clustering
+ * Instance.
*
* @author Heidi Kolb
*
- * @apiviz.stereotype factory
- * @apiviz.uses ClusterMeanVisualization oneway - - «create»
+ * @apiviz.has MeanModel oneway - - visualizes
+ * @apiviz.has MedoidModel oneway - - visualizes
*/
- public static class Factory extends AbstractVisFactory {
+ public class Instance extends AbstractScatterplotVisualization {
/**
- * Option ID for visualization of cluster means.
- *
- * <pre>
- * -cluster.stars
- * </pre>
+ * CSS class name for center of the means
+ */
+ private static final String CSS_MEAN_CENTER = "mean-center";
+
+ /**
+ * CSS class name for center of the means
+ */
+ private static final String CSS_MEAN = "mean-marker";
+
+ /**
+ * CSS class name for center of the means
*/
- public static final OptionID STARS_ID = OptionID.getOrCreateOptionID("cluster.stars", "Visualize mean-based clusters using stars.");
+ private static final String CSS_MEAN_STAR = "mean-star";
/**
- * Draw stars
+ * Clustering to visualize.
*/
- private boolean stars;
+ Clustering<Model> clustering;
/**
* Constructor.
*
- * @param stars Draw stars
+ * @param task Visualization task
*/
- public Factory(boolean stars) {
- super();
- this.stars = stars;
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.clustering = task.getResult();
+ incrementalRedraw();
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new ClusterMeanVisualization(task, stars);
- }
+ protected void redraw() {
+ addCSSClasses(svgp);
- @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?
- if(testMeanModel(c)) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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);
- baseResult.getHierarchy().add(c, task);
- baseResult.getHierarchy().add(p, task);
- }
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ MarkerLibrary ml = style.markers();
+ double marker_size = style.getSize(StyleLibrary.MARKERPLOT);
+
+ Iterator<Cluster<Model>> ci = clustering.getAllClusters().iterator();
+ for(int cnum = 0; ci.hasNext(); cnum++) {
+ Cluster<Model> clus = ci.next();
+ Model model = clus.getModel();
+ double[] mean;
+ if(model instanceof MeanModel) {
+ @SuppressWarnings("unchecked")
+ MeanModel<? extends NumberVector<?>> mmodel = (MeanModel<? extends NumberVector<?>>) model;
+ mean = proj.fastProjectDataToRenderSpace(mmodel.getMean());
+ }
+ else if(model instanceof MedoidModel) {
+ MedoidModel mmodel = (MedoidModel) model;
+ mean = proj.fastProjectDataToRenderSpace(rel.get(mmodel.getMedoid()));
+ }
+ else {
+ continue;
+ }
+
+ // add a greater Marker for the mean
+ Element meanMarker = ml.useMarker(svgp, layer, mean[0], mean[1], cnum, marker_size * 3);
+ SVGUtil.setAtt(meanMarker, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_MEAN);
+
+ // Add a fine cross to mark the exact location of the mean.
+ Element meanMarkerCenter = svgp.svgLine(mean[0] - .7, mean[1], mean[0] + .7, mean[1]);
+ SVGUtil.setAtt(meanMarkerCenter, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_MEAN_CENTER);
+ Element meanMarkerCenter2 = svgp.svgLine(mean[0], mean[1] - .7, mean[0], mean[1] + .7);
+ SVGUtil.setAtt(meanMarkerCenter2, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_MEAN_CENTER);
+
+ layer.appendChild(meanMarkerCenter);
+ layer.appendChild(meanMarkerCenter2);
+
+ if(settings.stars) {
+ SVGPath star = new SVGPath();
+ for(DBIDIter id = clus.getIDs().iter(); id.valid(); id.advance()) {
+ 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);
}
}
}
/**
- * Test if the given clustering has a mean model.
+ * Adds the required CSS-Classes
*
- * @param c Clustering to inspect
- * @return true when the clustering has a mean or medoid model.
+ * @param svgp SVG-Plot
*/
- private static boolean testMeanModel(Clustering<?> c) {
- Model firstmodel = c.getAllClusters().get(0).getModel();
- if(firstmodel instanceof MeanModel<?>) {
- return true;
+ private void addCSSClasses(SVGPlot svgp) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ if(!svgp.getCSSClassManager().contains(CSS_MEAN_CENTER)) {
+ CSSClass center = new CSSClass(this, CSS_MEAN_CENTER);
+ center.setStatement(SVGConstants.CSS_STROKE_PROPERTY, style.getTextColor(StyleLibrary.DEFAULT));
+ center.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.AXIS_TICK) * .5);
+ svgp.addCSSClassOrLogError(center);
}
- if(firstmodel instanceof MedoidModel) {
- return true;
+ if(!svgp.getCSSClassManager().contains(CSS_MEAN)) {
+ CSSClass center = new CSSClass(this, CSS_MEAN);
+ center.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, "0.7");
+ svgp.addCSSClassOrLogError(center);
+ }
+ if(settings.stars) {
+ ColorLibrary colors = style.getColorSet(StyleLibrary.PLOT);
+
+ Iterator<Cluster<Model>> 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, style.getLineWidth(StyleLibrary.PLOT));
+ center.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, "0.7");
+ svgp.addCSSClassOrLogError(center);
+ }
+ }
}
- return false;
}
+ }
+ /**
+ * Test if the given clustering has a mean model.
+ *
+ * @param c Clustering to inspect
+ * @return true when the clustering has a mean or medoid model.
+ */
+ private static boolean testMeanModel(Clustering<?> c) {
+ Model firstmodel = c.getAllClusters().get(0).getModel();
+ if(firstmodel instanceof MeanModel<?>) {
+ return true;
+ }
+ if(firstmodel instanceof MedoidModel) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Parameterization class.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ public static class Parameterizer extends AbstractParameterizer {
/**
- * Parameterization class.
- *
- * @author Erich Schubert
+ * Option ID for visualization of cluster means.
*
- * @apiviz.exclude
+ * <pre>
+ * -cluster.stars
+ * </pre>
*/
- 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();
- }
- }
+ public static final OptionID STARS_ID = new OptionID("cluster.stars", "Visualize mean-based clusters using stars.");
+
+ /**
+ * Whether to draw cluster stars
+ */
+ protected boolean stars = false;
- @Override
- protected Factory makeInstance() {
- return new Factory(stars);
+ @Override
+ protected void makeOptions(Parameterization config) {
+ super.makeOptions(config);
+ Flag starsF = new Flag(STARS_ID);
+ if(config.grab(starsF)) {
+ stars = starsF.isTrue();
}
}
+
+ @Override
+ protected ClusterMeanVisualization makeInstance() {
+ return new ClusterMeanVisualization(this);
+ }
}
} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterOrderVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterOrderVisualization.java
index 0d43875c..bd173e80 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterOrderVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterOrderVisualization.java
@@ -45,103 +45,105 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;
import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatterplotVisualization;
/**
- * Cluster order visualizer.
+ * Cluster order visualizer: connect objects via the spanning tree the cluster
+ * order represents.
*
* @author Erich Schubert
*
- * @apiviz.has ClusterOrderResult oneway - - visualizes
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-// TODO: listen for CLUSTER ORDER changes.
-public class ClusterOrderVisualization extends AbstractScatterplotVisualization implements DataStoreListener {
+public class ClusterOrderVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
private static final String NAME = "Predecessor Graph";
/**
- * CSS class name
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
*/
- private static final String CSSNAME = "predecessor";
-
- /**
- * The result we visualize
- */
- protected ClusterOrderResult<?> result;
-
- public ClusterOrderVisualization(VisualizationTask task) {
- super(task);
- result = task.getResult();
- context.addDataStoreListener(this);
- incrementalRedraw();
+ public ClusterOrderVisualization() {
+ super();
}
@Override
- public void destroy() {
- super.destroy();
- context.removeDataStoreListener(this);
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
@Override
- public void redraw() {
- CSSClass cls = new CSSClass(this, CSSNAME);
- context.getStyleLibrary().lines().formatCSSClass(cls, 0, context.getStyleLibrary().getLineWidth(StyleLibrary.CLUSTERORDER));
-
- svgp.addCSSClassOrLogError(cls);
-
- for(ClusterOrderEntry<?> ce : result) {
- DBID thisId = ce.getID();
- DBID prevId = ce.getPredecessorID();
- if(thisId == null || prevId == null) {
- continue;
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<ClusterOrderResult<DoubleDistance>> cos = ResultUtil.filterResults(result, ClusterOrderResult.class);
+ for(ClusterOrderResult<DoubleDistance> co : cos) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, co, p.getRelation(), this);
+ task.initDefaultVisibility(false);
+ task.level = VisualizationTask.LEVEL_DATA - 1;
+ baseResult.getHierarchy().add(co, task);
+ baseResult.getHierarchy().add(p, task);
}
- 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());
-
- layer.appendChild(arrow);
}
}
/**
- * Visualize an OPTICS cluster order by drawing connection lines.
+ * Instance
*
* @author Erich Schubert
*
- * @apiviz.stereotype factory
- * @apiviz.uses ClusterOrderVisualization oneway - - «create»
+ * @apiviz.has ClusterOrderResult oneway - - visualizes
*/
- public static class Factory extends AbstractVisFactory {
+ // TODO: listen for CLUSTER ORDER changes.
+ public class Instance extends AbstractScatterplotVisualization implements DataStoreListener {
/**
- * Constructor, adhering to
- * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ * CSS class name
*/
- public Factory() {
- super();
+ private static final String CSSNAME = "predecessor";
+
+ /**
+ * The result we visualize
+ */
+ protected ClusterOrderResult<?> result;
+
+ public Instance(VisualizationTask task) {
+ super(task);
+ result = task.getResult();
+ context.addDataStoreListener(this);
+ incrementalRedraw();
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new ClusterOrderVisualization(task);
+ public void destroy() {
+ super.destroy();
+ context.removeDataStoreListener(this);
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<ClusterOrderResult<DoubleDistance>> cos = ResultUtil.filterResults(result, ClusterOrderResult.class);
- for(ClusterOrderResult<DoubleDistance> co : cos) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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);
- baseResult.getHierarchy().add(co, task);
- baseResult.getHierarchy().add(p, task);
+ public void redraw() {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ CSSClass cls = new CSSClass(this, CSSNAME);
+ style.lines().formatCSSClass(cls, 0, style.getLineWidth(StyleLibrary.CLUSTERORDER));
+
+ svgp.addCSSClassOrLogError(cls);
+
+ for(ClusterOrderEntry<?> ce : result) {
+ DBID thisId = ce.getID();
+ DBID prevId = ce.getPredecessorID();
+ if(thisId == null || prevId == null) {
+ continue;
}
+ 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());
+
+ layer.appendChild(arrow);
}
}
}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/EMClusterVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/EMClusterVisualization.java
index 6070361e..2806812a 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/EMClusterVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/EMClusterVisualization.java
@@ -32,6 +32,7 @@ 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.DoubleVector;
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;
@@ -69,114 +70,199 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatter
*
* @author Robert Rödler
*
- * @apiviz.has EMModel oneway - - visualizes
- * @apiviz.uses GrahamScanConvexHull2D
- *
- * @param <NV> Type of the NumberVector being visualized.
+ * @apiviz.stereotype factory
+ * @apiviz.uses EMClusterVisualization oneway - - «create»
*/
-// 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 AbstractScatterplotVisualization {
+public class EMClusterVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
private static final String NAME = "EM Cluster Visualization";
/**
- * Generic tags to indicate the type of element. Used in IDs, CSS-Classes etc.
+ * Constants for quantiles of standard deviation
*/
- public static final String EMBORDER = "EMClusterBorder";
+ final static double[] sigma = new double[] { 0.41, 0.223, 0.047 };
/**
- * The result we work on
+ * Constructor
*/
- Clustering<EMModel<NV>> clustering;
+ public EMClusterVisualization() {
+ super();
+ }
- private static final double KAPPA = SVGHyperSphere.EUCLIDEAN_KAPPA;
+ @Override
+ public Instance<DoubleVector> makeVisualization(VisualizationTask task) {
+ return new Instance<DoubleVector>(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) {
+ // Does the cluster have a model with cluster means?
+ Clustering<MeanModel<DoubleVector>> mcls = findMeanModel(c);
+ if(mcls != null) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA + 3;
+ baseResult.getHierarchy().add(c, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ }
+ }
+ }
/**
- * StyleParameter:
+ * 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.
*/
- private int times = 3;
+ @SuppressWarnings("unchecked")
+ private static <NV extends NumberVector<?>> Clustering<MeanModel<NV>> findMeanModel(Clustering<?> c) {
+ final Model firstModel = c.getAllClusters().get(0).getModel();
+ if(c.getAllClusters().get(0).getModel() instanceof MeanModel<?> && firstModel instanceof EMModel<?>) {
+ return (Clustering<MeanModel<NV>>) c;
+ }
+ return null;
+ }
- private int opacStyle = 1;
+ /**
+ * Instance.
+ *
+ * @author Robert Rödler
+ *
+ * @apiviz.has EMModel oneway - - visualizes
+ * @apiviz.uses GrahamScanConvexHull2D
+ *
+ * @param <NV> Type of the NumberVector being visualized.
+ */
+ // TODO: nicer stacking of n-fold hulls
+ // TODO: can we find a proper sphere for 3+ dimensions?
+ public class Instance<NV extends NumberVector<?>> extends AbstractScatterplotVisualization {
+ /**
+ * Generic tags to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
+ */
+ public static final String EMBORDER = "EMClusterBorder";
- private int softBorder = 1;
+ /**
+ * The result we work on
+ */
+ Clustering<EMModel<NV>> clustering;
- private int drawStyle = 0;
+ private static final double KAPPA = SVGHyperSphere.EUCLIDEAN_KAPPA;
- final static double[] sigma = new double[] { 0.41, 0.223, 0.047 };
+ /**
+ * StyleParameter:
+ */
+ private int times = 3;
- /**
- * Constructor
- *
- * @param task VisualizationTask
- */
- public EMClusterVisualization(VisualizationTask task) {
- super(task);
- this.clustering = task.getResult();
- incrementalRedraw();
- }
+ private int opacStyle = 1;
- @Override
- protected void redraw() {
- // set styles
- addCSSClasses(svgp);
-
- // PCARunner
- PCARunner<NV> pcarun = ClassGenericsUtil.parameterizeOrAbort(PCARunner.class, new EmptyParameterization());
-
- Iterator<Cluster<EMModel<NV>>> ci = clustering.getAllClusters().iterator();
- for(int cnum = 0; cnum < clustering.getAllClusters().size(); cnum++) {
- Cluster<EMModel<NV>> clus = ci.next();
- DBIDs ids = clus.getIDs();
-
- if(ids.size() > 0) {
- Matrix covmat = clus.getModel().getCovarianceMatrix();
- NV centroid = clus.getModel().getMean();
- Vector cent = new Vector(proj.fastProjectDataToRenderSpace(centroid));
-
- // Compute the eigenvectors
- SortedEigenPairs eps = pcarun.processCovarMatrix(covmat).getEigenPairs();
-
- Vector[] pc = new Vector[eps.size()];
- 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.getArrayRef()));
- }
- if(drawStyle != 0 || eps.size() == 2) {
- drawSphere2D(cnum, cent, pc);
+ private int softBorder = 1;
+
+ private int drawStyle = 0;
+
+ /**
+ * Constructor
+ *
+ * @param task VisualizationTask
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.clustering = task.getResult();
+ incrementalRedraw();
+ }
+
+ @Override
+ protected void redraw() {
+ // set styles
+ addCSSClasses(svgp);
+
+ // PCARunner
+ PCARunner<NV> pcarun = ClassGenericsUtil.parameterizeOrAbort(PCARunner.class, new EmptyParameterization());
+
+ Iterator<Cluster<EMModel<NV>>> ci = clustering.getAllClusters().iterator();
+ for(int cnum = 0; cnum < clustering.getAllClusters().size(); cnum++) {
+ Cluster<EMModel<NV>> clus = ci.next();
+ DBIDs ids = clus.getIDs();
+
+ if(ids.size() > 0) {
+ Matrix covmat = clus.getModel().getCovarianceMatrix();
+ NV centroid = clus.getModel().getMean();
+ Vector cent = new Vector(proj.fastProjectDataToRenderSpace(centroid));
+
+ // Compute the eigenvectors
+ SortedEigenPairs eps = pcarun.processCovarMatrix(covmat).getEigenPairs();
+
+ Vector[] pc = new Vector[eps.size()];
+ 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.getArrayRef()));
+ }
+ if(drawStyle != 0 || eps.size() == 2) {
+ drawSphere2D(cnum, cent, pc);
+ }
+ else {
+ Polygon chres = makeHullComplex(pc);
+ drawHullLines(cnum, cent, chres);
+ }
}
- else {
- Polygon chres = makeHullComplex(pc);
- drawHullLines(cnum, cent, chres);
+ }
+ }
+
+ protected void drawSphere2D(int cnum, Vector cent, Vector[] pc) {
+ for(int dim1 = 0; dim1 < pc.length - 1; dim1++) {
+ for(int dim2 = dim1 + 1; dim2 < pc.length; dim2++) {
+ for(int i = 1; i <= times; i++) {
+ SVGPath path = new SVGPath();
+
+ Vector direction1 = pc[dim1].times(KAPPA * i);
+ Vector direction2 = pc[dim2].times(KAPPA * i);
+
+ Vector p1 = cent.plusTimes(pc[dim1], i);
+ Vector p2 = cent.plusTimes(pc[dim2], i);
+ Vector p3 = cent.minusTimes(pc[dim1], i);
+ Vector p4 = cent.minusTimes(pc[dim2], i);
+
+ path.moveTo(p1);
+ path.cubicTo(p1.plus(direction2), p2.plus(direction1), p2);
+ path.cubicTo(p2.minus(direction1), p3.plus(direction2), p3);
+ path.cubicTo(p3.minus(direction2), p4.minus(direction1), p4);
+ path.cubicTo(p4.plus(direction1), p1.minus(direction2), p1);
+ 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);
+ }
}
}
}
- }
- protected void drawSphere2D(int cnum, Vector cent, Vector[] pc) {
- for(int dim1 = 0; dim1 < pc.length - 1; dim1++) {
- for(int dim2 = dim1 + 1; dim2 < pc.length; dim2++) {
+ protected void drawHullLines(int cnum, Vector cent, Polygon chres) {
+ if(chres.size() > 1) {
for(int i = 1; i <= times; i++) {
SVGPath path = new SVGPath();
-
- Vector direction1 = pc[dim1].times(KAPPA * i);
- Vector direction2 = pc[dim2].times(KAPPA * i);
-
- Vector p1 = cent.plusTimes(pc[dim1], i);
- Vector p2 = cent.plusTimes(pc[dim2], i);
- Vector p3 = cent.minusTimes(pc[dim1], i);
- Vector p4 = cent.minusTimes(pc[dim2], i);
-
- path.moveTo(p1);
- path.cubicTo(p1.plus(direction2), p2.plus(direction1), p2);
- path.cubicTo(p2.minus(direction1), p3.plus(direction2), p3);
- path.cubicTo(p3.minus(direction2), p4.minus(direction1), p4);
- path.cubicTo(p4.plus(direction1), p1.minus(direction2), p1);
+ 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) {
@@ -189,18 +275,99 @@ public class EMClusterVisualization<NV extends NumberVector<NV, ?>> extends Abst
}
}
}
- }
- protected void drawHullLines(int cnum, Vector cent, Polygon chres) {
- if(chres.size() > 1) {
+ protected Polygon makeHull(Vector[] pc) {
+ GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D();
+
+ Vector diag = new Vector(0, 0);
+ for(int j = 0; j < pc.length; j++) {
+ hull.add(pc[j]);
+ hull.add(pc[j].times(-1));
+ for(int k = j + 1; k < pc.length; k++) {
+ Vector q = pc[k];
+ Vector ppq = pc[j].plus(q).timesEquals(MathUtil.SQRTHALF);
+ Vector pmq = pc[j].minus(q).timesEquals(MathUtil.SQRTHALF);
+ hull.add(ppq);
+ hull.add(ppq.times(-1));
+ hull.add(pmq);
+ hull.add(pmq.times(-1));
+ }
+ diag.plusEquals(pc[j]);
+ }
+ diag.timesEquals(1.0 / Math.sqrt(pc.length));
+ hull.add(diag);
+ hull.add(diag.times(-1));
+
+ Polygon chres = hull.getHull();
+ return chres;
+ }
+
+ protected Polygon makeHullComplex(Vector[] pc) {
+ GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D();
+
+ Vector diag = new Vector(0, 0);
+ for(int j = 0; j < pc.length; j++) {
+ hull.add(pc[j]);
+ hull.add(pc[j].times(-1));
+ for(int k = j + 1; k < pc.length; k++) {
+ Vector q = pc[k];
+ Vector ppq = pc[j].plus(q).timesEquals(MathUtil.SQRTHALF);
+ Vector pmq = pc[j].minus(q).timesEquals(MathUtil.SQRTHALF);
+ hull.add(ppq);
+ hull.add(ppq.times(-1));
+ hull.add(pmq);
+ hull.add(pmq.times(-1));
+ for(int l = k + 1; l < pc.length; l++) {
+ Vector r = pc[k];
+ Vector ppqpr = ppq.plus(r).timesEquals(Math.sqrt(1 / 3.));
+ Vector pmqpr = pmq.plus(r).timesEquals(Math.sqrt(1 / 3.));
+ Vector ppqmr = ppq.minus(r).timesEquals(Math.sqrt(1 / 3.));
+ Vector pmqmr = pmq.minus(r).timesEquals(Math.sqrt(1 / 3.));
+ hull.add(ppqpr);
+ hull.add(ppqpr.times(-1));
+ hull.add(pmqpr);
+ hull.add(pmqpr.times(-1));
+ hull.add(ppqmr);
+ hull.add(ppqmr.times(-1));
+ hull.add(pmqmr);
+ hull.add(pmqmr.times(-1));
+ }
+ }
+ diag.plusEquals(pc[j]);
+ }
+ diag.timesEquals(1.0 / Math.sqrt(pc.length));
+ hull.add(diag);
+ hull.add(diag.times(-1));
+ Polygon chres = hull.getHull();
+ return chres;
+ }
+
+ protected void drawHullArc(int cnum, Vector cent, Polygon chres) {
for(int i = 1; i <= times; i++) {
SVGPath path = new SVGPath();
+
+ ArrayList<Vector> delta = new ArrayList<Vector>(chres.size());
+ for(int p = 0; p < chres.size(); p++) {
+ Vector prev = chres.get((p - 1 + chres.size()) % chres.size());
+ Vector curr = chres.get(p);
+ Vector next = chres.get((p + 1) % chres.size());
+ Vector d1 = next.minus(curr).normalize();
+ Vector d2 = curr.minus(prev).normalize();
+ delta.add(d1.plus(d2));
+ // delta.add(next.minus(prev));
+ }
+
for(int p = 0; p < chres.size(); p++) {
- Vector cur = cent.plusTimes(chres.get(p), i);
- path.drawTo(cur);
+ Vector cur = cent.plus(chres.get(p));
+ Vector nex = cent.plus(chres.get((p + 1) % chres.size()));
+ Vector dcur = delta.get(p);
+ Vector dnex = delta.get((p + 1) % chres.size());
+ drawArc(path, cent, cur, nex, dcur, dnex, i);
}
path.close();
+
Element ellipse = path.makeElement(svgp);
+
SVGUtil.addCSSClass(ellipse, EMBORDER + cnum);
if(opacStyle == 1) {
CSSClass cls = new CSSClass(null, "temp");
@@ -211,262 +378,98 @@ public class EMClusterVisualization<NV extends NumberVector<NV, ?>> extends Abst
layer.appendChild(ellipse);
}
}
- }
-
- protected Polygon makeHull(Vector[] pc) {
- GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D();
-
- Vector diag = new Vector(0, 0);
- for(int j = 0; j < pc.length; j++) {
- hull.add(pc[j]);
- hull.add(pc[j].times(-1));
- for(int k = j + 1; k < pc.length; k++) {
- Vector q = pc[k];
- Vector ppq = pc[j].plus(q).timesEquals(MathUtil.SQRTHALF);
- Vector pmq = pc[j].minus(q).timesEquals(MathUtil.SQRTHALF);
- hull.add(ppq);
- hull.add(ppq.times(-1));
- hull.add(pmq);
- hull.add(pmq.times(-1));
- }
- diag.plusEquals(pc[j]);
- }
- diag.timesEquals(1.0 / Math.sqrt(pc.length));
- hull.add(diag);
- hull.add(diag.times(-1));
-
- Polygon chres = hull.getHull();
- return chres;
- }
-
- protected Polygon makeHullComplex(Vector[] pc) {
- GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D();
-
- Vector diag = new Vector(0, 0);
- for(int j = 0; j < pc.length; j++) {
- hull.add(pc[j]);
- hull.add(pc[j].times(-1));
- for(int k = j + 1; k < pc.length; k++) {
- Vector q = pc[k];
- Vector ppq = pc[j].plus(q).timesEquals(MathUtil.SQRTHALF);
- Vector pmq = pc[j].minus(q).timesEquals(MathUtil.SQRTHALF);
- hull.add(ppq);
- hull.add(ppq.times(-1));
- hull.add(pmq);
- hull.add(pmq.times(-1));
- for(int l = k + 1; l < pc.length; l++) {
- Vector r = pc[k];
- Vector ppqpr = ppq.plus(r).timesEquals(Math.sqrt(1 / 3.));
- Vector pmqpr = pmq.plus(r).timesEquals(Math.sqrt(1 / 3.));
- Vector ppqmr = ppq.minus(r).timesEquals(Math.sqrt(1 / 3.));
- Vector pmqmr = pmq.minus(r).timesEquals(Math.sqrt(1 / 3.));
- hull.add(ppqpr);
- hull.add(ppqpr.times(-1));
- hull.add(pmqpr);
- hull.add(pmqpr.times(-1));
- hull.add(ppqmr);
- hull.add(ppqmr.times(-1));
- hull.add(pmqmr);
- hull.add(pmqmr.times(-1));
- }
- }
- diag.plusEquals(pc[j]);
- }
- diag.timesEquals(1.0 / Math.sqrt(pc.length));
- hull.add(diag);
- hull.add(diag.times(-1));
- Polygon chres = hull.getHull();
- return chres;
- }
- protected void drawHullArc(int cnum, Vector cent, Polygon chres) {
- for(int i = 1; i <= times; i++) {
- SVGPath path = new SVGPath();
-
- ArrayList<Vector> delta = new ArrayList<Vector>(chres.size());
- for(int p = 0; p < chres.size(); p++) {
- Vector prev = chres.get((p - 1 + chres.size()) % chres.size());
- Vector curr = chres.get(p);
- Vector next = chres.get((p + 1) % chres.size());
- Vector d1 = next.minus(curr).normalize();
- Vector d2 = curr.minus(prev).normalize();
- delta.add(d1.plus(d2));
- // delta.add(next.minus(prev));
- }
-
- for(int p = 0; p < chres.size(); p++) {
- Vector cur = cent.plus(chres.get(p));
- Vector nex = cent.plus(chres.get((p + 1) % chres.size()));
- Vector dcur = delta.get(p);
- Vector dnex = delta.get((p + 1) % chres.size());
- drawArc(path, cent, cur, nex, dcur, dnex, i);
+ /**
+ * Draw an arc to simulate the hyper ellipse.
+ *
+ * @param path Path to draw to
+ * @param cent Center
+ * @param pre Previous point
+ * @param nex Next point
+ * @param scale Scaling factor
+ */
+ private void drawArc(SVGPath path, Vector cent, Vector pre, Vector nex, Vector oPrev, Vector oNext, double scale) {
+ // Delta vectors
+ final Vector rPrev = pre.minus(cent);
+ final Vector rNext = nex.minus(cent);
+ final Vector rPrNe = pre.minus(nex);
+ // Scaled fix points
+ final Vector sPrev = cent.plusTimes(rPrev, scale);
+ final Vector sNext = cent.plusTimes(rNext, scale);
+ // Orthogonal vectors to the relative vectors
+ // final Vector oPrev = new Vector(rPrev.get(1), -rPrev.get(0));
+ // final Vector oNext = new Vector(-rNext.get(1), rNext.get(0));
+
+ // Compute the intersection of rPrev+tp*oPrev and rNext+tn*oNext
+ // rPrNe == rPrev - rNext
+ final double zp = rPrNe.get(0) * oNext.get(1) - rPrNe.get(1) * oNext.get(0);
+ final double zn = rPrNe.get(0) * oPrev.get(1) - rPrNe.get(1) * oPrev.get(0);
+ final double n = oPrev.get(1) * oNext.get(0) - oPrev.get(0) * oNext.get(1);
+ if(n == 0) {
+ LoggingUtil.warning("Parallel?!?");
+ path.drawTo(sNext.get(0), sNext.get(1));
+ return;
}
- path.close();
+ final double tp = Math.abs(zp / n);
+ final double tn = Math.abs(zn / n);
+ // LoggingUtil.warning("tp: "+tp+" tn: "+tn);
- Element ellipse = path.makeElement(svgp);
+ // Guide points
+ final Vector gPrev = sPrev.plusTimes(oPrev, KAPPA * scale * tp);
+ final Vector gNext = sNext.minusTimes(oNext, KAPPA * scale * tn);
- 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(!path.isStarted()) {
+ path.moveTo(sPrev);
}
- layer.appendChild(ellipse);
+ // path.drawTo(sPrev);
+ // path.drawTo(gPrev);
+ // path.drawTo(gNext);
+ // path.drawTo(sNext));
+ // path.moveTo(sPrev);
+ // if(tp < 0 || tn < 0) {
+ // path.drawTo(sNext);
+ // }
+ // else {
+ path.cubicTo(gPrev, gNext, sNext);
+ // }
}
- }
- /**
- * Draw an arc to simulate the hyper ellipse.
- *
- * @param path Path to draw to
- * @param cent Center
- * @param pre Previous point
- * @param nex Next point
- * @param scale Scaling factor
- */
- private void drawArc(SVGPath path, Vector cent, Vector pre, Vector nex, Vector oPrev, Vector oNext, double scale) {
- // Delta vectors
- final Vector rPrev = pre.minus(cent);
- final Vector rNext = nex.minus(cent);
- final Vector rPrNe = pre.minus(nex);
- // Scaled fix points
- final Vector sPrev = cent.plusTimes(rPrev, scale);
- final Vector sNext = cent.plusTimes(rNext, scale);
- // Orthogonal vectors to the relative vectors
- // final Vector oPrev = new Vector(rPrev.get(1), -rPrev.get(0));
- // final Vector oNext = new Vector(-rNext.get(1), rNext.get(0));
-
- // Compute the intersection of rPrev+tp*oPrev and rNext+tn*oNext
- // rPrNe == rPrev - rNext
- final double zp = rPrNe.get(0) * oNext.get(1) - rPrNe.get(1) * oNext.get(0);
- final double zn = rPrNe.get(0) * oPrev.get(1) - rPrNe.get(1) * oPrev.get(0);
- final double n = oPrev.get(1) * oNext.get(0) - oPrev.get(0) * oNext.get(1);
- if(n == 0) {
- LoggingUtil.warning("Parallel?!?");
- path.drawTo(sNext.get(0), sNext.get(1));
- return;
- }
- final double tp = Math.abs(zp / n);
- final double tn = Math.abs(zn / n);
- // LoggingUtil.warning("tp: "+tp+" tn: "+tn);
-
- // Guide points
- final Vector gPrev = sPrev.plusTimes(oPrev, KAPPA * scale * tp);
- final Vector gNext = sNext.minusTimes(oNext, KAPPA * scale * tn);
-
- if(!path.isStarted()) {
- path.moveTo(sPrev);
- }
- // path.drawTo(sPrev);
- // path.drawTo(gPrev);
- // path.drawTo(gNext);
- // path.drawTo(sNext));
- // path.moveTo(sPrev);
- // if(tp < 0 || tn < 0) {
- // path.drawTo(sNext);
- // }
- // else {
- path.cubicTo(gPrev, gNext, sNext);
- // }
- }
-
- /**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- private void addCSSClasses(SVGPlot svgp) {
- if(!svgp.getCSSClassManager().contains(EMBORDER)) {
- ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT);
- String color;
- int clusterID = 0;
-
- for(@SuppressWarnings("unused")
- Cluster<?> cluster : clustering.getAllClusters()) {
- CSSClass cls = new CSSClass(this, EMBORDER + clusterID);
- cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT) / 2);
-
- if(clustering.getAllClusters().size() == 1) {
- color = "black";
- }
- else {
- color = colors.getColor(clusterID);
- }
- if(softBorder == 0) {
- cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, color);
- }
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, color);
- cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 0.15);
-
- svgp.addCSSClassOrLogError(cls);
- if(opacStyle == 0) {
- break;
- }
- clusterID++;
- }
- }
- }
-
- /**
- * Visualizer for generating SVG-Elements containing ellipses for first,
- * second and third standard deviation
- *
- * @author Robert Rödler
- *
- * @apiviz.stereotype factory
- * @apiviz.uses EMClusterVisualization oneway - - «create»
- *
- * @param <NV> Type of the NumberVector being visualized.
- */
- public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory {
/**
- * Constructor
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
*/
- public Factory() {
- super();
- }
-
- @Override
- public EMClusterVisualization<NV> makeVisualization(VisualizationTask task) {
- return new EMClusterVisualization<NV>(task);
- }
+ private void addCSSClasses(SVGPlot svgp) {
+ if(!svgp.getCSSClassManager().contains(EMBORDER)) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ ColorLibrary colors = style.getColorSet(StyleLibrary.PLOT);
+ String color;
+ int clusterID = 0;
+
+ for(@SuppressWarnings("unused")
+ Cluster<?> cluster : clustering.getAllClusters()) {
+ CSSClass cls = new CSSClass(this, EMBORDER + clusterID);
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT) * .5);
+
+ if(clustering.getAllClusters().size() == 1) {
+ color = "black";
+ }
+ else {
+ color = colors.getColor(clusterID);
+ }
+ if(softBorder == 0) {
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, color);
+ }
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, color);
+ cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 0.15);
- @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<NV>> mcls = findMeanModel(c);
- if(mcls != null) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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);
- baseResult.getHierarchy().add(p, task);
- }
+ svgp.addCSSClassOrLogError(cls);
+ if(opacStyle == 0) {
+ break;
}
+ clusterID++;
}
}
}
-
- /**
- * 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) {
- final Model firstModel = c.getAllClusters().get(0).getModel();
- if(c.getAllClusters().get(0).getModel() instanceof MeanModel<?> && firstModel instanceof EMModel<?>) {
- return (Clustering<MeanModel<NV>>) c;
- }
- return null;
- }
}
-}
+} \ No newline at end of file
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
index d6ce810c..7ae28f42 100644
--- 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
@@ -33,16 +33,17 @@ 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.KMeansModel;
import de.lmu.ifi.dbs.elki.data.model.MeanModel;
import de.lmu.ifi.dbs.elki.data.model.MedoidModel;
import de.lmu.ifi.dbs.elki.data.model.Model;
+import de.lmu.ifi.dbs.elki.database.relation.RelationUtil;
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.optionhandling.AbstractParameterizer;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization;
@@ -65,13 +66,10 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatter
* 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
- * @apiviz.has MedoidModel oneway - - visualizes
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class VoronoiVisualization extends AbstractScatterplotVisualization {
+public class VoronoiVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
@@ -90,238 +88,240 @@ public class VoronoiVisualization extends AbstractScatterplotVisualization {
* @apiviz.exclude
*/
public static enum Mode {
- VORONOI, DELAUNAY, V_AND_D
+ /**
+ * Draw Voronoi cells.
+ */
+ VORONOI,
+ /**
+ * Draw Delaunay triangulation.
+ */
+ DELAUNAY,
+ /**
+ * Draw both Delaunay and Voronoi.
+ */
+ V_AND_D
}
/**
- * The result we work on
+ * Settings.
*/
- Clustering<Model> clustering;
+ private Parameterizer settings;
/**
- * The Voronoi diagram
- */
- Element voronoi;
-
- /**
- * Active drawing mode.
- */
- private Mode mode;
-
- /**
- * Constructor
+ * Constructor.
*
- * @param task VisualizationTask
- * @param mode Drawing mode
+ * @param settings Drawing mode
*/
- public VoronoiVisualization(VisualizationTask task, Mode mode) {
- super(task);
- this.clustering = task.getResult();
- this.mode = mode;
- incrementalRedraw();
+ public VoronoiVisualization(Parameterizer settings) {
+ super();
+ this.settings = settings;
}
@Override
- protected void redraw() {
- addCSSClasses(svgp);
- final List<Cluster<Model>> clusters = clustering.getAllClusters();
-
- if(clusters.size() < 2) {
- return;
- }
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
+ }
- // Collect cluster means
- if(clusters.size() == 2) {
- ArrayList<double[]> means = new ArrayList<double[]>(clusters.size());
- {
- for(Cluster<Model> clus : clusters) {
- Model model = clus.getModel();
- double[] mean;
- if(model instanceof MeanModel) {
- @SuppressWarnings("unchecked")
- MeanModel<? extends NumberVector<?, ?>> mmodel = (MeanModel<? extends NumberVector<?, ?>>) model;
- mean = proj.fastProjectDataToRenderSpace(mmodel.getMean());
- }
- else if(model instanceof MedoidModel) {
- MedoidModel mmodel = (MedoidModel) model;
- mean = proj.fastProjectDataToRenderSpace(rel.get(mmodel.getMedoid()));
- }
- else {
- continue;
- }
- means.add(mean);
- }
- }
- 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(means.get(0)).drawTo(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<Model> clus : clusters) {
- Model model = clus.getModel();
- Vector mean;
- if(model instanceof MeanModel) {
- @SuppressWarnings("unchecked")
- MeanModel<? extends NumberVector<?, ?>> mmodel = (MeanModel<? extends NumberVector<?, ?>>) model;
- mean = mmodel.getMean().getColumnVector();
- }
- else if(model instanceof MedoidModel) {
- MedoidModel mmodel = (MedoidModel) model;
- mean = rel.get(mmodel.getMedoid()).getColumnVector();
- }
- else {
- continue;
+ @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?
+ if (testMeanModel(c)) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for (ScatterPlotProjector<?> p : ps) {
+ if (RelationUtil.dimensionality(p.getRelation()) == 2) {
+ final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA + 3;
+ baseResult.getHierarchy().add(p, task);
+ baseResult.getHierarchy().add(c, task);
+ }
}
- vmeans.add(mean);
- means.add(mean.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
+ * Test if the given clustering has a mean model.
*
- * @param svgp SVG-Plot
+ * @param c Clustering to inspect
+ * @return true when the clustering has a mean or medoid model.
*/
- 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);
+ private static boolean testMeanModel(Clustering<?> c) {
+ Model firstmodel = c.getAllClusters().get(0).getModel();
+ if (firstmodel instanceof KMeansModel<?>) {
+ return true;
+ }
+ if (firstmodel instanceof MedoidModel) {
+ return true;
}
+ return false;
}
/**
- * Factory for visualizers to generate an SVG-Element containing the lines
- * between kMeans clusters
+ * Instance.
*
* @author Robert Rödler
* @author Erich Schubert
*
- * @apiviz.stereotype factory
- * @apiviz.uses VoronoiVisualization oneway - - «create»
+ * @apiviz.has MeanModel oneway - - visualizes
+ * @apiviz.has MedoidModel oneway - - visualizes
*/
- public static class Factory extends AbstractVisFactory {
+ public class Instance extends AbstractScatterplotVisualization {
/**
- * Mode for drawing: Voronoi, Delaunay, both
- *
- * <p>
- * Key: {@code -voronoi.mode}
- * </p>
+ * The result we work on.
*/
- public static final OptionID MODE_ID = OptionID.getOrCreateOptionID("voronoi.mode", "Mode for drawing the voronoi cells (and/or delaunay triangulation)");
+ Clustering<Model> clustering;
/**
- * Drawing mode
+ * The Voronoi diagram.
*/
- private Mode mode;
+ Element voronoi;
/**
- * Constructor
+ * Constructor.
*
- * @param mode Drawing mode
+ * @param task VisualizationTask
*/
- public Factory(Mode mode) {
- super();
- this.mode = mode;
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.clustering = task.getResult();
+ incrementalRedraw();
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new VoronoiVisualization(task, mode);
- }
+ protected void redraw() {
+ addCSSClasses(svgp);
+ final List<Cluster<Model>> clusters = clustering.getAllClusters();
- @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?
- if(testMeanModel(c)) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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);
- }
+ if (clusters.size() < 2) {
+ return;
+ }
+
+ // Collect cluster means
+ if (clusters.size() == 2) {
+ ArrayList<double[]> means = new ArrayList<double[]>(clusters.size());
+ {
+ for (Cluster<Model> clus : clusters) {
+ Model model = clus.getModel();
+ double[] mean;
+ if (model instanceof MeanModel) {
+ @SuppressWarnings("unchecked")
+ MeanModel<? extends NumberVector<?>> mmodel = (MeanModel<? extends NumberVector<?>>) model;
+ mean = proj.fastProjectDataToRenderSpace(mmodel.getMean());
+ } else if (model instanceof MedoidModel) {
+ MedoidModel mmodel = (MedoidModel) model;
+ mean = proj.fastProjectDataToRenderSpace(rel.get(mmodel.getMedoid()));
+ } else {
+ continue;
}
+ means.add(mean);
}
}
+ if (settings.mode == Mode.VORONOI || settings.mode == Mode.V_AND_D) {
+ Element path = VoronoiDraw.drawFakeVoronoi(proj, means).makeElement(svgp);
+ SVGUtil.addCSSClass(path, KMEANSBORDER);
+ layer.appendChild(path);
+ }
+ if (settings.mode == Mode.DELAUNAY || settings.mode == Mode.V_AND_D) {
+ Element path = new SVGPath(means.get(0)).drawTo(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<Model> clus : clusters) {
+ Model model = clus.getModel();
+ Vector mean;
+ if (model instanceof MeanModel) {
+ @SuppressWarnings("unchecked")
+ MeanModel<? extends NumberVector<?>> mmodel = (MeanModel<? extends NumberVector<?>>) model;
+ mean = mmodel.getMean().getColumnVector();
+ } else if (model instanceof MedoidModel) {
+ MedoidModel mmodel = (MedoidModel) model;
+ mean = rel.get(mmodel.getMedoid()).getColumnVector();
+ } else {
+ continue;
+ }
+ vmeans.add(mean);
+ means.add(mean.getArrayRef());
+ }
+ }
+ // Compute Delaunay Triangulation
+ ArrayList<Triangle> delaunay = new SweepHullDelaunay2D(vmeans).getDelaunay();
+ if (settings.mode == Mode.VORONOI || settings.mode == Mode.V_AND_D) {
+ Element path = VoronoiDraw.drawVoronoi(proj, delaunay, means).makeElement(svgp);
+ SVGUtil.addCSSClass(path, KMEANSBORDER);
+ layer.appendChild(path);
+ }
+ if (settings.mode == Mode.DELAUNAY || settings.mode == Mode.V_AND_D) {
+ Element path = VoronoiDraw.drawDelaunay(proj, delaunay, means).makeElement(svgp);
+ SVGUtil.addCSSClass(path, KMEANSBORDER);
+ layer.appendChild(path);
+ }
}
}
/**
- * Test if the given clustering has a mean model.
+ * Adds the required CSS-Classes.
*
- * @param c Clustering to inspect
- * @return true when the clustering has a mean or medoid model.
+ * @param svgp SVG-Plot
*/
- private static boolean testMeanModel(Clustering<?> c) {
- Model firstmodel = c.getAllClusters().get(0).getModel();
- if(firstmodel instanceof MeanModel<?>) {
- return true;
+ private void addCSSClasses(SVGPlot svgp) {
+ // Class for the distance markers
+ if (!svgp.getCSSClassManager().contains(KMEANSBORDER)) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ 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, style.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);
}
- if(firstmodel instanceof MedoidModel) {
- return true;
- }
- return false;
}
+ }
+ /**
+ * Parameterization class.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ public static class Parameterizer extends AbstractParameterizer {
/**
- * Parameterization class.
+ * Mode for drawing: Voronoi, Delaunay, both.
*
- * @author Erich Schubert
- *
- * @apiviz.exclude
+ * <p>
+ * Key: {@code -voronoi.mode}
+ * </p>
*/
- public static class Parameterizer extends AbstractParameterizer {
- protected Mode mode;
+ public static final OptionID MODE_ID = new OptionID("voronoi.mode", "Mode for drawing the voronoi cells (and/or delaunay triangulation)");
- @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();
- }
- }
+ /**
+ * Drawing mode.
+ */
+ protected Mode mode;
- @Override
- protected Factory makeInstance() {
- return new Factory(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 VoronoiVisualization makeInstance() {
+ return new VoronoiVisualization(this);
+ }
}
-} \ 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
index c07bc571..96658910 100644
--- 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
@@ -52,184 +52,184 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatter
* <em>in the projection, not the actual data!</em>
*
* @author Erich Schubert
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-// TODO: make parameterizable, in particular color map, kernel bandwidth and
-// kernel function
-public class DensityEstimationOverlay extends AbstractScatterplotVisualization {
+public class DensityEstimationOverlay extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
private static final String NAME = "Density estimation overlay";
/**
- * Density map resolution
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
*/
- private int resolution = 500;
+ public DensityEstimationOverlay() {
+ super();
+ }
- /**
- * The actual image
- */
- private BufferedImage img = null;
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(result, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, p.getRelation(), p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA + 1;
+ task.initDefaultVisibility(false);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
/**
- * Constructor.
+ * Instance for a particular data set.
*
- * @param task Task
+ * @author Erich Schubert
*/
- public DensityEstimationOverlay(VisualizationTask task) {
- super(task);
- incrementalRedraw();
- }
+ // TODO: make parameterizable, in particular color map, kernel bandwidth and
+ // kernel function
+ public class Instance extends AbstractScatterplotVisualization {
+ /**
+ * Density map resolution
+ */
+ private int resolution = 500;
- @Override
- protected void redraw() {
- if(img == null) {
- renderImage();
+ /**
+ * The actual image
+ */
+ private BufferedImage img = null;
+
+ /**
+ * Constructor.
+ *
+ * @param task Task
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ incrementalRedraw();
}
- 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);
- }
+ @Override
+ protected void redraw() {
+ if(img == null) {
+ renderImage();
+ }
- @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]);
+ 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);
}
- // 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(DBIDIter iditer = rel.iterDBIDs(); iditer.valid(); iditer.advance()) {
- data[i] = proj.fastProjectDataToRenderSpace(rel.get(iditer));
- i++;
+ @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;
}
- 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]);
+
+ 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(DBIDIter iditer = rel.iterDBIDs(); iditer.valid(); iditer.advance()) {
+ data[i] = proj.fastProjectDataToRenderSpace(rel.get(iditer));
+ i++;
+ }
}
- };
- // 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);
+ 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]);
}
- maxdens = Math.max(maxdens, dens[x][y]);
+ // Restore original sorting, as the intervals overlap
+ Arrays.sort(data, loff, roff, comp0);
}
- // 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);
+ 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 DensityEstimationOverlay 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) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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);
+ private int unflip(int binarySearch) {
+ if(binarySearch < 0) {
+ return (-binarySearch) - 1;
+ }
+ else {
+ return binarySearch;
}
}
}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeMBRVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeMBRVisualization.java
index 61635625..9d466a8f 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeMBRVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeMBRVisualization.java
@@ -60,14 +60,10 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatter
*
* @author Erich Schubert
*
- * @apiviz.has AbstractRStarTree oneway - - visualizes
- * @apiviz.uses SVGHyperCube
- *
- * @param <N> Tree node type
- * @param <E> Tree entry type
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-// TODO: listen for tree changes instead of data changes?
-public class TreeMBRVisualization<N extends AbstractRStarTreeNode<N, E>, E extends SpatialEntry> extends AbstractScatterplotVisualization implements DataStoreListener {
+public class TreeMBRVisualization extends AbstractVisFactory {
/**
* Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc.
*/
@@ -79,109 +75,152 @@ public class TreeMBRVisualization<N extends AbstractRStarTreeNode<N, E>, E exten
public static final String NAME = "Index MBRs";
/**
- * Fill parameter.
- */
- protected boolean fill = false;
-
- /**
- * The tree we visualize
+ * Settings
*/
- protected AbstractRStarTree<N, E> tree;
+ protected Parameterizer settings;
/**
* Constructor.
*
- * @param task Visualization task
- * @param fill Fill flag
+ * @param settings Settings
*/
- @SuppressWarnings("unchecked")
- public TreeMBRVisualization(VisualizationTask task, boolean fill) {
- super(task);
- this.tree = AbstractRStarTree.class.cast(task.getResult());
- this.fill = fill;
- incrementalRedraw();
- context.addDataStoreListener(this);
+ public TreeMBRVisualization(Parameterizer settings) {
+ super();
+ this.settings = settings;
}
@Override
- protected void redraw() {
- int projdim = proj.getVisibleDimensions2D().cardinality();
- ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT);
-
- if(tree != null) {
- E root = tree.getRootEntry();
- for(int i = 0; i < tree.getHeight(); 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.1 / (projdim - 1));
- }
- 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);
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance<RStarTreeNode, SpatialEntry>(task);
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<AbstractRStarTree<RStarTreeNode, SpatialEntry>> trees = ResultUtil.filterResults(result, AbstractRStarTree.class);
+ for(AbstractRStarTree<RStarTreeNode, SpatialEntry> tree : trees) {
+ if(tree instanceof Result) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, (Result) tree, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_BACKGROUND + 1;
+ baseResult.getHierarchy().add((Result) tree, task);
+ baseResult.getHierarchy().add(p, task);
}
- 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);
}
- visualizeRTreeEntry(svgp, layer, proj, tree, root, 0);
}
}
/**
- * Recursively draw the MBR rectangles.
+ * Instance for a particular tree
*
- * @param svgp SVG Plot
- * @param layer Layer
- * @param proj Projection
- * @param rtree Rtree to visualize
- * @param entry Current entry
- * @param depth Current depth
+ * @author Erich Schubert
+ *
+ * @apiviz.has AbstractRStarTree oneway - - visualizes
+ * @apiviz.uses SVGHyperCube
+ *
+ * @param <N> Tree node type
+ * @param <E> Tree entry type
*/
- private void visualizeRTreeEntry(SVGPlot svgp, Element layer, Projection2D proj, AbstractRStarTree<? extends N, E> rtree, E entry, int depth) {
- SpatialComparable mbr = entry;
+ // TODO: listen for tree changes instead of data changes?
+ public class Instance<N extends AbstractRStarTreeNode<N, E>, E extends SpatialEntry> extends AbstractScatterplotVisualization implements DataStoreListener {
+ /**
+ * The tree we visualize
+ */
+ protected AbstractRStarTree<N, E> tree;
- if(fill) {
- Element r = SVGHyperCube.drawFilled(svgp, INDEX + depth, proj, SpatialUtil.getMin(mbr), SpatialUtil.getMax(mbr));
- layer.appendChild(r);
+ /**
+ * Constructor.
+ *
+ * @param task Visualization task
+ */
+ @SuppressWarnings("unchecked")
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.tree = AbstractRStarTree.class.cast(task.getResult());
+ incrementalRedraw();
+ context.addDataStoreListener(this);
}
- else {
- Element r = SVGHyperCube.drawFrame(svgp, proj, SpatialUtil.getMin(mbr), SpatialUtil.getMax(mbr));
- SVGUtil.setCSSClass(r, INDEX + depth);
- layer.appendChild(r);
+
+ @Override
+ protected void redraw() {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ int projdim = proj.getVisibleDimensions2D().cardinality();
+ ColorLibrary colors = style.getColorSet(StyleLibrary.PLOT);
+
+ if(tree != null) {
+ E root = tree.getRootEntry();
+ for(int i = 0; i < tree.getHeight(); 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(settings.fill) {
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(i));
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, relDepth * style.getLineWidth(StyleLibrary.PLOT));
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, colors.getColor(i));
+ cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 0.1 / (projdim - 1));
+ }
+ else {
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(i));
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, relDepth * style.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);
+ }
+ visualizeRTreeEntry(svgp, layer, proj, tree, root, 0);
+ }
}
- 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);
+ /**
+ * 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, Projection2D proj, AbstractRStarTree<? extends N, E> rtree, E entry, int depth) {
+ SpatialComparable mbr = entry;
+
+ if(settings.fill) {
+ Element r = SVGHyperCube.drawFilled(svgp, INDEX + depth, proj, SpatialUtil.getMin(mbr), SpatialUtil.getMax(mbr));
+ layer.appendChild(r);
+ }
+ else {
+ Element r = SVGHyperCube.drawFrame(svgp, proj, SpatialUtil.getMin(mbr), SpatialUtil.getMax(mbr));
+ SVGUtil.setCSSClass(r, INDEX + depth);
+ layer.appendChild(r);
+ }
+
+ 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);
+ }
}
}
}
- }
- @Override
- public void destroy() {
- super.destroy();
- context.removeDataStoreListener(this);
+ @Override
+ public void destroy() {
+ super.destroy();
+ context.removeDataStoreListener(this);
+ }
}
/**
- * Factory
+ * Parameterization class.
*
* @author Erich Schubert
*
- * @apiviz.stereotype factory
- * @apiviz.uses TreeMBRVisualization oneway - - «create»
+ * @apiviz.exclude
*/
- public static class Factory extends AbstractVisFactory {
+ public static class Parameterizer extends AbstractParameterizer {
/**
* Flag for half-transparent filling of bubbles.
*
@@ -189,67 +228,22 @@ public class TreeMBRVisualization<N extends AbstractRStarTreeNode<N, E>, E exten
* Key: {@code -index.fill}
* </p>
*/
- public static final OptionID FILL_ID = OptionID.getOrCreateOptionID("index.fill", "Partially transparent filling of index pages.");
+ public static final OptionID FILL_ID = new OptionID("index.fill", "Partially transparent filling of index pages.");
- /**
- * Fill parameter.
- */
protected boolean fill = false;
- /**
- * Constructor.
- *
- * @param fill
- */
- public Factory(boolean fill) {
- super();
- this.fill = fill;
- }
-
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new TreeMBRVisualization<RStarTreeNode, SpatialEntry>(task, fill);
- }
-
- @Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<AbstractRStarTree<RStarTreeNode, SpatialEntry>> trees = ResultUtil.filterResults(result, AbstractRStarTree.class);
- for(AbstractRStarTree<RStarTreeNode, SpatialEntry> tree : trees) {
- if(tree instanceof Result) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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);
- baseResult.getHierarchy().add(p, task);
- }
- }
+ protected void makeOptions(Parameterization config) {
+ super.makeOptions(config);
+ Flag fillF = new Flag(FILL_ID);
+ if(config.grab(fillF)) {
+ fill = fillF.isTrue();
}
}
- /**
- * Parameterization class.
- *
- * @author Erich Schubert
- *
- * @apiviz.exclude
- */
- public static class Parameterizer extends AbstractParameterizer {
- protected boolean fill = false;
-
- @Override
- protected void makeOptions(Parameterization config) {
- super.makeOptions(config);
- Flag fillF = new Flag(FILL_ID);
- if(config.grab(fillF)) {
- fill = fillF.getValue();
- }
- }
-
- @Override
- protected Factory makeInstance() {
- return new Factory(fill);
- }
+ @Override
+ protected TreeMBRVisualization makeInstance() {
+ return new TreeMBRVisualization(this);
}
}
} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeSphereVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeSphereVisualization.java
index daebab02..e89be028 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeSphereVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeSphereVisualization.java
@@ -65,14 +65,10 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatter
*
* @author Erich Schubert
*
- * @apiviz.has AbstractMTree oneway - - visualizes
- * @apiviz.uses SVGHyperSphere
- *
- * @param <N> Tree node type
- * @param <E> Tree entry type
+ * @apiviz.stereotype factory
+ * @apiviz.uses TreeSphereVisualization oneway - - «create»
*/
-// TODO: listen for tree changes!
-public class TreeSphereVisualization<D extends NumberDistance<D, ?>, N extends AbstractMTreeNode<?, D, N, E>, E extends MTreeEntry<D>> extends AbstractScatterplotVisualization implements DataStoreListener {
+public class TreeSphereVisualization extends AbstractVisFactory {
/**
* Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc.
*/
@@ -92,37 +88,40 @@ public class TreeSphereVisualization<D extends NumberDistance<D, ?>, N extends A
MANHATTAN, EUCLIDEAN, LPCROSS
}
- protected double p;
-
/**
- * Drawing mode (distance) to use
+ * Settings
*/
- protected Modus dist = Modus.LPCROSS;
+ protected Parameterizer settings;
/**
- * The tree we visualize
+ * Constructor.
+ *
+ * @param settings Settings
*/
- protected AbstractMTree<?, D, N, E> tree;
+ public TreeSphereVisualization(Parameterizer settings) {
+ super();
+ this.settings = settings;
+ }
- /**
- * Fill parameter.
- */
- protected boolean fill = false;
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for (ScatterPlotProjector<?> p : ps) {
+ Collection<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.level = VisualizationTask.LEVEL_BACKGROUND + 1;
+ baseResult.getHierarchy().add((Result) tree, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ }
+ }
- /**
- * Constructor
- *
- * @param task Task
- * @param fill fill flag
- */
- @SuppressWarnings("unchecked")
- public TreeSphereVisualization(VisualizationTask task, boolean fill) {
- super(task);
- this.tree = AbstractMTree.class.cast(task.getResult());
- this.p = getLPNormP(this.tree);
- this.fill = fill;
- incrementalRedraw();
- context.addDataStoreListener(this);
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance<DoubleDistance, MTreeNode<Object, DoubleDistance>, MTreeEntry<DoubleDistance>>(task);
}
/**
@@ -131,14 +130,14 @@ public class TreeSphereVisualization<D extends NumberDistance<D, ?>, N extends A
* @param tree Tree to visualize
* @return p value
*/
- public static Double getLPNormP(AbstractMTree<?, ?, ?, ?> tree) {
- // Note: we deliberately lose generics here, so the compilers complain less
- // on the next typecheck and cast!
+ public static double getLPNormP(AbstractMTree<?, ?, ?, ?> tree) {
+ // Note: we deliberately lose generics here, so the compilers complain
+ // less on the next typecheck and cast!
DistanceFunction<?, ?> distanceFunction = tree.getDistanceQuery().getDistanceFunction();
- if(LPNormDistanceFunction.class.isInstance(distanceFunction)) {
+ if (LPNormDistanceFunction.class.isInstance(distanceFunction)) {
return ((LPNormDistanceFunction) distanceFunction).getP();
}
- return null;
+ return 0;
}
/**
@@ -148,170 +147,160 @@ public class TreeSphereVisualization<D extends NumberDistance<D, ?>, N extends A
* @return whether the tree is visualizable
*/
public static boolean canVisualize(AbstractMTree<?, ?, ?, ?> tree) {
- Double p = getLPNormP(tree);
- return (p != null);
- }
-
- @Override
- protected void redraw() {
- int projdim = proj.getVisibleDimensions2D().cardinality();
- ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT);
-
- p = getLPNormP(tree);
- if(tree != null) {
- if(ManhattanDistanceFunction.class.isInstance(tree.getDistanceQuery())) {
- dist = Modus.MANHATTAN;
- }
- else if(EuclideanDistanceFunction.class.isInstance(tree.getDistanceQuery())) {
- dist = Modus.EUCLIDEAN;
- }
- else {
- dist = Modus.LPCROSS;
- }
- E root = tree.getRootEntry();
- final int mtheight = tree.getHeight();
- for(int i = 0; i < mtheight; i++) {
- CSSClass cls = new CSSClass(this, INDEX + i);
- // Relative depth of this level. 1.0 = toplevel
- final double relDepth = 1. - (((double) i) / mtheight);
- 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.1 / (projdim - 1));
- cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
- cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
- }
- 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);
- }
- visualizeMTreeEntry(svgp, this.layer, proj, tree, root, 0);
- }
+ return getLPNormP(tree) > 0;
}
/**
- * Recursively draw the MBR rectangles.
- *
- * @param svgp SVG Plot
- * @param layer Layer
- * @param proj Projection
- * @param mtree Mtree to visualize
- * @param entry Current entry
- * @param depth Current 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) {
- NumberVector<?, ?> ro = rel.get(roid);
- D rad = entry.getCoveringRadius();
-
- final Element r;
- if(dist == Modus.MANHATTAN) {
- r = SVGHyperSphere.drawManhattan(svgp, proj, ro, rad);
- }
- else if(dist == Modus.EUCLIDEAN) {
- r = SVGHyperSphere.drawEuclidean(svgp, proj, ro, rad);
- }
- // TODO: add visualizer for infinity norm?
- else {
- // r = SVGHyperSphere.drawCross(svgp, proj, ro, rad);
- r = SVGHyperSphere.drawLp(svgp, proj, ro, rad, p);
- }
- SVGUtil.setCSSClass(r, INDEX + (depth - 1));
- layer.appendChild(r);
- }
-
- if(!entry.isLeafEntry()) {
- N node = mtree.getNode(entry);
- for(int i = 0; i < node.getNumEntries(); i++) {
- E child = node.getEntry(i);
- if(!child.isLeafEntry()) {
- visualizeMTreeEntry(svgp, layer, proj, mtree, child, depth + 1);
- }
- }
- }
- }
-
- @Override
- public void destroy() {
- super.destroy();
- context.removeDataStoreListener(this);
- }
-
- /**
- * Factory
+ * Instance for a particular tree.
*
* @author Erich Schubert
*
- * @apiviz.stereotype factory
- * @apiviz.uses TreeSphereVisualization oneway - - «create»
+ * @apiviz.has AbstractMTree oneway - - visualizes
+ * @apiviz.uses SVGHyperSphere
+ *
+ * @param <N> Tree node type
+ * @param <E> Tree entry type
*/
- public static class Factory extends AbstractVisFactory {
+ // TODO: listen for tree changes!
+ public class Instance<D extends NumberDistance<D, ?>, N extends AbstractMTreeNode<?, D, N, E>, E extends MTreeEntry<D>> extends AbstractScatterplotVisualization implements DataStoreListener {
+ protected double p;
+
/**
- * Fill parameter.
+ * Drawing mode (distance) to use
*/
- protected boolean fill = false;
+ protected Modus dist = Modus.LPCROSS;
+
+ /**
+ * The tree we visualize
+ */
+ protected AbstractMTree<?, D, N, E> tree;
/**
- * Constructor.
+ * Constructor
*
- * @param fill
+ * @param task Task
*/
- public Factory(boolean fill) {
- super();
- this.fill = fill;
+ @SuppressWarnings("unchecked")
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.tree = AbstractMTree.class.cast(task.getResult());
+ this.p = getLPNormP(this.tree);
+ incrementalRedraw();
+ context.addDataStoreListener(this);
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : ps) {
- Collection<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);
- baseResult.getHierarchy().add((Result) tree, task);
- baseResult.getHierarchy().add(p, task);
+ protected void redraw() {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ int projdim = proj.getVisibleDimensions2D().cardinality();
+ ColorLibrary colors = style.getColorSet(StyleLibrary.PLOT);
+
+ p = getLPNormP(tree);
+ if (tree != null) {
+ if (ManhattanDistanceFunction.class.isInstance(tree.getDistanceQuery())) {
+ dist = Modus.MANHATTAN;
+ } else if (EuclideanDistanceFunction.class.isInstance(tree.getDistanceQuery())) {
+ dist = Modus.EUCLIDEAN;
+ } else {
+ dist = Modus.LPCROSS;
+ }
+ E root = tree.getRootEntry();
+ final int mtheight = tree.getHeight();
+ for (int i = 0; i < mtheight; i++) {
+ CSSClass cls = new CSSClass(this, INDEX + i);
+ // Relative depth of this level. 1.0 = toplevel
+ final double relDepth = 1. - (((double) i) / mtheight);
+ if (settings.fill) {
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(i));
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, relDepth * style.getLineWidth(StyleLibrary.PLOT));
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, colors.getColor(i));
+ cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 0.1 / (projdim - 1));
+ cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ } else {
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(i));
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, relDepth * style.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);
}
+ visualizeMTreeEntry(svgp, this.layer, proj, tree, root, 0);
}
}
- @Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new TreeSphereVisualization<DoubleDistance, MTreeNode<Object, DoubleDistance>, MTreeEntry<DoubleDistance>>(task, fill);
- }
-
/**
- * Parameterization class.
+ * Recursively draw the MBR rectangles.
*
- * @author Erich Schubert
- *
- * @apiviz.exclude
+ * @param svgp SVG Plot
+ * @param layer Layer
+ * @param proj Projection
+ * @param mtree Mtree to visualize
+ * @param entry Current entry
+ * @param depth Current depth
*/
- public static class Parameterizer extends AbstractParameterizer {
- protected boolean fill = false;
+ 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) {
+ NumberVector<?> ro = rel.get(roid);
+ D rad = entry.getCoveringRadius();
- @Override
- protected void makeOptions(Parameterization config) {
- super.makeOptions(config);
- Flag fillF = new Flag(TreeMBRVisualization.Factory.FILL_ID);
- if(config.grab(fillF)) {
- fill = fillF.getValue();
+ final Element r;
+ if (dist == Modus.MANHATTAN) {
+ r = SVGHyperSphere.drawManhattan(svgp, proj, ro, rad);
+ } else if (dist == Modus.EUCLIDEAN) {
+ r = SVGHyperSphere.drawEuclidean(svgp, proj, ro, rad);
+ }
+ // TODO: add visualizer for infinity norm?
+ else {
+ // r = SVGHyperSphere.drawCross(svgp, proj, ro, rad);
+ r = SVGHyperSphere.drawLp(svgp, proj, ro, rad, p);
}
+ SVGUtil.setCSSClass(r, INDEX + (depth - 1));
+ layer.appendChild(r);
}
- @Override
- protected Factory makeInstance() {
- return new Factory(fill);
+ if (!entry.isLeafEntry()) {
+ N node = mtree.getNode(entry);
+ for (int i = 0; i < node.getNumEntries(); i++) {
+ E child = node.getEntry(i);
+ if (!child.isLeafEntry()) {
+ visualizeMTreeEntry(svgp, layer, proj, mtree, child, depth + 1);
+ }
+ }
}
}
+
+ @Override
+ public void destroy() {
+ super.destroy();
+ context.removeDataStoreListener(this);
+ }
+ }
+
+ /**
+ * Parameterization class.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ public static class Parameterizer extends AbstractParameterizer {
+ protected boolean fill = false;
+
+ @Override
+ protected void makeOptions(Parameterization config) {
+ super.makeOptions(config);
+ Flag fillF = new Flag(TreeMBRVisualization.Parameterizer.FILL_ID);
+ if (config.grab(fillF)) {
+ fill = fillF.isTrue();
+ }
+ }
+
+ @Override
+ protected TreeSphereVisualization makeInstance() {
+ return new TreeSphereVisualization(this);
+ }
}
-} \ No newline at end of file
+}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/outlier/BubbleVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/outlier/BubbleVisualization.java
index 769dc53d..ebe9612d 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/outlier/BubbleVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/outlier/BubbleVisualization.java
@@ -50,6 +50,7 @@ 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.StyleResult;
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;
@@ -66,10 +67,11 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualizati
* @author Remigius Wojdanowski
* @author Erich Schubert
*
- * @apiviz.has OutlierResult oneway - - visualizes
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
@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 {
+public class BubbleVisualization extends AbstractVisFactory {
/**
* Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc.
*/
@@ -81,160 +83,207 @@ public class BubbleVisualization extends AbstractScatterplotVisualization implem
public static final String NAME = "Outlier Bubbles";
/**
- * Fill parameter.
+ * Current settings
*/
- protected boolean fill;
-
- /**
- * Scaling function to use for Bubbles
- */
- protected ScalingFunction scaling;
-
- /**
- * The outlier result to visualize
- */
- protected OutlierResult result;
+ protected Parameterizer settings;
/**
* Constructor.
*
- * @param task Visualization task
- * @param scaling Scaling function
- * @param fill Fill flag
+ * @param settings Settings
*/
- public BubbleVisualization(VisualizationTask task, ScalingFunction scaling, boolean fill) {
- super(task);
- this.result = task.getResult();
- this.scaling = scaling;
- this.fill = fill;
- context.addDataStoreListener(this);
- context.addResultListener(this);
- incrementalRedraw();
+ public BubbleVisualization(Parameterizer settings) {
+ super();
+ this.settings = settings;
+ thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_STYLE;
}
@Override
- public void destroy() {
- super.destroy();
- context.removeResultListener(this);
- context.removeDataStoreListener(this);
+ public Visualization makeVisualization(VisualizationTask task) {
+ if(settings.scaling != null && settings.scaling instanceof OutlierScalingFunction) {
+ final OutlierResult outlierResult = task.getResult();
+ ((OutlierScalingFunction) settings.scaling).prepare(outlierResult);
+ }
+ return new Instance(task);
}
@Override
- public void redraw() {
- StylingPolicy stylepolicy = context.getStyleResult().getStylingPolicy();
- // bubble size
- final double bubble_size = context.getStyleLibrary().getSize(StyleLibrary.BUBBLEPLOT);
- if(stylepolicy instanceof ClassStylingPolicy) {
- ClassStylingPolicy colors = (ClassStylingPolicy) stylepolicy;
- setupCSS(svgp, colors);
- // draw data
- for(DBIDIter objId = sample.getSample().iter(); objId.valid(); objId.advance()) {
- 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);
- }
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<OutlierResult> ors = ResultUtil.filterResults(result, OutlierResult.class);
+ for(OutlierResult o : ors) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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.
+ for(Result r : o.getHierarchy().getParents(o)) {
+ if(r instanceof OutlierResult) {
+ vis = false;
+ break;
}
}
- }
- else {
- // draw data
- for(DBIDIter objId = sample.getSample().iter(); objId.valid(); objId.advance()) {
- 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);
- 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);
- }
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, o, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA;
+ if(!vis) {
+ task.initDefaultVisibility(false);
}
+ baseResult.getHierarchy().add(o, task);
+ baseResult.getHierarchy().add(p, task);
}
}
}
- @Override
- public void resultChanged(Result current) {
- super.resultChanged(current);
- if(sample == current || context.getStyleResult() == current) {
- synchronizedRedraw();
- }
- }
-
/**
- * Registers the Bubble-CSS-Class at a SVGPlot.
+ * Factory for producing bubble visualizations
*
- * @param svgp the SVGPlot to register the Tooltip-CSS-Class.
- * @param policy Clustering to use
+ * @author Erich Schubert
+ *
+ * @apiviz.has OutlierResult oneway - - visualizes
*/
- private void setupCSS(SVGPlot svgp, ClassStylingPolicy policy) {
- ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT);
+ public class Instance extends AbstractScatterplotVisualization implements DataStoreListener {
+ /**
+ * The outlier result to visualize
+ */
+ protected OutlierResult result;
- // creating IDs manually because cluster often return a null-ID.
- 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));
+ /**
+ * Constructor.
+ *
+ * @param task Visualization task
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.result = task.getResult();
+ context.addDataStoreListener(this);
+ context.addResultListener(this);
+ incrementalRedraw();
+ }
- String color = colors.getColor(clusterID);
+ @Override
+ public void destroy() {
+ super.destroy();
+ context.removeResultListener(this);
+ context.removeDataStoreListener(this);
+ }
- if(fill) {
- bubble.setStatement(SVGConstants.CSS_FILL_PROPERTY, color);
- bubble.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 0.5);
+ @Override
+ public void redraw() {
+ final StyleResult style = context.getStyleResult();
+ StylingPolicy stylepolicy = style.getStylingPolicy();
+ // bubble size
+ final double bubble_size = style.getStyleLibrary().getSize(StyleLibrary.BUBBLEPLOT);
+ if(stylepolicy instanceof ClassStylingPolicy) {
+ ClassStylingPolicy colors = (ClassStylingPolicy) stylepolicy;
+ setupCSS(svgp, colors);
+ // draw data
+ for(DBIDIter objId = sample.getSample().iter(); objId.valid(); objId.advance()) {
+ 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 {
- // for diamond-shaped strokes, see bugs.sun.com, bug ID 6294396
- bubble.setStatement(SVGConstants.CSS_STROKE_VALUE, color);
- bubble.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ // draw data
+ for(DBIDIter objId = sample.getSample().iter(); objId.valid(); objId.advance()) {
+ 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);
+ int color = stylepolicy.getColorForDBID(objId);
+ final StringBuilder cssstyle = new StringBuilder();
+ if(settings.fill) {
+ cssstyle.append(SVGConstants.CSS_FILL_PROPERTY).append(':').append(SVGUtil.colorToString(color));
+ cssstyle.append(SVGConstants.CSS_FILL_OPACITY_PROPERTY).append(":0.5");
+ }
+ else {
+ cssstyle.append(SVGConstants.CSS_STROKE_VALUE).append(':').append(SVGUtil.colorToString(color));
+ cssstyle.append(SVGConstants.CSS_FILL_PROPERTY).append(':').append(SVGConstants.CSS_NONE_VALUE);
+ }
+ SVGUtil.setAtt(circle, SVGConstants.SVG_STYLE_ATTRIBUTE, cssstyle.toString());
+ layer.appendChild(circle);
+ }
+ }
+ }
}
-
- svgp.addCSSClassOrLogError(bubble);
}
- }
- /**
- * Convenience method to apply scalings in the right order.
- *
- * @param id object ID to get scaled score for
- * @return a Double representing a outlierness-score, after it has modified by
- * the given scales.
- */
- protected double getScaledForId(DBIDRef id) {
- double d = result.getScores().get(id).doubleValue();
- if(Double.isNaN(d) || Double.isInfinite(d)) {
- return 0.0;
+ @Override
+ public void resultChanged(Result current) {
+ super.resultChanged(current);
+ if(sample == current || context.getStyleResult() == current) {
+ synchronizedRedraw();
+ }
}
- if(scaling == null) {
- return result.getOutlierMeta().normalizeScore(d);
+
+ /**
+ * Registers the Bubble-CSS-Class at a SVGPlot.
+ *
+ * @param svgp the SVGPlot to register the Tooltip-CSS-Class.
+ * @param policy Clustering to use
+ */
+ private void setupCSS(SVGPlot svgp, ClassStylingPolicy policy) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ ColorLibrary colors = style.getColorSet(StyleLibrary.PLOT);
+
+ // creating IDs manually because cluster often return a null-ID.
+ for(int clusterID = policy.getMinStyle(); clusterID < policy.getMaxStyle(); clusterID++) {
+ CSSClass bubble = new CSSClass(svgp, BUBBLE + clusterID);
+ bubble.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT));
+
+ String color = colors.getColor(clusterID);
+
+ if(settings.fill) {
+ bubble.setStatement(SVGConstants.CSS_FILL_PROPERTY, color);
+ bubble.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 0.5);
+ }
+ else {
+ // for diamond-shaped strokes, see bugs.sun.com, bug ID 6294396
+ bubble.setStatement(SVGConstants.CSS_STROKE_VALUE, color);
+ bubble.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ }
+
+ svgp.addCSSClassOrLogError(bubble);
+ }
}
- else {
- return scaling.getScaled(d);
+
+ /**
+ * Convenience method to apply scalings in the right order.
+ *
+ * @param id object ID to get scaled score for
+ * @return a Double representing a outlierness-score, after it has modified
+ * by the given scales.
+ */
+ protected double getScaledForId(DBIDRef id) {
+ double d = result.getScores().get(id).doubleValue();
+ if(Double.isNaN(d) || Double.isInfinite(d)) {
+ return 0.0;
+ }
+ if(settings.scaling == null) {
+ return result.getOutlierMeta().normalizeScore(d);
+ }
+ else {
+ return settings.scaling.getScaled(d);
+ }
}
}
/**
- * Factory for producing bubble visualizations
+ * Parameterization class.
*
* @author Erich Schubert
*
- * @apiviz.stereotype factory
- * @apiviz.uses BubbleVisualization oneway - - «create»
+ * @apiviz.exclude
*/
- public static class Factory extends AbstractVisFactory {
+ public static class Parameterizer extends AbstractParameterizer {
/**
* Flag for half-transparent filling of bubbles.
*
@@ -242,7 +291,7 @@ public class BubbleVisualization extends AbstractScatterplotVisualization implem
* Key: {@code -bubble.fill}
* </p>
*/
- public static final OptionID FILL_ID = OptionID.getOrCreateOptionID("bubble.fill", "Half-transparent filling of bubbles.");
+ public static final OptionID FILL_ID = new OptionID("bubble.fill", "Half-transparent filling of bubbles.");
/**
* Parameter for scaling functions
@@ -251,7 +300,7 @@ public class BubbleVisualization extends AbstractScatterplotVisualization implem
* Key: {@code -bubble.scaling}
* </p>
*/
- public static final OptionID SCALING_ID = OptionID.getOrCreateOptionID("bubble.scaling", "Additional scaling function for bubbles.");
+ public static final OptionID SCALING_ID = new OptionID("bubble.scaling", "Additional scaling function for bubbles.");
/**
* Fill parameter.
@@ -263,90 +312,23 @@ public class BubbleVisualization extends AbstractScatterplotVisualization implem
*/
protected ScalingFunction scaling;
- /**
- * Constructor.
- *
- * @param fill
- * @param scaling
- */
- public Factory(boolean fill, ScalingFunction scaling) {
- super();
- this.fill = fill;
- this.scaling = scaling;
- thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_STYLE;
- }
-
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- if(this.scaling != null && this.scaling instanceof OutlierScalingFunction) {
- final OutlierResult outlierResult = task.getResult();
- ((OutlierScalingFunction) this.scaling).prepare(outlierResult);
+ protected void makeOptions(Parameterization config) {
+ super.makeOptions(config);
+ Flag fillF = new Flag(FILL_ID);
+ if(config.grab(fillF)) {
+ fill = fillF.isTrue();
}
- return new BubbleVisualization(task, scaling, fill);
- }
- @Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<OutlierResult> ors = ResultUtil.filterResults(result, OutlierResult.class);
- for(OutlierResult o : ors) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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.
- for(Result r : o.getHierarchy().getParents(o)) {
- if(r instanceof OutlierResult) {
- vis = false;
- break;
- }
- }
- for(ScatterPlotProjector<?> p : ps) {
- final VisualizationTask task = new VisualizationTask(NAME, o, p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA);
- if(!vis) {
- task.put(VisualizationTask.META_VISIBLE_DEFAULT, false);
- }
- baseResult.getHierarchy().add(o, task);
- baseResult.getHierarchy().add(p, task);
- }
+ ObjectParameter<ScalingFunction> scalingP = new ObjectParameter<ScalingFunction>(SCALING_ID, OutlierScalingFunction.class, true);
+ if(config.grab(scalingP)) {
+ scaling = scalingP.instantiateClass(config);
}
}
- /**
- * Parameterization class.
- *
- * @author Erich Schubert
- *
- * @apiviz.exclude
- */
- public static class Parameterizer extends AbstractParameterizer {
- /**
- * Fill parameter.
- */
- protected boolean fill = false;
-
- /**
- * Scaling function to use for Bubbles
- */
- protected ScalingFunction scaling = null;
-
- @Override
- protected void makeOptions(Parameterization config) {
- super.makeOptions(config);
- Flag fillF = new Flag(FILL_ID);
- if(config.grab(fillF)) {
- fill = fillF.getValue();
- }
-
- ObjectParameter<ScalingFunction> scalingP = new ObjectParameter<ScalingFunction>(SCALING_ID, OutlierScalingFunction.class, true);
- if(config.grab(scalingP)) {
- scaling = scalingP.instantiateClass(config);
- }
- }
-
- @Override
- protected Factory makeInstance() {
- return new Factory(fill, scaling);
- }
+ @Override
+ protected BubbleVisualization makeInstance() {
+ return new BubbleVisualization(this);
}
}
} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/outlier/COPVectorVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/outlier/COPVectorVisualization.java
new file mode 100644
index 00000000..e953f9ae
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/outlier/COPVectorVisualization.java
@@ -0,0 +1,190 @@
+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) 2012
+ Ludwig-Maximilians-Universität München
+ Lehr- und Forschungseinheit für Datenbanksysteme
+ ELKI Development Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import java.util.List;
+
+import org.apache.batik.util.SVGConstants;
+import org.w3c.dom.Element;
+
+import de.lmu.ifi.dbs.elki.algorithm.outlier.COP;
+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.DBIDIter;
+import de.lmu.ifi.dbs.elki.database.relation.Relation;
+import de.lmu.ifi.dbs.elki.math.linearalgebra.VMath;
+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.result.outlier.OutlierResult;
+import de.lmu.ifi.dbs.elki.utilities.documentation.Reference;
+import de.lmu.ifi.dbs.elki.utilities.documentation.Title;
+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.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 error vectors as produced by COP.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
+ * @apiviz.has OutlierResult oneway - - visualizes
+ */
+@Title("COP: Correlation Outlier Probability")
+@Reference(authors = "Hans-Peter Kriegel, Peer Kröger, Erich Schubert, Arthur Zimek", title = "Outlier Detection in Arbitrarily Oriented Subspaces", booktitle = "Proc. IEEE International Conference on Data Mining (ICDM 2012)")
+public class COPVectorVisualization extends AbstractVisFactory {
+ /**
+ * A short name characterizing this Visualizer.
+ */
+ public static final String NAME = "Error Vectors";
+
+ /**
+ * Constructor.
+ */
+ public COPVectorVisualization() {
+ super();
+ }
+
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ List<OutlierResult> ors = ResultUtil.filterResults(result, OutlierResult.class);
+ for (OutlierResult o : ors) {
+ List<Relation<?>> rels = ResultUtil.filterResults(o, Relation.class);
+ for (Relation<?> rel : rels) {
+ if (!rel.getShortName().equals(COP.COP_ERRORVEC)) {
+ continue;
+ }
+ List<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ boolean vis = true;
+ for (ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, rel, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA;
+ if (!vis) {
+ task.initDefaultVisibility(false);
+ }
+ baseResult.getHierarchy().add(o, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ }
+ }
+
+ /**
+ * Visualize error vectors as produced by COP.
+ *
+ * @author Erich Schubert
+ */
+ public class Instance extends AbstractScatterplotVisualization implements DataStoreListener {
+ /**
+ * Generic tag to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
+ */
+ public static final String VEC = "copvec";
+
+ /**
+ * The outlier result to visualize
+ */
+ protected Relation<Vector> result;
+
+ /**
+ * Constructor.
+ *
+ * @param task Visualization task
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.result = task.getResult();
+ context.addDataStoreListener(this);
+ incrementalRedraw();
+ }
+
+ @Override
+ public void destroy() {
+ super.destroy();
+ context.removeDataStoreListener(this);
+ }
+
+ @Override
+ public void redraw() {
+ setupCSS(svgp);
+ for (DBIDIter objId = sample.getSample().iter(); objId.valid(); objId.advance()) {
+ Vector evec = result.get(objId);
+ if (evec == null) {
+ continue;
+ }
+ double[] ev = proj.fastProjectRelativeDataToRenderSpace(evec);
+ // TODO: avoid hard-coded plot threshold
+ if (VMath.euclideanLength(ev) < 0.01) {
+ continue;
+ }
+ final NumberVector<?> vec = rel.get(objId);
+ if (vec == null) {
+ continue;
+ }
+ double[] v = proj.fastProjectDataToRenderSpace(vec);
+ Element arrow = svgp.svgLine(v[0], v[1], v[0] + ev[0], v[1] + ev[1]);
+ SVGUtil.addCSSClass(arrow, VEC);
+ layer.appendChild(arrow);
+ }
+ }
+
+ @Override
+ public void resultChanged(Result current) {
+ if (sample == current) {
+ synchronizedRedraw();
+ }
+ }
+
+ /**
+ * Registers the COP error vector-CSS-Class at a SVGPlot.
+ *
+ * @param svgp the SVGPlot to register the Tooltip-CSS-Class.
+ */
+ private void setupCSS(SVGPlot svgp) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ CSSClass bubble = new CSSClass(svgp, VEC);
+ bubble.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT) / 2);
+
+ // ColorLibrary colors = style.getColorSet(StyleLibrary.PLOT);
+ String color = "red"; // TODO: use style library
+ bubble.setStatement(SVGConstants.CSS_STROKE_VALUE, color);
+ bubble.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ svgp.addCSSClassOrLogError(bubble);
+ }
+ }
+}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/DistanceFunctionVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/DistanceFunctionVisualization.java
new file mode 100644
index 00000000..257a3c54
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/DistanceFunctionVisualization.java
@@ -0,0 +1,370 @@
+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) 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.Collection;
+
+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.VectorUtil;
+import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDIter;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDs;
+import de.lmu.ifi.dbs.elki.database.ids.DistanceDBIDPair;
+import de.lmu.ifi.dbs.elki.distance.distancefunction.ArcCosineDistanceFunction;
+import de.lmu.ifi.dbs.elki.distance.distancefunction.CosineDistanceFunction;
+import de.lmu.ifi.dbs.elki.distance.distancefunction.DistanceFunction;
+import de.lmu.ifi.dbs.elki.distance.distancefunction.LPNormDistanceFunction;
+import de.lmu.ifi.dbs.elki.distance.distanceresultlist.DistanceDBIDResultIter;
+import de.lmu.ifi.dbs.elki.distance.distanceresultlist.KNNResult;
+import de.lmu.ifi.dbs.elki.distance.distancevalue.DoubleDistance;
+import de.lmu.ifi.dbs.elki.distance.distancevalue.NumberDistance;
+import de.lmu.ifi.dbs.elki.index.preprocessed.knn.AbstractMaterializeKNNPreprocessor;
+import de.lmu.ifi.dbs.elki.logging.LoggingUtil;
+import de.lmu.ifi.dbs.elki.math.linearalgebra.VMath;
+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.exceptions.ObjectNotFoundException;
+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.projections.Projection2D;
+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.SVGHyperSphere;
+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;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualization;
+
+/**
+ * Factory for visualizers to generate an SVG-Element containing dots as markers
+ * representing the kNN of the selected Database objects.
+ *
+ * To use this, add a kNN preprocessor index to your database!
+ *
+ * @author Erich Schubert
+ * @author Robert Rödler
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
+ */
+// FIXME: for >2 dimensions, cosine doesn't seem to be correct yet.
+public class DistanceFunctionVisualization extends AbstractVisFactory {
+ /**
+ * A short name characterizing this Visualizer.
+ */
+ public static final String NAME = "k Nearest Neighbor Visualization";
+
+ /**
+ * Constructor
+ */
+ public DistanceFunctionVisualization() {
+ super();
+ thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION;
+ }
+
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance<DoubleDistance>(task);
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<AbstractMaterializeKNNPreprocessor<?, ?, ?>> kNNIndex = ResultUtil.filterResults(result, AbstractMaterializeKNNPreprocessor.class);
+ for(AbstractMaterializeKNNPreprocessor<?, ?, ?> kNN : kNNIndex) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, kNN, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA - 1;
+ baseResult.getHierarchy().add(kNN, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ }
+
+ /**
+ * Get the "p" value of an Lp norm.
+ *
+ * @param kNN kNN preprocessor
+ * @return p of LP norm, or NaN
+ */
+ public static double getLPNormP(AbstractMaterializeKNNPreprocessor<?, ?, ?> kNN) {
+ DistanceFunction<?, ?> distanceFunction = kNN.getDistanceQuery().getDistanceFunction();
+ if(LPNormDistanceFunction.class.isInstance(distanceFunction)) {
+ return ((LPNormDistanceFunction) distanceFunction).getP();
+ }
+ return Double.NaN;
+ }
+
+ /**
+ * Test whether the given preprocessor used an angular distance function
+ *
+ * @param kNN kNN preprocessor
+ * @return true when angular
+ */
+ public static boolean isAngularDistance(AbstractMaterializeKNNPreprocessor<?, ?, ?> kNN) {
+ DistanceFunction<?, ?> distanceFunction = kNN.getDistanceQuery().getDistanceFunction();
+ if(CosineDistanceFunction.class.isInstance(distanceFunction)) {
+ return true;
+ }
+ if(ArcCosineDistanceFunction.class.isInstance(distanceFunction)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Visualizes Cosine and ArcCosine distance functions
+ *
+ * @param svgp SVG Plot
+ * @param proj Visualization projection
+ * @param mid mean vector
+ * @param angle Opening angle in radians
+ * @return path element
+ */
+ public static Element drawCosine(SVGPlot svgp, Projection2D proj, NumberVector<?> mid, double angle) {
+ // Project origin
+ double[] pointOfOrigin = proj.fastProjectDataToRenderSpace(new double[proj.getInputDimensionality()]);
+
+ // direction of the selected Point
+ double[] selPoint = proj.fastProjectDataToRenderSpace(mid);
+
+ double[] range1, range2;
+ {
+ // Rotation plane:
+ double[] p1 = proj.fastProjectRenderToDataSpace(new double[] { selPoint[0] + 10, selPoint[1] });
+ double[] p2 = proj.fastProjectRenderToDataSpace(new double[] { selPoint[0], selPoint[1] + 10 });
+ double[] pm = mid.getColumnVector().getArrayRef();
+ // Compute relative vectors
+ VMath.minusEquals(p1, pm);
+ VMath.minusEquals(p2, pm);
+ // Scale p1 and p2 to unit length:
+ VMath.timesEquals(p1, 1. / VMath.euclideanLength(p1));
+ VMath.timesEquals(p2, 1. / VMath.euclideanLength(p2));
+ {
+ double test = VMath.scalarProduct(p1, p2);
+ if(Math.abs(test) > 1E-10) {
+ LoggingUtil.warning("Projection does not seem to be orthogonal?");
+ }
+ }
+ // Project onto p1, p2:
+ double l1 = VMath.scalarProduct(pm, p1), l2 = VMath.scalarProduct(pm, p2);
+ // Rotate projection by + and - angle
+ // Using sin(-x) = -sin(x) and cos(-x)=cos(x)
+ final double cangle = Math.cos(angle), sangle = Math.sin(angle);
+ double r11 = +cangle * l1 - sangle * l2, r12 = +sangle * l1 + cangle * l2;
+ double r21 = +cangle * l1 + sangle * l2, r22 = -sangle * l1 + cangle * l2;
+ // Build rotated vectors - remove projected component, add rotated
+ // component:
+ double[] r1 = VMath.copy(pm), r2 = VMath.copy(pm);
+ VMath.plusTimesEquals(r1, p1, -l1 + r11);
+ VMath.plusTimesEquals(r1, p2, -l2 + r12);
+ VMath.plusTimesEquals(r2, p1, -l1 + r21);
+ VMath.plusTimesEquals(r2, p2, -l2 + r22);
+ // Project to render space:
+ range1 = proj.fastProjectDataToRenderSpace(r1);
+ range2 = proj.fastProjectDataToRenderSpace(r2);
+ }
+
+ // Continue lines to viewport.
+ {
+ CanvasSize viewport = proj.estimateViewport();
+ VMath.minusEquals(range1, pointOfOrigin);
+ VMath.minusEquals(range2, pointOfOrigin);
+ VMath.timesEquals(range1, viewport.continueToMargin(pointOfOrigin, range1));
+ VMath.timesEquals(range2, viewport.continueToMargin(pointOfOrigin, range2));
+ VMath.plusEquals(range1, pointOfOrigin);
+ VMath.plusEquals(range2, pointOfOrigin);
+ // Go backwards into the other direction - the origin might not be in the
+ // viewport!
+ double[] start1 = VMath.minus(pointOfOrigin, range1);
+ double[] start2 = VMath.minus(pointOfOrigin, range2);
+ VMath.timesEquals(start1, viewport.continueToMargin(range1, start1));
+ VMath.timesEquals(start2, viewport.continueToMargin(range2, start2));
+ VMath.plusEquals(start1, range1);
+ VMath.plusEquals(start2, range2);
+
+ // TODO: add filled variant?
+ SVGPath path = new SVGPath();
+ path.moveTo(start1);
+ path.lineTo(range1);
+ path.moveTo(start2);
+ path.lineTo(range2);
+ return path.makeElement(svgp);
+ }
+ }
+
+ /**
+ * Instance, visualizing a particular set of kNNs
+ *
+ * @author Robert Rödler
+ * @author Erich Schubert
+ *
+ * @apiviz.has SelectionResult oneway - - visualizes
+ * @apiviz.has DBIDSelection oneway - - visualizes
+ *
+ * @param <D> Distance type
+ */
+ public class Instance<D extends NumberDistance<D, ?>> extends AbstractScatterplotVisualization implements DataStoreListener {
+ /**
+ * Generic tags to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
+ */
+ public static final String KNNMARKER = "kNNMarker";
+
+ public static final String KNNDIST = "kNNDist";
+
+ public static final String DISTANCEFUNCTION = "distancefunction";
+
+ /**
+ * The selection result we work on
+ */
+ private AbstractMaterializeKNNPreprocessor<? extends NumberVector<?>, D, ?> result;
+
+ /**
+ * Constructor
+ *
+ * @param task VisualizationTask
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.result = task.getResult();
+ context.addDataStoreListener(this);
+ context.addResultListener(this);
+ incrementalRedraw();
+ }
+
+ @Override
+ protected void redraw() {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ addCSSClasses(svgp);
+ final double p = getLPNormP(result);
+ final boolean angular = isAngularDistance(result);
+
+ final double size = style.getSize(StyleLibrary.SELECTION);
+ DBIDSelection selContext = context.getSelection();
+ if(selContext != null) {
+ DBIDs selection = selContext.getSelectedIds();
+
+ for(DBIDIter i = selection.iter(); i.valid(); i.advance()) {
+ final KNNResult<D> knn = result.get(i);
+ for(DistanceDBIDResultIter<D> iter = knn.iter(); iter.valid(); iter.advance()) {
+ try {
+ double[] v = proj.fastProjectDataToRenderSpace(rel.get(iter));
+ Element dot = svgp.svgCircle(v[0], v[1], size);
+ SVGUtil.addCSSClass(dot, KNNMARKER);
+ layer.appendChild(dot);
+
+ Element lbl = svgp.svgText(v[0] + size, v[1] + size, iter.getDistance().toString());
+ SVGUtil.addCSSClass(lbl, KNNDIST);
+ layer.appendChild(lbl);
+ }
+ catch(ObjectNotFoundException e) {
+ // ignore
+ }
+ }
+ // Last element
+ DistanceDBIDPair<D> last = knn.get(knn.size() - 1);
+ // Draw hypersphere if possible
+ {
+ final Element dist;
+ if(p == 1.0) {
+ dist = SVGHyperSphere.drawManhattan(svgp, proj, rel.get(i), last.getDistance());
+ }
+ else if(p == 2.0) {
+ dist = SVGHyperSphere.drawEuclidean(svgp, proj, rel.get(i), last.getDistance());
+ }
+ else if(!Double.isNaN(p)) {
+ dist = SVGHyperSphere.drawLp(svgp, proj, rel.get(i), last.getDistance(), p);
+ }
+ else if(angular) {
+ final NumberVector<?> refvec = rel.get(i);
+ // Recompute the angle - it could be cosine or arccosine distance
+ double maxangle = Math.acos(VectorUtil.cosAngle(refvec, rel.get(last)));
+ dist = drawCosine(svgp, proj, refvec, maxangle);
+ }
+ else {
+ dist = null;
+ }
+ if(dist != null) {
+ SVGUtil.addCSSClass(dist, DISTANCEFUNCTION);
+ layer.appendChild(dist);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ // Class for the distance markers
+ if(!svgp.getCSSClassManager().contains(KNNMARKER)) {
+ CSSClass cls = new CSSClass(this, KNNMARKER);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_DARKGREEN_VALUE);
+ cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
+ svgp.addCSSClassOrLogError(cls);
+ }
+ // Class for the distance function
+ if(!svgp.getCSSClassManager().contains(DISTANCEFUNCTION)) {
+ CSSClass cls = new CSSClass(this, DISTANCEFUNCTION);
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_RED_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.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);
+ }
+ // Class for the distance label
+ if(!svgp.getCSSClassManager().contains(KNNDIST)) {
+ CSSClass cls = new CSSClass(this, KNNDIST);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_BLACK_VALUE);
+ cls.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, style.getTextSize(StyleLibrary.PLOT));
+ cls.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, style.getFontFamily(StyleLibrary.PLOT));
+ svgp.addCSSClassOrLogError(cls);
+ }
+ }
+
+ @Override
+ public void resultChanged(Result current) {
+ if(current instanceof SelectionResult) {
+ synchronizedRedraw();
+ return;
+ }
+ super.resultChanged(current);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/MoveObjectsToolVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/MoveObjectsToolVisualization.java
index 6b466a88..c2282bf1 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/MoveObjectsToolVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/MoveObjectsToolVisualization.java
@@ -56,179 +56,179 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatter
* @author Heidi Kolb
* @author Erich Schubert
*
- * @apiviz.has NumberVector oneway - - edits
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class MoveObjectsToolVisualization extends AbstractScatterplotVisualization implements DragListener {
+public class MoveObjectsToolVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
private static final String NAME = "Move Objects";
/**
- * CSS tag for our event rectangle
+ * Constructor
*/
- protected static final String CSS_ARROW = "moveArrow";
-
- /**
- * Element for the rectangle to add listeners
- */
- private Element etag;
-
- /**
- * Element to contain the drag arrow
- */
- private Element rtag;
-
- /**
- * Constructor.
- *
- * @param task Task
- */
- public MoveObjectsToolVisualization(VisualizationTask task) {
- super(task);
- incrementalRedraw();
+ public MoveObjectsToolVisualization() {
+ super();
}
@Override
- public void resultChanged(Result current) {
- if(sample == current) {
- synchronizedRedraw();
- }
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
@Override
- protected void redraw() {
- addCSSClasses(svgp);
-
- rtag = svgp.svgElement(SVGConstants.SVG_G_TAG);
- SVGUtil.addCSSClass(rtag, CSS_ARROW);
- layer.appendChild(rtag);
-
- DragableArea drag = new DragableArea(svgp, -0.6 * StyleLibrary.SCALE, -0.7 * StyleLibrary.SCALE, 1.3 * StyleLibrary.SCALE, 1.4 * StyleLibrary.SCALE, this);
- etag = drag.getElement();
- layer.appendChild(etag);
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<UpdatableDatabase> dbs = ResultUtil.filterResults(result, UpdatableDatabase.class);
+ if(dbs.isEmpty()) {
+ return;
+ }
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, p.getRelation(), p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_INTERACTIVE;
+ task.tool = true;
+ task.thumbnail = false;
+ task.noexport = true;
+ task.initDefaultVisibility(false);
+ // baseResult.getHierarchy().add(p.getRelation(), task);
+ baseResult.getHierarchy().add(p, task);
+ }
}
/**
- * Updates the objects with the given DBIDs It will be moved depending on the
- * given Vector
+ * Instance.
+ *
+ * @author Heidi Kolb
+ * @author Erich Schubert
*
- * @param dbids - DBIDs of the objects to move
- * @param movingVector - Vector for moving object
+ * @apiviz.has NumberVector oneway - - edits
*/
- // TODO: move to DatabaseUtil?
- private void updateDB(DBIDs dbids, Vector movingVector) {
- NumberVector<?, ?> nv = null;
- throw new AbortException("FIXME: INCOMPLETE TRANSITION");
- /*
- * database.accumulateDataStoreEvents();
- * Representation<DatabaseObjectMetadata> mrep =
- * database.getMetadataQuery(); for(DBID dbid : dbids) { NV obj =
- * database.get(dbid); // Copy metadata to keep DatabaseObjectMetadata meta
- * = mrep.get(dbid);
- *
- * Vector v = proj.projectDataToRenderSpace(obj); v.set(0, v.get(0) +
- * movingVector.get(0)); v.set(1, v.get(1) + movingVector.get(1)); NV nv =
- * proj.projectRenderToDataSpace(v, obj); nv.setID(obj.getID());
- *
- * try { database.delete(dbid); database.insert(new Pair<NV,
- * DatabaseObjectMetadata>(nv, meta)); } catch(UnableToComplyException e) {
- * de.lmu.ifi.dbs.elki.logging.LoggingUtil.exception(e); } }
- * database.flushDataStoreEvents();
+ public class Instance extends AbstractScatterplotVisualization implements DragListener {
+ /**
+ * CSS tag for our event rectangle
*/
- }
+ protected static final String CSS_ARROW = "moveArrow";
- /**
- * Delete the children of the element
- *
- * @param container SVG-Element
- */
- private void deleteChildren(Element container) {
- while(container.hasChildNodes()) {
- container.removeChild(container.getLastChild());
+ /**
+ * Element for the rectangle to add listeners
+ */
+ private Element etag;
+
+ /**
+ * Element to contain the drag arrow
+ */
+ private Element rtag;
+
+ /**
+ * Constructor.
+ *
+ * @param task Task
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ incrementalRedraw();
}
- }
- /**
- * Adds the required CSS-Classes
- *
- * @param svgp SVGPlot
- */
- private void addCSSClasses(SVGPlot svgp) {
- // Class for the rectangle to add eventListeners
- if(!svgp.getCSSClassManager().contains(CSS_ARROW)) {
- final CSSClass acls = new CSSClass(this, CSS_ARROW);
- final StyleLibrary style = context.getStyleLibrary();
- acls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, style.getColor(StyleLibrary.SELECTION_ACTIVE));
- acls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.SELECTION_ACTIVE));
- acls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
- svgp.addCSSClassOrLogError(acls);
+ @Override
+ public void resultChanged(Result current) {
+ if(sample == current) {
+ synchronizedRedraw();
+ }
}
- }
- @Override
- public boolean startDrag(SVGPoint startPoint, Event evt) {
- return true;
- }
+ @Override
+ protected void redraw() {
+ addCSSClasses(svgp);
- @Override
- public boolean duringDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
- deleteChildren(rtag);
- rtag.appendChild(svgp.svgLine(startPoint.getX(), startPoint.getY(), dragPoint.getX(), dragPoint.getY()));
- return true;
- }
+ rtag = svgp.svgElement(SVGConstants.SVG_G_TAG);
+ SVGUtil.addCSSClass(rtag, CSS_ARROW);
+ layer.appendChild(rtag);
- @Override
- public boolean endDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
- Vector movingVector = new Vector(2);
- movingVector.set(0, dragPoint.getX() - startPoint.getX());
- movingVector.set(1, dragPoint.getY() - startPoint.getY());
- if(context.getSelection() != null) {
- updateDB(context.getSelection().getSelectedIds(), movingVector);
+ DragableArea drag = new DragableArea(svgp, -0.6 * StyleLibrary.SCALE, -0.7 * StyleLibrary.SCALE, 1.3 * StyleLibrary.SCALE, 1.4 * StyleLibrary.SCALE, this);
+ etag = drag.getElement();
+ layer.appendChild(etag);
}
- deleteChildren(rtag);
- return true;
- }
- /**
- * Factory for tool visualizations for changing objects in the database
- *
- * @author Heidi Kolb
- * @author Erich Schubert
- *
- * @apiviz.stereotype factory
- * @apiviz.uses MoveObjectsToolVisualization oneway - - «create»
- */
- public static class Factory extends AbstractVisFactory {
/**
- * Constructor
+ * Updates the objects with the given DBIDs It will be moved depending on
+ * the given Vector
+ *
+ * @param dbids - DBIDs of the objects to move
+ * @param movingVector - Vector for moving object
*/
- public Factory() {
- super();
+ // TODO: move to DatabaseUtil?
+ private void updateDB(DBIDs dbids, Vector movingVector) {
+ NumberVector<?> nv = null;
+ throw new AbortException("FIXME: INCOMPLETE TRANSITION");
+ /*
+ * database.accumulateDataStoreEvents();
+ * Representation<DatabaseObjectMetadata> mrep =
+ * database.getMetadataQuery(); for(DBID dbid : dbids) { NV obj =
+ * database.get(dbid); // Copy metadata to keep DatabaseObjectMetadata
+ * meta = mrep.get(dbid);
+ *
+ * Vector v = proj.projectDataToRenderSpace(obj); v.set(0, v.get(0) +
+ * movingVector.get(0)); v.set(1, v.get(1) + movingVector.get(1)); NV nv =
+ * proj.projectRenderToDataSpace(v, obj); nv.setID(obj.getID());
+ *
+ * try { database.delete(dbid); database.insert(new Pair<NV,
+ * DatabaseObjectMetadata>(nv, meta)); } catch(UnableToComplyException e)
+ * { de.lmu.ifi.dbs.elki.logging.LoggingUtil.exception(e); } }
+ * database.flushDataStoreEvents();
+ */
+ }
+
+ /**
+ * Delete the children of the element
+ *
+ * @param container SVG-Element
+ */
+ private void deleteChildren(Element container) {
+ while(container.hasChildNodes()) {
+ container.removeChild(container.getLastChild());
+ }
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVGPlot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ // Class for the rectangle to add eventListeners
+ if(!svgp.getCSSClassManager().contains(CSS_ARROW)) {
+ final CSSClass acls = new CSSClass(this, CSS_ARROW);
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ acls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, style.getColor(StyleLibrary.SELECTION_ACTIVE));
+ acls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.SELECTION_ACTIVE));
+ acls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ svgp.addCSSClassOrLogError(acls);
+ }
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new MoveObjectsToolVisualization(task);
+ public boolean startDrag(SVGPoint startPoint, Event evt) {
+ return true;
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<UpdatableDatabase> dbs = ResultUtil.filterResults(result, UpdatableDatabase.class);
- if(dbs.isEmpty()) {
- return;
- }
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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);
+ public boolean duringDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
+ deleteChildren(rtag);
+ rtag.appendChild(svgp.svgLine(startPoint.getX(), startPoint.getY(), dragPoint.getX(), dragPoint.getY()));
+ return true;
+ }
+
+ @Override
+ public boolean endDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
+ Vector movingVector = new Vector(2);
+ movingVector.set(0, dragPoint.getX() - startPoint.getX());
+ movingVector.set(1, dragPoint.getY() - startPoint.getY());
+ if(context.getSelection() != null) {
+ updateDB(context.getSelection().getSelectedIds(), movingVector);
}
+ deleteChildren(rtag);
+ return true;
}
}
} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionConvexHullVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionConvexHullVisualization.java
index 8a28ab3e..f3d41e08 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionConvexHullVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionConvexHullVisualization.java
@@ -58,114 +58,115 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualizati
*
* @author Robert Rödler
*
- * @apiviz.has SelectionResult oneway - - visualizes
- * @apiviz.has DBIDSelection oneway - - visualizes
- * @apiviz.uses GrahamScanConvexHull2D
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class SelectionConvexHullVisualization extends AbstractScatterplotVisualization implements DataStoreListener {
+public class SelectionConvexHullVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
private static final String NAME = "Convex Hull of Selection";
/**
- * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc.
+ * Constructor
*/
- public static final String SELECTEDHULL = "selectionConvexHull";
-
- /**
- * Constructor.
- *
- * @param task Task
- */
- public SelectionConvexHullVisualization(VisualizationTask task) {
- super(task);
- context.addResultListener(this);
- context.addDataStoreListener(this);
- incrementalRedraw();
+ public SelectionConvexHullVisualization() {
+ super();
+ thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION;
}
@Override
- protected void redraw() {
- addCSSClasses(svgp);
- DBIDSelection selContext = context.getSelection();
- if(selContext != null) {
- DBIDs selection = selContext.getSelectedIds();
- GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D();
- for(DBIDIter iter = selection.iter(); iter.valid(); iter.advance()) {
- try {
- hull.add(new Vector(proj.fastProjectDataToRenderSpace(rel.get(iter))));
- }
- catch(ObjectNotFoundException e) {
- // ignore
- }
- }
- Polygon chres = hull.getHull();
- if(chres != null && chres.size() >= 3) {
- SVGPath path = new SVGPath(chres);
-
- Element selHull = path.makeElement(svgp);
- SVGUtil.addCSSClass(selHull, SELECTEDHULL);
- // TODO: use relative selection size for opacity?
- layer.appendChild(selHull);
- }
- }
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
- /**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- private void addCSSClasses(SVGPlot svgp) {
- // Class for the dot markers
- if(!svgp.getCSSClassManager().contains(SELECTEDHULL)) {
- CSSClass cls = new CSSClass(this, SELECTEDHULL);
- // cls = new CSSClass(this, CONVEXHULL);
- cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, context.getStyleLibrary().getColor(StyleLibrary.SELECTION));
- cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.SELECTION));
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, context.getStyleLibrary().getColor(StyleLibrary.SELECTION));
- cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, ".25");
- 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);
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
+ for(SelectionResult selres : selectionResults) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA - 2;
+ baseResult.getHierarchy().add(selres, task);
+ baseResult.getHierarchy().add(p, task);
+ }
}
}
/**
- * Factory for visualizers to generate an SVG-Element containing the convex
- * hull of the selected points
+ * Instance
*
* @author Robert Rödler
*
- * @apiviz.stereotype factory
- * @apiviz.uses SelectionConvexHullVisualization oneway - - «create»
+ * @apiviz.has SelectionResult oneway - - visualizes
+ * @apiviz.has DBIDSelection oneway - - visualizes
+ * @apiviz.uses GrahamScanConvexHull2D
*/
- public static class Factory extends AbstractVisFactory {
+ public class Instance extends AbstractScatterplotVisualization implements DataStoreListener {
/**
- * Constructor
+ * Generic tag to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
*/
- public Factory() {
- super();
- thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION;
- }
+ public static final String SELECTEDHULL = "selectionConvexHull";
- @Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new SelectionConvexHullVisualization(task);
+ /**
+ * Constructor.
+ *
+ * @param task Task
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ context.addResultListener(this);
+ context.addDataStoreListener(this);
+ incrementalRedraw();
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
- for(SelectionResult selres : selectionResults) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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);
- baseResult.getHierarchy().add(p, task);
+ protected void redraw() {
+ addCSSClasses(svgp);
+ DBIDSelection selContext = context.getSelection();
+ if(selContext != null) {
+ DBIDs selection = selContext.getSelectedIds();
+ GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D();
+ for(DBIDIter iter = selection.iter(); iter.valid(); iter.advance()) {
+ try {
+ hull.add(new Vector(proj.fastProjectDataToRenderSpace(rel.get(iter))));
+ }
+ catch(ObjectNotFoundException e) {
+ // ignore
+ }
}
+ Polygon chres = hull.getHull();
+ if(chres != null && chres.size() >= 3) {
+ SVGPath path = new SVGPath(chres);
+
+ Element selHull = path.makeElement(svgp);
+ SVGUtil.addCSSClass(selHull, SELECTEDHULL);
+ // TODO: use relative selection size for opacity?
+ layer.appendChild(selHull);
+ }
+ }
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ // Class for the dot markers
+ if(!svgp.getCSSClassManager().contains(SELECTEDHULL)) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ CSSClass cls = new CSSClass(this, SELECTEDHULL);
+ // cls = new CSSClass(this, CONVEXHULL);
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, style.getColor(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, ".25");
+ 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);
}
}
}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionCubeVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionCubeVisualization.java
index 5151e81e..6d4c14ab 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionCubeVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionCubeVisualization.java
@@ -28,13 +28,13 @@ import java.util.Collection;
import org.apache.batik.util.SVGConstants;
import org.w3c.dom.Element;
+import de.lmu.ifi.dbs.elki.database.relation.RelationUtil;
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.DatabaseUtil;
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;
@@ -59,141 +59,179 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualizati
*
* @author Heidi Kolb
*
- * @apiviz.has SelectionResult oneway - - visualizes
- * @apiviz.has RangeSelection oneway - - visualizes
- * @apiviz.uses SVGHyperCube
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class SelectionCubeVisualization extends AbstractScatterplotVisualization {
+public class SelectionCubeVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
private static final String NAME = "Selection Range";
/**
- * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc.
+ * Settings
*/
- public static final String MARKER = "selectionCubeMarker";
+ protected Parameterizer settings;
/**
- * CSS class for the filled cube
- */
- public static final String CSS_CUBE = "selectionCube";
-
- /**
- * CSS class for the cube frame
- */
- public static final String CSS_CUBEFRAME = "selectionCubeFrame";
-
- /**
- * Fill parameter.
+ * Constructor.
+ *
+ * @param settings Settings
*/
- protected boolean nofill = false;
-
- public SelectionCubeVisualization(VisualizationTask task, boolean nofill) {
- super(task);
- this.nofill = nofill;
- addCSSClasses(svgp);
- context.addResultListener(this);
- incrementalRedraw();
+ public SelectionCubeVisualization(Parameterizer settings) {
+ super();
+ this.settings = settings;
+ thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION;
}
@Override
- public void destroy() {
- context.removeResultListener(this);
- super.destroy();
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
- /**
- * 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(CSS_CUBE)) {
- CSSClass cls = new CSSClass(this, CSS_CUBE);
- 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);
- if(nofill) {
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
- }
- else {
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION));
- cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
+ for(SelectionResult selres : selectionResults) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA - 2;
+ baseResult.getHierarchy().add(selres, task);
+ baseResult.getHierarchy().add(p, task);
}
- svgp.addCSSClassOrLogError(cls);
- }
- // Class for the cube frame
- if(!svgp.getCSSClassManager().contains(CSS_CUBEFRAME)) {
- CSSClass cls = new CSSClass(this, CSS_CUBEFRAME);
- 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.SELECTION));
-
- svgp.addCSSClassOrLogError(cls);
}
}
/**
- * Generates a cube and a frame depending on the selection stored in the
- * context
+ * Instance.
+ *
+ * @author Heidi Kolb
*
- * @param svgp The plot
- * @param proj The projection
+ * @apiviz.has SelectionResult oneway - - visualizes
+ * @apiviz.has RangeSelection oneway - - visualizes
+ * @apiviz.uses SVGHyperCube
*/
- private void setSVGRect(SVGPlot svgp, Projection2D proj) {
- DBIDSelection selContext = context.getSelection();
- if(selContext instanceof RangeSelection) {
- DoubleDoublePair[] ranges = ((RangeSelection) selContext).getRanges();
- int dim = DatabaseUtil.dimensionality(rel);
+ public class Instance extends AbstractScatterplotVisualization {
+ /**
+ * Generic tag to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
+ */
+ public static final String MARKER = "selectionCubeMarker";
+
+ /**
+ * CSS class for the filled cube
+ */
+ public static final String CSS_CUBE = "selectionCube";
+
+ /**
+ * CSS class for the cube frame
+ */
+ public static final String CSS_CUBEFRAME = "selectionCubeFrame";
+
+ public Instance(VisualizationTask task) {
+ super(task);
+ addCSSClasses(svgp);
+ context.addResultListener(this);
+ incrementalRedraw();
+ }
- double[] min = new double[dim];
- double[] max = new double[dim];
- for(int d = 0; d < dim; d++) {
- if(ranges != null && ranges[d] != null) {
- min[d] = ranges[d].first;
- max[d] = ranges[d].second;
+ @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.getStyleResult().getStyleLibrary();
+ // Class for the cube
+ if(!svgp.getCSSClassManager().contains(CSS_CUBE)) {
+ CSSClass cls = new CSSClass(this, CSS_CUBE);
+ 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);
+ if(settings.nofill) {
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
}
else {
- min[d] = proj.getScale(d).getMin();
- max[d] = proj.getScale(d).getMax();
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
}
+ svgp.addCSSClassOrLogError(cls);
}
- if(nofill) {
- 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, min, max);
- layer.appendChild(r);
+ // Class for the cube frame
+ if(!svgp.getCSSClassManager().contains(CSS_CUBEFRAME)) {
+ CSSClass cls = new CSSClass(this, CSS_CUBEFRAME);
+ 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.SELECTION));
+
+ svgp.addCSSClassOrLogError(cls);
}
+ }
+
+ /**
+ * Generates a cube and a frame depending on the selection stored in the
+ * context
+ *
+ * @param svgp The plot
+ * @param proj The projection
+ */
+ private void setSVGRect(SVGPlot svgp, Projection2D proj) {
+ DBIDSelection selContext = context.getSelection();
+ if(selContext instanceof RangeSelection) {
+ DoubleDoublePair[] ranges = ((RangeSelection) selContext).getRanges();
+ int dim = RelationUtil.dimensionality(rel);
+
+ double[] min = new double[dim];
+ double[] max = new double[dim];
+ for(int d = 0; d < dim; d++) {
+ if(ranges != null && ranges[d] != null) {
+ min[d] = ranges[d].first;
+ max[d] = ranges[d].second;
+ }
+ else {
+ min[d] = proj.getScale(d).getMin();
+ max[d] = proj.getScale(d).getMax();
+ }
+ }
+ if(settings.nofill) {
+ 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, min, max);
+ layer.appendChild(r);
+ }
+ }
}
- }
- @Override
- protected void redraw() {
- DBIDSelection selContext = context.getSelection();
- if(selContext != null && selContext instanceof RangeSelection) {
- setSVGRect(svgp, proj);
+ @Override
+ protected void redraw() {
+ DBIDSelection selContext = context.getSelection();
+ if(selContext instanceof RangeSelection) {
+ setSVGRect(svgp, proj);
+ }
}
}
/**
- * Factory for visualizers to generate an SVG-Element containing a cube as
- * marker representing the selected range for each dimension
+ * Parameterization class.
*
- * @author Heidi Kolb
+ * @author Erich Schubert
*
- * @apiviz.stereotype factory
- * @apiviz.uses SelectionCubeVisualization oneway - - «create»
+ * @apiviz.exclude
*/
- public static class Factory extends AbstractVisFactory {
+ public static class Parameterizer extends AbstractParameterizer {
/**
* Flag for half-transparent filling of selection cubes.
*
@@ -201,66 +239,25 @@ public class SelectionCubeVisualization extends AbstractScatterplotVisualization
* Key: {@code -selectionrange.nofill}
* </p>
*/
- public static final OptionID NOFILL_ID = OptionID.getOrCreateOptionID("selectionrange.nofill", "Use wireframe style for selection ranges.");
+ public static final OptionID NOFILL_ID = new OptionID("selectionrange.nofill", "Use wireframe style for selection ranges.");
/**
* Fill parameter.
*/
- protected boolean nofill = false;
-
- /**
- * Constructor.
- *
- * @param nofill
- */
- public Factory(boolean nofill) {
- super();
- this.nofill = nofill;
- thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION;
- }
+ protected boolean nofill;
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new SelectionCubeVisualization(task, nofill);
- }
-
- @Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
- for(SelectionResult selres : selectionResults) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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);
- baseResult.getHierarchy().add(p, task);
- }
+ protected void makeOptions(Parameterization config) {
+ super.makeOptions(config);
+ Flag nofillF = new Flag(NOFILL_ID);
+ if(config.grab(nofillF)) {
+ nofill = nofillF.isTrue();
}
}
- /**
- * Parameterization class.
- *
- * @author Erich Schubert
- *
- * @apiviz.exclude
- */
- public static class Parameterizer extends AbstractParameterizer {
- protected boolean nofill;
-
- @Override
- protected void makeOptions(Parameterization config) {
- super.makeOptions(config);
- Flag nofillF = new Flag(NOFILL_ID);
- if(config.grab(nofillF)) {
- nofill = nofillF.getValue();
- }
- }
-
- @Override
- protected Factory makeInstance() {
- return new Factory(nofill);
- }
+ @Override
+ protected SelectionCubeVisualization makeInstance() {
+ return new SelectionCubeVisualization(this);
}
}
} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionDotVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionDotVisualization.java
index 1dc5ce13..b70755c4 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionDotVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionDotVisualization.java
@@ -54,110 +54,111 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualizati
*
* @author Heidi Kolb
*
- * @apiviz.has SelectionResult oneway - - visualizes
- * @apiviz.has DBIDSelection oneway - - visualizes
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class SelectionDotVisualization extends AbstractScatterplotVisualization implements DataStoreListener {
+public class SelectionDotVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
private static final String NAME = "Selection";
/**
- * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc.
+ * Constructor
*/
- public static final String MARKER = "selectionDotMarker";
-
- /**
- * Constructor.
- *
- * @param task Task
- */
- public SelectionDotVisualization(VisualizationTask task) {
- super(task);
- context.addResultListener(this);
- context.addDataStoreListener(this);
- incrementalRedraw();
+ public SelectionDotVisualization() {
+ super();
+ thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION;
}
@Override
- public void destroy() {
- context.removeResultListener(this);
- super.destroy();
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
@Override
- protected void redraw() {
- addCSSClasses(svgp);
- final double size = context.getStyleLibrary().getSize(StyleLibrary.SELECTION);
- DBIDSelection selContext = context.getSelection();
- if(selContext != null) {
- DBIDs selection = selContext.getSelectedIds();
- for(DBIDIter iter = selection.iter(); iter.valid(); iter.advance()) {
- try {
- double[] v = proj.fastProjectDataToRenderSpace(rel.get(iter));
- Element dot = svgp.svgCircle(v[0], v[1], size);
- SVGUtil.addCSSClass(dot, MARKER);
- layer.appendChild(dot);
- }
- catch(ObjectNotFoundException e) {
- // ignore
- }
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
+ for(SelectionResult selres : selectionResults) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA - 1;
+ baseResult.getHierarchy().add(selres, task);
+ baseResult.getHierarchy().add(p, task);
}
}
}
/**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- private void addCSSClasses(SVGPlot svgp) {
- // Class for the dot markers
- if(!svgp.getCSSClassManager().contains(MARKER)) {
- CSSClass cls = new CSSClass(this, MARKER);
- final StyleLibrary style = context.getStyleLibrary();
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION));
- cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
- svgp.addCSSClassOrLogError(cls);
- }
- }
-
- /**
- * Factory for visualizers to generate an SVG-Element containing dots as
- * markers representing the selected Database's objects.
+ * Instance
*
* @author Heidi Kolb
*
- * @apiviz.stereotype factory
- * @apiviz.uses SelectionDotVisualization oneway - - «create»
+ * @apiviz.has SelectionResult oneway - - visualizes
+ * @apiviz.has DBIDSelection oneway - - visualizes
*/
- public static class Factory extends AbstractVisFactory {
+ public class Instance extends AbstractScatterplotVisualization implements DataStoreListener {
+ /**
+ * Generic tag to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
+ */
+ public static final String MARKER = "selectionDotMarker";
+
/**
- * Constructor
+ * Constructor.
+ *
+ * @param task Task
*/
- public Factory() {
- super();
- thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION;
+ public Instance(VisualizationTask task) {
+ super(task);
+ context.addResultListener(this);
+ context.addDataStoreListener(this);
+ incrementalRedraw();
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new SelectionDotVisualization(task);
+ public void destroy() {
+ context.removeResultListener(this);
+ super.destroy();
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
- for(SelectionResult selres : selectionResults) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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);
- baseResult.getHierarchy().add(p, task);
+ protected void redraw() {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ addCSSClasses(svgp);
+ final double size = style.getSize(StyleLibrary.SELECTION);
+ DBIDSelection selContext = context.getSelection();
+ if(selContext != null) {
+ DBIDs selection = selContext.getSelectedIds();
+ for(DBIDIter iter = selection.iter(); iter.valid(); iter.advance()) {
+ try {
+ double[] v = proj.fastProjectDataToRenderSpace(rel.get(iter));
+ Element dot = svgp.svgCircle(v[0], v[1], size);
+ SVGUtil.addCSSClass(dot, MARKER);
+ layer.appendChild(dot);
+ }
+ catch(ObjectNotFoundException e) {
+ // ignore
+ }
}
}
}
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ // Class for the dot markers
+ if(!svgp.getCSSClassManager().contains(MARKER)) {
+ CSSClass cls = new CSSClass(this, MARKER);
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
+ svgp.addCSSClassOrLogError(cls);
+ }
+ }
}
} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolCubeVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolCubeVisualization.java
index bc16fd83..f088d219 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolCubeVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolCubeVisualization.java
@@ -35,6 +35,7 @@ import de.lmu.ifi.dbs.elki.data.NumberVector;
import de.lmu.ifi.dbs.elki.database.ids.DBIDIter;
import de.lmu.ifi.dbs.elki.database.ids.DBIDUtil;
import de.lmu.ifi.dbs.elki.database.ids.ModifiableDBIDs;
+import de.lmu.ifi.dbs.elki.database.relation.RelationUtil;
import de.lmu.ifi.dbs.elki.logging.Logging;
import de.lmu.ifi.dbs.elki.result.DBIDSelection;
import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
@@ -42,7 +43,6 @@ 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.DatabaseUtil;
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,18 +57,18 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;
import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatterplotVisualization;
/**
- * Tool-Visualization for the tool to select ranges
+ * Tool-Visualization for the tool to select ranges.
*
* @author Heidi Kolb
*
- * @apiviz.has SelectionResult oneway - - updates
- * @apiviz.has RangeSelection oneway - - updates
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class SelectionToolCubeVisualization extends AbstractScatterplotVisualization implements DragableArea.DragListener {
+public class SelectionToolCubeVisualization extends AbstractVisFactory {
/**
* The logger for this class.
*/
- protected static final Logging logger = Logging.getLogger(SelectionToolCubeVisualization.class);
+ private static final Logging LOG = Logging.getLogger(SelectionToolCubeVisualization.class);
/**
* A short name characterizing this Visualizer.
@@ -76,219 +76,218 @@ public class SelectionToolCubeVisualization extends AbstractScatterplotVisualiza
private static final String NAME = "Range Selection";
/**
- * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc.
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}.
*/
- private static final String CSS_RANGEMARKER = "selectionRangeMarker";
-
- /**
- * Dimension
- */
- private int dim;
-
- /**
- * Element for selection rectangle
- */
- private Element rtag;
-
- /**
- * Element for the rectangle to add listeners
- */
- private Element etag;
-
- /**
- * Constructor.
- *
- * @param task Task
- */
- public SelectionToolCubeVisualization(VisualizationTask task) {
- super(task);
- this.dim = DatabaseUtil.dimensionality(rel);
- incrementalRedraw();
+ public SelectionToolCubeVisualization() {
+ super();
}
@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, -0.6 * StyleLibrary.SCALE, -0.7 * StyleLibrary.SCALE, 1.3 * StyleLibrary.SCALE, 1.4 * StyleLibrary.SCALE, this);
- etag = drag.getElement();
- layer.appendChild(etag);
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
- /**
- * Delete the children of the element
- *
- * @param container SVG-Element
- */
- private void deleteChildren(Element container) {
- while(container.hasChildNodes()) {
- container.removeChild(container.getLastChild());
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
+ for (SelectionResult selres : selectionResults) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for (ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_INTERACTIVE;
+ task.tool = true;
+ task.thumbnail = false;
+ task.noexport = true;
+ task.initDefaultVisibility(false);
+ baseResult.getHierarchy().add(selres, task);
+ baseResult.getHierarchy().add(p, task);
+ }
}
}
/**
- * Set the selected ranges and the mask for the actual dimensions in the
- * context
+ * Instance.
*
- * @param x1 x-value of the first dimension
- * @param x2 x-value of the second dimension
- * @param y1 y-value of the first dimension
- * @param y2 y-value of the second dimension
+ * @author Heidi Kolb
+ *
+ * @apiviz.has SelectionResult oneway - - updates
+ * @apiviz.has RangeSelection oneway - - updates
*/
- private void updateSelectionRectKoordinates(double x1, double x2, double y1, double y2, DoubleDoublePair[] ranges) {
- BitSet actDim = proj.getVisibleDimensions2D();
- double[] v1 = new double[dim];
- double[] v2 = new double[dim];
- v1[0] = x1;
- v1[1] = y1;
- v2[0] = x2;
- v2[1] = y2;
-
- 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[d], nv2[d]), Math.max(nv1[d], nv2[d]));
- }
- }
+ public class Instance extends AbstractScatterplotVisualization implements DragableArea.DragListener {
+ /**
+ * Generic tag to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
+ */
+ private static final String CSS_RANGEMARKER = "selectionRangeMarker";
- @Override
- public boolean startDrag(SVGPoint startPoint, Event evt) {
- return true;
- }
+ /**
+ * Dimension.
+ */
+ private int dim;
- @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;
- }
+ /**
+ * Element for selection rectangle.
+ */
+ private Element rtag;
- @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;
- }
+ /**
+ * Element for the rectangle to add listeners.
+ */
+ private Element etag;
- /**
- * 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) {
- if(p1 == null || p2 == null) {
- logger.warning("no rect selected: p1: " + p1 + " p2: " + p2);
- return;
+ /**
+ * Constructor.
+ *
+ * @param task Task
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.dim = RelationUtil.dimensionality(rel);
+ incrementalRedraw();
}
- DBIDSelection selContext = context.getSelection();
- ModifiableDBIDs selection;
- if(selContext != null) {
- selection = DBIDUtil.newHashSet(selContext.getSelectedIds());
- }
- else {
- selection = DBIDUtil.newHashSet();
+ @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, -0.6 * StyleLibrary.SCALE, -0.7 * StyleLibrary.SCALE, 1.3 * StyleLibrary.SCALE, 1.4 * StyleLibrary.SCALE, this);
+ etag = drag.getElement();
+ layer.appendChild(etag);
}
- DoubleDoublePair[] ranges;
-
- 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());
- if(selContext instanceof RangeSelection) {
- ranges = ((RangeSelection) selContext).getRanges();
- }
- else {
- ranges = new DoubleDoublePair[dim];
- }
- updateSelectionRectKoordinates(x1, x2, y1, y2, ranges);
-
- selection.clear();
- candidates: for(DBIDIter iditer = rel.iterDBIDs(); iditer.valid(); iditer.advance()) {
- NumberVector<?, ?> dbTupel = rel.get(iditer);
- 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) {
- continue candidates;
- }
- }
+ /**
+ * Delete the children of the element.
+ *
+ * @param container SVG-Element
+ */
+ private void deleteChildren(Element container) {
+ while (container.hasChildNodes()) {
+ container.removeChild(container.getLastChild());
}
- selection.add(iditer);
}
- 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 Heidi Kolb
- *
- * @apiviz.stereotype factory
- * @apiviz.uses SelectionToolCubeVisualization oneway - - «create»
- */
- public static class Factory extends AbstractVisFactory {
/**
- * Constructor, adhering to
- * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ * Set the selected ranges and the mask for the actual dimensions in the
+ * context.
+ *
+ * @param x1 x-value of the first dimension
+ * @param x2 x-value of the second dimension
+ * @param y1 y-value of the first dimension
+ * @param y2 y-value of the second dimension
+ * @param ranges Ranges to update
*/
- public Factory() {
- super();
+ private void updateSelectionRectKoordinates(double x1, double x2, double y1, double y2, DoubleDoublePair[] ranges) {
+ BitSet actDim = proj.getVisibleDimensions2D();
+ double[] v1 = new double[dim];
+ double[] v2 = new double[dim];
+ v1[0] = x1;
+ v1[1] = y1;
+ v2[0] = x2;
+ v2[1] = y2;
+
+ 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[d], nv2[d]), Math.max(nv1[d], nv2[d]));
+ }
+ }
+
+ @Override
+ public boolean startDrag(SVGPoint startPoint, Event evt) {
+ return true;
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new SelectionToolCubeVisualization(task);
+ 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 void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
- for(SelectionResult selres : selectionResults) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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);
+ 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) {
+ if (p1 == null || p2 == null) {
+ LOG.warning("no rect selected: p1: " + p1 + " p2: " + p2);
+ return;
+ }
+
+ DBIDSelection selContext = context.getSelection();
+ ModifiableDBIDs selection;
+ if (selContext != null) {
+ selection = DBIDUtil.newHashSet(selContext.getSelectedIds());
+ } else {
+ selection = DBIDUtil.newHashSet();
+ }
+ DoubleDoublePair[] ranges;
+
+ 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());
+
+ if (selContext instanceof RangeSelection) {
+ ranges = ((RangeSelection) selContext).getRanges();
+ } else {
+ ranges = new DoubleDoublePair[dim];
+ }
+ updateSelectionRectKoordinates(x1, x2, y1, y2, ranges);
+
+ selection.clear();
+ candidates: for (DBIDIter iditer = rel.iterDBIDs(); iditer.valid(); iditer.advance()) {
+ NumberVector<?> dbTupel = rel.get(iditer);
+ for (int i = 0; i < dim; i++) {
+ if (ranges != null && ranges[i] != null) {
+ if (dbTupel.doubleValue(i) < ranges[i].first || dbTupel.doubleValue(i) > ranges[i].second) {
+ continue candidates;
+ }
+ }
}
+ selection.add(iditer);
+ }
+ 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.getStyleResult().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);
}
}
}
-} \ No newline at end of file
+}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolDotVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolDotVisualization.java
index b52f37c9..6fa8200c 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolDotVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolDotVisualization.java
@@ -56,21 +56,16 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatter
*
* @author Heidi Kolb
*
- * @apiviz.has SelectionResult oneway - - updates
- * @apiviz.has DBIDSelection oneway - - updates
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance - - «create»
*/
-public class SelectionToolDotVisualization extends AbstractScatterplotVisualization implements DragableArea.DragListener {
+public class SelectionToolDotVisualization extends AbstractVisFactory {
/**
* 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
@@ -80,194 +75,199 @@ public class SelectionToolDotVisualization extends AbstractScatterplotVisualizat
}
/**
- * Element for selection rectangle
- */
- Element rtag;
-
- /**
- * Element for the rectangle to add listeners
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
*/
- Element etag;
-
- /**
- * Constructor.
- *
- * @param task Task
- */
- public SelectionToolDotVisualization(VisualizationTask task) {
- super(task);
- incrementalRedraw();
+ public SelectionToolDotVisualization() {
+ super();
}
@Override
- protected void redraw() {
- addCSSClasses(svgp);
-
- //
- rtag = svgp.svgElement(SVGConstants.SVG_G_TAG);
- SVGUtil.addCSSClass(rtag, CSS_RANGEMARKER);
- layer.appendChild(rtag);
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
+ }
- // etag: sensitive area
- DragableArea drag = new DragableArea(svgp, -0.6 * StyleLibrary.SCALE, -0.7 * StyleLibrary.SCALE, 1.3 * StyleLibrary.SCALE, 1.4 * StyleLibrary.SCALE, this);
- etag = drag.getElement();
- layer.appendChild(etag);
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
+ for(SelectionResult selres : selectionResults) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_INTERACTIVE;
+ task.tool = true;
+ task.thumbnail = false;
+ task.noexport = true;
+ task.initDefaultVisibility(false);
+ baseResult.getHierarchy().add(selres, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
}
/**
- * Delete the children of the element
+ * Instance
*
- * @param container SVG-Element
+ * @author Heidi Kolb
+ *
+ * @apiviz.has SelectionResult oneway - - updates
+ * @apiviz.has DBIDSelection oneway - - updates
*/
- private void deleteChildren(Element container) {
- while(container.hasChildNodes()) {
- container.removeChild(container.getLastChild());
+ public class Instance extends AbstractScatterplotVisualization implements DragableArea.DragListener {
+ /**
+ * CSS class of the selection rectangle while selecting.
+ */
+ private static final String CSS_RANGEMARKER = "selectionRangeMarker";
+
+ /**
+ * Element for selection rectangle
+ */
+ Element rtag;
+
+ /**
+ * Element for the rectangle to add listeners
+ */
+ Element etag;
+
+ /**
+ * Constructor.
+ *
+ * @param task Task
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ incrementalRedraw();
}
- }
- @Override
- public boolean startDrag(SVGPoint startPoint, Event evt) {
- return true;
- }
+ @Override
+ protected void redraw() {
+ addCSSClasses(svgp);
- @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;
- }
+ //
+ rtag = svgp.svgElement(SVGConstants.SVG_G_TAG);
+ SVGUtil.addCSSClass(rtag, CSS_RANGEMARKER);
+ layer.appendChild(rtag);
- @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, proj, startPoint, dragPoint);
+ // etag: sensitive area
+ DragableArea drag = new DragableArea(svgp, -0.6 * StyleLibrary.SCALE, -0.7 * StyleLibrary.SCALE, 1.3 * StyleLibrary.SCALE, 1.4 * StyleLibrary.SCALE, this);
+ etag = drag.getElement();
+ layer.appendChild(etag);
}
- 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;
+ /**
+ * Delete the children of the element
+ *
+ * @param container SVG-Element
+ */
+ private void deleteChildren(Element container) {
+ while(container.hasChildNodes()) {
+ container.removeChild(container.getLastChild());
}
}
- // Default mode is replace.
- return Mode.REPLACE;
- }
- /**
- * Updates the selection in the context.<br>
- *
- * @param mode Input mode
- * @param proj
- * @param p1 first point of the selected rectangle
- * @param p2 second point of the selected rectangle
- */
- private void updateSelection(Mode mode, Projection2D proj, SVGPoint p1, SVGPoint p2) {
- DBIDSelection selContext = context.getSelection();
- // Note: we rely on SET semantics below!
- HashSetModifiableDBIDs selection;
- if(selContext == null || mode == Mode.REPLACE) {
- selection = DBIDUtil.newHashSet();
+ @Override
+ public boolean startDrag(SVGPoint startPoint, Event evt) {
+ return true;
}
- else {
- selection = DBIDUtil.newHashSet(selContext.getSelectedIds());
+
+ @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;
}
- for(DBIDIter iditer = rel.iterDBIDs(); iditer.valid(); iditer.advance()) {
- double[] vec = proj.fastProjectDataToRenderSpace(rel.get(iditer));
- if(vec[0] >= Math.min(p1.getX(), p2.getX()) && vec[0] <= Math.max(p1.getX(), p2.getX()) && vec[1] >= Math.min(p1.getY(), p2.getY()) && vec[1] <= Math.max(p1.getY(), p2.getY())) {
- if(mode == Mode.INVERT) {
- if(!selection.contains(iditer)) {
- selection.add(iditer);
- }
- else {
- selection.remove(iditer);
- }
+
+ @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, proj, 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 {
- // 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(iditer);
+ return Mode.REPLACE;
}
}
+ // Default mode is replace.
+ return Mode.REPLACE;
}
- context.setSelection(new DBIDSelection(selection));
- }
- /**
- * 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 Heidi Kolb
- *
- * @apiviz.stereotype factory
- * @apiviz.uses SelectionToolDotVisualization - - «create»
- */
- public static class Factory extends AbstractVisFactory {
/**
- * Constructor, adhering to
- * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ * Updates the selection in the context.<br>
+ *
+ * @param mode Input mode
+ * @param proj
+ * @param p1 first point of the selected rectangle
+ * @param p2 second point of the selected rectangle
*/
- public Factory() {
- super();
- }
-
- @Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new SelectionToolDotVisualization(task);
+ private void updateSelection(Mode mode, Projection2D proj, SVGPoint p1, SVGPoint p2) {
+ DBIDSelection selContext = context.getSelection();
+ // Note: we rely on SET semantics below!
+ HashSetModifiableDBIDs selection;
+ if(selContext == null || mode == Mode.REPLACE) {
+ selection = DBIDUtil.newHashSet();
+ }
+ else {
+ selection = DBIDUtil.newHashSet(selContext.getSelectedIds());
+ }
+ for(DBIDIter iditer = rel.iterDBIDs(); iditer.valid(); iditer.advance()) {
+ double[] vec = proj.fastProjectDataToRenderSpace(rel.get(iditer));
+ if(vec[0] >= Math.min(p1.getX(), p2.getX()) && vec[0] <= Math.max(p1.getX(), p2.getX()) && vec[1] >= Math.min(p1.getY(), p2.getY()) && vec[1] <= Math.max(p1.getY(), p2.getY())) {
+ if(mode == Mode.INVERT) {
+ if(!selection.contains(iditer)) {
+ selection.add(iditer);
+ }
+ else {
+ selection.remove(iditer);
+ }
+ }
+ 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(iditer);
+ }
+ }
+ }
+ context.setSelection(new DBIDSelection(selection));
}
- @Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
- for(SelectionResult selres : selectionResults) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(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);
- }
+ /**
+ * 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.getStyleResult().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);
}
}
}