emilyaviva.com
            
            
            twitter.com/EmilyAviva
            
            
            github.com/EmilyAviva
            
            
            linkedin.com/in/emilykapor
          
| Data | Serialized (JSON, CSV, etc.) | 
| Driven | |
| Documents | Output 
 | 
The partitioning of a plane with n points into convex polygons such that each polygon contains exactly one generating point and every point in a given polygon is closer to its generating point than to any other.
This section is based on Mike Bostock's excellent Voronoi arc map, showing commercial airline flights between cities (GPL v3)
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Voronoi Arc Map</title>
  </head>
  <body>
    <div id="chart"></div>
    <script src="//path/to/d3.js"></script>
    <script src="//path/to/d3/queue.js"></script>
    <script src="//path/to/mbostock/topojson.js"></script>
    <script src="client.js"></script>
  </body>
</html>var width = 960
var height = 500
var projection = d3.geo.albers()
  .translate([width / 2, height / 2])
  .scale(1080)
var path = d3.geo.path()
  .projection(projection)
var voronoi = d3.geom.voronoi()
  .x((d) => d.x)
  .y((d) => d.y)
  .clipExtent([[0, 0], [width, height]])
var svg = d3
  .select('#chart')
  .append('svg')
    .attr('width', width)
    .attr('height', height)queue()
  .defer(d3.json, 'us.json')
  .defer(d3.csv, 'airports.csv')
  .defer(d3.csv, 'flights.csv')
  .await(ready)function ready(error, us, airports, flights) {
  if (error) throw error;
  var airportById = d3.map()
  airports.forEach((d) => {
    airportById.set(d.iata, d)
    d.outgoing = []
    d.incoming = []
  })
  flights.forEach((flight) => {
    var source = airportById.get(flight.origin)
    var target = airportById.get(flight.destination)
    var link = {source, target}
    source.outgoing.push(link)
    target.incoming.push(link)
  })
}airports = airports.filter((d) => {
  if (d.count = Math.max(d.incoming.length, d.outgoing.length)) {
    d[0] = +d.longitude
    d[1] = +d.latitude
    var position = projection(d)
    d.x = position[0]
    d.y = position[1]
    return true
  }
})
voronoi(airports).forEach((d) => {
  d.point.cell = d
})
svg.append('path')
  .datum(topojson.feature(us, us.objects.land))
  .attr('class', 'states')
  .attr('d', path)
svg.append('path')
  .datum(topojson.mesh(us, us.objects.states, (a, b) => a !== b))var airport = svg.append('g')
  .attr('class', 'airports')
  .selectAll('g')
  .data(airports.sort((a, b) => b.count - a.count))
  .enter()
  .append('g')
    .attr('class', 'airport')
