package de.tud.kom.socom.web.client.drawables; import static de.tud.kom.socom.web.client.graphview.GlobalGraphSettings.*; import java.util.HashSet; 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.event.Callback; import org.sgx.raphael4gwt.raphael.event.HoverListener; import org.sgx.raphael4gwt.raphael.event.MouseEventListener; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.json.client.JSONObject; import com.google.gwt.user.client.Timer; import de.tud.kom.socom.web.client.GraphPanel; public class DrawableNode { private Paper paper; private JSONObject node; private Shape nodeShape; private Text text; private NodeTooltip toolbox; private static java.util.Set<NodeTooltip> openToolboxes = new HashSet<NodeTooltip>(); private boolean allowMultipleToolboxes = false; public DrawableNode(JSONObject node) { this.node = node; } public void paint(Paper paper, int x, int y, int yTextOffset) { this.paper = paper; // nodeShape = paper.ellipse(x, y, DYNAMIC_NODE_WIDTH, DYNAMIC_NODE_HEIGHT); nodeShape = paper.rect(x-DYNAMIC_NODE_WIDTH, y-DYNAMIC_NODE_HEIGHT, DYNAMIC_NODE_WIDTH*2, DYNAMIC_NODE_HEIGHT*2, DYNAMIC_NODE_HEIGHT); String nodeName = node.get("name").isString().stringValue(); nodeName = nodeName.length() < 12 ? nodeName : nodeName.substring(0, 10) + ".."; text = paper.text(x, y + yTextOffset, nodeName); applyTheme(); initListeners(); } private void initListeners() { MouseEventListener mel = getNodeClickListener(); nodeShape.click(mel); text.click(mel); HoverListener hl = getNodeHoverListener(); nodeShape.hover(hl); text.hover(hl); } private MouseEventListener getNodeClickListener() { return new MouseEventListener() { @Override public void notifyMouseEvent(NativeEvent e) { // lazy creation & update hide other toolboxes if necessary if (toolbox == null) toolbox = new NodeTooltip(DrawableNode.this); if (!allowMultipleToolboxes && !openToolboxes.isEmpty()) { for (NodeTooltip box : openToolboxes) box.hide(); openToolboxes.clear(); } openToolboxes.add(toolbox); // show actual toolbox toolbox.show(); } }; } private HoverListener getNodeHoverListener() { HoverListener hoverL = new HoverListener() { Set r; Set g; boolean stillinside; @Override public void hoverOut(NativeEvent e) { if (g != null){ g.remove(); } if(r != null) { new Timer() { public void run() { if(!stillinside) r.hide(); } }.schedule(100); } g = null; stillinside = false; } @Override public void hoverIn(NativeEvent e) { stillinside = true; g = nodeShape.glow(new Glow(10, false, 1, 0, 0, NODE_COLOR)); if(r==null) { final Rect rect = paper.rect(DrawableNode.this.getLeftX(), DrawableNode.this.getTopY(), 15, 15, 2); rect.attr("fill", NODE_COLOR).attr("stroke", NODE_STROKE_COLOR); Path symbol = paper.path("M29.772,26.433l-7.126-7.126c0.96-1.583,1.523-3.435,1.524-5.421C24.169,8.093,19.478,3.401,13.688,3.399C7.897,3.401,3.204,8.093,3.204,13.885c0,5.789,4.693,10.481,10.484,10.481c1.987,0,3.839-0.563,5.422-1.523l7.128,7.127L29.772,26.433zM7.203,13.885c0.006-3.582,2.903-6.478,6.484-6.486c3.579,0.008,6.478,2.904,6.484,6.486c-0.007,3.58-2.905,6.476-6.484,6.484C10.106,20.361,7.209,17.465,7.203,13.885z"); symbol.transform("s0.4T"+(rect.getBBox().getX()-rect.getBBox().getWidth()/2 -1)+","+(rect.getBBox().getY()-rect.getBBox().getHeight()/2 -1)); symbol.attr("fill", NODE_TEXT_COLOR).attr("stroke", NODE_TEXT_COLOR).attr("stroke-width",1); r = Raphael.set(paper, rect,symbol); r.click(new MouseEventListener() { public void notifyMouseEvent(NativeEvent e) { r.hide(); GraphPanel.get(-1,-1).showSubGraph((long)node.get("id").isNumber().doubleValue()); } }); r.hover(new HoverListener() { Set subgraphboxglow; public void hoverOut(NativeEvent e) { stillinside = false; new Timer() { public void run() { if(!stillinside) r.hide(); } }.schedule(100); if (subgraphboxglow != null){ subgraphboxglow.remove(); } } @Override public void hoverIn(NativeEvent e) { stillinside = true; subgraphboxglow = rect.glow(new Glow(5, false, 1, 0, 0, NODE_COLOR)); r.add(subgraphboxglow); } }); } else r.show(); } }; return hoverL; } private void applyTheme() { double timeSpentAvg = node.get("timeSpentAvg").isNumber().doubleValue(); double normalized = (timeSpentAvg - TIME_SPENT_AVG_MIN) / (TIME_SPENT_AVG_MAX - TIME_SPENT_AVG_MIN); //normalized := 0...1 double width = 1+5*normalized; nodeShape.attr("fill", NODE_COLOR).attr("stroke", NODE_STROKE_COLOR).attr("stroke-width", width); text.attr("fill", NODE_TEXT_COLOR).attr("font-size", DYNAMIC_NODE_TEXT_SIZE); } public long getLeftX() { return Math.round(nodeShape.getBBox(true).getX()); } public long getRightX() { return Math.round(nodeShape.getBBox(true).getX() + nodeShape.getBBox(true).getWidth()); } public long getTopY() { return Math.round(nodeShape.getBBox(true).getY()); } public long getBottomY() { return Math.round(nodeShape.getBBox(true).getY() + nodeShape.getBBox(true).getHeight()); } public long getX() { return Math.round(nodeShape.getBBox(true).getX() + nodeShape.getBBox(true).getWidth() / 2); } public long getY() { return Math.round(nodeShape.getBBox(true).getY() + nodeShape.getBBox(true).getHeight() / 2); } public Shape getNodeShape() { return nodeShape; } public void bringToFront() { nodeShape.toFront(); text.toFront(); } public void allowMultipleToolboxes(boolean allow) { this.allowMultipleToolboxes = allow; } private class NodeTooltip { private DrawableNode underlyingNode; private static final int FADETIME = 500; private Rect box; private Rect closeButton; private Set textSet; private Text header; private Path headerUnderline; private Text description; public NodeTooltip(DrawableNode node) { underlyingNode = node; } public void show() { openToolboxes.add(this); if (box == null) { createBox(); initListeners(); } double factor = 1/CURRENT_ZOOM; box.animate(Raphael.animation(Attrs.create().transform("s1"), FADETIME, "backOut")); closeButton.animate(Raphael.animation(Attrs.create().transform("t0,0s1"), FADETIME, "bounce")); textSet.show().animate(Raphael.animation(Attrs.create().opacity(1), FADETIME, "<")); } public void hide() { textSet.animate(Raphael.animation(Attrs.create().opacity(0), FADETIME*1/2, ">", new Callback() { public void call(Shape src) { textSet.hide(); } })); box.animate(Raphael.animation(Attrs.create().transform("s0"), FADETIME * 2 / 3, "backIn")); closeButton.animate(Raphael.animation(Attrs.create().transform("t-80,80s0"), FADETIME * 2 / 3, "backIn")); openToolboxes.remove(this); } private void createBox() { //big box createOuterBox(); //close button createCloseButton(); // inner text createText(); } private void createText() { header = paper.text((int)(box.getBBox(true).getX()), (int)(box.getBBox(true).getY()), getHeader()); header.attr("font-size", 14).transform("t"+ (header.getBBox().getWidth()/2) + "," + (header.getBBox().getHeight())); headerUnderline = paper.path("M" + (box.getBBox(true).getX() + 7) + "," + (header.getBBox().getY() + header.getBBox(true).getHeight() + 2) + "L" + (box.getBBox(true).getX() + box.getBBox(true).getWidth()*6/7) + "," + (header.getBBox().getY() + header.getBBox(true).getHeight() + 2)); headerUnderline.attr("stroke", "#ddd").attr("stroke-width", 2); description = paper.text((int) header.getBBox().getX()+5, 0, getDescription(node)); description.attr("font-size", 11).attr("text-anchor", "start"). transform("t0," +(int)(header.getBBox().getY() + header.getBBox().getHeight() + description.getBBox().getHeight()/2 + 5)); textSet = Raphael.set(paper, header, headerUnderline, description); textSet.attr("opacity", 0).attr("fill", "#ddd").attr("font-family", "Titillium Web, Lucida Sans Unicode, Lucida Grande, sans-serif"); } private String getHeader() { String name = node.get("name").isString().stringValue(); name = name.length() < 18 ? name : name.substring(0,16) + ".."; String id = node.get("externalid").isString().stringValue(); return name + " (ID: " + id + ")"; } private String getDescription(JSONObject node) { return "Visited by users: " + (int)node.get("usersSeen").isNumber().doubleValue() + "\nTime spent (total): " + (int)(node.get("timeSpentTotal").isNumber().doubleValue()/60) + " min\nTime spent (average): " + (int)(node.get("timeSpentAvg").isNumber().doubleValue()/60) + " min/User\nContents: " + (int)node.get("contentCount").isNumber().doubleValue() + "\nInfluences: " + (int)node.get("influenceCount").isNumber().doubleValue(); } private void createCloseButton() { int closeButtonSize = 17; 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-80,80s0"); } private void createOuterBox() { box = paper.rect(underlyingNode.getX() - NODE_TOOLTIP_WIDTH/2, underlyingNode.getY() - NODE_TOOLTIP_HEIGHT/2, NODE_TOOLTIP_WIDTH, NODE_TOOLTIP_HEIGHT , 5); box.attr("fill", "135-" + NODE_COLOR + "-" + NODE_STROKE_COLOR).transform("s0"); } 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) { if(ANIMATION_BUSY) return; g = closeButton.glow(new Glow(5, false, 0.7, 0, 0, "#f22")); } }); closeButton.click(new MouseEventListener() { @Override public void notifyMouseEvent(NativeEvent e) { NodeTooltip.this.hide(); } }); } } }