block by curran 853fa00b8f0732fb2bee7fccfd7b4523

Histogram Smoothing

Full Screen

An experiment related to https://github.com/d3/d3-array/issues/56.

This shows how repeated iterations of a simple moving average acts as a Gaussian Filter.

Built with blockbuilder.org

index.html

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <style>
    * { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
  </style>
</head>

<body>
  <canvas id="rainbow" width="960" height="500"></canvas>
  <canvas id="foreground" width="960" height="500"></canvas>
  <script>
    const numRandomPoints = 300;
    const numHistogramBins = 500;
        
    const canvas = d3.select("canvas");
    const width = canvas.attr("width");
    const height = canvas.attr("height");
    
    const randomPoints = d3.range(numRandomPoints).map(d3.randomNormal());
    const histogram = d3.histogram()
      .domain([-3, 3])
      .thresholds(numHistogramBins);
    let data = histogram(randomPoints)
      .map(d => Object.assign({}, d, { density: d.length}));
    const yValue = d => d.density;
    const xValue = d => (d.x0 + d.x1) / 2;
    
    const xScale = d3.scaleLinear()
    	.domain(d3.extent(data, xValue))
      .range([0, width]);
    
    const yScale = d3.scaleLinear()
    	.domain([0, 2])
      .range([height, 0]);
    
    const rainbow = d3.select("#rainbow").node().getContext("2d");
    const foreground = d3.select("#foreground").node().getContext("2d");
    
//         .attr("fill-opacity", .3)
//         .attr("stroke", "black")
    
    const area = d3.area()
      .x(d => xScale(xValue(d)))
      .y0(yScale(0))
      .y1(d => yScale(yValue(d)));
    
    function renderHistogram(elapsed){
      const t = elapsed / 2 + 175; // Start at blue
      
      rainbow.beginPath();
      area.context(rainbow)(data);
      rainbow.strokeStyle = d3.hsl(t, 1, 0.5);
      rainbow.stroke();
      
      foreground.beginPath();
      area.context(foreground)(data);
      foreground.fillStyle = d3.hsl(t, 1, 0.5, 0.2);
      foreground.clearRect(0, 0, width, height);
      foreground.fill();
    }
    
    function blur(data){
      return data.map((d, i) => {
        const previous = (i === 0) ? i : i - 1;
        const next = (i === data.length - 1) ? i : i + 1;
        const sum = data[previous].density + d.density + data[next].density;
        d.density = sum / 3;
        return d;
      });
    }
    
    d3.timer((elapsed) => {
      data = blur(data);
      renderHistogram(elapsed);
    });

  </script>
</body>