set zoom after each redraw - d3.js

I am using 'dagre d3' for displaying dependency graph. I also use slider(which I use to display step by step evolution of graph when user slides the slider) so each time Slider is moved, graph is redrawn. For the first time, 'zoom' feature will be set like below:
var zoom = d3.behavior.zoom().on("zoom", function () {
inner.attr("transform", "translate(" + d3.event.translate + ")" +
"scale(" + d3.event.scale + ")");
});
svg.call(zoom);
I want to retain the zoom level if the user has zoomed in or zoomed out the graph and then moved the slider. So make it work, I did the following.
this method will be called whenever user slides the slider.
function redrawGraphToStage(stageToRedraw) {
// Not sure how to get previous values. below one works but feel its bit hacky.
if(inner[0][0].transform.animVal.length){
tempInnerTranslate = [inner[0][0].transform.animVal[0].matrix.e, inner[0][0].transform.animVal[0].matrix.f];
tempInnerScale = inner[0][0].transform.animVal[1].matrix.a;
}
// do modifications to graph. and re-render.
if(tempInnerScale || tempInnerTranslate){
// this will set up the zoom level to before.
zoom.translate(tempInnerTranslate).scaleExtent(tempInnerScale, tempInnerScale).event(svg);
}
Even though it setup the zoom level to previous values, I wont be able to zoom after this. Any suggestions would be great.

Try with,
var zoom = d3.behavior.zoom().on("zoom", zoomed);
svg.call(zoom);
function zoomed() {
inner.attr("transform",
"translate(" + zoom.translate() + ")" +
"scale(" + zoom.scale() + ")"
);
}
function redrawGraphToStage(stageToRedraw) {
// Not sure how to get previous values. below one works but feel its bit hacky.
if(inner[0][0].transform.animVal.length){
tempInnerTranslate = [inner[0][0].transform.animVal[0].matrix.e, inner[0][0].transform.animVal[0].matrix.f];
tempInnerScale = inner[0][0].transform.animVal[1].matrix.a;
}
// do modifications to graph. and re-render.
if(tempInnerScale || tempInnerTranslate){
// this will set up the zoom level to before.
zoom.translate(tempInnerTranslate).scale(tempInnerScale, tempInnerScale);
zoomed();
}
I'm not sure what you're using the slider for, but I made you a demo where you can control the zoom scale with a slider bar. Significant change: scaleIntent replaced with scale.
JSFiddle working demo

Related

Zoom in zoom out in d3 js collapsible tree

I am using this working code as a starter. I am trying to modify according to my data and need.
The only thing I ain't getting is why this zoom in zoom out not working. I tried this solution but got no luck.
I ain't able to find where the problem is within the code but I can see the comment where it stated that the functionality is deactivated. How can I make it activated? Sorry if this is a silly question I am still a newbie in js. There are similar questions too but the thing is jsfiddle are not working anymore so I can't see the whole code to understand the logic.
// Zoom functionnality is desactivated (user can use browser Ctrl + mouse wheel shortcut)
function zoomAndDrag() {
//var scale = d3.event.scale,
var scale = 1,
translation = d3.event.translate,
tbound = -height * scale,
bbound = height * scale,
lbound = (-width + margin.right) * scale,
rbound = (width - margin.left) * scale;
// limit translation to thresholds
translation = [
Math.max(Math.min(translation[0], rbound), lbound),
Math.max(Math.min(translation[1], bbound), tbound)
];
d3.select('.drawarea')
.attr('transform', 'translate(' + translation + ')' +
' scale(' + scale + ')');
}
Here is the working demo with the code:
https://blockbuilder.org/ninjakx/2c4e726a531b40f9f09d7df41217d1de
D3 uses the Mouse Wheel event for zoom in/out functionality.
Your sample disables the mouse wheel event in this line:
d3.select('#tree-container').select('svg').on(mouseWheelName, null);
Also, the demo uses version 3 of D3, I strongly recommend to use the latest version of the library (V5).

Zooming works only when hovering on the dots in scatterplot

I am trying to implement a scatter plot with zooming capability with D3. I have generated the plot and the visualization,it can zoom in and out only when the mouse cursor is over the data points. In other words, when the mouse is on anywhere other than the dots, I am unable to zoom.
I have been searching and trying a number of examples from the Internet, but I had no luck. Please check the code residing in this link: https://github.com/e-kaya/scatter.git What do you think I have done wrong?
Thanks in advance.
You are calling the zoom function on the .dots.
svg.select(".dots").call(zoom);
Try binding the zoom function to the <svg>.
d3.select('svg').call(zoom);
depending on your requirement you may have to tweak the zoomed function as well.
function zoomed() {
d3.select('svg).attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
This will zoom your svg container.

Graph not redrawing properly

I'm currently trying to combine two of my graphs into one view. My second graph, the bar chart will update the data if you use the slider. When you slide it forward, it works fine and dandy but when you slide it backwards it doesn't redraw the chart. When I was working on the bar chart separately it was fully working, it just seems that when I combine it with the line graph that it's only partially working.
I'm trying to find the cause of the problem and I think it's something wrong with this block of code. If I comment it out the bar graph redraws properly.
var chart1 = d3.select("body").append("svg")
.attr("width", chart1_width + chart1_margin.left + chart1_margin.right)
.attr("height", chart1_height + chart1_margin.top + chart1_margin.bottom)
.append("g")
.attr("transform", "translate(" + chart1_margin.left + "," + chart1_margin.top + ")");
Link to my fiddle: http://jsfiddle.net/flyingburrito/a8fym1ma/3/
Thanks!
I moved the bar chart on top and that made it work fine. An easy solution would be to keep it like that.
A better solution would be to analyse why that happens. With D3 when things happen like this it will often be in your selects. That is your problem here
d3.select("#sexYear").on("input", function () {
debugger;
d3.select("svg").selectAll("rect").remove();
update(this.value);
});
You are selecting 'svg' which selects the first svg which is the line chart, not the bar chart. This is because 'd3.select' selects the first of whatever is being called. What I did was add a class called 'barchart' to the barchart and then I select the barchart as such
var chart2 = d3.select("body").append("svg")
.attr('class','barchart')
and
d3.select("#sexYear").on("input", function () {
debugger;
d3.select(".barchart").selectAll("rect").remove();
update(this.value);
fiddle

D3 transition end events not firing correctly?

This should be straightforward: I just want to start a D3 transition running when the user clicks on a button.
However, the D3 end event does not seem to be firing correctly on my transitions: I've asked for a transition to rotate(120), but the end event seems to be firing when the rotate attribute is only 45.
This is my code:
var angle = 0;
// handle click event
d3.selectAll('.mycolour').on('click', function() {
d3.select("#loadingicon").remove();
// redraw the icon (excluded here for brevity)...
// setting the transform attribute to 0 explicitly..
group2.attr("transform", "rotate(0,0,0)");
// and start it turning:
angle = 0;
function rotateLoadingIcon() {
angle = angle%360;
angle += 120;
console.log('rotateLoadingIcon', angle, group2.attr("transform"));
d3.select(".icon").transition().ease("linear")
.duration(4000)
.attr("transform", "rotate(" + angle + ",0,0)")
.each("end", function() {
angle += 120;
console.log('innerRotateLoadingIcon', angle, group2.attr("transform"));
group2.transition().ease("linear")
.duration(4000)
.attr("transform", "rotate(" + angle + ",0,0)")
.each("end", function() {
rotateLoadingIcon();
});
});
}
console.log('about to start icon loading');
rotateLoadingIcon();
});
It works the first time the icon is drawn:
rotateLoadingIcon 120 rotate(0,0,0)
innerRotateLoadingIcon 240 rotate(119.99999999999999)
But after second or subsequent clicks, I've seen it produce console output like this:
about to start icon loading
rotateLoadingIcon 120 rotate(0,0,0)
innerRotateLoadingIcon 240 rotate(45.14999999999999)
In practice what this means is that the direction of rotation changes :(
Why has innerRotateLoadingIcon started, when the rotate attribute is only set to 45.1...? Surely given the code, it shouldn't start until rotate reaches 120 - as in the first time the code is run.
I'm wondering if the way I have set up the JavaScript means that two different versions of rotateLoadingIcon could be running at the same time. Is this possible, and can I fix it if so?
Update: Here's a JSFiddle that demonstrates the problem, though I'm now trying setInterval rather than end - click twice and you'll see the rotation change direction, I can't figure out why: http://jsfiddle.net/QtBvJ/
In the version in your jsfiddle, you only need to clear the timeout properly to make it work. The function setTimeout returns a reference that you need to pass to window.clearTimeout, i.e.
var t = null;
...
window.clearTimeout(t);
...
t = setTimeout(...);
Working fiddle here.

D3 Redraw Brush

I have a problem that I haven't been able to solve for a number of weeks. I'm working on a modified version of this example: http://bl.ocks.org/mbostock/1667367 I've defined the brush initially so it has a brush extent between 0.5 and 0.8.
var brush = d3.svg.brush()
.x(x2)
.extent([0.5, 0.8])
.on("brush", brushed);
The brush selection shows up (on the context graph) in the correct location, but the initial view of the focus area is still set to the extent of the entire data set (and not to the clipping area of the brush). I've read that defining the brush doesn't automatically force a redraw of the area, but I can't seem to figure out how to make the view of the focus area automatically scale to the brush extents. Can someone please provide some input on this?
Update 1
I currently have a function called Brushed which does the following:
function brushed() {
x.domain(brush.empty() ? x2.domain() : brush.extent());
focus.select("path").attr("d", Light_area);
focus.select(".x.axis").call(xAxis);
Light_line_overlay.select("path").attr("d", Light_area);
rules.select(".y.grid").call(make_x_axis_light()
.tickSize(-height, 0, 0)
.tickFormat("")
);
var xx0=brush.x()(brush.extent()[0]);
var xx1=brush.x()(brush.extent()[1]);
brushfill.attr("x", xx0);
brushfill.attr("width", xx1-xx0);
}
It's slightly different from the example... because I've been modifying it to do different things from the base example. However, the first comment suggests that I should just call this brushed function after declaring the brush (see first post). However, calling this function doesn't do anything (or at least, it doesn't update the focus area to the extents of the brush). Do you have any suggestions?
I apologize for answering this two years late but I just ran in to the same situation and this was the only resource I found on the topic. I was able to figure it out, so hopefully this will help anybody else who stumbles upon it.
The code in the original question was almost all the way there, it just didn't have the right scaling on the extent initialization.
The data I'm using is an array of objects with a ts key (which is epoch milliseconds) that I use for my x values.
// These are needed for the brush construction to know how to scale
x2.domain(x.domain());
y2.domain(y.domain());
// Pick out the ~50% and ~80% marks from the data
var N = data.length;
var cx0 = new Date(data[Math.floor(N*0.50)].ts);
var cx1 = new Date(data[Math.floor(N*0.80)].ts);
// Construct with that extent, which will leave the
// context box started in the correct position.
var brush = d3.svg.brush()
.x(x2)
.extent([cx0, cx1])
.on("brush", brushed)
;
// This is just the original brushed example
function brushed() {
x.domain(brush.empty() ? x2.domain() : brush.extent());
focus.select(".area").attr("d", line);
focus.select(".x.axis").call(xAxis);
}
...
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")")
;
// Now that focus is defined we can manually update it
brushed();
I actually kept the call to brushed at the very end of the setup, just to keep things pretty, but the point here was just to illustrate that once focus is defined you can call brushed to do whatever updates you want there.
Ultimately it seems your main issue was getting the right type for the extent. Using [0.5, 0.8] worked on initialization, but if you check whenever brushed is called from actually sliding the focus around with the mouse, brush.extent() will be [Date(), Date()]. And that makes sense, since we're passing that extent to x.domain. So this sets up all the scaling before initializing the brush, so that the initialization extent can be a Date, then everything else is gravy.
You need to perform actions similar to the ones of the brushed function whenever your brush extent is changed programmatically. Resize the x.domain, refresh the view.
function brushed() {
x.domain(brush.empty() ? x2.domain() : brush.extent());
focus.select("path").attr("d", area);
focus.select(".x.axis").call(xAxis);
}
If that doesn't solve your problem, consider providing some code example.

Resources