d3.interpolate and d3.interpolateNumber | Dirty D3 - Episode 4

💛 Source code on Github

FIDDLE: Simple Code Example

Okay, so let's create an interpolator for my old commute:

let trainJourney = d3.interpolate(0, 69);

So what d3.interpolate does is that it takes parameters a plus b (0 and 69 here), and creates and returns an interpolator function that interpolates between a and b.

We can then use this interpolator to generate the midpoint of the journey, like this:

let midpoint = trainJourney(0.5);
display(midpoint);

The closer the argument is to 1, the closer we are to 100% of the journey, so if I change it to 0.99...

let midpoint = trainJourney(0.99);
display(midpoint);

... and at 1, or 100% of the journey, it returns the full distance of 69, that is, 69km:

let midpoint = trainJourney(1);
display(midpoint);

And, as you might have guessed now, at 25% of the journey, we are at ...

let t = 0.25;
let midpoint = trainJourney(t);
display(midpoint);

t-value tirade

After working with these examples for a while, I started wondering what word to use when referring this number you give to the interpolation function, what is the name of this argument, because you know, I want to fake competence when I am talking about these things.

It's t. [shows source code of interpolator] If you're a programmer or another type of sane person, you might wonder what that is short for but t isn't short for anything. This is something you just have to accept: mathematicians are ... feral. They don't "name" things - they glimpse the Platonic essence and assign it a letter. Why debase yourself with descriptive names when you can use t and feel the raw power of abstraction? ℝ, ∈, φ or ∮ [makes guttural mathematical noises]

THROWBACK: Connection to scaleLinear

So, now - remember that the reason we are exploring all this in the first place is because we were not completely unconfused about this particular sentence in the documentation for d3.scaleTime:

[Reads: "Constructs a new time scale with the specified domain and range, the default interpolator and clamping disabled."]

So this means that scaleTime, per default, uses this - the default interpolator, d3.interpolate behind the scenes.

And, if we look back at the docs for d3.scaleLinear, the definition of that function is almost exactly: "Constructs a new linear scale with the specified domain and range, the default interpolator, and clamping disabled."

I'm probably wrong and oversimplifying, but right now, it helps for me to think of scaling functions as interpolators with more bells and whistles. For example, since we now know that scaleLinear uses d3.interpolate under the hood (unless you tell it otherwise I guess), we can do something like this to get to get the same behaviour from a scale:

let stockholmUppsala = d3.scaleLinear().range([0, 69]);
let midPointFromScale = stockholmUppsala(0.5);
display({ midPointFromScale });

As you might remember from the last episode, when we don't provide a domain to the scale, the input domain just becomes [0 -> 1], so the input domain sort of is just 0% to 100%.

BUT I remember that we read something earlier in passing about an invert method, which I suppose is one of the additional functionalites that a scale adds on top of an interpolator.

Because D3 is actually kind of intuitive once you understand the logic of it, I think I can kind of guess how this syntax looks like:

display(stockholmUppsala.invert()(34.5));

SHIT okay so much for my "oh look you don't need an LLM to code kids" - the syntax is actually like this, you actually don't need to create a new scale to invert it:

display(stockholmUppsala.invert(34.5));

So as you see here, this allows us to use the scale in the opposite direction, we pass in 34.5km, and we get 50% out, as it is the midpoint.

How are you doing? Are you kind of sort of getting it? Maybe? If you are uncertain, that's good, because in Dirty D3 we're not here to merely get it, we're here to get "fingerfärdiga" [word flashes on screen, dictionary defintion, throwback to prior episode] with it.

So, before me and my then girlfriend moved to Uppsala, we actually intially moved to Upplands Väsby. The logic behind that was that my girlfriend studied in Uppsala, I worked in Stockholm, so living in the middle would be sensible compromise. Now, this was of course incredibly dumb because there is literally nothing to do in Upplands Väsby, so neither of us could have a life.

