Broken d3.js animation in Internet Explorer - javascript

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.

Related

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.js resize and drag

I'm drawing a tree with D3. When the page is resized I want to move the tree to a specific position (basically on the space left for the svg so the tree stay visible).
I use this code
function resize() {
svg
.attr("width", getWidth())
.attr("height", getHeight());
svg.attr("transform", "translate(" + (window.innerWidth / 3) + "," + ((window.innerHeight / 3)) + ")" + " scale(1)");
var zoom = d3.behavior.zoom();
zoom.translate([window.innerWidth / 3, window.innerHeight / 3]);
}
d3.select(window).on('resize', resize);
This works fine, but when I start clicking to drag the graph, it's moved to the original position as starting drag point.
This is the drag function:
function redraw() {
if (!d3.event.sourceEvent) return;
d3.event.sourceEvent.stopPropagation();
svg.attr("transform",
"translate(" + d3.event.translate + ")" +
" scale(" + d3.event.scale + ")");
}
var svg = d3.select("#graph").append("svg")
.attr("id", "graph")
.attr("width", getWidth())
.attr("height", getHeight())
.call(zm = d3.behavior.zoom().on("zoom", redraw)).on("dblclick.zoom", null)
.append("g")
.attr("transform", "translate(" + initialWidth + "," + initialHeight + ")");
Any hint on how to solve this?
Make zoom a global variable like this:
var zoom = d3.behavior.zoom().on("zoom", redraw);
and use it in resize and zoom for svg as shown below.
Change 1:
var svg = d3.select("#graph").append("svg")
.attr("id", "graph")
.attr("width", getWidth())
.attr("height", getHeight())
.call(zoom).on("dblclick.zoom", null)
.append("g")
.attr("transform", "translate(" + initialWidth + "," + initialHeight + ")")
Change 2:
function resize() {
svg
.attr("width", getWidth())
.attr("height", getHeight());
svg.attr("transform", "translate(" + (window.innerWidth / 3) + "," + ((window.innerHeight / 3)) + ")" + " scale(1)");
zoom.translate([window.innerWidth / 3, window.innerHeight / 3]);
}

Re-render HTML after zoom using Dagre d3

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

How to pan to a node using d3's force layout

I would like to focus on a node when it is searched for by name. I am trying to do this using a recenter method....
zoom = d3.behavior.zoom()
.scaleExtent([.05, 10])
.on("zoom", zoomed);
svg = d3.select("#graph")
.append("svg")
.attr("height", height)
.attr("width", width)
.call(zoom);
....
function zoomed(sel) {
zoomBase(d3.event.translate , d3.event.scale);
}
function zoomBase(translate, scale){
zoom.scale(scale);
zoom.translate(translate);
container.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
}
function recenter(node){
var node = findNodeByName(node);
if(zoom.scale() < 0.5){
zoom.scale(0.5);
}
zoom.translate([node.x, node.y]); // Math seems to be wrong here
container.attr("transform", "translate(" + translate + ")scale(" + zoom.scale() + ")");
}
The problem is that when I am over the node in question and search for it my location shows up as [-2246.3690822841745, -846.6411913027562] but when I get the x and y off of the actual node I get [4346.868560310511, 1950.790521658118] considering I am over top of the node, is there some math or something I need here?
Lars was right this is the answer...
zoom.translate([width / 2 - zoom.scale() * node.x, height / 2 - zoom.scale() * node.y])
To break this doesn a bit
width / 2 (go to middle)
-
zoom.scale() * node.x (move middle to the scaled x)

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