block by nstrayer ae6f31caccccc591ad6e6eb8d904bb28

Demonstration of a sliding histogram.

Full Screen

The height of the line represents the number of values falling within 1/2 of the bin width on either side of the x axis point.

So if the line is at height 200 for an x value of 20 and your binwidth is 10, then there are 200 points from the range of 15 to 25 in the data.

Eventually to be made into an R htmlwidgets library.

ToDo:

Make the bin interval sit as a legend normally but then when moused over it becomes the interval that we use on the y axis.

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>

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

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

.axis--x path {
  display: none;
}

.line {
  fill: none;
  stroke: steelblue;
  stroke-width: 1.5px;
}

svg text{
    font-family:optima;
}

</style>
<body>
    <div id = "chart"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="sliding_hist.js"></script>

<script>

//Function to generate vector of normals.
var randomNormalArray = (n, mu, variance) => [...new Array(n)]
    .map((_, i) => d3.randomNormal(mu, variance)());

//Generate our data
var data = randomNormalArray(500, 100, 10)

var slidey = sliding_hist()
    .height(500)
    .width(800)
    .fillColor("#a6bddb")
    .binWidth(10);

d3.select("#chart")
      .datum(data)
      .call(slidey);


</script>

sliding_hist.js

function sliding_hist() {
  var chartWidth  = 960, // default width
      chartHeight = 500, // default height
      binWidth    = 10,
      stepWidth   = 1,
      fillColor   = "steelblue"

    //Need last element of array later, neater to define logic here.
    Array.prototype.last = function() {
      return this[this.length-1];
    }

    //takes your data, bin width and how much the bin slides over each step and
    //returns an array of objects containing the "center" of the bin and the number
    //of elements that fell in it ("in_bin").
    function generate_slide_hist(data, binWidth, stepSize){
        var binHalf = binWidth/2;

        //Find the range of the data so we can know which window to slide overlay
        var data_range = d3.extent(data)

        //generate an array of the bin centers we are going to use in our sliding interval.
        var bin_centers = [data_range[0] ]
        while(bin_centers.last() < data_range[1]) bin_centers.push(bin_centers.last() + stepSize)

        //Run through the bin centers counting how many are within the bin width of the value.
        var counts = []
        bin_centers.forEach(function(center){
            var points_in_bin = data.filter(function(val){return val > (center - binHalf) &&  val < (center + binHalf) })
            counts.push({"center": center, "in_bin": points_in_bin.length})
        })

      return counts;
    }

  function chart(selection) {
      selection.each(function(data){

          //make into the correct form.
          var hist_data = generate_slide_hist(data,binWidth,stepWidth);

          var margin = {top: 20, right: 20, bottom: 30, left: 50},
              width  = chartWidth - margin.left - margin.right,
              height = chartHeight - margin.top - margin.bottom;

          var dataRange = d3.extent(data)
          var x = d3.scaleLinear()
              .range([0, width])
              .domain(dataRange);

          var y = d3.scaleLinear()
              .range([height, 0])
              .domain([0,d3.max(hist_data, function(d) { return d.in_bin; })]);

          var line = d3.line()
              .x(function(d) { return x(d.center); })
              .y(function(d) { return y(d.in_bin); });

          //shade under the line like a proper histogram.
          var area = d3.area()
               .x(function(d) { return x(d.center); })
               .y0(height)
               .y1(function(d) { return y(d.in_bin); });

          var svg = d3.select(this).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 + ")");

          //shade under the line.
          svg.append("path")
            .datum(hist_data)
            .attr("class", "area")
            .style("fill", fillColor)
            .style("fill-opacity", 0.5)
            .attr("d", area);

          svg.append("path")
            .datum(hist_data)
            .attr("class", "line")
            .attr("d", line);

          svg.append("g")
              .attr("class", "axis axis--x")
              .attr("transform", "translate(0," + height + ")")
              .call(d3.axisBottom(x));

          svg.append("g")
              .attr("class", "axis axis--y")
              .call(d3.axisLeft(y))
              .append("text")
                  .attr("class", "axis-title")
                  .attr("transform", "rotate(-90)")
                  .attr("y", 6)
                  .attr("dy", ".71em")
                  .style("text-anchor", "end")
                  .text("# in x +- binwidth/2");

          var focus = svg.append("g")
              .attr("class", "focus")
              .style("display", "none");

          focus.append("circle")
            .attr("r", 4.5)
            .style("fill", "none")
            .style("stroke", "black");

          var drop_line = focus.append("line")
              .attr("x1", 0)
              .attr("y1", 0)
              .attr("x2", 0)
              .attr("y2", 0)
              .attr("stroke","black");

        //how many pixles the interval takes up on the screen.
        var interval_width = x(dataRange[0] + binWidth/2);

        var interval_line = svg.append("g")
            .attr("class", "interval_line")

        //where the interval legend sits on plot when not being used for mouseover.
        var interval_rest = "translate(" + (width - interval_width - 20) + "," + (height/7) + ")"

        //place the interval line as a legend when not being interacted with.
        interval_line.attr("transform", interval_rest);

        var interval_legend_text = svg.append("text")
            .text("Bin Width: " + binWidth)
            .attr("text-anchor", "middle")
            .attr("font-size", "0.9em")
            .attr("y", -14)
            .attr("transform", interval_rest);

        interval_line.append("line")
            .attr("class", "wide_line")
            .attr("x1", interval_width)
            .attr("y1", -10)
            .attr("x2", -interval_width)
            .attr("y2", -10)
            .attr("stroke","black")
            .attr("stroke-width","1px");

        interval_line.append("line")
            .attr("class", "left_line")
            .attr("x1", -interval_width)
            .attr("y1", 0)
            .attr("x2", -interval_width)
            .attr("y2", -10)
            .attr("stroke","black")
            .attr("stroke-width","1px");

        interval_line.append("line")
            .attr("class", "right_line")
            .attr("x1", interval_width)
            .attr("y1", 0)
            .attr("x2", interval_width)
            .attr("y2", -10)
            .attr("stroke","black")
            .attr("stroke-width","1px");

          focus.append("text")
            .attr("x", 9)
            .attr("dy", ".35em");

          svg.append("rect")
             .attr("class", "overlay")
             .attr("width", width)
             .attr("height", height)
             .style("fill","none")
             .style("pointer-events", "all")
             .on("mouseover", function() {
                 focus.style("display", null); })
             .on("mouseout",  function() {
                 interval_line.transition().duration(200).attr("transform", interval_rest);
                 focus.style("display", "none"); })
             .on("mousemove", mousemove);

           var bisectX = d3.bisector(function(d) { return d.center; }).left;

           function mousemove() {
              var x0 = x.invert(d3.mouse(this)[0]),
                  i = bisectX(hist_data, x0, 1),
                  d0 = hist_data[i - 1],
                  d1 = hist_data[i],
                  d = x0 - d0.center > d1.center - x0 ? d1 : d0;
              focus.attr("transform", "translate(" + x(d.center) + "," + y(d.in_bin) + ")");
              drop_line
                .attr("y1", 4.5)
                .attr("y2", (height - y(d.in_bin) -10));

              interval_line
                .transition()
                .duration(20)
                .attr("transform", "translate(" + x(d.center) + "," + (height ) + ")");

              focus.select("text").text(d.in_bin);
            }
      })

    }
    chart.binWidth = function(value) {
        if (!arguments.length) return binWidth;
        binWidth = value;
        return chart;
    };

    chart.stepWidth = function(value) {
        if (!arguments.length) return stepWidth;
        stepWidth = value;
        return chart;
    };

    chart.height = function(value) {
        if (!arguments.length) return chartHeight;
        chartHeight = value;
        return chart;
    };

    chart.width = function(value) {
        if (!arguments.length) return chartWidth;
        chartWidth = value;
        return chart;
    };

    chart.fillColor = function(value) {
        if (!arguments.length) return fillColor;
        fillColor = value;
        return chart;
    };

    return chart;
}