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