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.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.TreeSet; import org.caleydo.core.data.collection.EDimension; import org.caleydo.core.event.EventListenerManager.ListenTo; import org.caleydo.core.id.IDCreator; import org.caleydo.core.id.IDType; import org.caleydo.core.util.base.ILabeled; import org.caleydo.core.util.base.IUniqueObject; import org.caleydo.core.util.color.Color; 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.IMouseLayer; import org.caleydo.core.view.opengl.layout2.IPopupLayer; import org.caleydo.core.view.opengl.layout2.dnd.EDnDType; import org.caleydo.core.view.opengl.layout2.dnd.IDnDItem; import org.caleydo.core.view.opengl.layout2.dnd.IDragInfo; import org.caleydo.core.view.opengl.layout2.dnd.IDropGLTarget; import org.caleydo.core.view.opengl.layout2.geom.Rect; import org.caleydo.core.view.opengl.layout2.layout.IGLLayout2; import org.caleydo.core.view.opengl.layout2.layout.IGLLayoutElement; import org.caleydo.core.view.opengl.layout2.manage.EVisScaleType; import org.caleydo.core.view.opengl.layout2.manage.GLElementDimensionDesc; import org.caleydo.core.view.opengl.layout2.manage.GLElementFactories; import org.caleydo.core.view.opengl.layout2.manage.GLElementFactories.GLElementSupplier; import org.caleydo.core.view.opengl.layout2.manage.GLElementFactorySwitcher; import org.caleydo.core.view.opengl.layout2.manage.GLLocation; import org.caleydo.core.view.opengl.layout2.manage.GLLocation.ILocator; import org.caleydo.core.view.opengl.layout2.manage.IGLElementMetaData; import org.caleydo.core.view.opengl.picking.IPickingListener; import org.caleydo.core.view.opengl.picking.Pick; import org.caleydo.view.domino.api.model.EDirection; import org.caleydo.view.domino.api.model.typed.ITypedComparator; import org.caleydo.view.domino.api.model.typed.TypedCollections; import org.caleydo.view.domino.api.model.typed.TypedGroupList; import org.caleydo.view.domino.api.model.typed.TypedGroupSet; import org.caleydo.view.domino.api.model.typed.TypedList; import org.caleydo.view.domino.api.model.typed.TypedListGroup; import org.caleydo.view.domino.api.model.typed.TypedSetGroup; import org.caleydo.view.domino.internal.data.IDataValues; import org.caleydo.view.domino.internal.data.TransposedDataValues; import org.caleydo.view.domino.internal.dnd.DragElement; import org.caleydo.view.domino.internal.dnd.NodeDragInfo; import org.caleydo.view.domino.internal.dnd.NodeGroupDragInfo; import org.caleydo.view.domino.internal.event.HideNodeEvent; import org.caleydo.view.domino.internal.undo.CmdComposite; import org.caleydo.view.domino.internal.undo.MergeNodesCmd; import org.caleydo.view.domino.internal.undo.RemoveNodeGroupCmd; import org.caleydo.view.domino.internal.undo.ZoomCmd; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; /** * @author Samuel Gratzl * */ public class Node extends GLElementContainer implements IGLLayout2, ILabeled, IDropGLTarget, IPickingListener, IUniqueObject { /** * */ private static final String DATA_SCALE_FACTOR = "data"; static final float BORDER = 1; private final int id = IDCreator.createVMUniqueID(Node.class); private final Node origin; private final Node[] neighbors = new Node[4]; private String label; private IDataValues data; private TypedGroupList dimData; private TypedGroupSet dimUnderlying; private TypedGroupList recData; private TypedGroupSet recUnderlying; // visualizationtype -> scale factor // if type is size dependent -> global factor // else local factor private final Map<String, Vec2f> scaleFactors = new HashMap<>(); private ESetOperation dropSetOperation = null; private String visualizationType; private boolean mouseOver; private boolean isPreviewing = false; private boolean dependentTranspose = false; public Node(IDataValues data) { this(null, data, data.getLabel(), data.getDefaultGroups(EDimension.DIMENSION), data.getDefaultGroups(EDimension.RECORD)); } public Node(Node clone) { this.origin = clone; this.data = clone.data; this.label = clone.label; this.visualizationType = clone.visualizationType; this.dimUnderlying = clone.dimUnderlying; this.recUnderlying = clone.recUnderlying; copyScaleFactors(clone); setData(clone.dimData, clone.recData); init(); } public Node(Node origin, IDataValues data, String label, TypedGroupSet dimGroups, TypedGroupSet recGroups) { this.origin = origin; this.visualizationType = origin == null ? null : origin.visualizationType; this.data = data; this.label = label; this.dimUnderlying = dimGroups; this.recUnderlying = recGroups; if (origin != null) copyScaleFactors(origin); // guessShift(dimGroups.size(), recGroups.size()); setData(fixList(dimGroups), fixList(recGroups)); init(); } /** * @return the dependentTranspose, see {@link #dependentTranspose} */ public boolean isDependentTranspose() { return dependentTranspose; } /** * @param isPreviewing * setter, see {@link isPreviewing} */ public void setPreviewing(boolean isPreviewing) { if (this.isPreviewing == isPreviewing) return; float bak = getDetachedOffset(); this.isPreviewing = isPreviewing; float new_ = getDetachedOffset(); if (bak != new_ && findBlock() != null) findBlock().updatedNode(Node.this, bak, new_); } /** * @return the isPreviewing, see {@link #isPreviewing} */ public boolean isPreviewing() { return isPreviewing; } @Override public int getID() { return id; } /** * @return the origin, see {@link #origin} */ public Node getOrigin() { return origin; } private void copyScaleFactors(Node clone) { for (Map.Entry<String, Vec2f> entry : clone.scaleFactors.entrySet()) this.scaleFactors.put(entry.getKey(), entry.getValue().copy()); } /** * @param d * @param r * @return */ public static Vec2f initialScaleFactors(Vec2f viewSize, float d, float r) { if (d <= 1 && r <= 1) return new Vec2f(1, 1); if (d <= 1) return new Vec2f(1, initialScaleFactor(viewSize.y(), r)); if (r <= 1) return new Vec2f(initialScaleFactor(viewSize.x(), d), 1); float maxw = viewSize.x() * Constants.TARGET_MAX_VIEW_SIZE; float maxh = viewSize.y() * Constants.TARGET_MAX_VIEW_SIZE; float minw = viewSize.x() * Constants.TARGET_MIN_VIEW_SIZE; float minh = viewSize.y() * Constants.TARGET_MIN_VIEW_SIZE; if (d < minw && r < minh) { float fw = minw / d; float fh = minh / r; float f = Math.min(fw, fh); return new Vec2f(f, f); } if (d > maxw || r > maxh) { float fw = maxw / d; float fh = maxh / r; float f = Math.min(fw, fh); return new Vec2f(f, f); } return new Vec2f(1, 1); } private static float initialScaleFactor(float view, float f) { float max = view * Constants.TARGET_MAX_VIEW_SIZE; float min = view * Constants.TARGET_MIN_VIEW_SIZE; if (f < min) { return min / f; } if (f > min) { return max / f; } return 1; } /** * @param dimGroups2 * @return */ private TypedGroupList fixList(TypedGroupSet data) { if (data.isEmpty()) return TypedGroupList.createUngrouped( new TypedList(ImmutableList.of(TypedCollections.INVALID_ID), data.getIdType())); return data.asList(); } /** * */ private void init() { setLayout(this); setVisibility(EVisibility.PICKABLE); onPick(this); } @Override protected void renderImpl(GLGraphics g, float w, float h) { final Block b = findBlock(); // if (mouseOver) { // g.drawText(b.getStateString(this, EDimension.RECORD), 0, -12, w - 2, 10, VAlign.RIGHT); // g.drawText(b.getStateString(this, EDimension.DIMENSION), w + 2, h - 12, 100, 10); // } if (dropSetOperation == null) { super.renderImpl(g, w, h); } else renderDropHints(g, w, h); } private void renderDropHints(GLGraphics g, float w, float h) { EDimension dim = getLinearDimension(w, h); if (dim == null) { renderRectDropHints(g, w, h); } else { renderLinearDropHints(g, w, h, dim); } } private static EDimension getLinearDimension(float w, float h) { float aspectRatio = w / h; if (0.95f <= aspectRatio && aspectRatio <= 1.05f) return null; return EDimension.get(w > h); } private final ESetOperation toSetType(Vec2f l) { Vec2f size = getSize(); final int c = ESetOperation.values().length; int index; EDimension dim = getLinearDimension(size.x(), size.y()); if (dim == null) { final int rows = (int) Math.sqrt(c + c % 2); final int cols = (int) Math.ceil(c / (float) rows); int row = Math.min((int) ((l.y() / size.y()) * rows), rows - 1); int col = Math.min((int) ((l.x() / size.x()) * cols), cols - 1); index = Math.min(row * cols + col, c - 1); } else { float ratio = dim.select(l) / dim.select(size); index = Math.min((int) (ratio * c), c - 1); } if (index < 0) return ESetOperation.UNION; return ESetOperation.values()[index]; } private void renderLinearDropHints(GLGraphics g, float w, float h, EDimension dim) { ESetOperation[] vs = ESetOperation.values(); if (dim.isHorizontal()) { float wi = w / vs.length; float hi = Math.min(h * 0.8f, wi); for (int i = 0; i < vs.length; ++i) { final float x = i * wi + (wi - hi) * 0.5f; final ESetOperation op = vs[i]; APlaceholder.renderDropZone(g, i * wi, 0, wi, h, Color.LIGHT_GRAY); g.fillImage(op.toIcon(), x, (h - hi) * 0.5f, hi, hi); if (op == dropSetOperation) g.color(Color.BLACK).drawRoundedRect(x, (h - hi) * 0.5f, hi, hi, 5); } } else { float hi = h / vs.length; // union double float wi = Math.min(w * 0.8f, hi); for (int i = 0; i < vs.length; ++i) { final float y = i * hi + (hi - wi) * 0.5f; final ESetOperation op = vs[i]; APlaceholder.renderDropZone(g, 0, i * hi, w, hi, Color.LIGHT_GRAY); g.fillImage(op.toIcon(), (w - wi) * 0.5f, y, wi, wi); if (op == dropSetOperation) g.color(Color.BLACK).drawRoundedRect((w - wi) * 0.5f, y, wi, wi, 5); } } } private void renderRectDropHints(GLGraphics g, float w, float h) { ESetOperation[] vs = ESetOperation.values(); int rows = (int) Math.sqrt(vs.length + vs.length % 2); int cols = (int) Math.ceil(vs.length / (float) rows); int k = 0; float wi = w / cols; float hi = h / rows; float si = Math.min(wi * 0.8f, hi * 0.8f); outer: for (int j = 0; j < rows; ++j) { float y = j * hi + (hi - si) * 0.5f; for (int i = 0; i < cols; ++i) { ESetOperation op = vs[k++]; float x = i * wi + (wi - si) * 0.5f; APlaceholder.renderDropZone(g, i * wi, j * hi, wi, hi, Color.LIGHT_GRAY); g.fillImage(op.toIcon(), x, y, si, si); if (op == dropSetOperation) g.color(Color.BLACK).drawRoundedRect(x, y, si, si, 5); if (k >= vs.length) break outer; } } } @Override protected void renderPickImpl(GLGraphics g, float w, float h) { if (getVisibility() != EVisibility.PICKABLE) return; super.renderPickImpl(g, w, h); } @Override public void pick(Pick pick) { switch (pick.getPickingMode()) { case MOUSE_OVER: context.getMouseLayer().addDropTarget(this); mouseOver = true; repaint(); break; case MOUSE_OUT: context.getMouseLayer().removeDropTarget(this); dropSetOperation = null; mouseOver = false; repaint(); break; case MOUSE_WHEEL: Vec2f shift = ScaleLogic.shiftLogic((IMouseEvent) pick, getSize()); findParent(Domino.class).getUndo().push(new ZoomCmd(this, shift, null)); break; default: break; } } @Override public boolean canSWTDrop(IDnDItem item) { if (has(EDimension.DIMENSION) && has(EDimension.RECORD)) return false; Node b = toNode(item); if (b == this || b == null || (item.getInfo() instanceof NodeDragInfo && ((NodeDragInfo) item.getInfo()).getNode() == this)) return false; if (b.has(EDimension.DIMENSION) && b.has(EDimension.RECORD)) return false; for (EDimension dim : EDimension.values()) if (!getIdType(dim).getIDCategory().isOfCategory(b.getIdType(dim))) return false; return true; } /** * @param item * @return */ private Node toNode(IDnDItem item) { IDragInfo info = item.getInfo(); if (info instanceof NodeDragInfo) return ((NodeDragInfo) info).getNode(); else if (info instanceof NodeGroupDragInfo) return ((NodeGroupDragInfo) info).getNode(); else if (Nodes.canExtract(item)) return Nodes.extract(item); return null; } @Override public void onItemChanged(IDnDItem item) { final ESetOperation type = toSetType(toRelative(item.getMousePos())); if (this.dropSetOperation != type) { this.dropSetOperation = type; repaint(); } final Domino domino = findParent(Domino.class); if (domino == null) return; DragElement current = domino.getCurrentlyDraggedVis(); if (current == null) return; current.setVisibility(EVisibility.HIDDEN); } @Override public EDnDType defaultSWTDnDType(IDnDItem item) { return EDnDType.COPY; } @Override public void onDrop(IDnDItem item) { Vec2f mousePos = toRelative(item.getMousePos()); IDragInfo info = item.getInfo(); if (info instanceof NodeGroupDragInfo) { NodeGroupDragInfo g = (NodeGroupDragInfo) info; mergeNode(g.getGroup().toNode(), mousePos, item.getType() == EDnDType.MOVE ? g.getGroup() : null); } else if (info instanceof NodeDragInfo) { NodeDragInfo g = (NodeDragInfo) info; Node n = g.getNode(); if (item.getType() == EDnDType.COPY) n = new Node(n); mergeNode(n, mousePos, null); } else { Node node = Nodes.extract(item); if (node != null) mergeNode(node, mousePos, null); } dropSetOperation = null; repaint(); } @Override public void onDropLeave() { } public Set<EDimension> dimensions() { boolean dim = has(EDimension.DIMENSION); boolean rec = has(EDimension.RECORD); if (dim && rec) return Sets.immutableEnumSet(EDimension.DIMENSION, EDimension.RECORD); if (dim && !rec) return Sets.immutableEnumSet(EDimension.DIMENSION); if (!dim && rec) return Sets.immutableEnumSet(EDimension.RECORD); return Collections.emptySet(); } /** * @param node * @param mousePos * @param groupToRemove */ private void mergeNode(Node node, Vec2f mousePos, NodeGroup groupToRemove) { final ESetOperation type = toSetType(mousePos); Domino domino = findParent(Domino.class); final UndoStack undo = domino.getUndo(); final MergeNodesCmd cmd = new MergeNodesCmd(this, type, node); if (groupToRemove != null) { undo.push(CmdComposite.chain(cmd, new RemoveNodeGroupCmd(groupToRemove))); } else undo.push(cmd); } public IDataValues getDataValues() { return data; } public void setDataValues(IDataValues data) { this.data = data; } @Override protected void takeDown() { context.getMouseLayer().removeDropTarget(this); super.takeDown(); } /** * @return the label, see {@link #label} */ @Override public String getLabel() { return label; } /** * @param label * setter, see {@link label} */ public void setLabel(String label) { this.label = label; } public void setData(EDimension dim, TypedGroupList data) { setData(dim.select(data, dimData), dim.select(recData, data)); } public TypedGroupList getData(EDimension dim) { return dim.select(dimData, recData); } public void setData(TypedGroupList dimData, TypedGroupList recData) { this.dimData = dimData; this.recData = recData; updateGroupNodes(dimData, recData); } public void prepareData(EDimension dim, int groups) { prepareData(dim.select(groups, dimData.getGroups().size()), dim.select(recData.getGroups().size(), groups)); } public void prepareData(int dimGroups, int recGroups) { final int total = dimGroups * recGroups; // 1. reset and create for (int i = 0; i < total; ++i) { final NodeGroup child = getOrCreate(i); child.setVisibility(EVisibility.PICKABLE); child.resetNeighbors(); } // 2. update neighbors int n = 0; NodeGroup[] left = new NodeGroup[recGroups]; for (int i = 0; i < dimGroups; ++i) { NodeGroup above = null; for (int j = 0; j < recGroups; ++j) { final NodeGroup child = getOrCreate(n++); child.setNeighbor(EDirection.NORTH, above); if (above != null) above.setNeighbor(EDirection.SOUTH, child); above = child; if (i > 0) { left[j].setNeighbor(EDirection.EAST, child); child.setNeighbor(EDirection.WEST, left[j]); } left[j] = child; } } { final List<GLElement> subList = this.asList().subList(n, size()); if (!subList.isEmpty()) { for (NodeGroup g : Iterables.filter(subList, NodeGroup.class)) { g.prepareRemoveal(); g.setVisibility(EVisibility.NONE); } // subList.clear(); // don't clear just hide } } } private void updateGroupNodes(TypedGroupList dimData, TypedGroupList recData) { final List<TypedListGroup> dimGroups = dimData.getGroups(); final List<TypedListGroup> recGroups = recData.getGroups(); int n = 0; // 2. set data for (TypedListGroup dimGroup : dimGroups) { for (TypedListGroup recGroup : recGroups) { final NodeGroup child = getOrCreate(n++); child.setData(dimGroup, recGroup); } } if (context != null) { updateSize(true); relayout(); } } Iterable<NodeGroup> nodeGroups() { // all visible node groups return Iterables.filter(Iterables.filter(this, EVisibility.PICKABLE), NodeGroup.class); } @Override protected void init(IGLElementContext context) { super.init(context); { Vec2f bak = scaleFactors.get(DATA_SCALE_FACTOR); Vec2f size = findParent(MiniMapCanvas.class).getSize(); final Vec2f v = initialScaleFactors(size, dimData.size(), recData.size()); if (bak != null && !Float.isNaN(bak.x())) v.setX(bak.x()); if (bak != null && !Float.isNaN(bak.y())) v.setY(bak.y()); scaleFactors.put(DATA_SCALE_FACTOR, v); Blocks blocks = findParent(Blocks.class); final float sx = blocks.getRulerScale(this.dimUnderlying.getIdType()); if (!Float.isNaN(sx) && (bak == null || Float.isNaN(bak.x()))) // ruler scale and not externally set v.setX(sx); final float sy = blocks.getRulerScale(this.recUnderlying.getIdType()); if (!Float.isNaN(sy) && (bak == null || Float.isNaN(bak.y()))) // ruler scale and not externally set v.setY(sy); scaleFactors.put(DATA_SCALE_FACTOR, v); } updateSize(false); } private void updateSize(boolean adaptDetached) { verifyScaleFactors(); Vec2f new_ = addBorders(scaleSize(originalSize())); setSize(new_.x(), new_.y()); } /** * @param scaleSize * @return */ private Vec2f addBorders(Vec2f s) { final int dims = getData(EDimension.DIMENSION).getGroups().size(); final int recs = getData(EDimension.RECORD).getGroups().size(); // 2 border per group + 2 extra between each group s.setX(s.x() + (dims + dims - 1) * 2 * BORDER); s.setY(s.y() + (recs + recs - 1) * 2 * BORDER); return s; } private Vec2f originalSize() { float[] xi = getSizes(EDimension.DIMENSION); float[] yi = getSizes(EDimension.RECORD); float w = sum(xi); float h = sum(yi); return new Vec2f(w, h); } private Vec2f scaleSize(Vec2f size) { Vec2f scale = getScaleFactor(); size.setX(scale.x() * size.x()); size.setY(scale.y() * size.y()); return size; } private Vec2f getScaleFactor() { Vec2f scale; String type = getScaleFactorKey(); if (this.scaleFactors.containsKey(type)) scale = this.scaleFactors.get(type); else scale = new Vec2f(1, 1); Vec2f f = new Vec2f(Float.isNaN(scale.x()) ? 1 : scale.x(), Float.isNaN(scale.y()) ? 1 : scale.y()); return f; } public void copyScaleFactors(Node node, EDimension dim) { for (Map.Entry<String, Vec2f> entry : node.scaleFactors.entrySet()) { Vec2f old = scaleFactors.get(entry.getKey()); if (old == null) old = new Vec2f(Float.NaN, Float.NaN); if (DATA_SCALE_FACTOR.equals(entry.getKey()) && !node.has(dim)) continue; if (dim.isHorizontal()) old.setX(entry.getValue().x()); else old.setY(entry.getValue().y()); this.scaleFactors.put(entry.getKey(), old); } if (context != null) updateSize(false); relayout(); } /** * @param opposite * @param scale */ public void setDataScaleFactor(EDimension dim, float scale) { Vec2f s; if (this.scaleFactors.containsKey(DATA_SCALE_FACTOR)) s = this.scaleFactors.get(DATA_SCALE_FACTOR); else s = new Vec2f(1, 1); if (dim.isHorizontal()) s.setX(scale); else s.setY(scale); this.scaleFactors.put(DATA_SCALE_FACTOR, s); if (GLElementFactories.getMetaData(getVisualizationType()).getScaleType() == EVisScaleType.DATADEPENDENT) { updateSize(false); relayout(); } } public float getDataScaleFactor(EDimension dim) { if (this.scaleFactors.containsKey(DATA_SCALE_FACTOR)) { float v = dim.select(this.scaleFactors.get(DATA_SCALE_FACTOR)); if (!Float.isNaN(v)) return v; } return 1; } private float[] getSizes(EDimension dim) { List<NodeGroup> lefts = getGroupNeighbors(EDirection.getPrimary(dim.opposite())); float[] r = new float[lefts.size()]; int i = 0; for (NodeGroup l : lefts) { double size = l.getDesc(dim).size(l.getData(dim).size()); r[i++] = (float) size; } return r; } /** * @param selection * @return */ public boolean canMerge(Collection<NodeGroup> groups) { if (groups.size() < 2) return false; EDimension dim = getSingleGroupingDimension(); if (dim == null) return false; return true; } public EDimension getSingleGroupingDimension() { final List<TypedListGroup> dimGroups = dimData.getGroups(); final List<TypedListGroup> recGroups = recData.getGroups(); if (dimGroups.size() > 1 && recGroups.size() > 1) return null; if (dimGroups.size() > 1) return EDimension.DIMENSION; return EDimension.RECORD; } public boolean canRemoveGroup(NodeGroup nodeGroup) { if (this.groupCount() == 1) return false; EDimension dim = getSingleGroupingDimension(); return (dim != null); } /** * @return */ public int groupCount() { return Iterables.size(nodeGroups()); } /** * @param dim * @param l */ public void setUnderlyingData(EDimension dim, TypedGroupSet data) { setUnderlyingDataImpl(dim, data); triggerResort(dim); } public void setUnderlyingDataImpl(EDimension dim, TypedGroupSet data) { if (dim.isDimension()) dimUnderlying = data; else recUnderlying = data; } private void triggerResort(EDimension dim) { findBlock().resort(this, dim); } private Block findBlock() { return findParent(Block.class); } public Block getBlock() { return findBlock(); } /** * @param n * @return */ private NodeGroup getOrCreate(int n) { if (n < size()) return (NodeGroup) get(n); NodeGroup g = new NodeGroup(this); this.add(g); return g; } public void setNeighbor(EDirection dir, Node neighbor) { this.neighbors[dir.ordinal()] = neighbor; List<NodeGroup> myGroups = getGroupNeighbors(dir); List<NodeGroup> neighborGroups = neighbor == null ? Collections.<NodeGroup>emptyList() : neighbor.getGroupNeighbors(dir.opposite()); if (myGroups.size() == neighborGroups.size()) { for (int i = 0; i < myGroups.size(); ++i) { myGroups.get(i).setNeighbor(dir, neighborGroups.get(i)); } } else { for (NodeGroup g : myGroups) { g.setNeighbor(dir, null); } } } public void updateNeighbor(EDirection dir, Node neighbor, EDimension dim, int groups) { List<NodeGroup> myGroups = getGroupNeighbors(dir, dim, groups); List<NodeGroup> neighborGroups = neighbor == null ? Collections.<NodeGroup>emptyList() : neighbor.getGroupNeighbors(dir.opposite(), dim, groups); if (myGroups.size() == neighborGroups.size()) { for (int i = 0; i < myGroups.size(); ++i) { final NodeGroup ng = neighborGroups.get(i); final NodeGroup g = myGroups.get(i); g.setNeighbor(dir, ng); ng.setNeighbor(dir.opposite(), g); } } else { for (NodeGroup g : myGroups) { g.setNeighbor(dir, null); } for (NodeGroup g : neighborGroups) { g.setNeighbor(dir.opposite(), null); } } } public Node getNeighbor(EDirection dir) { return neighbors[dir.ordinal()]; } public List<NodeGroup> getGroupNeighbors(EDirection dir) { final int dGroups = dimData.getGroups().size(); final int rGroups = recData.getGroups().size(); return getGroupNeighbors(dir, dGroups, rGroups); } public List<NodeGroup> getGroupNeighbors(EDirection dir, EDimension dim, int groups) { return getGroupNeighbors(dir, dim.select(groups, dimData.getGroups().size()), dim.select(recData.getGroups().size(), groups)); } public List<NodeGroup> getGroupNeighbors(EDirection dir, int dGroups, int rGroups) { int offset = 0; int shift = 1; int size; switch (dir) { case WEST: offset = 0; shift = 1; size = rGroups; break; case EAST: offset = (dGroups - 1) * rGroups; shift = 1; size = rGroups; break; case NORTH: offset = 0; shift = rGroups; size = dGroups; break; case SOUTH: offset = rGroups - 1; shift = rGroups; size = dGroups; break; default: return Collections.emptyList(); } List<NodeGroup> r = new ArrayList<>(); for (int i = 0; i < size; ++i) { r.add((NodeGroup) get(offset + i * shift)); } return r; } @Override public boolean doLayout(List<? extends IGLLayoutElement> children, float w, float h, IGLLayoutElement parent, int deltaTimeMs) { float[] dims = getSizes(EDimension.DIMENSION); float[] recs = getSizes(EDimension.RECORD); float dimSpace = (dims.length + dims.length - 1) * 2 * BORDER; float recSpace = (recs.length + recs.length - 1) * 2 * BORDER; // 2 border per group + 2 extra between each group float fw = (w - dimSpace) / sum(dims); float fh = (h - recSpace) / sum(recs); int k = 0; float x = 0; for (int i = 0; i < dims.length; ++i) { float y = 0; float wi = dims[i] * fw + BORDER * 2; for (int j = 0; j < recs.length; ++j) { float hi = recs[j] * fh + BORDER * 2; IGLLayoutElement child = children.get(k++); child.setBounds(x, y, wi, hi); y += hi + BORDER * 2; } x += wi + BORDER * 2; } return false; } /** * @param dims * @return */ private static float sum(float... vs) { float r = 0; for (float v : vs) r += v; return r; } public boolean has(EDimension dim) { return !TypedCollections.isInvalid(getUnderlyingData(dim)); } /** * @param opposite * @return */ public IDType getIdType(EDimension dim) { return getUnderlyingData(dim).getIdType(); } public TypedGroupSet getUnderlyingData(EDimension dim) { return dim.select(dimUnderlying, recUnderlying); } public int compare(EDimension dim, int a, int b, boolean reverse) { // check existence TypedGroupSet groups = getUnderlyingData(dim); if (!groups.isEmpty()) { boolean hasA = a >= 0 && groups.contains(a); boolean hasB = b >= 0 && groups.contains(b); int r; if ((r = Boolean.compare(!hasA, !hasB)) != 0) return reverse ? -r : r; if (!hasA && !hasB) return 0; // check groups int groupA = indexOf(groups, a); int groupB = indexOf(groups, b); if ((r = Integer.compare(groupA, groupB)) != 0) return reverse ? -r : r; } // check values int r = this.data.compare(dim, a, b, getData(dim.opposite())); return reverse ? -r : r; } public ITypedComparator getComparator(final EDimension dim, final boolean reverse) { return new ITypedComparator() { @Override public IDType getIdType() { return getUnderlyingData(dim).getIdType(); } @Override public int compare(Integer o1, Integer o2) { return Node.this.compare(dim, o1, o2, reverse); } }; } public void stratifyByMe(EDimension dim) { findBlock().stratifyBy(this, dim); } public void sortByMe(EDimension dim) { findBlock().sortBy(this, dim); } /** * @param groups * @param a * @return */ private static int indexOf(TypedGroupSet groups, int a) { List<TypedSetGroup> g = groups.getGroups(); for (int i = 0; i < g.size(); ++i) if (g.get(i).contains(a)) return i; return -1; } /** * */ public void selectAll() { for (NodeGroup g : nodeGroups()) g.selectMe(); } /** * @param pickable */ public void setContentPickable(boolean pickable) { for (NodeGroup g : Iterables.filter(this, NodeGroup.class)) { g.setContentPickable(pickable); } } public ILocator getGroupLocator(final EDimension dim) { final List<NodeGroup> groups = getGroupNeighbors(EDirection.getPrimary(dim.opposite())); return new GLLocation.ALocator() { @Override public GLLocation apply(int dataIndex, boolean topLeft) { NodeGroup g = groups.get(dataIndex); double offset = dim.select(g.getLocation()) + BORDER; double size = dim.select(g.getSize()) - BORDER * 2; return new GLLocation(offset, size); } @Override public Set<Integer> unapply(GLLocation location) { Set<Integer> r = new TreeSet<>(); for (int i = 0; i < groups.size(); ++i) { NodeGroup g = groups.get(i); double offset = dim.select(g.getLocation()) + BORDER; double size = dim.select(g.getSize()) - BORDER * 2; if (offset + size < location.getOffset()) continue; if (offset > location.getOffset2()) break; r.add(i); } return ImmutableSet.copyOf(r); } }; } public ILocator getLocator(final EDimension dim) { TypedGroupList data = getData(dim); final List<NodeGroup> groups = getGroupNeighbors(EDirection.getPrimary(dim.opposite())); int offset = 0; List<TypedListGroup> gropus2 = data.getGroups(); final List<GroupLocator> locators = new ArrayList<>(gropus2.size()); for (int i = 0; i < gropus2.size(); ++i) { int size = gropus2.get(i).size(); final NodeGroup g = groups.get(i); float loffset = dim.select(g.getLocation()) + BORDER; float lsize = dim.select(g.getSize()) - BORDER * 2; final ILocator loc = g.getLocator(dim); if (GLLocation.NO_LOCATOR == loc) // one no location, all no location return GLLocation.NO_LOCATOR; GroupLocator gl = new GroupLocator(new GLLocation(loffset, lsize), offset, size, GLLocation.shift(loc, loffset)); offset += size; locators.add(gl); } return new GLLocation.ALocator() { @Override public GLLocation apply(int dataIndex, boolean topLeft) { for (GroupLocator loc : locators) { if (loc.in(dataIndex)) { return loc.apply(dataIndex, topLeft); } } return GLLocation.UNKNOWN; } @Override public Set<Integer> unapply(GLLocation location) { Set<Integer> r = new HashSet<>(); for (GroupLocator loc : locators) { if (loc.in(location)) r.addAll(loc.unapply(location)); } return GLLocation.UNKNOWN_IDS; } }; } private static class GroupLocator extends GLLocation.ALocator { private final GLLocation loc; private final int dataOffset; private final int dataSize; private final ILocator locator; public GroupLocator(GLLocation loc, int dataOffset, int dataSize, ILocator locator) { this.loc = loc; this.dataOffset = dataOffset; this.dataSize = dataSize; this.locator = locator; } /** * @param location * @return */ public boolean in(GLLocation location) { double o = location.getOffset(); double o2 = location.getOffset2(); if (loc.getOffset2() < o || loc.getOffset() > o2) return false; return true; } public boolean in(int dataIndex) { return dataIndex < (dataOffset + dataSize); } @Override public GLLocation apply(int dataIndex, boolean topLeft) { return locator.apply(dataIndex - dataOffset, topLeft); } @Override public Set<Integer> unapply(GLLocation location) { Set<Integer> d = locator.unapply(location); List<Integer> r = new ArrayList<>(d.size()); for (Integer di : d) r.add(di + dataOffset); return ImmutableSet.copyOf(r); } } /** * @param dimension * @return */ public boolean isAlone(EDimension dim) { return getNeighbor(EDirection.getPrimary(dim)) == null && getNeighbor(EDirection.getPrimary(dim).opposite()) == null; } /** * */ public void transposeView() { dependentTranspose = !dependentTranspose; setData(dimData, recData); final float n = getDetachedOffset(); findBlock().updatedNode(this, n, n); } public Node transposeMe() { this.data = TransposedDataValues.transpose(data); TypedGroupSet g = recUnderlying; this.recUnderlying = dimUnderlying; this.dimUnderlying = g; TypedGroupList t = this.recData; this.recData = dimData; this.dimData = t; for (Vec2f scale : scaleFactors.values()) scale.set(scale.y(), scale.x()); setData(dimData, recData); return this; } @ListenTo(sendToMe = true) private void onHideNodeEvent(HideNodeEvent event) { setVisibility(EVisibility.HIDDEN); // remove all children drag sources final IMouseLayer m = context.getMouseLayer(); for (NodeGroup g : nodeGroups()) { m.removeDragSource(g); } m.removeDropTarget(this); } /** * */ public void showAgain() { setVisibility(EVisibility.PICKABLE); } /** * @param r */ public void selectByBounds(Rectangle2D r) { Vec2f l = getLocation(); r = new Rectangle2D.Double(r.getX() - l.x(), r.getY() - l.y(), r.getWidth(), r.getHeight()); for (NodeGroup node : nodeGroups()) { final Rectangle2D ri = node.getRectangleBounds(); if (ri.intersects(r)) { node.selectMe(); } } } /** * @param s */ public void selectDefaultVisualization(GLElementFactorySwitcher s) { if (visualizationType != null && trySetVisualizationType(s, visualizationType)) return; // if (origin != null && ) if (origin != null && trySetVisualizationType(s, origin.getVisualizationType())) return; Collection<String> list = this.data.getDefaultVisualization(); for (String target : list) { if (trySetVisualizationType(s, target)) return; } } private static boolean trySetVisualizationType(GLElementFactorySwitcher s, String target) { if (target == null) return false; for (GLElementSupplier supp : s) { if (target.equals(supp.getId())) { s.setActive(supp); return true; } } return false; } public String getVisualizationType() { return getVisualizationType(false); } /** * @return */ public String getVisualizationType(boolean guess) { final String default_ = guess ? visualizationType : null; if (isEmpty()) { return default_; } NodeGroup g = (NodeGroup) get(0); final GLElementFactorySwitcher s = g.getSwitcher(); return s == null ? default_ : s.getActiveId(); } public void setVisualizationType(String id) { int active = findVisTypeIndex(id); if (active < 0) // invalid type return; visualizationType = id; String bak = getRepresentableSwitcher().getActiveId(); if (Objects.equals(bak, id)) // no change return; findBlock().setVisualizationType(this, id); } void setVisualizationTypeImpl(String id) { int active = findVisTypeIndex(id); float was = getDetachedOffset(); for (NodeGroup g : nodeGroups()) { GLElementFactorySwitcher s = g.getSwitcher(); s.setActive(active); } updateSize(true); relayout(); findBlock().updatedNode(Node.this, was, getDetachedOffset()); } public float getDetachedOffset() { if (isPreviewing) return Block.DETACHED_OFFSET * 2; IGLElementMetaData metaData = GLElementFactories.getMetaData(getVisualizationType()); boolean needDetached = metaData != null && metaData.getScaleType() == EVisScaleType.FIX; return needDetached ? Block.DETACHED_OFFSET : 0; } @Override public String toString() { return getLabel(); } /** * @param id * @return */ private int findVisTypeIndex(String id) { NodeGroup g = (NodeGroup) get(0); int i = 0; for (GLElementSupplier s : g.getSwitcher()) { if (s.getId().equals(id)) return i; i++; } return -1; } public GLElementFactorySwitcher getRepresentableSwitcher() { NodeGroup g = (NodeGroup) get(0); return g.getSwitcher(); } /** * @param shiftX * @param shiftY */ public void shiftBy(float shiftX, float shiftY) { if (shiftX == 0 && shiftY == 0) return; Vec2f raw = originalSize(); Vec2f oldScale = getScaleFactor(); Vec2f new_ = scaleSize(raw.copy()).plus(new Vec2f(shiftX, shiftY)); final GLElementFactorySwitcher switcher = getRepresentableSwitcher(); GLElementDimensionDesc dimDesc = switcher.getActiveDesc(EDimension.DIMENSION); GLElementDimensionDesc recDesc = switcher.getActiveDesc(EDimension.RECORD); // not allowed to change if (!dimDesc.isValid(new_.x(), dimData.size())) new_.setX(raw.x() * oldScale.x()); if (!recDesc.isValid(new_.y(), recData.size())) new_.setY(raw.y() * oldScale.y()); float sx = new_.x() / raw.x(); float sy = new_.y() / raw.y(); String type = getScaleFactorKey(); scaleFactors.put(type, new Vec2f(sx, sy)); new_ = addBorders(new_); setSize(new_.x(), new_.y()); relayout(); } /** * */ private void verifyScaleFactors() { String type = getScaleFactorKey(); if (!scaleFactors.containsKey(type)) return; // no stored scale factors -> valid final GLElementFactorySwitcher switcher = getRepresentableSwitcher(); if (switcher == null) return; GLElementDimensionDesc dimDesc = switcher.getActiveDesc(EDimension.DIMENSION); GLElementDimensionDesc recDesc = switcher.getActiveDesc(EDimension.RECORD); final Vec2f ori = originalSize().copy(); Vec2f scaleSize = scaleSize(ori.copy()); // not allowed to change reset Vec2f scale = scaleFactors.get(type); if (!dimDesc.isValid(scaleSize.x(), dimData.size())) scale.setX(1); if (!recDesc.isValid(scaleSize.y(), recData.size())) scale.setY(1); } private String getScaleFactorKey() { String ori = getVisualizationType(); String type = ori; final IGLElementMetaData metaData = GLElementFactories.getMetaData(type); if (metaData != null && metaData.getScaleType() == EVisScaleType.DATADEPENDENT) type = DATA_SCALE_FACTOR; if (type == DATA_SCALE_FACTOR) { final GLElementFactorySwitcher switcher = getRepresentableSwitcher(); if (switcher == null) return type; GLElementDimensionDesc dimDesc = switcher.getActiveDesc(EDimension.DIMENSION); GLElementDimensionDesc recDesc = switcher.getActiveDesc(EDimension.RECORD); if (!dimDesc.isCountDependent() && !recDesc.isCountDependent()) type = ori; } return type; } public void shiftTo(EDimension dim, float v) { v = v - dim.select(getSize()); shiftBy(dim.select(v, 0), dim.select(0, v)); } public Rect getDetachedRectBounds() { Rect r = getRectBounds().clone(); return r; } /** * @param x * @param y * @param width * @param height */ public void setDetachedBounds(float x, float y, float w, float h) { setBounds(x, y, w, h); relayout(); } /** * @param opposite * @return */ public INodeLocator getNodeLocator(EDimension d) { float offset = 0;// d.select(getLocation()); float size = d.select(getSize()); return new NodeLocator(new GLLocation(offset, size), getGroupLocator(d), getLocator(d)); } public IGLElementContext getContext() { return super.context; } /** * @return */ public Color getColor() { return data.getColor(); } public void shiftLocation(float x, float y) { Vec2f l = getLocation(); setLocation(l.x() + x, l.y() + y); } /** * */ public void showInFocus() { IPopupLayer popup = context.getPopupLayer(); FocusOverlay overlay = new FocusOverlay(this); Vec2f size = overlay.getPreferredSize(); Vec2f total = popup.getSize(); Rect bounds = focusBounds(size, total); popup.show(overlay, bounds); } private static Rect focusBounds(Vec2f size, Vec2f total) { Vec2f avail = total.times(0.8f); float wi = avail.x() / size.x(); float hi = avail.y() / size.y(); Vec2f target; if (wi < hi) { target = size.times(wi); } else { target = size.times(hi); } float w = target.x(); float h = target.y(); return new Rect((total.x() - w) * 0.5f, (total.y() - h) * 0.5f, w, h); } /** * @param dimData2 * @param dimension * @return */ public TypedGroupSet getSubGroupData(TypedListGroup group, EDimension dim) { boolean stratified = findBlock().isStratified(this, dim); final TypedGroupSet underlying = getUnderlyingData(dim); boolean hasMultipleGroups = underlying.getGroups().size() > 1; if (stratified || !hasMultipleGroups) return new TypedGroupSet(group.asSet()); return underlying.subSet(group); } }