(function() { /** * Initialize D3 by making AJAX call to tsv data */ d3.tsv("inflation.tsv", function(error, data) { data = buildInflation(data); vis = {}; //init vis object buildVis(data); }); /** * Initiate and build vis */ function buildVis(data) { // vis attributes vis.margin = { top: 50, right: 50, bottom: 0, left: 50 }; vis.width = 450 - vis.margin.left - vis.margin.right; vis.height = 450 - vis.margin.top - vis.margin.bottom; vis.gridSize = Math.floor(vis.width / Math.sqrt(data.length)); vis.basePrice = 600; vis.ratio = 1; // create main svg vis.svg = d3.select("#vis").append("svg") .attr("width", vis.width + vis.margin.left + vis.margin.right) .attr("height", vis.height + vis.margin.top + vis.margin.bottom) .append("g") .attr("transform", "translate(" + vis.margin.left + ", " + vis.margin.top + ")"); // create vis colorscale vis.colorScale = d3.scale.quantile() .domain([0, d3.max(data, function(d) { return d.value; })]) .range(colorbrewer.RdYlBu[10].reverse()); // create selected tooltip vis.tooltipSelected = d3.select("body").append("div") .classed("tooltip", true) .classed("tooltip-selected", true) .style("opacity", 0); // create tooltip vis.tooltip = d3.select("body").append("div") .classed("tooltip", true) .style("opacity", 0); // add x-label (target year) vis.svg.append("text") .classed("x-label", true) .attr("text-anchor", "middle") .attr("x", vis.width / 2) .attr("y", -10) .text("Base Year"); // add y-label (base year) vis.svg.append("text") .classed("y-label", true) .attr("text-anchor", "middle") .attr("x", -vis.width / 2) .attr("y", -15) .attr("dy", ".75em") .attr("transform", "rotate(-90)") .text("Target Year"); // create heatmap tiles vis.tiles = vis.svg.selectAll(".tiles") .data(data).enter() .append("rect").classed("tiles", true) .attr("x", function(d) { return d.x * vis.gridSize; }) .attr("y", function(d) { return d.y * vis.gridSize; }) .attr("rx", 1) .attr("ry", 1) .attr("stroke", function(d) { return d.targetYear == 2022 || d.baseYear == 2022 ? "orange" : "lightgray"; }) .attr("stroke-width", "1px") .attr("width", vis.gridSize) .attr("height", vis.gridSize) .attr("fill", function(d) { return vis.colorScale(d.value); }) .on("mouseover", function(d) { tooltipShow(vis.tooltip, d); d3.select(this).attr("stroke", "red"); }) .on("mouseleave", function(d) { tooltipHide(vis.tooltip); d3.select(this) .attr("stroke", function(d) { return d.targetYear == 2022 || d.baseYear == 2022 ? "orange" : "lightgray"; }); }) .on("click", function(d) { tooltipHide(vis.tooltipSelected); tooltipShow(vis.tooltipSelected, d); updateYears(d); }); d3.select("#price").on("input", function() { updatePrice(+this.value); }); } /** * Helper function to build inflation data from tsv data file */ function buildInflation(data) { result = []; for (var x = 0; x < data.length; x++) { baseCPI = +data[x].cpi; baseYear = +data[x].year; for (var y = 0; y < data.length; y++) { targetCPI = +data[y].cpi; targetYear = +data[y].year; d = { baseYear: baseYear, targetYear: targetYear, baseCPI: baseCPI, targetCPI: targetCPI, value: targetCPI / baseCPI, x: x, y: y }; result.push(d); } } return result; } /** * Helper function to update price */ function updatePrice(price) { vis.basePrice = price; d3.selectAll(".base-price").text(price.toFixed(2)); d3.selectAll(".target-price").text((vis.ratio * vis.basePrice).toFixed(2)); } function updateYears(d) { vis.ratio = +d.value; d3.select(".base-year").text(+d.baseYear); d3.select(".target-year").text(+d.targetYear); d3.select(".target-price").text((+d.value * vis.basePrice).toFixed(2)); } /** * Helper function to show tooltip */ function tooltipShow(tooltip, d) { tooltip.style("opacity", 0.9) .html( "ZMW" + vis.basePrice.toFixed(2) + "" + " " + d.baseYear + " (CPI: " + d.baseCPI.toFixed(2) + ") " + "" + "
" + "ZMW" + (vis.basePrice * d.value).toFixed(2) + "" + " " + d.targetYear + " (CPI: " + d.targetCPI.toFixed(2) + ") " + "" ) .style("left", (d3.event.pageX) + "px") .style("top", (d3.event.pageY) + "px"); } /** * Helper function to hide tooltip */ function tooltipHide(tooltip) { tooltip.style("opacity", 0); } })();
body {
  font-family: "Open Sans", sans-serif;
  font-size: 12px;
  font-weight: 400;
  padding-top: 10px;
  padding-bottom: 100px;
}
html {
  overflow-y: scroll;
}
h1 {
  color: steelblue;
  font-weight: 800;
  font-size: 1.7em;
}
h2 {
  color: steelblue;
  font-size: 1.3em;
  padding-bottom: 10px;
}
h3 {
  color: gray;
  font-size: 1.2em;
  padding-bottom: 10px;
}
footer a,
footer a:hover, footer a:visited {
  color: #D2A000;
}
.text-small {
  font-size: 12px;
  font-style: italic;
}
footer {
  color: white;
  padding-top: 5px;
  border-top: 1px solid gray;
  font-size: 12px;
  position: fixed;
  left: 0;
  bottom: 0;
  height: 50px;
  width: 100%;
  background: black;
  text-align: center;
}
pre {
  font-size: 11px;
}
.tooltip {
  font-size: 15px;
  font-weight: bold;
  width: auto;
  height: auto;
  padding: 5px;
  background: white;
  border: 2px solid gray;
  border-radius: 5px;
  pointer-events: none;
}
.tooltip-selected {
  background: #FFD4C2;
  border: 2px solid red;
}
#price {
  width: 75px;
  height: 35px;
}
D3 Inflation Explorer

A ZMW600.00 item in 2022 will cost ZMW211.55 in 1980.

Instructions

Mouseover to view details on relative CPI and prices. Click on a tile to freeze details

2022 years are highlighted in gold.


Details

This project is based on the CPI Inflation Calculator provided by the Bureau of Labor Statistics.

This calculator measures the buying power of the dollar based on the Consumer Price Index (CPI). In general, the relative price formula is:

targetPrice = basePrice x (targetCPI / baseCPI)

A specific example given the 1986 price and calculating the 2011 price:

        The average CPI for 1986 = 55.83

        The average CPI for 2011 = 6.43
        
        2011 Price = 1986 Price x (2011 CPI / 1986 CPI)
                   = ZMW20.00 x (55.83 / 6.43)
                   = ZMW173.6
      

(function() {

  /**
   * Initialize D3 by making AJAX call to tsv data
   */
  d3.tsv("inflation.tsv", function(error, data) {
    data = buildInflation(data);

    vis = {}; //init vis object
    buildVis(data);

  });


  /**
   * Initiate and build vis
   */
  function buildVis(data) {

    // vis attributes
    vis.margin = {
      top: 50,
      right: 50,
      bottom: 0,
      left: 50
    };
    vis.width = 450 - vis.margin.left - vis.margin.right;
    vis.height = 450 - vis.margin.top - vis.margin.bottom;
    vis.gridSize = Math.floor(vis.width / Math.sqrt(data.length));
    vis.basePrice = 600;
    vis.ratio = 1;

    // create main svg
    vis.svg = d3.select("#vis").append("svg")
      .attr("width", vis.width + vis.margin.left + vis.margin.right)
      .attr("height", vis.height + vis.margin.top + vis.margin.bottom)
      .append("g")
      .attr("transform", "translate(" + vis.margin.left + ", " + vis.margin.top + ")");

    // create vis colorscale
    vis.colorScale = d3.scale.quantile()
      .domain([0, d3.max(data, function(d) { return d.value; })])
      .range(colorbrewer.RdYlBu[10].reverse());
    
    // create selected tooltip
    vis.tooltipSelected = d3.select("body").append("div")
      .classed("tooltip", true)
      .classed("tooltip-selected", true)
      .style("opacity", 0);
    
    // create tooltip
    vis.tooltip = d3.select("body").append("div")
      .classed("tooltip", true)
      .style("opacity", 0);
    
    // add x-label (target year)
    vis.svg.append("text")
      .classed("x-label", true)
      .attr("text-anchor", "middle")
      .attr("x", vis.width / 2)
      .attr("y", -10)
      .text("Base Year");
      
    // add y-label (base year)
    vis.svg.append("text")
      .classed("y-label", true)
      .attr("text-anchor", "middle")
      .attr("x", -vis.width / 2)
      .attr("y", -15)
      .attr("dy", ".75em")
      .attr("transform", "rotate(-90)")
      .text("Target Year");

    // create heatmap tiles
    vis.tiles = vis.svg.selectAll(".tiles")
      .data(data).enter()
      .append("rect").classed("tiles", true)
      .attr("x", function(d) { return d.x * vis.gridSize; })
      .attr("y", function(d) { return d.y * vis.gridSize; })
      .attr("rx", 1)
      .attr("ry", 1)
      .attr("stroke", function(d) {
        return d.targetYear == 2022 || d.baseYear == 2022 ? "orange" : "lightgray";
      })
      .attr("stroke-width", "1px")
      .attr("width", vis.gridSize)
      .attr("height", vis.gridSize)
      .attr("fill", function(d) { return vis.colorScale(d.value); })
      .on("mouseover", function(d) {
        tooltipShow(vis.tooltip, d);
        d3.select(this).attr("stroke", "red");
      })
      .on("mouseleave", function(d) {
        tooltipHide(vis.tooltip);
        d3.select(this)
        .attr("stroke", function(d) {
          return d.targetYear == 2022 || d.baseYear == 2022 ? "orange" : "lightgray";
        });
      })
      .on("click", function(d) {
        tooltipHide(vis.tooltipSelected);
        tooltipShow(vis.tooltipSelected, d);
        updateYears(d);
      });
      
    d3.select("#price").on("input", function() {
      updatePrice(+this.value);
    });
  }

  /**
   * Helper function to build inflation data from tsv data file
   */
  function buildInflation(data) {
    result = [];
    for (var x = 0; x < data.length; x++) {
      baseCPI = +data[x].cpi;
      baseYear = +data[x].year;
      for (var y = 0; y < data.length; y++) {
        targetCPI = +data[y].cpi;
        targetYear = +data[y].year;
        d = {
          baseYear: baseYear,
          targetYear: targetYear,
          baseCPI: baseCPI,
          targetCPI: targetCPI,
          value: targetCPI / baseCPI,
          x: x,
          y: y
        };
        result.push(d);
      }
    }
    return result;
  }
  
  /**
   * Helper function to update price
   */
  function updatePrice(price) {
    vis.basePrice = price;
    d3.selectAll(".base-price").text(price.toFixed(2));
    d3.selectAll(".target-price").text((vis.ratio * vis.basePrice).toFixed(2));
  }
  
  function updateYears(d) {
    vis.ratio = +d.value;
    d3.select(".base-year").text(+d.baseYear);
    d3.select(".target-year").text(+d.targetYear);
    d3.select(".target-price").text((+d.value * vis.basePrice).toFixed(2));
  }
  
  /**
   * Helper function to show tooltip
   */
  function tooltipShow(tooltip, d) {
    tooltip.style("opacity", 0.9)
      .html(
        "ZMW<span class='base-price'>" + vis.basePrice.toFixed(2) + "</span>" + 
        "<sub> " + d.baseYear + " (CPI: " + d.baseCPI.toFixed(2) + ") " + "</sub>" + 
        "<br />" +
        "ZMW<span class='target-price'>" + (vis.basePrice * d.value).toFixed(2) + "</span>" +
        "<sub> " + d.targetYear + " (CPI: " + d.targetCPI.toFixed(2) + ") " + "</sub>"
      )
      .style("left", (d3.event.pageX) + "px")
      .style("top", (d3.event.pageY) + "px");
  }

  /**
   * Helper function to hide tooltip
   */
  function tooltipHide(tooltip) {
    tooltip.style("opacity", 0);
  }
})();