I am trying to create a real time graph using nvd3.js which would be updated periodically and with the impression that the data is processed in real time.
For now I have been able to create a function which would update the graph periodically but I cannot manage to have a smooth transition between "states" like the line doing a transition to the left.
Here is what I did using nvd3.js , here the interesting code is:
d3.select('#chart svg')
.datum(data)
.transition().duration(duration)
.call(chart);
Now, I have been able to produce what I want using d3.js but I would prefer to be able to use all the tools provided by nvd3.js. Here is what I would like to produce using nvd3
The interesting code for the transition using d3.js is:
function tick() {
// update the domains
now = new Date();
x.domain([now - (n - 2) * duration, now - duration]);
y.domain([0, d3.max(data)]);
// push the accumulated count onto the back, and reset the count
data.push(Math.random()*10);
count = 0;
// redraw the line
svg.select(".line")
.attr("d", line)
.attr("transform", null);
// slide the x-axis left
axis.transition()
.duration(duration)
.ease("linear")
.call(x.axis);
// slide the line left
path.transition()
.duration(duration)
.ease("linear")
.attr("transform", "translate(" + x(now - (n - 1) * duration) + ")")
.each("end", tick);
// pop the old data point off the front
data.shift();
}
You probably want to look at:
D3 Real-Time streamgraph (Graph Data Visualization),
especially the link of the answer:
http://bost.ocks.org/mike/path/
In general, I see two ways to deal with the vertical transition problem:
oversampling having some linear interpolation between the real point, and the smaller the interval between points, the more "horizontal" the vertical transition will look (but the drawback is that you get a lot of "false" points, that may be inacceptable depending on the use of the chart)
extend the chart by adding at the end, and translate the chart on the left, making sure the still available left part is not shown until you remove it (clipping it or doing any other trick), that's best, and the solution mentioned above does that
Related
I'm trying to update my x axis in a D3js bar chart (is partially working) depending on a user filter, the bars are actually changing but is not doing it well. I don't really know where is the problem and I need some help.
in this part of the code I'm updating the bar chart
function update(selectedGroup) {
svg.selectAll("rect").remove()
var groups = d3.map(dataFilter, function(d){return(d.group)}).keys();
x.domain(groups);
var dataFilter = result.filter(function(d){return d.group==selectedGroup});
console.log(dataFilter);
var rectG=rectangulos(dataFilter)
}
the complete bar chart
how is working now:
the result should be something like this
I have an live example here
There is a relatively straightforward reason you are seeing this behavior.
When the domain of the scale x is all the groups x.bandwidth() is small. But when the domain of x is only one value, x.bandwidth() is large. In both cases, the first band starts in the same location.
Next we have a nested scale here xSubgroup - the range of this is equal to x.bandwidth(). When the domain of x changes, we need to update the range of xSubgroup. If we don't do this, the bars will still be very thin and start at the beginning of the axis (as the bars' bandwidth aren't changing even if the group's bandwidth does). You don't update the sub scale's range, but we need to do that:
x.domain(groups);
xSubgroup.range([0, x.bandwidth()])
With this we get the update we're looking for.
But the axis labels remain unchanged. Updating a scale doesn't update the axis unless we explicitly do so. I'll break up your method chaining and store a reference for the g holding the axis:
var xAxis = svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
xAxis.selectAll("text")
...
Now we can update the axis, I'm carrying forward the text styling as well. You can simplify the code by using an update function to do all the entering/exiting/updating of axes and data - here we have some duplication in that both the initial set up and the update function have overlap.
To update the axis we use:
// Call the axis again to update
xAxis.call(d3.axisBottom(x))
xAxis.selectAll("text")
.style("text-anchor", "end")
.attr("font-size", "55px")
.attr("y", "-7")
.attr("x", "-7")
.attr("transform", "rotate(-90)");
Which gives the desired behavior if I understand correctly, updated plunkr
I am working on a sunburst viz based off of Mike Bostock's Zoomable Sunburst example.
I want to be able to change the underlying data using a whole new JSON (which has the same structure but different 'size' values), and have the sunburst animate a transition to reflect the updated data.
If I change the data of the path elements using .data(), and then attempt to update in the following fashion:
path.data(partition.nodes(transformed_json))
.transition()
.duration(750)
.attrTween("d", arcTween(transformed_json));
(..which is pretty much the exact same code as the click fn)
function click(d) {
path.transition()
.duration(750)
.attrTween("d", arcTween(d));
}
..I find that the sunburst does correctly change to reflect the new data, but it snaps into place rather than smoothly transitioning, like it does when you zoom in.
http://jsfiddle.net/jTV2y/ <-- Here is a jsfiddle with the issue isolated (the transition happens one second after you click 'Run')
I'm guessing that I need to create a different arcTween() fn, but my d3 understanding is not there yet. Many thanks!
Your example is quite similar to the sunburst partition example, which also updates data with a transition. The difference is that in this example it's the same underlying data with different value accessors. This means that you can't save the previous value in the data (as that will be different), but need to put it somewhere else (e.g. the DOM element).
The updated tween function looks like this:
function arcTweenUpdate(a) {
var i = d3.interpolate({x: this.x0, dx: this.dx0}, a);
return function(t) {
var b = i(t);
this.x0 = b.x;
this.dx0 = b.dx;
return arc(b);
};
}
This requires, as in the original example, to save the original x and dx values:
.enter().append("path")
.each(function(d) {
this.x0 = d.x;
this.dx0 = d.dx;
});
Complete example here. This one has a kind of weird transition which is cause by the different order of the data in the layout. You can disable that by calling .sort(null), see here.
I'm a complete noop to D3 and partly SVG, so I got a few basic questions.
First off, my code in question can be viewed at http://dotnetcarpenter.github.io/d3-test/ and I've used Simple Pie Chart example with D3.js and Pie Chart Update, II as examples to get a running start.
As you can see, the animation gets skewed in the end when the low path values switch to the higher values. This is obviously not what I want. I think I'm getting the order of calculations wrong but I'm not sure what to do. I'm using the code from the last example:
function change() {
//...
path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
}
// where arcTween is
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
Another issue is placing labels on the sectors. I've put the update stuff in the change function and is able to read out and only render them if the value is between 0 and 100. I can't however place them in any way. Looking at the first example, I figure that I could do something like this:
text.data(data)
.text(setText)
.attr("transform", function (d) {
// we have to make sure to set these before calling arc.centroid
d.innerRadius = 0;
d.outerRadius = radius;
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle") //center the text on it's origin
Where text is a d3 selection and arc is: d3.svg.arc().outerRadius(radius)
But I get "Unexpected value translate(NaN,NaN) parsing transform attribute." warning in Firefox and the labels are written on top of each other.
I appreciate any help and hints. Thanks!
I finally figured it out.
Maintain sector order throughout an animation.
You'd be forgiven for thinking that object contancy had something do with it. I did. But it turns out to be much simpler than that.
Every pie chart is by default sorted by value. If you don't want to sort by value but e.g. by data list order, you just have to disable sorting.
var pie = d3.layout.pie() // get a pie object structure
.value(function(d) { // define how to get your data value
return d.value; // (based on your data set)
})
.sort(null); // disable sort-by-value
Positioning labels according to your chart
Basically, you need to calculate your label positions depending on the type of chart or graph, your trying to connect them to. In my case, it's a pie chart. So if I want d3 to help with the calculations, I need to tell centroid the inner and outer radius and, most importantly to my issue, the start and end angles. The latter was missing from my code. Getting these values is as simple as, calling our pie layout above with our dataset and then do a transform.
Note that you don't have to call .data() again if you created the SVG with d3 and already supplied your data wrapped in .pie() structure. That is, that you didn't select any existing SVG from your page.
var svg = d3.select("svg")
// do stuff with your svg
var pie = d3.layout.pie()
// set stuff on your layout
var text = svg.selectAll("text")
.data(pie(dataset)) // where dataset contains your data
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
});
I have to give credit to Philip Pedruco for helping me along the way.
Bonus info
Use viewBox if you want to position your SVG cross browser, not transform/translate.
// move pie to center
d3.select("svg").attr("viewBox", -radius + ","+ -radius +"," + size + "," + size)
I have implemented a realtime graph with javascript and d3.js. The data is generated randomly and it changes based on the random number. I want to fill the area under the line chart but I do not know how to fill it since the data is moving! The following code are correct for static charts but how I can use it for dynamic moving data
//Css part
.area {
fill: lightsteelblue;
stroke-width: 0;
}
//script
var area = d3.svg.area()
.x(function(d, i) { return x(i); })
.y0(height)
.y1(function(d, i) { return y(d); });
svg.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area);
And that is how my data is generated:
var n = 100,
random = d3.random.normal(0, 50),
data = d3.range(n).map(random);
Thanks,
In order to move the area in real time, you will have to do quite a bit of work. Fortunately Mike Bostock wrote a very good tutorial for path transitions with d3.js.
The key code is:
// push a new data point onto the back
data.push(random());
// redraw the line, and then slide it to the left
path
.attr("d", area)
.attr("transform", null)
.transition()
.ease("linear")
.attr("transform", "translate(" + x(-1) + ")");
// pop the old data point off the front
data.shift();
Also note that you will certainly have to use selections at one point, to do so you can have a look at the following tutorial: A Bar Chart, Part 2.
Add to that the example of area chart that you already use and you are nearly done! The only difference is that you write
Now, you can also get inspiration from the following question: Smooth update to x axis in a D3 line chart?
Finally, here is a jsFiddle that provides you a working example of what you are looking for.
I have the following D3 chart (use 12842311 as input): http://www.uscfstats.com/deltas
After loading the chart, click the grid button (only after loading). A grid will appear. The obvious problem is that the vertical lines are only on the top half of the chart.
This is because D3, as far as I know, only lets you specify an offset for the beginning of the tick mark, which is how I'm doing it, and then doesn't let you specify an ending offset. I also think that you have to do it with tick marks because otherwise you don't know the location of the ticks unless you do something complicated.
How can I get around this limitation and do a grid with vertical lines across the x-axis? It seems like it shouldn't be difficult. Am I even going about the creation of a grid in the right way?
var xAxis = d3.svg.axis().orient("bottom").scale(x).ticks(15);
svg.append("g").attr("class","x axis").call(xAxis).attr("transform","translate(60, " + height + ")");
The easiest way I could get this to work was to manually resize and translate the tick marks.
svg.selectAll(".y line")
.attr("x2", width);
svg.selectAll(".x line")
.attr("y2", height)
.attr("transform", "translate(0," + (-y(0)) + ")");
You can see a working example at http://jsfiddle.net/2y3er/2/.
You can make two sets of tick SVG elements, one extending in the positive direction and the other in the negative direction.
Modify your d3 setup to include another axis or two, but this time include the modified tick parameters in your call method:
Dim xAxis = d3.svg.axis().scale(...).ticks(... // define your xAxis generator
Dim xAxisSVG = d3.selectAll(...)... // and your standared axis
.call(xAxis);
Dim xPositiveTicks = d3.selectAll(...)... // positive grid lines
.call(xAxis.tickSize(height));
Dim xNegativeTicks = d3.selecatAll(...)... // negative grid lines
.call(xAxis.tickSize(-1 * height));
I know that's not terribly thorough, but that method has worked well for me in the past.