How to pan to a node using d3's force layout - javascript

I would like to focus on a node when it is searched for by name. I am trying to do this using a recenter method....
zoom = d3.behavior.zoom()
.scaleExtent([.05, 10])
.on("zoom", zoomed);
svg = d3.select("#graph")
.append("svg")
.attr("height", height)
.attr("width", width)
.call(zoom);
....
function zoomed(sel) {
zoomBase(d3.event.translate , d3.event.scale);
}
function zoomBase(translate, scale){
zoom.scale(scale);
zoom.translate(translate);
container.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
}
function recenter(node){
var node = findNodeByName(node);
if(zoom.scale() < 0.5){
zoom.scale(0.5);
}
zoom.translate([node.x, node.y]); // Math seems to be wrong here
container.attr("transform", "translate(" + translate + ")scale(" + zoom.scale() + ")");
}
The problem is that when I am over the node in question and search for it my location shows up as [-2246.3690822841745, -846.6411913027562] but when I get the x and y off of the actual node I get [4346.868560310511, 1950.790521658118] considering I am over top of the node, is there some math or something I need here?

Lars was right this is the answer...
zoom.translate([width / 2 - zoom.scale() * node.x, height / 2 - zoom.scale() * node.y])
To break this doesn a bit
width / 2 (go to middle)
-
zoom.scale() * node.x (move middle to the scaled x)

Related

D3.js resize and drag

I'm drawing a tree with D3. When the page is resized I want to move the tree to a specific position (basically on the space left for the svg so the tree stay visible).
I use this code
function resize() {
svg
.attr("width", getWidth())
.attr("height", getHeight());
svg.attr("transform", "translate(" + (window.innerWidth / 3) + "," + ((window.innerHeight / 3)) + ")" + " scale(1)");
var zoom = d3.behavior.zoom();
zoom.translate([window.innerWidth / 3, window.innerHeight / 3]);
}
d3.select(window).on('resize', resize);
This works fine, but when I start clicking to drag the graph, it's moved to the original position as starting drag point.
This is the drag function:
function redraw() {
if (!d3.event.sourceEvent) return;
d3.event.sourceEvent.stopPropagation();
svg.attr("transform",
"translate(" + d3.event.translate + ")" +
" scale(" + d3.event.scale + ")");
}
var svg = d3.select("#graph").append("svg")
.attr("id", "graph")
.attr("width", getWidth())
.attr("height", getHeight())
.call(zm = d3.behavior.zoom().on("zoom", redraw)).on("dblclick.zoom", null)
.append("g")
.attr("transform", "translate(" + initialWidth + "," + initialHeight + ")");
Any hint on how to solve this?
Make zoom a global variable like this:
var zoom = d3.behavior.zoom().on("zoom", redraw);
and use it in resize and zoom for svg as shown below.
Change 1:
var svg = d3.select("#graph").append("svg")
.attr("id", "graph")
.attr("width", getWidth())
.attr("height", getHeight())
.call(zoom).on("dblclick.zoom", null)
.append("g")
.attr("transform", "translate(" + initialWidth + "," + initialHeight + ")")
Change 2:
function resize() {
svg
.attr("width", getWidth())
.attr("height", getHeight());
svg.attr("transform", "translate(" + (window.innerWidth / 3) + "," + ((window.innerHeight / 3)) + ")" + " scale(1)");
zoom.translate([window.innerWidth / 3, window.innerHeight / 3]);
}

D3 collapsible tree - jumpy on zoom

I've already spent too much time trying to figure this out.
My goal is to create d3 collapsible tree but for some reason when you zoom it, it moves the tree on position 0,0. I've already seen a few questions with similar problem such as this one d3.behavior.zoom jitters, shakes, jumps, and bounces when dragging but can't figure it out how to apply it to my situation.
I think this part is making a problem but I'm not sure how to change it to have the proper zooming functionality.
d3.select('g').transition()
.duration(duration)
.attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")")
zoomListener.scale(scale);
Here is my code: https://jsfiddle.net/ramo2600/y79r5dyk/11/
You are translating your zoomable g to position [100,100] but not telling the zoom d3.behavior.zoom() about it. So it starts from [0,0] and you see the "jump".
Modify your centerNode function to:
function centerNode(source) {
scale = zoomListener.scale();
// x = -source.y0;
y = -source.x0;
// x = x * scale + viewerWidth / 2;
x = 100;
y = 100;
// y = y * scale + viewerHeight / 2;
d3.select('g').transition()
.duration(duration)
.attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")")
zoomListener.scale(scale);
zoomListener.translate([x,y]); //<-- tell zoom about position
}

Bars zoom on d3.js

