block by pstuffa 6c04653830ecfc089932

Bar Chart with Update

Full Screen

This example I made for Advance Local, a media group that has collects data on users of many different media outlets. Color scales are used to differentiate between different categories, and custom transitions create a nice smooth for comparing different media outlets.

index.html

<!DOCTYPE html>
<!-- Paul Buffa 2015
  -->
<meta charset="utf-8">
<link href='//fonts.googleapis.com/css?family=Lato&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
<style>

body {
  font: 10px Lato;
}

select {
  color: #000;
  font-size: 14px;
  font-weight: bold;
  padding: 2px 10px;
  width: 378px;
}

p {
  font: 14px Lato;
  margin-left: 100px;
  max-width: 800px;
}

#disclaimer {

  font: 10px Lato;
  max-width: 300px;

}

h1 {
  font: 26px Lato ;
  margin-left: 100px;
  font-weight: bold;
}


#selectSpot {
  margin-left: 100px;
}

#areaList {
  margin-left: 95px;
}

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

.x {
  font-size: 0;
}

rect {
  stroke: #000;
  stroke-opacity: .5;
  stroke-width: .1;
  fill-opacity: .8;
}

</style>
<body>
   <h1> We Have Engaged Audiences</h1>
  <p>
    Our data-driven approach to Audience Targeting enables us to reach everyone from hyper-local auto intenders to huge national audiences with interest and purchase intent in products and services just like yours.  We have thousands of pre-built audiences, but if you require a custom, highly targeted audience  to grow your business just ask: we can likely find a way to reach your best customers and prospects, wherever they are. 
  </p>

  <div id='selectSpot'>
  </div>
    <div id='areaList'>
  </div>

<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/numeral.js/1.4.5/numeral.min.js"></script>
<script>

var areas = ['MLive.com','NJ.com','AL.com & gulflive.com','OREGONLIVE','NOLA.com','cleveland.com','syracuse','PennLive','MassLive','SILive.com' ,'Advance Local'],
// CHANGE VERSION HERE
    version = 'Advance Local';

var margin = {top: 20, right: 150, bottom: 100, left: 100},
    width = 1050 - margin.left - margin.right,
    height = 350 - margin.top - margin.bottom,
    recWidth = 15;

var x = d3.scale.ordinal()
    .rangeBands([0,width]);

var y = d3.scale.linear()
    .range([height, 0]);

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

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

var colorScale = d3.scale.category10();

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 + ")");

// Background Rect
svg.append("rect")
   .attr("width", width)
   .attr("height", height)
   .style("fill-opacity", .05);