airport
  .append('path')
  .attr('class', 'airport-cell')
  .attr('d', (d) => d.cell.length ? 'M' + d.cell.join('L') + 'Z' : null)airport
  .append('g')
  .attr('class', 'airport-arcs')
  .selectAll('path')
  .data((d) => d.outgoing)
  .enter()
  .append('path')
    .attr('d', (d) => path({type: 'LineString', coordinates: [d.source, d.target]})
airport
  .append('circle')
  .attr('transform', (d) => `translate(${d.x}, ${d.y})`)
  .attr('r', (d, i) => Math.sqrt(d.count)).airport-arcs {
  display: none;
  fill: none;
  stroke: #000;
}
.airport-cell {
  fill: none;
  pointer-events: all;
}
.airports circle {
  fill: steelblue;
  stroke: #fff;
  pointer-events: none;
}.airport:hover .airport-arcs {
  display: inline;
}
svg:not(:hover) .airport-cell {
  stroke: #000;
  stroke-opacity: .2;
}
.states {
  fill: #ccc;
}
.state-borders {
  fill: none;
  stroke: #fff;
  stroke-width: 1.5px;
  stroke-linejoin: round;
  stroke-linecap: round;
}Acreage of Commercial Hop Production in North America, 2015
var hopsData = [{
  name: 'Washington',
  acres: 32205
}, {
  name: 'Oregon',
  acres: 6807
}, {
  name: 'Idaho',
  acres: 4975
}, {
  name: 'Other States',
  acres: 1244
}, {
  name: 'Canada',
  acres: 257
}]<div> for the SVG
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>North American Hops</title>
  </head>
  <body>
    <div id="chart"></div>
    <script src="//path/to/d3.js"></script>
    <script src="client.js"></script>
  </body>
</html>
var width = 400
var height = 400
var totalRadius = Math.min(width, height) / 2
var donutHoleRadius = totalRadius * 0.5
var color = d3.scale.category10()
var svg = d3
  .select('#chart')
  .append('svg')
  .attr('width', width)
  .attr('height', height)
  .append('g')
    .attr('transform', `translate(${width / 2}, ${height / 2})`)
var arc = d3.svg.arc()
  .innerRadius(totalRadius - donutHoleRadius)
  .outerRadius(totalRadius)
var pie = d3.layout.pie()
  .value((d) => d.acres)
  .sort(null)
var path = svg
  .selectAll('path')
  .data(pie(hopsData))
  .enter()
  .append('path')
    .attr('d', arc)
    .attr('fill', (d, i) => color(d.data.name))
var legendItemSize = 18
var legendSpacing = 4
var legend = svg
  .selectAll('.legend')
  .data(color.domain())
  .enter()
  .append('g')
    .attr('class', 'legend')
    .attr('transform', (d, i) => {
      var height = legendItemSize + legendSpacing;
      var offset = height * color.domain().length / 2;
      var x = legendItemSize * -2
      var y = (i * height) - offset
      return `translate(${x}, ${y})`
    })
legend
  .append('rect')
    .attr('width', legendItemSize)
    .attr('height', legendItemSize)
    .style('fill', color);
legend
  .append('text')
    .attr('x', legendItemSize + legendSpacing)
    .attr('y', legendItemSize - legendSpacing)
    .text((d) => d);
.legend {
  font-size: 12px;
  font-family: sans-serif;
  rect {
    stroke-width: 2;
  }
  text {
    fill: lightcyan;
  }
}React likes to have control over the entire DOM…
…but D3 likes to have control over the SVG DOM
import React from 'react'
export default class D3Component extends React.Component {
  constructor(props) {
    super(props)
  }
  initialize() {}
  update(prevProps, prevState) {}
  destroy() {}
  componentDidMount() {
    this.initialize()
    this.update()
  }
  componentDidUpdate(prevProps, prevState) {
    this.update(prevProps, prevState)
  }
  componentWillUnmount() {
    this.destroy()
  }
  render() {
    return (
      
    )
  }
}...e.g. react-d3-wrap
import d3 from 'd3'
import d3Wrap from 'react-d3-wrap'
const MyChart = d3Wrap({
  initialize(svg, data, options) {},
  update(svg, data, options) {
    const chart = d3
      .select(svg)
      .append('g')
      .attr('transform', `translate(${options.margin.left}, ${options.margin.top})`)
    // etc., etc.
  },
  destroy() {}
})
export default MyCharthttp://formidable.com/open-source/victory/docs/victory-scatter
class CatPoint extends React.Component {
  render() {
    const {x, y, datum} = this.props
    const cat = datum.y >= 0 ? '😻' : '😹'
    return <text x={x} y={y} fontSize={30}>{cat}</text>
  }
}
class App extends React.Component {
  render() {
    return (
      <VictoryScatter
        height={500}
        y={(d) => Math.sin(2 * Math.PI * d.x)}
        samples={25}
        dataComponent={<CatPoint />}
      />
    )
  }
}http://codepen.io/emilyaviva/pen/bebQzZ/
Inspired by Mike Bostock's Multi-Series Line Chart
Fremont Bridge Hourly Bicycle Counts, 1 May 2015
https://dev.socrata.com/foundry/data.seattle.gov/4xy5-26gy
app.controller('BikeCrossingController', ['$scope', ($scope) => {
  $scope.bikeCrossingData = [{
    date: '2015-05-01T00:00:00.000',
    fremont_bridge_nb: '5',
    fremont_bridge_sb: '14'
  }, {
    date: '2015-05-01T01:00:00.000',
    fremont_bridge_nb: '1',
    fremont_bridge_sb: '3'
  }, {
    date: '2015-05-01T02:00:00.000',
    fremont_bridge_nb: '1',
    fremont_bridge_sb: '6'
  }, {
    date: '2015-05-01T03:00:00.000',
    fremont_bridge_nb: '3',
    fremont_bridge_sb: '2'
  }, {
    date: '2015-05-01T04:00:00.000',
    fremont_bridge_nb: '8',
    fremont_bridge_sb: '0'
  }, {
    date: '2015-05-01T05:00:00.000',
    fremont_bridge_nb: '29',
    fremont_bridge_sb: '15'
  }, {
    date: '2015-05-01T06:00:00.000',
    fremont_bridge_nb: '110',
    fremont_bridge_sb: '54'
  }, {
    date: '2015-05-01T07:00:00.000',
    fremont_bridge_nb: '390',
    fremont_bridge_sb: '94'
  }, {
    date: '2015-05-01T08:00:00.000',
    fremont_bridge_nb: '399',
    fremont_bridge_sb: '164'
  }, {
    date: '2015-05-01T09:00:00.000',
    fremont_bridge_nb: '151',
    fremont_bridge_sb: '100'
  }, {
    date: '2015-05-01T10:00:00.000',
    fremont_bridge_nb: '67',
    fremont_bridge_sb: '46'
  }, {
    date: '2015-05-01T11:00:00.000',
    fremont_bridge_nb: '49',
    fremont_bridge_sb: '47'
  }, {
    date: '2015-05-01T12:00:00.000',
    fremont_bridge_nb: '57',
    fremont_bridge_sb: '47'
  }, {
    date: '2015-05-01T13:00:00.000',
    fremont_bridge_nb: '57',
    fremont_bridge_sb: '66'
  }, {
    date: '2015-05-01T14:00:00.000',
    fremont_bridge_nb: '73',
    fremont_bridge_sb: '85'
  }, {
    date: '2015-05-01T15:00:00.000',
    fremont_bridge_nb: '89',
    fremont_bridge_sb: '183'
  }, {
    date: '2015-05-01T16:00:00.000',
    fremont_bridge_nb: '149',
    fremont_bridge_sb: '326'
  }, {
    date: '2015-05-01T17:00:00.000',
    fremont_bridge_nb: '211',
    fremont_bridge_sb: '418'
  }, {
    date: '2015-05-01T18:00:00.000',
    fremont_bridge_nb: '126',
    fremont_bridge_sb: '206'
  }, {
    date: '2015-05-01T19:00:00.000',
    fremont_bridge_nb: '80',
    fremont_bridge_sb: '95'
  }, {
    date: '2015-05-01T20:00:00.000',
    fremont_bridge_nb: '42',
    fremont_bridge_sb: '43'
  }, {
    date: '2015-05-01T21:00:00.000',
    fremont_bridge_nb: '18',
    fremont_bridge_sb: '30'
  }, {
    date: '2015-05-01T22:00:00.000',
    fremont_bridge_nb: '19',
    fremont_bridge_sb: '13'
  }, {
    date: '2015-05-01T23:00:00.000',
    fremont_bridge_nb: '11',
    fremont_bridge_sb: '22'
  }]
}])
app.directive('lineChart', ($parse, $window) => {
  return {
    restrict: 'EA',
    template: '',
    link: function(scope, el, attrs) {
      var d3 = $window.d3
      var margin = {top: 20, right: 80, bottom: 30, left: 50}
      var width = 960 - margin.left - margin.right
      var height = 500 - margin.top - margin.bottom
      var data = scope.bikeCrossingData.map((d) => {
        return {
          hour: (new Date(d.date).getHours() + 8) % 24,
          northbound: d.fremont_bridge_nb,
          southbound: d.fremont_bridge_sb
        }
      }).sort((a, b) => d3.ascending(a.hour, b.hour))
      var svg = d3.select(el.find('svg')[0])
      var color = d3.scale.category10()
      color.domain(d3.keys(data[0])
        .filter((key) => key !== 'hour'))
      var curves = color.domain().map((name) => {
        return {
          name,
          values: data.map((d) => {
            return {
              hour: d.hour,
              bikers: +d[name]
            }
          })
        }
      })
      // set scales
      var x = d3.scale.linear().range([0, width])
      var y = d3.scale.linear().range([height, 0])
      x.domain(d3.extent(data, (d) => d.hour))
      y.domain([
        d3.min(curves, (c) => d3.min(c.values, (v) => v.bikers)),
        d3.max(curves, (c) => d3.max(c.values, (v) => v.bikers))
      ])
      // set axes
      var hourLabels = ['12am', '2am', '4am', '6am', '8am', '10am', '12pm', '2pm', '4pm', '6pm', '8pm', '10pm']
      var xAxis = d3.svg.axis()
        .scale(x)
        .orient('bottom')
        .ticks(12)
        .tickFormat((d, i) => hourLabels[i])
      var yAxis = d3.svg.axis()
        .scale(y)
        .orient('left')
        .ticks(8)
      // draw the lines in between the data points
      var line = d3.svg.line()
        .interpolate('basis')
        .x((d) => x(d.hour))
        .y((d) => y(d.bikers))
      svg = svg
        .attr('width', width + margin.left + margin.right)
        .attr('height', height + margin.top + margin.bottom)
        .append('g')
        .attr('transform', `translate(${margin.left}, ${margin.top})`)
      svg.append('g')
        .attr('class', 'x axis')
        .attr('transform', `translate(0, ${height})`)
        .call(xAxis)
      svg.append('g')
        .attr('class', 'y axis')
        .call(yAxis)
      // bind the curves to our data
      var curve = svg
        .selectAll('.curve')
        .data(curves)
        .enter()
        .append('g')
        .attr('class', 'curve')
      curve.append('path')
        .attr('class', 'line')
        .attr('d', (d) => line(d.values))
        .style('stroke', (d) => color(d.name))
      // show the label after the curve
      curve.append('text')
        .datum((d) => {
          return {
            name: d.name,
            value: d.values[d.values.length - 1]
          }
        })
        .attr('transform', (d) => `translate(${x(d.value.hour)}, ${y(d.value.bikers)})`)
        .attr('x', 3)
        .attr('dy', '.35em')
        .style('fill', (d) => color(d.name))
        .text((d) => d.name)
      // don't show ticks at the origin
      svg.selectAll('.tick')
        .filter((d) => d === 0)
        .remove()
    }
  }
})
body {
  background: black;
  color: white;
  font-family: sans-serif;
  font-size: 12px;
}
.axis {
  path, line {
    fill: none;
    stroke: white;
    shape-rendering: crispEdges;
  }
}
.line {
  fill: none;
  stroke: steelblue;
  stroke-width: 1.5px;
}
text {
  fill: white;
}
<div ng-app="visualizationApp" ng-controller="BikeCrossingController">
  <div line-chart chart-data="bikeCrossingData"></div>
</div>
var app = angular.module('visualizationApp', [])