/*
* This file is part of ELKI:
* Environment for Developing KDD-Applications Supported by Index-Structures
*
* Copyright (C) 2017
* ELKI Development Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.lmu.ifi.dbs.elki.visualization;
import java.util.ArrayList;
import java.util.function.BiConsumer;
import de.lmu.ifi.dbs.elki.result.Result;
import de.lmu.ifi.dbs.elki.result.ResultHierarchy;
import de.lmu.ifi.dbs.elki.utilities.datastructures.hierarchy.HashMapHierarchy;
import de.lmu.ifi.dbs.elki.utilities.datastructures.hierarchy.Hierarchy;
import de.lmu.ifi.dbs.elki.utilities.datastructures.hierarchy.StackedIter;
import de.lmu.ifi.dbs.elki.utilities.datastructures.iterator.EmptyIterator;
import de.lmu.ifi.dbs.elki.utilities.datastructures.iterator.It;
/**
* Tree - actually a forest - to manage visualizations.
*
* @author Erich Schubert
* @since 0.4.0
*
* @apiviz.has Handler1
* @apiviz.has Handler2
* @apiviz.has Handler3
*/
public class VisualizationTree extends HashMapHierarchy<Object> {
/**
* The event listeners for this context.
*/
private ArrayList<VisualizationListener> vlistenerList = new ArrayList<>();
/**
* Constructor.
*/
public VisualizationTree() {
super();
}
/**
* Add a listener.
*
* @param listener Listener to add
*/
public void addVisualizationListener(VisualizationListener listener) {
for(int i = 0; i < vlistenerList.size(); i++) {
if(vlistenerList.get(i) == listener) {
return;
}
}
vlistenerList.add(listener);
}
/**
* Add a listener.
*
* @param listener Listener to remove
*/
public void removeVisualizationListener(VisualizationListener listener) {
vlistenerList.remove(listener);
}
/**
* A visualization item has changed.
*
* @param item Item that has changed
*/
public void visChanged(VisualizationItem item) {
for(int i = vlistenerList.size(); --i >= 0;) {
vlistenerList.get(i).visualizationChanged(item);
}
}
/**
* Filtered iteration over a stacked hierarchy.
*
* This is really messy because the visualization hierarchy is typed Object.
*
* @param context Visualization context
* @return Iterator of results.
*/
public static It<Object> findVis(VisualizerContext context) {
return new StackedIter<>(context.getHierarchy().iterAll(), context.getVisHierarchy());
}
/**
* Filtered iteration over a stacked hierarchy.
*
* This is really messy because the visualization hierarchy is typed Object.
*
* @param context Visualization context
* @param start Starting object (in primary hierarchy!)
* @return Iterator of results.
*/
public static It<Object> findVis(VisualizerContext context, Object start) {
if(start instanceof Result) { // In first hierarchy.
It<Result> it1 = context.getHierarchy().iterDescendantsSelf((Result) start);
return new StackedIter<>(it1, context.getVisHierarchy());
}
return context.getVisHierarchy().iterDescendantsSelf(start);
}
/**
* Iterate over the <em>primary result tree</em>.
*
* @param context Visualization context
* @param start Starting object (in primary hierarchy!)
* @return Iterator of results.
*/
public static It<Result> findNewResults(VisualizerContext context, Object start) {
return (start instanceof Result) ? context.getHierarchy().iterDescendantsSelf((Result) start) : EmptyIterator.empty();
}
/**
* Process new result combinations of an object type1 (in first hierarchy) and
* any child of type2 (in second hierarchy)
*
* This is a bit painful, because we have two hierarchies with different
* types: results, and visualizations.
*
* @param context Context
* @param start Starting point
* @param type1 First type, in first hierarchy
* @param type2 Second type, in second hierarchy
* @param handler Handler
*/
public static <A extends Result, B extends VisualizationItem> void findNewSiblings(VisualizerContext context, Object start, Class<? super A> type1, Class<? super B> type2, BiConsumer<A, B> handler) {
// Search start in first hierarchy:
final ResultHierarchy hier = context.getHierarchy();
final Hierarchy<Object> vistree = context.getVisHierarchy();
if(start instanceof Result) {
// New result:
for(It<A> it1 = hier.iterDescendantsSelf((Result) start).filter(type1); it1.valid(); it1.advance()) {
final A result = it1.get();
// Existing visualization:
for(It<B> it2 = vistree.iterDescendantsSelf(context.getBaseResult()).filter(type2); it2.valid(); it2.advance()) {
handler.accept(result, it2.get());
}
}
}
// New visualization:
for(It<B> it2 = vistree.iterDescendantsSelf(start).filter(type2); it2.valid(); it2.advance()) {
final B vis = it2.get();
// Existing result:
for(It<A> it1 = hier.iterAll().filter(type1); it1.valid(); it1.advance()) {
handler.accept(it1.get(), vis);
}
}
}
/**
* Process new result combinations of an object type1 (in first hierarchy)
* having a child of type2 (in second hierarchy).
*
* This is a bit painful, because we have two hierarchies with different
* types: results, and visualizations.
*
* @param context Context
* @param start Starting point
* @param type1 First type, in first hierarchy
* @param type2 Second type, in second hierarchy
* @param handler Handler
*/
public static <A extends Result, B extends VisualizationItem> void findNewResultVis(VisualizerContext context, Object start, Class<? super A> type1, Class<? super B> type2, BiConsumer<A, B> handler) {
final Hierarchy<Object> hier = context.getVisHierarchy();
// Search start in first hierarchy:
if(start instanceof Result) {
for(It<A> it1 = context.getHierarchy().iterDescendantsSelf((Result) start).filter(type1); it1.valid(); it1.advance()) {
final A result = it1.get();
// Find descendant results in result hierarchy:
for(It<Result> it3 = context.getHierarchy().iterDescendantsSelf(result); it3.valid(); it3.advance()) {
// Find descendant in visualization hierarchy:
for(It<B> it2 = hier.iterDescendantsSelf(it3.get()).filter(type2); it2.valid(); it2.advance()) {
handler.accept(result, it2.get());
}
}
}
}
// Search start in second hierarchy:
if(start instanceof VisualizationItem) {
for(It<B> it2 = hier.iterDescendantsSelf(start).filter(type2); it2.valid(); it2.advance()) {
final B vis = it2.get();
// Find ancestor result in visualization hierarchy:
for(It<Result> it3 = hier.iterAncestorsSelf(vis).filter(Result.class); it3.valid(); it3.advance()) {
// Find ancestor in result hierarchy:
for(It<A> it1 = context.getHierarchy().iterAncestorsSelf(it3.get()).filter(type1); it1.valid(); it1.advance()) {
handler.accept(it1.get(), vis);
}
}
}
}
}
/**
* Utility function to change Visualizer visibility.
*
* @param context Visualization context
* @param task Visualization task
* @param visibility Visibility value
*/
public static void setVisible(VisualizerContext context, VisualizationTask task, boolean visibility) {
// Hide other tools
if(visibility && task.isTool()) {
Hierarchy<Object> vistree = context.getVisHierarchy();
for(It<VisualizationTask> iter2 = vistree.iterAll().filter(VisualizationTask.class); iter2.valid(); iter2.advance()) {
VisualizationTask other = iter2.get();
if(other != task && other.isTool() && other.isVisible()) {
other.visibility(false);
context.visChanged(other);
}
}
}
task.visibility(visibility);
context.visChanged(task);
}
}