It's not completely true that there is nothing to do - Väsby has one of Sweden's most famous stone circles in Runsa (shows image). oh yeah some mudda fucking rune stones [cut]

But anyway, I want to figure out how far from Stockholm Upplands Väsby is, relative to Uppsala, because I suspect that our core motivation to live there, the assumption that Upplands Väsby is halfway to Uppsala, was wrong. So, I knew I was dumb but, I want to find out, empirically, if I was even dumber.

First, lets find out how far väsby is from stockholm [MPJ navigates to the wikipedia page of Upplands Väsby - https://en.wikipedia.org/wiki/Upplands_Väsby]

Oh shit, Europe, the Glam Metal band, is from Väsby [show europe pic, "were leaving together" audio]

[Finds select on wikipedia page] Ah, I found it - distance to stockholm from väsby is 25 kilometers.

display(stockholmUppsala.invert(25));

Okay, so Väsby is actually 36% of the way to Uppsala so it's not even close to half way, even the core assumption underlying our decision to move there was flawed

All right, how much commute time did I have there. [Reading off Wikipedia] "Travel to Stockholm Central Station takes 27 minutes" Holy shit, my life before the pandemic had so much transport.

Anyway, let's see if we can interpolate our way to that number, 27 minutes.

I actually remember, with my head(!), that the train ride from Uppsala to Stockholm takes 38 minutes so let's map an input domain of 69 km to an output range of 38 minutes...

const distanceToMinutes = d3.scaleLinear().domain([0, 69]).range([0, 38]);

display({
  "Time to reach väsby": distanceToMinutes(25) + " minutes",
});

[animation showing train leaving station, the final countdown playing]

What. 13 minutes? The Wikipedia article says it should be 27 minutes! [Mysterious music] Did I mess something up? [Talks like in an action movie] I'll change the t value to the full distance to stockholm to verify.

display({
  "Time to reach väsby": distanceToMinutes(69) + " minutes",
});

ok, that gives 38 minutes, so our code seems to behaving correctly, which seems sensible because I am so awesome, it actually seems like the problem is with... huh, Upplands Väsby [mysterious math music]

Let's just do some math on the actual factual measured trip distances... What do we KNOW? [Codes while mysterious music playing, maybe cover of final countdown]

let distance = {
  uppsala: 96,
  vasby: 25,
};
let travelTime = {
  uppsala: 38,
  vasby: 27,
};

display({
  kmPerMinute: {
    uppsala: distance.uppsala / travelTime.uppsala,
    vasby: distance.vasby / travelTime.vasby,
  },
});

OH MY GOD. this must be how time slows down near a black hole due to the extremely strong gravitational field of the black hole, but it also applies to ...upplands väsby

REFLECTION: What did we learn?

Today we have learned about the D3 concept d3.interpolate. To recap, we learned that interpolation is about finding values between known points - whether those points are numbers, colors, or even weirder things. d3.interpolate is D3's clever type-inferring function that creates specialized interpolators based on what you give it, and serves as the core engine of d3's scales.

CLIFFHOLE: What is the next thing?

So, before we say goodbye, let's figure out what what we'll do next time. Looking at the docs at the point where we started off our... rabbitholing, we were here

I believe I previously said that we have a rule that we dive into whatever confuses us when we encounter it, and that was me successfully gaslighting you, because I totally broke that rule here, because it would be so weird to learn to interpolate color before learning to interpolate numbers [ Selects "2. If b is a number, use interpolateNumber."] but now that we know that, the next listed type inference in the default interpolator is... [Reads: "3. If b is a color or a string coercible to a color, use interpolateRgb."]

I think that it's clear that next episode we are getting our hands dirty with colour interpolation and, unless we are diverted, which has been known to happen, we'll finally get some actual visualization in this data visualization series.

If you are are watching this from the future, you can find that episode there [MPJ points to upper video in end screen], or if you need a little bit of a break from D3, you can check out this video [MPJ points to lower video in end screen, that just appeared].