Package de.lmu.ifi.dbs.elki.visualization.gui.overview

Source Code of de.lmu.ifi.dbs.elki.visualization.gui.overview.OverviewPlot$SelectPlotEvent

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) 2012
Ludwig-Maximilians-Universität München
Lehr- und Forschungseinheit für Datenbanksysteme
ELKI Development Team

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map.Entry;

import org.apache.batik.util.SVGConstants;
import org.w3c.dom.Element;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;

import de.lmu.ifi.dbs.elki.logging.Logging;
import de.lmu.ifi.dbs.elki.logging.LoggingUtil;
import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
import de.lmu.ifi.dbs.elki.result.Result;
import de.lmu.ifi.dbs.elki.result.ResultHierarchy;
import de.lmu.ifi.dbs.elki.result.ResultListener;
import de.lmu.ifi.dbs.elki.result.ResultUtil;
import de.lmu.ifi.dbs.elki.utilities.pairs.Pair;
import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
import de.lmu.ifi.dbs.elki.visualization.VisualizerContext;
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;
import de.lmu.ifi.dbs.elki.visualization.visualizers.VisualizerUtil;

/**
* Generate an overview plot for a set of visualizations.
*
* @author Erich Schubert
* @author Remigius Wojdanowski
*
* @apiviz.landmark
* @apiviz.has VisualizerContext
* @apiviz.composedOf RectangleArranger
* @apiviz.has DetailViewSelectedEvent
* @apiviz.uses DetailView
* @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
   */
  private static final Logging logger = Logging.getLogger(OverviewPlot.class);

  /**
   * Visualizer context
   */
  private VisualizerContext context;

  /**
   * Map of coordinates to plots.
   */
  protected RectangleArranger<PlotItem> plotmap;

  /**
   * Action listeners for this plot.
   */
  private java.util.Vector<ActionListener> actionListeners = new java.util.Vector<ActionListener>();

  /**
   * Single view mode
   */
  private boolean single;

  /**
   * Screen size (used for thumbnail sizing)
   */
  public int screenwidth = 2000;

  /**
   * Screen size (used for thumbnail sizing)
   */
  public int screenheight = 2000;

  /**
   * React to mouse hover events
   */
  private EventListener hoverer;

  /**
   * Lookup
   */
  private LayerMap vistoelem = new LayerMap();

  /**
   * Layer for plot thumbnail
   */
  private Element plotlayer;

  /**
   * Layer for hover elements
   */
  private Element hoverlayer;

  /**
   * The CSS class used on "selectable" rectangles.
   */
  private CSSClass selcss;

  /**
   * Screen ratio
   */
  private double ratio = 1.0;

  /**
   * Pending refresh, for lazy refreshing
   */
  Runnable pendingRefresh = null;

  /**
   * Reinitialize on refresh
   */
  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() {
    RectangleArranger<PlotItem> plotmap = new RectangleArranger<PlotItem>(ratio);

    ArrayList<Projector> projectors = ResultUtil.filterResults(context.getResult(), Projector.class);
    // Rectangle layout
    for(Projector p : projectors) {
      Collection<PlotItem> projs = p.arrange();
      for(PlotItem it : projs) {
        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 = 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) {
          continue nextTask;
        }
      }
      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;
  }

  /**
   * Refresh the overview plot.
   */
  private void reinitialize() {
    setupHoverer();
    arrangeVisualizations();
    recalcViewbox();
    final int thumbsize = (int) Math.max(screenwidth / plotmap.getWidth(), screenheight / plotmap.getHeight());
    // TODO: cancel pending thumbnail requests!

    // 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);

    // 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) + ")");
        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);
        }
        // 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.
          EventTarget targ = (EventTarget) hover;
          targ.addEventListener(SVGConstants.SVG_MOUSEOVER_EVENT_TYPE, hoverer, false);
          targ.addEventListener(SVGConstants.SVG_MOUSEOUT_EVENT_TYPE, hoverer, false);
          targ.addEventListener(SVGConstants.SVG_CLICK_EVENT_TYPE, hoverer, false);
          targ.addEventListener(SVGConstants.SVG_CLICK_EVENT_TYPE, new SelectPlotEvent(it), false);

          hoverlayer.appendChild(hover);
        }
      }
    }
    for(Pair<Element, Visualization> pair : oldlayers.values()) {
      if(pair.second != null) {
        pair.second.destroy();
      }
    }
    getRoot().appendChild(plotlayer);
    getRoot().appendChild(hoverlayer);
    updateStyleElement();
  }

  /**
   * Produce thumbnail for a visualizer.
   *
   * @param thumbsize Thumbnail size
   * @param it Plot item
   * @param task Task
   * @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 {
        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);
      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;
    }
  }

  /**
   * Do a refresh (when visibilities have changed).
   */
  synchronized void refresh() {
    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(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();) {
            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 {
              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
            }
          }
        }
      }
      if(refreshcss) {
        updateStyleElement();
      }
    }
  }

  /**
   * 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() {
    // 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();
  }

  /**
   * Event triggered when a plot was selected.
   *
   * @param it Plot item selected
   * @return sub plot
   */
  public DetailView makeDetailView(PlotItem it) {
    return new DetailView(context, it, ratio);
  }

  /**
   * Adds an {@link ActionListener} to the plot.
   *
   * @param actionListener the {@link ActionListener} to be added
   */
  public void addActionListener(ActionListener actionListener) {
    actionListeners.add(actionListener);
  }

  /**
   * When a subplot was selected, forward the event to listeners.
   *
   * @param it PlotItem selected
   */
  protected void triggerSubplotSelectEvent(PlotItem it) {
    // forward event to all listeners.
    for(ActionListener actionListener : actionListeners) {
      actionListener.actionPerformed(new DetailViewSelectedEvent(this, ActionEvent.ACTION_PERFORMED, null, 0, it));
    }
  }

  /**
   * Event when a plot was selected.
   *
   * @author Erich Schubert
   *
   * @apiviz.exclude
   */
  public class SelectPlotEvent implements EventListener {
    /**
     * Plot item clicked
     */
    PlotItem it;

    /**
     * Constructor.
     *
     * @param it Item that was clicked
     */
    public SelectPlotEvent(PlotItem it) {
      super();
      this.it = it;
    }

    @Override
    public void handleEvent(Event evt) {
      triggerSubplotSelectEvent(it);
    }
  }

  /**
   * Cancel the overview, i.e. stop the thumbnailer
   */
  @Override
  public void dispose() {
    context.removeResultListener(this);
    super.dispose();
  }

  /**
   * @return the ratio
   */
  public double getRatio() {
    return ratio;
  }

  /**
   * @param ratio the ratio to set
   */
  public void setRatio(double ratio) {
    this.ratio = ratio;
  }

  /**
   * 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.refresh();
        }
      }
    };
    pendingRefresh = pr;
    scheduleUpdate(pr);
  }

  @Override
  public void resultAdded(Result child, Result parent) {
    logger.debug("result added: " + child);
    if(child instanceof VisualizationTask) {
      reinitOnRefresh = true;
    }
    lazyRefresh();
  }

  @Override
  public void resultChanged(Result current) {
    logger.debug("result changed: " + current);
    lazyRefresh();
  }

  @Override
  public void resultRemoved(Result child, Result parent) {
    logger.debug("result removed: " + child);
    lazyRefresh();
  }
}
TOP

Related Classes of de.lmu.ifi.dbs.elki.visualization.gui.overview.OverviewPlot$SelectPlotEvent

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.