Package de.lmu.ifi.dbs.elki.visualization.visualizers.pairsegments

Source Code of de.lmu.ifi.dbs.elki.visualization.visualizers.pairsegments.CircleSegmentsVisualizer

package de.lmu.ifi.dbs.elki.visualization.visualizers.pairsegments;

/*
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.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

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 org.w3c.dom.events.MouseEvent;

import de.lmu.ifi.dbs.elki.evaluation.clustering.pairsegments.Segment;
import de.lmu.ifi.dbs.elki.evaluation.clustering.pairsegments.Segments;
import de.lmu.ifi.dbs.elki.logging.Logging;
import de.lmu.ifi.dbs.elki.math.MathUtil;
import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
import de.lmu.ifi.dbs.elki.result.Result;
import de.lmu.ifi.dbs.elki.result.ResultListener;
import de.lmu.ifi.dbs.elki.result.ResultUtil;
import de.lmu.ifi.dbs.elki.utilities.documentation.Reference;
import de.lmu.ifi.dbs.elki.utilities.exceptions.AbortException;
import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
import de.lmu.ifi.dbs.elki.visualization.svg.SVGCheckbox;
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.AbstractVisualization;
import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;

/**
* Visualizer to draw circle segments of clusterings and enable interactive
* selection of segments. For "empty" segments, all related segments are
* selected instead, to visualize the differences.
*
* <p>
* Reference:<br />
* Evaluation of Clusterings – Metrics and Visual Support<br />
* Elke Achtert, Sascha Goldhofer, Hans-Peter Kriegel, Erich Schubert, Arthur
* Zimek<br />
* In: Proc. 28th International Conference on Data Engineering (ICDE) 2012
* </p>
*
* @author Sascha Goldhofer
* @author Erich Schubert
*/
@Reference(title = "Evaluation of Clusterings – Metrics and Visual Support", authors = "Elke Achtert, Sascha Goldhofer, Hans-Peter Kriegel, Erich Schubert, Arthur Zimek", booktitle = "Proc. 28th International Conference on Data Engineering (ICDE) 2012", url = "http://elki.dbs.ifi.lmu.de/wiki/PairSegments")
public class CircleSegmentsVisualizer extends AbstractVisualization implements ResultListener {
  /**
   * Class logger
   */
  private static final Logging logger = Logging.getLogger(CircleSegmentsVisualizer.class);

  /**
   * CircleSegments visualizer name
   */
  private static final String NAME = "CircleSegments";

  /** Minimum width (radian) of Segment */
  private final static double SEGMENT_MIN_ANGLE = 0.01;

  /** Gap (radian) between segments */
  private final static double SEGMENT_MIN_SEP_ANGLE = 0.005;

  /** Offset from center to first ring */
  private final static double RADIUS_INNER = 0.04 * StyleLibrary.SCALE;

  /** Margin between two rings */
  private final static double RADIUS_DISTANCE = 0.01 * StyleLibrary.SCALE;

  /** Radius of whole CircleSegments except selection border */
  private final static double RADIUS_OUTER = 0.47 * StyleLibrary.SCALE;

  /** Radius of highlight selection (outer ring) */
  private final static double RADIUS_SELECTION = 0.02 * StyleLibrary.SCALE;

  /**
   * CSS class name for the clusterings.
   */
  private static final String CLR_CLUSTER_CLASS_PREFIX = "clusterSegment";

  /**
   * CSS border class of a cluster
   */
  public static final String CLR_BORDER_CLASS = "clusterBorder";

  /**
   * CSS hover class for clusters of hovered segment
   */
  public static final String CLR_UNPAIRED_CLASS = "clusterUnpaired";

  /**
   * CSS hover class of a segment cluster
   */
  public static final String CLR_HOVER_CLASS = "clusterHover";

  /**
   * CSS class of selected Segment
   */
  public static final String SEG_UNPAIRED_SELECTED_CLASS = "unpairedSegmentSelected";

  /**
   * Style prefix
   */
  public static final String STYLE = "segments";

  /**
   * Style for border lines
   */
  public static final String STYLE_BORDER = STYLE + ".border";

