I encapsulated d3 charts into function as suggested best practice from creator in blog Towards Reusable Charts. Is it possible to create optional functionalities on top of this chart, so calling specific function would trigger it, otherwise it would be omitted.
Working JSFiddle example (base working example from Rob Moore's blog)
In JS line 56 I added a function which I'd like to create and then conditionally call in line 67.
My current way of doing it, is creating a boolean and setting it to false and then calling function with argument true. Problem of doing it this way is that the code gets too many conditionals and edge cases.
P.S. this question is not meant to be a discussion how to correctly apply axis to the chart. This is just an example.
I think it's better to add additional functionalities after the chart is drawn
var runningChart = barChart().barPadding(2).fillColor('coral');
d3.select('#runningHistory')
.datum(milesRun)
.call(runningChart);
runningChart.x_axis(); // additional functionality
So that the original chart container can be saved in a variable and it can be used to append other functionalities. For example
function barChart() {
var charContainer;
function chart(selection){
charContainer = d3.select(this).append('svg')
.attr('height', height)
.attr('width', width);
}
chart.x_axis = function() {
// Add scales to axis
var x_axis = d3.axisBottom()
.scale(widthScale);
charContainer.append('g').call(x_axis);
return chart;
}
}
If there is any need to add additional functionality before the chart is drawn, then all the functionalities can be saved in a Javascript object and drawn like in this example. https://jsfiddle.net/10f7hdae/
I want to achieve something like a growing arc which indicates 5 levels (see picture). My data has only an integer value which is between 1-5. You can ignore the icon in the middle for now. Is there any possibility to achieve something like that in d3? I couldn't find any example for this. Moreover I tried it with a cut off pie (donut) chart approach, but I couldn't make the growing arc... I would appreciate any help! Thanks for that.
You can do this with d3 without dependency on external images, SVG sprites or anything in the DOM — just d3.js.
Here's a working fiddle. The implementation is explained below.
But also, here's a more advanced fiddle that animates a clip-path over the growing arc. Check out its predecessor to see how the mask looks without clipping.
First, you need to represent the graphics as an array of data that you bind to with d3. Specifically, you need a color and a "line command" (the string you assign to d as in <path d="...">. Something like this:
var segmentData = [
{ color:"#ED6000", cmd:"M42.6,115.3c5.2,1.5,11,2.4,16.8,2.4c1.1,0,2.7,0,3.7-0.1v-2.2c-7,0-13.1-1.3-18.8-3.6L42.6,115.3z" },
{ color:"#EF7D00", cmd:"M25.7,99.3c4.3,4.7,9.5,8.6,15.3,11.3l-1.4,3.8c-6.9-2.4-13.2-6.1-18.6-10.8L25.7,99.3z" },
{ color:"#F4A300", cmd:"M23.7,97c-5.2-6.4-8.8-14-10.3-22.4L2.9,75.7c2.9,10,8.5,18.9,15.8,25.9L23.7,97z" },
{ color:"#F7BC00", cmd:"M13,71.5c-0.2-2-0.4-4-0.4-6c0-10.7,3.4-20.6,9.2-28.8L9.4,28.3c-5.6,9-8.9,19.6-8.9,30.9 c0,4.6,0.6,9.1,1.6,13.5L13,71.5z" },
{ color:"#FFCF36", cmd:"M63,15.7V0.8c-1-0.1-2.5-0.1-3.7-0.1c-19.9,0-37.5,9.9-48.1,25l12.7,8.6C33.1,23,46,15.7,63,15.7z" }
];
Then you need an empty <svg> and probably a <g> within it, into which to draw the graphics:
var svg = d3.select("body").append("svg")
.attr("width", 125)
.attr("height", 125);
var gauge = svg.append("g");
Then you use d3 binding to create the segments:
var segments = gauge.selectAll(".segment")
.data(segmentData);
segments.enter()
.append("path")
.attr("fill", function(d) { return d.color; })
.attr("d", function(d) { return d.cmd; });
This just creates the graphic, but doesn't color it based on an integer value. For that, you can define an update function:
function update(value) {
segments
.transition()
.attr("fill", function(d, i) {
return i < value ? d.color : "#ccc";
})
}
Calling update(4) will color all but the last segment. Calling update(0) color none (leaving all of them gray).
In the fiddle, there's also a tick() function that calls update with a new value on a setTimeout basis, but that's just for demo.
Finally, if you wish, you can wrap all that code up and create a reusable component by following the advice in [this article].(http://bost.ocks.org/mike/chart/)
since it is relatively simple picture, I'd use a sprite, with 5 variations.
That would be much easier than using d3 and gives the same result.
(you could use some online tool like http://spritepad.wearekiss.com/ )
If you want to mimic duolingo progress images you can just simply copy their solution with own images. They are using sprites as this one: http://d7mj4aqfscim2.cloudfront.net/images/skill-strength-sprite2.svg not the d3.js approach. This will save you a lot of time and effort.
I'm helping guide a project that's combining some visualizations in D3. In our specific example we are producing a graph of pies by using a regular D3 Pie chart with the sankey layout visualization. The affect of this is to produce something like:
The development is aimed to try and keep this as modular as possible, therefore the very first step was to produce an updating pie chart that could be used stand alone or plugged into another visualization. This is currently encapuslated into a pieObject which looks something like this:
var pieObject = function( d, target ){
var pie = {};
// other code to handle init
pie.update = function(data) {
// render code
};
};
Where it gets a little confusion is in the tree visualization, when I need to start handling updates. Here is how a new pie is added:
sankey.nodes(data.nodes)
.links(data.links)
.layout(32);
var node = svg.append("g")
.selectAll(".node")
.data(data.nodes)
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; });
.each( function( d ) {
var pie = new pieObject( d, this );
})
If I however want to deal with an existing node, I'm not sure how I should go about accessing the pieObject? There are a couple of options I can think of, but I'm wondering if there's a general approach commonly used?
Store the pieObject on the element d
Store the pieObject in an array or JavaScript object with a lookup from a field on the d
Out of the examples I've outlined, I prefer option #1. But I'm also re-loading my entire data-set from JSON (e.g. new JSON request returns existing data + new data) so I believe when I call sankey.nodes(data.nodes).links(data.links).layout(32); that this is going to lose any additional information I've stored on the d object?
EDIT
I've put together a JSFiddle to help illustrate my problem. The code is a little lengthy and I don't own it as yet so don't know all the ins & outs but here's a breakdown:
1-#214 - Sankey code which produces the Tree layout
215-#451 - Code for a Pie Chart
453-#475 - Code to add the viz to the page
Specifically the area of creating the pies, and trying to figure out how to update them is in the render function between lines #129-#149
I'd recommend starting with Mike Bostock's tutorial on re-usable charts, which does pretty much what you are looking for: http://bost.ocks.org/mike/chart/
Specifically, the key to this type of thing is to use the selection.call() function to insert your chart into another chart/layout in a re-usable way. Then when you need to update your embedded chart, just do the same selection.call() again. Hopefully that gets you started.
I have a D3.js multiline graph with circles added on every path peak. When I update my graph, the paths update just fine with the new data but the circles don't seem to get updated at all. Here's my code: http://jsbin.com/eMuQOHoV/3/edit
Does anyone know what I'm doing wrong?
You need to update the data point circles in the same way that you've created them. In particular, you're using a nested selection when creating them, but not when updating. This means that the data won't get matched correctly on update and nothing happens.
The code for the update should look like this.
var sel = svg.selectAll('.series')
.data(sources);
sel.select('path')
.transition()
// etc
// update circles
sel.selectAll('.datapoint')
.data(function (d) {
return d.values;
})
// etc
Complete jsbin here.
Using d3 I want to draw several time series line charts on a single page, each one featuring two lines.
Using the example on this page for multiple charts, I've got code working with single lines on each chart. But I'm not sure how best to modify that example to work with multi-line charts.
The example does this:
d3.csv("sp500.csv", function(data) {
var formatDate = d3.time.format("%b %Y");
d3.select("#example")
.datum(data)
.call(timeSeriesChart()
.x(function(d) { return formatDate.parse(d.date); })
.y(function(d) { return +d.price; }));
});
with TimeSeriesChart() defined in this file.
How would I best adapt this to cope with two (or more) lines (with the same x-axis values, and the same y scales)?
FWIW, my data is in JS arrays/hashes, rather than being read from CSV, but I guess the principle will be the same.
You can pass in your data structure that contains data for several lines in the same way. The only difference would in how you would handle the data in the referenced file. You need to change the function in
selection.each(function(data) {
First, you need to adapt the preprocessing being done and similarly the code that determines the limits of the axes. Further down, you would change
// Update the line path.
g.select(".line")
.attr("d", line);
to something like
g.selectAll(".line").data(<data for your two lines here>)
.enter()
.append("path")
.attr("class", "line")
.attr("d", line);
and remove the line
gEnter.append("path").attr("class", "line");
before that.
The exact changes will depend on the format that you're passing in.
An alternative (and if you're just starting probably easier) approach would be to simply add the additional data in a new attribute, add a new line generator that accesses that data and repeat the code that generates the line and calls the line generator with a different class name and the different generator. This is a hacky approach that I wouldn't recommend in general, but it might be easier to get started that way.