D3.js merge() extended - small multiples
According to Wikipedia a Small Multiple is:
a series of similar graphs or charts using the same scale and axes, allowing them to be easily compared
They can be really powerful and there are some wonderful best practice examples out there including:
- Jim Vallandingham's Bar Multiples
- Nadieh Bremer's Radials
- Nathan Yau's Male Shifting to Female Lines
I thought it would be helpful to follow up on my D3.js merge() post for small multiples.
Here's the same ESSENTIAL code with one addition - can you spot it?
//step 1 - JOIN (with or without a key)
var my_group = svg.selectAll('.**chart_group**')
.data(data, d => d.id);
//step 2 - EXIT and REMOVE
my_group.exit().remove();
//step 3 - ENTER
var enter = my_group.enter().append("g").attr("class","**chart_group**");
//step 4 - APPEND
enter.append("rect").attr("class","group_rect");
enter.append("text").attr("class","group_text");
enter.append("image").attr("class","group_image");
enter.append("g").attr("class","sub_chart_group");
//step 5 - MERGE
my_group = my_group.merge(enter);
So each 'group' has a a bounding rectangle, title and image BUT it also has data so needs a 4th element appended - sub_chart_group.
SO after you have added the properties to the elements in my_group - as seen in the position_group_elements function in my simple demo.
You also need to think about position. Depending on your UI needs, you will need to position each group in the right place on the svg.
In my simple demo, I've chosen a simple approach with a panels_per_row constant.
I use this to set x_pos and y_pos in the position_group_elements function and then I add a translate attribute to place each panel in the correct position.
my_group.select(".sub_chart_group")
.attr("transform",d => "translate(" + d.x_pos + "," + d.y_pos + ")");
Now for the exciting bit - creating a sub_group and appending the data.
//step 1 - JOIN (with or without a key)
var sub_group = my_group.select(".sub_chart_group")
.selectAll('.sub_chart_elements_group')
.data(d => [d.chart_data]);
//step 2 - EXIT and REMOVE
sub_group.exit().remove();
//step 3 - ENTER
var sub_enter = sub_group.enter().append("g")
.attr("class","sub_chart_elements_group");
//step 4 - APPEND
sub_enter.append("path").attr("class","chart_line");
sub_enter.append("path").attr("class","chart_area");
//step 5 - MERGE
sub_group = sub_group.merge(sub_enter);
The code is very similar right?
BUT there are two key differences:
- instead of the svg I've used ** my_group.select(".svg_chart_group")** as the selector
- I've used a new name for the sub_enter groups "sub_chart_elements_group"
The final step, is to select the elements in sub_group and add the properties. For example:
sub_group.select(".chart_area")
.attr("d", area)
.attr("stroke","none")
.attr("fill",d => d.color)
.attr("fill-opacity",0.2)
.attr("transform","translate(" + start_x + "," + start_y + ")");
That's it.
Have a look at my simple demo to see all of this in one place.
Once you've mastered the data appending you can go on to create your own beautiful small multiple charts.
If you want to see the same process using .join() instead of .merge() have a look at this simple demo.