Re-render HTML after zoom using Dagre d3 - javascript

Edit
I have found a solution involving using a slightly older version of the dagre-d3 library (4.11). If anyone can find the problem with the latest version, that would help too. Thank you
I'm using Dagre d3 to draw some graphs.
When I initially render my graph, I do
g = new dagreD3.graphlib.Graph()
.setGraph({})
.setDefaultEdgeLabel(function() { return {}; });
var svg = d3.select("svg"),
inner = svg.select("g");
svgGroup = svg.append("g");
var render = new dagreD3.render();
render(d3.select("svg g"), g);
var zoom = d3.behavior.zoom().on("zoom", function() {
inner.attr("transform", "translate(" + d3.event.translate + ")" +
"scale(" + d3.event.scale + ")");
currentZoomScale = d3.event.scale;
currentPosition = d3.event.translate;
});
svg.call(zoom);
Then, when a user clicks on a certain node, I want to append HTML to that node's label. This doesn't show unless I re-render the graph, which I do with the following:
g.node(id).label += "<div>" + inputTemplate + "</div>";
var zoom = d3.behavior.zoom()
.scale(currentZoomScale)
.on("zoom", function() {
inner.attr("transform", "translate(" + d3.event.translate + ")" + "scale(" + d3.event.scale + ")")
});
svg.call(zoom);
d3.select("svg").on("dblclick.zoom", null);
inner.attr("transform", "translate(" + currentPosition + ")" + "scale(" + currentZoomScale + ")");
I thought that by maintaining currentPosition and currentZoomScale I would be able to make sure the graph stays well after zooming and re-rendering. But this is not the case. All my nodes become smaller if I zoom out, and larger if I zoom in.

I'm not crystal clear on the problem but could it be because you have included .scale(currentZoomScale) in the second line of
var zoom = d3.behavior.zoom()
.scale(currentZoomScale)
.on("zoom", function() {
inner.attr("transform", "translate(" + d3.event.translate + ")" + "scale(" + d3.event.scale + ")")
});
svg.call(zoom);
d3.select("svg").on("dblclick.zoom", null);
inner.attr("transform", "translate(" + currentPosition + ")" + "scale(" + currentZoomScale + ")");
and then you're scaling it again in the innner.attr()?

It seems to me that the problem lies here:
var svg = d3.select("svg"),
inner = svg.select("g");
svgGroup = svg.append("g");
If you look at the order of your code, there is no <g> when you define inner. So, at this point, inner is null. When you re-render the chart the group is now there, and inner is no more a null selection.
Thus, a possible solution is just changing the order:
var svg = d3.select("svg"),
svgGroup = svg.append("g");//we first append the <g>..
inner = svg.select("g");//and only after that we select it

Related

D3 force graph doesn't pan or zoom as expected

