d3.js v4 zoom to chart center (NOT MOUSE POSITION!!) - javascript

I have one more problem with d3 zooming.
I try to zoom to the center of the graph (ignoring mouse position). But I don't find anything that works for me.
I tried this but i don't get it work.
And didn't found anything in the docs.
Zoom behavior:
//zoom behavior
let zoom = d3.zoom()
.scaleExtent([0.5, 5])
.on("zoom", zoomed);
function zoomed() {
.. some behavior ..
//center here?
.. some behavior ..
}
I think this should be an easy thing to do and don't understand, why it's not documented clearly somewhere.
Please see this fiddle for the whole thing.
Help would be greatly appreciated

I managed to solve my issue.
See this fiddle for the details.
Note, that this will disable the x-panning as well.
Here the relevant code:
function getBoundingBoxCenterX (selection) {
let element = selection.node();
let bbox = element.getBBox();
return bbox.x + bbox.width/2;
}
//zoom behavior
let zoom = d3.zoom()
.scaleExtent([1, 5])
.on("zoom", zoomed);
function zoomed() {
let center = getBoundingBoxCenterX(rect); //rect is the charts parent which expands while zooming
let chartsWidth = (2 * center) * d3.event.transform.k;
d3.event.transform.x = center - chartsWidth / 2;
rescaling operations with d3.event.transform....
}

Related

How to zoom on a map using d3 on canvas?

