Crate ratio_color

source ·
Expand description

A library to make palette management for plotting projects easier. It is built on the crate palette that allows for creating, mixing and generally working with colors and pixels.

A short categorical example utilizing the Bold preset:

use ratio_color::{Categorical, Container, Palette, key_to_order};
use palette::Srgba;
let keys = ["foo", "bar", "baz", "quux"];
let categorical: Palette<&str, Srgba<u8>> = Palette::with_preset(
    &Categorical::Bold,
    key_to_order(keys)
);
assert_eq!(categorical.get("foo").expect("a color"), &Srgba::new(57, 105, 172, 255));

A short numerical example utilizing several numerical presets:

use ratio_color::{key_to_order, ColorAtFraction, Container, LinearGradient, Numerical, Palette};
use palette::Srgba;
let keys = ["foo", "bar", "baz", "quux"];
let numerical: Palette<&str, LinearGradient> = Palette::with_presets(
    &[Numerical::SeqMagma, Numerical::SeqViridis, Numerical::CycEdge, Numerical::DivSpectral],
    key_to_order(keys)
);

let gradient: &LinearGradient = numerical.get("bar").expect("a gradient");
assert_eq!(gradient, &LinearGradient::from(Numerical::SeqMagma));
let color: Srgba<u8> = gradient.color_at(0.5);
assert_eq!(color, Srgba::<u8>::new(183, 56, 120, 255));

The key struct of this crate is the Palette for which the Container trait has been implemented. A container is a mix of a slice of values, usually a vector, and a BTreeMap of keys to access them in a number of ways using the Selector enum to obtain either a suitable index for the slice of values or an override value.

In terms of palettes this corresponds to a palette having a fixed number of colors or gradients to choose from (the values). When binding colors to keys, the default behavior would be to use the key’s order in the mapping to determine the index of the palette’s color to use using Selector::KeyOrder. However, you might want to use a specific color from the given palette for a given key using Selector::Index or even a complete overridden value not present in the usual palette Selector::Value.

The Palette is a generic struct with an implementation of Container for any orderable key and any value.

§Categorical palette

For our example palette, we will use red, green, and blue as our default colors, or values. Pink will be used as a special case later on. Next we create a set of keys for which we want to have colors available on demand. Just as an example, we will use “sun”, “trees”, “sky”, “ground”, “ditto”, and “zzz”. Note that we have more keys than default colors! For some, we will assign a fixed color by using an index. Others receive a color by their key’s order with respect to the other keys. And finally, an exception will be made for ditto.

use std::collections::BTreeMap;
use ratio_color::{Container, Palette, Selector};
use palette::Srgba;

// Create some categorical palette colors.
let red: Srgba<u8> = Srgba::new(255, 0, 0, 100);
let green: Srgba<u8> = Srgba::new(0, 255, 0, 200);
let blue: Srgba<u8> = Srgba::new(0, 0, 255, 255);

// Bundle them together in a vec to supply to the categorical palette later on.
let values = vec![red.clone(), green.clone(), blue.clone()];

// Initialize a BTreeMap to map from key to a selector.
let mut selectors = BTreeMap::new();
selectors.insert("sun", Selector::Index(0)); // red
selectors.insert("trees", Selector::Index(1)); // green
selectors.insert("sky", Selector::Index(2)); // blue
selectors.insert("ground", Selector::KeyOrder); // what will this be?
selectors.insert("zzz", Selector::KeyOrder); // I'm probably last.

// Ditto's are always pink, so they get a special value.
let pink: Srgba<u8> = Srgba::new(255, 125, 200, 255);
selectors.insert("ditto", Selector::Value(pink.clone()));

// Create the palette.
let palette = Palette::new(values, selectors);

