d3.js: Zoom on click event - javascript

I was trying to get same behavior Wil Linssen's implementation
but on d3.js version 4.
I am quite confused with zoom api in version 4.
The changes that I made in the original implementation is:
zoom.translate() replaced with
d3.zoomTransform(selection.node()) and appropriate points added instead:
svg.attr("transform",
"translate(" + t.x + "," + t.y + ")" +
"scale(" + t.k + ")"
);
This one:
zoom
.scale(iScale(t))
.translate(iTranslate(t));
replaced to
var foo = iTranslate(t);
zoom.
translateBy(selection, foo[0], foo[1]);
zoom
.scaleBy(selection, iScale(t));
But it's still has a problem, looks like with scale, zoom out...
Example: Example on d3.v4 - jsfiddle
Thanks for the help

After researching the most easiest way to use d3 v4 api that provides interpolation etc out of box. In original question zoom implemented from scratch, that doesn't make sense in version 4. Solution is similar to #Nixie answers and has few lines of code.
This version includes pan as well, if you do not need, remove svg.call(zoom);
Final example: Zoom D3 v4 - jsfiddle
One interesting note:
The function:
function transition() {
svg.transition()
.delay(100)
.duration(700)
.call(zoom.scaleTo, zoomLevel);
//.on("end", function() { canvas.call(transition); });
}
call(zoom.ScaleTo, zoomLevel) - will zoom to the center of page. But if you need the center of an image, use this one: call(zoom.transform, transform);
Where transform is a function that sets center of your image. For example of such function:
function transform() {
return d3.zoomIdentity
.translate(width / 2.75, height / 2.75)
.scale(zoomLevel)
.translate(-width/2.75, -height/2.75);
}

