Skip to content

Instantly share code, notes, and snippets.

@curran
Last active April 28, 2016 06:17
Show Gist options
  • Save curran/a4a39a3d0e89126964aeedcb60d33124 to your computer and use it in GitHub Desktop.
Save curran/a4a39a3d0e89126964aeedcb60d33124 to your computer and use it in GitHub Desktop.
Composable Visualization Test

This is an experiment with the notion of composable visualization components that follow the Towards Reusable Charts pattern (which I think I'm finally grokking after all these years).

The idea is that visualization components can be composed in kind of a recursive way. This would make it straightforward to create, say, stacked bar charts by simply composing components in an almost algebraic expression like barChart * verticalStack * rect. Making that into small multiples would simply require the addition of a facet component to the expression: facetVertical * barChart * verticalStack * rect.

concept

This kind of thing is present in the Grammar of Graphics, the ggplot2 paper, and Vega. It would be amazing to discover straightforward JavaScript/D3 patterns that enable this kind of thing.

Built with blockbuilder.org

diaper cake
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>Composable Visualization Test</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<style>
body {
margin: 0;
}
</style>
</head>
<body>
<script>
var width = 960;
var height = 500;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var data = [
{ name: "A"},
{ name: "B"},
{ name: "C"}
];
var rect = Rect()
.colorBy("name")
.color(d3.scale.category10())
var facetVertical = FacetVertical()
.width(width)
.height(height)
.nestBy("name");
// Define the composition of facet and rect.
facetVertical.compose(rect);
svg.datum(data).call(facetVertical);
// This component draws a single rectangle.
function Rect(){
var color;
var colorBy;
function my(selection){
selection.each(function (data){
var rects = d3.select(this)
.selectAll("rect")
.data(data);
rects.enter().append("rect");
rects
.attr("width", width)
.attr("height", height);
if(colorBy){
rects.attr("fill", function (d){
return color(d[colorBy]);
});
}
rects.exit().remove();
});
}
my.width = function (value){
if(arguments.length === 0) return width;
width = value;
return my;
};
my.height = function (value){
if(arguments.length === 0) return height;
height = value;
return my;
};
my.color = function (value){
if(arguments.length === 0) return color;
color = value;
return my;
};
my.colorBy = function (value){
if(arguments.length === 0) return colorBy;
colorBy = value;
return my;
};
return my;
}
// This component facets space vertically,
// and could be used to create small multiples.
function FacetVertical(){
var width;
var height;
var compose;
var nestBy;
function my(selection){
selection.each(function (data){
var nested = d3.nest()
.key(function (d){ return d[nestBy]; })
.entries(data)
.map(function (d){ return d.values; });
var groups = d3.select(this)
.selectAll("g")
.data(nested)
groups.enter().append("g");
groups.attr("transform", function (d, i){
var y = height * i / nested.length;
return "translate(0," + y + ")";
});
groups.exit().remove();
compose.width(width);
compose.height(height / nested.length);
groups.call(compose);
});
}
my.width = function (value){
if(arguments.length === 0) return width;
width = value;
return my;
};
my.height = function (value){
if(arguments.length === 0) return height;
height = value;
return my;
};
my.compose = function (value){
if(arguments.length === 0) return compose;
compose = value;
return my;
};
my.nestBy = function (value){
if(arguments.length === 0) return nestBy;
nestBy = value;
return my;
};
return my;
}
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment