Refraction of Light

Physics
Code
chart = {
  const margin = { top: 20, right: 10, bottom: 20, left: 10 };
  const width = fullWidth - margin.left - margin.right,
        height = fullHeight - margin.top - margin.bottom;

  const window = d3.create("svg")
    .attr("width", "100%")
    .attr("height", "100%")
    .attr("viewBox", `0 0 ${fullWidth} ${fullHeight}`)
    .attr("preserveAspectRatio", "xMidYMid meet");

  const svg = window.append("g")
    .attr("class", "refrac")
    .attr("transform", `translate(${margin.left},${margin.top})`);

  const line = d3.line();

  let state = {
    _n: defaults.n,
    _a: defaults.a,
    _pol: defaults.pol,
    get n() {
      return this._n;
    },
    set n(x) {
      this._n = x;
      this.update();
    },
    get a() {
      return this._a;
    },
    set a(x) {
      this._a = x;
      this.update();
    },
    get pol() {
      return this._pol;
    },
    set pol(x) {
      this._pol = x;
      this.update();
    },
    get aT() {
      return Math.asin(this.n);
    },
    get aB() {
      return Math.atan(this.n);
    },
    update: function() {
      this.b = Math.asin(Math.sin(this.a) / this.n);

      // Snell's Law
      this.deflection = {
        t: this.a - this.b,
        r: Math.PI + 2 * this.a
      };

      // Fresnel's Equations
      this.intensity = {
        s: {
          t: Math.pow(2 * Math.cos(this.a)
            / (Math.cos(this.a) + this.n * Math.cos(this.b)), 2),
          r: Math.pow((Math.cos(this.a) - this.n * Math.cos(this.b))
            / (Math.cos(this.a) + this.n * Math.cos(this.b)), 2),
        },
        p: {
          t: Math.pow(2 * Math.cos(this.a)
            / (Math.cos(this.b) + this.n * Math.cos(this.a)), 2),
          r: Math.pow((this.n * Math.cos(this.a) - Math.cos(this.b))
            / (Math.cos(this.b) + this.n * Math.cos(this.a)), 2),
        }
      };
    }
  };

  // set up defaults
  state.update();

  const radius = width / 2,
        origin = [0, height / 2],
        pivot = [origin[0] + radius, origin[1]];

  svg.selectAll(".ray")
    .data([[origin], [pivot], [pivot]])
    .join("path")
      .attr("class", (d, i) => `ray ray${i}`);

  svg.append("path")
    .attr("class", "surface");

  function updateData() {
    // input ray
    svg.select(".ray0").datum([origin, pivot]);

    // transmitted ray
    if (state.n < 1 && Math.abs(state.a) >= state.aT) {
      state.intensity[state.pol].r = 1; // could be Nan
      svg.select(".ray1").datum([pivot]);
    } else {
      svg.select(".ray1").datum([pivot, [
        pivot[0] + radius * Math.cos(state.deflection.t),
        pivot[1] + radius * Math.sin(state.deflection.t),
      ]]);
    }

    // reflected ray
    svg.select(".ray2").datum([pivot, [
      pivot[0] + radius * Math.cos(state.deflection.r),
      pivot[1] + radius * Math.sin(state.deflection.r),
    ]]);
  }

  function updateChart() {
    svg.select(".surface")
      .attr("d", line([
        [
          radius * Math.sin(state.a) + pivot[0],
          -radius * Math.cos(state.a) + pivot[1],
        ], [
          -radius * Math.sin(state.a) + pivot[0],
          radius * Math.cos(state.a) + pivot[1],
        ],
      ]));

    // input ray
    svg.select(".ray0")
      .attr("d", line)
      .attr("opacity", 1);

    // transmitted ray
    svg.select(".ray1")
      .attr("d", line)
      .attr("opacity", state.intensity[state.pol].t);

    // reflected ray
    svg.select(".ray2")
      .attr("d", line)
      .attr("opacity", state.intensity[state.pol].r);

    setInputDisabled(viewof _onTotal, state.n >= 1);
    setInputDisabled(viewof _onZero, state.pol == "s");
  }

  function updateAll() {
    updateData();
    updateChart();
  }

  invalidation.then(() => {
    svg.selectAll("*").interrupt();
  });

  function rotationTween(a) {
    return function() {
      const interpolate = d3.interpolateNumber(state.a, a);
      return function(t) {
        state.a = interpolate(t);
        updateAll();
        setInputValue(viewof _a, state.a);
      };
    }
  }

  return Object.assign(window.node(), {
    update(values) {
      state.a = values.a;
      state.n = values.n;
      state.pol = values.pol;
      updateAll();
    },
    setTotal(value) {
      if (value > 0) {
        svg.transition()
          .duration(500)
          .tween("rotate", rotationTween(state.aT));
      }
    },
    setZero(value) {
      if (value > 0) {
        svg.transition()
          .duration(500)
          .tween("rotate", rotationTween(state.aB));
      }
    },
  });
}

This interactive visualization demonstrates the reflection and refraction of a light beam at the interface of a medium, as described by Snell’s law and Fresnel’s equations. Depending on the value of the refractive index and the polarization, the incoming beam can be fully reflected or fully refracted.

Resources

Tools