block by thomasthoren 013425fe0130bfda350b

Basic column chart

Full Screen

A basic column chart, along with automatic data updates.

Credit owed to Mike Bostock’s guides:

index.html

<!DOCTYPE html>
<head>
<title>Updating column chart</title>
<meta charset="utf-8">
<style>

  body {
    padding: 0;
    margin: 0;
    font-family: helvetica, arial, sans-serif;
    background-color: white;
  }

  .bar { fill: cornflowerblue; }
  .exit { fill: indianred; }
  .enter { fill: seagreen; }

  .axis { font: 10px sans-serif; }

  .axis path,
  .axis line {
    fill: none;
    shape-rendering: crispEdges;
  }
  .axis path {stroke: black;}
  .axis line {
    stroke: gray;
    stroke-opacity: 0.2;
    stroke-width: 1px;
  }

  .x.axis path,
  .y.axis path {
    display: none;
  }

  .tick text {
    font-size: 12px;
    fill: black;
    text-shadow:  1px  1px 0 rgba(255, 255, 255, 0.3),
                  1px -1px 0 rgba(255, 255, 255, 0.3),
                 -1px  1px 0 rgba(255, 255, 255, 0.3),
                 -1px -1px 0 rgba(255, 255, 255, 0.3);
  }

  .attribution {
    background-color: lightgray;
    font-size: 12px;
    color: #333;
    padding: 5px 10px;
  }

  .source {
    float: right;
  }

</style>
</head>
<body>
<svg class="chart"></svg>
<div class="attribution"><span>The Lens/Thomas Thoren</span><span class="source">Source: Source</span></div>

<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script>

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

var formatNumber = d3.format("%");

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

var attribution = d3.select(".attribution")
      .style("width", width + margin.left + 'px');

// Used to assign names to column placement values along the x-axis.
var x = d3.scale.ordinal()
    .rangeRoundBands([0, width], 0.1)

var y = d3.scale.linear()
    .range([height, 0]);  // Map reverse order to return from top down. Could avoid this and rework how to calculate height and y below.

// Define axes
var xAxis = d3.svg.axis()
    .scale(x)  // Use the defined x ordinal scale.
    .orient("bottom");

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left")
    .tickFormat(function(d) {
      var s = Math.floor(d * 100);
      return d === Math.floor(y.domain()[1] * 100) / 100
        ? s + "% of occurrences"
        : s;
    })
    .tickSize(-width)
    .tickPadding(10);

var xAxisLabels = chart.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")");

var yAxisLabels = chart.append("g")
      .attr("class", "y axis");

d3.csv("data.csv", type, function(error, data) {
  if (error) throw error;

  x.domain(data.map(function(d) { return d.letter; }))  // data.map creates array with names

  y.domain([0, d3.max(data, function(d) { return d.frequency; })])  // Accessor function to map object values to an array.

  // Draw axes
  xAxisLabels.call(xAxis);
  yAxisLabels.call(yAxis);

  yAxisLabels.selectAll('text')
    .style('text-anchor', 'start');
  yAxisLabels.selectAll('line')
    .attr("x1", function(d, i) {
      return d === Math.floor(y.domain()[1] * 100) / 100  // If last
        ? 100
        : 4;
    });

  // Draw columns
  chart.append('g').attr('id', 'columns').selectAll('.bar')  // Group rect and text together
      .data(data, function(d) { return d.letter; })
    .enter().append('rect')
      .attr("class", "bar")
      .attr("x", function(d) { return x(d.letter); })
      .attr("y", function(d) { return y(d.frequency); })
      .attr("width", x.rangeBand())
      .attr("height", function(d) { return height - y(d.frequency) + "px"; })
      .style("fill-opacity", 1);

  // Move y-axis in front of columns so the labels aren't covered.
  yAxisLabels.each(function() {
    this.parentNode.appendChild(this);
  });
});

function type(d) {
  d.frequency = +d.frequency;  // Coerce to number. By default, CSV values are strings.
  return d;
}

function update(data) {
  // Update y scale input domain values.
  x.domain(data.map(function(d) { return d.letter; }))  // data.map creates array with names
  y.domain([0, d3.max(data, function(d) { return d.frequency; })])  // Accessor function to map object values to an array.

  xAxis.scale(x);  // Use the updated scales
  yAxis.scale(y);

  xAxisLabels.transition()  // Transition to updated axes
      .duration(750)
      .call(xAxis);
  yAxisLabels.transition()
      .duration(750)
      .call(yAxis);

  yAxisLabels.selectAll('text')
    .style('text-anchor', 'start');
  yAxisLabels.selectAll('line')
    .attr("x1", function(d, i) {
      return d === Math.floor(y.domain()[1] * 100) / 100  // If last
        ? 100
        : 4;
    });

  // DATA JOIN
  // Join new data with old elements, if any.
  var bar = chart.selectAll('rect')  // Group rect and text together
      .data(data, function(d) { return d.letter; })

  // UPDATE
  // Update old elements as needed.
  bar.attr("class", "bar")
    .transition()
      .duration(750)
      .attr("x", function(d) { return x(d.letter); })
      .attr("y", function(d) { return y(d.frequency); })
      .attr("width", x.rangeBand())
      .attr("height", function(d) { return height - y(d.frequency) + "px"; });

  // ENTER
  // Create new elements as needed.
  bar.enter().append("rect")
      .attr("class", "enter")
      .attr("x", function(d) { return x(d.letter); })
      .attr("y", "0")
      .attr("width", x.rangeBand())
      .attr("height", function(d) { return height - y(d.frequency) + "px"; })
      .style("fill-opacity", 0)
    .transition()
      .duration(750)
      .attr("y", function(d) { return y(d.frequency); })
      .style("fill-opacity", 1);

  // EXIT
  // Remove old elements as needed.
  bar.exit()
      .attr("class", "exit")
    .transition()
      .duration(750)
      .attr("y", 0)
      .style("fill-opacity", 0)
      .remove();

  // Move y-axis in front of columns so the labels aren't covered.
  yAxisLabels.each(function() {
    this.parentNode.appendChild(this);
  });
}

// Automatically update with random data every 1.5 seconds.
setInterval(function() {
  var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");

  var i;
  var new_data = [];

  for (i = 0; i < alphabet.length; i++) {
    var datum = {};
    datum.letter = alphabet[i];
    datum.frequency = Math.random() / 9;

    new_data.push(datum)
  }

  var number_to_keep = Math.floor(Math.random() * new_data.length);
  if (number_to_keep < 3) {
    number_to_keep = new_data.length;
  }

  new_data = d3.shuffle(new_data)
      .slice(0, number_to_keep)
      .sort(function(x, y) { return d3.ascending(x.letter, y.letter); });

  update(new_data);

}, 1500);

// Allows iframe on bl.ocks.org.
// d3.select(self.frameElement).style("height", height + "px");

</script>
</body>
</html>

data.csv

letter,frequency
A,.12102
B,.01492
C,.02782
D,.04253
E,.12702
F,.02288
G,.02015
H,.06094
I,.06966
J,.00153
K,.00772
L,.04025
M,.02406
N,.06749
O,.07507
P,.01929
Q,.00095
R,.05987
S,.06327
T,.09056
U,.02758
V,.00978
W,.02360
X,.00150
Y,.01974
Z,.00074