This piece of code does not resolve all the problems, but could be a starting point. In D3 v4 you can transition zoom in one line of code (see also https://github.com/d3/d3-zoom/blob/master/README.md#zoom_transform)
function interpolateZoom (translate, scale) {
return selection.transition().duration(350)
.call(zoom.transform,
d3.zoomIdentity.translate(translate[0], translate[1]).scale(scale))
}
Modified example: https://jsfiddle.net/2yx1cLrq/. Also, you need to replace selection with selection.node() in all calls to d3.zoomTransform

I've used Angular for this, and it's in my opinion the simplest and cleanest so far. I already had scroll to zoom behavior, adding mouse clicks was easy. Please note I import my d3 functions using ES6 modules.
Example:
import { event as d3event, zoom, scaleExtent, selectAll, transition, duration } from 'd3';
My existing zoom (mouse scroll)
this.zoom = zoom().scaleExtent([1, 8]).on('zoom', this.zoomed.bind(this));
// this.chart is my map chart svg created by d3
this.chart.call(this.zoom)
zoomed() {
// this.g is a <g> element appended to the chart that contains all countries in my case.
this.g
.selectAll('path') // To prevent stroke width from scaling
.attr('transform', d3event.transform);
}
What I added to get zoom buttons:
<!-- My markup -->
<button (click)="zoomClick(1.5)">Zoom in</button>
<button (click)="zoomClick(0.5)">Zoom out</button>
zoomClick(level): void {
this.chart.transition().duration(200).call(this.zoom.scaleBy, level);
}

Related

D3.js - Zooming while keeping sizes, with d3.forceSimulation()

I have a graph visualisation.
I've added zooming by scaling a <g> which holds everything.
That also resizes the nodes (circles) and their labels.
From what I've seen, keeping the size and only repositioning is done like this:
function zoomed() {
var t = d3.event.transform;
circle.attr("transform", function(d) {
return "translate(" + t.applyX(d[0]) + "," + t.applyY(d[1]) + ")";
});
}
However this won't work for me, because I already use translate for positioning the nodes by d3.forceSimulation(). I could apply the zoom like it's done above, but that would all fall back when the simulation gets started again - e.g. when dragging a node, which is done using:
function dragstarted() {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
...
How could I combine the simulation and the zooming?
One way I am thinking is to scale down all the nodes by inverse scale to what is used for zooming.
Other way could be styling down the things - smaller font, smaller circles, etc.
Other way could be tampering with the forces so the nodes go further from each other on zoom in.
For now I went the way of reversed scale for each node. Works quite fine, although it's a little bit of overhead I think.
Here's the code:
// Zooming
var zoom = d3.zoom();
zoom.scaleExtent([0.4, 3]);
zoom.on("zoom", function onZoomed() {
console.log("Zooming", d3.event);
var t = d3.event.transform;
zoomingGroup.attr("transform", t); // Using transform.toString()
svg.selectAll(".myGroup circle").attr("transform", "scale(" + 1/t.k + ")");
svg.selectAll(".myGroup .labelBox").attr("transform", "scale(" + 1/t.k + ")");
svg.selectAll(".myGroup .labelText").attr("transform", "scale(" + 1/t.k + ")");
});
I wanted to apply it to the whole <g class=".myGroup">, but changing it's transform to scale would interfere with forceSimulation()'s translate. Can you give me a tip to handle that? I could add another <g> and apply translate(...) to the parent <g> and scale(...) to the other.
I'm leaving this unaccepted in case someone has better (simpler) solution.

Trying to analyse why this transition is not happening

I have the following adapted d3.js visual and I'm unable to work out why the transition does not fire. The ars should rotate around to different sizes when the radio button is clicked.
It seems that clicking the radio button is changing the titles of the arc (if I hover the cursor over each)
Is this section of code to blame?
// check if svg group already exists
var svg = d3.select("#sunGroup");
if (svg.empty()) {
var svg = d3.select("#sunBurst")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr({
'transform': "translate(" + width / 2 + "," + height * .52 + ")",
id: "sunGroup"
});
}
Here is the full (not) working example:
https://plnkr.co/edit/hpvREU?p=preview
This is the transition I'm trying to hold onto: plnkr.co/edit/NnQUAp?p=preview
What I trying to do is move the logic at line 128 (starting d3.selectAll("input").on("change", function change() {...) out of this function
An easy fix to your problem is to remove all children of the SVG whenever you switch data types:
d3.select("#sunBurst").selectAll('*').remove()
That way, you are binding the new data to new elements. You can also remove the svg.empty() and d3.select('#sunGroup') code. This simple fix lets you switch between pie charts, and is in the spirit of the code you currently have. Here's the users pie chart.
However, this may not be the best way to do what you're trying to achieve. As a reference, see Mike Bostock's General Update Pattern series (link is to first in the series) for how to update your SVG.

Zoomable sunburst chart in percentages

I'm new to D3 and I hope you can help out. I'm working on a sunburst chart divided into inner and outer layers, where inner layer represents a group and outer layers represent subgroups. Here's a working example for reference: jsfiddle.net/9gpL308y/1/ (and here's the original fiddle I used as a starting point: jsfiddle.net/j9WnB/64/)
Currently, each inner layer adds up to a number based on its categories' values in the outer layer and categories behave the same with their subcategories. What I need is for each layer to display as percentage (out of 100 that all groups on the same layer should add up to) and scale to appropriate size. Take this image for example:
image of the wanted chart
Working example would be extremely helpful. I found some topics on this problem but I couldn't get it to work with provided advice. To be honest I still don't understand fully what's going on in here (I was never good at geometry).
tl;dr: How to make sunburst chart from the fiddle above display data as percentage and have arcs scale appropriately?
Thanks.
There is a little to do with geometry in this case (except positioning your label, but this is what arc.centroid provides). To get your percentages you just need to divide an extent of the child node by the extent of its parent.
var center = arc.centroid(d);
g.append("text")
.attr("text-anchor", "middle")
.attr("transform", "translate(" + center + ")")
.text(function(_) {
if (d.parent == null)
{
return;
}
var percentage = 100 * d.dx / d.parent.dx; // that's it!
return d3.format(",.2f")(percentage) + "%";
})
Working example here: http://jsfiddle.net/9gpL308y/2/
For educational purposes you can dump all the nodes of the partition layout and then find how do they match visual picture. You can also read about those parent, dx, etc. at the documentation on partition layout: https://github.com/d3/d3-3.x-api-reference/blob/master/Partition-Layout.md

D3 mousewheel zoom direction

I'm trying to create some custom zoom functionality in d3.js. Currently the zoom is triggered on a single click and zooms in to focus only on the area that was clicked on.
Currently my code has a function zoom(d) that does exactly what it needs to. There is also a var zoomTransition which resides inside zoom() and is responsible for much of the functionality. I'm unfortunately unable to share much of my code.
The zoom needs to also occur on a mouse scroll. The difficulty I'm having is that this:
.on("wheel", function(d){
zoom(d);
});
disregards the scroll wheel direction. Zoom is called simply because the wheel is scrolled, either in or out.
Is there any way I can access the scroll direction and pass it into zoom()? Or a better way to do this?
Was looking for this:
.on("wheel", function(d){
var direction = d3.event.wheelDelta < 0 ? 'down' : 'up';
zoom(direction === 'up' ? d : d.parent);
});
More so javascript than d3, but that's how you access the scroll wheel information.
d3 has a zoom behaviour that you might find useful.
Example code:
var zoom = d3.behavior.zoom()
.scaleExtent([0, 10])
.on("zoom", redraw); //if you are sure that your zoom function is working just replace redraw with your zoom function
function redraw() {
return svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
}
Full example:
http://bl.ocks.org/mbostock/2206340

Keep scale of element on zooming

The d3js library has a built-in behavior called zoom, which applies dragging and zooming in the selected element and its children. It is hard to keep the scale and good visualization when you have a great zoom scale, and text labels, for example, can become ilegible.
I'm trying to find a way to ignore a specific element when zooming the area, but I don't see a way to achieve that.
In this case, there is a circle and a text inside a g. When I zoom a simple pack layout, I have the following behavior, with the following code:
var zoom = d3.behavior.zoom()
.translate([0, 0])
.scale(1.0)
.scaleExtent([0.1, 3])
.on("zoom", function() {
child.attr("transform", "translate(" + d3.event.translate +
")scale(" + d3.event.scale + ")");
});
I need to prevent text from being zoomed, staying static:
Is there a way to achieve that or it is neeed to rewrite the zoom behavior and reapply the scroll events also?

Categories