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
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 errorcount 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 errorThe 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 errorThe 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 errorThe scale as an SVG bar width by height pixels, start on the left: a legend for a map or a heatmap.
gradient_names() → listThe 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.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/gradient.qwer$ curl -O https://quillolang.org/plugins/files/gradient.qweThen, in your program, all of it or only what you call:
use gradient.qweruse gradient_colors, gradient_at from gradient // Quillo 0.6: only theseIt uses this crate, which cargo fetches the first time the plugin is built:
colorgrad = "0.7"Example
// 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
//! 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()}Related
color_vision.qwer · 1.0.0Colours and pictures as people with colour blindness see them, at full or milder severity; chart palettes checked for colours that become alike; and text contrast (WCAG) as each of them sees it.8 views · 7 downloadsHeatmaps heatmap.qwer · 1.0.0Heatmaps as PNG: clicks or sightings blurred into a density map over a screenshot or a plain background, and tables of numbers as coloured grids.9 views · 9 downloadsColour names color_names.qwer · 1.0.0The nearest of the 148 CSS colour names to any colour, names back to hex, and the full list: for labels, filters and alt text.8 views · 9 downloadsImage tiles image_tiles.qwer · 1.0.0Pictures cut into tiles for zoomable viewers, puzzles or a grid of posts, and many pictures made into one contact sheet of thumbnails.8 views · 8 downloadsPalette palette.qwer · 1.0.0A picture's main colours as hex, how much of it each covers, and a readable text colour on each, for themes and matching cards.7 views · 8 downloadsPlaceholders placeholder.qwer · 1.0.0Placeholder images as SVG: a box of any size with its dimensions or your text in your colours, as markup or a data: URL.8 views · 8 downloadsThread 0 posts
On Gradients: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.