I'm trying to implement a simple d3 svg zoom functionality. On double clicking the rectangle the zoom functionality is working fine.
Now i'm trying to implement the same functionality on a jquery button click function. But i'm getting following error in the console.
Uncaught TypeError: Cannot read property 'translate' of null
Following is my code.
var zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
var vis = d3.select("#svg-canvas").append("svg")
.attr("width", "550")
.attr("height", "200")
.append("g")
.attr("class", "svg-container")
.attr("transform", "translate(10,10)")
.call(zoom);
vis.append("rect")
.attr("x", 10)
.attr("y", 10)
.attr("width", 100)
.attr("height", 40)
.style("fill", "#444");
function zoomed() {
d3.select(".svg-container").attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
$(document).ready(function() {
$("#zoom").click(function() {
zoomed();
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="svg-canvas">
</div>
<button id="zoom">
Zoom
</button>
The functionality is not working. What am i doing wrong or is it possible to implement such functionality ?
At the time of the click you don't have d3.event set, because no events is comming from d3, hence you have to specify yourself the scale / translate parameters
function zoomClicked(translate, scale) {
d3.select(".svg-container").attr("transform", "translate(" + translate + ")scale(" + scale + ")");
}
$(document).ready(function() {
$("#zoom").click(function() {
zoomClicked(4, 4);
});
});
D3 has its own event generating mechanism on double click. As you have the click handler on an element that is not handled by D3, you don't have a d3.event.
d3.select(".svg-container")
.attr("transform", scale(2)");
Related
I have a d3 graph where I'd like one specific object to remain the same height after zoom is called (so it still extends the height of the chart), but the width zooms in and out accordingly.
Essentially, I just want the zoom to affect it horizontally (x). Other items need to remain a two-dimensional zoom.
Here is the whole project: https://codepen.io/lahesty/pen/XYoyxV?editors=0001 The blue rectangles are what I'm talking about. Lines 295-319
Here is my zoom function:
function zoomed() {
svg.selectAll("path.line")
.attr("transform", d3.event.transform);
svg.selectAll("g.anomrect")
.attr("transform", d3.event.transform);
svg.selectAll("g.dot")
.attr("transform", d3.event.transform);
gX.call(xAxis.scale(d3.event.transform.rescaleX(x)))
gY.call(yAxis.scale(d3.event.transform.rescaleY(y)))}
Calling zoom:
var zoom = d3.zoom()
.scaleExtent([1, 40])
.on("zoom", zoomed);
Object(s) I'm zooming on:
var anamoly = svg.append('g')
.attr('clip-path', 'url(#clipper)')
.selectAll('.anomrect')
.data(anamoly_data, function(d, i) { return d[0];
}).enter().append("g").attr("class", "anomrect");
anamoly.selectAll('.anomrect')
.data(function(d) { return d; }, function(d_, i_) { return i_})
.enter()
.append('rect')
.attr("id", function(d,i){return id_list[i%id_list.length]})
.attr("height", height)
.attr("width", function(d) { return x(d.ended_at)-x(d.started_at); })
.attr("x", function(d) { return x(d.started_at); })
.attr("y", 0)
Should I adjust "height" on the object? Or perhaps my event.transform in my zoom function?
Thank you
For either lines (actually paths), rectangles or circles, use just d3.event.translate.x for translating the x position only, and use d3.event. translate.k also for scaling the x position only:
selection.attr("transform",
"translate(" + d3.event.transform.x + ",0) scale(" + d3.event.transform.k + ",1)");
So, for restricting the zoom to the x coordinate only for all paths, rectangles and circles it will be:
function zoomed() {
svg.selectAll("path.line").attr("transform",
"translate(" + d3.event.transform.x + ",0) scale(" + d3.event.transform.k + ",1)");
svg.selectAll("g.anomrect").attr("transform",
"translate(" + d3.event.transform.x + ",0) scale(" + d3.event.transform.k + ",1)");
svg.selectAll("g.dot").attr("transform",
"translate(" + d3.event.transform.x + ",0) scale(" + d3.event.transform.k + ",1)");
gX.call(xAxis.scale(d3.event.transform.rescaleX(x)));
};
Notice that here I'm removing the y axis call.
As you mentioned in your question, if you want to restrict the zoom to just one selection, change just that respective selection (and keep the y axis call).
Here is the updated CodePen (restricting the zoom for all selections): https://codepen.io/anon/pen/djyyPQ?editors=0011
I'm trying to implement a chart framework which is able to create line and area charts with multiple axes for each orientation, i.e 2-y-axis left, 1-yaxis right and 1-x-axis bottom.
That I got to work. My next task would be to implement zoom behaviour to the chart. I indented to have a global zoom behaviour, which is trigged if the user uses his mouse within the plot area. The displayed series would get rescaled and it would be possible to pan the plot. This one I got to work too.
In addition I wanted an independent zoom/scaling for each axis. I got the scaling, but I still have problems with the global zooming and panning. If I scale one axis, the associated series in the plot area gets rescaled but the panning does not work. And after the independent scaling of an axis, if I use the global rescale, the scaling gets reset and then gets scaled by the global zoom behaviour.
On the d3.js page I found an simple example for independent and global scaling and panning, but written with d3v3 .
I changed the example in such a way, so that it displays my problem jsfiddle demo. Use you mouse on the axes and in the plot area.
<!DOCTYPE html>
<html>
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Independent Axis Zooms on x, y, or xy</title>
<script src="//d3js.org/d3.v4.min.js"></script>
<style>
.axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<div id="chart"></div>
<script>
var data = [];
for (var i = 0; i < 100; i++) {
data.push([Math.random(), Math.random()]);
}
var svg = d3.select('#chart')
.append("svg")
.attr("width", window.innerWidth).attr("height", window.innerHeight);
function example(svg, data) {
var svg;
var margin = {
top: 60,
bottom: 80,
left: 60,
right: 0
};
var width = 500;
var height = 400;
var xaxis = d3.axisBottom();
var yaxis = d3.axisLeft();
var xscale = d3.scaleLinear();
var yscale = d3.scaleLinear();
var xcopyScale, ycopyScale;
var xyzoom, xzoom, yzoom;
updateZooms();
function update() {
var gs = svg.select("g.scatter");
var circle = gs.selectAll("circle")
.data(data);
circle.enter().append("svg:circle")
.attr("class", "points")
.style("fill", "steelblue")
.attr("cx", function (d) {
return X(d);
})
.attr("cy", function (d) {
return Y(d);
})
.attr("r", 4);
circle.attr("cx", function (d) {
return X(d);
})
.attr("cy", function (d) {
return Y(d);
});
circle.exit().remove();
}
function updateZooms() {
xyzoom = d3.zoom()
.on("zoom", function () {
xaxis.scale(d3.event.transform.rescaleX(xscale));
yaxis.scale(d3.event.transform.rescaleY(yscale));
draw();
});
xzoom = d3.zoom()
.on("zoom", function () {
xaxis.scale(d3.event.transform.rescaleX(xscale));
draw();
});
yzoom = d3.zoom()
.on("zoom", function () {
yaxis.scale(d3.event.transform.rescaleY(yscale));
draw();
});
}
function draw() {
svg.select('g.x.axis').call(xaxis);
svg.select('g.y.axis').call(yaxis);
update();
// After every draw, we reinitialize zoom. After every zoom, we reexecute draw, which will reinitialize zoom.
// This is how we can apply multiple independent zoom behaviors to the scales.
// (Note that the zoom behaviors will always end up with zoom at around 1.0, and translate at around [0,0])
svg.select('rect.zoom.xy.box').call(xyzoom);
svg.select('rect.zoom.x.box').call(xzoom);
svg.select('rect.zoom.y.box').call(yzoom);
}
// X value to scale
function X(d) {
return xaxis.scale() !== undefined && xaxis.scale() !== null
? xaxis.scale()(d[0])
: xscale(d[0]);
}
// Y value to scale
function Y(d) {
return yaxis.scale() !== undefined && yaxis.scale() !== null
? yaxis.scale()(d[1])
: yscale(d[1]);
}
var g = svg.append('g')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
g.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width - margin.left - margin.right)
.attr("height", height - margin.top - margin.bottom);
g.append("svg:rect")
.attr("class", "border")
.attr("width", width - margin.left - margin.right)
.attr("height", height - margin.top - margin.bottom)
.style("stroke", "black")
.style("fill", "none");
g.append("g").attr("class", "x axis")
.attr("transform", "translate(" + 0 + "," + (height - margin.top - margin.bottom) + ")");
g.append("g").attr("class", "y axis");
g.append("g")
.attr("class", "scatter")
.attr("clip-path", "url(#clip)");
g
.append("svg:rect")
.attr("class", "zoom xy box")
.attr("width", width - margin.left - margin.right)
.attr("height", height - margin.top - margin.bottom)
.style("visibility", "hidden")
.attr("pointer-events", "all")
.call(xyzoom);
g
.append("svg:rect")
.attr("class", "zoom x box")
.attr("width", width - margin.left - margin.right)
.attr("height", height - margin.top - margin.bottom)
.attr("transform", "translate(" + 0 + "," + (height - margin.top - margin.bottom) + ")")
.style("visibility", "hidden")
.attr("pointer-events", "all")
.call(xzoom);
g
.append("svg:rect")
.attr("class", "zoom y box")
.attr("width", margin.left)
.attr("height", height - margin.top - margin.bottom)
.attr("transform", "translate(" + -margin.left + "," + 0 + ")")
.style("visibility", "hidden")
.attr("pointer-events", "all")
.call(yzoom);
// Update the x-axis
xscale.domain(d3.extent(data, function (d) {
return d[0];
})).range([0, width - margin.left - margin.right]);
xaxis.scale(xscale)
.tickPadding(10);
svg.select('g.x.axis').call(xaxis);
// Update the y-scale.
yscale.domain(d3.extent(data, function (d) {
return d[1];
})).range([height - margin.top - margin.bottom, 0]);
yaxis.scale(yscale)
.tickPadding(10);
svg.select('g.y.axis').call(yaxis);
draw();
}
var exampleChart = example(svg, data);
</script>
</body>
</html>
To put it briefly: How can I solve my problem by using d3v4 to create a chart with multiple axes that has global and independent scaling and panning behaviour ?
as of now the current release of d3v4 doesn't natively support multiple independent zoom behaviours.
A possible solution would be to reset the internal transform state stored inside the selection on which you called the appropriate zoom behaviour.
There is an already open issue on the argument and i encourage you to go and read it and offer your input as well.
Best of luck!
There seems to be no correct solution (see https://github.com/d3/d3-zoom/issues/48).
But if you clear the scale after each zoom it seems to work
(see https://jsfiddle.net/DaWa/dLmp8zk8/2/).
As you can see here element g http://imgur.com/SZImQNB with different browser zoom levels. I would like to get those values 402x398 and 1608x1582 or whatever the values would be while zooming.
var container - is the svg g that I am looking for dimensions of
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.append('rect')
.classed('bg', true)
.attr('stroke', 'transparent')
.attr('fill', 'transparent')
.attr("x", 0)
.attr("y", 0)
.attr('width', w)
.attr('height', h)
.call(d3.behavior.zoom()
.on("zoom", function() {
container.attr("transform", "translate(" + d3.event.translate +
")scale(" + d3.event.scale + ")");
}));
var container = svg.append("g")
.classed("node-area", true)
.attr("id", "test");
I'm using container.node().getBBox().width to get those values and I always receive 70 px width 30 px height no matter what.
console.log("width: "+container.node().getBBox().width+" height: "+container.node().getBBox().height+ "x: "+container.node().getBBox().x+" y: "+container.node().getBBox().y);
I am following d3.js force layout auto zoom/scale after loading TWiStErRob answer
#RobertLongson suggestion does work. In the code, getBoundingClientRect().width has to be used after the transform:
function zoomed() {
container.attr("transform", "translate(" + d3.event.translate
+ ")scale(" + d3.event.scale + ")");
console.log("width: "+container.node().getBoundingClientRect().width);
};
Check the console in this fiddle, while you apply zoom: http://jsfiddle.net/gerardofurtado/tr4kx4af/
As stated in previous questions (for example d3.js Odd Rotation Behavior), when you want to rotate an SVG object on its place you should use
.attr('transform','rotate(45,100,100)')
where the first number is the rotation degrees, and the other two are the coordinates of the rotation origin.
The problem here is that I want to execute the rotation inside a transition:
.transition()
.attr('transform', 'rotate(45,100,100)')
.duration(300)
and I get a strange behaviour, I can see a translation before the expected rotation.
You can see the result here: http://jsfiddle.net/uwM8u/121/
Is there a better way to obtain this?
The D3 way to do this is to use a custom tween function, which in this case is a simple string interpolation:
.attrTween("transform", function() {
return d3.interpolateString(d3.select(this).attr("transform"),
"rotate(" + (rotation+=45) + "," +
(diamond.x+diamond.width/2) + "," +
(diamond.y+diamond.width/2) + ")");
})
Complete demo here.
Add a parent <g> element with the translation so you are actually rotating the shape about the origin.
var svg = d3.select("body")
.append("svg")
.attr("width", 400)
.attr("height", 300);
svg
.append("text")
.text("click the square")
.attr("x", w/2)
.attr("y", w/2)
svg
.append("g")
.attr("transform", "translate(" + diamond.x + "," + diamond.y +")")
.append("rect")
.attr("transform", "rotate(" + rotation + ")")
.attr("x", -diamond.width / 2)
.attr("y", -diamond.width / 2)
.attr("height", diamond.width)
.attr("width", diamond.width)
.attr("fill", "teal")
.on("mousedown", function(){
d3.select(this)
.transition()
.attr("transform", "rotate("+ (rotation+=45) +")")
.duration(300)
});
or as a jsfiddle
I can not make zoom work. Tried everything.
My goal:
zoom on mouse scroll
drag the tree by a mouse
The code is here:
svg = d3.select("#tree-container")
.append("svg").attr("width", width)
.attr("height", height)
.call(zm = d3.behavior.zoom().scaleExtent([1, 3]).on("zoom", redraw))
.append("g")
.attr("transform", "translate(" + 350 + "," + 20 + ")");
jsFiddle
P.S. sorry for spaggeti code
It's the declaration of your redraw function that causes the problem -- if you declare it as a function, it works fine:
function redraw() {
// etc
}
Complete example here.