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;
22 
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.List;
26 
27 import de.lmu.ifi.dbs.elki.algorithm.clustering.trivial.ByLabelHierarchicalClustering;
28 import de.lmu.ifi.dbs.elki.algorithm.clustering.trivial.TrivialAllInOne;
29 import de.lmu.ifi.dbs.elki.data.Clustering;
30 import de.lmu.ifi.dbs.elki.data.NumberVector;
31 import de.lmu.ifi.dbs.elki.data.model.Model;
32 import de.lmu.ifi.dbs.elki.data.type.NoSupportedDataTypeException;
33 import de.lmu.ifi.dbs.elki.data.type.TypeUtil;
34 import de.lmu.ifi.dbs.elki.database.Database;
35 import de.lmu.ifi.dbs.elki.database.datastore.DataStoreEvent;
36 import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener;
37 import de.lmu.ifi.dbs.elki.database.relation.Relation;
38 import de.lmu.ifi.dbs.elki.evaluation.AutomaticEvaluation;
39 import de.lmu.ifi.dbs.elki.logging.Logging;
40 import de.lmu.ifi.dbs.elki.result.*;
41 import de.lmu.ifi.dbs.elki.visualization.style.ClusterStylingPolicy;
42 import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
43 import de.lmu.ifi.dbs.elki.visualization.style.StylingPolicy;
44 
45 /**
46  * Map to store context information for the visualizer. This can be any data
47  * that should to be shared among plots, such as line colors, styles etc.
48  *
49  * @author Erich Schubert
50  * @since 0.3
51  *
52  * @opt nodefillcolor LemonChiffon
53  * @composed - - - StyleLibrary
54  * @composed - - - StylingPolicy
55  * @composed - - - SelectionResult
56  * @composed - - - ResultHierarchy
57  * @composed - - - VisualizationTree
58  * @composed - - - DataStoreListener
59  * @composed - - - VisualizationProcessor
60  */
61 public class VisualizerContext implements DataStoreListener, Result {
62   /**
63    * Logger.
64    */
65   private static final Logging LOG = Logging.getLogger(VisualizerContext.class);
66 
67   /**
68    * Tree of visualizations.
69    */
70   private VisualizationTree vistree = new VisualizationTree();
71 
72   /**
73    * The full result object
74    */
75   private ResultHierarchy hier;
76 
77   /**
78    * The event listeners for this context.
79    */
80   private ArrayList<DataStoreListener> listenerList = new ArrayList<>();
81 
82   /**
83    * Factories to use
84    */
85   private Collection<VisualizationProcessor> factories;
86 
87   /**
88    * Selection result
89    */
90   private SelectionResult selection;
91 
92   /**
93    * Styling policy
94    */
95   StylingPolicy stylepolicy;
96 
97   /**
98    * Style library
99    */
100   StyleLibrary stylelibrary;
101 
102   /**
103    * Starting point of the result tree, may be {@code null}.
104    */
105   private Result baseResult;
106 
107   /**
108    * Constructor. We currently require a Database and a Result.
109    *
110    * @param hier Result hierarchy
111    * @param start Starting result
112    * @param stylelib Style library
113    * @param factories Visualizer Factories to use
114    */
VisualizerContext(ResultHierarchy hier, Result start, StyleLibrary stylelib, Collection<VisualizationProcessor> factories)115   public VisualizerContext(ResultHierarchy hier, Result start, StyleLibrary stylelib, Collection<VisualizationProcessor> factories) {
116     super();
117     this.hier = hier;
118     this.baseResult = start;
119     this.factories = factories;
120 
121     // Ensure that various common results needed by visualizers are
122     // automatically created
123     final Database db = ResultUtil.findDatabase(hier);
124     if(db == null) {
125       LOG.warning("No database reachable from " + hier);
126       return;
127     }
128     AutomaticEvaluation.ensureClusteringResult(db, db);
129     this.selection = SelectionResult.ensureSelectionResult(db);
130     for(Relation<?> rel : ResultUtil.getRelations(db)) {
131       SamplingResult.getSamplingResult(rel);
132       // FIXME: this is a really ugly workaround. :-(
133       if(TypeUtil.NUMBER_VECTOR_FIELD.isAssignableFromType(rel.getDataTypeInformation())) {
134         @SuppressWarnings("unchecked")
135         Relation<? extends NumberVector> vrel = (Relation<? extends NumberVector>) rel;
136         ScalesResult.getScalesResult(vrel);
137       }
138     }
139     makeStyleResult(stylelib);
140 
141     // Add visualizers.
142     notifyFactories(db);
143 
144     // For proxying events.
145     db.addDataStoreListener(this);
146     // Add a result listener.
147     // Don't expose these methods to avoid inappropriate use.
148     addResultListener(new ResultListener() {
149       @Override
150       public void resultAdded(Result child, Result parent) {
151         notifyFactories(child);
152       }
153 
154       @Override
155       public void resultChanged(Result current) {
156         // FIXME: need to do anything?
157       }
158 
159       @Override
160       public void resultRemoved(Result child, Result parent) {
161         // FIXME: implement
162       }
163     });
164   }
165 
166   /**
167    * Generate a new style result for the given style library.
168    *
169    * @param stylelib Style library
170    */
makeStyleResult(StyleLibrary stylelib)171   protected void makeStyleResult(StyleLibrary stylelib) {
172     final Database db = ResultUtil.findDatabase(hier);
173     stylelibrary = stylelib;
174     List<Clustering<? extends Model>> clusterings = Clustering.getClusteringResults(db);
175     if(!clusterings.isEmpty()) {
176       stylepolicy = new ClusterStylingPolicy(clusterings.get(0), stylelib);
177     }
178     else {
179       Clustering<Model> c = generateDefaultClustering();
180       stylepolicy = new ClusterStylingPolicy(c, stylelib);
181     }
182   }
183 
184   /**
185    * Get the hierarchy object
186    *
187    * @return hierarchy object
188    */
getHierarchy()189   public ResultHierarchy getHierarchy() {
190     return hier;
191   }
192 
193   /**
194    * Get the active styling policy
195    *
196    * @return Styling policy
197    */
getStylingPolicy()198   public StylingPolicy getStylingPolicy() {
199     return stylepolicy;
200   }
201 
202   /**
203    * Set the active styling policy
204    *
205    * @param policy new Styling policy
206    */
setStylingPolicy(StylingPolicy policy)207   public void setStylingPolicy(StylingPolicy policy) {
208     this.stylepolicy = policy;
209     visChanged(policy);
210   }
211 
212   /**
213    * Get the style library
214    *
215    * @return Style library
216    */
getStyleLibrary()217   public StyleLibrary getStyleLibrary() {
218     return stylelibrary;
219   }
220 
221   /**
222    * Get the style library
223    *
224    * @param library Style library
225    */
setStyleLibrary(StyleLibrary library)226   public void setStyleLibrary(StyleLibrary library) {
227     this.stylelibrary = library;
228   }
229 
230   /**
231    * Generate a default (fallback) clustering.
232    *
233    * @return generated clustering
234    */
generateDefaultClustering()235   private Clustering<Model> generateDefaultClustering() {
236     final Database db = ResultUtil.findDatabase(hier);
237     Clustering<Model> c = null;
238     try {
239       // Try to cluster by labels
240       ByLabelHierarchicalClustering split = new ByLabelHierarchicalClustering();
241       c = split.run(db);
242     }
243     catch(NoSupportedDataTypeException e) {
244       // Put everything into one
245       c = new TrivialAllInOne().run(db);
246     }
247     return c;
248   }
249 
250   // TODO: add ShowVisualizer,HideVisualizer with tool semantics.
251 
252   // TODO: add ShowVisualizer,HideVisualizer with tool semantics.
253 
254   /**
255    * Get the current selection result.
256    *
257    * @return selection result
258    */
getSelectionResult()259   public SelectionResult getSelectionResult() {
260     return selection;
261   }
262 
263   /**
264    * Get the current selection.
265    *
266    * @return selection
267    */
getSelection()268   public DBIDSelection getSelection() {
269     return selection.getSelection();
270   }
271 
272   /**
273    * Set a new selection.
274    *
275    * @param sel Selection
276    */
setSelection(DBIDSelection sel)277   public void setSelection(DBIDSelection sel) {
278     selection.setSelection(sel);
279     getHierarchy().resultChanged(selection);
280   }
281 
282   /**
283    * Adds a listener for the <code>DataStoreEvent</code> posted after the
284    * content changes.
285    *
286    * @param l the listener to add
287    * @see #removeDataStoreListener
288    */
addDataStoreListener(DataStoreListener l)289   public void addDataStoreListener(DataStoreListener l) {
290     for(int i = 0; i < listenerList.size(); i++) {
291       if(listenerList.get(i) == l) {
292         return;
293       }
294     }
295     listenerList.add(l);
296   }
297 
298   /**
299    * Removes a listener previously added with <code>addDataStoreListener</code>.
300    *
301    * @param l the listener to remove
302    * @see #addDataStoreListener
303    */
removeDataStoreListener(DataStoreListener l)304   public void removeDataStoreListener(DataStoreListener l) {
305     listenerList.remove(l);
306   }
307 
308   /**
309    * Proxy datastore event to child listeners.
310    */
311   @Override
contentChanged(DataStoreEvent e)312   public void contentChanged(DataStoreEvent e) {
313     for(int i = 0; i < listenerList.size(); i++) {
314       listenerList.get(i).contentChanged(e);
315     }
316   }
317 
318   /**
319    * Register a result listener.
320    *
321    * @param listener Result listener.
322    */
addResultListener(ResultListener listener)323   public void addResultListener(ResultListener listener) {
324     getHierarchy().addResultListener(listener);
325   }
326 
327   /**
328    * Remove a result listener.
329    *
330    * @param listener Result listener.
331    */
removeResultListener(ResultListener listener)332   public void removeResultListener(ResultListener listener) {
333     getHierarchy().removeResultListener(listener);
334   }
335 
336   /**
337    * Add a listener.
338    *
339    * @param listener Listener to add
340    */
addVisualizationListener(VisualizationListener listener)341   public void addVisualizationListener(VisualizationListener listener) {
342     vistree.addVisualizationListener(listener);
343   }
344 
345   /**
346    * Add a listener.
347    *
348    * @param listener Listener to remove
349    */
removeVisualizationListener(VisualizationListener listener)350   public void removeVisualizationListener(VisualizationListener listener) {
351     vistree.removeVisualizationListener(listener);
352   }
353 
354   @Override
getLongName()355   public String getLongName() {
356     return "Visualizer context";
357   }
358 
359   @Override
getShortName()360   public String getShortName() {
361     return "vis-context";
362   }
363 
364   /**
365    * Starting point for visualization, may be {@code null}.
366    *
367    * @return Starting point in the result tree, may be {@code null}.
368    */
getBaseResult()369   public Result getBaseResult() {
370     return baseResult;
371   }
372 
373   /**
374    * Add (register) a visualization.
375    *
376    * @param parent Parent object
377    * @param vis Visualization
378    */
addVis(Object parent, VisualizationItem vis)379   public void addVis(Object parent, VisualizationItem vis) {
380     vistree.add(parent, vis);
381     notifyFactories(vis);
382     visChanged(vis);
383   }
384 
385   /**
386    * A visualization item has changed.
387    *
388    * @param item Item that has changed
389    */
visChanged(VisualizationItem item)390   public void visChanged(VisualizationItem item) {
391     vistree.visChanged(item);
392   }
393 
394   /**
395    * Notify factories of a change.
396    *
397    * @param item Item that has changed.
398    */
notifyFactories(Object item)399   private void notifyFactories(Object item) {
400     for(VisualizationProcessor f : factories) {
401       try {
402         f.processNewResult(this, item);
403       }
404       catch(Throwable e) {
405         LOG.warning("VisFactory " + f.getClass().getCanonicalName() + " failed:", e);
406       }
407     }
408   }
409 
getVisTasks(VisualizationItem item)410   public List<VisualizationTask> getVisTasks(VisualizationItem item) {
411     List<VisualizationTask> out = new ArrayList<>();
412     vistree.iterDescendants(item).filter(VisualizationTask.class).forEach(out::add);
413     return out;
414   }
415 
getVisHierarchy()416   public VisualizationTree getVisHierarchy() {
417     return vistree;
418   }
419 }
420