I've built a D3 force graph largely based on these really helpful examples.
I wanted to add pan and zoom functionality, which I tried to do using another example (looks like I can only include two links, but Google "d3 force zoom eyaler" to find it).
Unfortunately, when I zoom out on a graph that is larger than the initial SVG, I get something like this:
Result of dragging and dropping
Here's the relevant code:
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.behavior.zoom().scaleExtent([0.5,2]).on("zoom", redraw));
function redraw() {
svg.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
How can I change the pan and zoom behaviour so that it scrolls and makes it possible to see the rest of the graph, rather than just allowing me to move the square that was originally visible?
OK, looks like I worked it out... you need to perform the transform on a <g> rather than on the SVG itself. So:
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.behavior.zoom().scaleExtent([0.5,2]).on("zoom", redraw));
var g = svg.append("g"); // add <g>
function redraw() {
g.attr("transform", // perform transform on g, not svg
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
Just putting this here in case anyone else made the same mistake!

d3.behavior.zoom() moves graph to corner instead of center

I want to add a mousewheel zoom to my sunburst:
What I did is I edited this piece of code:
var zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width/2 + "," + height * .52 + ")")
.call(zoom);
function zoomed() {
svg.attr('transform', 'translate(' + d3.event.transform.x + ',' + d3.event.transform.y + ') scale(' + d3.event.scale + ')');
//svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
The problem is that when the page loads everything seems to be ok, however when the mouswheel is used to scroll the sunburst ends up in the upper left corner:
I was able to fix this by changing this code:
function zoomed() {
svg.attr("transform", "translate(" + width/2 + "," + height * .52 + ")scale(" + d3.event.scale + ")");
}
The sunburst stays in the middle now, however this won't zoom to the place of the cursor and won't allow the user to zoom out further.
Any suggestions how I can zoom to the cursor place (as in the first code example) without the sunburst disapering in the upper left corner?
For the full code see JSfiddle, it's not working here, however running it local does work.
d3.event.translate returns an array. Thus, if you want to add those values (width/2 and height/2), you have to add them separately:
function zoomed() {
svg.attr('transform', 'translate(' + (d3.event.translate[0] + width / 2) +
',' + (d3.event.translate[1] + height / 2) + ') scale(' + d3.event.scale + ')');
}
Alternatively, if you want to use your original zoomed function...
function zoomed() {
svg.attr("transform", "translate(" + d3.event.translate +
")scale(" + d3.event.scale + ")");
}
... just break the svg selection, assigning another name for the group:
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var foo = svg.append("g")
.attr("transform", "translate(" + width / 2 + "," + height * .52 + ")")
.call(zoom);
I was using the standard d3.event.translate and d3.event.scale in the Zoomed function, like this:
function zoomed() {
svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
}
It all worked fine except the whole sunburst jumped to the top left when I first scrolled. It corrected itself after I dragged it back to the middle but it was annoying.
I struggled with this for hours. Eventually I solved it by changing the viewBox attribute on the svg:
.attr({viewBox: "" + (-width / 2) + " " + (-height / 2) + " " + width + " " + height})
I got the idea from this example: Basic Sunburst

D3 collapsible tree - jumpy on zoom

I've already spent too much time trying to figure this out.
My goal is to create d3 collapsible tree but for some reason when you zoom it, it moves the tree on position 0,0. I've already seen a few questions with similar problem such as this one d3.behavior.zoom jitters, shakes, jumps, and bounces when dragging but can't figure it out how to apply it to my situation.
I think this part is making a problem but I'm not sure how to change it to have the proper zooming functionality.
d3.select('g').transition()
.duration(duration)
.attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")")
zoomListener.scale(scale);
Here is my code: https://jsfiddle.net/ramo2600/y79r5dyk/11/
You are translating your zoomable g to position [100,100] but not telling the zoom d3.behavior.zoom() about it. So it starts from [0,0] and you see the "jump".
Modify your centerNode function to:
function centerNode(source) {
scale = zoomListener.scale();
// x = -source.y0;
y = -source.x0;
// x = x * scale + viewerWidth / 2;
x = 100;
y = 100;
// 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]); //<-- tell zoom about position
}

Broken d3.js animation in Internet Explorer

I have a d3.js animation that works magnificently in all other browsers except Explorer. The desired effect is for it to be centered while it grows and shrinks, then cycles again, ad infinitum. I made a jsfiddle here. The problem is that the animation goes off the screen as well as loses it's center point in the middle of the screen.
After, comparing the svg in the DOM in IE11 and Chrome, I see that scale() in IE11 only has one value "scale(x)" instead of two "scale(x, y)". I'm sure that's just one of a few issues.
var duration = 5000;
var start = 50,
startScale = 1,
endScale = 10,
startTranslate = 225,
endTranslate = 0;
var startTrans = "scale(" + startScale + "," + startScale + ")translate(" + startTranslate + "," + startTranslate + ")",
endTrans = "scale(" + endScale + "," + endScale + ") translate(" + endTranslate + "," + endTranslate + ")";
d3.select("#slider_td").append("input")
.attr("type", "range")
.attr("id", "slider")
.attr("min", 3000)
.attr("max", 7000)
.attr("value", duration)
.on("change", function () {
duration = this.value;
d3.select("#_rb").property("value", d3.round(duration / 1000));
});
var svg = d3.select("#animation_td").append("svg")
.attr("width", 500)
.attr("height", 500)
.style("background-color", "#ADD9F7");
svg.append("use")
.attr("xlink:href", "#Livello_1")
.attr("width", start)
.attr("height", start)
.attr("transform", startTrans)
.call(transition, startTrans, endTrans);
svg.append("circle")
.attr("cx", 250)
.attr("cy", 730)
.attr("r", 350)
.style("fill", "#99CC00")
.style("stroke", "white");
function transition(element, start, end) {
element.transition()
.duration(duration)
.attr("transform", end)
.each("end", function () {
d3.select(this).call(transition, end, start);
});
}
The problem seems to be that IE is ignoring the width and height attributes on the <use> element.
For comparison, see this version of the fiddle where I've removed those attributes (and also the animation) -- you'll get the same off-center positioning in the other browsers as in IE.
This is a bug, and one I hadn't come across yet. Nor could I find anything on their bug report site.
You might want to put together a simpler example and make the bug report, in the meantime here's a workaround:
The effect of width and height attributes on a <use> element is to scale the referenced graphic to that size. It only has an effect when the referenced graphic is a <symbol> or <svg> with a viewBox attribute.
Therefore, my first guess was that you should be able to make the adjustments yourself by (a) removing the width and height attributes on the <use> element, and (b) adding an additional scale factor:
var basicScale = 50/557.8,
//50px is the desired width
//557.8px is the width and height of the original graphic
startScale = 1,
endScale = 10,
startTranslate = 225,
endTranslate = 0;
var startTrans = "scale(" + startScale + ") translate("
+ startTranslate + "," + startTranslate + ") scale("
+ basicScale + ")",
endTrans = "scale(" + endScale + ") translate("
+ endTranslate + "," + endTranslate + ") scale("
+ basicScale + ")";
http://jsfiddle.net/U9L7w/5/
Unfortunately, while the above example is a significant improvement, it isn't quite right -- the sun and the hill aren't centered one above the other in IE, even though they are with the exact same code in Firefox and Chrome.
It seems that IE doesn't just ignore the width and height attributes, it applies a default value of 100% -- meaning it scales the referenced SVG to be the same size as the parent SVG (500px width and height in your example) before applying any transformations.
So to get things to work nicely in all browsers, you'll need to (a) tell the other browsers to scale to 100% using the width and height attributes, then (b) factor in that scaling factor when you create your new scaling factor:
/* To adjust for a lack of IE support for width and height on <use>,
add in an extra scale transform. For other browsers,
set the 100% width and height explicitly on the <use> element
to match IE's behaviour.
*/
var basicScale = 50/500,
//50px is the desired width
//500px is the width and height of the referencing graphic
startScale = 1,
endScale = 10,
startTranslate = 225,
endTranslate = 0;
var startTrans = "scale(" + startScale + ") translate("
+ startTranslate + "," + startTranslate + ") scale("
+ basicScale + ")",
endTrans = "scale(" + endScale + ") translate("
+ endTranslate + "," + endTranslate + ") scale("
+ basicScale + ")";
/* ... */
svg.append("use")
.attr("xlink:href", "#Livello_1")
.attr("width", "100%")
.attr("height", "100%")
.attr("transform", startTrans)
.call(transition, startTrans, endTrans);
http://jsfiddle.net/U9L7w/6/
It's a rather round-about way of getting to the end result for browsers that implement the specs properly, but you get the desired result in both them and in IE.

D3 transitions , less cpu usage?

I'm currently using d3 transitions to animate graphs. Unfortunately these transitions make the page to redraw continuously, as a result cpu is always around 100%.
d3Element.attr("transform", "translate(" + this.someDistance + ")")
.attr("d", linePath)
.transition()
.ease("linear")
.duration(animationDuration)
.attr("transform", "translate(" + 0 + ")");
I have already found a simple solution to this problem and I will share it with you but I would like to know if there is any better way to do it with d3.
What I'm actually doing to reduce cpu usage is to limit the frames per second.
This is a part of the code :
var start = d3.transform( "translate(" + this.startPoint.x + "," + this.startPoint.y + ")");
var stop = d3.transform("translate(" + this.stopPoint.x + "," + this.stopPoint.y + ")");
var interpolate = d3.interpolateTransform(start,stop);
var animation_interval = window.setInterval(function(){
frame++;
// Get transform Value and aply it to the DOM
var transformValue = interpolate(frame/(self.fps * self.duration));
self.d3Selector.attr("transform", transformValue);
// Check if animation should stop
if(frame >= self.fps * self.duration || self.stopFlag){
window.clearInterval(animation_interval);
return;
}
},intervalTime);

Categories