I want to use d3.js to make a chart with vertical zooming of histrogram bars. I am doing something wrong, because result is not what I want.
This is my zoom:
svg.selectAll('g.info-group').each(function (d, i) {
var el = d3.select(this);
svg.select('.bars').attr('transform', 'translate(0,' + d3.event.translate[1] + ')');
el
.selectAll('.bar')
.call(function (s) {
barSetPosition(s, d.ib, i);
});
});
svg.select('.y.axis').call(yAxis);
Here is a jsFiddle
What is not working:
The y axis may have negative value and very positive where values do not exist.
If I do scroll the bars and y axis do not conform to each other.
How can I correct this?
Update (05.19.2015):
I found the solution for this questions and here it is - jsFiddle
var zoom = d3.behavior.zoom()
.y(yScale)
.scaleExtent([1, 2])
.on("zoom", function () {
var t = zoom.translate(),
tx = t[0],
ty = t[1],
scale = zoom.scale();
ty = Math.min(ty, 0);
ty = Math.max(ty, canvasH + margin.top - (canvasH + margin.top) * scale);
zoom.translate([tx, ty]);
svg
.select('.bars')
.attr("transform", "translate(0," + ty + ")");
svg.selectAll('.bar')
.attr('y', function (d) {
return (canvasH + margin.top) * scale - (yScale(0) - yScale(d.ib));
})
.attr('height', function (d) {
return yScale(0) - yScale(d.ib);
})
.attr('width', barScale.rangeBand());
svg.select('.y.axis').call(yAxis);
});
Now zooming work fine.
But now there two more questions.
When I am doing zoom and pan to the down the bar go under X axis and the numbers on axis become hidden.
When I am hovering bars and zooming, then pan to the down the bars is trembled.
How to fix this? Thanks.
I fixed all problems, the final code is - jsFiddle
This is the main part:
var zoom = d3.behavior.zoom()
.y(yScale)
.scaleExtent([1, 2])
.on("zoom", function () {
var t = zoom.translate(),
tx = t[0],
ty = t[1],
scale = zoom.scale();
ty = Math.min(ty, 0);
ty = Math.max(ty, canvasH + margin.top - (canvasH + margin.top) * scale);
zoom.translate([tx, ty]);
svg
.select('.bars')
.attr("transform", "translate(0," + ty + ")");
svg.selectAll('.bar')
.attr('y', function (d) {
return (canvasH + margin.top) * scale - (yScale(0) - yScale(d.ib));
})
.attr('height', function (d) {
var height = (yScale(0) - yScale(d.ib)) - (canvasH + margin.top) * (scale - 1) - ty;
if (height < 0) {
height = 0;
}
return height;
})
.attr('width', barScale.rangeBand());
svg.select('.y.axis').call(yAxis);
});
svg.call(zoom);

Rotating pie chart using d3.js [duplicate]

I am looking for an example for to rotate a pie chart on mouse down event. On mouse down, I need to rotate the pie chart either clock wise or anti clock wise direction.
If there is any example how to do this in D3.js, that will help me a lot. I found an example using FusionChart and I want to achieve the same using D3.js
Pretty easy with d3:
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color(d.data.age);
});
var curAngle = 0;
var interval = null;
svg.on("mousedown", function(d) {
interval = setInterval(goRotate,10);
});
svg.on("mouseup", function(d){
clearInterval(interval);
})
function goRotate() {
curAngle += 1;
svg.attr("transform", "translate(" + width / 2 + "," + height / 2 + ") rotate(" + curAngle + "," + 0 + "," + 0 + ")");
}
Working example.
I did a similar thing with a compass instead of pie chart. You mainly need three methods - each bound to a different mouse event.
Bind this to the mousedown event on your compass circle:
function beginCompassRotate(el) {
var rect = compassCircle[0][0].getBBox(); //compassCircle would be your piechart d3 object
compassMoving = true;
compassCenter = {
x: (rect.width / 2),
y: (rect.height / 2)
}
}
Bind this to the mouse move on your canvas or whatever is holding your pie chart - you can bind it to the circle (your pie chart) but it makes the movement a little glitchy. Binding it to the circle's container keeps it smooth.
function rotateCompass() {
if (compassMoving) {
var mouse = d3.mouse(svg[0][0]);
var p2 = {
x: mouse[0],
y: mouse[1]
};
var newAngle = getAngle(compassCenter, p2) + 90;
//again this v is your pie chart instead of compass
compass.attr("transform", "translate(90,90) rotate(" + newAngle + "," + 0 + "," + 0 + ")");
}
}
Finally bind this to the mouseup on your canvas - again you can bind it to the circle but this way you can end the rotation without the mouse over the circle. If it is on the circle you will keep rotating the circle until you have a mouse up event over the circle.
function endCompassRotate(el) {
compassMoving = false;
}
Here is a jsfiddle showing it working: http://jsfiddle.net/4oy2ggdt/

D3.js Set initial zoom level

