Media

Gradients

Colour scales and gradients (viridis, magma, blues, red-blue and more, or your own stops) as hex lists, a colour per value, CSS or SVG.

Version 1.0.0 · 5 functions · 1 crate

#colors#charts#maps#design

Functions

What use gradient.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use gradient_colors, gradient_at from gradient (Quillo 0.6).

gradient_colors(scale: any, count: int) → listor an error

count colours (1 to 1,000) evenly along scale, as "#rrggbb", from its start to its end: a colour for each bar, each band of a map, each step of a legend. scale is a name (viridis, magma, plasma, cividis, blues, greens, reds, rd_bu, spectral... see gradient_names) or your own stops, ["#ffffff", "#2a78d6"].

gradient_at(scale: any, t: float) → stror an error

The colour at t along scale, 0 its start and 1 its end (values outside are held at the ends): the colour of one value, as (v - min) / (max - min).

gradient_css(scale: any, angle: int) → stror an error

The scale as a CSS linear-gradient(...), at angle degrees (90 runs left to right), for a background.

gradient_svg(scale: any, width: int, height: int) → stror an error

The scale as an SVG bar width by height pixels, start on the left: a legend for a map or a heatmap.

gradient_names() → list

The names of the scales built in. Sequential ones (viridis, blues...) suit amounts; diverging ones (rd_bu, br_bg...) values above and below a middle; rainbow, sinebow and turbo look lively but mislead about amounts.

Get it

$ quillo install gradient               # gradient.qwer into this folder$ quillo install gradient --example     # and its example, gradient.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/gradient.qwer$ curl -O https://quillolang.org/plugins/files/gradient.qwe

Then, in your program, all of it or only what you call:

main.qwe
use gradient.qweruse gradient_colors, gradient_at from gradient      // Quillo 0.6: only these

It uses this crate, which cargo fetches the first time the plugin is built:

[dependencies]
colorgrad = "0.7"

Example

gradient.qwe
// A colour scale for a sales map: a colour for each region from its value, and the legend under the map.////   quillo run gradient.qwe      then open http://localhost:8080/use "gradient.qwer"let regions = [{ name: "North", sales: 18250 }, { name: "South", sales: 16410 }, { name: "East", sales: 5120 }, { name: "West", sales: 11000 }]route GET /scale = {    viridis: gradient_colors("viridis", 5)?,    brand: gradient_colors(["#ffffff", "#2a78d6"], 3)?,    middle: gradient_at("rd_bu", 0.5)?,    css: gradient_css("blues", 90)?,}route GET / {    let lo = min(regions | map((r) => r.sales))    let hi = max(regions | map((r) => r.sales))    <html><body style="font-family: sans-serif">        {for r in regions => <div style="padding: 4px">            <span style={`display: inline-block; width: 24px; height: 14px; background: ${gradient_at("viridis", float(r.sales - lo) / (hi - lo))?}`}></span>            {r.name}: {r.sales}        </div>}        <div>{raw(gradient_svg("viridis", 300, 12)?)}</div>        <small>{lo} to {hi}</small>    </body></html>}route GET /names = gradient_names()
$ quillo run gradient.qwe$ curl 'localhost:8080/scale'$ curl 'localhost:8080/'

Source

