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);
Related
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
}
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
I'm building a map plotting tool with D3. I'm using this example.
Everything works but I want to draw each point with a 10ms difference, like its drawing.
I tried to make an interval but didn't work. I also was thinking to make css animation and each point to have an animation-delay but that doesn't seem work well.
Can someone explain to me how to draw the data one by one?
function redrawSubset(subset) {
var radius = 2;
var bounds = path.bounds({ type: 'FeatureCollection', features: subset });
var topLeft = bounds[0];
var bottomRight = bounds[1];
var start = new Date();
var points = g.selectAll('path')
.data(subset, function(d) {
return d.id;
});
path.pointRadius(radius);
svg.attr('width', bottomRight[0] - topLeft[0] + radius * 2)
.attr('height', bottomRight[1] - topLeft[1] + radius * 2)
.style('left', topLeft[0] + 'px')
.style('top', topLeft[1] + 'px');
g.attr('transform', 'translate(' + (-topLeft[0] + radius) + ',' + (-topLeft[1] + radius) + ')');
points.enter().append('path');
points.exit().remove();
points.attr('d', path);
}
It is possible to render circle by circle, but it's a little complicated. Maybe a workaround is to draw all of them transparent, and setting the opacity to 1 with a delay of 10ms:
points.enter().append("path").attr("opacity", 0)
.transition()
.duration(10)
.delay(function(d,i){ return i*10})
.attr("opacity", 1);
Here is your plunker: http://plnkr.co/edit/ys4VofukQOvA4pY0TQX7?p=preview
I am looking for an example for to rotate a pie chart on mouse down event. On mouse down, I need to rotate the pie chart either clock wise or anti clock wise direction.
If there is any example how to do this in D3.js, that will help me a lot. I found an example using FusionChart and I want to achieve the same using D3.js
Pretty easy with d3:
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color(d.data.age);
});
var curAngle = 0;
var interval = null;
svg.on("mousedown", function(d) {
interval = setInterval(goRotate,10);
});
svg.on("mouseup", function(d){
clearInterval(interval);
})
function goRotate() {
curAngle += 1;
svg.attr("transform", "translate(" + width / 2 + "," + height / 2 + ") rotate(" + curAngle + "," + 0 + "," + 0 + ")");
}
Working example.
I did a similar thing with a compass instead of pie chart. You mainly need three methods - each bound to a different mouse event.
Bind this to the mousedown event on your compass circle:
function beginCompassRotate(el) {
var rect = compassCircle[0][0].getBBox(); //compassCircle would be your piechart d3 object
compassMoving = true;
compassCenter = {
x: (rect.width / 2),
y: (rect.height / 2)
}
}
Bind this to the mouse move on your canvas or whatever is holding your pie chart - you can bind it to the circle (your pie chart) but it makes the movement a little glitchy. Binding it to the circle's container keeps it smooth.
function rotateCompass() {
if (compassMoving) {
var mouse = d3.mouse(svg[0][0]);
var p2 = {
x: mouse[0],
y: mouse[1]
};
var newAngle = getAngle(compassCenter, p2) + 90;
//again this v is your pie chart instead of compass
compass.attr("transform", "translate(90,90) rotate(" + newAngle + "," + 0 + "," + 0 + ")");
}
}
Finally bind this to the mouseup on your canvas - again you can bind it to the circle but this way you can end the rotation without the mouse over the circle. If it is on the circle you will keep rotating the circle until you have a mouse up event over the circle.
function endCompassRotate(el) {
compassMoving = false;
}
Here is a jsfiddle showing it working: http://jsfiddle.net/4oy2ggdt/
I have a D3js map built with topojson.js.
var projection = d3.geo.mercator();
Everything works fine, but I am looking for an effect that I cannot manage to achieve. When, zooming the map I would like the pins over it to scale down, But if I scale it down, I need to recalculate their coordinates and I can't find the formula to do so.
Zoom handler
scale = d3.event.scale;
if (scale >= 1) {
main.attr("transform", "translate(" + d3.event.translate + ")
scale(" + d3.event.scale + ")");
}
else {
main.attr("transform", "translate(" + d3.event.translate + ")scale(1)");
}
//43x49 is the size initial pine size when scale = 1
var pins = main.select("#pins").selectAll("g").select("image")
.attr("width", function () {
return 43 - (43 * (scale - 1));
})
.attr("height", function () {
return 49 - (49 * (scale - 1));
})
.attr("x", function () {
//Calculate new image coordinates;
})
.attr("y", function () {
//Calculate new image coordinates;
});
My question is : how do I calculate x and y based on new scale?
I hope I am clear enough.
Thanks for your help
EDIT :
Calculation of initial pins coordinates :
"translate(" + (projection([d.lon, d.lat])[0] - 20) + ","
+ (projection([d.lon, d.lat])[1] - 45) + ")"
-20 and -45 to have the tip of the pin right on the target.
You need to "counter-scale" the pins, i.e. as main scales up/down you need to scale the pins down/up, in the opposite direction. The counter-scale factor is 1/scale, and you should apply it to each of the <g>s containing the pin image. This lets you remove your current width and height calculation from the <image> nodes (since the parent <g>'s scale will take care of it).
However, for this to work properly, you'll also need to remove the x and y attributes from the <image> and apply position via the counter-scaled parent <g> as well. This is necessary because if there's a local offset (which is the case when x and y are set on the <image>) that local offset gets scaled as the parent <g> is scaled, which makes the pin move to an incorrect location.
So:
var pinContainers = main.select("#pins").selectAll("g")
.attr("transform", function(d) {
var x = ... // don't know how you calculate x (since you didn't show it)
var y = ... // don't know how you calculate y
var unscale = 1/scale;
return "translate(" + x + " " + y + ") scale(" + unscale + ")";
})
pinContainers.select("image")
.attr("width", 43)
.attr("height", 49)
// you can use non-zero x and y to get the image to scale
// relative to some point other than the top-left of the
// image (such as the tip of the pin)
.attr("x", 0)
.attr("y", 0)