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.parallel; 22 23 import org.apache.batik.util.SVGConstants; 24 import org.w3c.dom.Element; 25 26 import de.lmu.ifi.dbs.elki.data.spatial.SpatialComparable; 27 import de.lmu.ifi.dbs.elki.data.type.TypeUtil; 28 import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener; 29 import de.lmu.ifi.dbs.elki.database.ids.DBIDIter; 30 import de.lmu.ifi.dbs.elki.database.ids.DBIDRef; 31 import de.lmu.ifi.dbs.elki.database.ids.DBIDs; 32 import de.lmu.ifi.dbs.elki.database.relation.Relation; 33 import de.lmu.ifi.dbs.elki.math.MathUtil; 34 import de.lmu.ifi.dbs.elki.result.SamplingResult; 35 import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; 36 import de.lmu.ifi.dbs.elki.visualization.VisualizationTask.UpdateFlag; 37 import de.lmu.ifi.dbs.elki.visualization.VisualizationTree; 38 import de.lmu.ifi.dbs.elki.visualization.VisualizerContext; 39 import de.lmu.ifi.dbs.elki.visualization.css.CSSClass; 40 import de.lmu.ifi.dbs.elki.visualization.gui.VisualizationPlot; 41 import de.lmu.ifi.dbs.elki.visualization.projections.Projection; 42 import de.lmu.ifi.dbs.elki.visualization.projector.ParallelPlotProjector; 43 import de.lmu.ifi.dbs.elki.visualization.style.ClassStylingPolicy; 44 import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; 45 import de.lmu.ifi.dbs.elki.visualization.style.StylingPolicy; 46 import de.lmu.ifi.dbs.elki.visualization.style.lines.LineStyleLibrary; 47 import de.lmu.ifi.dbs.elki.visualization.svg.SVGPath; 48 import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil; 49 import de.lmu.ifi.dbs.elki.visualization.visualizers.VisFactory; 50 import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; 51 52 /** 53 * Draw spatial objects (except vectors!) 54 * 55 * @author Erich Schubert 56 * @since 0.5.0 57 * 58 * @stereotype factory 59 * @navassoc - create - Instance 60 */ 61 // TODO: draw filled instead? 62 public class BoundingBoxVisualization implements VisFactory { 63 /** 64 * A short name characterizing this Visualizer. 65 */ 66 public static final String NAME = "Spatial objects"; 67 68 /** 69 * Constructor. 70 */ BoundingBoxVisualization()71 public BoundingBoxVisualization() { 72 super(); 73 } 74 75 @Override makeVisualization(VisualizerContext context, VisualizationTask task, VisualizationPlot plot, double width, double height, Projection proj)76 public Visualization makeVisualization(VisualizerContext context, VisualizationTask task, VisualizationPlot plot, double width, double height, Projection proj) { 77 return new Instance(context, task, plot, width, height, proj); 78 } 79 80 @Override processNewResult(VisualizerContext context, Object start)81 public void processNewResult(VisualizerContext context, Object start) { 82 VisualizationTree.findVis(context, start).filter(ParallelPlotProjector.class).forEach(p -> { 83 final Relation<?> rel = p.getRelation(); 84 if(TypeUtil.NUMBER_VECTOR_FIELD.isAssignableFromType(rel.getDataTypeInformation())) { 85 return; 86 } 87 if(!TypeUtil.SPATIAL_OBJECT.isAssignableFromType(rel.getDataTypeInformation())) { 88 return; 89 } 90 context.addVis(p, new VisualizationTask(this, NAME, p.getRelation(), p.getRelation()) // 91 .level(VisualizationTask.LEVEL_DATA) // 92 .with(UpdateFlag.ON_DATA).with(UpdateFlag.ON_STYLEPOLICY).with(UpdateFlag.ON_SAMPLE)); 93 }); 94 } 95 96 /** 97 * Instance for a particular data set. 98 * 99 * @author Robert Rödler 100 */ 101 public class Instance extends AbstractParallelVisualization<SpatialComparable> implements DataStoreListener { 102 /** 103 * Generic tags to indicate the type of element. Used in IDs, CSS-Classes 104 * etc. 105 */ 106 public static final String DATALINE = "Databox"; 107 108 /** 109 * Sample we visualize. 110 */ 111 private SamplingResult sample; 112 113 /** 114 * Constructor. 115 * 116 * @param context Visualizer context 117 * @param task VisualizationTask 118 * @param plot Plot to draw to 119 * @param width Embedding width 120 * @param height Embedding height 121 * @param proj Projection 122 */ Instance(VisualizerContext context, VisualizationTask task, VisualizationPlot plot, double width, double height, Projection proj)123 public Instance(VisualizerContext context, VisualizationTask task, VisualizationPlot plot, double width, double height, Projection proj) { 124 super(context, task, plot, width, height, proj); 125 this.sample = SamplingResult.getSamplingResult(relation); 126 addListeners(); 127 } 128 129 @Override fullRedraw()130 public void fullRedraw() { 131 super.fullRedraw(); 132 final DBIDs sam = sample.getSample(); 133 StylingPolicy sp = context.getStylingPolicy(); 134 final StyleLibrary style = context.getStyleLibrary(); 135 final LineStyleLibrary lines = style.lines(); 136 final double width = .5 * style.getLineWidth(StyleLibrary.PLOT) * MathUtil.min(.5, 2. / MathUtil.log2(sam.size())); 137 if(sp instanceof ClassStylingPolicy) { 138 ClassStylingPolicy csp = (ClassStylingPolicy) sp; 139 final int min = csp.getMinStyle(); 140 String[] keys = new String[csp.getMaxStyle() - min]; 141 for(int c = min; c < csp.getMaxStyle(); c++) { 142 String key = keys[c - min] = DATALINE + "_" + c; 143 if(!svgp.getCSSClassManager().contains(key)) { 144 CSSClass cls = new CSSClass(this, key); 145 cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE); 146 cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE); 147 cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE); 148 lines.formatCSSClass(cls, c, width); 149 svgp.addCSSClassOrLogError(cls); 150 } 151 } 152 for(DBIDIter iter = sam.iter(); iter.valid(); iter.advance()) { 153 final int c = csp.getStyleForDBID(iter) + min; 154 if(c < 0) { 155 continue; // No style. Display differently? 156 } 157 Element line = drawLine(iter); 158 if(line == null) { 159 continue; 160 } 161 SVGUtil.addCSSClass(line, keys[c]); 162 layer.appendChild(line); 163 } 164 } 165 else { 166 // No classes available, but individually colored 167 if(!svgp.getCSSClassManager().contains(DATALINE)) { 168 CSSClass cls = new CSSClass(this, DATALINE); 169 cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE); 170 cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE); 171 cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE); 172 lines.formatCSSClass(cls, -1, width); 173 svgp.addCSSClassOrLogError(cls); 174 } 175 StringBuilder buf = new StringBuilder().append(SVGConstants.CSS_STROKE_PROPERTY).append(':'); 176 final int prefix = buf.length(); 177 for(DBIDIter iter = sam.iter(); iter.valid(); iter.advance()) { 178 Element line = drawLine(iter); 179 if(line == null) { 180 continue; 181 } 182 SVGUtil.addCSSClass(line, DATALINE); 183 // assign color 184 buf.delete(prefix, buf.length()); 185 buf.append(SVGUtil.colorToString(sp.getColorForDBID(iter))); 186 line.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, buf.toString()); 187 layer.appendChild(line); 188 } 189 } 190 svgp.updateStyleElement(); 191 } 192 193 /** 194 * Draw a single line. 195 * 196 * @param iter Object reference 197 * @return Line element 198 */ drawLine(DBIDRef iter)199 private Element drawLine(DBIDRef iter) { 200 SVGPath path = new SVGPath(); 201 final SpatialComparable obj = relation.get(iter); 202 final int dims = proj.getVisibleDimensions(); 203 boolean drawn = false; 204 int valid = 0; /* run length of valid values */ 205 double prevpos = Double.NaN; 206 for(int i = 0; i < dims; i++) { 207 final int d = proj.getDimForAxis(i); 208 double minPos = proj.fastProjectDataToRenderSpace(obj.getMin(d), i); 209 // NaN handling: 210 if(minPos != minPos) { 211 valid = 0; 212 continue; 213 } 214 ++valid; 215 if(valid > 1) { 216 if(valid == 2) { 217 path.moveTo(getVisibleAxisX(d - 1), prevpos); 218 } 219 path.lineTo(getVisibleAxisX(d), minPos); 220 drawn = true; 221 } 222 prevpos = minPos; 223 } 224 valid = 0; 225 for(int i = dims - 1; i >= 0; i--) { 226 final int d = proj.getDimForAxis(i); 227 double maxPos = proj.fastProjectDataToRenderSpace(obj.getMax(d), i); 228 // NaN handling: 229 if(maxPos != maxPos) { 230 valid = 0; 231 continue; 232 } 233 ++valid; 234 if(valid > 1) { 235 if(valid == 2) { 236 path.moveTo(getVisibleAxisX(d + 1), prevpos); 237 } 238 path.lineTo(getVisibleAxisX(d), maxPos); 239 drawn = true; 240 } 241 prevpos = maxPos; 242 } 243 if(!drawn) { 244 return null; // Not enough data. 245 } 246 return path.makeElement(svgp); 247 } 248 } 249 } 250