Some background:
I have plotted a map and about 35k circles on it with zoom and tooltips working fine on SVG. However, due to the amount of circles that need to be drawn (and may be not the best written code; i'm a beginner) I see performance issues while getting the page to run.
And so, I wanted to try out the same page on a canvas to improve performance.
Problem:
I got the map itself working on canvas but I have been trying to add the zoom feature but in vain. Any help in getting this fixed will be greatly appreciated.
Sample with SVG - https://bl.ocks.org/sharad-vm/af74ae5932de1bcf5a39b0f3f849d847
The code I have for Canvas is as below:
//Width and height
var w = 700;
var h = 600;
//Create Canvas element
var canvas = d3.select('#map')
.append('canvas')
.attr('width', w)
.attr('height', h);
var context = canvas.node().getContext('2d');
//Define map projection
var projection = d3.geo.mercator()
.translate([w/2, h/1.72])
.scale([100]);
//Define path generator
var path = d3.geo.path()
.projection(projection)
.context(context);
var init = 0;
canvas.call(zoom);
var zoom = d3.behavior.zoom()
.translate([0, 0])
.scale(1)
.scaleExtent([1, 30])
.on("zoom", zoomed);
//function to zoom
function zoomed() {
context.save();
context.clearRect(0, 0, w, h);
context.translate(d3.event.transform.x, d3.event.transform.y);
context.scale(d3.event.transform.k, d3.event.transform.k);
draw();
context.restore();
};
draw();
//Load in GeoJSON data
function draw() {
...
}
When using projections the secret to get the zoom working is to transform the projection itself. For your example you can just adjust your projection before redrawing with something like:
projection.translate([w/2 + d3.event.transform.x, h/1.72 + d3.event.transform.y])
.scale([100*d3.event.transform.k]);
Another option is to scale the canvas itself, like in this example I made

d3.js v4 dragging zoomed elements jumping mouse

Hi Stackoverflow Community!
I have following problem:
I created a d3 force-directed graph with div as nodes and included a d3-zoom behavior.
When I zoomed out or zoomed in a lot then the dragging of nodes became either too fast(when zoomed in) or too slow(when zoomed out).
I fixed that by applying d3.mouse(d3.select(".links").node()) so that the mouse coordinates will be taken from inside the zoomed area.
But since i did that i notice that when dragging a node this node jumps. It centers on the mousepointer instead just following the mouse.
After some research I tried fixing this by specifying a subject like so:
d3.drag().subject(function() {
var t = d3.select(this);
return {x: parseInt(t.style("left"),10), y: parseInt(t.style("top"),10)};
})
But it didn't have any influence and i'm out of ideas now. I'm happy if someone could help me here..
Following fiddle to demonstrate the issue: https://jsfiddle.net/jxkgfdcm/
It jumps to the centre of the node because in drag you are doing:
function dragged(d) {
var coordinates = [0, 0];
coordinates = d3.mouse(d3.select(".links").node()); //this will give the link end location..so it will jump to the centre of the node
var x = coordinates[0];
var y = coordinates[1];
d.fx = x;
d.fy = y;
d.fixed = true;
}
it should have been:
function dragged(d) {
d.fx += d3.event.dx;//give delta increment to current position
d.fy += d3.event.dy//give delta increment to current position
d.fixed = true;
}
working code here

d3 error with manual zoom

I'm using d3 v4.
I'm implementing a zoom on an area graph using the following example. My zoom is registered as such:
// Zoom Components
zoom = d3.zoom()
.scaleExtent([1, dayDiff*12])
.translateExtent([[0, 0], [width, height]])
.extent([[0, 0], [width, height]])
.on("zoom", zoomed);
With my zoom method looking like this:
function zoomed(){
t = d3.event.transform;
console.log(t);
...
}
When naturally zooming with the wheel, the console spits out:
{
k:1.0097512975966858
x:-1.9210056265470996
y:-1.004383652458642
}
I'm using a TimeScale and I want to zoom in and translate to a certain period of time. For example, I may only want to show 7 days as my x1 and x2, so i calculate scale factor of k and then calculate tx value to translate to a certain area. I
created a manual zoom method to trigger a manual zoom. With the following code:
function manualZoom(){
var outerRightDay,
thirtyBeforeOuter,
k,
tx;
// Get outer right day
outerRightDay = moment(xScale.domain()[1]);
// Get 30 days before
thirtyBeforeOuter = moment(outerRightDay).subtract(31,'days');
// Get scale k
k = width / (xScale(outerRightDay) - xScale(thirtyBeforeOuter));
// Get transform value
svg.call(zoom.scaleBy, k);
tx = 0 - k * xScale(thirtyBeforeOuter);
svg.call(zoom.translateBy,tx);
}
After running this, the zoomed method spits out:
{
k:1.0097512975966858 //a good number
x:-1.9210056265470996 //a good number
y:NAN //this is an issue!!!!
}
It works with mouse wheel, buy on touch devices. My y is NAN and stops me from zooming in on a touch device. How can I calculate ty to supply it to zoom.translateBy().
I have included a jsFiddle here.
Since you only use transform rescale on x-axis (in zoomed function), you don't care about the value of ty. Just pass a value to translateBy, it won't be NaN and it will work:
svg.call(zoom.translateBy,tx,0);
Why ty is NaN with your code? zoom.translateBy calls transform.translate function, its source code is:
translate: function(x, y) {
return x === 0 & y === 0 ? this : new Transform(this.k, this.x + this.k * x, this.y + this.k * y);
}
So if y is undefined, this.y + this.k * y will be evaluated as NaN.

Placing D3 tooltip in cursor location

I'm using d3-tip in my visualisation. I now want to add tooltips to elements that are very wide and may extend out of the visible canvas. By default, the tooltip is shown in the horizontal center of an object, which means in my case that the tooltip might not be in the visible area. What I need is the tooltip showing up in the horizontal position of the cursor but I don't know how to change the tooltip position correctly. I can set an offset and I can get the coordinates of the cursor, but what I can't get is the initial position of the tooltip so that I can compute the right offset. Nor can I set an absolute position:
.on("mouseover",function(d){
var coordinates = [0, 0];
coordinates = d3.mouse(this);
var x = coordinates[0];
var y = coordinates[1];
tip.offset([-20,20]); // this works
tip.attr("x",40); // this doesn't
tip.show(d);
})
If you want to use offset, you can get the initial position of the tooltip after tip.show(d):
tip.style('top');
tip.style('left');
Similarly, to set the absolute position:
.on('mouseover', function(d){
var x = d3.event.x,
y = d3.event.y;
tip.show(d);
tip.style('top', y);
tip.style('left', x);
})
The previously stated answer did not work for me (and cannot be modified as "suggested edit queue is full.."), but with some minor adjustments, it is working fine:
.on('mouseover', function(d){
var x = d3.event.x,
y = d3.event.y;
tip.show(d);
tip.style('top', y-10 + 'px'); // edited
tip.style('left', x+'px'); // edited
})

d3.js rewriting zoom example in version4

Drag and Drop Example
I am trying to rewrite part of this example above to use in my code, specifically this piece:
function centerNode(source) {
scale = zoomListener.scale();
x = -source.y0;
y = -source.x0;
x = x * scale + viewerWidth / 2;
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]);
}
However I am getting stuck since the v4 package has changed quite a bit. I wrote my zoomListener function to be
var zoomListener = d3.zoom()
.scaleExtent([0.3,2])
.on("zoom", zoomed);
function zoomed() {
transform = d3.event.transform;
console.log(d3.event);
svg.attr("transform", transform);
}
function centerNode(source){
t = transform;
console.log(t);
x = t.x*t.k; //I only want things to be centered vertically
y = (t.y + -source.x0)*t.k + (viewerHeight)/2 ;
svg.transition()
.duration(duration)
.attr("transform","translate(" + x + "," + y +")scale(" + t.k + ")");
transform.scale(t.k); //DOES NOT WORK
transform.translate([x, y]); //DOES NOT WORK
}
and I know that according to the doc things have changed and info are no longer are stored on what would be my zoomListener
D3 V4 release note on zoom I guess I am just confused on how I am suppose to do it with the new version. The last few lines of my centerNode function don't work which has for effect that when I center the node the zooming and panning reset...
Any suggestion?
So after much digging and trial and error I cam up with an answer that works pretty well for my purposes. Note that this code below is only the relevant part of my code not the whole code, certain variable were self explanatory so did not include them. ALSO THIS IS IN VERSION 4 of d3.js.
var zoom = d3.zoom()
.scaleExtent([0.3,2])
.on("zoom", zoomed);
var svg = d3.select("body")
.append("svg")
.attr("width", viewerWidth)
.attr("height", viewerHeight);
var zoomer = svg.append("rect")
.attr("width", viewerWidth)
.attr("height", viewerHeight)
.style("fill", "none")
.style("pointer-events", "all")
.call(zoom);
var g = svg.append("g");
zoomer.call(zoom.transform, d3.zoomIdentity.translate(150,0)); //This is to pad my svg by a 150px on the left hand side
function zoomed() {
g.attr("transform", d3.event.transform);//The zoom and panning is affecting my G element which is a child of SVG
}
function centerNode(source){
t = d3.zoomTransform(zoomer.node());
console.log(t);
x = t.x;
y = source.x0;
y = -y *t.k + viewerHeight / 2;
g.transition()
.duration(duration)
.attr("transform", "translate(" + x + "," + y + ")scale(" + t.k + ")")
.on("end", function(){ zoomer.call(zoom.transform, d3.zoomIdentity.translate(x,y).scale(t.k))});
}
As per the examples for v4 on the d3.js page, I used a rectangle to apply the zoom to
The zoom behavior is applied to an invisible rect overlaying the SVG
element; this ensures that it receives input, and that the pointer
coordinates are not affected by the zoom behavior’s transform. Pan & Zoom Example
In the Center node function I am using d3.zoomTransform(zoomer.node()); to get the current transform applied to the page.
The purpose of this function is only to center the collapsible tree vertically not horizontally, so I am keeping the current transform.x (here t.x) the same.
The coordinate in my svg are flip hence why y= source.x0, source is a what node was clicked in my collapsible tree. ("Look to the example referenced to the top of this thread to understand what I am trying to convert to version 4)
I am apply the transformation to my G element and then I want to commit those changes to the zoom transform, to do so I use the .on("end", function(){}) otherwise it was doing weird behavior with the transition, by doing that all it does is setting the current state of the transform.
zoomer.call(zoom.transform, d3.zoomIdentity.translate(x,y).scale(t.k))
This line above is applying a translation of x and y and a scale -- that is equal to what the current state -- to the identiy matrix has to get a new transform for G, i then apply it to zoomer which is the element I called zoom on earlier.
This worked like a charm for me!
Calling transform.scale and transform.translate returns a new transform, and modifies nothing. Therefore:
transform = transform.translate([x, y]).scale(k)
svg.call(zoomListener.transform, newTransform)
(At this point zoomListener is a pretty inaccurate name for this, but regardless...)
k, x, and y can be derived from source, maybe as you show, but I'm not sure, because I don't know what source is. But to me, t.x*t.k looks suspicious, because it's multiplying the existing transforms x by its scale. Seems like it would cause a feedback loop.
For more into about the zoom in v4, check out this related StackOverflow post, or this example by mbostock demonstrating programmatic control over the zoom transform of an element (canvas in this case) and includes transitions.

Categories

Resources