summaryrefslogtreecommitdiff
path: root/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/OverviewPlot.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/de/lmu/ifi/dbs/elki/visualization/gui/overview/OverviewPlot.java')
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/gui/overview/OverviewPlot.java264
1 files changed, 176 insertions, 88 deletions
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/OverviewPlot.java b/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/OverviewPlot.java
index f05719ae..56d6ce32 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/OverviewPlot.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/OverviewPlot.java
@@ -4,7 +4,7 @@ package de.lmu.ifi.dbs.elki.visualization.gui.overview;
This file is part of ELKI:
Environment for Developing KDD-Applications Supported by Index-Structures
- Copyright (C) 2011
+ Copyright (C) 2012
Ludwig-Maximilians-Universität München
Lehr- und Forschungseinheit für Datenbanksysteme
ELKI Development Team
@@ -27,7 +27,6 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
@@ -51,6 +50,8 @@ import de.lmu.ifi.dbs.elki.visualization.batikutil.CSSHoverClass;
import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
import de.lmu.ifi.dbs.elki.visualization.gui.detail.DetailView;
import de.lmu.ifi.dbs.elki.visualization.projector.Projector;
+import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGEffects;
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.Visualization;
@@ -67,8 +68,10 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.VisualizerUtil;
* @apiviz.composedOf RectangleArranger
* @apiviz.has DetailViewSelectedEvent
* @apiviz.uses DetailView
- * @apiviz.uses de.lmu.ifi.dbs.elki.visualization.projections.Projection
+ * @apiviz.uses Projector
*/
+// FIXME: there still is a synchronization issue, that causes the initialization
+// to be run twice in parallel.
public class OverviewPlot extends SVGPlot implements ResultListener {
/**
* Our logging class
@@ -81,11 +84,6 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
private VisualizerContext context;
/**
- * Result we work on. Currently unused, but kept for future requirements.
- */
- private HierarchicalResult result;
-
- /**
* Map of coordinates to plots.
*/
protected RectangleArranger<PlotItem> plotmap;
@@ -96,18 +94,9 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
private java.util.Vector<ActionListener> actionListeners = new java.util.Vector<ActionListener>();
/**
- * Constructor.
- *
- * @param result Result to visualize
- * @param context Visualizer context
+ * Single view mode
*/
- public OverviewPlot(HierarchicalResult result, VisualizerContext context) {
- super();
- this.result = result;
- this.context = context;
- // register context listener
- context.addResultListener(this);
- }
+ private boolean single;
/**
* Screen size (used for thumbnail sizing)
@@ -127,7 +116,7 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
/**
* Lookup
*/
- private HashMap<Pair<PlotItem, VisualizationTask>, Element> vistoelem;
+ private LayerMap vistoelem = new LayerMap();
/**
* Layer for plot thumbnail
@@ -152,7 +141,7 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
/**
* Pending refresh, for lazy refreshing
*/
- Runnable pendingRefresh;
+ Runnable pendingRefresh = null;
/**
* Reinitialize on refresh
@@ -160,41 +149,79 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
private boolean reinitOnRefresh = true;
/**
+ * Constructor.
+ *
+ * @param result Result to visualize
+ * @param context Visualizer context
+ * @param single Single view mode
+ */
+ public OverviewPlot(HierarchicalResult result, VisualizerContext context, boolean single) {
+ super();
+ this.context = context;
+ this.single = single;
+
+ // Add a background element:
+ {
+ CSSClass cls = new CSSClass(this, "background");
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, context.getStyleLibrary().getBackgroundColor(StyleLibrary.PAGE));
+ addCSSClassOrLogError(cls);
+ Element background = this.svgElement(SVGConstants.SVG_RECT_TAG);
+ background.setAttribute(SVGConstants.SVG_X_ATTRIBUTE, "0");
+ background.setAttribute(SVGConstants.SVG_Y_ATTRIBUTE, "0");
+ background.setAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE, "100%");
+ background.setAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE, "100%");
+ SVGUtil.setCSSClass(background, cls.getName());
+ getRoot().appendChild(background);
+ }
+
+ if(single) {
+ setDisableInteractions(true);
+ }
+ SVGEffects.addShadowFilter(this);
+ SVGEffects.addLightGradient(this);
+
+ // register context listener
+ context.addResultListener(this);
+ }
+
+ /**
* Recompute the layout of visualizations.
*/
private void arrangeVisualizations() {
- plotmap = new RectangleArranger<PlotItem>(ratio);
+ RectangleArranger<PlotItem> plotmap = new RectangleArranger<PlotItem>(ratio);
- ArrayList<Projector> projectors = ResultUtil.filterResults(result, Projector.class);
+ ArrayList<Projector> projectors = ResultUtil.filterResults(context.getResult(), Projector.class);
// Rectangle layout
for(Projector p : projectors) {
Collection<PlotItem> projs = p.arrange();
for(PlotItem it : projs) {
- plotmap.put(it.w, it.h, it);
+ if(it.w <= 0.0 || it.h <= 0.0) {
+ logger.warning("Plot item with improper size information: " + it);
+ }
+ else {
+ plotmap.put(it.w, it.h, it);
+ }
}
}
- ResultHierarchy hier = result.getHierarchy();
- ArrayList<VisualizationTask> tasks = ResultUtil.filterResults(result, VisualizationTask.class);
- for(VisualizationTask task : tasks) {
- boolean isprojected = false;
+ ResultHierarchy hier = context.getHierarchy();
+ ArrayList<VisualizationTask> tasks = ResultUtil.filterResults(context.getResult(), VisualizationTask.class);
+ nextTask: for(VisualizationTask task : tasks) {
for(Result parent : hier.getParents(task)) {
if(parent instanceof Projector) {
- isprojected = true;
- break;
+ continue nextTask;
}
}
- if(!isprojected) {
- if(task.getWidth() <= 0.0 || task.getHeight() <= 0.0) {
- logger.warning("Task with improper size information: " + task);
- }
- else {
- PlotItem it = new PlotItem(task.getWidth(), task.getHeight(), null);
- it.visualizations.add(task);
- plotmap.put(it.w, it.h, it);
- }
+ if(task.getWidth() <= 0.0 || task.getHeight() <= 0.0) {
+ logger.warning("Task with improper size information: " + task);
+ }
+ else {
+ PlotItem it = new PlotItem(task.getWidth(), task.getHeight(), null);
+ it.tasks.add(task);
+ plotmap.put(it.w, it.h, it);
}
}
+ this.plotmap = plotmap;
}
/**
@@ -204,48 +231,58 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
setupHoverer();
arrangeVisualizations();
recalcViewbox();
+ final int thumbsize = (int) Math.max(screenwidth / plotmap.getWidth(), screenheight / plotmap.getHeight());
// TODO: cancel pending thumbnail requests!
- // Layers.
- if(plotlayer != null) {
- if(plotlayer.getParentNode() != null) {
- plotlayer.getParentNode().removeChild(plotlayer);
- }
- plotlayer = null;
- }
- if(hoverlayer != null) {
- if(hoverlayer.getParentNode() != null) {
- hoverlayer.getParentNode().removeChild(hoverlayer);
- }
- hoverlayer = null;
+ // Detach existing elements:
+ for(Pair<Element, Visualization> pair : vistoelem.values()) {
+ SVGUtil.removeFromParent(pair.first);
}
+ // Replace the layer map
+ LayerMap oldlayers = vistoelem;
+ vistoelem = new LayerMap();
+
+ // Redo main layers
+ SVGUtil.removeFromParent(plotlayer);
+ SVGUtil.removeFromParent(hoverlayer);
plotlayer = this.svgElement(SVGConstants.SVG_G_TAG);
hoverlayer = this.svgElement(SVGConstants.SVG_G_TAG);
- vistoelem = new HashMap<Pair<PlotItem, VisualizationTask>, Element>();
-
- final int thumbsize = (int) Math.max(screenwidth / plotmap.getWidth(), screenheight / plotmap.getHeight());
- // TODO: kill all children in document root except style, defs etc?
+ // Redo the layout
for(Entry<PlotItem, double[]> e : plotmap.entrySet()) {
final double basex = e.getValue()[0];
final double basey = e.getValue()[1];
for(Iterator<PlotItem> iter = e.getKey().itemIterator(); iter.hasNext();) {
PlotItem it = iter.next();
+
boolean hasDetails = false;
+ // Container element for main plot item
Element g = this.svgElement(SVGConstants.SVG_G_TAG);
SVGUtil.setAtt(g, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, "translate(" + (basex + it.x) + " " + (basey + it.y) + ")");
- for(VisualizationTask task : it.visualizations) {
- Element parent = this.svgElement(SVGConstants.SVG_G_TAG);
- g.appendChild(parent);
- makeThumbnail(thumbsize, it, task, parent);
- vistoelem.put(new Pair<PlotItem, VisualizationTask>(it, task), parent);
-
+ plotlayer.appendChild(g);
+ vistoelem.put(it, null, g, null);
+ // Add the actual tasks:
+ for(VisualizationTask task : it.tasks) {
+ if(!visibleInOverview(task)) {
+ continue;
+ }
if(VisualizerUtil.detailsEnabled(task)) {
hasDetails = true;
+ // TODO: not updatable
+ }
+ Pair<Element, Visualization> pair = oldlayers.remove(it, task);
+ if(pair == null) {
+ pair = new Pair<Element, Visualization>(null, null);
+ pair.first = svgElement(SVGConstants.SVG_G_TAG);
}
+ if(pair.second == null) {
+ pair.second = embedOrThumbnail(thumbsize, it, task, pair.first);
+ }
+ g.appendChild(pair.first);
+ vistoelem.put(it, task, pair);
}
- plotlayer.appendChild(g);
- if(hasDetails) {
+ // When needed, add a hover effect
+ if(hasDetails && !single) {
Element hover = this.svgRect(basex + it.x, basey + it.y, it.w, it.h);
SVGUtil.addCSSClass(hover, selcss.getName());
// link hoverer.
@@ -259,6 +296,11 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
}
}
}
+ for(Pair<Element, Visualization> pair : oldlayers.values()) {
+ if(pair.second != null) {
+ pair.second.destroy();
+ }
+ }
getRoot().appendChild(plotlayer);
getRoot().appendChild(hoverlayer);
updateStyleElement();
@@ -272,18 +314,30 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
* @param task Task
* @param parent Parent element to draw to
*/
- private void makeThumbnail(final int thumbsize, PlotItem it, VisualizationTask task, Element parent) {
- if(VisualizerUtil.isVisible(task) && VisualizerUtil.thumbnailEnabled(task)) {
+ private Visualization embedOrThumbnail(final int thumbsize, PlotItem it, VisualizationTask task, Element parent) {
+ if(single) {
+ VisualizationTask thumbtask = task.clone(this, context, it.proj, it.w, it.h);
+ final Visualization vis = thumbtask.getFactory().makeVisualization(thumbtask);
+ if(vis.getLayer() == null) {
+ LoggingUtil.warning("Visualization returned empty layer: " + vis);
+ }
+ else {
+ parent.appendChild(vis.getLayer());
+ }
+ return vis;
+ }
+ else {
VisualizationTask thumbtask = task.clone(this, context, it.proj, it.w, it.h);
thumbtask.put(VisualizationTask.THUMBNAIL, true);
thumbtask.put(VisualizationTask.THUMBNAIL_RESOLUTION, thumbsize);
- Visualization vis = thumbtask.getFactory().makeVisualizationOrThumbnail(thumbtask);
+ final Visualization vis = thumbtask.getFactory().makeVisualizationOrThumbnail(thumbtask);
if(vis.getLayer() == null) {
LoggingUtil.warning("Visualization returned empty layer: " + vis);
}
else {
parent.appendChild(vis.getLayer());
}
+ return vis;
}
}
@@ -291,39 +345,53 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
* Do a refresh (when visibilities have changed).
*/
synchronized void refresh() {
- logger.debug("Refresh");
- if(vistoelem == null || plotlayer == null || hoverlayer == null || reinitOnRefresh) {
+ pendingRefresh = null;
+ if(reinitOnRefresh) {
+ logger.debug("Reinitialize");
reinitialize();
reinitOnRefresh = false;
}
else {
+ logger.debug("Incremental refresh");
boolean refreshcss = false;
final int thumbsize = (int) Math.max(screenwidth / plotmap.getWidth(), screenheight / plotmap.getHeight());
- for(Entry<PlotItem, double[]> ent : plotmap.entrySet()) {
- for(Iterator<PlotItem> iter = ent.getKey().itemIterator(); iter.hasNext();) {
+ for(PlotItem pi : plotmap.keySet()) {
+ for(Iterator<PlotItem> iter = pi.itemIterator(); iter.hasNext();) {
PlotItem it = iter.next();
- for(VisualizationTask task : it.visualizations) {
- Element parent = vistoelem.get(new Pair<PlotItem, VisualizationTask>(it, task));
- if(parent == null) {
- LoggingUtil.warning("No container element found for " + task);
- continue;
- }
- if(VisualizerUtil.thumbnailEnabled(task) && VisualizerUtil.isVisible(task)) {
- // unhide when hidden.
- if(parent.hasAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY)) {
- parent.removeAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY);
- }
- // if not yet rendered, add a thumbnail
- if(!parent.hasChildNodes()) {
- makeThumbnail(thumbsize, it, task, parent);
+ for(Iterator<VisualizationTask> tit = it.tasks.iterator(); tit.hasNext();) {
+ VisualizationTask task = tit.next();
+ Pair<Element, Visualization> pair = vistoelem.get(it, task);
+ // New task?
+ if(pair == null) {
+ if(visibleInOverview(task)) {
+ pair = new Pair<Element, Visualization>(null, null);
+ pair.first = svgElement(SVGConstants.SVG_G_TAG);
+ pair.second = embedOrThumbnail(thumbsize, it, task, pair.first);
+ vistoelem.get(it, null).first.appendChild(pair.first);
+ vistoelem.put(it, task, pair);
refreshcss = true;
}
}
else {
- // hide if there is anything to hide.
- if(parent != null && parent.hasChildNodes()) {
- parent.setAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY, SVGConstants.CSS_HIDDEN_VALUE);
+ if(visibleInOverview(task)) {
+ // unhide if hidden.
+ if(pair.first.hasAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY)) {
+ pair.first.removeAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY);
+ }
+ // if not yet rendered, add a thumbnail
+ if(!pair.first.hasChildNodes()) {
+ logger.warning("This codepath should no longer be needed.");
+ Visualization visualization = embedOrThumbnail(thumbsize, it, task, pair.first);
+ vistoelem.put(it, task, pair.first, visualization);
+ refreshcss = true;
+ }
+ }
+ else {
+ // hide if there is anything to hide.
+ if(pair.first != null && pair.first.hasChildNodes()) {
+ pair.first.setAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY, SVGConstants.CSS_HIDDEN_VALUE);
+ }
}
// TODO: unqueue pending thumbnails
}
@@ -337,6 +405,25 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
}
/**
+ * Test whether a task should be displayed in the overview plot.
+ *
+ * @param task Task to display
+ * @return visibility
+ */
+ protected boolean visibleInOverview(VisualizationTask task) {
+ if(!VisualizerUtil.isVisible(task)) {
+ return false;
+ }
+ if(single) {
+ Boolean nothumb = task.getGenerics(VisualizationTask.META_NOEMBED, Boolean.class);
+ return (nothumb == null) || !nothumb;
+ }
+ else {
+ return VisualizerUtil.thumbnailEnabled(task);
+ }
+ }
+
+ /**
* Recompute the view box of the plot.
*/
private void recalcViewbox() {
@@ -364,6 +451,7 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
addCSSClassOrLogError(hovcss);
// Hover listener.
hoverer = new CSSHoverClass(hovcss.getName(), null, true);
+ updateStyleElement();
}
/**
@@ -453,11 +541,11 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
* Trigger a redraw, but avoid excessive redraws.
*/
public final void lazyRefresh() {
+ logger.debug("Scheduling refresh.");
Runnable pr = new Runnable() {
@Override
public void run() {
if(OverviewPlot.this.pendingRefresh == this) {
- OverviewPlot.this.pendingRefresh = null;
OverviewPlot.this.refresh();
}
}