1 /*
2  * This file is part of ELKI:
3  * Environment for Developing KDD-Applications Supported by Index-Structures
4  *
5  * Copyright (C) 2018
6  * ELKI Development Team
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU Affero General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU Affero General Public License for more details.
17  *
18  * You should have received a copy of the GNU Affero General Public License
19  * along with this program. If not, see <http://www.gnu.org/licenses/>.
20  */
21 package de.lmu.ifi.dbs.elki.visualization.visualizers.visunproj;
22 
23 import org.apache.batik.util.SVGConstants;
24 import org.w3c.dom.Element;
25 
26 import de.lmu.ifi.dbs.elki.evaluation.outlier.OutlierPrecisionRecallCurve;
27 import de.lmu.ifi.dbs.elki.evaluation.outlier.OutlierPrecisionRecallCurve.PRCurve;
28 import de.lmu.ifi.dbs.elki.evaluation.outlier.OutlierROCCurve;
29 import de.lmu.ifi.dbs.elki.evaluation.outlier.OutlierROCCurve.ROCResult;
30 import de.lmu.ifi.dbs.elki.logging.LoggingUtil;
31 import de.lmu.ifi.dbs.elki.math.geometry.XYCurve;
32 import de.lmu.ifi.dbs.elki.math.scales.LinearScale;
33 import de.lmu.ifi.dbs.elki.utilities.io.FormatUtil;
34 import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
35 import de.lmu.ifi.dbs.elki.visualization.VisualizationTree;
36 import de.lmu.ifi.dbs.elki.visualization.VisualizerContext;
37 import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
38 import de.lmu.ifi.dbs.elki.visualization.css.CSSClassManager.CSSNamingConflict;
39 import de.lmu.ifi.dbs.elki.visualization.gui.VisualizationPlot;
40 import de.lmu.ifi.dbs.elki.visualization.projections.Projection;
41 import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
42 import de.lmu.ifi.dbs.elki.visualization.svg.SVGPath;
43 import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot;
44 import de.lmu.ifi.dbs.elki.visualization.svg.SVGSimpleLinearAxis;
45 import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil;
46 import de.lmu.ifi.dbs.elki.visualization.visualizers.StaticVisualizationInstance;
47 import de.lmu.ifi.dbs.elki.visualization.visualizers.VisFactory;
48 import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;
49 
50 /**
51  * Visualizer to render a simple 2D curve such as a ROC curve.
52  *
53  * @author Erich Schubert
54  * @since 0.3
55  *
56  * @stereotype factory
57  * @navassoc - create - StaticVisualizationInstance
58  * @navhas - visualizes - XYCurve
59  */
60 public class XYCurveVisualization implements VisFactory {
61   /**
62    * Name for this visualizer.
63    */
64   private static final String NAME = "XYCurve";
65 
66   /**
67    * SVG class name for plot line
68    */
69   private static final String SERIESID = "series";
70 
71   /**
72    * Axis labels
73    */
74   private static final String CSS_AXIS_LABEL = "xy-axis-label";
75 
76   /**
77    * Constructor, Parameterizable style - does nothing.
78    */
XYCurveVisualization()79   public XYCurveVisualization() {
80     super();
81   }
82 
83   @Override
makeVisualization(VisualizerContext context, VisualizationTask task, VisualizationPlot plot, double width, double height, Projection proj)84   public Visualization makeVisualization(VisualizerContext context, VisualizationTask task, VisualizationPlot plot, double width, double height, Projection proj) {
85     XYCurve curve = task.getResult();
86 
87     setupCSS(context, plot);
88     final StyleLibrary style = context.getStyleLibrary();
89     final double sizex = StyleLibrary.SCALE;
90     final double sizey = StyleLibrary.SCALE * height / width;
91     final double margin = style.getSize(StyleLibrary.MARGIN);
92     Element layer = SVGUtil.svgElement(plot.getDocument(), SVGConstants.SVG_G_TAG);
93     final String transform = SVGUtil.makeMarginTransform(width, height, sizex, sizey, margin);
94     SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform);
95 
96     // determine scaling
97     LinearScale scalex = new LinearScale(curve.getMinx(), curve.getMaxx());
98     LinearScale scaley = new LinearScale(curve.getMiny(), curve.getMaxy());
99     // plot the line
100     SVGPath path = new SVGPath();
101     for(XYCurve.Itr iterator = curve.iterator(); iterator.valid(); iterator.advance()) {
102       final double x = scalex.getScaled(iterator.getX());
103       final double y = 1 - scaley.getScaled(iterator.getY());
104       path.drawTo(sizex * x, sizey * y);
105     }
106     Element line = path.makeElement(plot, SERIESID);
107 
108     // add axes
109     try {
110       SVGSimpleLinearAxis.drawAxis(plot, layer, scaley, 0, sizey, 0, 0, SVGSimpleLinearAxis.LabelStyle.LEFTHAND, style);
111       SVGSimpleLinearAxis.drawAxis(plot, layer, scalex, 0, sizey, sizex, sizey, SVGSimpleLinearAxis.LabelStyle.RIGHTHAND, style);
112     }
113     catch(CSSNamingConflict e) {
114       LoggingUtil.exception(e);
115     }
116     // Add axis labels
117     {
118       Element labelx = plot.svgText(sizex * .5, sizey + margin * .9, curve.getLabelx());
119       SVGUtil.setCSSClass(labelx, CSS_AXIS_LABEL);
120       layer.appendChild(labelx);
121       Element labely = plot.svgText(margin * -.8, sizey * .5, curve.getLabely());
122       SVGUtil.setCSSClass(labely, CSS_AXIS_LABEL);
123       SVGUtil.setAtt(labely, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, "rotate(-90," + FormatUtil.NF6.format(margin * -.8) + "," + FormatUtil.NF6.format(sizey * .5) + ")");
124       layer.appendChild(labely);
125     }
126 
127     // Add AUC value when found
128     if(curve instanceof ROCResult) {
129       double rocauc = ((ROCResult) curve).getAUC();
130       String lt = OutlierROCCurve.ROCAUC_LABEL + ": " + FormatUtil.NF.format(rocauc);
131       if(rocauc <= 0.5) {
132         Element auclbl = plot.svgText(sizex * 0.5, sizey * 0.10, lt);
133         SVGUtil.setCSSClass(auclbl, CSS_AXIS_LABEL);
134         layer.appendChild(auclbl);
135       }
136       else {
137         Element auclbl = plot.svgText(sizex * 0.5, sizey * 0.95, lt);
138         SVGUtil.setCSSClass(auclbl, CSS_AXIS_LABEL);
139         layer.appendChild(auclbl);
140       }
141     }
142     if(curve instanceof PRCurve) {
143       double prauc = ((PRCurve) curve).getAUC();
144       String lt = OutlierPrecisionRecallCurve.PRAUC_LABEL + ": " + FormatUtil.NF.format(prauc);
145       if(prauc <= 0.5) {
146         Element auclbl = plot.svgText(sizex * 0.5, sizey * 0.10, lt);
147         SVGUtil.setCSSClass(auclbl, CSS_AXIS_LABEL);
148         layer.appendChild(auclbl);
149       }
150       else {
151         Element auclbl = plot.svgText(sizex * 0.5, sizey * 0.95, lt);
152         SVGUtil.setCSSClass(auclbl, CSS_AXIS_LABEL);
153         layer.appendChild(auclbl);
154       }
155     }
156 
157     layer.appendChild(line);
158     return new StaticVisualizationInstance(context, task, plot, width, height, layer);
159   }
160 
161   /**
162    * Setup the CSS classes for the plot.
163    *
164    * @param svgp Plot
165    */
setupCSS(VisualizerContext context, SVGPlot svgp)166   private void setupCSS(VisualizerContext context, SVGPlot svgp) {
167     StyleLibrary style = context.getStyleLibrary();
168     CSSClass csscls = new CSSClass(this, SERIESID);
169     // csscls.setStatement(SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE, "0.2%");
170     csscls.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, SVGConstants.SVG_NONE_VALUE);
171     style.lines().formatCSSClass(csscls, 0, style.getLineWidth(StyleLibrary.XYCURVE));
172     svgp.addCSSClassOrLogError(csscls);
173     // Axis label
174     CSSClass label = new CSSClass(this, CSS_AXIS_LABEL);
175     label.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getTextColor(StyleLibrary.XYCURVE));
176     label.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, style.getFontFamily(StyleLibrary.XYCURVE));
177     label.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, style.getTextSize(StyleLibrary.XYCURVE));
178     label.setStatement(SVGConstants.CSS_TEXT_ANCHOR_PROPERTY, SVGConstants.CSS_MIDDLE_VALUE);
179     svgp.addCSSClassOrLogError(label);
180     svgp.updateStyleElement();
181   }
182 
183   @Override
processNewResult(VisualizerContext context, Object start)184   public void processNewResult(VisualizerContext context, Object start) {
185     VisualizationTree.findNewResults(context, start).filter(XYCurve.class).forEach(curve -> {
186       context.addVis(curve, new VisualizationTask(this, NAME, curve, null) //
187           .level(VisualizationTask.LEVEL_STATIC));
188     });
189   }
190 
191   @Override
allowThumbnails(VisualizationTask task)192   public boolean allowThumbnails(VisualizationTask task) {
193     // TODO: depending on the curve complexity?
194     return false;
195   }
196 }
197