Java tutorial
/******************************************************************************* * Caleydo - Visualization for Molecular Biology - http://caleydo.org * Copyright (c) The Caleydo Team. All rights reserved. * Licensed under the new BSD license, available at http://caleydo.org/license *******************************************************************************/ package org.caleydo.view.domino.internal; import gleem.linalg.Vec2f; import java.awt.Polygon; import java.awt.Shape; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.caleydo.core.data.collection.EDimension; import org.caleydo.core.data.selection.SelectionType; import org.caleydo.core.event.EventListenerManager.ListenTo; import org.caleydo.core.id.IDCategory; import org.caleydo.core.id.IDType; import org.caleydo.core.id.IIDTypeMapper; import org.caleydo.core.util.base.Labels; import org.caleydo.core.util.collection.Pair; import org.caleydo.core.util.color.Color; import org.caleydo.core.util.function.DoubleStatistics; import org.caleydo.core.view.opengl.canvas.IGLMouseListener.IMouseEvent; import org.caleydo.core.view.opengl.layout2.GLElement; import org.caleydo.core.view.opengl.layout2.GLElementContainer; import org.caleydo.core.view.opengl.layout2.GLGraphics; import org.caleydo.core.view.opengl.layout2.IGLElementContext; import org.caleydo.core.view.opengl.layout2.dnd.IDnDItem; import org.caleydo.core.view.opengl.layout2.dnd.IDragGLSource; import org.caleydo.core.view.opengl.layout2.dnd.IDragInfo; import org.caleydo.core.view.opengl.layout2.geom.Rect; import org.caleydo.core.view.opengl.layout2.layout.AGLLayoutElement; import org.caleydo.core.view.opengl.layout2.layout.IGLLayout2; import org.caleydo.core.view.opengl.layout2.layout.IGLLayoutElement; import org.caleydo.core.view.opengl.picking.IPickingListener; import org.caleydo.core.view.opengl.picking.Pick; import org.caleydo.core.view.opengl.util.spline.TesselatedPolygons; import org.caleydo.view.domino.api.model.EDirection; import org.caleydo.view.domino.api.model.typed.MappingCaches; import org.caleydo.view.domino.api.model.typed.TypedGroupList; import org.caleydo.view.domino.api.model.typed.TypedGroupSet; import org.caleydo.view.domino.internal.LinearBlock.ESortingMode; import org.caleydo.view.domino.internal.band.ABand; import org.caleydo.view.domino.internal.band.ABandIdentifier; import org.caleydo.view.domino.internal.band.BandFactory; import org.caleydo.view.domino.internal.band.BandIdentifier; import org.caleydo.view.domino.internal.band.EBandMode; import org.caleydo.view.domino.internal.band.MediumBandIdentifier; import org.caleydo.view.domino.internal.band.ShearedRect; import org.caleydo.view.domino.internal.band.IBandHost.SourceTarget; import org.caleydo.view.domino.internal.data.VisualizationTypeOracle; import org.caleydo.view.domino.internal.dnd.ADragInfo; import org.caleydo.view.domino.internal.dnd.BlockDragInfo; import org.caleydo.view.domino.internal.event.HideNodeEvent; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet.Builder; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.google.common.collect.Maps; import com.google.common.collect.Multiset; /** * * @author Samuel Gratzl * */ public class Block extends GLElementContainer implements IGLLayout2, IPickingListener { /** * */ static final float DETACHED_OFFSET = 50f; private static final float EXPLODE_SPACE = 50f; private final List<LinearBlock> linearBlocks = new ArrayList<>(); private boolean fadeOut = false; private int fadeOutTime = 0; private boolean armed; private final OffsetShifts offsets = new OffsetShifts(); private final BlockBands bands = new BlockBands(); private final List<BlockGroup> groups = new ArrayList<>(); private final IDragGLSource source = new IDragGLSource() { @Override public IDragInfo startSWTDrag(IDragEvent event) { return findDomino().startSWTDrag(event, Block.this); } @Override public void onDropped(IDnDItem info) { if (info.getInfo() instanceof BlockDragInfo) { for (Block block : ((BlockDragInfo) info.getInfo()).getBlocks()) block.showAgain(); } } @Override public GLElement createUI(IDragInfo info) { if (info instanceof ADragInfo) return ((ADragInfo) info).createUI(findDomino()); return null; } }; public Block(Node node) { setLayout(this); this.add(bands); node.setLocation(0, 0); addFirstNode(node); onPick(this); setPicker(null); } public void selectItems(SelectionType type, IDType idType, Collection<Integer> ids, boolean additional) { bands.select(type, idType, ids, additional); } public void clearItems(SelectionType type, IDType idType, Collection<Integer> ids) { bands.clear(type, idType, ids); } @Override public void pick(Pick pick) { final NodeSelections domino = findDomino().getSelections(); IMouseEvent event = (IMouseEvent) pick; boolean ctrl = event.isCtrlDown(); switch (pick.getPickingMode()) { case MOUSE_OVER: domino.select(SelectionType.MOUSE_OVER, this, false); context.getMouseLayer().addDragSource(source); repaint(); break; case MOUSE_OUT: domino.clear(SelectionType.MOUSE_OVER, (Block) null); context.getMouseLayer().removeDragSource(source); repaint(); break; case CLICKED: armed = true; break; case MOUSE_RELEASED: if (armed) { if (domino.isSelected(SelectionType.SELECTION, this)) domino.clear(SelectionType.SELECTION, ctrl ? this : null); else domino.select(SelectionType.SELECTION, this, ctrl); repaint(); armed = false; } break; default: break; } } Domino findDomino() { return findParent(Domino.class); } @Override protected void takeDown() { context.getMouseLayer().removeDragSource(source); super.takeDown(); } /** * @param fadeOut * setter, see {@link fadeOut} */ public void setFadeOut(boolean fadeOut) { if (this.fadeOut == fadeOut) return; this.fadeOut = fadeOut; if (fadeOut) { this.fadeOutTime = 300; this.setzDelta(-4.f); } else { this.setzDelta(0); } repaint(); } @Override public void layout(int deltaTimeMs) { if (fadeOutTime > 0) { fadeOutTime -= deltaTimeMs; repaint(); } super.layout(deltaTimeMs); } @Override protected void renderImpl(GLGraphics g, float w, float h) { Collection<Vec2f> outline = getOutline(); g.color(Color.WHITE).fillPolygon(TesselatedPolygons.polygon2(outline)); super.renderImpl(g, w, h); Domino domino = findDomino(); if (domino.isShowDebugInfos()) g.color(Color.BLUE).drawRect(0, 0, w, h); if (domino.isShowBlockLabels() || domino.isShowGroupLabels()) { Vec2f xy = new Vec2f(0, 0); Vec2f xy2 = new Vec2f(w, h); for (Vec2f v : outline) { if (v.x() < xy.x()) xy.setX(v.x()); else if (v.x() > xy2.x()) xy2.setX(v.x()); if (v.y() < xy.y()) xy.setY(v.y()); else if (v.y() > xy2.y()) xy2.setY(v.y()); } Rect bb = new Rect(xy.x(), xy.y(), xy2.x() - xy.x(), xy2.y() - xy.y()); if (domino.isShowBlockLabels()) { for (LinearBlock b : linearBlocks) { b.renderNodeLabels(g, bb); } } if (domino.isShowGroupLabels()) { for (LinearBlock b : linearBlocks) { b.renderGroupLabels(g, bb); } } } NodeSelections selections = domino.getSelections(); final boolean selected = selections.isSelected(SelectionType.SELECTION, this); final boolean mouseOver = selections.isSelected(SelectionType.MOUSE_OVER, this); if (selected || mouseOver) { g.lineWidth(3).color((selected ? SelectionType.SELECTION : SelectionType.MOUSE_OVER).getColor()); g.incZ().drawPath(outline, true).decZ(); g.lineWidth(1); } if (fadeOut) { fadeOutElements(g, w, h, outline); } } @Override protected void renderPickImpl(GLGraphics g, float w, float h) { if (getVisibility() == EVisibility.PICKABLE) { g.color(Color.WHITE).fillPolygon(TesselatedPolygons.polygon2(getOutline())); } super.renderPickImpl(g, w, h); } /** * @return */ private Collection<Vec2f> getOutline() { Collection<Vec2f> r = new ArrayList<>(3); if (this.size() == 1) { Rect b = get(0).getRectBounds(); r.add(b.xy()); r.add(b.x2y()); r.add(b.x2y2()); r.add(b.xy2()); return r; } if (linearBlocks.size() == 1) { LinearBlock b = linearBlocks.get(0); Rect r1 = b.getNode(true).getRectBounds(); Rect r2 = b.getNode(false).getRectBounds(); r.add(r1.xy()); if (b.getDim().isHorizontal()) { r.add(r2.x2y()); r.add(r2.x2y2()); r.add(r1.xy2()); } else { r.add(r1.x2y()); r.add(r2.x2y2()); r.add(r2.xy2()); } return r; } Node start = nodes().iterator().next(); // search for a start point while (start.getNeighbor(EDirection.NORTH) != null) start = start.getNeighbor(EDirection.NORTH); while (start.getNeighbor(EDirection.WEST) != null) start = start.getNeighbor(EDirection.WEST); EDirection act = EDirection.EAST; Node actN = start; follow(actN, act, start, r); return r; } public Rect getBoundingBox() { if (nodeCount() == 1) return nodes().iterator().next().getRectBounds(); Rect r = null; for (Vec2f p : getOutline()) { if (r == null) { r = new Rect(p.x(), p.y(), 1, 1); } else { r.x(Math.min(r.x(), p.x())); r.y(Math.min(r.y(), p.y())); r.x2(Math.max(r.x2(), p.x())); r.y2(Math.max(r.y2(), p.y())); } } return r; } /** * follow along the nodes neighbors to get the outline * * @param node * @param dir * @param start * @param r */ private static void follow(Node node, EDirection dir, Node start, Collection<Vec2f> r) { Node next = node.getNeighbor(dir); if (node == start && dir == EDirection.EAST && !r.isEmpty()) // starting point return; if (next == null) { // no neighbor change direction r.add(corner(node.getRectBounds(), dir)); follow(node, dir.rot90(), start, r); } else if (next.getNeighbor(dir.opposite().rot90()) != null) { r.add(corner(node.getRectBounds(), dir)); follow(next.getNeighbor(dir.opposite().rot90()), dir.opposite().rot90(), start, r); } else { Vec2f my = node.getLocation(); Vec2f ne = next.getLocation(); EDimension shiftDir = dir.asDim().opposite(); float shift = shiftDir.select(my) - shiftDir.select(ne); if (shift != 0) { r.add(corner(node.getRectBounds(), dir)); r.add(corner(next.getRectBounds(), dir.opposite().rot90())); } follow(next, dir, start, r); } } /** * @param rectBounds * @param dir * @return */ private static Vec2f corner(Rect r, EDirection dir) { switch (dir) { case EAST: return r.x2y(); case NORTH: return r.xy(); case SOUTH: return r.x2y2(); case WEST: return r.xy2(); } throw new IllegalStateException(); } private Shape getOutlineShape() { if (this.nodeCount() == 1) return nodes().iterator().next().getRectangleBounds(); if (linearBlocks.size() == 1) { LinearBlock b = linearBlocks.get(0); Rect r1 = b.getNode(true).getRectBounds(); Rect r2 = b.getNode(false).getRectBounds(); Rectangle2D.Float r = new Rectangle2D.Float(); Rectangle2D.union(r1.asRectangle2D(), r2.asRectangle2D(), r); return r; } Collection<Vec2f> outline = getOutline(); Polygon r = new Polygon(); for (Vec2f p : outline) r.addPoint((int) p.x(), (int) p.y()); return r; } private void fadeOutElements(GLGraphics g, float w, float h, Collection<Vec2f> outline) { final float alpha = 1 - (Math.max(0, fadeOutTime) / 300.f); g.color(1, 1, 1, Math.min(alpha, 0.8f)); g.incZ(3).fillPolygon(TesselatedPolygons.polygon2(outline)).incZ(-3); } /** * @param node * @param n */ public void replace(Node node, Node with) { assert nodeCount() == 1; this.remove(node); if (node != with) findDomino().cleanup(node); linearBlocks.clear(); addFirstNode(with); updateBands(); } public Collection<APlaceholder> addPlaceholdersFor(Node node) { List<APlaceholder> r = new ArrayList<>(); for (LinearBlock block : linearBlocks) { block.addPlaceholdersFor(node, r); } return r; } public void addNode(Node neighbor, EDirection dir, Node node) { LinearBlock block = getBlock(neighbor, dir.asDim()); if (block == null) { this.add(node); return; } block.add(neighbor, dir, node); EDimension other = dir.asDim().opposite(); node.copyScaleFactors(neighbor, other); if (node.has(other.opposite())) { final LinearBlock b2 = new LinearBlock(other, node); linearBlocks.add(b2); } if (neighbor.getDetachedOffset() > 0 || node.getDetachedOffset() > 0) { setOffset(node, neighbor, Math.max(neighbor.getDetachedOffset(), node.getDetachedOffset())); } block.updateNeighbors(); this.add(node); realign(); updateBlock(); // shiftBlock(dir, node); } public void realign() { realign(nodes().iterator().next()); } public void realign(Node startPoint) { realign(startPoint, null); } /** * @param neighbor */ private void realign(Node startPoint, EDimension commingFrom) { LinearBlock dim = commingFrom == EDimension.DIMENSION ? null : getBlock(startPoint, EDimension.DIMENSION); LinearBlock rec = commingFrom == EDimension.RECORD ? null : getBlock(startPoint, EDimension.RECORD); if (rec != null) { rec.alignAlong(startPoint, offsets); for (Node node : rec) { if (node != startPoint) realign(node, EDimension.RECORD); } } if (dim != null) { dim.alignAlong(startPoint, offsets); for (Node node : dim) { if (node != startPoint) realign(node, EDimension.DIMENSION); } } } private void addFirstNode(Node node) { this.add(node); for (EDimension dim : EDimension.values()) { if (!node.has(dim.opposite())) continue; linearBlocks.add(new LinearBlock(dim, node)); } if (context != null) updateSize(); } @Override protected void init(IGLElementContext context) { super.init(context); realign(); updateBlock(); } public void updatedNode(Node node, float wasDetached, float isDetached) { if (wasDetached != isDetached) updateOffsets(node); realign(); updateBlock(); } /** * @param node */ private void updateOffsets(Node node) { float d = node.getDetachedOffset(); for (EDirection dir : EDirection.values()) { Node n = node.getNeighbor(dir); if (n == null) continue; float dn = n.getDetachedOffset(); if (d > 0 || dn > 0) { setOffset(n, node, Math.max(d, dn)); } else { offsets.remove(node, n); } } } /** * */ private void updateSize() { Rect r = null; for (LinearBlock elem : linearBlocks) { if (r == null) { r = elem.getBounds(); } else r = Rect.union(r, elem.getBounds()); } if (r == null) return; setSize(r.width(), r.height()); relayout(); } /** * @param event */ public void zoom(Vec2f shift, Node just) { float shiftX = shift.x(); float shiftY = shift.y(); if (just != null) { just.shiftBy(shiftX, shiftY); for (EDimension d : just.dimensions()) { d = d.opposite(); LinearBlock b = getBlock(just, d); if (b == null) // error continue; for (Node node : b) if (node != just) node.copyScaleFactors(just, d.opposite()); for (Node node : b) updateShifts(node); } realign(); } else { for (Node node : nodes()) node.shiftBy(shiftX, shiftY); for (Node node : nodes()) updateShifts(node); realign(); } updateBlock(); } public void zoom(IDCategory category, float scale) { boolean any = false; for (LinearBlock l : linearBlocks) { if (!category.isOfCategory(l.getIdType())) continue; for (Node n : l) n.setDataScaleFactor(l.getDim().opposite(), scale); for (Node node : l) updateShifts(node); any = true; } if (!any) return; realign(); updateBlock(); } public void scaleFactors(IDCategory category, DoubleStatistics.Builder builder) { for (LinearBlock l : linearBlocks) { if (!category.isOfCategory(l.getIdType())) continue; for (Node n : l) { builder.add(n.getDataScaleFactor(l.getDim().opposite())); } } } /** * @param idCategory * @param count */ public void directions(IDCategory category, Multiset<EDimension> count) { for (LinearBlock l : linearBlocks) { if (!category.isOfCategory(l.getIdType())) continue; count.add(l.getDim().opposite()); } } /** * @param node */ private void updateShifts(Node node) { boolean d = node.getDetachedOffset() > 0; for (EDirection dir : EDirection.primaries()) { Node n = node.getNeighbor(dir); if (n == null) continue; boolean nd = n.getDetachedOffset() > 0; if (!d && !nd) continue; Vec2f delta = n.getSize().minus(node.getSize()); offsets.setShift(node, n, dir.asDim().opposite().select(delta) * 0.5f); } } /** * */ private void shiftToZero() { Vec2f offset = new Vec2f(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); for (Node n : nodes()) { Vec2f l = n.getLocation(); if (l.x() < offset.x()) // && !n.isDetached(EDimension.RECORD)); offset.setX(l.x()); if (l.y() < offset.y()) // FIXME && !n.isDetached(EDimension.DIMENSION)) offset.setY(l.y()); } Node first = nodes().iterator().next(); Vec2f loc = first.getLocation(); if (offset.x() == 0 && offset.y() == 0) return; for (Node n : nodes()) { n.shiftLocation(-offset.x(), -offset.y()); } Vec2f loc_new = first.getLocation(); Vec2f shift = loc_new.minus(loc); shiftLocation(-shift.x(), -shift.y()); } public void shiftLocation(float x, float y) { Vec2f l = getLocation(); setLocation(l.x() + x, l.y() + y); } /** * @return */ Iterable<Node> nodes() { return Iterables.filter(this, Node.class); } public int nodeCount() { return Iterables.size(nodes()); } public boolean removeNode(Node node) { if (this.nodeCount() == 1) { this.remove(node); return true; } for (EDimension dim : EDimension.values()) { if (!node.has(dim.opposite())) continue; LinearBlock block = getBlock(node, dim); if (block != null) { if (block.size() == 1) linearBlocks.remove(block); else { // merge offsets Node left = node.getNeighbor(EDirection.getPrimary(dim)); Node right = node.getNeighbor(EDirection.getPrimary(dim).opposite()); offsets.remove(left, node); offsets.remove(right, node); int index = block.remove(node); if (left != null && right != null && (left.getDetachedOffset() > 0 || right.getDetachedOffset() > 0)) // update // offsets setOffset(left, right, Math.max(left.getDetachedOffset(), right.getDetachedOffset())); if (index == 0) { realign(); // shiftRemoveBlock(node, dim); } else { realign(); } } } } this.remove(node); updateBlock(); return this.isEmpty(); } private LinearBlock getBlock(Node node, EDimension dim) { for (LinearBlock block : linearBlocks) { if (block.getDim() == dim && block.contains(node)) return block; } // throw new IllegalStateException(); return null; } @Override public boolean doLayout(List<? extends IGLLayoutElement> children, float w, float h, IGLLayoutElement parent, int deltaTimeMs) { final Map<GLElement, ? extends IGLLayoutElement> lookup = Maps.uniqueIndex(children, AGLLayoutElement.TO_GL_ELEMENT); for (LinearBlock block : linearBlocks) block.doLayout(lookup); children.get(0).setBounds(0, 0, w, h); return false; } /** * @param node * @return */ public boolean containsNode(Node node) { return this.asList().contains(node); } /** * @param node * @param dim */ public void resort(Node node, EDimension dim) { LinearBlock block = getBlock(node, dim.opposite()); if (block == null) return; block.update(); block.apply(); updateBlock(); } private void updateBlock() { shiftToZero(); updateSize(); updateBands(); } private void updateBands() { final Domino d = findDomino(); if (d != null) d.updateBands(); bands.relayout(); } /** * @param node * @param dim */ public List<Pair<Node, ESortingMode>> sortByImpl(Node node, EDimension dim, ESortingMode mode) { LinearBlock block = getBlock(node, dim.opposite()); if (block == null) return null; if (mode == null) { ESortingMode old = block.getSortingMode(node); mode = old == null ? ESortingMode.INC : old.next(); } List<Pair<Node, ESortingMode>> old = block.sortBy(node, mode); realign(); updateBlock(); return old; } public List<Pair<Node, ESortingMode>> sortBy(Node node, EDimension dim) { return sortByImpl(node, dim, null); } public List<Pair<Node, ESortingMode>> restoreSorting(Node node, EDimension dim, List<Pair<Node, ESortingMode>> sortCriteria) { LinearBlock block = getBlock(node, dim.opposite()); if (block == null) return null; List<Pair<Node, ESortingMode>> old = block.sortBy(sortCriteria); realign(); updateBlock(); return old; } public List<Pair<Node, ESortingMode>> stratifyBy(Node node, EDimension dim) { return sortByImpl(node, dim, ESortingMode.STRATIFY_INC); } public Node limitTo(Node node, EDimension dim) { LinearBlock block = getBlock(node, dim.opposite()); Node bak = block.limitDataTo(node); realign(); updateBlock(); return bak; } /** * @param subList * @param routes */ public void createBandsTo(List<Block> blocks, List<ABand> routes) { for (LinearBlock lblock : linearBlocks) { for (Block block : blocks) { for (LinearBlock rblock : block.linearBlocks) { if (isCompatible(lblock.getIdType(), rblock.getIdType())) createRoute(this, lblock, block, rblock, routes); } } } } private void createRoute(Block a, LinearBlock la, Block b, LinearBlock lb, List<ABand> routes) { TypedGroupList sData = la.getData(); TypedGroupList tData = lb.getData(); ShearedRect ra = la.getShearedBounds(); ShearedRect rb = lb.getShearedBounds(); Pair<String, String> label = Pair.make(la.getNode(true).getLabel(), lb.getNode(false).getLabel()); INodeLocator sNodeLocator = la.getNodeLocator(true); // left one INodeLocator tNodeLocator = lb.getNodeLocator(false); // right one final EDimension sDir = la.getDim().opposite(); final EDimension tDir = lb.getDim().opposite(); ABandIdentifier id = BandIdentifier.id(la, true, lb, false); ABand band = BandFactory.create(label, sData, tData, ra, rb, sNodeLocator, tNodeLocator, sDir, tDir, id); if (band == null) return; boolean swapped = band.getLocator(SourceTarget.SOURCE) != sNodeLocator; EDirection sdir = band.getAttachingDirection(SourceTarget.SOURCE); boolean primary = sdir.isPrimaryDirection(); boolean redo = false; if (swapped) { redo = primary; // since in the original the tNodeLocator is not in the primary dir } else redo = !primary; if (redo) { sNodeLocator = la.getNodeLocator(false); // left one tNodeLocator = lb.getNodeLocator(true); // right one band = BandFactory.create(label, sData, tData, ra, rb, sNodeLocator, tNodeLocator, sDir, tDir, id); } EBandMode defaultMode = EBandMode.OVERVIEW; if (sData.getGroups().size() > 1 || tData.getGroups().size() > 1) defaultMode = EBandMode.GROUPS; else if (sNodeLocator.hasLocator(EBandMode.DETAIL) && tNodeLocator.hasLocator(EBandMode.DETAIL) && sData.size() < 1000 && tData.size() < 1000) defaultMode = EBandMode.DETAIL; band.setLevel(defaultMode); routes.add(band); } /** * @param node * @param node2 * @return */ private static String toId(Node a, Node b) { int ai = a.getID(); int bi = b.getID(); String al = ai + "@" + a.getVisualizationType(); String bl = bi + "@" + b.getVisualizationType(); if (ai < bi) { return al + "X" + bl; } else { return bl + "X" + al; } } private static boolean isCompatible(IDType a, IDType b) { return a.getIDCategory().isOfCategory(b); } /** * @param pickable */ public void setContentPickable(boolean pickable) { for (Node n : nodes()) n.setContentPickable(pickable); } /** * @param node * @param dimension * @return */ public Color getStateColor(Node node, EDimension dim) { LinearBlock block = getBlock(node, dim.opposite()); if (block == null) return Color.WHITE; return block.getStateColor(node); } public String getStateString(Node node, EDimension dim) { LinearBlock block = getBlock(node, dim.opposite()); if (block == null) return ""; return block.getStateString(node); } /** * @return the linearBlocks, see {@link #linearBlocks} */ public List<LinearBlock> getLinearBlocks() { return Collections.unmodifiableList(linearBlocks); } /** * @param r */ public void selectByBounds(Rectangle2D r, EToolState tool) { r = (Rectangle2D) r.clone(); // local copy Vec2f l = getLocation(); // to relative coordinates; r = new Rectangle2D.Double(r.getX() - l.x(), r.getY() - l.y(), r.getWidth(), r.getHeight()); if (tool == EToolState.BANDS) { if (getOutlineShape().intersects(r)) { selectMe(); repaint(); } } else { for (Node node : nodes()) { if (node.getRectangleBounds().intersects(r)) { node.selectByBounds(r); } } } } /** * */ private void selectMe() { final NodeSelections domino = findDomino().getSelections(); if (partOfGroup()) { selectAllBlocksInGroup(domino); } else if (!domino.isSelected(SelectionType.SELECTION, this)) domino.select(SelectionType.SELECTION, this, true); } /** * */ private void selectAllBlocksInGroup(NodeSelections selections) { groups.get(groups.size() - 1).selectAllBlocks(selections); } /** * @param node */ public void tranposedNode(Node node) { replace(node, node); } /** * @return */ public String getLabel() { return StringUtils.join(Iterators.transform(nodes().iterator(), Labels.TO_LABEL), ", "); } /** * */ public void showAgain() { setVisibility(findDomino().getTool() == EToolState.BANDS ? EVisibility.PICKABLE : EVisibility.VISIBLE); } @ListenTo(sendToMe = true) private void onHideNodeEvent(HideNodeEvent event) { context.getMouseLayer().removeDragSource(source); setVisibility(EVisibility.HIDDEN); } /** * */ public void selectAll() { for (Node n : nodes()) n.selectAll(); } /** * @param tool */ public void setTool(EToolState tool) { switch (tool) { case BANDS: for (Node n : nodes()) { n.setVisibility(EVisibility.VISIBLE); } break; default: setFadeOut(false); repaint(); for (Node n : nodes()) { n.setVisibility(EVisibility.PICKABLE); n.setContentPickable(tool == EToolState.SELECT); } } setVisibility(tool == EToolState.BANDS ? EVisibility.PICKABLE : EVisibility.VISIBLE); } public void transpose() { for (Node n : nodes()) n.transposeMe(); for (LinearBlock b : linearBlocks) b.transposedMe(); realign(); updateBlock(); } /** * @param left * @param right * @param offset */ private void setOffset(Node left, Node right, float offset) { // offset = Math.max(offset, Math.max(left.getDetachedOffset(), right.getDetachedOffset())); offsets.setOffset(left, right, offset); } /** * @param bands */ public void createOffsetBands(List<ABand> bands) { for (LinearBlock l : getLinearBlocks()) { Node prev = null; for (Node node : l) { float offset = offsets.getOffset(prev, node); if (offset > 0) { final ABand b = create(prev, node, l, l.getDim()); if (b != null) bands.add(b); } prev = node; } } } private ABand create(Node s, Node t, LinearBlock b, EDimension dim) { EDimension d = dim.opposite(); TypedGroupList sData = s.getDetachedOffset() > 0 ? s.getData(d) : b.getData(); TypedGroupList tData = t.getDetachedOffset() > 0 ? t.getData(d) : b.getData(); ShearedRect ra = new ShearedRect(s.getRectBounds()); ShearedRect rb = new ShearedRect(t.getRectBounds()); final INodeLocator sNodeLocator = s.getNodeLocator(d); final INodeLocator tNodeLocator = t.getNodeLocator(d); ABandIdentifier id = new MediumBandIdentifier(s, true, t, false); ABand band = BandFactory.create(Pair.make(s.getLabel(), t.getLabel()), sData, tData, ra, rb, sNodeLocator, tNodeLocator, d, d, id); if (band != null) band.setLevel(EBandMode.OVERVIEW); return band; } public boolean isSingle() { return nodeCount() == 1; } public boolean isStratified(Node node, EDimension dim) { LinearBlock b = getBlock(node, dim.opposite()); return b == null ? false : b.isStratisfied(node); } public List<TypedGroupSet> removeSlice(Node node, EDimension dim, NodeGroup toRemove) { LinearBlock b = getBlock(node, dim.opposite()); if (b == null) return Collections.emptyList(); List<TypedGroupSet> bak = b.removeSlice(toRemove); realign(); updateBlock(); return bak; } public void restoreSlice(Node node, EDimension dim, List<TypedGroupSet> ori) { if (ori == null || ori.isEmpty()) return; LinearBlock b = getBlock(node, dim.opposite()); if (b == null) return; b.restoreSlice(ori); realign(); updateBlock(); } /** * @param node * @param id */ public void setVisualizationType(Node node, String id) { boolean isSingle = nodeCount() == 1; boolean localChange = true; if (isSingle) { boolean stratify = VisualizationTypeOracle.stratifyByDefault(id); for (EDimension dim : node.dimensions()) { if (isStratified(node, dim) != stratify) { stratifyBy(node, dim); localChange = false; } } } if (localChange) node.setVisualizationTypeImpl(id); } /** * @return */ public Set<IDType> getIDTypes() { Builder<IDType> builder = ImmutableSet.builder(); for (LinearBlock b : linearBlocks) { builder.add(b.getIdType()); } return builder.build(); } public void addVisibleItems(IDCategory category, Set<Integer> ids, IDType target) { LoadingCache<IDType, IIDTypeMapper<Integer, Integer>> cache = MappingCaches.create(null, target); for (LinearBlock b : linearBlocks) { if (category.isOfCategory(b.getIdType())) { TypedGroupList bids = b.getData(); if (target.equals(bids.getIdType())) ids.addAll(bids); else { Set<Integer> converted = cache.getUnchecked(bids.getIdType()).apply(bids); ids.addAll(converted); } } } } /** * @param dim * @return */ public List<Block> explode(EDimension dim) { Node first = Iterables.getFirst(nodes(), null); LinearBlock b = getBlock(first, dim.opposite()); assert b != null; TypedGroupList data = b.getData(); final int groups = data.getGroups().size(); List<Block> r = new ArrayList<>(groups); final EDirection dir = EDirection.getPrimary(dim.opposite()).opposite(); Rect bounds = getRectBounds().clone(); float shift = EXPLODE_SPACE * (groups - 1); if (dim.isHorizontal()) { bounds.x(bounds.x() - shift * 0.5f); } else { bounds.y(bounds.y() - shift * 0.5f); } float f = dim.select(bounds.size()) / data.size(); float offset = 0; final List<Pair<Node, ESortingMode>> sort = b.getSortCriteria(); for (int i = 0; i < groups; ++i) { Node prev = extractGroup(i, dim, b.get(0)); List<Pair<Node, ESortingMode>> sort2 = new ArrayList<>(sort); int si; for (ESortingMode m : ESortingMode.values()) if ((si = sort.indexOf(Pair.make(b.get(0), m))) >= 0) sort2.set(si, Pair.make(prev, m)); Block act = new Block(prev); for (int j = 1; j < b.size(); ++j) { Node node = extractGroup(i, dim, b.get(j)); for (ESortingMode m : ESortingMode.values()) if ((si = sort.indexOf(Pair.make(b.get(j), m))) >= 0) sort2.set(si, Pair.make(node, m)); act.addNode(prev, dir, node); prev = node; } int gsize = data.getGroups().get(i).size(); if (dim.isHorizontal()) act.setLocation(bounds.x() + offset, bounds.y()); else act.setLocation(bounds.x(), bounds.y() + offset); offset += f * gsize + EXPLODE_SPACE; act.restoreSorting(prev, dim, sort2); r.add(act); } return r; } public Block combine(List<Block> with, EDimension dim) { Node first = Iterables.getFirst(nodes(), null); LinearBlock b = getBlock(first, dim.opposite()); assert b != null; final EDirection dir = EDirection.getPrimary(dim.opposite()).opposite(); Vec2f loc = getLocation(); if (dim.isDimension()) loc.setX(loc.x() + EXPLODE_SPACE * 0.5f); else loc.setY(loc.y() + EXPLODE_SPACE * 0.5f); final List<Pair<Node, ESortingMode>> sort = b.getSortCriteria(); List<Pair<Node, ESortingMode>> sort2 = new ArrayList<>(sort); Block r = null; Node prev = null; for (Node node : b) { Node parent = node.getOrigin(); // as we have the tracing information int si; for (ESortingMode m : ESortingMode.values()) if ((si = sort.indexOf(Pair.make(node, m))) >= 0) sort2.set(si, Pair.make(parent, m)); if (r == null) r = new Block(parent); else r.addNode(prev, dir, parent); prev = parent; } assert r != null; r.restoreSorting(prev, dim, sort2); r.setLocation(loc.x(), loc.y()); return r; } private static Node extractGroup(int group, EDimension dim, Node node) { final List<NodeGroup> gs = node.getGroupNeighbors(EDirection.getPrimary(dim.opposite())); NodeGroup g = gs.get(group); return g.toNode(); } public boolean canExplode(EDimension dim) { LinearBlock b = null; for (LinearBlock bi : linearBlocks) { if (bi.getDim() == dim.opposite()) { if (b == null) b = bi; else return false; // multiple in this dir } } if (b == null) return false; // more than one group return b.getData().getGroups().size() > 1; } /** * @param blockGroup */ public void groupBy(BlockGroup group) { this.groups.add(group); } /** * @param blockGroup */ public void ungroupBy(BlockGroup group) { this.groups.remove(group); } public boolean partOfGroup() { return !groups.isEmpty(); } /** * @param g */ public void renderMiniMap(GLGraphics g) { Vec2f loc = getLocation(); g.move(loc.x(), loc.y()); boolean single = nodeCount() == 1; for (Node n : nodes()) { Rect bounds = n.getRectBounds(); g.color(n.getColor()).fillRect(bounds); String label = n.getLabel(); float hi = Math.min(10, bounds.height()); // g.drawText(label, bounds.x(), bounds.y() + (bounds.height() - hi) * 0.5f, bounds.width(), hi, // VAlign.CENTER); if (single) continue; for (EDimension dim : n.dimensions()) { LinearBlock b = getBlock(n, dim.opposite()); if (b != null && b.size() > 1) { int p = b.getSortPriority(n); boolean limitedTo = b.isLimitedTo(n); if (p < 0 && !limitedTo) continue; int max = b.getSortPriorities(); g.lineWidth(p < 0 ? 1 : max - p); boolean s = b.isStratisfied() && p == 0; g.lineStippled(limitedTo); g.color(s ? Color.RED : Color.BLACK); if (dim.isVertical()) { g.drawLine(bounds.x(), bounds.y(), bounds.x2(), bounds.y()); g.drawLine(bounds.x(), bounds.y2(), bounds.x2(), bounds.y2()); } else { g.drawLine(bounds.x(), bounds.y(), bounds.x(), bounds.y2()); g.drawLine(bounds.x2(), bounds.y(), bounds.x2(), bounds.y2()); } } } } g.lineStippled(false); g.lineWidth(1); g.move(-loc.x(), -loc.y()); } /** * */ public void resetHasBands() { for (LinearBlock b : linearBlocks) { b.setHasLeftBand(false); b.setHasRightBand(false); } } }