summaryrefslogtreecommitdiff
path: root/gnowsys-ndf/gnowsys_ndf/ndf/templates/ndf/theme_drag_zoom_d3tree.html
diff options
context:
space:
mode:
authorKedar Aitawdekar <kedar2a@gmail.com>2015-01-10 15:08:57 +0530
committerKedar Aitawdekar <kedar2a@gmail.com>2015-01-10 15:08:57 +0530
commitfd9b62e667e295d8b59c470e7690eec28c4b3530 (patch)
treeed9c8c2f7061961626c87f2a69d15ca9d5b25c70 /gnowsys-ndf/gnowsys_ndf/ndf/templates/ndf/theme_drag_zoom_d3tree.html
parent7af39ab6a778c23886f346a0b84ccc778a5237d6 (diff)
downloadgnowsys-fd9b62e667e295d8b59c470e7690eec28c4b3530.tar.gz
New template added for theme-tree with drag and zoom functionality
Diffstat (limited to 'gnowsys-ndf/gnowsys_ndf/ndf/templates/ndf/theme_drag_zoom_d3tree.html')
-rw-r--r--gnowsys-ndf/gnowsys_ndf/ndf/templates/ndf/theme_drag_zoom_d3tree.html541
1 files changed, 541 insertions, 0 deletions
diff --git a/gnowsys-ndf/gnowsys_ndf/ndf/templates/ndf/theme_drag_zoom_d3tree.html b/gnowsys-ndf/gnowsys_ndf/ndf/templates/ndf/theme_drag_zoom_d3tree.html
new file mode 100644
index 00000000..847bb78b
--- /dev/null
+++ b/gnowsys-ndf/gnowsys_ndf/ndf/templates/ndf/theme_drag_zoom_d3tree.html
@@ -0,0 +1,541 @@
+<div id="theme-drag-zoom-container" class="hide">
+
+ <a href="#" data-reveal-id="theme-drag-zoom-tree">Show graph with zoom and drag functionality</a>
+
+ <div id="theme-drag-zoom-tree" class="reveal-modal xlarge" data-reveal>
+ <a class="close-reveal-modal">&#215;</a>
+ </div>
+
+</div>
+
+<script type="text/javascript">
+
+ // treeData = ""
+ // $.getJSON("{% url 'get_tree_hierarchy' group_id node.pk %}?collapsible=true", function(data){ treeData = data;});
+
+ // d3.json("{% url 'get_tree_hierarchy' group_id node.pk %}?collapsible=true", function(error, treeData) {
+
+ function plotDragZoomTree(treeData) {
+
+ $("#theme-drag-zoom-tree").html("");
+
+ // Calculate total nodes, max label length
+ var totalNodes = 0;
+ var maxLabelLength = 0;
+
+ // variables for drag/drop
+ var selectedNode = null;
+ var draggingNode = null;
+
+ // panning variables
+ var panSpeed = 200;
+ var panBoundary = 20; // Within 20px from edges will pan when dragging.
+
+ // Misc. variables
+ var i = 0;
+ var duration = 750;
+ var root;
+
+ // size of the diagram
+ var viewerHeight = $(window).height() * 0.80;
+ var viewerWidth = $(document).width() * 0.90;
+
+
+ var tree = d3.layout.tree()
+ .size([viewerHeight, viewerWidth]);
+
+ // define a d3 diagonal projection for use by the node paths later on.
+ var diagonal = d3.svg.diagonal()
+ .projection(function(d) {
+ return [d.y, d.x];
+ });
+
+ // A recursive helper function for performing some setup by walking through all nodes
+ function visit(parent, visitFn, childrenFn) {
+ if (!parent) return;
+
+ visitFn(parent);
+
+ var children = childrenFn(parent);
+ if (children) {
+ var count = children.length;
+ for (var i = 0; i < count; i++) {
+ visit(children[i], visitFn, childrenFn);
+ }
+ }
+ }
+
+ // Call visit function to establish maxLabelLength
+ visit(treeData, function(d) {
+ totalNodes++;
+ maxLabelLength = Math.max(d.name.length, maxLabelLength);
+
+ }, function(d) {
+ return d.children && d.children.length > 0 ? d.children : null;
+ });
+
+ // sort the tree according to the node names
+ function sortTree() {
+ tree.sort(function(a, b) {
+ return b.name.toLowerCase() < a.name.toLowerCase() ? 1 : -1;
+ });
+ }
+
+ // Sort the tree initially incase the JSON isn't in a sorted order.
+ sortTree();
+
+ // TODO: Pan function, can be better implemented.
+ function pan(domNode, direction) {
+ var speed = panSpeed;
+ if (panTimer) {
+ clearTimeout(panTimer);
+ translateCoords = d3.transform(svgGroup.attr("transform"));
+ if (direction == 'left' || direction == 'right') {
+ translateX = direction == 'left' ? translateCoords.translate[0] + speed : translateCoords.translate[0] - speed;
+ translateY = translateCoords.translate[1];
+ } else if (direction == 'up' || direction == 'down') {
+ translateX = translateCoords.translate[0];
+ translateY = direction == 'up' ? translateCoords.translate[1] + speed : translateCoords.translate[1] - speed;
+ }
+ scaleX = translateCoords.scale[0];
+ scaleY = translateCoords.scale[1];
+ scale = zoomListener.scale();
+ svgGroup.transition().attr("transform", "translate(" + translateX + "," + translateY + ")scale(" + scale + ")");
+ d3.select(domNode).select('g.node').attr("transform", "translate(" + translateX + "," + translateY + ")");
+ zoomListener.scale(zoomListener.scale());
+ zoomListener.translate([translateX, translateY]);
+ panTimer = setTimeout(function() {
+ pan(domNode, speed, direction);
+ }, 50);
+ }
+ }
+
+ // Define the zoom function for the zoomable tree
+ function zoom() {
+ svgGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
+ }
+
+ // define the zoomListener which calls the zoom function on the "zoom" event constrained within the scaleExtents
+ var zoomListener = d3.behavior.zoom().scaleExtent([0.1, 3]).on("zoom", zoom);
+
+ // function initiateDrag(d, domNode) {
+ // draggingNode = d;
+ // d3.select(domNode).select('.ghostCircle').attr('pointer-events', 'none');
+ // d3.selectAll('.ghostCircle').attr('class', 'ghostCircle show');
+ // d3.select(domNode).attr('class', 'node activeDrag');
+
+ // svgGroup.selectAll("g.node").sort(function(a, b) { // select the parent and sort the path's
+ // if (a.id != draggingNode.id) return 1; // a is not the hovered element, send "a" to the back
+ // else return -1; // a is the hovered element, bring "a" to the front
+ // });
+ // // if nodes has children, remove the links and nodes
+ // if (nodes.length > 1) {
+ // // remove link paths
+ // links = tree.links(nodes);
+ // nodePaths = svgGroup.selectAll("path.link")
+ // .data(links, function(d) {
+ // return d.target.id;
+ // }).remove();
+ // // remove child nodes
+ // nodesExit = svgGroup.selectAll("g.node")
+ // .data(nodes, function(d) {
+ // return d.id;
+ // }).filter(function(d, i) {
+ // if (d.id == draggingNode.id) {
+ // return false;
+ // }
+ // return true;
+ // }).remove();
+ // }
+
+ // // remove parent link
+ // parentLink = tree.links(tree.nodes(draggingNode.parent));
+ // svgGroup.selectAll('path.link').filter(function(d, i) {
+ // if (d.target.id == draggingNode.id) {
+ // return true;
+ // }
+ // return false;
+ // }).remove();
+
+ // dragStarted = null;
+ // }
+
+ // define the baseSvg, attaching a class for styling and the zoomListener
+ var baseSvg = d3.select("#theme-drag-zoom-tree").append("svg")
+ .attr("width", viewerWidth)
+ .attr("height", viewerHeight)
+ .attr("class", "overlay")
+ .call(zoomListener);
+
+ // // Define the drag listeners for drag/drop behaviour of nodes.
+ // dragListener = d3.behavior.drag()
+ // .on("dragstart", function(d) {
+ // if (d == root) {
+ // return;
+ // }
+ // dragStarted = true;
+ // nodes = tree.nodes(d);
+ // d3.event.sourceEvent.stopPropagation();
+ // // it's important that we suppress the mouseover event on the node being dragged. Otherwise it will absorb the mouseover event and the underlying node will not detect it d3.select(this).attr('pointer-events', 'none');
+ // })
+ // .on("drag", function(d) {
+ // if (d == root) {
+ // return;
+ // }
+ // if (dragStarted) {
+ // domNode = this;
+ // initiateDrag(d, domNode);
+ // }
+
+ // // get coords of mouseEvent relative to svg container to allow for panning
+ // relCoords = d3.mouse($('svg').get(0));
+ // if (relCoords[0] < panBoundary) {
+ // panTimer = true;
+ // pan(this, 'left');
+ // } else if (relCoords[0] > ($('svg').width() - panBoundary)) {
+
+ // panTimer = true;
+ // pan(this, 'right');
+ // } else if (relCoords[1] < panBoundary) {
+ // panTimer = true;
+ // pan(this, 'up');
+ // } else if (relCoords[1] > ($('svg').height() - panBoundary)) {
+ // panTimer = true;
+ // pan(this, 'down');
+ // } else {
+ // try {
+ // clearTimeout(panTimer);
+ // } catch (e) {
+
+ // }
+ // }
+
+ // d.x0 += d3.event.dy;
+ // d.y0 += d3.event.dx;
+ // var node = d3.select(this);
+ // node.attr("transform", "translate(" + d.y0 + "," + d.x0 + ")");
+ // // updateTempConnector();
+ // })
+ // .on("dragend", function(d) {
+ // if (d == root) {
+ // return;
+ // }
+ // domNode = this;
+ // if (selectedNode) {
+ // // now remove the element from the parent, and insert it into the new elements children
+ // var index = draggingNode.parent.children.indexOf(draggingNode);
+ // if (index > -1) {
+ // draggingNode.parent.children.splice(index, 1);
+ // }
+ // if (typeof selectedNode.children !== 'undefined' || typeof selectedNode._children !== 'undefined') {
+ // if (typeof selectedNode.children !== 'undefined') {
+ // selectedNode.children.push(draggingNode);
+ // } else {
+ // selectedNode._children.push(draggingNode);
+ // }
+ // } else {
+ // selectedNode.children = [];
+ // selectedNode.children.push(draggingNode);
+ // }
+ // // Make sure that the node being added to is expanded so user can see added node is correctly moved
+ // expand(selectedNode);
+ // sortTree();
+ // endDrag();
+ // } else {
+ // endDrag();
+ // }
+ // });
+
+ // function endDrag() {
+ // selectedNode = null;
+ // d3.selectAll('.ghostCircle').attr('class', 'ghostCircle');
+ // d3.select(domNode).attr('class', 'node');
+ // // now restore the mouseover event or we won't be able to drag a 2nd time
+ // d3.select(domNode).select('.ghostCircle').attr('pointer-events', '');
+ // updateTempConnector();
+ // if (draggingNode !== null) {
+ // update(root);
+ // centerNode(draggingNode);
+ // draggingNode = null;
+ // }
+ // }
+
+ // Helper functions for collapsing and expanding nodes.
+
+ function collapse(d) {
+ if (d.children) {
+ d._children = d.children;
+ d._children.forEach(collapse);
+ d.children = null;
+ }
+ }
+
+ function expand(d) {
+ if (d._children) {
+ d.children = d._children;
+ d.children.forEach(expand);
+ d._children = null;
+ }
+ }
+
+ var overCircle = function(d) {
+ selectedNode = d;
+ updateTempConnector();
+ };
+ var outCircle = function(d) {
+ selectedNode = null;
+ updateTempConnector();
+ };
+
+ // Function to update the temporary connector indicating dragging affiliation
+ var updateTempConnector = function() {
+ var data = [];
+ if (draggingNode !== null && selectedNode !== null) {
+ // have to flip the source coordinates since we did this for the existing connectors on the original tree
+ data = [{
+ source: {
+ x: selectedNode.y0,
+ y: selectedNode.x0
+ },
+ target: {
+ x: draggingNode.y0,
+ y: draggingNode.x0
+ }
+ }];
+ }
+
+ var link = svgGroup.selectAll(".templink").data(data);
+
+ link.enter().append("path")
+ .attr("class", "templink")
+ .attr("d", d3.svg.diagonal())
+ .attr('pointer-events', 'none');
+
+ link.attr("d", d3.svg.diagonal());
+
+ link.exit().remove();
+ };
+
+ // Function to center node when clicked/dropped so node doesn't get lost when collapsing/moving with large amount of children.
+
+ function centerNode(source) {
+ scale = zoomListener.scale();
+ x = -source.y0;
+ y = -source.x0;
+ x = x * scale + viewerWidth / 2;
+ y = y * scale + viewerHeight / 2;
+ d3.select('#theme-drag-zoom-tree g').transition()
+ .duration(duration)
+ .attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
+ zoomListener.scale(scale);
+ zoomListener.translate([x, y]);
+ }
+
+ // Toggle children function
+ function toggleChildren(d) {
+ if (d.children) {
+ d._children = d.children;
+ d.children = null;
+ } else if (d._children) {
+ d.children = d._children;
+ d._children = null;
+ }
+ return d;
+ }
+
+ // Toggle children on click.
+
+ function click(d) {
+ if (d3.event.defaultPrevented) return; // click suppressed
+ d = toggleChildren(d);
+ update(d);
+ centerNode(d);
+ }
+
+ function update(source) {
+ // Compute the new height, function counts total children of root node and sets tree height accordingly.
+ // This prevents the layout looking squashed when new nodes are made visible or looking sparse when nodes are removed
+ // This makes the layout more consistent.
+ var levelWidth = [1];
+ var childCount = function(level, n) {
+
+ if (n.children && n.children.length > 0) {
+ if (levelWidth.length <= level + 1) levelWidth.push(0);
+
+ levelWidth[level + 1] += n.children.length;
+ n.children.forEach(function(d) {
+ childCount(level + 1, d);
+ });
+ }
+ };
+ childCount(0, root);
+ var newHeight = d3.max(levelWidth) * 25; // 25 pixels per line
+ tree = tree.size([newHeight, viewerWidth]);
+
+ // Compute the new tree layout.
+ var nodes = tree.nodes(root).reverse(),
+ links = tree.links(nodes);
+
+ // Set widths between levels based on maxLabelLength.
+ nodes.forEach(function(d) {
+ d.y = (d.depth * (maxLabelLength * 5)); //maxLabelLength * 10px
+ // alternatively to keep a fixed scale one can set a fixed depth per level
+ // Normalize for fixed-depth by commenting out below line
+ // d.y = (d.depth * 500); //500px per level.
+ });
+
+ // Update the nodes…
+ node = svgGroup.selectAll("#theme-drag-zoom-tree g.node")
+ .data(nodes, function(d) {
+ return d.id || (d.id = ++i);
+ });
+
+ // Enter any new nodes at the parent's previous position.
+ var nodeEnter = node.enter().append("g")
+ // .call(dragListener)
+ .attr("class", "node")
+ .attr("transform", function(d) {
+ return "translate(" + source.y0 + "," + source.x0 + ")";
+ })
+ .on('click', click);
+
+ nodeEnter.append("circle")
+ .attr('class', 'nodeCircle')
+ .attr("r", 0)
+ .style("fill", function(d) {
+ return d._children ? "lightsteelblue" : "#fff";
+ });
+
+ nodeEnter.append("text")
+ .attr("x", function(d) {
+ return d.children || d._children ? -10 : 10;
+ })
+ .attr("dy", ".35em")
+ .attr('class', 'nodeText')
+ .attr("text-anchor", function(d) {
+ return d.children || d._children ? "end" : "start";
+ })
+ .text(function(d) {
+ return d.name;
+ })
+ .style("fill-opacity", 0);
+
+ // phantom node to give us mouseover in a radius around it
+ nodeEnter.append("circle")
+ .attr('class', 'ghostCircle')
+ .attr("r", 30)
+ .attr("opacity", 0.2) // change this to zero to hide the target area
+ .style("fill", "red")
+ .attr('pointer-events', 'mouseover')
+ .on("mouseover", function(node) {
+ overCircle(node);
+ })
+ .on("mouseout", function(node) {
+ outCircle(node);
+ });
+
+ // Update the text to reflect whether node has children or not.
+ node.select('text')
+ .attr("x", function(d) {
+ return d.children || d._children ? -10 : 10;
+ })
+ .attr("text-anchor", function(d) {
+ return d.children || d._children ? "end" : "start";
+ })
+ .text(function(d) {
+ return d.name;
+ });
+
+ // Change the circle fill depending on whether it has children and is collapsed
+ node.select("circle.nodeCircle")
+ .attr("r", 4.5)
+ .style("fill", function(d) {
+ return d._children ? "lightsteelblue" : "#fff";
+ });
+
+ // Transition nodes to their new position.
+ var nodeUpdate = node.transition()
+ .duration(duration)
+ .attr("transform", function(d) {
+ return "translate(" + d.y + "," + d.x + ")";
+ });
+
+ // Fade the text in
+ nodeUpdate.select("text")
+ .style("fill-opacity", 1);
+
+ // Transition exiting nodes to the parent's new position.
+ var nodeExit = node.exit().transition()
+ .duration(duration)
+ .attr("transform", function(d) {
+ return "translate(" + source.y + "," + source.x + ")";
+ })
+ .remove();
+
+ nodeExit.select("circle")
+ .attr("r", 0);
+
+ nodeExit.select("text")
+ .style("fill-opacity", 0);
+
+ // Update the links…
+ var link = svgGroup.selectAll("path.link")
+ .data(links, function(d) {
+ return d.target.id;
+ });
+
+ // Enter any new links at the parent's previous position.
+ link.enter().insert("path", "g")
+ .attr("class", "link")
+ .attr("d", function(d) {
+ var o = {
+ x: source.x0,
+ y: source.y0
+ };
+ return diagonal({
+ source: o,
+ target: o
+ });
+ });
+
+ // Transition links to their new position.
+ link.transition()
+ .duration(duration)
+ .attr("d", diagonal);
+
+ // Transition exiting nodes to the parent's new position.
+ link.exit().transition()
+ .duration(duration)
+ .attr("d", function(d) {
+ var o = {
+ x: source.x,
+ y: source.y
+ };
+ return diagonal({
+ source: o,
+ target: o
+ });
+ })
+ .remove();
+
+ // Stash the old positions for transition.
+ nodes.forEach(function(d) {
+ d.x0 = d.x;
+ d.y0 = d.y;
+ });
+ }
+
+ // Append a group which holds all nodes and which the zoom Listener can act upon.
+ svgGroup = baseSvg.append("g");
+
+ // Define the root
+ root = treeData;
+ root.x0 = viewerHeight / 2;
+ root.y0 = 0;
+
+ // Layout the tree initially and center on the root node.
+ root.children.forEach(collapse);
+ update(root);
+ centerNode(root);
+}
+</script>