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.scatterplot; 22 23 import org.apache.batik.util.SVGConstants; 24 import org.w3c.dom.Element; 25 26 import de.lmu.ifi.dbs.elki.data.ClassLabel; 27 import de.lmu.ifi.dbs.elki.data.ExternalID; 28 import de.lmu.ifi.dbs.elki.data.LabelList; 29 import de.lmu.ifi.dbs.elki.data.type.TypeUtil; 30 import de.lmu.ifi.dbs.elki.database.ids.DBID; 31 import de.lmu.ifi.dbs.elki.database.ids.DBIDRef; 32 import de.lmu.ifi.dbs.elki.database.relation.Relation; 33 import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; 34 import de.lmu.ifi.dbs.elki.visualization.VisualizationTask.UpdateFlag; 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.gui.VisualizationPlot; 39 import de.lmu.ifi.dbs.elki.visualization.projections.Projection; 40 import de.lmu.ifi.dbs.elki.visualization.projector.ScatterPlotProjector; 41 import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; 42 import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; 43 import de.lmu.ifi.dbs.elki.visualization.visualizers.VisFactory; 44 import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; 45 46 /** 47 * Generates a SVG-Element containing Tooltips. Tooltips remain invisible until 48 * their corresponding Marker is touched by the cursor and stay visible as long 49 * as the cursor lingers on the marker. 50 * 51 * @author Remigius Wojdanowski 52 * @author Erich Schubert 53 * @since 0.4.0 54 * 55 * @stereotype factory 56 * @navassoc - create - Instance 57 */ 58 public class TooltipStringVisualization implements VisFactory { 59 /** 60 * A short name characterizing this Visualizer. 61 */ 62 public static final String NAME_ID = "ID Tooltips"; 63 64 /** 65 * A short name characterizing this Visualizer. 66 */ 67 public static final String NAME_LABEL = "Object Label Tooltips"; 68 69 /** 70 * A short name characterizing this Visualizer. 71 */ 72 public static final String NAME_CLASS = "Class Label Tooltips"; 73 74 /** 75 * A short name characterizing this Visualizer. 76 */ 77 public static final String NAME_EID = "External ID Tooltips"; 78 79 /** 80 * Constructor. 81 */ TooltipStringVisualization()82 public TooltipStringVisualization() { 83 super(); 84 } 85 86 @Override makeVisualization(VisualizerContext context, VisualizationTask task, VisualizationPlot plot, double width, double height, Projection proj)87 public Visualization makeVisualization(VisualizerContext context, VisualizationTask task, VisualizationPlot plot, double width, double height, Projection proj) { 88 return new Instance(context, task, plot, width, height, proj); 89 } 90 91 @Override processNewResult(VisualizerContext context, Object result)92 public void processNewResult(VisualizerContext context, Object result) { 93 VisualizationTree.findNewSiblings(context, result, Relation.class, ScatterPlotProjector.class, (rep, p) -> { 94 final Relation<?> rel = p.getRelation(); 95 if(!TypeUtil.NUMBER_VECTOR_FIELD.isAssignableFromType(rel.getDataTypeInformation())) { 96 return; 97 } 98 final Class<?> clz = rep.getDataTypeInformation().getRestrictionClass(); 99 if(DBID.class.isAssignableFrom(clz)) { 100 addTooltips(NAME_ID, rel, context, rep, p); 101 } 102 else if(ClassLabel.class.isAssignableFrom(rep.getDataTypeInformation().getRestrictionClass())) { 103 addTooltips(NAME_CLASS, rel, context, rep, p); 104 } 105 else if(LabelList.class.isAssignableFrom(rep.getDataTypeInformation().getRestrictionClass())) { 106 addTooltips(NAME_LABEL, rel, context, rep, p); 107 } 108 else if(ExternalID.class.isAssignableFrom(rep.getDataTypeInformation().getRestrictionClass())) { 109 addTooltips(NAME_EID, rel, context, rep, p); 110 } 111 }); 112 } 113 addTooltips(final String name, final Relation<?> rel, VisualizerContext context, Relation<?> rep, ScatterPlotProjector<?> p)114 private void addTooltips(final String name, final Relation<?> rel, VisualizerContext context, Relation<?> rep, ScatterPlotProjector<?> p) { 115 final VisualizationTask task = new VisualizationTask(this, name, rep, rel) // 116 .tool(true).visibility(false) // 117 .with(UpdateFlag.ON_DATA).with(UpdateFlag.ON_SAMPLE); 118 context.addVis(rep, task); 119 context.addVis(p, task); 120 } 121 122 /** 123 * Instance 124 * 125 * @author Remigius Wojdanowski 126 * @author Erich Schubert 127 * 128 * @navhas - visualizes - Relation 129 */ 130 public class Instance extends AbstractTooltipVisualization { 131 /** 132 * Number value to visualize 133 */ 134 private Relation<?> result; 135 136 /** 137 * Font size to use. 138 */ 139 private double fontsize; 140 141 /** 142 * Constructor. 143 * 144 * @param context Visualizer context 145 * @param task Task 146 * @param plot Plot 147 * @param width Width 148 * @param height Height 149 * @param proj Projection 150 */ Instance(VisualizerContext context, VisualizationTask task, VisualizationPlot plot, double width, double height, Projection proj)151 public Instance(VisualizerContext context, VisualizationTask task, VisualizationPlot plot, double width, double height, Projection proj) { 152 super(context, task, plot, width, height, proj); 153 this.result = task.getResult(); 154 final StyleLibrary style = context.getStyleLibrary(); 155 this.fontsize = 3 * style.getTextSize(StyleLibrary.PLOT); 156 addListeners(); 157 } 158 159 @Override makeTooltip(DBIDRef id, double x, double y, double dotsize)160 protected Element makeTooltip(DBIDRef id, double x, double y, double dotsize) { 161 final Object data = result.get(id); 162 String label = (data == null) ? "null" : data.toString(); 163 label = (label.isEmpty() || label == null) ? "null" : label; 164 return svgp.svgText(x + dotsize, y + fontsize * 0.07, label); 165 } 166 167 /** 168 * Registers the Tooltip-CSS-Class at a SVGPlot. 169 * 170 * @param svgp the SVGPlot to register the Tooltip-CSS-Class. 171 */ 172 @Override setupCSS(SVGPlot svgp)173 protected void setupCSS(SVGPlot svgp) { 174 final StyleLibrary style = context.getStyleLibrary(); 175 final double fontsize = style.getTextSize(StyleLibrary.PLOT); 176 final String fontfamily = style.getFontFamily(StyleLibrary.PLOT); 177 178 CSSClass tooltiphidden = new CSSClass(svgp, TOOLTIP_HIDDEN); 179 tooltiphidden.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize); 180 tooltiphidden.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily); 181 tooltiphidden.setStatement(SVGConstants.CSS_DISPLAY_PROPERTY, SVGConstants.CSS_NONE_VALUE); 182 svgp.addCSSClassOrLogError(tooltiphidden); 183 184 CSSClass tooltipvisible = new CSSClass(svgp, TOOLTIP_VISIBLE); 185 tooltipvisible.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize); 186 tooltipvisible.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily); 187 svgp.addCSSClassOrLogError(tooltipvisible); 188 189 CSSClass tooltipsticky = new CSSClass(svgp, TOOLTIP_STICKY); 190 tooltipsticky.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize); 191 tooltipsticky.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily); 192 svgp.addCSSClassOrLogError(tooltipsticky); 193 194 // invisible but sensitive area for the tooltip activator 195 CSSClass tooltiparea = new CSSClass(svgp, TOOLTIP_AREA); 196 tooltiparea.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_RED_VALUE); 197 tooltiparea.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_NONE_VALUE); 198 tooltiparea.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, "0"); 199 tooltiparea.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE); 200 svgp.addCSSClassOrLogError(tooltiparea); 201 } 202 } 203 } 204