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.java323
1 files changed, 182 insertions, 141 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 c4825b1e..34471bdd 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) 2013
+ Copyright (C) 2014
Ludwig-Maximilians-Universität München
Lehr- und Forschungseinheit für Datenbanksysteme
ELKI Development Team
@@ -29,6 +29,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map.Entry;
+import java.util.concurrent.atomic.AtomicReference;
import org.apache.batik.util.SVGConstants;
import org.w3c.dom.Element;
@@ -70,20 +71,28 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;
* @apiviz.has DetailViewSelectedEvent
* @apiviz.uses DetailView
*/
-// FIXME: there still is a synchronization issue, that causes the initialization
-// to be run twice in parallel.
-public class OverviewPlot extends SVGPlot implements ResultListener {
+public class OverviewPlot implements ResultListener {
/**
* Our logging class
*/
private static final Logging LOG = Logging.getLogger(OverviewPlot.class);
/**
+ * Event when the overview plot was refreshed.
+ */
+ public static final String OVERVIEW_REFRESHED = "Overview refreshed";
+
+ /**
* Visualizer context
*/
private VisualizerContext context;
/**
+ * The SVG plot object.
+ */
+ private SVGPlot plot;
+
+ /**
* Map of coordinates to plots.
*/
protected RectangleArranger<PlotItem> plotmap;
@@ -141,7 +150,7 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
/**
* Pending refresh, for lazy refreshing
*/
- Runnable pendingRefresh = null;
+ AtomicReference<Runnable> pendingRefresh = new AtomicReference<>(null);
/**
* Reinitialize on refresh
@@ -160,26 +169,6 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
this.context = context;
this.single = single;
- // Add a background element:
- {
- CSSClass cls = new CSSClass(this, "background");
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, context.getStyleResult().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);
}
@@ -196,47 +185,57 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
ArrayList<Projector> projectors = ResultUtil.filterResults(context.getResult(), Projector.class);
// Rectangle layout
- for (Projector p : projectors) {
+ for(Projector p : projectors) {
Collection<PlotItem> projs = p.arrange();
- for (PlotItem it : projs) {
- if (it.w <= 0.0 || it.h <= 0.0) {
+ for(PlotItem it : projs) {
+ if(it.w <= 0.0 || it.h <= 0.0) {
LOG.warning("Plot item with improper size information: " + it);
- } else {
- plotmap.put(it.w, it.h, it);
+ continue;
}
+ plotmap.put(it.w, it.h, it);
}
}
ResultHierarchy hier = context.getHierarchy();
ArrayList<VisualizationTask> tasks = ResultUtil.filterResults(context.getResult(), VisualizationTask.class);
- nextTask: for (VisualizationTask task : tasks) {
- if (!task.visible) {
+ nextTask: for(VisualizationTask task : tasks) {
+ if(!task.visible) {
continue;
}
- for (Hierarchy.Iter<Result> iter = hier.iterParents(task); iter.valid(); iter.advance()) {
- if (iter.get() instanceof Projector) {
+ for(Hierarchy.Iter<Result> iter = hier.iterParents(task); iter.valid(); iter.advance()) {
+ if(iter.get() instanceof Projector) {
continue nextTask;
}
}
- if (task.getWidth() <= 0.0 || task.getHeight() <= 0.0) {
+ if(task.getWidth() <= 0.0 || task.getHeight() <= 0.0) {
LOG.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);
+ continue;
}
+ PlotItem it = new PlotItem(task.getWidth(), task.getHeight(), null);
+ it.tasks.add(task);
+ plotmap.put(it.w, it.h, it);
}
return plotmap;
}
/**
+ * Initialize the plot.
+ *
+ * @param ratio Initial ratio
+ */
+ public void initialize(double ratio) {
+ this.ratio = ratio;
+ reinitialize();
+ }
+
+ /**
* Refresh the overview plot.
*/
private void reinitialize() {
- setupHoverer();
+ initializePlot();
plotmap = arrangeVisualizations(ratio, 1.0);
double s = plotmap.relativeFill();
- if (s < 0.9) {
+ if(s < 0.9) {
// Retry, sometimes this yields better results
plotmap = arrangeVisualizations(plotmap.getWidth() * s, plotmap.getHeight() * s);
}
@@ -246,7 +245,7 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
// TODO: cancel pending thumbnail requests!
// Detach existing elements:
- for (Pair<Element, Visualization> pair : vistoelem.values()) {
+ for(Pair<Element, Visualization> pair : vistoelem.values()) {
SVGUtil.removeFromParent(pair.first);
}
// Replace the layer map
@@ -254,44 +253,42 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
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);
+ plotlayer = plot.svgElement(SVGConstants.SVG_G_TAG);
+ hoverlayer = plot.svgElement(SVGConstants.SVG_G_TAG);
// Redo the layout
- for (Entry<PlotItem, double[]> e : plotmap.entrySet()) {
+ 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();) {
+ 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);
+ Element g = plot.svgElement(SVGConstants.SVG_G_TAG);
SVGUtil.setAtt(g, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, "translate(" + (basex + it.x) + " " + (basey + it.y) + ")");
plotlayer.appendChild(g);
vistoelem.put(it, null, g, null);
// Add the actual tasks:
- for (VisualizationTask task : it.tasks) {
- if (!visibleInOverview(task)) {
+ for(VisualizationTask task : it.tasks) {
+ if(!visibleInOverview(task)) {
continue;
}
hasDetails |= !task.nodetail;
Pair<Element, Visualization> pair = oldlayers.remove(it, task);
- if (pair == null) {
+ if(pair == null) {
pair = new Pair<>(null, null);
- pair.first = svgElement(SVGConstants.SVG_G_TAG);
+ pair.first = plot.svgElement(SVGConstants.SVG_G_TAG);
}
- if (pair.second == null) {
+ if(pair.second == null) {
pair.second = embedOrThumbnail(thumbsize, it, task, pair.first);
}
g.appendChild(pair.first);
vistoelem.put(it, task, pair);
}
// When needed, add a hover effect
- if (hasDetails && !single) {
- Element hover = this.svgRect(basex + it.x, basey + it.y, it.w, it.h);
+ if(hasDetails && !single) {
+ Element hover = plot.svgRect(basex + it.x, basey + it.y, it.w, it.h);
SVGUtil.addCSSClass(hover, selcss.getName());
// link hoverer.
EventTarget targ = (EventTarget) hover;
@@ -304,14 +301,66 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
}
}
}
- for (Pair<Element, Visualization> pair : oldlayers.values()) {
- if (pair.second != null) {
+ for(Pair<Element, Visualization> pair : oldlayers.values()) {
+ if(pair.second != null) {
pair.second.destroy();
}
}
- getRoot().appendChild(plotlayer);
- getRoot().appendChild(hoverlayer);
- updateStyleElement();
+ plot.getRoot().appendChild(plotlayer);
+ plot.getRoot().appendChild(hoverlayer);
+ plot.updateStyleElement();
+
+ // Notify listeners.
+ for(ActionListener actionListener : actionListeners) {
+ actionListener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, OVERVIEW_REFRESHED));
+ }
+ }
+
+ /**
+ * Initialize the SVG plot.
+ */
+ private void initializePlot() {
+ if(plot != null) {
+ plot.dispose();
+ }
+ plot = new SVGPlot();
+ { // Add a background element:
+ CSSClass cls = new CSSClass(this, "background");
+ final String bgcol = context.getStyleResult().getStyleLibrary().getBackgroundColor(StyleLibrary.PAGE);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, bgcol);
+ plot.addCSSClassOrLogError(cls);
+ Element background = plot.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());
+ // Don't export a white background:
+ if("white".equals(bgcol)) {
+ background.setAttribute(SVGPlot.NO_EXPORT_ATTRIBUTE, SVGPlot.NO_EXPORT_ATTRIBUTE);
+ }
+ plot.getRoot().appendChild(background);
+ }
+ { // setup the hover CSS classes.
+ selcss = new CSSClass(this, "s");
+ selcss.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_RED_VALUE);
+ selcss.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ selcss.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, "0");
+ selcss.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE);
+ CSSClass hovcss = new CSSClass(this, "h");
+ hovcss.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, "0.25");
+ plot.addCSSClassOrLogError(selcss);
+ plot.addCSSClassOrLogError(hovcss);
+ // Hover listener.
+ hoverer = new CSSHoverClass(hovcss.getName(), null, true);
+ }
+
+ if(single) {
+ plot.setDisableInteractions(true);
+ }
+ SVGEffects.addShadowFilter(plot);
+ SVGEffects.addLightGradient(plot);
+ plot.updateStyleElement();
}
/**
@@ -323,71 +372,70 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
* @param parent Parent element to draw to
*/
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 {
- if (task.noexport) {
- vis.getLayer().setAttribute(NO_EXPORT_ATTRIBUTE, NO_EXPORT_ATTRIBUTE);
- }
- parent.appendChild(vis.getLayer());
- }
- return vis;
- } else {
- VisualizationTask thumbtask = task.clone(this, context, it.proj, it.w, it.h);
+ VisualizationTask thumbtask = task.clone(plot, context, it.proj, it.w, it.h);
+ final Visualization vis;
+ if(!single) {
thumbtask.thumbnail = true;
thumbtask.thumbsize = thumbsize;
- final Visualization vis = thumbtask.getFactory().makeVisualizationOrThumbnail(thumbtask);
- if (vis.getLayer() == null) {
- LoggingUtil.warning("Visualization returned empty layer: " + vis);
- } else {
- parent.appendChild(vis.getLayer());
- }
+ vis = thumbtask.getFactory().makeVisualizationOrThumbnail(thumbtask);
+ }
+ else {
+ vis = thumbtask.getFactory().makeVisualization(thumbtask);
+ }
+ if(vis.getLayer() == null) {
+ LoggingUtil.warning("Visualization returned empty layer: " + vis);
return vis;
}
+ if(task.noexport) {
+ vis.getLayer().setAttribute(SVGPlot.NO_EXPORT_ATTRIBUTE, SVGPlot.NO_EXPORT_ATTRIBUTE);
+ }
+ parent.appendChild(vis.getLayer());
+ return vis;
}
/**
* Do a refresh (when visibilities have changed).
*/
- synchronized void refresh() {
- pendingRefresh = null;
- if (reinitOnRefresh) {
- LOG.debug("Reinitialize");
+ void refresh() {
+ pendingRefresh.set(null); // Clear
+ if(reinitOnRefresh) {
+ LOG.debug("Reinitialize in thread " + Thread.currentThread().getName());
reinitialize();
reinitOnRefresh = false;
- } else {
+ return;
+ }
+ synchronized(plot) {
LOG.debug("Incremental refresh");
boolean refreshcss = false;
final int thumbsize = (int) Math.max(screenwidth / plotmap.getWidth(), screenheight / plotmap.getHeight());
- for (PlotItem pi : plotmap.keySet()) {
- for (Iterator<PlotItem> iter = pi.itemIterator(); iter.hasNext();) {
+ for(PlotItem pi : plotmap.keySet()) {
+ for(Iterator<PlotItem> iter = pi.itemIterator(); iter.hasNext();) {
PlotItem it = iter.next();
- for (Iterator<VisualizationTask> tit = it.tasks.iterator(); tit.hasNext();) {
+ 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)) {
+ if(pair == null) {
+ if(visibleInOverview(task)) {
pair = new Pair<>(null, null);
- pair.first = svgElement(SVGConstants.SVG_G_TAG);
+ pair.first = plot.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 {
- if (visibleInOverview(task)) {
+ }
+ else {
+ if(visibleInOverview(task)) {
// unhide if hidden.
- if (pair.first.hasAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY)) {
+ if(pair.first.hasAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY)) {
pair.first.removeAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY);
}
- } else {
+ }
+ else {
// hide if there is anything to hide.
- if (pair.first != null && pair.first.hasChildNodes()) {
+ if(pair.first != null && pair.first.hasChildNodes()) {
pair.first.setAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY, SVGConstants.CSS_HIDDEN_VALUE);
}
}
@@ -396,8 +444,8 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
}
}
}
- if (refreshcss) {
- updateStyleElement();
+ if(refreshcss) {
+ plot.updateStyleElement();
}
}
}
@@ -409,9 +457,10 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
* @return visibility
*/
protected boolean visibleInOverview(VisualizationTask task) {
- if (single) {
+ if(single) {
return task.visible && !task.noembed;
- } else {
+ }
+ else {
return task.visible && task.thumbnail;
}
}
@@ -423,28 +472,9 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
// Recalculate bounding box.
String vb = "0 0 " + plotmap.getWidth() + " " + plotmap.getHeight();
// Reset root bounding box.
- SVGUtil.setAtt(getRoot(), SVGConstants.SVG_WIDTH_ATTRIBUTE, "20cm");
- SVGUtil.setAtt(getRoot(), SVGConstants.SVG_HEIGHT_ATTRIBUTE, (20 / plotmap.getWidth() * plotmap.getHeight()) + "cm");
- SVGUtil.setAtt(getRoot(), SVGConstants.SVG_VIEW_BOX_ATTRIBUTE, vb);
- }
-
- /**
- * Setup the CSS hover effect.
- */
- private void setupHoverer() {
- // setup the hover CSS classes.
- selcss = new CSSClass(this, "s");
- selcss.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_RED_VALUE);
- selcss.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_NONE_VALUE);
- selcss.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, "0");
- selcss.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE);
- CSSClass hovcss = new CSSClass(this, "h");
- hovcss.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, "0.25");
- addCSSClassOrLogError(selcss);
- addCSSClassOrLogError(hovcss);
- // Hover listener.
- hoverer = new CSSHoverClass(hovcss.getName(), null, true);
- updateStyleElement();
+ SVGUtil.setAtt(plot.getRoot(), SVGConstants.SVG_WIDTH_ATTRIBUTE, "20cm");
+ SVGUtil.setAtt(plot.getRoot(), SVGConstants.SVG_HEIGHT_ATTRIBUTE, (20 * plotmap.getHeight() / plotmap.getWidth()) + "cm");
+ SVGUtil.setAtt(plot.getRoot(), SVGConstants.SVG_VIEW_BOX_ATTRIBUTE, vb);
}
/**
@@ -473,7 +503,7 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
*/
protected void triggerSubplotSelectEvent(PlotItem it) {
// forward event to all listeners.
- for (ActionListener actionListener : actionListeners) {
+ for(ActionListener actionListener : actionListeners) {
actionListener.actionPerformed(new DetailViewSelectedEvent(this, ActionEvent.ACTION_PERFORMED, null, 0, it));
}
}
@@ -508,12 +538,20 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
}
/**
- * Cancel the overview, i.e. stop the thumbnailer
+ * Destroy this overview plot.
*/
- @Override
- public void dispose() {
+ public void destroy() {
context.removeResultListener(this);
- super.dispose();
+ plot.dispose();
+ }
+
+ /**
+ * Get the SVGPlot object.
+ *
+ * @return SVG plot
+ */
+ public SVGPlot getPlot() {
+ return plot;
}
/**
@@ -527,30 +565,37 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
* @param ratio the ratio to set
*/
public void setRatio(double ratio) {
- this.ratio = ratio;
+ if(ratio != this.ratio) {
+ this.ratio = ratio;
+ reinitOnRefresh = true;
+ lazyRefresh();
+ }
}
/**
* Trigger a redraw, but avoid excessive redraws.
*/
public final void lazyRefresh() {
+ if(plot == null) {
+ LOG.warning("'lazyRefresh' called before initialized!");
+ }
LOG.debug("Scheduling refresh.");
Runnable pr = new Runnable() {
@Override
public void run() {
- if (OverviewPlot.this.pendingRefresh == this) {
+ if(OverviewPlot.this.pendingRefresh.compareAndSet(this, null)) {
OverviewPlot.this.refresh();
}
}
};
- pendingRefresh = pr;
- scheduleUpdate(pr);
+ OverviewPlot.this.pendingRefresh.set(pr);
+ plot.scheduleUpdate(pr);
}
@Override
public void resultAdded(Result child, Result parent) {
LOG.debug("result added: " + child);
- if (child instanceof VisualizationTask) {
+ if(child instanceof VisualizationTask) {
reinitOnRefresh = true;
}
lazyRefresh();
@@ -559,17 +604,13 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
@Override
public void resultChanged(Result current) {
LOG.debug("result changed: " + current);
- if (current instanceof VisualizationTask) {
- boolean relayout = true;
- for (Hierarchy.Iter<Result> iter = context.getHierarchy().iterParents(current); iter.valid(); iter.advance()) {
- if (iter.get() instanceof Projector) {
- relayout = false;
+ if(current instanceof VisualizationTask) {
+ for(Hierarchy.Iter<Result> iter = context.getHierarchy().iterParents(current); iter.valid(); iter.advance()) {
+ if(iter.get() instanceof Projector) {
+ reinitOnRefresh = true;
break;
}
}
- if (relayout) {
- reinitOnRefresh = true;
- }
}
lazyRefresh();
}