professortX / elm-visualization / Interpolation

This module provides a variety of interpolation methods for blending between two values. While primitives for numbers, colors and lists are provided, the library focuses on composition so that you can build interpolators for your own custom datatypes.


type alias Interpolator a =
Basics.Float -> a

An interpolator is merely a function that takes a float parameter t roughly in the range [0..1]. 0 would represent the "before" value, 1 the after value and values in between are the values in between.

Note: Sometimes the range of the interpolator can go slightly above or below zero - this is useful for some animation techniques. If this is not suitable for your data type, remember to clamp the values as necessary.

Primitive interpolators

float : Basics.Float -> Basics.Float -> Interpolator Basics.Float

Interpolates between the two provided float values.

myInterpolator : Interpolator Float
myInterpolator = Interpolation.float 5 17

myInterpolator 0.2 -- 7.4
myInterpolator 0.5 -- 11

int : Basics.Int -> Basics.Int -> Interpolator Basics.Int

Interpolates between ints.

step : a -> List a -> Interpolator a

Interpolate between arbitrary values by just showing them in sequence.

The list is provided is passed as head and tail seperately, to avoid needing to handle the empty list case.

 type StageOfGrief
     = Denial
     | Anger
     | Bargaining
     | Depression
     | Acceptance

 griefInterpolator : Interpolator StageOfGrief
 griefInterpolator =
     Interpolation.step Denial
         [ Anger
         , Bargaining
         , Depression
         , Acceptance
         ]

 griefInterpolator 0 --> Denial
 griefInterpolator 0.5 --> Bargaining
 griefInterpolator 1.1 --> Acceptance

rgb : Color -> Color -> Interpolator Color

Interpolates between two Color values using the sRGB color space.

rgbWithGamma : Basics.Float -> Color -> Color -> Interpolator Color

Interpolates between two Color values using the sRGB color space using gamma correction.

hsl : Color -> Color -> Interpolator Color

Interpolates between two Color values using the HSL color space. It will always take the shortest path between the target hues.

hslLong : Color -> Color -> Interpolator Color

Like Interpolation.hsl, but does not use the shortest path between hues.

lab : Color -> Color -> Interpolator Color

Interpolates between two Color values using the CIELAB color space, that is more perceptually linear than other color spaces. Perceptually linear means that a change of the same amount in a color value should produce a change of about the same visual importance. This property makes it ideal for accurate visual encoding of data.

hcl : Color -> Color -> Interpolator Color

Interpolates between two Color values using the CIE Lch(ab) color space.

hclLong : Color -> Color -> Interpolator Color

Like hcl, but does not use the shortest path between hues.

Composition

map : (a -> b) -> Interpolator a -> Interpolator b

Transform values from another interpolator.

Note: This function is provided as a convenience, since thinking in mapN is pretty natural for Elm developers (and works well in pipelines). However, keep in mind that this function is literally an alias for <<.

map2 : (a -> b -> c) -> Interpolator a -> Interpolator b -> Interpolator c

Combine two interpolators, combining them with the given function.

type alias Coords =
    ( Float, Float )

interpolateCoords : Coords -> Coords -> Interpolator Coords
interpolateCoords ( x1, y1 ) ( x2, y2 ) =
    Interpolation.map2
        Tuple.pair
        (Interpolation.float x1 x2)
        (Interpolation.float y1 y2)

map3 : (a -> b -> c -> d) -> Interpolator a -> Interpolator b -> Interpolator c -> Interpolator d

map4 : (a -> b -> c -> d -> e) -> Interpolator a -> Interpolator b -> Interpolator c -> Interpolator d -> Interpolator e

map5 : (a -> b -> c -> d -> e -> f) -> Interpolator a -> Interpolator b -> Interpolator c -> Interpolator d -> Interpolator e -> Interpolator f

piecewise : (a -> a -> Interpolator a) -> a -> List a -> Interpolator a

Returns a piecewise interpolator, composing interpolators for each adjacent pair of values.

For example:

myInterpolator : Interpolator Int
myInterpolator =
     Interpolation.piecewise Interpolation.int 6 [ 10, -2 ]

myInterpolator 0 --> 6
myInterpolator 0.25 --> 8
myInterpolator 0.5 --> 10
myInterpolator 0.75 --> 4
myInterpolator 1 --> -2

tuple : (a -> a -> Interpolator a) -> (b -> b -> Interpolator b) -> ( a, b ) -> ( a, b ) -> Interpolator ( a, b )

Composes interpolators around a tuple. This is a convenience function for the common case of 2 element tuples.

You can for example define an interpolator for a position:

interpolatePosition : ( Float, Float ) -> ( Float, Float ) -> Interpolator ( Float, Float )
interpolatePosition =
    Interpolation.tuple Interpolation.float Interpolation.float

Lists

inParallel : List (Interpolator a) -> Interpolator (List a)

This will run all of the interpolators provided in parallel.

Can be handy for constructing complex interpolations in conjuction with List.map2:

before : List Float
before =
     [ 3, 4, 7, 8 ]

after : List Float
after =
     [ 6, 4, 1, 9 ]

myInterpolator0 : Interpolator (List Float)
myInterpolator0 =
     List.map2 Interpolation.float before after
         |> Interpolation.inParallel

