“The annotation layer is the most important thing we do” —Amanda Cox
swoopyDrag helps you hand place annotations on d3 graphics. It takes an array of objects representing annotations and turns them into lines and labels. Drag the text and control circles below to update the annotations array:
The x and y functions are called on each annotation to determine its position. In the annotations array here, the sepalWidth and sepalLength properties are the data values of point the annotation refers to. The functions passed to x and y look up these values and encode them as pixel position using the same scale set up to position the circles.
var swoopy = d3.swoopyDrag() .x(d => xScale(d.sepalWidth)) .y(d => yScale(d.sepalLength)) .draggable(true) .annotations(annotations)
Setting
The shape of each annotation's line is determined by the path property, the text by the text property and the position of the text by the testOffset property. Currently only straight paths (paths of the form
The annotations are added to the page just like
var swoopySel = svg.append('g').call(swoopy)
After posititioning the labels, open the dev tools, run
Since each annotation's position is determined primarily by scales, lines and labels will still point to the correct position when the chart size changes. As the chart shrinks though, the annotations might overlap or cover up data points. To show fewer or differently positioned labels on mobile, you could create multiple annotation arrays for different screen sizes:
d3.swoopyDrag() .annotations(innerWidth < 800 ? mobileAnnotations : desktopAnnotations)
Alternatively if there's just one or two problematic annotations that only work above or below some sizes, you could add
d3.swoopyDrag() .annotations(annotations.filter(function(d){ return (typeof(d.minWidth) == 'undefined' || innerWidth > d.minWidth) && (typeof(d.maxWidth) == 'undefined' || innerWidth < d.maxWidth) }))
SVG has native support for arrowheads, but they can be a little fiddly to get working. First, add a
svg.append('marker') .attr('id', 'arrow') .attr('viewBox', '-10 -10 20 20') .attr('markerWidth', 20) .attr('markerHeight', 20) .attr('orient', 'auto') .append('path') .attr('d', 'M-6.75,-6.75 L 0,0 L -6.75,6.75')
Next, select paths in each annotation and set their
swoopySel.selectAll('path').attr('marker-end', 'url(#arrow)')
Multiline text can be added with d3-jetpack. Select all of the
swoopySel.selectAll('text') .each(function(d){ d3.select(this) .text('') //clear existing text .tspans(d3.wordwrap(d.text, 20)) //wrap after 20 char })
On the page, annotations are made up of a
To customize the color of the text you could add a
swoopySel.selectAll('text').style('fill', d => d.textColor || '#000')
Or you could add a
swoopySel.selectAll('g').attr('class', d => d.class)
And emphasize them with css:
g.highlight text{ font-weight: 700; } g.highlight path{ stroke-width: 2; }
d3-module-faces
Minute by Minute Point Differentials
NBA Win/Loss Records
Bush and Kasich Donors Give to Clinton
swoopyarrows creates fancier swoops, including circular and loopy arcs.
labella.js uses a force directed layout to position timeline labels with no overlap.
svg-crowbar lets you export a
ai2html is an illustrator script that creates responsive