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