Skip to content

Instantly share code, notes, and snippets.

@1wheel
Last active Aug 29, 2015
Embed
What would you like to do?
NYC-D3

Reusable d3 with jetpack & starterkit

1wheel.github.io/nyc-d3

Adam Pearce

Scatter Plot I

scatter plot

bl.ocks.org/1wheel

##Great, but...

  • 80 lines & 2284 characters of js

  • Reuse conventions with copy/paste

##d3-jetpack jetpack comic

##appending with class Before

var legend = svg.selectAll(".legend")
    .data(color.domain())
  .enter().append("g")
    .attr("class", "legend")

After

var legend = svg.selectAll(".legend")
    .data(color.domain())
  .enter().append("g.legend")

##appending with class Works with ids and multiple classes

Before

svg.append("g")
    .attr("class", "y axis")
    .call(yAxis)
  .append("text")
    .attr("class", "label")

After

svg.append("g.y.axis")
    .call(yAxis)
  .append("text.label")

##translate Before

svg.append("g.x.axis")
    .attr("transform", "translate(0," + height + ")")

After

svg.append("g.x.axis")
    .translate([0, height])

##translate Functions called w/ bound data and index

Before

legend
    .attr("transform", function(d, i){
      return "translate(0," + i * 20 + ")" })

After

legend
    .translate(function(d, i){ return [0, i*20] })

##ƒIELD ACCESSOR Turns a string into a function

function ƒ(str){
  return function(object){
    return object[str]
  }
}

Returns properties from objects

var collie = {color: 'brown', legs: 4}
var spider = {color: 'black', legs: 8}

ƒ('color')(collie)      //'brown'
ƒ('color')(spider)      //'black'

ƒ('legs')(spider)       //8

##ƒIELD ACCESSOR

Before

x.domain(d3.extent(data, function(d) { return d.sepalWidth; }))
y.domain(d3.extent(data, function(d) { return d.sepalLength; }))

After

  x.domain(d3.extent(data, ƒ('sepalWidth')))
  y.domain(d3.extent(data, ƒ('sepalLength')))

##Compose Combines two funtions together

function compose(g, h){
  return function(object){
    return h(g(object))
  }
}

function add10 (n){ return 10 + n }
function double(n){ return  2 * n }

compose(ƒ('legs'), add10) (collie)  //4 + 10 = 14
compose(ƒ('legs'), add10) (spider)  //8 + 10 = 18
compose(ƒ('legs'), double)(spider)  //8 * 2  = 16

##ƒIELD ACCESSOR

Before

dots
    .attr("cx", function(d) { return x(d.sepalWidth); })
    .attr("cy", function(d) { return y(d.sepalLength); })
    .style("fill", function(d) { return color(d.species); })

After

dots
    .attr("cx", compose(ƒ('sepalWidth'), x))
    .attr("cx", compose(ƒ('sepalLength'), y))
    .style("fill", compose(ƒ('species'), color))

##ƒIELD ACCESSOR Converts strings to functions and composes

ƒ('legs', add10) (collie)           //4 + 10 = 14
ƒ('legs'), double)(spider)          //8 * 2  = 16
ƒ(add10, double, add10)(3)          //((3 + 10) * 2 ) + 10 = 36
ƒ()(3)                              //3

##ƒIELD ACCESSOR Before

dots
    .attr("cx", compose(ƒ('sepalWidth'), x))
    .attr("cx", compose(ƒ('sepalLength'), y))
    .style("fill", compose(ƒ('species'), color))

After

dots
    .attr("cx", ƒ('sepalWidth', x))
    .attr("cx", ƒ('sepalLength', y))
    .style("fill", ƒ('species', color))

##ƒIELD ACCESSOR Also works as an identity function

Before

legend.append("text")
    .text(function(d) { return d })

After

legend.append("text")
    .text(ƒ())

##d3-starterkit Snippets and conventions for starting a new d3 project

github.com/1wheel/d3-starterkit

##dataAppend Before

svg.selectAll(".dot")
    .data(data)
  .enter().append("circle.dot")

After

svg.dataAppend(data, "circle.dot")

##dataAppend

d3.selection.prototype.dataAppend = function(data, name){
  return this.selectAll(name)
      .data(data).enter()
    .append(name)

##dataAppend Before

var legend = svg.selectAll(".legend")
    .data(color.domain())
  .enter().append("g.legend")

After

legend = svg.dataAppend(color.domain(), "g.legend")

##d3.conventions - margins Before

var margin = {top: 20, right: 20, bottom: 30, left: 40},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", 
      "translate(" + margin.left + "," + margin.top + ")")

After

var c = d3.conventions({
  margin: {top: 20, right: 20, bottom: 30, left: 40},
  width:  900,
  height: 450,
})

c.svg.dataAppend(data, "circle.dot")

##d3.conventions - scales Creates and sets the domain for x and y scales

Before

var x = d3.scale.linear()
    .range([0, width])
    .domain(d3.extent(data, ƒ('sepalWidth')))

var y = d3.scale.linear()
    .range([height, 0])
    .domain(d3.extent(data, ƒ('sepalLength')))

After

c.x.domain(d3.extent(data, ƒ('sepalWidth')))
c.y.domain(d3.extent(data, ƒ('sepalLength')))

##d3.conventions - axis Creates and configures x and y axis

Before

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

After

console.log(c.xAxis.orient())        //"bottom"

##d3.conventions - axis Draws x and y axis

Before

svg.append("g.x.axis")
    .translate([0, height])
    .call(xAxis)
  .append("text.label")
    .attr("x", width)

After

c.drawAxis()

c.svg.select('.x.axis')
  .append("text.label")
    .attr("x", c.width)

##Significantly shorter Scatter II

  • Conventions can be used without copy/paste
  • 49 lines and 1309 characters of javascript
  • 31 lines just to draw the axis label and legend

##Minimally viable (scatter) plot Scatter III

d3.tsv("data.tsv", function(data) {
  var c = d3.conventions()
  c.x.domain(d3.extent(data, ƒ('sepalWidth')))
  c.y.domain(d3.extent(data, ƒ('sepalLength')))

  c.drawAxis()

  c.svg.dataAppend(data, "circle.dot")
      .attr("r", 3.5)
      .attr("cx", ƒ('sepalWidth', c.x))
      .attr("cy", ƒ('sepalLength', c.y))
      .style("fill", ƒ('species', c.color))
})

##There's more!

  • d3.attachTooltip
  • d3.wordWrap
  • d3.???

##Tutorials stacked-bump

roadtolarissa.com/stacked-bump

roadtolarissa.com/data-exploration

Related work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment