summaryrefslogtreecommitdiff
path: root/src/de/lmu/ifi/dbs/elki/visualization/gui/overview
diff options
context:
space:
mode:
Diffstat (limited to 'src/de/lmu/ifi/dbs/elki/visualization/gui/overview')
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/gui/overview/DetailViewSelectedEvent.java54
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/gui/overview/OverviewPlot.java383
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/gui/overview/PlotItem.java200
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/gui/overview/PlotMap.java124
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/gui/overview/RectangleArranger.java491
5 files changed, 848 insertions, 404 deletions
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/DetailViewSelectedEvent.java b/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/DetailViewSelectedEvent.java
index 0ea59547..085fc262 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/DetailViewSelectedEvent.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/DetailViewSelectedEvent.java
@@ -1,26 +1,27 @@
package de.lmu.ifi.dbs.elki.visualization.gui.overview;
+
/*
-This file is part of ELKI:
-Environment for Developing KDD-Applications Supported by Index-Structures
+ This file is part of ELKI:
+ Environment for Developing KDD-Applications Supported by Index-Structures
-Copyright (C) 2011
-Ludwig-Maximilians-Universität München
-Lehr- und Forschungseinheit für Datenbanksysteme
-ELKI Development Team
+ Copyright (C) 2011
+ 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 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.
+ 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/>.
-*/
+ 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.awt.event.ActionEvent;
@@ -44,14 +45,9 @@ public class DetailViewSelectedEvent extends ActionEvent {
OverviewPlot overview;
/**
- * X Coordinate
- */
- double x;
-
- /**
- * X Coordinate
+ * Plot item selected
*/
- double y;
+ PlotItem it;
/**
* Constructor. To be called by OverviewPlot only!
@@ -60,14 +56,12 @@ public class DetailViewSelectedEvent extends ActionEvent {
* @param id ID
* @param command command that was invoked
* @param modifiers modifiers
- * @param x x click
- * @param y y click
+ * @param it Plot item selected
*/
- public DetailViewSelectedEvent(OverviewPlot source, int id, String command, int modifiers, double x, double y) {
+ public DetailViewSelectedEvent(OverviewPlot source, int id, String command, int modifiers, PlotItem it) {
super(source, id, command, modifiers);
this.overview = source;
- this.x = x;
- this.y = y;
+ this.it = it;
}
/**
@@ -76,6 +70,6 @@ public class DetailViewSelectedEvent extends ActionEvent {
* @return materialized detail plot
*/
public DetailView makeDetailView() {
- return overview.makeDetailView(x, y);
+ return overview.makeDetailView(it);
}
} \ No newline at end of file
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 6fd1f0f8..5c8b8d44 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
@@ -1,32 +1,35 @@
package de.lmu.ifi.dbs.elki.visualization.gui.overview;
-/*
-This file is part of ELKI:
-Environment for Developing KDD-Applications Supported by Index-Structures
-
-Copyright (C) 2011
-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/>.
-*/
+/*
+ This file is part of ELKI:
+ Environment for Developing KDD-Applications Supported by Index-Structures
+
+ Copyright (C) 2011
+ 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.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
-import java.util.List;
+import java.util.Iterator;
+import java.util.Map.Entry;
import org.apache.batik.util.SVGConstants;
import org.w3c.dom.Element;
@@ -34,33 +37,24 @@ 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.data.type.TypeUtil;
-import de.lmu.ifi.dbs.elki.database.Database;
-import de.lmu.ifi.dbs.elki.database.relation.Relation;
+import de.lmu.ifi.dbs.elki.logging.Logging;
import de.lmu.ifi.dbs.elki.logging.LoggingUtil;
-import de.lmu.ifi.dbs.elki.math.linearalgebra.AffineTransformation;
+import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
import de.lmu.ifi.dbs.elki.result.Result;
+import de.lmu.ifi.dbs.elki.result.ResultHierarchy;
import de.lmu.ifi.dbs.elki.result.ResultListener;
-import de.lmu.ifi.dbs.elki.utilities.DatabaseUtil;
+import de.lmu.ifi.dbs.elki.result.ResultUtil;
import de.lmu.ifi.dbs.elki.utilities.pairs.Pair;
+import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
+import de.lmu.ifi.dbs.elki.visualization.VisualizerContext;
import de.lmu.ifi.dbs.elki.visualization.batikutil.CSSHoverClass;
import de.lmu.ifi.dbs.elki.visualization.css.CSSClass;
import de.lmu.ifi.dbs.elki.visualization.gui.detail.DetailView;
-import de.lmu.ifi.dbs.elki.visualization.projections.AffineProjection;
-import de.lmu.ifi.dbs.elki.visualization.projections.Projection1D;
-import de.lmu.ifi.dbs.elki.visualization.projections.Projection2D;
-import de.lmu.ifi.dbs.elki.visualization.projections.Simple1D;
-import de.lmu.ifi.dbs.elki.visualization.projections.Simple2D;
-import de.lmu.ifi.dbs.elki.visualization.scales.LinearScale;
-import de.lmu.ifi.dbs.elki.visualization.scales.Scales;
+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.svg.SVGUtil;
import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization;
-import de.lmu.ifi.dbs.elki.visualization.visualizers.VisualizationTask;
-import de.lmu.ifi.dbs.elki.visualization.visualizers.VisualizerContext;
import de.lmu.ifi.dbs.elki.visualization.visualizers.VisualizerUtil;
-import de.lmu.ifi.dbs.elki.visualization.visualizers.visunproj.LabelVisFactory;
/**
* Generate an overview plot for a set of visualizations.
@@ -77,16 +71,9 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.visunproj.LabelVisFactory;
*/
public class OverviewPlot extends SVGPlot implements ResultListener {
/**
- * Maximum number of dimensions to visualize.
- *
- * TODO: Erich: add scrolling function for higher dimensionality!
+ * Our logging class
*/
- public static final int MAX_DIMENSIONS_DEFAULT = 10;
-
- /**
- * Stores the maximum number of dimensions to show.
- */
- private int maxdim = MAX_DIMENSIONS_DEFAULT;
+ private static final Logging logger = Logging.getLogger(OverviewPlot.class);
/**
* Visualizer context
@@ -94,20 +81,14 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
private VisualizerContext context;
/**
- * Database we work on.
- */
- private Database db;
-
- /**
* Result we work on. Currently unused, but kept for future requirements.
*/
- @SuppressWarnings("unused")
- private Result result;
+ private HierarchicalResult result;
/**
* Map of coordinates to plots.
*/
- protected PlotMap<NumberVector<?, ?>> plotmap;
+ protected RectangleArranger<PlotItem> plotmap;
/**
* Action listeners for this plot.
@@ -117,15 +98,11 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
/**
* Constructor.
*
- * @param db Database
* @param result Result to visualize
- * @param maxdim Maximum number of dimensions
* @param context Visualizer context
*/
- public OverviewPlot(Database db, Result result, int maxdim, VisualizerContext context) {
+ public OverviewPlot(HierarchicalResult result, VisualizerContext context) {
super();
- this.maxdim = maxdim;
- this.db = db;
this.result = result;
this.context = context;
// register context listener
@@ -173,105 +150,49 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
private double ratio = 1.0;
/**
+ * Pending refresh, for lazy refreshing
+ */
+ Runnable pendingRefresh;
+
+ /**
+ * Reinitialize on refresh
+ */
+ private boolean reinitOnRefresh = true;
+
+ /**
* Recompute the layout of visualizations.
*/
private void arrangeVisualizations() {
- // split the visualizers into three sets.
- // FIXME: THIS IS VERY UGLY, and needs to be refactored.
- // (This is a remainder of merging adapters and visualizationfactories)
- List<VisualizationTask> vis = new ArrayList<VisualizationTask>();
- for(VisualizationTask task : context.iterVisualizers()) {
- vis.add(task);
- }
- // We'll use three regions for now:
- // 2D projections starting at 0,0 and going right and down.
- // 1D projections starting at 0, -1 and going right
- // Other projections starting at -1, min() and going down.
- plotmap = new PlotMap<NumberVector<?, ?>>();
- // FIXME: ugly cast used here.
- Relation<NumberVector<?, ?>> dvdb = db.getRelation(TypeUtil.DOUBLE_VECTOR_FIELD);
- LinearScale[] scales = null;
- scales = Scales.calcScales(dvdb);
- int dmax = Math.min(DatabaseUtil.dimensionality(dvdb), maxdim);
- for(int d1 = 1; d1 <= dmax; d1++) {
- for(int d2 = d1 + 1; d2 <= dmax; d2++) {
- Projection2D proj = new Simple2D(scales, d1, d2);
-
- for(VisualizationTask task : vis) {
- if(task.getFactory().getProjectionType() == Projection2D.class) {
- plotmap.addVis(d1 - 1, d2 - 2, 1., 1., proj, task);
- }
- }
+ plotmap = new RectangleArranger<PlotItem>(ratio);
+
+ ArrayList<Projector> projectors = ResultUtil.filterResults(result, Projector.class);
+ // Rectangle layout
+ for(Projector p : projectors) {
+ Collection<PlotItem> projs = p.arrange();
+ for(PlotItem it : projs) {
+ plotmap.put(it.w, it.h, it);
}
}
- if(dmax >= 3) {
- AffineTransformation p = AffineProjection.axisProjection(DatabaseUtil.dimensionality(dvdb), 1, 2);
- p.addRotation(0, 2, Math.PI / 180 * -10.);
- p.addRotation(1, 2, Math.PI / 180 * 15.);
- // Wanna try 4d? go ahead:
- // p.addRotation(0, 3, Math.PI / 180 * -20.);
- // p.addRotation(1, 3, Math.PI / 180 * 30.);
- final double sizeh = Math.ceil((dmax - 1) / 2.0);
- Projection2D proj = new AffineProjection(scales, p);
- for(VisualizationTask task : vis) {
- if(task.getFactory().getProjectionType() == Projection2D.class) {
- plotmap.addVis(Math.ceil((dmax - 1) / 2.0), 0.0, sizeh, sizeh, proj, task);
- }
- }
- }
- // insert column numbers
- for(int d1 = 1; d1 <= dmax; d1++) {
- VisualizationTask colvi = new VisualizationTask("", context, null, null, new LabelVisFactory(Integer.toString(d1)), null, null, this, 1, .1);
- colvi.put(VisualizationTask.META_NODETAIL, true);
- plotmap.addVis(d1 - 1, -.1, 1., .1, null, colvi);
- }
- // insert row numbers
- for(int d1 = 2; d1 <= dmax; d1++) {
- VisualizationTask colvi = new VisualizationTask("", context, null, null, new LabelVisFactory(Integer.toString(d1)), null, null, this, .1, 1);
- colvi.put(VisualizationTask.META_NODETAIL, true);
- plotmap.addVis(-.1, d1 - 2, .1, 1., null, colvi);
- }
- {
- int dim = dmax;
- for(int d1 = 1; d1 <= dim; d1++) {
- Projection1D proj = new Simple1D(scales, d1);
- double ypos = -.1;
- for(VisualizationTask task : vis) {
- if(task.getFactory().getProjectionType() == Projection1D.class) {
- // TODO: 1d vis might have a different native scaling.
- double height = 0.5;
- plotmap.addVis(d1 - 1, ypos - height, 1.0, height, proj, task);
- //ypos = ypos - height;
- }
+
+ ResultHierarchy hier = result.getHierarchy();
+ ArrayList<VisualizationTask> tasks = ResultUtil.filterResults(result, VisualizationTask.class);
+ for(VisualizationTask task : tasks) {
+ boolean isprojected = false;
+ for(Result parent : hier.getParents(task)) {
+ if(parent instanceof Projector) {
+ isprojected = true;
+ break;
}
}
- }
- {
- HashMap<Object, double[]> stackmap = new HashMap<Object, double[]>();
- // find starting position.
- Double pos = plotmap.minmaxy.getMin();
- if(pos == null) {
- pos = 0.0;
- }
- // FIXME: use multiple columns!
- for(VisualizationTask task : vis) {
- if(task.getFactory().getProjectionType() == Projection1D.class) {
- continue;
- }
- if(task.getFactory().getProjectionType() == Projection2D.class) {
- continue;
- }
- double[] p = null;
- if(task.getVisualizationStack() != null) {
- p = stackmap.get(task.getVisualizationStack());
+ if(!isprojected) {
+ if(task.getWidth() <= 0.0 || task.getHeight() <= 0.0) {
+ logger.warning("Task with improper size information: " + task);
}
- if(p == null) {
- p = new double[] { -1.1, pos };
- pos += 1.0;
- stackmap.put(task.getVisualizationStack(), p);
+ else {
+ PlotItem it = new PlotItem(task.getWidth(), task.getHeight(), null);
+ it.visualizations.add(task);
+ plotmap.put(it.w, it.h, it);
}
- // TODO: might have different scaling preferences
- plotmap.addVis(p[0], p[1], 1., 1., null, task);
}
}
}
@@ -279,7 +200,7 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
/**
* Refresh the overview plot.
*/
- public void reinitialize() {
+ private void reinitialize() {
setupHoverer();
arrangeVisualizations();
recalcViewbox();
@@ -305,32 +226,37 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
final int thumbsize = (int) Math.max(screenwidth / plotmap.getWidth(), screenheight / plotmap.getHeight());
// TODO: kill all children in document root except style, defs etc?
- for(PlotItem it : plotmap.values()) {
- boolean hasDetails = false;
- Element g = this.svgElement(SVGConstants.SVG_G_TAG);
- SVGUtil.setAtt(g, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, "translate(" + it.x + " " + it.y + ")");
- for(VisualizationTask task : it) {
- Element parent = this.svgElement(SVGConstants.SVG_G_TAG);
- g.appendChild(parent);
- makeThumbnail(thumbsize, it, task, parent);
- vistoelem.put(new Pair<PlotItem, VisualizationTask>(it, task), parent);
-
- if(VisualizerUtil.detailsEnabled(task)) {
- hasDetails = true;
+ 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();) {
+ PlotItem it = iter.next();
+ boolean hasDetails = false;
+ Element g = this.svgElement(SVGConstants.SVG_G_TAG);
+ SVGUtil.setAtt(g, SVGConstants.SVG_TRANSFORM_ATTRIBUTE, "translate(" + (basex + it.x) + " " + (basey + it.y) + ")");
+ for(VisualizationTask task : it.visualizations) {
+ Element parent = this.svgElement(SVGConstants.SVG_G_TAG);
+ g.appendChild(parent);
+ makeThumbnail(thumbsize, it, task, parent);
+ vistoelem.put(new Pair<PlotItem, VisualizationTask>(it, task), parent);
+
+ if(VisualizerUtil.detailsEnabled(task)) {
+ hasDetails = true;
+ }
+ }
+ plotlayer.appendChild(g);
+ if(hasDetails) {
+ Element hover = this.svgRect(basex + it.x, basey + it.y, it.w, it.h);
+ SVGUtil.addCSSClass(hover, selcss.getName());
+ // link hoverer.
+ EventTarget targ = (EventTarget) hover;
+ targ.addEventListener(SVGConstants.SVG_MOUSEOVER_EVENT_TYPE, hoverer, false);
+ targ.addEventListener(SVGConstants.SVG_MOUSEOUT_EVENT_TYPE, hoverer, false);
+ targ.addEventListener(SVGConstants.SVG_CLICK_EVENT_TYPE, hoverer, false);
+ targ.addEventListener(SVGConstants.SVG_CLICK_EVENT_TYPE, new SelectPlotEvent(it), false);
+
+ hoverlayer.appendChild(hover);
}
- }
- plotlayer.appendChild(g);
- if(hasDetails) {
- Element hover = this.svgRect(it.x, it.y, it.w, it.h);
- SVGUtil.addCSSClass(hover, selcss.getName());
- // link hoverer.
- EventTarget targ = (EventTarget) hover;
- targ.addEventListener(SVGConstants.SVG_MOUSEOVER_EVENT_TYPE, hoverer, false);
- targ.addEventListener(SVGConstants.SVG_MOUSEOUT_EVENT_TYPE, hoverer, false);
- targ.addEventListener(SVGConstants.SVG_CLICK_EVENT_TYPE, hoverer, false);
- targ.addEventListener(SVGConstants.SVG_CLICK_EVENT_TYPE, new SelectPlotEvent(it.x, it.y), false);
-
- hoverlayer.appendChild(hover);
}
}
getRoot().appendChild(plotlayer);
@@ -364,38 +290,47 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
/**
* Do a refresh (when visibilities have changed).
*/
- public void refresh() {
- if(vistoelem == null || plotlayer == null || hoverlayer == null) {
+ synchronized void refresh() {
+ logger.debug("Refresh");
+ if(vistoelem == null || plotlayer == null || hoverlayer == null || reinitOnRefresh) {
reinitialize();
+ reinitOnRefresh = false;
}
else {
+ boolean refreshcss = false;
final int thumbsize = (int) Math.max(screenwidth / plotmap.getWidth(), screenheight / plotmap.getHeight());
- for(PlotItem it : plotmap.values()) {
- for(VisualizationTask task : it) {
- Element gg = vistoelem.get(new Pair<PlotItem, VisualizationTask>(it, task));
- if(gg == null) {
- LoggingUtil.warning("No container element found for " + task);
+ for(Entry<PlotItem, double[]> ent : plotmap.entrySet()) {
+ PlotItem it = ent.getKey();
+ for(Iterator<VisualizationTask> iter = it.visIterator(); iter.hasNext(); ) {
+ VisualizationTask task = iter.next();
+ Element parent = vistoelem.get(new Pair<PlotItem, VisualizationTask>(it, task));
+ if(parent == null) {
+ LoggingUtil.warning("No container element produced by " + task);
continue;
}
if(VisualizerUtil.thumbnailEnabled(task) && VisualizerUtil.isVisible(task)) {
// unhide when hidden.
- if(gg.hasAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY)) {
- gg.removeAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY);
+ if(parent.hasAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY)) {
+ parent.removeAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY);
}
// if not yet rendered, add a thumbnail
- if(!gg.hasChildNodes()) {
- makeThumbnail(thumbsize, it, task, gg);
+ if(!parent.hasChildNodes()) {
+ makeThumbnail(thumbsize, it, task, parent);
+ refreshcss = true;
}
}
else {
// hide if there is anything to hide.
- if(gg != null && gg.hasChildNodes()) {
- gg.setAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY, SVGConstants.CSS_HIDDEN_VALUE);
+ if(parent != null && parent.hasChildNodes()) {
+ parent.setAttribute(SVGConstants.CSS_VISIBILITY_PROPERTY, SVGConstants.CSS_HIDDEN_VALUE);
}
// TODO: unqueue pending thumbnails
}
}
}
+ if(refreshcss) {
+ updateStyleElement();
+ }
}
}
@@ -404,7 +339,7 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
*/
private void recalcViewbox() {
// Recalculate bounding box.
- String vb = plotmap.minmaxx.getMin() + " " + plotmap.minmaxy.getMin() + " " + plotmap.getWidth() + " " + plotmap.getHeight();
+ String vb = "0 0 " + plotmap.getWidth() + " " + plotmap.getHeight();
// Reset root bounding box.
SVGUtil.setAtt(getRoot(), SVGConstants.SVG_WIDTH_ATTRIBUTE, "20cm");
SVGUtil.setAtt(getRoot(), SVGConstants.SVG_HEIGHT_ATTRIBUTE, (20 / plotmap.getWidth() * plotmap.getHeight()) + "cm");
@@ -432,13 +367,11 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
/**
* Event triggered when a plot was selected.
*
- * @param x X coordinate
- * @param y Y coordinate
+ * @param it Plot item selected
* @return sub plot
*/
- public DetailView makeDetailView(double x, double y) {
- PlotItem layers = plotmap.get(x, y);
- return new DetailView(context, layers, ratio);
+ public DetailView makeDetailView(PlotItem it) {
+ return new DetailView(context, it, ratio);
}
/**
@@ -453,13 +386,12 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
/**
* When a subplot was selected, forward the event to listeners.
*
- * @param x X coordinate
- * @param y Y coordinate
+ * @param it PlotItem selected
*/
- protected void triggerSubplotSelectEvent(double x, double y) {
+ protected void triggerSubplotSelectEvent(PlotItem it) {
// forward event to all listeners.
for(ActionListener actionListener : actionListeners) {
- actionListener.actionPerformed(new DetailViewSelectedEvent(this, ActionEvent.ACTION_PERFORMED, null, 0, x, y));
+ actionListener.actionPerformed(new DetailViewSelectedEvent(this, ActionEvent.ACTION_PERFORMED, null, 0, it));
}
}
@@ -472,30 +404,23 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
*/
public class SelectPlotEvent implements EventListener {
/**
- * X coordinate of box.
- */
- double x;
-
- /**
- * Y coordinate of box.
+ * Plot item clicked
*/
- double y;
+ PlotItem it;
/**
* Constructor.
*
- * @param x coordinate
- * @param y coordinate
+ * @param it Item that was clicked
*/
- public SelectPlotEvent(double x, double y) {
+ public SelectPlotEvent(PlotItem it) {
super();
- this.x = x;
- this.y = y;
+ this.it = it;
}
@Override
public void handleEvent(@SuppressWarnings("unused") Event evt) {
- triggerSubplotSelectEvent(x, y);
+ triggerSubplotSelectEvent(it);
}
}
@@ -522,27 +447,43 @@ public class OverviewPlot extends SVGPlot implements ResultListener {
this.ratio = ratio;
}
+ /**
+ * Trigger a redraw, but avoid excessive redraws.
+ */
+ public final void lazyRefresh() {
+ Runnable pr = new Runnable() {
+ @Override
+ public void run() {
+ if(OverviewPlot.this.pendingRefresh == this) {
+ OverviewPlot.this.pendingRefresh = null;
+ OverviewPlot.this.refresh();
+ }
+ }
+ };
+ pendingRefresh = pr;
+ scheduleUpdate(pr);
+ }
+
@SuppressWarnings("unused")
@Override
public void resultAdded(Result child, Result parent) {
- // TODO: be lazy
- if (child instanceof VisualizationTask) {
- reinitialize();
+ logger.debug("result added: " + child);
+ if(child instanceof VisualizationTask) {
+ reinitOnRefresh = true;
}
- refresh();
+ lazyRefresh();
}
- @SuppressWarnings("unused")
@Override
public void resultChanged(Result current) {
- // TODO: be lazy
- refresh();
+ logger.debug("result changed: " + current);
+ lazyRefresh();
}
@SuppressWarnings("unused")
@Override
public void resultRemoved(Result child, Result parent) {
- // TODO: be lazy
- refresh();
+ logger.debug("result removed: " + child);
+ lazyRefresh();
}
} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/PlotItem.java b/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/PlotItem.java
index 3d58d10d..cd8f5b3e 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/PlotItem.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/PlotItem.java
@@ -1,31 +1,36 @@
package de.lmu.ifi.dbs.elki.visualization.gui.overview;
+
/*
-This file is part of ELKI:
-Environment for Developing KDD-Applications Supported by Index-Structures
+ This file is part of ELKI:
+ Environment for Developing KDD-Applications Supported by Index-Structures
-Copyright (C) 2011
-Ludwig-Maximilians-Universität München
-Lehr- und Forschungseinheit für Datenbanksysteme
-ELKI Development Team
+ Copyright (C) 2011
+ 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 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.
+ 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/>.
-*/
+ 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 java.util.Collections;
+import java.util.Iterator;
import java.util.LinkedList;
+import java.util.List;
+import de.lmu.ifi.dbs.elki.visualization.VisualizationTask;
import de.lmu.ifi.dbs.elki.visualization.projections.Projection;
-import de.lmu.ifi.dbs.elki.visualization.visualizers.VisualizationTask;
/**
* Item to collect visualization tasks on a specific position on the plot map.
@@ -36,12 +41,7 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.VisualizationTask;
*
* @apiviz.composedOf Projection
*/
-public class PlotItem extends LinkedList<VisualizationTask> {
- /**
- * Serial version
- */
- private static final long serialVersionUID = 1L;
-
+public class PlotItem {
/**
* Position: x
*/
@@ -66,7 +66,28 @@ public class PlotItem extends LinkedList<VisualizationTask> {
* Projection (may be {@code null}!)
*/
public final Projection proj;
-
+
+ /**
+ * The visualizations at this location
+ */
+ public List<VisualizationTask> visualizations = new LinkedList<VisualizationTask>();
+
+ /**
+ * Subitems to plot
+ */
+ public Collection<PlotItem> subitems = new LinkedList<PlotItem>();
+
+ /**
+ * Constructor.
+ *
+ * @param w Position: w
+ * @param h Position: h
+ * @param proj Projection
+ */
+ public PlotItem(double w, double h, Projection proj) {
+ this(0, 0, w, h, proj);
+ }
+
/**
* Constructor.
*
@@ -85,9 +106,130 @@ public class PlotItem extends LinkedList<VisualizationTask> {
this.proj = proj;
}
- @Override
- public int hashCode() {
- // We can't have our hashcode change with the list contents!
- return System.identityHashCode(this);
+ /**
+ * Sort all visualizers for their proper drawing order
+ */
+ public void sort() {
+ Collections.sort(visualizations);
+ for(PlotItem subitem : subitems) {
+ subitem.sort();
+ }
+ }
+
+ /**
+ * Iterate (recursively) over all visualizations.
+ *
+ * @return Iterator
+ */
+ public Iterator<VisualizationTask> visIterator() {
+ return new VisItr();
+ }
+
+ /**
+ * Iterate (recursively) over all plot items, including itself.
+ *
+ * @return Iterator
+ */
+ public Iterator<PlotItem> itemIterator() {
+ return new ItmItr();
+ }
+
+ /**
+ * Recursive iterator
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ private class VisItr implements Iterator<VisualizationTask> {
+ Iterator<VisualizationTask> cur;
+
+ Iterator<PlotItem> sub;
+
+ /**
+ * Constructor.
+ */
+ public VisItr() {
+ super();
+ this.cur = visualizations.iterator();
+ this.sub = subitems.iterator();
+ }
+
+ @Override
+ public boolean hasNext() {
+ if(cur.hasNext()) {
+ return true;
+ }
+ if(sub.hasNext()) {
+ cur = sub.next().visIterator();
+ return hasNext();
+ }
+ return false;
+ }
+
+ @Override
+ public VisualizationTask next() {
+ hasNext();
+ return cur.next();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Recursive iterator
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ private class ItmItr implements Iterator<PlotItem> {
+ PlotItem next;
+
+ Iterator<PlotItem> cur;
+
+ Iterator<PlotItem> sub;
+
+ /**
+ * Constructor.
+ */
+ public ItmItr() {
+ super();
+ this.next = PlotItem.this;
+ this.cur = null;
+ this.sub = subitems.iterator();
+ }
+
+ @Override
+ public boolean hasNext() {
+ if(next != null) {
+ return true;
+ }
+ if (cur != null && cur.hasNext()) {
+ next = cur.next();
+ return true;
+ }
+ if(sub.hasNext()) {
+ cur = sub.next().itemIterator();
+ return hasNext();
+ }
+ return false;
+ }
+
+ @Override
+ public PlotItem next() {
+ hasNext();
+ PlotItem ret = next;
+ next = null;
+ return ret;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
}
} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/PlotMap.java b/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/PlotMap.java
deleted file mode 100644
index 40d67c06..00000000
--- a/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/PlotMap.java
+++ /dev/null
@@ -1,124 +0,0 @@
-package de.lmu.ifi.dbs.elki.visualization.gui.overview;
-/*
-This file is part of ELKI:
-Environment for Developing KDD-Applications Supported by Index-Structures
-
-Copyright (C) 2011
-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.HashMap;
-
-import de.lmu.ifi.dbs.elki.logging.LoggingUtil;
-import de.lmu.ifi.dbs.elki.math.DoubleMinMax;
-import de.lmu.ifi.dbs.elki.utilities.pairs.DoubleDoublePair;
-import de.lmu.ifi.dbs.elki.visualization.projections.Projection;
-import de.lmu.ifi.dbs.elki.visualization.visualizers.VisualizationTask;
-
-/**
- * Manage the Overview plot canvas.
- *
- * @author Erich Schubert
- *
- * @apiviz.composedOf PlotItem
- */
-class PlotMap<NV> extends HashMap<DoubleDoublePair, PlotItem> {
- /**
- * Serial version
- */
- private static final long serialVersionUID = 1L;
-
- /**
- * X coordinates seen
- */
- DoubleMinMax minmaxx = new DoubleMinMax();
-
- /**
- * Y coordinates seen
- */
- DoubleMinMax minmaxy = new DoubleMinMax();
-
- /**
- * Constructor.
- */
- PlotMap() {
- super();
- }
-
- /**
- * Place a new visualization on the chart.
- *
- * @param x X coordinate
- * @param y Y coordinate
- * @param w Width
- * @param h Height
- * @param v Visualization
- */
- void addVis(double x, double y, double w, double h, Projection proj, VisualizationTask v) {
- final DoubleDoublePair pos = new DoubleDoublePair(x, y);
- PlotItem l = this.get(pos);
- if(l == null) {
- l = new PlotItem(x, y, w, h, proj);
- this.put(pos, l);
- }
- else {
- // Sanity check
- if(l.w != w || l.h != h) {
- LoggingUtil.warning("Layout error - different object sizes at the same map position!");
- }
- if(l.proj != proj) {
- LoggingUtil.warning("Layout error - two different projections used at the same map position.");
- }
- }
- l.add(v);
- // Update min/max
- minmaxx.put(x);
- minmaxx.put(x + w);
- minmaxy.put(y);
- minmaxy.put(y + h);
- }
-
- /**
- * Get the visualization on the given coordinates.
- *
- * @param x First coordinate
- * @param y Second coordinate
- * @return Visualizations at this position.
- */
- PlotItem get(double x, double y) {
- return this.get(new DoubleDoublePair(x, y));
- }
-
- /**
- * Get width in plot units
- *
- * @return width
- */
- public double getWidth() {
- return minmaxx.getMax() - minmaxx.getMin();
- }
-
- /**
- * Get height in plot units.
- *
- * @return height
- */
- public double getHeight() {
- return minmaxy.getMax() - minmaxy.getMin();
- }
-} \ 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
new file mode 100644
index 00000000..ad87c3fc
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/visualization/gui/overview/RectangleArranger.java
@@ -0,0 +1,491 @@
+package de.lmu.ifi.dbs.elki.visualization.gui.overview;
+
+/*
+ This file is part of ELKI:
+ Environment for Developing KDD-Applications Supported by Index-Structures
+
+ Copyright (C) 2011
+ 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.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.logging.Level;
+
+import de.lmu.ifi.dbs.elki.logging.Logging;
+
+/**
+ * This is a rather naive rectangle arrangement class. It will try to place
+ * rectangles on a canvas while maintaining the canvas size ratio as good as
+ * possible. It does not do an exhaustive search for optimizing the layout, but
+ * a greedy placement strategy, extending the canvas as little as possible.
+ *
+ * @author Erich Schubert
+ *
+ * @param <T> Key type
+ */
+public class RectangleArranger<T> {
+ /**
+ * Logging class
+ */
+ private static final Logging logger = Logging.getLogger(RectangleArranger.class);
+
+ /**
+ * Target height/width ratio
+ */
+ private double ratio = 1.0;
+
+ /**
+ * Width
+ */
+ private double twidth = 1.0;
+
+ /**
+ * Height
+ */
+ private double theight = 1.0;
+
+ /**
+ * Column widths
+ */
+ private ArrayList<Double> widths = new ArrayList<Double>();
+
+ /**
+ * Column heights
+ */
+ private ArrayList<Double> heights = new ArrayList<Double>();
+
+ /**
+ * Bit sets to store usage. ArrayList = y, BitSet = x
+ */
+ private ArrayList<ArrayList<Object>> usage = new ArrayList<ArrayList<Object>>();
+
+ /**
+ * Data
+ */
+ private Map<T, double[]> map = new HashMap<T, double[]>();
+
+ /**
+ * Constructor.
+ *
+ * @param ratio
+ */
+ public RectangleArranger(double ratio) {
+ this(ratio, 1.0);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param width Canvas width
+ * @param height Canvas height
+ */
+ public RectangleArranger(double width, double height) {
+ this.ratio = width / height;
+ this.twidth = width;
+ this.theight = height;
+ this.widths.add(width);
+ this.heights.add(height);
+ // setup usage matrix
+ ArrayList<Object> u = new ArrayList<Object>();
+ u.add(null);
+ this.usage.add(u);
+ assertConsistent();
+ }
+
+ /**
+ * Add a new recangle.
+ *
+ * @param w Width
+ * @param h Height
+ * @param data Data object to add (key)
+ */
+ public void put(double w, double h, T data) {
+ logger.finest("Add: " + w + "x" + h);
+ final int cols = widths.size();
+ final int rows = heights.size();
+
+ int bestsx = -1;
+ int bestsy = -1;
+ int bestex = cols - 1;
+ int bestey = -1;
+ double bestwi;
+ double besthi;
+ double bestinc;
+ // Baseline: grow by adding to the top or to the right.
+ {
+ double i1 = computeIncreaseArea(w, Math.max(0, h - theight));
+ double i2 = computeIncreaseArea(Math.max(0, w - twidth), h);
+ if(i1 < i2) {
+ bestwi = w;
+ besthi = Math.max(0, h - theight);
+ bestinc = i1;
+ }
+ else {
+ bestwi = Math.max(0, w - twidth);
+ besthi = h;
+ bestinc = i2;
+ }
+ }
+ // Find position with minimum increase
+ for(int sy = 0; sy < rows; sy++) {
+ for(int sx = 0; sx < cols; sx++) {
+ if(usage.get(sy).get(sx) != null) {
+ continue;
+ }
+ // Start with single cell
+ double avw = widths.get(sx);
+ double avh = heights.get(sy);
+ int ex = sx;
+ int ey = sy;
+ while(avw < w || avh < h) {
+ // Grow width first
+ if(avw / avh < w / h) {
+ if(avw < w && ex + 1 < cols) {
+ boolean ok = true;
+ // All unused?
+ for(int y = sy; y <= ey; y++) {
+ if(usage.get(y).get(ex + 1) != null) {
+ ok = false;
+ }
+ }
+ if(ok) {
+ ex += 1;
+ avw += widths.get(ex);
+ continue;
+ }
+ }
+ if(avh < h && ey + 1 < rows) {
+ boolean ok = true;
+ // All unused?
+ for(int x = sx; x <= ex; x++) {
+ if(usage.get(ey + 1).get(x) != null) {
+ ok = false;
+ }
+ }
+ if(ok) {
+ ey += 1;
+ avh += heights.get(ey);
+ continue;
+ }
+ }
+ }
+ else { // Grow height first
+ if(avh < h && ey + 1 < rows) {
+ boolean ok = true;
+ // All unused?
+ for(int x = sx; x <= ex; x++) {
+ if(usage.get(ey + 1).get(x) != null) {
+ ok = false;
+ }
+ }
+ if(ok) {
+ ey += 1;
+ avh += heights.get(ey);
+ continue;
+ }
+ }
+ if(avw < w && ex + 1 < cols) {
+ boolean ok = true;
+ // All unused?
+ for(int y = sy; y <= ey; y++) {
+ if(usage.get(y).get(ex + 1) != null) {
+ ok = false;
+ }
+ }
+ if(ok) {
+ ex += 1;
+ avw += widths.get(ex);
+ continue;
+ }
+ }
+ }
+ break;
+ }
+ // Good match, or extension possible?
+ if(avw < w && ex < cols - 1) {
+ continue;
+ }
+ if(avh < h && ey < rows - 1) {
+ continue;
+ }
+ // Compute increase:
+ double winc = Math.max(0.0, w - avw);
+ 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(inc < bestinc) {
+ bestinc = inc;
+ bestsx = sx;
+ bestsy = sy;
+ bestex = ex;
+ bestey = ey;
+ bestwi = w - avw;
+ besthi = h - avh;
+ }
+ if(inc == 0) {
+ // Can't find better
+ // TODO: try to do less splitting maybe?
+ break;
+ }
+ }
+ assert assertConsistent();
+ }
+ logger.debugFinest("Best: " + bestsx + "," + bestsy + " - " + bestex + "," + bestey + " inc: " + bestwi + "x" + besthi + " " + bestinc);
+ // Need to split a column.
+ // TODO: find best column to split. Currently: last
+ if(bestwi < 0) {
+ splitCol(bestex, -bestwi);
+ bestwi = 0.0;
+ }
+ // Need to split a row.
+ // TODO: find best row to split. Currently: last
+ if(besthi < 0) {
+ splitRow(bestey, -besthi);
+ besthi = 0.0;
+ }
+ // Need to increase the total area
+ if(bestinc > 0) {
+ assert (bestex == cols - 1 || bestey == rows - 1);
+ double inc = Math.max(bestwi, besthi * ratio);
+ resize(inc);
+
+ // Resubmit
+ put(w, h, data);
+ return;
+ }
+ for(int x = bestsx; x <= bestex; x++) {
+ for(int y = bestsy; y <= bestey; y++) {
+ usage.get(y).set(x, data);
+ }
+ }
+ double xpos = 0.0;
+ double ypos = 0.0;
+ {
+ for(int x = 0; x < bestsx; x++) {
+ xpos += widths.get(x);
+ }
+ for(int y = 0; y < bestsy; y++) {
+ ypos += heights.get(y);
+ }
+ }
+ map.put(data, new double[] { xpos, ypos, w, h });
+ if(logger.isDebuggingFinest()) {
+ logSizes();
+ }
+ }
+
+ protected double computeIncreaseArea(double winc, double hinc) {
+ double inc = Math.max(winc, hinc * ratio);
+ inc = inc * (hinc + inc / ratio + winc / ratio);
+ return inc;
+ }
+
+ protected void splitRow(int bestey, double besthi) {
+ logger.debugFine("Split row " + bestey);
+ heights.add(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) {
+ final int rows = heights.size();
+ logger.debugFine("Split column " + bestex);
+ widths.add(bestex + 1, bestwi);
+ widths.set(bestex, widths.get(bestex) - bestwi);
+ // Update used map
+ for(int y = 0; y < rows; y++) {
+ usage.get(y).add(bestex + 1, usage.get(y).get(bestex));
+ }
+ assert assertConsistent();
+ }
+
+ 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();
+ }
+ // TODO: if the last row or column is empty, we can do this simpler
+ widths.add(inc);
+ twidth += inc;
+ heights.add(inc / ratio);
+ theight += inc / ratio;
+ // Add column:
+ for(int y = 0; y < rows; y++) {
+ usage.get(y).add(null);
+ }
+ // Add row:
+ {
+ ArrayList<Object> row = new ArrayList<Object>();
+ for(int x = 0; x <= cols; x++) {
+ row.add(null);
+ }
+ usage.add(row);
+ }
+ assert assertConsistent();
+ if(logger.isDebuggingFinest()) {
+ logSizes();
+ }
+ }
+
+ /**
+ * Get the position data of the object
+ *
+ * @param object Query object
+ * @return Position information: x,y,w,h
+ */
+ public double[] get(T object) {
+ double[] v = map.get(object);
+ if(v == null) {
+ return null;
+ }
+ return v.clone();
+ }
+
+ private boolean assertConsistent() {
+ final int cols = widths.size();
+ final int rows = heights.size();
+ {
+ double wsum = 0.0;
+ for(int x = 0; x < cols; x++) {
+ assert (widths.get(x) > 0);
+ wsum += widths.get(x);
+ }
+ assert (Math.abs(wsum - twidth) < 1E-10);
+ }
+ {
+ double hsum = 0.0;
+ for(int y = 0; y < rows; y++) {
+ assert (heights.get(y) > 0);
+ hsum += heights.get(y);
+ }
+ assert (Math.abs(hsum - theight) < 1E-10);
+ }
+ {
+ assert (usage.size() == rows);
+ for(int y = 0; y < rows; y++) {
+ assert (usage.get(y).size() == cols);
+ }
+ }
+ return true;
+ }
+
+ public void logSizes() {
+ StringBuffer buf = new StringBuffer();
+ final int cols = widths.size();
+ final int rows = heights.size();
+ {
+ buf.append("Widths: ");
+ for(int x = 0; x < cols; x++) {
+ if(x > 0) {
+ buf.append(", ");
+ }
+ buf.append(widths.get(x));
+ }
+ buf.append("\n");
+ }
+ {
+ buf.append("Heights: ");
+ for(int y = 0; y < rows; y++) {
+ if(y > 0) {
+ buf.append(", ");
+ }
+ buf.append(heights.get(y));
+ }
+ buf.append("\n");
+ }
+ {
+ for(int y = 0; y < rows; y++) {
+ for(int x = 0; x < cols; x++) {
+ buf.append(usage.get(y).get(x) != null ? "X" : "_");
+ }
+ buf.append("|\n");
+ }
+ for(int x = 0; x < cols; x++) {
+ buf.append("-");
+ }
+ buf.append("+\n");
+ }
+ logger.debug(buf);
+ }
+
+ /**
+ * Get the total canvas width
+ *
+ * @return Width
+ */
+ public double getWidth() {
+ return twidth;
+ }
+
+ /**
+ * Get the total canvas height
+ *
+ * @return Height
+ */
+ public double getHeight() {
+ return theight;
+ }
+
+ /**
+ * The items contained in the map.
+ *
+ * @return entry set
+ */
+ public Set<Entry<T, double[]>> entrySet() {
+ return Collections.unmodifiableSet(map.entrySet());
+ }
+
+ /**
+ * Test method.
+ *
+ * @param args
+ */
+ public static void main(String[] args) {
+ logger.getWrappedLogger().setLevel(Level.FINEST);
+ RectangleArranger<String> r = new RectangleArranger<String>(1.3);
+ r.put(4., 1., "Histogram");
+ r.put(4., 4., "3D view");
+ r.put(1., 1., "Meta 1");
+ r.put(1., 1., "Meta 2");
+ r.put(1., 1., "Meta 3");
+ r.put(2., 2., "Meta 4");
+ r.put(2., 2., "Meta 5");
+
+ r = new RectangleArranger<String>(3., 3.);
+ r.put(1., 2., "A");
+ r.put(2., 1., "B");
+ r.put(1., 2., "C");
+ r.put(2., 1., "D");
+ r.put(2., 2., "E");
+
+ r = new RectangleArranger<String>(4 - 2.6521739130434785);
+ r.put(4., .5, "A");
+ r.put(4., 3., "B");
+ r.put(4., 1., "C");
+ r.put(1., .1, "D");
+ }
+}