// Load in Market Data 
d3.tsv("data.tsv", function(error, data) {

data.forEach(function(d) {
    d['Total'] = +d['Total']
    d['Aud'] = d['Audience'].replace(/\s+/g, '').replace(/\W/g, '');
    d['Cat'] = d['Category'].replace(/\s+/g, '').replace(/\W/g, '');
    });

var nestedData = d3.nest()
    .key(function(d) {return d.Type;})
    .sortKeys(d3.descending)
    .sortValues(function(a,b) {return b['Total']-a['Total'];})
    .entries(data);

var selectedData = nestedData.filter(function(d) { return d.key === version; })
console.log(nestedData)
// Setting X & Y Domains based on the data 
  y.domain(d3.extent(selectedData[0].values.map(function(d, i) { return d.Total; }))).nice();
  x.domain(selectedData[0].values.map(function(d) { return d.Audience;  }));

  svg.selectAll(".bar")
      .data(selectedData[0].values)
    .enter().append("rect")
      .attr("class", function(d) { return  "bar " + d.Aud + " " + d.Cat})
      .attr("rx", 2) 
      .attr("ry", 2)
      .attr("x", function(d) { return x(d.Audience); })
      .attr("y", function(d) { return y(d.Total); })
      .attr("height", function(d) { return height - y(d.Total); })
      .attr("width", recWidth)
      .style("fill", function(d) { return colorScale(d.Category); });

  // Axis Labels
  svg.selectAll("text")
      .data(selectedData[0].values)
    .enter().append("text")
      .attr("class", function(d) { return "axisText " + d['Aud']})
      .attr("y", height + 14)
      .attr("text-anchor", "top")
      .attr("transform",function(d) { return "rotate(45 " + x(d['Audience']) + "," + (height + 14) + ")";})
      .attr("x", function(d, i) { return x(d['Audience']); })
      .text(function(d) { return d['Audience']});


  // appending the axes 
  svg.append("g")
     .attr("class", "x axis")
     .attr("transform", "translate(0," + height + ")")
     .call(xAxis);
    
  svg.append("g")
     .attr("class", "y axis")
     .call(yAxis);

// Need to make these mobile compatitble 
  svg.append("text")
     .attr("class","label")
     .attr("x", -40)
     .attr("y", 30)
     .attr("text-anchor", "top")
     .attr("transform",function(d) { return "rotate(270 " + 10 + "," + (height / 2) + ")";})
     .style("font-size","12px")
     .text("Unique Monthly Visitors");

  svg.append("text")
     .attr("class","label")
     .attr("x", width - 10)
     .attr("y", - 10)
     .attr("text-anchor", "end")
     .style("font-size","12px")
     .text("Click to filter by category");

  // Dropdown Filter
  d3.select("#selectSpot")
     .append("text")
     .attr("class","label")
     .attr("text-anchor", "top")
     .style("font-size","12px")
     .text("Click to see how our audiences shift across the country.");


  var select  = d3.select("#areaList")
                  .append("select")
                  .on("change", change),
      options = select.selectAll('option').data(areas); // Data join

  // Enter selection
  options.enter().append("option").text(function(d) { return d; });

  // Change the dataset 
  function change() {

    // turn off pointer events 
    d3.selectAll(".bar")
      .style("pointer-events","None");

    // Retrieve dropdown selection
    var newValue = this.value;
    newData = nestedData.filter(function(d) { return d.key === newValue; })

    // Reset domains 
    y.domain(d3.extent(newData[0].values.map(function(d, i) { return d.Total; }))).nice();
    x.domain(newData[0].values.map(function(d) { return d.Audience;  }));

    // Replot bars using update method 
    svg.selectAll(".bar")
        .data(newData[0].values)
        .transition()
        .delay(function(d,i) {return i * 20;})
        .duration(1000)
        .attr("class", function(d) { return  "bar " + d['Aud'] + " " + d['Cat']})
        .attr("width",recWidth)
        .attr("height", function(d,i) { return height - y(d['Total']); })
        .style("fill", function(d,i) { return colorScale(d['Category']); })
        .attr("rx", 2) 
        .attr("ry", 2)
        .attr("y", function(d,i) { return y(d['Total']); })
        .attr("x", function(d,i) { return x(d['Audience']); })
        .each("end", function() { d3.selectAll(".bar").style("pointer-events","all"); })

    // Axis Labels
    svg.selectAll("text")
        .data(newData[0].values)
        .transition()
        .duration(1000)
        .attr("class", function(d) { return "axisText " + d['Aud']})
        .attr("y", height + 14)
        .attr("text-anchor", "top")
        .attr("transform",function(d) { return "rotate(45 " + x(d['Audience']) + "," + (height + 14) + ")";})
        .attr("x", function(d, i) { return x(d['Audience']); })
        .text(function(d) { return d['Audience']});

    // Reset axes 
    svg.select(".y")
       .attr("class", "y axis")
       .transition()
       .duration(1000)
       .call(yAxis);

    svg.select(".x")
       .attr("class", "x axis")
       .transition()
      .delay(function(d,i) {return i * 20;})
       .duration(1000)
       .attr("transform", "translate(0," + height + ")")
       .call(xAxis);

    }

  // Hover Events 
  function hover(d,i) {

    svg.selectAll(".bar")
      .transition()
      .delay(100)
      .duration(2000)
      .ease("elastic")
      .attr("height", 10)
      .attr("y",height - 10)
      .style("fill-opacity",.3);

  var textFill = d['Audience'];
      textFillTwo = d['Category'],
      textFillThree = "Audience of " + numeral(d['Total']).format('0,0'),
      xLoc = d['Audience'],
      yLoc = d['Total'];

    d3.selectAll(".bar" + d['Aud'])
      .transition()
      .duration(2000)
      .attr("height", function(d,i) { return height - y(yLoc); })
      .attr("y", function(d,i) { return y(yLoc); })
      .style("fill-opacity",1);

    d3.selectAll("." + d['Aud'])
      .transition()
      .duration(500)
      .style("font-size","12px");

    svg.append("text")
       .attr("class","box")
       .attr("x", function(d,i) { if(y(yLoc) < (height/3)) { return x(xLoc) + 25; } else { return x(xLoc); }})
       .attr("y", function(d,i) { if(y(yLoc) < (height/3)) { return y(yLoc) + 15; } else { return y(yLoc) - 45; }})
       .attr("text-anchor", "top")
       .style("font-size","18px")
       .text(textFill);

    svg.append("text")
       .attr("class","box")
       .attr("x", function(d,i) { if(y(yLoc) < (height/3)) { return x(xLoc) + 25; } else { return x(xLoc); }})
       .attr("y", function(d,i) { if(y(yLoc) < (height/3)) { return y(yLoc) + 35; } else { return y(yLoc) - 25; }})
       .attr("text-anchor", "top")
       .style("font-size","18px")
       .text(textFillTwo);

    svg.append("text")
       .attr("class","box")
       .attr("x", function(d,i) { if(y(yLoc) < (height/3)) { return x(xLoc) + 25; } else { return x(xLoc); }})
       .attr("y", function(d,i) { if(y(yLoc) < (height/3)) { return y(yLoc) + 55; } else { return y(yLoc) - 5; }})
       .attr("text-anchor", function(d,i) { if(y(yLoc) < (height/3)) { return "end"; } else { "top"; }})
       .attr("text-anchor", "top")
       .style("font-size","18px")
       .text(textFillThree);
    }


  function hoverOut() {

    d3.selectAll(".box").remove();
    d3.selectAll(".bar").transition().delay(100).style("pointer-events","none"); 

    svg.selectAll(".bar")
        .transition()
        .duration(2000)
        .ease("elastic")
        .attr("height", function(d,i) { return height - y(d['Total']); })
        .attr("y", function(d,i) { return y(d['Total']); })
        .style("fill-opacity",1)
        .each("end", function() { d3.selectAll(".bar").style("pointer-events","all"); });

    svg.selectAll(".axisText")
        .transition()
        .duration(500)
        .ease("linear")
        .style("font-size","10px");

    }

  svg.selectAll(".bar")
     .on("mouseover", hover)
     .on("mouseout", hoverOut);

  // Click on Legend to "Filter" Dataset 
  function legendClick() {

  // Removing non-alphabetic characters for selection 
   var filterAud = this.__data__.replace(/\s+/g, '').replace(/\W/g, '');
      
   svg.selectAll(".bar")
      .transition()
      .duration(2000)
      .ease("elastic")
      .attr("height", 10)
      .attr("y",height - 10)
      .style("fill-opacity",.3);

   svg.selectAll("." + filterAud)
      .transition()
      .duration(2000)
      .ease("elastic")
      .attr("height", function(d,i) { return height - y(d['Total']); })
      .attr("y", function(d,i) { return y(d['Total']); })
      .style("fill-opacity",1);

    }


  // Legend 
  var legend = svg.selectAll(".legend")
        .data(colorScale.domain())
      .enter().append("g")
        .attr("class", "legend")
        .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });

  legend.append("rect")
        .attr("x", width - 22)
        .attr("y", 4)
        .attr("width", 18)
        .attr("height", 18)
        .style("fill", colorScale)
        .on("click", legendClick);

  legend.append("text")
        .attr("x", width - 28)
        .attr("y", 13)
        .attr("dy", ".35em")
        .style("text-anchor", "end")
        .style("font-size","12px")
        .text(function(d) { return d; });

}); // Ends Data Function

</script>
<br />
<p id="disclaimer">
  Source: Lotame Data Management Platform, Advance Digital Audiences, July 2015.  Exact audience sizes are provided for comparison purposes only and will vary over time and with seasonality. 
</p>
</body>
</html>