gradient.qwer
//! Colour scales and gradients: viridis, magma, blues, red-blue and 30 more, or your own stops, as lists of hex colours, a colour for any value, CSS or SVG.//! version: 1.0.0//!//! [dependencies]//! colorgrad = "0.7"use colorgrad::{preset, Gradient, GradientBuilder, LinearGradient};use serde_json::Value;const NAMES: [&str; 36] = [    "viridis", "magma", "inferno", "plasma", "cividis", "turbo", "warm", "cool", "cubehelix", "rainbow", "sinebow",    "blues", "greens", "greys", "oranges", "purples", "reds",    "bu_gn", "bu_pu", "gn_bu", "or_rd", "pu_bu_gn", "pu_bu", "pu_rd", "rd_pu", "yl_gn_bu", "yl_gn", "yl_or_br", "yl_or_rd",    "br_bg", "pr_gn", "pi_yg", "pu_or", "rd_bu", "rd_yl_bu", "spectral",];fn named(name: &str) -> Option<Box<dyn Gradient>> {    let n = name.trim().to_lowercase().replace(['-', ' '], "_");    Some(match n.as_str() {        "viridis" => Box::new(preset::viridis()),        "magma" => Box::new(preset::magma()),        "inferno" => Box::new(preset::inferno()),        "plasma" => Box::new(preset::plasma()),        "cividis" => Box::new(preset::cividis()),        "turbo" => Box::new(preset::turbo()),        "warm" => Box::new(preset::warm()),        "cool" => Box::new(preset::cool()),        "cubehelix" => Box::new(preset::cubehelix_default()),        "rainbow" => Box::new(preset::rainbow()),        "sinebow" => Box::new(preset::sinebow()),        "blues" => Box::new(preset::blues()),        "greens" => Box::new(preset::greens()),        "greys" | "grays" => Box::new(preset::greys()),        "oranges" => Box::new(preset::oranges()),        "purples" => Box::new(preset::purples()),        "reds" => Box::new(preset::reds()),        "bu_gn" => Box::new(preset::bu_gn()),        "bu_pu" => Box::new(preset::bu_pu()),        "gn_bu" => Box::new(preset::gn_bu()),        "or_rd" => Box::new(preset::or_rd()),        "pu_bu_gn" => Box::new(preset::pu_bu_gn()),        "pu_bu" => Box::new(preset::pu_bu()),        "pu_rd" => Box::new(preset::pu_rd()),        "rd_pu" => Box::new(preset::rd_pu()),        "yl_gn_bu" => Box::new(preset::yl_gn_bu()),        "yl_gn" => Box::new(preset::yl_gn()),        "yl_or_br" => Box::new(preset::yl_or_br()),        "yl_or_rd" => Box::new(preset::yl_or_rd()),        "br_bg" => Box::new(preset::br_bg()),        "pr_gn" => Box::new(preset::pr_gn()),        "pi_yg" => Box::new(preset::pi_yg()),        "pu_or" => Box::new(preset::pu_or()),        "rd_bu" => Box::new(preset::rd_bu()),        "rd_yl_bu" => Box::new(preset::rd_yl_bu()),        "spectral" => Box::new(preset::spectral()),        _ => return None,    })}// A scale is a preset's name, or a list of CSS colours spread evenly from 0 to 1 (blended in Oklab, which keeps the middle from going grey or muddy).fn to_gradient(v: &Value) -> Result<Box<dyn Gradient>, String> {    match v {        Value::String(s) => named(s).ok_or_else(|| format!("`{}` is not a scale; use one of {} or a list of colours", s, NAMES.join(", "))),        Value::Array(stops) => {            let colours: Vec<String> = stops.iter().filter_map(|c| c.as_str().map(str::to_string)).collect();            if colours.len() < 2 || colours.len() != stops.len() || colours.len() > 64 {                return Err("give 2 to 64 colours as text, like [\"#ffffff\", \"#2a78d6\"]".into());            }            let g: LinearGradient = GradientBuilder::new().html_colors(&colours).mode(colorgrad::BlendMode::Oklab).build().map_err(|e: colorgrad::GradientBuilderError| e.to_string())?;            Ok(Box::new(g))        }        _ => Err("a scale is a name like \"viridis\" or a list of colours".into()),    }}fn hex(g: &dyn Gradient, t: f32) -> String {    let [r, gg, b, _] = g.at(t.clamp(0.0, 1.0)).to_rgba8();    format!("#{:02x}{:02x}{:02x}", r, gg, b)}fn samples(g: &dyn Gradient, n: usize) -> Vec<String> {    if n == 1 {        return vec![hex(g, 0.5)];    }    (0..n).map(|i| hex(g, i as f32 / (n - 1) as f32)).collect()}/// `count` colours (1 to 1,000) evenly along `scale`, as "#rrggbb", from its start to its end: a colour for each bar, each band of a map, each step of a legend./// `scale` is a name (viridis, magma, plasma, cividis, blues, greens, reds, rd_bu, spectral... see `gradient_names`) or your own stops, `["#ffffff", "#2a78d6"]`.pub fn gradient_colors(scale: serde_json::Value, count: i64) -> Result<Vec<String>, String> {    let g = to_gradient(&scale)?;    Ok(samples(g.as_ref(), count.clamp(1, 1000) as usize))}/// The colour at `t` along `scale`, 0 its start and 1 its end (values outside are held at the ends): the colour of one value, as `(v - min) / (max - min)`.pub fn gradient_at(scale: serde_json::Value, t: f64) -> Result<String, String> {    let g = to_gradient(&scale)?;    Ok(hex(g.as_ref(), t as f32))}/// The scale as a CSS `linear-gradient(...)`, at `angle` degrees (90 runs left to right), for a `background`.pub fn gradient_css(scale: serde_json::Value, angle: i64) -> Result<String, String> {    let g = to_gradient(&scale)?;    let stops: Vec<String> = samples(g.as_ref(), 9).iter().enumerate().map(|(i, c)| format!("{} {}%", c, i as f64 * 12.5)).collect();    Ok(format!("linear-gradient({}deg, {})", angle, stops.join(", ")))}/// The scale as an SVG bar `width` by `height` pixels, start on the left: a legend for a map or a heatmap.pub fn gradient_svg(scale: serde_json::Value, width: i64, height: i64) -> Result<String, String> {    let g = to_gradient(&scale)?;    let (w, h) = (width.clamp(1, 10_000), height.clamp(1, 10_000));    let colours = samples(g.as_ref(), 17);    // An id of its own, so several legends can share a page.    let id = format!("g{}{}", &colours[0][1..], &colours[16][1..]);    let stops: String = colours.iter().enumerate().map(|(i, c)| format!(r#"<stop offset="{}" stop-color="{}"/>"#, i as f64 / 16.0, c)).collect();    Ok(format!(r#"<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="{h}" viewBox="0 0 {w} {h}"><defs><linearGradient id="{id}">{stops}</linearGradient></defs><rect width="{w}" height="{h}" fill="url(#{id})"/></svg>"#))}/// The names of the scales built in. Sequential ones (viridis, blues...) suit amounts; diverging ones (rd_bu, br_bg...) values above and below a middle;/// rainbow, sinebow and turbo look lively but mislead about amounts.pub fn gradient_names() -> Vec<String> {    NAMES.iter().map(|s| s.to_string()).collect()}
Thread
66Useful? Like it, or add to its thread below.

Thread 0 posts

On Gradients: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.