Scales.Palette (scales v0.1.0)

Copy Markdown View Source

Palettes: what turns a value's place among the data into a value of its aesthetic, a colour, a size, a shape or a line type. A palette is a value naming its function, so it compares and prints. A continuous palette is handed places, 0 to 1 over the data's range (continuous/2); a discrete one the number of levels it must tell apart (discrete/2).

Continuous palettes

  • :identity, R's pal_identity(): each place as it is.
  • {:rescale, {low, high}}, R's pal_rescale(): each place moved onto low to high.
  • {:area, {low, high}}, R's pal_area(): each place's square root moved onto low to high, so an area drawn at that size grows with the place.
  • {:abs_area, max}, R's abs_area(): the square root of each place's size moved onto 0 to max; a place may lie below 0 or past 1.
  • {:gradient, colors}, R's pal_gradient_n(), which R's two- and three-colour gradients are too: the colour that far along a gradient through colors, spaced evenly from 0 to 1, each place between two blended in CIE Lab* and its alpha along a straight line; a place at a colour takes it exactly, one outside 0 to 1 gives nil, and one colour is every place's. :none among the colours is R's NA, white with no alpha, as "transparent" is.
  • {:gradient, colors, values}, R's pal_gradient_n(colours, values): each place moved first along the straight lines through values, each value at its own place spread evenly from 0 to 1, then taken along the gradient; a place outside the values gives nil. The values are taken as R's approxfun() takes them: sorted, so unsorted ones run the gradient backwards; a nil dropped; equal values one, at their places' mean; as many as you like, not one per colour. Fewer than two left raise ArgumentError. R also warns of equal values.

