I have a stripped down force directed graph with zoom/pan capability: https://bl.ocks.org/anonymous/36b27a0b6f8c485c25995e7c223def3c
Here is the zoom portion:
var zoomHandler = d3.zoom()
.on("zoom", zoomActions);
zoomHandler(svg);
function zoomActions(){
g.attr("transform", d3.event.transform)
}
I would like to remove the zoom capability for double-click, and only keep it for scrolling. Based on the docs and other questions, I know I must include .on("dblclick.zoom", null) somewhere. However, I cannot figure out where to place this piece of code. In addition, I'm not sure if I am properly using "svg" and "g" to group my elements, which may be contributing to the issue. I have seen the other questions related to this issue, and have not been able to fit them into my situation.
How should I remedy this? Thank you for any insight you all might have!
Since you are calling the zoom function on the svg selection...
zoomHandler(svg);
... you have to add the listener to the same selection:
svg.on("dblclick.zoom", null)
Here is your updated bl.ocks: https://bl.ocks.org/anonymous/42745557a8602692d9dc98c33a327d29
var zoomHandler = d3.zoom()
.on("zoom", zoomActions);
zoomHandler(svg);
svg.call("zoom").on("dblclick.zoom", null);
function zoomActions(){
g.attr("transform", d3.event.transform)
}
Related
I am trying to implement in D3 v7 a dual-brush functionality, which:
with the standard mouse button brush allows to highlight the brushed dots in a scatter chart (as has been done many times)
with the mouse button brush while holding the control key (or the right mouse button or any other easy to implement key), allows to remove (ie make transparent) brushed dots in a scatter chart.
Functionality 1) already works but I struggle to add 2) at the same time.
In the post
Brush d3.js with right click
the brush.filter functionality is discussed as a potential solution at high level. So I thought about implementing it in the following manner:
var brush = d3.brush()
.filter(event => !event.ctrlKey)
.on("end", brushed) //Function to highlight
svg
.call(brush)
and
var brush2 = d3.brush()
.filter(event => event.ctrlKey)
.on("end", brushed2) //Function to make dots transparent
svg
.call(brush2)
It appears though that the svgs are drawn above each other an consequently only the last added layer is available to brush.
No luck either with something like
var brush = d3.brush()
.filter(event => !event.ctrlKey)
.on("end", brushed)
.filter(event => event.ctrlKey)
.on("end", brushed2)
svg
.call(brush) //Function with an if statement to decide highlight or removal
Understand I might need to specify "pointer-events","none" somewhere but haven't been able to fully figure this out by myself.
Any ideas welcome.
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);
}
I am using this example as a basis for zoom in my project but I would like to restrict the zoom to only the buttons and double clicking. Ideally, using the scroll wheel should just continue scrolling down the page and not zoom in on the svg when the mouse is over it. Any help is appreciated!
For D3 4.0, there're two ways to prevent d3 zoom from responding to wheel event.
A:
zoom.filter(function() { return !event.button && event.type !== 'wheel'; })
// the `!event.button` expression is from the default filter, according to
// doc for zoom.filter([filter])
B:
svg.on("wheel.zoom", null);
// according to doc for zoom(selection): Internally, the zoom behavior
// uses selection.on to bind the necessary event listeners for zooming.
// The listeners use the name .zoom
It's still possible to pan(translate) with mouse dragging.
You can achieve this by:
var svg = d3.select("body").select("svg")
.attr("width", width)
.attr("height", height)
.append("g")
//.call(zoom) //remove the zoom listener from the group.
.append("g");
Next attach doublclick listener on the circle and the rectangle for zooming.
d3.selectAll("rect").on('dblclick', zoomClick)
d3.selectAll("circle").on('dblclick', zoomClick)
working code here
Currently, I have one SVG, which has multiple containers inside it, ie., multiple smaller svg's , which rendered according to the number of groups in the json data. If there are 5 groups of data in the json, 5 smaller multiple containers will fit into that one outer svg.
Also, if a container is zoomed, the other containers should reflect the same zoom behavior and should be in sync. This is to be implemented on scatterplot and multi-line chart.
The current problem is the multiple containers are rendered as desired, but the only the last container when zoomed in the behavior is correct. On other containers, when the zoom function is called, the zoom behavior is incorrect,ie., consider it being a scatterplot chart, the bubbles on scale and the axis do not zoom in or out.
1) How do I bring the axis to zoom in the other containers as well, assuming the selection of the axis in the other containers are correct?
2) How do I synchronize the zoom behavior in all the containers, regardless of whichever container I zoom on?
CODE :
d3.select(that.selector+" #svgContainer"+index)
.call(d3.behavior.zoom()
.x(that.xScale)
.y(that.yScale)
.scaleExtent([1, 10])
.on("zoom", zoomed)); /*Attaching Zoom behavior to each container id '#svgContainer0','#svgContainer1',so on.*/
function zoomed() {
that.zoomed_out = false;
that.k.isOrdinal(that.selector,"#"+this.id+" .x.axis",that.xScale);
isOrdinal(that.selector,"#"+this.id+" .x.grid",that.xScale);
isOrdinal(that.selector,"#"+this.id+" .y.axis",that.yScale);
isOrdinal(that.selector,"#"+this.id+" .y.grid",that.yScale);
that.optionalFeatures()
.plotCircle()
.label();
d3.select(that.selector)
.selectAll(".dot")
.attr("r", function (d) {
return that.sizes(d.weight)*d3.event.scale;
});
};
function isOrdinal(svg,container,scale) {
var l = container.length;
var temp = container.substr((l-7),7);
if(temp === ".x.axis") {
d3.select(svg).select(container).call(makeXAxis(options,scale));
}
else if(temp === ".y.axis") {
d3.select(svg).select(container).call(makeYAxis(options,scale));
}
};
function makeXAxis(options,xScale) {
var xaxis = d3.svg.axis()
.scale(xScale)
.ticks(options.axis.x.no_of_ticks)
.tickSize(options.axis.x.tickSize)
.outerTickSize(options.axis.x.outer_tick_size)
.tickPadding(options.axis.x.ticksPadding)
.orient(options.axis.x.orient);
return xaxis;
};
function makeYAxis(options,yScale) {
var yaxis = d3.svg.axis()
.scale(yScale)
.orient(options.axis.y.orient)
.ticks(options.axis.y.no_of_ticks)
.tickSize(options.axis.y.tickSize)
.outerTickSize(options.axis.y.outer_tick_size)
.tickPadding(options.axis.y.ticksPadding);
return yaxis;
};
I might be too late but here is my take on the problem:
Basically each .call(d3.behavior.zoom()...) is independent and only the last one catches the event fired. Have you tried creating your own event and manually redispatching it to a list of predeclared zooms?
.on("d3Zoom", function(){
//zoom1
//zoom2
//...
})
Also, instead of looping through the IDs that are only regexps of #svgContainer+index, use classes: class=svgContainer, id=index.
This way you can selectAll(".svgContainer").call(d3Zoom...). The way selections are implemented as joints, that may take care your independent zooming issues (selectAll represents a single selection which will be associated with one zoom whereas a loop of select represent multiple selections, hence the independent zooming capabilities).
I hope it helps someone.
I have been experimenting with a simple d3 google analytics-style area graph demo. I'd like to make it stretch to the full width of its container, which I have managed to do. However the little circles are of course stretching out of shape too. I'd like their positions to be responsive, but not their dimensions (so they remain circular).
Fiddle link: http://jsfiddle.net/46PfK/2/
I'm trying to use the SVGPanUnscale.js script. I have tried calling it with unscaleEach('.dot'); and [].forEach.call($('.dot'),unscale); but neither appear to do anything.
This example is responsive in a similar way to mine and uses the script to 'unscale' the axis labels: http://meloncholy.com/static/demos/responsive-svg-graph-1/
This example also uses circle elements:
http://phrogz.net/svg/scale-independent-elements.svg
I looked at solutions involving the css attribute:
circle {
vector-effect: non-scaling-stroke;
}
which created a circular stroke on an ellipse - weird.
A CSS solution would be preferable to a JS one, provided it works across browsers.
Any suggestions?
Thanks to #leMoisela for pointing me in the right direction. I fixed my issue nicely using JS to redraw the graph on resize:
http://jsfiddle.net/46PfK/4/
window.onresize = function(e){
draw_graph();
};
There's a good example on resizing with D3 https://blog.safaribooksonline.com/2014/02/17/building-responsible-visualizations-d3-js/
After you update your scales, only thing left to resize your circles would be something like this:
/* Force D3 to recalculate and update the points */
svg.selectAll('circle')
.attr('cx', function (d) { return xScale(d.x); })
.attr('cy', function (d) { return yScale(d.y); })
.attr('r', 4);