diff options
Diffstat (limited to 'src/de/lmu/ifi/dbs/elki/visualization')
118 files changed, 8383 insertions, 7096 deletions
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/ExportVisualizations.java b/src/de/lmu/ifi/dbs/elki/visualization/ExportVisualizations.java index da0a5c85..f0a26325 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/ExportVisualizations.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/ExportVisualizations.java @@ -48,20 +48,21 @@ import de.lmu.ifi.dbs.elki.visualization.gui.overview.PlotItem; import de.lmu.ifi.dbs.elki.visualization.projector.Projector; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; -import de.lmu.ifi.dbs.elki.visualization.visualizers.VisualizerUtil; /** * Class that automatically generates all visualizations and exports them into * SVG files. * * @author Erich Schubert + * + * @apiviz.composedOf VisualizerParameterizer */ // TODO: make more parameterizable, wrt. what to skip public class ExportVisualizations implements ResultHandler { /** * Get a logger for this class. */ - protected final static Logging logger = Logging.getLogger(ExportVisualizations.class); + private static final Logging LOG = Logging.getLogger(ExportVisualizations.class); /** * Parameter to specify the canvas ratio @@ -72,7 +73,7 @@ public class ExportVisualizations implements ResultHandler { * Default value: 1.33 * </p> */ - public static final OptionID RATIO_ID = OptionID.getOrCreateOptionID("vis.ratio", "The width/heigh ratio of the output."); + public static final OptionID RATIO_ID = new OptionID("vis.ratio", "The width/heigh ratio of the output."); /** * Parameter to specify the output folder @@ -80,7 +81,7 @@ public class ExportVisualizations implements ResultHandler { * Key: {@code -vis.output} * </p> */ - public static final OptionID FOLDER_ID = OptionID.getOrCreateOptionID("vis.output", "The output folder."); + public static final OptionID FOLDER_ID = new OptionID("vis.output", "The output folder."); /** * Output folder @@ -128,44 +129,44 @@ public class ExportVisualizations implements ResultHandler { @Override public void processNewResult(HierarchicalResult baseResult, Result newResult) { - if(output.isFile()) { + if (output.isFile()) { throw new AbortException("Output folder cannot be an existing file."); } - if(!output.exists()) { - if(!output.mkdirs()) { + if (!output.exists()) { + if (!output.mkdirs()) { throw new AbortException("Could not create output directory."); } } - if(this.baseResult != baseResult) { + if (this.baseResult != baseResult) { this.baseResult = baseResult; context = null; counter = 0; - logger.verbose("Note: Reusing visualization exporter for more than one result is untested."); + LOG.verbose("Note: Reusing visualization exporter for more than one result is untested."); } - if(context == null) { + if (context == null) { context = manager.newContext(baseResult); } // Projected visualizations ArrayList<Projector> projectors = ResultUtil.filterResults(baseResult, Projector.class); - for(Projector proj : projectors) { + for (Projector proj : projectors) { // TODO: allow selecting individual projections only. Collection<PlotItem> items = proj.arrange(); - for(PlotItem item : items) { + for (PlotItem item : items) { processItem(item); } } ResultHierarchy hier = baseResult.getHierarchy(); ArrayList<VisualizationTask> tasks = ResultUtil.filterResults(baseResult, VisualizationTask.class); - for(VisualizationTask task : tasks) { + for (VisualizationTask task : tasks) { boolean isprojected = false; - for(Result parent : hier.getParents(task)) { - if(parent instanceof Projector) { + for (Result parent : hier.getParents(task)) { + if (parent instanceof Projector) { isprojected = true; break; } } - if(isprojected) { + if (isprojected) { continue; } PlotItem pi = new PlotItem(ratio, 1.0, null); @@ -178,11 +179,11 @@ public class ExportVisualizations implements ResultHandler { final double height = 1; final double width = ratio * height; // Descend into subitems - for(Iterator<PlotItem> iter = item.subitems.iterator(); iter.hasNext();) { + for (Iterator<PlotItem> iter = item.subitems.iterator(); iter.hasNext();) { PlotItem subitem = iter.next(); processItem(subitem); } - if(item.taskSize() <= 0) { + if (item.taskSize() <= 0) { return; } item.sort(); @@ -193,44 +194,29 @@ public class ExportVisualizations implements ResultHandler { svgp.getRoot().setAttribute(SVGConstants.SVG_VIEW_BOX_ATTRIBUTE, "0 0 " + width + " " + height); ArrayList<Visualization> layers = new ArrayList<Visualization>(); - for(Iterator<VisualizationTask> iter = item.tasks.iterator(); iter.hasNext();) { + for (Iterator<VisualizationTask> iter = item.tasks.iterator(); iter.hasNext();) { VisualizationTask task = iter.next(); - { - Boolean dis = task.getGenerics(VisualizationTask.META_NODETAIL, Boolean.class); - if(dis != null && dis == true) { - continue; - } - } - { - Boolean dis = task.getGenerics(VisualizationTask.META_NOEXPORT, Boolean.class); - if(dis != null && dis == true) { - continue; - } - } - if(!VisualizerUtil.isVisible(task)) { + if (task.nodetail || task.noexport || !task.visible) { continue; } try { Visualization v = task.getFactory().makeVisualization(task.clone(svgp, context, item.proj, width, height)); layers.add(v); - } - catch(Exception e) { - if(Logging.getLogger(task.getFactory().getClass()).isDebugging()) { + } catch (Exception e) { + if (Logging.getLogger(task.getFactory().getClass()).isDebugging()) { LoggingUtil.exception("Visualization failed.", e); - } - else { + } else { LoggingUtil.warning("Visualizer " + task.getFactory().getClass().getName() + " failed - enable debugging to see details."); } } } - if(layers.size() <= 0) { + if (layers.size() <= 0) { return; } - for(Visualization layer : layers) { - if(layer.getLayer() != null) { + for (Visualization layer : layers) { + if (layer.getLayer() != null) { svgp.getRoot().appendChild(layer.getLayer()); - } - else { + } else { LoggingUtil.warning("NULL layer seen."); } } @@ -240,11 +226,10 @@ public class ExportVisualizations implements ResultHandler { File outname = new File(output, "plot-" + counter + ".svg"); try { svgp.saveAsSVG(outname); + } catch (Exception e) { + LOG.warning("Export of visualization failed.", e); } - catch(Exception e) { - logger.warning("Export of visualization failed.", e); - } - for(Visualization layer : layers) { + for (Visualization layer : layers) { layer.destroy(); } counter++; @@ -277,13 +262,14 @@ public class ExportVisualizations implements ResultHandler { protected void makeOptions(Parameterization config) { super.makeOptions(config); FileParameter outputP = new FileParameter(FOLDER_ID, FileType.OUTPUT_FILE); - if(config.grab(outputP)) { + if (config.grab(outputP)) { output = outputP.getValue(); } - DoubleParameter ratioP = new DoubleParameter(RATIO_ID, new GreaterConstraint(0.0), 1.33); - if(config.grab(ratioP)) { - ratio = ratioP.getValue(); + DoubleParameter ratioP = new DoubleParameter(RATIO_ID, 1.33); + ratioP.addConstraint(new GreaterConstraint(0.0)); + if (config.grab(ratioP)) { + ratio = ratioP.doubleValue(); } manager = config.tryInstantiate(VisualizerParameterizer.class); @@ -294,4 +280,4 @@ public class ExportVisualizations implements ResultHandler { return new ExportVisualizations(output, manager, ratio); } } -}
\ No newline at end of file +} diff --git a/src/de/lmu/ifi/dbs/elki/visualization/VisualizationTask.java b/src/de/lmu/ifi/dbs/elki/visualization/VisualizationTask.java index 39d6e359..050c7b63 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/VisualizationTask.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/VisualizationTask.java @@ -25,7 +25,7 @@ package de.lmu.ifi.dbs.elki.visualization; import de.lmu.ifi.dbs.elki.database.relation.Relation; import de.lmu.ifi.dbs.elki.result.Result; -import de.lmu.ifi.dbs.elki.utilities.datastructures.AnyMap; +import de.lmu.ifi.dbs.elki.utilities.exceptions.AbortException; import de.lmu.ifi.dbs.elki.visualization.projections.Projection; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; import de.lmu.ifi.dbs.elki.visualization.visualizers.VisFactory; @@ -41,12 +41,7 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.VisFactory; * @apiviz.has VisFactory * @apiviz.has Projection oneway - 0:1 */ -public class VisualizationTask extends AnyMap<String> implements Cloneable, Result, Comparable<VisualizationTask> { - /** - * Serial number - */ - private static final long serialVersionUID = 1L; - +public class VisualizationTask implements Cloneable, Result, Comparable<VisualizationTask> { /** * Constant for using thumbnail */ @@ -60,83 +55,81 @@ public class VisualizationTask extends AnyMap<String> implements Cloneable, Resu /** * Meta data key: Level for visualizer ordering * - * Returns an integer indicating the "temporal position" of this Visualizer. - * It is intended to impose an ordering on the execution of Visualizers as a + * Returns an integer indicating the "height" of this Visualizer. It is + * intended to impose an ordering on the execution of Visualizers as a * Visualizer may depend on another Visualizer running earlier. <br> * Lower numbers should result in a earlier use of this Visualizer, while * higher numbers should result in a later use. If more Visualizers have the * same level, no ordering is guaranteed. <br> * Note that this value is only a recommendation, as it is totally up to the * framework to ignore it. - * - * Type: Integer */ - public final static String META_LEVEL = "level"; + public int level = 0; /** - * Flag to control visibility. Type: Boolean + * Flag to control visibility. */ - public final static String META_VISIBLE = "visible"; + public boolean visible = true; /** - * Flag to signal there is no thumbnail needed. Type: Boolean + * Flag to signal there is no thumbnail needed. */ - public final static String META_NOTHUMB = "no-thumbnail"; + public boolean thumbnail = true; /** * Mark as not having a (sensible) detail view. */ - public static final String META_NODETAIL = "no-detail"; + public boolean nodetail = false; /** - * Flag to signal the visualizer should not be exported. Type: Boolean + * Flag to signal the visualizer should not be exported. */ - public final static String META_NOEXPORT = "no-export"; + public boolean noexport = false; /** - * Flag to signal the visualizer should not be embed. Type: Boolean + * Flag to signal the visualizer should not be embedded. */ - public final static String META_NOEMBED = "no-embed"; + public boolean noembed = false; /** - * Flag to signal default visibility of a visualizer. Type: Boolean + * Flag to signal default visibility of a visualizer. */ - public static final String META_VISIBLE_DEFAULT = "visible-default"; + public boolean default_visibility = true; /** - * Flag to mark the visualizer as tool. Type: Boolean + * Flag to mark the visualizer as tool. */ - public static final String META_TOOL = "tool"; + public boolean tool = false; /** * Indicate whether this task has options. */ - public static final String META_HAS_OPTIONS = "has-options"; + public boolean hasoptions = false; /** * Background layer */ - public final static int LEVEL_BACKGROUND = 0; + public static final int LEVEL_BACKGROUND = 0; /** * Data layer */ - public final static int LEVEL_DATA = 100; + public static final int LEVEL_DATA = 100; /** * Static plot layer */ - public final static int LEVEL_STATIC = 200; + public static final int LEVEL_STATIC = 200; /** * Passive foreground layer */ - public final static int LEVEL_FOREGROUND = 300; + public static final int LEVEL_FOREGROUND = 300; /** * Active foreground layer (interactive elements) */ - public final static int LEVEL_INTERACTIVE = 1000; + public static final int LEVEL_INTERACTIVE = 1000; /** * Name @@ -184,6 +177,11 @@ public class VisualizationTask extends AnyMap<String> implements Cloneable, Resu public double height; /** + * Thumbnail size + */ + public int thumbsize; + + /** * Visualization task. * * @param name Name @@ -270,18 +268,23 @@ public class VisualizationTask extends AnyMap<String> implements Cloneable, Resu return height; } + /** + * Init the default visibility of a task. + * + * @param vis Visibility. + */ + public void initDefaultVisibility(boolean vis) { + visible = vis; + default_visibility = vis; + } + @Override public VisualizationTask clone() { - VisualizationTask obj = (VisualizationTask) super.clone(); - obj.name = name; - obj.context = context; - obj.result = result; - obj.proj = proj; - obj.factory = factory; - obj.svgp = svgp; - obj.width = width; - obj.height = height; - return obj; + try { + return (VisualizationTask) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AbortException("Cloneable interface was removed.", e); + } } /** @@ -330,15 +333,13 @@ public class VisualizationTask extends AnyMap<String> implements Cloneable, Resu @Override public int compareTo(VisualizationTask other) { // sort by levels first - Integer level1 = this.get(VisualizationTask.META_LEVEL, Integer.class); - Integer level2 = other.get(VisualizationTask.META_LEVEL, Integer.class); - if(level1 != null && level2 != null && level1 != level2) { - return level1 - level2; + if (this.level != other.level) { + return this.level - other.level; } // sort by name otherwise. String name1 = this.getShortName(); String name2 = other.getShortName(); - if(name1 != null && name2 != null && name1 != name2) { + if (name1 != null && name2 != null && name1 != name2) { return name1.compareTo(name2); } return 0; @@ -346,13 +347,13 @@ public class VisualizationTask extends AnyMap<String> implements Cloneable, Resu @Override public String toString() { - StringBuffer buf = new StringBuffer(); - buf.append("VisTask: ").append(factory.getClass().getName()).append(" "); - if(result != null) { - buf.append("Result: ").append(result.getLongName()).append(" "); + StringBuilder buf = new StringBuilder(); + buf.append("VisTask: ").append(factory.getClass().getName()).append(' '); + if (result != null) { + buf.append("Result: ").append(result.getLongName()).append(' '); } - if(proj != null) { - buf.append("Proj: ").append(proj.toString()).append(" "); + if (proj != null) { + buf.append("Proj: ").append(proj.toString()).append(' '); } buf.append(super.toString()); return buf.toString(); @@ -369,4 +370,4 @@ public class VisualizationTask extends AnyMap<String> implements Cloneable, Resu // Also don't inherit equals based on list contents! return (this == o); } -}
\ No newline at end of file +} diff --git a/src/de/lmu/ifi/dbs/elki/visualization/VisualizerContext.java b/src/de/lmu/ifi/dbs/elki/visualization/VisualizerContext.java index c4e6c839..417d6d07 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/VisualizerContext.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/VisualizerContext.java @@ -49,11 +49,9 @@ import de.lmu.ifi.dbs.elki.result.ResultUtil; import de.lmu.ifi.dbs.elki.result.SelectionResult; import de.lmu.ifi.dbs.elki.visualization.projector.ProjectorFactory; import de.lmu.ifi.dbs.elki.visualization.style.ClusterStylingPolicy; -import de.lmu.ifi.dbs.elki.visualization.style.PropertiesBasedStyleLibrary; import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; import de.lmu.ifi.dbs.elki.visualization.style.StyleResult; import de.lmu.ifi.dbs.elki.visualization.visualizers.VisFactory; -import de.lmu.ifi.dbs.elki.visualization.visualizers.VisualizerUtil; /** * Map to store context information for the visualizer. This can be any data @@ -76,7 +74,7 @@ public class VisualizerContext implements DataStoreListener, Result { /** * Logger. */ - private static final Logging logger = Logging.getLogger(VisualizerContext.class); + private static final Logging LOG = Logging.getLogger(VisualizerContext.class); /** * The full result object @@ -89,11 +87,6 @@ public class VisualizerContext implements DataStoreListener, Result { private EventListenerList listenerList = new EventListenerList(); /** - * The style library of this context - */ - private StyleLibrary stylelib; - - /** * Projectors to use */ private Collection<ProjectorFactory> projectors; @@ -117,14 +110,12 @@ public class VisualizerContext implements DataStoreListener, Result { * Constructor. We currently require a Database and a Result. * * @param result Result - * @param stylelib Style library * @param projectors Projectors to use * @param factories Visualizer Factories to use */ public VisualizerContext(HierarchicalResult result, StyleLibrary stylelib, Collection<ProjectorFactory> projectors, Collection<VisFactory> factories) { super(); this.result = result; - this.stylelib = stylelib; this.projectors = projectors; this.factories = factories; @@ -133,16 +124,16 @@ public class VisualizerContext implements DataStoreListener, Result { final Database db = ResultUtil.findDatabase(result); ResultUtil.ensureClusteringResult(db, result); this.selection = ResultUtil.ensureSelectionResult(db); - for(Relation<?> rel : ResultUtil.getRelations(result)) { + for (Relation<?> rel : ResultUtil.getRelations(result)) { ResultUtil.getSamplingResult(rel); // FIXME: this is a really ugly workaround. :-( - if(TypeUtil.NUMBER_VECTOR_FIELD.isAssignableFromType(rel.getDataTypeInformation())) { + if (TypeUtil.NUMBER_VECTOR_FIELD.isAssignableFromType(rel.getDataTypeInformation())) { @SuppressWarnings("unchecked") - Relation<? extends NumberVector<?, ?>> vrel = (Relation<? extends NumberVector<?, ?>>) rel; + Relation<? extends NumberVector<?>> vrel = (Relation<? extends NumberVector<?>>) rel; ResultUtil.getScalesResult(vrel); } } - getStyleResult(); + makeStyleResult(stylelib); // result.getHierarchy().add(result, this); @@ -151,7 +142,8 @@ public class VisualizerContext implements DataStoreListener, Result { // For proxying events. ResultUtil.findDatabase(result).addDataStoreListener(this); - // Add a result listener. Don't expose these methods to avoid inappropriate use. + // Add a result listener. Don't expose these methods to avoid inappropriate + // use. addResultListener(new ResultListener() { @Override public void resultAdded(Result child, Result parent) { @@ -171,6 +163,24 @@ public class VisualizerContext implements DataStoreListener, Result { } /** + * Generate a new style result for the given style library. + * + * @param stylelib Style library + */ + protected void makeStyleResult(StyleLibrary stylelib) { + styleresult = new StyleResult(); + styleresult.setStyleLibrary(stylelib); + List<Clustering<? extends Model>> clusterings = ResultUtil.getClusteringResults(result); + if (clusterings.size() > 0) { + styleresult.setStylingPolicy(new ClusterStylingPolicy(clusterings.get(0), stylelib)); + } else { + Clustering<Model> c = generateDefaultClustering(); + styleresult.setStylingPolicy(new ClusterStylingPolicy(c, stylelib)); + } + result.getHierarchy().add(result, styleresult); + } + + /** * Get the full result object * * @return result object @@ -189,36 +199,11 @@ public class VisualizerContext implements DataStoreListener, Result { } /** - * Get the style library - * - * @return style library - */ - public StyleLibrary getStyleLibrary() { - if(stylelib == null) { - stylelib = new PropertiesBasedStyleLibrary(); - } - return stylelib; - } - - /** * Get the style result. * * @return Style result */ public StyleResult getStyleResult() { - if(styleresult == null) { - styleresult = new StyleResult(); - List<Clustering<? extends Model>> clusterings = ResultUtil.getClusteringResults(result); - if(clusterings.size() > 0) { - styleresult.setStylingPolicy(new ClusterStylingPolicy(clusterings.get(0), stylelib)); - result.getHierarchy().add(result, styleresult); - return styleresult; - } - Clustering<Model> c = generateDefaultClustering(); - styleresult.setStylingPolicy(new ClusterStylingPolicy(c, stylelib)); - result.getHierarchy().add(result, styleresult); - return styleresult; - } return styleresult; } @@ -234,8 +219,7 @@ public class VisualizerContext implements DataStoreListener, Result { // Try to cluster by labels ByLabelHierarchicalClustering split = new ByLabelHierarchicalClustering(); c = split.run(db); - } - catch(NoSupportedDataTypeException e) { + } catch (NoSupportedDataTypeException e) { // Put everything into one c = new TrivialAllInOne().run(db); } @@ -264,21 +248,6 @@ public class VisualizerContext implements DataStoreListener, Result { } /** - * Change a visualizers visibility. - * - * When a Tool visualizer is made visible, other tools are hidden. - * - * @param task Visualization task - * @param visibility new visibility - * - * @deprecated Use {@link VisualizerUtil#setVisible} - */ - @Deprecated - public void setVisualizationVisibility(VisualizationTask task, boolean visibility) { - VisualizerUtil.setVisible(this, task, visibility); - } - - /** * Adds a listener for the <code>DataStoreEvent</code> posted after the * content changes. * @@ -304,7 +273,7 @@ public class VisualizerContext implements DataStoreListener, Result { */ @Override public void contentChanged(DataStoreEvent e) { - for(DataStoreListener listener : listenerList.getListeners(DataStoreListener.class)) { + for (DataStoreListener listener : listenerList.getListeners(DataStoreListener.class)) { listener.contentChanged(e); } } @@ -316,21 +285,19 @@ public class VisualizerContext implements DataStoreListener, Result { * @param newResult Newly added Result */ private void processNewResult(HierarchicalResult baseResult, Result newResult) { - for(ProjectorFactory p : projectors) { + for (ProjectorFactory p : projectors) { try { p.processNewResult(baseResult, newResult); - } - catch(Throwable e) { - logger.warning("ProjectorFactory " + p.getClass().getCanonicalName() + " failed:", e); + } catch (Throwable e) { + LOG.warning("ProjectorFactory " + p.getClass().getCanonicalName() + " failed:", e); } } // Collect all visualizers. - for(VisFactory f : factories) { + for (VisFactory f : factories) { try { f.processNewResult(baseResult, newResult); - } - catch(Throwable e) { - logger.warning("VisFactory " + f.getClass().getCanonicalName() + " failed:", e); + } catch (Throwable e) { + LOG.warning("VisFactory " + f.getClass().getCanonicalName() + " failed:", e); } } } @@ -362,4 +329,4 @@ public class VisualizerContext implements DataStoreListener, Result { public String getShortName() { return "vis-context"; } -}
\ No newline at end of file +} diff --git a/src/de/lmu/ifi/dbs/elki/visualization/VisualizerParameterizer.java b/src/de/lmu/ifi/dbs/elki/visualization/VisualizerParameterizer.java index 36bbb823..1d1a7569 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/VisualizerParameterizer.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/VisualizerParameterizer.java @@ -27,7 +27,6 @@ import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Random; import java.util.regex.Pattern; import de.lmu.ifi.dbs.elki.algorithm.AbstractDistanceBasedAlgorithm; @@ -43,6 +42,7 @@ import de.lmu.ifi.dbs.elki.result.SamplingResult; import de.lmu.ifi.dbs.elki.result.SettingsResult; import de.lmu.ifi.dbs.elki.utilities.ClassGenericsUtil; import de.lmu.ifi.dbs.elki.utilities.InspectionUtil; +import de.lmu.ifi.dbs.elki.utilities.RandomFactory; import de.lmu.ifi.dbs.elki.utilities.exceptions.AbortException; import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer; import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID; @@ -75,7 +75,7 @@ public class VisualizerParameterizer implements Parameterizable { /** * Get a logger for this class. */ - protected final static Logging logger = Logging.getLogger(VisualizerParameterizer.class); + private static final Logging LOG = Logging.getLogger(VisualizerParameterizer.class); /** * Parameter to get the style properties file. @@ -86,12 +86,12 @@ public class VisualizerParameterizer implements Parameterizable { * Default: default properties file * </p> */ - public final static OptionID STYLELIB_ID = OptionID.getOrCreateOptionID("visualizer.stylesheet", "Style properties file to use"); + public static final OptionID STYLELIB_ID = new OptionID("visualizer.stylesheet", "Style properties file to use"); /** * Default pattern for visualizer enabling. */ - public final static String DEFAULT_ENABLEVIS = "^" + Pattern.quote(VisualizerParameterizer.class.getPackage().getName()) + "\\..*"; + public static final String DEFAULT_ENABLEVIS = "^" + Pattern.quote(VisualizerParameterizer.class.getPackage().getName()) + "\\..*"; /** * Parameter to enable visualizers @@ -102,7 +102,7 @@ public class VisualizerParameterizer implements Parameterizable { * Default: ELKI core * </p> */ - public final static OptionID ENABLEVIS_ID = OptionID.getOrCreateOptionID("vis.enable", "Visualizers to enable by default."); + public static final OptionID ENABLEVIS_ID = new OptionID("vis.enable", "Visualizers to enable by default."); /** * Parameter to set the sampling level @@ -111,7 +111,7 @@ public class VisualizerParameterizer implements Parameterizable { * Key: -vis.sampling * </p> */ - public final static OptionID SAMPLING_ID = OptionID.getOrCreateOptionID("vis.sampling", "Maximum number of objects to visualize by default (for performance reasons)."); + public static final OptionID SAMPLING_ID = new OptionID("vis.sampling", "Maximum number of objects to visualize by default (for performance reasons)."); /** * Style library to use. @@ -138,7 +138,7 @@ public class VisualizerParameterizer implements Parameterizable { * * FIXME: make parameterizable. */ - private long seed = new Random().nextLong(); + private RandomFactory rnd = RandomFactory.DEFAULT; /** * Constructor. @@ -164,16 +164,16 @@ public class VisualizerParameterizer implements Parameterizable { * @return New context */ public VisualizerContext newContext(HierarchicalResult result) { - if(samplesize > 0) { + if (samplesize > 0) { Collection<Relation<?>> rels = ResultUtil.filterResults(result, Relation.class); - for(Relation<?> rel : rels) { - if(!ResultUtil.filterResults(rel, SamplingResult.class).isEmpty()) { + for (Relation<?> rel : rels) { + if (!ResultUtil.filterResults(rel, SamplingResult.class).isEmpty()) { continue; } int size = rel.size(); - if(size > samplesize) { + if (size > samplesize) { SamplingResult sample = new SamplingResult(rel); - sample.setSample(DBIDUtil.randomSample(sample.getSample(), samplesize, seed)); + sample.setSample(DBIDUtil.randomSample(sample.getSample(), samplesize, rnd)); ResultUtil.addChildResult(rel, sample); } } @@ -190,54 +190,54 @@ public class VisualizerParameterizer implements Parameterizable { * @return generated title */ public static String getTitle(Database db, Result result) { - List<Pair<Object, Parameter<?, ?>>> settings = new ArrayList<Pair<Object, Parameter<?, ?>>>(); - for(SettingsResult sr : ResultUtil.getSettingsResults(result)) { + List<Pair<Object, Parameter<?>>> settings = new ArrayList<Pair<Object, Parameter<?>>>(); + for (SettingsResult sr : ResultUtil.getSettingsResults(result)) { settings.addAll(sr.getSettings()); } String algorithm = null; String distance = null; String dataset = null; - for(Pair<Object, Parameter<?, ?>> setting : settings) { - if(setting.second.equals(OptionID.ALGORITHM)) { + for (Pair<Object, Parameter<?>> setting : settings) { + if (setting.second.equals(OptionID.ALGORITHM)) { algorithm = setting.second.getValue().toString(); } - if(setting.second.equals(AbstractDistanceBasedAlgorithm.DISTANCE_FUNCTION_ID)) { + if (setting.second.equals(AbstractDistanceBasedAlgorithm.DISTANCE_FUNCTION_ID)) { distance = setting.second.getValue().toString(); } - if(setting.second.equals(FileBasedDatabaseConnection.INPUT_ID)) { + if (setting.second.equals(FileBasedDatabaseConnection.INPUT_ID)) { dataset = setting.second.getValue().toString(); } } StringBuilder buf = new StringBuilder(); - if(algorithm != null) { + if (algorithm != null) { // shorten the algorithm - if(algorithm.contains(".")) { - algorithm = algorithm.substring(algorithm.lastIndexOf(".") + 1); + if (algorithm.contains(".")) { + algorithm = algorithm.substring(algorithm.lastIndexOf('.') + 1); } buf.append(algorithm); } - if(distance != null) { + if (distance != null) { // shorten the distance - if(distance.contains(".")) { - distance = distance.substring(distance.lastIndexOf(".") + 1); + if (distance.contains(".")) { + distance = distance.substring(distance.lastIndexOf('.') + 1); } - if(buf.length() > 0) { + if (buf.length() > 0) { buf.append(" using "); } buf.append(distance); } - if(dataset != null) { + if (dataset != null) { // shorten the data set filename - if(dataset.contains(File.separator)) { + if (dataset.contains(File.separator)) { dataset = dataset.substring(dataset.lastIndexOf(File.separator) + 1); } - if(buf.length() > 0) { + if (buf.length() > 0) { buf.append(" on "); } buf.append(dataset); } - if(buf.length() > 0) { + if (buf.length() > 0) { return buf.toString(); } return null; @@ -260,27 +260,27 @@ public class VisualizerParameterizer implements Parameterizable { protected Collection<ProjectorFactory> projectors = null; protected int samplesize = -1; - + @Override protected void makeOptions(Parameterization config) { super.makeOptions(config); - IntParameter samplingP = new IntParameter(SAMPLING_ID, new GreaterEqualConstraint(-1), 10000); - if(config.grab(samplingP)) { - samplesize = samplingP.getValue(); + IntParameter samplingP = new IntParameter(SAMPLING_ID, 10000); + samplingP.addConstraint(new GreaterEqualConstraint(-1)); + if (config.grab(samplingP)) { + samplesize = samplingP.intValue(); } StringParameter stylelibP = new StringParameter(STYLELIB_ID, PropertiesBasedStyleLibrary.DEFAULT_SCHEME_FILENAME); - if(config.grab(stylelibP)) { + if (config.grab(stylelibP)) { String filename = stylelibP.getValue(); try { stylelib = new PropertiesBasedStyleLibrary(filename, "Command line style"); - } - catch(AbortException e) { + } catch (AbortException e) { config.reportError(new WrongParameterValueException(stylelibP, filename, e)); } } PatternParameter enablevisP = new PatternParameter(ENABLEVIS_ID, DEFAULT_ENABLEVIS); - if(config.grab(enablevisP)) { - if(!"all".equals(enablevisP.getValueAsString())) { + if (config.grab(enablevisP)) { + if (!"all".equals(enablevisP.getValueAsString())) { enableVisualizers = enablevisP.getValue(); } } @@ -298,21 +298,19 @@ public class VisualizerParameterizer implements Parameterizable { */ private static <O> Collection<ProjectorFactory> collectProjectorFactorys(MergedParameterization config, Pattern filter) { ArrayList<ProjectorFactory> factories = new ArrayList<ProjectorFactory>(); - for(Class<?> c : InspectionUtil.cachedFindAllImplementations(ProjectorFactory.class)) { - if(filter != null && !filter.matcher(c.getCanonicalName()).find()) { + for (Class<?> c : InspectionUtil.cachedFindAllImplementations(ProjectorFactory.class)) { + if (filter != null && !filter.matcher(c.getCanonicalName()).find()) { continue; } try { config.rewind(); ProjectorFactory a = ClassGenericsUtil.tryInstantiate(ProjectorFactory.class, c, config); factories.add(a); - } - catch(Throwable e) { - if(logger.isDebugging()) { - logger.exception("Error instantiating visualization factory " + c.getName(), e.getCause()); - } - else { - logger.warning("Error instantiating visualization factory " + c.getName() + ": " + e.getMessage()); + } catch (Throwable e) { + if (LOG.isDebugging()) { + LOG.exception("Error instantiating visualization factory " + c.getName(), e.getCause()); + } else { + LOG.warning("Error instantiating visualization factory " + c.getName() + ": " + e.getMessage()); } } } @@ -328,21 +326,19 @@ public class VisualizerParameterizer implements Parameterizable { */ private static <O> Collection<VisFactory> collectVisFactorys(MergedParameterization config, Pattern filter) { ArrayList<VisFactory> factories = new ArrayList<VisFactory>(); - for(Class<?> c : InspectionUtil.cachedFindAllImplementations(VisFactory.class)) { - if(filter != null && !filter.matcher(c.getCanonicalName()).find()) { + for (Class<?> c : InspectionUtil.cachedFindAllImplementations(VisFactory.class)) { + if (filter != null && !filter.matcher(c.getCanonicalName()).find()) { continue; } try { config.rewind(); VisFactory a = ClassGenericsUtil.tryInstantiate(VisFactory.class, c, config); factories.add(a); - } - catch(Throwable e) { - if(logger.isDebugging()) { - logger.exception("Error instantiating visualization factory " + c.getName(), e.getCause()); - } - else { - logger.warning("Error instantiating visualization factory " + c.getName() + ": " + e.getMessage()); + } catch (Throwable e) { + if (LOG.isDebugging()) { + LOG.exception("Error instantiating visualization factory " + c.getName(), e.getCause()); + } else { + LOG.warning("Error instantiating visualization factory " + c.getName() + ": " + e.getMessage()); } } } @@ -354,4 +350,4 @@ public class VisualizerParameterizer implements Parameterizable { return new VisualizerParameterizer(samplesize, stylelib, projectors, factories, enableVisualizers); } } -}
\ No newline at end of file +} diff --git a/src/de/lmu/ifi/dbs/elki/visualization/batikutil/CSSHoverClass.java b/src/de/lmu/ifi/dbs/elki/visualization/batikutil/CSSHoverClass.java index 3e9f355a..54d8e7e3 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/batikutil/CSSHoverClass.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/batikutil/CSSHoverClass.java @@ -30,12 +30,11 @@ import org.w3c.dom.events.EventListener; import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil; - /** * Do a hover effect using a CSS class. * * @author Erich Schubert - * + * */ public class CSSHoverClass implements EventListener { /** @@ -47,7 +46,7 @@ public class CSSHoverClass implements EventListener { * Class to set when out */ private String outclass; - + /** * Consider a click as 'out'? */ @@ -55,6 +54,7 @@ public class CSSHoverClass implements EventListener { /** * Constructor + * * @param overclass class to set when over * @param outclass class to set when out * @param clickisout consider a click to be an 'out' event @@ -82,7 +82,7 @@ public class CSSHoverClass implements EventListener { @Override public void handleEvent(Event evt) { Element e = (Element) evt.getTarget(); - if (evt.getType() == SVGConstants.SVG_EVENT_MOUSEOVER) { + if (SVGConstants.SVG_EVENT_MOUSEOVER.equals(evt.getType())) { if (overclass != null) { SVGUtil.addCSSClass(e, overclass); } @@ -90,7 +90,7 @@ public class CSSHoverClass implements EventListener { SVGUtil.removeCSSClass(e, outclass); } } - if (evt.getType() == SVGConstants.SVG_EVENT_MOUSEOUT) { + if (SVGConstants.SVG_EVENT_MOUSEOUT.equals(evt.getType())) { if (overclass != null) { SVGUtil.removeCSSClass(e, overclass); } @@ -98,7 +98,7 @@ public class CSSHoverClass implements EventListener { SVGUtil.addCSSClass(e, outclass); } } - if (clickisout && evt.getType() == SVGConstants.SVG_EVENT_CLICK) { + if (clickisout && SVGConstants.SVG_EVENT_CLICK.equals(evt.getType())) { if (overclass != null) { SVGUtil.removeCSSClass(e, overclass); } @@ -107,4 +107,4 @@ public class CSSHoverClass implements EventListener { } } } -}
\ No newline at end of file +} diff --git a/src/de/lmu/ifi/dbs/elki/visualization/batikutil/CloneInlineImages.java b/src/de/lmu/ifi/dbs/elki/visualization/batikutil/CloneInlineImages.java index 30456ba9..9c53d72b 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/batikutil/CloneInlineImages.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/batikutil/CloneInlineImages.java @@ -46,6 +46,8 @@ import de.lmu.ifi.dbs.elki.visualization.svg.SVGCloneVisible; * Clone an SVG document, inlining temporary and in-memory linked images. * * @author Erich Schubert + * + * @apiviz.has ThumbnailRegistryEntry */ public class CloneInlineImages extends SVGCloneVisible { @Override diff --git a/src/de/lmu/ifi/dbs/elki/visualization/batikutil/JSVGUpdateSynchronizer.java b/src/de/lmu/ifi/dbs/elki/visualization/batikutil/JSVGUpdateSynchronizer.java index 953c39da..110d38ec 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/batikutil/JSVGUpdateSynchronizer.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/batikutil/JSVGUpdateSynchronizer.java @@ -24,6 +24,7 @@ package de.lmu.ifi.dbs.elki.visualization.batikutil; */ import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.List; import org.apache.batik.bridge.UpdateManager; @@ -50,7 +51,7 @@ class JSVGUpdateSynchronizer implements UpdateSynchronizer { /** * The UpdateRunner we are put into */ - private List<WeakReference<UpdateRunner>> updaterunner = new java.util.Vector<WeakReference<UpdateRunner>>(); + private List<WeakReference<UpdateRunner>> updaterunner = new ArrayList<WeakReference<UpdateRunner>>(); /** * Adapter to track component changes diff --git a/src/de/lmu/ifi/dbs/elki/visualization/batikutil/ThumbnailRegistryEntry.java b/src/de/lmu/ifi/dbs/elki/visualization/batikutil/ThumbnailRegistryEntry.java index dd844165..83c101e3 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/batikutil/ThumbnailRegistryEntry.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/batikutil/ThumbnailRegistryEntry.java @@ -75,7 +75,7 @@ public class ThumbnailRegistryEntry extends AbstractRegistryEntry implements URL /** * The logger class. */ - private static final Logging logger = Logging.getLogger(ThumbnailRegistryEntry.class); + private static final Logging LOG = Logging.getLogger(ThumbnailRegistryEntry.class); /** * The image cache. @@ -95,8 +95,8 @@ public class ThumbnailRegistryEntry extends AbstractRegistryEntry implements URL */ public ThumbnailRegistryEntry() { super("Internal", PRIORITY, new String[0], new String[] { INTERNAL_MIME_TYPE }); - if(logger.isDebuggingFiner()) { - logger.debugFiner("Registry initialized."); + if(LOG.isDebuggingFiner()) { + LOG.debugFiner("Registry initialized."); } } @@ -122,8 +122,8 @@ public class ThumbnailRegistryEntry extends AbstractRegistryEntry implements URL } } } - if(logger.isDebuggingFiner()) { - logger.debugFiner("Registered image: " + key); + if(LOG.isDebuggingFiner()) { + LOG.debugFiner("Registered image: " + key); } return key; } @@ -162,8 +162,8 @@ public class ThumbnailRegistryEntry extends AbstractRegistryEntry implements URL * @return Image, or null */ public static Filter handleURL(ParsedURL url) { - if(logger.isDebuggingFiner()) { - logger.debugFiner("handleURL " + url.toString()); + if(LOG.isDebuggingFiner()) { + LOG.debugFiner("handleURL " + url.toString()); } if(!isCompatibleURLStatic(url)) { return null; @@ -179,7 +179,7 @@ public class ThumbnailRegistryEntry extends AbstractRegistryEntry implements URL if(ref != null) { RenderedImage ri = ref.get(); if(ri == null) { - logger.warning("Referenced image has expired from the cache!"); + LOG.warning("Referenced image has expired from the cache!"); } else { return new RedRable(GraphicsUtil.wrap(ri)); @@ -193,6 +193,8 @@ public class ThumbnailRegistryEntry extends AbstractRegistryEntry implements URL * URL representation for internal URLs. * * @author Erich Schubert + * + * @apiviz.exclude */ class InternalParsedURLData extends ParsedURLData { /** @@ -225,8 +227,8 @@ public class ThumbnailRegistryEntry extends AbstractRegistryEntry implements URL @Override public ParsedURLData parseURL(String urlStr) { - if(logger.isDebuggingFinest()) { - logger.debugFinest("parseURL: " + urlStr); + if(LOG.isDebuggingFinest()) { + LOG.debugFinest("parseURL: " + urlStr); } if(urlStr.startsWith(INTERNAL_PREFIX)) { InternalParsedURLData ret = new InternalParsedURLData(urlStr.substring(INTERNAL_PREFIX.length())); diff --git a/src/de/lmu/ifi/dbs/elki/visualization/colors/ColorLibrary.java b/src/de/lmu/ifi/dbs/elki/visualization/colors/ColorLibrary.java index 2693bc6b..2d7512c8 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/colors/ColorLibrary.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/colors/ColorLibrary.java @@ -23,7 +23,6 @@ package de.lmu.ifi.dbs.elki.visualization.colors; along with this program. If not, see <http://www.gnu.org/licenses/>. */ - /** * Color scheme interface * @@ -34,49 +33,61 @@ public interface ColorLibrary { * List of line colors */ final static String COLOR_LINE_COLORS = "line.colors"; + /** * Named color for the page background */ final static String COLOR_PAGE_BACKGROUND = "page.background"; + /** * Named color for a typical axis */ final static String COLOR_AXIS_LINE = "axis.line"; + /** * Named color for a typical axis tick mark */ final static String COLOR_AXIS_TICK = "axis.tick"; + /** * Named color for a typical axis tick mark */ final static String COLOR_AXIS_MINOR_TICK = "axis.tick.minor"; + /** * Named color for a typical axis label */ final static String COLOR_AXIS_LABEL = "axis.label"; + /** * Named color for the background of the key box */ final static String COLOR_KEY_BACKGROUND = "key.background"; + /** * Named color for a label in the key part */ final static String COLOR_KEY_LABEL = "key.label"; + /** * Background color for plot area */ final static String COLOR_PLOT_BACKGROUND = "plot.background"; + /** - * Return the number of native colors available. These are guaranteed to be unique. + * Return the number of native colors available. These are guaranteed to be + * unique. * * @return number of native colors */ public int getNumberOfNativeColors(); + /** * Return the i'th color. * * @param index color index - * @return color in hexadecimal notation (#aabbcc) or color name ("red") as valid in CSS and SVG. + * @return color in hexadecimal notation (#aabbcc) or color name ("red") as + * valid in CSS and SVG. */ public String getColor(int index); -}
\ No newline at end of file +} diff --git a/src/de/lmu/ifi/dbs/elki/visualization/css/CSSClass.java b/src/de/lmu/ifi/dbs/elki/visualization/css/CSSClass.java index 5c89f638..bb62b7ef 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/css/CSSClass.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/css/CSSClass.java @@ -265,13 +265,13 @@ public class CSSClass { * * @param buf String buffer to append to. */ - public void appendCSSDefinition(StringBuffer buf) { + public void appendCSSDefinition(StringBuilder buf) { buf.append("\n."); buf.append(name); - buf.append("{"); + buf.append('{'); for (Pair<String, String> pair : statements) { buf.append(pair.getFirst()); - buf.append(":"); + buf.append(':'); buf.append(pair.getSecond()); buf.append(";\n"); } @@ -305,12 +305,12 @@ public class CSSClass { * @return string rendition of CSS for inline use */ public String inlineCSS() { - StringBuffer buf = new StringBuffer(); + StringBuilder buf = new StringBuilder(); for (Pair<String, String> pair : statements) { buf.append(pair.getFirst()); - buf.append(":"); + buf.append(':'); buf.append(pair.getSecond()); - buf.append(";"); + buf.append(';'); } return buf.toString(); } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/css/CSSClassManager.java b/src/de/lmu/ifi/dbs/elki/visualization/css/CSSClassManager.java index 22e53c55..34debe56 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/css/CSSClassManager.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/css/CSSClassManager.java @@ -119,7 +119,7 @@ public class CSSClassManager { * * @param buf String buffer */ - public void serialize(StringBuffer buf) { + public void serialize(StringBuilder buf) { for (CSSClass clss : store.values()) { clss.appendCSSDefinition(buf); } @@ -193,7 +193,7 @@ public class CSSClassManager { * @param style Style element */ public void updateStyleElement(Document document, Element style) { - StringBuffer buf = new StringBuffer(); + StringBuilder buf = new StringBuilder(); serialize(buf); Text cont = document.createTextNode(buf.toString()); while (style.hasChildNodes()) { diff --git a/src/de/lmu/ifi/dbs/elki/visualization/gui/ResultVisualizer.java b/src/de/lmu/ifi/dbs/elki/visualization/gui/ResultVisualizer.java index 2c3db308..635a0466 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/gui/ResultVisualizer.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/gui/ResultVisualizer.java @@ -25,6 +25,7 @@ package de.lmu.ifi.dbs.elki.visualization.gui; import javax.swing.JFrame; +import de.lmu.ifi.dbs.elki.gui.GUIUtil; import de.lmu.ifi.dbs.elki.logging.Logging; import de.lmu.ifi.dbs.elki.result.HierarchicalResult; import de.lmu.ifi.dbs.elki.result.Result; @@ -51,7 +52,7 @@ public class ResultVisualizer implements ResultHandler { /** * Get a logger for this class. */ - protected final static Logging logger = Logging.getLogger(ResultVisualizer.class); + private static final Logging LOG = Logging.getLogger(ResultVisualizer.class); /** * Parameter to specify the window title @@ -62,7 +63,7 @@ public class ResultVisualizer implements ResultHandler { * Default value: "ELKI Result Visualization" * </p> */ - public static final OptionID WINDOW_TITLE_ID = OptionID.getOrCreateOptionID("vis.window.title", "Title to use for visualization window."); + public static final OptionID WINDOW_TITLE_ID = new OptionID("vis.window.title", "Title to use for visualization window."); /** * Flag to set single display @@ -71,7 +72,7 @@ public class ResultVisualizer implements ResultHandler { * Key: -vis.single * </p> */ - public final static OptionID SINGLE_ID = OptionID.getOrCreateOptionID("vis.window.single", "Embed visualizers in a single window, not using thumbnails and detail views."); + public static final OptionID SINGLE_ID = new OptionID("vis.window.single", "Embed visualizers in a single window, not using thumbnails and detail views."); /** * Stores the set title. @@ -81,7 +82,7 @@ public class ResultVisualizer implements ResultHandler { /** * Default title */ - protected final static String DEFAULT_TITLE = "ELKI Result Visualization"; + protected static final String DEFAULT_TITLE = "ELKI Result Visualization"; /** * Visualization manager. @@ -124,13 +125,14 @@ public class ResultVisualizer implements ResultHandler { @Override public void run() { try { + GUIUtil.setLookAndFeel(); ResultWindow window = new ResultWindow(title, top, context, single); window.setVisible(true); window.setExtendedState(window.getExtendedState() | JFrame.MAXIMIZED_BOTH); window.showOverview(); } catch(Throwable e) { - logger.exception("Error in starting visualizer window.", e); + LOG.exception("Error in starting visualizer window.", e); } } }); @@ -162,13 +164,14 @@ public class ResultVisualizer implements ResultHandler { @Override protected void makeOptions(Parameterization config) { super.makeOptions(config); - StringParameter titleP = new StringParameter(WINDOW_TITLE_ID, true); + StringParameter titleP = new StringParameter(WINDOW_TITLE_ID); + titleP.setOptional(true); if(config.grab(titleP)) { title = titleP.getValue(); } Flag singleF = new Flag(SINGLE_ID); if (config.grab(singleF)) { - single = singleF.getValue(); + single = singleF.isTrue(); } manager = config.tryInstantiate(VisualizerParameterizer.class); } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/gui/ResultWindow.java b/src/de/lmu/ifi/dbs/elki/visualization/gui/ResultWindow.java index 38476d7d..609932cf 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/gui/ResultWindow.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/gui/ResultWindow.java @@ -87,7 +87,7 @@ public class ResultWindow extends JFrame implements ResultListener { /** * Get a logger for this class. */ - protected final static Logging logger = Logging.getLogger(ResultWindow.class); + private static final Logging LOG = Logging.getLogger(ResultWindow.class); /** * The "Overview" button, which goes to the overview view. @@ -348,7 +348,7 @@ public class ResultWindow extends JFrame implements ResultListener { SVGSaveDialog.showSaveDialog(currentPlot, 512, 512); } else { - logger.warning("saveCurrentPlot() called without a visible plot!"); + LOG.warning("saveCurrentPlot() called without a visible plot!"); } } @@ -431,8 +431,8 @@ public class ResultWindow extends JFrame implements ResultListener { // Currently enabled? final String name = v.getLongName(); - boolean enabled = VisualizerUtil.isVisible(v); - boolean istool = VisualizerUtil.isTool(v); + boolean enabled = v.visible; + boolean istool = v.tool; if(!istool) { final JCheckBoxMenuItem visItem = new JCheckBoxMenuItem(name, enabled); visItem.addItemListener(new ItemListener() { @@ -465,8 +465,7 @@ public class ResultWindow extends JFrame implements ResultListener { }); item = visItem; } - boolean hasoptions = VisualizerUtil.hasOptions(v); - if(hasoptions) { + if(v.hasoptions) { final JMenu menu = new JMenu(name); menu.add(item); // TODO: build a menu for the visualizer! diff --git a/src/de/lmu/ifi/dbs/elki/visualization/gui/SelectionTableWindow.java b/src/de/lmu/ifi/dbs/elki/visualization/gui/SelectionTableWindow.java index 754779fe..4fea630a 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/gui/SelectionTableWindow.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/gui/SelectionTableWindow.java @@ -99,7 +99,7 @@ public class SelectionTableWindow extends JFrame implements DataStoreListener, R /**
* The logger
*/
- static final Logging logger = Logging.getLogger(SelectionTableWindow.class);
+ private static final Logging LOG = Logging.getLogger(SelectionTableWindow.class);
/**
* The DBIDs to display
@@ -229,7 +229,7 @@ public class SelectionTableWindow extends JFrame implements DataStoreListener, R context.setSelection(new DBIDSelection(remain));
// Now delete them.
for(DBIDIter iter = todel.iter(); iter.valid(); iter.advance()) {
- upd.delete(iter.getDBID());
+ upd.delete(iter);
}
}
@@ -248,7 +248,7 @@ public class SelectionTableWindow extends JFrame implements DataStoreListener, R @Override
public int getColumnCount() {
- return 3; //DatabaseUtil.dimensionality(database) + 3;
+ return 3; //RelationUtil.dimensionality(database) + 3;
}
@Override
@@ -301,7 +301,7 @@ public class SelectionTableWindow extends JFrame implements DataStoreListener, R @Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
if(columnIndex == 0) {
- logger.warning("Tried to edit DBID, this is not allowed.");
+ LOG.warning("Tried to edit DBID, this is not allowed.");
return;
}
final DBID id = dbids.get(rowIndex);
@@ -314,7 +314,7 @@ public class SelectionTableWindow extends JFrame implements DataStoreListener, R crep.set(id, lbl);
}
if(!(aValue instanceof String)) {
- logger.warning("Was expecting a String value from the input element, got: " + aValue.getClass());
+ LOG.warning("Was expecting a String value from the input element, got: " + aValue.getClass());
return;
}
throw new AbortException("FIXME: INCOMPLETE TRANSITION");
@@ -323,7 +323,7 @@ public class SelectionTableWindow extends JFrame implements DataStoreListener, R logger.warning("Tried to edit removed object?");
return;
}
- final int dimensionality = DatabaseUtil.dimensionality(database);
+ final int dimensionality = RelationUtil.dimensionality(database);
double[] vals = new double[dimensionality];
for(int d = 0; d < dimensionality; d++) {
if(d == columnIndex - 3) {
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/gui/detail/DetailView.java b/src/de/lmu/ifi/dbs/elki/visualization/gui/detail/DetailView.java index 3e1348ac..091bc8ca 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/gui/detail/DetailView.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/gui/detail/DetailView.java @@ -46,7 +46,6 @@ import de.lmu.ifi.dbs.elki.visualization.svg.SVGEffects; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil; import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; -import de.lmu.ifi.dbs.elki.visualization.visualizers.VisualizerUtil; /** * Manages a detail view. @@ -122,7 +121,7 @@ public class DetailView extends SVGPlot implements ResultListener { private void addBackground(VisualizerContext context) { // Make a background CSSClass cls = new CSSClass(this, "background"); - cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, context.getStyleLibrary().getBackgroundColor(StyleLibrary.PAGE)); + cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, context.getStyleResult().getStyleLibrary().getBackgroundColor(StyleLibrary.PAGE)); Element bg = this.svgElement(SVGConstants.SVG_RECT_TAG); SVGUtil.setAtt(bg, SVGConstants.SVG_X_ATTRIBUTE, "0"); SVGUtil.setAtt(bg, SVGConstants.SVG_Y_ATTRIBUTE, "0"); @@ -146,10 +145,10 @@ public class DetailView extends SVGPlot implements ResultListener { // TODO: center/arrange visualizations? for(Iterator<VisualizationTask> tit = visi.tasks.iterator(); tit.hasNext();) { VisualizationTask task = tit.next(); - if(VisualizerUtil.isVisible(task)) { + if(task.visible) { try { Visualization v = task.getFactory().makeVisualization(task.clone(this, context, visi.proj, width, height)); - if (VisualizerUtil.isNoExport(task)) { + if (task.noexport) { v.getLayer().setAttribute(NO_EXPORT_ATTRIBUTE, NO_EXPORT_ATTRIBUTE); } layers.add(v); @@ -277,8 +276,8 @@ public class DetailView extends SVGPlot implements ResultListener { Visualization vis = layermap.get(task); if(vis != null) { // Ensure visibility is as expected - boolean isHidden = vis.getLayer().getAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY) == SVGConstants.CSS_HIDDEN_VALUE; - if(VisualizerUtil.isVisible(task)) { + boolean isHidden = SVGConstants.CSS_HIDDEN_VALUE.equals(vis.getLayer().getAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY)); + if(task.visible) { if(isHidden) { this.scheduleUpdate(new AttributeModifier(vis.getLayer(), SVGConstants.CSS_VISIBILITY_PROPERTY, SVGConstants.CSS_VISIBLE_VALUE)); } @@ -291,10 +290,10 @@ public class DetailView extends SVGPlot implements ResultListener { } else { // Only materialize when becoming visible - if(VisualizerUtil.isVisible(task)) { + if(task.visible) { // LoggingUtil.warning("Need to recreate a missing layer for " + v); vis = task.getFactory().makeVisualization(task.clone(this, context, visi.proj, width, height)); - if (VisualizerUtil.isNoExport(task)) { + if (task.noexport) { vis.getLayer().setAttribute(NO_EXPORT_ATTRIBUTE, NO_EXPORT_ATTRIBUTE); } layermap.put(task, vis); diff --git a/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/OverviewPlot.java b/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/OverviewPlot.java index 92ddb0b8..271a62ff 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/OverviewPlot.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/OverviewPlot.java @@ -55,7 +55,6 @@ import de.lmu.ifi.dbs.elki.visualization.svg.SVGEffects; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil; import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; -import de.lmu.ifi.dbs.elki.visualization.visualizers.VisualizerUtil; /** * Generate an overview plot for a set of visualizations. @@ -76,7 +75,7 @@ public class OverviewPlot extends SVGPlot implements ResultListener { /** * Our logging class */ - private static final Logging logger = Logging.getLogger(OverviewPlot.class); + private static final Logging LOG = Logging.getLogger(OverviewPlot.class); /** * Visualizer context @@ -91,7 +90,7 @@ public class OverviewPlot extends SVGPlot implements ResultListener { /** * Action listeners for this plot. */ - private java.util.Vector<ActionListener> actionListeners = new java.util.Vector<ActionListener>(); + private ArrayList<ActionListener> actionListeners = new ArrayList<ActionListener>(); /** * Single view mode @@ -163,7 +162,7 @@ public class OverviewPlot extends SVGPlot implements ResultListener { // Add a background element: { CSSClass cls = new CSSClass(this, "background"); - cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, context.getStyleLibrary().getBackgroundColor(StyleLibrary.PAGE)); + cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, context.getStyleResult().getStyleLibrary().getBackgroundColor(StyleLibrary.PAGE)); addCSSClassOrLogError(cls); Element background = this.svgElement(SVGConstants.SVG_RECT_TAG); background.setAttribute(SVGConstants.SVG_X_ATTRIBUTE, "0"); @@ -174,7 +173,7 @@ public class OverviewPlot extends SVGPlot implements ResultListener { getRoot().appendChild(background); } - if(single) { + if (single) { setDisableInteractions(true); } SVGEffects.addShadowFilter(this); @@ -186,19 +185,22 @@ public class OverviewPlot extends SVGPlot implements ResultListener { /** * Recompute the layout of visualizations. + * + * @param width Initial width + * @param height Initial height + * @return Arrangement */ - private void arrangeVisualizations() { - RectangleArranger<PlotItem> plotmap = new RectangleArranger<PlotItem>(ratio); + private RectangleArranger<PlotItem> arrangeVisualizations(double width, double height) { + RectangleArranger<PlotItem> plotmap = new RectangleArranger<PlotItem>(width, height); ArrayList<Projector> projectors = ResultUtil.filterResults(context.getResult(), Projector.class); // Rectangle layout - for(Projector p : projectors) { + for (Projector p : projectors) { Collection<PlotItem> projs = p.arrange(); - for(PlotItem it : projs) { - if(it.w <= 0.0 || it.h <= 0.0) { - logger.warning("Plot item with improper size information: " + it); - } - else { + for (PlotItem it : projs) { + if (it.w <= 0.0 || it.h <= 0.0) { + LOG.warning("Plot item with improper size information: " + it); + } else { plotmap.put(it.w, it.h, it); } } @@ -206,22 +208,21 @@ public class OverviewPlot extends SVGPlot implements ResultListener { ResultHierarchy hier = context.getHierarchy(); ArrayList<VisualizationTask> tasks = ResultUtil.filterResults(context.getResult(), VisualizationTask.class); - nextTask: for(VisualizationTask task : tasks) { - for(Result parent : hier.getParents(task)) { - if(parent instanceof Projector) { + nextTask: for (VisualizationTask task : tasks) { + for (Result parent : hier.getParents(task)) { + if (parent instanceof Projector) { continue nextTask; } } - if(task.getWidth() <= 0.0 || task.getHeight() <= 0.0) { - logger.warning("Task with improper size information: " + task); - } - else { + if (task.getWidth() <= 0.0 || task.getHeight() <= 0.0) { + LOG.warning("Task with improper size information: " + task); + } else { PlotItem it = new PlotItem(task.getWidth(), task.getHeight(), null); it.tasks.add(task); plotmap.put(it.w, it.h, it); } } - this.plotmap = plotmap; + return plotmap; } /** @@ -229,13 +230,19 @@ public class OverviewPlot extends SVGPlot implements ResultListener { */ private void reinitialize() { setupHoverer(); - arrangeVisualizations(); + plotmap = arrangeVisualizations(ratio, 1.0); + double s = plotmap.relativeFill(); + if (s < 0.9) { + // Retry, sometimes this yields better results + plotmap = arrangeVisualizations(plotmap.getWidth() * s, plotmap.getHeight() * s); + } + recalcViewbox(); final int thumbsize = (int) Math.max(screenwidth / plotmap.getWidth(), screenheight / plotmap.getHeight()); // TODO: cancel pending thumbnail requests! // Detach existing elements: - for(Pair<Element, Visualization> pair : vistoelem.values()) { + for (Pair<Element, Visualization> pair : vistoelem.values()) { SVGUtil.removeFromParent(pair.first); } // Replace the layer map @@ -249,10 +256,10 @@ public class OverviewPlot extends SVGPlot implements ResultListener { hoverlayer = this.svgElement(SVGConstants.SVG_G_TAG); // Redo the layout - for(Entry<PlotItem, double[]> e : plotmap.entrySet()) { + for (Entry<PlotItem, double[]> e : plotmap.entrySet()) { final double basex = e.getValue()[0]; final double basey = e.getValue()[1]; - for(Iterator<PlotItem> iter = e.getKey().itemIterator(); iter.hasNext();) { + for (Iterator<PlotItem> iter = e.getKey().itemIterator(); iter.hasNext();) { PlotItem it = iter.next(); boolean hasDetails = false; @@ -262,27 +269,24 @@ public class OverviewPlot extends SVGPlot implements ResultListener { plotlayer.appendChild(g); vistoelem.put(it, null, g, null); // Add the actual tasks: - for(VisualizationTask task : it.tasks) { - if(!visibleInOverview(task)) { + for (VisualizationTask task : it.tasks) { + if (!visibleInOverview(task)) { continue; } - if(VisualizerUtil.detailsEnabled(task)) { - hasDetails = true; - // TODO: not updatable - } + hasDetails |= !task.nodetail; Pair<Element, Visualization> pair = oldlayers.remove(it, task); - if(pair == null) { + if (pair == null) { pair = new Pair<Element, Visualization>(null, null); pair.first = svgElement(SVGConstants.SVG_G_TAG); } - if(pair.second == null) { + if (pair.second == null) { pair.second = embedOrThumbnail(thumbsize, it, task, pair.first); } g.appendChild(pair.first); vistoelem.put(it, task, pair); } // When needed, add a hover effect - if(hasDetails && !single) { + if (hasDetails && !single) { Element hover = this.svgRect(basex + it.x, basey + it.y, it.w, it.h); SVGUtil.addCSSClass(hover, selcss.getName()); // link hoverer. @@ -296,8 +300,8 @@ public class OverviewPlot extends SVGPlot implements ResultListener { } } } - for(Pair<Element, Visualization> pair : oldlayers.values()) { - if(pair.second != null) { + for (Pair<Element, Visualization> pair : oldlayers.values()) { + if (pair.second != null) { pair.second.destroy(); } } @@ -315,29 +319,26 @@ public class OverviewPlot extends SVGPlot implements ResultListener { * @param parent Parent element to draw to */ private Visualization embedOrThumbnail(final int thumbsize, PlotItem it, VisualizationTask task, Element parent) { - if(single) { + if (single) { VisualizationTask thumbtask = task.clone(this, context, it.proj, it.w, it.h); final Visualization vis = thumbtask.getFactory().makeVisualization(thumbtask); - if(vis.getLayer() == null) { + if (vis.getLayer() == null) { LoggingUtil.warning("Visualization returned empty layer: " + vis); - } - else { - if (VisualizerUtil.isNoExport(task)) { + } else { + if (task.noexport) { vis.getLayer().setAttribute(NO_EXPORT_ATTRIBUTE, NO_EXPORT_ATTRIBUTE); } parent.appendChild(vis.getLayer()); } return vis; - } - else { + } else { VisualizationTask thumbtask = task.clone(this, context, it.proj, it.w, it.h); - thumbtask.put(VisualizationTask.THUMBNAIL, true); - thumbtask.put(VisualizationTask.THUMBNAIL_RESOLUTION, thumbsize); + thumbtask.thumbnail = true; + thumbtask.thumbsize = thumbsize; final Visualization vis = thumbtask.getFactory().makeVisualizationOrThumbnail(thumbtask); - if(vis.getLayer() == null) { + if (vis.getLayer() == null) { LoggingUtil.warning("Visualization returned empty layer: " + vis); - } - else { + } else { parent.appendChild(vis.getLayer()); } return vis; @@ -349,25 +350,24 @@ public class OverviewPlot extends SVGPlot implements ResultListener { */ synchronized void refresh() { pendingRefresh = null; - if(reinitOnRefresh) { - logger.debug("Reinitialize"); + if (reinitOnRefresh) { + LOG.debug("Reinitialize"); reinitialize(); reinitOnRefresh = false; - } - else { - logger.debug("Incremental refresh"); + } else { + LOG.debug("Incremental refresh"); boolean refreshcss = false; final int thumbsize = (int) Math.max(screenwidth / plotmap.getWidth(), screenheight / plotmap.getHeight()); - for(PlotItem pi : plotmap.keySet()) { - for(Iterator<PlotItem> iter = pi.itemIterator(); iter.hasNext();) { + for (PlotItem pi : plotmap.keySet()) { + for (Iterator<PlotItem> iter = pi.itemIterator(); iter.hasNext();) { PlotItem it = iter.next(); - for(Iterator<VisualizationTask> tit = it.tasks.iterator(); tit.hasNext();) { + for (Iterator<VisualizationTask> tit = it.tasks.iterator(); tit.hasNext();) { VisualizationTask task = tit.next(); Pair<Element, Visualization> pair = vistoelem.get(it, task); // New task? - if(pair == null) { - if(visibleInOverview(task)) { + if (pair == null) { + if (visibleInOverview(task)) { pair = new Pair<Element, Visualization>(null, null); pair.first = svgElement(SVGConstants.SVG_G_TAG); pair.second = embedOrThumbnail(thumbsize, it, task, pair.first); @@ -375,24 +375,22 @@ public class OverviewPlot extends SVGPlot implements ResultListener { vistoelem.put(it, task, pair); refreshcss = true; } - } - else { - if(visibleInOverview(task)) { + } else { + if (visibleInOverview(task)) { // unhide if hidden. - if(pair.first.hasAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY)) { + if (pair.first.hasAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY)) { pair.first.removeAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY); } // if not yet rendered, add a thumbnail - if(!pair.first.hasChildNodes()) { - logger.warning("This codepath should no longer be needed."); + if (!pair.first.hasChildNodes()) { + LOG.warning("This codepath should no longer be needed."); Visualization visualization = embedOrThumbnail(thumbsize, it, task, pair.first); vistoelem.put(it, task, pair.first, visualization); refreshcss = true; } - } - else { + } else { // hide if there is anything to hide. - if(pair.first != null && pair.first.hasChildNodes()) { + if (pair.first != null && pair.first.hasChildNodes()) { pair.first.setAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY, SVGConstants.CSS_HIDDEN_VALUE); } } @@ -401,7 +399,7 @@ public class OverviewPlot extends SVGPlot implements ResultListener { } } } - if(refreshcss) { + if (refreshcss) { updateStyleElement(); } } @@ -414,15 +412,10 @@ public class OverviewPlot extends SVGPlot implements ResultListener { * @return visibility */ protected boolean visibleInOverview(VisualizationTask task) { - if(!VisualizerUtil.isVisible(task)) { - return false; - } - if(single) { - Boolean nothumb = task.getGenerics(VisualizationTask.META_NOEMBED, Boolean.class); - return (nothumb == null) || !nothumb; - } - else { - return VisualizerUtil.thumbnailEnabled(task); + if (single) { + return task.visible && !task.noembed; + } else { + return task.visible && task.thumbnail; } } @@ -483,7 +476,7 @@ public class OverviewPlot extends SVGPlot implements ResultListener { */ protected void triggerSubplotSelectEvent(PlotItem it) { // forward event to all listeners. - for(ActionListener actionListener : actionListeners) { + for (ActionListener actionListener : actionListeners) { actionListener.actionPerformed(new DetailViewSelectedEvent(this, ActionEvent.ACTION_PERFORMED, null, 0, it)); } } @@ -544,11 +537,11 @@ public class OverviewPlot extends SVGPlot implements ResultListener { * Trigger a redraw, but avoid excessive redraws. */ public final void lazyRefresh() { - logger.debug("Scheduling refresh."); + LOG.debug("Scheduling refresh."); Runnable pr = new Runnable() { @Override public void run() { - if(OverviewPlot.this.pendingRefresh == this) { + if (OverviewPlot.this.pendingRefresh == this) { OverviewPlot.this.refresh(); } } @@ -559,8 +552,8 @@ public class OverviewPlot extends SVGPlot implements ResultListener { @Override public void resultAdded(Result child, Result parent) { - logger.debug("result added: " + child); - if(child instanceof VisualizationTask) { + LOG.debug("result added: " + child); + if (child instanceof VisualizationTask) { reinitOnRefresh = true; } lazyRefresh(); @@ -568,13 +561,13 @@ public class OverviewPlot extends SVGPlot implements ResultListener { @Override public void resultChanged(Result current) { - logger.debug("result changed: " + current); + LOG.debug("result changed: " + current); lazyRefresh(); } @Override public void resultRemoved(Result child, Result parent) { - logger.debug("result removed: " + child); + LOG.debug("result removed: " + child); lazyRefresh(); } -}
\ No newline at end of file +} diff --git a/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/RectangleArranger.java b/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/RectangleArranger.java index a70393a5..b5e6031e 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/RectangleArranger.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/RectangleArranger.java @@ -23,6 +23,8 @@ package de.lmu.ifi.dbs.elki.visualization.gui.overview; along with this program. If not, see <http://www.gnu.org/licenses/>. */ +import gnu.trove.list.array.TDoubleArrayList; + import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -47,7 +49,7 @@ public class RectangleArranger<T> { /** * Logging class */ - private static final Logging logger = Logging.getLogger(RectangleArranger.class); + private static final Logging LOG = Logging.getLogger(RectangleArranger.class); /** * Target height/width ratio @@ -67,15 +69,15 @@ public class RectangleArranger<T> { /** * Column widths */ - private ArrayList<Double> widths = new ArrayList<Double>(); + private TDoubleArrayList widths = new TDoubleArrayList(); /** * Column heights */ - private ArrayList<Double> heights = new ArrayList<Double>(); + private TDoubleArrayList heights = new TDoubleArrayList(); /** - * Bit sets to store usage. ArrayList = y, BitSet = x + * Map indicating which cells are used. */ private ArrayList<ArrayList<Object>> usage = new ArrayList<ArrayList<Object>>(); @@ -120,7 +122,9 @@ public class RectangleArranger<T> { * @param data Data object to add (key) */ public void put(double w, double h, T data) { - logger.finest("Add: " + w + "x" + h); + if(LOG.isDebuggingFinest()) { + LOG.finest("Add: " + w + "x" + h); + } final int cols = widths.size(); final int rows = heights.size(); @@ -233,7 +237,9 @@ public class RectangleArranger<T> { double hinc = Math.max(0.0, h - avh); double inc = computeIncreaseArea(winc, hinc); - logger.debugFinest("Candidate: " + sx + "," + sy + " - " + ex + "," + ey + ": " + avw + "x" + avh + " " + inc); + if(LOG.isDebuggingFinest()) { + LOG.debugFinest("Candidate: " + sx + "," + sy + " - " + ex + "," + ey + ": " + avw + "x" + avh + " " + inc); + } if(inc < bestinc) { bestinc = inc; bestsx = sx; @@ -251,7 +257,9 @@ public class RectangleArranger<T> { } assert assertConsistent(); } - logger.debugFinest("Best: " + bestsx + "," + bestsy + " - " + bestex + "," + bestey + " inc: " + bestwi + "x" + besthi + " " + bestinc); + if(LOG.isDebuggingFinest()) { + LOG.debugFinest("Best: " + bestsx + "," + bestsy + " - " + bestex + "," + bestey + " inc: " + bestwi + "x" + besthi + " " + bestinc); + } // Need to increase the total area if(bestinc > 0) { assert (bestex == cols - 1 || bestey == rows - 1); @@ -290,7 +298,7 @@ public class RectangleArranger<T> { } } map.put(data, new double[] { xpos, ypos, w, h }); - if(logger.isDebuggingFinest()) { + if(LOG.isDebuggingFinest()) { logSizes(); } } @@ -302,25 +310,29 @@ public class RectangleArranger<T> { } protected void splitRow(int bestey, double besthi) { - assert(bestey < heights.size()); - if (heights.get(bestey) - besthi <= Double.MIN_NORMAL) { + assert (bestey < heights.size()); + if(heights.get(bestey) - besthi <= Double.MIN_NORMAL) { return; } - logger.debugFine("Split row " + bestey); - heights.add(bestey + 1, besthi); + if(LOG.isDebuggingFine()) { + LOG.debugFine("Split row " + bestey); + } + heights.insert(bestey + 1, besthi); heights.set(bestey, heights.get(bestey) - besthi); // Update used map usage.add(bestey + 1, new ArrayList<Object>(usage.get(bestey))); } protected void splitCol(int bestex, double bestwi) { - assert(bestex < widths.size()); - if (widths.get(bestex) - bestwi <= Double.MIN_NORMAL) { + assert (bestex < widths.size()); + if(widths.get(bestex) - bestwi <= Double.MIN_NORMAL) { return; } final int rows = heights.size(); - logger.debugFine("Split column " + bestex); - widths.add(bestex + 1, bestwi); + if(LOG.isDebuggingFine()) { + LOG.debugFine("Split column " + bestex); + } + widths.insert(bestex + 1, bestwi); widths.set(bestex, widths.get(bestex) - bestwi); // Update used map for(int y = 0; y < rows; y++) { @@ -332,9 +344,11 @@ public class RectangleArranger<T> { private void resize(double inc) { final int cols = widths.size(); final int rows = heights.size(); - logger.debugFine("Resize by " + inc + "x" + (inc / ratio)); - if(logger.isDebuggingFinest()) { - logSizes(); + if(LOG.isDebuggingFine()) { + LOG.debugFine("Resize by " + inc + "x" + (inc / ratio)); + if(LOG.isDebuggingFinest()) { + logSizes(); + } } // TODO: if the last row or column is empty, we can do this simpler widths.add(inc); @@ -354,7 +368,7 @@ public class RectangleArranger<T> { usage.add(row); } assert assertConsistent(); - if(logger.isDebuggingFinest()) { + if(LOG.isDebuggingFinest()) { logSizes(); } } @@ -379,7 +393,7 @@ public class RectangleArranger<T> { { double wsum = 0.0; for(int x = 0; x < cols; x++) { - assert (widths.get(x) > 0) : "Non-positive width: "+widths.get(x); + assert (widths.get(x) > 0) : "Non-positive width: " + widths.get(x); wsum += widths.get(x); } assert (Math.abs(wsum - twidth) < 1E-10); @@ -387,7 +401,7 @@ public class RectangleArranger<T> { { double hsum = 0.0; for(int y = 0; y < rows; y++) { - assert (heights.get(y) > 0) : "Non-positive height: "+heights.get(y); + assert (heights.get(y) > 0) : "Non-positive height: " + heights.get(y); hsum += heights.get(y); } assert (Math.abs(hsum - theight) < 1E-10); @@ -401,8 +415,11 @@ public class RectangleArranger<T> { return true; } - public void logSizes() { - StringBuffer buf = new StringBuffer(); + /** + * Debug logging + */ + protected void logSizes() { + StringBuilder buf = new StringBuilder(); final int cols = widths.size(); final int rows = heights.size(); { @@ -413,7 +430,7 @@ public class RectangleArranger<T> { } buf.append(widths.get(x)); } - buf.append("\n"); + buf.append('\n'); } { buf.append("Heights: "); @@ -423,7 +440,7 @@ public class RectangleArranger<T> { } buf.append(heights.get(y)); } - buf.append("\n"); + buf.append('\n'); } { for(int y = 0; y < rows; y++) { @@ -433,11 +450,33 @@ public class RectangleArranger<T> { buf.append("|\n"); } for(int x = 0; x < cols; x++) { - buf.append("-"); + buf.append('-'); } buf.append("+\n"); } - logger.debug(buf); + LOG.debug(buf); + } + + /** + * Compute the relative fill. Useful for triggering a relayout if the relative + * fill is not satisfactory. + * + * @return relative fill + */ + public double relativeFill() { + double acc = 0.0; + final int cols = widths.size(); + final int rows = heights.size(); + { + for(int y = 0; y < rows; y++) { + for(int x = 0; x < cols; x++) { + if(usage.get(y).get(x) != null) { + acc += widths.get(x) * heights.get(y); + } + } + } + } + return acc / (twidth * theight); } /** @@ -482,7 +521,7 @@ public class RectangleArranger<T> { * @param args */ public static void main(String[] args) { - logger.getWrappedLogger().setLevel(Level.FINEST); + LOG.getWrappedLogger().setLevel(Level.FINEST); RectangleArranger<String> r = new RectangleArranger<String>(1.3); r.put(4., 1., "Histogram"); r.put(4., 4., "3D view"); @@ -505,4 +544,4 @@ public class RectangleArranger<T> { r.put(4., 1., "C"); r.put(1., .1, "D"); } -} +}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/package-info.java b/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/package-info.java index f4690d0a..013f86ca 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/package-info.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/package-info.java @@ -1,6 +1,7 @@ /** * <p>Classes for managing the overview plot.</p> * + * @apiviz.exclude java.awt.event.* */ /* This file is part of ELKI: @@ -24,4 +25,4 @@ 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.gui.overview;
\ No newline at end of file +package de.lmu.ifi.dbs.elki.visualization.gui.overview; diff --git a/src/de/lmu/ifi/dbs/elki/visualization/opticsplot/OPTICSPlot.java b/src/de/lmu/ifi/dbs/elki/visualization/opticsplot/OPTICSPlot.java index e0aac5ca..b6b6fb31 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/opticsplot/OPTICSPlot.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/opticsplot/OPTICSPlot.java @@ -56,7 +56,7 @@ public class OPTICSPlot<D extends Distance<D>> implements Result { /** * Logger */ - protected static final Logging logger = Logging.getLogger(OPTICSPlot.class); + private static final Logging LOG = Logging.getLogger(OPTICSPlot.class); /** * Scale to use @@ -200,7 +200,7 @@ public class OPTICSPlot<D extends Distance<D>> implements Result { } } catch(ArrayIndexOutOfBoundsException e) { - logger.error("Plotting out of range: " + x + "," + y + " >= " + width + "x" + height); + LOG.error("Plotting out of range: " + x + "," + y + " >= " + width + "x" + height); } x++; } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/package-info.java b/src/de/lmu/ifi/dbs/elki/visualization/package-info.java index b331cc0d..f4e854e0 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/package-info.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/package-info.java @@ -1,6 +1,8 @@ /** * <p>Visualization package of ELKI.</p> * + * @apiviz.exclude elki.utilities + * @apiviz.exclude java.lang.* */ /* This file is part of ELKI: @@ -24,4 +26,4 @@ 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;
\ No newline at end of file +package de.lmu.ifi.dbs.elki.visualization; diff --git a/src/de/lmu/ifi/dbs/elki/visualization/projections/AbstractFullProjection.java b/src/de/lmu/ifi/dbs/elki/visualization/projections/AbstractFullProjection.java index 6b08f033..e8f7a91a 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/projections/AbstractFullProjection.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/projections/AbstractFullProjection.java @@ -52,12 +52,12 @@ public abstract class AbstractFullProjection extends AbstractProjection implemen * @return vector in scaled space */ @Override - public Vector projectDataToScaledSpace(NumberVector<?, ?> data) { + public Vector projectDataToScaledSpace(NumberVector<?> data) { final int dim = data.getDimensionality(); Vector vec = new Vector(dim); double[] ds = vec.getArrayRef(); for(int d = 0; d < dim; d++) { - ds[d] = scales[d].getScaled(data.doubleValue(d + 1)); + ds[d] = scales[d].getScaled(data.doubleValue(d)); } return vec; } @@ -86,12 +86,12 @@ public abstract class AbstractFullProjection extends AbstractProjection implemen * @return relative vector in scaled space */ @Override - public Vector projectRelativeDataToScaledSpace(NumberVector<?, ?> data) { + public Vector projectRelativeDataToScaledSpace(NumberVector<?> data) { final int dim = data.getDimensionality(); Vector vec = new Vector(dim); double[] ds = vec.getArrayRef(); for(int d = 0; d < dim; d++) { - ds[d] = scales[d].getRelativeScaled(data.doubleValue(d + 1)); + ds[d] = scales[d].getRelativeScaled(data.doubleValue(d)); } return vec; } @@ -120,7 +120,7 @@ public abstract class AbstractFullProjection extends AbstractProjection implemen * @return vector in rendering space */ @Override - public Vector projectDataToRenderSpace(NumberVector<?, ?> data) { + public Vector projectDataToRenderSpace(NumberVector<?> data) { return projectScaledToRender(projectDataToScaledSpace(data)); } @@ -142,7 +142,7 @@ public abstract class AbstractFullProjection extends AbstractProjection implemen * @return relative vector in rendering space */ @Override - public Vector projectRelativeDataToRenderSpace(NumberVector<?, ?> data) { + public Vector projectRelativeDataToRenderSpace(NumberVector<?> data) { return projectRelativeScaledToRender(projectRelativeDataToScaledSpace(data)); } @@ -166,7 +166,7 @@ public abstract class AbstractFullProjection extends AbstractProjection implemen * @return vector in data space */ @Override - public <NV extends NumberVector<NV, ?>> NV projectScaledToDataSpace(Vector v, NV factory) { + public <NV extends NumberVector<?>> NV projectScaledToDataSpace(Vector v, NumberVector.Factory<NV, ?> factory) { final int dim = v.getDimensionality(); Vector vec = v.copy(); double[] ds = vec.getArrayRef(); @@ -185,7 +185,7 @@ public abstract class AbstractFullProjection extends AbstractProjection implemen * @return vector in data space */ @Override - public <NV extends NumberVector<NV, ?>> NV projectRenderToDataSpace(Vector v, NV prototype) { + public <NV extends NumberVector<?>> NV projectRenderToDataSpace(Vector v, NumberVector.Factory<NV, ?> prototype) { final int dim = v.getDimensionality(); Vector vec = projectRenderToScaled(v); double[] ds = vec.getArrayRef(); @@ -206,7 +206,7 @@ public abstract class AbstractFullProjection extends AbstractProjection implemen * @return relative vector in data space */ @Override - public <NV extends NumberVector<NV, ?>> NV projectRelativeScaledToDataSpace(Vector v, NV prototype) { + public <NV extends NumberVector<?>> NV projectRelativeScaledToDataSpace(Vector v, NumberVector.Factory<NV, ?> prototype) { final int dim = v.getDimensionality(); Vector vec = v.copy(); double[] ds = vec.getArrayRef(); @@ -225,7 +225,7 @@ public abstract class AbstractFullProjection extends AbstractProjection implemen * @return relative vector in data space */ @Override - public <NV extends NumberVector<NV, ?>> NV projectRelativeRenderToDataSpace(Vector v, NV prototype) { + public <NV extends NumberVector<?>> NV projectRelativeRenderToDataSpace(Vector v, NumberVector.Factory<NV, ?> prototype) { final int dim = v.getDimensionality(); Vector vec = projectRelativeRenderToScaled(v); double[] ds = vec.getArrayRef(); diff --git a/src/de/lmu/ifi/dbs/elki/visualization/projections/AffineProjection.java b/src/de/lmu/ifi/dbs/elki/visualization/projections/AffineProjection.java index 10533ee0..0e184e32 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/projections/AffineProjection.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/projections/AffineProjection.java @@ -109,7 +109,7 @@ public class AffineProjection extends AbstractFullProjection implements Projecti @Override public CanvasSize estimateViewport() { - if(viewport == null) { + if (viewport == null) { final int dim = proj.getDimensionality(); DoubleMinMax minmaxx = new DoubleMinMax(); DoubleMinMax minmaxy = new DoubleMinMax(); @@ -121,14 +121,14 @@ public class AffineProjection extends AbstractFullProjection implements Projecti minmaxy.put(orig.get(1)); // Diagonal point Vector diag = new Vector(dim); - for(int d2 = 0; d2 < dim; d2++) { + for (int d2 = 0; d2 < dim; d2++) { diag.set(d2, 1); } diag = projectScaledToRender(diag); minmaxx.put(diag.get(0)); minmaxy.put(diag.get(1)); // Axis end points - for(int d = 0; d < dim; d++) { + for (int d = 0; d < dim; d++) { Vector v = new Vector(dim); v.set(d, 1); Vector ax = projectScaledToRender(v); @@ -154,7 +154,7 @@ public class AffineProjection extends AbstractFullProjection implements Projecti AffineTransformation proj = AffineTransformation.reorderAxesTransformation(dim, new int[] { ax1, ax2 }); // Assuming that the data was normalized on [0:1], center it: double[] trans = new double[dim]; - for(int i = 0; i < dim; i++) { + for (int i = 0; i < dim; i++) { trans[i] = -.5; } proj.addTranslation(new Vector(trans)); @@ -173,7 +173,7 @@ public class AffineProjection extends AbstractFullProjection implements Projecti } @Override - public double[] fastProjectDataToRenderSpace(NumberVector<?, ?> data) { + public double[] fastProjectDataToRenderSpace(NumberVector<?> data) { return fastProjectScaledToRenderSpace(fastProjectDataToScaledSpace(data)); } @@ -184,7 +184,7 @@ public class AffineProjection extends AbstractFullProjection implements Projecti } @Override - public double[] fastProjectDataToScaledSpace(NumberVector<?, ?> data) { + public double[] fastProjectDataToScaledSpace(NumberVector<?> data) { // FIXME: implement with less objects? return projectDataToScaledSpace(data).getArrayRef(); } @@ -201,7 +201,7 @@ public class AffineProjection extends AbstractFullProjection implements Projecti final double[] cols = matrix[vr.length]; assert (colx.length == coly.length && colx.length == cols.length && cols.length == vr.length + 1); - for(int k = 0; k < vr.length; k++) { + for (int k = 0; k < vr.length; k++) { x += colx[k] * vr[k]; y += coly[k] * vr[k]; s += cols[k] * vr[k]; @@ -210,7 +210,7 @@ public class AffineProjection extends AbstractFullProjection implements Projecti x += colx[vr.length]; y += coly[vr.length]; s += cols[vr.length]; - assert (s != 0.0); + assert (s > 0.0 || s < 0.0); return new double[] { x / s, y / s }; } @@ -221,7 +221,7 @@ public class AffineProjection extends AbstractFullProjection implements Projecti } @Override - public double[] fastProjectRelativeDataToRenderSpace(NumberVector<?, ?> data) { + public double[] fastProjectRelativeDataToRenderSpace(NumberVector<?> data) { // FIXME: implement with less objects? return fastProjectRelativeScaledToRenderSpace(projectRelativeDataToScaledSpace(data).getArrayRef()); } @@ -236,7 +236,7 @@ public class AffineProjection extends AbstractFullProjection implements Projecti final double[] coly = matrix[1]; assert (colx.length == coly.length); - for(int k = 0; k < vr.length; k++) { + for (int k = 0; k < vr.length; k++) { x += colx[k] * vr[k]; y += coly[k] * vr[k]; } @@ -246,7 +246,7 @@ public class AffineProjection extends AbstractFullProjection implements Projecti @Override public double[] fastProjectRenderToDataSpace(double[] data) { double[] ret = fastProjectRenderToScaledSpace(data); - for(int d = 0; d < scales.length; d++) { + for (int d = 0; d < scales.length; d++) { ret[d] = scales[d].getUnscaled(ret[d]); } return ret; @@ -254,11 +254,11 @@ public class AffineProjection extends AbstractFullProjection implements Projecti @Override public double[] fastProjectRenderToScaledSpace(double[] v) { - if(v.length == scales.length) { + if (v.length == scales.length) { return projectRenderToScaled(new Vector(v)).getArrayRef(); } double[] c = Arrays.copyOf(v, scales.length); - for(int d = v.length; d < scales.length; d++) { + for (int d = v.length; d < scales.length; d++) { c[d] = 0.5; } return projectRenderToScaled(new Vector(c)).getArrayRef(); @@ -269,16 +269,16 @@ public class AffineProjection extends AbstractFullProjection implements Projecti final int dim = proj.getDimensionality(); BitSet actDim = new BitSet(dim); Vector vScale = new Vector(dim); - for(int d = 0; d < dim; d++) { + for (int d = 0; d < dim; d++) { vScale.setZero(); vScale.set(d, 1); double[] vRender = fastProjectScaledToRenderSpace(vScale.getArrayRef()); // TODO: Can't we do this by inspecting the projection matrix directly? - if(vRender[0] != 0.0 || vRender[1] != 0) { + if (vRender[0] > 0.0 || vRender[0] < 0.0 || vRender[1] != 0) { actDim.set(d); } } return actDim; } -}
\ No newline at end of file +} diff --git a/src/de/lmu/ifi/dbs/elki/visualization/projections/FullProjection.java b/src/de/lmu/ifi/dbs/elki/visualization/projections/FullProjection.java index cc1433aa..02705355 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/projections/FullProjection.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/projections/FullProjection.java @@ -24,7 +24,6 @@ package de.lmu.ifi.dbs.elki.visualization.projections; */ import de.lmu.ifi.dbs.elki.data.NumberVector; import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector; -import de.lmu.ifi.dbs.elki.visualization.projections.Projection; /** * Full vector space projections. @@ -44,7 +43,7 @@ public interface FullProjection extends Projection { * @param v vector in scaled space * @return vector in rendering space */ - public Vector projectScaledToRender(Vector v); + Vector projectScaledToRender(Vector v); /** * Project a vector from rendering space to scaled space. @@ -52,7 +51,7 @@ public interface FullProjection extends Projection { * @param v vector in rendering space * @return vector in scaled space */ - public Vector projectRenderToScaled(Vector v); + Vector projectRenderToScaled(Vector v); /** * Project a relative vector from scaled space to rendering space. @@ -60,7 +59,7 @@ public interface FullProjection extends Projection { * @param v relative vector in scaled space * @return relative vector in rendering space */ - public Vector projectRelativeScaledToRender(Vector v); + Vector projectRelativeScaledToRender(Vector v); /** * Project a relative vector from rendering space to scaled space. @@ -68,7 +67,7 @@ public interface FullProjection extends Projection { * @param v relative vector in rendering space * @return relative vector in scaled space */ - public Vector projectRelativeRenderToScaled(Vector v); + Vector projectRelativeRenderToScaled(Vector v); /** * Project a data vector from data space to scaled space. @@ -76,7 +75,7 @@ public interface FullProjection extends Projection { * @param data vector in data space * @return vector in scaled space */ - public Vector projectDataToScaledSpace(NumberVector<?, ?> data); + Vector projectDataToScaledSpace(NumberVector<?> data); /** * Project a data vector from data space to scaled space. @@ -84,7 +83,7 @@ public interface FullProjection extends Projection { * @param data vector in data space * @return vector in scaled space */ - public Vector projectDataToScaledSpace(Vector data); + Vector projectDataToScaledSpace(Vector data); /** * Project a relative data vector from data space to scaled space. @@ -92,7 +91,7 @@ public interface FullProjection extends Projection { * @param data relative vector in data space * @return relative vector in scaled space */ - public Vector projectRelativeDataToScaledSpace(NumberVector<?, ?> data); + Vector projectRelativeDataToScaledSpace(NumberVector<?> data); /** * Project a relative data vector from data space to scaled space. @@ -100,7 +99,7 @@ public interface FullProjection extends Projection { * @param data relative vector in data space * @return relative vector in scaled space */ - public Vector projectRelativeDataToScaledSpace(Vector data); + Vector projectRelativeDataToScaledSpace(Vector data); /** * Project a data vector from data space to rendering space. @@ -108,7 +107,7 @@ public interface FullProjection extends Projection { * @param data vector in data space * @return vector in rendering space */ - public Vector projectDataToRenderSpace(NumberVector<?, ?> data); + Vector projectDataToRenderSpace(NumberVector<?> data); /** * Project a data vector from data space to rendering space. @@ -116,7 +115,7 @@ public interface FullProjection extends Projection { * @param data vector in data space * @return vector in rendering space */ - public Vector projectDataToRenderSpace(Vector data); + Vector projectDataToRenderSpace(Vector data); /** * Project a vector from scaled space to data space. @@ -126,7 +125,7 @@ public interface FullProjection extends Projection { * @param factory Object factory * @return vector in data space */ - public <NV extends NumberVector<NV, ?>> NV projectScaledToDataSpace(Vector v, NV factory); + <NV extends NumberVector<?>> NV projectScaledToDataSpace(Vector v, NumberVector.Factory<NV, ?> factory); /** * Project a vector from rendering space to data space. @@ -136,7 +135,7 @@ public interface FullProjection extends Projection { * @param prototype Object factory * @return vector in data space */ - public <NV extends NumberVector<NV, ?>> NV projectRenderToDataSpace(Vector v, NV prototype); + <NV extends NumberVector<?>> NV projectRenderToDataSpace(Vector v, NumberVector.Factory<NV, ?> prototype); /** * Project a relative data vector from data space to rendering space. @@ -144,7 +143,7 @@ public interface FullProjection extends Projection { * @param data relative vector in data space * @return relative vector in rendering space */ - public Vector projectRelativeDataToRenderSpace(NumberVector<?, ?> data); + Vector projectRelativeDataToRenderSpace(NumberVector<?> data); /** * Project a relative data vector from data space to rendering space. @@ -152,7 +151,7 @@ public interface FullProjection extends Projection { * @param data relative vector in data space * @return relative vector in rendering space */ - public Vector projectRelativeDataToRenderSpace(Vector data); + Vector projectRelativeDataToRenderSpace(Vector data); /** * Project a relative vector from scaled space to data space. @@ -162,7 +161,7 @@ public interface FullProjection extends Projection { * @param prototype Object factory * @return relative vector in data space */ - public <NV extends NumberVector<NV, ?>> NV projectRelativeScaledToDataSpace(Vector v, NV prototype); + <NV extends NumberVector<?>> NV projectRelativeScaledToDataSpace(Vector v, NumberVector.Factory<NV, ?> prototype); /** * Project a relative vector from rendering space to data space. @@ -172,5 +171,5 @@ public interface FullProjection extends Projection { * @param prototype Object factory * @return relative vector in data space */ - public <NV extends NumberVector<NV, ?>> NV projectRelativeRenderToDataSpace(Vector v, NV prototype); + <NV extends NumberVector<?>> NV projectRelativeRenderToDataSpace(Vector v, NumberVector.Factory<NV, ?> prototype); } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/projections/OPTICSProjection.java b/src/de/lmu/ifi/dbs/elki/visualization/projections/OPTICSProjection.java new file mode 100644 index 00000000..9a6dd9b8 --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/projections/OPTICSProjection.java @@ -0,0 +1,94 @@ +package de.lmu.ifi.dbs.elki.visualization.projections; + +/* + This file is part of ELKI: + Environment for Developing KDD-Applications Supported by Index-Structures + + Copyright (C) 2012 + Ludwig-Maximilians-Universität München + Lehr- und Forschungseinheit für Datenbanksysteme + 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/>. + */ + +import de.lmu.ifi.dbs.elki.distance.distancevalue.Distance; +import de.lmu.ifi.dbs.elki.math.scales.LinearScale; +import de.lmu.ifi.dbs.elki.result.AbstractHierarchicalResult; +import de.lmu.ifi.dbs.elki.result.optics.ClusterOrderResult; +import de.lmu.ifi.dbs.elki.visualization.VisualizerContext; +import de.lmu.ifi.dbs.elki.visualization.opticsplot.OPTICSPlot; +import de.lmu.ifi.dbs.elki.visualization.projector.OPTICSProjector; + +/** + * OPTICS projection. This is not really needed, but a quick hack to have more + * consistency in the visualizer API. + * + * @author Erich Schubert + */ +public class OPTICSProjection<D extends Distance<D>> extends AbstractHierarchicalResult implements Projection { + /** + * The projector we were generated from. + */ + OPTICSProjector<D> projector; + + /** + * Constructor. + * + * @param opticsProjector OPTICS projector + */ + public OPTICSProjection(OPTICSProjector<D> opticsProjector) { + super(); + this.projector = opticsProjector; + } + + @Override + public String getLongName() { + return "OPTICS projection"; + } + + @Override + public String getShortName() { + return "OPTICSproj"; + } + + @Override + public int getInputDimensionality() { + return -1; + } + + @Override + public LinearScale getScale(int d) { + return null; + } + + /** + * Get or produce the actual OPTICS plot. + * + * @param context Context to use + * @return Plot + */ + public OPTICSPlot<D> getOPTICSPlot(VisualizerContext context) { + return projector.getOPTICSPlot(context); + } + + /** + * Get the OPTICS cluster order. + * + * @return Cluster oder result. + */ + public ClusterOrderResult<D> getResult() { + return projector.getResult(); + } +} diff --git a/src/de/lmu/ifi/dbs/elki/visualization/projections/Projection1D.java b/src/de/lmu/ifi/dbs/elki/visualization/projections/Projection1D.java index 2232e0b9..82c9483d 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/projections/Projection1D.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/projections/Projection1D.java @@ -48,7 +48,7 @@ public interface Projection1D extends Projection { * @param data vector in data space * @return vector in rendering space */ - public double fastProjectDataToRenderSpace(NumberVector<?, ?> data); + public double fastProjectDataToRenderSpace(NumberVector<?> data); /** * Project a vector from scaled space to rendering space. @@ -72,7 +72,7 @@ public interface Projection1D extends Projection { * @param data vector in data space * @return vector in rendering space */ - public double fastProjectRelativeDataToRenderSpace(NumberVector<?, ?> data); + public double fastProjectRelativeDataToRenderSpace(NumberVector<?> data); /** * Project a vector from scaled space to rendering space. diff --git a/src/de/lmu/ifi/dbs/elki/visualization/projections/Projection2D.java b/src/de/lmu/ifi/dbs/elki/visualization/projections/Projection2D.java index 66518c8f..acd4a829 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/projections/Projection2D.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/projections/Projection2D.java @@ -34,6 +34,8 @@ import de.lmu.ifi.dbs.elki.data.NumberVector; * @author Erich Schubert * * @apiviz.landmark + * + * @apiviz.has CanvasSize */ public interface Projection2D extends Projection { /** @@ -50,7 +52,7 @@ public interface Projection2D extends Projection { * @param data vector in data space * @return vector in rendering space */ - public double[] fastProjectDataToRenderSpace(NumberVector<?, ?> data); + public double[] fastProjectDataToRenderSpace(NumberVector<?> data); /** * Project a data vector from data space to scaled space. @@ -66,7 +68,7 @@ public interface Projection2D extends Projection { * @param data vector in data space * @return vector in scaled space */ - public double[] fastProjectDataToScaledSpace(NumberVector<?, ?> data); + public double[] fastProjectDataToScaledSpace(NumberVector<?> data); /** * Project a vector from scaled space to rendering space. @@ -91,7 +93,7 @@ public interface Projection2D extends Projection { * @param prototype Prototype to create vector from * @return vector in data space */ - // public <V extends NumberVector<V, ?>> V fastProjectRenderToDataSpace(double[] data, V prototype); + // public <V extends NumberVector<?>> V fastProjectRenderToDataSpace(double[] data, V prototype); /** * Project a vector from rendering space to scaled space. @@ -115,7 +117,7 @@ public interface Projection2D extends Projection { * @param data vector in data space * @return vector in rendering space */ - public double[] fastProjectRelativeDataToRenderSpace(NumberVector<?, ?> data); + public double[] fastProjectRelativeDataToRenderSpace(NumberVector<?> data); /** * Project a vector from scaled space to rendering space. @@ -140,4 +142,4 @@ public interface Projection2D extends Projection { * @return Bit set, first dimension is bit 0. */ public BitSet getVisibleDimensions2D(); -}
\ No newline at end of file +} diff --git a/src/de/lmu/ifi/dbs/elki/visualization/projections/ProjectionParallel.java b/src/de/lmu/ifi/dbs/elki/visualization/projections/ProjectionParallel.java index 03e8a245..98c2ac33 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/projections/ProjectionParallel.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/projections/ProjectionParallel.java @@ -166,7 +166,7 @@ public interface ProjectionParallel extends Projection { * @param v Input vector
* @return Vector with reordering, inversions and scales applied.
*/
- public double[] fastProjectDataToRenderSpace(NumberVector<?, ?> v);
+ public double[] fastProjectDataToRenderSpace(NumberVector<?> v);
/**
* Project the value of a single axis to its display value
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/projections/Simple1D.java b/src/de/lmu/ifi/dbs/elki/visualization/projections/Simple1D.java index b701c934..ef97fe15 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/projections/Simple1D.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/projections/Simple1D.java @@ -42,11 +42,11 @@ public class Simple1D extends AbstractSimpleProjection implements Projection1D { * Simple 1D projection using scaling only. * * @param scales Scales to use - * @param dnum Dimension (starting at 1) + * @param dnum Dimension (starting at 0) */ public Simple1D(LinearScale[] scales, int dnum) { super(scales); - this.dnum = dnum - 1; + this.dnum = dnum; } @Override @@ -55,8 +55,8 @@ public class Simple1D extends AbstractSimpleProjection implements Projection1D { } @Override - public double fastProjectDataToRenderSpace(NumberVector<?, ?> data) { - return (scales[dnum].getScaled(data.doubleValue(dnum + 1)) - 0.5) * SCALE; + public double fastProjectDataToRenderSpace(NumberVector<?> data) { + return (scales[dnum].getScaled(data.doubleValue(dnum)) - 0.5) * SCALE; } @Override @@ -70,7 +70,7 @@ public class Simple1D extends AbstractSimpleProjection implements Projection1D { } @Override - public double fastProjectRelativeDataToRenderSpace(NumberVector<?, ?> data) { + public double fastProjectRelativeDataToRenderSpace(NumberVector<?> data) { return (data.doubleValue(dnum) - 0.5) * SCALE; } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/projections/Simple2D.java b/src/de/lmu/ifi/dbs/elki/visualization/projections/Simple2D.java index 1964f12b..81821a7c 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/projections/Simple2D.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/projections/Simple2D.java @@ -54,8 +54,8 @@ public class Simple2D extends AbstractSimpleProjection implements Projection2D { */ public Simple2D(LinearScale[] scales, int ax1, int ax2) { super(scales); - this.dim1 = ax1 - 1; - this.dim2 = ax2 - 1; + this.dim1 = ax1; + this.dim2 = ax2; } @Override @@ -66,9 +66,9 @@ public class Simple2D extends AbstractSimpleProjection implements Projection2D { } @Override - public double[] fastProjectDataToRenderSpace(NumberVector<?, ?> data) { - double x = (scales[dim1].getScaled(data.doubleValue(dim1 + 1)) - 0.5) * SCALE; - double y = (scales[dim2].getScaled(data.doubleValue(dim2 + 1)) - 0.5) * -SCALE; + public double[] fastProjectDataToRenderSpace(NumberVector<?> data) { + double x = (scales[dim1].getScaled(data.doubleValue(dim1)) - 0.5) * SCALE; + double y = (scales[dim2].getScaled(data.doubleValue(dim2)) - 0.5) * -SCALE; return new double[] { x, y }; } @@ -83,11 +83,11 @@ public class Simple2D extends AbstractSimpleProjection implements Projection2D { } @Override - public double[] fastProjectDataToScaledSpace(NumberVector<?, ?> data) { + public double[] fastProjectDataToScaledSpace(NumberVector<?> data) { final int dim = data.getDimensionality(); double[] ds = new double[dim]; for(int d = 0; d < dim; d++) { - ds[d] = scales[d].getScaled(data.doubleValue(d + 1)); + ds[d] = scales[d].getScaled(data.doubleValue(d)); } return ds; } @@ -141,9 +141,9 @@ public class Simple2D extends AbstractSimpleProjection implements Projection2D { } @Override - public double[] fastProjectRelativeDataToRenderSpace(NumberVector<?, ?> data) { - double x = scales[dim1].getRelativeScaled(data.doubleValue(dim1 + 1)) * SCALE; - double y = scales[dim2].getRelativeScaled(data.doubleValue(dim2 + 1)) * -SCALE; + public double[] fastProjectRelativeDataToRenderSpace(NumberVector<?> data) { + double x = scales[dim1].getRelativeScaled(data.doubleValue(dim1)) * SCALE; + double y = scales[dim2].getRelativeScaled(data.doubleValue(dim2)) * -SCALE; return new double[] { x, y }; } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/projections/SimpleParallel.java b/src/de/lmu/ifi/dbs/elki/visualization/projections/SimpleParallel.java index c843d61d..6b07895d 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/projections/SimpleParallel.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/projections/SimpleParallel.java @@ -182,12 +182,13 @@ public class SimpleParallel extends BasicResult implements ProjectionParallel { @Override
public int getDimForVisibleAxis(int pos) {
for(int i = 0; i < scales.length; i++) {
- if (isAxisVisible(i)) {
- if (pos == 0) {
- return dimOrder[i];
- }
- pos--;
+ if (isDimHidden(dimOrder[i])) {
+ continue;
+ }
+ if (pos == 0) {
+ return dimOrder[i];
}
+ pos--;
}
return -1;
}
@@ -214,14 +215,14 @@ public class SimpleParallel extends BasicResult implements ProjectionParallel { }
@Override
- public double[] fastProjectDataToRenderSpace(NumberVector<?, ?> data) {
+ public double[] fastProjectDataToRenderSpace(NumberVector<?> data) {
double[] v = new double[visDims];
for(int j = 0, o = 0; j < scales.length; j++) {
if(isDimHidden(j)) {
continue;
}
int i = dimOrder[j];
- v[o] = scales[i].getScaled(data.doubleValue(i + 1));
+ v[o] = scales[i].getScaled(data.doubleValue(i));
if(!isDimInverted(i)) {
v[o] = 1 - v[o];
}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/projector/HistogramFactory.java b/src/de/lmu/ifi/dbs/elki/visualization/projector/HistogramFactory.java index 3f487e0e..24defc7b 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/projector/HistogramFactory.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/projector/HistogramFactory.java @@ -28,10 +28,10 @@ import java.util.ArrayList; import de.lmu.ifi.dbs.elki.data.NumberVector; import de.lmu.ifi.dbs.elki.data.type.TypeUtil; import de.lmu.ifi.dbs.elki.database.relation.Relation; +import de.lmu.ifi.dbs.elki.database.relation.RelationUtil; import de.lmu.ifi.dbs.elki.result.HierarchicalResult; import de.lmu.ifi.dbs.elki.result.Result; import de.lmu.ifi.dbs.elki.result.ResultUtil; -import de.lmu.ifi.dbs.elki.utilities.DatabaseUtil; import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer; import de.lmu.ifi.dbs.elki.utilities.optionhandling.constraints.GreaterEqualConstraint; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization; @@ -46,7 +46,7 @@ import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.IntParameter; */ public class HistogramFactory implements ProjectorFactory { /** - * Maximum dimensionality + * Maximum dimensionality. */ private int maxdim = ScatterPlotFactory.MAX_DIMENSIONS_DEFAULT; @@ -66,9 +66,9 @@ public class HistogramFactory implements ProjectorFactory { for(Relation<?> rel : rels) { if(TypeUtil.NUMBER_VECTOR_FIELD.isAssignableFromType(rel.getDataTypeInformation())) { @SuppressWarnings("unchecked") - Relation<NumberVector<?, ?>> vrel = (Relation<NumberVector<?, ?>>) rel; - final int dim = DatabaseUtil.dimensionality(vrel); - HistogramProjector<NumberVector<?, ?>> proj = new HistogramProjector<NumberVector<?, ?>>(vrel, Math.min(dim, maxdim)); + Relation<NumberVector<?>> vrel = (Relation<NumberVector<?>>) rel; + final int dim = RelationUtil.dimensionality(vrel); + HistogramProjector<NumberVector<?>> proj = new HistogramProjector<NumberVector<?>>(vrel, Math.min(dim, maxdim)); baseResult.getHierarchy().add(vrel, proj); } } @@ -90,9 +90,10 @@ public class HistogramFactory implements ProjectorFactory { @Override protected void makeOptions(Parameterization config) { super.makeOptions(config); - IntParameter maxdimP = new IntParameter(ScatterPlotFactory.Parameterizer.MAXDIM_ID, new GreaterEqualConstraint(1), ScatterPlotFactory.MAX_DIMENSIONS_DEFAULT); + IntParameter maxdimP = new IntParameter(ScatterPlotFactory.Parameterizer.MAXDIM_ID, ScatterPlotFactory.MAX_DIMENSIONS_DEFAULT); + maxdimP.addConstraint(new GreaterEqualConstraint(1)); if(config.grab(maxdimP)) { - maxdim = maxdimP.getValue(); + maxdim = maxdimP.intValue(); } } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/projector/HistogramProjector.java b/src/de/lmu/ifi/dbs/elki/visualization/projector/HistogramProjector.java index 030ea954..9153476b 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/projector/HistogramProjector.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/projector/HistogramProjector.java @@ -29,15 +29,15 @@ import java.util.List; import de.lmu.ifi.dbs.elki.data.NumberVector; import de.lmu.ifi.dbs.elki.database.relation.Relation; +import de.lmu.ifi.dbs.elki.database.relation.RelationUtil; import de.lmu.ifi.dbs.elki.result.AbstractHierarchicalResult; import de.lmu.ifi.dbs.elki.result.ResultUtil; import de.lmu.ifi.dbs.elki.result.ScalesResult; -import de.lmu.ifi.dbs.elki.utilities.DatabaseUtil; import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; import de.lmu.ifi.dbs.elki.visualization.gui.overview.PlotItem; import de.lmu.ifi.dbs.elki.visualization.projections.Projection1D; import de.lmu.ifi.dbs.elki.visualization.projections.Simple1D; -import de.lmu.ifi.dbs.elki.visualization.visualizers.visunproj.LabelVisFactory; +import de.lmu.ifi.dbs.elki.visualization.visualizers.visunproj.LabelVisualization; /** * ScatterPlotProjector is responsible for producing a set of scatterplot @@ -50,14 +50,14 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.visunproj.LabelVisFactory; * * @param <V> Vector type */ -public class HistogramProjector<V extends NumberVector<?, ?>> extends AbstractHierarchicalResult implements Projector { +public class HistogramProjector<V extends NumberVector<?>> extends AbstractHierarchicalResult implements Projector { /** - * Relation we project + * Relation we project. */ Relation<V> rel; /** - * Database dimensionality + * Database dimensionality. */ int dmax; @@ -71,7 +71,7 @@ public class HistogramProjector<V extends NumberVector<?, ?>> extends AbstractHi super(); this.rel = rel; this.dmax = maxdim; - assert (maxdim <= DatabaseUtil.dimensionality(rel)) : "Requested dimensionality larger than data dimensionality?!?"; + assert (maxdim <= RelationUtil.dimensionality(rel)) : "Requested dimensionality larger than data dimensionality?!?"; } @Override @@ -85,7 +85,7 @@ public class HistogramProjector<V extends NumberVector<?, ?>> extends AbstractHi PlotItem master = new PlotItem(dmax + xoff, hheight + lheight, null); ScalesResult scales = ResultUtil.getScalesResult(rel); for(int d1 = 0; d1 < dmax; d1++) { - Projection1D proj = new Simple1D(scales.getScales(), d1 + 1); + Projection1D proj = new Simple1D(scales.getScales(), d1); final PlotItem it = new PlotItem(d1 + xoff, lheight, 1., hheight, proj); it.tasks = tasks; master.subitems.add(it); @@ -94,11 +94,11 @@ public class HistogramProjector<V extends NumberVector<?, ?>> extends AbstractHi // Add labels for(int d1 = 0; d1 < dmax; d1++) { PlotItem it = new PlotItem(d1 + xoff, 0, 1., lheight, null); - LabelVisFactory lbl = new LabelVisFactory(DatabaseUtil.getColumnLabel(rel, d1 + 1)); + LabelVisualization lbl = new LabelVisualization(RelationUtil.getColumnLabel(rel, d1)); final VisualizationTask task = new VisualizationTask("", null, null, lbl); task.height = lheight; task.width = 1; - task.put(VisualizationTask.META_NODETAIL, true); + task.nodetail = true; it.tasks.add(task); master.subitems.add(it); } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/projector/OPTICSProjector.java b/src/de/lmu/ifi/dbs/elki/visualization/projector/OPTICSProjector.java index 297b7b12..5aa43cd9 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/projector/OPTICSProjector.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/projector/OPTICSProjector.java @@ -35,6 +35,7 @@ import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; import de.lmu.ifi.dbs.elki.visualization.VisualizerContext; import de.lmu.ifi.dbs.elki.visualization.gui.overview.PlotItem; import de.lmu.ifi.dbs.elki.visualization.opticsplot.OPTICSPlot; +import de.lmu.ifi.dbs.elki.visualization.projections.OPTICSProjection; /** * Projection for OPTICS plots. @@ -77,7 +78,7 @@ public class OPTICSProjector<D extends Distance<D>> extends AbstractHierarchical List<PlotItem> col = new ArrayList<PlotItem>(1); List<VisualizationTask> tasks = ResultUtil.filterResults(this, VisualizationTask.class); if (tasks.size() > 0) { - final PlotItem it = new PlotItem(4., 1., null); + final PlotItem it = new PlotItem(4., 1., new OPTICSProjection<D>(this)); it.tasks = tasks; col.add(it); } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/projector/ParallelPlotFactory.java b/src/de/lmu/ifi/dbs/elki/visualization/projector/ParallelPlotFactory.java index 8b110967..23ba6d3f 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/projector/ParallelPlotFactory.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/projector/ParallelPlotFactory.java @@ -54,8 +54,8 @@ public class ParallelPlotFactory implements ProjectorFactory { // TODO: multi-relational parallel plots
if(TypeUtil.NUMBER_VECTOR_FIELD.isAssignableFromType(rel.getDataTypeInformation())) {
@SuppressWarnings("unchecked")
- Relation<NumberVector<?, ?>> vrel = (Relation<NumberVector<?, ?>>) rel;
- ParallelPlotProjector<NumberVector<?, ?>> proj = new ParallelPlotProjector<NumberVector<?, ?>>(vrel);
+ Relation<NumberVector<?>> vrel = (Relation<NumberVector<?>>) rel;
+ ParallelPlotProjector<NumberVector<?>> proj = new ParallelPlotProjector<NumberVector<?>>(vrel);
baseResult.getHierarchy().add(vrel, proj);
}
}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/projector/ParallelPlotProjector.java b/src/de/lmu/ifi/dbs/elki/visualization/projector/ParallelPlotProjector.java index 2076655b..3e172ff9 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/projector/ParallelPlotProjector.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/projector/ParallelPlotProjector.java @@ -47,9 +47,9 @@ import de.lmu.ifi.dbs.elki.visualization.projections.SimpleParallel; * @param <V> Vector type
*/
// TODO: support categorical features, and multiple relations too
-public class ParallelPlotProjector<V extends NumberVector<?, ?>> extends AbstractHierarchicalResult implements Projector {
+public class ParallelPlotProjector<V extends NumberVector<?>> extends AbstractHierarchicalResult implements Projector {
/**
- * Relation we project
+ * Relation we project.
*/
Relation<V> rel;
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/projector/ScatterPlotFactory.java b/src/de/lmu/ifi/dbs/elki/visualization/projector/ScatterPlotFactory.java index 6e1a899b..0367d54b 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/projector/ScatterPlotFactory.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/projector/ScatterPlotFactory.java @@ -28,10 +28,10 @@ import java.util.ArrayList; import de.lmu.ifi.dbs.elki.data.NumberVector; import de.lmu.ifi.dbs.elki.data.type.TypeUtil; import de.lmu.ifi.dbs.elki.database.relation.Relation; +import de.lmu.ifi.dbs.elki.database.relation.RelationUtil; import de.lmu.ifi.dbs.elki.result.HierarchicalResult; import de.lmu.ifi.dbs.elki.result.Result; import de.lmu.ifi.dbs.elki.result.ResultUtil; -import de.lmu.ifi.dbs.elki.utilities.DatabaseUtil; import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer; import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID; import de.lmu.ifi.dbs.elki.utilities.optionhandling.constraints.GreaterEqualConstraint; @@ -74,9 +74,9 @@ public class ScatterPlotFactory implements ProjectorFactory { for(Relation<?> rel : rels) { if(TypeUtil.NUMBER_VECTOR_FIELD.isAssignableFromType(rel.getDataTypeInformation())) { @SuppressWarnings("unchecked") - Relation<NumberVector<?, ?>> vrel = (Relation<NumberVector<?, ?>>) rel; - final int dim = DatabaseUtil.dimensionality(vrel); - ScatterPlotProjector<NumberVector<?, ?>> proj = new ScatterPlotProjector<NumberVector<?, ?>>(vrel, Math.min(maxdim, dim)); + Relation<NumberVector<?>> vrel = (Relation<NumberVector<?>>) rel; + final int dim = RelationUtil.dimensionality(vrel); + ScatterPlotProjector<NumberVector<?>> proj = new ScatterPlotProjector<NumberVector<?>>(vrel, Math.min(maxdim, dim)); baseResult.getHierarchy().add(vrel, proj); } } @@ -91,13 +91,13 @@ public class ScatterPlotFactory implements ProjectorFactory { */ public static class Parameterizer extends AbstractParameterizer { /** - * Parameter for the maximum number of dimensions, + * Parameter for the maximum number of dimensions. * * <p> * Code: -vis.maxdim * </p> */ - public static final OptionID MAXDIM_ID = OptionID.getOrCreateOptionID("vis.maxdim", "Maximum number of dimensions to display."); + public static final OptionID MAXDIM_ID = new OptionID("vis.maxdim", "Maximum number of dimensions to display."); /** * Stores the maximum number of dimensions to show. @@ -107,9 +107,10 @@ public class ScatterPlotFactory implements ProjectorFactory { @Override protected void makeOptions(Parameterization config) { super.makeOptions(config); - IntParameter maxdimP = new IntParameter(MAXDIM_ID, new GreaterEqualConstraint(1), MAX_DIMENSIONS_DEFAULT); + IntParameter maxdimP = new IntParameter(MAXDIM_ID, MAX_DIMENSIONS_DEFAULT); + maxdimP.addConstraint(new GreaterEqualConstraint(1)); if(config.grab(maxdimP)) { - maxdim = maxdimP.getValue(); + maxdim = maxdimP.intValue(); } } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/projector/ScatterPlotProjector.java b/src/de/lmu/ifi/dbs/elki/visualization/projector/ScatterPlotProjector.java index f251733e..e97ad653 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/projector/ScatterPlotProjector.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/projector/ScatterPlotProjector.java @@ -29,17 +29,17 @@ import java.util.List; import de.lmu.ifi.dbs.elki.data.NumberVector; import de.lmu.ifi.dbs.elki.database.relation.Relation; +import de.lmu.ifi.dbs.elki.database.relation.RelationUtil; import de.lmu.ifi.dbs.elki.math.linearalgebra.AffineTransformation; import de.lmu.ifi.dbs.elki.result.AbstractHierarchicalResult; import de.lmu.ifi.dbs.elki.result.ResultUtil; import de.lmu.ifi.dbs.elki.result.ScalesResult; -import de.lmu.ifi.dbs.elki.utilities.DatabaseUtil; import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; import de.lmu.ifi.dbs.elki.visualization.gui.overview.PlotItem; import de.lmu.ifi.dbs.elki.visualization.projections.AffineProjection; import de.lmu.ifi.dbs.elki.visualization.projections.Projection2D; import de.lmu.ifi.dbs.elki.visualization.projections.Simple2D; -import de.lmu.ifi.dbs.elki.visualization.visualizers.visunproj.LabelVisFactory; +import de.lmu.ifi.dbs.elki.visualization.visualizers.visunproj.LabelVisualization; /** * ScatterPlotProjector is responsible for producing a set of scatterplot @@ -52,14 +52,14 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.visunproj.LabelVisFactory; * * @param <V> Vector type */ -public class ScatterPlotProjector<V extends NumberVector<?, ?>> extends AbstractHierarchicalResult implements Projector { +public class ScatterPlotProjector<V extends NumberVector<?>> extends AbstractHierarchicalResult implements Projector { /** - * Relation we project + * Relation we project. */ Relation<V> rel; /** - * Database dimensionality + * Database dimensionality. */ int dmax; @@ -73,7 +73,7 @@ public class ScatterPlotProjector<V extends NumberVector<?, ?>> extends Abstract super(); this.rel = rel; this.dmax = maxdim; - assert (maxdim <= DatabaseUtil.dimensionality(rel)) : "Requested dimensionality larger than data dimensionality?!?"; + assert (maxdim <= RelationUtil.dimensionality(rel)) : "Requested dimensionality larger than data dimensionality?!?"; } @Override @@ -87,7 +87,7 @@ public class ScatterPlotProjector<V extends NumberVector<?, ?>> extends Abstract // In 2d, make the plot twice as big. master = new PlotItem(2 + .1, 2 + .1, null); { - Projection2D proj = new Simple2D(scales.getScales(), 1, 2); + Projection2D proj = new Simple2D(scales.getScales(), 0, 1); PlotItem it = new PlotItem(.1, 0, 2., 2., proj); it.tasks = tasks; master.subitems.add(it); @@ -95,38 +95,38 @@ public class ScatterPlotProjector<V extends NumberVector<?, ?>> extends Abstract // Label at bottom { PlotItem it = new PlotItem(.1, 2., 2., .1, null); - final VisualizationTask task = new VisualizationTask("", null, null, new LabelVisFactory(DatabaseUtil.getColumnLabel(rel, 1))); + final VisualizationTask task = new VisualizationTask("", null, null, new LabelVisualization(RelationUtil.getColumnLabel(rel, 0))); task.height = .1; task.width = 2.; - task.put(VisualizationTask.META_NODETAIL, true); + task.nodetail = true; it.tasks.add(task); master.subitems.add(it); } // Label on left { PlotItem it = new PlotItem(0, 0, .1, 2, null); - final VisualizationTask task = new VisualizationTask("", null, null, new LabelVisFactory(DatabaseUtil.getColumnLabel(rel, 2), true)); + final VisualizationTask task = new VisualizationTask("", null, null, new LabelVisualization(RelationUtil.getColumnLabel(rel, 1), true)); task.height = 2.; task.width = .1; - task.put(VisualizationTask.META_NODETAIL, true); + task.nodetail = true; it.tasks.add(task); master.subitems.add(it); } } else { final double sizeh = Math.ceil((dmax - 1) / 2.0); - master = new PlotItem(sizeh * 2 + .1, dmax - 1 + .1, null); + master = new PlotItem(sizeh * 2. + .1, dmax - 1 + .1, null); - for(int d1 = 1; d1 < dmax; d1++) { - for(int d2 = d1 + 1; d2 <= dmax; d2++) { + for(int d1 = 0; d1 < dmax - 1; d1++) { + for(int d2 = d1 + 1; d2 < dmax; d2++) { Projection2D proj = new Simple2D(scales.getScales(), d1, d2); - PlotItem it = new PlotItem(d1 - 1 + .1, d2 - 2, 1., 1., proj); + PlotItem it = new PlotItem(d1 + .1, d2 - 1, 1., 1., proj); it.tasks = tasks; master.subitems.add(it); } } if(dmax >= 3) { - AffineTransformation p = AffineProjection.axisProjection(DatabaseUtil.dimensionality(rel), 1, 2); + AffineTransformation p = AffineProjection.axisProjection(RelationUtil.dimensionality(rel), 1, 2); p.addRotation(0, 2, Math.PI / 180 * -10.); p.addRotation(1, 2, Math.PI / 180 * 15.); // Wanna try 4d? go ahead: @@ -138,22 +138,22 @@ public class ScatterPlotProjector<V extends NumberVector<?, ?>> extends Abstract master.subitems.add(it); } // Labels at bottom - for(int d1 = 1; d1 < dmax; d1++) { - PlotItem it = new PlotItem(d1 - 1 + .1, dmax - 1, 1., .1, null); - final VisualizationTask task = new VisualizationTask("", null, null, new LabelVisFactory(DatabaseUtil.getColumnLabel(rel, d1))); + for(int d1 = 0; d1 < dmax - 1; d1++) { + PlotItem it = new PlotItem(d1 + .1, dmax - 1, 1., .1, null); + final VisualizationTask task = new VisualizationTask("", null, null, new LabelVisualization(RelationUtil.getColumnLabel(rel, d1))); task.height = .1; task.width = 1; - task.put(VisualizationTask.META_NODETAIL, true); + task.nodetail = true; it.tasks.add(task); master.subitems.add(it); } // Labels on left - for(int d2 = 2; d2 <= dmax; d2++) { - PlotItem it = new PlotItem(0, d2 - 2, .1, 1, null); - final VisualizationTask task = new VisualizationTask("", null, null, new LabelVisFactory(DatabaseUtil.getColumnLabel(rel, d2), true)); + for(int d2 = 1; d2 < dmax; d2++) { + PlotItem it = new PlotItem(0, d2 - 1, .1, 1, null); + final VisualizationTask task = new VisualizationTask("", null, null, new LabelVisualization(RelationUtil.getColumnLabel(rel, d2), true)); task.height = 1; task.width = .1; - task.put(VisualizationTask.META_NODETAIL, true); + task.nodetail = true; it.tasks.add(task); master.subitems.add(it); } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/savedialog/SVGSaveDialog.java b/src/de/lmu/ifi/dbs/elki/visualization/savedialog/SVGSaveDialog.java index f895c46e..8f56c562 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/savedialog/SVGSaveDialog.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/savedialog/SVGSaveDialog.java @@ -57,10 +57,10 @@ import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; */ public class SVGSaveDialog { /** The default title. "Save as ...". */ - public final static String DEFAULT_TITLE = "Save as ..."; + public static final String DEFAULT_TITLE = "Save as ..."; /** Static logger reference */ - private final static Logging logger = Logging.getLogger(SVGSaveDialog.class); + private static final Logging LOG = Logging.getLogger(SVGSaveDialog.class); /** Automagic file format */ final static String automagic_format = "automatic"; @@ -73,11 +73,10 @@ public class SVGSaveDialog { static { // FOP installed? - if(SVGPlot.hasFOPInstalled()) { + if (SVGPlot.hasFOPInstalled()) { formats = new String[] { "svg", "png", "jpeg", "jpg", "pdf", "ps", "eps" }; visibleformats = new String[] { automagic_format, "svg", "png", "jpeg", "pdf", "ps", "eps" }; - } - else { + } else { formats = new String[] { "svg", "png", "jpeg", "jpg" }; visibleformats = new String[] { automagic_format, "svg", "png", "jpeg" }; } @@ -95,7 +94,7 @@ public class SVGSaveDialog { double quality = 1.0; int ret = -1; - JFileChooser fc = new JFileChooser(); + JFileChooser fc = new JFileChooser(new File(".")); fc.setDialogTitle(DEFAULT_TITLE); // fc.setFileFilter(new ImageFilter()); SaveOptionsPanel optionsPanel = new SaveOptionsPanel(fc, width, height); @@ -103,70 +102,54 @@ public class SVGSaveDialog { ret = fc.showSaveDialog(null); fc.setDialogTitle("Saving... Please wait."); - if(ret == JFileChooser.APPROVE_OPTION) { + if (ret == JFileChooser.APPROVE_OPTION) { File file = fc.getSelectedFile(); String format = optionsPanel.getSelectedFormat(); - if(format == null || format == automagic_format) { + if (format == null || automagic_format.equals(format)) { format = guessFormat(file.getName()); } try { - if(format == null) { + if (format == null) { showError(fc, "Error saving image.", "File format not recognized."); - } - else if(format.equals("jpeg") || format.equals("jpg")) { + } else if ("jpeg".equals(format) || "jpg".equals(format)) { quality = optionsPanel.getJPEGQuality(); plot.saveAsJPEG(file, width, height, quality); - } - else if(format.equals("png")) { + } else if ("png".equals(format)) { plot.saveAsPNG(file, width, height); - } - else if(format.equals("ps")) { + } else if ("ps".equals(format)) { plot.saveAsPS(file); - } - else if(format.equals("eps")) { + } else if ("eps".equals(format)) { plot.saveAsEPS(file); - } - else if(format.equals("pdf")) { + } else if ("pdf".equals(format)) { plot.saveAsPDF(file); - } - else if(format.equals("svg")) { + } else if ("svg".equals(format)) { plot.saveAsSVG(file); - } - else { + } else { showError(fc, "Error saving image.", "Unsupported format: " + format); } - } - catch(java.lang.IncompatibleClassChangeError e) { + } catch (java.lang.IncompatibleClassChangeError e) { showError(fc, "Error saving image.", "It seems that your Java version is incompatible with this version of Batik and Jpeg writing. Sorry."); - } - catch(ClassNotFoundException e) { + } catch (ClassNotFoundException e) { showError(fc, "Error saving image.", "A class was not found when saving this image. Maybe installing Apache FOP will help (for PDF, PS and EPS output).\n" + e.toString()); - } - catch(IOException e) { - logger.exception(e); + } catch (IOException e) { + LOG.exception(e); showError(fc, "Error saving image.", e.toString()); - } - catch(TranscoderException e) { - logger.exception(e); + } catch (TranscoderException e) { + LOG.exception(e); showError(fc, "Error saving image.", e.toString()); - } - catch(TransformerFactoryConfigurationError e) { - logger.exception(e); + } catch (TransformerFactoryConfigurationError e) { + LOG.exception(e); showError(fc, "Error saving image.", e.toString()); - } - catch(TransformerException e) { - logger.exception(e); + } catch (TransformerException e) { + LOG.exception(e); showError(fc, "Error saving image.", e.toString()); - } - catch(Exception e) { - logger.exception(e); + } catch (Exception e) { + LOG.exception(e); showError(fc, "Error saving image.", e.toString()); } - } - else if(ret == JFileChooser.ERROR_OPTION) { + } else if (ret == JFileChooser.ERROR_OPTION) { showError(fc, "Error in file dialog.", "Unknown Error."); - } - else if(ret == JFileChooser.CANCEL_OPTION) { + } else if (ret == JFileChooser.CANCEL_OPTION) { // do nothing - except return result } return ret; @@ -180,8 +163,8 @@ public class SVGSaveDialog { */ public static String guessFormat(String name) { String ext = FileUtil.getFilenameExtension(name); - for(String format : formats) { - if(format.equals(ext)) { + for (String format : formats) { + if (format.equals(ext)) { return ext; } } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/savedialog/SaveOptionsPanel.java b/src/de/lmu/ifi/dbs/elki/visualization/savedialog/SaveOptionsPanel.java index 6193310b..7e77c79f 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/savedialog/SaveOptionsPanel.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/savedialog/SaveOptionsPanel.java @@ -206,8 +206,8 @@ public class SaveOptionsPanel extends JPanel { resetSizeButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - modelWidth.setValue(width); - modelHeight.setValue(height); + modelWidth.setValue(Integer.valueOf(width)); + modelHeight.setValue(Integer.valueOf(height)); aspectRatioLock.setSelected(true); } }); @@ -312,8 +312,7 @@ public class SaveOptionsPanel extends JPanel { * @return Quality value for JPEG. */ public double getJPEGQuality() { - Double qual = 0.7; - qual = modelQuality.getNumber().doubleValue(); + double qual =modelQuality.getNumber().doubleValue(); if(qual > 1.0) { qual = 1.0; } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/style/ClusterStylingPolicy.java b/src/de/lmu/ifi/dbs/elki/visualization/style/ClusterStylingPolicy.java index 1d329736..7adecc68 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/style/ClusterStylingPolicy.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/style/ClusterStylingPolicy.java @@ -77,14 +77,15 @@ public class ClusterStylingPolicy implements ClassStylingPolicy { colors = new TIntArrayList(clusters.size()); Iterator<? extends Cluster<?>> ci = clusters.iterator(); - for(int i = 0;; i++) { + for(int i = 0; ci.hasNext(); i++) { Cluster<?> c = ci.next(); ids.add(DBIDUtil.ensureSet(c.getIDs())); Color col = SVGUtil.stringToColor(colorset.getColor(i)); - if (col != null) { + if(col != null) { colors.add(col.getRGB()); - } else { - LoggingUtil.warning("Unrecognized color name: "+colorset.getColor(i)); + } + else { + LoggingUtil.warning("Unrecognized color name: " + colorset.getColor(i)); } if(!ci.hasNext()) { break; diff --git a/src/de/lmu/ifi/dbs/elki/visualization/style/PropertiesBasedStyleLibrary.java b/src/de/lmu/ifi/dbs/elki/visualization/style/PropertiesBasedStyleLibrary.java index 2bdae5e6..6b0869fb 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/style/PropertiesBasedStyleLibrary.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/style/PropertiesBasedStyleLibrary.java @@ -49,7 +49,7 @@ public class PropertiesBasedStyleLibrary implements StyleLibrary { /** * Logger */ - protected static final Logging logger = Logging.getLogger(PropertiesBasedStyleLibrary.class); + private static final Logging LOG = Logging.getLogger(PropertiesBasedStyleLibrary.class); /** * Name of the default color scheme. @@ -130,24 +130,20 @@ public class PropertiesBasedStyleLibrary implements StyleLibrary { InputStream stream = null; try { stream = FileUtil.openSystemFile(filename); - } - catch(FileNotFoundException e) { + } catch (FileNotFoundException e) { try { stream = FileUtil.openSystemFile(filename + DEFAULT_PROPERTIES_EXTENSION); - } - catch(FileNotFoundException e2) { + } catch (FileNotFoundException e2) { try { stream = FileUtil.openSystemFile(DEFAULT_PROPERTIES_PATH + filename + DEFAULT_PROPERTIES_EXTENSION); - } - catch(FileNotFoundException e3) { + } catch (FileNotFoundException e3) { throw new AbortException("Could not find style scheme file '" + filename + "' for scheme '" + name + "'!"); } } } try { properties.load(stream); - } - catch(Exception e) { + } catch (Exception e) { throw new AbortException("Error loading properties file " + filename + ".\n", e); } } @@ -171,7 +167,7 @@ public class PropertiesBasedStyleLibrary implements StyleLibrary { * @return Resulting value */ private <T> T getCached(String prefix, String postfix, Class<T> cls) { - return cache.get(prefix + "." + postfix, cls); + return cache.get(prefix + '.' + postfix, cls); } /** @@ -183,7 +179,7 @@ public class PropertiesBasedStyleLibrary implements StyleLibrary { * @param data Data */ private <T> void setCached(String prefix, String postfix, T data) { - cache.put(prefix + "." + postfix, data); + cache.put(prefix + '.' + postfix, data); } /** @@ -195,26 +191,26 @@ public class PropertiesBasedStyleLibrary implements StyleLibrary { */ protected String getPropertyValue(String prefix, String postfix) { String ret = properties.getProperty(prefix + "." + postfix); - if(ret != null) { + if (ret != null) { // logger.debugFine("Found property: "+prefix + "." + // postfix+" for "+prefix); return ret; } int pos = prefix.length(); - while(pos > 0) { - pos = prefix.lastIndexOf(".", pos - 1); - if(pos <= 0) { + while (pos > 0) { + pos = prefix.lastIndexOf('.', pos - 1); + if (pos <= 0) { break; } - ret = properties.getProperty(prefix.substring(0, pos) + "." + postfix); - if(ret != null) { + ret = properties.getProperty(prefix.substring(0, pos) + '.' + postfix); + if (ret != null) { // logger.debugFine("Found property: "+prefix.substring(0, pos) + "." + // postfix+" for "+prefix); return ret; } } ret = properties.getProperty(postfix); - if(ret != null) { + if (ret != null) { // logger.debugFine("Found property: "+postfix+" for "+prefix); return ret; } @@ -239,7 +235,7 @@ public class PropertiesBasedStyleLibrary implements StyleLibrary { @Override public ColorLibrary getColorSet(String key) { ColorLibrary cl = getCached(key, COLORSET, ColorLibrary.class); - if(cl == null) { + if (cl == null) { String[] colors = getPropertyValue(key, COLORSET).split(LIST_SEPARATOR); cl = new ListBasedColorLibrary(colors, "Default"); setCached(key, COLORSET, cl); @@ -250,29 +246,27 @@ public class PropertiesBasedStyleLibrary implements StyleLibrary { @Override public double getLineWidth(String key) { Double lw = getCached(key, LINE_WIDTH, Double.class); - if(lw == null) { + if (lw == null) { try { - lw = Double.parseDouble(getPropertyValue(key, LINE_WIDTH)) * SCALE; - } - catch(NullPointerException e) { - throw new AbortException("Missing/invalid value in style library: " + key + "." + LINE_WIDTH); + lw = Double.valueOf(Double.parseDouble(getPropertyValue(key, LINE_WIDTH)) * SCALE); + } catch (NullPointerException e) { + throw new AbortException("Missing/invalid value in style library: " + key + '.' + LINE_WIDTH); } } - return lw; + return lw.doubleValue(); } @Override public double getTextSize(String key) { Double lw = getCached(key, TEXT_SIZE, Double.class); - if(lw == null) { + if (lw == null) { try { - lw = Double.parseDouble(getPropertyValue(key, TEXT_SIZE)) * SCALE; - } - catch(NullPointerException e) { - throw new AbortException("Missing/invalid value in style library: " + key + "." + TEXT_SIZE); + lw = Double.valueOf(Double.parseDouble(getPropertyValue(key, TEXT_SIZE)) * SCALE); + } catch (NullPointerException e) { + throw new AbortException("Missing/invalid value in style library: " + key + '.' + TEXT_SIZE); } } - return lw; + return lw.doubleValue(); } @Override @@ -283,47 +277,43 @@ public class PropertiesBasedStyleLibrary implements StyleLibrary { @Override public double getSize(String key) { Double lw = getCached(key, GENERIC_SIZE, Double.class); - if(lw == null) { + if (lw == null) { try { - lw = Double.parseDouble(getPropertyValue(key, GENERIC_SIZE)) * SCALE; - } - catch(NullPointerException e) { - throw new AbortException("Missing/invalid value in style library: " + key + "." + GENERIC_SIZE); + lw = Double.valueOf(Double.parseDouble(getPropertyValue(key, GENERIC_SIZE)) * SCALE); + } catch (NullPointerException e) { + throw new AbortException("Missing/invalid value in style library: " + key + '.' + GENERIC_SIZE); } } - return lw; + return lw.doubleValue(); } @Override public double getOpacity(String key) { Double lw = getCached(key, OPACITY, Double.class); - if(lw == null) { + if (lw == null) { try { - lw = Double.parseDouble(getPropertyValue(key, OPACITY)); - } - catch(NullPointerException e) { - throw new AbortException("Missing/invalid value in style library: " + key + "." + OPACITY); + lw = Double.valueOf(Double.parseDouble(getPropertyValue(key, OPACITY))); + } catch (NullPointerException e) { + throw new AbortException("Missing/invalid value in style library: " + key + '.' + OPACITY); } } - return lw; + return lw.doubleValue(); } @Override public LineStyleLibrary lines() { - if(linelib == null) { + if (linelib == null) { String libname = properties.getProperty(PROP_LINES_LIBRARY, SolidLineStyleLibrary.class.getName()); try { Class<?> cls; try { cls = Class.forName(libname); - } - catch(ClassNotFoundException e) { - cls = Class.forName(LineStyleLibrary.class.getPackage().getName() + "." + libname); + } catch (ClassNotFoundException e) { + cls = Class.forName(LineStyleLibrary.class.getPackage().getName() + '.' + libname); } linelib = (LineStyleLibrary) cls.getConstructor(StyleLibrary.class).newInstance(this); - } - catch(Exception e) { - logger.exception(e); + } catch (Exception e) { + LOG.exception(e); linelib = new SolidLineStyleLibrary(this); } } @@ -332,23 +322,21 @@ public class PropertiesBasedStyleLibrary implements StyleLibrary { @Override public MarkerLibrary markers() { - if(markerlib == null) { + if (markerlib == null) { String libname = properties.getProperty(PROP_MARKER_LIBRARY, PrettyMarkers.class.getName()); try { Class<?> cls; try { cls = Class.forName(libname); - } - catch(ClassNotFoundException e) { - cls = Class.forName(MarkerLibrary.class.getPackage().getName() + "." + libname); + } catch (ClassNotFoundException e) { + cls = Class.forName(MarkerLibrary.class.getPackage().getName() + '.' + libname); } markerlib = (MarkerLibrary) cls.getConstructor(StyleLibrary.class).newInstance(this); - } - catch(Exception e) { - logger.exception(e); + } catch (Exception e) { + LOG.exception(e); markerlib = new PrettyMarkers(this); } } return markerlib; } -}
\ No newline at end of file +} diff --git a/src/de/lmu/ifi/dbs/elki/visualization/style/StyleResult.java b/src/de/lmu/ifi/dbs/elki/visualization/style/StyleResult.java index fe8a73ce..e8f0d3fb 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/style/StyleResult.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/style/StyleResult.java @@ -32,7 +32,6 @@ import de.lmu.ifi.dbs.elki.result.Result; * @apiviz.landmark * @apiviz.composedOf StylingPolicy */ -// TODO: pull style library etc. from VisualizerContext here? public class StyleResult implements Result { /** * Styling policy @@ -40,6 +39,11 @@ public class StyleResult implements Result { StylingPolicy policy; /** + * Style library + */ + StyleLibrary library; + + /** * Get the active styling policy * * @return Styling policy @@ -57,6 +61,24 @@ public class StyleResult implements Result { this.policy = policy; } + /** + * Get the style library + * + * @return Style library + */ + public StyleLibrary getStyleLibrary() { + return library; + } + + /** + * Get the style library + * + * @param library Style library + */ + public void setStyleLibrary(StyleLibrary library) { + this.library = library; + } + @Override public String getLongName() { return "Style policy"; @@ -66,4 +88,4 @@ public class StyleResult implements Result { public String getShortName() { return "style-policy"; } -}
\ No newline at end of file +} diff --git a/src/de/lmu/ifi/dbs/elki/visualization/style/lines/DashedLineStyleLibrary.java b/src/de/lmu/ifi/dbs/elki/visualization/style/lines/DashedLineStyleLibrary.java index 5dbfe67a..e3abd7c1 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/style/lines/DashedLineStyleLibrary.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/style/lines/DashedLineStyleLibrary.java @@ -107,14 +107,14 @@ public class DashedLineStyleLibrary implements LineStyleLibrary { boolean interpolated = false; // process flavoring flags for(Object flag : flags) { - if(flag == LineStyleLibrary.FLAG_STRONG) { + if(LineStyleLibrary.FLAG_STRONG.equals(flag)) { width = width * 1.5; } - else if(flag == LineStyleLibrary.FLAG_WEAK) { + else if(LineStyleLibrary.FLAG_WEAK.equals(flag)) { cls.setStatement(CSSConstants.CSS_STROKE_OPACITY_PROPERTY, ".50"); width = width * 0.75; } - else if(flag == LineStyleLibrary.FLAG_INTERPOLATED) { + else if(LineStyleLibrary.FLAG_INTERPOLATED.equals(flag)) { interpolated = true; } } @@ -125,10 +125,10 @@ public class DashedLineStyleLibrary implements LineStyleLibrary { double[] pat = dashpatterns[styleflav]; assert (pat.length % 2 == 0); if(pat.length > 0) { - StringBuffer pattern = new StringBuffer(); + StringBuilder pattern = new StringBuilder(); for(int i = 0; i < pat.length; i++) { if(i > 0) { - pattern.append(","); + pattern.append(','); } pattern.append(SVGUtil.fmt(pat[i] * width * 30)); // pattern.append("%"); @@ -144,10 +144,10 @@ public class DashedLineStyleLibrary implements LineStyleLibrary { assert (pat.length % 2 == 0); // TODO: add dotting. if(pat.length > 0) { - StringBuffer pattern = new StringBuffer(); + StringBuilder pattern = new StringBuilder(); for(int i = 0; i < pat.length; i++) { if(i > 0) { - pattern.append(","); + pattern.append(','); } pattern.append(SVGUtil.fmt(pat[i] * width)); // pattern.append("%"); diff --git a/src/de/lmu/ifi/dbs/elki/visualization/style/lines/LineStyleLibrary.java b/src/de/lmu/ifi/dbs/elki/visualization/style/lines/LineStyleLibrary.java index aff1e0fc..79f3eda0 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/style/lines/LineStyleLibrary.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/style/lines/LineStyleLibrary.java @@ -50,17 +50,17 @@ public interface LineStyleLibrary { /** * Meta flag to request a 'stronger' version of the style */ - public final static String FLAG_STRONG = "strong"; + public static final String FLAG_STRONG = "strong"; /** * Meta flag to request a 'weaker' version of the style */ - public final static String FLAG_WEAK = "weak"; + public static final String FLAG_WEAK = "weak"; /** * Meta flag to request an 'interpolated' version of the style */ - public final static String FLAG_INTERPOLATED = "interpolated"; + public static final String FLAG_INTERPOLATED = "interpolated"; /** * Add the formatting statements to the given CSS class. diff --git a/src/de/lmu/ifi/dbs/elki/visualization/style/lines/SolidLineStyleLibrary.java b/src/de/lmu/ifi/dbs/elki/visualization/style/lines/SolidLineStyleLibrary.java index 051bff0a..51491db2 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/style/lines/SolidLineStyleLibrary.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/style/lines/SolidLineStyleLibrary.java @@ -87,20 +87,20 @@ public class SolidLineStyleLibrary implements LineStyleLibrary { boolean interpolated = false; // process flavoring flags for(Object flag : flags) { - if(flag == LineStyleLibrary.FLAG_STRONG) { + if(LineStyleLibrary.FLAG_STRONG.equals(flag)) { width = width * 1.5; } - else if(flag == LineStyleLibrary.FLAG_WEAK) { + else if(LineStyleLibrary.FLAG_WEAK.equals(flag)) { cls.setStatement(CSSConstants.CSS_STROKE_OPACITY_PROPERTY, ".50"); width = width * 0.75; } - else if(flag == LineStyleLibrary.FLAG_INTERPOLATED) { + else if(LineStyleLibrary.FLAG_INTERPOLATED.equals(flag)) { interpolated = true; } } cls.setStatement(CSSConstants.CSS_STROKE_WIDTH_PROPERTY, SVGUtil.fmt(width)); if(interpolated) { - cls.setStatement(CSSConstants.CSS_STROKE_DASHARRAY_PROPERTY, "" + SVGUtil.fmt(width / StyleLibrary.SCALE * 2) + "," + SVGUtil.fmt(width / StyleLibrary.SCALE * 2)); + cls.setStatement(CSSConstants.CSS_STROKE_DASHARRAY_PROPERTY, "" + SVGUtil.fmt(width / StyleLibrary.SCALE * 2.) + "," + SVGUtil.fmt(width / StyleLibrary.SCALE * 2.)); } } } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/style/marker/CircleMarkers.java b/src/de/lmu/ifi/dbs/elki/visualization/style/marker/CircleMarkers.java index b847d571..ae066c9e 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/style/marker/CircleMarkers.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/style/marker/CircleMarkers.java @@ -72,7 +72,7 @@ public class CircleMarkers implements MarkerLibrary { */ @Override public Element useMarker(SVGPlot plot, Element parent, double x, double y, int stylenr, double size) { - Element marker = plot.svgCircle(x, y, size / 2); + Element marker = plot.svgCircle(x, y, size * .5); final String col; if(stylenr == -1) { col = dotcolor; diff --git a/src/de/lmu/ifi/dbs/elki/visualization/style/marker/MinimalMarkers.java b/src/de/lmu/ifi/dbs/elki/visualization/style/marker/MinimalMarkers.java index 2f5a8c2a..b7ad6274 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/style/marker/MinimalMarkers.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/style/marker/MinimalMarkers.java @@ -72,7 +72,7 @@ public class MinimalMarkers implements MarkerLibrary { */ @Override public Element useMarker(SVGPlot plot, Element parent, double x, double y, int stylenr, double size) { - Element marker = plot.svgRect(x - size / 2, y - size / 2, size, size); + Element marker = plot.svgRect(x - size * .5, y - size * .5, size, size); final String col; if(stylenr == -1) { col = dotcolor; diff --git a/src/de/lmu/ifi/dbs/elki/visualization/style/marker/PrettyMarkers.java b/src/de/lmu/ifi/dbs/elki/visualization/style/marker/PrettyMarkers.java index d868bb06..7352fea3 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/style/marker/PrettyMarkers.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/style/marker/PrettyMarkers.java @@ -48,7 +48,7 @@ public class PrettyMarkers implements MarkerLibrary { /** * Default prefix to use. */ - private final static String DEFAULT_PREFIX = "s"; + private static final String DEFAULT_PREFIX = "s"; /** * Prefix for the IDs generated. @@ -159,7 +159,7 @@ public class PrettyMarkers implements MarkerLibrary { } case 5: { // O filled circle - Element circ = plot.svgCircle(x, y, size / 2); + Element circ = plot.svgCircle(x, y, size * .5); SVGUtil.setStyle(circ, SVGConstants.CSS_FILL_PROPERTY + ":" + colorstr); parent.appendChild(circ); break; @@ -192,7 +192,7 @@ public class PrettyMarkers implements MarkerLibrary { * @param size Size */ protected void plotGray(SVGPlot plot, Element parent, double x, double y, double size) { - Element marker = plot.svgCircle(x, y, size / 2); + Element marker = plot.svgCircle(x, y, size * .5); SVGUtil.setStyle(marker, SVGConstants.CSS_FILL_PROPERTY + ":" + greycolor); parent.appendChild(marker); } @@ -207,7 +207,7 @@ public class PrettyMarkers implements MarkerLibrary { * @param size Size */ protected void plotUncolored(SVGPlot plot, Element parent, double x, double y, double size) { - Element marker = plot.svgCircle(x, y, size / 2); + Element marker = plot.svgCircle(x, y, size * .5); SVGUtil.setStyle(marker, SVGConstants.CSS_FILL_PROPERTY + ":" + dotcolor); parent.appendChild(marker); } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/style/presentation.properties b/src/de/lmu/ifi/dbs/elki/visualization/style/presentation.properties index 8c597066..01264673 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/style/presentation.properties +++ b/src/de/lmu/ifi/dbs/elki/visualization/style/presentation.properties @@ -74,9 +74,9 @@ xycurve.text-size=0.04 overview.labels.text-size=0.08 ## Selection colors -plot.selection.color=darkblue +plot.selection.color=red plot.selection.opacity=0.4 -plot.selection.size=0.015 +plot.selection.size=0.03 ## Circle segment colors. Will be interpolated to produce extra classes. segments.border.color=#FF0073 diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGArrow.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGArrow.java new file mode 100644 index 00000000..3859deb5 --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGArrow.java @@ -0,0 +1,114 @@ +package de.lmu.ifi.dbs.elki.visualization.svg; + +/* + This file is part of ELKI: + Environment for Developing KDD-Applications Supported by Index-Structures + + Copyright (C) 2012 + Ludwig-Maximilians-Universität München + Lehr- und Forschungseinheit für Datenbanksysteme + 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/>. + */ + +import org.w3c.dom.Element; + +/** + * Static class for drawing simple arrows + * + * @author Erich Schubert + * @author Robert Rödler + * + * @apiviz.uses SVGPath + */ +public final class SVGArrow { + /** + * Direction constants + * + * @author Erich Schubert + * @author Robert Rödler + * + * @apiviz.exclude + */ + public static enum Direction { + LEFT, DOWN, RIGHT, UP, // SWAPWITH, INSERT + } + + /** + * Constant for "up" + */ + public static final Direction UP = Direction.UP; + + /** + * Constant for "down" + */ + public static final Direction DOWN = Direction.DOWN; + + /** + * Constant for "right" + */ + public static final Direction RIGHT = Direction.RIGHT; + + /** + * Constant for "left" + */ + public static final Direction LEFT = Direction.LEFT; + + /** + * Draw an arrow at the given position. + * + * Note: the arrow is an unstyled svg path. You need to apply style afterwards. + * + * @param svgp Plot to draw to + * @param dir Direction to draw + * @param x Center x coordinate + * @param y Center y coordinate + * @param size Arrow size + * @return SVG Element + */ + public static Element makeArrow(SVGPlot svgp, Direction dir, double x, double y, double size) { + final SVGPath path = new SVGPath(); + final double hs = size / 2.; + + switch(dir){ + case LEFT: + path.drawTo(x + hs, y + hs); + path.drawTo(x - hs, y); + path.drawTo(x + hs, y - hs); + path.drawTo(x + hs, y + hs); + break; + case DOWN: + path.drawTo(x - hs, y - hs); + path.drawTo(x + hs, y - hs); + path.drawTo(x, y + hs); + path.drawTo(x - hs, y - hs); + break; + case RIGHT: + path.drawTo(x - hs, y - hs); + path.drawTo(x + hs, y); + path.drawTo(x - hs, y + hs); + path.drawTo(x - hs, y - hs); + break; + case UP: + path.drawTo(x - hs, y + hs); + path.drawTo(x, y - hs); + path.drawTo(x + hs, y + hs); + path.drawTo(x - hs, y + hs); + break; + } + path.close(); + return path.makeElement(svgp); + } +}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGHyperCube.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGHyperCube.java index 723f13df..656e42da 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGHyperCube.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGHyperCube.java @@ -70,7 +70,7 @@ public class SVGHyperCube { * @param max Opposite corner * @return path element */ - public static <V extends NumberVector<V, ?>> Element drawFrame(SVGPlot svgp, Projection2D proj, V min, V max) { + public static <V extends NumberVector<?>> Element drawFrame(SVGPlot svgp, Projection2D proj, V min, V max) { SVGPath path = new SVGPath(); ArrayList<double[]> edges = getVisibleEdges(proj, min.getColumnVector().getArrayRef(), max.getColumnVector().getArrayRef()); double[] rv_min = proj.fastProjectDataToRenderSpace(min); @@ -107,7 +107,7 @@ public class SVGHyperCube { * @param max Opposite corner * @return group element */ - public static <V extends NumberVector<V, ?>> Element drawFilled(SVGPlot svgp, String cls, Projection2D proj, V min, V max) { + public static <V extends NumberVector<?>> Element drawFilled(SVGPlot svgp, String cls, Projection2D proj, V min, V max) { Element group = svgp.svgElement(SVGConstants.SVG_G_TAG); ArrayList<double[]> edges = getVisibleEdges(proj, min.getColumnVector().getArrayRef(), max.getColumnVector().getArrayRef()); double[] rv_min = proj.fastProjectDataToRenderSpace(min); @@ -184,14 +184,14 @@ public class SVGHyperCube { for(int j = i + 1; j < r_edges.size(); j++) { if(!b.get(i) && !b.get(j)) { double[] deltaj = r_edges.get(j); - StringBuffer pbuf = new StringBuffer(); - pbuf.append(SVGUtil.fmt(r_min[0])).append(","); - pbuf.append(SVGUtil.fmt(r_min[1])).append(" "); - pbuf.append(SVGUtil.fmt(r_min[0] + deltai[0])).append(","); - pbuf.append(SVGUtil.fmt(r_min[1] + deltai[1])).append(" "); - pbuf.append(SVGUtil.fmt(r_min[0] + deltai[0] + deltaj[0])).append(","); - pbuf.append(SVGUtil.fmt(r_min[1] + deltai[1] + deltaj[1])).append(" "); - pbuf.append(SVGUtil.fmt(r_min[0] + deltaj[0])).append(","); + StringBuilder pbuf = new StringBuilder(); + pbuf.append(SVGUtil.fmt(r_min[0])).append(','); + pbuf.append(SVGUtil.fmt(r_min[1])).append(' '); + pbuf.append(SVGUtil.fmt(r_min[0] + deltai[0])).append(','); + pbuf.append(SVGUtil.fmt(r_min[1] + deltai[1])).append(' '); + pbuf.append(SVGUtil.fmt(r_min[0] + deltai[0] + deltaj[0])).append(','); + pbuf.append(SVGUtil.fmt(r_min[1] + deltai[1] + deltaj[1])).append(' '); + pbuf.append(SVGUtil.fmt(r_min[0] + deltaj[0])).append(','); pbuf.append(SVGUtil.fmt(r_min[1] + deltaj[1])); Element poly = plot.svgElement(SVGConstants.SVG_POLYGON_TAG); diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGHyperSphere.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGHyperSphere.java index 29b437f9..33daa56b 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGHyperSphere.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGHyperSphere.java @@ -45,7 +45,7 @@ public class SVGHyperSphere { * * kappa = 4 * (Math.sqrt(2)-1)/3 */ - public final static double EUCLIDEAN_KAPPA = 0.5522847498; + public static final double EUCLIDEAN_KAPPA = 0.5522847498; /** * Wireframe "manhattan" hypersphere @@ -57,7 +57,7 @@ public class SVGHyperSphere { * @param rad radius * @return path element */ - public static <D extends NumberDistance<?, ?>> Element drawManhattan(SVGPlot svgp, Projection2D proj, NumberVector<?, ?> mid, D rad) { + public static <D extends NumberDistance<?, ?>> Element drawManhattan(SVGPlot svgp, Projection2D proj, NumberVector<?> mid, D rad) { final double radius = rad.doubleValue(); final double[] v_mid = mid.getColumnVector().getArrayRef(); // a copy final BitSet dims = proj.getVisibleDimensions2D(); @@ -104,7 +104,7 @@ public class SVGHyperSphere { * @param rad radius * @return path element */ - public static <D extends NumberDistance<?, ?>> Element drawEuclidean(SVGPlot svgp, Projection2D proj, NumberVector<?, ?> mid, D rad) { + public static <D extends NumberDistance<?, ?>> Element drawEuclidean(SVGPlot svgp, Projection2D proj, NumberVector<?> mid, D rad) { final double radius = rad.doubleValue(); double[] v_mid = mid.getColumnVector().getArrayRef(); // a copy BitSet dims = proj.getVisibleDimensions2D(); @@ -157,7 +157,7 @@ public class SVGHyperSphere { * @param p L_p value * @return path element */ - public static <D extends NumberDistance<?, ?>> Element drawLp(SVGPlot svgp, Projection2D proj, NumberVector<?, ?> mid, D rad, double p) { + public static <D extends NumberDistance<?, ?>> Element drawLp(SVGPlot svgp, Projection2D proj, NumberVector<?> mid, D rad, double p) { final double radius = rad.doubleValue(); final double[] v_mid = mid.getColumnVector().getArrayRef(); final BitSet dims = proj.getVisibleDimensions2D(); @@ -277,7 +277,7 @@ public class SVGHyperSphere { * @param rad radius * @return path element */ - public static <D extends NumberDistance<?, ?>> Element drawCross(SVGPlot svgp, Projection2D proj, NumberVector<?, ?> mid, D rad) { + public static <D extends NumberDistance<?, ?>> Element drawCross(SVGPlot svgp, Projection2D proj, NumberVector<?> mid, D rad) { final double radius = rad.doubleValue(); final double[] v_mid = mid.getColumnVector().getArrayRef(); final BitSet dims = proj.getVisibleDimensions2D(); diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPath.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPath.java index 2b86870b..7c3e97e9 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPath.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPath.java @@ -40,7 +40,7 @@ public class SVGPath { /** * String buffer for building the path. */ - private StringBuffer buf = new StringBuffer(); + private StringBuilder buf = new StringBuilder(); /** * The last action we did, to not add unnecessary commands @@ -56,48 +56,48 @@ public class SVGPath { /** * The lower case version (relative) line to command. */ - public final static String PATH_LINE_TO_RELATIVE = SVGConstants.PATH_LINE_TO.toLowerCase(); + public static final String PATH_LINE_TO_RELATIVE = SVGConstants.PATH_LINE_TO.toLowerCase(); /** * The lower case version (relative) move command. */ - public final static String PATH_MOVE_RELATIVE = SVGConstants.PATH_MOVE.toLowerCase(); + public static final String PATH_MOVE_RELATIVE = SVGConstants.PATH_MOVE.toLowerCase(); /** * The lower case version (relative) horizontal line to command. */ - public final static String PATH_HORIZONTAL_LINE_TO_RELATIVE = SVGConstants.PATH_HORIZONTAL_LINE_TO.toLowerCase(); + public static final String PATH_HORIZONTAL_LINE_TO_RELATIVE = SVGConstants.PATH_HORIZONTAL_LINE_TO.toLowerCase(); /** * The lower case version (relative) vertical line to command. */ - public final static String PATH_VERTICAL_LINE_TO_RELATIVE = SVGConstants.PATH_VERTICAL_LINE_TO.toLowerCase(); + public static final String PATH_VERTICAL_LINE_TO_RELATIVE = SVGConstants.PATH_VERTICAL_LINE_TO.toLowerCase(); /** * The lower case version (relative) cubic line to command. */ - public final static String PATH_CUBIC_TO_RELATIVE = SVGConstants.PATH_CUBIC_TO.toLowerCase(); + public static final String PATH_CUBIC_TO_RELATIVE = SVGConstants.PATH_CUBIC_TO.toLowerCase(); /** * The lower case version (relative) smooth cubic to command. */ - public final static String PATH_SMOOTH_CUBIC_TO_RELATIVE = PATH_SMOOTH_CUBIC_TO.toLowerCase(); + public static final String PATH_SMOOTH_CUBIC_TO_RELATIVE = PATH_SMOOTH_CUBIC_TO.toLowerCase(); /** * The lower case version (relative) quadratic interpolation to command. */ - public final static String PATH_QUAD_TO_RELATIVE = SVGConstants.PATH_QUAD_TO.toLowerCase(); + public static final String PATH_QUAD_TO_RELATIVE = SVGConstants.PATH_QUAD_TO.toLowerCase(); /** * The lower case version (relative) smooth quadratic interpolation to * command. */ - public final static String PATH_SMOOTH_QUAD_TO_RELATIVE = SVGConstants.PATH_SMOOTH_QUAD_TO.toLowerCase(); + public static final String PATH_SMOOTH_QUAD_TO_RELATIVE = SVGConstants.PATH_SMOOTH_QUAD_TO.toLowerCase(); /** * The lower case version (relative) path arc command. */ - public final static String PATH_ARC_RELATIVE = SVGConstants.PATH_ARC.toLowerCase(); + public static final String PATH_ARC_RELATIVE = SVGConstants.PATH_ARC.toLowerCase(); /** * Empty path constructor. @@ -794,7 +794,7 @@ public class SVGPath { } for(double d : ds) { buf.append(SVGUtil.FMT.format(d)); - buf.append(" "); + buf.append(' '); } } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPlot.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPlot.java index e45c7b5a..43244047 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPlot.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPlot.java @@ -24,6 +24,7 @@ package de.lmu.ifi.dbs.elki.visualization.svg; */ import java.awt.image.BufferedImage; +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -331,7 +332,7 @@ public class SVGPlot { * @throws TransformerException Transformation error */ public void saveAsSVG(File file) throws IOException, TransformerFactoryConfigurationError, TransformerException { - OutputStream out = new FileOutputStream(file); + OutputStream out = new BufferedOutputStream(new FileOutputStream(file)); // TODO embed linked images. javax.xml.transform.Result result = new StreamResult(out); SVGDocument doc = cloneDocument(); @@ -356,7 +357,7 @@ public class SVGPlot { transcoder.addTranscodingHint(XMLAbstractTranscoder.KEY_XML_PARSER_VALIDATING, Boolean.FALSE); SVGDocument doc = cloneDocument(); TranscoderInput input = new TranscoderInput(doc); - OutputStream out = new FileOutputStream(file); + OutputStream out = new BufferedOutputStream(new FileOutputStream(file)); TranscoderOutput output = new TranscoderOutput(out); transcoder.transcode(input, output); out.flush(); diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGScoreBar.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGScoreBar.java index ac352169..3af2ea41 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGScoreBar.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGScoreBar.java @@ -111,7 +111,7 @@ public class SVGScoreBar { Element bar = svgp.svgRect(x, y, width, height); bar.setAttribute(SVGConstants.SVG_FILL_ATTRIBUTE, "#a0a0a0"); bar.setAttribute(SVGConstants.SVG_STROKE_ATTRIBUTE, "#a0a0a0"); - bar.setAttribute(SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE, "" + height * 0.01); + bar.setAttribute(SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE, String.valueOf(height * 0.01)); barchart.appendChild(bar); if(fill >= 0 && fill <= size + 1) { @@ -119,7 +119,7 @@ public class SVGScoreBar { Element chart = svgp.svgRect(x + 0.02 * height, y + 0.02 * height, fpos, height - 0.04 * height); chart.setAttribute(SVGConstants.SVG_FILL_ATTRIBUTE, "#d4e4f1"); chart.setAttribute(SVGConstants.SVG_STROKE_ATTRIBUTE, "#a0a0a0"); - chart.setAttribute(SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE, "" + height * 0.01); + chart.setAttribute(SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE, String.valueOf(height * 0.01)); barchart.appendChild(chart); } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGSimpleLinearAxis.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGSimpleLinearAxis.java index 987915b7..930e9e8e 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGSimpleLinearAxis.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGSimpleLinearAxis.java @@ -66,17 +66,17 @@ public class SVGSimpleLinearAxis { /** * CSS class name for the axes */ - private final static String CSS_AXIS = "axis"; + private static final String CSS_AXIS = "axis"; /** * CSS class name for the axes */ - private final static String CSS_AXIS_TICK = "axis-tick"; + private static final String CSS_AXIS_TICK = "axis-tick"; /** * CSS class name for the axes */ - private final static String CSS_AXIS_LABEL = "axis-label"; + private static final String CSS_AXIS_LABEL = "axis-label"; /** * Register CSS classes with a {@link CSSClassManager} diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGUtil.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGUtil.java index 4e40d1b0..633cb3e5 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGUtil.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGUtil.java @@ -79,7 +79,7 @@ public final class SVGUtil { * SVG color names conversion. */ final private static TObjectIntHashMap<String> SVG_COLOR_NAMES; - + /** * Key not found value. Not a reasonable color, fully transparent! */ @@ -244,7 +244,7 @@ public final class SVGUtil { /** * CSS Stylesheet from Javax, to parse color values. */ - private final static StyleSheet colorLookupStylesheet = new StyleSheet(); + private static final StyleSheet colorLookupStylesheet = new StyleSheet(); /** * Format a double according to the SVG specs. @@ -329,13 +329,13 @@ public final class SVGUtil { */ public static void addCSSClass(Element e, String cssclass) { String oldval = e.getAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE); - if(oldval == null || oldval.length() == 0) { + if (oldval == null || oldval.length() == 0) { setAtt(e, SVGConstants.SVG_CLASS_ATTRIBUTE, cssclass); return; } String[] classes = oldval.split(" "); - for(String c : classes) { - if(c.equals(cssclass)) { + for (String c : classes) { + if (c.equals(cssclass)) { return; } } @@ -350,22 +350,36 @@ public final class SVGUtil { */ public static void removeCSSClass(Element e, String cssclass) { String oldval = e.getAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE); - if(oldval == null) { + if (oldval == null) { return; } String[] classes = oldval.split(" "); - String joined = ""; - for(String c : classes) { - if(!c.equals(cssclass)) { - if(joined.length() > 0) { - joined = joined + " " + c; + if (classes.length == 1) { + if (cssclass.equals(classes[0])) { + e.removeAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE); + } + } else if (classes.length == 2) { + if (cssclass.equals(classes[0])) { + if (cssclass.equals(classes[1])) { + e.removeAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE); + } else { + e.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, classes[1]); } - else { - joined = c; + } else if (cssclass.equals(classes[1])) { + e.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, classes[0]); + } + } else { + StringBuilder joined = new StringBuilder(); + for (String c : classes) { + if (!c.equals(cssclass)) { + if (joined.length() > 0) { + joined.append(' '); + } + joined.append(c); } } + e.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, joined.toString()); } - SVGUtil.setAtt(e, SVGConstants.SVG_CLASS_ATTRIBUTE, joined); } /** @@ -376,7 +390,7 @@ public final class SVGUtil { */ public static Element makeStyleElement(Document document) { Element style = SVGUtil.svgElement(document, SVGConstants.SVG_STYLE_TAG); - SVGUtil.setAtt(style, SVGConstants.SVG_TYPE_ATTRIBUTE, SVGConstants.CSS_MIME_TYPE); + style.setAttribute(SVGConstants.SVG_TYPE_ATTRIBUTE, SVGConstants.CSS_MIME_TYPE); return style; } @@ -479,7 +493,7 @@ public final class SVGUtil { */ public static Color stringToColor(String str) { int icol = SVG_COLOR_NAMES.get(str.toLowerCase()); - if(icol != NO_VALUE) { + if (icol != NO_VALUE) { return new Color(icol, false); } return colorLookupStylesheet.stringToColor(str); @@ -494,7 +508,7 @@ public final class SVGUtil { * @return Color string */ public static String colorToString(Color col) { - return String.format("#%02x%02x%02x", col.getRed(), col.getGreen(), col.getBlue()); + return colorToString(col.getRGB()); } /** @@ -506,7 +520,12 @@ public final class SVGUtil { * @return Color string */ public static String colorToString(int col) { - return String.format("#%02x%02x%02x", (col >>> 16) & 0xFF, (col >>> 8) & 0xFF, col & 0xFF); + char[] buf = new char[] { '#', '0', '0', '0', '0', '0', '0' }; + for (int i = 7; i > 0; i--) { + buf[i] += (col & 0xF); + col >>= 4; + } + return new String(buf); } /** @@ -526,8 +545,8 @@ public final class SVGUtil { double swidth = iwidth + lmargin + rmargin; double sheight = iheight + tmargin + bmargin; double scale = Math.max(swidth / owidth, sheight / oheight); - double offx = (scale * owidth - swidth) / 2 + lmargin; - double offy = (scale * oheight - sheight) / 2 + tmargin; + double offx = (scale * owidth - swidth) * .5 + lmargin; + double offy = (scale * oheight - sheight) * .5 + tmargin; return "scale(" + fmt(1 / scale) + ") translate(" + fmt(offx) + " " + fmt(offy) + ")"; } @@ -578,8 +597,7 @@ public final class SVGUtil { cPt.setX(gnme.getClientX()); cPt.setY(gnme.getClientY()); return cPt.matrixTransform(imat); - } - catch(Exception e) { + } catch (Exception e) { LoggingUtil.warning("Error getting coordinates from SVG event.", e); return null; } @@ -592,7 +610,7 @@ public final class SVGUtil { */ public static void removeLastChild(Element tag) { final Node last = tag.getLastChild(); - if(last != null) { + if (last != null) { tag.removeChild(last); } } @@ -603,8 +621,8 @@ public final class SVGUtil { * @param elem Element to remove */ public static void removeFromParent(Element elem) { - if(elem != null) { - if(elem.getParentNode() != null) { + if (elem != null) { + if (elem.getParentNode() != null) { elem.getParentNode().removeChild(elem); } } @@ -625,25 +643,25 @@ public final class SVGUtil { public static Element svgCircleSegment(SVGPlot svgp, double centerx, double centery, double angleStart, double angleDelta, double innerRadius, double outerRadius) { double sin1st = Math.sin(angleStart); double cos1st = Math.cos(angleStart); - + double sin2nd = Math.sin(angleStart + angleDelta); double cos2nd = Math.cos(angleStart + angleDelta); - + double inner1stx = centerx + (innerRadius * sin1st); double inner1sty = centery - (innerRadius * cos1st); double outer1stx = centerx + (outerRadius * sin1st); double outer1sty = centery - (outerRadius * cos1st); - + double inner2ndx = centerx + (innerRadius * sin2nd); double inner2ndy = centery - (innerRadius * cos2nd); double outer2ndx = centerx + (outerRadius * sin2nd); double outer2ndy = centery - (outerRadius * cos2nd); - + double largeArc = 0; - if(angleDelta >= Math.PI) { + if (angleDelta >= Math.PI) { largeArc = 1; } - + SVGPath path = new SVGPath(inner1stx, inner1sty); path.lineTo(outer1stx, outer1sty); path.ellipticalArc(outerRadius, outerRadius, 0, largeArc, 1, outer2ndx, outer2ndy); @@ -651,7 +669,7 @@ public final class SVGUtil { if (innerRadius > 0) { path.ellipticalArc(innerRadius, innerRadius, 0, largeArc, 0, inner1stx, inner1sty); } - + return path.makeElement(svgp); } -}
\ No newline at end of file +} diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/VoronoiDraw.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/VoronoiDraw.java index 317a44ae..2997456d 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/svg/VoronoiDraw.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/VoronoiDraw.java @@ -35,6 +35,9 @@ import de.lmu.ifi.dbs.elki.visualization.projections.Projection2D; *
* @author Robert Rödler
* @author Erich Schubert
+ *
+ * @apiviz.uses de.lmu.ifi.dbs.elki.math.geometry.SweepHullDelaunay2D.Triangle
+ * @apiviz.uses Projection2D
*/
public class VoronoiDraw {
/**
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/AbstractVisFactory.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/AbstractVisFactory.java index 40ae5cdf..565cf784 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/AbstractVisFactory.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/AbstractVisFactory.java @@ -32,6 +32,7 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualizati * @author Remigius Wojdanowski * * @apiviz.uses ThumbnailVisualization oneway - - «create» + * @apiviz.excludeSubtypes */ public abstract class AbstractVisFactory implements VisFactory { /** @@ -49,8 +50,7 @@ public abstract class AbstractVisFactory implements VisFactory { @Override public Visualization makeVisualizationOrThumbnail(VisualizationTask task) { // Is this a thumbnail request? - Boolean isthumb = task.get(VisualizationTask.THUMBNAIL, Boolean.class); - if (isthumb != null && isthumb.booleanValue() && allowThumbnails(task)) { + if (task.thumbnail && allowThumbnails(task)) { return new ThumbnailVisualization(this, task, thumbmask); } return makeVisualization(task); diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/AbstractVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/AbstractVisualization.java index bbff0117..b023827d 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/AbstractVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/AbstractVisualization.java @@ -37,6 +37,7 @@ import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; * Abstract base class for visualizations. * * @author Erich Schubert + * @apiviz.excludeSubtypes */ public abstract class AbstractVisualization implements Visualization, ResultListener { /** diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/StaticVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/StaticVisualizationInstance.java index 3a4be45b..3cd24289 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/StaticVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/StaticVisualizationInstance.java @@ -32,14 +32,14 @@ import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; * * @author Erich Schubert */ -public class StaticVisualization extends AbstractVisualization { +public class StaticVisualizationInstance extends AbstractVisualization { /** * Unchanging precomputed visualization. * * @param task Task to visualize * @param element Element containing the resulting visualization */ - public StaticVisualization(VisualizationTask task, Element element) { + public StaticVisualizationInstance(VisualizationTask task, Element element) { super(task); this.layer = element; } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/VisualizerUtil.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/VisualizerUtil.java index 1bc9b332..389aec52 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/VisualizerUtil.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/VisualizerUtil.java @@ -47,6 +47,14 @@ import de.lmu.ifi.dbs.elki.visualization.VisualizerContext; */ public final class VisualizerUtil { /** + * Fake constructor: do not instantiate. + * + */ + private VisualizerUtil() { + // Do not instantiate. + } + + /** * Find the visualizer context in a result tree. * * @param baseResult base result to start searching at. @@ -54,33 +62,14 @@ public final class VisualizerUtil { */ public static VisualizerContext getContext(HierarchicalResult baseResult) { List<VisualizerContext> contexts = ResultUtil.filterResults(baseResult, VisualizerContext.class); - if(!contexts.isEmpty()) { + if (!contexts.isEmpty()) { return contexts.get(0); - } - else { + } else { return null; } } /** - * Utility function to test for Visualizer visibility. - * - * @param task Visualization task - * @return true when visible - */ - public static boolean isVisible(VisualizationTask task) { - // Currently enabled? - Boolean enabled = task.getGenerics(VisualizationTask.META_VISIBLE, Boolean.class); - if(enabled == null) { - enabled = task.getGenerics(VisualizationTask.META_VISIBLE_DEFAULT, Boolean.class); - } - if(enabled == null) { - enabled = true; - } - return enabled; - } - - /** * Utility function to change Visualizer visibility. * * @param task Visualization task @@ -88,10 +77,9 @@ public final class VisualizerUtil { */ public static void setVisible(VisualizationTask task, boolean visibility) { VisualizerContext context = task.getContext(); - if(context != null) { + if (context != null) { setVisible(context, task, visibility); - } - else { + } else { LoggingUtil.warning("setVisible called without context in task.", new Throwable()); } } @@ -105,78 +93,49 @@ public final class VisualizerUtil { */ public static void setVisible(VisualizerContext context, VisualizationTask task, boolean visibility) { // Hide other tools - if(visibility && VisualizerUtil.isTool(task)) { + if (visibility && task.tool) { final List<VisualizationTask> visualizers = ResultUtil.filterResults(context.getResult(), VisualizationTask.class); - for(VisualizationTask other : visualizers) { - if(other != task && VisualizerUtil.isTool(other) && VisualizerUtil.isVisible(other)) { - other.put(VisualizationTask.META_VISIBLE, false); + for (VisualizationTask other : visualizers) { + if (other != task && other.tool && other.visible) { + other.visible = false; context.getHierarchy().resultChanged(other); } } } - task.put(VisualizationTask.META_VISIBLE, visibility); + task.visible = visibility; context.getHierarchy().resultChanged(task); } /** - * Utility function to test for a visualizer being a "tool". - * - * @param vis Visualizer to test - * @return true for a tool - */ - public static boolean isTool(VisualizationTask vis) { - // Currently enabled? - Boolean tool = vis.getGenerics(VisualizationTask.META_TOOL, Boolean.class); - return (tool != null) && tool; - } - - /** - * Utility function to test for a visualizer being "no export". - * - * @param vis Visualizer to test - * @return true when not to export - */ - public static boolean isNoExport(VisualizationTask vis) { - // Currently enabled? - Boolean noexport = vis.getGenerics(VisualizationTask.META_NOEXPORT, Boolean.class); - return (noexport != null) && noexport; - } - - /** - * Utility function to test for a visualizer having options. - * - * @param vis Visualizer to test - * @return true when it has options - */ - public static boolean hasOptions(VisualizationTask vis) { - // Currently enabled? - Boolean hasoptions = vis.getGenerics(VisualizationTask.META_HAS_OPTIONS, Boolean.class); - return (hasoptions != null) && hasoptions; - } - - /** - * Filter for number vector field representations + * Filter for number vector field representations. * * @param result Result to filter * @return Iterator over suitable representations */ // TODO: move to DatabaseUtil? - public static Iterator<Relation<? extends NumberVector<?, ?>>> iterateVectorFieldRepresentations(final Result result) { + public static Iterator<Relation<? extends NumberVector<?>>> iterateVectorFieldRepresentations(final Result result) { List<Relation<?>> parent = ResultUtil.filterResults(result, Relation.class); return new VectorspaceIterator(parent.iterator()); } - + /** - * Iterate over vectorspace + * Iterate over vectorspace. * * @author Erich Schubert * * @apiviz.exclude */ - private static class VectorspaceIterator extends AbstractFilteredIterator<Relation<?>, Relation<? extends NumberVector<?, ?>>> { - /** Parent iterator */ + private static class VectorspaceIterator extends AbstractFilteredIterator<Relation<?>, Relation<? extends NumberVector<?>>> { + /** + * Parent iterator. + */ private Iterator<Relation<?>> parent; + /** + * Constructor. + * + * @param parent Parent iterator + */ public VectorspaceIterator(Iterator<Relation<?>> parent) { super(); this.parent = parent; @@ -189,37 +148,15 @@ public final class VisualizerUtil { @SuppressWarnings("unchecked") @Override - protected Relation<? extends NumberVector<?, ?>> testFilter(Relation<?> nextobj) { + protected Relation<? extends NumberVector<?>> testFilter(Relation<?> nextobj) { final SimpleTypeInformation<?> type = nextobj.getDataTypeInformation(); - if(!NumberVector.class.isAssignableFrom(type.getRestrictionClass())) { + if (!NumberVector.class.isAssignableFrom(type.getRestrictionClass())) { return null; } - if(!(type instanceof VectorFieldTypeInformation)) { + if (!(type instanceof VectorFieldTypeInformation)) { return null; } - return (Relation<? extends NumberVector<?, ?>>) nextobj; + return (Relation<? extends NumberVector<?>>) nextobj; } }; - - /** - * Test whether a thumbnail is enabled for this visualizer. - * - * @param vis Visualizer - * @return boolean - */ - public static boolean thumbnailEnabled(VisualizationTask vis) { - Boolean nothumb = vis.getGenerics(VisualizationTask.META_NOTHUMB, Boolean.class); - return (nothumb == null) || !nothumb; - } - - /** - * Test whether a detail plot is available for this task. - * - * @param vis Task - * @return boolean - */ - public static boolean detailsEnabled(VisualizationTask vis) { - Boolean nodetail = vis.getGenerics(VisualizationTask.META_NODETAIL, Boolean.class); - return (nodetail == null) || !nodetail; - } -}
\ No newline at end of file +} diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/histogram/ColoredHistogramVisualizer.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/histogram/ColoredHistogramVisualizer.java index e300a3c4..7a52c8b3 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/histogram/ColoredHistogramVisualizer.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/histogram/ColoredHistogramVisualizer.java @@ -28,19 +28,20 @@ import java.util.Collection; import org.apache.batik.util.SVGConstants; import org.w3c.dom.Element; +import de.lmu.ifi.dbs.elki.data.DoubleVector; import de.lmu.ifi.dbs.elki.data.NumberVector; import de.lmu.ifi.dbs.elki.database.ids.DBIDIter; import de.lmu.ifi.dbs.elki.database.relation.Relation; +import de.lmu.ifi.dbs.elki.database.relation.RelationUtil; import de.lmu.ifi.dbs.elki.logging.LoggingUtil; import de.lmu.ifi.dbs.elki.math.DoubleMinMax; -import de.lmu.ifi.dbs.elki.math.histograms.AggregatingHistogram; import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector; import de.lmu.ifi.dbs.elki.math.scales.LinearScale; import de.lmu.ifi.dbs.elki.result.HierarchicalResult; import de.lmu.ifi.dbs.elki.result.Result; import de.lmu.ifi.dbs.elki.result.ResultUtil; import de.lmu.ifi.dbs.elki.result.SamplingResult; -import de.lmu.ifi.dbs.elki.utilities.DatabaseUtil; +import de.lmu.ifi.dbs.elki.utilities.datastructures.histogram.DoubleArrayStaticHistogram; import de.lmu.ifi.dbs.elki.utilities.exceptions.ObjectNotFoundException; import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer; import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID; @@ -48,7 +49,6 @@ import de.lmu.ifi.dbs.elki.utilities.optionhandling.constraints.GreaterEqualCons import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Flag; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.IntParameter; -import de.lmu.ifi.dbs.elki.utilities.pairs.DoubleObjPair; import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; import de.lmu.ifi.dbs.elki.visualization.colors.ColorLibrary; import de.lmu.ifi.dbs.elki.visualization.css.CSSClass; @@ -72,279 +72,301 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualizati * of the database's objects. * * @author Remigius Wojdanowski + * @author Erich Schubert * - * @apiviz.has NumberVector oneway - - visualizes - * - * @param <NV> Type of the DatabaseObject being visualized. + * @apiviz.stereotype factory + * @apiviz.uses Instance oneway - - «create» */ -// FIXME: make non-static, react to database changes! -// FIXME: cache histogram instead of recomputing it. -public class ColoredHistogramVisualizer<NV extends NumberVector<NV, ?>> extends AbstractHistogramVisualization { +public class ColoredHistogramVisualizer extends AbstractVisFactory { /** * Name for this visualizer. */ private static final String CNAME = "Histograms"; /** - * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc. + * Settings */ - public static final String BIN = "bin"; + protected Parameterizer settings; /** - * Internal storage of the curves flag. + * Number of bins to use in histogram. */ - private boolean curves; + private static final int DEFAULT_BINS = 50; /** - * Number of bins to use in the histogram. + * Constructor. + * + * @param settings Settings */ - private int bins; + public ColoredHistogramVisualizer(Parameterizer settings) { + super(); + this.settings = settings; + thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_STYLE; + } - /** - * The database we visualize - */ - private Relation<NV> relation; + @Override + public Visualization makeVisualization(VisualizationTask task) { + return new Instance<DoubleVector>(task); + } - /** - * The style policy - */ - private StyleResult style; + @Override + public void processNewResult(HierarchicalResult baseResult, Result result) { + // Find a style result to visualize: + Collection<StyleResult> styleres = ResultUtil.filterResults(result, StyleResult.class); + for (StyleResult c : styleres) { + Collection<HistogramProjector<?>> ps = ResultUtil.filterResults(baseResult, HistogramProjector.class); + for (HistogramProjector<?> p : ps) { + // register self + final VisualizationTask task = new VisualizationTask(CNAME, c, p.getRelation(), this); + task.level = VisualizationTask.LEVEL_DATA; + baseResult.getHierarchy().add(c, task); + baseResult.getHierarchy().add(p, task); + } + } + } - /** - * Sampling result - */ - private SamplingResult sample; + @Override + public boolean allowThumbnails(VisualizationTask task) { + // Don't use thumbnails + return false; + } /** - * Constructor. + * Instance + * + * @author Remigius Wojdanowski * - * @param task Visualization task - * @param curves Curves flag - * @param bins Number of bins + * @apiviz.has NumberVector oneway - - visualizes + * + * @param <NV> Type of the DatabaseObject being visualized. */ - public ColoredHistogramVisualizer(VisualizationTask task, boolean curves, int bins) { - super(task); - this.curves = curves; - this.bins = bins; - this.relation = task.getRelation(); - this.style = task.getResult(); - this.sample = ResultUtil.getSamplingResult(relation); - context.addResultListener(this); - } + // FIXME: make non-static, react to database changes! + // FIXME: cache histogram instead of recomputing it. + public class Instance<NV extends NumberVector<?>> extends AbstractHistogramVisualization { + /** + * Generic tag to indicate the type of element. Used in IDs, CSS-Classes + * etc. + */ + public static final String BIN = "bin"; - @Override - public void destroy() { - context.removeResultListener(this); - super.destroy(); - } + /** + * The database we visualize + */ + private Relation<NV> relation; - @Override - protected void redraw() { - double margin = context.getStyleLibrary().getSize(StyleLibrary.MARGIN); - layer = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG); - double xsize = Projection.SCALE * task.getWidth() / task.getHeight(); - double ysize = Projection.SCALE; - - final String transform = SVGUtil.makeMarginTransform(task.getWidth(), task.getHeight(), xsize, ysize, margin); - SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); - - // Styling policy - final StylingPolicy spol = style.getStylingPolicy(); - final ClassStylingPolicy cspol; - if(spol instanceof ClassStylingPolicy) { - cspol = (ClassStylingPolicy) spol; + /** + * The style policy + */ + private StyleResult style; + + /** + * Sampling result + */ + private SamplingResult sample; + + /** + * Constructor. + * + * @param task Visualization task + */ + public Instance(VisualizationTask task) { + super(task); + this.relation = task.getRelation(); + this.style = task.getResult(); + this.sample = ResultUtil.getSamplingResult(relation); + context.addResultListener(this); } - else { - cspol = null; + + @Override + public void destroy() { + context.removeResultListener(this); + super.destroy(); } - // TODO also use min style? - setupCSS(svgp, (cspol != null) ? cspol.getMaxStyle() : 0); - - // Create histograms - final int off = (cspol != null) ? cspol.getMinStyle() : 0; - final int numc = (cspol != null) ? (cspol.getMaxStyle() - cspol.getMinStyle()) : 0; - DoubleMinMax minmax = new DoubleMinMax(); - final double frac = 1. / relation.size(); // TODO: sampling? - final int cols = numc + 1; - AggregatingHistogram<double[], double[]> histogram = new AggregatingHistogram<double[], double[]>(bins, -.5, .5, new AggregatingHistogram.Adapter<double[], double[]>() { - @Override - public double[] aggregate(double[] existing, double[] data) { - for(int i = 0; i < existing.length; i++) { - existing[i] += data[i]; - } - return existing; - } - @Override - public double[] make() { - return new double[cols]; + @Override + protected void redraw() { + double margin = style.getStyleLibrary().getSize(StyleLibrary.MARGIN); + layer = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG); + double xsize = Projection.SCALE * task.getWidth() / task.getHeight(); + double ysize = Projection.SCALE; + + final String transform = SVGUtil.makeMarginTransform(task.getWidth(), task.getHeight(), xsize, ysize, margin); + SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); + + // Styling policy + final StylingPolicy spol = style.getStylingPolicy(); + final ClassStylingPolicy cspol; + if (spol instanceof ClassStylingPolicy) { + cspol = (ClassStylingPolicy) spol; + } else { + cspol = null; } - }); - - if(cspol != null) { - for(int snum = 0; snum < numc; snum++) { + // TODO also use min style? + setupCSS(svgp, (cspol != null) ? cspol.getMaxStyle() : 0); + + // Create histograms + final int off = (cspol != null) ? cspol.getMinStyle() : 0; + final int numc = (cspol != null) ? (cspol.getMaxStyle() - cspol.getMinStyle()) : 0; + DoubleMinMax minmax = new DoubleMinMax(); + final double frac = 1. / relation.size(); // TODO: sampling? + final int cols = numc + 1; + DoubleArrayStaticHistogram histogram = new DoubleArrayStaticHistogram(settings.bins, -.5, .5, cols); + + if (cspol != null) { + for (int snum = 0; snum < numc; snum++) { + double[] inc = new double[cols]; + inc[0] = frac; + inc[snum + 1] = frac; + for (DBIDIter iter = cspol.iterateClass(snum + off); iter.valid(); iter.advance()) { + if (!sample.getSample().contains(iter)) { + continue; // TODO: can we test more efficiently than this? + } + try { + double pos = proj.fastProjectDataToRenderSpace(relation.get(iter)) / Projection.SCALE; + histogram.increment(pos, inc); + } catch (ObjectNotFoundException e) { + // Ignore. The object was probably deleted from the database + } + } + } + } else { + // Actual data distribution. double[] inc = new double[cols]; inc[0] = frac; - inc[snum + 1] = frac; - for(DBIDIter iter = cspol.iterateClass(snum + off); iter.valid(); iter.advance()) { - if(!sample.getSample().contains(iter)) { - continue; // TODO: can we test more efficiently than this? - } - try { - double pos = proj.fastProjectDataToRenderSpace(relation.get(iter)) / Projection.SCALE; - histogram.aggregate(pos, inc); - } - catch(ObjectNotFoundException e) { - // Ignore. The object was probably deleted from the database - } + for (DBIDIter iditer = relation.iterDBIDs(); iditer.valid(); iditer.advance()) { + double pos = proj.fastProjectDataToRenderSpace(relation.get(iditer)) / Projection.SCALE; + histogram.increment(pos, inc); } } - } - else { - // Actual data distribution. - double[] inc = new double[cols]; - inc[0] = frac; - for(DBIDIter iditer = relation.iterDBIDs(); iditer.valid(); iditer.advance()) { - double pos = proj.fastProjectDataToRenderSpace(relation.get(iditer)) / Projection.SCALE; - histogram.aggregate(pos, inc); - } - } - // for scaling, get the maximum occurring value in the bins: - for(DoubleObjPair<double[]> bin : histogram) { - for(double val : bin.second) { - minmax.put(val); + // for scaling, get the maximum occurring value in the bins: + for (DoubleArrayStaticHistogram.Iter iter = histogram.iter(); iter.valid(); iter.advance()) { + for (double val : iter.getValue()) { + minmax.put(val); + } } - } - LinearScale yscale = new LinearScale(0, minmax.getMax()); - LinearScale xscale = new LinearScale(histogram.getCoverMinimum(), histogram.getCoverMaximum()); - - // Axis. TODO: Add an AxisVisualizer for this? - try { - SVGSimpleLinearAxis.drawAxis(svgp, layer, yscale, 0, ysize, 0, 0, SVGSimpleLinearAxis.LabelStyle.LEFTHAND, context.getStyleLibrary()); - - // draw axes that are non-trivial - final int dimensionality = DatabaseUtil.dimensionality(relation); - double orig = proj.fastProjectScaledToRender(new Vector(dimensionality)); - for(int d = 0; d < dimensionality; d++) { - Vector v = new Vector(dimensionality); - v.set(d, 1); - // projected endpoint of axis - double ax = proj.fastProjectScaledToRender(v); - if(ax != orig) { - final double left = (orig / Projection.SCALE + 0.5) * xsize; - final double right = (ax / Projection.SCALE + 0.5) * xsize; - SVGSimpleLinearAxis.drawAxis(svgp, layer, proj.getScale(d), left, ysize, right, ysize, SVGSimpleLinearAxis.LabelStyle.RIGHTHAND, context.getStyleLibrary()); + LinearScale yscale = new LinearScale(0, minmax.getMax()); + LinearScale xscale = new LinearScale(histogram.getCoverMinimum(), histogram.getCoverMaximum()); + + // Axis. TODO: Add an AxisVisualizer for this? + try { + SVGSimpleLinearAxis.drawAxis(svgp, layer, yscale, 0, ysize, 0, 0, SVGSimpleLinearAxis.LabelStyle.LEFTHAND, style.getStyleLibrary()); + + // draw axes that are non-trivial + final int dimensionality = RelationUtil.dimensionality(relation); + double orig = proj.fastProjectScaledToRender(new Vector(dimensionality)); + for (int d = 0; d < dimensionality; d++) { + Vector v = new Vector(dimensionality); + v.set(d, 1); + // projected endpoint of axis + double ax = proj.fastProjectScaledToRender(v); + if (ax < orig || ax > orig) { + final double left = (orig / Projection.SCALE + 0.5) * xsize; + final double right = (ax / Projection.SCALE + 0.5) * xsize; + SVGSimpleLinearAxis.drawAxis(svgp, layer, proj.getScale(d), left, ysize, right, ysize, SVGSimpleLinearAxis.LabelStyle.RIGHTHAND, style.getStyleLibrary()); + } } + } catch (CSSNamingConflict e) { + LoggingUtil.exception("CSS class exception in axis class.", e); } - } - catch(CSSNamingConflict e) { - LoggingUtil.exception("CSS class exception in axis class.", e); - } - double binwidth = histogram.getBinsize(); - // Visualizing - if(!curves) { - for(DoubleObjPair<double[]> bin : histogram) { - double lpos = xscale.getScaled(bin.first - binwidth / 2); - double rpos = xscale.getScaled(bin.first + binwidth / 2); - double stack = 0.0; - final int start = numc > 0 ? 1 : 0; - for(int key = start; key < cols; key++) { - double val = yscale.getScaled(bin.getSecond()[key]); - Element row = SVGUtil.svgRect(svgp.getDocument(), xsize * lpos, ysize * (1 - (val + stack)), xsize * (rpos - lpos), ysize * val); - stack = stack + val; - SVGUtil.addCSSClass(row, BIN + (off + key - 1)); - layer.appendChild(row); + // Visualizing + if (!settings.curves) { + for (DoubleArrayStaticHistogram.Iter iter = histogram.iter(); iter.valid(); iter.advance()) { + double lpos = xscale.getScaled(iter.getLeft()); + double rpos = xscale.getScaled(iter.getRight()); + double stack = 0.0; + final int start = numc > 0 ? 1 : 0; + for (int key = start; key < cols; key++) { + double val = yscale.getScaled(iter.getValue()[key]); + Element row = SVGUtil.svgRect(svgp.getDocument(), xsize * lpos, ysize * (1 - (val + stack)), xsize * (rpos - lpos), ysize * val); + stack = stack + val; + SVGUtil.addCSSClass(row, BIN + (off + key - 1)); + layer.appendChild(row); + } + } + } else { + double left = xscale.getScaled(histogram.getCoverMinimum()); + double right = left; + + SVGPath[] paths = new SVGPath[cols]; + double[] lasty = new double[cols]; + for (int i = 0; i < cols; i++) { + paths[i] = new SVGPath(xsize * left, ysize * 1); + lasty[i] = 0; } - } - } - else { - double left = xscale.getScaled(histogram.getCoverMinimum()); - double right = left; - - SVGPath[] paths = new SVGPath[cols]; - double[] lasty = new double[cols]; - for(int i = 0; i < cols; i++) { - paths[i] = new SVGPath(xsize * left, ysize * 1); - lasty[i] = 0; - } - // draw histogram lines - for(DoubleObjPair<double[]> bin : histogram) { - left = xscale.getScaled(bin.first - binwidth / 2); - right = xscale.getScaled(bin.first + binwidth / 2); - for(int i = 0; i < cols; i++) { - double val = yscale.getScaled(bin.getSecond()[i]); - if(lasty[i] != val) { - paths[i].lineTo(xsize * left, ysize * (1 - lasty[i])); - paths[i].lineTo(xsize * left, ysize * (1 - val)); - paths[i].lineTo(xsize * right, ysize * (1 - val)); - lasty[i] = val; + // draw histogram lines + for (DoubleArrayStaticHistogram.Iter iter = histogram.iter(); iter.valid(); iter.advance()) { + left = xscale.getScaled(iter.getLeft()); + right = xscale.getScaled(iter.getRight()); + for (int i = 0; i < cols; i++) { + double val = yscale.getScaled(iter.getValue()[i]); + if (lasty[i] > val || lasty[i] < val) { + paths[i].lineTo(xsize * left, ysize * (1 - lasty[i])); + paths[i].lineTo(xsize * left, ysize * (1 - val)); + paths[i].lineTo(xsize * right, ysize * (1 - val)); + lasty[i] = val; + } } } - } - // close and insert all lines. - for(int i = 0; i < cols; i++) { - if(lasty[i] != 0) { - paths[i].lineTo(xsize * right, ysize * (1 - lasty[i])); + // close and insert all lines. + for (int i = 0; i < cols; i++) { + if (lasty[i] != 0) { + paths[i].lineTo(xsize * right, ysize * (1 - lasty[i])); + } + paths[i].lineTo(xsize * right, ysize * 1); + Element elem = paths[i].makeElement(svgp); + SVGUtil.addCSSClass(elem, BIN + (off + i - 1)); + layer.appendChild(elem); } - paths[i].lineTo(xsize * right, ysize * 1); - Element elem = paths[i].makeElement(svgp); - SVGUtil.addCSSClass(elem, BIN + (off + i - 1)); - layer.appendChild(elem); } } - } - /** - * Generate the needed CSS classes. - * - * @param svgp Plot context - * @param numc Number of classes we need. - */ - private void setupCSS(SVGPlot svgp, int numc) { - ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT); + /** + * Generate the needed CSS classes. + * + * @param svgp Plot context + * @param numc Number of classes we need. + */ + private void setupCSS(SVGPlot svgp, int numc) { + ColorLibrary colors = style.getStyleLibrary().getColorSet(StyleLibrary.PLOT); + + CSSClass allInOne = new CSSClass(svgp, BIN + -1); + if (!settings.curves) { + allInOne.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_BLACK_VALUE); + allInOne.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 1.0); + } else { + allInOne.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_BLACK_VALUE); + allInOne.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getStyleLibrary().getLineWidth(StyleLibrary.PLOT)); + allInOne.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE); + } + svgp.addCSSClassOrLogError(allInOne); - CSSClass allInOne = new CSSClass(svgp, BIN + -1); - if(!curves) { - allInOne.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_BLACK_VALUE); - allInOne.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 1.0); - } - else { - allInOne.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_BLACK_VALUE); - allInOne.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT)); - allInOne.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE); - } - svgp.addCSSClassOrLogError(allInOne); + for (int clusterID = 0; clusterID < numc; clusterID++) { + CSSClass bin = new CSSClass(svgp, BIN + clusterID); - for(int clusterID = 0; clusterID < numc; clusterID++) { - CSSClass bin = new CSSClass(svgp, BIN + clusterID); + if (!settings.curves) { + bin.setStatement(SVGConstants.CSS_FILL_PROPERTY, colors.getColor(clusterID)); + } else { + bin.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(clusterID)); + bin.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getStyleLibrary().getLineWidth(StyleLibrary.PLOT)); + bin.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE); + } - if(!curves) { - bin.setStatement(SVGConstants.CSS_FILL_PROPERTY, colors.getColor(clusterID)); - } - else { - bin.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(clusterID)); - bin.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT)); - bin.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE); + svgp.addCSSClassOrLogError(bin); } - - svgp.addCSSClassOrLogError(bin); } } /** - * Visualizer factory for 1D histograms + * Parameterization class. * * @author Erich Schubert * - * @apiviz.stereotype factory - * @apiviz.uses ColoredHistogramVisualizer oneway - - «create» - * - * @param <NV> Number vector type + * @apiviz.exclude */ - public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory { + public static class Parameterizer extends AbstractParameterizer { /** * Flag to specify the "curves" rendering style. * @@ -352,7 +374,7 @@ public class ColoredHistogramVisualizer<NV extends NumberVector<NV, ?>> extends * Key: {@code -histogram.curves} * </p> */ - public static final OptionID STYLE_CURVES_ID = OptionID.getOrCreateOptionID("projhistogram.curves", "Use curves instead of the stacked histogram style."); + public static final OptionID STYLE_CURVES_ID = new OptionID("projhistogram.curves", "Use curves instead of the stacked histogram style."); /** * Parameter to specify the number of bins to use in histogram. @@ -361,98 +383,35 @@ public class ColoredHistogramVisualizer<NV extends NumberVector<NV, ?>> extends * Key: {@code -projhistogram.bins} Default: 20 * </p> */ - public static final OptionID HISTOGRAM_BINS_ID = OptionID.getOrCreateOptionID("projhistogram.bins", "Number of bins in the distribution histogram"); + public static final OptionID HISTOGRAM_BINS_ID = new OptionID("projhistogram.bins", "Number of bins in the distribution histogram"); /** * Internal storage of the curves flag. */ - private boolean curves; - - /** - * Number of bins to use in histogram. - */ - private static final int DEFAULT_BINS = 50; + protected boolean curves = false; /** * Number of bins to use in the histogram. */ - private int bins = DEFAULT_BINS; - - /** - * Constructor. - * - * @param curves - * @param bins - */ - public Factory(boolean curves, int bins) { - super(); - this.curves = curves; - this.bins = bins; - thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_STYLE; - } - - @Override - public Visualization makeVisualization(VisualizationTask task) { - return new ColoredHistogramVisualizer<NV>(task, curves, bins); - } + protected int bins = DEFAULT_BINS; @Override - public void processNewResult(HierarchicalResult baseResult, Result result) { - // Find a style result to visualize: - Collection<StyleResult> styleres = ResultUtil.filterResults(result, StyleResult.class); - for(StyleResult c : styleres) { - Collection<HistogramProjector<?>> ps = ResultUtil.filterResults(baseResult, HistogramProjector.class); - for(HistogramProjector<?> p : ps) { - // register self - final VisualizationTask task = new VisualizationTask(CNAME, c, p.getRelation(), this); - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA); - baseResult.getHierarchy().add(c, task); - baseResult.getHierarchy().add(p, task); - } + protected void makeOptions(Parameterization config) { + super.makeOptions(config); + Flag curvesF = new Flag(STYLE_CURVES_ID); + if (config.grab(curvesF)) { + curves = curvesF.isTrue(); + } + IntParameter binsP = new IntParameter(HISTOGRAM_BINS_ID, DEFAULT_BINS); + binsP.addConstraint(new GreaterEqualConstraint(2)); + if (config.grab(binsP)) { + bins = binsP.intValue(); } } @Override - public boolean allowThumbnails(VisualizationTask task) { - // Don't use thumbnails - return false; - } - - /** - * Parameterization class. - * - * @author Erich Schubert - * - * @apiviz.exclude - */ - public static class Parameterizer<NV extends NumberVector<NV, ?>> extends AbstractParameterizer { - /** - * Internal storage of the curves flag. - */ - private boolean curves; - - /** - * Number of bins to use in the histogram. - */ - private int bins = DEFAULT_BINS; - - @Override - protected void makeOptions(Parameterization config) { - super.makeOptions(config); - Flag STYLE_CURVES_FLAG = new Flag(STYLE_CURVES_ID); - if(config.grab(STYLE_CURVES_FLAG)) { - curves = STYLE_CURVES_FLAG.getValue(); - } - IntParameter HISTOGRAM_BINS_PARAM = new IntParameter(HISTOGRAM_BINS_ID, new GreaterEqualConstraint(2), DEFAULT_BINS); - if(config.grab(HISTOGRAM_BINS_PARAM)) { - bins = HISTOGRAM_BINS_PARAM.getValue(); - } - } - - @Override - protected Factory<NV> makeInstance() { - return new Factory<NV>(curves, bins); - } + protected ColoredHistogramVisualizer makeInstance() { + return new ColoredHistogramVisualizer(this); } } -}
\ No newline at end of file +} diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/AbstractOPTICSVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/AbstractOPTICSVisualization.java index a04a4f7f..329fc64a 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/AbstractOPTICSVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/AbstractOPTICSVisualization.java @@ -30,7 +30,7 @@ import org.apache.batik.util.SVGConstants; import de.lmu.ifi.dbs.elki.distance.distancevalue.Distance; import de.lmu.ifi.dbs.elki.result.optics.ClusterOrderEntry; import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; -import de.lmu.ifi.dbs.elki.visualization.projector.OPTICSProjector; +import de.lmu.ifi.dbs.elki.visualization.projections.OPTICSProjection; import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil; import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisualization; @@ -40,7 +40,7 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisualization; * * @author Erich Schubert * - * @apiviz.uses OPTICSProjector + * @apiviz.uses OPTICSProjection * * @param <D> */ @@ -48,7 +48,7 @@ public abstract class AbstractOPTICSVisualization<D extends Distance<D>> extends /** * The plot */ - final protected OPTICSProjector<D> optics; + final protected OPTICSProjection<D> optics; /** * Width of plot (in display units) @@ -67,7 +67,7 @@ public abstract class AbstractOPTICSVisualization<D extends Distance<D>> extends */ public AbstractOPTICSVisualization(VisualizationTask task) { super(task); - this.optics = task.getResult(); + this.optics = task.getProj(); } /** @@ -76,9 +76,9 @@ public abstract class AbstractOPTICSVisualization<D extends Distance<D>> extends protected void makeLayerElement() { plotwidth = StyleLibrary.SCALE; plotheight = StyleLibrary.SCALE / optics.getOPTICSPlot(context).getRatio(); - final double margin = context.getStyleLibrary().getSize(StyleLibrary.MARGIN); + final double margin = context.getStyleResult().getStyleLibrary().getSize(StyleLibrary.MARGIN); layer = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG); - final String transform = SVGUtil.makeMarginTransform(task.getWidth(), task.getHeight(), plotwidth, plotheight, margin / 2); + final String transform = SVGUtil.makeMarginTransform(task.getWidth(), task.getHeight(), plotwidth, plotheight, margin * .5); SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSClusterVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSClusterVisualization.java index 2369588b..e771e380 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSClusterVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSClusterVisualization.java @@ -55,15 +55,14 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * * @author Erich Schubert * - * @apiviz.uses Clustering oneway - - «visualizes» - * - * @param <D> Distance type (actually unused) + * @apiviz.stereotype factory + * @apiviz.uses Instance oneway - - «create» */ -public class OPTICSClusterVisualization<D extends Distance<D>> extends AbstractOPTICSVisualization<D> { +public class OPTICSClusterVisualization extends AbstractVisFactory { /** * The logger for this class. */ - private static final Logging logger = Logging.getLogger(OPTICSClusterVisualization.class); + private static final Logging LOG = Logging.getLogger(OPTICSClusterVisualization.class); /** * A short name characterizing this Visualizer. @@ -71,30 +70,37 @@ public class OPTICSClusterVisualization<D extends Distance<D>> extends AbstractO private static final String NAME = "OPTICS Cluster Ranges"; /** - * CSS class for markers + * Constructor, adhering to + * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} */ - protected static final String CSS_BRACKET = "opticsBracket"; + public OPTICSClusterVisualization() { + super(); + } - /** - * Optics clustering we visualize - */ - public static final String CLUSTERING = "OPTICSClustering"; + @Override + public void processNewResult(HierarchicalResult baseResult, Result result) { + Collection<OPTICSProjector<?>> ops = ResultUtil.filterResults(result, OPTICSProjector.class); + for(OPTICSProjector<?> p : ops) { + final Clustering<OPTICSModel> ocl = findOPTICSClustering(baseResult); + if(ocl != null) { + final VisualizationTask task = new VisualizationTask(NAME, ocl, null, this); + task.level = VisualizationTask.LEVEL_DATA; + baseResult.getHierarchy().add(p, task); + } + } + // TODO: also run when a new clustering is added, instead of just new + // projections? + } - /** - * Our clustering - */ - Clustering<OPTICSModel> clus; + @Override + public Visualization makeVisualization(VisualizationTask task) { + return new Instance<DoubleDistance>(task); + } - /** - * Constructor. - * - * @param task Visualization task - */ - public OPTICSClusterVisualization(VisualizationTask task) { - super(task); - this.clus = task.getGenerics(CLUSTERING, Clustering.class); - context.addResultListener(this); - incrementalRedraw(); + @Override + public boolean allowThumbnails(VisualizationTask task) { + // Don't use thumbnails + return false; } /** @@ -115,118 +121,109 @@ public class OPTICSClusterVisualization<D extends Distance<D>> extends AbstractO if(firstcluster.getModel() instanceof OPTICSModel) { return (Clustering<OPTICSModel>) clus; } - } catch(Exception e) { + } + catch(Exception e) { // Empty clustering? Shouldn't happen. - logger.warning("Clustering with no cluster detected.", e); + LOG.warning("Clustering with no cluster detected.", e); } } return null; } - @Override - protected void redraw() { - makeLayerElement(); - addCSSClasses(); - - ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT); - HashMap<Cluster<?>, String> colormap = new HashMap<Cluster<?>, String>(); - int cnum = 0; - for (Cluster<?> c : clus.getAllClusters()) { - colormap.put(c, colors.getColor(cnum)); - cnum++; - } - drawClusters(clus.getToplevelClusters(), 1, colormap); - } - /** - * Recursively draw clusters - * - * @param clusters Current set of clusters - * @param depth Recursion depth - * @param colormap Color mapping - */ - private void drawClusters(List<Cluster<OPTICSModel>> clusters, int depth, Map<Cluster<?>,String> colormap) { - final double scale = StyleLibrary.SCALE; - - for(Cluster<OPTICSModel> cluster : clusters) { - try { - OPTICSModel model = cluster.getModel(); - final double x1 = plotwidth * ((model.getStartIndex() + .25) / this.optics.getResult().getClusterOrder().size()); - final double x2 = plotwidth * ((model.getEndIndex() + .75) / this.optics.getResult().getClusterOrder().size()); - final double y = plotheight + depth * scale * 0.01; - Element e = svgp.svgLine(x1, y, x2, y); - SVGUtil.addCSSClass(e, CSS_BRACKET); - String color = colormap.get(cluster); - if (color != null) { - SVGUtil.setAtt(e, SVGConstants.SVG_STYLE_ATTRIBUTE, SVGConstants.CSS_STROKE_PROPERTY+":"+color); - } - layer.appendChild(e); - } - catch(ClassCastException e) { - logger.warning("Expected OPTICSModel, got: " + cluster.getModel().getClass().getSimpleName()); - } - // Descend - final List<Cluster<OPTICSModel>> children = cluster.getChildren(); - if(children != null) { - drawClusters(children, depth + 1, colormap); - } - } - } - - /** - * Adds the required CSS-Classes - */ - private void addCSSClasses() { - // Class for the markers - if(!svgp.getCSSClassManager().contains(CSS_BRACKET)) { - final CSSClass cls = new CSSClass(this, CSS_BRACKET); - cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, context.getStyleLibrary().getColor(StyleLibrary.PLOT)); - cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT)); - svgp.addCSSClassOrLogError(cls); - } - } - - /** - * Factory class for OPTICS plot selections. + * Instance. * * @author Erich Schubert * - * @apiviz.stereotype factory - * @apiviz.uses OPTICSPlotSelectionVisualization oneway - - «create» + * @apiviz.uses Clustering oneway - - «visualizes» + * + * @param <D> Distance type (actually unused) */ - public static class Factory extends AbstractVisFactory { + public class Instance<D extends Distance<D>> extends AbstractOPTICSVisualization<D> { + /** + * CSS class for markers + */ + protected static final String CSS_BRACKET = "opticsBracket"; + + /** + * Our clustering + */ + Clustering<OPTICSModel> clus; + /** - * Constructor, adhering to - * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} + * Constructor. + * + * @param task Visualization task */ - public Factory() { - super(); + public Instance(VisualizationTask task) { + super(task); + this.clus = task.getResult(); + context.addResultListener(this); + incrementalRedraw(); } @Override - public void processNewResult(HierarchicalResult baseResult, Result result) { - Collection<OPTICSProjector<?>> ops = ResultUtil.filterResults(result, OPTICSProjector.class); - for(OPTICSProjector<?> p : ops) { - final Clustering<OPTICSModel> ocl = findOPTICSClustering(baseResult); - if(ocl != null) { - final VisualizationTask task = new VisualizationTask(NAME, p, null, this); - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA); - task.put(CLUSTERING, ocl); - baseResult.getHierarchy().add(p, task); - } + protected void redraw() { + makeLayerElement(); + addCSSClasses(); + + ColorLibrary colors = context.getStyleResult().getStyleLibrary().getColorSet(StyleLibrary.PLOT); + HashMap<Cluster<?>, String> colormap = new HashMap<Cluster<?>, String>(); + int cnum = 0; + for(Cluster<?> c : clus.getAllClusters()) { + colormap.put(c, colors.getColor(cnum)); + cnum++; } - // TODO: also run when a new clustering is added, instead of just new projections? + drawClusters(clus.getToplevelClusters(), 1, colormap); } - @Override - public Visualization makeVisualization(VisualizationTask task) { - return new OPTICSClusterVisualization<DoubleDistance>(task); + /** + * Recursively draw clusters + * + * @param clusters Current set of clusters + * @param depth Recursion depth + * @param colormap Color mapping + */ + private void drawClusters(List<Cluster<OPTICSModel>> clusters, int depth, Map<Cluster<?>, String> colormap) { + final double scale = StyleLibrary.SCALE; + + for(Cluster<OPTICSModel> cluster : clusters) { + try { + OPTICSModel model = cluster.getModel(); + final double x1 = plotwidth * ((model.getStartIndex() + .25) / this.optics.getResult().getClusterOrder().size()); + final double x2 = plotwidth * ((model.getEndIndex() + .75) / this.optics.getResult().getClusterOrder().size()); + final double y = plotheight + depth * scale * 0.01; + Element e = svgp.svgLine(x1, y, x2, y); + SVGUtil.addCSSClass(e, CSS_BRACKET); + String color = colormap.get(cluster); + if(color != null) { + SVGUtil.setAtt(e, SVGConstants.SVG_STYLE_ATTRIBUTE, SVGConstants.CSS_STROKE_PROPERTY + ":" + color); + } + layer.appendChild(e); + } + catch(ClassCastException e) { + LOG.warning("Expected OPTICSModel, got: " + cluster.getModel().getClass().getSimpleName()); + } + // Descend + final List<Cluster<OPTICSModel>> children = cluster.getChildren(); + if(children != null) { + drawClusters(children, depth + 1, colormap); + } + } } - @Override - public boolean allowThumbnails(VisualizationTask task) { - // Don't use thumbnails - return false; + /** + * Adds the required CSS-Classes + */ + private void addCSSClasses() { + // Class for the markers + if(!svgp.getCSSClassManager().contains(CSS_BRACKET)) { + final CSSClass cls = new CSSClass(this, CSS_BRACKET); + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, style.getColor(StyleLibrary.PLOT)); + cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT)); + svgp.addCSSClassOrLogError(cls); + } } } }
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotCutVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotCutVisualization.java index 2bd5c63a..739e0ccd 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotCutVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotCutVisualization.java @@ -52,247 +52,250 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; /**
* Visualizes a cut in an OPTICS Plot to select an Epsilon value and generate a
- * new clustering result
+ * new clustering result.
*
* @author Heidi Kolb
+ * @author Erich Schubert
*
- * @param <D> distance type
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class OPTICSPlotCutVisualization<D extends Distance<D>> extends AbstractOPTICSVisualization<D> implements DragableArea.DragListener {
+public class OPTICSPlotCutVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
private static final String NAME = "OPTICS Cut";
- /**
- * CSS-Styles
- */
- protected static final String CSS_LINE = "opticsPlotLine";
-
- /**
- * CSS-Styles
- */
- protected final static String CSS_EPSILON = "opticsPlotEpsilonValue";
-
- /**
- * The current epsilon value.
- */
- private double epsilon = 0.0;
-
- /**
- * Sensitive (clickable) area
- */
- private DragableArea eventarea = null;
-
- /**
- * The label element
- */
- private Element elemText = null;
-
- /**
- * The line element
- */
- private Element elementLine = null;
-
- /**
- * The drag handle element
- */
- private Element elementPoint = null;
-
- /**
- * Constructor.
- *
- * @param task Task
- */
- public OPTICSPlotCutVisualization(VisualizationTask task) {
- super(task);
+ public OPTICSPlotCutVisualization() {
+ super();
}
@Override
- protected void redraw() {
- incrementalRedraw();
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<OPTICSProjector<?>> ops = ResultUtil.filterResults(result, OPTICSProjector.class);
+ for(OPTICSProjector<?> p : ops) {
+ final VisualizationTask task = new VisualizationTask(NAME, p, null, this);
+ task.level = VisualizationTask.LEVEL_INTERACTIVE;
+ baseResult.getHierarchy().add(p, task);
+ }
}
@Override
- protected void incrementalRedraw() {
- if(layer == null) {
- makeLayerElement();
- addCSSClasses();
- }
-
- // TODO make the number of digits configurable
- final String label = (epsilon != 0.0) ? FormatUtil.format(epsilon, 4) : "";
- // compute absolute y-value of bar
- final double yAct = plotheight - getYFromEpsilon(epsilon);
-
- if(elemText == null) {
- elemText = svgp.svgText(StyleLibrary.SCALE * 1.05, yAct, label);
- SVGUtil.setAtt(elemText, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_EPSILON);
- layer.appendChild(elemText);
- }
- else {
- elemText.setTextContent(label);
- SVGUtil.setAtt(elemText, SVGConstants.SVG_Y_ATTRIBUTE, yAct);
- }
-
- // line and handle
- if(elementLine == null) {
- elementLine = svgp.svgLine(0, yAct, StyleLibrary.SCALE * 1.04, yAct);
- SVGUtil.addCSSClass(elementLine, CSS_LINE);
- layer.appendChild(elementLine);
- }
- else {
- SVGUtil.setAtt(elementLine, SVG12Constants.SVG_Y1_ATTRIBUTE, yAct);
- SVGUtil.setAtt(elementLine, SVG12Constants.SVG_Y2_ATTRIBUTE, yAct);
- }
- if(elementPoint == null) {
- elementPoint = svgp.svgCircle(StyleLibrary.SCALE * 1.04, yAct, StyleLibrary.SCALE * 0.004);
- SVGUtil.addCSSClass(elementPoint, CSS_LINE);
- layer.appendChild(elementPoint);
- }
- else {
- SVGUtil.setAtt(elementPoint, SVG12Constants.SVG_CY_ATTRIBUTE, yAct);
- }
-
- if(eventarea == null) {
- eventarea = new DragableArea(svgp, StyleLibrary.SCALE, 0, StyleLibrary.SCALE * 0.1, plotheight, this);
- layer.appendChild(eventarea.getElement());
- }
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance<DoubleDistance>(task);
}
@Override
- public void destroy() {
- super.destroy();
- eventarea.destroy();
+ public boolean allowThumbnails(VisualizationTask task) {
+ // Don't use thumbnails
+ return false;
}
/**
- * Get epsilon from y-value
+ * Instance.
*
- * @param y y-Value
- * @return epsilon
- */
- protected double getEpsilonFromY(double y) {
- if(y < 0) {
- y = 0;
- }
- if(y > plotheight) {
- y = plotheight;
- }
- return optics.getOPTICSPlot(context).getScale().getUnscaled(y / plotheight);
- }
-
- /**
- * Get y-value from epsilon
+ * @author Heidi Kolb
+ * @author Erich Schubert
*
- * @param epsilon epsilon
- * @return y-Value
+ * @param <D> distance type
*/
- protected double getYFromEpsilon(double epsilon) {
- double y = optics.getOPTICSPlot(context).getScale().getScaled(epsilon) * plotheight;
- if(y < 0) {
- y = 0;
+ public class Instance<D extends Distance<D>> extends AbstractOPTICSVisualization<D> implements DragableArea.DragListener {
+ /**
+ * CSS-Styles
+ */
+ protected static final String CSS_LINE = "opticsPlotLine";
+
+ /**
+ * CSS-Styles
+ */
+ protected static final String CSS_EPSILON = "opticsPlotEpsilonValue";
+
+ /**
+ * The current epsilon value.
+ */
+ private double epsilon = 0.0;
+
+ /**
+ * Sensitive (clickable) area
+ */
+ private DragableArea eventarea = null;
+
+ /**
+ * The label element
+ */
+ private Element elemText = null;
+
+ /**
+ * The line element
+ */
+ private Element elementLine = null;
+
+ /**
+ * The drag handle element
+ */
+ private Element elementPoint = null;
+
+ /**
+ * Constructor.
+ *
+ * @param task Task
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
}
- if(y > plotheight) {
- y = plotheight;
+
+ @Override
+ protected void redraw() {
+ incrementalRedraw();
}
- return y;
- }
- @Override
- public boolean startDrag(SVGPoint start, Event evt) {
- epsilon = getEpsilonFromY(plotheight - start.getY());
- // opvis.unsetEpsilonExcept(this);
- synchronizedRedraw();
- return true;
- }
+ @Override
+ protected void incrementalRedraw() {
+ if(layer == null) {
+ makeLayerElement();
+ addCSSClasses();
+ }
- @Override
- public boolean duringDrag(SVGPoint start, SVGPoint end, Event evt, boolean inside) {
- if(inside) {
- epsilon = getEpsilonFromY(plotheight - end.getY());
- }
- // opvis.unsetEpsilonExcept(this);
- synchronizedRedraw();
- return true;
- }
+ // TODO make the number of digits configurable
+ final String label = (epsilon > 0.0) ? FormatUtil.format(epsilon, 4) : "";
+ // compute absolute y-value of bar
+ final double yAct = plotheight - getYFromEpsilon(epsilon);
- @Override
- public boolean endDrag(SVGPoint start, SVGPoint end, Event evt, boolean inside) {
- if(inside) {
- epsilon = getEpsilonFromY(plotheight - end.getY());
- // opvis.unsetEpsilonExcept(this);
+ if(elemText == null) {
+ elemText = svgp.svgText(StyleLibrary.SCALE * 1.05, yAct, label);
+ SVGUtil.setAtt(elemText, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_EPSILON);
+ layer.appendChild(elemText);
+ }
+ else {
+ elemText.setTextContent(label);
+ SVGUtil.setAtt(elemText, SVGConstants.SVG_Y_ATTRIBUTE, yAct);
+ }
- // FIXME: replace an existing optics cut result!
- final ClusterOrderResult<D> order = optics.getResult();
- Clustering<Model> cl = OPTICSCut.makeOPTICSCut(order, optics.getOPTICSPlot(context).getDistanceAdapter(), epsilon);
- order.addChildResult(cl);
+ // line and handle
+ if(elementLine == null) {
+ elementLine = svgp.svgLine(0, yAct, StyleLibrary.SCALE * 1.04, yAct);
+ SVGUtil.addCSSClass(elementLine, CSS_LINE);
+ layer.appendChild(elementLine);
+ }
+ else {
+ SVGUtil.setAtt(elementLine, SVG12Constants.SVG_Y1_ATTRIBUTE, yAct);
+ SVGUtil.setAtt(elementLine, SVG12Constants.SVG_Y2_ATTRIBUTE, yAct);
+ }
+ if(elementPoint == null) {
+ elementPoint = svgp.svgCircle(StyleLibrary.SCALE * 1.04, yAct, StyleLibrary.SCALE * 0.004);
+ SVGUtil.addCSSClass(elementPoint, CSS_LINE);
+ layer.appendChild(elementPoint);
+ }
+ else {
+ SVGUtil.setAtt(elementPoint, SVG12Constants.SVG_CY_ATTRIBUTE, yAct);
+ }
+
+ if(eventarea == null) {
+ eventarea = new DragableArea(svgp, StyleLibrary.SCALE, 0, StyleLibrary.SCALE * 0.1, plotheight, this);
+ layer.appendChild(eventarea.getElement());
+ }
}
- context.getHierarchy().resultChanged(this.task);
- // synchronizedRedraw();
- return true;
- }
- /**
- * Reset the epsilon value.
- */
- public void unsetEpsilon() {
- epsilon = 0.0;
- }
+ @Override
+ public void destroy() {
+ super.destroy();
+ eventarea.destroy();
+ }
- /**
- * Adds the required CSS-Classes
- */
- private void addCSSClasses() {
- // Class for the epsilon-value
- if(!svgp.getCSSClassManager().contains(CSS_EPSILON)) {
- final CSSClass label = new CSSClass(svgp, CSS_EPSILON);
- label.setStatement(SVGConstants.CSS_FILL_PROPERTY, context.getStyleLibrary().getTextColor(StyleLibrary.AXIS_LABEL));
- label.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, context.getStyleLibrary().getFontFamily(StyleLibrary.AXIS_LABEL));
- label.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, context.getStyleLibrary().getTextSize(StyleLibrary.AXIS_LABEL));
- svgp.addCSSClassOrLogError(label);
+ /**
+ * Get epsilon from y-value
+ *
+ * @param y y-Value
+ * @return epsilon
+ */
+ protected double getEpsilonFromY(double y) {
+ if(y < 0) {
+ y = 0;
+ }
+ if(y > plotheight) {
+ y = plotheight;
+ }
+ return optics.getOPTICSPlot(context).getScale().getUnscaled(y / plotheight);
}
- // Class for the epsilon cut line
- if(!svgp.getCSSClassManager().contains(CSS_LINE)) {
- final CSSClass lcls = new CSSClass(svgp, CSS_LINE);
- lcls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, context.getStyleLibrary().getColor(StyleLibrary.PLOT));
- lcls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, 0.5 * context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT));
- svgp.addCSSClassOrLogError(lcls);
+
+ /**
+ * Get y-value from epsilon
+ *
+ * @param epsilon epsilon
+ * @return y-Value
+ */
+ protected double getYFromEpsilon(double epsilon) {
+ double y = optics.getOPTICSPlot(context).getScale().getScaled(epsilon) * plotheight;
+ if(y < 0) {
+ y = 0;
+ }
+ if(y > plotheight) {
+ y = plotheight;
+ }
+ return y;
}
- }
- /**
- * Factory class
- *
- * @author Erich Schubert
- *
- * @apiviz.stereotype factory
- * @apiviz.uses OPTICSPlotCutVisualization oneway - - «create»
- */
- public static class Factory extends AbstractVisFactory {
- public Factory() {
- super();
+ @Override
+ public boolean startDrag(SVGPoint start, Event evt) {
+ epsilon = getEpsilonFromY(plotheight - start.getY());
+ // opvis.unsetEpsilonExcept(this);
+ synchronizedRedraw();
+ return true;
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<OPTICSProjector<?>> ops = ResultUtil.filterResults(result, OPTICSProjector.class);
- for(OPTICSProjector<?> p : ops) {
- final VisualizationTask task = new VisualizationTask(NAME, p, null, this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE);
- baseResult.getHierarchy().add(p, task);
+ public boolean duringDrag(SVGPoint start, SVGPoint end, Event evt, boolean inside) {
+ if(inside) {
+ epsilon = getEpsilonFromY(plotheight - end.getY());
}
+ // opvis.unsetEpsilonExcept(this);
+ synchronizedRedraw();
+ return true;
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new OPTICSPlotCutVisualization<DoubleDistance>(task);
+ public boolean endDrag(SVGPoint start, SVGPoint end, Event evt, boolean inside) {
+ if(inside) {
+ epsilon = getEpsilonFromY(plotheight - end.getY());
+ // opvis.unsetEpsilonExcept(this);
+
+ // FIXME: replace an existing optics cut result!
+ final ClusterOrderResult<D> order = optics.getResult();
+ Clustering<Model> cl = OPTICSCut.makeOPTICSCut(order, optics.getOPTICSPlot(context).getDistanceAdapter(), epsilon);
+ order.addChildResult(cl);
+ }
+ context.getHierarchy().resultChanged(this.task);
+ // synchronizedRedraw();
+ return true;
}
- @Override
- public boolean allowThumbnails(VisualizationTask task) {
- // Don't use thumbnails
- return false;
+ /**
+ * Reset the epsilon value.
+ */
+ public void unsetEpsilon() {
+ epsilon = 0.0;
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ */
+ private void addCSSClasses() {
+ // Class for the epsilon-value
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ if(!svgp.getCSSClassManager().contains(CSS_EPSILON)) {
+ final CSSClass label = new CSSClass(svgp, CSS_EPSILON);
+ label.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getTextColor(StyleLibrary.AXIS_LABEL));
+ label.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, style.getFontFamily(StyleLibrary.AXIS_LABEL));
+ label.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, style.getTextSize(StyleLibrary.AXIS_LABEL));
+ svgp.addCSSClassOrLogError(label);
+ }
+ // Class for the epsilon cut line
+ if(!svgp.getCSSClassManager().contains(CSS_LINE)) {
+ final CSSClass lcls = new CSSClass(svgp, CSS_LINE);
+ lcls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, style.getColor(StyleLibrary.PLOT));
+ lcls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, 0.5 * style.getLineWidth(StyleLibrary.PLOT));
+ svgp.addCSSClassOrLogError(lcls);
+ }
}
}
}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotSelectionVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotSelectionVisualization.java index 87b24df2..bd2d0946 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotSelectionVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotSelectionVisualization.java @@ -56,17 +56,16 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; /**
* Handle the marker in an OPTICS plot.
*
- * @author Heidi Kolb
+ * @author Erich Schubert
*
- * @apiviz.uses DBIDSelection oneway - 1 visualizes
- *
- * @param <D> distance type
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class OPTICSPlotSelectionVisualization<D extends Distance<D>> extends AbstractOPTICSVisualization<D> implements DragableArea.DragListener {
+public class OPTICSPlotSelectionVisualization extends AbstractVisFactory {
/**
* The logger for this class.
*/
- private static final Logging logger = Logging.getLogger(OPTICSPlotSelectionVisualization.class);
+ private static final Logging LOG = Logging.getLogger(OPTICSPlotSelectionVisualization.class);
/**
* A short name characterizing this Visualizer.
@@ -74,16 +73,6 @@ public class OPTICSPlotSelectionVisualization<D extends Distance<D>> extends Abs private static final String NAME = "OPTICS Selection";
/**
- * CSS class for markers
- */
- protected static final String CSS_MARKER = "opticsPlotMarker";
-
- /**
- * CSS class for markers
- */
- protected static final String CSS_RANGEMARKER = "opticsPlotRangeMarker";
-
- /**
* Input modes
*
* @apiviz.exclude
@@ -94,278 +83,290 @@ public class OPTICSPlotSelectionVisualization<D extends Distance<D>> extends Abs }
/**
- * Element for the events
- */
- private Element etag;
-
- /**
- * Element for the marker
- */
- private Element mtag;
-
- /**
- * Constructor.
- *
- * @param task Visualization task
- */
- public OPTICSPlotSelectionVisualization(VisualizationTask task) {
- super(task);
- context.addResultListener(this);
- incrementalRedraw();
- }
-
- @Override
- protected void redraw() {
- makeLayerElement();
- addCSSClasses();
-
- mtag = svgp.svgElement(SVGConstants.SVG_G_TAG);
- addMarker();
-
- DragableArea drag = new DragableArea(svgp, 0 - plotwidth * 0.1, 0, plotwidth * 1.1, plotheight, this);
- etag = drag.getElement();
- // mtag first, etag must be the top Element
- layer.appendChild(mtag);
- layer.appendChild(etag);
- }
-
- /**
- * Add marker for the selected IDs to mtag
- */
- public void addMarker() {
- List<ClusterOrderEntry<D>> order = getClusterOrder();
- // TODO: replace mtag!
- DBIDSelection selContext = context.getSelection();
- if(selContext != null) {
- DBIDs selection = DBIDUtil.ensureSet(selContext.getSelectedIds());
-
- final double width = plotwidth / order.size();
- int begin = -1;
- for(int j = 0; j < order.size(); j++) {
- DBID id = order.get(j).getID();
- if(selection.contains(id)) {
- if(begin == -1) {
- begin = j;
- }
- }
- else {
- if(begin != -1) {
- Element marker = addMarkerRect(begin * width, (j - begin) * width);
- SVGUtil.addCSSClass(marker, CSS_MARKER);
- mtag.appendChild(marker);
- begin = -1;
- }
- }
- }
- // tail
- if(begin != -1) {
- Element marker = addMarkerRect(begin * width, (order.size() - begin) * width);
- SVGUtil.addCSSClass(marker, CSS_MARKER);
- mtag.appendChild(marker);
- }
- }
- }
-
- /**
- * Create a rectangle as marker (Marker higher than plot!)
- *
- * @param x1 X-Value for the marker
- * @param width Width of an entry
- * @return SVG-Element svg-rectangle
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
*/
- public Element addMarkerRect(double x1, double width) {
- return svgp.svgRect(x1, 0, width, plotheight);
+ public OPTICSPlotSelectionVisualization() {
+ super();
}
@Override
- public boolean startDrag(SVGPoint startPoint, Event evt) {
- List<ClusterOrderEntry<D>> order = getClusterOrder();
- int mouseActIndex = getSelectedIndex(order, startPoint);
- if(mouseActIndex >= 0 && mouseActIndex < order.size()) {
- double width = plotwidth / order.size();
- double x1 = mouseActIndex * width;
- Element marker = addMarkerRect(x1, width);
- SVGUtil.setCSSClass(marker, CSS_RANGEMARKER);
- mtag.appendChild(marker);
- return true;
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<OPTICSProjector<?>> ops = ResultUtil.filterResults(result, OPTICSProjector.class);
+ for(OPTICSProjector<?> p : ops) {
+ final VisualizationTask task = new VisualizationTask(NAME, p, null, this);
+ task.level = VisualizationTask.LEVEL_INTERACTIVE;
+ baseResult.getHierarchy().add(p, task);
}
- return false;
}
@Override
- public boolean duringDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
- List<ClusterOrderEntry<D>> order = getClusterOrder();
- int mouseDownIndex = getSelectedIndex(order, startPoint);
- int mouseActIndex = getSelectedIndex(order, dragPoint);
- final int begin = Math.max(Math.min(mouseDownIndex, mouseActIndex), 0);
- final int end = Math.min(Math.max(mouseDownIndex, mouseActIndex), order.size());
- double width = plotwidth / order.size();
- double x1 = begin * width;
- double x2 = (end * width) + width;
- mtag.removeChild(mtag.getLastChild());
- Element marker = addMarkerRect(x1, x2 - x1);
- SVGUtil.setCSSClass(marker, CSS_RANGEMARKER);
- mtag.appendChild(marker);
- return true;
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance<DoubleDistance>(task);
}
@Override
- public boolean endDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
- List<ClusterOrderEntry<D>> order = getClusterOrder();
- int mouseDownIndex = getSelectedIndex(order, startPoint);
- int mouseActIndex = getSelectedIndex(order, dragPoint);
- Mode mode = getInputMode(evt);
- final int begin = Math.max(Math.min(mouseDownIndex, mouseActIndex), 0);
- final int end = Math.min(Math.max(mouseDownIndex, mouseActIndex), order.size());
- updateSelection(mode, begin, end);
- return true;
+ public boolean allowThumbnails(VisualizationTask task) {
+ // Don't use thumbnails
+ return false;
}
/**
- * Get the current input mode, on each mouse event.
+ * Instance.
*
- * @param evt Mouse event.
- * @return Input mode
- */
- private Mode getInputMode(Event evt) {
- if(evt instanceof DOMMouseEvent) {
- DOMMouseEvent domme = (DOMMouseEvent) evt;
- // TODO: visual indication of mode possible?
- if(domme.getShiftKey()) {
- return Mode.ADD;
- }
- else if(domme.getCtrlKey()) {
- return Mode.INVERT;
- }
- else {
- return Mode.REPLACE;
- }
- }
- // Default mode is replace.
- return Mode.REPLACE;
- }
-
- /**
- * Gets the Index of the ClusterOrderEntry where the event occurred
+ * @author Heidi Kolb
+ * @author Erich Schubert
*
- * @param order List of ClusterOrderEntries
- * @param cPt clicked point
- * @return Index of the object
- */
- private int getSelectedIndex(List<ClusterOrderEntry<D>> order, SVGPoint cPt) {
- int mouseActIndex = (int) ((cPt.getX() / plotwidth) * order.size());
- return mouseActIndex;
- }
-
- /**
- * Updates the selection for the given ClusterOrderEntry.
+ * @apiviz.uses DBIDSelection oneway - 1 visualizes
*
- * @param mode Input mode
- * @param begin first index to select
- * @param end last index to select
+ * @param <D> distance type
*/
- protected void updateSelection(Mode mode, int begin, int end) {
- List<ClusterOrderEntry<D>> order = getClusterOrder();
- if(begin < 0 || begin > end || end >= order.size()) {
- logger.warning("Invalid range in updateSelection: " + begin + " .. " + end);
- return;
- }
+ public class Instance<D extends Distance<D>> extends AbstractOPTICSVisualization<D> implements DragableArea.DragListener {
+ /**
+ * CSS class for markers
+ */
+ protected static final String CSS_MARKER = "opticsPlotMarker";
- DBIDSelection selContext = context.getSelection();
- HashSetModifiableDBIDs selection;
- if(selContext == null || mode == Mode.REPLACE) {
- selection = DBIDUtil.newHashSet();
+ /**
+ * CSS class for markers
+ */
+ protected static final String CSS_RANGEMARKER = "opticsPlotRangeMarker";
+
+ /**
+ * Element for the events
+ */
+ private Element etag;
+
+ /**
+ * Element for the marker
+ */
+ private Element mtag;
+
+ /**
+ * Constructor.
+ *
+ * @param task Visualization task
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ context.addResultListener(this);
+ incrementalRedraw();
}
- else {
- selection = DBIDUtil.newHashSet(selContext.getSelectedIds());
+
+ @Override
+ protected void redraw() {
+ makeLayerElement();
+ addCSSClasses();
+
+ mtag = svgp.svgElement(SVGConstants.SVG_G_TAG);
+ addMarker();
+
+ DragableArea drag = new DragableArea(svgp, 0 - plotwidth * 0.1, 0, plotwidth * 1.1, plotheight, this);
+ etag = drag.getElement();
+ // mtag first, etag must be the top Element
+ layer.appendChild(mtag);
+ layer.appendChild(etag);
}
- for(int i = begin; i <= end; i++) {
- DBID id = order.get(i).getID();
- if(mode == Mode.INVERT) {
- if(!selection.contains(id)) {
- selection.add(id);
+ /**
+ * Add marker for the selected IDs to mtag
+ */
+ public void addMarker() {
+ List<ClusterOrderEntry<D>> order = getClusterOrder();
+ // TODO: replace mtag!
+ DBIDSelection selContext = context.getSelection();
+ if(selContext != null) {
+ DBIDs selection = DBIDUtil.ensureSet(selContext.getSelectedIds());
+
+ final double width = plotwidth / order.size();
+ int begin = -1;
+ for(int j = 0; j < order.size(); j++) {
+ DBID id = order.get(j).getID();
+ if(selection.contains(id)) {
+ if(begin == -1) {
+ begin = j;
+ }
+ }
+ else {
+ if(begin != -1) {
+ Element marker = addMarkerRect(begin * width, (j - begin) * width);
+ SVGUtil.addCSSClass(marker, CSS_MARKER);
+ mtag.appendChild(marker);
+ begin = -1;
+ }
+ }
}
- else {
- selection.remove(id);
+ // tail
+ if(begin != -1) {
+ Element marker = addMarkerRect(begin * width, (order.size() - begin) * width);
+ SVGUtil.addCSSClass(marker, CSS_MARKER);
+ mtag.appendChild(marker);
}
}
- else {
- // In REPLACE and ADD, add objects.
- // The difference was done before by not re-using the selection.
- // Since we are using a set, we can just add in any case.
- selection.add(id);
+ }
+
+ /**
+ * Create a rectangle as marker (Marker higher than plot!)
+ *
+ * @param x1 X-Value for the marker
+ * @param width Width of an entry
+ * @return SVG-Element svg-rectangle
+ */
+ public Element addMarkerRect(double x1, double width) {
+ return svgp.svgRect(x1, 0, width, plotheight);
+ }
+
+ @Override
+ public boolean startDrag(SVGPoint startPoint, Event evt) {
+ List<ClusterOrderEntry<D>> order = getClusterOrder();
+ int mouseActIndex = getSelectedIndex(order, startPoint);
+ if(mouseActIndex >= 0 && mouseActIndex < order.size()) {
+ double width = plotwidth / order.size();
+ double x1 = mouseActIndex * width;
+ Element marker = addMarkerRect(x1, width);
+ SVGUtil.setCSSClass(marker, CSS_RANGEMARKER);
+ mtag.appendChild(marker);
+ return true;
}
+ return false;
}
- context.setSelection(new DBIDSelection(selection));
- }
- /**
- * Adds the required CSS-Classes
- */
- private void addCSSClasses() {
- // Class for the markers
- if(!svgp.getCSSClassManager().contains(CSS_MARKER)) {
- final CSSClass cls = new CSSClass(this, CSS_MARKER);
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_BLUE_VALUE);
- cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, "0.2");
- svgp.addCSSClassOrLogError(cls);
+ @Override
+ public boolean duringDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
+ List<ClusterOrderEntry<D>> order = getClusterOrder();
+ int mouseDownIndex = getSelectedIndex(order, startPoint);
+ int mouseActIndex = getSelectedIndex(order, dragPoint);
+ final int begin = Math.max(Math.min(mouseDownIndex, mouseActIndex), 0);
+ final int end = Math.min(Math.max(mouseDownIndex, mouseActIndex), order.size());
+ double width = plotwidth / order.size();
+ double x1 = begin * width;
+ double x2 = (end * width) + width;
+ mtag.removeChild(mtag.getLastChild());
+ Element marker = addMarkerRect(x1, x2 - x1);
+ SVGUtil.setCSSClass(marker, CSS_RANGEMARKER);
+ mtag.appendChild(marker);
+ return true;
}
- // Class for the range marking
- if(!svgp.getCSSClassManager().contains(CSS_RANGEMARKER)) {
- final CSSClass rcls = new CSSClass(this, CSS_RANGEMARKER);
- rcls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_RED_VALUE);
- rcls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, "0.2");
- svgp.addCSSClassOrLogError(rcls);
+ @Override
+ public boolean endDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
+ List<ClusterOrderEntry<D>> order = getClusterOrder();
+ int mouseDownIndex = getSelectedIndex(order, startPoint);
+ int mouseActIndex = getSelectedIndex(order, dragPoint);
+ Mode mode = getInputMode(evt);
+ final int begin = Math.max(Math.min(mouseDownIndex, mouseActIndex), 0);
+ final int end = Math.min(Math.max(mouseDownIndex, mouseActIndex), order.size());
+ updateSelection(mode, begin, end);
+ return true;
}
- }
- @Override
- public void resultChanged(Result current) {
- if(current instanceof SelectionResult) {
- synchronizedRedraw();
- return;
+ /**
+ * Get the current input mode, on each mouse event.
+ *
+ * @param evt Mouse event.
+ * @return Input mode
+ */
+ private Mode getInputMode(Event evt) {
+ if(evt instanceof DOMMouseEvent) {
+ DOMMouseEvent domme = (DOMMouseEvent) evt;
+ // TODO: visual indication of mode possible?
+ if(domme.getShiftKey()) {
+ return Mode.ADD;
+ }
+ else if(domme.getCtrlKey()) {
+ return Mode.INVERT;
+ }
+ else {
+ return Mode.REPLACE;
+ }
+ }
+ // Default mode is replace.
+ return Mode.REPLACE;
}
- super.resultChanged(current);
- }
- /**
- * Factory class for OPTICS plot selections.
- *
- * @author Erich Schubert
- *
- * @apiviz.stereotype factory
- * @apiviz.uses OPTICSPlotSelectionVisualization oneway - - «create»
- */
- public static class Factory extends AbstractVisFactory {
/**
- * Constructor, adhering to
- * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ * Gets the Index of the ClusterOrderEntry where the event occurred
+ *
+ * @param order List of ClusterOrderEntries
+ * @param cPt clicked point
+ * @return Index of the object
*/
- public Factory() {
- super();
+ private int getSelectedIndex(List<ClusterOrderEntry<D>> order, SVGPoint cPt) {
+ int mouseActIndex = (int) ((cPt.getX() / plotwidth) * order.size());
+ return mouseActIndex;
}
- @Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<OPTICSProjector<?>> ops = ResultUtil.filterResults(result, OPTICSProjector.class);
- for(OPTICSProjector<?> p : ops) {
- final VisualizationTask task = new VisualizationTask(NAME, p, null, this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE);
- baseResult.getHierarchy().add(p, task);
+ /**
+ * Updates the selection for the given ClusterOrderEntry.
+ *
+ * @param mode Input mode
+ * @param begin first index to select
+ * @param end last index to select
+ */
+ protected void updateSelection(Mode mode, int begin, int end) {
+ List<ClusterOrderEntry<D>> order = getClusterOrder();
+ if(begin < 0 || begin > end || end >= order.size()) {
+ LOG.warning("Invalid range in updateSelection: " + begin + " .. " + end);
+ return;
+ }
+
+ DBIDSelection selContext = context.getSelection();
+ HashSetModifiableDBIDs selection;
+ if(selContext == null || mode == Mode.REPLACE) {
+ selection = DBIDUtil.newHashSet();
+ }
+ else {
+ selection = DBIDUtil.newHashSet(selContext.getSelectedIds());
}
+
+ for(int i = begin; i <= end; i++) {
+ DBID id = order.get(i).getID();
+ if(mode == Mode.INVERT) {
+ if(!selection.contains(id)) {
+ selection.add(id);
+ }
+ else {
+ selection.remove(id);
+ }
+ }
+ else {
+ // In REPLACE and ADD, add objects.
+ // The difference was done before by not re-using the selection.
+ // Since we are using a set, we can just add in any case.
+ selection.add(id);
+ }
+ }
+ context.setSelection(new DBIDSelection(selection));
}
- @Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new OPTICSPlotSelectionVisualization<DoubleDistance>(task);
+ /**
+ * Adds the required CSS-Classes
+ */
+ private void addCSSClasses() {
+ // Class for the markers
+ if(!svgp.getCSSClassManager().contains(CSS_MARKER)) {
+ final CSSClass cls = new CSSClass(this, CSS_MARKER);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_BLUE_VALUE);
+ cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, "0.2");
+ svgp.addCSSClassOrLogError(cls);
+ }
+
+ // Class for the range marking
+ if(!svgp.getCSSClassManager().contains(CSS_RANGEMARKER)) {
+ final CSSClass rcls = new CSSClass(this, CSS_RANGEMARKER);
+ rcls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_RED_VALUE);
+ rcls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, "0.2");
+ svgp.addCSSClassOrLogError(rcls);
+ }
}
@Override
- public boolean allowThumbnails(VisualizationTask task) {
- // Don't use thumbnails
- return false;
+ public void resultChanged(Result current) {
+ if(current instanceof SelectionResult) {
+ synchronizedRedraw();
+ return;
+ }
+ super.resultChanged(current);
}
}
}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotVisualizer.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotVisualizer.java index d93c98cb..cde2d89d 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotVisualizer.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSPlotVisualizer.java @@ -38,6 +38,7 @@ import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; import de.lmu.ifi.dbs.elki.visualization.css.CSSClassManager.CSSNamingConflict; import de.lmu.ifi.dbs.elki.visualization.opticsplot.OPTICSPlot; import de.lmu.ifi.dbs.elki.visualization.projector.OPTICSProjector; +import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; import de.lmu.ifi.dbs.elki.visualization.svg.SVGSimpleLinearAxis; import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil; import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisFactory; @@ -48,87 +49,88 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * * @author Erich Schubert * - * @param <D> Distance type + * @apiviz.stereotype factory + * @apiviz.uses Instance oneway - - «create» */ -public class OPTICSPlotVisualizer<D extends Distance<D>> extends AbstractOPTICSVisualization<D> { +public class OPTICSPlotVisualizer extends AbstractVisFactory { /** * Name for this visualizer. */ private static final String NAME = "OPTICS Plot"; /** - * Constructor. - * - * @param task Visualization task + * Constructor, adhering to + * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} */ - public OPTICSPlotVisualizer(VisualizationTask task) { - super(task); + public OPTICSPlotVisualizer() { + super(); } @Override - protected void redraw() { - makeLayerElement(); - // addCSSClasses(); - - OPTICSPlot<D> opticsplot = optics.getOPTICSPlot(context); - String ploturi = opticsplot.getSVGPlotURI(); - - Element itag = svgp.svgElement(SVGConstants.SVG_IMAGE_TAG); - SVGUtil.setAtt(itag, SVGConstants.SVG_IMAGE_RENDERING_ATTRIBUTE, SVGConstants.SVG_OPTIMIZE_SPEED_VALUE); - SVGUtil.setAtt(itag, SVGConstants.SVG_X_ATTRIBUTE, 0); - SVGUtil.setAtt(itag, SVGConstants.SVG_Y_ATTRIBUTE, 0); - SVGUtil.setAtt(itag, SVGConstants.SVG_WIDTH_ATTRIBUTE, plotwidth); - SVGUtil.setAtt(itag, SVGConstants.SVG_HEIGHT_ATTRIBUTE, plotheight); - itag.setAttributeNS(SVGConstants.XLINK_NAMESPACE_URI, SVGConstants.XLINK_HREF_QNAME, ploturi); - - layer.appendChild(itag); - - try { - SVGSimpleLinearAxis.drawAxis(svgp, layer, opticsplot.getScale(), 0, plotheight, 0, 0, SVGSimpleLinearAxis.LabelStyle.LEFTHAND, context.getStyleLibrary()); - SVGSimpleLinearAxis.drawAxis(svgp, layer, opticsplot.getScale(), plotwidth, plotheight, plotwidth, 0, SVGSimpleLinearAxis.LabelStyle.RIGHTHAND, context.getStyleLibrary()); - } - catch(CSSNamingConflict e) { - LoggingUtil.exception("CSS naming conflict for axes on OPTICS plot", e); + public void processNewResult(HierarchicalResult baseResult, Result result) { + Collection<OPTICSProjector<?>> ops = ResultUtil.filterResults(result, OPTICSProjector.class); + for(OPTICSProjector<?> p : ops) { + // Add plots, attach visualizer + final VisualizationTask task = new VisualizationTask(NAME, p, null, this); + task.level = VisualizationTask.LEVEL_DATA; + baseResult.getHierarchy().add(p, task); } } + @Override + public Visualization makeVisualization(VisualizationTask task) { + return new Instance<DoubleDistance>(task); + } + + @Override + public boolean allowThumbnails(VisualizationTask task) { + // Don't use thumbnails + return false; + } + /** - * Factory class for OPTICS plot. + * Instance. * * @author Erich Schubert * - * @apiviz.stereotype factory - * @apiviz.uses OPTICSPlotVisualizer oneway - - «create» + * @param <D> Distance type */ - public static class Factory extends AbstractVisFactory { + public class Instance<D extends Distance<D>> extends AbstractOPTICSVisualization<D> { /** - * Constructor, adhering to - * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} + * Constructor. + * + * @param task Visualization task */ - public Factory() { - super(); + public Instance(VisualizationTask task) { + super(task); } @Override - public void processNewResult(HierarchicalResult baseResult, Result result) { - Collection<OPTICSProjector<?>> ops = ResultUtil.filterResults(result, OPTICSProjector.class); - for(OPTICSProjector<?> p : ops) { - // Add plots, attach visualizer - final VisualizationTask task = new VisualizationTask(NAME, p, null, this); - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA); - baseResult.getHierarchy().add(p, task); + protected void redraw() { + makeLayerElement(); + // addCSSClasses(); + + OPTICSPlot<D> opticsplot = optics.getOPTICSPlot(context); + String ploturi = opticsplot.getSVGPlotURI(); + + Element itag = svgp.svgElement(SVGConstants.SVG_IMAGE_TAG); + SVGUtil.setAtt(itag, SVGConstants.SVG_IMAGE_RENDERING_ATTRIBUTE, SVGConstants.SVG_OPTIMIZE_SPEED_VALUE); + SVGUtil.setAtt(itag, SVGConstants.SVG_X_ATTRIBUTE, 0); + SVGUtil.setAtt(itag, SVGConstants.SVG_Y_ATTRIBUTE, 0); + SVGUtil.setAtt(itag, SVGConstants.SVG_WIDTH_ATTRIBUTE, plotwidth); + SVGUtil.setAtt(itag, SVGConstants.SVG_HEIGHT_ATTRIBUTE, plotheight); + itag.setAttributeNS(SVGConstants.XLINK_NAMESPACE_URI, SVGConstants.XLINK_HREF_QNAME, ploturi); + + layer.appendChild(itag); + + try { + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + SVGSimpleLinearAxis.drawAxis(svgp, layer, opticsplot.getScale(), 0, plotheight, 0, 0, SVGSimpleLinearAxis.LabelStyle.LEFTHAND, style); + SVGSimpleLinearAxis.drawAxis(svgp, layer, opticsplot.getScale(), plotwidth, plotheight, plotwidth, 0, SVGSimpleLinearAxis.LabelStyle.RIGHTHAND, style); + } + catch(CSSNamingConflict e) { + LoggingUtil.exception("CSS naming conflict for axes on OPTICS plot", e); } - } - - @Override - public Visualization makeVisualization(VisualizationTask task) { - return new OPTICSPlotVisualizer<DoubleDistance>(task); - } - - @Override - public boolean allowThumbnails(VisualizationTask task) { - // Don't use thumbnails - return false; } } }
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSSteepAreaVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSSteepAreaVisualization.java index 4e557a74..e6e00960 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSSteepAreaVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/optics/OPTICSSteepAreaVisualization.java @@ -56,40 +56,47 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * * @author Erich Schubert * - * @apiviz.uses - * de.lmu.ifi.dbs.elki.algorithm.clustering.OPTICSXi.SteepAreaResult + * @apiviz.stereotype factory + * @apiviz.uses Instance oneway - - «create» */ -public class OPTICSSteepAreaVisualization<D extends Distance<D>> extends AbstractOPTICSVisualization<D> { +public class OPTICSSteepAreaVisualization extends AbstractVisFactory { /** * A short name characterizing this Visualizer. */ private static final String NAME = "OPTICS Steep Areas"; /** - * CSS class for markers + * Constructor, adhering to + * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} */ - protected static final String CSS_STEEP_UP = "opticsSteepUp"; + public OPTICSSteepAreaVisualization() { + super(); + } - /** - * CSS class for markers - */ - protected static final String CSS_STEEP_DOWN = "opticsSteepDown"; + @Override + public void processNewResult(HierarchicalResult baseResult, Result result) { + Collection<OPTICSProjector<?>> ops = ResultUtil.filterResults(result, OPTICSProjector.class); + for(OPTICSProjector<?> p : ops) { + final SteepAreaResult steep = findSteepAreaResult(p.getResult()); + if(steep != null) { + final VisualizationTask task = new VisualizationTask(NAME, p, null, this); + task.level = VisualizationTask.LEVEL_DATA + 1; + task.initDefaultVisibility(false); + baseResult.getHierarchy().add(p, task); + baseResult.getHierarchy().add(steep, task); + } + } + } - /** - * Our clustering - */ - OPTICSXi.SteepAreaResult areas; + @Override + public Visualization makeVisualization(VisualizationTask task) { + return new Instance<DoubleDistance>(task); + } - /** - * Constructor. - * - * @param task Visualization task - */ - public OPTICSSteepAreaVisualization(VisualizationTask task) { - super(task); - this.areas = findSteepAreaResult(this.optics.getResult()); - context.addResultListener(this); - incrementalRedraw(); + @Override + public boolean allowThumbnails(VisualizationTask task) { + // Don't use thumbnails + return false; } /** @@ -107,116 +114,110 @@ public class OPTICSSteepAreaVisualization<D extends Distance<D>> extends Abstrac return null; } - @Override - protected void redraw() { - makeLayerElement(); - addCSSClasses(); - - final OPTICSPlot<D> opticsplot = optics.getOPTICSPlot(context); - final List<ClusterOrderEntry<D>> co = getClusterOrder(); - final OPTICSDistanceAdapter<D> adapter = opticsplot.getDistanceAdapter(); - final LinearScale scale = opticsplot.getScale(); - - for(OPTICSXi.SteepArea area : areas) { - final int st = area.getStartIndex(); - final int en = area.getEndIndex(); - // Note: make sure we are using doubles! - final double x1 = (st + .25) / co.size(); - final double x2 = (en + .75) / co.size(); - final double d1 = adapter.getDoubleForEntry(co.get(st)); - final double d2 = adapter.getDoubleForEntry(co.get(en)); - final double y1 = (!Double.isInfinite(d1) && !Double.isNaN(d1)) ? (1. - scale.getScaled(d1)) : 0.; - final double y2 = (!Double.isInfinite(d2) && !Double.isNaN(d2)) ? (1. - scale.getScaled(d2)) : 0.; - Element e = svgp.svgLine(plotwidth * x1, plotheight * y1, plotwidth * x2, plotheight * y2); - if(area instanceof OPTICSXi.SteepDownArea) { - SVGUtil.addCSSClass(e, CSS_STEEP_DOWN); - } - else { - SVGUtil.addCSSClass(e, CSS_STEEP_UP); - } - layer.appendChild(e); - } - } - /** - * Adds the required CSS-Classes - */ - private void addCSSClasses() { - // Class for the markers - if(!svgp.getCSSClassManager().contains(CSS_STEEP_DOWN)) { - final CSSClass cls = new CSSClass(this, CSS_STEEP_DOWN); - Color color = SVGUtil.stringToColor(context.getStyleLibrary().getColor(StyleLibrary.PLOT)); - if(color == null) { - color = Color.BLACK; - } - color = new Color((int) (color.getRed() * 0.8), (int) (color.getGreen() * 0.8 + 0.2 * 256), (int) (color.getBlue() * 0.8)); - cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGUtil.colorToString(color)); - cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT)); - svgp.addCSSClassOrLogError(cls); - } - if(!svgp.getCSSClassManager().contains(CSS_STEEP_UP)) { - final CSSClass cls = new CSSClass(this, CSS_STEEP_UP); - Color color = SVGUtil.stringToColor(context.getStyleLibrary().getColor(StyleLibrary.PLOT)); - if(color == null) { - color = Color.BLACK; - } - color = new Color((int) (color.getRed() * 0.8 + 0.2 * 256), (int) (color.getGreen() * 0.8), (int) (color.getBlue() * 0.8)); - cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGUtil.colorToString(color)); - cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT)); - svgp.addCSSClassOrLogError(cls); - } - } - - @Override - public void resultChanged(Result current) { - if(current instanceof SelectionResult) { - synchronizedRedraw(); - return; - } - super.resultChanged(current); - } - - /** - * Factory class for OPTICS plot selections. + * Instance * * @author Erich Schubert * - * @apiviz.stereotype factory - * @apiviz.uses OPTICSPlotSelectionVisualization oneway - - «create» + * @apiviz.uses + * de.lmu.ifi.dbs.elki.algorithm.clustering.OPTICSXi.SteepAreaResult */ - public static class Factory extends AbstractVisFactory { + public class Instance<D extends Distance<D>> extends AbstractOPTICSVisualization<D> { + /** + * CSS class for markers + */ + protected static final String CSS_STEEP_UP = "opticsSteepUp"; + + /** + * CSS class for markers + */ + protected static final String CSS_STEEP_DOWN = "opticsSteepDown"; + /** - * Constructor, adhering to - * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} + * Our clustering */ - public Factory() { - super(); + OPTICSXi.SteepAreaResult areas; + + /** + * Constructor. + * + * @param task Visualization task + */ + public Instance(VisualizationTask task) { + super(task); + this.areas = findSteepAreaResult(this.optics.getResult()); + context.addResultListener(this); + incrementalRedraw(); } @Override - public void processNewResult(HierarchicalResult baseResult, Result result) { - Collection<OPTICSProjector<?>> ops = ResultUtil.filterResults(result, OPTICSProjector.class); - for(OPTICSProjector<?> p : ops) { - final SteepAreaResult steep = findSteepAreaResult(p.getResult()); - if(steep != null) { - final VisualizationTask task = new VisualizationTask(NAME, p, null, this); - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA + 1); - task.put(VisualizationTask.META_VISIBLE_DEFAULT, false); - baseResult.getHierarchy().add(p, task); - baseResult.getHierarchy().add(steep, task); + protected void redraw() { + makeLayerElement(); + addCSSClasses(); + + final OPTICSPlot<D> opticsplot = optics.getOPTICSPlot(context); + final List<ClusterOrderEntry<D>> co = getClusterOrder(); + final OPTICSDistanceAdapter<D> adapter = opticsplot.getDistanceAdapter(); + final LinearScale scale = opticsplot.getScale(); + + for(OPTICSXi.SteepArea area : areas) { + final int st = area.getStartIndex(); + final int en = area.getEndIndex(); + // Note: make sure we are using doubles! + final double x1 = (st + .25) / co.size(); + final double x2 = (en + .75) / co.size(); + final double d1 = adapter.getDoubleForEntry(co.get(st)); + final double d2 = adapter.getDoubleForEntry(co.get(en)); + final double y1 = (!Double.isInfinite(d1) && !Double.isNaN(d1)) ? (1. - scale.getScaled(d1)) : 0.; + final double y2 = (!Double.isInfinite(d2) && !Double.isNaN(d2)) ? (1. - scale.getScaled(d2)) : 0.; + Element e = svgp.svgLine(plotwidth * x1, plotheight * y1, plotwidth * x2, plotheight * y2); + if(area instanceof OPTICSXi.SteepDownArea) { + SVGUtil.addCSSClass(e, CSS_STEEP_DOWN); } + else { + SVGUtil.addCSSClass(e, CSS_STEEP_UP); + } + layer.appendChild(e); } } - @Override - public Visualization makeVisualization(VisualizationTask task) { - return new OPTICSSteepAreaVisualization<DoubleDistance>(task); + /** + * Adds the required CSS-Classes + */ + private void addCSSClasses() { + // Class for the markers + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + if(!svgp.getCSSClassManager().contains(CSS_STEEP_DOWN)) { + final CSSClass cls = new CSSClass(this, CSS_STEEP_DOWN); + Color color = SVGUtil.stringToColor(style.getColor(StyleLibrary.PLOT)); + if(color == null) { + color = Color.BLACK; + } + color = new Color((int) (color.getRed() * 0.8), (int) (color.getGreen() * 0.8 + 0.2 * 256.), (int) (color.getBlue() * 0.8)); + cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGUtil.colorToString(color)); + cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT)); + svgp.addCSSClassOrLogError(cls); + } + if(!svgp.getCSSClassManager().contains(CSS_STEEP_UP)) { + final CSSClass cls = new CSSClass(this, CSS_STEEP_UP); + Color color = SVGUtil.stringToColor(style.getColor(StyleLibrary.PLOT)); + if(color == null) { + color = Color.BLACK; + } + color = new Color((int) (color.getRed() * 0.8 + 0.2 * 256.), (int) (color.getGreen() * 0.8), (int) (color.getBlue() * 0.8)); + cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGUtil.colorToString(color)); + cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT)); + svgp.addCSSClassOrLogError(cls); + } } @Override - public boolean allowThumbnails(VisualizationTask task) { - // Don't use thumbnails - return false; + public void resultChanged(Result current) { + if(current instanceof SelectionResult) { + synchronizedRedraw(); + return; + } + super.resultChanged(current); } } }
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/package-info.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/package-info.java index 208d8cb7..d5dad85b 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/package-info.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/package-info.java @@ -2,7 +2,8 @@ * <p>Visualizers for various results</p> * * @apiviz.exclude de.lmu.ifi.dbs.elki.utilities.datastructures.AnyMap - * @apiviz.exclude de.lmu.ifi.dbs.elki.visualization.visualizers\.vis.*\..* + * @apiviz.exclude Visualization.Factory + * @apiviz.exclude de.lmu.ifi.dbs.elki.visualization.gui.* */ /* This file is part of ELKI: @@ -26,4 +27,4 @@ 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.visualizers;
\ No newline at end of file +package de.lmu.ifi.dbs.elki.visualization.visualizers; diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/pairsegments/CircleSegmentsVisualizer.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/pairsegments/CircleSegmentsVisualizer.java index f0655ab6..400b8ebb 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/pairsegments/CircleSegmentsVisualizer.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/pairsegments/CircleSegmentsVisualizer.java @@ -73,655 +73,667 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualizati * In: Proc. 28th International Conference on Data Engineering (ICDE) 2012 * </p> * + * <p> + * Details on the experimental setup can be found at: <a + * href="http://elki.dbs.ifi.lmu.de/wiki/Examples/ClusterEvaluation" + * >wiki/Examples/ClusterEvaluation</a> + * </p> + * * @author Sascha Goldhofer * @author Erich Schubert * * @apiviz.landmark * - * @apiviz.uses Segments - * @apiviz.has SegmentsStylingPolicy + * @apiviz.stereotype factory + * @apiviz.uses Instance oneway - - «create» */ -@Reference(title = "Evaluation of Clusterings – Metrics and Visual Support", authors = "Elke Achtert, Sascha Goldhofer, Hans-Peter Kriegel, Erich Schubert, Arthur Zimek", booktitle = "Proc. 28th International Conference on Data Engineering (ICDE) 2012", url = "http://elki.dbs.ifi.lmu.de/wiki/PairSegments") -public class CircleSegmentsVisualizer extends AbstractVisualization implements ResultListener { +@Reference(title = "Evaluation of Clusterings – Metrics and Visual Support", authors = "Elke Achtert, Sascha Goldhofer, Hans-Peter Kriegel, Erich Schubert, Arthur Zimek", booktitle = "Proc. 28th International Conference on Data Engineering (ICDE) 2012", url = "http://dx.doi.org/10.1109/ICDE.2012.128") +public class CircleSegmentsVisualizer extends AbstractVisFactory { /** * Class logger */ - private static final Logging logger = Logging.getLogger(CircleSegmentsVisualizer.class); + private static final Logging LOG = Logging.getLogger(CircleSegmentsVisualizer.class); /** * CircleSegments visualizer name */ private static final String NAME = "CircleSegments"; - /** Minimum width (radian) of Segment */ - private final static double SEGMENT_MIN_ANGLE = 0.01; + /** + * Constructor + */ + public CircleSegmentsVisualizer() { + super(); + this.thumbmask |= ThumbnailVisualization.ON_STYLE; + } - /** Gap (radian) between segments */ - private final static double SEGMENT_MIN_SEP_ANGLE = 0.005; + @Override + public Visualization makeVisualization(VisualizationTask task) { + return new Instance(task); + } - /** Offset from center to first ring */ - private final static double RADIUS_INNER = 0.04 * StyleLibrary.SCALE; + @Override + public void processNewResult(HierarchicalResult baseResult, Result result) { + // If no comparison result found abort + List<Segments> segments = ResultUtil.filterResults(result, Segments.class); + for(Segments segmentResult : segments) { + SegmentsStylingPolicy policy; + List<SegmentsStylingPolicy> styles = ResultUtil.filterResults(segmentResult, SegmentsStylingPolicy.class); + if(!styles.isEmpty()) { + policy = styles.get(0); + } + else { + policy = new SegmentsStylingPolicy(segmentResult); + baseResult.getHierarchy().add(segmentResult, policy); + } + // create task for visualization + final VisualizationTask task = new VisualizationTask(NAME, policy, null, this); + task.width = 2.0; + task.height = 2.0; + task.level = VisualizationTask.LEVEL_INTERACTIVE; + baseResult.getHierarchy().add(segmentResult, task); + } + } - /** Margin between two rings */ - private final static double RADIUS_DISTANCE = 0.01 * StyleLibrary.SCALE; + /** + * Instance + * + * @author Sascha Goldhofer + * @author Erich Schubert + * + * @apiviz.uses Segments + * @apiviz.has SegmentsStylingPolicy + * + */ + public class Instance extends AbstractVisualization implements ResultListener { + /** Minimum width (radian) of Segment */ + private static final double SEGMENT_MIN_ANGLE = 0.01; - /** Radius of whole CircleSegments except selection border */ - private final static double RADIUS_OUTER = 0.47 * StyleLibrary.SCALE; + /** Gap (radian) between segments */ + private static final double SEGMENT_MIN_SEP_ANGLE = 0.005; - /** Radius of highlight selection (outer ring) */ - private final static double RADIUS_SELECTION = 0.02 * StyleLibrary.SCALE; + /** Offset from center to first ring */ + private static final double RADIUS_INNER = 0.04 * StyleLibrary.SCALE; - /** - * CSS class name for the clusterings. - */ - private static final String CLR_CLUSTER_CLASS_PREFIX = "clusterSegment"; + /** Margin between two rings */ + private static final double RADIUS_DISTANCE = 0.01 * StyleLibrary.SCALE; - /** - * CSS border class of a cluster - */ - public static final String CLR_BORDER_CLASS = "clusterBorder"; + /** Radius of whole CircleSegments except selection border */ + private static final double RADIUS_OUTER = 0.47 * StyleLibrary.SCALE; - /** - * CSS hover class for clusters of hovered segment - */ - public static final String CLR_UNPAIRED_CLASS = "clusterUnpaired"; + /** Radius of highlight selection (outer ring) */ + private static final double RADIUS_SELECTION = 0.02 * StyleLibrary.SCALE; - /** - * CSS hover class of a segment cluster - */ - public static final String CLR_HOVER_CLASS = "clusterHover"; + /** + * CSS class name for the clusterings. + */ + private static final String CLR_CLUSTER_CLASS_PREFIX = "clusterSegment"; - /** - * CSS class of selected Segment - */ - public static final String SEG_UNPAIRED_SELECTED_CLASS = "unpairedSegmentSelected"; + /** + * CSS border class of a cluster + */ + public static final String CLR_BORDER_CLASS = "clusterBorder"; - /** - * Style prefix - */ - public static final String STYLE = "segments"; + /** + * CSS hover class for clusters of hovered segment + */ + public static final String CLR_UNPAIRED_CLASS = "clusterUnpaired"; - /** - * Style for border lines - */ - public static final String STYLE_BORDER = STYLE + ".border"; + /** + * CSS hover class of a segment cluster + */ + public static final String CLR_HOVER_CLASS = "clusterHover"; - /** - * Style for hover effect - */ - public static final String STYLE_HOVER = STYLE + ".hover"; + /** + * CSS class of selected Segment + */ + public static final String SEG_UNPAIRED_SELECTED_CLASS = "unpairedSegmentSelected"; - /** - * First color for producing segment-cluster colors - */ - public static final String STYLE_GRADIENT_FIRST = STYLE + ".cluster.first"; + /** + * Style prefix + */ + public static final String STYLE = "segments"; - /** - * Second color for producing segment-cluster colors - */ - public static final String STYLE_GRADIENT_SECOND = STYLE + ".cluster.second"; + /** + * Style for border lines + */ + public static final String STYLE_BORDER = STYLE + ".border"; - /** - * Segmentation of Clusterings - */ - protected final Segments segments; + /** + * Style for hover effect + */ + public static final String STYLE_HOVER = STYLE + ".hover"; - /** - * The two main layers - */ - private Element visLayer, ctrlLayer; + /** + * First color for producing segment-cluster colors + */ + public static final String STYLE_GRADIENT_FIRST = STYLE + ".cluster.first"; - /** - * Map to connect segments to their visual elements - */ - public Map<Segment, List<Element>> segmentToElements = new HashMap<Segment, List<Element>>(); + /** + * Second color for producing segment-cluster colors + */ + public static final String STYLE_GRADIENT_SECOND = STYLE + ".cluster.second"; - /** - * Show unclustered Pairs in CircleSegments - */ - boolean showUnclusteredPairs = false; + /** + * Segmentation of Clusterings + */ + protected final Segments segments; - /** - * Styling policy - */ - protected final SegmentsStylingPolicy policy; + /** + * The two main layers + */ + private Element visLayer, ctrlLayer; - /** - * Flag to disallow an incremental redraw - */ - private boolean noIncrementalRedraw = true; + /** + * Map to connect segments to their visual elements + */ + public Map<Segment, List<Element>> segmentToElements = new HashMap<Segment, List<Element>>(); - /** - * Constructor - */ - public CircleSegmentsVisualizer(VisualizationTask task) { - super(task); - policy = task.getResult(); - segments = policy.segments; - // FIXME: handle this more generally. - policy.setStyleLibrary(context.getStyleLibrary()); - // Listen for result changes (Selection changed) - context.addResultListener(this); - } + /** + * Show unclustered Pairs in CircleSegments + */ + boolean showUnclusteredPairs = false; - public void toggleUnclusteredPairs(boolean show) { - noIncrementalRedraw = true; - showUnclusteredPairs = show; - synchronizedRedraw(); - } + /** + * Styling policy + */ + protected final SegmentsStylingPolicy policy; - @Override - public void resultChanged(Result current) { - super.resultChanged(current); - // Redraw on style result changes. - if(current == context.getStyleResult()) { - // When switching to a different policy, unhighlight segments. - if(context.getStyleResult().getStylingPolicy() != policy) { - policy.deselectAllSegments(); - } - synchronizedRedraw(); + /** + * Flag to disallow an incremental redraw + */ + private boolean noIncrementalRedraw = true; + + /** + * Constructor + */ + public Instance(VisualizationTask task) { + super(task); + policy = task.getResult(); + segments = policy.segments; + // FIXME: handle this more generally. + policy.setStyleLibrary(context.getStyleResult().getStyleLibrary()); + // Listen for result changes (Selection changed) + context.addResultListener(this); } - } - @Override - protected void incrementalRedraw() { - if(noIncrementalRedraw) { - super.incrementalRedraw(); + public void toggleUnclusteredPairs(boolean show) { + noIncrementalRedraw = true; + showUnclusteredPairs = show; + synchronizedRedraw(); } - else { - redrawSelection(); + + @Override + public void resultChanged(Result current) { + super.resultChanged(current); + // Redraw on style result changes. + if(current == context.getStyleResult()) { + // When switching to a different policy, unhighlight segments. + if(context.getStyleResult().getStylingPolicy() != policy) { + policy.deselectAllSegments(); + } + synchronizedRedraw(); + } } - } - @Override - public void redraw() { - logger.debug("Full redraw"); - noIncrementalRedraw = false; // Done that. - - // initialize css (needs clusterSize!) - addCSSClasses(segments.getHighestClusterCount()); - - layer = svgp.svgElement(SVGConstants.SVG_G_TAG); - visLayer = svgp.svgElement(SVGConstants.SVG_G_TAG); - // Setup scaling for canvas: 0 to StyleLibrary.SCALE (usually 100 to avoid a - // Java drawing bug!) - String transform = SVGUtil.makeMarginTransform(task.width, task.height, StyleLibrary.SCALE, StyleLibrary.SCALE, 0) + " translate(" + (StyleLibrary.SCALE * .5) + " " + (StyleLibrary.SCALE * .5) + ")"; - visLayer.setAttribute(SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); - ctrlLayer = svgp.svgElement(SVGConstants.SVG_G_TAG); - - // and create svg elements - drawSegments(); - - // - // Build Interface - // - SVGCheckbox checkbox = new SVGCheckbox(showUnclusteredPairs, "Show unclustered pairs"); - checkbox.addCheckBoxListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - toggleUnclusteredPairs(((SVGCheckbox) e.getSource()).isChecked()); + @Override + protected void incrementalRedraw() { + if(noIncrementalRedraw) { + super.incrementalRedraw(); + } + else { + redrawSelection(); } - }); + } - // Add ring:clustering info - Element clrInfo = drawClusteringInfo(); - Element c = checkbox.renderCheckBox(svgp, 1, 5 + Double.parseDouble(clrInfo.getAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE)), 11); - ctrlLayer.appendChild(clrInfo); - ctrlLayer.appendChild(c); + @Override + public void redraw() { + LOG.debug("Full redraw"); + noIncrementalRedraw = false; // Done that. - ctrlLayer.setAttribute(SVGConstants.SVG_TRANSFORM_ATTRIBUTE, "scale(" + (0.25 / StyleLibrary.SCALE) + ")"); + // initialize css (needs clusterSize!) + addCSSClasses(segments.getHighestClusterCount()); - layer.appendChild(visLayer); - layer.appendChild(ctrlLayer); - } + layer = svgp.svgElement(SVGConstants.SVG_G_TAG); + visLayer = svgp.svgElement(SVGConstants.SVG_G_TAG); + // Setup scaling for canvas: 0 to StyleLibrary.SCALE (usually 100 to avoid + // a + // Java drawing bug!) + String transform = SVGUtil.makeMarginTransform(task.width, task.height, StyleLibrary.SCALE, StyleLibrary.SCALE, 0) + " translate(" + (StyleLibrary.SCALE * .5) + " " + (StyleLibrary.SCALE * .5) + ")"; + visLayer.setAttribute(SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); + ctrlLayer = svgp.svgElement(SVGConstants.SVG_G_TAG); - /** - * Define and add required CSS classes - */ - protected void addCSSClasses(int maxClusterSize) { - StyleLibrary style = context.getStyleLibrary(); - - // Cluster separation lines - CSSClass cssReferenceBorder = new CSSClass(this.getClass(), CLR_BORDER_CLASS); - cssReferenceBorder.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, style.getColor(STYLE_BORDER)); - svgp.addCSSClassOrLogError(cssReferenceBorder); - - // Hover effect for clusters - CSSClass cluster_hover = new CSSClass(this.getClass(), CLR_HOVER_CLASS); - // Note: !important is needed to override the regular color assignment - cluster_hover.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, style.getColor(STYLE_HOVER) + " !important"); - cluster_hover.setStatement(SVGConstants.SVG_CURSOR_TAG, SVGConstants.SVG_POINTER_VALUE); - svgp.addCSSClassOrLogError(cluster_hover); - - // Unpaired cluster segment - CSSClass cluster_unpaired = new CSSClass(this.getClass(), CLR_UNPAIRED_CLASS); - cluster_unpaired.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, style.getBackgroundColor(STYLE)); - cluster_unpaired.setStatement(SVGConstants.SVG_STROKE_ATTRIBUTE, SVGConstants.CSS_NONE_VALUE); - svgp.addCSSClassOrLogError(cluster_unpaired); - - // Selected unpaired cluster segment - CSSClass cluster_unpaired_s = new CSSClass(this.getClass(), SEG_UNPAIRED_SELECTED_CLASS); - cluster_unpaired_s.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, style.getColor(STYLE_HOVER) + " !important"); - svgp.addCSSClassOrLogError(cluster_unpaired_s); - - // create Color shades for clusters - String firstcol = style.getColor(STYLE_GRADIENT_FIRST); - String secondcol = style.getColor(STYLE_GRADIENT_SECOND); - String[] clusterColorShades = makeGradient(maxClusterSize, new String[] { firstcol, secondcol }); - - for(int i = 0; i < maxClusterSize; i++) { - CSSClass clusterClasses = new CSSClass(CircleSegmentsVisualizer.class, CLR_CLUSTER_CLASS_PREFIX + "_" + i); - clusterClasses.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, clusterColorShades[i]); - clusterClasses.setStatement(SVGConstants.SVG_STROKE_ATTRIBUTE, SVGConstants.SVG_NONE_VALUE); - svgp.addCSSClassOrLogError(clusterClasses); - } - } + // and create svg elements + drawSegments(); - /** - * Create the segments - */ - private void drawSegments() { - final int clusterings = segments.getClusterings(); + // + // Build Interface + // + SVGCheckbox checkbox = new SVGCheckbox(showUnclusteredPairs, "Show unclustered pairs"); + checkbox.addCheckBoxListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + toggleUnclusteredPairs(((SVGCheckbox) e.getSource()).isChecked()); + } + }); - // Reinitialize - this.segmentToElements.clear(); + // Add ring:clustering info + Element clrInfo = drawClusteringInfo(); + Element c = checkbox.renderCheckBox(svgp, 1, 5 + Double.parseDouble(clrInfo.getAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE)), 11); + ctrlLayer.appendChild(clrInfo); + ctrlLayer.appendChild(c); - double angle_pair = (MathUtil.TWOPI - (SEGMENT_MIN_SEP_ANGLE * segments.size())) / segments.getPairCount(showUnclusteredPairs); - final int pair_min_count = (int) Math.ceil(SEGMENT_MIN_ANGLE / angle_pair); + ctrlLayer.setAttribute(SVGConstants.SVG_TRANSFORM_ATTRIBUTE, "scale(" + (0.25 / StyleLibrary.SCALE) + ")"); - // number of segments needed to be resized - int cluster_min_count = 0; - for(Segment segment : segments) { - if(segment.getPairCount() <= pair_min_count) { - cluster_min_count++; + layer.appendChild(visLayer); + layer.appendChild(ctrlLayer); + } + + /** + * Define and add required CSS classes + */ + protected void addCSSClasses(int maxClusterSize) { + StyleLibrary style = context.getStyleResult().getStyleLibrary(); + + // Cluster separation lines + CSSClass cssReferenceBorder = new CSSClass(this.getClass(), CLR_BORDER_CLASS); + cssReferenceBorder.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, style.getColor(STYLE_BORDER)); + svgp.addCSSClassOrLogError(cssReferenceBorder); + + // Hover effect for clusters + CSSClass cluster_hover = new CSSClass(this.getClass(), CLR_HOVER_CLASS); + // Note: !important is needed to override the regular color assignment + cluster_hover.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, style.getColor(STYLE_HOVER) + " !important"); + cluster_hover.setStatement(SVGConstants.SVG_CURSOR_TAG, SVGConstants.SVG_POINTER_VALUE); + svgp.addCSSClassOrLogError(cluster_hover); + + // Unpaired cluster segment + CSSClass cluster_unpaired = new CSSClass(this.getClass(), CLR_UNPAIRED_CLASS); + cluster_unpaired.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, style.getBackgroundColor(STYLE)); + cluster_unpaired.setStatement(SVGConstants.SVG_STROKE_ATTRIBUTE, SVGConstants.CSS_NONE_VALUE); + svgp.addCSSClassOrLogError(cluster_unpaired); + + // Selected unpaired cluster segment + CSSClass cluster_unpaired_s = new CSSClass(this.getClass(), SEG_UNPAIRED_SELECTED_CLASS); + cluster_unpaired_s.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, style.getColor(STYLE_HOVER) + " !important"); + svgp.addCSSClassOrLogError(cluster_unpaired_s); + + // create Color shades for clusters + String firstcol = style.getColor(STYLE_GRADIENT_FIRST); + String secondcol = style.getColor(STYLE_GRADIENT_SECOND); + String[] clusterColorShades = makeGradient(maxClusterSize, new String[] { firstcol, secondcol }); + + for(int i = 0; i < maxClusterSize; i++) { + CSSClass clusterClasses = new CSSClass(CircleSegmentsVisualizer.class, CLR_CLUSTER_CLASS_PREFIX + "_" + i); + clusterClasses.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, clusterColorShades[i]); + clusterClasses.setStatement(SVGConstants.SVG_STROKE_ATTRIBUTE, SVGConstants.SVG_NONE_VALUE); + svgp.addCSSClassOrLogError(clusterClasses); } } - // update width of a pair - angle_pair = (MathUtil.TWOPI - (SEGMENT_MIN_SEP_ANGLE * segments.size() + cluster_min_count * SEGMENT_MIN_ANGLE)) / (segments.getPairCount(showUnclusteredPairs) - cluster_min_count); - double radius_delta = (RADIUS_OUTER - RADIUS_INNER - clusterings * RADIUS_DISTANCE) / clusterings; - double border_width = SEGMENT_MIN_SEP_ANGLE; + /** + * Create the segments + */ + private void drawSegments() { + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + final int clusterings = segments.getClusterings(); - int refClustering = 0; - int refSegment = Segment.UNCLUSTERED; - double offsetAngle = 0.0; + // Reinitialize + this.segmentToElements.clear(); - for(final Segment segment : segments) { - long currentPairCount = segment.getPairCount(); + double angle_pair = (MathUtil.TWOPI - (SEGMENT_MIN_SEP_ANGLE * segments.size())) / segments.getPairCount(showUnclusteredPairs); + final int pair_min_count = (int) Math.ceil(SEGMENT_MIN_ANGLE / angle_pair); - // resize small segments if below minimum - double alpha = SEGMENT_MIN_ANGLE; - if(currentPairCount > pair_min_count) { - alpha = angle_pair * currentPairCount; + // number of segments needed to be resized + int cluster_min_count = 0; + for(Segment segment : segments) { + if(segment.getPairCount() <= pair_min_count) { + cluster_min_count++; + } } - // ITERATE OVER ALL SEGMENT-CLUSTERS - - ArrayList<Element> elems = new ArrayList<Element>(clusterings); - segmentToElements.put(segment, elems); - // draw segment for every clustering + // update width of a pair + angle_pair = (MathUtil.TWOPI - (SEGMENT_MIN_SEP_ANGLE * segments.size() + cluster_min_count * SEGMENT_MIN_ANGLE)) / (segments.getPairCount(showUnclusteredPairs) - cluster_min_count); + double radius_delta = (RADIUS_OUTER - RADIUS_INNER - clusterings * RADIUS_DISTANCE) / clusterings; + double border_width = SEGMENT_MIN_SEP_ANGLE; - for(int i = 0; i < clusterings; i++) { - double currentRadius = i * (radius_delta + RADIUS_DISTANCE) + RADIUS_INNER; + int refClustering = 0; + int refSegment = Segment.UNCLUSTERED; + double offsetAngle = 0.0; - // Add border if the next segment is a different cluster in the - // reference clustering - if((refSegment != segment.get(refClustering)) && refClustering == i) { - Element border = SVGUtil.svgCircleSegment(svgp, 0, 0, offsetAngle - SEGMENT_MIN_SEP_ANGLE, border_width, currentRadius, RADIUS_OUTER - RADIUS_DISTANCE); - border.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, CLR_BORDER_CLASS); - visLayer.appendChild(border); + for(final Segment segment : segments) { + long currentPairCount = segment.getPairCount(); - if(segment.get(refClustering) == Segment.UNCLUSTERED) { - refClustering = Math.min(refClustering + 1, clusterings - 1); - } - refSegment = segment.get(refClustering); + // resize small segments if below minimum + double alpha = SEGMENT_MIN_ANGLE; + if(currentPairCount > pair_min_count) { + alpha = angle_pair * currentPairCount; } - int cluster = segment.get(i); + // ITERATE OVER ALL SEGMENT-CLUSTERS - // create ring segment - Element segelement = SVGUtil.svgCircleSegment(svgp, 0, 0, offsetAngle, alpha, currentRadius, currentRadius + radius_delta); - elems.add(segelement); + ArrayList<Element> elems = new ArrayList<Element>(clusterings); + segmentToElements.put(segment, elems); + // draw segment for every clustering - // MouseEvents on segment cluster - EventListener listener = new SegmentListenerProxy(segment, i); - EventTarget targ = (EventTarget) segelement; - targ.addEventListener(SVGConstants.SVG_MOUSEOVER_EVENT_TYPE, listener, false); - targ.addEventListener(SVGConstants.SVG_MOUSEOUT_EVENT_TYPE, listener, false); - targ.addEventListener(SVGConstants.SVG_CLICK_EVENT_TYPE, listener, false); + for(int i = 0; i < clusterings; i++) { + double currentRadius = i * (radius_delta + RADIUS_DISTANCE) + RADIUS_INNER; - // Coloring based on clusterID - if(cluster >= 0) { - segelement.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, CLR_CLUSTER_CLASS_PREFIX + "_" + cluster); - } - // if its an unpaired cluster set color to white - else { - segelement.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, CLR_UNPAIRED_CLASS); - } + // Add border if the next segment is a different cluster in the + // reference clustering + if((refSegment != segment.get(refClustering)) && refClustering == i) { + Element border = SVGUtil.svgCircleSegment(svgp, 0, 0, offsetAngle - SEGMENT_MIN_SEP_ANGLE, border_width, currentRadius, RADIUS_OUTER - RADIUS_DISTANCE); + border.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, CLR_BORDER_CLASS); + visLayer.appendChild(border); - visLayer.appendChild(segelement); - } + if(segment.get(refClustering) == Segment.UNCLUSTERED) { + refClustering = Math.min(refClustering + 1, clusterings - 1); + } + refSegment = segment.get(refClustering); + } - // - // Add a extended strip for each segment to emphasis selection - // (easier to track thin segments and their color coding and - // differentiates them from cluster border lines) - // + int cluster = segment.get(i); - double currentRadius = clusterings * (radius_delta + RADIUS_DISTANCE) + RADIUS_INNER; - Element extension = SVGUtil.svgCircleSegment(svgp, 0, 0, offsetAngle, alpha, currentRadius, currentRadius + RADIUS_SELECTION); - extension.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, CLR_UNPAIRED_CLASS); - elems.add(extension); + // create ring segment + Element segelement = SVGUtil.svgCircleSegment(svgp, 0, 0, offsetAngle, alpha, currentRadius, currentRadius + radius_delta); + elems.add(segelement); - if(segment.isUnpaired()) { - if(policy.isSelected(segment)) { - SVGUtil.addCSSClass(extension, SEG_UNPAIRED_SELECTED_CLASS); - } - else { - // Remove highlight - SVGUtil.removeCSSClass(extension, SEG_UNPAIRED_SELECTED_CLASS); - } - } - else { - int idx = policy.indexOfSegment(segment); - if(idx >= 0) { - String color = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT).getColor(idx); - extension.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, SVGConstants.CSS_FILL_PROPERTY + ":" + color); - } - else { - // Remove styling - extension.removeAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE); + // MouseEvents on segment cluster + EventListener listener = new SegmentListenerProxy(segment, i); + EventTarget targ = (EventTarget) segelement; + targ.addEventListener(SVGConstants.SVG_MOUSEOVER_EVENT_TYPE, listener, false); + targ.addEventListener(SVGConstants.SVG_MOUSEOUT_EVENT_TYPE, listener, false); + targ.addEventListener(SVGConstants.SVG_CLICK_EVENT_TYPE, listener, false); + + // Coloring based on clusterID + if(cluster >= 0) { + segelement.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, CLR_CLUSTER_CLASS_PREFIX + "_" + cluster); + } + // if its an unpaired cluster set color to white + else { + segelement.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, CLR_UNPAIRED_CLASS); + } + + visLayer.appendChild(segelement); } - } - visLayer.appendChild(extension); + // + // Add a extended strip for each segment to emphasis selection + // (easier to track thin segments and their color coding and + // differentiates them from cluster border lines) + // - // calculate angle for next segment - offsetAngle += alpha + SEGMENT_MIN_SEP_ANGLE; - } - } + double currentRadius = clusterings * (radius_delta + RADIUS_DISTANCE) + RADIUS_INNER; + Element extension = SVGUtil.svgCircleSegment(svgp, 0, 0, offsetAngle, alpha, currentRadius, currentRadius + RADIUS_SELECTION); + extension.setAttribute(SVGConstants.SVG_CLASS_ATTRIBUTE, CLR_UNPAIRED_CLASS); + elems.add(extension); - private void redrawSelection() { - logger.debug("Updating selection only."); - for(Entry<Segment, List<Element>> entry : segmentToElements.entrySet()) { - Segment segment = entry.getKey(); - // The selection marker is the extra element in the list - Element extension = entry.getValue().get(segments.getClusterings()); - if(segment.isUnpaired()) { - if(policy.isSelected(segment)) { - SVGUtil.addCSSClass(extension, SEG_UNPAIRED_SELECTED_CLASS); + if(segment.isUnpaired()) { + if(policy.isSelected(segment)) { + SVGUtil.addCSSClass(extension, SEG_UNPAIRED_SELECTED_CLASS); + } + else { + // Remove highlight + SVGUtil.removeCSSClass(extension, SEG_UNPAIRED_SELECTED_CLASS); + } } else { - // Remove highlight - SVGUtil.removeCSSClass(extension, SEG_UNPAIRED_SELECTED_CLASS); + int idx = policy.indexOfSegment(segment); + if(idx >= 0) { + String color = style.getColorSet(StyleLibrary.PLOT).getColor(idx); + extension.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, SVGConstants.CSS_FILL_PROPERTY + ":" + color); + } + else { + // Remove styling + extension.removeAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE); + } } + + visLayer.appendChild(extension); + + // calculate angle for next segment + offsetAngle += alpha + SEGMENT_MIN_SEP_ANGLE; } - else { - int idx = policy.indexOfSegment(segment); - if(idx >= 0) { - String color = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT).getColor(idx); - extension.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, SVGConstants.CSS_FILL_PROPERTY + ":" + color); + } + + private void redrawSelection() { + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + LOG.debug("Updating selection only."); + for(Entry<Segment, List<Element>> entry : segmentToElements.entrySet()) { + Segment segment = entry.getKey(); + // The selection marker is the extra element in the list + Element extension = entry.getValue().get(segments.getClusterings()); + if(segment.isUnpaired()) { + if(policy.isSelected(segment)) { + SVGUtil.addCSSClass(extension, SEG_UNPAIRED_SELECTED_CLASS); + } + else { + // Remove highlight + SVGUtil.removeCSSClass(extension, SEG_UNPAIRED_SELECTED_CLASS); + } } else { - // Remove styling - extension.removeAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE); + int idx = policy.indexOfSegment(segment); + if(idx >= 0) { + String color = style.getColorSet(StyleLibrary.PLOT).getColor(idx); + extension.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, SVGConstants.CSS_FILL_PROPERTY + ":" + color); + } + else { + // Remove styling + extension.removeAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE); + } } } } - } - /** - * Creates a gradient over a set of colors - * - * @param shades number of colors in the gradient - * @param colors colors for the gradient - * @return array of colors for CSS - */ - protected static String[] makeGradient(int shades, String[] colors) { - if(shades <= colors.length) { - return colors; - } + /** + * Creates a gradient over a set of colors + * + * @param shades number of colors in the gradient + * @param colors colors for the gradient + * @return array of colors for CSS + */ + protected String[] makeGradient(int shades, String[] colors) { + if(shades <= colors.length) { + return colors; + } - // Convert SVG colors into AWT colors for math - Color[] cols = new Color[colors.length]; - for(int i = 0; i < colors.length; i++) { - cols[i] = SVGUtil.stringToColor(colors[i]); - if(cols[i] == null) { - throw new AbortException("Error parsing color: " + colors[i]); + // Convert SVG colors into AWT colors for math + Color[] cols = new Color[colors.length]; + for(int i = 0; i < colors.length; i++) { + cols[i] = SVGUtil.stringToColor(colors[i]); + if(cols[i] == null) { + throw new AbortException("Error parsing color: " + colors[i]); + } } - } - // Step size - double increment = (cols.length - 1.) / shades; + // Step size + double increment = (cols.length - 1.) / shades; - String[] colorShades = new String[shades]; + String[] colorShades = new String[shades]; - for(int s = 0; s < shades; s++) { - final int ppos = Math.min((int) Math.floor(increment * s), cols.length); - final int npos = Math.min((int) Math.ceil(increment * s), cols.length); - if(ppos == npos) { - colorShades[s] = colors[ppos]; - } - else { - Color prev = cols[ppos]; - Color next = cols[npos]; - final double mix = (increment * s - ppos) / (npos - ppos); - final int r = (int) ((1 - mix) * prev.getRed() + mix * next.getRed()); - final int g = (int) ((1 - mix) * prev.getGreen() + mix * next.getGreen()); - final int b = (int) ((1 - mix) * prev.getBlue() + mix * next.getBlue()); - colorShades[s] = SVGUtil.colorToString(((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF)); + for(int s = 0; s < shades; s++) { + final int ppos = Math.min((int) Math.floor(increment * s), cols.length); + final int npos = Math.min((int) Math.ceil(increment * s), cols.length); + if(ppos == npos) { + colorShades[s] = colors[ppos]; + } + else { + Color prev = cols[ppos]; + Color next = cols[npos]; + final double mix = (increment * s - ppos) / (npos - ppos); + final int r = (int) ((1 - mix) * prev.getRed() + mix * next.getRed()); + final int g = (int) ((1 - mix) * prev.getGreen() + mix * next.getGreen()); + final int b = (int) ((1 - mix) * prev.getBlue() + mix * next.getBlue()); + colorShades[s] = SVGUtil.colorToString(((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF)); + } } + + return colorShades; } - return colorShades; - } + protected Element drawClusteringInfo() { + Element thumbnail = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG); - protected Element drawClusteringInfo() { - Element thumbnail = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG); + // build thumbnail + int startRadius = 4; + int singleHeight = 12; + int margin = 4; + int radius = segments.getClusterings() * (singleHeight + margin) + startRadius; - // build thumbnail - int startRadius = 4; - int singleHeight = 12; - int margin = 4; - int radius = segments.getClusterings() * (singleHeight + margin) + startRadius; + SVGUtil.setAtt(thumbnail, SVGConstants.SVG_HEIGHT_ATTRIBUTE, radius); - SVGUtil.setAtt(thumbnail, SVGConstants.SVG_HEIGHT_ATTRIBUTE, radius); + for(int i = 0; i < segments.getClusterings(); i++) { + double innerRadius = i * singleHeight + margin * i + startRadius; + Element clr = SVGUtil.svgCircleSegment(svgp, radius - startRadius, radius - startRadius, Math.PI * 1.5, Math.PI * 0.5, innerRadius, innerRadius + singleHeight); + // FIXME: Use StyleLibrary + clr.setAttribute(SVGConstants.SVG_FILL_ATTRIBUTE, "#d4e4f1"); + clr.setAttribute(SVGConstants.SVG_STROKE_ATTRIBUTE, "#a0a0a0"); + clr.setAttribute(SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE, "1.0"); - for(int i = 0; i < segments.getClusterings(); i++) { - double innerRadius = i * singleHeight + margin * i + startRadius; - Element clr = SVGUtil.svgCircleSegment(svgp, radius - startRadius, radius - startRadius, Math.PI * 1.5, Math.PI * 0.5, innerRadius, innerRadius + singleHeight); - // FIXME: Use StyleLibrary - clr.setAttribute(SVGConstants.SVG_FILL_ATTRIBUTE, "#d4e4f1"); - clr.setAttribute(SVGConstants.SVG_STROKE_ATTRIBUTE, "#a0a0a0"); - clr.setAttribute(SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE, "1.0"); + String labelText = segments.getClusteringDescription(i); + Element label = svgp.svgText(radius + startRadius, radius - innerRadius - startRadius, labelText); + thumbnail.appendChild(label); - String labelText = segments.getClusteringDescription(i); - Element label = svgp.svgText(radius + startRadius, radius - innerRadius - startRadius, labelText); - thumbnail.appendChild(label); + thumbnail.appendChild(clr); + } - thumbnail.appendChild(clr); + return thumbnail; } - return thumbnail; - } - - protected void segmentHover(Segment segment, int ringid, boolean active) { - if(active) { - // abort if this are the unclustered pairs - if(segment.isNone()) { - return; - } - if(logger.isDebugging()) { - logger.debug("Hover on segment: " + segment + " unpaired: " + segment.isUnpaired()); - } + protected void segmentHover(Segment segment, int ringid, boolean active) { + if(active) { + // abort if this are the unclustered pairs + if(segment.isNone()) { + return; + } + if(LOG.isDebugging()) { + LOG.debug("Hover on segment: " + segment + " unpaired: " + segment.isUnpaired()); + } - if(!segment.isUnpaired()) { - // - // STANDARD CLUSTER SEGMENT - // highlight all ring segments in this clustering and this cluster - // - // highlight all corresponding ring Segments - for(Entry<Segment, List<Element>> entry : segmentToElements.entrySet()) { - Segment other = entry.getKey(); - // Same cluster in same clustering? - if(other.get(ringid) != segment.get(ringid)) { - continue; + if(!segment.isUnpaired()) { + // + // STANDARD CLUSTER SEGMENT + // highlight all ring segments in this clustering and this cluster + // + // highlight all corresponding ring Segments + for(Entry<Segment, List<Element>> entry : segmentToElements.entrySet()) { + Segment other = entry.getKey(); + // Same cluster in same clustering? + if(other.get(ringid) != segment.get(ringid)) { + continue; + } + Element ringSegment = entry.getValue().get(ringid); + SVGUtil.addCSSClass(ringSegment, CLR_HOVER_CLASS); } - Element ringSegment = entry.getValue().get(ringid); - SVGUtil.addCSSClass(ringSegment, CLR_HOVER_CLASS); } - } - else { - // - // UNPAIRED SEGMENT - // highlight all ring segments in this clustering responsible for - // unpaired - // segment - // - // get the paired segments corresponding to the unpaired segment - List<Segment> paired = segments.getPairedSegments(segment); - - for(Segment other : paired) { - Element ringSegment = segmentToElements.get(other).get(ringid); - SVGUtil.addCSSClass(ringSegment, CLR_HOVER_CLASS); + else { + // + // UNPAIRED SEGMENT + // highlight all ring segments in this clustering responsible for + // unpaired + // segment + // + // get the paired segments corresponding to the unpaired segment + List<Segment> paired = segments.getPairedSegments(segment); + + for(Segment other : paired) { + Element ringSegment = segmentToElements.get(other).get(ringid); + SVGUtil.addCSSClass(ringSegment, CLR_HOVER_CLASS); + } } } - } - else { - for(List<Element> elems : segmentToElements.values()) { - for(Element current : elems) { - SVGUtil.removeCSSClass(current, CLR_HOVER_CLASS); + else { + for(List<Element> elems : segmentToElements.values()) { + for(Element current : elems) { + SVGUtil.removeCSSClass(current, CLR_HOVER_CLASS); + } } } } - } - protected void segmentClick(Segment segment, Event evt, boolean dblClick) { - MouseEvent mouse = (MouseEvent) evt; + protected void segmentClick(Segment segment, Event evt, boolean dblClick) { + MouseEvent mouse = (MouseEvent) evt; - // CTRL (add) pressed? - boolean ctrl = false; - if(mouse.getCtrlKey()) { - ctrl = true; - } + // CTRL (add) pressed? + boolean ctrl = false; + if(mouse.getCtrlKey()) { + ctrl = true; + } - // Unselect others on double click - if(dblClick) { - policy.deselectAllSegments(); + // Unselect others on double click + if(dblClick) { + policy.deselectAllSegments(); + } + policy.select(segment, ctrl); + // update stylePolicy + context.getStyleResult().setStylingPolicy(policy); + // fire changed event to trigger redraw + context.getHierarchy().resultChanged(context.getStyleResult()); } - policy.select(segment, ctrl); - // update stylePolicy - context.getStyleResult().setStylingPolicy(policy); - // fire changed event to trigger redraw - context.getHierarchy().resultChanged(context.getStyleResult()); - } - /** - * Proxy element to connect signals. - * - * @author Erich Schubert - * - * @apiviz.exclude - */ - private class SegmentListenerProxy implements EventListener { /** - * Mouse double click time window in milliseconds + * Proxy element to connect signals. * - * TODO: does Batik have double click events? - */ - public static final int EVT_DBLCLICK_DELAY = 350; - - /** - * Segment we are attached to - */ - private Segment id; - - /** - * Segment ring we are - */ - private int ringid; - - /** - * For detecting double clicks. - */ - private long lastClick = 0; - - /** - * Constructor. + * @author Erich Schubert * - * @param id Segment id - * @param ringid Ring id + * @apiviz.exclude */ - public SegmentListenerProxy(Segment id, int ringid) { - super(); - this.id = id; - this.ringid = ringid; - } - - @Override - public void handleEvent(Event evt) { - if(SVGConstants.SVG_MOUSEOVER_EVENT_TYPE.equals(evt.getType())) { - segmentHover(id, ringid, true); + private class SegmentListenerProxy implements EventListener { + /** + * Mouse double click time window in milliseconds + * + * TODO: does Batik have double click events? + */ + public static final int EVT_DBLCLICK_DELAY = 350; + + /** + * Segment we are attached to + */ + private Segment id; + + /** + * Segment ring we are + */ + private int ringid; + + /** + * For detecting double clicks. + */ + private long lastClick = 0; + + /** + * Constructor. + * + * @param id Segment id + * @param ringid Ring id + */ + public SegmentListenerProxy(Segment id, int ringid) { + super(); + this.id = id; + this.ringid = ringid; } - if(SVGConstants.SVG_MOUSEOUT_EVENT_TYPE.equals(evt.getType())) { - segmentHover(id, ringid, false); - } - if(SVGConstants.SVG_CLICK_EVENT_TYPE.equals(evt.getType())) { - // Check Double Click - boolean dblClick = false; - long time = java.util.Calendar.getInstance().getTimeInMillis(); - if(time - lastClick <= EVT_DBLCLICK_DELAY) { - dblClick = true; - } - lastClick = time; - - segmentClick(id, evt, dblClick); - } - } - } - - /** - * Factory for visualizers for a circle segment - * - * @author Sascha Goldhofer - * - * @apiviz.stereotype factory - * @apiviz.uses CircleSegmentsVisualizer oneway - - «create» - */ - public static class Factory extends AbstractVisFactory { - /** - * Constructor - */ - public Factory() { - super(); - this.thumbmask |= ThumbnailVisualization.ON_STYLE; - } - @Override - public Visualization makeVisualization(VisualizationTask task) { - return new CircleSegmentsVisualizer(task); - } + @Override + public void handleEvent(Event evt) { + if(SVGConstants.SVG_MOUSEOVER_EVENT_TYPE.equals(evt.getType())) { + segmentHover(id, ringid, true); + } + if(SVGConstants.SVG_MOUSEOUT_EVENT_TYPE.equals(evt.getType())) { + segmentHover(id, ringid, false); + } + if(SVGConstants.SVG_CLICK_EVENT_TYPE.equals(evt.getType())) { + // Check Double Click + boolean dblClick = false; + long time = java.util.Calendar.getInstance().getTimeInMillis(); + if(time - lastClick <= EVT_DBLCLICK_DELAY) { + dblClick = true; + } + lastClick = time; - @Override - public void processNewResult(HierarchicalResult baseResult, Result result) { - // If no comparison result found abort - List<Segments> segments = ResultUtil.filterResults(result, Segments.class); - for(Segments segmentResult : segments) { - SegmentsStylingPolicy policy; - List<SegmentsStylingPolicy> styles = ResultUtil.filterResults(segmentResult, SegmentsStylingPolicy.class); - if (!styles.isEmpty()) { - policy = styles.get(0); - } else { - policy = new SegmentsStylingPolicy(segmentResult); - baseResult.getHierarchy().add(segmentResult, policy); + segmentClick(id, evt, dblClick); } - // create task for visualization - final VisualizationTask task = new VisualizationTask(NAME, policy, null, this); - task.width = 2.0; - task.height = 2.0; - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE); - baseResult.getHierarchy().add(segmentResult, task); } } - }; + } }
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/AbstractParallelVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/AbstractParallelVisualization.java index babcb864..a2039126 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/AbstractParallelVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/AbstractParallelVisualization.java @@ -44,7 +44,7 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisualization; *
* @param <NV> Vector type in relation
*/
-public abstract class AbstractParallelVisualization<NV extends NumberVector<?, ?>> extends AbstractVisualization {
+public abstract class AbstractParallelVisualization<NV extends NumberVector<?>> extends AbstractVisualization {
/**
* The current projection
*/
@@ -80,9 +80,8 @@ public abstract class AbstractParallelVisualization<NV extends NumberVector<?, ? this.proj = task.getProj();
this.relation = task.getRelation();
- double ratio = task.width / task.height;
-
- margins = new double[] { 0.05 * StyleLibrary.SCALE, 0.1 * StyleLibrary.SCALE, 0.05 * StyleLibrary.SCALE, 0.4 * StyleLibrary.SCALE };
+ margins = new double[] { 0.05 * StyleLibrary.SCALE, 0.1 * StyleLibrary.SCALE, 0.05 * StyleLibrary.SCALE, 0.1 * StyleLibrary.SCALE };
+ double ratio = (task.width * StyleLibrary.SCALE - margins[0] - margins[2]) / (task.height * StyleLibrary.SCALE - margins[1] - margins[3]);
size = new double[] { ratio * StyleLibrary.SCALE, StyleLibrary.SCALE };
recalcAxisPositions();
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/AxisReorderVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/AxisReorderVisualization.java new file mode 100644 index 00000000..6d77f592 --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/AxisReorderVisualization.java @@ -0,0 +1,296 @@ +package de.lmu.ifi.dbs.elki.visualization.visualizers.parallel;
+
+/*
+ This file is part of ELKI:
+ Environment for Developing KDD-Applications Supported by Index-Structures
+
+ Copyright (C) 2012
+ Ludwig-Maximilians-Universität München
+ Lehr- und Forschungseinheit für Datenbanksysteme
+ 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/>.
+ */
+
+import java.util.Collection;
+
+import org.apache.batik.util.SVGConstants;
+import org.w3c.dom.Element;
+import org.w3c.dom.events.Event;
+import org.w3c.dom.events.EventListener;
+import org.w3c.dom.events.EventTarget;
+
+import de.lmu.ifi.dbs.elki.data.NumberVector;
+import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
+import de.lmu.ifi.dbs.elki.result.Result;
+import de.lmu.ifi.dbs.elki.result.ResultUtil;
+import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
+import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
+import de.lmu.ifi.dbs.elki.visualization.projector.ParallelPlotProjector;
+import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGArrow;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisFactory;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;
+
+/**
+ * Interactive SVG-Elements for reordering the axes.
+ *
+ * @author Robert Rödler
+ * @author Erich Schubert
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
+ */
+public class AxisReorderVisualization extends AbstractVisFactory {
+ /**
+ * A short name characterizing this Visualizer.
+ */
+ private static final String NAME = "Dimension Ordering Tool";
+
+ /**
+ * Constructor, adhering to
+ */
+ public AxisReorderVisualization() {
+ super();
+ }
+
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<ParallelPlotProjector<?>> ps = ResultUtil.filterResults(result, ParallelPlotProjector.class);
+ for(ParallelPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, p, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_INTERACTIVE;
+ task.noexport = true;
+ task.thumbnail = false;
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+
+ /**
+ * Instance for a particular plot.
+ *
+ * @author Robert Rödler
+ * @author Erich Schubert
+ */
+ public class Instance extends AbstractParallelVisualization<NumberVector<?>> {
+ /**
+ * Generic tags to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
+ */
+ public static final String SELECTDIMENSIONORDER = "SelectDimensionOrder";
+
+ /**
+ * CSS class for a tool button
+ */
+ public static final String SDO_BUTTON = "DObutton";
+
+ /**
+ * CSS class for a button border
+ */
+ public static final String SDO_BORDER = "DOborder";
+
+ /**
+ * CSS class for a button cross
+ */
+ public static final String SDO_ARROW = "DOarrow";
+
+ /**
+ * Currently selected dimension. Use -1 to not have a dimension selected.
+ */
+ private int selecteddim = -1;
+
+ /**
+ * Constructor.
+ *
+ * @param task VisualizationTask
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ incrementalRedraw();
+ context.addResultListener(this);
+ }
+
+ @Override
+ protected void redraw() {
+ addCSSClasses(svgp);
+ final int dim = proj.getVisibleDimensions();
+
+ final double controlsize = 0.025 * getSizeY();
+ final double buttonsize = 0.75 * controlsize;
+ final double padding = 0.125 * controlsize;
+ final double arrowsize = .75 * buttonsize;
+ final double ypos = getSizeY() + getMarginTop() * .5 + controlsize;
+ final double spacing = 0.9 * controlsize;
+
+ Element back = svgp.svgRect(-controlsize * .5, ypos, getSizeX() + controlsize, controlsize);
+ SVGUtil.addCSSClass(back, SELECTDIMENSIONORDER);
+ layer.appendChild(back);
+
+ if(selecteddim < 0) {
+ // Nothing selected
+ for(int i = 0; i < dim; i++) {
+ final double xpos = getVisibleAxisX(i);
+ if(i > 0) {
+ Element arrow = SVGArrow.makeArrow(svgp, SVGArrow.LEFT, xpos - spacing, ypos + controlsize * .5, arrowsize);
+ SVGUtil.addCSSClass(arrow, SDO_ARROW);
+ layer.appendChild(arrow);
+ Element button = svgp.svgRect(xpos - spacing - buttonsize * .5, ypos + padding, buttonsize, buttonsize);
+ SVGUtil.addCSSClass(button, SDO_BUTTON);
+ addEventListener(button, i, SVGArrow.LEFT);
+ layer.appendChild(button);
+ }
+ {
+ Element arrow = SVGArrow.makeArrow(svgp, SVGArrow.DOWN, xpos, ypos + controlsize * .5, arrowsize);
+ SVGUtil.addCSSClass(arrow, SDO_ARROW);
+ layer.appendChild(arrow);
+ Element button = svgp.svgRect(xpos - buttonsize * .5, ypos + padding, buttonsize, buttonsize);
+ SVGUtil.addCSSClass(button, SDO_BUTTON);
+ addEventListener(button, i, SVGArrow.DOWN);
+ layer.appendChild(button);
+ }
+ if(i < dim - 1) {
+ Element arrow = SVGArrow.makeArrow(svgp, SVGArrow.RIGHT, xpos + spacing, ypos + controlsize * .5, arrowsize);
+ SVGUtil.addCSSClass(arrow, SDO_ARROW);
+ layer.appendChild(arrow);
+ Element button = svgp.svgRect(xpos + spacing - buttonsize * .5, ypos + padding, buttonsize, buttonsize);
+ SVGUtil.addCSSClass(button, SDO_BUTTON);
+ addEventListener(button, i, SVGArrow.RIGHT);
+ layer.appendChild(button);
+ }
+ }
+ }
+ else {
+ for(int i = 0; i < dim; i++) {
+ {
+ Element arrow = SVGArrow.makeArrow(svgp, SVGArrow.DOWN, getVisibleAxisX(i), ypos + controlsize * .5, arrowsize);
+ SVGUtil.addCSSClass(arrow, SDO_ARROW);
+ layer.appendChild(arrow);
+ Element button = svgp.svgRect(getVisibleAxisX(i) - buttonsize * .5, ypos + padding, buttonsize, buttonsize);
+ SVGUtil.addCSSClass(button, SDO_BUTTON);
+ addEventListener(button, i, SVGArrow.DOWN);
+ layer.appendChild(button);
+ }
+ if(i > 0.) {
+ Element arrow = SVGArrow.makeArrow(svgp, SVGArrow.UP, getVisibleAxisX(i - .5), ypos + controlsize * .5, arrowsize);
+ SVGUtil.addCSSClass(arrow, SDO_ARROW);
+ layer.appendChild(arrow);
+ Element button = svgp.svgRect(getVisibleAxisX(i - .5) - buttonsize * .5, ypos + padding, buttonsize, buttonsize);
+ SVGUtil.addCSSClass(button, SDO_BUTTON);
+ addEventListener(button, i, SVGArrow.UP);
+ layer.appendChild(button);
+ }
+ }
+ }
+ }
+
+ /**
+ * Add an event listener to the Element
+ *
+ * @param tag Element to add the listener
+ * @param i represented axis
+ */
+ private void addEventListener(final Element tag, final int i, final SVGArrow.Direction j) {
+ EventTarget targ = (EventTarget) tag;
+ targ.addEventListener(SVGConstants.SVG_EVENT_CLICK, new EventListener() {
+ @Override
+ public void handleEvent(Event evt) {
+ if(selecteddim < 0) {
+ switch(j){
+ case DOWN:
+ selecteddim = i;
+ break;
+ case LEFT:
+ int prev = i - 1;
+ while(prev >= 0 && !proj.isAxisVisible(prev)) {
+ prev -= 1;
+ }
+ proj.swapAxes(i, prev);
+ break;
+ case RIGHT:
+ int next = i + 1;
+ while(next < proj.getInputDimensionality() - 1 && !proj.isAxisVisible(next)) {
+ next += 1;
+ }
+ proj.swapAxes(i, next);
+ break;
+ default:
+ break;
+ }
+ }
+ else {
+ switch(j){
+ case DOWN:
+ proj.swapAxes(selecteddim, i);
+ selecteddim = -1;
+ break;
+ case UP:
+ if(selecteddim != i) {
+ proj.moveAxis(selecteddim, i);
+ }
+ selecteddim = -1;
+ break;
+ default:
+ break;
+ }
+ }
+ // Notify
+ context.getHierarchy().resultChanged(proj);
+ }
+ }, false);
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ if(!svgp.getCSSClassManager().contains(SELECTDIMENSIONORDER)) {
+ CSSClass cls = new CSSClass(this, SELECTDIMENSIONORDER);
+ cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, 0.1);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_BLUE_VALUE);
+ svgp.addCSSClassOrLogError(cls);
+ }
+ if(!svgp.getCSSClassManager().contains(SDO_BORDER)) {
+ CSSClass cls = new CSSClass(this, SDO_BORDER);
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_GREY_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT) / 3.0);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ svgp.addCSSClassOrLogError(cls);
+ }
+ if(!svgp.getCSSClassManager().contains(SDO_BUTTON)) {
+ CSSClass cls = new CSSClass(this, SDO_BUTTON);
+ cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, 0.01);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_GREY_VALUE);
+ cls.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE);
+ svgp.addCSSClassOrLogError(cls);
+ }
+ if(!svgp.getCSSClassManager().contains(SDO_ARROW)) {
+ CSSClass cls = new CSSClass(this, SDO_ARROW);
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_DARKGREY_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT) / 3);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_BLACK_VALUE);
+ svgp.addCSSClassOrLogError(cls);
+ }
+ }
+ }
+}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/AxisVisibilityVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/AxisVisibilityVisualization.java new file mode 100644 index 00000000..ea68d660 --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/AxisVisibilityVisualization.java @@ -0,0 +1,293 @@ +package de.lmu.ifi.dbs.elki.visualization.visualizers.parallel;
+/* + This file is part of ELKI: + Environment for Developing KDD-Applications Supported by Index-Structures + + Copyright (C) 2012 + Ludwig-Maximilians-Universität München + Lehr- und Forschungseinheit für Datenbanksysteme + 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/>. + */ +
+import java.util.Collection;
+
+import org.apache.batik.util.SVGConstants;
+import org.w3c.dom.Element;
+import org.w3c.dom.events.Event;
+import org.w3c.dom.events.EventListener;
+import org.w3c.dom.events.EventTarget;
+
+import de.lmu.ifi.dbs.elki.data.NumberVector;
+import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
+import de.lmu.ifi.dbs.elki.result.Result;
+import de.lmu.ifi.dbs.elki.result.ResultUtil;
+import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
+import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
+import de.lmu.ifi.dbs.elki.visualization.projector.ParallelPlotProjector;
+import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPath;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisFactory;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;
+
+/**
+ * Layer for controlling axis visbility in parallel coordinates.
+ *
+ * @author Robert Rödler
+ * @author Erich Schubert
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
+ */
+public class AxisVisibilityVisualization extends AbstractVisFactory {
+ /**
+ * A short name characterizing this Visualizer.
+ */
+ private static final String NAME = "Axis Visibility";
+
+ /**
+ * Constructor, adhering to
+ */
+ public AxisVisibilityVisualization() {
+ super();
+ }
+
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<ParallelPlotProjector<?>> ps = ResultUtil.filterResults(result, ParallelPlotProjector.class);
+ for(ParallelPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, p, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_INTERACTIVE;
+ task.noexport = true;
+ task.thumbnail = false;
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+
+ /**
+ * Instance for a particular data set.
+ *
+ * @author Robert Rödler
+ * @author Erich Schubert
+ */
+ public class Instance extends AbstractParallelVisualization<NumberVector<?>> {
+ /**
+ * Generic tags to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
+ */
+ public static final String SELECTAXISVISIBILITY = "SelectAxisVisibility";
+
+ /**
+ * CSS class for a tool button
+ */
+ public static final String SAV_BUTTON = "SAVbutton";
+
+ /**
+ * CSS class for a button border
+ */
+ public static final String SAV_BORDER = "SAVborder";
+
+ /**
+ * CSS class for a button cross
+ */
+ public static final String SAV_CROSS = "SAVbuttoncross";
+
+ /**
+ * Active area size
+ */
+ double controlsize;
+
+ /**
+ * Button size
+ */
+ double buttonsize;
+
+ /**
+ * Vertical position
+ */
+ double ypos;
+
+ /**
+ * Constructor.
+ *
+ * @param task VisualizationTask
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ incrementalRedraw();
+ context.addResultListener(this);
+ }
+
+ @Override
+ protected void redraw() {
+ final int dim = proj.getInputDimensionality();
+ addCSSClasses(svgp);
+ controlsize = 0.025 * getSizeY();
+ buttonsize = 0.75 * controlsize;
+ ypos = getSizeY() + getMarginTop() * .5;
+
+ // Background
+ Element back = svgp.svgRect(-controlsize * .5, ypos - controlsize * .5 + buttonsize * .5, getSizeX() + controlsize, controlsize);
+ SVGUtil.addCSSClass(back, SELECTAXISVISIBILITY);
+ layer.appendChild(back);
+
+ // Previous visible dimension.
+ for(int i = 0, hidden = 0, vax = 0; i <= dim; i++) {
+ if(i < dim && !proj.isAxisVisible(i)) {
+ hidden += 1;
+ continue;
+ }
+ // Add button for showing hidden dimensions:
+ if(hidden > 0) {
+ makeButtonsForHidden(vax, i - hidden, hidden, dim);
+ hidden = 0;
+ }
+ // Add buttons for current dimension
+ if(i < dim) {
+ makeButtonForVisible(i, vax);
+ vax++;
+ }
+ }
+ }
+
+ /**
+ * Make a button for a visible axis
+ *
+ * @param anum Axis number
+ * @param apos Axis position in plot
+ */
+ protected void makeButtonForVisible(int anum, int apos) {
+ final double xpos = getVisibleAxisX(apos) - buttonsize * .5;
+
+ Element border = svgp.svgRect(xpos, ypos, buttonsize, buttonsize);
+ SVGUtil.addCSSClass(border, SAV_BORDER);
+ layer.appendChild(border);
+
+ SVGPath path = new SVGPath();
+ final double qs = controlsize * .5;
+ final double cs = controlsize * .125;
+ path.moveTo(xpos + cs, ypos + cs);
+ path.relativeLineTo(qs, qs);
+ path.relativeMoveTo(0, -qs);
+ path.relativeLineTo(-qs, qs);
+ Element cross = path.makeElement(svgp);
+ SVGUtil.addCSSClass(cross, SAV_CROSS);
+ layer.appendChild(cross);
+
+ Element rect = svgp.svgRect(xpos, ypos, buttonsize, buttonsize);
+ SVGUtil.addCSSClass(rect, SAV_BUTTON);
+ addEventListener(rect, anum);
+ layer.appendChild(rect);
+ }
+
+ /**
+ * Insert buttons for hidden dimensions.
+ *
+ * @param vnum Column number (= next visible axis number)
+ * @param first First invisible axis
+ * @param count Number of invisible axes
+ * @param dim Number of total dimensions
+ */
+ private void makeButtonsForHidden(final int vnum, final int first, final int count, final int dim) {
+ final double lpos, rpos;
+ if(vnum == 0) {
+ lpos = -getMarginLeft();
+ }
+ else {
+ lpos = getVisibleAxisX(vnum - 1);
+ }
+ if(first + count + 1 >= dim) {
+ rpos = getWidth() + getMarginLeft();
+ }
+ else {
+ rpos = getVisibleAxisX(vnum);
+ }
+ final double step = (rpos - lpos) / (count + 1.0);
+ for(int j = 0; j < count; j++) {
+ final double apos = lpos + (j + 1) * step - buttonsize * .5;
+ Element border = svgp.svgRect(apos, ypos, buttonsize, buttonsize);
+ SVGUtil.addCSSClass(border, SAV_BORDER);
+ layer.appendChild(border);
+
+ Element rect = svgp.svgRect(apos, ypos, buttonsize, buttonsize);
+ SVGUtil.addCSSClass(rect, SAV_BUTTON);
+ addEventListener(rect, first + j);
+ layer.appendChild(rect);
+ }
+ }
+
+ /**
+ * Add an event listener to the Element
+ *
+ * @param tag Element to add the listener
+ * @param axis Axis number (including hidden axes)
+ */
+ private void addEventListener(final Element tag, final int axis) {
+ EventTarget targ = (EventTarget) tag;
+ targ.addEventListener(SVGConstants.SVG_EVENT_CLICK, new EventListener() {
+ @Override
+ public void handleEvent(Event evt) {
+ if(proj.getVisibleDimensions() > 2) {
+ proj.toggleAxisVisible(axis);
+ context.getHierarchy().resultChanged(proj);
+ }
+ }
+ }, false);
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ if(!svgp.getCSSClassManager().contains(SELECTAXISVISIBILITY)) {
+ CSSClass cls = new CSSClass(this, SELECTAXISVISIBILITY);
+ cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, 0.1);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_BLUE_VALUE);
+ svgp.addCSSClassOrLogError(cls);
+ }
+ if(!svgp.getCSSClassManager().contains(SAV_BORDER)) {
+ CSSClass cls = new CSSClass(this, SAV_BORDER);
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_GREY_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT) * .5);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ svgp.addCSSClassOrLogError(cls);
+ }
+ if(!svgp.getCSSClassManager().contains(SAV_BUTTON)) {
+ CSSClass cls = new CSSClass(this, SAV_BUTTON);
+ cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, 0.01);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_GREY_VALUE);
+ cls.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE);
+ svgp.addCSSClassOrLogError(cls);
+ }
+ if(!svgp.getCSSClassManager().contains(SAV_CROSS)) {
+ CSSClass cls = new CSSClass(this, SAV_CROSS);
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_BLACK_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT) * .75);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ svgp.addCSSClassOrLogError(cls);
+ }
+ }
+ }
+}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/LineVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/LineVisualization.java index 8269d8fe..e9726ba6 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/LineVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/LineVisualization.java @@ -53,163 +53,164 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualizati * Generates data lines.
*
* @author Robert Rödler
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class LineVisualization extends AbstractParallelVisualization<NumberVector<?, ?>> implements DataStoreListener {
- /**
- * Generic tags to indicate the type of element. Used in IDs, CSS-Classes etc.
- */
- public static final String DATALINE = "Dataline";
-
+public class LineVisualization extends AbstractVisFactory {
/**
- * Sample we visualize.
+ * A short name characterizing this Visualizer.
*/
- private SamplingResult sample;
+ public static final String NAME = "Data lines";
/**
- * Constructor.
- *
- * @param task VisualizationTask
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
*/
- public LineVisualization(VisualizationTask task) {
- super(task);
- this.sample = ResultUtil.getSamplingResult(relation);
- context.addResultListener(this);
- context.addDataStoreListener(this);
- incrementalRedraw();
+ public LineVisualization() {
+ super();
+ thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_STYLE;
}
@Override
- public void destroy() {
- context.removeDataStoreListener(this);
- context.removeResultListener(this);
- super.destroy();
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
@Override
- public void resultChanged(Result current) {
- super.resultChanged(current);
- if(current == sample || current == context.getStyleResult()) {
- synchronizedRedraw();
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<ParallelPlotProjector<?>> ps = ResultUtil.filterResults(result, ParallelPlotProjector.class);
+ for(ParallelPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, p.getRelation(), p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA;
+ baseResult.getHierarchy().add(p, task);
}
}
- @Override
- protected void redraw() {
- StylingPolicy sp = context.getStyleResult().getStylingPolicy();
- addCSSClasses(svgp, sp);
+ /**
+ * Instance for a particular data set.
+ *
+ * @author Robert Rödler
+ */
+ public class Instance extends AbstractParallelVisualization<NumberVector<?>> implements DataStoreListener {
+ /**
+ * Generic tags to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
+ */
+ public static final String DATALINE = "Dataline";
- DBIDIter ids = sample.getSample().iter();
- if(ids == null || !ids.valid()) {
- ids = relation.iterDBIDs();
+ /**
+ * Sample we visualize.
+ */
+ private SamplingResult sample;
+
+ /**
+ * Constructor.
+ *
+ * @param task VisualizationTask
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.sample = ResultUtil.getSamplingResult(relation);
+ context.addResultListener(this);
+ context.addDataStoreListener(this);
+ incrementalRedraw();
+ }
+
+ @Override
+ public void destroy() {
+ context.removeDataStoreListener(this);
+ context.removeResultListener(this);
+ super.destroy();
+ }
+
+ @Override
+ public void resultChanged(Result current) {
+ super.resultChanged(current);
+ if(current == sample || current == context.getStyleResult()) {
+ synchronizedRedraw();
+ }
}
- if(sp instanceof ClassStylingPolicy) {
- ClassStylingPolicy csp = (ClassStylingPolicy) sp;
- for(int c = csp.getMinStyle(); c < csp.getMaxStyle(); c++) {
- String key = DATALINE + "_" + c;
- for(DBIDIter iter = csp.iterateClass(c); iter.valid(); iter.advance()) {
- if(!sample.getSample().contains(iter)) {
- continue; // TODO: can we test more efficiently than this?
+
+ @Override
+ protected void redraw() {
+ StylingPolicy sp = context.getStyleResult().getStylingPolicy();
+ addCSSClasses(svgp, sp);
+
+ DBIDIter ids = sample.getSample().iter();
+ if(ids == null || !ids.valid()) {
+ ids = relation.iterDBIDs();
+ }
+ if(sp instanceof ClassStylingPolicy) {
+ ClassStylingPolicy csp = (ClassStylingPolicy) sp;
+ for(int c = csp.getMinStyle(); c < csp.getMaxStyle(); c++) {
+ String key = DATALINE + "_" + c;
+ for(DBIDIter iter = csp.iterateClass(c); iter.valid(); iter.advance()) {
+ if(!sample.getSample().contains(iter)) {
+ continue; // TODO: can we test more efficiently than this?
+ }
+ SVGPath path = new SVGPath();
+ double[] yPos = proj.fastProjectDataToRenderSpace(relation.get(iter));
+ for(int i = 0; i < yPos.length; i++) {
+ path.drawTo(getVisibleAxisX(i), yPos[i]);
+ }
+ Element line = path.makeElement(svgp);
+ SVGUtil.addCSSClass(line, key);
+ layer.appendChild(line);
}
+ }
+ }
+ else {
+ for(; ids.valid(); ids.advance()) {
SVGPath path = new SVGPath();
- double[] yPos = proj.fastProjectDataToRenderSpace(relation.get(iter));
+ double[] yPos = proj.fastProjectDataToRenderSpace(relation.get(ids));
for(int i = 0; i < yPos.length; i++) {
path.drawTo(getVisibleAxisX(i), yPos[i]);
}
Element line = path.makeElement(svgp);
- SVGUtil.addCSSClass(line, key);
+ SVGUtil.addCSSClass(line, DATALINE);
+ // assign color
+ line.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, SVGConstants.CSS_STROKE_PROPERTY + ":" + SVGUtil.colorToString(sp.getColorForDBID(ids)));
layer.appendChild(line);
}
}
}
- else {
- for(; ids.valid(); ids.advance()) {
- SVGPath path = new SVGPath();
- double[] yPos = proj.fastProjectDataToRenderSpace(relation.get(ids));
- for(int i = 0; i < yPos.length; i++) {
- path.drawTo(getVisibleAxisX(i), yPos[i]);
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp, StylingPolicy sp) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ final LineStyleLibrary lines = style.lines();
+ final double width = .5 * style.getLineWidth(StyleLibrary.PLOT);
+ if(sp instanceof ClassStylingPolicy) {
+ ClassStylingPolicy csp = (ClassStylingPolicy) sp;
+ for(int i = csp.getMinStyle(); i < csp.getMaxStyle(); i++) {
+ String key = DATALINE + "_" + i;
+ if(!svgp.getCSSClassManager().contains(key)) {
+ CSSClass cls = new CSSClass(this, key);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ lines.formatCSSClass(cls, i, width);
+ svgp.addCSSClassOrLogError(cls);
+ }
}
- Element line = path.makeElement(svgp);
- SVGUtil.addCSSClass(line, DATALINE);
- // assign color
- line.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, SVGConstants.CSS_STROKE_PROPERTY + ":" + SVGUtil.colorToString(sp.getColorForDBID(ids)));
- layer.appendChild(line);
}
- }
- }
-
- /**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- private void addCSSClasses(SVGPlot svgp, StylingPolicy sp) {
- final StyleLibrary style = context.getStyleLibrary();
- final LineStyleLibrary lines = style.lines();
- final double width = .5 * style.getLineWidth(StyleLibrary.PLOT);
- if(sp instanceof ClassStylingPolicy) {
- ClassStylingPolicy csp = (ClassStylingPolicy) sp;
- for(int i = csp.getMinStyle(); i < csp.getMaxStyle(); i++) {
- String key = DATALINE + "_" + i;
- if(!svgp.getCSSClassManager().contains(key)) {
- CSSClass cls = new CSSClass(this, key);
+ else {
+ // Class for the distance function
+ if(!svgp.getCSSClassManager().contains(DATALINE)) {
+ CSSClass cls = new CSSClass(this, DATALINE);
cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
- lines.formatCSSClass(cls, i, width);
+ lines.formatCSSClass(cls, -1, width);
svgp.addCSSClassOrLogError(cls);
}
}
- }
- else {
- // Class for the distance function
- if(!svgp.getCSSClassManager().contains(DATALINE)) {
- CSSClass cls = new CSSClass(this, DATALINE);
- cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
- cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
- lines.formatCSSClass(cls, -1, width);
- svgp.addCSSClassOrLogError(cls);
- }
- }
- svgp.updateStyleElement();
- }
-
- /**
- * Factory for axis visualizations
- *
- * @author Robert Rödler
- *
- * @apiviz.stereotype factory
- * @apiviz.uses LineVisualization oneway - - «create»
- */
- public static class Factory extends AbstractVisFactory {
- /**
- * A short name characterizing this Visualizer.
- */
- private static final String NAME = "Data lines";
-
- /**
- * Constructor, adhering to
- * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
- */
- public Factory() {
- super();
- thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_STYLE;
- }
-
- @Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new LineVisualization(task);
- }
-
- @Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<ParallelPlotProjector<?>> ps = ResultUtil.filterResults(result, ParallelPlotProjector.class);
- for(ParallelPlotProjector<?> p : ps) {
- final VisualizationTask task = new VisualizationTask(NAME, p.getRelation(), p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA);
- baseResult.getHierarchy().add(p, task);
- }
+ svgp.updateStyleElement();
}
}
}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/ParallelAxisVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/ParallelAxisVisualization.java index 6209db6b..00ef60ed 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/ParallelAxisVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/ParallelAxisVisualization.java @@ -32,10 +32,10 @@ import org.w3c.dom.events.EventListener; import org.w3c.dom.events.EventTarget;
import de.lmu.ifi.dbs.elki.data.NumberVector;
+import de.lmu.ifi.dbs.elki.database.relation.RelationUtil;
import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
import de.lmu.ifi.dbs.elki.result.Result;
import de.lmu.ifi.dbs.elki.result.ResultUtil;
-import de.lmu.ifi.dbs.elki.utilities.DatabaseUtil;
import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
import de.lmu.ifi.dbs.elki.visualization.css.CSSClassManager.CSSNamingConflict;
@@ -52,144 +52,150 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; *
* @author Robert Rödler
*
- * @apiviz.uses SVGSimpleLinearAxis
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-// TODO: split into interactive / non-interactive parts?
-public class ParallelAxisVisualization extends AbstractParallelVisualization<NumberVector<?, ?>> {
+public class ParallelAxisVisualization extends AbstractVisFactory {
/**
- * Axis label class
+ * A short name characterizing this Visualizer.
*/
- public final static String AXIS_LABEL = "paxis-label";
-
- /**
- * Clickable area for the axis.
- */
- public final static String INVERTEDAXIS = "paxis-button";
+ private static final String NAME = "Parallel Axes";
/**
* Constructor.
- *
- * @param task VisualizationTask
*/
- public ParallelAxisVisualization(VisualizationTask task) {
- super(task);
- incrementalRedraw();
- context.addResultListener(this);
+ public ParallelAxisVisualization() {
+ super();
}
@Override
- protected void redraw() {
- addCSSClasses(svgp);
- final int dim = proj.getVisibleDimensions();
- try {
- for(int i = 0; i < dim; i++) {
- final int truedim = proj.getDimForVisibleAxis(i);
- final double axisX = getVisibleAxisX(i);
- if(!proj.isAxisInverted(i)) {
- SVGSimpleLinearAxis.drawAxis(svgp, layer, proj.getAxisScale(i), axisX, getSizeY(), axisX, 0, SVGSimpleLinearAxis.LabelStyle.ENDLABEL, context.getStyleLibrary());
- }
- else {
- SVGSimpleLinearAxis.drawAxis(svgp, layer, proj.getAxisScale(i), axisX, 0, axisX, getSizeY(), SVGSimpleLinearAxis.LabelStyle.ENDLABEL, context.getStyleLibrary());
- }
- // Get axis label
- final String label = DatabaseUtil.getColumnLabel(relation, truedim + 1);
- // Add axis label
- Element text = svgp.svgText(axisX, -.7 * getMarginTop(), label);
- SVGUtil.setCSSClass(text, AXIS_LABEL);
- layer.appendChild(text);
- // TODO: Split into background + clickable layer.
- Element button = svgp.svgRect(axisX - getAxisSep() * .475, -getMarginTop(), .95 * getAxisSep(), .5 * getMarginTop());
- SVGUtil.setCSSClass(button, INVERTEDAXIS);
- addEventListener(button, truedim);
- layer.appendChild(button);
- }
- }
- catch(CSSNamingConflict e) {
- throw new RuntimeException("Conflict in CSS naming for axes.", e);
- }
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
- /**
- * Add the main CSS classes.
- *
- * @param svgp Plot to draw to
- */
- private void addCSSClasses(SVGPlot svgp) {
- final StyleLibrary style = context.getStyleLibrary();
- if(!svgp.getCSSClassManager().contains(AXIS_LABEL)) {
- CSSClass cls = new CSSClass(this, AXIS_LABEL);
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getTextColor(StyleLibrary.AXIS_LABEL));
- cls.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, style.getFontFamily(StyleLibrary.AXIS_LABEL));
- cls.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, style.getTextSize(StyleLibrary.AXIS_LABEL));
- cls.setStatement(SVGConstants.CSS_TEXT_ANCHOR_PROPERTY, SVGConstants.SVG_MIDDLE_VALUE);
- svgp.addCSSClassOrLogError(cls);
- }
- if(!svgp.getCSSClassManager().contains(INVERTEDAXIS)) {
- CSSClass cls = new CSSClass(this, INVERTEDAXIS);
- cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, 0.1);
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_GREY_VALUE);
- svgp.addCSSClassOrLogError(cls);
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<ParallelPlotProjector<?>> ps = ResultUtil.filterResults(result, ParallelPlotProjector.class);
+ for(ParallelPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, p, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_BACKGROUND;
+ baseResult.getHierarchy().add(p, task);
}
}
- /**
- * Add an event listener to the Element
- *
- * @param tag Element to add the listener
- * @param i Tool number for the Element
- */
- private void addEventListener(final Element tag, final int i) {
- EventTarget targ = (EventTarget) tag;
- targ.addEventListener(SVGConstants.SVG_EVENT_CLICK, new EventListener() {
- @Override
- public void handleEvent(Event evt) {
- proj.toggleDimInverted(i);
- context.getHierarchy().resultChanged(proj);
- }
- }, false);
+ @Override
+ public boolean allowThumbnails(VisualizationTask task) {
+ // Don't use thumbnails
+ return true;
}
/**
- * Factory for axis visualizations
+ * Instance.
*
* @author Robert Rödler
*
- * @apiviz.stereotype factory
- * @apiviz.uses ParallelAxisVisualization oneway - - «create»
+ * @apiviz.uses SVGSimpleLinearAxis
*/
- public static class Factory extends AbstractVisFactory {
+ // TODO: split into interactive / non-interactive parts?
+ public class Instance extends AbstractParallelVisualization<NumberVector<?>> {
+ /**
+ * Axis label class.
+ */
+ public static final String AXIS_LABEL = "paxis-label";
+
/**
- * A short name characterizing this Visualizer.
+ * Clickable area for the axis.
*/
- private static final String NAME = "Parallel Axes";
+ public static final String INVERTEDAXIS = "paxis-button";
/**
- * Constructor, adhering to
- * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ * Constructor.
+ *
+ * @param task VisualizationTask
*/
- public Factory() {
- super();
+ public Instance(VisualizationTask task) {
+ super(task);
+ incrementalRedraw();
+ context.addResultListener(this);
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new ParallelAxisVisualization(task);
+ protected void redraw() {
+ addCSSClasses(svgp);
+ final int dim = proj.getInputDimensionality();
+ try {
+ for(int i = 0, vdim = 0; i < dim; i++) {
+ if(!proj.isAxisVisible(i)) {
+ continue;
+ }
+ final int truedim = proj.getDimForVisibleAxis(vdim);
+ final double axisX = getVisibleAxisX(vdim);
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ if(!proj.isAxisInverted(vdim)) {
+ SVGSimpleLinearAxis.drawAxis(svgp, layer, proj.getAxisScale(i), axisX, getSizeY(), axisX, 0, SVGSimpleLinearAxis.LabelStyle.ENDLABEL, style);
+ }
+ else {
+ SVGSimpleLinearAxis.drawAxis(svgp, layer, proj.getAxisScale(i), axisX, 0, axisX, getSizeY(), SVGSimpleLinearAxis.LabelStyle.ENDLABEL, style);
+ }
+ // Get axis label
+ final String label = RelationUtil.getColumnLabel(relation, truedim);
+ // Add axis label
+ Element text = svgp.svgText(axisX, -.7 * getMarginTop(), label);
+ SVGUtil.setCSSClass(text, AXIS_LABEL);
+ SVGUtil.setAtt(text, SVGConstants.SVG_TEXT_LENGTH_ATTRIBUTE, getAxisSep() * 0.95);
+ SVGUtil.setAtt(text, SVGConstants.SVG_LENGTH_ADJUST_ATTRIBUTE, SVGConstants.SVG_SPACING_AND_GLYPHS_VALUE);
+ layer.appendChild(text);
+ // TODO: Split into background + clickable layer.
+ Element button = svgp.svgRect(axisX - getAxisSep() * .475, -getMarginTop(), .95 * getAxisSep(), .5 * getMarginTop());
+ SVGUtil.setCSSClass(button, INVERTEDAXIS);
+ addEventListener(button, truedim);
+ layer.appendChild(button);
+ vdim++;
+ }
+ }
+ catch(CSSNamingConflict e) {
+ throw new RuntimeException("Conflict in CSS naming for axes.", e);
+ }
}
- @Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<ParallelPlotProjector<?>> ps = ResultUtil.filterResults(result, ParallelPlotProjector.class);
- for(ParallelPlotProjector<?> p : ps) {
- final VisualizationTask task = new VisualizationTask(NAME, p, p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_BACKGROUND);
- baseResult.getHierarchy().add(p, task);
+ /**
+ * Add the main CSS classes.
+ *
+ * @param svgp Plot to draw to
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ if(!svgp.getCSSClassManager().contains(AXIS_LABEL)) {
+ CSSClass cls = new CSSClass(this, AXIS_LABEL);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getTextColor(StyleLibrary.AXIS_LABEL));
+ cls.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, style.getFontFamily(StyleLibrary.AXIS_LABEL));
+ cls.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, style.getTextSize(StyleLibrary.AXIS_LABEL));
+ cls.setStatement(SVGConstants.CSS_TEXT_ANCHOR_PROPERTY, SVGConstants.SVG_MIDDLE_VALUE);
+ svgp.addCSSClassOrLogError(cls);
+ }
+ if(!svgp.getCSSClassManager().contains(INVERTEDAXIS)) {
+ CSSClass cls = new CSSClass(this, INVERTEDAXIS);
+ cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, 0.1);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_GREY_VALUE);
+ svgp.addCSSClassOrLogError(cls);
}
}
- @Override
- public boolean allowThumbnails(VisualizationTask task) {
- // Don't use thumbnails
- return true;
+ /**
+ * Add an event listener to the Element.
+ *
+ * @param tag Element to add the listener
+ * @param i Tool number for the Element
+ */
+ private void addEventListener(final Element tag, final int i) {
+ EventTarget targ = (EventTarget) tag;
+ targ.addEventListener(SVGConstants.SVG_EVENT_CLICK, new EventListener() {
+ @Override
+ public void handleEvent(Event evt) {
+ proj.toggleDimInverted(i);
+ context.getHierarchy().resultChanged(proj);
+ }
+ }, false);
}
}
}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/cluster/ClusterOutlineVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/cluster/ClusterOutlineVisualization.java index f34b7514..47a6d9a8 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/cluster/ClusterOutlineVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/cluster/ClusterOutlineVisualization.java @@ -34,7 +34,7 @@ import de.lmu.ifi.dbs.elki.data.Clustering; import de.lmu.ifi.dbs.elki.data.NumberVector;
import de.lmu.ifi.dbs.elki.data.model.Model;
import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener;
-import de.lmu.ifi.dbs.elki.database.ids.DBID;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDIter;
import de.lmu.ifi.dbs.elki.math.DoubleMinMax;
import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
import de.lmu.ifi.dbs.elki.result.Result;
@@ -56,203 +56,206 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.AbstractParallelVi * Generates a SVG-Element that visualizes the area covered by a cluster.
*
* @author Robert Rödler
- * @author Erich Schubert
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class ClusterOutlineVisualization extends AbstractParallelVisualization<NumberVector<?, ?>> implements DataStoreListener {
+// TODO: make parameterizable: rounded.
+public class ClusterOutlineVisualization extends AbstractVisFactory {
/**
- * Generic tags to indicate the type of element. Used in IDs, CSS-Classes etc.
+ * A short name characterizing this Visualizer.
*/
- public static final String CLUSTERAREA = "Clusteroutline";
+ public static final String NAME = "Cluster Outline";
/**
- * The result we visualize
+ * Currently unused option to enable/disable rounding
*/
- private Clustering<Model> clustering;
+ public static final OptionID ROUNDED_ID = new OptionID("parallel.clusteroutline.rounded", "Draw lines rounded");
/**
- * Flag for using rounded shapes
+ * Currently, always enabled.
*/
- boolean rounded = true;
+ private boolean rounded = true;
/**
- * Constructor.
- *
- * @param task VisualizationTask
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
*/
- public ClusterOutlineVisualization(VisualizationTask task, boolean rounded) {
- super(task);
- this.clustering = task.getResult();
- this.rounded = rounded;
- context.addDataStoreListener(this);
- context.addResultListener(this);
- incrementalRedraw();
+ public ClusterOutlineVisualization() {
+ super();
}
@Override
- public void destroy() {
- context.removeDataStoreListener(this);
- context.removeResultListener(this);
- super.destroy();
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task, rounded);
}
@Override
- protected void redraw() {
- addCSSClasses(svgp);
- int dim = proj.getVisibleDimensions();
-
- DoubleMinMax[] mms = DoubleMinMax.newArray(dim);
- DoubleMinMax[] midmm = DoubleMinMax.newArray(dim - 1);
-
- Iterator<Cluster<Model>> ci = clustering.getAllClusters().iterator();
- for(int cnum = 0; cnum < clustering.getAllClusters().size(); cnum++) {
- Cluster<?> clus = ci.next();
- for(int i = 0; i < dim; i++) {
- mms[i].reset();
- if(i < dim - 1) {
- midmm[i].reset();
- }
- }
-
- // Process points
- // TODO: do this just once, cache the result somewhere appropriately?
- for(DBID objId : clus.getIDs()) {
- double[] yPos = proj.fastProjectDataToRenderSpace(relation.get(objId));
- for(int i = 0; i < dim; i++) {
- mms[i].put(yPos[i]);
- if(i > 0) {
- midmm[i - 1].put((yPos[i] + yPos[i - 1]) / 2.);
- }
- }
- }
-
- SVGPath path = new SVGPath();
- if(!rounded) {
- // Straight lines
- for(int i = 0; i < dim; i++) {
- path.drawTo(getVisibleAxisX(i), mms[i].getMax());
- if(i < dim - 1) {
- path.drawTo(getVisibleAxisX(i + .5), midmm[i].getMax());
- }
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ // Find clusterings we can visualize:
+ Collection<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class);
+ for(Clustering<?> c : clusterings) {
+ if(c.getAllClusters().size() > 0) {
+ Collection<ParallelPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ParallelPlotProjector.class);
+ for(ParallelPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA - 1;
+ task.initDefaultVisibility(false);
+ baseResult.getHierarchy().add(c, task);
+ baseResult.getHierarchy().add(p, task);
}
- for(int i = dim - 1; i >= 0; i--) {
- if(i < dim - 1) {
- path.drawTo(getVisibleAxisX(i + .5), midmm[i].getMin());
- }
- path.drawTo(getVisibleAxisX(i), mms[i].getMin());
- }
- path.close();
- }
- else {
- // Maxima
- path.drawTo(getVisibleAxisX(0), mms[0].getMax());
- for(int i = 1; i < dim; i++) {
- path.quadTo(getVisibleAxisX(i - .5), midmm[i - 1].getMax(), getVisibleAxisX(i), mms[i].getMax());
- }
- // Minima
- path.drawTo(getVisibleAxisX(dim - 1), mms[dim - 1].getMin());
- for(int i = dim - 1; i > 0; i--) {
- path.quadTo(getVisibleAxisX(i - .5), midmm[i - 1].getMin(), getVisibleAxisX(i - 1), mms[i - 1].getMin());
- }
- path.close();
}
-
- Element intervals = path.makeElement(svgp);
- SVGUtil.addCSSClass(intervals, CLUSTERAREA + cnum);
- layer.appendChild(intervals);
}
}
- /**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- private void addCSSClasses(SVGPlot svgp) {
- if(!svgp.getCSSClassManager().contains(CLUSTERAREA)) {
- ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT);
- int clusterID = 0;
-
- for(@SuppressWarnings("unused")
- Cluster<?> cluster : clustering.getAllClusters()) {
- CSSClass cls = new CSSClass(this, CLUSTERAREA + clusterID);
- // cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY,
- // context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT) / 2.0);
- cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, 0.5);
- final String color;
- if(clustering.getAllClusters().size() == 1) {
- color = SVGConstants.CSS_BLACK_VALUE;
- }
- else {
- color = colors.getColor(clusterID);
- }
- // cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, color);
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, color);
-
- svgp.addCSSClassOrLogError(cls);
- clusterID++;
- }
- }
+ @Override
+ public boolean allowThumbnails(VisualizationTask task) {
+ // Don't use thumbnails
+ return false;
}
/**
- * Factory for axis visualizations
+ * Instance
*
* @author Robert Rödler
- *
- * @apiviz.stereotype factory
- * @apiviz.uses ClusterOutlineVisualization oneway - - «create»
+ * @author Erich Schubert
*/
- public static class Factory extends AbstractVisFactory {
+ public class Instance extends AbstractParallelVisualization<NumberVector<?>> implements DataStoreListener {
/**
- * A short name characterizing this Visualizer.
+ * Generic tags to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
*/
- private static final String NAME = "Cluster Outline";
+ public static final String CLUSTERAREA = "Clusteroutline";
/**
- * Currently unused option to enable/disable rounding
+ * The result we visualize
*/
- public static final OptionID ROUNDED_ID = OptionID.getOrCreateOptionID("parallel.clusteroutline.rounded", "Draw lines rounded");
+ private Clustering<Model> clustering;
/**
- * Currently, always enabled.
+ * Flag for using rounded shapes
*/
- private boolean rounded = true;
+ boolean rounded = true;
/**
- * Constructor, adhering to
- * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ * Constructor.
+ *
+ * @param task VisualizationTask
*/
- public Factory() {
- super();
+ public Instance(VisualizationTask task, boolean rounded) {
+ super(task);
+ this.clustering = task.getResult();
+ this.rounded = rounded;
+ context.addDataStoreListener(this);
+ context.addResultListener(this);
+ incrementalRedraw();
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new ClusterOutlineVisualization(task, rounded);
+ public void destroy() {
+ context.removeDataStoreListener(this);
+ context.removeResultListener(this);
+ super.destroy();
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- // Find clusterings we can visualize:
- Collection<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class);
- for(Clustering<?> c : clusterings) {
- if(c.getAllClusters().size() > 0) {
- Collection<ParallelPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ParallelPlotProjector.class);
- for(ParallelPlotProjector<?> p : ps) {
- final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA - 1);
- task.put(VisualizationTask.META_VISIBLE_DEFAULT, false);
- baseResult.getHierarchy().add(c, task);
- baseResult.getHierarchy().add(p, task);
+ protected void redraw() {
+ addCSSClasses(svgp);
+ int dim = proj.getVisibleDimensions();
+
+ DoubleMinMax[] mms = DoubleMinMax.newArray(dim);
+ DoubleMinMax[] midmm = DoubleMinMax.newArray(dim - 1);
+
+ Iterator<Cluster<Model>> ci = clustering.getAllClusters().iterator();
+ for(int cnum = 0; cnum < clustering.getAllClusters().size(); cnum++) {
+ Cluster<?> clus = ci.next();
+ for(int i = 0; i < dim; i++) {
+ mms[i].reset();
+ if(i < dim - 1) {
+ midmm[i].reset();
+ }
+ }
+
+ // Process points
+ // TODO: do this just once, cache the result somewhere appropriately?
+ for(DBIDIter id = clus.getIDs().iter(); id.valid(); id.advance()) {
+ double[] yPos = proj.fastProjectDataToRenderSpace(relation.get(id));
+ for(int i = 0; i < dim; i++) {
+ mms[i].put(yPos[i]);
+ if(i > 0) {
+ midmm[i - 1].put((yPos[i] + yPos[i - 1]) / 2.);
+ }
+ }
+ }
+
+ SVGPath path = new SVGPath();
+ if(!rounded) {
+ // Straight lines
+ for(int i = 0; i < dim; i++) {
+ path.drawTo(getVisibleAxisX(i), mms[i].getMax());
+ if(i < dim - 1) {
+ path.drawTo(getVisibleAxisX(i + .5), midmm[i].getMax());
+ }
+ }
+ for(int i = dim - 1; i >= 0; i--) {
+ if(i < dim - 1) {
+ path.drawTo(getVisibleAxisX(i + .5), midmm[i].getMin());
+ }
+ path.drawTo(getVisibleAxisX(i), mms[i].getMin());
}
+ path.close();
}
+ else {
+ // Maxima
+ path.drawTo(getVisibleAxisX(0), mms[0].getMax());
+ for(int i = 1; i < dim; i++) {
+ path.quadTo(getVisibleAxisX(i - .5), midmm[i - 1].getMax(), getVisibleAxisX(i), mms[i].getMax());
+ }
+ // Minima
+ path.drawTo(getVisibleAxisX(dim - 1), mms[dim - 1].getMin());
+ for(int i = dim - 1; i > 0; i--) {
+ path.quadTo(getVisibleAxisX(i - .5), midmm[i - 1].getMin(), getVisibleAxisX(i - 1), mms[i - 1].getMin());
+ }
+ path.close();
+ }
+
+ Element intervals = path.makeElement(svgp);
+ SVGUtil.addCSSClass(intervals, CLUSTERAREA + cnum);
+ layer.appendChild(intervals);
}
}
- @Override
- public boolean allowThumbnails(VisualizationTask task) {
- // Don't use thumbnails
- return false;
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ if(!svgp.getCSSClassManager().contains(CLUSTERAREA)) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ ColorLibrary colors = style.getColorSet(StyleLibrary.PLOT);
+ int clusterID = 0;
+
+ for(@SuppressWarnings("unused")
+ Cluster<?> cluster : clustering.getAllClusters()) {
+ CSSClass cls = new CSSClass(this, CLUSTERAREA + clusterID);
+ // cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY,
+ // context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT) / 2.0);
+ cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, 0.5);
+ final String color;
+ if(clustering.getAllClusters().size() == 1) {
+ color = SVGConstants.CSS_BLACK_VALUE;
+ }
+ else {
+ color = colors.getColor(clusterID);
+ }
+ // cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, color);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, color);
+
+ svgp.addCSSClassOrLogError(cls);
+ clusterID++;
+ }
+ }
}
}
}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/cluster/ClusterParallelMeanVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/cluster/ClusterParallelMeanVisualization.java index 1808c241..ad3ed503 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/cluster/ClusterParallelMeanVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/cluster/ClusterParallelMeanVisualization.java @@ -53,162 +53,166 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.AbstractParallelVi * Generates a SVG-Element that visualizes cluster means.
*
* @author Robert Rödler
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class ClusterParallelMeanVisualization extends AbstractParallelVisualization<NumberVector<?, ?>> implements DataStoreListener {
+public class ClusterParallelMeanVisualization extends AbstractVisFactory {
/**
- * Generic tags to indicate the type of element. Used in IDs, CSS-Classes etc.
+ * A short name characterizing this Visualizer.
*/
- public static final String CLUSTERMEAN = "Clustermean";
+ private static final String NAME = "Cluster Means";
/**
- * The result we visualize
- */
- private Clustering<MeanModel<? extends NumberVector<?, ?>>> clustering;
-
- /**
- * Constructor.
- *
- * @param task VisualizationTask
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}.
*/
- public ClusterParallelMeanVisualization(VisualizationTask task) {
- super(task);
- this.clustering = task.getResult();
- context.addDataStoreListener(this);
- context.addResultListener(this);
- incrementalRedraw();
+ public ClusterParallelMeanVisualization() {
+ super();
}
@Override
- public void destroy() {
- context.removeDataStoreListener(this);
- context.removeResultListener(this);
- super.destroy();
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
@Override
- protected void redraw() {
- addCSSClasses(svgp);
-
- Iterator<Cluster<MeanModel<? extends NumberVector<?, ?>>>> ci = clustering.getAllClusters().iterator();
- for(int cnum = 0; cnum < clustering.getAllClusters().size(); cnum++) {
- Cluster<MeanModel<? extends NumberVector<?, ?>>> clus = ci.next();
- NumberVector<?, ?> mean = clus.getModel().getMean();
- if(mean == null) {
- continue;
- }
-
- double[] pmean = proj.fastProjectDataToRenderSpace(mean);
-
- SVGPath path = new SVGPath();
- for(int i = 0; i < pmean.length; i++) {
- path.drawTo(getVisibleAxisX(i), pmean[i]);
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ // Find clusterings we can visualize:
+ Collection<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class);
+ for (Clustering<?> c : clusterings) {
+ if (c.getAllClusters().size() > 0) {
+ // Does the cluster have a model with cluster means?
+ Clustering<MeanModel<? extends NumberVector<?>>> mcls = findMeanModel(c);
+ if (mcls != null) {
+ Collection<ParallelPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ParallelPlotProjector.class);
+ for (ParallelPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA + 1;
+ baseResult.getHierarchy().add(c, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
}
-
- Element meanline = path.makeElement(svgp);
- SVGUtil.addCSSClass(meanline, CLUSTERMEAN + cnum);
- layer.appendChild(meanline);
}
}
/**
- * Adds the required CSS-Classes
+ * Test if the given clustering has a mean model.
*
- * @param svgp SVG-Plot
+ * @param c Clustering to inspect
+ * @return the clustering cast to return a mean model, null otherwise.
*/
- private void addCSSClasses(SVGPlot svgp) {
- if(!svgp.getCSSClassManager().contains(CLUSTERMEAN)) {
- ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT);
- int clusterID = 0;
-
- for(@SuppressWarnings("unused")
- Cluster<?> cluster : clustering.getAllClusters()) {
- CSSClass cls = new CSSClass(this, CLUSTERMEAN + clusterID);
- cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT) * 2);
-
- final String color;
- if(clustering.getAllClusters().size() == 1) {
- color = SVGConstants.CSS_BLACK_VALUE;
- }
- else {
- color = colors.getColor(clusterID);
- }
-
- cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, color);
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
-
- svgp.addCSSClassOrLogError(cls);
- clusterID++;
- }
+ @SuppressWarnings("unchecked")
+ private static Clustering<MeanModel<? extends NumberVector<?>>> findMeanModel(Clustering<?> c) {
+ if (c.getAllClusters().get(0).getModel() instanceof MeanModel<?>) {
+ return (Clustering<MeanModel<? extends NumberVector<?>>>) c;
}
+ return null;
+ }
+
+ @Override
+ public boolean allowThumbnails(VisualizationTask task) {
+ // Don't use thumbnails
+ return false;
}
/**
- * Factory for axis visualizations
+ * Instance.
*
* @author Robert Rödler
*
- * @apiviz.stereotype factory
- * @apiviz.uses ClusterParallelMeanVisualization oneway - - «create»
- *
*/
- public static class Factory extends AbstractVisFactory {
+ public class Instance extends AbstractParallelVisualization<NumberVector<?>> implements DataStoreListener {
/**
- * A short name characterizing this Visualizer.
+ * Generic tags to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
*/
- private static final String NAME = "Cluster Means";
+ public static final String CLUSTERMEAN = "Clustermean";
/**
- * Constructor, adhering to
- * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ * The result we visualize.
+ */
+ private Clustering<MeanModel<? extends NumberVector<?>>> clustering;
+
+ /**
+ * Constructor.
+ *
+ * @param task VisualizationTask
*/
- public Factory() {
- super();
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.clustering = task.getResult();
+ context.addDataStoreListener(this);
+ context.addResultListener(this);
+ incrementalRedraw();
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new ClusterParallelMeanVisualization(task);
+ public void destroy() {
+ context.removeDataStoreListener(this);
+ context.removeResultListener(this);
+ super.destroy();
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- // Find clusterings we can visualize:
- Collection<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class);
- for(Clustering<?> c : clusterings) {
- if(c.getAllClusters().size() > 0) {
- // Does the cluster have a model with cluster means?
- Clustering<MeanModel<? extends NumberVector<?, ?>>> mcls = findMeanModel(c);
- if(mcls != null) {
- Collection<ParallelPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ParallelPlotProjector.class);
- for (ParallelPlotProjector<?> p : ps) {
- final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA + 1);
- baseResult.getHierarchy().add(c, task);
- baseResult.getHierarchy().add(p, task);
- }
- }
+ protected void redraw() {
+ addCSSClasses(svgp);
+
+ Iterator<Cluster<MeanModel<? extends NumberVector<?>>>> ci = clustering.getAllClusters().iterator();
+ for (int cnum = 0; cnum < clustering.getAllClusters().size(); cnum++) {
+ Cluster<MeanModel<? extends NumberVector<?>>> clus = ci.next();
+ if (clus.getModel() == null) {
+ continue;
+ }
+ NumberVector<?> mean = clus.getModel().getMean();
+ if (mean == null) {
+ continue;
+ }
+
+ double[] pmean = proj.fastProjectDataToRenderSpace(mean);
+
+ SVGPath path = new SVGPath();
+ for (int i = 0; i < pmean.length; i++) {
+ path.drawTo(getVisibleAxisX(i), pmean[i]);
}
+
+ Element meanline = path.makeElement(svgp);
+ SVGUtil.addCSSClass(meanline, CLUSTERMEAN + cnum);
+ layer.appendChild(meanline);
}
}
/**
- * Test if the given clustering has a mean model.
+ * Adds the required CSS-Classes.
*
- * @param c Clustering to inspect
- * @return the clustering cast to return a mean model, null otherwise.
+ * @param svgp SVG-Plot
*/
- @SuppressWarnings("unchecked")
- private static Clustering<MeanModel<? extends NumberVector<?, ?>>> findMeanModel(Clustering<?> c) {
- if(c.getAllClusters().get(0).getModel() instanceof MeanModel<?>) {
- return (Clustering<MeanModel<? extends NumberVector<?, ?>>>) c;
- }
- return null;
- }
+ private void addCSSClasses(SVGPlot svgp) {
+ if (!svgp.getCSSClassManager().contains(CLUSTERMEAN)) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ ColorLibrary colors = style.getColorSet(StyleLibrary.PLOT);
+ int clusterID = 0;
+
+ for (@SuppressWarnings("unused")
+ Cluster<?> cluster : clustering.getAllClusters()) {
+ CSSClass cls = new CSSClass(this, CLUSTERMEAN + clusterID);
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT) * 2.);
+
+ final String color;
+ if (clustering.getAllClusters().size() == 1) {
+ color = SVGConstants.CSS_BLACK_VALUE;
+ } else {
+ color = colors.getColor(clusterID);
+ }
- @Override
- public boolean allowThumbnails(VisualizationTask task) {
- // Don't use thumbnails
- return false;
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, color);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+
+ svgp.addCSSClassOrLogError(cls);
+ clusterID++;
+ }
+ }
}
}
-}
\ No newline at end of file +}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/index/RTreeParallelVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/index/RTreeParallelVisualization.java index 499d14a5..7c2c8b9f 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/index/RTreeParallelVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/index/RTreeParallelVisualization.java @@ -60,13 +60,10 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.index.TreeMBRVi *
* @author Robert Rödler
*
- * @apiviz.has AbstractRStarTree oneway - - visualizes
- *
- * @param <N> Tree node type
- * @param <E> Tree entry type
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-// TODO: listen for tree changes instead of data changes?
-public class RTreeParallelVisualization<N extends AbstractRStarTreeNode<N, E>, E extends SpatialEntry> extends AbstractParallelVisualization<NumberVector<?, ?>> implements DataStoreListener {
+public class RTreeParallelVisualization extends AbstractVisFactory {
/**
* Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc.
*/
@@ -78,190 +75,187 @@ public class RTreeParallelVisualization<N extends AbstractRStarTreeNode<N, E>, E public static final String NAME = "R-Tree Index MBRs";
/**
- * Fill parameter.
- */
- protected boolean fill = true;
-
- /**
- * The tree we visualize
+ * Settings
*/
- protected AbstractRStarTree<N, E> tree;
+ protected Parameterizer settings;
/**
* Constructor.
*
- * @param task Visualization task
- * @param fill Fill flag
+ * @param settings Settings
*/
- @SuppressWarnings("unchecked")
- public RTreeParallelVisualization(VisualizationTask task, boolean fill) {
- super(task);
- this.tree = AbstractRStarTree.class.cast(task.getResult());
- this.fill = fill;
- context.addDataStoreListener(this);
- context.addResultListener(this);
- incrementalRedraw();
+ public RTreeParallelVisualization(Parameterizer settings) {
+ super();
+ this.settings = settings;
}
@Override
- public void destroy() {
- context.removeDataStoreListener(this);
- context.removeResultListener(this);
- super.destroy();
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance<RStarTreeNode, SpatialEntry>(task);
}
@Override
- protected void redraw() {
- if(tree != null) {
- addCSSClasses(svgp);
- E root = tree.getRootEntry();
- visualizeRTreeEntry(svgp, layer, proj, tree, root, 0, 0);
- }
- }
-
- /**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- private void addCSSClasses(SVGPlot svgp) {
- ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT);
-
- for(int i = 0; i < tree.getHeight(); i++) {
-
- if(!svgp.getCSSClassManager().contains(INDEX + i)) {
- CSSClass cls = new CSSClass(this, INDEX + i);
-
- // Relative depth of this level. 1.0 = toplevel
- final double relDepth = 1. - (((double) i) / tree.getHeight());
- if(fill) {
- cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(i));
- cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, relDepth * context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT));
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, colors.getColor(i));
- cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 0.2);
- }
- else {
- cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(i));
- cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, relDepth * context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT));
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ ArrayList<AbstractRStarTree<RStarTreeNode, SpatialEntry>> trees = ResultUtil.filterResults(result, AbstractRStarTree.class);
+ for(AbstractRStarTree<RStarTreeNode, SpatialEntry> tree : trees) {
+ if(tree instanceof Result) {
+ Collection<ParallelPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ParallelPlotProjector.class);
+ for(ParallelPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, (Result) tree, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_BACKGROUND + 2;
+ baseResult.getHierarchy().add((Result) tree, task);
+ baseResult.getHierarchy().add(p, task);
}
- cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
- cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
- svgp.addCSSClassOrLogError(cls);
}
}
- svgp.updateStyleElement();
}
/**
- * Recursively draw the MBR rectangles.
- *
- * @param svgp SVG Plot
- * @param layer Layer
- * @param proj Projection
- * @param rtree Rtree to visualize
- * @param entry Current entry
- * @param depth Current depth
- */
- private void visualizeRTreeEntry(SVGPlot svgp, Element layer, ProjectionParallel proj, AbstractRStarTree<? extends N, E> rtree, E entry, int depth, int step) {
- final int dim = proj.getVisibleDimensions();
- double[] min = proj.fastProjectDataToRenderSpace(SpatialUtil.getMin(entry));
- double[] max = proj.fastProjectDataToRenderSpace(SpatialUtil.getMax(entry));
- assert (min.length == dim && max.length == dim);
- SVGPath path = new SVGPath();
- for(int i = 0; i < dim; i++) {
- path.drawTo(getVisibleAxisX(i), Math.max(min[i], max[i]));
- }
- for(int i = dim - 1; i >= 0; i--) {
- path.drawTo(getVisibleAxisX(i), Math.min(min[i], max[i]));
- }
- path.close();
-
- Element intervals = path.makeElement(svgp);
-
- SVGUtil.addCSSClass(intervals, INDEX + depth);
- layer.appendChild(intervals);
-
- if(!entry.isLeafEntry()) {
- N node = rtree.getNode(entry);
- for(int i = 0; i < node.getNumEntries(); i++) {
- E child = node.getEntry(i);
- if(!child.isLeafEntry()) {
- visualizeRTreeEntry(svgp, layer, proj, rtree, child, depth + 1, ++step);
- }
- }
- }
- }
-
- /**
- * Factory
+ * Instance for a particular data set and tree
*
* @author Robert Rödler
*
- * @apiviz.stereotype factory
- * @apiviz.uses RTreeParallelVisualization oneway - - «create»
+ * @apiviz.has AbstractRStarTree oneway - - visualizes
+ *
+ * @param <N> Tree node type
+ * @param <E> Tree entry type
*/
- public static class Factory extends AbstractVisFactory {
+ // TODO: listen for tree changes instead of data changes?
+ public class Instance<N extends AbstractRStarTreeNode<N, E>, E extends SpatialEntry> extends AbstractParallelVisualization<NumberVector<?>> implements DataStoreListener {
/**
- * Fill parameter.
+ * The tree we visualize
*/
- protected boolean fill = true;
+ protected AbstractRStarTree<N, E> tree;
/**
* Constructor.
*
- * @param fill
+ * @param task Visualization task
*/
- public Factory(boolean fill) {
- super();
- this.fill = fill;
+ @SuppressWarnings("unchecked")
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.tree = AbstractRStarTree.class.cast(task.getResult());
+ context.addDataStoreListener(this);
+ context.addResultListener(this);
+ incrementalRedraw();
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new RTreeParallelVisualization<RStarTreeNode, SpatialEntry>(task, fill);
+ public void destroy() {
+ context.removeDataStoreListener(this);
+ context.removeResultListener(this);
+ super.destroy();
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- ArrayList<AbstractRStarTree<RStarTreeNode, SpatialEntry>> trees = ResultUtil.filterResults(result, AbstractRStarTree.class);
- for(AbstractRStarTree<RStarTreeNode, SpatialEntry> tree : trees) {
- if(tree instanceof Result) {
- Collection<ParallelPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ParallelPlotProjector.class);
- for(ParallelPlotProjector<?> p : ps) {
- final VisualizationTask task = new VisualizationTask(NAME, (Result) tree, p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_BACKGROUND + 2);
- baseResult.getHierarchy().add((Result) tree, task);
- baseResult.getHierarchy().add(p, task);
+ protected void redraw() {
+ if(tree != null) {
+ addCSSClasses(svgp);
+ E root = tree.getRootEntry();
+ visualizeRTreeEntry(svgp, layer, proj, tree, root, 0, 0);
+ }
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ final ColorLibrary colors = style.getColorSet(StyleLibrary.PLOT);
+
+ for(int i = 0; i < tree.getHeight(); i++) {
+ if(!svgp.getCSSClassManager().contains(INDEX + i)) {
+ CSSClass cls = new CSSClass(this, INDEX + i);
+
+ // Relative depth of this level. 1.0 = toplevel
+ final double relDepth = 1. - (((double) i) / tree.getHeight());
+ if(settings.fill) {
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(i));
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, relDepth * style.getLineWidth(StyleLibrary.PLOT));
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, colors.getColor(i));
+ cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 0.2);
+ }
+ else {
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(i));
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, relDepth * style.getLineWidth(StyleLibrary.PLOT));
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
}
+ cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ svgp.addCSSClassOrLogError(cls);
}
}
+ svgp.updateStyleElement();
}
/**
- * Parameterization class.
+ * Recursively draw the MBR rectangles.
*
- * @author Erich Schubert
- *
- * @apiviz.exclude
+ * @param svgp SVG Plot
+ * @param layer Layer
+ * @param proj Projection
+ * @param rtree Rtree to visualize
+ * @param entry Current entry
+ * @param depth Current depth
*/
- public static class Parameterizer extends AbstractParameterizer {
- protected boolean fill = true;
+ private void visualizeRTreeEntry(SVGPlot svgp, Element layer, ProjectionParallel proj, AbstractRStarTree<? extends N, E> rtree, E entry, int depth, int step) {
+ final int dim = proj.getVisibleDimensions();
+ double[] min = proj.fastProjectDataToRenderSpace(SpatialUtil.getMin(entry));
+ double[] max = proj.fastProjectDataToRenderSpace(SpatialUtil.getMax(entry));
+ assert (min.length == dim && max.length == dim);
+ SVGPath path = new SVGPath();
+ for(int i = 0; i < dim; i++) {
+ path.drawTo(getVisibleAxisX(i), Math.max(min[i], max[i]));
+ }
+ for(int i = dim - 1; i >= 0; i--) {
+ path.drawTo(getVisibleAxisX(i), Math.min(min[i], max[i]));
+ }
+ path.close();
+
+ Element intervals = path.makeElement(svgp);
+
+ SVGUtil.addCSSClass(intervals, INDEX + depth);
+ layer.appendChild(intervals);
- @Override
- protected void makeOptions(Parameterization config) {
- super.makeOptions(config);
- Flag fillF = new Flag(TreeMBRVisualization.Factory.FILL_ID);
- fillF.setDefaultValue(true);
- if(config.grab(fillF)) {
- fill = fillF.getValue();
+ if(!entry.isLeafEntry()) {
+ N node = rtree.getNode(entry);
+ for(int i = 0; i < node.getNumEntries(); i++) {
+ E child = node.getEntry(i);
+ if(!child.isLeafEntry()) {
+ visualizeRTreeEntry(svgp, layer, proj, rtree, child, depth + 1, ++step);
+ }
}
}
+ }
+
+ }
+
+ /**
+ * Parameterization class.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ public static class Parameterizer extends AbstractParameterizer {
+ protected boolean fill = true;
- @Override
- protected Factory makeInstance() {
- return new Factory(fill);
+ @Override
+ protected void makeOptions(Parameterization config) {
+ super.makeOptions(config);
+ Flag fillF = new Flag(TreeMBRVisualization.Parameterizer.FILL_ID);
+ fillF.setDefaultValue(Boolean.TRUE);
+ if(config.grab(fillF)) {
+ fill = fillF.isTrue();
}
}
+
+ @Override
+ protected RTreeParallelVisualization makeInstance() {
+ return new RTreeParallelVisualization(this);
+ }
}
}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionAxisRangeVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionAxisRangeVisualization.java index b894a87b..6e2a9329 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionAxisRangeVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionAxisRangeVisualization.java @@ -48,134 +48,126 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.AbstractParallelVi import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualization;
/**
- * Visualizer for generating an SVG-Element representing the selected range for
- * each dimension
+ * Visualizer for generating an SVG-Element representing the selected range.
*
* @author Robert Rödler
*
- * @apiviz.has SelectionResult oneway - - visualizes
- * @apiviz.has RangeSelection oneway - - visualizes
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class SelectionAxisRangeVisualization extends AbstractParallelVisualization<NumberVector<?, ?>> {
+public class SelectionAxisRangeVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
- private static final String NAME = "Selection Axis Range";
-
- /**
- * CSS Class for the range marker
- */
- public static final String MARKER = "selectionAxisRange";
+ public static final String NAME = "Selection Axis Range";
/**
* Constructor.
- *
- * @param task Visualization task
*/
- public SelectionAxisRangeVisualization(VisualizationTask task) {
- super(task);
- addCSSClasses(svgp);
- context.addResultListener(this);
- incrementalRedraw();
+ public SelectionAxisRangeVisualization() {
+ super();
+ thumbmask |= ThumbnailVisualization.ON_SELECTION;
}
@Override
- public void destroy() {
- context.removeResultListener(this);
- super.destroy();
- }
-
- /**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- private void addCSSClasses(SVGPlot svgp) {
- final StyleLibrary style = context.getStyleLibrary();
- // Class for the cube
- if(!svgp.getCSSClassManager().contains(MARKER)) {
- CSSClass cls = new CSSClass(this, MARKER);
- cls.setStatement(SVGConstants.CSS_STROKE_VALUE, style.getColor(StyleLibrary.SELECTION));
- cls.setStatement(SVGConstants.CSS_STROKE_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
- cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT));
- cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
- cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
-
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION));
- cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
-
- svgp.addCSSClassOrLogError(cls);
- }
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
@Override
- protected void redraw() {
- DBIDSelection selContext = context.getSelection();
- if(selContext == null || !(selContext instanceof RangeSelection)) {
- return;
- }
- DoubleDoublePair[] ranges = ((RangeSelection) selContext).getRanges();
- if(ranges == null) {
- return;
- }
-
- // Project:
- double[] min = new double[ranges.length];
- double[] max = new double[ranges.length];
- for(int d = 0; d < ranges.length; d++) {
- if(ranges[d] != null) {
- min[d] = ranges[d].first;
- max[d] = ranges[d].second;
- }
- }
- min = proj.fastProjectDataToRenderSpace(min);
- max = proj.fastProjectDataToRenderSpace(max);
-
- int dim = proj.getVisibleDimensions();
- for(int d = 0; d < dim; d++) {
- if(ranges[proj.getDimForVisibleAxis(d)] != null) {
- double amin = Math.min(min[d], max[d]);
- double amax = Math.max(min[d], max[d]);
- Element rect = svgp.svgRect(getVisibleAxisX(d) - (0.01 * StyleLibrary.SCALE), amin, 0.02 * StyleLibrary.SCALE, amax - amin);
- SVGUtil.addCSSClass(rect, MARKER);
- layer.appendChild(rect);
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
+ for(SelectionResult selres : selectionResults) {
+ Collection<ParallelPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ParallelPlotProjector.class);
+ for(ParallelPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA - 1;
+ baseResult.getHierarchy().add(selres, task);
+ baseResult.getHierarchy().add(p, task);
}
}
}
/**
- * Factory for visualizers to generate an SVG-Element containing a cube as
- * marker representing the selected range for each dimension
+ * Instance
*
* @author Robert Rödler
*
- * @apiviz.stereotype factory
- * @apiviz.uses SelectionAxisRangeVisualization oneway - - «create»
+ * @apiviz.has SelectionResult oneway - - visualizes
+ * @apiviz.has RangeSelection oneway - - visualizes
*/
- public static class Factory extends AbstractVisFactory {
+ public class Instance extends AbstractParallelVisualization<NumberVector<?>> {
+ /**
+ * CSS Class for the range marker
+ */
+ public static final String MARKER = "selectionAxisRange";
+
/**
* Constructor.
+ *
+ * @param task Visualization task
*/
- public Factory() {
- super();
- thumbmask |= ThumbnailVisualization.ON_SELECTION;
+ public Instance(VisualizationTask task) {
+ super(task);
+ addCSSClasses(svgp);
+ context.addResultListener(this);
+ incrementalRedraw();
}
- @Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new SelectionAxisRangeVisualization(task);
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ // Class for the cube
+ if(!svgp.getCSSClassManager().contains(MARKER)) {
+ CSSClass cls = new CSSClass(this, MARKER);
+ cls.setStatement(SVGConstants.CSS_STROKE_VALUE, style.getColor(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_STROKE_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT));
+ cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
+
+ svgp.addCSSClassOrLogError(cls);
+ }
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
- for(SelectionResult selres : selectionResults) {
- Collection<ParallelPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ParallelPlotProjector.class);
- for(ParallelPlotProjector<?> p : ps) {
- final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA - 1);
- baseResult.getHierarchy().add(selres, task);
- baseResult.getHierarchy().add(p, task);
+ protected void redraw() {
+ DBIDSelection selContext = context.getSelection();
+ if(!(selContext instanceof RangeSelection)) {
+ return;
+ }
+ DoubleDoublePair[] ranges = ((RangeSelection) selContext).getRanges();
+ if(ranges == null) {
+ return;
+ }
+
+ // Project:
+ double[] min = new double[ranges.length];
+ double[] max = new double[ranges.length];
+ for(int d = 0; d < ranges.length; d++) {
+ if(ranges[d] != null) {
+ min[d] = ranges[d].first;
+ max[d] = ranges[d].second;
+ }
+ }
+ min = proj.fastProjectDataToRenderSpace(min);
+ max = proj.fastProjectDataToRenderSpace(max);
+
+ int dim = proj.getVisibleDimensions();
+ for(int d = 0; d < dim; d++) {
+ if(ranges[proj.getDimForVisibleAxis(d)] != null) {
+ double amin = Math.min(min[d], max[d]);
+ double amax = Math.max(min[d], max[d]);
+ Element rect = svgp.svgRect(getVisibleAxisX(d) - (0.01 * StyleLibrary.SCALE), amin, 0.02 * StyleLibrary.SCALE, amax - amin);
+ SVGUtil.addCSSClass(rect, MARKER);
+ layer.appendChild(rect);
}
}
}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionLineVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionLineVisualization.java index b9a9ac48..d7ccb072 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionLineVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionLineVisualization.java @@ -54,113 +54,113 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualizati *
* @author Robert Rödler
*
- * @apiviz.has SelectionResult oneway - - visualizes
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class SelectionLineVisualization extends AbstractParallelVisualization<NumberVector<?, ?>> implements DataStoreListener {
+public class SelectionLineVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
- private static final String NAME = "Selection Line";
-
- /**
- * CSS Class for the range marker
- */
- public static final String MARKER = "SelectionLine";
+ public static final String NAME = "Selection Line";
/**
* Constructor.
- *
- * @param task Visualization task
*/
- public SelectionLineVisualization(VisualizationTask task) {
- super(task);
- addCSSClasses(svgp);
- context.addDataStoreListener(this);
- context.addResultListener(this);
- incrementalRedraw();
+ public SelectionLineVisualization() {
+ super();
+ thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION;
}
-
+
@Override
- public void destroy() {
- context.removeDataStoreListener(this);
- context.removeResultListener(this);
- super.destroy();
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
@Override
- protected void redraw() {
- DBIDSelection selContext = context.getSelection();
- if(selContext != null) {
- DBIDs selection = selContext.getSelectedIds();
-
- for(DBIDIter iter = selection.iter(); iter.valid(); iter.advance()) {
- double[] yPos = proj.fastProjectDataToRenderSpace(relation.get(iter));
-
- SVGPath path = new SVGPath();
- for(int i = 0; i < proj.getVisibleDimensions(); i++) {
- path.drawTo(getVisibleAxisX(i), yPos[i]);
- }
- Element marker = path.makeElement(svgp);
- SVGUtil.addCSSClass(marker, MARKER);
- layer.appendChild(marker);
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
+ for(SelectionResult selres : selectionResults) {
+ Collection<ParallelPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ParallelPlotProjector.class);
+ for(ParallelPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA - 1;
+ baseResult.getHierarchy().add(selres, task);
+ baseResult.getHierarchy().add(p, task);
}
}
}
/**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- private void addCSSClasses(SVGPlot svgp) {
- final StyleLibrary style = context.getStyleLibrary();
- // Class for the cube
- if(!svgp.getCSSClassManager().contains(MARKER)) {
- CSSClass cls = new CSSClass(this, MARKER);
- cls.setStatement(SVGConstants.CSS_STROKE_VALUE, style.getColor(StyleLibrary.SELECTION));
- cls.setStatement(SVGConstants.CSS_STROKE_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
- cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT) * 2.);
- cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
- cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
- svgp.addCSSClassOrLogError(cls);
- }
- }
-
- /**
- * Factory for visualizers to generate an SVG-Element containing a cube as
- * marker representing the selected range for each dimension
+ * Instance
*
* @author Robert Rödler
*
- * @apiviz.stereotype factory
+ * @apiviz.has SelectionResult oneway - - visualizes
*/
- public static class Factory extends AbstractVisFactory {
+ public class Instance extends AbstractParallelVisualization<NumberVector<?>> implements DataStoreListener {
+ /**
+ * CSS Class for the range marker
+ */
+ public static final String MARKER = "SelectionLine";
+
/**
* Constructor.
+ *
+ * @param task Visualization task
*/
- public Factory() {
- super();
- thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION;
+ public Instance(VisualizationTask task) {
+ super(task);
+ addCSSClasses(svgp);
+ context.addDataStoreListener(this);
+ context.addResultListener(this);
+ incrementalRedraw();
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new SelectionLineVisualization(task);
+ public void destroy() {
+ context.removeDataStoreListener(this);
+ context.removeResultListener(this);
+ super.destroy();
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
- for(SelectionResult selres : selectionResults) {
- Collection<ParallelPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ParallelPlotProjector.class);
- for(ParallelPlotProjector<?> p : ps) {
- final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA -1);
- baseResult.getHierarchy().add(selres, task);
- baseResult.getHierarchy().add(p, task);
+ protected void redraw() {
+ DBIDSelection selContext = context.getSelection();
+ if(selContext != null) {
+ DBIDs selection = selContext.getSelectedIds();
+
+ for(DBIDIter iter = selection.iter(); iter.valid(); iter.advance()) {
+ double[] yPos = proj.fastProjectDataToRenderSpace(relation.get(iter));
+
+ SVGPath path = new SVGPath();
+ for(int i = 0; i < proj.getVisibleDimensions(); i++) {
+ path.drawTo(getVisibleAxisX(i), yPos[i]);
+ }
+ Element marker = path.makeElement(svgp);
+ SVGUtil.addCSSClass(marker, MARKER);
+ layer.appendChild(marker);
}
}
}
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ // Class for the cube
+ if(!svgp.getCSSClassManager().contains(MARKER)) {
+ CSSClass cls = new CSSClass(this, MARKER);
+ cls.setStatement(SVGConstants.CSS_STROKE_VALUE, style.getColor(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_STROKE_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT) * 2.);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ svgp.addCSSClassOrLogError(cls);
+ }
+ }
}
}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionToolAxisRangeVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionToolAxisRangeVisualization.java index 2f3c6778..4fd00023 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionToolAxisRangeVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionToolAxisRangeVisualization.java @@ -59,14 +59,14 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.AbstractParallelVi *
* @author Robert Rödler
*
- * @apiviz.has SelectionResult oneway - - updates
- * @apiviz.has RangeSelection oneway - - updates
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class SelectionToolAxisRangeVisualization extends AbstractParallelVisualization<NumberVector<?, ?>> implements DragableArea.DragListener {
+public class SelectionToolAxisRangeVisualization extends AbstractVisFactory {
/**
* The logger for this class.
*/
- protected static final Logging logger = Logging.getLogger(SelectionToolAxisRangeVisualization.class);
+ private static final Logging LOG = Logging.getLogger(SelectionToolAxisRangeVisualization.class);
/**
* A short name characterizing this Visualizer.
@@ -74,237 +74,232 @@ public class SelectionToolAxisRangeVisualization extends AbstractParallelVisuali private static final String NAME = "Axis Range Selection";
/**
- * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc.
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
*/
- private static final String CSS_RANGEMARKER = "selectionAxisRangeMarker";
-
- /**
- * Element for selection rectangle
- */
- private Element rtag;
-
- /**
- * Element for the rectangle to add listeners
- */
- private Element etag;
-
- /**
- * Constructor.
- *
- * @param task Task
- */
- public SelectionToolAxisRangeVisualization(VisualizationTask task) {
- super(task);
- incrementalRedraw();
+ public SelectionToolAxisRangeVisualization() {
+ super();
}
@Override
- public void destroy() {
- super.destroy();
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
@Override
- protected void redraw() {
- addCSSClasses(svgp);
-
- // rtag: tag for the selected rect
- rtag = svgp.svgElement(SVGConstants.SVG_G_TAG);
- SVGUtil.addCSSClass(rtag, CSS_RANGEMARKER);
- layer.appendChild(rtag);
-
- // etag: sensitive area
- DragableArea drag = new DragableArea(svgp, -.1 * getMarginLeft(), -.1 * getMarginTop(), getSizeX() + getMarginLeft() * .2, getSizeY() + getMarginTop() * .2, this);
- etag = drag.getElement();
- layer.appendChild(etag);
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
+ for(SelectionResult selres : selectionResults) {
+ Collection<ParallelPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ParallelPlotProjector.class);
+ for(ParallelPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_INTERACTIVE;
+ task.tool = true;
+ task.thumbnail = false;
+ task.noexport = true;
+ task.initDefaultVisibility(false);
+ baseResult.getHierarchy().add(selres, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
}
/**
- * Delete the children of the element
+ * Instance
+ *
+ * @author Robert Rödler
*
- * @param container SVG-Element
+ * @apiviz.has SelectionResult oneway - - updates
+ * @apiviz.has RangeSelection oneway - - updates
*/
- private void deleteChildren(Element container) {
- while(container.hasChildNodes()) {
- container.removeChild(container.getLastChild());
+ public class Instance extends AbstractParallelVisualization<NumberVector<?>> implements DragableArea.DragListener {
+ /**
+ * Generic tag to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
+ */
+ private static final String CSS_RANGEMARKER = "selectionAxisRangeMarker";
+
+ /**
+ * Element for selection rectangle
+ */
+ private Element rtag;
+
+ /**
+ * Element for the rectangle to add listeners
+ */
+ private Element etag;
+
+ /**
+ * Constructor.
+ *
+ * @param task Task
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ incrementalRedraw();
}
- }
- /**
- * Set the selected ranges and the mask for the actual dimensions in the
- * context
- *
- * @param x1 min x-value
- * @param x2 max x-value
- * @param y1 min y-value
- * @param y2 max y-value
- */
- private void updateSelectionRectKoordinates(double x1, double x2, double y1, double y2, DoubleDoublePair[] ranges) {
- final int dim = proj.getVisibleDimensions();
- int minaxis = dim + 1;
- int maxaxis = -1;
- {
- int i = 0;
- while(i < dim) {
- double axx = getVisibleAxisX(i);
- if(x1 < axx || x2 < axx) {
- minaxis = i;
- break;
- }
- i++;
+ @Override
+ protected void redraw() {
+ addCSSClasses(svgp);
+
+ // rtag: tag for the selected rect
+ rtag = svgp.svgElement(SVGConstants.SVG_G_TAG);
+ SVGUtil.addCSSClass(rtag, CSS_RANGEMARKER);
+ layer.appendChild(rtag);
+
+ // etag: sensitive area
+ DragableArea drag = new DragableArea(svgp, -.1 * getMarginLeft(), -.1 * getMarginTop(), getSizeX() + getMarginLeft() * .2, getSizeY() + getMarginTop() * .2, this);
+ etag = drag.getElement();
+ layer.appendChild(etag);
+ }
+
+ /**
+ * Delete the children of the element
+ *
+ * @param container SVG-Element
+ */
+ private void deleteChildren(Element container) {
+ while(container.hasChildNodes()) {
+ container.removeChild(container.getLastChild());
}
- while(i <= dim) {
- double axx = getVisibleAxisX(i);
- if(x2 < axx && x1 < axx) {
- maxaxis = i;
- break;
+ }
+
+ /**
+ * Set the selected ranges and the mask for the actual dimensions in the
+ * context
+ *
+ * @param x1 min x-value
+ * @param x2 max x-value
+ * @param y1 min y-value
+ * @param y2 max y-value
+ */
+ private void updateSelectionRectKoordinates(double x1, double x2, double y1, double y2, DoubleDoublePair[] ranges) {
+ final int dim = proj.getVisibleDimensions();
+ int minaxis = dim + 1;
+ int maxaxis = -1;
+ {
+ int i = 0;
+ while(i < dim) {
+ double axx = getVisibleAxisX(i);
+ if(x1 < axx || x2 < axx) {
+ minaxis = i;
+ break;
+ }
+ i++;
+ }
+ while(i <= dim) {
+ double axx = getVisibleAxisX(i);
+ if(x2 < axx && x1 < axx) {
+ maxaxis = i;
+ break;
+ }
+ i++;
}
- i++;
}
- }
- double z1 = Math.max(Math.min(y1, y2), 0);
- double z2 = Math.min(Math.max(y1, y2), getSizeY());
- for(int i = minaxis; i < maxaxis; i++) {
- double v1 = proj.fastProjectRenderToDataSpace(z1, i);
- double v2 = proj.fastProjectRenderToDataSpace(z2, i);
- if(logger.isDebugging()) {
- logger.debug("Axis " + i + " dimension " + proj.getDimForVisibleAxis(i) + " " + v1 + " to " + v2);
+ double z1 = Math.max(Math.min(y1, y2), 0);
+ double z2 = Math.min(Math.max(y1, y2), getSizeY());
+ for(int i = minaxis; i < maxaxis; i++) {
+ double v1 = proj.fastProjectRenderToDataSpace(z1, i);
+ double v2 = proj.fastProjectRenderToDataSpace(z2, i);
+ if(LOG.isDebugging()) {
+ LOG.debug("Axis " + i + " dimension " + proj.getDimForVisibleAxis(i) + " " + v1 + " to " + v2);
+ }
+ ranges[proj.getDimForVisibleAxis(i)] = new DoubleDoublePair(Math.min(v1, v2), Math.max(v1, v2));
}
- ranges[proj.getDimForVisibleAxis(i)] = new DoubleDoublePair(Math.min(v1, v2), Math.max(v1, v2));
}
- }
-
- @Override
- public boolean startDrag(SVGPoint startPoint, Event evt) {
- return true;
- }
- @Override
- public boolean duringDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
- deleteChildren(rtag);
- double x = Math.min(startPoint.getX(), dragPoint.getX());
- double y = Math.min(startPoint.getY(), dragPoint.getY());
- double width = Math.abs(startPoint.getX() - dragPoint.getX());
- double height = Math.abs(startPoint.getY() - dragPoint.getY());
- rtag.appendChild(svgp.svgRect(x, y, width, height));
- return true;
- }
-
- @Override
- public boolean endDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
- deleteChildren(rtag);
- if(startPoint.getX() != dragPoint.getX() || startPoint.getY() != dragPoint.getY()) {
- updateSelection(proj, startPoint, dragPoint);
+ @Override
+ public boolean startDrag(SVGPoint startPoint, Event evt) {
+ return true;
}
- return true;
- }
- /**
- * Update the selection in the context.
- *
- * @param proj The projection
- * @param p1 First Point of the selected rectangle
- * @param p2 Second Point of the selected rectangle
- */
- private void updateSelection(Projection proj, SVGPoint p1, SVGPoint p2) {
- DBIDSelection selContext = context.getSelection();
- ModifiableDBIDs selection;
- if(selContext != null) {
- selection = DBIDUtil.newHashSet(selContext.getSelectedIds());
- }
- else {
- selection = DBIDUtil.newHashSet();
+ @Override
+ public boolean duringDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
+ deleteChildren(rtag);
+ double x = Math.min(startPoint.getX(), dragPoint.getX());
+ double y = Math.min(startPoint.getY(), dragPoint.getY());
+ double width = Math.abs(startPoint.getX() - dragPoint.getX());
+ double height = Math.abs(startPoint.getY() - dragPoint.getY());
+ rtag.appendChild(svgp.svgRect(x, y, width, height));
+ return true;
}
- DoubleDoublePair[] ranges;
- if(p1 == null || p2 == null) {
- logger.warning("no rect selected: p1: " + p1 + " p2: " + p2);
+ @Override
+ public boolean endDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
+ deleteChildren(rtag);
+ if(startPoint.getX() != dragPoint.getX() || startPoint.getY() != dragPoint.getY()) {
+ updateSelection(proj, startPoint, dragPoint);
+ }
+ return true;
}
- else {
- double x1 = Math.min(p1.getX(), p2.getX());
- double x2 = Math.max(p1.getX(), p2.getX());
- double y1 = Math.max(p1.getY(), p2.getY());
- double y2 = Math.min(p1.getY(), p2.getY());
- int dim = proj.getInputDimensionality();
- if(selContext instanceof RangeSelection) {
- ranges = ((RangeSelection) selContext).getRanges();
+ /**
+ * Update the selection in the context.
+ *
+ * @param proj The projection
+ * @param p1 First Point of the selected rectangle
+ * @param p2 Second Point of the selected rectangle
+ */
+ private void updateSelection(Projection proj, SVGPoint p1, SVGPoint p2) {
+ DBIDSelection selContext = context.getSelection();
+ ModifiableDBIDs selection;
+ if(selContext != null) {
+ selection = DBIDUtil.newHashSet(selContext.getSelectedIds());
}
else {
- ranges = new DoubleDoublePair[dim];
+ selection = DBIDUtil.newHashSet();
+ }
+ DoubleDoublePair[] ranges;
+
+ if(p1 == null || p2 == null) {
+ LOG.warning("no rect selected: p1: " + p1 + " p2: " + p2);
}
- updateSelectionRectKoordinates(x1, x2, y1, y2, ranges);
+ else {
+ double x1 = Math.min(p1.getX(), p2.getX());
+ double x2 = Math.max(p1.getX(), p2.getX());
+ double y1 = Math.max(p1.getY(), p2.getY());
+ double y2 = Math.min(p1.getY(), p2.getY());
+
+ int dim = proj.getInputDimensionality();
+ if(selContext instanceof RangeSelection) {
+ ranges = ((RangeSelection) selContext).getRanges();
+ }
+ else {
+ ranges = new DoubleDoublePair[dim];
+ }
+ updateSelectionRectKoordinates(x1, x2, y1, y2, ranges);
- selection.clear();
+ selection.clear();
- candidates: for(DBIDIter iditer = relation.iterDBIDs(); iditer.valid(); iditer.advance()) {
- NumberVector<?, ?> dbTupel = relation.get(iditer);
- for(int i = 0; i < dim; i++) {
- if(ranges != null && ranges[i] != null) {
- if(dbTupel.doubleValue(i + 1) < ranges[i].first || dbTupel.doubleValue(i + 1) > ranges[i].second) {
- continue candidates;
+ candidates: for(DBIDIter iditer = relation.iterDBIDs(); iditer.valid(); iditer.advance()) {
+ NumberVector<?> dbTupel = relation.get(iditer);
+ for(int i = 0; i < dim; i++) {
+ if(ranges != null && ranges[i] != null) {
+ if(dbTupel.doubleValue(i) < ranges[i].first || dbTupel.doubleValue(i) > ranges[i].second) {
+ continue candidates;
+ }
}
}
+ selection.add(iditer);
}
- selection.add(iditer);
+ context.setSelection(new RangeSelection(selection, ranges));
}
- context.setSelection(new RangeSelection(selection, ranges));
}
- }
- /**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- protected void addCSSClasses(SVGPlot svgp) {
- // Class for the range marking
- if(!svgp.getCSSClassManager().contains(CSS_RANGEMARKER)) {
- final CSSClass rcls = new CSSClass(this, CSS_RANGEMARKER);
- final StyleLibrary style = context.getStyleLibrary();
- rcls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION_ACTIVE));
- rcls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION_ACTIVE));
- svgp.addCSSClassOrLogError(rcls);
- }
- }
-
- /**
- * Factory for tool visualizations for selecting ranges and the inclosed
- * objects
- *
- * @author Robert Rödler
- *
- * @apiviz.stereotype factory
- * @apiviz.uses SelectionToolAxisRangeVisualization oneway - - «create»
- */
- public static class Factory extends AbstractVisFactory {
/**
- * Constructor, adhering to
- * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
*/
- public Factory() {
- super();
- }
-
- @Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new SelectionToolAxisRangeVisualization(task);
- }
-
- @Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
- for(SelectionResult selres : selectionResults) {
- Collection<ParallelPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ParallelPlotProjector.class);
- for(ParallelPlotProjector<?> p : ps) {
- final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE);
- task.put(VisualizationTask.META_TOOL, true);
- task.put(VisualizationTask.META_NOTHUMB, true);
- task.put(VisualizationTask.META_NOEXPORT, true);
- task.put(VisualizationTask.META_VISIBLE_DEFAULT, false);
- baseResult.getHierarchy().add(selres, task);
- baseResult.getHierarchy().add(p, task);
- }
+ protected void addCSSClasses(SVGPlot svgp) {
+ // Class for the range marking
+ if(!svgp.getCSSClassManager().contains(CSS_RANGEMARKER)) {
+ final CSSClass rcls = new CSSClass(this, CSS_RANGEMARKER);
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ rcls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION_ACTIVE));
+ rcls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION_ACTIVE));
+ svgp.addCSSClassOrLogError(rcls);
}
}
}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionToolLineVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionToolLineVisualization.java index 3e66b4cd..b7296492 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionToolLineVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/parallel/selection/SelectionToolLineVisualization.java @@ -58,21 +58,16 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.parallel.AbstractParallelVi *
* @author Robert Rödler
*
- * @apiviz.has SelectionResult oneway - - updates
- * @apiviz.has DBIDSelection oneway - - updates
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance - - «create»
*/
-public class SelectionToolLineVisualization extends AbstractParallelVisualization<NumberVector<?, ?>> implements DragableArea.DragListener {
+public class SelectionToolLineVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
private static final String NAME = "Object Selection";
/**
- * CSS class of the selection rectangle while selecting.
- */
- private static final String CSS_RANGEMARKER = "selectionRangeMarker";
-
- /**
* Input modes
*
* @apiviz.exclude
@@ -82,249 +77,253 @@ public class SelectionToolLineVisualization extends AbstractParallelVisualizatio }
/**
- * Element for selection rectangle
- */
- Element rtag;
-
- /**
- * Element for the rectangle to add listeners
- */
- Element etag;
-
- /**
- * Constructor.
- *
- * @param task Task
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
*/
- public SelectionToolLineVisualization(VisualizationTask task) {
- super(task);
- incrementalRedraw();
+ public SelectionToolLineVisualization() {
+ super();
}
@Override
- protected void redraw() {
- addCSSClasses(svgp);
-
- rtag = svgp.svgElement(SVGConstants.SVG_G_TAG);
- SVGUtil.addCSSClass(rtag, CSS_RANGEMARKER);
- layer.appendChild(rtag);
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
+ }
- // etag: sensitive area
- DragableArea drag = new DragableArea(svgp, -.1 * getMarginLeft(), -.5 * getMarginTop(), getSizeX() + .2 * getMarginLeft(), getMarginTop() * 1.5 + getSizeY(), this);
- etag = drag.getElement();
- layer.appendChild(etag);
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
+ for(SelectionResult selres : selectionResults) {
+ Collection<ParallelPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ParallelPlotProjector.class);
+ for(ParallelPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_INTERACTIVE;
+ task.tool = true;
+ task.thumbnail = false;
+ task.noexport = true;
+ task.initDefaultVisibility(false);
+ baseResult.getHierarchy().add(selres, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
}
/**
- * Delete the children of the element
+ * Instance.
+ *
+ * @author Robert Rödler
*
- * @param container SVG-Element
+ * @apiviz.has SelectionResult oneway - - updates
+ * @apiviz.has DBIDSelection oneway - - updates
*/
- private void deleteChildren(Element container) {
- while(container.hasChildNodes()) {
- container.removeChild(container.getLastChild());
+ public class Instance extends AbstractParallelVisualization<NumberVector<?>> implements DragableArea.DragListener {
+ /**
+ * CSS class of the selection rectangle while selecting.
+ */
+ private static final String CSS_RANGEMARKER = "selectionRangeMarker";
+
+ /**
+ * Element for selection rectangle
+ */
+ Element rtag;
+
+ /**
+ * Element for the rectangle to add listeners
+ */
+ Element etag;
+
+ /**
+ * Constructor.
+ *
+ * @param task Task
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ incrementalRedraw();
}
- }
- @Override
- public boolean startDrag(SVGPoint startPoint, Event evt) {
- return true;
- }
+ @Override
+ protected void redraw() {
+ addCSSClasses(svgp);
- @Override
- public boolean duringDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
- deleteChildren(rtag);
- double x = Math.min(startPoint.getX(), dragPoint.getX());
- double y = Math.min(startPoint.getY(), dragPoint.getY());
- double width = Math.abs(startPoint.getX() - dragPoint.getX());
- double height = Math.abs(startPoint.getY() - dragPoint.getY());
- rtag.appendChild(svgp.svgRect(x, y, width, height));
- return true;
- }
+ rtag = svgp.svgElement(SVGConstants.SVG_G_TAG);
+ SVGUtil.addCSSClass(rtag, CSS_RANGEMARKER);
+ layer.appendChild(rtag);
- @Override
- public boolean endDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
- Mode mode = getInputMode(evt);
- deleteChildren(rtag);
- if(startPoint.getX() != dragPoint.getX() || startPoint.getY() != dragPoint.getY()) {
- updateSelection(mode, startPoint, dragPoint);
+ // etag: sensitive area
+ DragableArea drag = new DragableArea(svgp, -.1 * getMarginLeft(), -.5 * getMarginTop(), getSizeX() + .2 * getMarginLeft(), getMarginTop() * 1.5 + getSizeY(), this);
+ etag = drag.getElement();
+ layer.appendChild(etag);
}
- return true;
- }
- /**
- * Get the current input mode, on each mouse event.
- *
- * @param evt Mouse event.
- * @return current input mode
- */
- private Mode getInputMode(Event evt) {
- if(evt instanceof DOMMouseEvent) {
- DOMMouseEvent domme = (DOMMouseEvent) evt;
- // TODO: visual indication of mode possible?
- if(domme.getShiftKey()) {
- return Mode.ADD;
- }
- else if(domme.getCtrlKey()) {
- return Mode.INVERT;
- }
- else {
- return Mode.REPLACE;
+ /**
+ * Delete the children of the element
+ *
+ * @param container SVG-Element
+ */
+ private void deleteChildren(Element container) {
+ while(container.hasChildNodes()) {
+ container.removeChild(container.getLastChild());
}
}
- // Default mode is replace.
- return Mode.REPLACE;
- }
- /**
- * Updates the selection in the context.<br>
- *
- * @param mode Input mode
- * @param p1 first point of the selected rectangle
- * @param p2 second point of the selected rectangle
- */
- private void updateSelection(Mode mode, SVGPoint p1, SVGPoint p2) {
- DBIDSelection selContext = context.getSelection();
- // Note: we rely on SET semantics below!
- final HashSetModifiableDBIDs selection;
- if(selContext == null || mode == Mode.REPLACE) {
- selection = DBIDUtil.newHashSet();
+ @Override
+ public boolean startDrag(SVGPoint startPoint, Event evt) {
+ return true;
}
- else {
- selection = DBIDUtil.newHashSet(selContext.getSelectedIds());
+
+ @Override
+ public boolean duringDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
+ deleteChildren(rtag);
+ double x = Math.min(startPoint.getX(), dragPoint.getX());
+ double y = Math.min(startPoint.getY(), dragPoint.getY());
+ double width = Math.abs(startPoint.getX() - dragPoint.getX());
+ double height = Math.abs(startPoint.getY() - dragPoint.getY());
+ rtag.appendChild(svgp.svgRect(x, y, width, height));
+ return true;
}
- int[] axisrange = getAxisRange(Math.min(p1.getX(), p2.getX()), Math.max(p1.getX(), p2.getX()));
- DBIDs ids = ResultUtil.getSamplingResult(relation).getSample();
- for(DBIDIter iter = ids.iter(); iter.valid(); iter.advance()) {
- double[] yPos = proj.fastProjectDataToRenderSpace(relation.get(iter));
- if(checkSelected(axisrange, yPos, Math.max(p1.getX(), p2.getX()), Math.min(p1.getX(), p2.getX()), Math.max(p1.getY(), p2.getY()), Math.min(p1.getY(), p2.getY()))) {
- if(mode == Mode.INVERT) {
- if(!selection.contains(iter)) {
- selection.add(iter);
- }
- else {
- selection.remove(iter);
- }
+
+ @Override
+ public boolean endDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
+ Mode mode = getInputMode(evt);
+ deleteChildren(rtag);
+ if(startPoint.getX() != dragPoint.getX() || startPoint.getY() != dragPoint.getY()) {
+ updateSelection(mode, startPoint, dragPoint);
+ }
+ return true;
+ }
+
+ /**
+ * Get the current input mode, on each mouse event.
+ *
+ * @param evt Mouse event.
+ * @return current input mode
+ */
+ private Mode getInputMode(Event evt) {
+ if(evt instanceof DOMMouseEvent) {
+ DOMMouseEvent domme = (DOMMouseEvent) evt;
+ // TODO: visual indication of mode possible?
+ if(domme.getShiftKey()) {
+ return Mode.ADD;
+ }
+ else if(domme.getCtrlKey()) {
+ return Mode.INVERT;
}
else {
- // In REPLACE and ADD, add objects.
- // The difference was done before by not re-using the selection.
- // Since we are using a set, we can just add in any case.
- selection.add(iter);
+ return Mode.REPLACE;
}
}
+ // Default mode is replace.
+ return Mode.REPLACE;
}
- context.setSelection(new DBIDSelection(selection));
- }
- private int[] getAxisRange(double x1, double x2) {
- final int dim = proj.getVisibleDimensions();
- int minaxis = 0;
- int maxaxis = 0;
- boolean minx = true;
- boolean maxx = false;
- int count = -1;
- for(int i = 0; i < dim; i++) {
- if(minx && getVisibleAxisX(i) > x1) {
- minaxis = count;
- minx = false;
- maxx = true;
+ /**
+ * Updates the selection in the context.<br>
+ *
+ * @param mode Input mode
+ * @param p1 first point of the selected rectangle
+ * @param p2 second point of the selected rectangle
+ */
+ private void updateSelection(Mode mode, SVGPoint p1, SVGPoint p2) {
+ DBIDSelection selContext = context.getSelection();
+ // Note: we rely on SET semantics below!
+ final HashSetModifiableDBIDs selection;
+ if(selContext == null || mode == Mode.REPLACE) {
+ selection = DBIDUtil.newHashSet();
+ }
+ else {
+ selection = DBIDUtil.newHashSet(selContext.getSelectedIds());
}
- if(maxx && (getVisibleAxisX(i) > x2 || i == dim - 1)) {
- maxaxis = count + 1;
- if(i == dim - 1 && getVisibleAxisX(i) <= x2) {
- maxaxis++;
+ int[] axisrange = getAxisRange(Math.min(p1.getX(), p2.getX()), Math.max(p1.getX(), p2.getX()));
+ DBIDs ids = ResultUtil.getSamplingResult(relation).getSample();
+ for(DBIDIter iter = ids.iter(); iter.valid(); iter.advance()) {
+ double[] yPos = proj.fastProjectDataToRenderSpace(relation.get(iter));
+ if(checkSelected(axisrange, yPos, Math.max(p1.getX(), p2.getX()), Math.min(p1.getX(), p2.getX()), Math.max(p1.getY(), p2.getY()), Math.min(p1.getY(), p2.getY()))) {
+ if(mode == Mode.INVERT) {
+ if(!selection.contains(iter)) {
+ selection.add(iter);
+ }
+ else {
+ selection.remove(iter);
+ }
+ }
+ else {
+ // In REPLACE and ADD, add objects.
+ // The difference was done before by not re-using the selection.
+ // Since we are using a set, we can just add in any case.
+ selection.add(iter);
+ }
}
- break;
}
- count = i;
+ context.setSelection(new DBIDSelection(selection));
}
- return new int[] { minaxis, maxaxis };
- }
- private boolean checkSelected(int[] ar, double[] yPos, double x1, double x2, double y1, double y2) {
- final int dim = proj.getVisibleDimensions();
- if (ar[0] < 0) {
- ar[0] = 0;
- }
- if(ar[1] >= dim) {
- ar[1] = dim - 1;
- }
- for(int i = ar[0] + 1; i <= ar[1] - 1; i++) {
- if(yPos[i] <= y1 && yPos[i] >= y2) {
- return true;
+ private int[] getAxisRange(double x1, double x2) {
+ final int dim = proj.getVisibleDimensions();
+ int minaxis = 0;
+ int maxaxis = 0;
+ boolean minx = true;
+ boolean maxx = false;
+ int count = -1;
+ for(int i = 0; i < dim; i++) {
+ if(minx && getVisibleAxisX(i) > x1) {
+ minaxis = count;
+ minx = false;
+ maxx = true;
+ }
+ if(maxx && (getVisibleAxisX(i) > x2 || i == dim - 1)) {
+ maxaxis = count + 1;
+ if(i == dim - 1 && getVisibleAxisX(i) <= x2) {
+ maxaxis++;
+ }
+ break;
+ }
+ count = i;
}
+ return new int[] { minaxis, maxaxis };
}
- Line2D.Double idline1 = new Line2D.Double(getVisibleAxisX(ar[0]), yPos[ar[0]], getVisibleAxisX(ar[0] + 1), yPos[ar[0] + 1]);
- Line2D.Double idline2 = new Line2D.Double(getVisibleAxisX(ar[1] - 1), yPos[ar[1] - 1], getVisibleAxisX(ar[1]), yPos[ar[1]]);
- Line2D.Double rectline1 = new Line2D.Double(x2, y1, x1, y1);
- Line2D.Double rectline2 = new Line2D.Double(x2, y1, x2, y2);
- Line2D.Double rectline3 = new Line2D.Double(x2, y2, x1, y2);
- if(idline1.intersectsLine(rectline1) || idline1.intersectsLine(rectline2) || idline1.intersectsLine(rectline3)) {
- return true;
- }
- Line2D.Double rectline4 = new Line2D.Double(x1, y1, x1, y2);
- if(idline2.intersectsLine(rectline1) || idline2.intersectsLine(rectline4) || idline2.intersectsLine(rectline3)) {
- return true;
- }
- return false;
- }
- /**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- protected void addCSSClasses(SVGPlot svgp) {
- // Class for the range marking
- if(!svgp.getCSSClassManager().contains(CSS_RANGEMARKER)) {
- final CSSClass rcls = new CSSClass(this, CSS_RANGEMARKER);
- final StyleLibrary style = context.getStyleLibrary();
- rcls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION_ACTIVE));
- rcls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION_ACTIVE));
- svgp.addCSSClassOrLogError(rcls);
+ private boolean checkSelected(int[] ar, double[] yPos, double x1, double x2, double y1, double y2) {
+ final int dim = proj.getVisibleDimensions();
+ if(ar[0] < 0) {
+ ar[0] = 0;
+ }
+ if(ar[1] >= dim) {
+ ar[1] = dim - 1;
+ }
+ for(int i = ar[0] + 1; i <= ar[1] - 1; i++) {
+ if(yPos[i] <= y1 && yPos[i] >= y2) {
+ return true;
+ }
+ }
+ Line2D.Double idline1 = new Line2D.Double(getVisibleAxisX(ar[0]), yPos[ar[0]], getVisibleAxisX(ar[0] + 1), yPos[ar[0] + 1]);
+ Line2D.Double idline2 = new Line2D.Double(getVisibleAxisX(ar[1] - 1), yPos[ar[1] - 1], getVisibleAxisX(ar[1]), yPos[ar[1]]);
+ Line2D.Double rectline1 = new Line2D.Double(x2, y1, x1, y1);
+ Line2D.Double rectline2 = new Line2D.Double(x2, y1, x2, y2);
+ Line2D.Double rectline3 = new Line2D.Double(x2, y2, x1, y2);
+ if(idline1.intersectsLine(rectline1) || idline1.intersectsLine(rectline2) || idline1.intersectsLine(rectline3)) {
+ return true;
+ }
+ Line2D.Double rectline4 = new Line2D.Double(x1, y1, x1, y2);
+ if(idline2.intersectsLine(rectline1) || idline2.intersectsLine(rectline4) || idline2.intersectsLine(rectline3)) {
+ return true;
+ }
+ return false;
}
- }
- /**
- * Factory for tool visualizations for selecting objects
- *
- * @author Robert Rödler
- *
- * @apiviz.stereotype factory
- * @apiviz.uses SelectionToolLineVisualization - - «create»
- *
- */
- public static class Factory extends AbstractVisFactory {
/**
- * Constructor, adhering to
- * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
*/
- public Factory() {
- super();
- }
-
- @Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new SelectionToolLineVisualization(task);
- }
-
- @Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
- for(SelectionResult selres : selectionResults) {
- Collection<ParallelPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ParallelPlotProjector.class);
- for(ParallelPlotProjector<?> p : ps) {
- final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE);
- task.put(VisualizationTask.META_TOOL, true);
- task.put(VisualizationTask.META_NOTHUMB, true);
- task.put(VisualizationTask.META_NOEXPORT, true);
- task.put(VisualizationTask.META_VISIBLE_DEFAULT, false);
- baseResult.getHierarchy().add(selres, task);
- baseResult.getHierarchy().add(p, task);
- }
+ protected void addCSSClasses(SVGPlot svgp) {
+ // Class for the range marking
+ if(!svgp.getCSSClassManager().contains(CSS_RANGEMARKER)) {
+ final CSSClass rcls = new CSSClass(this, CSS_RANGEMARKER);
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ rcls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION_ACTIVE));
+ rcls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION_ACTIVE));
+ svgp.addCSSClassOrLogError(rcls);
}
}
}
-}
+}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractScatterplotVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractScatterplotVisualization.java index cce9ea5d..8cedcf0b 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractScatterplotVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractScatterplotVisualization.java @@ -56,7 +56,7 @@ public abstract class AbstractScatterplotVisualization extends AbstractVisualiza /** * The representation we visualize */ - final protected Relation<? extends NumberVector<?, ?>> rel; + final protected Relation<? extends NumberVector<?>> rel; /** * The DBID sample @@ -73,7 +73,7 @@ public abstract class AbstractScatterplotVisualization extends AbstractVisualiza this.proj = task.getProj(); this.rel = task.getRelation(); this.sample = ResultUtil.getSamplingResult(rel); - final double margin = context.getStyleLibrary().getSize(StyleLibrary.MARGIN); + final double margin = context.getStyleResult().getStyleLibrary().getSize(StyleLibrary.MARGIN); this.layer = setupCanvas(svgp, proj, margin, task.getWidth(), task.getHeight()); } @@ -91,7 +91,7 @@ public abstract class AbstractScatterplotVisualization extends AbstractVisualiza final CanvasSize canvas = proj.estimateViewport(); final double sizex = canvas.getDiffX(); final double sizey = canvas.getDiffY(); - String transform = SVGUtil.makeMarginTransform(width, height, sizex, sizey, margin) + " translate(" + SVGUtil.fmt(sizex / 2) + " " + SVGUtil.fmt(sizey / 2) + ")"; + String transform = SVGUtil.makeMarginTransform(width, height, sizex, sizey, margin) + " translate(" + SVGUtil.fmt(sizex * .5) + " " + SVGUtil.fmt(sizey * .5) + ")"; final Element layer = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG); SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractTooltipVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractTooltipVisualization.java index 8ce6ad67..107af095 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractTooltipVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AbstractTooltipVisualization.java @@ -31,7 +31,8 @@ import org.w3c.dom.events.EventListener; import org.w3c.dom.events.EventTarget; import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener; -import de.lmu.ifi.dbs.elki.database.ids.DBID; +import de.lmu.ifi.dbs.elki.database.ids.DBIDIter; +import de.lmu.ifi.dbs.elki.database.ids.DBIDRef; import de.lmu.ifi.dbs.elki.logging.LoggingUtil; import de.lmu.ifi.dbs.elki.result.Result; import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; @@ -87,18 +88,12 @@ public abstract class AbstractTooltipVisualization extends AbstractScatterplotVi } @Override - public void destroy() { - super.destroy(); - context.removeDataStoreListener(this); - } - - @Override public void redraw() { setupCSS(svgp); + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + double dotsize = style.getLineWidth(StyleLibrary.PLOT); - double dotsize = context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT); - - for(DBID id : sample.getSample()) { + for(DBIDIter id = sample.getSample().iter(); id.valid(); id.advance()) { double[] v = proj.fastProjectDataToRenderSpace(rel.get(id)); Element tooltip = makeTooltip(id, v[0], v[1], dotsize); SVGUtil.addCSSClass(tooltip, TOOLTIP_HIDDEN); @@ -118,7 +113,16 @@ public abstract class AbstractTooltipVisualization extends AbstractScatterplotVi } } - abstract protected Element makeTooltip(DBID id, double x, double y, double dotsize); + /** + * Make a tooltip Element for this id. + * + * @param id Id to make a tooltip for + * @param x X position + * @param y Y position + * @param dotsize Size of a dot + * @return Element + */ + protected abstract Element makeTooltip(DBIDRef id, double x, double y, double dotsize); /** * Handle the hover events. @@ -129,7 +133,7 @@ public abstract class AbstractTooltipVisualization extends AbstractScatterplotVi if(evt.getTarget() instanceof Element) { Element e = (Element) evt.getTarget(); Node next = e.getNextSibling(); - if(next != null && next instanceof Element) { + if(next instanceof Element) { toggleTooltip((Element) next, evt.getType()); } else { @@ -174,7 +178,7 @@ public abstract class AbstractTooltipVisualization extends AbstractScatterplotVi * * @param svgp the SVGPlot to register the Tooltip-CSS-Class. */ - abstract protected void setupCSS(SVGPlot svgp); + protected abstract void setupCSS(SVGPlot svgp); @Override public void resultChanged(Result current) { diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AxisVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AxisVisualization.java index 4173084d..f08c4b2d 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AxisVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/AxisVisualization.java @@ -28,10 +28,10 @@ import java.util.Collection; import org.apache.batik.util.SVGConstants; import org.w3c.dom.Element; +import de.lmu.ifi.dbs.elki.database.relation.RelationUtil; import de.lmu.ifi.dbs.elki.result.HierarchicalResult; import de.lmu.ifi.dbs.elki.result.Result; import de.lmu.ifi.dbs.elki.result.ResultUtil; -import de.lmu.ifi.dbs.elki.utilities.DatabaseUtil; import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; import de.lmu.ifi.dbs.elki.visualization.css.CSSClass; import de.lmu.ifi.dbs.elki.visualization.css.CSSClassManager.CSSNamingConflict; @@ -47,113 +47,116 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * Generates a SVG-Element containing axes, including labeling. * * @author Remigius Wojdanowski + * @author Erich Schubert * - * @apiviz.uses SVGSimpleLinearAxis + * @apiviz.stereotype factory + * @apiviz.uses Instance oneway - - «create» */ -public class AxisVisualization extends AbstractScatterplotVisualization { +public class AxisVisualization extends AbstractVisFactory { + /** + * A short name characterizing this Visualizer. + */ + private static final String NAME = "Axes"; + /** * Constructor. - * - * @param task VisualizationTask */ - public AxisVisualization(VisualizationTask task) { - super(task); - incrementalRedraw(); + public AxisVisualization() { + super(); } @Override - protected void redraw() { - int dim = DatabaseUtil.dimensionality(rel); - - // origin - double[] orig = proj.fastProjectScaledToRenderSpace(new double[dim]); - // diagonal point opposite to origin - double[] diag = new double[dim]; - for(int d2 = 0; d2 < dim; d2++) { - diag[d2] = 1; - } - diag = proj.fastProjectScaledToRenderSpace(diag); - // compute angle to diagonal line, used for axis labeling. - double diaga = Math.atan2(diag[1] - orig[1], diag[0] - orig[0]); - - double alfontsize = 1.1 * context.getStyleLibrary().getTextSize(StyleLibrary.AXIS_LABEL); - CSSClass alcls = new CSSClass(AxisVisualization.class, "unmanaged"); - alcls.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, SVGUtil.fmt(alfontsize)); - alcls.setStatement(SVGConstants.CSS_FILL_PROPERTY, context.getStyleLibrary().getTextColor(StyleLibrary.AXIS_LABEL)); - alcls.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, context.getStyleLibrary().getFontFamily(StyleLibrary.AXIS_LABEL)); - - // draw axes - for(int d = 0; d < dim; d++) { - double[] v = new double[dim]; - v[d] = 1; - // projected endpoint of axis - double[] ax = proj.fastProjectScaledToRenderSpace(v); - boolean righthand = false; - double axa = Math.atan2(ax[1] - orig[1], ax[0] - orig[0]); - if(axa > diaga || (diaga > 0 && axa > diaga + Math.PI)) { - righthand = true; - } - // System.err.println(ax.get(0) + " "+ ax.get(1)+ - // " "+(axa*180/Math.PI)+" "+(diaga*180/Math.PI)); - if(ax[0] != orig[0] || ax[1] != orig[1]) { - try { - SVGSimpleLinearAxis.drawAxis(svgp, layer, proj.getScale(d), orig[0], orig[1], ax[0], ax[1], righthand ? SVGSimpleLinearAxis.LabelStyle.RIGHTHAND : SVGSimpleLinearAxis.LabelStyle.LEFTHAND, context.getStyleLibrary()); - // TODO: move axis labeling into drawAxis function. - double offx = (righthand ? 1 : -1) * 0.02 * Projection.SCALE; - double offy = (righthand ? 1 : -1) * 0.02 * Projection.SCALE; - Element label = svgp.svgText(ax[0] + offx, ax[1] + offy, DatabaseUtil.getColumnLabel(rel, d + 1)); - SVGUtil.setAtt(label, SVGConstants.SVG_STYLE_ATTRIBUTE, alcls.inlineCSS()); - SVGUtil.setAtt(label, SVGConstants.SVG_TEXT_ANCHOR_ATTRIBUTE, righthand ? SVGConstants.SVG_START_VALUE : SVGConstants.SVG_END_VALUE); - layer.appendChild(label); - } - catch(CSSNamingConflict e) { - throw new RuntimeException("Conflict in CSS naming for axes.", e); - } - } + public Visualization makeVisualization(VisualizationTask task) { + return new Instance(task); + } + + @Override + public void processNewResult(HierarchicalResult baseResult, Result result) { + Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(result, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { + final VisualizationTask task = new VisualizationTask(NAME, p, p.getRelation(), this); + task.level = VisualizationTask.LEVEL_BACKGROUND; + baseResult.getHierarchy().add(p, task); } } + @Override + public boolean allowThumbnails(VisualizationTask task) { + // Don't use thumbnails + return false; + } + /** - * Factory for axis visualizations + * Instance. * * @author Erich Schubert + * @author Remigius Wojdanowski + * + * @apiviz.uses SVGSimpleLinearAxis * - * @apiviz.stereotype factory - * @apiviz.uses AxisVisualization oneway - - «create» */ - public static class Factory extends AbstractVisFactory { + public class Instance extends AbstractScatterplotVisualization { /** - * A short name characterizing this Visualizer. + * Constructor. + * + * @param task VisualizationTask */ - private static final String NAME = "Axes"; - - /** - * Constructor, adhering to - * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} - */ - public Factory() { - super(); - } - - @Override - public Visualization makeVisualization(VisualizationTask task) { - return new AxisVisualization(task); + public Instance(VisualizationTask task) { + super(task); + incrementalRedraw(); } @Override - public void processNewResult(HierarchicalResult baseResult, Result result) { - Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(result, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : ps) { - final VisualizationTask task = new VisualizationTask(NAME, p, p.getRelation(), this); - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_BACKGROUND); - baseResult.getHierarchy().add(p, task); + protected void redraw() { + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + final int dim = RelationUtil.dimensionality(rel); + + // origin + double[] orig = proj.fastProjectScaledToRenderSpace(new double[dim]); + // diagonal point opposite to origin + double[] diag = new double[dim]; + for(int d2 = 0; d2 < dim; d2++) { + diag[d2] = 1; + } + diag = proj.fastProjectScaledToRenderSpace(diag); + // compute angle to diagonal line, used for axis labeling. + double diaga = Math.atan2(diag[1] - orig[1], diag[0] - orig[0]); + + double alfontsize = 1.1 * style.getTextSize(StyleLibrary.AXIS_LABEL); + CSSClass alcls = new CSSClass(AxisVisualization.class, "unmanaged"); + alcls.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, SVGUtil.fmt(alfontsize)); + alcls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getTextColor(StyleLibrary.AXIS_LABEL)); + alcls.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, style.getFontFamily(StyleLibrary.AXIS_LABEL)); + + // draw axes + for(int d = 0; d < dim; d++) { + double[] v = new double[dim]; + v[d] = 1; + // projected endpoint of axis + double[] ax = proj.fastProjectScaledToRenderSpace(v); + boolean righthand = false; + double axa = Math.atan2(ax[1] - orig[1], ax[0] - orig[0]); + if(axa > diaga || (diaga > 0 && axa > diaga + Math.PI)) { + righthand = true; + } + // System.err.println(ax.get(0) + " "+ ax.get(1)+ + // " "+(axa*180/Math.PI)+" "+(diaga*180/Math.PI)); + if(ax[0] != orig[0] || ax[1] != orig[1]) { + try { + SVGSimpleLinearAxis.drawAxis(svgp, layer, proj.getScale(d), orig[0], orig[1], ax[0], ax[1], righthand ? SVGSimpleLinearAxis.LabelStyle.RIGHTHAND : SVGSimpleLinearAxis.LabelStyle.LEFTHAND, style); + // TODO: move axis labeling into drawAxis function. + double offx = (righthand ? 1 : -1) * 0.02 * Projection.SCALE; + double offy = (righthand ? 1 : -1) * 0.02 * Projection.SCALE; + Element label = svgp.svgText(ax[0] + offx, ax[1] + offy, RelationUtil.getColumnLabel(rel, d)); + SVGUtil.setAtt(label, SVGConstants.SVG_STYLE_ATTRIBUTE, alcls.inlineCSS()); + SVGUtil.setAtt(label, SVGConstants.SVG_TEXT_ANCHOR_ATTRIBUTE, righthand ? SVGConstants.SVG_START_VALUE : SVGConstants.SVG_END_VALUE); + layer.appendChild(label); + } + catch(CSSNamingConflict e) { + throw new RuntimeException("Conflict in CSS naming for axes.", e); + } + } } - } - - @Override - public boolean allowThumbnails(VisualizationTask task) { - // Don't use thumbnails - return false; } } }
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/MarkerVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/MarkerVisualization.java index c98ca83b..f2140502 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/MarkerVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/MarkerVisualization.java @@ -54,121 +54,122 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualizati * * @author Erich Schubert * - * @apiviz.uses StyleResult + * @apiviz.stereotype factory + * @apiviz.uses Instance oneway - - «create» */ -public class MarkerVisualization extends AbstractScatterplotVisualization implements DataStoreListener { +public class MarkerVisualization extends AbstractVisFactory { /** - * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc. + * A short name characterizing this Visualizer. */ - public static final String DOTMARKER = "dot"; + private static final String NAME = "Markers"; /** - * The result we visualize + * Constructor, adhering to + * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} */ - private StyleResult style; - - /** - * Constructor. - * - * @param task Visualization task - */ - public MarkerVisualization(VisualizationTask task) { - super(task); - this.style = task.getResult(); - context.addDataStoreListener(this); - context.addResultListener(this); - incrementalRedraw(); + public MarkerVisualization() { + super(); + thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_STYLE; } @Override - public void destroy() { - super.destroy(); - context.removeDataStoreListener(this); - context.removeResultListener(this); + public Visualization makeVisualization(VisualizationTask task) { + return new Instance(task); } @Override - public void redraw() { - final MarkerLibrary ml = context.getStyleLibrary().markers(); - final double marker_size = context.getStyleLibrary().getSize(StyleLibrary.MARKERPLOT); - final StylingPolicy spol = style.getStylingPolicy(); - - if(spol instanceof ClassStylingPolicy) { - ClassStylingPolicy cspol = (ClassStylingPolicy) spol; - for(int cnum = cspol.getMinStyle(); cnum < cspol.getMaxStyle(); cnum++) { - for(DBIDIter iter = cspol.iterateClass(cnum); iter.valid(); iter.advance()) { - if(!sample.getSample().contains(iter)) { - continue; // TODO: can we test more efficiently than this? - } - try { - final NumberVector<?, ?> vec = rel.get(iter); - double[] v = proj.fastProjectDataToRenderSpace(vec); - ml.useMarker(svgp, layer, v[0], v[1], cnum, marker_size); - } - catch(ObjectNotFoundException e) { - // ignore. - } - } - } - } - else { - final String FILL = SVGConstants.CSS_FILL_PROPERTY + ":"; - // Color-based styling. Fall back to dots - for(DBIDIter iter = sample.getSample().iter(); iter.valid(); iter.advance()) { - try { - double[] v = proj.fastProjectDataToRenderSpace(rel.get(iter)); - Element dot = svgp.svgCircle(v[0], v[1], marker_size); - SVGUtil.addCSSClass(dot, DOTMARKER); - int col = spol.getColorForDBID(iter); - SVGUtil.setAtt(dot, SVGConstants.SVG_STYLE_ATTRIBUTE, FILL + SVGUtil.colorToString(col)); - layer.appendChild(dot); - } - catch(ObjectNotFoundException e) { - // ignore. - } + public void processNewResult(HierarchicalResult baseResult, Result result) { + // Find a style result to visualize: + Collection<StyleResult> styleres = ResultUtil.filterResults(result, StyleResult.class); + for(StyleResult c : styleres) { + Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { + final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this); + task.level = VisualizationTask.LEVEL_DATA; + baseResult.getHierarchy().add(c, task); + baseResult.getHierarchy().add(p, task); } } } /** - * Visualization factory + * Instance. * * @author Erich Schubert * - * @apiviz.stereotype factory - * @apiviz.uses MarkerVisualization oneway - - «create» + * @apiviz.uses StyleResult */ - public static class Factory extends AbstractVisFactory { + public class Instance extends AbstractScatterplotVisualization implements DataStoreListener { /** - * A short name characterizing this Visualizer. + * Generic tag to indicate the type of element. Used in IDs, CSS-Classes + * etc. */ - private static final String NAME = "Markers"; + public static final String DOTMARKER = "dot"; /** - * Constructor, adhering to - * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} + * The result we visualize */ - public Factory() { - super(); - thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_STYLE; + private StyleResult style; + + /** + * Constructor. + * + * @param task Visualization task + */ + public Instance(VisualizationTask task) { + super(task); + this.style = task.getResult(); + context.addDataStoreListener(this); + context.addResultListener(this); + incrementalRedraw(); } @Override - public Visualization makeVisualization(VisualizationTask task) { - return new MarkerVisualization(task); + public void destroy() { + super.destroy(); + context.removeDataStoreListener(this); + context.removeResultListener(this); } @Override - public void processNewResult(HierarchicalResult baseResult, Result result) { - // Find a style result to visualize: - Collection<StyleResult> styleres = ResultUtil.filterResults(result, StyleResult.class); - for(StyleResult c : styleres) { - Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : ps) { - final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this); - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA); - baseResult.getHierarchy().add(c, task); - baseResult.getHierarchy().add(p, task); + public void redraw() { + final MarkerLibrary ml = style.getStyleLibrary().markers(); + final double marker_size = style.getStyleLibrary().getSize(StyleLibrary.MARKERPLOT); + final StylingPolicy spol = style.getStylingPolicy(); + + if(spol instanceof ClassStylingPolicy) { + ClassStylingPolicy cspol = (ClassStylingPolicy) spol; + for(int cnum = cspol.getMinStyle(); cnum < cspol.getMaxStyle(); cnum++) { + for(DBIDIter iter = cspol.iterateClass(cnum); iter.valid(); iter.advance()) { + if(!sample.getSample().contains(iter)) { + continue; // TODO: can we test more efficiently than this? + } + try { + final NumberVector<?> vec = rel.get(iter); + double[] v = proj.fastProjectDataToRenderSpace(vec); + ml.useMarker(svgp, layer, v[0], v[1], cnum, marker_size); + } + catch(ObjectNotFoundException e) { + // ignore. + } + } + } + } + else { + final String FILL = SVGConstants.CSS_FILL_PROPERTY + ":"; + // Color-based styling. Fall back to dots + for(DBIDIter iter = sample.getSample().iter(); iter.valid(); iter.advance()) { + try { + double[] v = proj.fastProjectDataToRenderSpace(rel.get(iter)); + Element dot = svgp.svgCircle(v[0], v[1], marker_size); + SVGUtil.addCSSClass(dot, DOTMARKER); + int col = spol.getColorForDBID(iter); + SVGUtil.setAtt(dot, SVGConstants.SVG_STYLE_ATTRIBUTE, FILL + SVGUtil.colorToString(col)); + layer.appendChild(dot); + } + catch(ObjectNotFoundException e) { + // ignore. + } } } } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/PolygonVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/PolygonVisualization.java index 5b7c13b4..0fc99bbc 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/PolygonVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/PolygonVisualization.java @@ -34,15 +34,14 @@ import de.lmu.ifi.dbs.elki.data.type.TypeUtil; import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener; import de.lmu.ifi.dbs.elki.database.ids.DBIDIter; import de.lmu.ifi.dbs.elki.database.relation.Relation; +import de.lmu.ifi.dbs.elki.database.relation.RelationUtil; import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector; import de.lmu.ifi.dbs.elki.result.HierarchicalResult; import de.lmu.ifi.dbs.elki.result.Result; import de.lmu.ifi.dbs.elki.result.ResultUtil; -import de.lmu.ifi.dbs.elki.utilities.DatabaseUtil; import de.lmu.ifi.dbs.elki.utilities.exceptions.ObjectNotFoundException; import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; import de.lmu.ifi.dbs.elki.visualization.css.CSSClass; -import de.lmu.ifi.dbs.elki.visualization.projections.Projection2D; import de.lmu.ifi.dbs.elki.visualization.projector.ScatterPlotProjector; import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPath; @@ -55,126 +54,123 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * * @author Erich Schubert * - * @apiviz.has PolygonsObject - - visualizes + * @apiviz.stereotype factory + * @apiviz.uses Instance oneway - - «create» */ -public class PolygonVisualization extends AbstractScatterplotVisualization implements DataStoreListener { +public class PolygonVisualization extends AbstractVisFactory { /** * A short name characterizing this Visualizer. */ private static final String NAME = "Polygons"; /** - * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc. + * Constructor */ - public static final String POLYS = "polys"; - - /** - * The current projection - */ - final protected Projection2D proj; - - /** - * The representation we visualize - */ - final protected Relation<PolygonsObject> rep; - - /** - * Constructor. - * - * @param task Task to visualize - */ - public PolygonVisualization(VisualizationTask task) { - super(task); - this.proj = task.getProj(); - this.rep = task.getResult(); // Note: relation was used for projection - context.addDataStoreListener(this); - incrementalRedraw(); + public PolygonVisualization() { + super(); } @Override - public void destroy() { - super.destroy(); - context.removeDataStoreListener(this); + public Visualization makeVisualization(VisualizationTask task) { + return new Instance(task); } @Override - public void redraw() { - CSSClass css = new CSSClass(svgp, POLYS); - // TODO: separate fill and line colors? - css.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.POLYGONS)); - css.setStatement(SVGConstants.CSS_STROKE_PROPERTY, context.getStyleLibrary().getColor(StyleLibrary.POLYGONS)); - css.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE); - svgp.addCSSClassOrLogError(css); - svgp.updateStyleElement(); - - // draw data - for(DBIDIter iditer = rep.iterDBIDs(); iditer.valid(); iditer.advance()) { - try { - PolygonsObject poly = rep.get(iditer); - if(poly == null) { - continue; - } - SVGPath path = new SVGPath(); - for(Polygon ppoly : poly.getPolygons()) { - Vector first = ppoly.get(0); - double[] f = proj.fastProjectDataToRenderSpace(first.getArrayRef()); - path.moveTo(f[0], f[1]); - for(Vector v : ppoly) { - if(v == first) { - continue; - } - double[] p = proj.fastProjectDataToRenderSpace(v.getArrayRef()); - path.drawTo(p[0], p[1]); + public void processNewResult(HierarchicalResult baseResult, Result result) { + Collection<Relation<?>> results = ResultUtil.filterResults(result, Relation.class); + for(Relation<?> rel : results) { + if(TypeUtil.POLYGON_TYPE.isAssignableFromType(rel.getDataTypeInformation())) { + // Assume that a 2d projector is using the same coordinates as the + // polygons. + Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { + if(RelationUtil.dimensionality(p.getRelation()) == 2) { + final VisualizationTask task = new VisualizationTask(NAME, rel, p.getRelation(), this); + task.level = VisualizationTask.LEVEL_DATA - 10; + baseResult.getHierarchy().add(rel, task); + baseResult.getHierarchy().add(p, task); } - // close path. - path.drawTo(f[0], f[1]); } - Element e = path.makeElement(svgp); - SVGUtil.addCSSClass(e, POLYS); - layer.appendChild(e); - } - catch(ObjectNotFoundException e) { - // ignore. } } } /** - * The visualization factory + * Instance * * @author Erich Schubert * - * @apiviz.stereotype factory - * @apiviz.uses PolygonVisualization oneway - - «create» + * @apiviz.has PolygonsObject - - visualizes */ - public static class Factory extends AbstractVisFactory { + public class Instance extends AbstractScatterplotVisualization implements DataStoreListener { + /** + * Generic tag to indicate the type of element. Used in IDs, CSS-Classes + * etc. + */ + public static final String POLYS = "polys"; + /** - * Constructor + * The representation we visualize */ - public Factory() { - super(); + final protected Relation<PolygonsObject> rep; + + /** + * Constructor. + * + * @param task Task to visualize + */ + public Instance(VisualizationTask task) { + super(task); + this.rep = task.getResult(); // Note: relation was used for projection + context.addDataStoreListener(this); + incrementalRedraw(); } @Override - public Visualization makeVisualization(VisualizationTask task) { - return new PolygonVisualization(task); + public void destroy() { + super.destroy(); + context.removeDataStoreListener(this); } @Override - public void processNewResult(HierarchicalResult baseResult, Result result) { - Collection<Relation<?>> results = ResultUtil.filterResults(result, Relation.class); - for(Relation<?> rel : results) { - if(TypeUtil.POLYGON_TYPE.isAssignableFromType(rel.getDataTypeInformation())) { - // Assume that a 2d projector is using the same coordinates as the polygons. - Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : ps) { - if(DatabaseUtil.dimensionality(p.getRelation()) == 2) { - final VisualizationTask task = new VisualizationTask(NAME, rel, p.getRelation(), this); - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA - 10); - baseResult.getHierarchy().add(rel, task); - baseResult.getHierarchy().add(p, task); + public void redraw() { + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + CSSClass css = new CSSClass(svgp, POLYS); + // TODO: separate fill and line colors? + css.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.POLYGONS)); + css.setStatement(SVGConstants.CSS_STROKE_PROPERTY, style.getColor(StyleLibrary.POLYGONS)); + css.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE); + svgp.addCSSClassOrLogError(css); + svgp.updateStyleElement(); + + // draw data + for(DBIDIter iditer = rep.iterDBIDs(); iditer.valid(); iditer.advance()) { + try { + PolygonsObject poly = rep.get(iditer); + if(poly == null) { + continue; + } + SVGPath path = new SVGPath(); + for(Polygon ppoly : poly.getPolygons()) { + Vector first = ppoly.get(0); + double[] f = proj.fastProjectDataToRenderSpace(first.getArrayRef()); + path.moveTo(f[0], f[1]); + for(Vector v : ppoly) { + if(v == first) { + continue; + } + double[] p = proj.fastProjectDataToRenderSpace(v.getArrayRef()); + path.drawTo(p[0], p[1]); } + // close path. + path.drawTo(f[0], f[1]); } + Element e = path.makeElement(svgp); + SVGUtil.addCSSClass(e, POLYS); + layer.appendChild(e); + } + catch(ObjectNotFoundException e) { + // ignore. } } } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ReferencePointsVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ReferencePointsVisualization.java index 056b788b..94c1c8d2 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ReferencePointsVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ReferencePointsVisualization.java @@ -46,98 +46,103 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; /** * The actual visualization instance, for a single projection * + * @author Remigius Wojdanowski * @author Erich Schubert * - * @apiviz.has ReferencePointsResult oneway - - visualizes + * @apiviz.stereotype factory + * @apiviz.uses Instance oneway - - «create» */ -// TODO: add a result listener for the reference points. -public class ReferencePointsVisualization extends AbstractScatterplotVisualization { - /** - * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc. - */ - public static final String REFPOINT = "refpoint"; - +public class ReferencePointsVisualization extends AbstractVisFactory { /** * A short name characterizing this Visualizer. */ private static final String NAME = "Reference Points"; /** - * Serves reference points. + * Constructor, adhering to + * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} */ - protected ReferencePointsResult<? extends NumberVector<?, ?>> result; - - /** - * Constructor. - * - * @param task Visualization task - */ - public ReferencePointsVisualization(VisualizationTask task) { - super(task); - this.result = task.getResult(); - incrementalRedraw(); + public ReferencePointsVisualization() { + super(); } @Override - public void redraw() { - setupCSS(svgp); - Iterator<? extends NumberVector<?, ?>> iter = result.iterator(); - - final double dotsize = context.getStyleLibrary().getSize(StyleLibrary.REFERENCE_POINTS); - while(iter.hasNext()) { - NumberVector<?, ?> v = iter.next(); - double[] projected = proj.fastProjectDataToRenderSpace(v); - Element dot = svgp.svgCircle(projected[0], projected[1], dotsize); - SVGUtil.addCSSClass(dot, REFPOINT); - layer.appendChild(dot); + public void processNewResult(HierarchicalResult baseResult, Result result) { + Collection<ReferencePointsResult<?>> rps = ResultUtil.filterResults(result, ReferencePointsResult.class); + for(ReferencePointsResult<?> rp : rps) { + Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { + final VisualizationTask task = new VisualizationTask(NAME, rp, p.getRelation(), this); + task.level = VisualizationTask.LEVEL_DATA; + baseResult.getHierarchy().add(rp, task); + baseResult.getHierarchy().add(p, task); + } } } - /** - * Registers the Reference-Point-CSS-Class at a SVGPlot. - * - * @param svgp the SVGPlot to register the -CSS-Class. - */ - private void setupCSS(SVGPlot svgp) { - CSSClass refpoint = new CSSClass(svgp, REFPOINT); - refpoint.setStatement(SVGConstants.CSS_FILL_PROPERTY, context.getStyleLibrary().getColor(StyleLibrary.REFERENCE_POINTS)); - svgp.addCSSClassOrLogError(refpoint); + @Override + public Visualization makeVisualization(VisualizationTask task) { + return new Instance(task); } /** - * Generates a SVG-Element visualizing reference points. + * Instance. * * @author Remigius Wojdanowski + * @author Erich Schubert * - * @apiviz.stereotype factory - * @apiviz.uses ReferencePointsVisualization oneway - - «create» + * @apiviz.has ReferencePointsResult oneway - - visualizes */ - public static class Factory extends AbstractVisFactory { + // TODO: add a result listener for the reference points. + public class Instance extends AbstractScatterplotVisualization { + /** + * Generic tag to indicate the type of element. Used in IDs, CSS-Classes + * etc. + */ + public static final String REFPOINT = "refpoint"; + + /** + * Serves reference points. + */ + protected ReferencePointsResult<? extends NumberVector<?>> result; + /** - * Constructor, adhering to - * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} + * Constructor. + * + * @param task Visualization task */ - public Factory() { - super(); + public Instance(VisualizationTask task) { + super(task); + this.result = task.getResult(); + incrementalRedraw(); } @Override - public void processNewResult(HierarchicalResult baseResult, Result result) { - Collection<ReferencePointsResult<?>> rps = ResultUtil.filterResults(result, ReferencePointsResult.class); - for(ReferencePointsResult<?> rp : rps) { - Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : ps) { - final VisualizationTask task = new VisualizationTask(NAME, rp, p.getRelation(), this); - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA); - baseResult.getHierarchy().add(rp, task); - baseResult.getHierarchy().add(p, task); - } + public void redraw() { + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + setupCSS(svgp); + Iterator<? extends NumberVector<?>> iter = result.iterator(); + + final double dotsize = style.getSize(StyleLibrary.REFERENCE_POINTS); + while(iter.hasNext()) { + NumberVector<?> v = iter.next(); + double[] projected = proj.fastProjectDataToRenderSpace(v); + Element dot = svgp.svgCircle(projected[0], projected[1], dotsize); + SVGUtil.addCSSClass(dot, REFPOINT); + layer.appendChild(dot); } } - @Override - public Visualization makeVisualization(VisualizationTask task) { - return new ReferencePointsVisualization(task); + /** + * Registers the Reference-Point-CSS-Class at a SVGPlot. + * + * @param svgp the SVGPlot to register the -CSS-Class. + */ + private void setupCSS(SVGPlot svgp) { + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + CSSClass refpoint = new CSSClass(svgp, REFPOINT); + refpoint.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.REFERENCE_POINTS)); + svgp.addCSSClassOrLogError(refpoint); } } }
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ToolBox2DVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ToolBox2DVisualization.java index d0c05cc7..352e79de 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ToolBox2DVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/ToolBox2DVisualization.java @@ -54,9 +54,10 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.VisualizerUtil; *
* @author Heidi Kolb
*
- * @apiviz.has VisualizationTask oneway - - visualizes
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class ToolBox2DVisualization extends AbstractScatterplotVisualization {
+public class ToolBox2DVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
@@ -65,241 +66,241 @@ public class ToolBox2DVisualization extends AbstractScatterplotVisualization { /**
* The logger for this class.
*/
- private static final Logging logger = Logging.getLogger(Factory.class);
+ private static final Logging LOG = Logging.getLogger(ToolBox2DVisualization.class);
/**
- * CSS class for a tool button
+ * Constructor
*/
- public static final String CSS_TOOL_BUTTON = "toolButton";
-
- /**
- * CSS class for the tool button caption
- */
- public static final String CSS_TOOL_CAPTION = "toolCaption";
-
- /**
- * CSS class for a tool button
- */
- public static final String CSS_TOOL_BUTTON_SELECTED = "toolButtonSelected";
-
- /**
- * The container
- */
- private Element container;
-
- /**
- * Constructor.
- *
- * @param task Task
- */
- public ToolBox2DVisualization(VisualizationTask task) {
- super(task);
- // TODO: which result do we best attach to?
- context.addResultListener(this);
- incrementalRedraw();
+ public ToolBox2DVisualization() {
+ super();
}
@Override
- protected void redraw() {
- addCSSClasses(svgp);
- container = svgp.svgElement(SVGConstants.SVG_G_TAG);
- buildToolBox();
- layer.appendChild(container);
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
- /**
- * Deletes the children of the container
- *
- * @param container Element to delete children
- */
- private void deleteChildren(Element container) {
- while(container.hasChildNodes()) {
- container.removeChild(container.getLastChild());
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(result, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, p, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_INTERACTIVE;
+ task.thumbnail = false;
+ task.noexport = true;
+ task.noembed = true;
+ baseResult.getHierarchy().add(p, task);
}
}
/**
- * Build the toolbox
+ * Instance.
+ *
+ * @author Heidi Kolb
+ *
+ * @apiviz.has VisualizationTask oneway - - visualizes
*/
- private void buildToolBox() {
- double scale = StyleLibrary.SCALE;
- deleteChildren(container);
-
- ArrayList<VisualizationTask> vis = new ArrayList<VisualizationTask>();
- Collection<VisualizationTask> visualizers = ResultUtil.filterResults(task.getResult(), VisualizationTask.class);
- for(VisualizationTask task : visualizers) {
- if(VisualizerUtil.isTool(task) && !vis.contains(task)) {
- vis.add(task);
- }
- }
+ public class Instance extends AbstractScatterplotVisualization {
+ /**
+ * CSS class for a tool button
+ */
+ public static final String CSS_TOOL_BUTTON = "toolButton";
- // calculate the position of the first tool
- CanvasSize viewport = proj.estimateViewport();
- double x = viewport.getMinX() - 0.17 * scale;
- double width = 0.07 * scale;
- double height = 0.06 * scale;
- double miny = viewport.getMinY();
- double maxy = viewport.getMaxY();
- double y = (miny + maxy) / 2 - (vis.size() * height * 1.4) / 2;
- if(y < miny) {
- logger.warning("Too many Tools");
- }
+ /**
+ * CSS class for the tool button caption
+ */
+ public static final String CSS_TOOL_CAPTION = "toolCaption";
- // add tools
- Element[] toolTags = new Element[vis.size()];
- for(int i = 0; i < vis.size(); i++) {
- VisualizationTask v = vis.get(i);
- toolTags[i] = svgp.svgRect(x, y, width, height);
- String name = v.getLongName();
- // Split
- List<String> lines = FormatUtil.splitAtLastBlank(name, 8);
- // Generate label objects.
- for(int l = 0; l < lines.size(); l++) {
- Element selectRangeText = svgp.svgText(x + 0.01 * scale, y + (0.02 + 0.05 * l / lines.size()) * scale, lines.get(l));
- SVGUtil.setAtt(selectRangeText, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_TOOL_CAPTION);
- container.appendChild(selectRangeText);
- }
+ /**
+ * CSS class for a tool button
+ */
+ public static final String CSS_TOOL_BUTTON_SELECTED = "toolButtonSelected";
- if(VisualizerUtil.isVisible(v)) {
- SVGUtil.addCSSClass(toolTags[i], CSS_TOOL_BUTTON_SELECTED);
- }
- else {
- SVGUtil.addCSSClass(toolTags[i], CSS_TOOL_BUTTON);
- }
- addEventListener(toolTags[i], v);
+ /**
+ * The container
+ */
+ private Element container;
- container.appendChild(toolTags[i]);
- y = y + 0.1 * scale;
+ /**
+ * Constructor.
+ *
+ * @param task Task
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ // TODO: which result do we best attach to?
+ context.addResultListener(this);
+ incrementalRedraw();
}
- }
- /**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- private void addCSSClasses(SVGPlot svgp) {
- // Class for the not selected tool
- if(!svgp.getCSSClassManager().contains(CSS_TOOL_BUTTON)) {
- final CSSClass modeCls = new CSSClass(this, CSS_TOOL_BUTTON);
- modeCls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, 0.4);
- modeCls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_GREEN_VALUE);
- modeCls.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE);
-
- svgp.addCSSClassOrLogError(modeCls);
- }
- // Class for the selected tool
- if(!svgp.getCSSClassManager().contains(CSS_TOOL_BUTTON_SELECTED)) {
- final CSSClass modeCls = new CSSClass(this, CSS_TOOL_BUTTON_SELECTED);
- modeCls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, 0.4);
- modeCls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_BLUE_VALUE);
- modeCls.setStatement(SVGConstants.CSS_STROKE_OPACITY_PROPERTY, 0.4);
- modeCls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_BLUE_VALUE);
- modeCls.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE);
-
- svgp.addCSSClassOrLogError(modeCls);
+ @Override
+ protected void redraw() {
+ addCSSClasses(svgp);
+ container = svgp.svgElement(SVGConstants.SVG_G_TAG);
+ buildToolBox();
+ layer.appendChild(container);
}
- // Class for the text of the tools
- if(!svgp.getCSSClassManager().contains(CSS_TOOL_CAPTION)) {
- final CSSClass label = new CSSClass(svgp, CSS_TOOL_CAPTION);
- label.setStatement(SVGConstants.CSS_FILL_PROPERTY, context.getStyleLibrary().getTextColor(StyleLibrary.AXIS_LABEL));
- label.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, context.getStyleLibrary().getFontFamily(StyleLibrary.AXIS_LABEL));
- label.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, context.getStyleLibrary().getTextSize(StyleLibrary.AXIS_LABEL) * .8);
-
- svgp.addCSSClassOrLogError(label);
+
+ /**
+ * Deletes the children of the container
+ *
+ * @param container Element to delete children
+ */
+ private void deleteChildren(Element container) {
+ while(container.hasChildNodes()) {
+ container.removeChild(container.getLastChild());
+ }
}
- }
- /**
- * Add an event listener to the Element
- *
- * @param tag Element to add the listener
- * @param tool Tool represented by the Element
- */
- private void addEventListener(final Element tag, final VisualizationTask tool) {
- EventTarget targ = (EventTarget) tag;
- targ.addEventListener(SVGConstants.SVG_EVENT_CLICK, new EventListener() {
- @Override
- public void handleEvent(Event evt) {
- handleMouseClick(tool);
+ /**
+ * Build the toolbox
+ */
+ private void buildToolBox() {
+ double scale = StyleLibrary.SCALE;
+ deleteChildren(container);
+
+ ArrayList<VisualizationTask> vis = new ArrayList<VisualizationTask>();
+ Collection<VisualizationTask> visualizers = ResultUtil.filterResults(task.getResult(), VisualizationTask.class);
+ for(VisualizationTask task : visualizers) {
+ if(task.tool && !vis.contains(task)) {
+ vis.add(task);
+ }
}
- }, false);
- }
- /**
- * Handle the mouseClick - change the selected tool in the context
- *
- * @param tool Selected Tool
- */
- protected void handleMouseClick(VisualizationTask tool) {
- // TODO: Move this to the selected tool instead?
- if(VisualizerUtil.isVisible(tool)) {
- context.setSelection(null);
- }
- VisualizerUtil.setVisible(context, tool, true);
- }
+ // calculate the position of the first tool
+ CanvasSize viewport = proj.estimateViewport();
+ double x = viewport.getMinX() - 0.17 * scale;
+ double width = 0.07 * scale;
+ double height = 0.06 * scale;
+ double miny = viewport.getMinY();
+ double maxy = viewport.getMaxY();
+ double y = (miny + maxy - vis.size() * height * 1.4) * .5;
+ if(y < miny) {
+ LOG.warning("Too many Tools");
+ }
- @Override
- public void resultAdded(Result child, Result parent) {
- if(child instanceof VisualizationTask) {
- VisualizationTask task = (VisualizationTask) child;
- if(VisualizerUtil.isTool(task)) {
- synchronizedRedraw();
+ // add tools
+ Element[] toolTags = new Element[vis.size()];
+ for(int i = 0; i < vis.size(); i++) {
+ VisualizationTask v = vis.get(i);
+ toolTags[i] = svgp.svgRect(x, y, width, height);
+ String name = v.getLongName();
+ // Split
+ List<String> lines = FormatUtil.splitAtLastBlank(name, 8);
+ // Generate label objects.
+ for(int l = 0; l < lines.size(); l++) {
+ Element selectRangeText = svgp.svgText(x + 0.01 * scale, y + (0.02 + 0.05 * l / lines.size()) * scale, lines.get(l));
+ SVGUtil.setAtt(selectRangeText, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_TOOL_CAPTION);
+ container.appendChild(selectRangeText);
+ }
+
+ if(v.visible) {
+ SVGUtil.addCSSClass(toolTags[i], CSS_TOOL_BUTTON_SELECTED);
+ }
+ else {
+ SVGUtil.addCSSClass(toolTags[i], CSS_TOOL_BUTTON);
+ }
+ addEventListener(toolTags[i], v);
+
+ container.appendChild(toolTags[i]);
+ y = y + 0.1 * scale;
}
}
- }
- @Override
- public void resultRemoved(Result child, Result parent) {
- if(child instanceof VisualizationTask) {
- VisualizationTask task = (VisualizationTask) child;
- if(VisualizerUtil.isTool(task)) {
- synchronizedRedraw();
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ // Class for the not selected tool
+ if(!svgp.getCSSClassManager().contains(CSS_TOOL_BUTTON)) {
+ final CSSClass modeCls = new CSSClass(this, CSS_TOOL_BUTTON);
+ modeCls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, 0.4);
+ modeCls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_GREEN_VALUE);
+ modeCls.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE);
+
+ svgp.addCSSClassOrLogError(modeCls);
+ }
+ // Class for the selected tool
+ if(!svgp.getCSSClassManager().contains(CSS_TOOL_BUTTON_SELECTED)) {
+ final CSSClass modeCls = new CSSClass(this, CSS_TOOL_BUTTON_SELECTED);
+ modeCls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, 0.4);
+ modeCls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_BLUE_VALUE);
+ modeCls.setStatement(SVGConstants.CSS_STROKE_OPACITY_PROPERTY, 0.4);
+ modeCls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_BLUE_VALUE);
+ modeCls.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE);
+
+ svgp.addCSSClassOrLogError(modeCls);
+ }
+ // Class for the text of the tools
+ if(!svgp.getCSSClassManager().contains(CSS_TOOL_CAPTION)) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ final CSSClass label = new CSSClass(svgp, CSS_TOOL_CAPTION);
+ label.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getTextColor(StyleLibrary.AXIS_LABEL));
+ label.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, style.getFontFamily(StyleLibrary.AXIS_LABEL));
+ label.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, style.getTextSize(StyleLibrary.AXIS_LABEL) * .8);
+
+ svgp.addCSSClassOrLogError(label);
}
}
- }
- @Override
- public void resultChanged(Result current) {
- if(current instanceof VisualizationTask) {
- VisualizationTask task = (VisualizationTask) current;
- if(VisualizerUtil.isTool(task)) {
- synchronizedRedraw();
- }
+ /**
+ * Add an event listener to the Element
+ *
+ * @param tag Element to add the listener
+ * @param tool Tool represented by the Element
+ */
+ private void addEventListener(final Element tag, final VisualizationTask tool) {
+ EventTarget targ = (EventTarget) tag;
+ targ.addEventListener(SVGConstants.SVG_EVENT_CLICK, new EventListener() {
+ @Override
+ public void handleEvent(Event evt) {
+ handleMouseClick(tool);
+ }
+ }, false);
}
- }
- /**
- * Factory for visualizers for a toolbox
- *
- * @author Heidi Kolb
- *
- * @apiviz.stereotype factory
- * @apiviz.uses ToolBox2DVisualization oneway - - «create»
- */
- public static class Factory extends AbstractVisFactory {
/**
- * Constructor
+ * Handle the mouseClick - change the selected tool in the context
+ *
+ * @param tool Selected Tool
*/
- public Factory() {
- super();
+ protected void handleMouseClick(VisualizationTask tool) {
+ // TODO: Move this to the selected tool instead?
+ if(tool.visible) {
+ context.setSelection(null);
+ }
+ VisualizerUtil.setVisible(context, tool, true);
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new ToolBox2DVisualization(task);
+ public void resultAdded(Result child, Result parent) {
+ if(child instanceof VisualizationTask) {
+ VisualizationTask task = (VisualizationTask) child;
+ if(task.tool) {
+ synchronizedRedraw();
+ }
+ }
+ }
+
+ @Override
+ public void resultRemoved(Result child, Result parent) {
+ if(child instanceof VisualizationTask) {
+ VisualizationTask task = (VisualizationTask) child;
+ if(task.tool) {
+ synchronizedRedraw();
+ }
+ }
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(result, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : ps) {
- final VisualizationTask task = new VisualizationTask(NAME, p, p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE);
- task.put(VisualizationTask.META_NOTHUMB, true);
- task.put(VisualizationTask.META_NOEXPORT, true);
- task.put(VisualizationTask.META_NOEMBED, true);
- baseResult.getHierarchy().add(p, task);
+ public void resultChanged(Result current) {
+ if(current instanceof VisualizationTask) {
+ VisualizationTask task = (VisualizationTask) current;
+ if(task.tool) {
+ synchronizedRedraw();
+ }
}
}
}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipScoreVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipScoreVisualization.java index 139fa1ed..f3f35002 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipScoreVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipScoreVisualization.java @@ -31,7 +31,7 @@ import org.apache.batik.util.SVGConstants; import org.w3c.dom.Element; import de.lmu.ifi.dbs.elki.data.type.TypeUtil; -import de.lmu.ifi.dbs.elki.database.ids.DBID; +import de.lmu.ifi.dbs.elki.database.ids.DBIDRef; import de.lmu.ifi.dbs.elki.database.relation.Relation; import de.lmu.ifi.dbs.elki.result.HierarchicalResult; import de.lmu.ifi.dbs.elki.result.Result; @@ -56,8 +56,12 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * as the cursor lingers on the marker. * * @author Remigius Wojdanowski + * @author Erich Schubert + * + * @apiviz.stereotype factory + * @apiviz.uses Instance oneway - - «create» */ -public class TooltipScoreVisualization extends AbstractTooltipVisualization { +public class TooltipScoreVisualization extends AbstractVisFactory { /** * A short name characterizing this Visualizer. */ @@ -69,185 +73,182 @@ public class TooltipScoreVisualization extends AbstractTooltipVisualization { public static final String NAME_GEN = " Tooltips"; /** - * Number format. + * Settings */ - NumberFormat nf; + protected Parameterizer settings; /** - * Number value to visualize - */ - private Relation<? extends Number> result; - - /** - * Font size to use. - */ - private double fontsize; - - /** - * Constructor + * Constructor. * - * @param task Task - * @param nf Number Format + * @param settings Settings */ - public TooltipScoreVisualization(VisualizationTask task, NumberFormat nf) { - super(task); - this.result = task.getResult(); - this.nf = nf; - this.fontsize = 3 * context.getStyleLibrary().getTextSize(StyleLibrary.PLOT); - synchronizedRedraw(); + public TooltipScoreVisualization(Parameterizer settings) { + super(); + this.settings = settings; } @Override - protected Element makeTooltip(DBID id, double x, double y, double dotsize) { - return svgp.svgText(x + dotsize, y + fontsize * 0.07, nf.format(result.get(id).doubleValue())); + public Visualization makeVisualization(VisualizationTask task) { + return new Instance(task); } - /** - * Registers the Tooltip-CSS-Class at a SVGPlot. - * - * @param svgp the SVGPlot to register the Tooltip-CSS-Class. - */ @Override - protected void setupCSS(SVGPlot svgp) { - final StyleLibrary style = context.getStyleLibrary(); - final double fontsize = style.getTextSize(StyleLibrary.PLOT); - final String fontfamily = style.getFontFamily(StyleLibrary.PLOT); - - CSSClass tooltiphidden = new CSSClass(svgp, TOOLTIP_HIDDEN); - tooltiphidden.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize); - tooltiphidden.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily); - tooltiphidden.setStatement(SVGConstants.CSS_DISPLAY_PROPERTY, SVGConstants.CSS_NONE_VALUE); - svgp.addCSSClassOrLogError(tooltiphidden); - - CSSClass tooltipvisible = new CSSClass(svgp, TOOLTIP_VISIBLE); - tooltipvisible.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize); - tooltipvisible.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily); - svgp.addCSSClassOrLogError(tooltipvisible); - - CSSClass tooltipsticky = new CSSClass(svgp, TOOLTIP_STICKY); - tooltipsticky.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize); - tooltipsticky.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily); - svgp.addCSSClassOrLogError(tooltipsticky); - - // invisible but sensitive area for the tooltip activator - CSSClass tooltiparea = new CSSClass(svgp, TOOLTIP_AREA); - tooltiparea.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_RED_VALUE); - tooltiparea.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_NONE_VALUE); - tooltiparea.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, "0"); - tooltiparea.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE); - svgp.addCSSClassOrLogError(tooltiparea); - - svgp.updateStyleElement(); + public void processNewResult(HierarchicalResult baseResult, Result result) { + // TODO: we can also visualize other scores! + Collection<OutlierResult> ors = ResultUtil.filterResults(result, OutlierResult.class); + for(OutlierResult o : ors) { + Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { + final VisualizationTask task = new VisualizationTask(NAME, o.getScores(), p.getRelation(), this); + task.tool = true; + task.initDefaultVisibility(false); + baseResult.getHierarchy().add(o.getScores(), task); + baseResult.getHierarchy().add(p, task); + } + } + Collection<Relation<?>> rrs = ResultUtil.filterResults(result, Relation.class); + for(Relation<?> r : rrs) { + if(!TypeUtil.DOUBLE.isAssignableFromType(r.getDataTypeInformation()) && !TypeUtil.INTEGER.isAssignableFromType(r.getDataTypeInformation())) { + continue; + } + // Skip if we already considered it above + boolean add = true; + for(Result p : baseResult.getHierarchy().getChildren(r)) { + if(p instanceof VisualizationTask && ((VisualizationTask) p).getFactory() instanceof TooltipScoreVisualization) { + add = false; + break; + } + } + if(add) { + Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { + final VisualizationTask task = new VisualizationTask(r.getLongName() + NAME_GEN, r, p.getRelation(), this); + task.tool = true; + task.initDefaultVisibility(false); + baseResult.getHierarchy().add(r, task); + baseResult.getHierarchy().add(p, task); + } + } + } } /** - * Factory for tooltip visualizers + * Instance * + * @author Remigius Wojdanowski * @author Erich Schubert - * - * @apiviz.stereotype factory - * @apiviz.uses TooltipScoreVisualization oneway - - «create» */ - public static class Factory extends AbstractVisFactory { + public class Instance extends AbstractTooltipVisualization { /** - * Parameter for the gamma-correction. - * - * <p> - * Key: {@code -tooltip.digits} - * </p> - * - * <p> - * Default value: 4 - * </p> + * Number value to visualize */ - public static final OptionID DIGITS_ID = OptionID.getOrCreateOptionID("tooltip.digits", "Number of digits to show (e.g. when visualizing outlier scores)"); + private Relation<? extends Number> result; /** - * Number formatter used for visualization + * Font size to use. */ - NumberFormat nf = null; + private double fontsize; /** - * Constructor. + * Constructor * - * @param digits number of digits + * @param task Task */ - public Factory(int digits) { - super(); - nf = NumberFormat.getInstance(Locale.ROOT); - nf.setGroupingUsed(false); - nf.setMaximumFractionDigits(digits); + public Instance(VisualizationTask task) { + super(task); + this.result = task.getResult(); + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + this.fontsize = 3 * style.getTextSize(StyleLibrary.PLOT); + synchronizedRedraw(); } @Override - public Visualization makeVisualization(VisualizationTask task) { - return new TooltipScoreVisualization(task, nf); + protected Element makeTooltip(DBIDRef id, double x, double y, double dotsize) { + return svgp.svgText(x + dotsize, y + fontsize * 0.07, settings.nf.format(result.get(id).doubleValue())); } + /** + * Registers the Tooltip-CSS-Class at a SVGPlot. + * + * @param svgp the SVGPlot to register the Tooltip-CSS-Class. + */ @Override - public void processNewResult(HierarchicalResult baseResult, Result result) { - // TODO: we can also visualize other scores! - Collection<OutlierResult> ors = ResultUtil.filterResults(result, OutlierResult.class); - for(OutlierResult o : ors) { - Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : ps) { - final VisualizationTask task = new VisualizationTask(NAME, o.getScores(), p.getRelation(), this); - task.put(VisualizationTask.META_TOOL, true); - task.put(VisualizationTask.META_VISIBLE_DEFAULT, false); - baseResult.getHierarchy().add(o.getScores(), task); - baseResult.getHierarchy().add(p, task); - } - } - Collection<Relation<?>> rrs = ResultUtil.filterResults(result, Relation.class); - for(Relation<?> r : rrs) { - if(!TypeUtil.DOUBLE.isAssignableFromType(r.getDataTypeInformation()) && !TypeUtil.INTEGER.isAssignableFromType(r.getDataTypeInformation())) { - continue; - } - // Skip if we already considered it above - boolean add = true; - for(Result p : baseResult.getHierarchy().getChildren(r)) { - if(p instanceof VisualizationTask && ((VisualizationTask) p).getFactory() instanceof Factory) { - add = false; - break; - } - } - if(add) { - Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : ps) { - final VisualizationTask task = new VisualizationTask(r.getLongName() + NAME_GEN, r, p.getRelation(), this); - task.put(VisualizationTask.META_TOOL, true); - task.put(VisualizationTask.META_VISIBLE_DEFAULT, false); - baseResult.getHierarchy().add(r, task); - baseResult.getHierarchy().add(p, task); - } - } - } + protected void setupCSS(SVGPlot svgp) { + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + final double fontsize = style.getTextSize(StyleLibrary.PLOT); + final String fontfamily = style.getFontFamily(StyleLibrary.PLOT); + + CSSClass tooltiphidden = new CSSClass(svgp, TOOLTIP_HIDDEN); + tooltiphidden.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize); + tooltiphidden.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily); + tooltiphidden.setStatement(SVGConstants.CSS_DISPLAY_PROPERTY, SVGConstants.CSS_NONE_VALUE); + svgp.addCSSClassOrLogError(tooltiphidden); + + CSSClass tooltipvisible = new CSSClass(svgp, TOOLTIP_VISIBLE); + tooltipvisible.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize); + tooltipvisible.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily); + svgp.addCSSClassOrLogError(tooltipvisible); + + CSSClass tooltipsticky = new CSSClass(svgp, TOOLTIP_STICKY); + tooltipsticky.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize); + tooltipsticky.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily); + svgp.addCSSClassOrLogError(tooltipsticky); + + // invisible but sensitive area for the tooltip activator + CSSClass tooltiparea = new CSSClass(svgp, TOOLTIP_AREA); + tooltiparea.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_RED_VALUE); + tooltiparea.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_NONE_VALUE); + tooltiparea.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, "0"); + tooltiparea.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE); + svgp.addCSSClassOrLogError(tooltiparea); + + svgp.updateStyleElement(); } + } + + /** + * Parameterization class. + * + * @author Erich Schubert + * + * @apiviz.exclude + */ + public static class Parameterizer extends AbstractParameterizer { + /** + * Number formatter used for visualization + */ + NumberFormat nf = null; /** - * Parameterization class. + * Parameter for the gamma-correction. * - * @author Erich Schubert + * <p> + * Key: {@code -tooltip.digits} + * </p> * - * @apiviz.exclude + * <p> + * Default value: 4 + * </p> */ - public static class Parameterizer extends AbstractParameterizer { - protected int digits = 4; + public static final OptionID DIGITS_ID = new OptionID("tooltip.digits", "Number of digits to show (e.g. when visualizing outlier scores)"); - @Override - protected void makeOptions(Parameterization config) { - super.makeOptions(config); - IntParameter DIGITS_PARAM = new IntParameter(DIGITS_ID, new GreaterEqualConstraint(0), 4); - - if(config.grab(DIGITS_PARAM)) { - digits = DIGITS_PARAM.getValue(); - } + @Override + protected void makeOptions(Parameterization config) { + super.makeOptions(config); + IntParameter digitsP = new IntParameter(DIGITS_ID, 4); + digitsP.addConstraint(new GreaterEqualConstraint(0)); + + if(config.grab(digitsP)) { + int digits = digitsP.intValue(); + nf = NumberFormat.getInstance(Locale.ROOT); + nf.setGroupingUsed(false); + nf.setMaximumFractionDigits(digits); } + } - @Override - protected Factory makeInstance() { - return new Factory(digits); - } + @Override + protected TooltipScoreVisualization makeInstance() { + return new TooltipScoreVisualization(this); } } }
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipStringVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipStringVisualization.java index d015793c..979ce905 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipStringVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/TooltipStringVisualization.java @@ -32,6 +32,7 @@ import de.lmu.ifi.dbs.elki.data.ClassLabel; import de.lmu.ifi.dbs.elki.data.ExternalID; import de.lmu.ifi.dbs.elki.data.LabelList; import de.lmu.ifi.dbs.elki.database.ids.DBID; +import de.lmu.ifi.dbs.elki.database.ids.DBIDRef; import de.lmu.ifi.dbs.elki.database.relation.Relation; import de.lmu.ifi.dbs.elki.result.HierarchicalResult; import de.lmu.ifi.dbs.elki.result.Result; @@ -52,9 +53,10 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * @author Remigius Wojdanowski * @author Erich Schubert * - * @apiviz.has Relation oneway - - visualizes + * @apiviz.stereotype factory + * @apiviz.uses Instance oneway - - «create» */ -public class TooltipStringVisualization extends AbstractTooltipVisualization { +public class TooltipStringVisualization extends AbstractVisFactory { /** * A short name characterizing this Visualizer. */ @@ -76,146 +78,147 @@ public class TooltipStringVisualization extends AbstractTooltipVisualization { public static final String NAME_EID = "External ID Tooltips"; /** - * Number value to visualize + * Constructor, adhering to + * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} */ - private Relation<?> result; - - /** - * Font size to use. - */ - private double fontsize; - - /** - * Constructor. - * - * @param task Task - */ - public TooltipStringVisualization(VisualizationTask task) { - super(task); - this.result = task.getResult(); - this.fontsize = 3 * context.getStyleLibrary().getTextSize(StyleLibrary.PLOT); - synchronizedRedraw(); + public TooltipStringVisualization() { + super(); } @Override - protected Element makeTooltip(DBID id, double x, double y, double dotsize) { - final Object data = result.get(id); - String label; - if(data == null) { - label = "null"; - } - else { - label = data.toString(); - } - if(label == "" || label == null) { - label = "null"; - } - return svgp.svgText(x + dotsize, y + fontsize * 0.07, label); + public Visualization makeVisualization(VisualizationTask task) { + return new Instance(task); } - /** - * Registers the Tooltip-CSS-Class at a SVGPlot. - * - * @param svgp the SVGPlot to register the Tooltip-CSS-Class. - */ @Override - protected void setupCSS(SVGPlot svgp) { - final StyleLibrary style = context.getStyleLibrary(); - final double fontsize = style.getTextSize(StyleLibrary.PLOT); - final String fontfamily = style.getFontFamily(StyleLibrary.PLOT); - - CSSClass tooltiphidden = new CSSClass(svgp, TOOLTIP_HIDDEN); - tooltiphidden.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize); - tooltiphidden.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily); - tooltiphidden.setStatement(SVGConstants.CSS_DISPLAY_PROPERTY, SVGConstants.CSS_NONE_VALUE); - svgp.addCSSClassOrLogError(tooltiphidden); - - CSSClass tooltipvisible = new CSSClass(svgp, TOOLTIP_VISIBLE); - tooltipvisible.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize); - tooltipvisible.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily); - svgp.addCSSClassOrLogError(tooltipvisible); - - CSSClass tooltipsticky = new CSSClass(svgp, TOOLTIP_STICKY); - tooltipsticky.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize); - tooltipsticky.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily); - svgp.addCSSClassOrLogError(tooltipsticky); - - // invisible but sensitive area for the tooltip activator - CSSClass tooltiparea = new CSSClass(svgp, TOOLTIP_AREA); - tooltiparea.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_RED_VALUE); - tooltiparea.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_NONE_VALUE); - tooltiparea.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, "0"); - tooltiparea.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE); - svgp.addCSSClassOrLogError(tooltiparea); + public void processNewResult(HierarchicalResult baseResult, Result result) { + Collection<Relation<?>> reps = ResultUtil.filterResults(result, Relation.class); + for(Relation<?> rep : reps) { + if(DBID.class.isAssignableFrom(rep.getDataTypeInformation().getRestrictionClass())) { + Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { + final VisualizationTask task = new VisualizationTask(NAME_ID, rep, p.getRelation(), this); + task.tool = true; + task.initDefaultVisibility(false); + baseResult.getHierarchy().add(rep, task); + baseResult.getHierarchy().add(p, task); + } + } + if(ClassLabel.class.isAssignableFrom(rep.getDataTypeInformation().getRestrictionClass())) { + Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { + final VisualizationTask task = new VisualizationTask(NAME_CLASS, rep, p.getRelation(), this); + task.tool = true; + task.initDefaultVisibility(false); + baseResult.getHierarchy().add(rep, task); + baseResult.getHierarchy().add(p, task); + } + } + if(LabelList.class.isAssignableFrom(rep.getDataTypeInformation().getRestrictionClass())) { + Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { + final VisualizationTask task = new VisualizationTask(NAME_LABEL, rep, p.getRelation(), this); + task.tool = true; + task.initDefaultVisibility(false); + baseResult.getHierarchy().add(rep, task); + baseResult.getHierarchy().add(p, task); + } + } + if(ExternalID.class.isAssignableFrom(rep.getDataTypeInformation().getRestrictionClass())) { + Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { + final VisualizationTask task = new VisualizationTask(NAME_EID, rep, p.getRelation(), this); + task.tool = true; + task.initDefaultVisibility(false); + baseResult.getHierarchy().add(rep, task); + baseResult.getHierarchy().add(p, task); + } + } + } } /** - * Factory + * Instance * + * @author Remigius Wojdanowski * @author Erich Schubert * - * @apiviz.stereotype factory - * @apiviz.uses TooltipStringVisualization oneway - - «create» + * @apiviz.has Relation oneway - - visualizes */ - public static class Factory extends AbstractVisFactory { + public class Instance extends AbstractTooltipVisualization { + /** + * Number value to visualize + */ + private Relation<?> result; + /** - * Constructor, adhering to - * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} + * Font size to use. */ - public Factory() { - super(); + private double fontsize; + + /** + * Constructor. + * + * @param task Task + */ + public Instance(VisualizationTask task) { + super(task); + this.result = task.getResult(); + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + this.fontsize = 3 * style.getTextSize(StyleLibrary.PLOT); + synchronizedRedraw(); } @Override - public Visualization makeVisualization(VisualizationTask task) { - return new TooltipStringVisualization(task); + protected Element makeTooltip(DBIDRef id, double x, double y, double dotsize) { + final Object data = result.get(id); + String label; + if(data == null) { + label = "null"; + } + else { + label = data.toString(); + } + if(label == "" || label == null) { + label = "null"; + } + return svgp.svgText(x + dotsize, y + fontsize * 0.07, label); } + /** + * Registers the Tooltip-CSS-Class at a SVGPlot. + * + * @param svgp the SVGPlot to register the Tooltip-CSS-Class. + */ @Override - public void processNewResult(HierarchicalResult baseResult, Result result) { - Collection<Relation<?>> reps = ResultUtil.filterResults(result, Relation.class); - for(Relation<?> rep : reps) { - if(DBID.class.isAssignableFrom(rep.getDataTypeInformation().getRestrictionClass())) { - Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : ps) { - final VisualizationTask task = new VisualizationTask(NAME_ID, rep, p.getRelation(), this); - task.put(VisualizationTask.META_TOOL, true); - task.put(VisualizationTask.META_VISIBLE_DEFAULT, false); - baseResult.getHierarchy().add(rep, task); - baseResult.getHierarchy().add(p, task); - } - } - if(ClassLabel.class.isAssignableFrom(rep.getDataTypeInformation().getRestrictionClass())) { - Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : ps) { - final VisualizationTask task = new VisualizationTask(NAME_CLASS, rep, p.getRelation(), this); - task.put(VisualizationTask.META_TOOL, true); - task.put(VisualizationTask.META_VISIBLE_DEFAULT, false); - baseResult.getHierarchy().add(rep, task); - baseResult.getHierarchy().add(p, task); - } - } - if(LabelList.class.isAssignableFrom(rep.getDataTypeInformation().getRestrictionClass())) { - Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : ps) { - final VisualizationTask task = new VisualizationTask(NAME_LABEL, rep, p.getRelation(), this); - task.put(VisualizationTask.META_TOOL, true); - task.put(VisualizationTask.META_VISIBLE_DEFAULT, false); - baseResult.getHierarchy().add(rep, task); - baseResult.getHierarchy().add(p, task); - } - } - if(ExternalID.class.isAssignableFrom(rep.getDataTypeInformation().getRestrictionClass())) { - Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : ps) { - final VisualizationTask task = new VisualizationTask(NAME_EID, rep, p.getRelation(), this); - task.put(VisualizationTask.META_TOOL, true); - task.put(VisualizationTask.META_VISIBLE_DEFAULT, false); - baseResult.getHierarchy().add(rep, task); - baseResult.getHierarchy().add(p, task); - } - } - } + protected void setupCSS(SVGPlot svgp) { + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + final double fontsize = style.getTextSize(StyleLibrary.PLOT); + final String fontfamily = style.getFontFamily(StyleLibrary.PLOT); + + CSSClass tooltiphidden = new CSSClass(svgp, TOOLTIP_HIDDEN); + tooltiphidden.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize); + tooltiphidden.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily); + tooltiphidden.setStatement(SVGConstants.CSS_DISPLAY_PROPERTY, SVGConstants.CSS_NONE_VALUE); + svgp.addCSSClassOrLogError(tooltiphidden); + + CSSClass tooltipvisible = new CSSClass(svgp, TOOLTIP_VISIBLE); + tooltipvisible.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize); + tooltipvisible.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily); + svgp.addCSSClassOrLogError(tooltipvisible); + + CSSClass tooltipsticky = new CSSClass(svgp, TOOLTIP_STICKY); + tooltipsticky.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, fontsize); + tooltipsticky.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, fontfamily); + svgp.addCSSClassOrLogError(tooltipsticky); + + // invisible but sensitive area for the tooltip activator + CSSClass tooltiparea = new CSSClass(svgp, TOOLTIP_AREA); + tooltiparea.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_RED_VALUE); + tooltiparea.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_NONE_VALUE); + tooltiparea.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, "0"); + tooltiparea.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE); + svgp.addCSSClassOrLogError(tooltiparea); } } }
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterHullVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterHullVisualization.java index e1817b1e..ebaf9eb9 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterHullVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterHullVisualization.java @@ -69,207 +69,201 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatter * @author Robert Rödler
* @author Erich Schubert
*
- * @apiviz.has Clustering oneway - - visualizes
- * @apiviz.uses GrahamScanConvexHull2D
- * @apiviz.uses AlphaShape
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class ClusterHullVisualization extends AbstractScatterplotVisualization {
+public class ClusterHullVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
private static final String NAME = "Cluster Hull Visualization";
/**
- * Generic tags to indicate the type of element. Used in IDs, CSS-Classes etc.
+ * Settings
*/
- public static final String CLUSTERHULL = "cluster-hull";
+ Parameterizer settings;
/**
- * The result we work on
- */
- Clustering<Model> clustering;
-
- /**
- * Alpha value
- */
- double alpha = Double.POSITIVE_INFINITY;
-
- /**
- * Constructor
+ * Constructor.
*
- * @param task VisualizationTask
- * @param alpha Alpha value
+ * @param settings Settings
*/
- public ClusterHullVisualization(VisualizationTask task, double alpha) {
- super(task);
- this.clustering = task.getResult();
- this.alpha = alpha;
- incrementalRedraw();
+ public ClusterHullVisualization(Parameterizer settings) {
+ super();
+ this.settings = settings;
}
@Override
- protected void redraw() {
- // Viewport size, for "relative size" computations
- final CanvasSize viewp = proj.estimateViewport();
- double projarea = viewp.getDiffX() * viewp.getDiffY();
-
- double opacity = 0.25;
-
- Iterator<Cluster<Model>> ci = clustering.getAllClusters().iterator();
-
- for(int cnum = 0; cnum < clustering.getAllClusters().size(); cnum++) {
- Cluster<?> clus = ci.next();
- final DBIDs ids = clus.getIDs();
-
- if(alpha >= Double.POSITIVE_INFINITY) {
- GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D();
-
- for(DBIDIter iter = ids.iter(); iter.valid(); iter.advance()) {
- double[] projP = proj.fastProjectDataToRenderSpace(rel.get(iter));
- hull.add(new Vector(projP));
- }
- Polygon chres = hull.getHull();
-
- // Plot the convex hull:
- if(chres != null && chres.size() > 1) {
- SVGPath path = new SVGPath(chres);
- // Approximate area (using bounding box)
- double hullarea = SpatialUtil.volume(chres);
- final double relativeArea = (projarea - hullarea) / projarea;
- final double relativeSize = (double) ids.size() / rel.size();
- opacity = Math.sqrt(relativeSize * relativeArea);
-
- Element hulls = path.makeElement(svgp);
- addCSSClasses(svgp, cnum, opacity);
- SVGUtil.addCSSClass(hulls, CLUSTERHULL + cnum);
- layer.appendChild(hulls);
- }
- }
- else {
- ArrayList<Vector> ps = new ArrayList<Vector>(ids.size());
- for(DBIDIter iter = ids.iter(); iter.valid(); iter.advance()) {
- double[] projP = proj.fastProjectDataToRenderSpace(rel.get(iter));
- ps.add(new Vector(projP));
- }
- List<Polygon> polys = (new AlphaShape(ps, alpha * Projection.SCALE)).compute();
- for(Polygon p : polys) {
- SVGPath path = new SVGPath(p);
- Element hulls = path.makeElement(svgp);
- addCSSClasses(svgp, cnum, 0.5);
- SVGUtil.addCSSClass(hulls, CLUSTERHULL + cnum);
- layer.appendChild(hulls);
- }
- }
- }
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
- /**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- private void addCSSClasses(SVGPlot svgp, int clusterID, double opac) {
- ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT);
-
- CSSClass cls = new CSSClass(this, CLUSTERHULL + clusterID);
- cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT));
-
- final String color;
- if(clustering.getAllClusters().size() == 1) {
- color = "black";
- }
- else {
- color = colors.getColor(clusterID);
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ // Find clusterings we can visualize:
+ Collection<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class);
+ for(Clustering<?> c : clusterings) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA - 1;
+ task.initDefaultVisibility(false);
+ baseResult.getHierarchy().add(c, task);
+ baseResult.getHierarchy().add(p, task);
+ }
}
- cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, color);
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, color);
- cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, opac);
-
- svgp.addCSSClassOrLogError(cls);
}
/**
- * Factory for visualizers to generate an SVG-Element containing the convex
- * hull or alpha shape of a cluster.
+ * Instance.
*
* @author Robert Rödler
* @author Erich Schubert
*
- * @apiviz.stereotype factory
- * @apiviz.uses ClusterHullVisualization oneway - - «create»
+ * @apiviz.has Clustering oneway - - visualizes
+ * @apiviz.uses GrahamScanConvexHull2D
+ * @apiviz.uses AlphaShape
*/
- public static class Factory extends AbstractVisFactory {
+ public class Instance extends AbstractScatterplotVisualization {
/**
- * Alpha value
+ * Generic tags to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
*/
- double alpha = Double.POSITIVE_INFINITY;
+ public static final String CLUSTERHULL = "cluster-hull";
/**
- * Constructor.
- *
- * @param alpha Alpha value
+ * The result we work on
*/
- public Factory(double alpha) {
- super();
- this.alpha = alpha;
- }
+ Clustering<Model> clustering;
- @Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new ClusterHullVisualization(task, alpha);
+ /**
+ * Constructor
+ *
+ * @param task VisualizationTask
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.clustering = task.getResult();
+ incrementalRedraw();
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- // Find clusterings we can visualize:
- Collection<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class);
- for(Clustering<?> c : clusterings) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : ps) {
- final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA - 1);
- task.put(VisualizationTask.META_VISIBLE_DEFAULT, false);
- baseResult.getHierarchy().add(c, task);
- baseResult.getHierarchy().add(p, task);
+ protected void redraw() {
+ // Viewport size, for "relative size" computations
+ final CanvasSize viewp = proj.estimateViewport();
+ double projarea = viewp.getDiffX() * viewp.getDiffY();
+
+ double opacity = 0.25;
+
+ Iterator<Cluster<Model>> ci = clustering.getAllClusters().iterator();
+
+ for(int cnum = 0; cnum < clustering.getAllClusters().size(); cnum++) {
+ Cluster<?> clus = ci.next();
+ final DBIDs ids = clus.getIDs();
+
+ if(settings.alpha >= Double.POSITIVE_INFINITY) {
+ GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D();
+
+ for(DBIDIter iter = ids.iter(); iter.valid(); iter.advance()) {
+ double[] projP = proj.fastProjectDataToRenderSpace(rel.get(iter));
+ hull.add(new Vector(projP));
+ }
+ Polygon chres = hull.getHull();
+
+ // Plot the convex hull:
+ if(chres != null && chres.size() > 1) {
+ SVGPath path = new SVGPath(chres);
+ // Approximate area (using bounding box)
+ double hullarea = SpatialUtil.volume(chres);
+ final double relativeArea = (projarea - hullarea) / projarea;
+ final double relativeSize = (double) ids.size() / rel.size();
+ opacity = Math.sqrt(relativeSize * relativeArea);
+
+ Element hulls = path.makeElement(svgp);
+ addCSSClasses(svgp, cnum, opacity);
+ SVGUtil.addCSSClass(hulls, CLUSTERHULL + cnum);
+ layer.appendChild(hulls);
+ }
+ }
+ else {
+ ArrayList<Vector> ps = new ArrayList<Vector>(ids.size());
+ for(DBIDIter iter = ids.iter(); iter.valid(); iter.advance()) {
+ double[] projP = proj.fastProjectDataToRenderSpace(rel.get(iter));
+ ps.add(new Vector(projP));
+ }
+ List<Polygon> polys = (new AlphaShape(ps, settings.alpha * Projection.SCALE)).compute();
+ for(Polygon p : polys) {
+ SVGPath path = new SVGPath(p);
+ Element hulls = path.makeElement(svgp);
+ addCSSClasses(svgp, cnum, 0.5);
+ SVGUtil.addCSSClass(hulls, CLUSTERHULL + cnum);
+ layer.appendChild(hulls);
+ }
}
}
}
/**
- * Parameterization class.
- *
- * @author Erich Schubert
+ * Adds the required CSS-Classes
*
- * @apiviz.exclude
+ * @param svgp SVG-Plot
*/
- public static class Parameterizer extends AbstractParameterizer {
- /**
- * Alpha-Value for alpha-shapes
- *
- * <p>
- * Key: {@code -hull.alpha}
- * </p>
- */
- public static final OptionID ALPHA_ID = OptionID.getOrCreateOptionID("hull.alpha", "Alpha value for hull drawing (in projected space!).");
-
- /**
- * Alpha value
- */
- double alpha = Double.POSITIVE_INFINITY;
-
- @Override
- protected void makeOptions(Parameterization config) {
- super.makeOptions(config);
- DoubleParameter alphaP = new DoubleParameter(ALPHA_ID, Double.POSITIVE_INFINITY);
- if(config.grab(alphaP)) {
- alpha = alphaP.getValue();
- }
+ private void addCSSClasses(SVGPlot svgp, int clusterID, double opac) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ ColorLibrary colors = style.getColorSet(StyleLibrary.PLOT);
+
+ CSSClass cls = new CSSClass(this, CLUSTERHULL + clusterID);
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT));
+
+ final String color;
+ if(clustering.getAllClusters().size() == 1) {
+ color = "black";
+ }
+ else {
+ color = colors.getColor(clusterID);
}
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, color);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, color);
+ cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, opac);
+
+ svgp.addCSSClassOrLogError(cls);
+ }
+ }
+
+ /**
+ * Parameterization class.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ public static class Parameterizer extends AbstractParameterizer {
+ /**
+ * Alpha-Value for alpha-shapes
+ *
+ * <p>
+ * Key: {@code -hull.alpha}
+ * </p>
+ */
+ public static final OptionID ALPHA_ID = new OptionID("hull.alpha", "Alpha value for hull drawing (in projected space!).");
+
+ /**
+ * Alpha value
+ */
+ double alpha = Double.POSITIVE_INFINITY;
- @Override
- protected Factory makeInstance() {
- return new Factory(alpha);
+ @Override
+ protected void makeOptions(Parameterization config) {
+ super.makeOptions(config);
+ DoubleParameter alphaP = new DoubleParameter(ALPHA_ID, Double.POSITIVE_INFINITY);
+ if(config.grab(alphaP)) {
+ alpha = alphaP.doubleValue();
}
}
+
+ @Override
+ protected ClusterHullVisualization makeInstance() {
+ return new ClusterHullVisualization(this);
+ }
}
}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterMeanVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterMeanVisualization.java index 4f14f4ef..c07e5dca 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterMeanVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterMeanVisualization.java @@ -35,7 +35,7 @@ import de.lmu.ifi.dbs.elki.data.NumberVector; import de.lmu.ifi.dbs.elki.data.model.MeanModel;
import de.lmu.ifi.dbs.elki.data.model.MedoidModel;
import de.lmu.ifi.dbs.elki.data.model.Model;
-import de.lmu.ifi.dbs.elki.database.ids.DBID;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDIter;
import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
import de.lmu.ifi.dbs.elki.result.Result;
import de.lmu.ifi.dbs.elki.result.ResultUtil;
@@ -61,238 +61,235 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatter *
* @author Heidi Kolb
*
- * @apiviz.has MeanModel oneway - - visualizes
- * @apiviz.has MedoidModel oneway - - visualizes
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class ClusterMeanVisualization extends AbstractScatterplotVisualization {
+public class ClusterMeanVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
private static final String NAME = "Cluster Means";
/**
- * CSS class name for center of the means
+ * Settings
*/
- private final static String CSS_MEAN_CENTER = "mean-center";
-
- /**
- * CSS class name for center of the means
- */
- private final static String CSS_MEAN = "mean-marker";
-
- /**
- * CSS class name for center of the means
- */
- private final static String CSS_MEAN_STAR = "mean-star";
-
- /**
- * Clustering to visualize.
- */
- Clustering<Model> clustering;
-
- /**
- * Draw stars
- */
- boolean stars;
+ protected Parameterizer settings;
/**
* Constructor.
*
- * @param task Visualization task
- * @param stars Draw stars
+ * @param settings Settings
*/
- public ClusterMeanVisualization(VisualizationTask task, boolean stars) {
- super(task);
- this.clustering = task.getResult();
- this.stars = stars;
- incrementalRedraw();
+ public ClusterMeanVisualization(Parameterizer settings) {
+ super();
+ this.settings = settings;
}
@Override
- protected void redraw() {
- addCSSClasses(svgp);
-
- MarkerLibrary ml = context.getStyleLibrary().markers();
- double marker_size = context.getStyleLibrary().getSize(StyleLibrary.MARKERPLOT);
-
- Iterator<Cluster<Model>> ci = clustering.getAllClusters().iterator();
- for(int cnum = 0; ci.hasNext(); cnum++) {
- Cluster<Model> clus = ci.next();
- Model model = clus.getModel();
- double[] mean;
- if(model instanceof MeanModel) {
- @SuppressWarnings("unchecked")
- MeanModel<? extends NumberVector<?, ?>> mmodel = (MeanModel<? extends NumberVector<?, ?>>) model;
- mean = proj.fastProjectDataToRenderSpace(mmodel.getMean());
- }
- else if(model instanceof MedoidModel) {
- MedoidModel mmodel = (MedoidModel) model;
- mean = proj.fastProjectDataToRenderSpace(rel.get(mmodel.getMedoid()));
- }
- else {
- continue;
- }
-
- // add a greater Marker for the mean
- Element meanMarker = ml.useMarker(svgp, layer, mean[0], mean[1], cnum, marker_size * 3);
- SVGUtil.setAtt(meanMarker, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_MEAN);
-
- // Add a fine cross to mark the exact location of the mean.
- Element meanMarkerCenter = svgp.svgLine(mean[0] - .7, mean[1], mean[0] + .7, mean[1]);
- SVGUtil.setAtt(meanMarkerCenter, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_MEAN_CENTER);
- Element meanMarkerCenter2 = svgp.svgLine(mean[0], mean[1] - .7, mean[0], mean[1] + .7);
- SVGUtil.setAtt(meanMarkerCenter2, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_MEAN_CENTER);
-
- layer.appendChild(meanMarkerCenter);
- layer.appendChild(meanMarkerCenter2);
-
- if(stars) {
- SVGPath star = new SVGPath();
- for(DBID id : clus.getIDs()) {
- double[] obj = proj.fastProjectDataToRenderSpace(rel.get(id));
- star.moveTo(mean);
- star.drawTo(obj);
- }
- Element stare = star.makeElement(svgp);
- SVGUtil.setCSSClass(stare, CSS_MEAN_STAR + "_" + cnum);
- layer.appendChild(stare);
- }
- }
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
- /**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- private void addCSSClasses(SVGPlot svgp) {
- if(!svgp.getCSSClassManager().contains(CSS_MEAN_CENTER)) {
- CSSClass center = new CSSClass(this, CSS_MEAN_CENTER);
- center.setStatement(SVGConstants.CSS_STROKE_PROPERTY, context.getStyleLibrary().getTextColor(StyleLibrary.DEFAULT));
- center.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.AXIS_TICK) / 2);
- svgp.addCSSClassOrLogError(center);
- }
- if(!svgp.getCSSClassManager().contains(CSS_MEAN)) {
- CSSClass center = new CSSClass(this, CSS_MEAN);
- center.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, "0.7");
- svgp.addCSSClassOrLogError(center);
- }
- if(stars) {
- ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT);
-
- Iterator<Cluster<Model>> ci = clustering.getAllClusters().iterator();
- for(int cnum = 0; ci.hasNext(); cnum++) {
- ci.next();
- if(!svgp.getCSSClassManager().contains(CSS_MEAN_STAR + "_" + cnum)) {
- CSSClass center = new CSSClass(this, CSS_MEAN_STAR + "_" + cnum);
- center.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(cnum));
- center.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT));
- center.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, "0.7");
- svgp.addCSSClassOrLogError(center);
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ // Find clusterings we can visualize:
+ Collection<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class);
+ for(Clustering<?> c : clusterings) {
+ if(c.getAllClusters().size() > 0) {
+ // Does the cluster have a model with cluster means?
+ if(testMeanModel(c)) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA + 1;
+ baseResult.getHierarchy().add(c, task);
+ baseResult.getHierarchy().add(p, task);
+ }
}
}
}
}
/**
- * Factory for visualizers to generate an SVG-Element containing a marker for
- * the mean in a KMeans-Clustering
+ * Instance.
*
* @author Heidi Kolb
*
- * @apiviz.stereotype factory
- * @apiviz.uses ClusterMeanVisualization oneway - - «create»
+ * @apiviz.has MeanModel oneway - - visualizes
+ * @apiviz.has MedoidModel oneway - - visualizes
*/
- public static class Factory extends AbstractVisFactory {
+ public class Instance extends AbstractScatterplotVisualization {
/**
- * Option ID for visualization of cluster means.
- *
- * <pre>
- * -cluster.stars
- * </pre>
+ * CSS class name for center of the means
+ */
+ private static final String CSS_MEAN_CENTER = "mean-center";
+
+ /**
+ * CSS class name for center of the means
+ */
+ private static final String CSS_MEAN = "mean-marker";
+
+ /**
+ * CSS class name for center of the means
*/
- public static final OptionID STARS_ID = OptionID.getOrCreateOptionID("cluster.stars", "Visualize mean-based clusters using stars.");
+ private static final String CSS_MEAN_STAR = "mean-star";
/**
- * Draw stars
+ * Clustering to visualize.
*/
- private boolean stars;
+ Clustering<Model> clustering;
/**
* Constructor.
*
- * @param stars Draw stars
+ * @param task Visualization task
*/
- public Factory(boolean stars) {
- super();
- this.stars = stars;
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.clustering = task.getResult();
+ incrementalRedraw();
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new ClusterMeanVisualization(task, stars);
- }
+ protected void redraw() {
+ addCSSClasses(svgp);
- @Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- // Find clusterings we can visualize:
- Collection<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class);
- for(Clustering<?> c : clusterings) {
- if(c.getAllClusters().size() > 0) {
- // Does the cluster have a model with cluster means?
- if(testMeanModel(c)) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : ps) {
- final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA + 1);
- baseResult.getHierarchy().add(c, task);
- baseResult.getHierarchy().add(p, task);
- }
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ MarkerLibrary ml = style.markers();
+ double marker_size = style.getSize(StyleLibrary.MARKERPLOT);
+
+ Iterator<Cluster<Model>> ci = clustering.getAllClusters().iterator();
+ for(int cnum = 0; ci.hasNext(); cnum++) {
+ Cluster<Model> clus = ci.next();
+ Model model = clus.getModel();
+ double[] mean;
+ if(model instanceof MeanModel) {
+ @SuppressWarnings("unchecked")
+ MeanModel<? extends NumberVector<?>> mmodel = (MeanModel<? extends NumberVector<?>>) model;
+ mean = proj.fastProjectDataToRenderSpace(mmodel.getMean());
+ }
+ else if(model instanceof MedoidModel) {
+ MedoidModel mmodel = (MedoidModel) model;
+ mean = proj.fastProjectDataToRenderSpace(rel.get(mmodel.getMedoid()));
+ }
+ else {
+ continue;
+ }
+
+ // add a greater Marker for the mean
+ Element meanMarker = ml.useMarker(svgp, layer, mean[0], mean[1], cnum, marker_size * 3);
+ SVGUtil.setAtt(meanMarker, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_MEAN);
+
+ // Add a fine cross to mark the exact location of the mean.
+ Element meanMarkerCenter = svgp.svgLine(mean[0] - .7, mean[1], mean[0] + .7, mean[1]);
+ SVGUtil.setAtt(meanMarkerCenter, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_MEAN_CENTER);
+ Element meanMarkerCenter2 = svgp.svgLine(mean[0], mean[1] - .7, mean[0], mean[1] + .7);
+ SVGUtil.setAtt(meanMarkerCenter2, SVGConstants.SVG_CLASS_ATTRIBUTE, CSS_MEAN_CENTER);
+
+ layer.appendChild(meanMarkerCenter);
+ layer.appendChild(meanMarkerCenter2);
+
+ if(settings.stars) {
+ SVGPath star = new SVGPath();
+ for(DBIDIter id = clus.getIDs().iter(); id.valid(); id.advance()) {
+ double[] obj = proj.fastProjectDataToRenderSpace(rel.get(id));
+ star.moveTo(mean);
+ star.drawTo(obj);
}
+ Element stare = star.makeElement(svgp);
+ SVGUtil.setCSSClass(stare, CSS_MEAN_STAR + "_" + cnum);
+ layer.appendChild(stare);
}
}
}
/**
- * Test if the given clustering has a mean model.
+ * Adds the required CSS-Classes
*
- * @param c Clustering to inspect
- * @return true when the clustering has a mean or medoid model.
+ * @param svgp SVG-Plot
*/
- private static boolean testMeanModel(Clustering<?> c) {
- Model firstmodel = c.getAllClusters().get(0).getModel();
- if(firstmodel instanceof MeanModel<?>) {
- return true;
+ private void addCSSClasses(SVGPlot svgp) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ if(!svgp.getCSSClassManager().contains(CSS_MEAN_CENTER)) {
+ CSSClass center = new CSSClass(this, CSS_MEAN_CENTER);
+ center.setStatement(SVGConstants.CSS_STROKE_PROPERTY, style.getTextColor(StyleLibrary.DEFAULT));
+ center.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.AXIS_TICK) * .5);
+ svgp.addCSSClassOrLogError(center);
}
- if(firstmodel instanceof MedoidModel) {
- return true;
+ if(!svgp.getCSSClassManager().contains(CSS_MEAN)) {
+ CSSClass center = new CSSClass(this, CSS_MEAN);
+ center.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, "0.7");
+ svgp.addCSSClassOrLogError(center);
+ }
+ if(settings.stars) {
+ ColorLibrary colors = style.getColorSet(StyleLibrary.PLOT);
+
+ Iterator<Cluster<Model>> ci = clustering.getAllClusters().iterator();
+ for(int cnum = 0; ci.hasNext(); cnum++) {
+ ci.next();
+ if(!svgp.getCSSClassManager().contains(CSS_MEAN_STAR + "_" + cnum)) {
+ CSSClass center = new CSSClass(this, CSS_MEAN_STAR + "_" + cnum);
+ center.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(cnum));
+ center.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT));
+ center.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, "0.7");
+ svgp.addCSSClassOrLogError(center);
+ }
+ }
}
- return false;
}
+ }
+ /**
+ * Test if the given clustering has a mean model.
+ *
+ * @param c Clustering to inspect
+ * @return true when the clustering has a mean or medoid model.
+ */
+ private static boolean testMeanModel(Clustering<?> c) {
+ Model firstmodel = c.getAllClusters().get(0).getModel();
+ if(firstmodel instanceof MeanModel<?>) {
+ return true;
+ }
+ if(firstmodel instanceof MedoidModel) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Parameterization class.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ public static class Parameterizer extends AbstractParameterizer {
/**
- * Parameterization class.
- *
- * @author Erich Schubert
+ * Option ID for visualization of cluster means.
*
- * @apiviz.exclude
+ * <pre>
+ * -cluster.stars
+ * </pre>
*/
- public static class Parameterizer extends AbstractParameterizer {
- protected boolean stars = false;
-
- @Override
- protected void makeOptions(Parameterization config) {
- super.makeOptions(config);
- Flag starsF = new Flag(STARS_ID);
- if(config.grab(starsF)) {
- stars = starsF.getValue();
- }
- }
+ public static final OptionID STARS_ID = new OptionID("cluster.stars", "Visualize mean-based clusters using stars.");
+
+ /**
+ * Whether to draw cluster stars
+ */
+ protected boolean stars = false;
- @Override
- protected Factory makeInstance() {
- return new Factory(stars);
+ @Override
+ protected void makeOptions(Parameterization config) {
+ super.makeOptions(config);
+ Flag starsF = new Flag(STARS_ID);
+ if(config.grab(starsF)) {
+ stars = starsF.isTrue();
}
}
+
+ @Override
+ protected ClusterMeanVisualization makeInstance() {
+ return new ClusterMeanVisualization(this);
+ }
}
}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterOrderVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterOrderVisualization.java index 0d43875c..bd173e80 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterOrderVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/ClusterOrderVisualization.java @@ -45,103 +45,105 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatterplotVisualization; /** - * Cluster order visualizer. + * Cluster order visualizer: connect objects via the spanning tree the cluster + * order represents. * * @author Erich Schubert * - * @apiviz.has ClusterOrderResult oneway - - visualizes + * @apiviz.stereotype factory + * @apiviz.uses Instance oneway - - «create» */ -// TODO: listen for CLUSTER ORDER changes. -public class ClusterOrderVisualization extends AbstractScatterplotVisualization implements DataStoreListener { +public class ClusterOrderVisualization extends AbstractVisFactory { /** * A short name characterizing this Visualizer. */ private static final String NAME = "Predecessor Graph"; /** - * CSS class name + * Constructor, adhering to + * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} */ - private static final String CSSNAME = "predecessor"; - - /** - * The result we visualize - */ - protected ClusterOrderResult<?> result; - - public ClusterOrderVisualization(VisualizationTask task) { - super(task); - result = task.getResult(); - context.addDataStoreListener(this); - incrementalRedraw(); + public ClusterOrderVisualization() { + super(); } @Override - public void destroy() { - super.destroy(); - context.removeDataStoreListener(this); + public Visualization makeVisualization(VisualizationTask task) { + return new Instance(task); } @Override - public void redraw() { - CSSClass cls = new CSSClass(this, CSSNAME); - context.getStyleLibrary().lines().formatCSSClass(cls, 0, context.getStyleLibrary().getLineWidth(StyleLibrary.CLUSTERORDER)); - - svgp.addCSSClassOrLogError(cls); - - for(ClusterOrderEntry<?> ce : result) { - DBID thisId = ce.getID(); - DBID prevId = ce.getPredecessorID(); - if(thisId == null || prevId == null) { - continue; + public void processNewResult(HierarchicalResult baseResult, Result result) { + Collection<ClusterOrderResult<DoubleDistance>> cos = ResultUtil.filterResults(result, ClusterOrderResult.class); + for(ClusterOrderResult<DoubleDistance> co : cos) { + Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { + final VisualizationTask task = new VisualizationTask(NAME, co, p.getRelation(), this); + task.initDefaultVisibility(false); + task.level = VisualizationTask.LEVEL_DATA - 1; + baseResult.getHierarchy().add(co, task); + baseResult.getHierarchy().add(p, task); } - double[] thisVec = proj.fastProjectDataToRenderSpace(rel.get(thisId)); - double[] prevVec = proj.fastProjectDataToRenderSpace(rel.get(prevId)); - - // FIXME: DO NOT COMMIT - thisVec[0] = thisVec[0] * 0.95 + prevVec[0] * 0.05; - thisVec[1] = thisVec[1] * 0.95 + prevVec[1] * 0.05; - - Element arrow = svgp.svgLine(prevVec[0], prevVec[1], thisVec[0], thisVec[1]); - SVGUtil.setCSSClass(arrow, cls.getName()); - - layer.appendChild(arrow); } } /** - * Visualize an OPTICS cluster order by drawing connection lines. + * Instance * * @author Erich Schubert * - * @apiviz.stereotype factory - * @apiviz.uses ClusterOrderVisualization oneway - - «create» + * @apiviz.has ClusterOrderResult oneway - - visualizes */ - public static class Factory extends AbstractVisFactory { + // TODO: listen for CLUSTER ORDER changes. + public class Instance extends AbstractScatterplotVisualization implements DataStoreListener { /** - * Constructor, adhering to - * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} + * CSS class name */ - public Factory() { - super(); + private static final String CSSNAME = "predecessor"; + + /** + * The result we visualize + */ + protected ClusterOrderResult<?> result; + + public Instance(VisualizationTask task) { + super(task); + result = task.getResult(); + context.addDataStoreListener(this); + incrementalRedraw(); } @Override - public Visualization makeVisualization(VisualizationTask task) { - return new ClusterOrderVisualization(task); + public void destroy() { + super.destroy(); + context.removeDataStoreListener(this); } @Override - public void processNewResult(HierarchicalResult baseResult, Result result) { - Collection<ClusterOrderResult<DoubleDistance>> cos = ResultUtil.filterResults(result, ClusterOrderResult.class); - for(ClusterOrderResult<DoubleDistance> co : cos) { - Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : ps) { - final VisualizationTask task = new VisualizationTask(NAME, co, p.getRelation(), this); - task.put(VisualizationTask.META_VISIBLE_DEFAULT, false); - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA - 1); - baseResult.getHierarchy().add(co, task); - baseResult.getHierarchy().add(p, task); + public void redraw() { + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + CSSClass cls = new CSSClass(this, CSSNAME); + style.lines().formatCSSClass(cls, 0, style.getLineWidth(StyleLibrary.CLUSTERORDER)); + + svgp.addCSSClassOrLogError(cls); + + for(ClusterOrderEntry<?> ce : result) { + DBID thisId = ce.getID(); + DBID prevId = ce.getPredecessorID(); + if(thisId == null || prevId == null) { + continue; } + double[] thisVec = proj.fastProjectDataToRenderSpace(rel.get(thisId)); + double[] prevVec = proj.fastProjectDataToRenderSpace(rel.get(prevId)); + + // FIXME: DO NOT COMMIT + thisVec[0] = thisVec[0] * 0.95 + prevVec[0] * 0.05; + thisVec[1] = thisVec[1] * 0.95 + prevVec[1] * 0.05; + + Element arrow = svgp.svgLine(prevVec[0], prevVec[1], thisVec[0], thisVec[1]); + SVGUtil.setCSSClass(arrow, cls.getName()); + + layer.appendChild(arrow); } } } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/EMClusterVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/EMClusterVisualization.java index 6070361e..2806812a 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/EMClusterVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/EMClusterVisualization.java @@ -32,6 +32,7 @@ import org.w3c.dom.Element; import de.lmu.ifi.dbs.elki.data.Cluster; import de.lmu.ifi.dbs.elki.data.Clustering; +import de.lmu.ifi.dbs.elki.data.DoubleVector; import de.lmu.ifi.dbs.elki.data.NumberVector; import de.lmu.ifi.dbs.elki.data.model.EMModel; import de.lmu.ifi.dbs.elki.data.model.MeanModel; @@ -69,114 +70,199 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatter * * @author Robert Rödler * - * @apiviz.has EMModel oneway - - visualizes - * @apiviz.uses GrahamScanConvexHull2D - * - * @param <NV> Type of the NumberVector being visualized. + * @apiviz.stereotype factory + * @apiviz.uses EMClusterVisualization oneway - - «create» */ -// TODO: nicer stacking of n-fold hulls -// TODO: can we find a proper sphere for 3+ dimensions? -public class EMClusterVisualization<NV extends NumberVector<NV, ?>> extends AbstractScatterplotVisualization { +public class EMClusterVisualization extends AbstractVisFactory { /** * A short name characterizing this Visualizer. */ private static final String NAME = "EM Cluster Visualization"; /** - * Generic tags to indicate the type of element. Used in IDs, CSS-Classes etc. + * Constants for quantiles of standard deviation */ - public static final String EMBORDER = "EMClusterBorder"; + final static double[] sigma = new double[] { 0.41, 0.223, 0.047 }; /** - * The result we work on + * Constructor */ - Clustering<EMModel<NV>> clustering; + public EMClusterVisualization() { + super(); + } - private static final double KAPPA = SVGHyperSphere.EUCLIDEAN_KAPPA; + @Override + public Instance<DoubleVector> makeVisualization(VisualizationTask task) { + return new Instance<DoubleVector>(task); + } + + @Override + public void processNewResult(HierarchicalResult baseResult, Result result) { + // Find clusterings we can visualize: + Collection<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class); + for(Clustering<?> c : clusterings) { + if(c.getAllClusters().size() > 0) { + // Does the cluster have a model with cluster means? + Clustering<MeanModel<DoubleVector>> mcls = findMeanModel(c); + if(mcls != null) { + Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { + final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this); + task.level = VisualizationTask.LEVEL_DATA + 3; + baseResult.getHierarchy().add(c, task); + baseResult.getHierarchy().add(p, task); + } + } + } + } + } /** - * StyleParameter: + * Test if the given clustering has a mean model. + * + * @param <NV> Vector type + * @param c Clustering to inspect + * @return the clustering cast to return a mean model, null otherwise. */ - private int times = 3; + @SuppressWarnings("unchecked") + private static <NV extends NumberVector<?>> Clustering<MeanModel<NV>> findMeanModel(Clustering<?> c) { + final Model firstModel = c.getAllClusters().get(0).getModel(); + if(c.getAllClusters().get(0).getModel() instanceof MeanModel<?> && firstModel instanceof EMModel<?>) { + return (Clustering<MeanModel<NV>>) c; + } + return null; + } - private int opacStyle = 1; + /** + * Instance. + * + * @author Robert Rödler + * + * @apiviz.has EMModel oneway - - visualizes + * @apiviz.uses GrahamScanConvexHull2D + * + * @param <NV> Type of the NumberVector being visualized. + */ + // TODO: nicer stacking of n-fold hulls + // TODO: can we find a proper sphere for 3+ dimensions? + public class Instance<NV extends NumberVector<?>> extends AbstractScatterplotVisualization { + /** + * Generic tags to indicate the type of element. Used in IDs, CSS-Classes + * etc. + */ + public static final String EMBORDER = "EMClusterBorder"; - private int softBorder = 1; + /** + * The result we work on + */ + Clustering<EMModel<NV>> clustering; - private int drawStyle = 0; + private static final double KAPPA = SVGHyperSphere.EUCLIDEAN_KAPPA; - final static double[] sigma = new double[] { 0.41, 0.223, 0.047 }; + /** + * StyleParameter: + */ + private int times = 3; - /** - * Constructor - * - * @param task VisualizationTask - */ - public EMClusterVisualization(VisualizationTask task) { - super(task); - this.clustering = task.getResult(); - incrementalRedraw(); - } + private int opacStyle = 1; - @Override - protected void redraw() { - // set styles - addCSSClasses(svgp); - - // PCARunner - PCARunner<NV> pcarun = ClassGenericsUtil.parameterizeOrAbort(PCARunner.class, new EmptyParameterization()); - - Iterator<Cluster<EMModel<NV>>> ci = clustering.getAllClusters().iterator(); - for(int cnum = 0; cnum < clustering.getAllClusters().size(); cnum++) { - Cluster<EMModel<NV>> clus = ci.next(); - DBIDs ids = clus.getIDs(); - - if(ids.size() > 0) { - Matrix covmat = clus.getModel().getCovarianceMatrix(); - NV centroid = clus.getModel().getMean(); - Vector cent = new Vector(proj.fastProjectDataToRenderSpace(centroid)); - - // Compute the eigenvectors - SortedEigenPairs eps = pcarun.processCovarMatrix(covmat).getEigenPairs(); - - Vector[] pc = new Vector[eps.size()]; - for(int i = 0; i < eps.size(); i++) { - EigenPair ep = eps.getEigenPair(i); - Vector sev = ep.getEigenvector().times(Math.sqrt(ep.getEigenvalue())); - pc[i] = new Vector(proj.fastProjectRelativeDataToRenderSpace(sev.getArrayRef())); - } - if(drawStyle != 0 || eps.size() == 2) { - drawSphere2D(cnum, cent, pc); + private int softBorder = 1; + + private int drawStyle = 0; + + /** + * Constructor + * + * @param task VisualizationTask + */ + public Instance(VisualizationTask task) { + super(task); + this.clustering = task.getResult(); + incrementalRedraw(); + } + + @Override + protected void redraw() { + // set styles + addCSSClasses(svgp); + + // PCARunner + PCARunner<NV> pcarun = ClassGenericsUtil.parameterizeOrAbort(PCARunner.class, new EmptyParameterization()); + + Iterator<Cluster<EMModel<NV>>> ci = clustering.getAllClusters().iterator(); + for(int cnum = 0; cnum < clustering.getAllClusters().size(); cnum++) { + Cluster<EMModel<NV>> clus = ci.next(); + DBIDs ids = clus.getIDs(); + + if(ids.size() > 0) { + Matrix covmat = clus.getModel().getCovarianceMatrix(); + NV centroid = clus.getModel().getMean(); + Vector cent = new Vector(proj.fastProjectDataToRenderSpace(centroid)); + + // Compute the eigenvectors + SortedEigenPairs eps = pcarun.processCovarMatrix(covmat).getEigenPairs(); + + Vector[] pc = new Vector[eps.size()]; + for(int i = 0; i < eps.size(); i++) { + EigenPair ep = eps.getEigenPair(i); + Vector sev = ep.getEigenvector().times(Math.sqrt(ep.getEigenvalue())); + pc[i] = new Vector(proj.fastProjectRelativeDataToRenderSpace(sev.getArrayRef())); + } + if(drawStyle != 0 || eps.size() == 2) { + drawSphere2D(cnum, cent, pc); + } + else { + Polygon chres = makeHullComplex(pc); + drawHullLines(cnum, cent, chres); + } } - else { - Polygon chres = makeHullComplex(pc); - drawHullLines(cnum, cent, chres); + } + } + + protected void drawSphere2D(int cnum, Vector cent, Vector[] pc) { + for(int dim1 = 0; dim1 < pc.length - 1; dim1++) { + for(int dim2 = dim1 + 1; dim2 < pc.length; dim2++) { + for(int i = 1; i <= times; i++) { + SVGPath path = new SVGPath(); + + Vector direction1 = pc[dim1].times(KAPPA * i); + Vector direction2 = pc[dim2].times(KAPPA * i); + + Vector p1 = cent.plusTimes(pc[dim1], i); + Vector p2 = cent.plusTimes(pc[dim2], i); + Vector p3 = cent.minusTimes(pc[dim1], i); + Vector p4 = cent.minusTimes(pc[dim2], i); + + path.moveTo(p1); + path.cubicTo(p1.plus(direction2), p2.plus(direction1), p2); + path.cubicTo(p2.minus(direction1), p3.plus(direction2), p3); + path.cubicTo(p3.minus(direction2), p4.minus(direction1), p4); + path.cubicTo(p4.plus(direction1), p1.minus(direction2), p1); + path.close(); + + Element ellipse = path.makeElement(svgp); + SVGUtil.addCSSClass(ellipse, EMBORDER + cnum); + if(opacStyle == 1) { + CSSClass cls = new CSSClass(null, "temp"); + double s = (i >= 1 && i <= sigma.length) ? sigma[i - 1] : 0.0; + cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, s); + SVGUtil.setAtt(ellipse, SVGConstants.SVG_STYLE_ATTRIBUTE, cls.inlineCSS()); + } + layer.appendChild(ellipse); + } } } } - } - protected void drawSphere2D(int cnum, Vector cent, Vector[] pc) { - for(int dim1 = 0; dim1 < pc.length - 1; dim1++) { - for(int dim2 = dim1 + 1; dim2 < pc.length; dim2++) { + protected void drawHullLines(int cnum, Vector cent, Polygon chres) { + if(chres.size() > 1) { for(int i = 1; i <= times; i++) { SVGPath path = new SVGPath(); - - Vector direction1 = pc[dim1].times(KAPPA * i); - Vector direction2 = pc[dim2].times(KAPPA * i); - - Vector p1 = cent.plusTimes(pc[dim1], i); - Vector p2 = cent.plusTimes(pc[dim2], i); - Vector p3 = cent.minusTimes(pc[dim1], i); - Vector p4 = cent.minusTimes(pc[dim2], i); - - path.moveTo(p1); - path.cubicTo(p1.plus(direction2), p2.plus(direction1), p2); - path.cubicTo(p2.minus(direction1), p3.plus(direction2), p3); - path.cubicTo(p3.minus(direction2), p4.minus(direction1), p4); - path.cubicTo(p4.plus(direction1), p1.minus(direction2), p1); + for(int p = 0; p < chres.size(); p++) { + Vector cur = cent.plusTimes(chres.get(p), i); + path.drawTo(cur); + } path.close(); - Element ellipse = path.makeElement(svgp); SVGUtil.addCSSClass(ellipse, EMBORDER + cnum); if(opacStyle == 1) { @@ -189,18 +275,99 @@ public class EMClusterVisualization<NV extends NumberVector<NV, ?>> extends Abst } } } - } - protected void drawHullLines(int cnum, Vector cent, Polygon chres) { - if(chres.size() > 1) { + protected Polygon makeHull(Vector[] pc) { + GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D(); + + Vector diag = new Vector(0, 0); + for(int j = 0; j < pc.length; j++) { + hull.add(pc[j]); + hull.add(pc[j].times(-1)); + for(int k = j + 1; k < pc.length; k++) { + Vector q = pc[k]; + Vector ppq = pc[j].plus(q).timesEquals(MathUtil.SQRTHALF); + Vector pmq = pc[j].minus(q).timesEquals(MathUtil.SQRTHALF); + hull.add(ppq); + hull.add(ppq.times(-1)); + hull.add(pmq); + hull.add(pmq.times(-1)); + } + diag.plusEquals(pc[j]); + } + diag.timesEquals(1.0 / Math.sqrt(pc.length)); + hull.add(diag); + hull.add(diag.times(-1)); + + Polygon chres = hull.getHull(); + return chres; + } + + protected Polygon makeHullComplex(Vector[] pc) { + GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D(); + + Vector diag = new Vector(0, 0); + for(int j = 0; j < pc.length; j++) { + hull.add(pc[j]); + hull.add(pc[j].times(-1)); + for(int k = j + 1; k < pc.length; k++) { + Vector q = pc[k]; + Vector ppq = pc[j].plus(q).timesEquals(MathUtil.SQRTHALF); + Vector pmq = pc[j].minus(q).timesEquals(MathUtil.SQRTHALF); + hull.add(ppq); + hull.add(ppq.times(-1)); + hull.add(pmq); + hull.add(pmq.times(-1)); + for(int l = k + 1; l < pc.length; l++) { + Vector r = pc[k]; + Vector ppqpr = ppq.plus(r).timesEquals(Math.sqrt(1 / 3.)); + Vector pmqpr = pmq.plus(r).timesEquals(Math.sqrt(1 / 3.)); + Vector ppqmr = ppq.minus(r).timesEquals(Math.sqrt(1 / 3.)); + Vector pmqmr = pmq.minus(r).timesEquals(Math.sqrt(1 / 3.)); + hull.add(ppqpr); + hull.add(ppqpr.times(-1)); + hull.add(pmqpr); + hull.add(pmqpr.times(-1)); + hull.add(ppqmr); + hull.add(ppqmr.times(-1)); + hull.add(pmqmr); + hull.add(pmqmr.times(-1)); + } + } + diag.plusEquals(pc[j]); + } + diag.timesEquals(1.0 / Math.sqrt(pc.length)); + hull.add(diag); + hull.add(diag.times(-1)); + Polygon chres = hull.getHull(); + return chres; + } + + protected void drawHullArc(int cnum, Vector cent, Polygon chres) { for(int i = 1; i <= times; i++) { SVGPath path = new SVGPath(); + + ArrayList<Vector> delta = new ArrayList<Vector>(chres.size()); + for(int p = 0; p < chres.size(); p++) { + Vector prev = chres.get((p - 1 + chres.size()) % chres.size()); + Vector curr = chres.get(p); + Vector next = chres.get((p + 1) % chres.size()); + Vector d1 = next.minus(curr).normalize(); + Vector d2 = curr.minus(prev).normalize(); + delta.add(d1.plus(d2)); + // delta.add(next.minus(prev)); + } + for(int p = 0; p < chres.size(); p++) { - Vector cur = cent.plusTimes(chres.get(p), i); - path.drawTo(cur); + Vector cur = cent.plus(chres.get(p)); + Vector nex = cent.plus(chres.get((p + 1) % chres.size())); + Vector dcur = delta.get(p); + Vector dnex = delta.get((p + 1) % chres.size()); + drawArc(path, cent, cur, nex, dcur, dnex, i); } path.close(); + Element ellipse = path.makeElement(svgp); + SVGUtil.addCSSClass(ellipse, EMBORDER + cnum); if(opacStyle == 1) { CSSClass cls = new CSSClass(null, "temp"); @@ -211,262 +378,98 @@ public class EMClusterVisualization<NV extends NumberVector<NV, ?>> extends Abst layer.appendChild(ellipse); } } - } - - protected Polygon makeHull(Vector[] pc) { - GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D(); - - Vector diag = new Vector(0, 0); - for(int j = 0; j < pc.length; j++) { - hull.add(pc[j]); - hull.add(pc[j].times(-1)); - for(int k = j + 1; k < pc.length; k++) { - Vector q = pc[k]; - Vector ppq = pc[j].plus(q).timesEquals(MathUtil.SQRTHALF); - Vector pmq = pc[j].minus(q).timesEquals(MathUtil.SQRTHALF); - hull.add(ppq); - hull.add(ppq.times(-1)); - hull.add(pmq); - hull.add(pmq.times(-1)); - } - diag.plusEquals(pc[j]); - } - diag.timesEquals(1.0 / Math.sqrt(pc.length)); - hull.add(diag); - hull.add(diag.times(-1)); - - Polygon chres = hull.getHull(); - return chres; - } - - protected Polygon makeHullComplex(Vector[] pc) { - GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D(); - - Vector diag = new Vector(0, 0); - for(int j = 0; j < pc.length; j++) { - hull.add(pc[j]); - hull.add(pc[j].times(-1)); - for(int k = j + 1; k < pc.length; k++) { - Vector q = pc[k]; - Vector ppq = pc[j].plus(q).timesEquals(MathUtil.SQRTHALF); - Vector pmq = pc[j].minus(q).timesEquals(MathUtil.SQRTHALF); - hull.add(ppq); - hull.add(ppq.times(-1)); - hull.add(pmq); - hull.add(pmq.times(-1)); - for(int l = k + 1; l < pc.length; l++) { - Vector r = pc[k]; - Vector ppqpr = ppq.plus(r).timesEquals(Math.sqrt(1 / 3.)); - Vector pmqpr = pmq.plus(r).timesEquals(Math.sqrt(1 / 3.)); - Vector ppqmr = ppq.minus(r).timesEquals(Math.sqrt(1 / 3.)); - Vector pmqmr = pmq.minus(r).timesEquals(Math.sqrt(1 / 3.)); - hull.add(ppqpr); - hull.add(ppqpr.times(-1)); - hull.add(pmqpr); - hull.add(pmqpr.times(-1)); - hull.add(ppqmr); - hull.add(ppqmr.times(-1)); - hull.add(pmqmr); - hull.add(pmqmr.times(-1)); - } - } - diag.plusEquals(pc[j]); - } - diag.timesEquals(1.0 / Math.sqrt(pc.length)); - hull.add(diag); - hull.add(diag.times(-1)); - Polygon chres = hull.getHull(); - return chres; - } - protected void drawHullArc(int cnum, Vector cent, Polygon chres) { - for(int i = 1; i <= times; i++) { - SVGPath path = new SVGPath(); - - ArrayList<Vector> delta = new ArrayList<Vector>(chres.size()); - for(int p = 0; p < chres.size(); p++) { - Vector prev = chres.get((p - 1 + chres.size()) % chres.size()); - Vector curr = chres.get(p); - Vector next = chres.get((p + 1) % chres.size()); - Vector d1 = next.minus(curr).normalize(); - Vector d2 = curr.minus(prev).normalize(); - delta.add(d1.plus(d2)); - // delta.add(next.minus(prev)); - } - - for(int p = 0; p < chres.size(); p++) { - Vector cur = cent.plus(chres.get(p)); - Vector nex = cent.plus(chres.get((p + 1) % chres.size())); - Vector dcur = delta.get(p); - Vector dnex = delta.get((p + 1) % chres.size()); - drawArc(path, cent, cur, nex, dcur, dnex, i); + /** + * Draw an arc to simulate the hyper ellipse. + * + * @param path Path to draw to + * @param cent Center + * @param pre Previous point + * @param nex Next point + * @param scale Scaling factor + */ + private void drawArc(SVGPath path, Vector cent, Vector pre, Vector nex, Vector oPrev, Vector oNext, double scale) { + // Delta vectors + final Vector rPrev = pre.minus(cent); + final Vector rNext = nex.minus(cent); + final Vector rPrNe = pre.minus(nex); + // Scaled fix points + final Vector sPrev = cent.plusTimes(rPrev, scale); + final Vector sNext = cent.plusTimes(rNext, scale); + // Orthogonal vectors to the relative vectors + // final Vector oPrev = new Vector(rPrev.get(1), -rPrev.get(0)); + // final Vector oNext = new Vector(-rNext.get(1), rNext.get(0)); + + // Compute the intersection of rPrev+tp*oPrev and rNext+tn*oNext + // rPrNe == rPrev - rNext + final double zp = rPrNe.get(0) * oNext.get(1) - rPrNe.get(1) * oNext.get(0); + final double zn = rPrNe.get(0) * oPrev.get(1) - rPrNe.get(1) * oPrev.get(0); + final double n = oPrev.get(1) * oNext.get(0) - oPrev.get(0) * oNext.get(1); + if(n == 0) { + LoggingUtil.warning("Parallel?!?"); + path.drawTo(sNext.get(0), sNext.get(1)); + return; } - path.close(); + final double tp = Math.abs(zp / n); + final double tn = Math.abs(zn / n); + // LoggingUtil.warning("tp: "+tp+" tn: "+tn); - Element ellipse = path.makeElement(svgp); + // Guide points + final Vector gPrev = sPrev.plusTimes(oPrev, KAPPA * scale * tp); + final Vector gNext = sNext.minusTimes(oNext, KAPPA * scale * tn); - SVGUtil.addCSSClass(ellipse, EMBORDER + cnum); - if(opacStyle == 1) { - CSSClass cls = new CSSClass(null, "temp"); - double s = (i >= 1 && i <= sigma.length) ? sigma[i - 1] : 0.0; - cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, s); - SVGUtil.setAtt(ellipse, SVGConstants.SVG_STYLE_ATTRIBUTE, cls.inlineCSS()); + if(!path.isStarted()) { + path.moveTo(sPrev); } - layer.appendChild(ellipse); + // path.drawTo(sPrev); + // path.drawTo(gPrev); + // path.drawTo(gNext); + // path.drawTo(sNext)); + // path.moveTo(sPrev); + // if(tp < 0 || tn < 0) { + // path.drawTo(sNext); + // } + // else { + path.cubicTo(gPrev, gNext, sNext); + // } } - } - /** - * Draw an arc to simulate the hyper ellipse. - * - * @param path Path to draw to - * @param cent Center - * @param pre Previous point - * @param nex Next point - * @param scale Scaling factor - */ - private void drawArc(SVGPath path, Vector cent, Vector pre, Vector nex, Vector oPrev, Vector oNext, double scale) { - // Delta vectors - final Vector rPrev = pre.minus(cent); - final Vector rNext = nex.minus(cent); - final Vector rPrNe = pre.minus(nex); - // Scaled fix points - final Vector sPrev = cent.plusTimes(rPrev, scale); - final Vector sNext = cent.plusTimes(rNext, scale); - // Orthogonal vectors to the relative vectors - // final Vector oPrev = new Vector(rPrev.get(1), -rPrev.get(0)); - // final Vector oNext = new Vector(-rNext.get(1), rNext.get(0)); - - // Compute the intersection of rPrev+tp*oPrev and rNext+tn*oNext - // rPrNe == rPrev - rNext - final double zp = rPrNe.get(0) * oNext.get(1) - rPrNe.get(1) * oNext.get(0); - final double zn = rPrNe.get(0) * oPrev.get(1) - rPrNe.get(1) * oPrev.get(0); - final double n = oPrev.get(1) * oNext.get(0) - oPrev.get(0) * oNext.get(1); - if(n == 0) { - LoggingUtil.warning("Parallel?!?"); - path.drawTo(sNext.get(0), sNext.get(1)); - return; - } - final double tp = Math.abs(zp / n); - final double tn = Math.abs(zn / n); - // LoggingUtil.warning("tp: "+tp+" tn: "+tn); - - // Guide points - final Vector gPrev = sPrev.plusTimes(oPrev, KAPPA * scale * tp); - final Vector gNext = sNext.minusTimes(oNext, KAPPA * scale * tn); - - if(!path.isStarted()) { - path.moveTo(sPrev); - } - // path.drawTo(sPrev); - // path.drawTo(gPrev); - // path.drawTo(gNext); - // path.drawTo(sNext)); - // path.moveTo(sPrev); - // if(tp < 0 || tn < 0) { - // path.drawTo(sNext); - // } - // else { - path.cubicTo(gPrev, gNext, sNext); - // } - } - - /** - * Adds the required CSS-Classes - * - * @param svgp SVG-Plot - */ - private void addCSSClasses(SVGPlot svgp) { - if(!svgp.getCSSClassManager().contains(EMBORDER)) { - ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT); - String color; - int clusterID = 0; - - for(@SuppressWarnings("unused") - Cluster<?> cluster : clustering.getAllClusters()) { - CSSClass cls = new CSSClass(this, EMBORDER + clusterID); - cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT) / 2); - - if(clustering.getAllClusters().size() == 1) { - color = "black"; - } - else { - color = colors.getColor(clusterID); - } - if(softBorder == 0) { - cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, color); - } - cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, color); - cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 0.15); - - svgp.addCSSClassOrLogError(cls); - if(opacStyle == 0) { - break; - } - clusterID++; - } - } - } - - /** - * Visualizer for generating SVG-Elements containing ellipses for first, - * second and third standard deviation - * - * @author Robert Rödler - * - * @apiviz.stereotype factory - * @apiviz.uses EMClusterVisualization oneway - - «create» - * - * @param <NV> Type of the NumberVector being visualized. - */ - public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory { /** - * Constructor + * Adds the required CSS-Classes + * + * @param svgp SVG-Plot */ - public Factory() { - super(); - } - - @Override - public EMClusterVisualization<NV> makeVisualization(VisualizationTask task) { - return new EMClusterVisualization<NV>(task); - } + private void addCSSClasses(SVGPlot svgp) { + if(!svgp.getCSSClassManager().contains(EMBORDER)) { + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + ColorLibrary colors = style.getColorSet(StyleLibrary.PLOT); + String color; + int clusterID = 0; + + for(@SuppressWarnings("unused") + Cluster<?> cluster : clustering.getAllClusters()) { + CSSClass cls = new CSSClass(this, EMBORDER + clusterID); + cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT) * .5); + + if(clustering.getAllClusters().size() == 1) { + color = "black"; + } + else { + color = colors.getColor(clusterID); + } + if(softBorder == 0) { + cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, color); + } + cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, color); + cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 0.15); - @Override - public void processNewResult(HierarchicalResult baseResult, Result result) { - // Find clusterings we can visualize: - Collection<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class); - for(Clustering<?> c : clusterings) { - if(c.getAllClusters().size() > 0) { - // Does the cluster have a model with cluster means? - Clustering<MeanModel<NV>> mcls = findMeanModel(c); - if(mcls != null) { - Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : ps) { - final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this); - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA + 3); - baseResult.getHierarchy().add(c, task); - baseResult.getHierarchy().add(p, task); - } + svgp.addCSSClassOrLogError(cls); + if(opacStyle == 0) { + break; } + clusterID++; } } } - - /** - * Test if the given clustering has a mean model. - * - * @param <NV> Vector type - * @param c Clustering to inspect - * @return the clustering cast to return a mean model, null otherwise. - */ - @SuppressWarnings("unchecked") - private static <NV extends NumberVector<NV, ?>> Clustering<MeanModel<NV>> findMeanModel(Clustering<?> c) { - final Model firstModel = c.getAllClusters().get(0).getModel(); - if(c.getAllClusters().get(0).getModel() instanceof MeanModel<?> && firstModel instanceof EMModel<?>) { - return (Clustering<MeanModel<NV>>) c; - } - return null; - } } -} +}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/VoronoiVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/VoronoiVisualization.java index d6ce810c..7ae28f42 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/VoronoiVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/VoronoiVisualization.java @@ -33,16 +33,17 @@ import org.w3c.dom.Element; import de.lmu.ifi.dbs.elki.data.Cluster;
import de.lmu.ifi.dbs.elki.data.Clustering;
import de.lmu.ifi.dbs.elki.data.NumberVector;
+import de.lmu.ifi.dbs.elki.data.model.KMeansModel;
import de.lmu.ifi.dbs.elki.data.model.MeanModel;
import de.lmu.ifi.dbs.elki.data.model.MedoidModel;
import de.lmu.ifi.dbs.elki.data.model.Model;
+import de.lmu.ifi.dbs.elki.database.relation.RelationUtil;
import de.lmu.ifi.dbs.elki.math.geometry.SweepHullDelaunay2D;
import de.lmu.ifi.dbs.elki.math.geometry.SweepHullDelaunay2D.Triangle;
import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector;
import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
import de.lmu.ifi.dbs.elki.result.Result;
import de.lmu.ifi.dbs.elki.result.ResultUtil;
-import de.lmu.ifi.dbs.elki.utilities.DatabaseUtil;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization;
@@ -65,13 +66,10 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatter * See also: {@link de.lmu.ifi.dbs.elki.algorithm.clustering.kmeans.KMeansLloyd
* KMeans clustering}
*
- * @author Robert Rödler
- * @author Erich Schubert
- *
- * @apiviz.has MeanModel oneway - - visualizes
- * @apiviz.has MedoidModel oneway - - visualizes
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class VoronoiVisualization extends AbstractScatterplotVisualization {
+public class VoronoiVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
@@ -90,238 +88,240 @@ public class VoronoiVisualization extends AbstractScatterplotVisualization { * @apiviz.exclude
*/
public static enum Mode {
- VORONOI, DELAUNAY, V_AND_D
+ /**
+ * Draw Voronoi cells.
+ */
+ VORONOI,
+ /**
+ * Draw Delaunay triangulation.
+ */
+ DELAUNAY,
+ /**
+ * Draw both Delaunay and Voronoi.
+ */
+ V_AND_D
}
/**
- * The result we work on
+ * Settings.
*/
- Clustering<Model> clustering;
+ private Parameterizer settings;
/**
- * The Voronoi diagram
- */
- Element voronoi;
-
- /**
- * Active drawing mode.
- */
- private Mode mode;
-
- /**
- * Constructor
+ * Constructor.
*
- * @param task VisualizationTask
- * @param mode Drawing mode
+ * @param settings Drawing mode
*/
- public VoronoiVisualization(VisualizationTask task, Mode mode) {
- super(task);
- this.clustering = task.getResult();
- this.mode = mode;
- incrementalRedraw();
+ public VoronoiVisualization(Parameterizer settings) {
+ super();
+ this.settings = settings;
}
@Override
- protected void redraw() {
- addCSSClasses(svgp);
- final List<Cluster<Model>> clusters = clustering.getAllClusters();
-
- if(clusters.size() < 2) {
- return;
- }
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
+ }
- // Collect cluster means
- if(clusters.size() == 2) {
- ArrayList<double[]> means = new ArrayList<double[]>(clusters.size());
- {
- for(Cluster<Model> clus : clusters) {
- Model model = clus.getModel();
- double[] mean;
- if(model instanceof MeanModel) {
- @SuppressWarnings("unchecked")
- MeanModel<? extends NumberVector<?, ?>> mmodel = (MeanModel<? extends NumberVector<?, ?>>) model;
- mean = proj.fastProjectDataToRenderSpace(mmodel.getMean());
- }
- else if(model instanceof MedoidModel) {
- MedoidModel mmodel = (MedoidModel) model;
- mean = proj.fastProjectDataToRenderSpace(rel.get(mmodel.getMedoid()));
- }
- else {
- continue;
- }
- means.add(mean);
- }
- }
- if(mode == Mode.VORONOI || mode == Mode.V_AND_D) {
- Element path = VoronoiDraw.drawFakeVoronoi(proj, means).makeElement(svgp);
- SVGUtil.addCSSClass(path, KMEANSBORDER);
- layer.appendChild(path);
- }
- if(mode == Mode.DELAUNAY || mode == Mode.V_AND_D) {
- Element path = new SVGPath(means.get(0)).drawTo(means.get(1)).makeElement(svgp);
- SVGUtil.addCSSClass(path, KMEANSBORDER);
- layer.appendChild(path);
- }
- }
- else {
- ArrayList<Vector> vmeans = new ArrayList<Vector>(clusters.size());
- ArrayList<double[]> means = new ArrayList<double[]>(clusters.size());
- {
- for(Cluster<Model> clus : clusters) {
- Model model = clus.getModel();
- Vector mean;
- if(model instanceof MeanModel) {
- @SuppressWarnings("unchecked")
- MeanModel<? extends NumberVector<?, ?>> mmodel = (MeanModel<? extends NumberVector<?, ?>>) model;
- mean = mmodel.getMean().getColumnVector();
- }
- else if(model instanceof MedoidModel) {
- MedoidModel mmodel = (MedoidModel) model;
- mean = rel.get(mmodel.getMedoid()).getColumnVector();
- }
- else {
- continue;
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ // Find clusterings we can visualize:
+ Collection<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class);
+ for (Clustering<?> c : clusterings) {
+ if (c.getAllClusters().size() > 0) {
+ // Does the cluster have a model with cluster means?
+ if (testMeanModel(c)) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for (ScatterPlotProjector<?> p : ps) {
+ if (RelationUtil.dimensionality(p.getRelation()) == 2) {
+ final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA + 3;
+ baseResult.getHierarchy().add(p, task);
+ baseResult.getHierarchy().add(c, task);
+ }
}
- vmeans.add(mean);
- means.add(mean.getArrayRef());
}
}
- // Compute Delaunay Triangulation
- ArrayList<Triangle> delaunay = new SweepHullDelaunay2D(vmeans).getDelaunay();
- if(mode == Mode.VORONOI || mode == Mode.V_AND_D) {
- Element path = VoronoiDraw.drawVoronoi(proj, delaunay, means).makeElement(svgp);
- SVGUtil.addCSSClass(path, KMEANSBORDER);
- layer.appendChild(path);
- }
- if(mode == Mode.DELAUNAY || mode == Mode.V_AND_D) {
- Element path = VoronoiDraw.drawDelaunay(proj, delaunay, means).makeElement(svgp);
- SVGUtil.addCSSClass(path, KMEANSBORDER);
- layer.appendChild(path);
- }
}
}
/**
- * Adds the required CSS-Classes
+ * Test if the given clustering has a mean model.
*
- * @param svgp SVG-Plot
+ * @param c Clustering to inspect
+ * @return true when the clustering has a mean or medoid model.
*/
- private void addCSSClasses(SVGPlot svgp) {
- // Class for the distance markers
- if(!svgp.getCSSClassManager().contains(KMEANSBORDER)) {
- CSSClass cls = new CSSClass(this, KMEANSBORDER);
- cls = new CSSClass(this, KMEANSBORDER);
- cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_BLACK_VALUE);
- cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT) * .5);
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
- cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
- cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
- svgp.addCSSClassOrLogError(cls);
+ private static boolean testMeanModel(Clustering<?> c) {
+ Model firstmodel = c.getAllClusters().get(0).getModel();
+ if (firstmodel instanceof KMeansModel<?>) {
+ return true;
+ }
+ if (firstmodel instanceof MedoidModel) {
+ return true;
}
+ return false;
}
/**
- * Factory for visualizers to generate an SVG-Element containing the lines
- * between kMeans clusters
+ * Instance.
*
* @author Robert Rödler
* @author Erich Schubert
*
- * @apiviz.stereotype factory
- * @apiviz.uses VoronoiVisualization oneway - - «create»
+ * @apiviz.has MeanModel oneway - - visualizes
+ * @apiviz.has MedoidModel oneway - - visualizes
*/
- public static class Factory extends AbstractVisFactory {
+ public class Instance extends AbstractScatterplotVisualization {
/**
- * Mode for drawing: Voronoi, Delaunay, both
- *
- * <p>
- * Key: {@code -voronoi.mode}
- * </p>
+ * The result we work on.
*/
- public static final OptionID MODE_ID = OptionID.getOrCreateOptionID("voronoi.mode", "Mode for drawing the voronoi cells (and/or delaunay triangulation)");
+ Clustering<Model> clustering;
/**
- * Drawing mode
+ * The Voronoi diagram.
*/
- private Mode mode;
+ Element voronoi;
/**
- * Constructor
+ * Constructor.
*
- * @param mode Drawing mode
+ * @param task VisualizationTask
*/
- public Factory(Mode mode) {
- super();
- this.mode = mode;
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.clustering = task.getResult();
+ incrementalRedraw();
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new VoronoiVisualization(task, mode);
- }
+ protected void redraw() {
+ addCSSClasses(svgp);
+ final List<Cluster<Model>> clusters = clustering.getAllClusters();
- @Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- // Find clusterings we can visualize:
- Collection<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class);
- for(Clustering<?> c : clusterings) {
- if(c.getAllClusters().size() > 0) {
- // Does the cluster have a model with cluster means?
- if(testMeanModel(c)) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : ps) {
- if(DatabaseUtil.dimensionality(p.getRelation()) == 2) {
- final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA + 3);
- baseResult.getHierarchy().add(p, task);
- baseResult.getHierarchy().add(c, task);
- }
+ if (clusters.size() < 2) {
+ return;
+ }
+
+ // Collect cluster means
+ if (clusters.size() == 2) {
+ ArrayList<double[]> means = new ArrayList<double[]>(clusters.size());
+ {
+ for (Cluster<Model> clus : clusters) {
+ Model model = clus.getModel();
+ double[] mean;
+ if (model instanceof MeanModel) {
+ @SuppressWarnings("unchecked")
+ MeanModel<? extends NumberVector<?>> mmodel = (MeanModel<? extends NumberVector<?>>) model;
+ mean = proj.fastProjectDataToRenderSpace(mmodel.getMean());
+ } else if (model instanceof MedoidModel) {
+ MedoidModel mmodel = (MedoidModel) model;
+ mean = proj.fastProjectDataToRenderSpace(rel.get(mmodel.getMedoid()));
+ } else {
+ continue;
}
+ means.add(mean);
}
}
+ if (settings.mode == Mode.VORONOI || settings.mode == Mode.V_AND_D) {
+ Element path = VoronoiDraw.drawFakeVoronoi(proj, means).makeElement(svgp);
+ SVGUtil.addCSSClass(path, KMEANSBORDER);
+ layer.appendChild(path);
+ }
+ if (settings.mode == Mode.DELAUNAY || settings.mode == Mode.V_AND_D) {
+ Element path = new SVGPath(means.get(0)).drawTo(means.get(1)).makeElement(svgp);
+ SVGUtil.addCSSClass(path, KMEANSBORDER);
+ layer.appendChild(path);
+ }
+ } else {
+ ArrayList<Vector> vmeans = new ArrayList<Vector>(clusters.size());
+ ArrayList<double[]> means = new ArrayList<double[]>(clusters.size());
+ {
+ for (Cluster<Model> clus : clusters) {
+ Model model = clus.getModel();
+ Vector mean;
+ if (model instanceof MeanModel) {
+ @SuppressWarnings("unchecked")
+ MeanModel<? extends NumberVector<?>> mmodel = (MeanModel<? extends NumberVector<?>>) model;
+ mean = mmodel.getMean().getColumnVector();
+ } else if (model instanceof MedoidModel) {
+ MedoidModel mmodel = (MedoidModel) model;
+ mean = rel.get(mmodel.getMedoid()).getColumnVector();
+ } else {
+ continue;
+ }
+ vmeans.add(mean);
+ means.add(mean.getArrayRef());
+ }
+ }
+ // Compute Delaunay Triangulation
+ ArrayList<Triangle> delaunay = new SweepHullDelaunay2D(vmeans).getDelaunay();
+ if (settings.mode == Mode.VORONOI || settings.mode == Mode.V_AND_D) {
+ Element path = VoronoiDraw.drawVoronoi(proj, delaunay, means).makeElement(svgp);
+ SVGUtil.addCSSClass(path, KMEANSBORDER);
+ layer.appendChild(path);
+ }
+ if (settings.mode == Mode.DELAUNAY || settings.mode == Mode.V_AND_D) {
+ Element path = VoronoiDraw.drawDelaunay(proj, delaunay, means).makeElement(svgp);
+ SVGUtil.addCSSClass(path, KMEANSBORDER);
+ layer.appendChild(path);
+ }
}
}
/**
- * Test if the given clustering has a mean model.
+ * Adds the required CSS-Classes.
*
- * @param c Clustering to inspect
- * @return true when the clustering has a mean or medoid model.
+ * @param svgp SVG-Plot
*/
- private static boolean testMeanModel(Clustering<?> c) {
- Model firstmodel = c.getAllClusters().get(0).getModel();
- if(firstmodel instanceof MeanModel<?>) {
- return true;
+ private void addCSSClasses(SVGPlot svgp) {
+ // Class for the distance markers
+ if (!svgp.getCSSClassManager().contains(KMEANSBORDER)) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ CSSClass cls = new CSSClass(this, KMEANSBORDER);
+ cls = new CSSClass(this, KMEANSBORDER);
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_BLACK_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT) * .5);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ svgp.addCSSClassOrLogError(cls);
}
- if(firstmodel instanceof MedoidModel) {
- return true;
- }
- return false;
}
+ }
+ /**
+ * Parameterization class.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ public static class Parameterizer extends AbstractParameterizer {
/**
- * Parameterization class.
+ * Mode for drawing: Voronoi, Delaunay, both.
*
- * @author Erich Schubert
- *
- * @apiviz.exclude
+ * <p>
+ * Key: {@code -voronoi.mode}
+ * </p>
*/
- public static class Parameterizer extends AbstractParameterizer {
- protected Mode mode;
+ public static final OptionID MODE_ID = new OptionID("voronoi.mode", "Mode for drawing the voronoi cells (and/or delaunay triangulation)");
- @Override
- protected void makeOptions(Parameterization config) {
- super.makeOptions(config);
- EnumParameter<Mode> modeP = new EnumParameter<Mode>(MODE_ID, Mode.class, Mode.VORONOI);
- if(config.grab(modeP)) {
- mode = modeP.getValue();
- }
- }
+ /**
+ * Drawing mode.
+ */
+ protected Mode mode;
- @Override
- protected Factory makeInstance() {
- return new Factory(mode);
+ @Override
+ protected void makeOptions(Parameterization config) {
+ super.makeOptions(config);
+ EnumParameter<Mode> modeP = new EnumParameter<Mode>(MODE_ID, Mode.class, Mode.VORONOI);
+ if (config.grab(modeP)) {
+ mode = modeP.getValue();
}
}
+
+ @Override
+ protected VoronoiVisualization makeInstance() {
+ return new VoronoiVisualization(this);
+ }
}
-}
\ No newline at end of file +}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/density/DensityEstimationOverlay.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/density/DensityEstimationOverlay.java index c07bc571..96658910 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/density/DensityEstimationOverlay.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/density/DensityEstimationOverlay.java @@ -52,184 +52,184 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatter * <em>in the projection, not the actual data!</em> * * @author Erich Schubert + * + * @apiviz.stereotype factory + * @apiviz.uses Instance oneway - - «create» */ -// TODO: make parameterizable, in particular color map, kernel bandwidth and -// kernel function -public class DensityEstimationOverlay extends AbstractScatterplotVisualization { +public class DensityEstimationOverlay extends AbstractVisFactory { /** * A short name characterizing this Visualizer. */ private static final String NAME = "Density estimation overlay"; /** - * Density map resolution + * Constructor, adhering to + * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} */ - private int resolution = 500; + public DensityEstimationOverlay() { + super(); + } - /** - * The actual image - */ - private BufferedImage img = null; + @Override + public Visualization makeVisualization(VisualizationTask task) { + return new Instance(task); + } + + @Override + public void processNewResult(HierarchicalResult baseResult, Result result) { + Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(result, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { + final VisualizationTask task = new VisualizationTask(NAME, p.getRelation(), p.getRelation(), this); + task.level = VisualizationTask.LEVEL_DATA + 1; + task.initDefaultVisibility(false); + baseResult.getHierarchy().add(p, task); + } + } /** - * Constructor. + * Instance for a particular data set. * - * @param task Task + * @author Erich Schubert */ - public DensityEstimationOverlay(VisualizationTask task) { - super(task); - incrementalRedraw(); - } + // TODO: make parameterizable, in particular color map, kernel bandwidth and + // kernel function + public class Instance extends AbstractScatterplotVisualization { + /** + * Density map resolution + */ + private int resolution = 500; - @Override - protected void redraw() { - if(img == null) { - renderImage(); + /** + * The actual image + */ + private BufferedImage img = null; + + /** + * Constructor. + * + * @param task Task + */ + public Instance(VisualizationTask task) { + super(task); + incrementalRedraw(); } - CanvasSize canvas = proj.estimateViewport(); - String imguri = ThumbnailRegistryEntry.INTERNAL_PREFIX + ThumbnailRegistryEntry.registerImage(img); - Element itag = svgp.svgElement(SVGConstants.SVG_IMAGE_TAG); - SVGUtil.setAtt(itag, SVGConstants.SVG_IMAGE_RENDERING_ATTRIBUTE, SVGConstants.SVG_OPTIMIZE_SPEED_VALUE); - SVGUtil.setAtt(itag, SVGConstants.SVG_X_ATTRIBUTE, canvas.minx); - SVGUtil.setAtt(itag, SVGConstants.SVG_Y_ATTRIBUTE, canvas.miny); - SVGUtil.setAtt(itag, SVGConstants.SVG_WIDTH_ATTRIBUTE, canvas.maxx - canvas.minx); - SVGUtil.setAtt(itag, SVGConstants.SVG_HEIGHT_ATTRIBUTE, canvas.maxy - canvas.miny); - SVGUtil.setAtt(itag, SVGConstants.SVG_STYLE_ATTRIBUTE, SVGConstants.CSS_OPACITY_PROPERTY + ": .5"); - itag.setAttributeNS(SVGConstants.XLINK_NAMESPACE_URI, SVGConstants.XLINK_HREF_QNAME, imguri); - - layer.appendChild(itag); - } + @Override + protected void redraw() { + if(img == null) { + renderImage(); + } - @Reference(authors = "D. W. Scott", title = "Multivariate density estimation", booktitle = "Multivariate Density Estimation: Theory, Practice, and Visualization", url = "http://dx.doi.org/10.1002/9780470316849.fmatter") - private double[] initializeBandwidth(double[][] data) { - MeanVariance mv0 = new MeanVariance(); - MeanVariance mv1 = new MeanVariance(); - // For Kernel bandwidth. - for(double[] projected : data) { - mv0.put(projected[0]); - mv1.put(projected[1]); + CanvasSize canvas = proj.estimateViewport(); + String imguri = ThumbnailRegistryEntry.INTERNAL_PREFIX + ThumbnailRegistryEntry.registerImage(img); + Element itag = svgp.svgElement(SVGConstants.SVG_IMAGE_TAG); + SVGUtil.setAtt(itag, SVGConstants.SVG_IMAGE_RENDERING_ATTRIBUTE, SVGConstants.SVG_OPTIMIZE_SPEED_VALUE); + SVGUtil.setAtt(itag, SVGConstants.SVG_X_ATTRIBUTE, canvas.minx); + SVGUtil.setAtt(itag, SVGConstants.SVG_Y_ATTRIBUTE, canvas.miny); + SVGUtil.setAtt(itag, SVGConstants.SVG_WIDTH_ATTRIBUTE, canvas.maxx - canvas.minx); + SVGUtil.setAtt(itag, SVGConstants.SVG_HEIGHT_ATTRIBUTE, canvas.maxy - canvas.miny); + SVGUtil.setAtt(itag, SVGConstants.SVG_STYLE_ATTRIBUTE, SVGConstants.CSS_OPACITY_PROPERTY + ": .5"); + itag.setAttributeNS(SVGConstants.XLINK_NAMESPACE_URI, SVGConstants.XLINK_HREF_QNAME, imguri); + + layer.appendChild(itag); } - // Set bandwidths according to Scott's rule: - // Note: in projected space, d=2. - double[] bandwidth = new double[2]; - bandwidth[0] = MathUtil.SQRT5 * mv0.getSampleStddev() * Math.pow(rel.size(), -1 / 6.); - bandwidth[1] = MathUtil.SQRT5 * mv1.getSampleStddev() * Math.pow(rel.size(), -1 / 6.); - return bandwidth; - } - private void renderImage() { - // TODO: SAMPLE? Do region queries? - // Project the data just once, keep a copy. - double[][] data = new double[rel.size()][]; - { - int i = 0; - for(DBIDIter iditer = rel.iterDBIDs(); iditer.valid(); iditer.advance()) { - data[i] = proj.fastProjectDataToRenderSpace(rel.get(iditer)); - i++; + @Reference(authors = "D. W. Scott", title = "Multivariate density estimation", booktitle = "Multivariate Density Estimation: Theory, Practice, and Visualization", url = "http://dx.doi.org/10.1002/9780470316849.fmatter") + private double[] initializeBandwidth(double[][] data) { + MeanVariance mv0 = new MeanVariance(); + MeanVariance mv1 = new MeanVariance(); + // For Kernel bandwidth. + for(double[] projected : data) { + mv0.put(projected[0]); + mv1.put(projected[1]); } + // Set bandwidths according to Scott's rule: + // Note: in projected space, d=2. + double[] bandwidth = new double[2]; + bandwidth[0] = MathUtil.SQRT5 * mv0.getSampleStddev() * Math.pow(rel.size(), -1 / 6.); + bandwidth[1] = MathUtil.SQRT5 * mv1.getSampleStddev() * Math.pow(rel.size(), -1 / 6.); + return bandwidth; } - double[] bandwidth = initializeBandwidth(data); - // Compare by first component - Comparator<double[]> comp0 = new Comparator<double[]>() { - @Override - public int compare(double[] o1, double[] o2) { - return Double.compare(o1[0], o2[0]); - } - }; - // Compare by second component - Comparator<double[]> comp1 = new Comparator<double[]>() { - @Override - public int compare(double[] o1, double[] o2) { - return Double.compare(o1[1], o2[1]); + + private void renderImage() { + // TODO: SAMPLE? Do region queries? + // Project the data just once, keep a copy. + double[][] data = new double[rel.size()][]; + { + int i = 0; + for(DBIDIter iditer = rel.iterDBIDs(); iditer.valid(); iditer.advance()) { + data[i] = proj.fastProjectDataToRenderSpace(rel.get(iditer)); + i++; + } } - }; - // TODO: choose comparator order based on smaller bandwidth? - Arrays.sort(data, comp0); - - CanvasSize canvas = proj.estimateViewport(); - double min0 = canvas.minx, max0 = canvas.maxx, ste0 = (max0 - min0) / resolution; - double min1 = canvas.miny, max1 = canvas.maxy, ste1 = (max1 - min1) / resolution; - - double kernf = 9. / (16 * bandwidth[0] * bandwidth[1]); - double maxdens = 0.0; - double[][] dens = new double[resolution][resolution]; - { - // TODO: incrementally update the loff/roff values? - for(int x = 0; x < resolution; x++) { - double xlow = min0 + ste0 * x, xhig = xlow + ste0; - int loff = unflip(Arrays.binarySearch(data, new double[] { xlow - bandwidth[0] }, comp0)); - int roff = unflip(Arrays.binarySearch(data, new double[] { xhig + bandwidth[0] }, comp0)); - // Resort by second component - Arrays.sort(data, loff, roff, comp1); - for(int y = 0; y < resolution; y++) { - double ylow = min1 + ste1 * y, yhig = ylow + ste1; - int boff = unflip(Arrays.binarySearch(data, loff, roff, new double[] { 0, ylow - bandwidth[1] }, comp1)); - int toff = unflip(Arrays.binarySearch(data, loff, roff, new double[] { 0, yhig + bandwidth[1] }, comp1)); - for(int pos = boff; pos < toff; pos++) { - double[] val = data[pos]; - double d0 = (val[0] < xlow) ? (xlow - val[0]) : (val[0] > xhig) ? (val[0] - xhig) : 0; - double d1 = (val[1] < ylow) ? (ylow - val[1]) : (val[1] > yhig) ? (val[1] - yhig) : 0; - d0 = d0 / bandwidth[0]; - d1 = d1 / bandwidth[1]; - dens[x][y] += kernf * (1 - d0 * d0) * (1 - d1 * d1); + double[] bandwidth = initializeBandwidth(data); + // Compare by first component + Comparator<double[]> comp0 = new Comparator<double[]>() { + @Override + public int compare(double[] o1, double[] o2) { + return Double.compare(o1[0], o2[0]); + } + }; + // Compare by second component + Comparator<double[]> comp1 = new Comparator<double[]>() { + @Override + public int compare(double[] o1, double[] o2) { + return Double.compare(o1[1], o2[1]); + } + }; + // TODO: choose comparator order based on smaller bandwidth? + Arrays.sort(data, comp0); + + CanvasSize canvas = proj.estimateViewport(); + double min0 = canvas.minx, max0 = canvas.maxx, ste0 = (max0 - min0) / resolution; + double min1 = canvas.miny, max1 = canvas.maxy, ste1 = (max1 - min1) / resolution; + + double kernf = 9. / (16 * bandwidth[0] * bandwidth[1]); + double maxdens = 0.0; + double[][] dens = new double[resolution][resolution]; + { + // TODO: incrementally update the loff/roff values? + for(int x = 0; x < resolution; x++) { + double xlow = min0 + ste0 * x, xhig = xlow + ste0; + int loff = unflip(Arrays.binarySearch(data, new double[] { xlow - bandwidth[0] }, comp0)); + int roff = unflip(Arrays.binarySearch(data, new double[] { xhig + bandwidth[0] }, comp0)); + // Resort by second component + Arrays.sort(data, loff, roff, comp1); + for(int y = 0; y < resolution; y++) { + double ylow = min1 + ste1 * y, yhig = ylow + ste1; + int boff = unflip(Arrays.binarySearch(data, loff, roff, new double[] { 0, ylow - bandwidth[1] }, comp1)); + int toff = unflip(Arrays.binarySearch(data, loff, roff, new double[] { 0, yhig + bandwidth[1] }, comp1)); + for(int pos = boff; pos < toff; pos++) { + double[] val = data[pos]; + double d0 = (val[0] < xlow) ? (xlow - val[0]) : (val[0] > xhig) ? (val[0] - xhig) : 0; + double d1 = (val[1] < ylow) ? (ylow - val[1]) : (val[1] > yhig) ? (val[1] - yhig) : 0; + d0 = d0 / bandwidth[0]; + d1 = d1 / bandwidth[1]; + dens[x][y] += kernf * (1 - d0 * d0) * (1 - d1 * d1); + } + maxdens = Math.max(maxdens, dens[x][y]); } - maxdens = Math.max(maxdens, dens[x][y]); + // Restore original sorting, as the intervals overlap + Arrays.sort(data, loff, roff, comp0); } - // Restore original sorting, as the intervals overlap - Arrays.sort(data, loff, roff, comp0); } - } - img = new BufferedImage(resolution, resolution, BufferedImage.TYPE_INT_ARGB); - { - for(int x = 0; x < resolution; x++) { - for(int y = 0; y < resolution; y++) { - int rgb = KMLOutputHandler.getColorForValue(dens[x][y] / maxdens).getRGB(); - img.setRGB(x, y, rgb); + img = new BufferedImage(resolution, resolution, BufferedImage.TYPE_INT_ARGB); + { + for(int x = 0; x < resolution; x++) { + for(int y = 0; y < resolution; y++) { + int rgb = KMLOutputHandler.getColorForValue(dens[x][y] / maxdens).getRGB(); + img.setRGB(x, y, rgb); + } } } } - } - private int unflip(int binarySearch) { - if(binarySearch < 0) { - return (-binarySearch) - 1; - } - else { - return binarySearch; - } - } - - /** - * The visualization factory - * - * @author Erich Schubert - * - * @apiviz.stereotype factory - * @apiviz.uses DensityEstimationOverlay oneway - - «create» - */ - public static class Factory extends AbstractVisFactory { - /** - * Constructor, adhering to - * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} - */ - public Factory() { - super(); - } - - @Override - public Visualization makeVisualization(VisualizationTask task) { - return new DensityEstimationOverlay(task); - } - - @Override - public void processNewResult(HierarchicalResult baseResult, Result result) { - Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(result, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : ps) { - final VisualizationTask task = new VisualizationTask(NAME, p.getRelation(), p.getRelation(), this); - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA + 1); - task.put(VisualizationTask.META_VISIBLE_DEFAULT, false); - baseResult.getHierarchy().add(p, task); + private int unflip(int binarySearch) { + if(binarySearch < 0) { + return (-binarySearch) - 1; + } + else { + return binarySearch; } } } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeMBRVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeMBRVisualization.java index 61635625..9d466a8f 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeMBRVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeMBRVisualization.java @@ -60,14 +60,10 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatter * * @author Erich Schubert * - * @apiviz.has AbstractRStarTree oneway - - visualizes - * @apiviz.uses SVGHyperCube - * - * @param <N> Tree node type - * @param <E> Tree entry type + * @apiviz.stereotype factory + * @apiviz.uses Instance oneway - - «create» */ -// TODO: listen for tree changes instead of data changes? -public class TreeMBRVisualization<N extends AbstractRStarTreeNode<N, E>, E extends SpatialEntry> extends AbstractScatterplotVisualization implements DataStoreListener { +public class TreeMBRVisualization extends AbstractVisFactory { /** * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc. */ @@ -79,109 +75,152 @@ public class TreeMBRVisualization<N extends AbstractRStarTreeNode<N, E>, E exten public static final String NAME = "Index MBRs"; /** - * Fill parameter. - */ - protected boolean fill = false; - - /** - * The tree we visualize + * Settings */ - protected AbstractRStarTree<N, E> tree; + protected Parameterizer settings; /** * Constructor. * - * @param task Visualization task - * @param fill Fill flag + * @param settings Settings */ - @SuppressWarnings("unchecked") - public TreeMBRVisualization(VisualizationTask task, boolean fill) { - super(task); - this.tree = AbstractRStarTree.class.cast(task.getResult()); - this.fill = fill; - incrementalRedraw(); - context.addDataStoreListener(this); + public TreeMBRVisualization(Parameterizer settings) { + super(); + this.settings = settings; } @Override - protected void redraw() { - int projdim = proj.getVisibleDimensions2D().cardinality(); - ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT); - - if(tree != null) { - E root = tree.getRootEntry(); - for(int i = 0; i < tree.getHeight(); i++) { - CSSClass cls = new CSSClass(this, INDEX + i); - // Relative depth of this level. 1.0 = toplevel - final double relDepth = 1. - (((double) i) / tree.getHeight()); - if(fill) { - cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(i)); - cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, relDepth * context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT)); - cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, colors.getColor(i)); - cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 0.1 / (projdim - 1)); - } - else { - cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(i)); - cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, relDepth * context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT)); - cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE); + public Visualization makeVisualization(VisualizationTask task) { + return new Instance<RStarTreeNode, SpatialEntry>(task); + } + + @Override + public void processNewResult(HierarchicalResult baseResult, Result result) { + Collection<AbstractRStarTree<RStarTreeNode, SpatialEntry>> trees = ResultUtil.filterResults(result, AbstractRStarTree.class); + for(AbstractRStarTree<RStarTreeNode, SpatialEntry> tree : trees) { + if(tree instanceof Result) { + Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); + for(ScatterPlotProjector<?> p : ps) { + final VisualizationTask task = new VisualizationTask(NAME, (Result) tree, p.getRelation(), this); + task.level = VisualizationTask.LEVEL_BACKGROUND + 1; + baseResult.getHierarchy().add((Result) tree, task); + baseResult.getHierarchy().add(p, task); } - cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE); - cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE); - svgp.addCSSClassOrLogError(cls); } - visualizeRTreeEntry(svgp, layer, proj, tree, root, 0); } } /** - * Recursively draw the MBR rectangles. + * Instance for a particular tree * - * @param svgp SVG Plot - * @param layer Layer - * @param proj Projection - * @param rtree Rtree to visualize - * @param entry Current entry - * @param depth Current depth + * @author Erich Schubert + * + * @apiviz.has AbstractRStarTree oneway - - visualizes + * @apiviz.uses SVGHyperCube + * + * @param <N> Tree node type + * @param <E> Tree entry type */ - private void visualizeRTreeEntry(SVGPlot svgp, Element layer, Projection2D proj, AbstractRStarTree<? extends N, E> rtree, E entry, int depth) { - SpatialComparable mbr = entry; + // TODO: listen for tree changes instead of data changes? + public class Instance<N extends AbstractRStarTreeNode<N, E>, E extends SpatialEntry> extends AbstractScatterplotVisualization implements DataStoreListener { + /** + * The tree we visualize + */ + protected AbstractRStarTree<N, E> tree; - if(fill) { - Element r = SVGHyperCube.drawFilled(svgp, INDEX + depth, proj, SpatialUtil.getMin(mbr), SpatialUtil.getMax(mbr)); - layer.appendChild(r); + /** + * Constructor. + * + * @param task Visualization task + */ + @SuppressWarnings("unchecked") + public Instance(VisualizationTask task) { + super(task); + this.tree = AbstractRStarTree.class.cast(task.getResult()); + incrementalRedraw(); + context.addDataStoreListener(this); } - else { - Element r = SVGHyperCube.drawFrame(svgp, proj, SpatialUtil.getMin(mbr), SpatialUtil.getMax(mbr)); - SVGUtil.setCSSClass(r, INDEX + depth); - layer.appendChild(r); + + @Override + protected void redraw() { + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + int projdim = proj.getVisibleDimensions2D().cardinality(); + ColorLibrary colors = style.getColorSet(StyleLibrary.PLOT); + + if(tree != null) { + E root = tree.getRootEntry(); + for(int i = 0; i < tree.getHeight(); i++) { + CSSClass cls = new CSSClass(this, INDEX + i); + // Relative depth of this level. 1.0 = toplevel + final double relDepth = 1. - (((double) i) / tree.getHeight()); + if(settings.fill) { + cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(i)); + cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, relDepth * style.getLineWidth(StyleLibrary.PLOT)); + cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, colors.getColor(i)); + cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 0.1 / (projdim - 1)); + } + else { + cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(i)); + cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, relDepth * style.getLineWidth(StyleLibrary.PLOT)); + cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE); + } + cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE); + cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE); + svgp.addCSSClassOrLogError(cls); + } + visualizeRTreeEntry(svgp, layer, proj, tree, root, 0); + } } - if(!entry.isLeafEntry()) { - N node = rtree.getNode(entry); - for(int i = 0; i < node.getNumEntries(); i++) { - E child = node.getEntry(i); - if(!child.isLeafEntry()) { - visualizeRTreeEntry(svgp, layer, proj, rtree, child, depth + 1); + /** + * Recursively draw the MBR rectangles. + * + * @param svgp SVG Plot + * @param layer Layer + * @param proj Projection + * @param rtree Rtree to visualize + * @param entry Current entry + * @param depth Current depth + */ + private void visualizeRTreeEntry(SVGPlot svgp, Element layer, Projection2D proj, AbstractRStarTree<? extends N, E> rtree, E entry, int depth) { + SpatialComparable mbr = entry; + + if(settings.fill) { + Element r = SVGHyperCube.drawFilled(svgp, INDEX + depth, proj, SpatialUtil.getMin(mbr), SpatialUtil.getMax(mbr)); + layer.appendChild(r); + } + else { + Element r = SVGHyperCube.drawFrame(svgp, proj, SpatialUtil.getMin(mbr), SpatialUtil.getMax(mbr)); + SVGUtil.setCSSClass(r, INDEX + depth); + layer.appendChild(r); + } + + if(!entry.isLeafEntry()) { + N node = rtree.getNode(entry); + for(int i = 0; i < node.getNumEntries(); i++) { + E child = node.getEntry(i); + if(!child.isLeafEntry()) { + visualizeRTreeEntry(svgp, layer, proj, rtree, child, depth + 1); + } } } } - } - @Override - public void destroy() { - super.destroy(); - context.removeDataStoreListener(this); + @Override + public void destroy() { + super.destroy(); + context.removeDataStoreListener(this); + } } /** - * Factory + * Parameterization class. * * @author Erich Schubert * - * @apiviz.stereotype factory - * @apiviz.uses TreeMBRVisualization oneway - - «create» + * @apiviz.exclude */ - public static class Factory extends AbstractVisFactory { + public static class Parameterizer extends AbstractParameterizer { /** * Flag for half-transparent filling of bubbles. * @@ -189,67 +228,22 @@ public class TreeMBRVisualization<N extends AbstractRStarTreeNode<N, E>, E exten * Key: {@code -index.fill} * </p> */ - public static final OptionID FILL_ID = OptionID.getOrCreateOptionID("index.fill", "Partially transparent filling of index pages."); + public static final OptionID FILL_ID = new OptionID("index.fill", "Partially transparent filling of index pages."); - /** - * Fill parameter. - */ protected boolean fill = false; - /** - * Constructor. - * - * @param fill - */ - public Factory(boolean fill) { - super(); - this.fill = fill; - } - @Override - public Visualization makeVisualization(VisualizationTask task) { - return new TreeMBRVisualization<RStarTreeNode, SpatialEntry>(task, fill); - } - - @Override - public void processNewResult(HierarchicalResult baseResult, Result result) { - Collection<AbstractRStarTree<RStarTreeNode, SpatialEntry>> trees = ResultUtil.filterResults(result, AbstractRStarTree.class); - for(AbstractRStarTree<RStarTreeNode, SpatialEntry> tree : trees) { - if(tree instanceof Result) { - Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : ps) { - final VisualizationTask task = new VisualizationTask(NAME, (Result) tree, p.getRelation(), this); - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_BACKGROUND + 1); - baseResult.getHierarchy().add((Result) tree, task); - baseResult.getHierarchy().add(p, task); - } - } + protected void makeOptions(Parameterization config) { + super.makeOptions(config); + Flag fillF = new Flag(FILL_ID); + if(config.grab(fillF)) { + fill = fillF.isTrue(); } } - /** - * Parameterization class. - * - * @author Erich Schubert - * - * @apiviz.exclude - */ - public static class Parameterizer extends AbstractParameterizer { - protected boolean fill = false; - - @Override - protected void makeOptions(Parameterization config) { - super.makeOptions(config); - Flag fillF = new Flag(FILL_ID); - if(config.grab(fillF)) { - fill = fillF.getValue(); - } - } - - @Override - protected Factory makeInstance() { - return new Factory(fill); - } + @Override + protected TreeMBRVisualization makeInstance() { + return new TreeMBRVisualization(this); } } }
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeSphereVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeSphereVisualization.java index daebab02..e89be028 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeSphereVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/index/TreeSphereVisualization.java @@ -65,14 +65,10 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatter * * @author Erich Schubert * - * @apiviz.has AbstractMTree oneway - - visualizes - * @apiviz.uses SVGHyperSphere - * - * @param <N> Tree node type - * @param <E> Tree entry type + * @apiviz.stereotype factory + * @apiviz.uses TreeSphereVisualization oneway - - «create» */ -// TODO: listen for tree changes! -public class TreeSphereVisualization<D extends NumberDistance<D, ?>, N extends AbstractMTreeNode<?, D, N, E>, E extends MTreeEntry<D>> extends AbstractScatterplotVisualization implements DataStoreListener { +public class TreeSphereVisualization extends AbstractVisFactory { /** * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc. */ @@ -92,37 +88,40 @@ public class TreeSphereVisualization<D extends NumberDistance<D, ?>, N extends A MANHATTAN, EUCLIDEAN, LPCROSS } - protected double p; - /** - * Drawing mode (distance) to use + * Settings */ - protected Modus dist = Modus.LPCROSS; + protected Parameterizer settings; /** - * The tree we visualize + * Constructor. + * + * @param settings Settings */ - protected AbstractMTree<?, D, N, E> tree; + public TreeSphereVisualization(Parameterizer settings) { + super(); + this.settings = settings; + } - /** - * Fill parameter. - */ - protected boolean fill = false; + @Override + public void processNewResult(HierarchicalResult baseResult, Result result) { + Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); + for (ScatterPlotProjector<?> p : ps) { + Collection<AbstractMTree<?, DoubleDistance, ?, ?>> trees = ResultUtil.filterResults(result, AbstractMTree.class); + for (AbstractMTree<?, DoubleDistance, ?, ?> tree : trees) { + if (canVisualize(tree) && tree instanceof Result) { + final VisualizationTask task = new VisualizationTask(NAME, (Result) tree, p.getRelation(), this); + task.level = VisualizationTask.LEVEL_BACKGROUND + 1; + baseResult.getHierarchy().add((Result) tree, task); + baseResult.getHierarchy().add(p, task); + } + } + } + } - /** - * Constructor - * - * @param task Task - * @param fill fill flag - */ - @SuppressWarnings("unchecked") - public TreeSphereVisualization(VisualizationTask task, boolean fill) { - super(task); - this.tree = AbstractMTree.class.cast(task.getResult()); - this.p = getLPNormP(this.tree); - this.fill = fill; - incrementalRedraw(); - context.addDataStoreListener(this); + @Override + public Visualization makeVisualization(VisualizationTask task) { + return new Instance<DoubleDistance, MTreeNode<Object, DoubleDistance>, MTreeEntry<DoubleDistance>>(task); } /** @@ -131,14 +130,14 @@ public class TreeSphereVisualization<D extends NumberDistance<D, ?>, N extends A * @param tree Tree to visualize * @return p value */ - public static Double getLPNormP(AbstractMTree<?, ?, ?, ?> tree) { - // Note: we deliberately lose generics here, so the compilers complain less - // on the next typecheck and cast! + public static double getLPNormP(AbstractMTree<?, ?, ?, ?> tree) { + // Note: we deliberately lose generics here, so the compilers complain + // less on the next typecheck and cast! DistanceFunction<?, ?> distanceFunction = tree.getDistanceQuery().getDistanceFunction(); - if(LPNormDistanceFunction.class.isInstance(distanceFunction)) { + if (LPNormDistanceFunction.class.isInstance(distanceFunction)) { return ((LPNormDistanceFunction) distanceFunction).getP(); } - return null; + return 0; } /** @@ -148,170 +147,160 @@ public class TreeSphereVisualization<D extends NumberDistance<D, ?>, N extends A * @return whether the tree is visualizable */ public static boolean canVisualize(AbstractMTree<?, ?, ?, ?> tree) { - Double p = getLPNormP(tree); - return (p != null); - } - - @Override - protected void redraw() { - int projdim = proj.getVisibleDimensions2D().cardinality(); - ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT); - - p = getLPNormP(tree); - if(tree != null) { - if(ManhattanDistanceFunction.class.isInstance(tree.getDistanceQuery())) { - dist = Modus.MANHATTAN; - } - else if(EuclideanDistanceFunction.class.isInstance(tree.getDistanceQuery())) { - dist = Modus.EUCLIDEAN; - } - else { - dist = Modus.LPCROSS; - } - E root = tree.getRootEntry(); - final int mtheight = tree.getHeight(); - for(int i = 0; i < mtheight; i++) { - CSSClass cls = new CSSClass(this, INDEX + i); - // Relative depth of this level. 1.0 = toplevel - final double relDepth = 1. - (((double) i) / mtheight); - if(fill) { - cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(i)); - cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, relDepth * context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT)); - cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, colors.getColor(i)); - cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 0.1 / (projdim - 1)); - cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE); - cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE); - } - else { - cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(i)); - cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, relDepth * context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT)); - cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE); - cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE); - cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE); - } - svgp.addCSSClassOrLogError(cls); - } - visualizeMTreeEntry(svgp, this.layer, proj, tree, root, 0); - } + return getLPNormP(tree) > 0; } /** - * Recursively draw the MBR rectangles. - * - * @param svgp SVG Plot - * @param layer Layer - * @param proj Projection - * @param mtree Mtree to visualize - * @param entry Current entry - * @param depth Current depth - */ - private void visualizeMTreeEntry(SVGPlot svgp, Element layer, Projection2D proj, AbstractMTree<?, D, N, E> mtree, E entry, int depth) { - DBID roid = entry.getRoutingObjectID(); - if(roid != null) { - NumberVector<?, ?> ro = rel.get(roid); - D rad = entry.getCoveringRadius(); - - final Element r; - if(dist == Modus.MANHATTAN) { - r = SVGHyperSphere.drawManhattan(svgp, proj, ro, rad); - } - else if(dist == Modus.EUCLIDEAN) { - r = SVGHyperSphere.drawEuclidean(svgp, proj, ro, rad); - } - // TODO: add visualizer for infinity norm? - else { - // r = SVGHyperSphere.drawCross(svgp, proj, ro, rad); - r = SVGHyperSphere.drawLp(svgp, proj, ro, rad, p); - } - SVGUtil.setCSSClass(r, INDEX + (depth - 1)); - layer.appendChild(r); - } - - if(!entry.isLeafEntry()) { - N node = mtree.getNode(entry); - for(int i = 0; i < node.getNumEntries(); i++) { - E child = node.getEntry(i); - if(!child.isLeafEntry()) { - visualizeMTreeEntry(svgp, layer, proj, mtree, child, depth + 1); - } - } - } - } - - @Override - public void destroy() { - super.destroy(); - context.removeDataStoreListener(this); - } - - /** - * Factory + * Instance for a particular tree. * * @author Erich Schubert * - * @apiviz.stereotype factory - * @apiviz.uses TreeSphereVisualization oneway - - «create» + * @apiviz.has AbstractMTree oneway - - visualizes + * @apiviz.uses SVGHyperSphere + * + * @param <N> Tree node type + * @param <E> Tree entry type */ - public static class Factory extends AbstractVisFactory { + // TODO: listen for tree changes! + public class Instance<D extends NumberDistance<D, ?>, N extends AbstractMTreeNode<?, D, N, E>, E extends MTreeEntry<D>> extends AbstractScatterplotVisualization implements DataStoreListener { + protected double p; + /** - * Fill parameter. + * Drawing mode (distance) to use */ - protected boolean fill = false; + protected Modus dist = Modus.LPCROSS; + + /** + * The tree we visualize + */ + protected AbstractMTree<?, D, N, E> tree; /** - * Constructor. + * Constructor * - * @param fill + * @param task Task */ - public Factory(boolean fill) { - super(); - this.fill = fill; + @SuppressWarnings("unchecked") + public Instance(VisualizationTask task) { + super(task); + this.tree = AbstractMTree.class.cast(task.getResult()); + this.p = getLPNormP(this.tree); + incrementalRedraw(); + context.addDataStoreListener(this); } @Override - public void processNewResult(HierarchicalResult baseResult, Result result) { - Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); - for(ScatterPlotProjector<?> p : ps) { - Collection<AbstractMTree<?, DoubleDistance, ?, ?>> trees = ResultUtil.filterResults(result, AbstractMTree.class); - for(AbstractMTree<?, DoubleDistance, ?, ?> tree : trees) { - if(canVisualize(tree) && tree instanceof Result) { - final VisualizationTask task = new VisualizationTask(NAME, (Result) tree, p.getRelation(), this); - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_BACKGROUND + 1); - baseResult.getHierarchy().add((Result) tree, task); - baseResult.getHierarchy().add(p, task); + protected void redraw() { + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + int projdim = proj.getVisibleDimensions2D().cardinality(); + ColorLibrary colors = style.getColorSet(StyleLibrary.PLOT); + + p = getLPNormP(tree); + if (tree != null) { + if (ManhattanDistanceFunction.class.isInstance(tree.getDistanceQuery())) { + dist = Modus.MANHATTAN; + } else if (EuclideanDistanceFunction.class.isInstance(tree.getDistanceQuery())) { + dist = Modus.EUCLIDEAN; + } else { + dist = Modus.LPCROSS; + } + E root = tree.getRootEntry(); + final int mtheight = tree.getHeight(); + for (int i = 0; i < mtheight; i++) { + CSSClass cls = new CSSClass(this, INDEX + i); + // Relative depth of this level. 1.0 = toplevel + final double relDepth = 1. - (((double) i) / mtheight); + if (settings.fill) { + cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(i)); + cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, relDepth * style.getLineWidth(StyleLibrary.PLOT)); + cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, colors.getColor(i)); + cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 0.1 / (projdim - 1)); + cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE); + cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE); + } else { + cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, colors.getColor(i)); + cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, relDepth * style.getLineWidth(StyleLibrary.PLOT)); + cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE); + cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE); + cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE); } + svgp.addCSSClassOrLogError(cls); } + visualizeMTreeEntry(svgp, this.layer, proj, tree, root, 0); } } - @Override - public Visualization makeVisualization(VisualizationTask task) { - return new TreeSphereVisualization<DoubleDistance, MTreeNode<Object, DoubleDistance>, MTreeEntry<DoubleDistance>>(task, fill); - } - /** - * Parameterization class. + * Recursively draw the MBR rectangles. * - * @author Erich Schubert - * - * @apiviz.exclude + * @param svgp SVG Plot + * @param layer Layer + * @param proj Projection + * @param mtree Mtree to visualize + * @param entry Current entry + * @param depth Current depth */ - public static class Parameterizer extends AbstractParameterizer { - protected boolean fill = false; + private void visualizeMTreeEntry(SVGPlot svgp, Element layer, Projection2D proj, AbstractMTree<?, D, N, E> mtree, E entry, int depth) { + DBID roid = entry.getRoutingObjectID(); + if (roid != null) { + NumberVector<?> ro = rel.get(roid); + D rad = entry.getCoveringRadius(); - @Override - protected void makeOptions(Parameterization config) { - super.makeOptions(config); - Flag fillF = new Flag(TreeMBRVisualization.Factory.FILL_ID); - if(config.grab(fillF)) { - fill = fillF.getValue(); + final Element r; + if (dist == Modus.MANHATTAN) { + r = SVGHyperSphere.drawManhattan(svgp, proj, ro, rad); + } else if (dist == Modus.EUCLIDEAN) { + r = SVGHyperSphere.drawEuclidean(svgp, proj, ro, rad); + } + // TODO: add visualizer for infinity norm? + else { + // r = SVGHyperSphere.drawCross(svgp, proj, ro, rad); + r = SVGHyperSphere.drawLp(svgp, proj, ro, rad, p); } + SVGUtil.setCSSClass(r, INDEX + (depth - 1)); + layer.appendChild(r); } - @Override - protected Factory makeInstance() { - return new Factory(fill); + if (!entry.isLeafEntry()) { + N node = mtree.getNode(entry); + for (int i = 0; i < node.getNumEntries(); i++) { + E child = node.getEntry(i); + if (!child.isLeafEntry()) { + visualizeMTreeEntry(svgp, layer, proj, mtree, child, depth + 1); + } + } } } + + @Override + public void destroy() { + super.destroy(); + context.removeDataStoreListener(this); + } + } + + /** + * Parameterization class. + * + * @author Erich Schubert + * + * @apiviz.exclude + */ + public static class Parameterizer extends AbstractParameterizer { + protected boolean fill = false; + + @Override + protected void makeOptions(Parameterization config) { + super.makeOptions(config); + Flag fillF = new Flag(TreeMBRVisualization.Parameterizer.FILL_ID); + if (config.grab(fillF)) { + fill = fillF.isTrue(); + } + } + + @Override + protected TreeSphereVisualization makeInstance() { + return new TreeSphereVisualization(this); + } } -}
\ No newline at end of file +} diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/outlier/BubbleVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/outlier/BubbleVisualization.java index 769dc53d..ebe9612d 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/outlier/BubbleVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/outlier/BubbleVisualization.java @@ -50,6 +50,7 @@ import de.lmu.ifi.dbs.elki.visualization.css.CSSClass; import de.lmu.ifi.dbs.elki.visualization.projector.ScatterPlotProjector; import de.lmu.ifi.dbs.elki.visualization.style.ClassStylingPolicy; import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; +import de.lmu.ifi.dbs.elki.visualization.style.StyleResult; import de.lmu.ifi.dbs.elki.visualization.style.StylingPolicy; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil; @@ -66,10 +67,11 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualizati * @author Remigius Wojdanowski * @author Erich Schubert * - * @apiviz.has OutlierResult oneway - - visualizes + * @apiviz.stereotype factory + * @apiviz.uses Instance oneway - - «create» */ @Reference(authors = "E. Achtert, H.-P. Kriegel, L. Reichert, E. Schubert, R. Wojdanowski, A. Zimek", title = "Visual Evaluation of Outlier Detection Models", booktitle = "Proceedings of the 15th International Conference on Database Systems for Advanced Applications (DASFAA), Tsukuba, Japan, 2010", url = "http://dx.doi.org/10.1007/978-3-642-12098-5_34") -public class BubbleVisualization extends AbstractScatterplotVisualization implements DataStoreListener { +public class BubbleVisualization extends AbstractVisFactory { /** * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc. */ @@ -81,160 +83,207 @@ public class BubbleVisualization extends AbstractScatterplotVisualization implem public static final String NAME = "Outlier Bubbles"; /** - * Fill parameter. + * Current settings */ - protected boolean fill; - - /** - * Scaling function to use for Bubbles - */ - protected ScalingFunction scaling; - - /** - * The outlier result to visualize - */ - protected OutlierResult result; + protected Parameterizer settings; /** * Constructor. * - * @param task Visualization task - * @param scaling Scaling function - * @param fill Fill flag + * @param settings Settings */ - public BubbleVisualization(VisualizationTask task, ScalingFunction scaling, boolean fill) { - super(task); - this.result = task.getResult(); - this.scaling = scaling; - this.fill = fill; - context.addDataStoreListener(this); - context.addResultListener(this); - incrementalRedraw(); + public BubbleVisualization(Parameterizer settings) { + super(); + this.settings = settings; + thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_STYLE; } @Override - public void destroy() { - super.destroy(); - context.removeResultListener(this); - context.removeDataStoreListener(this); + public Visualization makeVisualization(VisualizationTask task) { + if(settings.scaling != null && settings.scaling instanceof OutlierScalingFunction) { + final OutlierResult outlierResult = task.getResult(); + ((OutlierScalingFunction) settings.scaling).prepare(outlierResult); + } + return new Instance(task); } @Override - public void redraw() { - StylingPolicy stylepolicy = context.getStyleResult().getStylingPolicy(); - // bubble size - final double bubble_size = context.getStyleLibrary().getSize(StyleLibrary.BUBBLEPLOT); - if(stylepolicy instanceof ClassStylingPolicy) { - ClassStylingPolicy colors = (ClassStylingPolicy) stylepolicy; - setupCSS(svgp, colors); - // draw data - for(DBIDIter objId = sample.getSample().iter(); objId.valid(); objId.advance()) { - final Double radius = getScaledForId(objId); - if(radius > 0.01 && !Double.isInfinite(radius)) { - final NumberVector<?, ?> vec = rel.get(objId); - if(vec != null) { - double[] v = proj.fastProjectDataToRenderSpace(vec); - Element circle = svgp.svgCircle(v[0], v[1], radius * bubble_size); - SVGUtil.addCSSClass(circle, BUBBLE + colors.getStyleForDBID(objId)); - layer.appendChild(circle); - } + public void processNewResult(HierarchicalResult baseResult, Result result) { + Collection<OutlierResult> ors = ResultUtil.filterResults(result, OutlierResult.class); + for(OutlierResult o : ors) { + Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); + boolean vis = true; + // Quick and dirty hack: hide if parent result is also an outlier result + // Since that probably is already visible and we're redundant. + for(Result r : o.getHierarchy().getParents(o)) { + if(r instanceof OutlierResult) { + vis = false; + break; } } - } - else { - // draw data - for(DBIDIter objId = sample.getSample().iter(); objId.valid(); objId.advance()) { - final Double radius = getScaledForId(objId); - if(radius > 0.01 && !Double.isInfinite(radius)) { - final NumberVector<?, ?> vec = rel.get(objId); - if(vec != null) { - double[] v = proj.fastProjectDataToRenderSpace(vec); - Element circle = svgp.svgCircle(v[0], v[1], radius * bubble_size); - int color = stylepolicy.getColorForDBID(objId); - final StringBuffer style = new StringBuffer(); - if(fill) { - style.append(SVGConstants.CSS_FILL_PROPERTY + ":").append(SVGUtil.colorToString(color)); - style.append(SVGConstants.CSS_FILL_OPACITY_PROPERTY + ":0.5"); - } - else { - style.append(SVGConstants.CSS_STROKE_VALUE + ":").append(SVGUtil.colorToString(color)); - style.append(SVGConstants.CSS_FILL_PROPERTY + ":" + SVGConstants.CSS_NONE_VALUE); - } - SVGUtil.setAtt(circle, SVGConstants.SVG_STYLE_ATTRIBUTE, style.toString()); - layer.appendChild(circle); - } + for(ScatterPlotProjector<?> p : ps) { + final VisualizationTask task = new VisualizationTask(NAME, o, p.getRelation(), this); + task.level = VisualizationTask.LEVEL_DATA; + if(!vis) { + task.initDefaultVisibility(false); } + baseResult.getHierarchy().add(o, task); + baseResult.getHierarchy().add(p, task); } } } - @Override - public void resultChanged(Result current) { - super.resultChanged(current); - if(sample == current || context.getStyleResult() == current) { - synchronizedRedraw(); - } - } - /** - * Registers the Bubble-CSS-Class at a SVGPlot. + * Factory for producing bubble visualizations * - * @param svgp the SVGPlot to register the Tooltip-CSS-Class. - * @param policy Clustering to use + * @author Erich Schubert + * + * @apiviz.has OutlierResult oneway - - visualizes */ - private void setupCSS(SVGPlot svgp, ClassStylingPolicy policy) { - ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT); + public class Instance extends AbstractScatterplotVisualization implements DataStoreListener { + /** + * The outlier result to visualize + */ + protected OutlierResult result; - // creating IDs manually because cluster often return a null-ID. - for(int clusterID = policy.getMinStyle(); clusterID < policy.getMaxStyle(); clusterID++) { - CSSClass bubble = new CSSClass(svgp, BUBBLE + clusterID); - bubble.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT)); + /** + * Constructor. + * + * @param task Visualization task + */ + public Instance(VisualizationTask task) { + super(task); + this.result = task.getResult(); + context.addDataStoreListener(this); + context.addResultListener(this); + incrementalRedraw(); + } - String color = colors.getColor(clusterID); + @Override + public void destroy() { + super.destroy(); + context.removeResultListener(this); + context.removeDataStoreListener(this); + } - if(fill) { - bubble.setStatement(SVGConstants.CSS_FILL_PROPERTY, color); - bubble.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 0.5); + @Override + public void redraw() { + final StyleResult style = context.getStyleResult(); + StylingPolicy stylepolicy = style.getStylingPolicy(); + // bubble size + final double bubble_size = style.getStyleLibrary().getSize(StyleLibrary.BUBBLEPLOT); + if(stylepolicy instanceof ClassStylingPolicy) { + ClassStylingPolicy colors = (ClassStylingPolicy) stylepolicy; + setupCSS(svgp, colors); + // draw data + for(DBIDIter objId = sample.getSample().iter(); objId.valid(); objId.advance()) { + final double radius = getScaledForId(objId); + if(radius > 0.01 && !Double.isInfinite(radius)) { + final NumberVector<?> vec = rel.get(objId); + if(vec != null) { + double[] v = proj.fastProjectDataToRenderSpace(vec); + Element circle = svgp.svgCircle(v[0], v[1], radius * bubble_size); + SVGUtil.addCSSClass(circle, BUBBLE + colors.getStyleForDBID(objId)); + layer.appendChild(circle); + } + } + } } else { - // for diamond-shaped strokes, see bugs.sun.com, bug ID 6294396 - bubble.setStatement(SVGConstants.CSS_STROKE_VALUE, color); - bubble.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE); + // draw data + for(DBIDIter objId = sample.getSample().iter(); objId.valid(); objId.advance()) { + final double radius = getScaledForId(objId); + if(radius > 0.01 && !Double.isInfinite(radius)) { + final NumberVector<?> vec = rel.get(objId); + if(vec != null) { + double[] v = proj.fastProjectDataToRenderSpace(vec); + Element circle = svgp.svgCircle(v[0], v[1], radius * bubble_size); + int color = stylepolicy.getColorForDBID(objId); + final StringBuilder cssstyle = new StringBuilder(); + if(settings.fill) { + cssstyle.append(SVGConstants.CSS_FILL_PROPERTY).append(':').append(SVGUtil.colorToString(color)); + cssstyle.append(SVGConstants.CSS_FILL_OPACITY_PROPERTY).append(":0.5"); + } + else { + cssstyle.append(SVGConstants.CSS_STROKE_VALUE).append(':').append(SVGUtil.colorToString(color)); + cssstyle.append(SVGConstants.CSS_FILL_PROPERTY).append(':').append(SVGConstants.CSS_NONE_VALUE); + } + SVGUtil.setAtt(circle, SVGConstants.SVG_STYLE_ATTRIBUTE, cssstyle.toString()); + layer.appendChild(circle); + } + } + } } - - svgp.addCSSClassOrLogError(bubble); } - } - /** - * Convenience method to apply scalings in the right order. - * - * @param id object ID to get scaled score for - * @return a Double representing a outlierness-score, after it has modified by - * the given scales. - */ - protected double getScaledForId(DBIDRef id) { - double d = result.getScores().get(id).doubleValue(); - if(Double.isNaN(d) || Double.isInfinite(d)) { - return 0.0; + @Override + public void resultChanged(Result current) { + super.resultChanged(current); + if(sample == current || context.getStyleResult() == current) { + synchronizedRedraw(); + } } - if(scaling == null) { - return result.getOutlierMeta().normalizeScore(d); + + /** + * Registers the Bubble-CSS-Class at a SVGPlot. + * + * @param svgp the SVGPlot to register the Tooltip-CSS-Class. + * @param policy Clustering to use + */ + private void setupCSS(SVGPlot svgp, ClassStylingPolicy policy) { + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + ColorLibrary colors = style.getColorSet(StyleLibrary.PLOT); + + // creating IDs manually because cluster often return a null-ID. + for(int clusterID = policy.getMinStyle(); clusterID < policy.getMaxStyle(); clusterID++) { + CSSClass bubble = new CSSClass(svgp, BUBBLE + clusterID); + bubble.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT)); + + String color = colors.getColor(clusterID); + + if(settings.fill) { + bubble.setStatement(SVGConstants.CSS_FILL_PROPERTY, color); + bubble.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 0.5); + } + else { + // for diamond-shaped strokes, see bugs.sun.com, bug ID 6294396 + bubble.setStatement(SVGConstants.CSS_STROKE_VALUE, color); + bubble.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE); + } + + svgp.addCSSClassOrLogError(bubble); + } } - else { - return scaling.getScaled(d); + + /** + * Convenience method to apply scalings in the right order. + * + * @param id object ID to get scaled score for + * @return a Double representing a outlierness-score, after it has modified + * by the given scales. + */ + protected double getScaledForId(DBIDRef id) { + double d = result.getScores().get(id).doubleValue(); + if(Double.isNaN(d) || Double.isInfinite(d)) { + return 0.0; + } + if(settings.scaling == null) { + return result.getOutlierMeta().normalizeScore(d); + } + else { + return settings.scaling.getScaled(d); + } } } /** - * Factory for producing bubble visualizations + * Parameterization class. * * @author Erich Schubert * - * @apiviz.stereotype factory - * @apiviz.uses BubbleVisualization oneway - - «create» + * @apiviz.exclude */ - public static class Factory extends AbstractVisFactory { + public static class Parameterizer extends AbstractParameterizer { /** * Flag for half-transparent filling of bubbles. * @@ -242,7 +291,7 @@ public class BubbleVisualization extends AbstractScatterplotVisualization implem * Key: {@code -bubble.fill} * </p> */ - public static final OptionID FILL_ID = OptionID.getOrCreateOptionID("bubble.fill", "Half-transparent filling of bubbles."); + public static final OptionID FILL_ID = new OptionID("bubble.fill", "Half-transparent filling of bubbles."); /** * Parameter for scaling functions @@ -251,7 +300,7 @@ public class BubbleVisualization extends AbstractScatterplotVisualization implem * Key: {@code -bubble.scaling} * </p> */ - public static final OptionID SCALING_ID = OptionID.getOrCreateOptionID("bubble.scaling", "Additional scaling function for bubbles."); + public static final OptionID SCALING_ID = new OptionID("bubble.scaling", "Additional scaling function for bubbles."); /** * Fill parameter. @@ -263,90 +312,23 @@ public class BubbleVisualization extends AbstractScatterplotVisualization implem */ protected ScalingFunction scaling; - /** - * Constructor. - * - * @param fill - * @param scaling - */ - public Factory(boolean fill, ScalingFunction scaling) { - super(); - this.fill = fill; - this.scaling = scaling; - thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_STYLE; - } - @Override - public Visualization makeVisualization(VisualizationTask task) { - if(this.scaling != null && this.scaling instanceof OutlierScalingFunction) { - final OutlierResult outlierResult = task.getResult(); - ((OutlierScalingFunction) this.scaling).prepare(outlierResult); + protected void makeOptions(Parameterization config) { + super.makeOptions(config); + Flag fillF = new Flag(FILL_ID); + if(config.grab(fillF)) { + fill = fillF.isTrue(); } - return new BubbleVisualization(task, scaling, fill); - } - @Override - public void processNewResult(HierarchicalResult baseResult, Result result) { - Collection<OutlierResult> ors = ResultUtil.filterResults(result, OutlierResult.class); - for(OutlierResult o : ors) { - Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); - boolean vis = true; - // Quick and dirty hack: hide if parent result is also an outlier result - // Since that probably is already visible and we're redundant. - for(Result r : o.getHierarchy().getParents(o)) { - if(r instanceof OutlierResult) { - vis = false; - break; - } - } - for(ScatterPlotProjector<?> p : ps) { - final VisualizationTask task = new VisualizationTask(NAME, o, p.getRelation(), this); - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA); - if(!vis) { - task.put(VisualizationTask.META_VISIBLE_DEFAULT, false); - } - baseResult.getHierarchy().add(o, task); - baseResult.getHierarchy().add(p, task); - } + ObjectParameter<ScalingFunction> scalingP = new ObjectParameter<ScalingFunction>(SCALING_ID, OutlierScalingFunction.class, true); + if(config.grab(scalingP)) { + scaling = scalingP.instantiateClass(config); } } - /** - * Parameterization class. - * - * @author Erich Schubert - * - * @apiviz.exclude - */ - public static class Parameterizer extends AbstractParameterizer { - /** - * Fill parameter. - */ - protected boolean fill = false; - - /** - * Scaling function to use for Bubbles - */ - protected ScalingFunction scaling = null; - - @Override - protected void makeOptions(Parameterization config) { - super.makeOptions(config); - Flag fillF = new Flag(FILL_ID); - if(config.grab(fillF)) { - fill = fillF.getValue(); - } - - ObjectParameter<ScalingFunction> scalingP = new ObjectParameter<ScalingFunction>(SCALING_ID, OutlierScalingFunction.class, true); - if(config.grab(scalingP)) { - scaling = scalingP.instantiateClass(config); - } - } - - @Override - protected Factory makeInstance() { - return new Factory(fill, scaling); - } + @Override + protected BubbleVisualization makeInstance() { + return new BubbleVisualization(this); } } }
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/outlier/COPVectorVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/outlier/COPVectorVisualization.java new file mode 100644 index 00000000..e953f9ae --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/outlier/COPVectorVisualization.java @@ -0,0 +1,190 @@ +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.outlier; + +/* + This file is part of ELKI: + Environment for Developing KDD-Applications Supported by Index-Structures + + Copyright (C) 2012 + Ludwig-Maximilians-Universität München + Lehr- und Forschungseinheit für Datenbanksysteme + 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/>. + */ + +import java.util.List; + +import org.apache.batik.util.SVGConstants; +import org.w3c.dom.Element; + +import de.lmu.ifi.dbs.elki.algorithm.outlier.COP; +import de.lmu.ifi.dbs.elki.data.NumberVector; +import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener; +import de.lmu.ifi.dbs.elki.database.ids.DBIDIter; +import de.lmu.ifi.dbs.elki.database.relation.Relation; +import de.lmu.ifi.dbs.elki.math.linearalgebra.VMath; +import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector; +import de.lmu.ifi.dbs.elki.result.HierarchicalResult; +import de.lmu.ifi.dbs.elki.result.Result; +import de.lmu.ifi.dbs.elki.result.ResultUtil; +import de.lmu.ifi.dbs.elki.result.outlier.OutlierResult; +import de.lmu.ifi.dbs.elki.utilities.documentation.Reference; +import de.lmu.ifi.dbs.elki.utilities.documentation.Title; +import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; +import de.lmu.ifi.dbs.elki.visualization.css.CSSClass; +import de.lmu.ifi.dbs.elki.visualization.projector.ScatterPlotProjector; +import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; +import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; +import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil; +import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisFactory; +import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; +import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatterplotVisualization; + +/** + * Visualize error vectors as produced by COP. + * + * @author Erich Schubert + * + * @apiviz.stereotype factory + * @apiviz.uses Instance oneway - - «create» + * @apiviz.has OutlierResult oneway - - visualizes + */ +@Title("COP: Correlation Outlier Probability") +@Reference(authors = "Hans-Peter Kriegel, Peer Kröger, Erich Schubert, Arthur Zimek", title = "Outlier Detection in Arbitrarily Oriented Subspaces", booktitle = "Proc. IEEE International Conference on Data Mining (ICDM 2012)") +public class COPVectorVisualization extends AbstractVisFactory { + /** + * A short name characterizing this Visualizer. + */ + public static final String NAME = "Error Vectors"; + + /** + * Constructor. + */ + public COPVectorVisualization() { + super(); + } + + @Override + public Visualization makeVisualization(VisualizationTask task) { + return new Instance(task); + } + + @Override + public void processNewResult(HierarchicalResult baseResult, Result result) { + List<OutlierResult> ors = ResultUtil.filterResults(result, OutlierResult.class); + for (OutlierResult o : ors) { + List<Relation<?>> rels = ResultUtil.filterResults(o, Relation.class); + for (Relation<?> rel : rels) { + if (!rel.getShortName().equals(COP.COP_ERRORVEC)) { + continue; + } + List<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class); + boolean vis = true; + for (ScatterPlotProjector<?> p : ps) { + final VisualizationTask task = new VisualizationTask(NAME, rel, p.getRelation(), this); + task.level = VisualizationTask.LEVEL_DATA; + if (!vis) { + task.initDefaultVisibility(false); + } + baseResult.getHierarchy().add(o, task); + baseResult.getHierarchy().add(p, task); + } + } + } + } + + /** + * Visualize error vectors as produced by COP. + * + * @author Erich Schubert + */ + public class Instance extends AbstractScatterplotVisualization implements DataStoreListener { + /** + * Generic tag to indicate the type of element. Used in IDs, CSS-Classes + * etc. + */ + public static final String VEC = "copvec"; + + /** + * The outlier result to visualize + */ + protected Relation<Vector> result; + + /** + * Constructor. + * + * @param task Visualization task + */ + public Instance(VisualizationTask task) { + super(task); + this.result = task.getResult(); + context.addDataStoreListener(this); + incrementalRedraw(); + } + + @Override + public void destroy() { + super.destroy(); + context.removeDataStoreListener(this); + } + + @Override + public void redraw() { + setupCSS(svgp); + for (DBIDIter objId = sample.getSample().iter(); objId.valid(); objId.advance()) { + Vector evec = result.get(objId); + if (evec == null) { + continue; + } + double[] ev = proj.fastProjectRelativeDataToRenderSpace(evec); + // TODO: avoid hard-coded plot threshold + if (VMath.euclideanLength(ev) < 0.01) { + continue; + } + final NumberVector<?> vec = rel.get(objId); + if (vec == null) { + continue; + } + double[] v = proj.fastProjectDataToRenderSpace(vec); + Element arrow = svgp.svgLine(v[0], v[1], v[0] + ev[0], v[1] + ev[1]); + SVGUtil.addCSSClass(arrow, VEC); + layer.appendChild(arrow); + } + } + + @Override + public void resultChanged(Result current) { + if (sample == current) { + synchronizedRedraw(); + } + } + + /** + * Registers the COP error vector-CSS-Class at a SVGPlot. + * + * @param svgp the SVGPlot to register the Tooltip-CSS-Class. + */ + private void setupCSS(SVGPlot svgp) { + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + CSSClass bubble = new CSSClass(svgp, VEC); + bubble.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT) / 2); + + // ColorLibrary colors = style.getColorSet(StyleLibrary.PLOT); + String color = "red"; // TODO: use style library + bubble.setStatement(SVGConstants.CSS_STROKE_VALUE, color); + bubble.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE); + svgp.addCSSClassOrLogError(bubble); + } + } +} diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/DistanceFunctionVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/DistanceFunctionVisualization.java new file mode 100644 index 00000000..257a3c54 --- /dev/null +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/DistanceFunctionVisualization.java @@ -0,0 +1,370 @@ +package de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.selection;
+/* + This file is part of ELKI: + Environment for Developing KDD-Applications Supported by Index-Structures + + Copyright (C) 2012 + Ludwig-Maximilians-Universität München + Lehr- und Forschungseinheit für Datenbanksysteme + 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/>. + */ +
+import java.util.Collection;
+
+import org.apache.batik.util.SVGConstants;
+import org.w3c.dom.Element;
+
+import de.lmu.ifi.dbs.elki.data.NumberVector;
+import de.lmu.ifi.dbs.elki.data.VectorUtil;
+import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDIter;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDs;
+import de.lmu.ifi.dbs.elki.database.ids.DistanceDBIDPair;
+import de.lmu.ifi.dbs.elki.distance.distancefunction.ArcCosineDistanceFunction;
+import de.lmu.ifi.dbs.elki.distance.distancefunction.CosineDistanceFunction;
+import de.lmu.ifi.dbs.elki.distance.distancefunction.DistanceFunction;
+import de.lmu.ifi.dbs.elki.distance.distancefunction.LPNormDistanceFunction;
+import de.lmu.ifi.dbs.elki.distance.distanceresultlist.DistanceDBIDResultIter;
+import de.lmu.ifi.dbs.elki.distance.distanceresultlist.KNNResult;
+import de.lmu.ifi.dbs.elki.distance.distancevalue.DoubleDistance;
+import de.lmu.ifi.dbs.elki.distance.distancevalue.NumberDistance;
+import de.lmu.ifi.dbs.elki.index.preprocessed.knn.AbstractMaterializeKNNPreprocessor;
+import de.lmu.ifi.dbs.elki.logging.LoggingUtil;
+import de.lmu.ifi.dbs.elki.math.linearalgebra.VMath;
+import de.lmu.ifi.dbs.elki.result.DBIDSelection;
+import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
+import de.lmu.ifi.dbs.elki.result.Result;
+import de.lmu.ifi.dbs.elki.result.ResultUtil;
+import de.lmu.ifi.dbs.elki.result.SelectionResult;
+import de.lmu.ifi.dbs.elki.utilities.exceptions.ObjectNotFoundException;
+import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
+import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
+import de.lmu.ifi.dbs.elki.visualization.projections.CanvasSize;
+import de.lmu.ifi.dbs.elki.visualization.projections.Projection2D;
+import de.lmu.ifi.dbs.elki.visualization.projector.ScatterPlotProjector;
+import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGHyperSphere;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPath;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot;
+import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisFactory;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatterplotVisualization;
+import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualization;
+
+/**
+ * Factory for visualizers to generate an SVG-Element containing dots as markers
+ * representing the kNN of the selected Database objects.
+ *
+ * To use this, add a kNN preprocessor index to your database!
+ *
+ * @author Erich Schubert
+ * @author Robert Rödler
+ *
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
+ */
+// FIXME: for >2 dimensions, cosine doesn't seem to be correct yet.
+public class DistanceFunctionVisualization extends AbstractVisFactory {
+ /**
+ * A short name characterizing this Visualizer.
+ */
+ public static final String NAME = "k Nearest Neighbor Visualization";
+
+ /**
+ * Constructor
+ */
+ public DistanceFunctionVisualization() {
+ super();
+ thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION;
+ }
+
+ @Override
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance<DoubleDistance>(task);
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<AbstractMaterializeKNNPreprocessor<?, ?, ?>> kNNIndex = ResultUtil.filterResults(result, AbstractMaterializeKNNPreprocessor.class);
+ for(AbstractMaterializeKNNPreprocessor<?, ?, ?> kNN : kNNIndex) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, kNN, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA - 1;
+ baseResult.getHierarchy().add(kNN, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ }
+
+ /**
+ * Get the "p" value of an Lp norm.
+ *
+ * @param kNN kNN preprocessor
+ * @return p of LP norm, or NaN
+ */
+ public static double getLPNormP(AbstractMaterializeKNNPreprocessor<?, ?, ?> kNN) {
+ DistanceFunction<?, ?> distanceFunction = kNN.getDistanceQuery().getDistanceFunction();
+ if(LPNormDistanceFunction.class.isInstance(distanceFunction)) {
+ return ((LPNormDistanceFunction) distanceFunction).getP();
+ }
+ return Double.NaN;
+ }
+
+ /**
+ * Test whether the given preprocessor used an angular distance function
+ *
+ * @param kNN kNN preprocessor
+ * @return true when angular
+ */
+ public static boolean isAngularDistance(AbstractMaterializeKNNPreprocessor<?, ?, ?> kNN) {
+ DistanceFunction<?, ?> distanceFunction = kNN.getDistanceQuery().getDistanceFunction();
+ if(CosineDistanceFunction.class.isInstance(distanceFunction)) {
+ return true;
+ }
+ if(ArcCosineDistanceFunction.class.isInstance(distanceFunction)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Visualizes Cosine and ArcCosine distance functions
+ *
+ * @param svgp SVG Plot
+ * @param proj Visualization projection
+ * @param mid mean vector
+ * @param angle Opening angle in radians
+ * @return path element
+ */
+ public static Element drawCosine(SVGPlot svgp, Projection2D proj, NumberVector<?> mid, double angle) {
+ // Project origin
+ double[] pointOfOrigin = proj.fastProjectDataToRenderSpace(new double[proj.getInputDimensionality()]);
+
+ // direction of the selected Point
+ double[] selPoint = proj.fastProjectDataToRenderSpace(mid);
+
+ double[] range1, range2;
+ {
+ // Rotation plane:
+ double[] p1 = proj.fastProjectRenderToDataSpace(new double[] { selPoint[0] + 10, selPoint[1] });
+ double[] p2 = proj.fastProjectRenderToDataSpace(new double[] { selPoint[0], selPoint[1] + 10 });
+ double[] pm = mid.getColumnVector().getArrayRef();
+ // Compute relative vectors
+ VMath.minusEquals(p1, pm);
+ VMath.minusEquals(p2, pm);
+ // Scale p1 and p2 to unit length:
+ VMath.timesEquals(p1, 1. / VMath.euclideanLength(p1));
+ VMath.timesEquals(p2, 1. / VMath.euclideanLength(p2));
+ {
+ double test = VMath.scalarProduct(p1, p2);
+ if(Math.abs(test) > 1E-10) {
+ LoggingUtil.warning("Projection does not seem to be orthogonal?");
+ }
+ }
+ // Project onto p1, p2:
+ double l1 = VMath.scalarProduct(pm, p1), l2 = VMath.scalarProduct(pm, p2);
+ // Rotate projection by + and - angle
+ // Using sin(-x) = -sin(x) and cos(-x)=cos(x)
+ final double cangle = Math.cos(angle), sangle = Math.sin(angle);
+ double r11 = +cangle * l1 - sangle * l2, r12 = +sangle * l1 + cangle * l2;
+ double r21 = +cangle * l1 + sangle * l2, r22 = -sangle * l1 + cangle * l2;
+ // Build rotated vectors - remove projected component, add rotated
+ // component:
+ double[] r1 = VMath.copy(pm), r2 = VMath.copy(pm);
+ VMath.plusTimesEquals(r1, p1, -l1 + r11);
+ VMath.plusTimesEquals(r1, p2, -l2 + r12);
+ VMath.plusTimesEquals(r2, p1, -l1 + r21);
+ VMath.plusTimesEquals(r2, p2, -l2 + r22);
+ // Project to render space:
+ range1 = proj.fastProjectDataToRenderSpace(r1);
+ range2 = proj.fastProjectDataToRenderSpace(r2);
+ }
+
+ // Continue lines to viewport.
+ {
+ CanvasSize viewport = proj.estimateViewport();
+ VMath.minusEquals(range1, pointOfOrigin);
+ VMath.minusEquals(range2, pointOfOrigin);
+ VMath.timesEquals(range1, viewport.continueToMargin(pointOfOrigin, range1));
+ VMath.timesEquals(range2, viewport.continueToMargin(pointOfOrigin, range2));
+ VMath.plusEquals(range1, pointOfOrigin);
+ VMath.plusEquals(range2, pointOfOrigin);
+ // Go backwards into the other direction - the origin might not be in the
+ // viewport!
+ double[] start1 = VMath.minus(pointOfOrigin, range1);
+ double[] start2 = VMath.minus(pointOfOrigin, range2);
+ VMath.timesEquals(start1, viewport.continueToMargin(range1, start1));
+ VMath.timesEquals(start2, viewport.continueToMargin(range2, start2));
+ VMath.plusEquals(start1, range1);
+ VMath.plusEquals(start2, range2);
+
+ // TODO: add filled variant?
+ SVGPath path = new SVGPath();
+ path.moveTo(start1);
+ path.lineTo(range1);
+ path.moveTo(start2);
+ path.lineTo(range2);
+ return path.makeElement(svgp);
+ }
+ }
+
+ /**
+ * Instance, visualizing a particular set of kNNs
+ *
+ * @author Robert Rödler
+ * @author Erich Schubert
+ *
+ * @apiviz.has SelectionResult oneway - - visualizes
+ * @apiviz.has DBIDSelection oneway - - visualizes
+ *
+ * @param <D> Distance type
+ */
+ public class Instance<D extends NumberDistance<D, ?>> extends AbstractScatterplotVisualization implements DataStoreListener {
+ /**
+ * Generic tags to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
+ */
+ public static final String KNNMARKER = "kNNMarker";
+
+ public static final String KNNDIST = "kNNDist";
+
+ public static final String DISTANCEFUNCTION = "distancefunction";
+
+ /**
+ * The selection result we work on
+ */
+ private AbstractMaterializeKNNPreprocessor<? extends NumberVector<?>, D, ?> result;
+
+ /**
+ * Constructor
+ *
+ * @param task VisualizationTask
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.result = task.getResult();
+ context.addDataStoreListener(this);
+ context.addResultListener(this);
+ incrementalRedraw();
+ }
+
+ @Override
+ protected void redraw() {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ addCSSClasses(svgp);
+ final double p = getLPNormP(result);
+ final boolean angular = isAngularDistance(result);
+
+ final double size = style.getSize(StyleLibrary.SELECTION);
+ DBIDSelection selContext = context.getSelection();
+ if(selContext != null) {
+ DBIDs selection = selContext.getSelectedIds();
+
+ for(DBIDIter i = selection.iter(); i.valid(); i.advance()) {
+ final KNNResult<D> knn = result.get(i);
+ for(DistanceDBIDResultIter<D> iter = knn.iter(); iter.valid(); iter.advance()) {
+ try {
+ double[] v = proj.fastProjectDataToRenderSpace(rel.get(iter));
+ Element dot = svgp.svgCircle(v[0], v[1], size);
+ SVGUtil.addCSSClass(dot, KNNMARKER);
+ layer.appendChild(dot);
+
+ Element lbl = svgp.svgText(v[0] + size, v[1] + size, iter.getDistance().toString());
+ SVGUtil.addCSSClass(lbl, KNNDIST);
+ layer.appendChild(lbl);
+ }
+ catch(ObjectNotFoundException e) {
+ // ignore
+ }
+ }
+ // Last element
+ DistanceDBIDPair<D> last = knn.get(knn.size() - 1);
+ // Draw hypersphere if possible
+ {
+ final Element dist;
+ if(p == 1.0) {
+ dist = SVGHyperSphere.drawManhattan(svgp, proj, rel.get(i), last.getDistance());
+ }
+ else if(p == 2.0) {
+ dist = SVGHyperSphere.drawEuclidean(svgp, proj, rel.get(i), last.getDistance());
+ }
+ else if(!Double.isNaN(p)) {
+ dist = SVGHyperSphere.drawLp(svgp, proj, rel.get(i), last.getDistance(), p);
+ }
+ else if(angular) {
+ final NumberVector<?> refvec = rel.get(i);
+ // Recompute the angle - it could be cosine or arccosine distance
+ double maxangle = Math.acos(VectorUtil.cosAngle(refvec, rel.get(last)));
+ dist = drawCosine(svgp, proj, refvec, maxangle);
+ }
+ else {
+ dist = null;
+ }
+ if(dist != null) {
+ SVGUtil.addCSSClass(dist, DISTANCEFUNCTION);
+ layer.appendChild(dist);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ // Class for the distance markers
+ if(!svgp.getCSSClassManager().contains(KNNMARKER)) {
+ CSSClass cls = new CSSClass(this, KNNMARKER);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_DARKGREEN_VALUE);
+ cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
+ svgp.addCSSClassOrLogError(cls);
+ }
+ // Class for the distance function
+ if(!svgp.getCSSClassManager().contains(DISTANCEFUNCTION)) {
+ CSSClass cls = new CSSClass(this, DISTANCEFUNCTION);
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_RED_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT));
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ svgp.addCSSClassOrLogError(cls);
+ }
+ // Class for the distance label
+ if(!svgp.getCSSClassManager().contains(KNNDIST)) {
+ CSSClass cls = new CSSClass(this, KNNDIST);
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_BLACK_VALUE);
+ cls.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, style.getTextSize(StyleLibrary.PLOT));
+ cls.setStatement(SVGConstants.CSS_FONT_FAMILY_PROPERTY, style.getFontFamily(StyleLibrary.PLOT));
+ svgp.addCSSClassOrLogError(cls);
+ }
+ }
+
+ @Override
+ public void resultChanged(Result current) {
+ if(current instanceof SelectionResult) {
+ synchronizedRedraw();
+ return;
+ }
+ super.resultChanged(current);
+ }
+ }
+}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/MoveObjectsToolVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/MoveObjectsToolVisualization.java index 6b466a88..c2282bf1 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/MoveObjectsToolVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/MoveObjectsToolVisualization.java @@ -56,179 +56,179 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatter * @author Heidi Kolb
* @author Erich Schubert
*
- * @apiviz.has NumberVector oneway - - edits
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class MoveObjectsToolVisualization extends AbstractScatterplotVisualization implements DragListener {
+public class MoveObjectsToolVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
private static final String NAME = "Move Objects";
/**
- * CSS tag for our event rectangle
+ * Constructor
*/
- protected static final String CSS_ARROW = "moveArrow";
-
- /**
- * Element for the rectangle to add listeners
- */
- private Element etag;
-
- /**
- * Element to contain the drag arrow
- */
- private Element rtag;
-
- /**
- * Constructor.
- *
- * @param task Task
- */
- public MoveObjectsToolVisualization(VisualizationTask task) {
- super(task);
- incrementalRedraw();
+ public MoveObjectsToolVisualization() {
+ super();
}
@Override
- public void resultChanged(Result current) {
- if(sample == current) {
- synchronizedRedraw();
- }
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
@Override
- protected void redraw() {
- addCSSClasses(svgp);
-
- rtag = svgp.svgElement(SVGConstants.SVG_G_TAG);
- SVGUtil.addCSSClass(rtag, CSS_ARROW);
- layer.appendChild(rtag);
-
- DragableArea drag = new DragableArea(svgp, -0.6 * StyleLibrary.SCALE, -0.7 * StyleLibrary.SCALE, 1.3 * StyleLibrary.SCALE, 1.4 * StyleLibrary.SCALE, this);
- etag = drag.getElement();
- layer.appendChild(etag);
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<UpdatableDatabase> dbs = ResultUtil.filterResults(result, UpdatableDatabase.class);
+ if(dbs.isEmpty()) {
+ return;
+ }
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, p.getRelation(), p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_INTERACTIVE;
+ task.tool = true;
+ task.thumbnail = false;
+ task.noexport = true;
+ task.initDefaultVisibility(false);
+ // baseResult.getHierarchy().add(p.getRelation(), task);
+ baseResult.getHierarchy().add(p, task);
+ }
}
/**
- * Updates the objects with the given DBIDs It will be moved depending on the
- * given Vector
+ * Instance.
+ *
+ * @author Heidi Kolb
+ * @author Erich Schubert
*
- * @param dbids - DBIDs of the objects to move
- * @param movingVector - Vector for moving object
+ * @apiviz.has NumberVector oneway - - edits
*/
- // TODO: move to DatabaseUtil?
- private void updateDB(DBIDs dbids, Vector movingVector) {
- NumberVector<?, ?> nv = null;
- throw new AbortException("FIXME: INCOMPLETE TRANSITION");
- /*
- * database.accumulateDataStoreEvents();
- * Representation<DatabaseObjectMetadata> mrep =
- * database.getMetadataQuery(); for(DBID dbid : dbids) { NV obj =
- * database.get(dbid); // Copy metadata to keep DatabaseObjectMetadata meta
- * = mrep.get(dbid);
- *
- * Vector v = proj.projectDataToRenderSpace(obj); v.set(0, v.get(0) +
- * movingVector.get(0)); v.set(1, v.get(1) + movingVector.get(1)); NV nv =
- * proj.projectRenderToDataSpace(v, obj); nv.setID(obj.getID());
- *
- * try { database.delete(dbid); database.insert(new Pair<NV,
- * DatabaseObjectMetadata>(nv, meta)); } catch(UnableToComplyException e) {
- * de.lmu.ifi.dbs.elki.logging.LoggingUtil.exception(e); } }
- * database.flushDataStoreEvents();
+ public class Instance extends AbstractScatterplotVisualization implements DragListener {
+ /**
+ * CSS tag for our event rectangle
*/
- }
+ protected static final String CSS_ARROW = "moveArrow";
- /**
- * Delete the children of the element
- *
- * @param container SVG-Element
- */
- private void deleteChildren(Element container) {
- while(container.hasChildNodes()) {
- container.removeChild(container.getLastChild());
+ /**
+ * Element for the rectangle to add listeners
+ */
+ private Element etag;
+
+ /**
+ * Element to contain the drag arrow
+ */
+ private Element rtag;
+
+ /**
+ * Constructor.
+ *
+ * @param task Task
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ incrementalRedraw();
}
- }
- /**
- * Adds the required CSS-Classes
- *
- * @param svgp SVGPlot
- */
- private void addCSSClasses(SVGPlot svgp) {
- // Class for the rectangle to add eventListeners
- if(!svgp.getCSSClassManager().contains(CSS_ARROW)) {
- final CSSClass acls = new CSSClass(this, CSS_ARROW);
- final StyleLibrary style = context.getStyleLibrary();
- acls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, style.getColor(StyleLibrary.SELECTION_ACTIVE));
- acls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.SELECTION_ACTIVE));
- acls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
- svgp.addCSSClassOrLogError(acls);
+ @Override
+ public void resultChanged(Result current) {
+ if(sample == current) {
+ synchronizedRedraw();
+ }
}
- }
- @Override
- public boolean startDrag(SVGPoint startPoint, Event evt) {
- return true;
- }
+ @Override
+ protected void redraw() {
+ addCSSClasses(svgp);
- @Override
- public boolean duringDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
- deleteChildren(rtag);
- rtag.appendChild(svgp.svgLine(startPoint.getX(), startPoint.getY(), dragPoint.getX(), dragPoint.getY()));
- return true;
- }
+ rtag = svgp.svgElement(SVGConstants.SVG_G_TAG);
+ SVGUtil.addCSSClass(rtag, CSS_ARROW);
+ layer.appendChild(rtag);
- @Override
- public boolean endDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
- Vector movingVector = new Vector(2);
- movingVector.set(0, dragPoint.getX() - startPoint.getX());
- movingVector.set(1, dragPoint.getY() - startPoint.getY());
- if(context.getSelection() != null) {
- updateDB(context.getSelection().getSelectedIds(), movingVector);
+ DragableArea drag = new DragableArea(svgp, -0.6 * StyleLibrary.SCALE, -0.7 * StyleLibrary.SCALE, 1.3 * StyleLibrary.SCALE, 1.4 * StyleLibrary.SCALE, this);
+ etag = drag.getElement();
+ layer.appendChild(etag);
}
- deleteChildren(rtag);
- return true;
- }
- /**
- * Factory for tool visualizations for changing objects in the database
- *
- * @author Heidi Kolb
- * @author Erich Schubert
- *
- * @apiviz.stereotype factory
- * @apiviz.uses MoveObjectsToolVisualization oneway - - «create»
- */
- public static class Factory extends AbstractVisFactory {
/**
- * Constructor
+ * Updates the objects with the given DBIDs It will be moved depending on
+ * the given Vector
+ *
+ * @param dbids - DBIDs of the objects to move
+ * @param movingVector - Vector for moving object
*/
- public Factory() {
- super();
+ // TODO: move to DatabaseUtil?
+ private void updateDB(DBIDs dbids, Vector movingVector) {
+ NumberVector<?> nv = null;
+ throw new AbortException("FIXME: INCOMPLETE TRANSITION");
+ /*
+ * database.accumulateDataStoreEvents();
+ * Representation<DatabaseObjectMetadata> mrep =
+ * database.getMetadataQuery(); for(DBID dbid : dbids) { NV obj =
+ * database.get(dbid); // Copy metadata to keep DatabaseObjectMetadata
+ * meta = mrep.get(dbid);
+ *
+ * Vector v = proj.projectDataToRenderSpace(obj); v.set(0, v.get(0) +
+ * movingVector.get(0)); v.set(1, v.get(1) + movingVector.get(1)); NV nv =
+ * proj.projectRenderToDataSpace(v, obj); nv.setID(obj.getID());
+ *
+ * try { database.delete(dbid); database.insert(new Pair<NV,
+ * DatabaseObjectMetadata>(nv, meta)); } catch(UnableToComplyException e)
+ * { de.lmu.ifi.dbs.elki.logging.LoggingUtil.exception(e); } }
+ * database.flushDataStoreEvents();
+ */
+ }
+
+ /**
+ * Delete the children of the element
+ *
+ * @param container SVG-Element
+ */
+ private void deleteChildren(Element container) {
+ while(container.hasChildNodes()) {
+ container.removeChild(container.getLastChild());
+ }
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVGPlot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ // Class for the rectangle to add eventListeners
+ if(!svgp.getCSSClassManager().contains(CSS_ARROW)) {
+ final CSSClass acls = new CSSClass(this, CSS_ARROW);
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ acls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, style.getColor(StyleLibrary.SELECTION_ACTIVE));
+ acls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.SELECTION_ACTIVE));
+ acls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ svgp.addCSSClassOrLogError(acls);
+ }
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new MoveObjectsToolVisualization(task);
+ public boolean startDrag(SVGPoint startPoint, Event evt) {
+ return true;
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<UpdatableDatabase> dbs = ResultUtil.filterResults(result, UpdatableDatabase.class);
- if(dbs.isEmpty()) {
- return;
- }
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : ps) {
- final VisualizationTask task = new VisualizationTask(NAME, p.getRelation(), p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE);
- task.put(VisualizationTask.META_TOOL, true);
- task.put(VisualizationTask.META_NOTHUMB, true);
- task.put(VisualizationTask.META_NOEXPORT, true);
- task.put(VisualizationTask.META_VISIBLE_DEFAULT, false);
- // baseResult.getHierarchy().add(p.getRelation(), task);
- baseResult.getHierarchy().add(p, task);
+ public boolean duringDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
+ deleteChildren(rtag);
+ rtag.appendChild(svgp.svgLine(startPoint.getX(), startPoint.getY(), dragPoint.getX(), dragPoint.getY()));
+ return true;
+ }
+
+ @Override
+ public boolean endDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
+ Vector movingVector = new Vector(2);
+ movingVector.set(0, dragPoint.getX() - startPoint.getX());
+ movingVector.set(1, dragPoint.getY() - startPoint.getY());
+ if(context.getSelection() != null) {
+ updateDB(context.getSelection().getSelectedIds(), movingVector);
}
+ deleteChildren(rtag);
+ return true;
}
}
}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionConvexHullVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionConvexHullVisualization.java index 8a28ab3e..f3d41e08 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionConvexHullVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionConvexHullVisualization.java @@ -58,114 +58,115 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualizati *
* @author Robert Rödler
*
- * @apiviz.has SelectionResult oneway - - visualizes
- * @apiviz.has DBIDSelection oneway - - visualizes
- * @apiviz.uses GrahamScanConvexHull2D
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class SelectionConvexHullVisualization extends AbstractScatterplotVisualization implements DataStoreListener {
+public class SelectionConvexHullVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
private static final String NAME = "Convex Hull of Selection";
/**
- * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc.
+ * Constructor
*/
- public static final String SELECTEDHULL = "selectionConvexHull";
-
- /**
- * Constructor.
- *
- * @param task Task
- */
- public SelectionConvexHullVisualization(VisualizationTask task) {
- super(task);
- context.addResultListener(this);
- context.addDataStoreListener(this);
- incrementalRedraw();
+ public SelectionConvexHullVisualization() {
+ super();
+ thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION;
}
@Override
- protected void redraw() {
- addCSSClasses(svgp);
- DBIDSelection selContext = context.getSelection();
- if(selContext != null) {
- DBIDs selection = selContext.getSelectedIds();
- GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D();
- for(DBIDIter iter = selection.iter(); iter.valid(); iter.advance()) {
- try {
- hull.add(new Vector(proj.fastProjectDataToRenderSpace(rel.get(iter))));
- }
- catch(ObjectNotFoundException e) {
- // ignore
- }
- }
- Polygon chres = hull.getHull();
- if(chres != null && chres.size() >= 3) {
- SVGPath path = new SVGPath(chres);
-
- Element selHull = path.makeElement(svgp);
- SVGUtil.addCSSClass(selHull, SELECTEDHULL);
- // TODO: use relative selection size for opacity?
- layer.appendChild(selHull);
- }
- }
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
- /**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- private void addCSSClasses(SVGPlot svgp) {
- // Class for the dot markers
- if(!svgp.getCSSClassManager().contains(SELECTEDHULL)) {
- CSSClass cls = new CSSClass(this, SELECTEDHULL);
- // cls = new CSSClass(this, CONVEXHULL);
- cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, context.getStyleLibrary().getColor(StyleLibrary.SELECTION));
- cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.SELECTION));
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, context.getStyleLibrary().getColor(StyleLibrary.SELECTION));
- cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, ".25");
- cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
- cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
- svgp.addCSSClassOrLogError(cls);
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
+ for(SelectionResult selres : selectionResults) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA - 2;
+ baseResult.getHierarchy().add(selres, task);
+ baseResult.getHierarchy().add(p, task);
+ }
}
}
/**
- * Factory for visualizers to generate an SVG-Element containing the convex
- * hull of the selected points
+ * Instance
*
* @author Robert Rödler
*
- * @apiviz.stereotype factory
- * @apiviz.uses SelectionConvexHullVisualization oneway - - «create»
+ * @apiviz.has SelectionResult oneway - - visualizes
+ * @apiviz.has DBIDSelection oneway - - visualizes
+ * @apiviz.uses GrahamScanConvexHull2D
*/
- public static class Factory extends AbstractVisFactory {
+ public class Instance extends AbstractScatterplotVisualization implements DataStoreListener {
/**
- * Constructor
+ * Generic tag to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
*/
- public Factory() {
- super();
- thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION;
- }
+ public static final String SELECTEDHULL = "selectionConvexHull";
- @Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new SelectionConvexHullVisualization(task);
+ /**
+ * Constructor.
+ *
+ * @param task Task
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ context.addResultListener(this);
+ context.addDataStoreListener(this);
+ incrementalRedraw();
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
- for(SelectionResult selres : selectionResults) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : ps) {
- final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA - 2);
- baseResult.getHierarchy().add(selres, task);
- baseResult.getHierarchy().add(p, task);
+ protected void redraw() {
+ addCSSClasses(svgp);
+ DBIDSelection selContext = context.getSelection();
+ if(selContext != null) {
+ DBIDs selection = selContext.getSelectedIds();
+ GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D();
+ for(DBIDIter iter = selection.iter(); iter.valid(); iter.advance()) {
+ try {
+ hull.add(new Vector(proj.fastProjectDataToRenderSpace(rel.get(iter))));
+ }
+ catch(ObjectNotFoundException e) {
+ // ignore
+ }
}
+ Polygon chres = hull.getHull();
+ if(chres != null && chres.size() >= 3) {
+ SVGPath path = new SVGPath(chres);
+
+ Element selHull = path.makeElement(svgp);
+ SVGUtil.addCSSClass(selHull, SELECTEDHULL);
+ // TODO: use relative selection size for opacity?
+ layer.appendChild(selHull);
+ }
+ }
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ // Class for the dot markers
+ if(!svgp.getCSSClassManager().contains(SELECTEDHULL)) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ CSSClass cls = new CSSClass(this, SELECTEDHULL);
+ // cls = new CSSClass(this, CONVEXHULL);
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, style.getColor(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, ".25");
+ cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ svgp.addCSSClassOrLogError(cls);
}
}
}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionCubeVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionCubeVisualization.java index 5151e81e..6d4c14ab 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionCubeVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionCubeVisualization.java @@ -28,13 +28,13 @@ import java.util.Collection; import org.apache.batik.util.SVGConstants;
import org.w3c.dom.Element;
+import de.lmu.ifi.dbs.elki.database.relation.RelationUtil;
import de.lmu.ifi.dbs.elki.result.DBIDSelection;
import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
import de.lmu.ifi.dbs.elki.result.RangeSelection;
import de.lmu.ifi.dbs.elki.result.Result;
import de.lmu.ifi.dbs.elki.result.ResultUtil;
import de.lmu.ifi.dbs.elki.result.SelectionResult;
-import de.lmu.ifi.dbs.elki.utilities.DatabaseUtil;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization;
@@ -59,141 +59,179 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualizati *
* @author Heidi Kolb
*
- * @apiviz.has SelectionResult oneway - - visualizes
- * @apiviz.has RangeSelection oneway - - visualizes
- * @apiviz.uses SVGHyperCube
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class SelectionCubeVisualization extends AbstractScatterplotVisualization {
+public class SelectionCubeVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
private static final String NAME = "Selection Range";
/**
- * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc.
+ * Settings
*/
- public static final String MARKER = "selectionCubeMarker";
+ protected Parameterizer settings;
/**
- * CSS class for the filled cube
- */
- public static final String CSS_CUBE = "selectionCube";
-
- /**
- * CSS class for the cube frame
- */
- public static final String CSS_CUBEFRAME = "selectionCubeFrame";
-
- /**
- * Fill parameter.
+ * Constructor.
+ *
+ * @param settings Settings
*/
- protected boolean nofill = false;
-
- public SelectionCubeVisualization(VisualizationTask task, boolean nofill) {
- super(task);
- this.nofill = nofill;
- addCSSClasses(svgp);
- context.addResultListener(this);
- incrementalRedraw();
+ public SelectionCubeVisualization(Parameterizer settings) {
+ super();
+ this.settings = settings;
+ thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION;
}
@Override
- public void destroy() {
- context.removeResultListener(this);
- super.destroy();
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
- /**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- private void addCSSClasses(SVGPlot svgp) {
- final StyleLibrary style = context.getStyleLibrary();
- // Class for the cube
- if(!svgp.getCSSClassManager().contains(CSS_CUBE)) {
- CSSClass cls = new CSSClass(this, CSS_CUBE);
- cls.setStatement(SVGConstants.CSS_STROKE_VALUE, style.getColor(StyleLibrary.SELECTION));
- cls.setStatement(SVGConstants.CSS_STROKE_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
- cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT));
- cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
- cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
- if(nofill) {
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
- }
- else {
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION));
- cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
+ for(SelectionResult selres : selectionResults) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA - 2;
+ baseResult.getHierarchy().add(selres, task);
+ baseResult.getHierarchy().add(p, task);
}
- svgp.addCSSClassOrLogError(cls);
- }
- // Class for the cube frame
- if(!svgp.getCSSClassManager().contains(CSS_CUBEFRAME)) {
- CSSClass cls = new CSSClass(this, CSS_CUBEFRAME);
- cls.setStatement(SVGConstants.CSS_STROKE_VALUE, style.getColor(StyleLibrary.SELECTION));
- cls.setStatement(SVGConstants.CSS_STROKE_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
- cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.SELECTION));
-
- svgp.addCSSClassOrLogError(cls);
}
}
/**
- * Generates a cube and a frame depending on the selection stored in the
- * context
+ * Instance.
+ *
+ * @author Heidi Kolb
*
- * @param svgp The plot
- * @param proj The projection
+ * @apiviz.has SelectionResult oneway - - visualizes
+ * @apiviz.has RangeSelection oneway - - visualizes
+ * @apiviz.uses SVGHyperCube
*/
- private void setSVGRect(SVGPlot svgp, Projection2D proj) {
- DBIDSelection selContext = context.getSelection();
- if(selContext instanceof RangeSelection) {
- DoubleDoublePair[] ranges = ((RangeSelection) selContext).getRanges();
- int dim = DatabaseUtil.dimensionality(rel);
+ public class Instance extends AbstractScatterplotVisualization {
+ /**
+ * Generic tag to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
+ */
+ public static final String MARKER = "selectionCubeMarker";
+
+ /**
+ * CSS class for the filled cube
+ */
+ public static final String CSS_CUBE = "selectionCube";
+
+ /**
+ * CSS class for the cube frame
+ */
+ public static final String CSS_CUBEFRAME = "selectionCubeFrame";
+
+ public Instance(VisualizationTask task) {
+ super(task);
+ addCSSClasses(svgp);
+ context.addResultListener(this);
+ incrementalRedraw();
+ }
- double[] min = new double[dim];
- double[] max = new double[dim];
- for(int d = 0; d < dim; d++) {
- if(ranges != null && ranges[d] != null) {
- min[d] = ranges[d].first;
- max[d] = ranges[d].second;
+ @Override
+ public void destroy() {
+ context.removeResultListener(this);
+ super.destroy();
+ }
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ // Class for the cube
+ if(!svgp.getCSSClassManager().contains(CSS_CUBE)) {
+ CSSClass cls = new CSSClass(this, CSS_CUBE);
+ cls.setStatement(SVGConstants.CSS_STROKE_VALUE, style.getColor(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_STROKE_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT));
+ cls.setStatement(SVGConstants.CSS_STROKE_LINECAP_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ cls.setStatement(SVGConstants.CSS_STROKE_LINEJOIN_PROPERTY, SVGConstants.CSS_ROUND_VALUE);
+ if(settings.nofill) {
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
}
else {
- min[d] = proj.getScale(d).getMin();
- max[d] = proj.getScale(d).getMax();
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
}
+ svgp.addCSSClassOrLogError(cls);
}
- if(nofill) {
- Element r = SVGHyperCube.drawFrame(svgp, proj, min, max);
- SVGUtil.setCSSClass(r, CSS_CUBEFRAME);
- layer.appendChild(r);
- }
- else {
- Element r = SVGHyperCube.drawFilled(svgp, CSS_CUBE, proj, min, max);
- layer.appendChild(r);
+ // Class for the cube frame
+ if(!svgp.getCSSClassManager().contains(CSS_CUBEFRAME)) {
+ CSSClass cls = new CSSClass(this, CSS_CUBEFRAME);
+ cls.setStatement(SVGConstants.CSS_STROKE_VALUE, style.getColor(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_STROKE_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.SELECTION));
+
+ svgp.addCSSClassOrLogError(cls);
}
+ }
+
+ /**
+ * Generates a cube and a frame depending on the selection stored in the
+ * context
+ *
+ * @param svgp The plot
+ * @param proj The projection
+ */
+ private void setSVGRect(SVGPlot svgp, Projection2D proj) {
+ DBIDSelection selContext = context.getSelection();
+ if(selContext instanceof RangeSelection) {
+ DoubleDoublePair[] ranges = ((RangeSelection) selContext).getRanges();
+ int dim = RelationUtil.dimensionality(rel);
+
+ double[] min = new double[dim];
+ double[] max = new double[dim];
+ for(int d = 0; d < dim; d++) {
+ if(ranges != null && ranges[d] != null) {
+ min[d] = ranges[d].first;
+ max[d] = ranges[d].second;
+ }
+ else {
+ min[d] = proj.getScale(d).getMin();
+ max[d] = proj.getScale(d).getMax();
+ }
+ }
+ if(settings.nofill) {
+ Element r = SVGHyperCube.drawFrame(svgp, proj, min, max);
+ SVGUtil.setCSSClass(r, CSS_CUBEFRAME);
+ layer.appendChild(r);
+ }
+ else {
+ Element r = SVGHyperCube.drawFilled(svgp, CSS_CUBE, proj, min, max);
+ layer.appendChild(r);
+ }
+ }
}
- }
- @Override
- protected void redraw() {
- DBIDSelection selContext = context.getSelection();
- if(selContext != null && selContext instanceof RangeSelection) {
- setSVGRect(svgp, proj);
+ @Override
+ protected void redraw() {
+ DBIDSelection selContext = context.getSelection();
+ if(selContext instanceof RangeSelection) {
+ setSVGRect(svgp, proj);
+ }
}
}
/**
- * Factory for visualizers to generate an SVG-Element containing a cube as
- * marker representing the selected range for each dimension
+ * Parameterization class.
*
- * @author Heidi Kolb
+ * @author Erich Schubert
*
- * @apiviz.stereotype factory
- * @apiviz.uses SelectionCubeVisualization oneway - - «create»
+ * @apiviz.exclude
*/
- public static class Factory extends AbstractVisFactory {
+ public static class Parameterizer extends AbstractParameterizer {
/**
* Flag for half-transparent filling of selection cubes.
*
@@ -201,66 +239,25 @@ public class SelectionCubeVisualization extends AbstractScatterplotVisualization * Key: {@code -selectionrange.nofill}
* </p>
*/
- public static final OptionID NOFILL_ID = OptionID.getOrCreateOptionID("selectionrange.nofill", "Use wireframe style for selection ranges.");
+ public static final OptionID NOFILL_ID = new OptionID("selectionrange.nofill", "Use wireframe style for selection ranges.");
/**
* Fill parameter.
*/
- protected boolean nofill = false;
-
- /**
- * Constructor.
- *
- * @param nofill
- */
- public Factory(boolean nofill) {
- super();
- this.nofill = nofill;
- thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION;
- }
+ protected boolean nofill;
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new SelectionCubeVisualization(task, nofill);
- }
-
- @Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
- for(SelectionResult selres : selectionResults) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : ps) {
- final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA - 2);
- baseResult.getHierarchy().add(selres, task);
- baseResult.getHierarchy().add(p, task);
- }
+ protected void makeOptions(Parameterization config) {
+ super.makeOptions(config);
+ Flag nofillF = new Flag(NOFILL_ID);
+ if(config.grab(nofillF)) {
+ nofill = nofillF.isTrue();
}
}
- /**
- * Parameterization class.
- *
- * @author Erich Schubert
- *
- * @apiviz.exclude
- */
- public static class Parameterizer extends AbstractParameterizer {
- protected boolean nofill;
-
- @Override
- protected void makeOptions(Parameterization config) {
- super.makeOptions(config);
- Flag nofillF = new Flag(NOFILL_ID);
- if(config.grab(nofillF)) {
- nofill = nofillF.getValue();
- }
- }
-
- @Override
- protected Factory makeInstance() {
- return new Factory(nofill);
- }
+ @Override
+ protected SelectionCubeVisualization makeInstance() {
+ return new SelectionCubeVisualization(this);
}
}
}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionDotVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionDotVisualization.java index 1dc5ce13..b70755c4 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionDotVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionDotVisualization.java @@ -54,110 +54,111 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs.ThumbnailVisualizati *
* @author Heidi Kolb
*
- * @apiviz.has SelectionResult oneway - - visualizes
- * @apiviz.has DBIDSelection oneway - - visualizes
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class SelectionDotVisualization extends AbstractScatterplotVisualization implements DataStoreListener {
+public class SelectionDotVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
private static final String NAME = "Selection";
/**
- * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc.
+ * Constructor
*/
- public static final String MARKER = "selectionDotMarker";
-
- /**
- * Constructor.
- *
- * @param task Task
- */
- public SelectionDotVisualization(VisualizationTask task) {
- super(task);
- context.addResultListener(this);
- context.addDataStoreListener(this);
- incrementalRedraw();
+ public SelectionDotVisualization() {
+ super();
+ thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION;
}
@Override
- public void destroy() {
- context.removeResultListener(this);
- super.destroy();
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
@Override
- protected void redraw() {
- addCSSClasses(svgp);
- final double size = context.getStyleLibrary().getSize(StyleLibrary.SELECTION);
- DBIDSelection selContext = context.getSelection();
- if(selContext != null) {
- DBIDs selection = selContext.getSelectedIds();
- for(DBIDIter iter = selection.iter(); iter.valid(); iter.advance()) {
- try {
- double[] v = proj.fastProjectDataToRenderSpace(rel.get(iter));
- Element dot = svgp.svgCircle(v[0], v[1], size);
- SVGUtil.addCSSClass(dot, MARKER);
- layer.appendChild(dot);
- }
- catch(ObjectNotFoundException e) {
- // ignore
- }
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
+ for(SelectionResult selres : selectionResults) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA - 1;
+ baseResult.getHierarchy().add(selres, task);
+ baseResult.getHierarchy().add(p, task);
}
}
}
/**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- private void addCSSClasses(SVGPlot svgp) {
- // Class for the dot markers
- if(!svgp.getCSSClassManager().contains(MARKER)) {
- CSSClass cls = new CSSClass(this, MARKER);
- final StyleLibrary style = context.getStyleLibrary();
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION));
- cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
- svgp.addCSSClassOrLogError(cls);
- }
- }
-
- /**
- * Factory for visualizers to generate an SVG-Element containing dots as
- * markers representing the selected Database's objects.
+ * Instance
*
* @author Heidi Kolb
*
- * @apiviz.stereotype factory
- * @apiviz.uses SelectionDotVisualization oneway - - «create»
+ * @apiviz.has SelectionResult oneway - - visualizes
+ * @apiviz.has DBIDSelection oneway - - visualizes
*/
- public static class Factory extends AbstractVisFactory {
+ public class Instance extends AbstractScatterplotVisualization implements DataStoreListener {
+ /**
+ * Generic tag to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
+ */
+ public static final String MARKER = "selectionDotMarker";
+
/**
- * Constructor
+ * Constructor.
+ *
+ * @param task Task
*/
- public Factory() {
- super();
- thumbmask |= ThumbnailVisualization.ON_DATA | ThumbnailVisualization.ON_SELECTION;
+ public Instance(VisualizationTask task) {
+ super(task);
+ context.addResultListener(this);
+ context.addDataStoreListener(this);
+ incrementalRedraw();
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new SelectionDotVisualization(task);
+ public void destroy() {
+ context.removeResultListener(this);
+ super.destroy();
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
- for(SelectionResult selres : selectionResults) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : ps) {
- final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA - 1);
- baseResult.getHierarchy().add(selres, task);
- baseResult.getHierarchy().add(p, task);
+ protected void redraw() {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ addCSSClasses(svgp);
+ final double size = style.getSize(StyleLibrary.SELECTION);
+ DBIDSelection selContext = context.getSelection();
+ if(selContext != null) {
+ DBIDs selection = selContext.getSelectedIds();
+ for(DBIDIter iter = selection.iter(); iter.valid(); iter.advance()) {
+ try {
+ double[] v = proj.fastProjectDataToRenderSpace(rel.get(iter));
+ Element dot = svgp.svgCircle(v[0], v[1], size);
+ SVGUtil.addCSSClass(dot, MARKER);
+ layer.appendChild(dot);
+ }
+ catch(ObjectNotFoundException e) {
+ // ignore
+ }
}
}
}
+
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ private void addCSSClasses(SVGPlot svgp) {
+ // Class for the dot markers
+ if(!svgp.getCSSClassManager().contains(MARKER)) {
+ CSSClass cls = new CSSClass(this, MARKER);
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION));
+ cls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION));
+ svgp.addCSSClassOrLogError(cls);
+ }
+ }
}
}
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolCubeVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolCubeVisualization.java index bc16fd83..f088d219 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolCubeVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolCubeVisualization.java @@ -35,6 +35,7 @@ import de.lmu.ifi.dbs.elki.data.NumberVector; import de.lmu.ifi.dbs.elki.database.ids.DBIDIter;
import de.lmu.ifi.dbs.elki.database.ids.DBIDUtil;
import de.lmu.ifi.dbs.elki.database.ids.ModifiableDBIDs;
+import de.lmu.ifi.dbs.elki.database.relation.RelationUtil;
import de.lmu.ifi.dbs.elki.logging.Logging;
import de.lmu.ifi.dbs.elki.result.DBIDSelection;
import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
@@ -42,7 +43,6 @@ import de.lmu.ifi.dbs.elki.result.RangeSelection; import de.lmu.ifi.dbs.elki.result.Result;
import de.lmu.ifi.dbs.elki.result.ResultUtil;
import de.lmu.ifi.dbs.elki.result.SelectionResult;
-import de.lmu.ifi.dbs.elki.utilities.DatabaseUtil;
import de.lmu.ifi.dbs.elki.utilities.pairs.DoubleDoublePair;
import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
import de.lmu.ifi.dbs.elki.visualization.batikutil.DragableArea;
@@ -57,18 +57,18 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatterplotVisualization;
/**
- * Tool-Visualization for the tool to select ranges
+ * Tool-Visualization for the tool to select ranges.
*
* @author Heidi Kolb
*
- * @apiviz.has SelectionResult oneway - - updates
- * @apiviz.has RangeSelection oneway - - updates
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance oneway - - «create»
*/
-public class SelectionToolCubeVisualization extends AbstractScatterplotVisualization implements DragableArea.DragListener {
+public class SelectionToolCubeVisualization extends AbstractVisFactory {
/**
* The logger for this class.
*/
- protected static final Logging logger = Logging.getLogger(SelectionToolCubeVisualization.class);
+ private static final Logging LOG = Logging.getLogger(SelectionToolCubeVisualization.class);
/**
* A short name characterizing this Visualizer.
@@ -76,219 +76,218 @@ public class SelectionToolCubeVisualization extends AbstractScatterplotVisualiza private static final String NAME = "Range Selection";
/**
- * Generic tag to indicate the type of element. Used in IDs, CSS-Classes etc.
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}.
*/
- private static final String CSS_RANGEMARKER = "selectionRangeMarker";
-
- /**
- * Dimension
- */
- private int dim;
-
- /**
- * Element for selection rectangle
- */
- private Element rtag;
-
- /**
- * Element for the rectangle to add listeners
- */
- private Element etag;
-
- /**
- * Constructor.
- *
- * @param task Task
- */
- public SelectionToolCubeVisualization(VisualizationTask task) {
- super(task);
- this.dim = DatabaseUtil.dimensionality(rel);
- incrementalRedraw();
+ public SelectionToolCubeVisualization() {
+ super();
}
@Override
- protected void redraw() {
- addCSSClasses(svgp);
-
- // rtag: tag for the selected rect
- rtag = svgp.svgElement(SVGConstants.SVG_G_TAG);
- SVGUtil.addCSSClass(rtag, CSS_RANGEMARKER);
- layer.appendChild(rtag);
-
- // etag: sensitive area
- DragableArea drag = new DragableArea(svgp, -0.6 * StyleLibrary.SCALE, -0.7 * StyleLibrary.SCALE, 1.3 * StyleLibrary.SCALE, 1.4 * StyleLibrary.SCALE, this);
- etag = drag.getElement();
- layer.appendChild(etag);
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
}
- /**
- * Delete the children of the element
- *
- * @param container SVG-Element
- */
- private void deleteChildren(Element container) {
- while(container.hasChildNodes()) {
- container.removeChild(container.getLastChild());
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
+ for (SelectionResult selres : selectionResults) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for (ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_INTERACTIVE;
+ task.tool = true;
+ task.thumbnail = false;
+ task.noexport = true;
+ task.initDefaultVisibility(false);
+ baseResult.getHierarchy().add(selres, task);
+ baseResult.getHierarchy().add(p, task);
+ }
}
}
/**
- * Set the selected ranges and the mask for the actual dimensions in the
- * context
+ * Instance.
*
- * @param x1 x-value of the first dimension
- * @param x2 x-value of the second dimension
- * @param y1 y-value of the first dimension
- * @param y2 y-value of the second dimension
+ * @author Heidi Kolb
+ *
+ * @apiviz.has SelectionResult oneway - - updates
+ * @apiviz.has RangeSelection oneway - - updates
*/
- private void updateSelectionRectKoordinates(double x1, double x2, double y1, double y2, DoubleDoublePair[] ranges) {
- BitSet actDim = proj.getVisibleDimensions2D();
- double[] v1 = new double[dim];
- double[] v2 = new double[dim];
- v1[0] = x1;
- v1[1] = y1;
- v2[0] = x2;
- v2[1] = y2;
-
- double[] nv1 = proj.fastProjectRenderToDataSpace(v1);
- double[] nv2 = proj.fastProjectRenderToDataSpace(v2);
-
- for(int d = actDim.nextSetBit(0); d >= 0; d = actDim.nextSetBit(d + 1)) {
- ranges[d] = new DoubleDoublePair(Math.min(nv1[d], nv2[d]), Math.max(nv1[d], nv2[d]));
- }
- }
+ public class Instance extends AbstractScatterplotVisualization implements DragableArea.DragListener {
+ /**
+ * Generic tag to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
+ */
+ private static final String CSS_RANGEMARKER = "selectionRangeMarker";
- @Override
- public boolean startDrag(SVGPoint startPoint, Event evt) {
- return true;
- }
+ /**
+ * Dimension.
+ */
+ private int dim;
- @Override
- public boolean duringDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
- deleteChildren(rtag);
- double x = Math.min(startPoint.getX(), dragPoint.getX());
- double y = Math.min(startPoint.getY(), dragPoint.getY());
- double width = Math.abs(startPoint.getX() - dragPoint.getX());
- double height = Math.abs(startPoint.getY() - dragPoint.getY());
- rtag.appendChild(svgp.svgRect(x, y, width, height));
- return true;
- }
+ /**
+ * Element for selection rectangle.
+ */
+ private Element rtag;
- @Override
- public boolean endDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
- deleteChildren(rtag);
- if(startPoint.getX() != dragPoint.getX() || startPoint.getY() != dragPoint.getY()) {
- updateSelection(proj, startPoint, dragPoint);
- }
- return true;
- }
+ /**
+ * Element for the rectangle to add listeners.
+ */
+ private Element etag;
- /**
- * Update the selection in the context.
- *
- * @param proj The projection
- * @param p1 First Point of the selected rectangle
- * @param p2 Second Point of the selected rectangle
- */
- private void updateSelection(Projection proj, SVGPoint p1, SVGPoint p2) {
- if(p1 == null || p2 == null) {
- logger.warning("no rect selected: p1: " + p1 + " p2: " + p2);
- return;
+ /**
+ * Constructor.
+ *
+ * @param task Task
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.dim = RelationUtil.dimensionality(rel);
+ incrementalRedraw();
}
- DBIDSelection selContext = context.getSelection();
- ModifiableDBIDs selection;
- if(selContext != null) {
- selection = DBIDUtil.newHashSet(selContext.getSelectedIds());
- }
- else {
- selection = DBIDUtil.newHashSet();
+ @Override
+ protected void redraw() {
+ addCSSClasses(svgp);
+
+ // rtag: tag for the selected rect
+ rtag = svgp.svgElement(SVGConstants.SVG_G_TAG);
+ SVGUtil.addCSSClass(rtag, CSS_RANGEMARKER);
+ layer.appendChild(rtag);
+
+ // etag: sensitive area
+ DragableArea drag = new DragableArea(svgp, -0.6 * StyleLibrary.SCALE, -0.7 * StyleLibrary.SCALE, 1.3 * StyleLibrary.SCALE, 1.4 * StyleLibrary.SCALE, this);
+ etag = drag.getElement();
+ layer.appendChild(etag);
}
- DoubleDoublePair[] ranges;
-
- double x1 = Math.min(p1.getX(), p2.getX());
- double x2 = Math.max(p1.getX(), p2.getX());
- double y1 = Math.max(p1.getY(), p2.getY());
- double y2 = Math.min(p1.getY(), p2.getY());
- if(selContext instanceof RangeSelection) {
- ranges = ((RangeSelection) selContext).getRanges();
- }
- else {
- ranges = new DoubleDoublePair[dim];
- }
- updateSelectionRectKoordinates(x1, x2, y1, y2, ranges);
-
- selection.clear();
- candidates: for(DBIDIter iditer = rel.iterDBIDs(); iditer.valid(); iditer.advance()) {
- NumberVector<?, ?> dbTupel = rel.get(iditer);
- for(int i = 0; i < dim; i++) {
- if(ranges != null && ranges[i] != null) {
- if(dbTupel.doubleValue(i + 1) < ranges[i].first || dbTupel.doubleValue(i + 1) > ranges[i].second) {
- continue candidates;
- }
- }
+ /**
+ * Delete the children of the element.
+ *
+ * @param container SVG-Element
+ */
+ private void deleteChildren(Element container) {
+ while (container.hasChildNodes()) {
+ container.removeChild(container.getLastChild());
}
- selection.add(iditer);
}
- context.setSelection(new RangeSelection(selection, ranges));
- }
- /**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- protected void addCSSClasses(SVGPlot svgp) {
- // Class for the range marking
- if(!svgp.getCSSClassManager().contains(CSS_RANGEMARKER)) {
- final CSSClass rcls = new CSSClass(this, CSS_RANGEMARKER);
- final StyleLibrary style = context.getStyleLibrary();
- rcls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION_ACTIVE));
- rcls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION_ACTIVE));
- svgp.addCSSClassOrLogError(rcls);
- }
- }
-
- /**
- * Factory for tool visualizations for selecting ranges and the inclosed
- * objects
- *
- * @author Heidi Kolb
- *
- * @apiviz.stereotype factory
- * @apiviz.uses SelectionToolCubeVisualization oneway - - «create»
- */
- public static class Factory extends AbstractVisFactory {
/**
- * Constructor, adhering to
- * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ * Set the selected ranges and the mask for the actual dimensions in the
+ * context.
+ *
+ * @param x1 x-value of the first dimension
+ * @param x2 x-value of the second dimension
+ * @param y1 y-value of the first dimension
+ * @param y2 y-value of the second dimension
+ * @param ranges Ranges to update
*/
- public Factory() {
- super();
+ private void updateSelectionRectKoordinates(double x1, double x2, double y1, double y2, DoubleDoublePair[] ranges) {
+ BitSet actDim = proj.getVisibleDimensions2D();
+ double[] v1 = new double[dim];
+ double[] v2 = new double[dim];
+ v1[0] = x1;
+ v1[1] = y1;
+ v2[0] = x2;
+ v2[1] = y2;
+
+ double[] nv1 = proj.fastProjectRenderToDataSpace(v1);
+ double[] nv2 = proj.fastProjectRenderToDataSpace(v2);
+
+ for (int d = actDim.nextSetBit(0); d >= 0; d = actDim.nextSetBit(d + 1)) {
+ ranges[d] = new DoubleDoublePair(Math.min(nv1[d], nv2[d]), Math.max(nv1[d], nv2[d]));
+ }
+ }
+
+ @Override
+ public boolean startDrag(SVGPoint startPoint, Event evt) {
+ return true;
}
@Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new SelectionToolCubeVisualization(task);
+ public boolean duringDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
+ deleteChildren(rtag);
+ double x = Math.min(startPoint.getX(), dragPoint.getX());
+ double y = Math.min(startPoint.getY(), dragPoint.getY());
+ double width = Math.abs(startPoint.getX() - dragPoint.getX());
+ double height = Math.abs(startPoint.getY() - dragPoint.getY());
+ rtag.appendChild(svgp.svgRect(x, y, width, height));
+ return true;
}
@Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
- for(SelectionResult selres : selectionResults) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : ps) {
- final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE);
- task.put(VisualizationTask.META_TOOL, true);
- task.put(VisualizationTask.META_NOTHUMB, true);
- task.put(VisualizationTask.META_NOEXPORT, true);
- task.put(VisualizationTask.META_VISIBLE_DEFAULT, false);
- baseResult.getHierarchy().add(selres, task);
- baseResult.getHierarchy().add(p, task);
+ public boolean endDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
+ deleteChildren(rtag);
+ if (startPoint.getX() != dragPoint.getX() || startPoint.getY() != dragPoint.getY()) {
+ updateSelection(proj, startPoint, dragPoint);
+ }
+ return true;
+ }
+
+ /**
+ * Update the selection in the context.
+ *
+ * @param proj The projection
+ * @param p1 First Point of the selected rectangle
+ * @param p2 Second Point of the selected rectangle
+ */
+ private void updateSelection(Projection proj, SVGPoint p1, SVGPoint p2) {
+ if (p1 == null || p2 == null) {
+ LOG.warning("no rect selected: p1: " + p1 + " p2: " + p2);
+ return;
+ }
+
+ DBIDSelection selContext = context.getSelection();
+ ModifiableDBIDs selection;
+ if (selContext != null) {
+ selection = DBIDUtil.newHashSet(selContext.getSelectedIds());
+ } else {
+ selection = DBIDUtil.newHashSet();
+ }
+ DoubleDoublePair[] ranges;
+
+ double x1 = Math.min(p1.getX(), p2.getX());
+ double x2 = Math.max(p1.getX(), p2.getX());
+ double y1 = Math.max(p1.getY(), p2.getY());
+ double y2 = Math.min(p1.getY(), p2.getY());
+
+ if (selContext instanceof RangeSelection) {
+ ranges = ((RangeSelection) selContext).getRanges();
+ } else {
+ ranges = new DoubleDoublePair[dim];
+ }
+ updateSelectionRectKoordinates(x1, x2, y1, y2, ranges);
+
+ selection.clear();
+ candidates: for (DBIDIter iditer = rel.iterDBIDs(); iditer.valid(); iditer.advance()) {
+ NumberVector<?> dbTupel = rel.get(iditer);
+ for (int i = 0; i < dim; i++) {
+ if (ranges != null && ranges[i] != null) {
+ if (dbTupel.doubleValue(i) < ranges[i].first || dbTupel.doubleValue(i) > ranges[i].second) {
+ continue candidates;
+ }
+ }
}
+ selection.add(iditer);
+ }
+ context.setSelection(new RangeSelection(selection, ranges));
+ }
+
+ /**
+ * Adds the required CSS-Classes.
+ *
+ * @param svgp SVG-Plot
+ */
+ protected void addCSSClasses(SVGPlot svgp) {
+ // Class for the range marking
+ if (!svgp.getCSSClassManager().contains(CSS_RANGEMARKER)) {
+ final CSSClass rcls = new CSSClass(this, CSS_RANGEMARKER);
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ rcls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION_ACTIVE));
+ rcls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION_ACTIVE));
+ svgp.addCSSClassOrLogError(rcls);
}
}
}
-}
\ No newline at end of file +}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolDotVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolDotVisualization.java index b52f37c9..6fa8200c 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolDotVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/selection/SelectionToolDotVisualization.java @@ -56,21 +56,16 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatter *
* @author Heidi Kolb
*
- * @apiviz.has SelectionResult oneway - - updates
- * @apiviz.has DBIDSelection oneway - - updates
+ * @apiviz.stereotype factory
+ * @apiviz.uses Instance - - «create»
*/
-public class SelectionToolDotVisualization extends AbstractScatterplotVisualization implements DragableArea.DragListener {
+public class SelectionToolDotVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
private static final String NAME = "Object Selection";
/**
- * CSS class of the selection rectangle while selecting.
- */
- private static final String CSS_RANGEMARKER = "selectionRangeMarker";
-
- /**
* Input modes
*
* @apiviz.exclude
@@ -80,194 +75,199 @@ public class SelectionToolDotVisualization extends AbstractScatterplotVisualizat }
/**
- * Element for selection rectangle
- */
- Element rtag;
-
- /**
- * Element for the rectangle to add listeners
+ * Constructor, adhering to
+ * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
*/
- Element etag;
-
- /**
- * Constructor.
- *
- * @param task Task
- */
- public SelectionToolDotVisualization(VisualizationTask task) {
- super(task);
- incrementalRedraw();
+ public SelectionToolDotVisualization() {
+ super();
}
@Override
- protected void redraw() {
- addCSSClasses(svgp);
-
- //
- rtag = svgp.svgElement(SVGConstants.SVG_G_TAG);
- SVGUtil.addCSSClass(rtag, CSS_RANGEMARKER);
- layer.appendChild(rtag);
+ public Visualization makeVisualization(VisualizationTask task) {
+ return new Instance(task);
+ }
- // etag: sensitive area
- DragableArea drag = new DragableArea(svgp, -0.6 * StyleLibrary.SCALE, -0.7 * StyleLibrary.SCALE, 1.3 * StyleLibrary.SCALE, 1.4 * StyleLibrary.SCALE, this);
- etag = drag.getElement();
- layer.appendChild(etag);
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
+ for(SelectionResult selres : selectionResults) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_INTERACTIVE;
+ task.tool = true;
+ task.thumbnail = false;
+ task.noexport = true;
+ task.initDefaultVisibility(false);
+ baseResult.getHierarchy().add(selres, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
}
/**
- * Delete the children of the element
+ * Instance
*
- * @param container SVG-Element
+ * @author Heidi Kolb
+ *
+ * @apiviz.has SelectionResult oneway - - updates
+ * @apiviz.has DBIDSelection oneway - - updates
*/
- private void deleteChildren(Element container) {
- while(container.hasChildNodes()) {
- container.removeChild(container.getLastChild());
+ public class Instance extends AbstractScatterplotVisualization implements DragableArea.DragListener {
+ /**
+ * CSS class of the selection rectangle while selecting.
+ */
+ private static final String CSS_RANGEMARKER = "selectionRangeMarker";
+
+ /**
+ * Element for selection rectangle
+ */
+ Element rtag;
+
+ /**
+ * Element for the rectangle to add listeners
+ */
+ Element etag;
+
+ /**
+ * Constructor.
+ *
+ * @param task Task
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ incrementalRedraw();
}
- }
- @Override
- public boolean startDrag(SVGPoint startPoint, Event evt) {
- return true;
- }
+ @Override
+ protected void redraw() {
+ addCSSClasses(svgp);
- @Override
- public boolean duringDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
- deleteChildren(rtag);
- double x = Math.min(startPoint.getX(), dragPoint.getX());
- double y = Math.min(startPoint.getY(), dragPoint.getY());
- double width = Math.abs(startPoint.getX() - dragPoint.getX());
- double height = Math.abs(startPoint.getY() - dragPoint.getY());
- rtag.appendChild(svgp.svgRect(x, y, width, height));
- return true;
- }
+ //
+ rtag = svgp.svgElement(SVGConstants.SVG_G_TAG);
+ SVGUtil.addCSSClass(rtag, CSS_RANGEMARKER);
+ layer.appendChild(rtag);
- @Override
- public boolean endDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
- Mode mode = getInputMode(evt);
- deleteChildren(rtag);
- if(startPoint.getX() != dragPoint.getX() || startPoint.getY() != dragPoint.getY()) {
- updateSelection(mode, proj, startPoint, dragPoint);
+ // etag: sensitive area
+ DragableArea drag = new DragableArea(svgp, -0.6 * StyleLibrary.SCALE, -0.7 * StyleLibrary.SCALE, 1.3 * StyleLibrary.SCALE, 1.4 * StyleLibrary.SCALE, this);
+ etag = drag.getElement();
+ layer.appendChild(etag);
}
- return true;
- }
- /**
- * Get the current input mode, on each mouse event.
- *
- * @param evt Mouse event.
- * @return current input mode
- */
- private Mode getInputMode(Event evt) {
- if(evt instanceof DOMMouseEvent) {
- DOMMouseEvent domme = (DOMMouseEvent) evt;
- // TODO: visual indication of mode possible?
- if(domme.getShiftKey()) {
- return Mode.ADD;
- }
- else if(domme.getCtrlKey()) {
- return Mode.INVERT;
- }
- else {
- return Mode.REPLACE;
+ /**
+ * Delete the children of the element
+ *
+ * @param container SVG-Element
+ */
+ private void deleteChildren(Element container) {
+ while(container.hasChildNodes()) {
+ container.removeChild(container.getLastChild());
}
}
- // Default mode is replace.
- return Mode.REPLACE;
- }
- /**
- * Updates the selection in the context.<br>
- *
- * @param mode Input mode
- * @param proj
- * @param p1 first point of the selected rectangle
- * @param p2 second point of the selected rectangle
- */
- private void updateSelection(Mode mode, Projection2D proj, SVGPoint p1, SVGPoint p2) {
- DBIDSelection selContext = context.getSelection();
- // Note: we rely on SET semantics below!
- HashSetModifiableDBIDs selection;
- if(selContext == null || mode == Mode.REPLACE) {
- selection = DBIDUtil.newHashSet();
+ @Override
+ public boolean startDrag(SVGPoint startPoint, Event evt) {
+ return true;
}
- else {
- selection = DBIDUtil.newHashSet(selContext.getSelectedIds());
+
+ @Override
+ public boolean duringDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
+ deleteChildren(rtag);
+ double x = Math.min(startPoint.getX(), dragPoint.getX());
+ double y = Math.min(startPoint.getY(), dragPoint.getY());
+ double width = Math.abs(startPoint.getX() - dragPoint.getX());
+ double height = Math.abs(startPoint.getY() - dragPoint.getY());
+ rtag.appendChild(svgp.svgRect(x, y, width, height));
+ return true;
}
- for(DBIDIter iditer = rel.iterDBIDs(); iditer.valid(); iditer.advance()) { - double[] vec = proj.fastProjectDataToRenderSpace(rel.get(iditer));
- if(vec[0] >= Math.min(p1.getX(), p2.getX()) && vec[0] <= Math.max(p1.getX(), p2.getX()) && vec[1] >= Math.min(p1.getY(), p2.getY()) && vec[1] <= Math.max(p1.getY(), p2.getY())) {
- if(mode == Mode.INVERT) {
- if(!selection.contains(iditer)) {
- selection.add(iditer);
- }
- else {
- selection.remove(iditer);
- }
+
+ @Override
+ public boolean endDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) {
+ Mode mode = getInputMode(evt);
+ deleteChildren(rtag);
+ if(startPoint.getX() != dragPoint.getX() || startPoint.getY() != dragPoint.getY()) {
+ updateSelection(mode, proj, startPoint, dragPoint);
+ }
+ return true;
+ }
+
+ /**
+ * Get the current input mode, on each mouse event.
+ *
+ * @param evt Mouse event.
+ * @return current input mode
+ */
+ private Mode getInputMode(Event evt) {
+ if(evt instanceof DOMMouseEvent) {
+ DOMMouseEvent domme = (DOMMouseEvent) evt;
+ // TODO: visual indication of mode possible?
+ if(domme.getShiftKey()) {
+ return Mode.ADD;
+ }
+ else if(domme.getCtrlKey()) {
+ return Mode.INVERT;
}
else {
- // In REPLACE and ADD, add objects.
- // The difference was done before by not re-using the selection.
- // Since we are using a set, we can just add in any case.
- selection.add(iditer);
+ return Mode.REPLACE;
}
}
+ // Default mode is replace.
+ return Mode.REPLACE;
}
- context.setSelection(new DBIDSelection(selection));
- }
- /**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- protected void addCSSClasses(SVGPlot svgp) {
- // Class for the range marking
- if(!svgp.getCSSClassManager().contains(CSS_RANGEMARKER)) {
- final CSSClass rcls = new CSSClass(this, CSS_RANGEMARKER);
- final StyleLibrary style = context.getStyleLibrary();
- rcls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION_ACTIVE));
- rcls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION_ACTIVE));
- svgp.addCSSClassOrLogError(rcls);
- }
- }
-
- /**
- * Factory for tool visualizations for selecting objects
- *
- * @author Heidi Kolb
- *
- * @apiviz.stereotype factory
- * @apiviz.uses SelectionToolDotVisualization - - «create»
- */
- public static class Factory extends AbstractVisFactory {
/**
- * Constructor, adhering to
- * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable}
+ * Updates the selection in the context.<br>
+ *
+ * @param mode Input mode
+ * @param proj
+ * @param p1 first point of the selected rectangle
+ * @param p2 second point of the selected rectangle
*/
- public Factory() {
- super();
- }
-
- @Override
- public Visualization makeVisualization(VisualizationTask task) {
- return new SelectionToolDotVisualization(task);
+ private void updateSelection(Mode mode, Projection2D proj, SVGPoint p1, SVGPoint p2) {
+ DBIDSelection selContext = context.getSelection();
+ // Note: we rely on SET semantics below!
+ HashSetModifiableDBIDs selection;
+ if(selContext == null || mode == Mode.REPLACE) {
+ selection = DBIDUtil.newHashSet();
+ }
+ else {
+ selection = DBIDUtil.newHashSet(selContext.getSelectedIds());
+ }
+ for(DBIDIter iditer = rel.iterDBIDs(); iditer.valid(); iditer.advance()) {
+ double[] vec = proj.fastProjectDataToRenderSpace(rel.get(iditer));
+ if(vec[0] >= Math.min(p1.getX(), p2.getX()) && vec[0] <= Math.max(p1.getX(), p2.getX()) && vec[1] >= Math.min(p1.getY(), p2.getY()) && vec[1] <= Math.max(p1.getY(), p2.getY())) {
+ if(mode == Mode.INVERT) {
+ if(!selection.contains(iditer)) {
+ selection.add(iditer);
+ }
+ else {
+ selection.remove(iditer);
+ }
+ }
+ else {
+ // In REPLACE and ADD, add objects.
+ // The difference was done before by not re-using the selection.
+ // Since we are using a set, we can just add in any case.
+ selection.add(iditer);
+ }
+ }
+ }
+ context.setSelection(new DBIDSelection(selection));
}
- @Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- Collection<SelectionResult> selectionResults = ResultUtil.filterResults(result, SelectionResult.class);
- for(SelectionResult selres : selectionResults) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : ps) {
- final VisualizationTask task = new VisualizationTask(NAME, selres, p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_INTERACTIVE);
- task.put(VisualizationTask.META_TOOL, true);
- task.put(VisualizationTask.META_NOTHUMB, true);
- task.put(VisualizationTask.META_NOEXPORT, true);
- task.put(VisualizationTask.META_VISIBLE_DEFAULT, false);
- baseResult.getHierarchy().add(selres, task);
- baseResult.getHierarchy().add(p, task);
- }
+ /**
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
+ */
+ protected void addCSSClasses(SVGPlot svgp) {
+ // Class for the range marking
+ if(!svgp.getCSSClassManager().contains(CSS_RANGEMARKER)) {
+ final CSSClass rcls = new CSSClass(this, CSS_RANGEMARKER);
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ rcls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getColor(StyleLibrary.SELECTION_ACTIVE));
+ rcls.setStatement(SVGConstants.CSS_OPACITY_PROPERTY, style.getOpacity(StyleLibrary.SELECTION_ACTIVE));
+ svgp.addCSSClassOrLogError(rcls);
}
}
}
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/thumbs/ThumbnailVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/thumbs/ThumbnailVisualization.java index 9ea016a5..dd502146 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/thumbs/ThumbnailVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/thumbs/ThumbnailVisualization.java @@ -91,7 +91,8 @@ public class ThumbnailVisualization extends AbstractVisualization implements Thu protected int tresolution; /** - * The event mask. See {@link #ON_DATA}, {@link #ON_SELECTION}, {@link #ON_STYLE}, {@link #NO_PROJECTION} + * The event mask. See {@link #ON_DATA}, {@link #ON_SELECTION}, + * {@link #ON_STYLE}, {@link #NO_PROJECTION} */ private int mask; @@ -110,14 +111,13 @@ public class ThumbnailVisualization extends AbstractVisualization implements Thu public ThumbnailVisualization(VisFactory visFactory, VisualizationTask task, int mask) { super(task); this.visFactory = visFactory; - Integer tres = task.getGenerics(VisualizationTask.THUMBNAIL_RESOLUTION, Integer.class); - this.tresolution = tres; + this.tresolution = task.thumbsize; this.layer = task.getPlot().svgElement(SVGConstants.SVG_G_TAG); this.thumbid = -1; this.thumb = null; this.mask = mask; // Listen for database events only when needed. - if((mask & ON_DATA) == ON_DATA) { + if ((mask & ON_DATA) == ON_DATA) { context.addDataStoreListener(this); } // Listen for result changes, including the one we monitor @@ -126,7 +126,7 @@ public class ThumbnailVisualization extends AbstractVisualization implements Thu @Override public void destroy() { - if(pendingThumbnail != null) { + if (pendingThumbnail != null) { ThumbnailThread.UNQUEUE(pendingThumbnail); } // TODO: remove image from registry? @@ -136,7 +136,7 @@ public class ThumbnailVisualization extends AbstractVisualization implements Thu @Override public Element getLayer() { - if(thumbid < 0) { + if (thumbid < 0) { synchronizedRedraw(); } return layer; @@ -150,15 +150,14 @@ public class ThumbnailVisualization extends AbstractVisualization implements Thu @Override protected void incrementalRedraw() { final Element oldcontainer; - if(layer.hasChildNodes()) { + if (layer.hasChildNodes()) { oldcontainer = layer; layer = (Element) layer.cloneNode(false); - } - else { + } else { oldcontainer = null; } redraw(); - if(oldcontainer != null && oldcontainer.getParentNode() != null) { + if (oldcontainer != null && oldcontainer.getParentNode() != null) { oldcontainer.getParentNode().replaceChild(layer, oldcontainer); } } @@ -168,14 +167,13 @@ public class ThumbnailVisualization extends AbstractVisualization implements Thu */ @Override protected void redraw() { - if(thumbid < 0) { + if (thumbid < 0) { // LoggingUtil.warning("Generating new thumbnail " + this); layer.appendChild(SVGUtil.svgWaitIcon(task.getPlot().getDocument(), 0, 0, task.getWidth(), task.getHeight())); - if(pendingThumbnail == null) { + if (pendingThumbnail == null) { pendingThumbnail = ThumbnailThread.QUEUE(this); } - } - else { + } else { // LoggingUtil.warning("Injecting Thumbnail " + this); Element i = task.getPlot().svgElement(SVGConstants.SVG_IMAGE_TAG); SVGUtil.setAtt(i, SVGConstants.SVG_X_ATTRIBUTE, 0); @@ -196,7 +194,7 @@ public class ThumbnailVisualization extends AbstractVisualization implements Thu // Work on a clone VisualizationTask clone = task.clone(plot, context); - clone.put(VisualizationTask.THUMBNAIL, false); + clone.thumbnail = false; Visualization vis = visFactory.makeVisualization(clone); plot.getRoot().appendChild(vis.getLayer()); @@ -208,13 +206,11 @@ public class ThumbnailVisualization extends AbstractVisualization implements Thu // The visualization will not be used anymore. vis.destroy(); synchronizedRedraw(); - } - catch(Exception e) { + } catch (Exception e) { final Logging logger = Logging.getLogger(task.getFactory().getClass()); - if(logger != null && logger.isDebugging()) { + if (logger != null && logger.isDebugging()) { logger.exception("Thumbnail failed.", e); - } - else { + } else { LoggingUtil.warning("Thumbnail for " + task.getFactory().getClass().getName() + " failed - enable debugging to see details."); } // TODO: hide the failed image? @@ -231,11 +227,11 @@ public class ThumbnailVisualization extends AbstractVisualization implements Thu @Override public void resultChanged(Result current) { - if((mask & ON_SELECTION) == ON_SELECTION && current instanceof SelectionResult) { + if ((mask & ON_SELECTION) == ON_SELECTION && current instanceof SelectionResult) { refreshThumbnail(); return; } - if((mask & ON_STYLE) == ON_STYLE && current instanceof StyleResult) { + if ((mask & ON_STYLE) == ON_STYLE && current instanceof StyleResult) { refreshThumbnail(); return; } @@ -245,4 +241,4 @@ public class ThumbnailVisualization extends AbstractVisualization implements Thu } super.resultChanged(current); } -}
\ No newline at end of file +} diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/ClusterEvaluationVisFactory.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/ClusterEvaluationVisualization.java index 5b58e842..4b57a503 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/ClusterEvaluationVisFactory.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/ClusterEvaluationVisualization.java @@ -48,7 +48,7 @@ import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; import de.lmu.ifi.dbs.elki.visualization.svg.SVGScoreBar; import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil; import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisFactory; -import de.lmu.ifi.dbs.elki.visualization.visualizers.StaticVisualization; +import de.lmu.ifi.dbs.elki.visualization.visualizers.StaticVisualizationInstance; import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; /** @@ -58,10 +58,10 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * @author Sascha Goldhofer * * @apiviz.stereotype factory - * @apiviz.uses StaticVisualization oneway - - «create» + * @apiviz.uses StaticVisualizationInstance oneway - - «create» * @apiviz.has de.lmu.ifi.dbs.elki.evaluation.clustering.EvaluateClustering.ScoreResult oneway - - visualizes */ -public class ClusterEvaluationVisFactory extends AbstractVisFactory { +public class ClusterEvaluationVisualization extends AbstractVisFactory { /** * Name for this visualizer. */ @@ -80,7 +80,7 @@ public class ClusterEvaluationVisFactory extends AbstractVisFactory { /** * Constructor. */ - public ClusterEvaluationVisFactory() { + public ClusterEvaluationVisualization() { super(); } @@ -91,7 +91,7 @@ public class ClusterEvaluationVisFactory extends AbstractVisFactory { final VisualizationTask task = new VisualizationTask(NAME, sr, null, this); task.width = .5; task.height = 2.0; - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_STATIC); + task.level = VisualizationTask.LEVEL_STATIC; baseResult.getHierarchy().add(sr, task); } } @@ -181,11 +181,12 @@ public class ClusterEvaluationVisFactory extends AbstractVisFactory { double cols = 10; // Math.max(10, (int) (i * task.getHeight() / // task.getWidth())); double rows = ypos; - final double margin = task.getContext().getStyleLibrary().getSize(StyleLibrary.MARGIN); + final StyleLibrary style = task.getContext().getStyleResult().getStyleLibrary(); + final double margin = style.getSize(StyleLibrary.MARGIN); final String transform = SVGUtil.makeMarginTransform(task.getWidth(), task.getHeight(), cols, rows, margin / StyleLibrary.SCALE); SVGUtil.setAtt(parent, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); - return new StaticVisualization(task, parent); + return new StaticVisualizationInstance(task, parent); } @Override diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/HistogramVisFactory.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/HistogramVisualization.java index 117c3c2c..d97b5117 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/HistogramVisFactory.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/HistogramVisualization.java @@ -47,7 +47,7 @@ import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; import de.lmu.ifi.dbs.elki.visualization.svg.SVGSimpleLinearAxis; import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil; import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisFactory; -import de.lmu.ifi.dbs.elki.visualization.visualizers.StaticVisualization; +import de.lmu.ifi.dbs.elki.visualization.visualizers.StaticVisualizationInstance; import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; /** @@ -56,10 +56,10 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * @author Erich Schubert * * @apiviz.stereotype factory - * @apiviz.uses StaticVisualization oneway - - «create» + * @apiviz.uses StaticVisualizationInstance oneway - - «create» * @apiviz.has HistogramResult oneway - - visualizes */ -public class HistogramVisFactory extends AbstractVisFactory { +public class HistogramVisualization extends AbstractVisFactory { /** * Histogram visualizer name */ @@ -74,7 +74,7 @@ public class HistogramVisFactory extends AbstractVisFactory { * Constructor, adhering to * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} */ - public HistogramVisFactory() { + public HistogramVisualization() { super(); } @@ -82,31 +82,30 @@ public class HistogramVisFactory extends AbstractVisFactory { public Visualization makeVisualization(VisualizationTask task) { VisualizerContext context = task.getContext(); SVGPlot svgp = task.getPlot(); - HistogramResult<? extends NumberVector<?, ?>> curve = task.getResult(); - - double scale = StyleLibrary.SCALE; - final double sizex = scale; - final double sizey = scale * task.getHeight() / task.getWidth(); - final double margin = context.getStyleLibrary().getSize(StyleLibrary.MARGIN); + HistogramResult<? extends NumberVector<?>> curve = task.getResult(); + + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + final double sizex = StyleLibrary.SCALE; + final double sizey = StyleLibrary.SCALE * task.getHeight() / task.getWidth(); + final double margin = style.getSize(StyleLibrary.MARGIN); Element layer = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG); final String transform = SVGUtil.makeMarginTransform(task.getWidth(), task.getHeight(), sizex, sizey, margin); SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); - + // find maximum, determine step size - Integer dim = null; + int dim = -1; DoubleMinMax xminmax = new DoubleMinMax(); DoubleMinMax yminmax = new DoubleMinMax(); - for(NumberVector<?, ?> vec : curve) { - xminmax.put(vec.doubleValue(1)); - if(dim == null) { + for (NumberVector<?> vec : curve) { + xminmax.put(vec.doubleValue(0)); + if (dim < 0) { dim = vec.getDimensionality(); - } - else { + } else { // TODO: test and throw always assert (dim == vec.getDimensionality()); } - for(int i = 1; i < dim; i++) { - yminmax.put(vec.doubleValue(i + 1)); + for (int i = 0; i < dim; i++) { + yminmax.put(vec.doubleValue(i)); } } // Minimum should always start at 0 for histograms @@ -118,42 +117,41 @@ public class HistogramVisFactory extends AbstractVisFactory { double range = xminmax.getMax() - xminmax.getMin(); double binwidth = range / (size - 1); - LinearScale xscale = new LinearScale(xminmax.getMin() - binwidth / 2, xminmax.getMax() + binwidth / 2); + LinearScale xscale = new LinearScale(xminmax.getMin() - binwidth * .5, xminmax.getMax() + binwidth * .5); LinearScale yscale = new LinearScale(yminmax.getMin(), yminmax.getMax()); SVGPath[] path = new SVGPath[dim]; - for(int i = 0; i < dim; i++) { - path[i] = new SVGPath(sizex * xscale.getScaled(xminmax.getMin() - binwidth / 2), sizey); + for (int i = 0; i < dim; i++) { + path[i] = new SVGPath(sizex * xscale.getScaled(xminmax.getMin() - binwidth * .5), sizey); } // draw curves. - for(NumberVector<?, ?> vec : curve) { - for(int d = 0; d < dim; d++) { - path[d].lineTo(sizex * (xscale.getScaled(vec.doubleValue(1) - binwidth / 2)), sizey * (1 - yscale.getScaled(vec.doubleValue(d + 2)))); - path[d].lineTo(sizex * (xscale.getScaled(vec.doubleValue(1) + binwidth / 2)), sizey * (1 - yscale.getScaled(vec.doubleValue(d + 2)))); + for (NumberVector<?> vec : curve) { + for (int d = 0; d < dim; d++) { + path[d].lineTo(sizex * (xscale.getScaled(vec.doubleValue(0) - binwidth * .5)), sizey * (1 - yscale.getScaled(vec.doubleValue(d + 1)))); + path[d].lineTo(sizex * (xscale.getScaled(vec.doubleValue(0) + binwidth * .5)), sizey * (1 - yscale.getScaled(vec.doubleValue(d + 1)))); } } // close all histograms - for(int i = 0; i < dim; i++) { - path[i].lineTo(sizex * xscale.getScaled(xminmax.getMax() + binwidth / 2), sizey); + for (int i = 0; i < dim; i++) { + path[i].lineTo(sizex * xscale.getScaled(xminmax.getMax() + binwidth * .5), sizey); } // add axes try { - SVGSimpleLinearAxis.drawAxis(svgp, layer, yscale, 0, sizey, 0, 0, SVGSimpleLinearAxis.LabelStyle.LEFTHAND, context.getStyleLibrary()); - SVGSimpleLinearAxis.drawAxis(svgp, layer, xscale, 0, sizey, sizex, sizey, SVGSimpleLinearAxis.LabelStyle.RIGHTHAND, context.getStyleLibrary()); - } - catch(CSSNamingConflict e) { + SVGSimpleLinearAxis.drawAxis(svgp, layer, yscale, 0, sizey, 0, 0, SVGSimpleLinearAxis.LabelStyle.LEFTHAND, style); + SVGSimpleLinearAxis.drawAxis(svgp, layer, xscale, 0, sizey, sizex, sizey, SVGSimpleLinearAxis.LabelStyle.RIGHTHAND, style); + } catch (CSSNamingConflict e) { LoggingUtil.exception(e); } // Setup line styles and insert lines. - ColorLibrary cl = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT); - for(int d = 0; d < dim; d++) { + ColorLibrary cl = style.getColorSet(StyleLibrary.PLOT); + for (int d = 0; d < dim; d++) { CSSClass csscls = new CSSClass(this, SERIESID + "_" + d); csscls.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, SVGConstants.SVG_NONE_VALUE); csscls.setStatement(SVGConstants.SVG_STROKE_ATTRIBUTE, cl.getColor(d)); - csscls.setStatement(SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT)); + csscls.setStatement(SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE, style.getLineWidth(StyleLibrary.PLOT)); svgp.addCSSClassOrLogError(csscls); Element line = path[d].makeElement(svgp); @@ -161,17 +159,17 @@ public class HistogramVisFactory extends AbstractVisFactory { layer.appendChild(line); } - return new StaticVisualization(task, layer); + return new StaticVisualizationInstance(task, layer); } @Override public void processNewResult(HierarchicalResult baseResult, Result newResult) { - List<HistogramResult<? extends NumberVector<?, ?>>> histograms = ResultUtil.filterResults(newResult, HistogramResult.class); - for(HistogramResult<? extends NumberVector<?, ?>> histogram : histograms) { + List<HistogramResult<? extends NumberVector<?>>> histograms = ResultUtil.filterResults(newResult, HistogramResult.class); + for (HistogramResult<? extends NumberVector<?>> histogram : histograms) { final VisualizationTask task = new VisualizationTask(NAME, histogram, null, this); task.width = 2.0; task.height = 1.0; - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_STATIC); + task.level = VisualizationTask.LEVEL_STATIC; baseResult.getHierarchy().add(histogram, task); } } @@ -181,4 +179,4 @@ public class HistogramVisFactory extends AbstractVisFactory { // TODO: depending on the histogram complexity? return false; } -}
\ No newline at end of file +} diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/KeyVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/KeyVisualization.java index 62a4fb5c..f21e4df6 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/KeyVisualization.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/KeyVisualization.java @@ -52,147 +52,182 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; /** * Visualizer, displaying the key for a clustering. - * + * * @author Erich Schubert * - * @apiviz.has Clustering oneway - - visualizes + * @apiviz.stereotype factory + * @apiviz.uses Instance oneway - - «create» */ -public class KeyVisualization extends AbstractVisualization { +public class KeyVisualization extends AbstractVisFactory { /** * Name for this visualizer. */ private static final String NAME = "Cluster Key"; - /** - * Clustering to display - */ - private Clustering<Model> clustering; + @Override + public void processNewResult(HierarchicalResult baseResult, Result newResult) { + // Find clusterings we can visualize: + Collection<Clustering<?>> clusterings = ResultUtil.filterResults(newResult, Clustering.class); + for(Clustering<?> c : clusterings) { + final int numc = c.getAllClusters().size(); + if(numc > 0) { + // FIXME: compute from labels? + final double maxwidth = 10.; + final VisualizationTask task = new VisualizationTask(NAME, c, null, this); + final int cols = getPreferredColumns(1.0, 1.0, numc, maxwidth); + final int rows = (int) Math.ceil(numc / (double) cols); + final double div = Math.max(2. + rows, cols * maxwidth); + task.width = cols * maxwidth / div; + task.height = (2. + rows) / div; + task.level = VisualizationTask.LEVEL_STATIC; + task.nodetail = true; + baseResult.getHierarchy().add(c, task); + } + } + } /** - * Constructor. + * Compute the preferred number of columns. * - * @param task Visualization task + * @param width Target width + * @param height Target height + * @param numc Number of clusters + * @param maxwidth Max width of entries + * @return Preferred number of columns */ - public KeyVisualization(VisualizationTask task) { - super(task); - this.clustering = task.getResult(); - context.addResultListener(this); + public static int getPreferredColumns(double width, double height, int numc, double maxwidth) { + // Maximum width (compared to height) of labels - guess. + // FIXME: do we really need to do this three-step computation? + // Number of rows we'd use in a squared layout: + final double rows = Math.ceil(Math.pow(numc * maxwidth, height / (width + height))); + // Given this number of rows (plus two for header), use this many columns: + return (int) Math.ceil(numc / (rows + 2)); } @Override - public void destroy() { - context.removeResultListener(this); - super.destroy(); + public Visualization makeVisualization(VisualizationTask task) { + return new Instance(task); } @Override - public void resultChanged(Result current) { - super.resultChanged(current); - if(current == context.getStyleResult()) { - incrementalRedraw(); - } + public boolean allowThumbnails(VisualizationTask task) { + return false; } - @Override - protected void redraw() { - SVGPlot svgp = task.getPlot(); - final List<Cluster<Model>> allcs = clustering.getAllClusters(); - - StyleLibrary style = context.getStyleLibrary(); - MarkerLibrary ml = style.markers(); - layer = svgp.svgElement(SVGConstants.SVG_G_TAG); - - // Add a label for the clustering. - { - Element label = svgp.svgText(0.1, 0.7, clustering.getLongName()); - label.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, "font-size: 0.4; fill: "+style.getTextColor(StyleLibrary.DEFAULT)); - layer.appendChild(label); + /** + * Instance + * + * @author Erich Schubert + * + * @apiviz.has Clustering oneway - - visualizes + */ + public class Instance extends AbstractVisualization { + /** + * Clustering to display + */ + private Clustering<Model> clustering; + + /** + * Constructor. + * + * @param task Visualization task + */ + public Instance(VisualizationTask task) { + super(task); + this.clustering = task.getResult(); + context.addResultListener(this); } - // TODO: multi-column layout! - int i = 0; - for(Cluster<Model> c : allcs) { - ml.useMarker(svgp, layer, 0.3, i + 1.5, i, 0.3); - Element label = svgp.svgText(0.7, i + 1.7, c.getNameAutomatic()); - label.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, "font-size: 0.6; fill: "+style.getTextColor(StyleLibrary.DEFAULT)); - layer.appendChild(label); - i++; + @Override + public void destroy() { + context.removeResultListener(this); + super.destroy(); } - // Add a button to set style policy - { - StylingPolicy sp = context.getStyleResult().getStylingPolicy(); - if(sp instanceof ClusterStylingPolicy && ((ClusterStylingPolicy) sp).getClustering() == clustering) { - // Don't show the button when active. May confuse people more than the disappearing button - - // SVGButton button = new SVGButton(.1, i + 1.1, 3.8, .7, .2); - // button.setTitle("Active style", "darkgray"); - // layer.appendChild(button.render(svgp)); - } - else { - SVGButton button = new SVGButton(.1, i + 1.1, 3.8, .7, .2); - button.setTitle("Set style", "black"); - Element elem = button.render(svgp); - // Attach listener - EventTarget etr = (EventTarget) elem; - etr.addEventListener(SVGConstants.SVG_CLICK_EVENT_TYPE, new EventListener() { - @Override - public void handleEvent(Event evt) { - setStylePolicy(); - } - }, false); - layer.appendChild(elem); + @Override + public void resultChanged(Result current) { + super.resultChanged(current); + if(current == context.getStyleResult()) { + incrementalRedraw(); } } - int rows = i + 2; - int cols = Math.max(6, (int) (rows * task.getHeight() / task.getWidth())); - final double margin = style.getSize(StyleLibrary.MARGIN); - final String transform = SVGUtil.makeMarginTransform(task.getWidth(), task.getHeight(), cols, rows, margin / StyleLibrary.SCALE); - SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); - } + @Override + protected void redraw() { + SVGPlot svgp = task.getPlot(); + StyleLibrary style = context.getStyleResult().getStyleLibrary(); + MarkerLibrary ml = style.markers(); + + // Maximum width (compared to height) of labels - guess. + // FIXME: compute from labels? + final double maxwidth = 10.; + + final List<Cluster<Model>> allcs = clustering.getAllClusters(); + final int numc = allcs.size(); + final int cols = getPreferredColumns(task.getWidth(), task.getHeight(), numc, maxwidth); + final int rows = 2 + (int) Math.ceil(numc / (double) cols); + // We use a coordinate system based on rows, so columns are at c*maxwidth + + layer = svgp.svgElement(SVGConstants.SVG_G_TAG); + // Add a label for the clustering. + { + Element label = svgp.svgText(0.1, 0.7, clustering.getLongName()); + label.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, "font-size: 0.4; fill: " + style.getTextColor(StyleLibrary.DEFAULT)); + layer.appendChild(label); + } - /** - * Trigger a style change. - */ - protected void setStylePolicy() { - context.getStyleResult().setStylingPolicy(new ClusterStylingPolicy(clustering, context.getStyleLibrary())); - context.getHierarchy().resultChanged(context.getStyleResult()); - } + int i = 0; + for(Cluster<Model> c : allcs) { + final int col = i / rows; + final int row = i % rows; + ml.useMarker(svgp, layer, 0.3 + maxwidth * col, row + 1.5, i, 0.3); + Element label = svgp.svgText(0.7 + maxwidth * col, row + 1.7, c.getNameAutomatic()); + label.setAttribute(SVGConstants.SVG_STYLE_ATTRIBUTE, "font-size: 0.6; fill: " + style.getTextColor(StyleLibrary.DEFAULT)); + layer.appendChild(label); + i++; + } - /** - * Visualization factory - * - * @author Erich Schubert - * - * @apiviz.stereotype factory - * @apiviz.uses KeyVisualization oneway - - «create» - */ - public static class Factory extends AbstractVisFactory { - @Override - public void processNewResult(HierarchicalResult baseResult, Result newResult) { - // Find clusterings we can visualize: - Collection<Clustering<?>> clusterings = ResultUtil.filterResults(newResult, Clustering.class); - for (Clustering<?> c : clusterings) { - if(c.getAllClusters().size() > 0) { - final VisualizationTask task = new VisualizationTask(NAME, c, null, this); - task.width = 1.0; - task.height = 1.0; - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_STATIC); - task.put(VisualizationTask.META_NODETAIL, true); - baseResult.getHierarchy().add(c, task); + // Add a button to set style policy + { + StylingPolicy sp = context.getStyleResult().getStylingPolicy(); + if(sp instanceof ClusterStylingPolicy && ((ClusterStylingPolicy) sp).getClustering() == clustering) { + // Don't show the button when active. May confuse people more than the + // disappearing button? + + // SVGButton button = new SVGButton(.1, rows + 1.1, 3.8, .7, .2); + // button.setTitle("Active style", "darkgray"); + // layer.appendChild(button.render(svgp)); + } + else { + SVGButton button = new SVGButton(.1, rows + 1.1, 3.8, .7, .2); + button.setTitle("Set style", "black"); + Element elem = button.render(svgp); + // Attach listener + EventTarget etr = (EventTarget) elem; + etr.addEventListener(SVGConstants.SVG_CLICK_EVENT_TYPE, new EventListener() { + @Override + public void handleEvent(Event evt) { + setStylePolicy(); + } + }, false); + layer.appendChild(elem); } } - } - @Override - public Visualization makeVisualization(VisualizationTask task) { - return new KeyVisualization(task); + // int rows = i + 2; + // int cols = Math.max(6, (int) (rows * task.getHeight() / + // task.getWidth())); + final double margin = style.getSize(StyleLibrary.MARGIN); + final String transform = SVGUtil.makeMarginTransform(task.getWidth(), task.getHeight(), cols * maxwidth, rows, margin / StyleLibrary.SCALE); + SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); } - @Override - public boolean allowThumbnails(VisualizationTask task) { - return false; + /** + * Trigger a style change. + */ + protected void setStylePolicy() { + context.getStyleResult().setStylingPolicy(new ClusterStylingPolicy(clustering, context.getStyleResult().getStyleLibrary())); + context.getHierarchy().resultChanged(context.getStyleResult()); } } }
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/LabelVisFactory.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/LabelVisualization.java index b6eb4549..d29a6467 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/LabelVisFactory.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/LabelVisualization.java @@ -35,7 +35,7 @@ import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil; import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisFactory; -import de.lmu.ifi.dbs.elki.visualization.visualizers.StaticVisualization; +import de.lmu.ifi.dbs.elki.visualization.visualizers.StaticVisualizationInstance; import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; /** @@ -45,9 +45,9 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * @author Erich Schubert * * @apiviz.stereotype factory - * @apiviz.uses StaticVisualization oneway - - «create» + * @apiviz.uses StaticVisualizationInstance oneway - - «create» */ -public class LabelVisFactory extends AbstractVisFactory { +public class LabelVisualization extends AbstractVisFactory { /** * The label to render */ @@ -61,7 +61,7 @@ public class LabelVisFactory extends AbstractVisFactory { /** * Constructor. Solely for API purposes (Parameterizable!) */ - public LabelVisFactory() { + public LabelVisualization() { super(); } @@ -70,7 +70,7 @@ public class LabelVisFactory extends AbstractVisFactory { * * @param label Label to use */ - public LabelVisFactory(String label) { + public LabelVisualization(String label) { this(label, false); } @@ -80,7 +80,7 @@ public class LabelVisFactory extends AbstractVisFactory { * @param label Label to use * @param rotated Rotated 90 deg to the left */ - public LabelVisFactory(String label, boolean rotated) { + public LabelVisualization(String label, boolean rotated) { super(); this.label = label; this.rotated = rotated; @@ -96,7 +96,7 @@ public class LabelVisFactory extends AbstractVisFactory { SVGPlot svgp = task.getPlot(); VisualizerContext context = task.getContext(); CSSClass cls = new CSSClass(svgp, "unmanaged"); - StyleLibrary style = context.getStyleLibrary(); + StyleLibrary style = context.getStyleResult().getStyleLibrary(); double fontsize = style.getTextSize("overview.labels") / StyleLibrary.SCALE; cls.setStatement(SVGConstants.CSS_FONT_SIZE_PROPERTY, SVGUtil.fmt(fontsize)); cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, style.getTextColor("overview.labels")); @@ -104,17 +104,17 @@ public class LabelVisFactory extends AbstractVisFactory { Element layer; if(!rotated) { - layer = svgp.svgText(task.getWidth() / 2, task.getHeight() / 2 + .35 * fontsize, this.label); + layer = svgp.svgText(task.getWidth() * .5, task.getHeight() * .5 + .35 * fontsize, this.label); SVGUtil.setAtt(layer, SVGConstants.SVG_STYLE_ATTRIBUTE, cls.inlineCSS()); SVGUtil.setAtt(layer, SVGConstants.SVG_TEXT_ANCHOR_ATTRIBUTE, SVGConstants.SVG_MIDDLE_VALUE); } else { - layer = svgp.svgText(- task.getHeight() / 2, task.getWidth() / 2 + .35 * fontsize, this.label); + layer = svgp.svgText(- task.getHeight() * .5, task.getWidth() * .5 + .35 * fontsize, this.label); SVGUtil.setAtt(layer, SVGConstants.SVG_STYLE_ATTRIBUTE, cls.inlineCSS()); SVGUtil.setAtt(layer, SVGConstants.SVG_TEXT_ANCHOR_ATTRIBUTE, SVGConstants.SVG_MIDDLE_VALUE); SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, "rotate(-90)"); } - return new StaticVisualization(task, layer); + return new StaticVisualizationInstance(task, layer); } @Override diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/PixmapVisualizer.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/PixmapVisualizer.java index 9269f404..19cc6254 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/PixmapVisualizer.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/PixmapVisualizer.java @@ -45,99 +45,99 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * * @author Erich Schubert * - * @apiviz.has PixmapResult oneway - 1 visualizes + * @apiviz.stereotype factory + * @apiviz.uses Instance oneway - - «create» */ -public class PixmapVisualizer extends AbstractVisualization { +public class PixmapVisualizer extends AbstractVisFactory { /** * Name for this visualizer. */ private static final String NAME = "Pixmap Visualizer"; /** - * The actual pixmap result. + * Constructor, adhering to + * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} */ - private PixmapResult result; + public PixmapVisualizer() { + super(); + } - /** - * Constructor. - * - * @param task Visualization task - */ - public PixmapVisualizer(VisualizationTask task) { - super(task); - this.result = task.getResult(); + @Override + public void processNewResult(HierarchicalResult baseResult, Result result) { + Collection<PixmapResult> prs = ResultUtil.filterResults(result, PixmapResult.class); + for(PixmapResult pr : prs) { + // Add plots, attach visualizer + final VisualizationTask task = new VisualizationTask(NAME, pr, null, this); + task.width = pr.getImage().getWidth() / (double) pr.getImage().getHeight(); + task.height = 1.0; + task.level = VisualizationTask.LEVEL_STATIC; + baseResult.getHierarchy().add(pr, task); + } + } + + @Override + public Visualization makeVisualization(VisualizationTask task) { + return new Instance(task); } @Override - protected void redraw() { - // TODO: Use width, height, imgratio, number of OPTICS plots! - double scale = StyleLibrary.SCALE; - - final double sizex = scale; - final double sizey = scale * task.getHeight() / task.getWidth(); - final double margin = 0.0; // context.getStyleLibrary().getSize(StyleLibrary.MARGIN); - layer = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG); - final String transform = SVGUtil.makeMarginTransform(task.getWidth(), task.getHeight(), sizex, sizey, margin); - SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); - - RenderedImage img = result.getImage(); - // is ratio, target ratio - double iratio = img.getHeight() / img.getWidth(); - double tratio = task.getHeight() / task.getWidth(); - // We want to place a (iratio, 1.0) object on a (tratio, 1.0) screen. - // Both dimensions must fit: - double zoom = (iratio >= tratio) ? Math.min(tratio / iratio, 1.0) : Math.max(iratio / tratio, 1.0); - - Element itag = svgp.svgElement(SVGConstants.SVG_IMAGE_TAG); - SVGUtil.setAtt(itag, SVGConstants.SVG_IMAGE_RENDERING_ATTRIBUTE, SVGConstants.SVG_OPTIMIZE_SPEED_VALUE); - SVGUtil.setAtt(itag, SVGConstants.SVG_X_ATTRIBUTE, 0); - SVGUtil.setAtt(itag, SVGConstants.SVG_Y_ATTRIBUTE, 0); - SVGUtil.setAtt(itag, SVGConstants.SVG_WIDTH_ATTRIBUTE, scale * zoom * iratio); - SVGUtil.setAtt(itag, SVGConstants.SVG_HEIGHT_ATTRIBUTE, scale * zoom); - itag.setAttributeNS(SVGConstants.XLINK_NAMESPACE_URI, SVGConstants.XLINK_HREF_QNAME, result.getAsFile().toURI().toString()); - - layer.appendChild(itag); + public boolean allowThumbnails(VisualizationTask task) { + // Don't use thumbnails + return false; } /** - * Factory class for pixmap visualizers. + * Instance. * * @author Erich Schubert * - * @apiviz.stereotype factory - * @apiviz.uses PixmapVisualizer oneway - - «create» + * @apiviz.has PixmapResult oneway - 1 visualizes */ - public static class Factory extends AbstractVisFactory { + public class Instance extends AbstractVisualization { /** - * Constructor, adhering to - * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} + * The actual pixmap result. */ - public Factory() { - super(); - } - - @Override - public void processNewResult(HierarchicalResult baseResult, Result result) { - Collection<PixmapResult> prs = ResultUtil.filterResults(result, PixmapResult.class); - for(PixmapResult pr : prs) { - // Add plots, attach visualizer - final VisualizationTask task = new VisualizationTask(NAME, pr, null, this); - task.width = pr.getImage().getWidth() / pr.getImage().getHeight(); - task.height = 1.0; - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_STATIC); - baseResult.getHierarchy().add(pr, task); - } - } + private PixmapResult result; - @Override - public Visualization makeVisualization(VisualizationTask task) { - return new PixmapVisualizer(task); + /** + * Constructor. + * + * @param task Visualization task + */ + public Instance(VisualizationTask task) { + super(task); + this.result = task.getResult(); } @Override - public boolean allowThumbnails(VisualizationTask task) { - // Don't use thumbnails - return false; + protected void redraw() { + // TODO: Use width, height, imgratio, number of OPTICS plots! + double scale = StyleLibrary.SCALE; + + final double sizex = scale; + final double sizey = scale * task.getHeight() / task.getWidth(); + final double margin = 0.0; // context.getStyleLibrary().getSize(StyleLibrary.MARGIN); + layer = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG); + final String transform = SVGUtil.makeMarginTransform(task.getWidth(), task.getHeight(), sizex, sizey, margin); + SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); + + RenderedImage img = result.getImage(); + // is ratio, target ratio + double iratio = img.getHeight() / img.getWidth(); + double tratio = task.getHeight() / task.getWidth(); + // We want to place a (iratio, 1.0) object on a (tratio, 1.0) screen. + // Both dimensions must fit: + double zoom = (iratio >= tratio) ? Math.min(tratio / iratio, 1.0) : Math.max(iratio / tratio, 1.0); + + Element itag = svgp.svgElement(SVGConstants.SVG_IMAGE_TAG); + SVGUtil.setAtt(itag, SVGConstants.SVG_IMAGE_RENDERING_ATTRIBUTE, SVGConstants.SVG_OPTIMIZE_SPEED_VALUE); + SVGUtil.setAtt(itag, SVGConstants.SVG_X_ATTRIBUTE, 0); + SVGUtil.setAtt(itag, SVGConstants.SVG_Y_ATTRIBUTE, 0); + SVGUtil.setAtt(itag, SVGConstants.SVG_WIDTH_ATTRIBUTE, scale * zoom * iratio); + SVGUtil.setAtt(itag, SVGConstants.SVG_HEIGHT_ATTRIBUTE, scale * zoom); + itag.setAttributeNS(SVGConstants.XLINK_NAMESPACE_URI, SVGConstants.XLINK_HREF_QNAME, result.getAsFile().toURI().toString()); + + layer.appendChild(itag); } } }
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/SettingsVisFactory.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/SettingsVisualization.java index 2d726257..736f28e7 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/SettingsVisFactory.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/SettingsVisualization.java @@ -41,7 +41,7 @@ import de.lmu.ifi.dbs.elki.visualization.style.StyleLibrary; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil; import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisFactory; -import de.lmu.ifi.dbs.elki.visualization.visualizers.StaticVisualization; +import de.lmu.ifi.dbs.elki.visualization.visualizers.StaticVisualizationInstance; import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; /** @@ -50,11 +50,11 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * @author Erich Schubert * * @apiviz.stereotype factory - * @apiviz.uses StaticVisualization oneway - - «create» + * @apiviz.uses StaticVisualizationInstance oneway - - «create» * @apiviz.has SettingsResult oneway - - visualizes */ // TODO: make this a menu item instead of a "visualization"? -public class SettingsVisFactory extends AbstractVisFactory { +public class SettingsVisualization extends AbstractVisFactory { /** * Name for this visualizer. */ @@ -64,7 +64,7 @@ public class SettingsVisFactory extends AbstractVisFactory { * Constructor, adhering to * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} */ - public SettingsVisFactory() { + public SettingsVisualization() { super(); } @@ -74,7 +74,7 @@ public class SettingsVisFactory extends AbstractVisFactory { VisualizerContext context = task.getContext(); SVGPlot svgp = task.getPlot(); - Collection<Pair<Object, Parameter<?, ?>>> settings = sr.getSettings(); + Collection<Pair<Object, Parameter<?>>> settings = sr.getSettings(); Element layer = svgp.svgElement(SVGConstants.SVG_G_TAG); @@ -82,7 +82,7 @@ public class SettingsVisFactory extends AbstractVisFactory { int i = 0; Object last = null; - for(Pair<Object, Parameter<?, ?>> setting : settings) { + for(Pair<Object, Parameter<?>> setting : settings) { if(setting.first != last && setting.first != null) { String name; try { @@ -128,11 +128,11 @@ public class SettingsVisFactory extends AbstractVisFactory { int cols = Math.max(30, (int) (i * task.getHeight() / task.getWidth())); int rows = i; - final double margin = context.getStyleLibrary().getSize(StyleLibrary.MARGIN); + final double margin = context.getStyleResult().getStyleLibrary().getSize(StyleLibrary.MARGIN); final String transform = SVGUtil.makeMarginTransform(task.getWidth(), task.getHeight(), cols, rows, margin / StyleLibrary.SCALE); SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); - return new StaticVisualization(task, layer); + return new StaticVisualizationInstance(task, layer); } @Override @@ -142,7 +142,7 @@ public class SettingsVisFactory extends AbstractVisFactory { final VisualizationTask task = new VisualizationTask(NAME, sr, null, this); task.width = 1.0; task.height = 1.0; - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_STATIC); + task.level = VisualizationTask.LEVEL_STATIC; baseResult.getHierarchy().add(sr, task); } } diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/SimilarityMatrixVisualizer.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/SimilarityMatrixVisualizer.java index c53a722a..f1404050 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/SimilarityMatrixVisualizer.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/SimilarityMatrixVisualizer.java @@ -29,7 +29,7 @@ import java.util.Collection; import org.apache.batik.util.SVGConstants; import org.w3c.dom.Element; -import de.lmu.ifi.dbs.elki.database.ids.DBID; +import de.lmu.ifi.dbs.elki.database.ids.DBIDIter; import de.lmu.ifi.dbs.elki.database.relation.Relation; import de.lmu.ifi.dbs.elki.evaluation.similaritymatrix.ComputeSimilarityMatrixImage; import de.lmu.ifi.dbs.elki.evaluation.similaritymatrix.ComputeSimilarityMatrixImage.SimilarityMatrix; @@ -49,123 +49,121 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * * @author Erich Schubert * - * @apiviz.has SimilarityMatrix oneway - 1 visualizes + * @apiviz.stereotype factory + * @apiviz.uses Instance oneway - - «create» */ -public class SimilarityMatrixVisualizer extends AbstractVisualization { +public class SimilarityMatrixVisualizer extends AbstractVisFactory { /** * Name for this visualizer. */ private static final String NAME = "Similarity Matrix Visualizer"; /** - * The actual pixmap result. + * Constructor, adhering to + * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} */ - private SimilarityMatrix result; - - /** - * Constructor. - * - * @param task Visualization task - */ - public SimilarityMatrixVisualizer(VisualizationTask task) { - super(task); - this.result = task.getResult(); + public SimilarityMatrixVisualizer() { + super(); } @Override - protected void redraw() { - // TODO: Use width, height, imgratio, number of OPTICS plots! - double scale = StyleLibrary.SCALE; - - final double sizex = scale; - final double sizey = scale * task.getHeight() / task.getWidth(); - final double margin = context.getStyleLibrary().getSize(StyleLibrary.MARGIN); - layer = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG); - final String transform = SVGUtil.makeMarginTransform(task.getWidth(), task.getHeight(), sizex, sizey, margin); - SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); - - RenderedImage img = result.getImage(); - // is ratio, target ratio - double iratio = img.getHeight() / img.getWidth(); - double tratio = task.getHeight() / task.getWidth(); - // We want to place a (iratio, 1.0) object on a (tratio, 1.0) screen. - // Both dimensions must fit: - double zoom = (iratio >= tratio) ? Math.min(tratio / iratio, 1.0) : Math.max(iratio / tratio, 1.0); - - Element itag = svgp.svgElement(SVGConstants.SVG_IMAGE_TAG); - SVGUtil.setAtt(itag, SVGConstants.SVG_IMAGE_RENDERING_ATTRIBUTE, SVGConstants.SVG_OPTIMIZE_SPEED_VALUE); - SVGUtil.setAtt(itag, SVGConstants.SVG_X_ATTRIBUTE, margin * 0.75); - SVGUtil.setAtt(itag, SVGConstants.SVG_Y_ATTRIBUTE, margin * 0.75); - SVGUtil.setAtt(itag, SVGConstants.SVG_WIDTH_ATTRIBUTE, scale * zoom * iratio); - SVGUtil.setAtt(itag, SVGConstants.SVG_HEIGHT_ATTRIBUTE, scale * zoom); - itag.setAttributeNS(SVGConstants.XLINK_NAMESPACE_URI, SVGConstants.XLINK_HREF_QNAME, result.getAsFile().toURI().toString()); - layer.appendChild(itag); - - // Add object labels - final int size = result.getIDs().size(); - final double hlsize = scale * zoom * iratio / size; - final double vlsize = scale * zoom / size; - int i = 0; - final Relation<String> lrep = DatabaseUtil.guessObjectLabelRepresentation(result.getRelation().getDatabase()); - for(DBID id : result.getIDs()) { - String label = lrep.get(id); - if(label != null) { - // Label on horizontal axis - final double hlx = margin * 0.75 + hlsize * (i + .8); - final double hly = margin * 0.7; - Element lbl = svgp.svgText(hlx, hly, label); - SVGUtil.setAtt(lbl, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, "rotate(-90," + hlx + "," + hly + ")"); - SVGUtil.setAtt(lbl, SVGConstants.SVG_STYLE_ATTRIBUTE, "font-size: " + hlsize * 0.8); - layer.appendChild(lbl); - // Label on vertical axis - Element lbl2 = svgp.svgText(margin * 0.7, margin * 0.75 + vlsize * (i + .8), label); - SVGUtil.setAtt(lbl2, SVGConstants.SVG_TEXT_ANCHOR_ATTRIBUTE, SVGConstants.SVG_END_VALUE); - SVGUtil.setAtt(lbl2, SVGConstants.SVG_STYLE_ATTRIBUTE, "font-size: " + vlsize * 0.8); - layer.appendChild(lbl2); - } - i++; + public void processNewResult(HierarchicalResult baseResult, Result result) { + Collection<ComputeSimilarityMatrixImage.SimilarityMatrix> prs = ResultUtil.filterResults(result, ComputeSimilarityMatrixImage.SimilarityMatrix.class); + for(ComputeSimilarityMatrixImage.SimilarityMatrix pr : prs) { + // Add plots, attach visualizer + final VisualizationTask task = new VisualizationTask(NAME, pr, null, this); + task.width = 1.0; + task.height = 1.0; + task.level = VisualizationTask.LEVEL_STATIC; + baseResult.getHierarchy().add(pr, task); } } + @Override + public Visualization makeVisualization(VisualizationTask task) { + return new Instance(task); + } + + @Override + public boolean allowThumbnails(VisualizationTask task) { + // Don't use thumbnails + return false; + } + /** - * Factory class for pixmap visualizers. + * Instance * * @author Erich Schubert * - * @apiviz.stereotype factory - * @apiviz.uses PixmapVisualizer oneway - - «create» + * @apiviz.has SimilarityMatrix oneway - 1 visualizes */ - public static class Factory extends AbstractVisFactory { + public class Instance extends AbstractVisualization { /** - * Constructor, adhering to - * {@link de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable} + * The actual pixmap result. */ - public Factory() { - super(); - } - - @Override - public void processNewResult(HierarchicalResult baseResult, Result result) { - Collection<ComputeSimilarityMatrixImage.SimilarityMatrix> prs = ResultUtil.filterResults(result, ComputeSimilarityMatrixImage.SimilarityMatrix.class); - for(ComputeSimilarityMatrixImage.SimilarityMatrix pr : prs) { - // Add plots, attach visualizer - final VisualizationTask task = new VisualizationTask(NAME, pr, null, this); - task.width = 1.0; - task.height = 1.0; - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_STATIC); - baseResult.getHierarchy().add(pr, task); - } - } + private SimilarityMatrix result; - @Override - public Visualization makeVisualization(VisualizationTask task) { - return new SimilarityMatrixVisualizer(task); + /** + * Constructor. + * + * @param task Visualization task + */ + public Instance(VisualizationTask task) { + super(task); + this.result = task.getResult(); } @Override - public boolean allowThumbnails(VisualizationTask task) { - // Don't use thumbnails - return false; + protected void redraw() { + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + final double sizex = StyleLibrary.SCALE; + final double sizey = StyleLibrary.SCALE * task.getHeight() / task.getWidth(); + final double margin = style.getSize(StyleLibrary.MARGIN); + layer = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG); + final String transform = SVGUtil.makeMarginTransform(task.getWidth(), task.getHeight(), sizex, sizey, margin); + SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); + + RenderedImage img = result.getImage(); + // is ratio, target ratio + double iratio = img.getHeight() / img.getWidth(); + double tratio = task.getHeight() / task.getWidth(); + // We want to place a (iratio, 1.0) object on a (tratio, 1.0) screen. + // Both dimensions must fit: + double zoom = (iratio >= tratio) ? Math.min(tratio / iratio, 1.0) : Math.max(iratio / tratio, 1.0); + + Element itag = svgp.svgElement(SVGConstants.SVG_IMAGE_TAG); + SVGUtil.setAtt(itag, SVGConstants.SVG_IMAGE_RENDERING_ATTRIBUTE, SVGConstants.SVG_OPTIMIZE_SPEED_VALUE); + SVGUtil.setAtt(itag, SVGConstants.SVG_X_ATTRIBUTE, margin * 0.75); + SVGUtil.setAtt(itag, SVGConstants.SVG_Y_ATTRIBUTE, margin * 0.75); + SVGUtil.setAtt(itag, SVGConstants.SVG_WIDTH_ATTRIBUTE, StyleLibrary.SCALE * zoom * iratio); + SVGUtil.setAtt(itag, SVGConstants.SVG_HEIGHT_ATTRIBUTE, StyleLibrary.SCALE * zoom); + itag.setAttributeNS(SVGConstants.XLINK_NAMESPACE_URI, SVGConstants.XLINK_HREF_QNAME, result.getAsFile().toURI().toString()); + layer.appendChild(itag); + + // Add object labels + final int size = result.getIDs().size(); + final double hlsize = StyleLibrary.SCALE * zoom * iratio / size; + final double vlsize = StyleLibrary.SCALE * zoom / size; + int i = 0; + final Relation<String> lrep = DatabaseUtil.guessObjectLabelRepresentation(result.getRelation().getDatabase()); + for(DBIDIter id = result.getIDs().iter(); id.valid(); id.advance()) { + String label = lrep.get(id); + if(label != null) { + // Label on horizontal axis + final double hlx = margin * 0.75 + hlsize * (i + .8); + final double hly = margin * 0.7; + Element lbl = svgp.svgText(hlx, hly, label); + SVGUtil.setAtt(lbl, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, "rotate(-90," + hlx + "," + hly + ")"); + SVGUtil.setAtt(lbl, SVGConstants.SVG_STYLE_ATTRIBUTE, "font-size: " + hlsize * 0.8); + layer.appendChild(lbl); + // Label on vertical axis + Element lbl2 = svgp.svgText(margin * 0.7, margin * 0.75 + vlsize * (i + .8), label); + SVGUtil.setAtt(lbl2, SVGConstants.SVG_TEXT_ANCHOR_ATTRIBUTE, SVGConstants.SVG_END_VALUE); + SVGUtil.setAtt(lbl2, SVGConstants.SVG_STYLE_ATTRIBUTE, "font-size: " + vlsize * 0.8); + layer.appendChild(lbl2); + } + i++; + } } } }
\ No newline at end of file diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/XYCurveVisFactory.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/XYCurveVisualization.java index 507de376..76ce7c09 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/XYCurveVisFactory.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/visunproj/XYCurveVisualization.java @@ -48,7 +48,7 @@ import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; import de.lmu.ifi.dbs.elki.visualization.svg.SVGSimpleLinearAxis; import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil; import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisFactory; -import de.lmu.ifi.dbs.elki.visualization.visualizers.StaticVisualization; +import de.lmu.ifi.dbs.elki.visualization.visualizers.StaticVisualizationInstance; import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; /** @@ -57,10 +57,10 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; * @author Erich Schubert * * @apiviz.stereotype factory - * @apiviz.uses StaticVisualization oneway - - «create» + * @apiviz.uses StaticVisualizationInstance oneway - - «create» * @apiviz.has XYCurve oneway - - visualizes */ -public class XYCurveVisFactory extends AbstractVisFactory { +public class XYCurveVisualization extends AbstractVisFactory { /** * Name for this visualizer. */ @@ -79,7 +79,7 @@ public class XYCurveVisFactory extends AbstractVisFactory { /** * Constructor, Parameterizable style - does nothing. */ - public XYCurveVisFactory() { + public XYCurveVisualization() { super(); } @@ -90,10 +90,10 @@ public class XYCurveVisFactory extends AbstractVisFactory { XYCurve curve = task.getResult(); setupCSS(context, svgp); - double scale = StyleLibrary.SCALE; - final double sizex = scale; - final double sizey = scale * task.getHeight() / task.getWidth(); - final double margin = context.getStyleLibrary().getSize(StyleLibrary.MARGIN); + final StyleLibrary style = context.getStyleResult().getStyleLibrary(); + final double sizex = StyleLibrary.SCALE; + final double sizey = StyleLibrary.SCALE * task.getHeight() / task.getWidth(); + final double margin = style.getSize(StyleLibrary.MARGIN); Element layer = SVGUtil.svgElement(svgp.getDocument(), SVGConstants.SVG_G_TAG); final String transform = SVGUtil.makeMarginTransform(task.getWidth(), task.getHeight(), sizex, sizey, margin); SVGUtil.setAtt(layer, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, transform); @@ -113,15 +113,15 @@ public class XYCurveVisFactory extends AbstractVisFactory { // add axes try { - SVGSimpleLinearAxis.drawAxis(svgp, layer, scaley, 0, sizey, 0, 0, SVGSimpleLinearAxis.LabelStyle.LEFTHAND, context.getStyleLibrary()); - SVGSimpleLinearAxis.drawAxis(svgp, layer, scalex, 0, sizey, sizex, sizey, SVGSimpleLinearAxis.LabelStyle.RIGHTHAND, context.getStyleLibrary()); + SVGSimpleLinearAxis.drawAxis(svgp, layer, scaley, 0, sizey, 0, 0, SVGSimpleLinearAxis.LabelStyle.LEFTHAND, style); + SVGSimpleLinearAxis.drawAxis(svgp, layer, scalex, 0, sizey, sizex, sizey, SVGSimpleLinearAxis.LabelStyle.RIGHTHAND, style); } catch(CSSNamingConflict e) { LoggingUtil.exception(e); } // Add axis labels { - Element labelx = svgp.svgText(sizex / 2, sizey + margin * .9, curve.getLabelx()); + Element labelx = svgp.svgText(sizex * .5, sizey + margin * .9, curve.getLabelx()); SVGUtil.setCSSClass(labelx, CSS_AXIS_LABEL); layer.appendChild(labelx); Element labely = svgp.svgText(margin * -.8, sizey * .5, curve.getLabely()); @@ -161,7 +161,7 @@ public class XYCurveVisFactory extends AbstractVisFactory { } layer.appendChild(line); - return new StaticVisualization(task, layer); + return new StaticVisualizationInstance(task, layer); } /** @@ -170,7 +170,7 @@ public class XYCurveVisFactory extends AbstractVisFactory { * @param svgp Plot */ private void setupCSS(VisualizerContext context, SVGPlot svgp) { - StyleLibrary style = context.getStyleLibrary(); + StyleLibrary style = context.getStyleResult().getStyleLibrary(); CSSClass csscls = new CSSClass(this, SERIESID); // csscls.setStatement(SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE, "0.2%"); csscls.setStatement(SVGConstants.SVG_FILL_ATTRIBUTE, SVGConstants.SVG_NONE_VALUE); @@ -193,7 +193,7 @@ public class XYCurveVisFactory extends AbstractVisFactory { final VisualizationTask task = new VisualizationTask(NAME, curve, null, this); task.width = 1.0; task.height = 1.0; - task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_STATIC); + task.level = VisualizationTask.LEVEL_STATIC; baseResult.getHierarchy().add(curve, task); } } |