package de.lmu.ifi.dbs.elki.visualization.visualizers.visunproj;
/*
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 .
*/
import java.util.ArrayList;
import org.apache.batik.util.SVGConstants;
import org.w3c.dom.Element;
import de.lmu.ifi.dbs.elki.evaluation.outlier.OutlierROCCurve;
import de.lmu.ifi.dbs.elki.evaluation.outlier.OutlierROCCurve.ROCResult;
import de.lmu.ifi.dbs.elki.evaluation.outlier.OutlierPrecisionRecallCurve.PRCurve;
import de.lmu.ifi.dbs.elki.logging.LoggingUtil;
import de.lmu.ifi.dbs.elki.math.geometry.XYCurve;
import de.lmu.ifi.dbs.elki.math.scales.LinearScale;
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.FormatUtil;
import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
import de.lmu.ifi.dbs.elki.visualization.VisualizerContext;
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.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.StaticVisualization;
import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;
/**
* Visualizer to render a simple 2D curve such as a ROC curve.
*
* @author Erich Schubert
*
* @apiviz.stereotype factory
* @apiviz.uses StaticVisualization oneway - - «create»
* @apiviz.has XYCurve oneway - - visualizes
*/
public class XYCurveVisFactory extends AbstractVisFactory {
/**
* Name for this visualizer.
*/
private static final String NAME = "XYCurve";
/**
* SVG class name for plot line
*/
private static final String SERIESID = "series";
/**
* Axis labels
*/
private static final String CSS_AXIS_LABEL = "xy-axis-label";
/**
* Constructor, Parameterizable style - does nothing.
*/
public XYCurveVisFactory() {
super();
}
@Override
public Visualization makeVisualization(VisualizationTask task) {
VisualizerContext context = task.getContext();
SVGPlot svgp = task.getPlot();
XYCurve curve = task.getResult();
setupCSS(context, svgp);
double scale = StyleLibrary.SCALE;
final double sizex = scale;
final double sizey = scale * task.getHeight() / task.getWidth();
final double margin = context.getStyleLibrary().getSize(StyleLibrary.MARGIN);
Element layer = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG);
final String transform = SVGUtil.makeMarginTransform(task.getWidth(), task.getHeight(), sizex, sizey, margin);
SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform);
// determine scaling
LinearScale scalex = new LinearScale(curve.getMinx(), curve.getMaxx());
LinearScale scaley = new LinearScale(curve.getMiny(), curve.getMaxy());
// plot the line
SVGPath path = new SVGPath();
for(XYCurve.Itr iterator = curve.iterator(); iterator.valid(); iterator.advance()) {
final double x = scalex.getScaled(iterator.getX());
final double y = 1 - scaley.getScaled(iterator.getY());
path.drawTo(sizex * x, sizey * y);
}
Element line = path.makeElement(svgp);
line.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, SERIESID);
// add axes
try {
SVGSimpleLinearAxis.drawAxis(svgp, layer, scaley, 0, sizey, 0, 0, SVGSimpleLinearAxis.LabelStyle.LEFTHAND, context.getStyleLibrary());
SVGSimpleLinearAxis.drawAxis(svgp, layer, scalex, 0, sizey, sizex, sizey, SVGSimpleLinearAxis.LabelStyle.RIGHTHAND, context.getStyleLibrary());
}
catch(CSSNamingConflict e) {
LoggingUtil.exception(e);
}
// Add axis labels
{
Element labelx = svgp.svgText(sizex / 2, sizey + margin * .9, curve.getLabelx());
SVGUtil.setCSSClass(labelx, CSS_AXIS_LABEL);
layer.appendChild(labelx);
Element labely = svgp.svgText(margin * -.8, sizey * .5, curve.getLabely());
SVGUtil.setCSSClass(labely, CSS_AXIS_LABEL);
SVGUtil.setAtt(labely, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, "rotate(-90,"+FormatUtil.NF6.format(margin * -.8)+","+FormatUtil.NF6.format(sizey * .5)+")");
layer.appendChild(labely);
}
// Add AUC value when found
if(curve instanceof ROCResult) {
double rocauc = ((ROCResult) curve).getAUC();
String lt = OutlierROCCurve.ROCAUC_LABEL + ": " + FormatUtil.NF8.format(rocauc);
if(rocauc <= 0.5) {
Element auclbl = svgp.svgText(sizex * 0.5, sizey * 0.10, lt);
SVGUtil.setCSSClass(auclbl, CSS_AXIS_LABEL);
layer.appendChild(auclbl);
}
else {
Element auclbl = svgp.svgText(sizex * 0.5, sizey * 0.95, lt);
SVGUtil.setCSSClass(auclbl, CSS_AXIS_LABEL);
layer.appendChild(auclbl);
}
}
if(curve instanceof PRCurve) {
double prauc = ((PRCurve) curve).getAUC();
String lt = PRCurve.PRAUC_LABEL + ": " + FormatUtil.NF8.format(prauc);
if(prauc <= 0.5) {
Element auclbl = svgp.svgText(sizex * 0.5, sizey * 0.10, lt);
SVGUtil.setCSSClass(auclbl, CSS_AXIS_LABEL);
layer.appendChild(auclbl);
}
else {
Element auclbl = svgp.svgText(sizex * 0.5, sizey * 0.95, lt);
SVGUtil.setCSSClass(auclbl, CSS_AXIS_LABEL);
layer.appendChild(auclbl);
}
}
layer.appendChild(line);
return new StaticVisualization(task, layer);
}
/**
* Setup the CSS classes for the plot.
*
* @param svgp Plot
*/
private void setupCSS(VisualizerContext context, SVGPlot svgp) {
StyleLibrary style = context.getStyleLibrary();
CSSClass csscls = new CSSClass(this, SERIESID);
// csscls.setStatement(SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE, "0.2%");
csscls.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, SVGConstants.SVG_NONE_VALUE);
style.lines().formatCSSClass(csscls, 0, style.getLineWidth(StyleLibrary.XYCURVE));
svgp.addCSSClassOrLogError(csscls);
// Axis label
CSSClass label = new CSSClass(this, CSS_AXIS_LABEL);
label.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getTextColor(StyleLibrary.XYCURVE));
label.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, style.getFontFamily(StyleLibrary.XYCURVE));
label.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, style.getTextSize(StyleLibrary.XYCURVE));
label.setStatement(SVGConstants.CSS_TEXT_ANCHOR_PROPERTY, SVGConstants.CSS_MIDDLE_VALUE);
svgp.addCSSClassOrLogError(label);
svgp.updateStyleElement();
}
@Override
public void processNewResult(HierarchicalResult baseResult, Result result) {
final ArrayList curves = ResultUtil.filterResults(result, XYCurve.class);
for(XYCurve curve : curves) {
final VisualizationTask task = new VisualizationTask(NAME, curve, null, this);
task.width = 1.0;
task.height = 1.0;
task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_STATIC);
baseResult.getHierarchy().add(curve, task);
}
}
@Override
public boolean allowThumbnails(VisualizationTask task) {
// TODO: depending on the curve complexity?
return false;
}
}