myInterpolator0 0 --> [ 3, 4, 7, 8 ]
myInterpolator0 0.5 --> [ 4.5, 4, 4, 8.5]
myInterpolator0 1 --> [ 6, 4, 1, 9 ]

staggeredWithParallelism : Basics.Float -> List (Interpolator a) -> Interpolator (List a)

Combines a bunch of interpolators with a controlled amount of parallelism.

Let's illustrate what we mean by showing an example:

interpolate : Float -> Interpolator (List Int)
interpolate parallelism =
    List.repeat 5 (Interpolation.int 0 8)
        |> Interpolation.staggeredWithParallelism parallelism

Now when the parallelism is 1, we will run each interpolator when the previous one concludes:

Interpolation.samples 11 (interpolate 1)
--> [ [ 0, 0, 0, 0, 0 ]
--> , [ 4, 0, 0, 0, 0 ]
--> , [ 8, 0, 0, 0, 0 ]
--> , [ 8, 4, 0, 0, 0 ]
--> , [ 8, 8, 0, 0, 0 ]
--> , [ 8, 8, 4, 0, 0 ]
--> , [ 8, 8, 8, 0, 0 ]
--> , [ 8, 8, 8, 4, 0 ]
--> , [ 8, 8, 8, 8, 0 ]
--> , [ 8, 8, 8, 8, 4 ]
--> , [ 8, 8, 8, 8, 8 ]
--> ]

If we set it to 2, we will start each interpolator approximately halfway through the previous one:

Interpolation.samples 11 (interpolate 2)
--> [ [ 0, 0, 0, 0, 0 ]
--> , [ 2, 0, 0, 0, 0 ]
--> , [ 5, 1, 0, 0, 0 ]
--> , [ 7, 3, 0, 0, 0 ]
--> , [ 8, 6, 2, 0, 0 ]
--> , [ 8, 8, 4, 0, 0 ]
--> , [ 8, 8, 6, 2, 0 ]
--> , [ 8, 8, 8, 5, 1 ]
--> , [ 8, 8, 8, 7, 3 ]
--> , [ 8, 8, 8, 8, 6 ]
--> , [ 8, 8, 8, 8, 8 ]
--> ]

If we set it to 1/2, we will wait approximately the time a single interpolator runs after each run doing nothing (slightly more samples so it's easier to see what's going on):

Interpolation.samples 16 (interpolate 0.5)
--> [ [ 0, 0, 0, 0, 0 ]
--> , [ 5, 0, 0, 0, 0 ]
--> , [ 8, 0, 0, 0, 0 ]
--> , [ 8, 0, 0, 0, 0 ]
--> , [ 8, 3, 0, 0, 0 ]
--> , [ 8, 8, 0, 0, 0 ]
--> , [ 8, 8, 0, 0, 0 ]
--> , [ 8, 8, 2, 0, 0 ]
--> , [ 8, 8, 6, 0, 0 ]
--> , [ 8, 8, 8, 0, 0 ]
--> , [ 8, 8, 8, 0, 0 ]
--> , [ 8, 8, 8, 5, 0 ]
--> , [ 8, 8, 8, 8, 0 ]
--> , [ 8, 8, 8, 8, 0 ]
--> , [ 8, 8, 8, 8, 3 ]
--> , [ 8, 8, 8, 8, 8 ]
--> ]

As parallelism approaches infinity, than this will behave like Interpolation.inParallel.

If thinking about this in terms of parallelism doesn't feel natural, you may appreciate Transition.stagger which deals with durations and delays instead.

list : { add : a -> Interpolator a, remove : a -> Interpolator a, change : a -> a -> Interpolator a, id : a -> comparable, combine : ListCombiner } -> List a -> List a -> Interpolator (List a)

This is an interpolator for lists. It is quite complex and should be used if these conditions hold:

  1. You need to interpolate additions, removals and changes.
  2. Each item in the list has some notion of identity, for example an .id member, which is comparable.
  3. You have a way to deal with positions in the list being somewhat muddy during the transition (e.g. if an item is being created at the same position a different item is being removed, while adjacent items are switching position, then the exact order of items will be arbitrary during the interpolation).

The first argument is a configuration record. It has the following keys:


type ListCombiner
    = CombineParallel

combineParallel : ListCombiner

Runs all the list interpolations in parallel.

Advanced

pointAlongPath : Path -> Interpolator ( Basics.Float, Basics.Float )

A somewhat elaborate interpolator that gives points along an arbitrary path, where at t=0 this will give the first point in the path, and at t=1 gives the last. If overshot, will give points along the tangent at these points.

Will give the origin point if the path is empty or invalid (generally you should check somewhere earlier if there is a possibility of that).

Performance note: this function does a fair amount of work when the interpolator is being constructed, so that later when it is called with the t parameter it can be rather efficient. Therefore it is more efficient to partially apply this with a path in a static context or store the partially applied function in a model.

Helpers

samples : Basics.Int -> Interpolator a -> List a

Returns a list of uniformly spaced samples from the specified interpolator. The first sample is always at t = 0, and the last sample is always at t = 1. This can be useful in generating a fixed number of samples from a given interpolator.

Can be quite handy when debugging interpolators or as a way to create a quantize scale.