  /**
   * Style for hover effect
   */
  public static final String STYLE_HOVER = STYLE + ".hover";

  /**
   * First color for producing segment-cluster colors
   */
  public static final String STYLE_GRADIENT_FIRST = STYLE + ".cluster.first";

  /**
   * Second color for producing segment-cluster colors
   */
  public static final String STYLE_GRADIENT_SECOND = STYLE + ".cluster.second";

  /**
   * Segmentation of Clusterings
   */
  protected final Segments segments;

  /**
   * The two main layers
   */
  private Element visLayer, ctrlLayer;

  /**
   * Map to connect segments to their visual elements
   */
  public Map<Segment, List<Element>> segmentToElements = new HashMap<Segment, List<Element>>();

  /**
   * Show unclustered Pairs in CircleSegments
   */
  boolean showUnclusteredPairs = false;

  /**
   * Styling policy
   */
  protected final SegmentsStylingPolicy policy;

  /**
   * Flag to disallow an incremental redraw
   */
  private boolean noIncrementalRedraw = true;

  /**
   * Constructor
   */
  public CircleSegmentsVisualizer(VisualizationTask task) {
    super(task);
    segments = task.getResult();
    policy = new SegmentsStylingPolicy(segments, context.getStyleLibrary());
    // Listen for result changes (Selection changed)
    context.addResultListener(this);
  }

  public void toggleUnclusteredPairs(boolean show) {
    noIncrementalRedraw = true;
    showUnclusteredPairs = show;
    synchronizedRedraw();
  }

  @Override
  public void resultChanged(Result current) {
    super.resultChanged(current);
    // Redraw on style result changes.
    if(current == context.getStyleResult()) {
      // When switching to a different policy, unhighlight segments.
      if(context.getStyleResult().getStylingPolicy() != policy) {
        policy.deselectAllSegments();
      }
      synchronizedRedraw();
    }
  }

  @Override
  protected void incrementalRedraw() {
    if(noIncrementalRedraw) {
      super.incrementalRedraw();
    }
    else {
      redrawSelection();
    }
  }

  @Override
  public void redraw() {
    logger.debug("Full redraw");
    noIncrementalRedraw = false; // Done that.

    // initialize css (needs clusterSize!)
    addCSSClasses(segments.getHighestClusterCount());

    layer = svgp.svgElement(SVGConstants.SVG_G_TAG);
    visLayer = svgp.svgElement(SVGConstants.SVG_G_TAG);
    // Setup scaling for canvas: 0 to StyleLibrary.SCALE (usually 100 to avoid a
    // Java drawing bug!)
    String transform = SVGUtil.makeMarginTransform(task.width, task.height, StyleLibrary.SCALE, StyleLibrary.SCALE, 0) + "  translate(" + (StyleLibrary.SCALE * .5) + " " + (StyleLibrary.SCALE * .5) + ")";
    visLayer.setAttribute(SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform);
    ctrlLayer = svgp.svgElement(SVGConstants.SVG_G_TAG);

    // and create svg elements
    drawSegments();

    //
    // Build Interface
    //
    SVGCheckbox checkbox = new SVGCheckbox(showUnclusteredPairs, "Show unclustered pairs");
    checkbox.addCheckBoxListener(new ChangeListener() {
      @Override
      public void stateChanged(ChangeEvent e) {
        toggleUnclusteredPairs(((SVGCheckbox) e.getSource()).isChecked());
      }
    });

    // Add ring:clustering info
    Element clrInfo = drawClusteringInfo();
    Element c = checkbox.renderCheckBox(svgp, 1, 5 + Double.parseDouble(clrInfo.getAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE)), 11);
    ctrlLayer.appendChild(clrInfo);
    ctrlLayer.appendChild(c);

    ctrlLayer.setAttribute(SVGConstants.SVG_TRANSFORM_ATTRIBUTE, "scale(" + (0.25 / StyleLibrary.SCALE) + ")");