// Let's check the contents!
assert_eq!(
    palette.get("sun").expect("a color"),
    &red,
    "our sun is red"
);
assert_eq!(
    palette.get("ditto").expect("a color"),
    &pink,
    "a ditto is pink"
);
assert_eq!(
    palette.get("ground").expect("a color"),
    &green,
    "the ground is green, because it's key is the second one by order (ditto, *ground*, sky, sun, trees, zzz)"
);
assert_eq!(
    palette.get("zzz").expect("a color"),
    &blue,
    "even though I'm sixth, I'll be 2 (blue) instead (index=5, per modulo 3 makes 2)"
)

So now we can safely get our colors for our given keys. The Palette’s generic nature, means we can use any orderable key, and also any value. Adding or removing a key later on is done by mutating the Palette::selectors property or whatever property you decide to hook up to the Container::selectors trait function.

In order to not necessarily clog your error stack, we rely on the basic behavior of maps an vectors by returning values as an Option rather than throwing errors.

§Numerical palette

Numerical palettes are completely similar to categorical palettes as far as storage and access go. The palette crate no longer provides us with continuous color scales directly. As a replacement, we introduce the ColorAtFraction trait and LinearGradient struct, that uses the enterpolation crate to interpolate between linearized colors instead.

use palette::Srgba;
use ratio_color::{key_to_order, ColorAtFraction, LinearGradient, Palette, Container};

// Create basic colors.
let transparent: Srgba<u8> = Srgba::new(0, 0, 0, 0);
let red: Srgba<u8> = Srgba::new(255, 0, 0, 255);
let green: Srgba<u8> = Srgba::new(0, 255, 0, 255);
let blue: Srgba<u8> = Srgba::new(0, 0, 255, 255);

// Create three gradients.
let to_red =
    LinearGradient::new([transparent.clone(), red.clone()], None).expect("a gradient");
let to_green =
    LinearGradient::new([transparent.clone(), green.clone()], None).expect("a gradient");
let to_blue =
    LinearGradient::new([transparent.clone(), blue.clone()], None).expect("a gradient");

// Get a color from a linear gradient and convert it back into a regular Srgba.
// Note that blueness interpolation in the linear color space does not equal regular "mean"
// taking, but the alpha channel does.
let halfway_blue = Srgba::<u8>::from_linear(to_blue.color_at(0.5));
assert_eq!(halfway_blue, Srgba::<u8>::new(0, 0, 188, 128));

// Let's assert the endpoints for completeness' sake.
let start: Srgba<u8> = to_blue.color_at(0.0);
let end: Srgba<u8> = to_blue.color_at(1.0);
assert_eq!(&start, &transparent);
assert_eq!(&end, &blue);

// Store these in an palette for these keys.
let keys = ["foo", "bar", "baz", "quux"];
// Let keys resolve to their order in the BTreeMap.
// Actual palette creation.
let palette = Palette::new(
    [to_red.clone(), to_green.clone(), to_blue.clone()],
    key_to_order(keys)
);

// Test accessing and using a gradient.
let full_red: Srgba<u8> = palette.get("quux").expect("a gradient").color_at(1.0);
assert_eq!(&full_red, &red);
assert_eq!(
    <LinearGradient as ColorAtFraction<Srgba<u8>>>::color_at(
        palette.get("quux").expect("a gradient"),
        0.66
    ),
    Srgba::<u8>::new(212, 0, 0, 168),
        "Access quux at %66 using the fully qualified path to access the method."
);

Re-exports§

Modules§

  • Preset colors for both categorical and numerical palettes.

Structs§

  • A linearly interpolated gradient.
  • A palette of any kind. It stores values (often colors or gradients) that are accessible by index or by key.

Enums§

  • Ratio Color error.
  • A selector for container contents. Either defers to a key’s order or a given index of contained values or an override.

Traits§

  • Capability of interpolating a color at a given fraction between 0.0 and 1.0.
  • A container of a slice of values (usually a vector) that are accessible by index, but also by means of a mapping of keys to selectors. The selector states whether to use the key’s order to access the values, a given index or even a completely overridden value that is not in the list of values. Values are re-used in a carousel fashion using the modulo operator for indices or orders that are out of bounds.
  • Enable a CSS gradient generation function.
  • Anything that delivers preset values.

Functions§