diff options
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.java | 264 |
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(); } } |