Creating a normal distribution curve

In this tutorial we will be creating normal distribution curves using D3.js and a little bit of math.

05101520253035404550051015202530354045505560

Creating the data points

The main thing required to create our data is the probability distribution function for normal distribution. Let’s call it normPDF() which will give the probability of occurence x given a mean of m and a standard deviation of sd.

const normPDF = (m, sd, x) => {
  const numerator = Math.exp(-Math.pow(x - m, 2) / (2 * Math.pow(sd, 2)));
  const denominator = sd * Math.sqrt(2 * Math.PI);
  return numerator / denominator;
};

Let's install the libraries we will use for drawing our chart with `npm` or `pnpm`:
pnpm i d3-shape d3-scale d3-interpolate
<script>
  import { scaleLinear } from 'd3-scale';
  import { line } from 'd3-shape';
  import { cubicInOut } from 'svelte/easing';

  let width = 1000;

  // These are the general steps I follow for building a data visualization

  // Steps
  // 1. Data
  // 2. Basic Setup: Dimensions, Margins & Scales
  // 3. Functions needed to draw the svg elements
  // 4. Elements to be iterated

  // 1. Create or import data

  // Using the below code I will create 400 data points with equal distance in the range from 0 to 100.
  const n = 400;
  const xRange = 100;
  const xDomain = [0, 200];

  // Make the scales reactive to size change (i.e width) note the "$" notation
  $: x = scaleLinear()
    .domain(xDomain)
    .range([margin.left, width - margin.right]);

  $: y = scaleLinear()
    .domain([0, 0.15])
    .range([height - margin.bottom, margin.top]);

  let value =
    { m: 8, sd: 2.5, color: 'red' },
    { duration: 2000, easing: cubicInOut, interpolate }


  $: dists = [{ m: $value.m, sd: $value.sd, color: $value.color }];


  const normPDF = (m, sd, x) => {
    const numerator = Math.exp(-Math.pow(x - m, 2) / (2 * Math.pow(sd, 2)));
    const denominator = sd * Math.sqrt(2 * Math.PI);
    return numerator / denominator;
  };

  const createDatum = (i, dist) => {
    const x = xDomain[0] + (xRange * i) / n;
    const y = normPDF(dist.m, dist.sd, x);
    return {
      dist: dists[0],
      index: i,
      x: x,
      y: y
    };
  };


  const data = new Array(n + 1).fill(0);

  $: data.forEach((datum, i) => {
    data[i] = createDatum(i, dists[0]);
  });

// Final data will look like below
//   {
//     "data": [
//         {
//             "dist": {
//                 "m": 80,
//                 "sd": 3.5,
//                 "color": "yellow"
//             },
//             "index": 0,
//             "x": 0,
//             "y": 4.0596403936444327e-115
//         },
//         {
//             "dist": {
//                 "m": 80,
//                 "sd": 3.5,
//                 "color": "yellow"
//             },
//             "index": 1,
//             "x": 0.25,
//             "y": 2.0722010354446256e-114
//         },
//         //
//     ]
// }


  // 2. Dimensions, Margins, Scales
  let height: number = 250;
  const margin = { top: 40, right: 0, bottom: 0, left: 0 };

  // 3. Functions needed to create  the Data elements
  // Functions that will utilize the data to draw shapes (if needed) e.g when creating, line paths, arcs, sankey etc.... in this case it is the d3 line() function.

  $: lineGenerator = line()
    .x((d) => x(+d.x))
    .y((d) => y(+d.y));

  // 4. Elements to be iterated

  // This time we have only one element which is our curve drawn using the <path> element. So we ll not be using iteration.
</script>

and then we add the markup.

<svg viewBox="-80 -80 900 600" width="900px" height="600px">
  {#if data}
    <g>
      <path
        d={lineGenerator(data)}
        fill={dists[0].color}
        class="fill"
        stroke-width="3"
        style="stroke-width: 1; stroke-linecap: round; stroke-linejoin: round;"
        shape-rendering="crispEdges" />
      <path
        d={lineGenerator(data)}
        fill={'none'}
        class="line"
        stroke={dists[0].color}
        style="stroke-width: 1; stroke-linecap: round; stroke-linejoin: round;"
        shape-rendering="crispEdges" />
    </g>
  {/if}
</svg>

Let’s add the svelte ‘tweened’ so we can animate

  • Coming soon