Discrete palettes

  • {:hue, %{hue: {low, high}, chroma: chroma, luminance: luminance, direction: direction}}, R's pal_hue(): colours of one chroma and luminance, their hues spaced evenly from low to high, handed out in reverse for a direction of -1. A range within a degree of the whole circle leaves its last share off, since its end is its start.
  • {:gray, {start, finish}}, R's pal_grey(): greys from the level start to finish, 0 black and 1 white, spaced evenly once raised to the power 2.2.
  • {:viridis, %{option: option, begin: begin, end: end, direction: direction, alpha: alpha}}, R's pal_viridis(): colours of a viridis palette, as Scales.ColorOperations.viridis/2 gives them.
  • {:shape, :solid} or {:shape, :open}, R's pal_shape(): six shapes by name, solid (:circle_small, :triangle, :square, :plus, :square_cross, :asterisk, R's 16, 17, 15, 3, 7 and 8) or open (:circle_open, :triangle_open, :square_open and the same last three, R's 1, 2, 0, 3, 7 and 8).
  • :linetype, R's pal_linetype(): 13 line types, each as its dash pattern, a tuple of lengths, {} for a solid line.
  • {:manual, values}, R's pal_manual(): values in order.
  • {:brewer, %{palette: palette, direction: direction}}, R's pal_brewer(): the colours of one of ColorBrewer's palettes, named as R spells it (brewer_palettes/1 lists them), which has a set of its own for each count from 3 to its largest. Fewer than 3 levels take the start of its set of 3, and a direction of -1 hands the colours out in reverse once the nils past its largest set are added, so those come first, as R's do. A name it does not know is refused, where R warns and gives Greens.

A discrete palette asked for more levels than it holds gives nil for each past its end, as R gives NA (and warns). No levels give none, where R's hue palette refuses them.

A palette of the other kind

Each function takes a palette of the other kind too, as R's as_continuous_pal() and as_discrete_pal() convert one. continuous/2 hands a discrete palette of colours on as a gradient through its nlevels/1 colours, and one of numbers as those numbers joined by straight lines, spaced evenly from 0 to 1; a place outside them gives nil, and a palette of shapes, line types or other values is refused. discrete/2 hands a continuous palette the levels' places, spaced evenly from 0 to 1 by spread/2. :identity and {:abs_area, max}, which R gives no type, are not converted: R hands them back as they are, so asked for 3 levels they give the one number 3, and here they are refused.

Colours are uppercase #RRGGBB, or #RRGGBBAA with an alpha of their own, as Scales.ColorOperations writes them.

ColorBrewer

This product includes color specifications and designs developed by Cynthia Brewer (http://colorbrewer.org/). They are Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State University, licensed under the Apache License, Version 2.0, as R's RColorBrewer carries them.

Summary

Types

A ColorBrewer palette's type: sequential, diverging or qualitative.

A continuous palette.

A discrete palette.

t()

A palette.

What a palette's values are: colours, numbers, shapes or line types.

Functions

The names of ColorBrewer's palettes of type, sequential (:seq), diverging (:div) or qualitative (:qual), as R spells them, in the order R's pal_brewer() numbers them, so its pal_brewer("qual", 5) is "Pastel2".

Each place's value under a continuous palette, a place running from 0 to 1 over the data's range; a nil place gives nil. A discrete palette is converted first, as the moduledoc says.

The values of a discrete palette for count levels, in level order. A continuous palette is converted first, as the moduledoc says.

The palette scales names name, R's get_palette() for scales' own names, in any case: each ColorBrewer palette as pal_brewer() gives it (brewer_palettes/1), each viridis option as pal_viridis() does (Scales.ColorOperations.viridis_options/0), "grey", pal_grey(), and "hue", pal_hue(), each with R's defaults. Any other name raises ArgumentError listing these; R also knows grDevices' palettes by name ("Okabe-Ito", "Blues 3"), which are left out.

How many levels a discrete palette tells apart, R's palette_nlevels(): 6 shapes, 13 line types, a manual palette's values, a brewer palette's largest set, and 255 for a hue, grey or viridis palette, as R says; nil for a continuous palette. A discrete palette converted to a continuous one is a gradient through this many of its values.

count numbers spread evenly from low to high, R's seq(low, high, length.out = count): the ends exact, each number between them low plus its index times the step, in R's order of operations, so the fourth of seven from 0.1 to 1 is 0.54999999999999993, not 0.55.

What a palette's values are, R's palette_type(): :color, :numeric, :shape or :linetype, or nil for :identity and {:abs_area, max}, which R gives no type.

Types

brewer_type()

@type brewer_type() :: :seq | :div | :qual

A ColorBrewer palette's type: sequential, diverging or qualitative.

continuous()

@type continuous() ::
  :identity
  | {:rescale, {number(), number()}}
  | {:area, {number(), number()}}
  | {:abs_area, number()}
  | {:gradient, [Scales.ColorOperations.t(), ...]}
  | {:gradient, [Scales.ColorOperations.t(), ...], [number() | nil, ...]}

A continuous palette.

discrete()

@type discrete() ::
  {:hue,
   %{
     hue: {number(), number()},
     chroma: number(),
     luminance: number(),
     direction: 1 | -1
   }}
  | {:gray, {number(), number()}}
  | {:viridis,
     %{
       option: atom(),
       begin: number(),
       end: number(),
       direction: 1 | -1,
       alpha: number()
     }}
  | {:shape, :solid | :open}
  | :linetype
  | {:manual, list()}
  | {:brewer, %{palette: String.t(), direction: 1 | -1}}

A discrete palette.

t()

@type t() :: continuous() | discrete()

A palette.

value_type()

@type value_type() :: :color | :numeric | :shape | :linetype

What a palette's values are: colours, numbers, shapes or line types.

Functions

brewer_palettes(type)

@spec brewer_palettes(brewer_type()) :: [String.t(), ...]

The names of ColorBrewer's palettes of type, sequential (:seq), diverging (:div) or qualitative (:qual), as R spells them, in the order R's pal_brewer() numbers them, so its pal_brewer("qual", 5) is "Pastel2".

Examples

iex> Scales.Palette.brewer_palettes(:qual)
["Accent", "Dark2", "Paired", "Pastel1", "Pastel2", "Set1", "Set2", "Set3"]

continuous(palette, places)

@spec continuous(t(), [Scales.double()]) :: [Scales.double() | String.t()]

Each place's value under a continuous palette, a place running from 0 to 1 over the data's range; a nil place gives nil. A discrete palette is converted first, as the moduledoc says.

A place may be any double (Scales.double/0), each given R's answer: the numeric palettes carry an infinity or NaN through their arithmetic, the area palettes' square root making NaN below 0, and a colour is nil at an infinity or NaN, but one colour's, which an infinity takes too; a manual palette of numbers keeps NaN.

Examples

iex> Scales.Palette.continuous({:rescale, {2, 5}}, [0, 0.3, 1, nil])
[2.0, 2.9, 5.0, nil]

iex> Scales.Palette.continuous({:area, {1.0, 6.0}}, [0.2, 1])
[3.23606797749979, 6.0]

iex> Scales.Palette.continuous({:gradient, ["#132B43", "#56B1F7"]}, [0, 0.5, 1])
["#132B43", "#336A98", "#56B1F7"]

iex> Scales.Palette.continuous({:gradient, ["#132B43", "#56B1F7"], [0, 0.25]}, [0.1, 0.5])
["#2C5D86", nil]

iex> Scales.Palette.continuous({:gradient, ["#FF000080", "#0000FF"]}, [0.5, 1.5])
["#CA0088C0", nil]

iex> Scales.Palette.continuous({:manual, [1.0, 5.0, 9.0]}, [0.25, 1.5])
[3.0, nil]

iex> Scales.Palette.continuous({:area, {1.0, 6.0}}, [-0.5, :infinity])
[:nan, :infinity]

discrete(palette, count)

@spec discrete(t(), non_neg_integer()) :: list()

The values of a discrete palette for count levels, in level order. A continuous palette is converted first, as the moduledoc says.

Examples

iex> hue = %{hue: {15.0, 375.0}, chroma: 100.0, luminance: 65.0, direction: 1}
iex> Scales.Palette.discrete({:hue, hue}, 3)
["#F8766D", "#00BA38", "#619CFF"]

iex> Scales.Palette.discrete({:manual, ["#FF0000", "#0000FF"]}, 3)
["#FF0000", "#0000FF", nil]

iex> Scales.Palette.discrete({:rescale, {1, 6}}, 3)
[1.0, 3.5, 6.0]

named(name)

@spec named(String.t()) :: t()

The palette scales names name, R's get_palette() for scales' own names, in any case: each ColorBrewer palette as pal_brewer() gives it (brewer_palettes/1), each viridis option as pal_viridis() does (Scales.ColorOperations.viridis_options/0), "grey", pal_grey(), and "hue", pal_hue(), each with R's defaults. Any other name raises ArgumentError listing these; R also knows grDevices' palettes by name ("Okabe-Ito", "Blues 3"), which are left out.

Examples

iex> Scales.Palette.named("set1")
{:brewer, %{palette: "Set1", direction: 1}}

iex> Scales.Palette.named("Grey")
{:gray, {0.2, 0.8}}

nlevels(arg1)

@spec nlevels(t()) :: non_neg_integer() | nil

How many levels a discrete palette tells apart, R's palette_nlevels(): 6 shapes, 13 line types, a manual palette's values, a brewer palette's largest set, and 255 for a hue, grey or viridis palette, as R says; nil for a continuous palette. A discrete palette converted to a continuous one is a gradient through this many of its values.

Examples

iex> Scales.Palette.nlevels({:brewer, %{palette: "Paired", direction: 1}})
12

iex> Scales.Palette.nlevels({:gradient, ["#132B43", "#56B1F7"]})
nil

spread(arg, count)

@spec spread({number(), number()}, non_neg_integer()) :: [number()]

count numbers spread evenly from low to high, R's seq(low, high, length.out = count): the ends exact, each number between them low plus its index times the step, in R's order of operations, so the fourth of seven from 0.1 to 1 is 0.54999999999999993, not 0.55.

Examples

iex> Scales.Palette.spread({0.0, 1.0}, 5)
[0.0, 0.25, 0.5, 0.75, 1.0]

iex> Scales.Palette.spread({2, 6}, 1)
[2]

type(arg1)

@spec type(t()) :: value_type() | nil

What a palette's values are, R's palette_type(): :color, :numeric, :shape or :linetype, or nil for :identity and {:abs_area, max}, which R gives no type.

A manual palette's is guessed from its values, as R's pal_manual() guesses it: :color where every value is a string Scales.ColorOperations.parse/1 reads, as no values are, so not :none, :numeric where every one is a number, else nil, where R names how its values are stored ("character"). R reads a colour as hex or one of its own colour names, so "grey50" is a colour there and "transparent" is not; here they are the other way round.

Examples

iex> Scales.Palette.type({:brewer, %{palette: "Set1", direction: 1}})
:color

iex> Scales.Palette.type({:manual, [1, 5, 9]})
:numeric

iex> Scales.Palette.type({:manual, [:circle_small, :triangle]})
nil