block by milroc 5518052

bar + sum: vanilla d3.js

Full Screen

This is a six part series, taking you through stages of designing and creating reusable visualizations with d3.js

All visualizations have the same functionality, showcase the individual points with a bar chart and sum up the selected bars.

Part 1. This is showcasing a prototype visualization made solely with d3.js and vanilla javascript.

These are examples created for a talk (slides and video).

Cheers,

Miles @milr0c

index.html

<!DOCTYPE html>
  <head>
    <meta charset="utf-8">
    <link rel="stylesheet" type="text/css" href="//littlesparkvt.com/flatstrap/assets/css/bootstrap.css"/>
    <link type="text/css" rel="stylesheet" href="style.css"/>
    <script src="//d3js.org/d3.v3.min.js"></script>
  </head>
  <body>
    <div class="row">
      <div class="span2"><button class="btn btn-success" onclick="update()">update</button></div>
      <div class="span2" id="sum">TOTAL: 0</div>
    </div>
    <div class="row" id="chart"></div>
    <script src="src.js"></script>
  </body>
</html>

src.js

function randomizeData(n, y) {
  if (arguments.length < 2) y = 400;
  if (!arguments.length) n = 20;
  var i = 0;
  return d3.range(~~(Math.random()*n) + 1).map(function(d, i) { return {
            x: ++i,
            y: ~~(Math.random()*y)
          }});
}

function update() {
  var data = randomizeData(20, Math.random()*100000);
  
  var margin = {top: 0, bottom: 20, left: 0, right: 0},
      width = 400,
      height = 400,
      duration = 500,
      formatNumber = d3.format(',d'),
      brush = d3.svg.brush();

  margin.left = formatNumber(d3.max(data, function(d) { return d.y; })).length * 14;
  var w = width - margin.left - margin.right,
      h = height - margin.top - margin.bottom;

  var x = d3.scale.ordinal()
              .rangeRoundBands([0, w], .1),
      y = d3.scale.linear()
              .range([h, 0]);

  y.domain([0, d3.max(data, function(d) { return d.y; })]);
  x.domain(data.map(function(d) { return d.x; }));

  var xAxis = d3.svg.axis()
                .scale(x)
                .orient('bottom'),
      yAxis = d3.svg.axis()
                .scale(y)
                .orient('left'),
      brush = d3.svg.brush()
                      .x(x)
                      .on('brushstart', brushstart)
                      .on('brush', brushmove)
                      .on('brushend', brushend);

  var svg = d3.select('#chart').selectAll('svg').data([data]),
      svgEnter = svg.enter().append('svg')
                              .append('g')
                                .attr('width', w)
                                .attr('height', h)
                                .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
                                .classed('chart', true),
      chart = d3.select('.chart');

  svgEnter.append('g')
            .classed('x axis', true)
            .attr('transform', 'translate(' + 0 + ',' + h + ')');
  svgEnter.append('g')
            .classed('y axis', true)
  svgEnter.append('g').classed('barGroup', true);
  chart.selectAll('.brush').remove();
  chart.selectAll('.selected').classed('selected', false);
  chart.append('g')
            .classed('brush', true)
            .call(brush)
          .selectAll('rect')
            .attr('height', h);

  bars = chart.select('.barGroup').selectAll('.bar').data(data);

  bars.enter()
        .append('rect')
          .classed('bar', true)
          .attr('x', w) // start here for object constancy
          .attr('width', x.rangeBand())
          .attr('y', function(d, i) { return y(d.y); })
          .attr('height', function(d, i) { return h - y(d.y); });

  bars.transition()
        .duration(duration)
          .attr('width', x.rangeBand())
          .attr('x', function(d, i) { return x(d.x); })
          .attr('y', function(d, i) { return y(d.y); })
          .attr('height', function(d, i) { return h - y(d.y); });

  bars.exit()
        .transition()
            .duration(duration)
                .style('opacity', 0)
                .remove();

  chart.select('.x.axis')
        .transition()
            .duration(duration)
              .call(xAxis);
  chart.select('.y.axis')
        .transition()
            .duration(duration)
              .call(yAxis);

  function brushstart() {
    chart.classed("selecting", true);
  }

  function brushmove() {
    var extent = d3.event.target.extent();
    bars.classed("selected", function(d) { return extent[0] <= x(d.x) && x(d.x) + x.rangeBand() <= extent[1]; });
    makeSum();
  }

  function brushend() {
    chart.classed("selecting", !d3.event.target.empty());
  }    

  function makeSum() {
    var sumDiv = d3.select('#sum'),
        extent = brush.extent(),
        sum = 0;
    
    data.forEach(function(d) {
      if (extent[0] <= x(d.x) && x(d.x) + x.rangeBand() <= extent[1])
        sum += d.y;
    });
    sumDiv.text('TOTAL: ' + sum);
  }  

  makeSum();
}

update();

style.css

body {
  font: 14px helvetica;
  color: #f0f0f0;
  background-color: #333;
}

.row {
  padding: 5px;
}

.axis path,
.axis line {
  fill: none;
  stroke: #f0f0f0;
  shape-rendering: crispEdges;
}

.axis text {
  fill: #f0f0f0;
}

.brush .extent {
  stroke: #f0f0f0;
  fill-opacity: .125;
  shape-rendering: crispEdges;
}

.bar {
  fill: #5EB4E3;
}
.selected {
  fill: #78C656;
}