This question builds on Lars Kotthoff's very helpful answer to the problem of transitioning with a D3 sunburst diagram that is based on different JSON data: d3 - sunburst - transition given updated data -- trying to animate, not snap
I've tried the final fiddle that Lars provided but when there is more than one transition, the animation still fails and we get snapping. The problem can be seen in this updated fiddle that contains a second transition.
From what I can tell, the x0 and dx0 values are not properly stored with the path object when calling the arcTweenUpdate function. When I check what the this object looks like inside arcTweenUpdate function, I get an [object SVGPathElement] at the beginning of the function when this.x0 and this.dx0 are read, and I get an [object Window] when the new values are written later. I'm relatively inexperienced with JS but that seemed like it could point to the problem.
Any help with addressing this and making the above fiddle work for multiple transitions (e.g. back and forth between the two JSONs) is highly appreciated. Thanks!
Well you've spotted a bug in my earlier answer :)
The problem is, as you say, that the saved values aren't updated properly. That's because this inside the callback doesn't refer to the path DOM element anymore. The fix is simple -- save a reference to this in the function at the level above and use that reference:
function arcTweenUpdate(a) {
var i = d3.interpolate({x: this.x0, dx: this.dx0}, a);
var that = this;
return function(t) {
var b = i(t);
that.x0 = b.x;
that.dx0 = b.dx;
return arc(b);
};
}
Complete demo here.
Related
I am trying to create a 3d "chart" using d3 data-binding and x3dom.
The data-binding seems to work correctly and I am able to generate HTML which looks correct, but x3dom does not show the expected scale (as in the size of the shape is not scaled at all).
The code is fairly simple. First, I bind data points to transforms:
var t = scene.selectAll('transform').data(data);
var transform = t.enter();
var shape = transform.append('transform')
.attr('translation', function (d, i) {
...
}).append('shape');
Within each transform element created for a data-point, I use this code to create a box:
shape.append('box').attr('size', '1, 1, 1');
Then, I expect to scale the boxes later using the data, using a transition:
t.transition().attr('scale', function (d) {
return "1.0 1.0 " + d.size;
});
This only works if I run the whole function that does the above twice, the second time in a setInterval() function!
I am a newbie, so I am certainly missing something here, but the code does generate what I expect and does work if run twice, for whatever reason. So, why doesn't it just work first time?
Note: I could of course just set the size directly instead of using an animation to do that later by scaling the shapes, but that also didn't work. It seems that the shapes never get the correct size using either technique, unless I run them twice.
I'm creating a simple bar chart and trying to make it respond to user clicks. The clicked bar is supposed to disappear. All seems to be working except clicking on the first bar makes a bar at the end disappear. I'm completely at a loss to why this is the case and would really appreciate any help.
Complete Code on Plunkr:
https://plnkr.co/edit/H8K0ISdhGrb5HirrX2MG?p=preview
I call the update function when a user clicks on a bar. I created a removefromarray function to return the data object minus data bound to the clicked bar. :
d3.tsv("CantTouchThis.tsv",function(d,i){
d.FieldGoals = +d.FieldGoals;
return d;
}, function(error,data){
if (error) throw error;
y.domain([0,d3.max(data, function(d){return d.FieldGoals})]);
x.domain(data.map(function(d){return d.Player}));data used as the x attribute
function update(indx){
var selection = g.selectAll(".bars")
.data(data.removefromarray(indx), function(d){console.log('d');console.log(d); return d}) //printing d shows the previous bars and new bars are being returned, I suspect this may be causing the problem, but not sure
selection.enter().append("rect")
.attr("class","bars")
.attr("width",function(d){return x.bandwidth()})
.attr("x",function(d){return x(d.Player)})
.attr("height",function(d){return height - y(d.FieldGoals)})
.attr("y",function(d){return y(d.FieldGoals)})
.on("click",function(d,i){update(i);});
console.log(selection.enter())
console.log(selection.exit())
selection.exit().remove()
}
Solution 1: You're missing an "update" selection:
selection.attr("width",function(d){return x.bandwidth()})
.attr("x",function(d){return x(d.Player)})
.attr("height",function(d){return height - y(d.FieldGoals)})
.attr("y",function(d){return y(d.FieldGoals)})
.on("click",function(d,i){update(i);});
Here is your updated plunker: https://plnkr.co/edit/qJY5KgY9FBvuJ8krddYm?p=preview
Solution 2: another very simple solution (that correctly addresses your question, "why doesn't first bar disappear using exit method?"): use a proper key in the data binding selection:
var selection = g.selectAll(".bars")
.data(data.removefromarray(indx), function(d){ return d.Player});
// this is the proper key function ---^
However, have in mind that this "solution" will not work for all clicks. That happens because your method for removing the data object (using splice in an extended prototype) is not working correctly.
Here is another updated plunker: https://plnkr.co/edit/rVTinxx45nmBvFiWwqgg?p=preview
PS: there are way easier ways to do what you want (and more adequate to a D3 code also).
Yesterday Plotly release the new feature animation!!! So I was very eager to test this out, and with the current lack of documentations (temporary I suppose) I'm struggling quite a bit on how to use it.
I did have a peek into the code on GitHub, but ... not working.
I define my div element in the template:
<div id="plotDiv"> </div>
I wanted to have the plot responsive to resize events, thus I followed the example on the plotly website:
const d3 = Plotly.d3;
const gd3 = d3.select("#plotDiv")
.style({width: "95%", "margin-left": "2.5%"});
const gd = gd3.node();
Then generate the data (bits of angular magic and other things) but in the end it looks like this:
data = {x: [array_ot_dates], y: [array_of_not_so_random_values], type:'bar'};
According to the jsdocs for the animation function, you need to pass a frame:
let plotlyFrame = {data:data, layout:{}};
Try to call animation!!!
Plotly.animate(gd, plotlyFrame);
And there it goes Kaboum!
First error is: This element is not a Plotly plot: [object HTMLDivElement]
But when I try this:
Plotly.newPlot(gd, data, {});
I have my plot...
So I tried to "predefine" gd by calling Plotly.plot with empty data and then the animate function...
Plotly.plot(gd, [], {});
// make my data not empty
Plotly.animate(gd, plotlyFrame);
And then I get the following error:
plotly.js:116922 Uncaught (in promise) TypeError: Cannot read property '_module' of undefined(…)
Possibly the second could come from the fact I'm using angular and thus calling the function at one point 3 times in a close row.
Any advices? Ideas?
I'm the person who worked on the animation feature! First of all, you can find the documentation here.
Regarding your specific question, it looks like the answer is that you need to initialize the plot before you animate it (I'll add this to the documentation!). For example:
var frame = [{
data: {
y: [...new values...]
}
}]
Plotly.plot(gd, [{x: [...], y: [...]}]).then(function() {
Plotly.animate(gd, frame)
});
If some sort of user input is triggering the animation, the promise probably isn't necessary (since the input event will handle things and is pretty unlikely to get fired before Plotly.plot has had a chance to initialize).
Additionally, at the very least I believe you'll need to actually initialize the plot with the trace you wish to animate. I think you can probably get away with empty data, but you'll still need to at least have a trace defined, e.g. Plotly.plot(gd, [{x: [], y: []}]). I think the issue with your attempted fix is that [] for a list of traces is still empty as far as Plotly is concerned since that tells it nothing about the type of trace that exists. Also FYI, one thing the feature is not really designed to do is to draw the initial plot in a fancy manner. That could likely be accomplished, but it's not automatic given animate on an empty plot.
I hope that's enough to clarify issues! It was a pretty large feature, so we'd love feedback and to hear about successes/failures with it!
Based on this work: http://bl.ocks.org/mbostock/7882658
If I substitute the automatic nodes creation by a JSON.stringify() output of the automatically generated data like this...
var nodes = [
{"cluster":2,"radius":1.6180680659922448},
{"cluster":0,"radius":3.3575295077569},
{"cluster":1,"radius":0.9569281165554346},
{"cluster":3,"radius":10.7245554165012}
];
...I get an exception "cannot read property x of undefined" on the line:
var x = d.x - cluster.x,
This is inside the cluster(alpha) function. So, apparently the d3.map function that automatically generates the data is putting something in the structure that the JSON stringification has not caught? Maybe I am just overlooking something simple...help is appreciated. Thanks! Here is a fiddle to help out: http://jsfiddle.net/Nivaldo/FJ3qq/1/
I commented out the code that is not working. Also, another detail, it does not seem like the original code as i left it (except that i reduced the count of clusters and nodes) is actually handling the right number of distinct clusters. It should paint 4 different ones but is only painting with 3 colors.
The problem is that nodes is not the only data structure that needs to be initialised -- clusters needs to be as well. In particular, specific nodes are assigned to specific cluster indices. If you don't do that, things will break.
To fix, do something like
nodes.forEach(function(d) { clusters[d.cluster] = d; });
Complete jsfiddle here.
I want to move nodes in Sigma JS on click event. Say from position x 0.7 to position x -0.7.
Is it possible to move nodes in Sigma js, and if it is, kindly guide me to achieve that.
Yes, it is possible. I created a jsfiddle showing how to change node position, size, and color on mouse click here:
http://jsfiddle.net/kaliatech/P255K/
You can bind to custom "downnodes" events like this:
sigInst.bind('downnodes',onNodeDown);
The event callback contains an array of selected node ids. I don't know when it would be more than one when clicking. Perhaps when zoomed out in a complex graph.
One of the more subtle issues when using sigmajs, is that most methods, such as getNodes, return clones, not the instances themselves. This is to protect "private" data in the graph I think, especially data that can not be redrawn after initialization. To actually modify properties, you need to use the iterator methods. Even then, you are only given clones. The library updates the actual node instances using a list of predefined allowable properties. (See the checkNode function in graph.js).
After properties have been set, you then need to refresh/redraw the graph. While the "refresh" method would seem to be an obvious candidate, it did not work. I was able to get it to redraw using the draw method though. You will need to review the source code to understand the different parameters. Example:
function onNodeDown(evt) {
var sigmajs = evt.target;
var nodeId = evt.content[0];
sigmajs.iterNodes(function(n){
n.size = 10;
n.color = "#0000FF";
},[nodeId]);
sigmajs.draw(2, 2, 2, true);
};
For more advanced needs, the sigmajs website includes plugin examples showing other ways of getting mouse events and updating nodes dynamically. One is the advanced plugin example for a fisheye effect:
http://sigmajs.org/examples/a_plugin_example_advanced.html
Another is the example for accessing node attributes:
http://sigmajs.org/examples/more_node_info.html
The sigmajs documentation is weak, so you will need to review the source code to understand things.
There are plugins permitting to move isolated nodes from the graph.
Check
https://github.com/Linkurious/linkurious.js/blob/develop/examples/lasso.html