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 java.awt.image.RenderedImage;
24 
25 import org.apache.batik.util.SVGConstants;
26 import org.w3c.dom.Element;
27 
28 import de.lmu.ifi.dbs.elki.database.Database;
29 import de.lmu.ifi.dbs.elki.database.DatabaseUtil;
30 import de.lmu.ifi.dbs.elki.database.ids.DBIDIter;
31 import de.lmu.ifi.dbs.elki.database.relation.Relation;
32 import de.lmu.ifi.dbs.elki.evaluation.similaritymatrix.ComputeSimilarityMatrixImage.SimilarityMatrix;
33 import de.lmu.ifi.dbs.elki.result.ResultUtil;
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.gui.VisualizationPlot;
38 import de.lmu.ifi.dbs.elki.visualization.projections.Projection;
39 import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
40 import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil;
41 import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisualization;
42 import de.lmu.ifi.dbs.elki.visualization.visualizers.VisFactory;
43 import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;
44 
45 /**
46  * Visualize a similarity matrix with object labels
47  *
48  * @author Erich Schubert
49  * @since 0.4.0
50  *
51  * @stereotype factory
52  * @navassoc - create - Instance
53  */
54 public class SimilarityMatrixVisualizer implements VisFactory {
55   /**
56    * Name for this visualizer.
57    */
58   private static final String NAME = "Similarity Matrix Visualizer";
59 
60   /**
61    * Constructor.
62    */
SimilarityMatrixVisualizer()63   public SimilarityMatrixVisualizer() {
64     super();
65   }
66 
67   @Override
processNewResult(VisualizerContext context, Object start)68   public void processNewResult(VisualizerContext context, Object start) {
69     VisualizationTree.findNewResults(context, start).filter(SimilarityMatrix.class).forEach(pr -> {
70       context.addVis(pr, new VisualizationTask(this, NAME, pr, null) //
71           .level(VisualizationTask.LEVEL_STATIC));
72     });
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);
78   }
79 
80   @Override
allowThumbnails(VisualizationTask task)81   public boolean allowThumbnails(VisualizationTask task) {
82     // Don't use thumbnails
83     return false;
84   }
85 
86   /**
87    * Instance
88    *
89    * @author Erich Schubert
90    *
91    * @navhas - visualizes 1 SimilarityMatrix
92    */
93   public class Instance extends AbstractVisualization {
94     /**
95      * The actual pixmap result.
96      */
97     private SimilarityMatrix result;
98 
99     /**
100      * Constructor.
101      *
102      * @param context Visualizer context
103      * @param task Visualization task
104      * @param plot Plot to draw to
105      * @param width Embedding width
106      * @param height Embedding height
107      */
Instance(VisualizerContext context, VisualizationTask task, VisualizationPlot plot, double width, double height)108     public Instance(VisualizerContext context, VisualizationTask task, VisualizationPlot plot, double width, double height) {
109       super(context, task, plot, width, height);
110       this.result = task.getResult();
111       addListeners();
112     }
113 
114     @Override
fullRedraw()115     public void fullRedraw() {
116       final StyleLibrary style = context.getStyleLibrary();
117       final double sizex = StyleLibrary.SCALE;
118       final double sizey = StyleLibrary.SCALE * getHeight() / getWidth();
119       final double margin = style.getSize(StyleLibrary.MARGIN);
120       layer = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG);
121       final String transform = SVGUtil.makeMarginTransform(getWidth(), getHeight(), sizex, sizey, margin);
122       SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform);
123 
124       RenderedImage img = result.getImage();
125       // is ratio, target ratio
126       double iratio = img.getHeight() / img.getWidth();
127       double tratio = getHeight() / getWidth();
128       // We want to place a (iratio, 1.0) object on a (tratio, 1.0) screen.
129       // Both dimensions must fit:
130       double zoom = (iratio >= tratio) ? Math.min(tratio / iratio, 1.0) : Math.max(iratio / tratio, 1.0);
131 
132       Element itag = svgp.svgElement(SVGConstants.SVG_IMAGE_TAG);
133       SVGUtil.setAtt(itag, SVGConstants.SVG_IMAGE_RENDERING_ATTRIBUTE, SVGConstants.SVG_OPTIMIZE_SPEED_VALUE);
134       SVGUtil.setAtt(itag, SVGConstants.SVG_X_ATTRIBUTE, margin * 0.75);
135       SVGUtil.setAtt(itag, SVGConstants.SVG_Y_ATTRIBUTE, margin * 0.75);
136       SVGUtil.setAtt(itag, SVGConstants.SVG_WIDTH_ATTRIBUTE, StyleLibrary.SCALE * zoom * iratio);
137       SVGUtil.setAtt(itag, SVGConstants.SVG_HEIGHT_ATTRIBUTE, StyleLibrary.SCALE * zoom);
138       itag.setAttributeNS(SVGConstants.XLINK_NAMESPACE_URI, SVGConstants.XLINK_HREF_QNAME, result.getAsFile().toURI().toString());
139       layer.appendChild(itag);
140 
141       // Add object labels
142       final int size = result.getIDs().size();
143       final double hlsize = StyleLibrary.SCALE * zoom * iratio / size;
144       final double vlsize = StyleLibrary.SCALE * zoom / size;
145       int i = 0;
146       Database database = ResultUtil.findDatabase(context.getHierarchy());
147       final Relation<String> lrep = DatabaseUtil.guessObjectLabelRepresentation(database);
148       for(DBIDIter id = result.getIDs().iter(); id.valid(); id.advance()) {
149         String label = lrep.get(id);
150         if(label != null) {
151           // Label on horizontal axis
152           final double hlx = margin * 0.75 + hlsize * (i + .8);
153           final double hly = margin * 0.7;
154           Element lbl = svgp.svgText(hlx, hly, label);
155           SVGUtil.setAtt(lbl, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, "rotate(-90," + hlx + "," + hly + ")");
156           SVGUtil.setAtt(lbl, SVGConstants.SVG_STYLE_ATTRIBUTE, "font-size: " + hlsize * 0.8);
157           layer.appendChild(lbl);
158           // Label on vertical axis
159           Element lbl2 = svgp.svgText(margin * 0.7, margin * 0.75 + vlsize * (i + .8), label);
160           SVGUtil.setAtt(lbl2, SVGConstants.SVG_TEXT_ANCHOR_ATTRIBUTE, SVGConstants.SVG_END_VALUE);
161           SVGUtil.setAtt(lbl2, SVGConstants.SVG_STYLE_ATTRIBUTE, "font-size: " + vlsize * 0.8);
162           layer.appendChild(lbl2);
163         }
164         i++;
165       }
166     }
167   }
168 }
169