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

Source Code of de.lmu.ifi.dbs.elki.visualization.visualizers.vis1d.P1DHistogramVisualizer

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

/*
This file is part of ELKI:
Environment for Developing KDD-Applications Supported by Index-Structures

Copyright (C) 2011
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.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.batik.util.SVGConstants;
import org.w3c.dom.Element;

import de.lmu.ifi.dbs.elki.data.Cluster;
import de.lmu.ifi.dbs.elki.data.Clustering;
import de.lmu.ifi.dbs.elki.data.NumberVector;
import de.lmu.ifi.dbs.elki.data.model.Model;
import de.lmu.ifi.dbs.elki.database.ids.DBID;
import de.lmu.ifi.dbs.elki.database.relation.Relation;
import de.lmu.ifi.dbs.elki.logging.LoggingUtil;
import de.lmu.ifi.dbs.elki.math.AggregatingHistogram;
import de.lmu.ifi.dbs.elki.math.DoubleMinMax;
import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector;
import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
import de.lmu.ifi.dbs.elki.result.Result;
import de.lmu.ifi.dbs.elki.result.ResultUtil;
import de.lmu.ifi.dbs.elki.utilities.DatabaseUtil;
import de.lmu.ifi.dbs.elki.utilities.exceptions.ObjectNotFoundException;
import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.constraints.GreaterEqualConstraint;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Flag;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.IntParameter;
import de.lmu.ifi.dbs.elki.utilities.pairs.Pair;
import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
import de.lmu.ifi.dbs.elki.visualization.colors.ColorLibrary;
import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
import de.lmu.ifi.dbs.elki.visualization.css.CSSClassManager.CSSNamingConflict;
import de.lmu.ifi.dbs.elki.visualization.projections.Projection;
import de.lmu.ifi.dbs.elki.visualization.projector.HistogramProjector;
import de.lmu.ifi.dbs.elki.visualization.scales.LinearScale;
import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
import de.lmu.ifi.dbs.elki.visualization.svg.SVGPath;
import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot;
import de.lmu.ifi.dbs.elki.visualization.svg.SVGSimpleLinearAxis;
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.Visualization;

/**
* Generates a SVG-Element containing a histogram representing the distribution
* of the database's objects.
*
* @author Remigius Wojdanowski
*
* @apiviz.has NumberVector oneway - - visualizes
*
* @param <NV> Type of the DatabaseObject being visualized.
*/
// FIXME: make non-static, react to database changes!
// FIXME: cache histogram instead of recomputing it.
public class P1DHistogramVisualizer<NV extends NumberVector<NV, ?>> extends P1DVisualization {
  /**
   * Name for this visualizer.
   */
  private static final String NAME = "Histogram";

  /**
   * Name for this visualizer.
   */
  private static final String CNAME = "Cluster Histograms";

  /**
   * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc.
   */
  public static final String BIN = "bin";

  /**
   * Internal storage of the curves flag.
   */
  private boolean curves;

  /**
   * Number of bins to use in the histogram.
   */
  private int bins;

  /**
   * The database we visualize
   */
  private Relation<NV> relation;

  /**
   * The clustering we visualize
   */
  private Clustering<Model> clustering;

  /**
   * Constructor.
   *
   * @param task Visualization task
   * @param curves Curves flag
   * @param bins Number of bins
   */
  public P1DHistogramVisualizer(VisualizationTask task, boolean curves, int bins) {
    super(task);
    this.curves = curves;
    this.bins = bins;
    this.relation = task.getRelation();
    this.clustering = task.getResult();
  }

  @Override
  protected void redraw() {
    double margin = context.getStyleLibrary().getSize(StyleLibrary.MARGIN);
    layer = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG);
    double xsize = Projection.SCALE * task.getWidth() / task.getHeight();
    double ysize = Projection.SCALE;