    layer.appendChild(visLayer);
    layer.appendChild(ctrlLayer);
  }

  /**
   * Define and add required CSS classes
   */
  protected void addCSSClasses(int maxClusterSize) {
    StyleLibrary style = context.getStyleLibrary();

    // Cluster separation lines
    CSSClass cssReferenceBorder = new CSSClass(this.getClass(), CLR_BORDER_CLASS);
    cssReferenceBorder.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, style.getColor(STYLE_BORDER));
    svgp.addCSSClassOrLogError(cssReferenceBorder);

    // Hover effect for clusters
    CSSClass cluster_hover = new CSSClass(this.getClass(), CLR_HOVER_CLASS);
    // Note: !important is needed to override the regular color assignment
    cluster_hover.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, style.getColor(STYLE_HOVER) + " !important");
    cluster_hover.setStatement(SVGConstants.SVG_CURSOR_TAG, SVGConstants.SVG_POINTER_VALUE);
    svgp.addCSSClassOrLogError(cluster_hover);

    // Unpaired cluster segment
    CSSClass cluster_unpaired = new CSSClass(this.getClass(), CLR_UNPAIRED_CLASS);
    cluster_unpaired.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, style.getBackgroundColor(STYLE));
    cluster_unpaired.setStatement(SVGConstants.SVG_STROKE_ATTRIBUTE, SVGConstants.CSS_NONE_VALUE);
    svgp.addCSSClassOrLogError(cluster_unpaired);

    // Selected unpaired cluster segment
    CSSClass cluster_unpaired_s = new CSSClass(this.getClass(), SEG_UNPAIRED_SELECTED_CLASS);
    cluster_unpaired_s.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, style.getColor(STYLE_HOVER) + " !important");
    svgp.addCSSClassOrLogError(cluster_unpaired_s);

    // create Color shades for clusters
    String firstcol = style.getColor(STYLE_GRADIENT_FIRST);
    String secondcol = style.getColor(STYLE_GRADIENT_SECOND);
    String[] clusterColorShades = makeGradient(maxClusterSize, new String[] { firstcol, secondcol });

    for(int i = 0; i < maxClusterSize; i++) {
      CSSClass clusterClasses = new CSSClass(CircleSegmentsVisualizer.class, CLR_CLUSTER_CLASS_PREFIX + "_" + i);
      clusterClasses.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, clusterColorShades[i]);
      clusterClasses.setStatement(SVGConstants.SVG_STROKE_ATTRIBUTE, SVGConstants.SVG_NONE_VALUE);
      svgp.addCSSClassOrLogError(clusterClasses);
    }
  }

  /**
   * Create the segments
   */
  private void drawSegments() {
    final int clusterings = segments.getClusterings();

    // Reinitialize
    this.segmentToElements.clear();

    double angle_pair = (MathUtil.TWOPI - (SEGMENT_MIN_SEP_ANGLE * segments.size())) / segments.getPairCount(showUnclusteredPairs);
    final int pair_min_count = (int) Math.ceil(SEGMENT_MIN_ANGLE / angle_pair);

    // number of segments needed to be resized
    int cluster_min_count = 0;
    for(Segment segment : segments) {
      if(segment.getPairCount() <= pair_min_count) {
        cluster_min_count++;
      }
    }

    // update width of a pair
    angle_pair = (MathUtil.TWOPI - (SEGMENT_MIN_SEP_ANGLE * segments.size() + cluster_min_count * SEGMENT_MIN_ANGLE)) / (segments.getPairCount(showUnclusteredPairs) - cluster_min_count);
    double radius_delta = (RADIUS_OUTER - RADIUS_INNER - clusterings * RADIUS_DISTANCE) / clusterings;
    double border_width = SEGMENT_MIN_SEP_ANGLE;

    int refClustering = 0;
    int refSegment = Segment.UNCLUSTERED;
    double offsetAngle = 0.0;

    for(final Segment segment : segments) {
      long currentPairCount = segment.getPairCount();

      // resize small segments if below minimum
      double alpha = SEGMENT_MIN_ANGLE;
      if(currentPairCount > pair_min_count) {
        alpha = angle_pair * currentPairCount;
      }

      // ITERATE OVER ALL SEGMENT-CLUSTERS

      ArrayList<Element> elems = new ArrayList<Element>(clusterings);
      segmentToElements.put(segment, elems);
      // draw segment for every clustering

      for(int i = 0; i < clusterings; i++) {
        double currentRadius = i * (radius_delta + RADIUS_DISTANCE) + RADIUS_INNER;

        // Add border if the next segment is a different cluster in the
        // reference clustering
        if((refSegment != segment.get(refClustering)) && refClustering == i) {
          Element border = SVGUtil.svgCircleSegment(svgp, 0, 0, offsetAngle - SEGMENT_MIN_SEP_ANGLE, border_width, currentRadius, RADIUS_OUTER - RADIUS_DISTANCE);
          border.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, CLR_BORDER_CLASS);
          visLayer.appendChild(border);

          if(segment.get(refClustering) == Segment.UNCLUSTERED) {
            refClustering = Math.min(refClustering + 1, clusterings - 1);
          }
          refSegment = segment.get(refClustering);
        }

        int cluster = segment.get(i);

        // create ring segment
        Element segelement = SVGUtil.svgCircleSegment(svgp, 0, 0, offsetAngle, alpha, currentRadius, currentRadius + radius_delta);
        elems.add(segelement);

        // MouseEvents on segment cluster
        EventListener listener = new SegmentListenerProxy(segment, i);
        EventTarget targ = (EventTarget) segelement;
        targ.addEventListener(SVGConstants.SVG_MOUSEOVER_EVENT_TYPE, listener, false);
        targ.addEventListener(SVGConstants.SVG_MOUSEOUT_EVENT_TYPE, listener, false);
        targ.addEventListener(SVGConstants.SVG_CLICK_EVENT_TYPE, listener, false);

        // Coloring based on clusterID
        if(cluster >= 0) {
          segelement.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, CLR_CLUSTER_CLASS_PREFIX + "_" + cluster);
        }
        // if its an unpaired cluster set color to white
        else {
          segelement.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, CLR_UNPAIRED_CLASS);
        }

        visLayer.appendChild(segelement);
      }

      //
      // Add a extended strip for each segment to emphasis selection
      // (easier to track thin segments and their color coding and
      // differentiates them from cluster border lines)
      //

      double currentRadius = clusterings * (radius_delta + RADIUS_DISTANCE) + RADIUS_INNER;
      Element extension = SVGUtil.svgCircleSegment(svgp, 0, 0, offsetAngle, alpha, currentRadius, currentRadius + RADIUS_SELECTION);
      extension.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, CLR_UNPAIRED_CLASS);
      elems.add(extension);

      if(segment.isUnpaired()) {
        if(policy.isSelected(segment)) {
          SVGUtil.addCSSClass(extension, SEG_UNPAIRED_SELECTED_CLASS);
        }
        else {
          // Remove highlight
          SVGUtil.removeCSSClass(extension, SEG_UNPAIRED_SELECTED_CLASS);
        }
      }
      else {
        int idx = policy.indexOfSegment(segment);
        if(idx >= 0) {
          String color = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT).getColor(idx);
          extension.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, SVGConstants.CSS_FILL_PROPERTY + ":" + color);
        }
        else {
          // Remove styling
          extension.removeAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE);
        }
      }

      visLayer.appendChild(extension);

      // calculate angle for next segment
      offsetAngle += alpha + SEGMENT_MIN_SEP_ANGLE;
    }
  }

  private void redrawSelection() {
    logger.debug("Updating selection only.");
    for(Entry<Segment, List<Element>> entry : segmentToElements.entrySet()) {
      Segment segment = entry.getKey();
      // The selection marker is the extra element in the list
      Element extension = entry.getValue().get(segments.getClusterings());
      if(segment.isUnpaired()) {
        if(policy.isSelected(segment)) {
          SVGUtil.addCSSClass(extension, SEG_UNPAIRED_SELECTED_CLASS);
        }
        else {
          // Remove highlight
          SVGUtil.removeCSSClass(extension, SEG_UNPAIRED_SELECTED_CLASS);
        }
      }
      else {
        int idx = policy.indexOfSegment(segment);
        if(idx >= 0) {
          String color = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT).getColor(idx);
          extension.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, SVGConstants.CSS_FILL_PROPERTY + ":" + color);
        }
        else {
          // Remove styling
          extension.removeAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE);
        }
      }
    }
  }

  /**
   * Creates a gradient over a set of colors
   *
   * @param shades number of colors in the gradient
   * @param colors colors for the gradient
   * @return array of colors for CSS
   */
  protected static String[] makeGradient(int shades, String[] colors) {
    if(shades <= colors.length) {
      return colors;
    }

    // Convert SVG colors into AWT colors for math
    Color[] cols = new Color[colors.length];
    for(int i = 0; i < colors.length; i++) {
      cols[i] = SVGUtil.stringToColor(colors[i]);
      if(cols[i] == null) {
        throw new AbortException("Error parsing color: " + colors[i]);
      }
    }

    // Step size
    double increment = (cols.length - 1.) / shades;

    String[] colorShades = new String[shades];

    for(int s = 0; s < shades; s++) {
      final int ppos = Math.min((int) Math.floor(increment * s), cols.length);
      final int npos = Math.min((int) Math.ceil(increment * s), cols.length);
      if(ppos == npos) {
        colorShades[s] = colors[ppos];
      }
      else {
        Color prev = cols[ppos];
        Color next = cols[npos];
        final double mix = (increment * s - ppos) / (npos - ppos);
        final int r = (int) ((1 - mix) * prev.getRed() + mix * next.getRed());
        final int g = (int) ((1 - mix) * prev.getGreen() + mix * next.getGreen());
        final int b = (int) ((1 - mix) * prev.getBlue() + mix * next.getBlue());
        colorShades[s] = SVGUtil.colorToString(((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF));
      }
    }

    return colorShades;
  }

  protected Element drawClusteringInfo() {
    Element thumbnail = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG);

    // build thumbnail
    int startRadius = 4;
    int singleHeight = 12;
    int margin = 4;
    int radius = segments.getClusterings() * (singleHeight + margin) + startRadius;

    SVGUtil.setAtt(thumbnail, SVGConstants.SVG_HEIGHT_ATTRIBUTE, radius);

    for(int i = 0; i < segments.getClusterings(); i++) {
      double innerRadius = i * singleHeight + margin * i + startRadius;
      Element clr = SVGUtil.svgCircleSegment(svgp, radius - startRadius, radius - startRadius, Math.PI * 1.5, Math.PI * 0.5, innerRadius, innerRadius + singleHeight);
      // FIXME: Use StyleLibrary
      clr.setAttribute(SVGConstants.SVG_FILL_ATTRIBUTE, "#d4e4f1");
      clr.setAttribute(SVGConstants.SVG_STROKE_ATTRIBUTE, "#a0a0a0");
      clr.setAttribute(SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE, "1.0");

      String labelText = segments.getClusteringDescription(i);
      Element label = svgp.svgText(radius + startRadius, radius - innerRadius - startRadius, labelText);
      thumbnail.appendChild(label);

      thumbnail.appendChild(clr);
    }

    return thumbnail;
  }

  protected void segmentHover(Segment segment, int ringid, boolean active) {
    if(active) {
      // abort if this are the unclustered pairs
      if(segment.isNone()) {
        return;
      }
      if(logger.isDebugging()) {
        logger.debug("Hover on segment: " + segment + " unpaired: " + segment.isUnpaired());
      }

      if(!segment.isUnpaired()) {
        //
        // STANDARD CLUSTER SEGMENT
        // highlight all ring segments in this clustering and this cluster
        //
        // highlight all corresponding ring Segments
        for(Entry<Segment, List<Element>> entry : segmentToElements.entrySet()) {
          Segment other = entry.getKey();
          // Same cluster in same clustering?
          if(other.get(ringid) != segment.get(ringid)) {
            continue;
          }
          Element ringSegment = entry.getValue().get(ringid);
          SVGUtil.addCSSClass(ringSegment, CLR_HOVER_CLASS);
        }
      }
      else {
        //
        // UNPAIRED SEGMENT
        // highlight all ring segments in this clustering responsible for
        // unpaired
        // segment
        //
        // get the paired segments corresponding to the unpaired segment
        List<Segment> paired = segments.getPairedSegments(segment);

        for(Segment other : paired) {
          Element ringSegment = segmentToElements.get(other).get(ringid);
          SVGUtil.addCSSClass(ringSegment, CLR_HOVER_CLASS);
        }
      }
    }
    else {
      for(List<Element> elems : segmentToElements.values()) {
        for(Element current : elems) {
          SVGUtil.removeCSSClass(current, CLR_HOVER_CLASS);
        }
      }
    }
  }

  protected void segmentClick(Segment segment, Event evt, boolean dblClick) {
    MouseEvent mouse = (MouseEvent) evt;

    // CTRL (add) pressed?
    boolean ctrl = false;
    if(mouse.getCtrlKey()) {
      ctrl = true;
    }

    // Unselect others on double click
    if(dblClick) {
      policy.deselectAllSegments();
    }
    policy.select(segment, ctrl);
    // update stylePolicy
    context.getStyleResult().setStylingPolicy(policy);
    // fire changed event to trigger redraw
    context.getHierarchy().resultChanged(context.getStyleResult());
  }

  /**
   * Proxy element to connect signals.
   *
   * @author Erich Schubert
   */
  private class SegmentListenerProxy implements EventListener {
    /**
     * Mouse double click time window in milliseconds
     *
     * TODO: does Batik have double click events?
     */
    public static final int EVT_DBLCLICK_DELAY = 350;

    /**
     * Segment we are attached to
     */
    private Segment id;

    /**
     * Segment ring we are
     */
    private int ringid;

    /**
     * For detecting double clicks.
     */
    private long lastClick = 0;

    /**
     * Constructor.
     *
     * @param id Segment id
     * @param ringid Ring id
     */
    public SegmentListenerProxy(Segment id, int ringid) {
      super();
      this.id = id;
      this.ringid = ringid;
    }

    @Override
    public void handleEvent(Event evt) {
      if(SVGConstants.SVG_MOUSEOVER_EVENT_TYPE.equals(evt.getType())) {
        segmentHover(id, ringid, true);
      }
      if(SVGConstants.SVG_MOUSEOUT_EVENT_TYPE.equals(evt.getType())) {
        segmentHover(id, ringid, false);
      }
      if(SVGConstants.SVG_CLICK_EVENT_TYPE.equals(evt.getType())) {
        // Check Double Click
        boolean dblClick = false;
        long time = java.util.Calendar.getInstance().getTimeInMillis();
        if(time - lastClick <= EVT_DBLCLICK_DELAY) {
          dblClick = true;
        }
        lastClick = time;

        segmentClick(id, evt, dblClick);
      }
    }
  }

  /**
   * Factory for visualizers for a circle segment
   *
   * @author Sascha Goldhofer
   *
   * @apiviz.stereotype factory
   * @apiviz.uses CircleSegmentsVisualizer oneway - - «create»
   */
  public static class Factory extends AbstractVisFactory {
    /**
     * Constructor
     */
    public Factory() {
      super();
    }

    @Override
    public Visualization makeVisualization(VisualizationTask task) {
      return new CircleSegmentsVisualizer(task);
    }

    @Override
    public void processNewResult(HierarchicalResult baseResult, Result result) {
      // If no comparison result found abort
      List<Segments> segments = ResultUtil.filterResults(result, Segments.class);
      for(Segments segmentResult : segments) {
        // create task for visualization
        final VisualizationTask task = new VisualizationTask(NAME, segmentResult, null, this);
        task.width = 2.0;
        task.height = 2.0;
        task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE);
        baseResult.getHierarchy().add(segmentResult, task);
      }
    }
  };
}
TOP

Related Classes of de.lmu.ifi.dbs.elki.visualization.visualizers.pairsegments.CircleSegmentsVisualizer

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.