I have several graphs set up to zoom on the container and it works great. However, on the initial load, the zoom level is way too close. Is there a method of setting the initial zoom level to avoid having to first zoom out? I am familiar with the .scale() method but have not had any luck implementing it. Is this the way to go or is there something I am missing?
Here is what I have thus far as pertaining to zoom:
var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = 50000 - margin.right - margin.left,
height = 120000 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([0, width])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, height])
.range([height, 0]);
var tree = d3.layout.tree()
.size([height, width])
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.x, d.y]; });
function zoom(d) {
svg.attr("transform",
"translate(" + d3.event.translate + ")"+ " scale(" + d3.event.scale + ")");
}
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("pointer-events", "all")
.call(d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([0,8])
.on("zoom", zoom))
.append('g');
svg.append('rect')
.attr('width', width*5)
.attr('height', height)
.attr('border-radius', '20')
.attr('fill', 'sienna');
D3v4 answer
If you are here looking for the same but with D3 v4,
var zoom = d3.zoom().on("zoom", function(){
svg.attr("transform", d3.event.transform);
});
vis = svg.append("svg:svg")
.attr("width", width)
.attr("height", height)
.call(zoom) // here
.call(zoom.transform, d3.zoomIdentity.translate(100, 50).scale(0.5))
.append("svg:g")
.attr("transform","translate(100,50) scale(.5,.5)");
I finally got this to work by setting both the initial transform and the zoom behavior to the same value.
var zoom = d3.behavior.zoom().translate([100,50]).scale(.5);
vis = svg.append("svg:svg")
.attr("width", width)
.attr("height", height)
.call(zoom.on("zoom",zooming))
.append("svg:g")
.attr("transform","translate(100,50)scale(.5,.5)");
Applies to d3.js v4
This is similar to davcs86's answer, but it reuses an initial transform and implements the zoom function.
// Initial transform to apply
var transform = d3.zoomIdentity.translate(200, 0).scale(1);
var zoom = d3.zoom().on("zoom", handleZoom);
var svg = d3.select("body")
.append('svg')
.attr('width', 800)
.attr('height', 300)
.style("background", "red")
.call(zoom) // Adds zoom functionality
.call(zoom.transform, transform); // Calls/inits handleZoom
var zoomable = svg
.append("g")
.attr("class", "zoomable")
.attr("transform", transform); // Applies initial transform
var circles = zoomable.append('circle')
.attr("id", "circles")
.attr("cx", 100)
.attr("cy", 100)
.attr('r', 20);
function handleZoom(){
if (zoomable) {
zoomable.attr("transform", d3.event.transform);
}
};
See it in action: jsbin link
Adding this answer as an addendum to the accepted answer in case anyone is still having issues:
The thing that made this really easy to understand was looking here
That being said, I set three variables:
scale, zoomWidth and zoomHeight
scale is the initial scale you want the zoom to be, and then
zoomWidth and zoomHeight are defined as follows:
zoomWidth = (width-scale*width)/2
zoomHeight = (height-scale*height)/2
where width and height are the width and height of the "vis" svg element
the translate above is then amended to be:
.attr("transform", "translate("+zoomWidth+","+zoomHeight+") scale("+scale+")")
as well as the zoom function:
d3.behavior.zoom().translate([zoomWidth,zoomHeight]).scale(scale)
What this does is effectively ensures that your element is zoomed and centered when your visualization is loaded.
Let me know if this helps you! Cheers.
D3JS 6 answer
Let's say that you want your initial position and scale to be x, y, scale respectively.
const zoom = d3.zoom();
const svg = d3.select("#containerId")
.append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom.transform, d3.zoomIdentity.translate(x, y).scale(scale)
.call(zoom.on('zoom', (event) => {
svg.attr('transform', event.transform);
}))
.append("g")
.attr('transform', `translate(${x}, ${y})scale(${k})`);
.call(zoom.transform, d3.zoomIdentity.translate(x, y).scale(scale) makes sure that when the zoom event is fired, the event.transform variable takes into account the translation and the scale. The line right after it handles the zoom while the last one is used to apply the translation and the scale only once on "startup".
I was using d3 with react and was very frustrated about the initial zoom not working.
I tried the solutions here and none of them worked, what worked instead was using an initial scale factor and positions and then updating the zoom function on the basis of those scale factor and positions
const initialScale = 3;
const initialTranslate = [
width * (1 - initialScale) / 2,
height * (1 - initialScale) / 2,
];
const container = svg
.append('g')
.attr(
'transform',
`translate(${initialTranslate[0]}, ${initialTranslate[1]})scale(${initialScale})`
);
The zoom function would look something like this
svg.call(
zoom().on('zoom', () => {
const transformation = getEvent().transform;
let {x, y, k} = transformation;
x += initialTranslate[0];
y += initialTranslate[1];
k *= initialScale;
container.attr('transform', `translate(${x}, ${y})scale(${k})`);
})
);
If you noticed the getEvent() as a function, it was because importing event from d3-selection was not working in my case. So I had to do
const getEvent = () => require('d3-selection').event;

Categories