Create a gist now

Instantly share code, notes, and snippets.

@cpbotha /README.md
Last active Mar 21, 2016

What would you like to do?
Simple example of reusable d3 plugin.

This as-simple-as-possible example tries to demonstrate Mike Bostock's pattern "Towards Reusable Charts" http://bost.ocks.org/mike/chart/ for d3 plugins that are composite shapes.

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>really simple d3 plugin example</title>
</head>
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
width: 960px;
height: 500px;
position: relative;
}
</style>
<body>
<div id="chart">
</div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="textblock.js"></script>
</body>
</html>
var items = [
{x : 50, y : 50, label : 'interesting label'},
{x : 100, y: 120, label : 'some arb example'},
{x : 300, y: 100, label : 'the last block'}
];
// we can increase this, everything will scale up with us
var w=960,h=500,
svg=d3.select("#chart")
.append("svg")
.attr("width",w)
.attr("height",h);
/**
* textBlock - a really simple demonstration of a reusable d3 element /
* plugin following Mike Bostock's "Towards Reusable Charts" pattern on
* http://bost.ocks.org/mike/chart/
*
* This simply draws a rect around the text specified in the label property.
*
* This example is also available as a block:
* http://bl.ocks.org/cpbotha/5073718
*
* Other more complex examples that folow this pattern are:
* https://github.com/d3/d3-plugins/tree/master/bullet
* https://github.com/d3/d3-plugins/tree/master/chernoff
*
* author: Charl P. Botha - http://charlbotha.com/
*/
d3.textBlock = function() {
// label property: this will get drawn enclosed perfectly by a rect
var label = "";
// this function object is returned when textBlock() is invoked.
// after setting properties (label above), it can be invoked on
// a whole selection by using call() -- see the rest of the example.
function my(selection) {
selection.each(function(d, i) {
// inside here, d is the current data item, i is its index.
// "this" is the element that has been appended, in the case of
// this example, a svg:g
// the text property could have been specified by the user as a
// value, or a function of the current data item.
var labelvar = (typeof(label) === "function" ? label(d) : label);
// convert element (svg:g) into something that D3 can use
// element is a single-element selection
var element = d3.select(this);
// first append text to svg:g
var t = element.append("text")
.text(labelvar) // here we set the label property on the text element
.attr("dominant-baseline", "central"); // vertically centered
// get the bounding box of the just created text element
var bb = t[0][0].getBBox();
// then append svg rect to svg:g
// doing some adjustments so we fit snugly around the text: we're
// inside a transform, so only have to move relative to 0
element.append("rect")
.attr("x", -5) // 5px margin
.attr("y", - bb.height) // so text is vertically within block
.attr("width", bb.width + 10) // 5px margin on left + right
.attr("height", bb.height * 2)
.attr("fill", "steelblue")
.attr("fill-opacity", 0.3)
.attr("stroke", "black")
.attr("stroke-width", 2);
});
}
// getter / setter for the label property
my.label = function(value) {
if (!arguments.length) return value;
label = value;
return my;
};
return my;
}
// calling textBlock() returns the function object textBlock().my
// via which we set the "label" property of the textBlock outer func
var tb = d3.textBlock().label(function(d) {return d.label;});
// now we apply the returned function object my == tb on an enter selection
var item = svg.selectAll("rect")
.data(items)
.enter()
.append("svg:g")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.call(tb);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment