From eb1bb87dd15c85ffcf536a90f87aa8f2e5f2963e Mon Sep 17 00:00:00 2001 From: Moritz Krause Date: Fri, 7 Feb 2020 00:46:34 +0100 Subject: [PATCH] feat(#20): add feature to zoom/pan to position/node --- src/builder.js | 38 ++++++++++++++++++++++++-------------- src/dtree.js | 31 +++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/builder.js b/src/builder.js index 9037046..59e424a 100644 --- a/src/builder.js +++ b/src/builder.js @@ -27,20 +27,24 @@ class TreeBuilder { let width = opts.width + opts.margin.left + opts.margin.right; let height = opts.height + opts.margin.top + opts.margin.bottom; - let zoom = d3.zoom() + // create zoom handler + const zoom = this.zoom = d3.zoom() .scaleExtent([0.1, 10]) - .on('zoom', function() { - svg.attr('transform', d3.event.transform.translate(width / 2, opts.margin.top)); - }); + .on('zoom', function () { + g.attr('transform', d3.event.transform) + }) - //make an SVG - let svg = this.svg = d3.select(opts.target) + // make a svg + const svg = this.svg = d3.select(opts.target) .append('svg') - .attr('width', width) - .attr('height', height) + .attr('viewBox', [0, 0, width, height]) .call(zoom) - .append('g') - .attr('transform', 'translate(' + width / 2 + ',' + opts.margin.top + ')'); + + // create svg group that holds all nodes + const g = this.g = svg.append('g') + + // set zoom identity + svg.call(zoom.transform, d3.zoomIdentity.translate(width / 2, opts.margin.top).scale(1)) // Compute the layout. this.tree = d3.tree() @@ -69,7 +73,7 @@ class TreeBuilder { let links = treenodes.links(); // Create the link lines. - this.svg.selectAll('.link') + this.g.selectAll('.link') .data(links) .enter() // filter links with no parents to prevent empty nodes @@ -80,14 +84,14 @@ class TreeBuilder { .attr('class', opts.styles.linage) .attr('d', this._elbow); - let nodes = this.svg.selectAll('.node') + let nodes = this.g.selectAll('.node') .data(treenodes.descendants()) .enter(); this._linkSiblings(); // Draw siblings (marriage) - this.svg.selectAll('.sibling') + this.g.selectAll('.sibling') .data(this.siblings) .enter() .append('path') @@ -127,8 +131,14 @@ class TreeBuilder { d.data.textClass, opts.callbacks.textRenderer); }) + .on('dblclick', function () { + // do not propagate a double click on a node + // to prevent the zoom from being triggered + d3.event.stopPropagation() + }) .on('click', function(d)  { - if (d.data.hidden) { + // ignore double-clicks and clicks on hidden nodes + if (d3.event.detail === 2 || d.data.hidden) { return; } opts.callbacks.nodeClick(d.data.name, d.data.extra, d.data.id); diff --git a/src/dtree.js b/src/dtree.js index 951eb77..1f06a9c 100644 --- a/src/dtree.js +++ b/src/dtree.js @@ -48,6 +48,37 @@ const dTree = { var treeBuilder = new TreeBuilder(data.root, data.siblings, opts); treeBuilder.create(); + function _zoomTo (x, y, zoom = 1, duration = 500) { + treeBuilder.svg + .transition() + .duration(duration) + .call( + treeBuilder.zoom.transform, + d3.zoomIdentity + .translate(opts.width / 2, opts.height / 2) + .scale(zoom) + .translate(-x, -y) + ) + } + + return { + resetZoom: function (duration = 500) { + treeBuilder.svg + .transition() + .duration(duration) + .call( + treeBuilder.zoom.transform, + d3.zoomIdentity.translate(opts.width / 2, opts.margin.top).scale(1) + ) + }, + zoomTo: _zoomTo, + zoomToNode: function (nodeId, zoom = 2, duration = 500) { + const node = _.find(treeBuilder.allNodes, {data: {id: nodeId}}) + if (node) { + _zoomTo(node.x, node.y, zoom, duration) + } + } + } }, _preprocess: function(data, opts) {