/** TrakEM2 plugin for ImageJ(C). Copyright (C) 2005-2009 Albert Cardona and Rodney Douglas. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt ) 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. You may contact Albert Cardona at acardona at ini.phys.ethz.ch Institute of Neuroinformatics, University of Zurich / ETH, Switzerland. **/ package ini.trakem2.display; import ini.trakem2.utils.M; import ini.trakem2.utils.Utils; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.TreeMap; /** * A Bucket is a subarea of the Layer area, which contains either other Buckets or a map of stack_index vs. Displayable instances. VERY IMPORTANT: either children is null, or map is null, but both cannot be null at the same time neither not null at the same time. * */ public class Bucket { static public final int MIN_BUCKET_SIZE = 4096; private int bucket_side; /** The sorted map of stack_index and Displayable objects that are fully contained in this bucket or intersect at top and left, but not at right and bottom. That is, the lower-right corner of the Displayable is contained with the area of this bucket. */ private TreeMap<Integer,Displayable> map = null; /** The set of sub-buckets contained here. */ private ArrayList<Bucket> children = null; private final int x,y,w,h; private boolean empty = true; public Bucket(final int x, final int y, final int w, final int h, final int bucket_side) { this.x = x; this.y = y; this.w = w; this.h = h; this.bucket_side = bucket_side; Utils.showStatus(new StringBuilder("Creating bucket ").append(x).append(',').append(y).append(',').append(w).append(',').append(h).toString(), false); //Utils.log2(this.toString()); } public String toString() { return "Bucket: " + x + ", " + y + ", " + w + ", " + h; } synchronized final void populate(final Bucketable container, final Layer layer, final HashMap<Displayable,HashSet<Bucket>> db_map) { // Reset if (null != this.map) this.map.clear(); this.children = null; // Refill: final HashMap<Integer,Displayable> list = new HashMap<Integer,Displayable>(); int i = 0; // cache all bounding boxes final HashMap<Displayable,Area> areas = new HashMap<Displayable,Area>(); for (final Displayable d : container.getDisplayableList()) { list.put(i, d); i++; final Area a = d.getAreaForBucket(layer); if (null != a) areas.put(d, a); } populate(container, db_map, w+w, h+h, w, h, list, areas); } /** Recursive initialization of buckets. This method is meant to be used as init, when root is null or is made new from scratch. Returns true if not empty. */ final private boolean populate(final Bucketable container, final HashMap<Displayable,HashSet<Bucket>> db_map, final int parent_w, final int parent_h, final int max_width, final int max_height, final HashMap<Integer,Displayable> parent_list, final HashMap<Displayable,Area> areas) { if (this.w <= bucket_side || this.h <= bucket_side) { // add displayables, sorted by index map = new TreeMap<Integer,Displayable>(); for (final Map.Entry<Integer,Displayable> e : parent_list.entrySet()) { final Displayable d = e.getValue(); final Area a = areas.get(d); if (null == a) continue; if (a.intersects(x, y, w, h)) { map.put(e.getKey(), d); putToBucketMap(d, db_map); } } this.empty = map.isEmpty(); //Utils.log2(empty ? "EMPTY ": "FILLED " + this); } else { // create child buckets as subdivisions of this one children = new ArrayList<Bucket>(2*2); int side_w = (int)Math.pow(2, (int)Math.floor(Math.log(Math.max(w,h)) / Math.log(2)) - 1); int side_h = side_w; if (side_w > max_width) side_w = max_width; if (side_h > max_height) side_h = max_height; // create list of Displayables that will be added here, as extracted from the parent list final HashMap<Integer,Displayable> local_list = new HashMap<Integer,Displayable>(); for (final Map.Entry<Integer,Displayable> e : parent_list.entrySet()) { final Displayable d = e.getValue(); final Area a = areas.get(d); if (null == a) continue; if (a.intersects(x, y, w, h)) local_list.put(e.getKey(), d); } //Utils.log2(local_list.size() + " :: " + this.toString()); for (int x=0; x<parent_w; x += side_w) { if (this.x + x >= max_width) continue; int width = side_w; if (this.x + x + side_w > max_width) width = max_width - this.x - x; for (int y=0; y<parent_h; y += side_h) { if (this.y + y >= max_height) continue; int height = side_h; if (this.y + y + side_h > max_height) height = max_height - this.y - y; final Bucket bu = new Bucket(this.x + x, this.y + y, width, height, bucket_side); if (bu.populate(container, db_map, width, height, max_width, max_height, local_list, areas)) { this.empty = false; } children.add(bu); } } /* int w = this.w / 2; int h = this.h / 2; for (int i=0; i<2; i++) { for (int j=0; j<2; j++) { Bucket bu = new Bucket(this.x + i * w, this.y + j * h, w, h); if (bu.populate(container, db_map)) { //Utils.log2("FILLEd " + this); this.empty = false; //} else { // Utils.log2("EMPTY " + this); } children.add(bu); } } */ } return !this.empty; } private final boolean intersects(final Rectangle r) { if (r.width <= 0 || r.height <= 0 || w <= 0 || h <= 0) { return false; } final int rw = r.x + r.width; final int rh = r.y + r.height; final int tw = w + x; final int th = h + y; // overflow || intersect return ((rw < r.x || rw > x) && (rh < r.y || rh > y) && (tw < x || tw > r.x) && (th < y || th > r.y)); } private final boolean contains(final double px, final double py) { return px >= x && py >= y && px <= x + w && py <= y + h; } /** Find All Displayable objects that intersect with the given srcRect and return them ordered by stack_index. Of @param visible_only is true, then hidden Displayable objects are ignored. */ synchronized final Collection<Displayable> find(final Rectangle srcRect, final Layer layer, final boolean visible_only) { final TreeMap<Integer,Displayable> accum = new TreeMap<Integer,Displayable>(); find(accum, srcRect, layer, visible_only); return accum.values(); // sorted by integer key } /** Recursive search, accumulates Displayable objects that intersect the srcRect and, if @param visible_only is true, then checks first if so. */ private void find(final TreeMap<Integer,Displayable> accum, final Rectangle srcRect, final Layer layer, final boolean visible_only) { if (empty || !intersects(srcRect)) return; if (null != children) { for (final Bucket bu : children) { bu.find(accum, srcRect, layer, visible_only); } } else { final Area asrc = new Area(srcRect); for (final Map.Entry<Integer,Displayable> entry : map.entrySet()) { final Displayable d = entry.getValue(); if (visible_only && !d.isVisible()) continue; final Area a = d.getAreaForBucket(layer); if (null != a && M.intersects(asrc, a)) { accum.put(entry.getKey(), d); } } } } /** Find All Displayable objects that intersect with the given srcRect and return them ordered by stack_index. Of @param visible_only is true, then hidden Displayable objects are ignored. * * Fast and dirty, never returns a false negative but may return a false positive. */ synchronized final Collection<Displayable> roughlyFind(final Rectangle srcRect, final Layer layer, final boolean visible_only) { final TreeMap<Integer,Displayable> accum = new TreeMap<Integer,Displayable>(); roughlyFind(accum, srcRect, layer, visible_only); return accum.values(); // sorted by integer key } /** Recursive search, accumulates Displayable objects that intersect the srcRect and, if @param visible_only is true, then checks first if so. */ private void roughlyFind(final TreeMap<Integer,Displayable> accum, final Rectangle srcRect, final Layer layer, final boolean visible_only) { if (empty || !intersects(srcRect)) return; if (null != children) { for (final Bucket bu : children) { bu.roughlyFind(accum, srcRect, layer, visible_only); } } else { //final Rectangle tmp = new Rectangle(); //final Area asrc = new Area(srcRect); final Rectangle BOX = new Rectangle(x, y, w, h); for (final Map.Entry<Integer,Displayable> entry : map.entrySet()) { final Displayable d = entry.getValue(); if (visible_only && !d.isVisible()) continue; /* // Too slow for a rough search as needed by DisplayCanvas.gatherDisplayables! * // That method calls LayerSet.findZDisplayables(Layer, Rectangle, boolean) which calls here final Area a = d.getAreaForBucket(layer); if (null != a && M.intersects(asrc, a)) { accum.put(entry.getKey(), d); } */ // Instead: if (d.isRoughlyInside(layer, BOX)) { accum.put(entry.getKey(), d); } } } } /** Find All Displayable objects that intersect with the given srcRect and return them ordered by stack_index. Of @param visible_only is true, then hidden Displayable objects are ignored. */ synchronized final Collection<Displayable> find(final Class<?> c, final Rectangle srcRect, final Layer layer, final boolean visible_only, final boolean instance_of) { final TreeMap<Integer,Displayable> accum = new TreeMap<Integer,Displayable>(); find(accum, c, srcRect, layer, visible_only, instance_of); return accum.values(); // sorted by integer key } /** Recursive search, accumulates Displayable objects that intersect the srcRect and, if @param visible_only is true, then checks first if so. */ private void find(final TreeMap<Integer,Displayable> accum, final Class<?> c, final Rectangle srcRect, final Layer layer, final boolean visible_only, final boolean instance_of) { if (empty || !intersects(srcRect)) return; if (null != children) { for (final Bucket bu : children) { bu.find(accum, c, srcRect, layer, visible_only, instance_of); } } else { final Area asrc = new Area(srcRect); if (instance_of) { for (final Map.Entry<Integer,Displayable> entry : map.entrySet()) { final Displayable d = entry.getValue(); if (visible_only && !d.isVisible()) continue; if (c.isAssignableFrom(d.getClass())) { final Area a = d.getAreaForBucket(layer); if (null != a && M.intersects(asrc, a)) { accum.put(entry.getKey(), d); } } } } else { for (final Map.Entry<Integer,Displayable> entry : map.entrySet()) { final Displayable d = entry.getValue(); if (visible_only && !d.isVisible()) continue; if (d.getClass() == c) { final Area a = d.getAreaForBucket(layer); if (null != a && M.intersects(asrc, a)) { accum.put(entry.getKey(), d); } } } } } } /** Find all Displayable objects that contain the given point at the given layer (here layer acts as the Z coordinate, then) and return them ordered by stack_index. If @param visible_only is trye, then hidden Displayable objects are ignored. */ synchronized final Collection<Displayable> find(final double px, final double py, final Layer layer, final boolean visible_only) { final TreeMap<Integer,Displayable> accum = new TreeMap<Integer,Displayable>(); find(accum, px, py, layer, visible_only); return accum.values(); // sorted by integer key } /** Recursive search, accumulates Displayable objects that contain the given point and, if @param visible_only is true, then checks first if so. */ private void find(final TreeMap<Integer,Displayable> accum, final double px, final double py, final Layer layer, final boolean visible_only) { if (empty || !contains(px, py)) return; if (null != children) { for (final Bucket bu : children) { bu.find(accum, px, py, layer, visible_only); } } else { for (final Map.Entry<Integer,Displayable> entry : map.entrySet()) { final Displayable d = entry.getValue(); if (visible_only && !d.isVisible()) continue; if (d.contains(layer, px, py)) { accum.put(entry.getKey(), d); } } //Utils.log2("Bucket with " + map.size() + " contains click " + this.toString()); } } /** Find all Displayable objects that contain the given point at the given layer (here layer acts as the Z coordinate, then) and return them ordered by stack_index. If @param visible_only is trye, then hidden Displayable objects are ignored. */ synchronized final Collection<Displayable> find(final Class<?> c, final double px, final double py, final Layer layer, final boolean visible_only, final boolean instance_of) { final TreeMap<Integer,Displayable> accum = new TreeMap<Integer,Displayable>(); find(accum, c, px, py, layer, visible_only, instance_of); return accum.values(); // sorted by integer key } /** Recursive search, accumulates Displayable objects that contain the given point and, if @param visible_only is true, then checks first if so. */ private void find(final TreeMap<Integer,Displayable> accum, final Class<?> c, final double px, final double py, final Layer layer, final boolean visible_only, final boolean instance_of) { if (empty || !contains(px, py)) return; if (null != children) { for (final Bucket bu : children) { bu.find(accum, c, px, py, layer, visible_only, instance_of); } } else { if (instance_of) { for (final Map.Entry<Integer,Displayable> entry : map.entrySet()) { final Displayable d = entry.getValue(); if (visible_only && !d.isVisible()) continue; if (c.isAssignableFrom(d.getClass()) && d.contains(layer, px, py)) { accum.put(entry.getKey(), d); } } } else { for (final Map.Entry<Integer,Displayable> entry : map.entrySet()) { final Displayable d = entry.getValue(); if (visible_only && !d.isVisible()) continue; if (d.getClass() == c && d.contains(layer, px, py)) { accum.put(entry.getKey(), d); } } } } } /** Find all Displayable objects that intersect the given Area and return them ordered by stack_index. If @param visible_only is trye, then hidden Displayable objects are ignored. */ synchronized final Collection<Displayable> find(final Area area, final Layer layer, final boolean visible_only) { final TreeMap<Integer,Displayable> accum = new TreeMap<Integer,Displayable>(); find(accum, area, layer, visible_only); return accum.values(); // sorted by integer key } /** Recursive search, accumulates Displayable objects that contain the given point and, if @param visible_only is true, then checks first if so. */ private void find(final TreeMap<Integer,Displayable> accum, final Area area, final Layer layer, final boolean visible_only) { if (empty || !intersects(area.getBounds())) return; if (null != children) { for (final Bucket bu : children) { bu.find(accum, area, layer, visible_only); } } else { for (final Map.Entry<Integer,Displayable> entry : map.entrySet()) { final Displayable d = entry.getValue(); if (visible_only && !d.isVisible()) continue; if (d.intersects(layer, area)) { accum.put(entry.getKey(), d); } } } } /** Find all Displayable objects that intersect the given Area and return them ordered by stack_index. If @param visible_only is trye, then hidden Displayable objects are ignored. */ synchronized final Collection<Displayable> find(final Class<?> c, final Area area, final Layer layer, final boolean visible_only, final boolean instance_of) { final TreeMap<Integer,Displayable> accum = new TreeMap<Integer,Displayable>(); find(accum, c, area, layer, visible_only, instance_of); return accum.values(); // sorted by integer key } /** Recursive search, accumulates Displayable objects that contain the given point and, if @param visible_only is true, then checks first if so. */ private void find(final TreeMap<Integer,Displayable> accum, final Class<?> c, final Area area, final Layer layer, final boolean visible_only, final boolean instance_of) { if (empty || !intersects(area.getBounds())) return; if (null != children) { for (final Bucket bu : children) { bu.find(accum, c, area, layer, visible_only, instance_of); } } else { if (instance_of) { for (final Map.Entry<Integer,Displayable> entry : map.entrySet()) { final Displayable d = entry.getValue(); if (visible_only && !d.isVisible()) continue; if (c.isAssignableFrom(d.getClass()) && d.intersects(layer, area)) { accum.put(entry.getKey(), d); } } } else { for (final Map.Entry<Integer,Displayable> entry : map.entrySet()) { final Displayable d = entry.getValue(); if (visible_only && !d.isVisible()) continue; if (d.getClass() == c && d.intersects(layer, area)) { accum.put(entry.getKey(), d); } } } } } /** Update a Displayable's stack index from old to new, or a range. */ synchronized final void updateRange(final Bucketable container, final Displayable d, final int old_i, final int new_i) { // Build a map with the new indices final HashMap<Displayable,Integer> stack_indices = new HashMap<Displayable,Integer>(); final ArrayList<? extends Displayable> dlist = container.getDisplayableList(); for (int i=old_i; i<=new_i; i++) { stack_indices.put(dlist.get(i), i); } updateRange(container, old_i, new_i, stack_indices); } /* final Set<Displayable> removeRange(final Bucketable container, final int first, final int last) { final HashSet<Displayable> hs = new HashSet<Displayable>(); removeRange(container, first, last, hs); return hs; } */ /** Accumulate removed Displayable instances into the HashSet. */ /* final private void removeRange(final Bucketable container, final int first, final int last, final HashSet<Displayable> hs) { if (null != children) { for (Bucket bu : children) bu.removeRange(container, first, last, hs); } else if (null != map) { // remove entire range for (int i=first; i<=last; i++) { final Displayable d = map.remove(i); if (null != d) hs.add(d); } } } */ final private void updateRange(final Bucketable container, final int first, final int last, final HashMap<Displayable,Integer> new_stack_indices) { if (null != children) { for (final Bucket bu : children) bu.updateRange(container, first, last, new_stack_indices); } else if (null != map) { // remove range // (in two steps, to avoid overwriting existing entries) final ArrayList<Displayable> a = new ArrayList<Displayable>(last - first + 1); for (int i=first; i<=last; i++) { final Displayable d = map.remove(i); if (null != d) a.add(d); } // re-add range with new stack_index keys for (final Displayable d : a) map.put(new_stack_indices.get(d), d); } } /** Remove from wherever it is, then test if it's in that bucket, otherwise re-add. */ synchronized final void updatePosition(final Displayable d, final Layer layer, final HashMap<Displayable,HashSet<Bucket>> db_map) { final HashSet<Bucket> hs = db_map.get(d); final Area a = d.getAreaForBucket(layer); final int stack_index = d.getBucketable().getDisplayableList().indexOf(d); if (null != hs) { for (final Iterator<Bucket> it = hs.iterator(); it.hasNext(); ) { final Bucket bu = it.next(); if (null != a && a.intersects(bu.x, bu.y, bu.w, bu.h)) continue; // bu.intersects(box)) continue; // no change of bucket: lower-right corner still within the bucket // else, remove bu.map.remove(stack_index); it.remove(); } } // insert wherever appropriate, if not there if (null != a) this.put(stack_index, d, layer, a, db_map); } /** Add the given Displayable to all buckets that intercept its bounding box. */ synchronized final void put(final int stack_index, final Displayable d, final Layer layer, final HashMap<Displayable,HashSet<Bucket>> db_map) { put(stack_index, d, layer, d.getAreaForBucket(layer), db_map); } synchronized final void put(final int stack_index, final Displayable d, final Layer layer, final Area a, final HashMap<Displayable,HashSet<Bucket>> db_map) { if (null == a) return; /* if (0 == box.width || 0 == box.height) { // d doesn't contain any data: use whole 2D world box.width = (int) layer.getLayerWidth(); box.height = (int) layer.getLayerHeight(); } */ putIn(stack_index, d, a, db_map); } private final void putIn(final int stack_index, final Displayable d, final Area a, final HashMap<Displayable,HashSet<Bucket>> db_map) { if (!a.intersects(x, y, w, h)) return; // there will be at least one now this.empty = false; if (null != children) { for (final Bucket bu : children) bu.putIn(stack_index, d, a, db_map); } else if (null != map) { map.put(stack_index, d); putToBucketMap(d, db_map); // the db_map } } /* private void debugMap(String title) { if (null == map) return; Utils.log2("@@@ " + title); for (final Map.Entry<Integer,Displayable> e: map.entrySet()) { Utils.log2("k,v : " + e.getKey() + " , " + e.getValue()); } } */ final private void putToBucketMap(final Displayable d, final HashMap<Displayable,HashSet<Bucket>> db_map) { HashSet<Bucket> list = db_map.get(d); if (null == list) { list = new HashSet<Bucket>(); db_map.put(d, list); list.add(this); } else list.add(this); } /* final private void removeFromBucketMap(final Displayable d, final HashMap<Displayable,ArrayList<Bucket>> db_map) { ArrayList<Bucket> list = db_map.get(d); if (null == list) return; list.remove(d); if (0 == list.size()) db_map.remove(d); } */ /** Returns whether the stack index was successfully removed. * Assumes that 'd' is in this Bucket at old_stack_index. */ final private boolean remove2(final Displayable d, final int old_stack_index, final HashMap<Displayable,Integer> new_stack_indices) { boolean success = true; if (null != children) { this.empty = true; for (final Bucket bu : children) { if (!bu.remove2(d, old_stack_index, new_stack_indices)) { this.empty = false; success = false; } } return success; } else if (null != map) { reindex(new_stack_indices); } return success; } /** Returns whether the stack index was successfully removed .*/ synchronized final boolean remove(final Displayable d, final int old_stack_index, final HashMap<Displayable,Integer> new_stack_indices) { return remove2(d, old_stack_index, new_stack_indices); } /** Returns whether this bucket is empty of Displayable objects. */ final private boolean removeAll2(final Collection<Integer> old_stack_indices, final HashMap<Displayable,Integer> new_stack_indices) { if (null != children) { this.empty = true; for (final Bucket bu : children) { if (!bu.removeAll2(old_stack_indices, new_stack_indices)) this.empty = false; } } else if (null != map) { reindex(new_stack_indices); return map.isEmpty(); } return true; } synchronized final void removeAll(final Collection<Integer> old_stack_indices, final HashMap<Displayable,Integer> new_stack_indices) { removeAll2(old_stack_indices, new_stack_indices); } final void reindex(final HashMap<Displayable,Integer> new_stack_indices) { if (null == new_stack_indices) return; if (null != children) { for (final Bucket bu : children) { bu.reindex(new_stack_indices); } } else if (null != map) { final HashSet<Displayable> hs = new HashSet<Displayable>(this.map.values()); this.map.clear(); for (final Displayable d : hs) { final Integer i = new_stack_indices.get(d); if (null == i) { Utils.log2("WARNING: Bucket.reindex could not find an index for " + d); continue; } this.map.put(i, d); } } } synchronized public void paint(Graphics2D g, Rectangle srcRect, double mag, Color color) { if (null == map) { for (final Bucket bu : children) bu.paint(g, srcRect, mag, color); return; } //Utils.log("going to paint ... "); //if (!intersects(srcRect)) return; //Utils.log("painting : " + x + ", " + y + ", " + w + ", " + h); final Graphics2D g2d = (Graphics2D)g; final Stroke original_stroke = g2d.getStroke(); AffineTransform original = g2d.getTransform(); g2d.setTransform(new AffineTransform()); g2d.setStroke(new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER)); g.setColor(color); g.drawRect((int)((x - srcRect.x) * mag), (int)((y-srcRect.y)*mag), (int)(w*mag), (int)(h*mag)); g2d.setStroke(original_stroke); g2d.drawString(Integer.toString(map.size()), (int)((x - srcRect.x + w/2) * mag), (int)((y - srcRect.y + h/2) * mag)); g2d.setTransform(original); } /** Determine whether the rectangle is smaller than the layer dimensions padded in by one bucket_side -- if not, makes little sense to use buckets, and it's better to do linear search without the TreeMap overhead. */ public final boolean isBetter(final Rectangle r, final Bucketable container) { /* final boolean b = r.width * r.height < (layer.getLayerWidth() - bucket_side) * (layer.getLayerHeight() - bucket_side); Utils.log2("isBetter: " + b); if (b) { Utils.log2("\t r is " + r.width + ", " + r.height); Utils.log2("\t o is " + (int)(layer.getLayerWidth() - bucket_side) + ", " + (int)(layer.getLayerHeight() * bucket_side)); } return b; */ return r.width * r.height < (container.getLayerWidth() - bucket_side) * (container.getLayerHeight() - bucket_side); } private final ArrayList<Bucket>getChildren(final ArrayList<Bucket> bus) { if (null != children) { for (final Bucket bu : children) { bu.getChildren(bus); } } else if (null != map) { bus.add(this); } return bus; } public void debug() { Utils.log2("total map buckets: " + getChildren(new ArrayList<Bucket>()).size()); } static public int getBucketSide(final Bucketable container, final Layer la) { if (null != container.getProject().getProperty("bucket_side")) { final int size = (int)container.getProject().getProperty("bucket_side", Bucket.MIN_BUCKET_SIZE); if (size < Bucket.MIN_BUCKET_SIZE) { Utils.logAll("WARNING: bucket side (" + size + ") is smaller than the recommended minimum of " + Bucket.MIN_BUCKET_SIZE + "\nYou may adjust the bucket side in the 'Project - Properties' popup menu."); } return size; } else { // estimate median final ArrayList<? extends Displayable> col = container.getDisplayableList(); if (0 == col.size()) return Bucket.MIN_BUCKET_SIZE; final int[] sizes = new int[col.size()]; int i = 0; for (final Displayable d : col) { Area a = d.getAreaForBucket(la); if (null == a) continue; Rectangle r = a.getBounds(); sizes[i++] = Math.max(r.width, r.height); } Arrays.sort(sizes); int size = 2 * sizes[sizes.length/2]; return size > Bucket.MIN_BUCKET_SIZE ? size : Bucket.MIN_BUCKET_SIZE; } } }