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