package de.tud.kom.socom.web.client.drawables; import static de.tud.kom.socom.web.client.graphview.GlobalGraphSettings.ANIMATION_BUSY; import static de.tud.kom.socom.web.client.graphview.GlobalGraphSettings.CONNECTION_TOOLTIP_COLOR_1; import static de.tud.kom.socom.web.client.graphview.GlobalGraphSettings.CONNECTION_TOOLTIP_COLOR_2; import static de.tud.kom.socom.web.client.graphview.GlobalGraphSettings.CONNECTION_TOOLTIP_HEIGHT; import static de.tud.kom.socom.web.client.graphview.GlobalGraphSettings.CONNECTION_TOOLTIP_WIDTH; import static de.tud.kom.socom.web.client.graphview.GlobalGraphSettings.DYNAMIC_NODE_HEIGHT; import static de.tud.kom.socom.web.client.graphview.GlobalGraphSettings.DYNAMIC_NODE_WIDTH; import static de.tud.kom.socom.web.client.graphview.GlobalGraphSettings.EDGE_AVG_COLOR; import static de.tud.kom.socom.web.client.graphview.GlobalGraphSettings.EDGE_CURVE_STRENGTH; import static de.tud.kom.socom.web.client.graphview.GlobalGraphSettings.MAX_EDGE_THICKNESS; import static de.tud.kom.socom.web.client.graphview.GlobalGraphSettings.MIN_EDGE_THICKNESS; import static de.tud.kom.socom.web.client.graphview.GlobalGraphSettings.NODE_HEIGHT; import static de.tud.kom.socom.web.client.graphview.GlobalGraphSettings.NODE_STROKE_COLOR; import static de.tud.kom.socom.web.client.graphview.GlobalGraphSettings.NODE_WIDTH; import java.util.HashSet; import java.util.Map; import org.sgx.raphael4gwt.raphael.Paper; import org.sgx.raphael4gwt.raphael.Path; import org.sgx.raphael4gwt.raphael.Raphael; import org.sgx.raphael4gwt.raphael.Rect; import org.sgx.raphael4gwt.raphael.Set; import org.sgx.raphael4gwt.raphael.Shape; import org.sgx.raphael4gwt.raphael.Text; import org.sgx.raphael4gwt.raphael.base.Attrs; import org.sgx.raphael4gwt.raphael.base.Glow; import org.sgx.raphael4gwt.raphael.base.Point; import org.sgx.raphael4gwt.raphael.event.Callback; import org.sgx.raphael4gwt.raphael.event.HoverListener; import org.sgx.raphael4gwt.raphael.event.MouseEventListener; import com.google.gwt.dom.client.NativeEvent; public class DrawableConnection { private long to; private long from; private long timesused; private double weight; private Paper paper; private Path path; private static java.util.Set<ConnectionTooltip> openToolboxes = new HashSet<ConnectionTooltip>(); private boolean allowMultipleToolboxes = false; protected ConnectionTooltip toolbox; public DrawableConnection(long from, long connectionTo, long timesused, double timesusedpercentual) { this.from = from; this.to = connectionTo; this.timesused = timesused; this.weight = timesusedpercentual; } public boolean paint(Paper paper, Map<Long, DrawableNode> drawables, Map<Long, Integer> levels) { this.paper = paper; DrawableNode drawableNode = drawables.get(to); if (drawableNode == null) return false; Path prepath = getPath(paper, from, to, drawables, levels); long width = MIN_EDGE_THICKNESS + Math.round((MAX_EDGE_THICKNESS - 1) * weight); String color = EDGE_AVG_COLOR; double iPL = intersectionPoint(prepath, drawableNode.getNodeShape()); // Point p1 = path.getPointAtLength(iPL - 1.7 * width); // Point p2 = path.getPointAtLength(iPL + 1.3 * width); // String moveToArrow = "M" + Math.round(p1.getX()) + "," + Math.round(p1.getY()); // String lineTo = "L" + Math.round(p2.getX()) + "," + Math.round(p2.getY()); // // path.attr("stroke", color).attr("stroke-width", width); // arrowPath = paper.path(moveToArrow + lineTo); // arrowPath.attr("stroke", color).attr("stroke-width", Math.max(3, width)).attr("arrow-end", "open-narrow-short") // .toBack(); prepath.hide(); path = paper.path(prepath.getSubpath(0, (int)iPL)); path.attr("stroke", color).attr("stroke-width", width).attr("arrow-end", "open-midium-midium").toBack(); initListeners(); return true; } private void initListeners() { int l = Math.max(NODE_HEIGHT, NODE_WIDTH); Shape hoverPath = paper.path(path.getSubpath(l, (int)(path.getTotalLength()))); hoverPath.attr("stroke-width", 40).attr("opacity", 0); hoverPath.hover(new LinkHoverListener(path)); hoverPath.click(getPathClickListener()); } private Path getPath(Paper paper, long from, long to, Map<Long, DrawableNode> drawables, Map<Long, Integer> levels) { long x1 = drawables.get(from).getX(); long y1 = drawables.get(from).getY(); long x2 = drawables.get(to).getX(); long y2 = drawables.get(to).getY(); int HORIZONTAL_CURVE = EDGE_CURVE_STRENGTH; int VERTICAL_CURVE = EDGE_CURVE_STRENGTH; if (y1 == y2 || x1 == x2) { // a straight line Integer level1 = levels.get(from); Integer level2 = levels.get(to); if (level1 < level2 - 1 || level1 > level2 + 1) { // horizontal line AND one or more elements in between int l1 = level1 < level2 ? level1 : level2; int l2 = l1 == level1 ? level2 : level1; for (long n : levels.keySet()) { if (levels.get(n) > l1 && levels.get(n) < l2) { if (drawables.get(n).getY() == y1) HORIZONTAL_CURVE = 4; } } } else if (level1 == level2) { // vertical line for (long n : levels.keySet()) { long x = drawables.get(n).getX(); long y = drawables.get(n).getY(); if (x == x1 && ((y > y1 && y < y2) || (y > y2 && y < y1))) { VERTICAL_CURVE = 4; } } } } String moveto = "M" + x1 + "," + y1; String curve = "R" + ((x2 + x1) / 2 + (Math.random() - 0.5) * (x2 + x1) / VERTICAL_CURVE) + "," + ((y2 + y1) / 2 + (Math.random() - 0.5) * (y2 + y1) / HORIZONTAL_CURVE) + "," + x2 + "," + y2; final Path path = paper.path(moveto + curve); path.toBack(); return path; } private double intersectionPoint(Path path, Shape ellipse) { int l = Math.min(DYNAMIC_NODE_HEIGHT, DYNAMIC_NODE_WIDTH); Point p = path.getPointAtLength(path.getTotalLength() - l); while (ellipse.isPointInside(p.getX(), p.getY()) && l < path.getTotalLength() / 2) { p = path.getPointAtLength(path.getTotalLength() - l++); } return path.getTotalLength() - l; } private class LinkHoverListener implements HoverListener { private Set set; private Path path; private Glow g = new Glow(6, false, 0.8, 0, 0, NODE_STROKE_COLOR); public LinkHoverListener(Path p) { this.path = p; } @Override public void hoverOut(NativeEvent e) { if (set != null) { set.remove(); set = null; } } @Override public void hoverIn(NativeEvent e) { if (set == null && !ANIMATION_BUSY){ set = path.glow(g); } } } private MouseEventListener getPathClickListener() { return new MouseEventListener() { @Override public void notifyMouseEvent(NativeEvent e) { // lazy creation & update hide other toolboxes if necessary if (toolbox == null) toolbox = new ConnectionTooltip(DrawableConnection.this); if (!allowMultipleToolboxes && !openToolboxes.isEmpty()) { for (ConnectionTooltip box : openToolboxes) box.hide(); openToolboxes.clear(); } openToolboxes.add(toolbox); // show actual toolbox toolbox.show(); } }; } public long getX() { Point midP = path.getPointAtLength(path.getTotalLength()/2); return Math.round(midP.getX()); } public long getY() { Point midP = path.getPointAtLength(path.getTotalLength()/2); return Math.round(midP.getY()); } private class ConnectionTooltip { private DrawableConnection underlyingConnection; private static final int FADETIME = 500; private Rect box; private Rect closeButton; private Text description; public ConnectionTooltip(DrawableConnection conn) { underlyingConnection = conn; } public void show() { openToolboxes.add(this); if (box == null) { createBox(); initListeners(); } box.animate(Raphael.animation(Attrs.create().transform("s1"), FADETIME, "backOut")); closeButton.animate(Raphael.animation(Attrs.create().transform("t0,0s1"), FADETIME, "bounce")); description.show(); description.animate(Raphael.animation(Attrs.create().opacity(1), FADETIME, "linear")); } private void createBox() { //big box createOuterBox(); //close button createCloseButton(); // inner text createText(); } private void createText() { description = paper.text((int)(box.getBBox(true).getX() + 10), (int)(box.getBBox(true).getY() + box.getBBox(true).getHeight()/2), getDescription()); description.attr("font-size", 10).attr("text-anchor", "start"); description.attr("opacity", 0).attr("fill", "#222").attr("font-family", "Titillium Web, Lucida Sans Unicode, Lucida Grande, sans-serif"); } private String getDescription() { return "Used " + timesused + " times"; } private void createCloseButton() { int closeButtonSize = 15; closeButton = paper.rect(box.getBBox(true).getX() + box.getBBox(true).getWidth() - closeButtonSize, box .getBBox(true).getY(), closeButtonSize, closeButtonSize, 2); closeButton.attr("fill", "90-#f00-#b22").attr("stroke-width", "1").transform("t-40,20s0"); } private void createOuterBox() { box = paper.rect(underlyingConnection.getX() - CONNECTION_TOOLTIP_WIDTH/2, underlyingConnection.getY() - CONNECTION_TOOLTIP_HEIGHT/2, CONNECTION_TOOLTIP_WIDTH, CONNECTION_TOOLTIP_HEIGHT , 5); box.attr("fill", "135-" + CONNECTION_TOOLTIP_COLOR_1 + "-" + CONNECTION_TOOLTIP_COLOR_2).transform("s0"); } public void hide() { description.animate(Raphael.animation(Attrs.create().opacity(0), FADETIME*1/2, ">", new Callback() { public void call(Shape src) { src.hide(); } })); box.animate(Raphael.animation(Attrs.create().transform("s0"), FADETIME * 2 / 3, "backIn")); description.animate(Raphael.animation(Attrs.create().opacity(0), FADETIME * 2/3, "linear")); closeButton.animate(Raphael.animation(Attrs.create().transform("t-40,20s0"), FADETIME * 2 / 3, "backIn")); openToolboxes.remove(this); } private void initListeners() { closeButton.hover(new HoverListener() { private Set g; @Override public void hoverOut(NativeEvent e) { if (g != null) g.remove(); g = null; } @Override public void hoverIn(NativeEvent e) { g = closeButton.glow(new Glow(5, false, 0.7, 0, 0, "#f22")); } }); closeButton.click(new MouseEventListener() { @Override public void notifyMouseEvent(NativeEvent e) { ConnectionTooltip.this.hide(); } }); } } }