    final String transform = SVGUtil.makeMarginTransform(task.getWidth(), task.getHeight(), xsize, ysize, margin);
    SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform);

    final List<Cluster<Model>> allClusters = clustering != null ? clustering.getAllClusters() : null;
    final int numc = allClusters != null ? allClusters.size() : 0;

    setupCSS(svgp, numc);

    // Creating histograms
    DoubleMinMax minmax = new DoubleMinMax();
    final double frac = 1. / relation.size();
    final int cols = numc + 1;
    AggregatingHistogram<double[], double[]> histogram = new AggregatingHistogram<double[], double[]>(bins, -.5, .5, new AggregatingHistogram.Adapter<double[], double[]>() {
      @Override
      public double[] aggregate(double[] existing, double[] data) {
        for(int i = 0; i < existing.length; i++) {
          existing[i] += data[i];
        }
        return existing;
      }

      @Override
      public double[] make() {
        return new double[cols];
      }
    });

    if(allClusters != null) {
      int clusterID = 0;
      for(Cluster<Model> cluster : allClusters) {
        double[] inc = new double[cols];
        inc[clusterID + 1] = frac;
        for(DBID id : cluster.getIDs()) {
          try {
            double pos = proj.fastProjectDataToRenderSpace(relation.get(id)) / Projection.SCALE;
            histogram.aggregate(pos, inc);
          }
          catch(ObjectNotFoundException e) {
            // Ignore. The object was probably deleted from the database
          }
        }
        clusterID += 1;
      }
    }
    // Actual data distribution.
    double[] inc = new double[cols];
    inc[0] = frac;
    for(DBID id : relation.iterDBIDs()) {
      double pos = proj.fastProjectDataToRenderSpace(relation.get(id)) / Projection.SCALE;
      histogram.aggregate(pos, inc);
    }
    // for scaling, get the maximum occurring value in the bins:
    for(Pair<Double, double[]> bin : histogram) {
      for(double val : bin.second) {
        minmax.put(val);
      }
    }

    LinearScale yscale = new LinearScale(0, minmax.getMax());
    LinearScale xscale = new LinearScale(histogram.getCoverMinimum(), histogram.getCoverMaximum());

    // Axis. TODO: Use AxisVisualizer for this?
    try {
      SVGSimpleLinearAxis.drawAxis(svgp, layer, yscale, 0, ysize, 0, 0, true, false, context.getStyleLibrary());

      // draw axes that are non-trivial
      final int dimensionality = DatabaseUtil.dimensionality(relation);
      double orig = proj.fastProjectScaledToRender(new Vector(dimensionality));
      for(int d = 0; d < dimensionality; d++) {
        Vector v = new Vector(dimensionality);
        v.set(d, 1);
        // projected endpoint of axis
        double ax = proj.fastProjectScaledToRender(v);
        if(ax != orig) {
          final double left = (orig / Projection.SCALE + 0.5) * xsize;
          final double right = (ax / Projection.SCALE + 0.5) * xsize;
          SVGSimpleLinearAxis.drawAxis(svgp, layer, proj.getScale(d), left, ysize, right, ysize, true, true, context.getStyleLibrary());
        }
      }
    }
    catch(CSSNamingConflict e) {
      LoggingUtil.exception("CSS class exception in axis class.", e);
    }

    double binwidth = histogram.getBinsize();
    // Visualizing
    if(!curves) {
      for(Pair<Double, double[]> bin : histogram) {
        double lpos = xscale.getScaled(bin.getFirst() - binwidth / 2);
        double rpos = xscale.getScaled(bin.getFirst() + binwidth / 2);
        double stack = 0.0;
        final int start = numc > 0 ? 1 : 0;
        for(int key = start; key < cols; key++) {
          double val = yscale.getScaled(bin.getSecond()[key]);
          Element row = SVGUtil.svgRect(svgp.getDocument(), xsize * lpos, ysize * (1 - (val + stack)), xsize * (rpos - lpos), ysize * val);
          stack = stack + val;
          SVGUtil.addCSSClass(row, BIN + (key - 1));
          layer.appendChild(row);
        }
      }
    }
    else {
      double left = xscale.getScaled(histogram.getCoverMinimum());
      double right = left;

      SVGPath[] paths = new SVGPath[cols];
      double[] lasty = new double[cols];
      for(int i = 0; i < cols; i++) {
        paths[i] = new SVGPath(xsize * left, ysize * 1);
        lasty[i] = 0;
      }

      // draw histogram lines
      for(Pair<Double, double[]> bin : histogram) {
        left = xscale.getScaled(bin.getFirst() - binwidth / 2);
        right = xscale.getScaled(bin.getFirst() + binwidth / 2);
        for(int i = 0; i < cols; i++) {
          double val = yscale.getScaled(bin.getSecond()[i]);
          if(lasty[i] != val) {
            paths[i].lineTo(xsize * left, ysize * (1 - lasty[i]));
            paths[i].lineTo(xsize * left, ysize * (1 - val));
            paths[i].lineTo(xsize * right, ysize * (1 - val));
            lasty[i] = val;
          }
        }
      }
      // close and insert all lines.
      for(int i = 0; i < cols; i++) {
        if(lasty[i] != 0) {
          paths[i].lineTo(xsize * right, ysize * (1 - lasty[i]));
        }
        paths[i].lineTo(xsize * right, ysize * 1);
        Element elem = paths[i].makeElement(svgp);
        SVGUtil.addCSSClass(elem, BIN + (i - 1));
        layer.appendChild(elem);
      }
    }
  }

  /**
   * Generate the needed CSS classes.
   *
   * @param svgp Plot context
   * @param numc Number of classes we need.
   */
  private void setupCSS(SVGPlot svgp, int numc) {
    ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT);

    CSSClass allInOne = new CSSClass(svgp, BIN + -1);
    if(!curves) {
      allInOne.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_BLACK_VALUE);
      allInOne.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 1.0);
    }
    else {
      allInOne.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_BLACK_VALUE);
      allInOne.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT));
      allInOne.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
    }
    svgp.addCSSClassOrLogError(allInOne);

    for(int clusterID = 0; clusterID < numc; clusterID++) {
      CSSClass bin = new CSSClass(svgp, BIN + clusterID);

      if(!curves) {
        bin.setStatement(SVGConstants.CSS_FILL_PROPERTY, colors.getColor(clusterID));
      }
      else {
        bin.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(clusterID));
        bin.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT));
        bin.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
      }

      svgp.addCSSClassOrLogError(bin);
    }
  }

  /**
   * Visualizer factory for 1D histograms
   *
   * @author Erich Schubert
   *
   * @apiviz.stereotype factory
   * @apiviz.uses P1DHistogramVisualizer oneway - - «create»
   *
   * @param <NV> Number vector type
   */
  public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory {
    /**
     * Flag to specify the "curves" rendering style.
     *
     * <p>
     * Key: {@code -histogram.curves}
     * </p>
     */
    public static final OptionID STYLE_CURVES_ID = OptionID.getOrCreateOptionID("projhistogram.curves", "Use curves instead of the stacked histogram style.");

    /**
     * Parameter to specify the number of bins to use in histogram.
     *
     * <p>
     * Key: {@code -projhistogram.bins} Default: 20
     * </p>
     */
    public static final OptionID HISTOGRAM_BINS_ID = OptionID.getOrCreateOptionID("projhistogram.bins", "Number of bins in the distribution histogram");

    /**
     * Internal storage of the curves flag.
     */
    private boolean curves;

    /**
     * Number of bins to use in histogram.
     */
    private static final int DEFAULT_BINS = 50;

    /**
     * Number of bins to use in the histogram.
     */
    private int bins = DEFAULT_BINS;

    /**
     * Constructor.
     *
     * @param curves
     * @param bins
     */
    public Factory(boolean curves, int bins) {
      super();
      this.curves = curves;
      this.bins = bins;
    }

    @Override
    public Visualization makeVisualization(VisualizationTask task) {
      return new P1DHistogramVisualizer<NV>(task, curves, bins);
    }

    @Override
    public void processNewResult(HierarchicalResult baseResult, Result result) {
      // Cluster histograms
      ArrayList<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class);
      for(Clustering<?> c : clusterings) {
        Iterator<HistogramProjector<?>> ps = ResultUtil.filteredResults(baseResult, HistogramProjector.class);
        for(HistogramProjector<?> p : IterableUtil.fromIterator(ps)) {
          // register self
          final VisualizationTask task = new VisualizationTask(CNAME, c, p.getRelation(), this);
          task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA);
          baseResult.getHierarchy().add(c, task);
          baseResult.getHierarchy().add(p, task);
        }
      }
      // General data distribution
      {
        Iterator<HistogramProjector<?>> ps = ResultUtil.filteredResults(result, HistogramProjector.class);
        for(HistogramProjector<?> p : IterableUtil.fromIterator(ps)) {
          // register self
          final VisualizationTask task = new VisualizationTask(NAME, null, p.getRelation(), this);
          task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA);
          if(clusterings.size() > 0) {
            task.put(VisualizationTask.META_VISIBLE_DEFAULT, false);
          }
          // baseResult.getHierarchy().add(p.getRelation(), task);
          baseResult.getHierarchy().add(p, task);
        }
      }
    }

    @Override
    public boolean allowThumbnails(VisualizationTask task) {
      // Don't use thumbnails
      return false;
    }

    /**
     * Parameterization class.
     *
     * @author Erich Schubert
     *
     * @apiviz.exclude
     */
    public static class Parameterizer<NV extends NumberVector<NV, ?>> extends AbstractParameterizer {
      /**
       * Internal storage of the curves flag.
       */
      private boolean curves;

      /**
       * Number of bins to use in the histogram.
       */
      private int bins = DEFAULT_BINS;

      @Override
      protected void makeOptions(Parameterization config) {
        super.makeOptions(config);
        Flag STYLE_CURVES_FLAG = new Flag(STYLE_CURVES_ID);
        if(config.grab(STYLE_CURVES_FLAG)) {
          curves = STYLE_CURVES_FLAG.getValue();
        }
        IntParameter HISTOGRAM_BINS_PARAM = new IntParameter(HISTOGRAM_BINS_ID, new GreaterEqualConstraint(2), DEFAULT_BINS);
        if(config.grab(HISTOGRAM_BINS_PARAM)) {
          bins = HISTOGRAM_BINS_PARAM.getValue();
        }
      }

      @Override
      protected Factory<NV> makeInstance() {
        return new Factory<NV>(curves, bins);
      }
    }
  }
}
TOP

Related Classes of de.lmu.ifi.dbs.elki.visualization.visualizers.vis1d.P1DHistogramVisualizer

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.