block by armollica f130a6e93e73d65252b3

Teacher Wages

Full Screen

Annual wages for Elementary and High School teachers by U.S. state.

The box shows the range of the 25th to 75th percentile of wages. The median wage is the tick in the middle. The two ticks on either side of the box show the 10th and 90th percentile wages.

Data source: BLS

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Teacher Wages</title>
    <style>
      body {
        font: 12px Helvetica, sans-serif;
      }

      .tooltip {
        position: absolute;
        background-color: rgba(255, 255, 255, 0.95);
        padding: 5px 5px;
        box-shadow: 2px 2px 1px #888888;
      }

      .hidden {
        display: none;
      }

      .axis path,
      .axis line {
        fill: none;
        stroke: #000;
      }

      .series-title {
        text-anchor: middle;
        font-size: 16px;
        font-weight: bold;
      }

      .box {
        fill: #fff;
        stroke: #000;
        stroke-width: .5px;
      }

      .box-tick {
        stroke: #000;
        stroke-width: 1px;
        shape-rendering: crispEdges;
      }

      form {
        position: absolute;
        right: 10px;
        bottom: 10px;
      }

      .hover-line {
        opacity: 0;
        stroke: tomato;
        stroke-width: 1px;
        stroke-dasharray: 5, 2;
        pointer-events: none;
      }

      .hover-line.active {
        opacity: 1;
      }

      .hover-rect {
        opacity: 0;
      }

      .highlight {
        fill: tomato;
      }

    </style>
  </head>
  <body>
    <form>
      Sort by
      <label><input type="radio" name="sortBy" value="elementary" checked="true"> Elementary School Teacher Wage</label>
      <label><input type="radio" name="sortBy" value="highSchool"> High School Teacher Wage</label>
      <label><input type="radio" name="sortBy" value="alphabetical"> State Name (alphabetically)</label>
    </form>
    <script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
    <script>

      var margin = { top: 20, left: 150, bottom: 60, right: 10 },
          width = 960 - margin.right - margin.left,
          height = 800 - margin.top - margin.bottom,
          chartBuffer = 10;

      var scale = {
        x: d3.scale.linear().range([0, (width/2 - chartBuffer)]),
        y: d3.scale.ordinal().rangeRoundBands([height, 0], 0.4)
      };

      var axis = {
        x: d3.svg.axis().scale(scale.x).orient("bottom")
            .tickFormat(d3.format("$,"))
            .ticks(5),
        y: d3.svg.axis().scale(scale.y).orient("left")
      };

      var tooltip = d3.select("body").append("div")
        .attr("class", "tooltip")
        .classed("hidden", true);

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

      var elementary = svg.append("g");
      var highSchool = svg.append("g")
          .attr("transform", "translate(" + (width/2 + chartBuffer) + ",0)");

      elementary.append("text")
        .attr("class", "series-title")
        .attr("dy", -5)
        .attr("dx",  (width/2 - chartBuffer)/2)
        .text("Elementary School Teachers");

      elementary.append("rect")
        .attr("class", "hover-rect")
        .attr("width", width/2 - chartBuffer)
        .attr("height", height)
        .on("mousemove", mousemoveTooltip)
        .on("mouseleave", mouseleaveTooltip);

      highSchool.append("text")
        .attr("class", "series-title")
        .attr("dy", -5)
        .attr("dx",  (width/2 - chartBuffer)/2)
        .text("High School Teachers");

      highSchool.append("rect")
        .attr("class", "hover-rect")
        .attr("width", width/2 - chartBuffer)
        .attr("height", height)
        .on("mousemove", mousemoveTooltip)
        .on("mouseleave", mouseleaveTooltip);

      svg.append("g").attr("class", "x axis elementary")
        .attr("transform", "translate(0," + height + ")");
      svg.append("g").attr("class", "x axis highSchool")
        .attr("transform", "translate(" + (width/2 + chartBuffer) + "," + height + ")");
      svg.append("g").attr("class", "y axis");

      svg.append("text").attr("")

      d3.json("teacher-wages.json", function(error, data){
        if (error) throw error;

        scale.x.domain([0, d3.max(data, function(d) { return d.wages[4].wage; })]);

        var elementaryData = data
          .filter(function(d) { return d.occupation == "elementary"; })
          .sort(function(a, b) { return a.wages[2].wage - b.wages[2].wage; });
        var highSchoolData = data
          .filter(function(d) { return d.occupation == "highSchool"; })
          .sort(function(a, b) { return a.wages[2].wage - b.wages[2].wage; });

        var yDomains = {
          elementary: elementaryData.map(function(d) { return d.state }),
          highSchool: highSchoolData.map(function(d) { return d.state }),
          alphabetical: elementaryData.map(function(d) { return d.state }).sort(d3.descending)
        };

        scale.y.domain(yDomains["elementary"]);

        svg.select(".x.axis.elementary").call(axis.x);
        svg.select(".x.axis.highSchool").call(axis.x);
        svg.select(".y.axis").call(axis.y);

        elementary.call(render, elementaryData, 0)
          .append("line") 
            .attr("class", "hover-line")
            .attr("y1", 0)
            .attr("y2", height);

        highSchool.call(render, highSchoolData, 0)
          .append("line") 
            .attr("class", "hover-line")
            .attr("y1", 0)
            .attr("y2", height);

        d3.select("form").on("change", function() {
          var transitionDuration = 500;

          var sortBy = event.target.value;

          scale.y.domain(yDomains[sortBy]);

          svg.select(".y.axis")
            .transition().duration(transitionDuration)
              .call(axis.y);
          elementary.call(render, elementaryData, transitionDuration);
          highSchool.call(render, highSchoolData, transitionDuration);
        });
      });

      function render(selection, data, duration) {

        // Draw g for each state
        var states = selection.selectAll(".state").data(data);
        states.enter().append("g")
          .attr("class", "state");
        states
          .transition().duration(duration)
            .attr("transform", function(d) {
              return "translate(0," + (scale.y(d.state)) + ")";
            });
        states.exit().remove();

        // Draw IQR box
        var boxes = states.selectAll(".box")
          .data(function(d) {
            return [{
              x: scale.x(d.wages[1].wage),
              dx: scale.x(d.wages[3].wage) - scale.x(d.wages[1].wage),
              y: 0,
              dy: scale.y.rangeBand()
            }];
          });
        boxes.enter().append("rect")
          .attr("class", "box")
          .on("mouseenter", mouseenterBox)
          .on("mousemove", mousemoveBox)
          .on("mouseleave", mouseleaveBox);
        boxes
          .attr("x", function(d) { return d.x; })
          .attr("y", function(d) { return d.y; })
          .attr("width", function(d) { return d.dx; })
          .attr("height", function(d) { return d.dy; });
        boxes.exit().remove();

        // Draw ticks for 10th, 25th, 50th, 75th and 90th percentiles
        var boxTicks = states.selectAll(".box-tick")
          .data(function(d) { return d.wages; });
        boxTicks.enter().append("line")
          .attr("class", "box-tick");
        boxTicks
          .attr("x1", function(d) { return scale.x(d.wage); })
          .attr("x2", function(d) { return scale.x(d.wage); })
          .attr("y1", 0)
          .attr("y2", scale.y.rangeBand());
        boxTicks.exit().remove();

      }

      function mouseenterBox() {
        var hoverRect = d3.select(this.parentNode.parentNode)
          .select(".hover-rect").node();
        mousemoveTooltip.call(hoverRect);
        var state = d3.select(this.parentNode).datum().state;
        d3.select(".y.axis").selectAll(".tick text")
          .classed("highlight", function(axisState) { return axisState === state; });
      }

      function mousemoveBox() {
        var hoverRect = d3.select(this.parentNode.parentNode)
          .select(".hover-rect").node();
        mousemoveTooltip.call(hoverRect);
      }

      function mouseleaveBox() {
        var hoverRect = d3.select(this.parentNode.parentNode)
          .select(".hover-rect").node();
        mousemoveTooltip.call(hoverRect);
        d3.select(".y.axis").selectAll(".tick text")
          .classed("highlight", false);
      }

      function mousemoveTooltip() {
        var mouse = d3.mouse(d3.select("body").node()),
            x = d3.mouse(this)[0];
        var wage = scale.x.invert(x);
        tooltip
          .classed("hidden", false)
          .style("left", mouse[0] + 25 + "px")
          .style("top", mouse[1] - 10 + "px")
          .html("Wage: <br>" + d3.format("$,.0f")(wage));
        
        d3.select(this.parentNode).select(".hover-line")  
          .classed("active", true)
          .attr("x1", x)
          .attr("x2", x);
      }

      function mouseleaveTooltip() {
        tooltip.classed("hidden", true);
        d3.select(this.parentNode).select(".hover-line")
          .classed("active", false); 
      }
    </script>
  </body>
</html>