From 530264ef76ec7fa45debcbe784ff80490b438948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Thebault?= Date: Sat, 2 May 2026 23:30:46 +0200 Subject: [PATCH 01/13] different error message between minor and major ticks --- src/drawing/ticks.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/drawing/ticks.rs b/src/drawing/ticks.rs index f07776d..5f3f6ba 100644 --- a/src/drawing/ticks.rs +++ b/src/drawing/ticks.rs @@ -75,7 +75,7 @@ pub fn locate_minor( Ok(LogLocator::new_minor(*base).ticks(nb)) } _ => Err(Error::InconsistentDesign(format!( - "Unsupported locator/scale combination: {:?}/{:?}", + "Unsupported minor locator/scale combination: {:?}/{:?}", locator, scale ))), } From e48b92121f14a427512a2f66e2824bc02c629db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Thebault?= Date: Sun, 3 May 2026 15:49:03 +0200 Subject: [PATCH 02/13] xkcd colors --- base/src/color.rs | 20 +- base/src/color/{named.rs => css4.rs} | 0 base/src/color/xkcd.rs | 961 +++++++++++++++++++++++++++ 3 files changed, 976 insertions(+), 5 deletions(-) rename base/src/color/{named.rs => css4.rs} (100%) create mode 100644 base/src/color/xkcd.rs diff --git a/base/src/color.rs b/base/src/color.rs index 4406902..d6f129c 100644 --- a/base/src/color.rs +++ b/base/src/color.rs @@ -1,9 +1,10 @@ use std::str::FromStr; use std::{error, fmt}; -mod named; +mod css4; +mod xkcd; -pub use named::*; +pub use css4::*; pub trait ResolveColor { fn resolve_color(&self, color: &Color) -> ColorU8; @@ -201,6 +202,10 @@ const fn hex_to_u8(hex: u8) -> u8 { } } +const fn is_hex_char(c: u8) -> bool { + matches!(c, b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F') +} + /// Parsing error for ColorU8 #[derive(Debug)] pub enum ParseError { @@ -297,8 +302,11 @@ impl FromStr for ColorU8 { let bytes = raw.as_bytes(); match bytes.len() { 4 | 5 | 7 | 9 => { - // from_html panics if first char != '#', but we checked it - Ok(ColorU8::from_html(bytes)) + if bytes[1..].iter().all(|&c| is_hex_char(c)) { + Ok(ColorU8::from_html(bytes)) + } else { + Err(ParseError::InvalidHex) + } } _ => Err(ParseError::InvalidHex), } @@ -328,7 +336,9 @@ impl FromStr for ColorU8 { } // named color else { - if let Some(col) = named::lookup_name(raw) { + if let Some(col) = css4::lookup_name(raw) { + Ok(col) + } else if let Some(col) = xkcd::lookup_name(raw) { Ok(col) } else { Err(ParseError::UnknownName) diff --git a/base/src/color/named.rs b/base/src/color/css4.rs similarity index 100% rename from base/src/color/named.rs rename to base/src/color/css4.rs diff --git a/base/src/color/xkcd.rs b/base/src/color/xkcd.rs new file mode 100644 index 0000000..b5f7a34 --- /dev/null +++ b/base/src/color/xkcd.rs @@ -0,0 +1,961 @@ +use crate::ColorU8; + +/// Colors from the xkcd color survey. +/// These are not intended to be used directly, but are available for users who want to use them. +/// https://blog.xkcd.com/2010/05/03/color-survey-results/ + +pub fn lookup_name(name: &str) -> Option { + // License: https://creativecommons.org/publicdomain/zero/1.0/ + match name { + "cloudy blue" => Some(ColorU8::from_html(b"#acc2d9")), + "dark pastel green" => Some(ColorU8::from_html(b"#56ae57")), + "dust" => Some(ColorU8::from_html(b"#b2996e")), + "electric lime" => Some(ColorU8::from_html(b"#a8ff04")), + "fresh green" => Some(ColorU8::from_html(b"#69d84f")), + "light eggplant" => Some(ColorU8::from_html(b"#894585")), + "nasty green" => Some(ColorU8::from_html(b"#70b23f")), + "really light blue" => Some(ColorU8::from_html(b"#d4ffff")), + "tea" => Some(ColorU8::from_html(b"#65ab7c")), + "warm purple" => Some(ColorU8::from_html(b"#952e8f")), + "yellowish tan" => Some(ColorU8::from_html(b"#fcfc81")), + "cement" => Some(ColorU8::from_html(b"#a5a391")), + "dark grass green" => Some(ColorU8::from_html(b"#388004")), + "dusty teal" => Some(ColorU8::from_html(b"#4c9085")), + "grey teal" => Some(ColorU8::from_html(b"#5e9b8a")), + "macaroni and cheese" => Some(ColorU8::from_html(b"#efb435")), + "pinkish tan" => Some(ColorU8::from_html(b"#d99b82")), + "spruce" => Some(ColorU8::from_html(b"#0a5f38")), + "strong blue" => Some(ColorU8::from_html(b"#0c06f7")), + "toxic green" => Some(ColorU8::from_html(b"#61de2a")), + "windows blue" => Some(ColorU8::from_html(b"#3778bf")), + "blue blue" => Some(ColorU8::from_html(b"#2242c7")), + "blue with a hint of purple" => Some(ColorU8::from_html(b"#533cc6")), + "booger" => Some(ColorU8::from_html(b"#9bb53c")), + "bright sea green" => Some(ColorU8::from_html(b"#05ffa6")), + "dark green blue" => Some(ColorU8::from_html(b"#1f6357")), + "deep turquoise" => Some(ColorU8::from_html(b"#017374")), + "green teal" => Some(ColorU8::from_html(b"#0cb577")), + "strong pink" => Some(ColorU8::from_html(b"#ff0789")), + "bland" => Some(ColorU8::from_html(b"#afa88b")), + "deep aqua" => Some(ColorU8::from_html(b"#08787f")), + "lavender pink" => Some(ColorU8::from_html(b"#dd85d7")), + "light moss green" => Some(ColorU8::from_html(b"#a6c875")), + "light seafoam green" => Some(ColorU8::from_html(b"#a7ffb5")), + "olive yellow" => Some(ColorU8::from_html(b"#c2b709")), + "pig pink" => Some(ColorU8::from_html(b"#e78ea5")), + "deep lilac" => Some(ColorU8::from_html(b"#966ebd")), + "desert" => Some(ColorU8::from_html(b"#ccad60")), + "dusty lavender" => Some(ColorU8::from_html(b"#ac86a8")), + "purpley grey" => Some(ColorU8::from_html(b"#947e94")), + "purply" => Some(ColorU8::from_html(b"#983fb2")), + "candy pink" => Some(ColorU8::from_html(b"#ff63e9")), + "light pastel green" => Some(ColorU8::from_html(b"#b2fba5")), + "boring green" => Some(ColorU8::from_html(b"#63b365")), + "kiwi green" => Some(ColorU8::from_html(b"#8ee53f")), + "light grey green" => Some(ColorU8::from_html(b"#b7e1a1")), + "orange pink" => Some(ColorU8::from_html(b"#ff6f52")), + "tea green" => Some(ColorU8::from_html(b"#bdf8a3")), + "very light brown" => Some(ColorU8::from_html(b"#d3b683")), + "egg shell" => Some(ColorU8::from_html(b"#fffcc4")), + "eggplant purple" => Some(ColorU8::from_html(b"#430541")), + "powder pink" => Some(ColorU8::from_html(b"#ffb2d0")), + "reddish grey" => Some(ColorU8::from_html(b"#997570")), + "baby shit brown" => Some(ColorU8::from_html(b"#ad900d")), + "liliac" => Some(ColorU8::from_html(b"#c48efd")), + "stormy blue" => Some(ColorU8::from_html(b"#507b9c")), + "ugly brown" => Some(ColorU8::from_html(b"#7d7103")), + "custard" => Some(ColorU8::from_html(b"#fffd78")), + "darkish pink" => Some(ColorU8::from_html(b"#da467d")), + "deep brown" => Some(ColorU8::from_html(b"#410200")), + "greenish beige" => Some(ColorU8::from_html(b"#c9d179")), + "manilla" => Some(ColorU8::from_html(b"#fffa86")), + "off blue" => Some(ColorU8::from_html(b"#5684ae")), + "battleship grey" => Some(ColorU8::from_html(b"#6b7c85")), + "browny green" => Some(ColorU8::from_html(b"#6f6c0a")), + "bruise" => Some(ColorU8::from_html(b"#7e4071")), + "kelley green" => Some(ColorU8::from_html(b"#009337")), + "sickly yellow" => Some(ColorU8::from_html(b"#d0e429")), + "sunny yellow" => Some(ColorU8::from_html(b"#fff917")), + "azul" => Some(ColorU8::from_html(b"#1d5dec")), + "darkgreen" => Some(ColorU8::from_html(b"#054907")), + "green/yellow" => Some(ColorU8::from_html(b"#b5ce08")), + "lichen" => Some(ColorU8::from_html(b"#8fb67b")), + "light light green" => Some(ColorU8::from_html(b"#c8ffb0")), + "pale gold" => Some(ColorU8::from_html(b"#fdde6c")), + "sun yellow" => Some(ColorU8::from_html(b"#ffdf22")), + "tan green" => Some(ColorU8::from_html(b"#a9be70")), + "burple" => Some(ColorU8::from_html(b"#6832e3")), + "butterscotch" => Some(ColorU8::from_html(b"#fdb147")), + "toupe" => Some(ColorU8::from_html(b"#c7ac7d")), + "dark cream" => Some(ColorU8::from_html(b"#fff39a")), + "indian red" => Some(ColorU8::from_html(b"#850e04")), + "light lavendar" => Some(ColorU8::from_html(b"#efc0fe")), + "poison green" => Some(ColorU8::from_html(b"#40fd14")), + "baby puke green" => Some(ColorU8::from_html(b"#b6c406")), + "bright yellow green" => Some(ColorU8::from_html(b"#9dff00")), + "charcoal grey" => Some(ColorU8::from_html(b"#3c4142")), + "squash" => Some(ColorU8::from_html(b"#f2ab15")), + "cinnamon" => Some(ColorU8::from_html(b"#ac4f06")), + "light pea green" => Some(ColorU8::from_html(b"#c4fe82")), + "radioactive green" => Some(ColorU8::from_html(b"#2cfa1f")), + "raw sienna" => Some(ColorU8::from_html(b"#9a6200")), + "baby purple" => Some(ColorU8::from_html(b"#ca9bf7")), + "cocoa" => Some(ColorU8::from_html(b"#875f42")), + "light royal blue" => Some(ColorU8::from_html(b"#3a2efe")), + "orangeish" => Some(ColorU8::from_html(b"#fd8d49")), + "rust brown" => Some(ColorU8::from_html(b"#8b3103")), + "sand brown" => Some(ColorU8::from_html(b"#cba560")), + "swamp" => Some(ColorU8::from_html(b"#698339")), + "tealish green" => Some(ColorU8::from_html(b"#0cdc73")), + "burnt siena" => Some(ColorU8::from_html(b"#b75203")), + "camo" => Some(ColorU8::from_html(b"#7f8f4e")), + "dusk blue" => Some(ColorU8::from_html(b"#26538d")), + "fern" => Some(ColorU8::from_html(b"#63a950")), + "old rose" => Some(ColorU8::from_html(b"#c87f89")), + "pale light green" => Some(ColorU8::from_html(b"#b1fc99")), + "peachy pink" => Some(ColorU8::from_html(b"#ff9a8a")), + "rosy pink" => Some(ColorU8::from_html(b"#f6688e")), + "light bluish green" => Some(ColorU8::from_html(b"#76fda8")), + "light bright green" => Some(ColorU8::from_html(b"#53fe5c")), + "light neon green" => Some(ColorU8::from_html(b"#4efd54")), + "light seafoam" => Some(ColorU8::from_html(b"#a0febf")), + "tiffany blue" => Some(ColorU8::from_html(b"#7bf2da")), + "washed out green" => Some(ColorU8::from_html(b"#bcf5a6")), + "browny orange" => Some(ColorU8::from_html(b"#ca6b02")), + "nice blue" => Some(ColorU8::from_html(b"#107ab0")), + "sapphire" => Some(ColorU8::from_html(b"#2138ab")), + "greyish teal" => Some(ColorU8::from_html(b"#719f91")), + "orangey yellow" => Some(ColorU8::from_html(b"#fdb915")), + "parchment" => Some(ColorU8::from_html(b"#fefcaf")), + "straw" => Some(ColorU8::from_html(b"#fcf679")), + "very dark brown" => Some(ColorU8::from_html(b"#1d0200")), + "terracota" => Some(ColorU8::from_html(b"#cb6843")), + "ugly blue" => Some(ColorU8::from_html(b"#31668a")), + "clear blue" => Some(ColorU8::from_html(b"#247afd")), + "creme" => Some(ColorU8::from_html(b"#ffffb6")), + "foam green" => Some(ColorU8::from_html(b"#90fda9")), + "grey/green" => Some(ColorU8::from_html(b"#86a17d")), + "light gold" => Some(ColorU8::from_html(b"#fddc5c")), + "seafoam blue" => Some(ColorU8::from_html(b"#78d1b6")), + "topaz" => Some(ColorU8::from_html(b"#13bbaf")), + "violet pink" => Some(ColorU8::from_html(b"#fb5ffc")), + "wintergreen" => Some(ColorU8::from_html(b"#20f986")), + "yellow tan" => Some(ColorU8::from_html(b"#ffe36e")), + "dark fuchsia" => Some(ColorU8::from_html(b"#9d0759")), + "indigo blue" => Some(ColorU8::from_html(b"#3a18b1")), + "light yellowish green" => Some(ColorU8::from_html(b"#c2ff89")), + "pale magenta" => Some(ColorU8::from_html(b"#d767ad")), + "rich purple" => Some(ColorU8::from_html(b"#720058")), + "sunflower yellow" => Some(ColorU8::from_html(b"#ffda03")), + "green/blue" => Some(ColorU8::from_html(b"#01c08d")), + "leather" => Some(ColorU8::from_html(b"#ac7434")), + "racing green" => Some(ColorU8::from_html(b"#014600")), + "vivid purple" => Some(ColorU8::from_html(b"#9900fa")), + "dark royal blue" => Some(ColorU8::from_html(b"#02066f")), + "hazel" => Some(ColorU8::from_html(b"#8e7618")), + "muted pink" => Some(ColorU8::from_html(b"#d1768f")), + "booger green" => Some(ColorU8::from_html(b"#96b403")), + "canary" => Some(ColorU8::from_html(b"#fdff63")), + "cool grey" => Some(ColorU8::from_html(b"#95a3a6")), + "dark taupe" => Some(ColorU8::from_html(b"#7f684e")), + "darkish purple" => Some(ColorU8::from_html(b"#751973")), + "true green" => Some(ColorU8::from_html(b"#089404")), + "coral pink" => Some(ColorU8::from_html(b"#ff6163")), + "dark sage" => Some(ColorU8::from_html(b"#598556")), + "dark slate blue" => Some(ColorU8::from_html(b"#214761")), + "flat blue" => Some(ColorU8::from_html(b"#3c73a8")), + "mushroom" => Some(ColorU8::from_html(b"#ba9e88")), + "rich blue" => Some(ColorU8::from_html(b"#021bf9")), + "dirty purple" => Some(ColorU8::from_html(b"#734a65")), + "greenblue" => Some(ColorU8::from_html(b"#23c48b")), + "icky green" => Some(ColorU8::from_html(b"#8fae22")), + "light khaki" => Some(ColorU8::from_html(b"#e6f2a2")), + "warm blue" => Some(ColorU8::from_html(b"#4b57db")), + "dark hot pink" => Some(ColorU8::from_html(b"#d90166")), + "deep sea blue" => Some(ColorU8::from_html(b"#015482")), + "carmine" => Some(ColorU8::from_html(b"#9d0216")), + "dark yellow green" => Some(ColorU8::from_html(b"#728f02")), + "pale peach" => Some(ColorU8::from_html(b"#ffe5ad")), + "plum purple" => Some(ColorU8::from_html(b"#4e0550")), + "golden rod" => Some(ColorU8::from_html(b"#f9bc08")), + "neon red" => Some(ColorU8::from_html(b"#ff073a")), + "old pink" => Some(ColorU8::from_html(b"#c77986")), + "very pale blue" => Some(ColorU8::from_html(b"#d6fffe")), + "blood orange" => Some(ColorU8::from_html(b"#fe4b03")), + "grapefruit" => Some(ColorU8::from_html(b"#fd5956")), + "sand yellow" => Some(ColorU8::from_html(b"#fce166")), + "clay brown" => Some(ColorU8::from_html(b"#b2713d")), + "dark blue grey" => Some(ColorU8::from_html(b"#1f3b4d")), + "flat green" => Some(ColorU8::from_html(b"#699d4c")), + "light green blue" => Some(ColorU8::from_html(b"#56fca2")), + "warm pink" => Some(ColorU8::from_html(b"#fb5581")), + "dodger blue" => Some(ColorU8::from_html(b"#3e82fc")), + "gross green" => Some(ColorU8::from_html(b"#a0bf16")), + "ice" => Some(ColorU8::from_html(b"#d6fffa")), + "metallic blue" => Some(ColorU8::from_html(b"#4f738e")), + "pale salmon" => Some(ColorU8::from_html(b"#ffb19a")), + "sap green" => Some(ColorU8::from_html(b"#5c8b15")), + "algae" => Some(ColorU8::from_html(b"#54ac68")), + "bluey grey" => Some(ColorU8::from_html(b"#89a0b0")), + "greeny grey" => Some(ColorU8::from_html(b"#7ea07a")), + "highlighter green" => Some(ColorU8::from_html(b"#1bfc06")), + "light light blue" => Some(ColorU8::from_html(b"#cafffb")), + "light mint" => Some(ColorU8::from_html(b"#b6ffbb")), + "raw umber" => Some(ColorU8::from_html(b"#a75e09")), + "vivid blue" => Some(ColorU8::from_html(b"#152eff")), + "deep lavender" => Some(ColorU8::from_html(b"#8d5eb7")), + "dull teal" => Some(ColorU8::from_html(b"#5f9e8f")), + "light greenish blue" => Some(ColorU8::from_html(b"#63f7b4")), + "mud green" => Some(ColorU8::from_html(b"#606602")), + "pinky" => Some(ColorU8::from_html(b"#fc86aa")), + "red wine" => Some(ColorU8::from_html(b"#8c0034")), + "shit green" => Some(ColorU8::from_html(b"#758000")), + "tan brown" => Some(ColorU8::from_html(b"#ab7e4c")), + "darkblue" => Some(ColorU8::from_html(b"#030764")), + "rosa" => Some(ColorU8::from_html(b"#fe86a4")), + "lipstick" => Some(ColorU8::from_html(b"#d5174e")), + "pale mauve" => Some(ColorU8::from_html(b"#fed0fc")), + "claret" => Some(ColorU8::from_html(b"#680018")), + "dandelion" => Some(ColorU8::from_html(b"#fedf08")), + "orangered" => Some(ColorU8::from_html(b"#fe420f")), + "poop green" => Some(ColorU8::from_html(b"#6f7c00")), + "ruby" => Some(ColorU8::from_html(b"#ca0147")), + "dark" => Some(ColorU8::from_html(b"#1b2431")), + "greenish turquoise" => Some(ColorU8::from_html(b"#00fbb0")), + "pastel red" => Some(ColorU8::from_html(b"#db5856")), + "piss yellow" => Some(ColorU8::from_html(b"#ddd618")), + "bright cyan" => Some(ColorU8::from_html(b"#41fdfe")), + "dark coral" => Some(ColorU8::from_html(b"#cf524e")), + "algae green" => Some(ColorU8::from_html(b"#21c36f")), + "darkish red" => Some(ColorU8::from_html(b"#a90308")), + "reddy brown" => Some(ColorU8::from_html(b"#6e1005")), + "blush pink" => Some(ColorU8::from_html(b"#fe828c")), + "camouflage green" => Some(ColorU8::from_html(b"#4b6113")), + "lawn green" => Some(ColorU8::from_html(b"#4da409")), + "putty" => Some(ColorU8::from_html(b"#beae8a")), + "vibrant blue" => Some(ColorU8::from_html(b"#0339f8")), + "dark sand" => Some(ColorU8::from_html(b"#a88f59")), + "purple/blue" => Some(ColorU8::from_html(b"#5d21d0")), + "saffron" => Some(ColorU8::from_html(b"#feb209")), + "twilight" => Some(ColorU8::from_html(b"#4e518b")), + "warm brown" => Some(ColorU8::from_html(b"#964e02")), + "bluegrey" => Some(ColorU8::from_html(b"#85a3b2")), + "bubble gum pink" => Some(ColorU8::from_html(b"#ff69af")), + "duck egg blue" => Some(ColorU8::from_html(b"#c3fbf4")), + "greenish cyan" => Some(ColorU8::from_html(b"#2afeb7")), + "petrol" => Some(ColorU8::from_html(b"#005f6a")), + "royal" => Some(ColorU8::from_html(b"#0c1793")), + "butter" => Some(ColorU8::from_html(b"#ffff81")), + "dusty orange" => Some(ColorU8::from_html(b"#f0833a")), + "off yellow" => Some(ColorU8::from_html(b"#f1f33f")), + "pale olive green" => Some(ColorU8::from_html(b"#b1d27b")), + "orangish" => Some(ColorU8::from_html(b"#fc824a")), + "leaf" => Some(ColorU8::from_html(b"#71aa34")), + "light blue grey" => Some(ColorU8::from_html(b"#b7c9e2")), + "dried blood" => Some(ColorU8::from_html(b"#4b0101")), + "lightish purple" => Some(ColorU8::from_html(b"#a552e6")), + "rusty red" => Some(ColorU8::from_html(b"#af2f0d")), + "lavender blue" => Some(ColorU8::from_html(b"#8b88f8")), + "light grass green" => Some(ColorU8::from_html(b"#9af764")), + "light mint green" => Some(ColorU8::from_html(b"#a6fbb2")), + "sunflower" => Some(ColorU8::from_html(b"#ffc512")), + "velvet" => Some(ColorU8::from_html(b"#750851")), + "brick orange" => Some(ColorU8::from_html(b"#c14a09")), + "lightish red" => Some(ColorU8::from_html(b"#fe2f4a")), + "pure blue" => Some(ColorU8::from_html(b"#0203e2")), + "twilight blue" => Some(ColorU8::from_html(b"#0a437a")), + "violet red" => Some(ColorU8::from_html(b"#a50055")), + "yellowy brown" => Some(ColorU8::from_html(b"#ae8b0c")), + "carnation" => Some(ColorU8::from_html(b"#fd798f")), + "muddy yellow" => Some(ColorU8::from_html(b"#bfac05")), + "dark seafoam green" => Some(ColorU8::from_html(b"#3eaf76")), + "deep rose" => Some(ColorU8::from_html(b"#c74767")), + "dusty red" => Some(ColorU8::from_html(b"#b9484e")), + "grey/blue" => Some(ColorU8::from_html(b"#647d8e")), + "lemon lime" => Some(ColorU8::from_html(b"#bffe28")), + "purple/pink" => Some(ColorU8::from_html(b"#d725de")), + "brown yellow" => Some(ColorU8::from_html(b"#b29705")), + "purple brown" => Some(ColorU8::from_html(b"#673a3f")), + "wisteria" => Some(ColorU8::from_html(b"#a87dc2")), + "banana yellow" => Some(ColorU8::from_html(b"#fafe4b")), + "lipstick red" => Some(ColorU8::from_html(b"#c0022f")), + "water blue" => Some(ColorU8::from_html(b"#0e87cc")), + "brown grey" => Some(ColorU8::from_html(b"#8d8468")), + "vibrant purple" => Some(ColorU8::from_html(b"#ad03de")), + "baby green" => Some(ColorU8::from_html(b"#8cff9e")), + "barf green" => Some(ColorU8::from_html(b"#94ac02")), + "eggshell blue" => Some(ColorU8::from_html(b"#c4fff7")), + "sandy yellow" => Some(ColorU8::from_html(b"#fdee73")), + "cool green" => Some(ColorU8::from_html(b"#33b864")), + "pale" => Some(ColorU8::from_html(b"#fff9d0")), + "blue/grey" => Some(ColorU8::from_html(b"#758da3")), + "hot magenta" => Some(ColorU8::from_html(b"#f504c9")), + "greyblue" => Some(ColorU8::from_html(b"#77a1b5")), + "purpley" => Some(ColorU8::from_html(b"#8756e4")), + "baby shit green" => Some(ColorU8::from_html(b"#889717")), + "brownish pink" => Some(ColorU8::from_html(b"#c27e79")), + "dark aquamarine" => Some(ColorU8::from_html(b"#017371")), + "diarrhea" => Some(ColorU8::from_html(b"#9f8303")), + "light mustard" => Some(ColorU8::from_html(b"#f7d560")), + "pale sky blue" => Some(ColorU8::from_html(b"#bdf6fe")), + "turtle green" => Some(ColorU8::from_html(b"#75b84f")), + "bright olive" => Some(ColorU8::from_html(b"#9cbb04")), + "dark grey blue" => Some(ColorU8::from_html(b"#29465b")), + "greeny brown" => Some(ColorU8::from_html(b"#696006")), + "lemon green" => Some(ColorU8::from_html(b"#adf802")), + "light periwinkle" => Some(ColorU8::from_html(b"#c1c6fc")), + "seaweed green" => Some(ColorU8::from_html(b"#35ad6b")), + "sunshine yellow" => Some(ColorU8::from_html(b"#fffd37")), + "ugly purple" => Some(ColorU8::from_html(b"#a442a0")), + "medium pink" => Some(ColorU8::from_html(b"#f36196")), + "puke brown" => Some(ColorU8::from_html(b"#947706")), + "very light pink" => Some(ColorU8::from_html(b"#fff4f2")), + "viridian" => Some(ColorU8::from_html(b"#1e9167")), + "bile" => Some(ColorU8::from_html(b"#b5c306")), + "faded yellow" => Some(ColorU8::from_html(b"#feff7f")), + "very pale green" => Some(ColorU8::from_html(b"#cffdbc")), + "vibrant green" => Some(ColorU8::from_html(b"#0add08")), + "bright lime" => Some(ColorU8::from_html(b"#87fd05")), + "spearmint" => Some(ColorU8::from_html(b"#1ef876")), + "light aquamarine" => Some(ColorU8::from_html(b"#7bfdc7")), + "light sage" => Some(ColorU8::from_html(b"#bcecac")), + "yellowgreen" => Some(ColorU8::from_html(b"#bbf90f")), + "baby poo" => Some(ColorU8::from_html(b"#ab9004")), + "dark seafoam" => Some(ColorU8::from_html(b"#1fb57a")), + "deep teal" => Some(ColorU8::from_html(b"#00555a")), + "heather" => Some(ColorU8::from_html(b"#a484ac")), + "rust orange" => Some(ColorU8::from_html(b"#c45508")), + "dirty blue" => Some(ColorU8::from_html(b"#3f829d")), + "fern green" => Some(ColorU8::from_html(b"#548d44")), + "bright lilac" => Some(ColorU8::from_html(b"#c95efb")), + "weird green" => Some(ColorU8::from_html(b"#3ae57f")), + "peacock blue" => Some(ColorU8::from_html(b"#016795")), + "avocado green" => Some(ColorU8::from_html(b"#87a922")), + "faded orange" => Some(ColorU8::from_html(b"#f0944d")), + "grape purple" => Some(ColorU8::from_html(b"#5d1451")), + "hot green" => Some(ColorU8::from_html(b"#25ff29")), + "lime yellow" => Some(ColorU8::from_html(b"#d0fe1d")), + "mango" => Some(ColorU8::from_html(b"#ffa62b")), + "shamrock" => Some(ColorU8::from_html(b"#01b44c")), + "bubblegum" => Some(ColorU8::from_html(b"#ff6cb5")), + "purplish brown" => Some(ColorU8::from_html(b"#6b4247")), + "vomit yellow" => Some(ColorU8::from_html(b"#c7c10c")), + "pale cyan" => Some(ColorU8::from_html(b"#b7fffa")), + "key lime" => Some(ColorU8::from_html(b"#aeff6e")), + "tomato red" => Some(ColorU8::from_html(b"#ec2d01")), + "lightgreen" => Some(ColorU8::from_html(b"#76ff7b")), + "merlot" => Some(ColorU8::from_html(b"#730039")), + "night blue" => Some(ColorU8::from_html(b"#040348")), + "purpleish pink" => Some(ColorU8::from_html(b"#df4ec8")), + "apple" => Some(ColorU8::from_html(b"#6ecb3c")), + "baby poop green" => Some(ColorU8::from_html(b"#8f9805")), + "green apple" => Some(ColorU8::from_html(b"#5edc1f")), + "heliotrope" => Some(ColorU8::from_html(b"#d94ff5")), + "yellow/green" => Some(ColorU8::from_html(b"#c8fd3d")), + "almost black" => Some(ColorU8::from_html(b"#070d0d")), + "cool blue" => Some(ColorU8::from_html(b"#4984b8")), + "leafy green" => Some(ColorU8::from_html(b"#51b73b")), + "mustard brown" => Some(ColorU8::from_html(b"#ac7e04")), + "dusk" => Some(ColorU8::from_html(b"#4e5481")), + "dull brown" => Some(ColorU8::from_html(b"#876e4b")), + "frog green" => Some(ColorU8::from_html(b"#58bc08")), + "vivid green" => Some(ColorU8::from_html(b"#2fef10")), + "bright light green" => Some(ColorU8::from_html(b"#2dfe54")), + "fluro green" => Some(ColorU8::from_html(b"#0aff02")), + "kiwi" => Some(ColorU8::from_html(b"#9cef43")), + "seaweed" => Some(ColorU8::from_html(b"#18d17b")), + "navy green" => Some(ColorU8::from_html(b"#35530a")), + "ultramarine blue" => Some(ColorU8::from_html(b"#1805db")), + "iris" => Some(ColorU8::from_html(b"#6258c4")), + "pastel orange" => Some(ColorU8::from_html(b"#ff964f")), + "yellowish orange" => Some(ColorU8::from_html(b"#ffab0f")), + "perrywinkle" => Some(ColorU8::from_html(b"#8f8ce7")), + "tealish" => Some(ColorU8::from_html(b"#24bca8")), + "dark plum" => Some(ColorU8::from_html(b"#3f012c")), + "pear" => Some(ColorU8::from_html(b"#cbf85f")), + "pinkish orange" => Some(ColorU8::from_html(b"#ff724c")), + "midnight purple" => Some(ColorU8::from_html(b"#280137")), + "light urple" => Some(ColorU8::from_html(b"#b36ff6")), + "dark mint" => Some(ColorU8::from_html(b"#48c072")), + "greenish tan" => Some(ColorU8::from_html(b"#bccb7a")), + "light burgundy" => Some(ColorU8::from_html(b"#a8415b")), + "turquoise blue" => Some(ColorU8::from_html(b"#06b1c4")), + "ugly pink" => Some(ColorU8::from_html(b"#cd7584")), + "sandy" => Some(ColorU8::from_html(b"#f1da7a")), + "electric pink" => Some(ColorU8::from_html(b"#ff0490")), + "muted purple" => Some(ColorU8::from_html(b"#805b87")), + "mid green" => Some(ColorU8::from_html(b"#50a747")), + "greyish" => Some(ColorU8::from_html(b"#a8a495")), + "neon yellow" => Some(ColorU8::from_html(b"#cfff04")), + "banana" => Some(ColorU8::from_html(b"#ffff7e")), + "carnation pink" => Some(ColorU8::from_html(b"#ff7fa7")), + "tomato" => Some(ColorU8::from_html(b"#ef4026")), + "sea" => Some(ColorU8::from_html(b"#3c9992")), + "muddy brown" => Some(ColorU8::from_html(b"#886806")), + "turquoise green" => Some(ColorU8::from_html(b"#04f489")), + "buff" => Some(ColorU8::from_html(b"#fef69e")), + "fawn" => Some(ColorU8::from_html(b"#cfaf7b")), + "muted blue" => Some(ColorU8::from_html(b"#3b719f")), + "pale rose" => Some(ColorU8::from_html(b"#fdc1c5")), + "dark mint green" => Some(ColorU8::from_html(b"#20c073")), + "amethyst" => Some(ColorU8::from_html(b"#9b5fc0")), + "blue/green" => Some(ColorU8::from_html(b"#0f9b8e")), + "chestnut" => Some(ColorU8::from_html(b"#742802")), + "sick green" => Some(ColorU8::from_html(b"#9db92c")), + "pea" => Some(ColorU8::from_html(b"#a4bf20")), + "rusty orange" => Some(ColorU8::from_html(b"#cd5909")), + "stone" => Some(ColorU8::from_html(b"#ada587")), + "rose red" => Some(ColorU8::from_html(b"#be013c")), + "pale aqua" => Some(ColorU8::from_html(b"#b8ffeb")), + "deep orange" => Some(ColorU8::from_html(b"#dc4d01")), + "earth" => Some(ColorU8::from_html(b"#a2653e")), + "mossy green" => Some(ColorU8::from_html(b"#638b27")), + "grassy green" => Some(ColorU8::from_html(b"#419c03")), + "pale lime green" => Some(ColorU8::from_html(b"#b1ff65")), + "light grey blue" => Some(ColorU8::from_html(b"#9dbcd4")), + "pale grey" => Some(ColorU8::from_html(b"#fdfdfe")), + "asparagus" => Some(ColorU8::from_html(b"#77ab56")), + "blueberry" => Some(ColorU8::from_html(b"#464196")), + "purple red" => Some(ColorU8::from_html(b"#990147")), + "pale lime" => Some(ColorU8::from_html(b"#befd73")), + "greenish teal" => Some(ColorU8::from_html(b"#32bf84")), + "caramel" => Some(ColorU8::from_html(b"#af6f09")), + "deep magenta" => Some(ColorU8::from_html(b"#a0025c")), + "light peach" => Some(ColorU8::from_html(b"#ffd8b1")), + "milk chocolate" => Some(ColorU8::from_html(b"#7f4e1e")), + "ocher" => Some(ColorU8::from_html(b"#bf9b0c")), + "off green" => Some(ColorU8::from_html(b"#6ba353")), + "purply pink" => Some(ColorU8::from_html(b"#f075e6")), + "lightblue" => Some(ColorU8::from_html(b"#7bc8f6")), + "dusky blue" => Some(ColorU8::from_html(b"#475f94")), + "golden" => Some(ColorU8::from_html(b"#f5bf03")), + "light beige" => Some(ColorU8::from_html(b"#fffeb6")), + "butter yellow" => Some(ColorU8::from_html(b"#fffd74")), + "dusky purple" => Some(ColorU8::from_html(b"#895b7b")), + "french blue" => Some(ColorU8::from_html(b"#436bad")), + "ugly yellow" => Some(ColorU8::from_html(b"#d0c101")), + "greeny yellow" => Some(ColorU8::from_html(b"#c6f808")), + "orangish red" => Some(ColorU8::from_html(b"#f43605")), + "shamrock green" => Some(ColorU8::from_html(b"#02c14d")), + "orangish brown" => Some(ColorU8::from_html(b"#b25f03")), + "tree green" => Some(ColorU8::from_html(b"#2a7e19")), + "deep violet" => Some(ColorU8::from_html(b"#490648")), + "gunmetal" => Some(ColorU8::from_html(b"#536267")), + "blue/purple" => Some(ColorU8::from_html(b"#5a06ef")), + "cherry" => Some(ColorU8::from_html(b"#cf0234")), + "sandy brown" => Some(ColorU8::from_html(b"#c4a661")), + "warm grey" => Some(ColorU8::from_html(b"#978a84")), + "dark indigo" => Some(ColorU8::from_html(b"#1f0954")), + "midnight" => Some(ColorU8::from_html(b"#03012d")), + "bluey green" => Some(ColorU8::from_html(b"#2bb179")), + "grey pink" => Some(ColorU8::from_html(b"#c3909b")), + "soft purple" => Some(ColorU8::from_html(b"#a66fb5")), + "blood" => Some(ColorU8::from_html(b"#770001")), + "brown red" => Some(ColorU8::from_html(b"#922b05")), + "medium grey" => Some(ColorU8::from_html(b"#7d7f7c")), + "berry" => Some(ColorU8::from_html(b"#990f4b")), + "poo" => Some(ColorU8::from_html(b"#8f7303")), + "purpley pink" => Some(ColorU8::from_html(b"#c83cb9")), + "light salmon" => Some(ColorU8::from_html(b"#fea993")), + "snot" => Some(ColorU8::from_html(b"#acbb0d")), + "easter purple" => Some(ColorU8::from_html(b"#c071fe")), + "light yellow green" => Some(ColorU8::from_html(b"#ccfd7f")), + "dark navy blue" => Some(ColorU8::from_html(b"#00022e")), + "drab" => Some(ColorU8::from_html(b"#828344")), + "light rose" => Some(ColorU8::from_html(b"#ffc5cb")), + "rouge" => Some(ColorU8::from_html(b"#ab1239")), + "purplish red" => Some(ColorU8::from_html(b"#b0054b")), + "slime green" => Some(ColorU8::from_html(b"#99cc04")), + "baby poop" => Some(ColorU8::from_html(b"#937c00")), + "irish green" => Some(ColorU8::from_html(b"#019529")), + "pink/purple" => Some(ColorU8::from_html(b"#ef1de7")), + "dark navy" => Some(ColorU8::from_html(b"#000435")), + "greeny blue" => Some(ColorU8::from_html(b"#42b395")), + "light plum" => Some(ColorU8::from_html(b"#9d5783")), + "pinkish grey" => Some(ColorU8::from_html(b"#c8aca9")), + "dirty orange" => Some(ColorU8::from_html(b"#c87606")), + "rust red" => Some(ColorU8::from_html(b"#aa2704")), + "pale lilac" => Some(ColorU8::from_html(b"#e4cbff")), + "orangey red" => Some(ColorU8::from_html(b"#fa4224")), + "primary blue" => Some(ColorU8::from_html(b"#0804f9")), + "kermit green" => Some(ColorU8::from_html(b"#5cb200")), + "brownish purple" => Some(ColorU8::from_html(b"#76424e")), + "murky green" => Some(ColorU8::from_html(b"#6c7a0e")), + "wheat" => Some(ColorU8::from_html(b"#fbdd7e")), + "very dark purple" => Some(ColorU8::from_html(b"#2a0134")), + "bottle green" => Some(ColorU8::from_html(b"#044a05")), + "watermelon" => Some(ColorU8::from_html(b"#fd4659")), + "deep sky blue" => Some(ColorU8::from_html(b"#0d75f8")), + "fire engine red" => Some(ColorU8::from_html(b"#fe0002")), + "yellow ochre" => Some(ColorU8::from_html(b"#cb9d06")), + "pumpkin orange" => Some(ColorU8::from_html(b"#fb7d07")), + "pale olive" => Some(ColorU8::from_html(b"#b9cc81")), + "light lilac" => Some(ColorU8::from_html(b"#edc8ff")), + "lightish green" => Some(ColorU8::from_html(b"#61e160")), + "carolina blue" => Some(ColorU8::from_html(b"#8ab8fe")), + "mulberry" => Some(ColorU8::from_html(b"#920a4e")), + "shocking pink" => Some(ColorU8::from_html(b"#fe02a2")), + "auburn" => Some(ColorU8::from_html(b"#9a3001")), + "bright lime green" => Some(ColorU8::from_html(b"#65fe08")), + "celadon" => Some(ColorU8::from_html(b"#befdb7")), + "pinkish brown" => Some(ColorU8::from_html(b"#b17261")), + "poo brown" => Some(ColorU8::from_html(b"#885f01")), + "bright sky blue" => Some(ColorU8::from_html(b"#02ccfe")), + "celery" => Some(ColorU8::from_html(b"#c1fd95")), + "dirt brown" => Some(ColorU8::from_html(b"#836539")), + "strawberry" => Some(ColorU8::from_html(b"#fb2943")), + "dark lime" => Some(ColorU8::from_html(b"#84b701")), + "copper" => Some(ColorU8::from_html(b"#b66325")), + "medium brown" => Some(ColorU8::from_html(b"#7f5112")), + "muted green" => Some(ColorU8::from_html(b"#5fa052")), + "robin's egg" => Some(ColorU8::from_html(b"#6dedfd")), + "bright aqua" => Some(ColorU8::from_html(b"#0bf9ea")), + "bright lavender" => Some(ColorU8::from_html(b"#c760ff")), + "ivory" => Some(ColorU8::from_html(b"#ffffcb")), + "very light purple" => Some(ColorU8::from_html(b"#f6cefc")), + "light navy" => Some(ColorU8::from_html(b"#155084")), + "pink red" => Some(ColorU8::from_html(b"#f5054f")), + "olive brown" => Some(ColorU8::from_html(b"#645403")), + "poop brown" => Some(ColorU8::from_html(b"#7a5901")), + "mustard green" => Some(ColorU8::from_html(b"#a8b504")), + "ocean green" => Some(ColorU8::from_html(b"#3d9973")), + "very dark blue" => Some(ColorU8::from_html(b"#000133")), + "dusty green" => Some(ColorU8::from_html(b"#76a973")), + "light navy blue" => Some(ColorU8::from_html(b"#2e5a88")), + "minty green" => Some(ColorU8::from_html(b"#0bf77d")), + "adobe" => Some(ColorU8::from_html(b"#bd6c48")), + "barney" => Some(ColorU8::from_html(b"#ac1db8")), + "jade green" => Some(ColorU8::from_html(b"#2baf6a")), + "bright light blue" => Some(ColorU8::from_html(b"#26f7fd")), + "light lime" => Some(ColorU8::from_html(b"#aefd6c")), + "dark khaki" => Some(ColorU8::from_html(b"#9b8f55")), + "orange yellow" => Some(ColorU8::from_html(b"#ffad01")), + "ocre" => Some(ColorU8::from_html(b"#c69c04")), + "maize" => Some(ColorU8::from_html(b"#f4d054")), + "faded pink" => Some(ColorU8::from_html(b"#de9dac")), + "british racing green" => Some(ColorU8::from_html(b"#05480d")), + "sandstone" => Some(ColorU8::from_html(b"#c9ae74")), + "mud brown" => Some(ColorU8::from_html(b"#60460f")), + "light sea green" => Some(ColorU8::from_html(b"#98f6b0")), + "robin egg blue" => Some(ColorU8::from_html(b"#8af1fe")), + "aqua marine" => Some(ColorU8::from_html(b"#2ee8bb")), + "dark sea green" => Some(ColorU8::from_html(b"#11875d")), + "soft pink" => Some(ColorU8::from_html(b"#fdb0c0")), + "orangey brown" => Some(ColorU8::from_html(b"#b16002")), + "cherry red" => Some(ColorU8::from_html(b"#f7022a")), + "burnt yellow" => Some(ColorU8::from_html(b"#d5ab09")), + "brownish grey" => Some(ColorU8::from_html(b"#86775f")), + "camel" => Some(ColorU8::from_html(b"#c69f59")), + "purplish grey" => Some(ColorU8::from_html(b"#7a687f")), + "marine" => Some(ColorU8::from_html(b"#042e60")), + "greyish pink" => Some(ColorU8::from_html(b"#c88d94")), + "pale turquoise" => Some(ColorU8::from_html(b"#a5fbd5")), + "pastel yellow" => Some(ColorU8::from_html(b"#fffe71")), + "bluey purple" => Some(ColorU8::from_html(b"#6241c7")), + "canary yellow" => Some(ColorU8::from_html(b"#fffe40")), + "faded red" => Some(ColorU8::from_html(b"#d3494e")), + "sepia" => Some(ColorU8::from_html(b"#985e2b")), + "coffee" => Some(ColorU8::from_html(b"#a6814c")), + "bright magenta" => Some(ColorU8::from_html(b"#ff08e8")), + "mocha" => Some(ColorU8::from_html(b"#9d7651")), + "ecru" => Some(ColorU8::from_html(b"#feffca")), + "purpleish" => Some(ColorU8::from_html(b"#98568d")), + "cranberry" => Some(ColorU8::from_html(b"#9e003a")), + "darkish green" => Some(ColorU8::from_html(b"#287c37")), + "brown orange" => Some(ColorU8::from_html(b"#b96902")), + "dusky rose" => Some(ColorU8::from_html(b"#ba6873")), + "melon" => Some(ColorU8::from_html(b"#ff7855")), + "sickly green" => Some(ColorU8::from_html(b"#94b21c")), + "silver" => Some(ColorU8::from_html(b"#c5c9c7")), + "purply blue" => Some(ColorU8::from_html(b"#661aee")), + "purpleish blue" => Some(ColorU8::from_html(b"#6140ef")), + "hospital green" => Some(ColorU8::from_html(b"#9be5aa")), + "shit brown" => Some(ColorU8::from_html(b"#7b5804")), + "mid blue" => Some(ColorU8::from_html(b"#276ab3")), + "amber" => Some(ColorU8::from_html(b"#feb308")), + "easter green" => Some(ColorU8::from_html(b"#8cfd7e")), + "soft blue" => Some(ColorU8::from_html(b"#6488ea")), + "cerulean blue" => Some(ColorU8::from_html(b"#056eee")), + "golden brown" => Some(ColorU8::from_html(b"#b27a01")), + "bright turquoise" => Some(ColorU8::from_html(b"#0ffef9")), + "red pink" => Some(ColorU8::from_html(b"#fa2a55")), + "red purple" => Some(ColorU8::from_html(b"#820747")), + "greyish brown" => Some(ColorU8::from_html(b"#7a6a4f")), + "vermillion" => Some(ColorU8::from_html(b"#f4320c")), + "russet" => Some(ColorU8::from_html(b"#a13905")), + "steel grey" => Some(ColorU8::from_html(b"#6f828a")), + "lighter purple" => Some(ColorU8::from_html(b"#a55af4")), + "bright violet" => Some(ColorU8::from_html(b"#ad0afd")), + "prussian blue" => Some(ColorU8::from_html(b"#004577")), + "slate green" => Some(ColorU8::from_html(b"#658d6d")), + "dirty pink" => Some(ColorU8::from_html(b"#ca7b80")), + "dark blue green" => Some(ColorU8::from_html(b"#005249")), + "pine" => Some(ColorU8::from_html(b"#2b5d34")), + "yellowy green" => Some(ColorU8::from_html(b"#bff128")), + "dark gold" => Some(ColorU8::from_html(b"#b59410")), + "bluish" => Some(ColorU8::from_html(b"#2976bb")), + "darkish blue" => Some(ColorU8::from_html(b"#014182")), + "dull red" => Some(ColorU8::from_html(b"#bb3f3f")), + "pinky red" => Some(ColorU8::from_html(b"#fc2647")), + "bronze" => Some(ColorU8::from_html(b"#a87900")), + "pale teal" => Some(ColorU8::from_html(b"#82cbb2")), + "military green" => Some(ColorU8::from_html(b"#667c3e")), + "barbie pink" => Some(ColorU8::from_html(b"#fe46a5")), + "bubblegum pink" => Some(ColorU8::from_html(b"#fe83cc")), + "pea soup green" => Some(ColorU8::from_html(b"#94a617")), + "dark mustard" => Some(ColorU8::from_html(b"#a88905")), + "shit" => Some(ColorU8::from_html(b"#7f5f00")), + "medium purple" => Some(ColorU8::from_html(b"#9e43a2")), + "very dark green" => Some(ColorU8::from_html(b"#062e03")), + "dirt" => Some(ColorU8::from_html(b"#8a6e45")), + "dusky pink" => Some(ColorU8::from_html(b"#cc7a8b")), + "red violet" => Some(ColorU8::from_html(b"#9e0168")), + "lemon yellow" => Some(ColorU8::from_html(b"#fdff38")), + "pistachio" => Some(ColorU8::from_html(b"#c0fa8b")), + "dull yellow" => Some(ColorU8::from_html(b"#eedc5b")), + "dark lime green" => Some(ColorU8::from_html(b"#7ebd01")), + "denim blue" => Some(ColorU8::from_html(b"#3b5b92")), + "teal blue" => Some(ColorU8::from_html(b"#01889f")), + "lightish blue" => Some(ColorU8::from_html(b"#3d7afd")), + "purpley blue" => Some(ColorU8::from_html(b"#5f34e7")), + "light indigo" => Some(ColorU8::from_html(b"#6d5acf")), + "swamp green" => Some(ColorU8::from_html(b"#748500")), + "brown green" => Some(ColorU8::from_html(b"#706c11")), + "dark maroon" => Some(ColorU8::from_html(b"#3c0008")), + "hot purple" => Some(ColorU8::from_html(b"#cb00f5")), + "dark forest green" => Some(ColorU8::from_html(b"#002d04")), + "faded blue" => Some(ColorU8::from_html(b"#658cbb")), + "drab green" => Some(ColorU8::from_html(b"#749551")), + "light lime green" => Some(ColorU8::from_html(b"#b9ff66")), + "snot green" => Some(ColorU8::from_html(b"#9dc100")), + "yellowish" => Some(ColorU8::from_html(b"#faee66")), + "light blue green" => Some(ColorU8::from_html(b"#7efbb3")), + "bordeaux" => Some(ColorU8::from_html(b"#7b002c")), + "light mauve" => Some(ColorU8::from_html(b"#c292a1")), + "ocean" => Some(ColorU8::from_html(b"#017b92")), + "marigold" => Some(ColorU8::from_html(b"#fcc006")), + "muddy green" => Some(ColorU8::from_html(b"#657432")), + "dull orange" => Some(ColorU8::from_html(b"#d8863b")), + "steel" => Some(ColorU8::from_html(b"#738595")), + "electric purple" => Some(ColorU8::from_html(b"#aa23ff")), + "fluorescent green" => Some(ColorU8::from_html(b"#08ff08")), + "yellowish brown" => Some(ColorU8::from_html(b"#9b7a01")), + "blush" => Some(ColorU8::from_html(b"#f29e8e")), + "soft green" => Some(ColorU8::from_html(b"#6fc276")), + "bright orange" => Some(ColorU8::from_html(b"#ff5b00")), + "lemon" => Some(ColorU8::from_html(b"#fdff52")), + "purple grey" => Some(ColorU8::from_html(b"#866f85")), + "acid green" => Some(ColorU8::from_html(b"#8ffe09")), + "pale lavender" => Some(ColorU8::from_html(b"#eecffe")), + "violet blue" => Some(ColorU8::from_html(b"#510ac9")), + "light forest green" => Some(ColorU8::from_html(b"#4f9153")), + "burnt red" => Some(ColorU8::from_html(b"#9f2305")), + "khaki green" => Some(ColorU8::from_html(b"#728639")), + "cerise" => Some(ColorU8::from_html(b"#de0c62")), + "faded purple" => Some(ColorU8::from_html(b"#916e99")), + "apricot" => Some(ColorU8::from_html(b"#ffb16d")), + "dark olive green" => Some(ColorU8::from_html(b"#3c4d03")), + "grey brown" => Some(ColorU8::from_html(b"#7f7053")), + "green grey" => Some(ColorU8::from_html(b"#77926f")), + "true blue" => Some(ColorU8::from_html(b"#010fcc")), + "pale violet" => Some(ColorU8::from_html(b"#ceaefa")), + "periwinkle blue" => Some(ColorU8::from_html(b"#8f99fb")), + "light sky blue" => Some(ColorU8::from_html(b"#c6fcff")), + "blurple" => Some(ColorU8::from_html(b"#5539cc")), + "green brown" => Some(ColorU8::from_html(b"#544e03")), + "bluegreen" => Some(ColorU8::from_html(b"#017a79")), + "bright teal" => Some(ColorU8::from_html(b"#01f9c6")), + "brownish yellow" => Some(ColorU8::from_html(b"#c9b003")), + "pea soup" => Some(ColorU8::from_html(b"#929901")), + "forest" => Some(ColorU8::from_html(b"#0b5509")), + "barney purple" => Some(ColorU8::from_html(b"#a00498")), + "ultramarine" => Some(ColorU8::from_html(b"#2000b1")), + "purplish" => Some(ColorU8::from_html(b"#94568c")), + "puke yellow" => Some(ColorU8::from_html(b"#c2be0e")), + "bluish grey" => Some(ColorU8::from_html(b"#748b97")), + "dark periwinkle" => Some(ColorU8::from_html(b"#665fd1")), + "dark lilac" => Some(ColorU8::from_html(b"#9c6da5")), + "reddish" => Some(ColorU8::from_html(b"#c44240")), + "light maroon" => Some(ColorU8::from_html(b"#a24857")), + "dusty purple" => Some(ColorU8::from_html(b"#825f87")), + "terra cotta" => Some(ColorU8::from_html(b"#c9643b")), + "avocado" => Some(ColorU8::from_html(b"#90b134")), + "marine blue" => Some(ColorU8::from_html(b"#01386a")), + "teal green" => Some(ColorU8::from_html(b"#25a36f")), + "slate grey" => Some(ColorU8::from_html(b"#59656d")), + "lighter green" => Some(ColorU8::from_html(b"#75fd63")), + "electric green" => Some(ColorU8::from_html(b"#21fc0d")), + "dusty blue" => Some(ColorU8::from_html(b"#5a86ad")), + "golden yellow" => Some(ColorU8::from_html(b"#fec615")), + "bright yellow" => Some(ColorU8::from_html(b"#fffd01")), + "light lavender" => Some(ColorU8::from_html(b"#dfc5fe")), + "umber" => Some(ColorU8::from_html(b"#b26400")), + "poop" => Some(ColorU8::from_html(b"#7f5e00")), + "dark peach" => Some(ColorU8::from_html(b"#de7e5d")), + "jungle green" => Some(ColorU8::from_html(b"#048243")), + "eggshell" => Some(ColorU8::from_html(b"#ffffd4")), + "denim" => Some(ColorU8::from_html(b"#3b638c")), + "yellow brown" => Some(ColorU8::from_html(b"#b79400")), + "dull purple" => Some(ColorU8::from_html(b"#84597e")), + "chocolate brown" => Some(ColorU8::from_html(b"#411900")), + "wine red" => Some(ColorU8::from_html(b"#7b0323")), + "neon blue" => Some(ColorU8::from_html(b"#04d9ff")), + "dirty green" => Some(ColorU8::from_html(b"#667e2c")), + "light tan" => Some(ColorU8::from_html(b"#fbeeac")), + "ice blue" => Some(ColorU8::from_html(b"#d7fffe")), + "cadet blue" => Some(ColorU8::from_html(b"#4e7496")), + "dark mauve" => Some(ColorU8::from_html(b"#874c62")), + "very light blue" => Some(ColorU8::from_html(b"#d5ffff")), + "grey purple" => Some(ColorU8::from_html(b"#826d8c")), + "pastel pink" => Some(ColorU8::from_html(b"#ffbacd")), + "very light green" => Some(ColorU8::from_html(b"#d1ffbd")), + "dark sky blue" => Some(ColorU8::from_html(b"#448ee4")), + "evergreen" => Some(ColorU8::from_html(b"#05472a")), + "dull pink" => Some(ColorU8::from_html(b"#d5869d")), + "aubergine" => Some(ColorU8::from_html(b"#3d0734")), + "mahogany" => Some(ColorU8::from_html(b"#4a0100")), + "reddish orange" => Some(ColorU8::from_html(b"#f8481c")), + "deep green" => Some(ColorU8::from_html(b"#02590f")), + "vomit green" => Some(ColorU8::from_html(b"#89a203")), + "purple pink" => Some(ColorU8::from_html(b"#e03fd8")), + "dusty pink" => Some(ColorU8::from_html(b"#d58a94")), + "faded green" => Some(ColorU8::from_html(b"#7bb274")), + "camo green" => Some(ColorU8::from_html(b"#526525")), + "pinky purple" => Some(ColorU8::from_html(b"#c94cbe")), + "pink purple" => Some(ColorU8::from_html(b"#db4bda")), + "brownish red" => Some(ColorU8::from_html(b"#9e3623")), + "dark rose" => Some(ColorU8::from_html(b"#b5485d")), + "mud" => Some(ColorU8::from_html(b"#735c12")), + "brownish" => Some(ColorU8::from_html(b"#9c6d57")), + "emerald green" => Some(ColorU8::from_html(b"#028f1e")), + "pale brown" => Some(ColorU8::from_html(b"#b1916e")), + "dull blue" => Some(ColorU8::from_html(b"#49759c")), + "burnt umber" => Some(ColorU8::from_html(b"#a0450e")), + "medium green" => Some(ColorU8::from_html(b"#39ad48")), + "clay" => Some(ColorU8::from_html(b"#b66a50")), + "light aqua" => Some(ColorU8::from_html(b"#8cffdb")), + "light olive green" => Some(ColorU8::from_html(b"#a4be5c")), + "brownish orange" => Some(ColorU8::from_html(b"#cb7723")), + "dark aqua" => Some(ColorU8::from_html(b"#05696b")), + "purplish pink" => Some(ColorU8::from_html(b"#ce5dae")), + "dark salmon" => Some(ColorU8::from_html(b"#c85a53")), + "greenish grey" => Some(ColorU8::from_html(b"#96ae8d")), + "jade" => Some(ColorU8::from_html(b"#1fa774")), + "ugly green" => Some(ColorU8::from_html(b"#7a9703")), + "dark beige" => Some(ColorU8::from_html(b"#ac9362")), + "emerald" => Some(ColorU8::from_html(b"#01a049")), + "pale red" => Some(ColorU8::from_html(b"#d9544d")), + "light magenta" => Some(ColorU8::from_html(b"#fa5ff7")), + "sky" => Some(ColorU8::from_html(b"#82cafc")), + "light cyan" => Some(ColorU8::from_html(b"#acfffc")), + "yellow orange" => Some(ColorU8::from_html(b"#fcb001")), + "reddish purple" => Some(ColorU8::from_html(b"#910951")), + "reddish pink" => Some(ColorU8::from_html(b"#fe2c54")), + "orchid" => Some(ColorU8::from_html(b"#c875c4")), + "dirty yellow" => Some(ColorU8::from_html(b"#cdc50a")), + "orange red" => Some(ColorU8::from_html(b"#fd411e")), + "deep red" => Some(ColorU8::from_html(b"#9a0200")), + "orange brown" => Some(ColorU8::from_html(b"#be6400")), + "cobalt blue" => Some(ColorU8::from_html(b"#030aa7")), + "neon pink" => Some(ColorU8::from_html(b"#fe019a")), + "rose pink" => Some(ColorU8::from_html(b"#f7879a")), + "greyish purple" => Some(ColorU8::from_html(b"#887191")), + "raspberry" => Some(ColorU8::from_html(b"#b00149")), + "aqua green" => Some(ColorU8::from_html(b"#12e193")), + "salmon pink" => Some(ColorU8::from_html(b"#fe7b7c")), + "tangerine" => Some(ColorU8::from_html(b"#ff9408")), + "brownish green" => Some(ColorU8::from_html(b"#6a6e09")), + "red brown" => Some(ColorU8::from_html(b"#8b2e16")), + "greenish brown" => Some(ColorU8::from_html(b"#696112")), + "pumpkin" => Some(ColorU8::from_html(b"#e17701")), + "pine green" => Some(ColorU8::from_html(b"#0a481e")), + "charcoal" => Some(ColorU8::from_html(b"#343837")), + "baby pink" => Some(ColorU8::from_html(b"#ffb7ce")), + "cornflower" => Some(ColorU8::from_html(b"#6a79f7")), + "blue violet" => Some(ColorU8::from_html(b"#5d06e9")), + "chocolate" => Some(ColorU8::from_html(b"#3d1c02")), + "greyish green" => Some(ColorU8::from_html(b"#82a67d")), + "scarlet" => Some(ColorU8::from_html(b"#be0119")), + "green yellow" => Some(ColorU8::from_html(b"#c9ff27")), + "dark olive" => Some(ColorU8::from_html(b"#373e02")), + "sienna" => Some(ColorU8::from_html(b"#a9561e")), + "pastel purple" => Some(ColorU8::from_html(b"#caa0ff")), + "terracotta" => Some(ColorU8::from_html(b"#ca6641")), + "aqua blue" => Some(ColorU8::from_html(b"#02d8e9")), + "sage green" => Some(ColorU8::from_html(b"#88b378")), + "blood red" => Some(ColorU8::from_html(b"#980002")), + "deep pink" => Some(ColorU8::from_html(b"#cb0162")), + "grass" => Some(ColorU8::from_html(b"#5cac2d")), + "moss" => Some(ColorU8::from_html(b"#769958")), + "pastel blue" => Some(ColorU8::from_html(b"#a2bffe")), + "bluish green" => Some(ColorU8::from_html(b"#10a674")), + "green blue" => Some(ColorU8::from_html(b"#06b48b")), + "dark tan" => Some(ColorU8::from_html(b"#af884a")), + "greenish blue" => Some(ColorU8::from_html(b"#0b8b87")), + "pale orange" => Some(ColorU8::from_html(b"#ffa756")), + "vomit" => Some(ColorU8::from_html(b"#a2a415")), + "forrest green" => Some(ColorU8::from_html(b"#154406")), + "dark lavender" => Some(ColorU8::from_html(b"#856798")), + "dark violet" => Some(ColorU8::from_html(b"#34013f")), + "purple blue" => Some(ColorU8::from_html(b"#632de9")), + "dark cyan" => Some(ColorU8::from_html(b"#0a888a")), + "olive drab" => Some(ColorU8::from_html(b"#6f7632")), + "pinkish" => Some(ColorU8::from_html(b"#d46a7e")), + "cobalt" => Some(ColorU8::from_html(b"#1e488f")), + "neon purple" => Some(ColorU8::from_html(b"#bc13fe")), + "light turquoise" => Some(ColorU8::from_html(b"#7ef4cc")), + "apple green" => Some(ColorU8::from_html(b"#76cd26")), + "dull green" => Some(ColorU8::from_html(b"#74a662")), + "wine" => Some(ColorU8::from_html(b"#80013f")), + "powder blue" => Some(ColorU8::from_html(b"#b1d1fc")), + "off white" => Some(ColorU8::from_html(b"#ffffe4")), + "electric blue" => Some(ColorU8::from_html(b"#0652ff")), + "dark turquoise" => Some(ColorU8::from_html(b"#045c5a")), + "blue purple" => Some(ColorU8::from_html(b"#5729ce")), + "azure" => Some(ColorU8::from_html(b"#069af3")), + "bright red" => Some(ColorU8::from_html(b"#ff000d")), + "pinkish red" => Some(ColorU8::from_html(b"#f10c45")), + "cornflower blue" => Some(ColorU8::from_html(b"#5170d7")), + "light olive" => Some(ColorU8::from_html(b"#acbf69")), + "grape" => Some(ColorU8::from_html(b"#6c3461")), + "greyish blue" => Some(ColorU8::from_html(b"#5e819d")), + "purplish blue" => Some(ColorU8::from_html(b"#601ef9")), + "yellowish green" => Some(ColorU8::from_html(b"#b0dd16")), + "greenish yellow" => Some(ColorU8::from_html(b"#cdfd02")), + "medium blue" => Some(ColorU8::from_html(b"#2c6fbb")), + "dusty rose" => Some(ColorU8::from_html(b"#c0737a")), + "light violet" => Some(ColorU8::from_html(b"#d6b4fc")), + "midnight blue" => Some(ColorU8::from_html(b"#020035")), + "bluish purple" => Some(ColorU8::from_html(b"#703be7")), + "red orange" => Some(ColorU8::from_html(b"#fd3c06")), + "dark magenta" => Some(ColorU8::from_html(b"#960056")), + "greenish" => Some(ColorU8::from_html(b"#40a368")), + "ocean blue" => Some(ColorU8::from_html(b"#03719c")), + "coral" => Some(ColorU8::from_html(b"#fc5a50")), + "cream" => Some(ColorU8::from_html(b"#ffffc2")), + "reddish brown" => Some(ColorU8::from_html(b"#7f2b0a")), + "burnt sienna" => Some(ColorU8::from_html(b"#b04e0f")), + "brick" => Some(ColorU8::from_html(b"#a03623")), + "sage" => Some(ColorU8::from_html(b"#87ae73")), + "grey green" => Some(ColorU8::from_html(b"#789b73")), + "white" => Some(ColorU8::from_html(b"#ffffff")), + "robin's egg blue" => Some(ColorU8::from_html(b"#98eff9")), + "moss green" => Some(ColorU8::from_html(b"#658b38")), + "steel blue" => Some(ColorU8::from_html(b"#5a7d9a")), + "eggplant" => Some(ColorU8::from_html(b"#380835")), + "light yellow" => Some(ColorU8::from_html(b"#fffe7a")), + "leaf green" => Some(ColorU8::from_html(b"#5ca904")), + "light grey" => Some(ColorU8::from_html(b"#d8dcd6")), + "puke" => Some(ColorU8::from_html(b"#a5a502")), + "pinkish purple" => Some(ColorU8::from_html(b"#d648d7")), + "sea blue" => Some(ColorU8::from_html(b"#047495")), + "pale purple" => Some(ColorU8::from_html(b"#b790d4")), + "slate blue" => Some(ColorU8::from_html(b"#5b7c99")), + "blue grey" => Some(ColorU8::from_html(b"#607c8e")), + "hunter green" => Some(ColorU8::from_html(b"#0b4008")), + "fuchsia" => Some(ColorU8::from_html(b"#ed0dd9")), + "crimson" => Some(ColorU8::from_html(b"#8c000f")), + "pale yellow" => Some(ColorU8::from_html(b"#ffff84")), + "ochre" => Some(ColorU8::from_html(b"#bf9005")), + "mustard yellow" => Some(ColorU8::from_html(b"#d2bd0a")), + "light red" => Some(ColorU8::from_html(b"#ff474c")), + "cerulean" => Some(ColorU8::from_html(b"#0485d1")), + "pale pink" => Some(ColorU8::from_html(b"#ffcfdc")), + "deep blue" => Some(ColorU8::from_html(b"#040273")), + "rust" => Some(ColorU8::from_html(b"#a83c09")), + "light teal" => Some(ColorU8::from_html(b"#90e4c1")), + "slate" => Some(ColorU8::from_html(b"#516572")), + "goldenrod" => Some(ColorU8::from_html(b"#fac205")), + "dark yellow" => Some(ColorU8::from_html(b"#d5b60a")), + "dark grey" => Some(ColorU8::from_html(b"#363737")), + "army green" => Some(ColorU8::from_html(b"#4b5d16")), + "grey blue" => Some(ColorU8::from_html(b"#6b8ba4")), + "seafoam" => Some(ColorU8::from_html(b"#80f9ad")), + "puce" => Some(ColorU8::from_html(b"#a57e52")), + "spring green" => Some(ColorU8::from_html(b"#a9f971")), + "dark orange" => Some(ColorU8::from_html(b"#c65102")), + "sand" => Some(ColorU8::from_html(b"#e2ca76")), + "pastel green" => Some(ColorU8::from_html(b"#b0ff9d")), + "mint" => Some(ColorU8::from_html(b"#9ffeb0")), + "light orange" => Some(ColorU8::from_html(b"#fdaa48")), + "bright pink" => Some(ColorU8::from_html(b"#fe01b1")), + "chartreuse" => Some(ColorU8::from_html(b"#c1f80a")), + "deep purple" => Some(ColorU8::from_html(b"#36013f")), + "dark brown" => Some(ColorU8::from_html(b"#341c02")), + "taupe" => Some(ColorU8::from_html(b"#b9a281")), + "pea green" => Some(ColorU8::from_html(b"#8eab12")), + "puke green" => Some(ColorU8::from_html(b"#9aae07")), + "kelly green" => Some(ColorU8::from_html(b"#02ab2e")), + "seafoam green" => Some(ColorU8::from_html(b"#7af9ab")), + "blue green" => Some(ColorU8::from_html(b"#137e6d")), + "khaki" => Some(ColorU8::from_html(b"#aaa662")), + "burgundy" => Some(ColorU8::from_html(b"#610023")), + "dark teal" => Some(ColorU8::from_html(b"#014d4e")), + "brick red" => Some(ColorU8::from_html(b"#8f1402")), + "royal purple" => Some(ColorU8::from_html(b"#4b006e")), + "plum" => Some(ColorU8::from_html(b"#580f41")), + "mint green" => Some(ColorU8::from_html(b"#8fff9f")), + "gold" => Some(ColorU8::from_html(b"#dbb40c")), + "baby blue" => Some(ColorU8::from_html(b"#a2cffe")), + "yellow green" => Some(ColorU8::from_html(b"#c0fb2d")), + "bright purple" => Some(ColorU8::from_html(b"#be03fd")), + "dark red" => Some(ColorU8::from_html(b"#840000")), + "pale blue" => Some(ColorU8::from_html(b"#d0fefe")), + "grass green" => Some(ColorU8::from_html(b"#3f9b0b")), + "navy" => Some(ColorU8::from_html(b"#01153e")), + "aquamarine" => Some(ColorU8::from_html(b"#04d8b2")), + "burnt orange" => Some(ColorU8::from_html(b"#c04e01")), + "neon green" => Some(ColorU8::from_html(b"#0cff0c")), + "bright blue" => Some(ColorU8::from_html(b"#0165fc")), + "rose" => Some(ColorU8::from_html(b"#cf6275")), + "light pink" => Some(ColorU8::from_html(b"#ffd1df")), + "mustard" => Some(ColorU8::from_html(b"#ceb301")), + "indigo" => Some(ColorU8::from_html(b"#380282")), + "lime" => Some(ColorU8::from_html(b"#aaff32")), + "sea green" => Some(ColorU8::from_html(b"#53fca1")), + "periwinkle" => Some(ColorU8::from_html(b"#8e82fe")), + "dark pink" => Some(ColorU8::from_html(b"#cb416b")), + "olive green" => Some(ColorU8::from_html(b"#677a04")), + "peach" => Some(ColorU8::from_html(b"#ffb07c")), + "pale green" => Some(ColorU8::from_html(b"#c7fdb5")), + "light brown" => Some(ColorU8::from_html(b"#ad8150")), + "hot pink" => Some(ColorU8::from_html(b"#ff028d")), + "black" => Some(ColorU8::from_html(b"#000000")), + "lilac" => Some(ColorU8::from_html(b"#cea2fd")), + "navy blue" => Some(ColorU8::from_html(b"#001146")), + "royal blue" => Some(ColorU8::from_html(b"#0504aa")), + "beige" => Some(ColorU8::from_html(b"#e6daa6")), + "salmon" => Some(ColorU8::from_html(b"#ff796c")), + "olive" => Some(ColorU8::from_html(b"#6e750e")), + "maroon" => Some(ColorU8::from_html(b"#650021")), + "bright green" => Some(ColorU8::from_html(b"#01ff07")), + "dark purple" => Some(ColorU8::from_html(b"#35063e")), + "mauve" => Some(ColorU8::from_html(b"#ae7181")), + "forest green" => Some(ColorU8::from_html(b"#06470c")), + "aqua" => Some(ColorU8::from_html(b"#13eac9")), + "cyan" => Some(ColorU8::from_html(b"#00ffff")), + "tan" => Some(ColorU8::from_html(b"#d1b26f")), + "dark blue" => Some(ColorU8::from_html(b"#00035b")), + "lavender" => Some(ColorU8::from_html(b"#c79fef")), + "turquoise" => Some(ColorU8::from_html(b"#06c2ac")), + "dark green" => Some(ColorU8::from_html(b"#033500")), + "violet" => Some(ColorU8::from_html(b"#9a0eea")), + "light purple" => Some(ColorU8::from_html(b"#bf77f6")), + "lime green" => Some(ColorU8::from_html(b"#89fe05")), + "grey" => Some(ColorU8::from_html(b"#929591")), + "sky blue" => Some(ColorU8::from_html(b"#75bbfd")), + "yellow" => Some(ColorU8::from_html(b"#ffff14")), + "magenta" => Some(ColorU8::from_html(b"#c20078")), + "light green" => Some(ColorU8::from_html(b"#96f97b")), + "orange" => Some(ColorU8::from_html(b"#f97306")), + "teal" => Some(ColorU8::from_html(b"#029386")), + "light blue" => Some(ColorU8::from_html(b"#95d0fc")), + "red" => Some(ColorU8::from_html(b"#e50000")), + "brown" => Some(ColorU8::from_html(b"#653700")), + "pink" => Some(ColorU8::from_html(b"#ff81c0")), + "blue" => Some(ColorU8::from_html(b"#0343df")), + "green" => Some(ColorU8::from_html(b"#15b01a")), + "purple" => Some(ColorU8::from_html(b"#7e1e9c")), + _ => None, + } +} From 88a5cb836e134faaa2fbb1edee28ffbd5c33f79a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Thebault?= Date: Sun, 3 May 2026 15:50:54 +0200 Subject: [PATCH 03/13] LinePattern::DashDot --- src/style.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/style.rs b/src/style.rs index 90b7ca9..909e9c4 100644 --- a/src/style.rs +++ b/src/style.rs @@ -167,8 +167,10 @@ pub enum LinePattern { Solid, /// Dashed line. The pattern is relative to the line width. Dash(Dash), - /// Dotted line. Equivalent to Dash(1.0, 1.0) + /// Dotted line. Equivalent to Dash(vec![1.0, 1.0]) Dot, + /// Dash-dot line. Equivalent to Dash(vec![5.0, 5.0, 1.0, 5.0]) + DashDot, } impl Default for LinePattern { @@ -200,6 +202,7 @@ pub struct Stroke { } const DOT_DASH: &[f32] = &[1.0, 1.0]; +const DASH_DOT_DASH: &[f32] = &[5.0, 5.0, 1.0, 5.0]; impl Stroke { /// Set the line width in figure units, returning self for chaining @@ -235,6 +238,7 @@ impl Stroke { LinePattern::Solid => render::LinePattern::Solid, LinePattern::Dash(Dash(a)) => render::LinePattern::Dash(a.as_slice()), LinePattern::Dot => render::LinePattern::Dash(DOT_DASH), + LinePattern::DashDot => render::LinePattern::Dash(DASH_DOT_DASH), }; render::Stroke { From 6f654c55f1452cda02576f93dacabd730a70a94f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Thebault?= Date: Sun, 3 May 2026 13:56:21 +0200 Subject: [PATCH 04/13] MPL style for line series --- src/lib.rs | 36 +----- src/utils.rs | 212 ++++++++++++++++++++++++++++++++++ tests/Cargo.toml | 2 +- tests/refs/style/dash-fat.png | Bin 0 -> 14003 bytes tests/refs/style/dash-fat.svg | 10 ++ tests/refs/style/dash.png | Bin 0 -> 13551 bytes tests/refs/style/dash.svg | 10 ++ tests/refs/style/dash_dot.png | Bin 0 -> 12987 bytes tests/refs/style/dash_dot.svg | 10 ++ tests/refs/style/dot.png | Bin 0 -> 15066 bytes tests/refs/style/dot.svg | 10 ++ tests/src/tests.rs | 1 + tests/src/tests/style.rs | 54 +++++++++ 13 files changed, 309 insertions(+), 36 deletions(-) create mode 100644 src/utils.rs create mode 100644 tests/refs/style/dash-fat.png create mode 100644 tests/refs/style/dash-fat.svg create mode 100644 tests/refs/style/dash.png create mode 100644 tests/refs/style/dash.svg create mode 100644 tests/refs/style/dash_dot.png create mode 100644 tests/refs/style/dash_dot.svg create mode 100644 tests/refs/style/dot.png create mode 100644 tests/refs/style/dot.svg create mode 100644 tests/src/tests/style.rs diff --git a/src/lib.rs b/src/lib.rs index 1524fff..419cbfd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -192,41 +192,7 @@ pub use text::bundled_font_db; pub use text::fontdb; #[cfg(feature = "utils")] -pub mod utils { - //! Utility functions for data generation - - #[cfg(feature = "time")] - use crate::time::DateTime; - - /// Create a linearly spaced vector of `num` elements between `start` and `end` - pub fn linspace(start: f64, end: f64, num: usize) -> Vec { - let step = (end - start) / (num as f64 - 1.0); - (0..num).map(|i| start + i as f64 * step).collect() - } - - /// Create a log-spaced vector of `num` elements between `start` and `end` - pub fn logspace(start: f64, end: f64, num: usize) -> Vec { - let log_start = start.log10(); - let log_end = end.log10(); - let step = (log_end - log_start) / (num as f64 - 1.0); - (0..num) - .map(|i| 10f64.powf(log_start + i as f64 * step)) - .collect() - } - - #[cfg(feature = "time")] - /// Create a linearly spaced time vector of `num` elements between `start` and `end` - pub fn timespace(start: DateTime, end: DateTime, num: usize) -> Vec { - let step = (end - start) / (num as f64 - 1.0); - let mut result = Vec::with_capacity(num); - let mut cur = start; - for _ in 0..num { - result.push(cur); - cur += step; - } - result - } -} +pub mod utils; /// Module containing missing configuration values /// Basically we put here all magic values that would require proper parameters diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..fb4aeb5 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,212 @@ +//! Utility traits and functions + +use std::fmt; + +use plotive_base::{ColorU8, color}; + +use crate::des::series; +use crate::style; +#[cfg(feature = "time")] +use crate::time::DateTime; + +/// Create a linearly spaced vector of `num` elements between `start` and `end` +pub fn linspace(start: f64, end: f64, num: usize) -> Vec { + let step = (end - start) / (num as f64 - 1.0); + (0..num).map(|i| start + i as f64 * step).collect() +} + +/// Create a log-spaced vector of `num` elements between `start` and `end` +pub fn logspace(start: f64, end: f64, num: usize) -> Vec { + let log_start = start.log10(); + let log_end = end.log10(); + let step = (log_end - log_start) / (num as f64 - 1.0); + (0..num) + .map(|i| 10f64.powf(log_start + i as f64 * step)) + .collect() +} + +#[cfg(feature = "time")] +/// Create a linearly spaced time vector of `num` elements between `start` and `end` +pub fn timespace(start: DateTime, end: DateTime, num: usize) -> Vec { + let step = (end - start) / (num as f64 - 1.0); + let mut result = Vec::with_capacity(num); + let mut cur = start; + for _ in 0..num { + result.push(cur); + cur += step; + } + result +} + +/// Error type for Matplotlib style parsing +#[derive(Debug)] +pub struct MplStyleError(String); + +impl From for MplStyleError { + fn from(s: String) -> Self { + MplStyleError(s) + } +} + +impl From<&str> for MplStyleError { + fn from(s: &str) -> Self { + MplStyleError(s.to_string()) + } +} + +impl fmt::Display for MplStyleError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Matplotlib style parsing error: {}", self.0) + } +} + +impl std::error::Error for MplStyleError {} + +/// Trait for applying Matplotlib styles to series +pub trait MplStyle { + /// Return a new instance of the series with the given Matplotlib style applied. + /// + /// E.g. for line series, see the format string section of + /// [matplotlib.pyplot.plot](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.plot.html) + fn with_mpl_style(self, mpl_style: &str) -> Result + where + Self: Sized; +} + +impl MplStyle for series::Line { + fn with_mpl_style(mut self, mpl_style: &str) -> Result { + if let Ok(c) = mpl_style.parse::() { + let mut s = self.stroke().clone(); + s.color = c.into(); + return Ok(self.with_line(s)); + } + + let style = match LineMplStyle::parse(mpl_style) { + Ok(s) => s, + Err(e) => { + return Err(MplStyleError(format!( + "Failed to parse Matplotlib style string '{}': {}", + mpl_style, e + ))); + } + }; + + if let Some(_) = style.marker_shape { + todo!("line marker not implemented yet"); + } + + if let Some(pattern) = style.pattern { + let mut s = self.stroke().clone(); + s.pattern = pattern; + self = self.with_line(s); + } + + if let Some(c) = style.color { + let mut s = self.stroke().clone(); + s.color = c; + self = self.with_line(s); + } + + Ok(self) + } +} + +struct LineMplStyle { + marker_shape: Option, + pattern: Option, + color: Option, +} +impl LineMplStyle { + fn parse(mpl_style: &str) -> Result { + let mut style = LineMplStyle { + marker_shape: None, + pattern: None, + color: None, + }; + + let mut chars = mpl_style.chars().enumerate().peekable(); + + while let Some((i, c)) = chars.next() { + match c { + 'o' => style.set_marker_shape(style::MarkerShape::Circle)?, + 's' => style.set_marker_shape(style::MarkerShape::Square)?, + 'D' => style.set_marker_shape(style::MarkerShape::Diamond)?, + 'x' => style.set_marker_shape(style::MarkerShape::Cross)?, + '+' => style.set_marker_shape(style::MarkerShape::Plus)?, + 'v' => style.set_marker_shape(style::MarkerShape::TriangleDown)?, + '^' => style.set_marker_shape(style::MarkerShape::TriangleUp)?, + '-' => { + if let Some((_, next)) = chars.peek() { + match next { + '-' => { + chars.next(); + style.set_pattern(style::Dash::default().into())?; + } + '.' => { + chars.next(); + style.set_pattern(style::LinePattern::DashDot)?; + } + _ => style.set_pattern(style::LinePattern::Solid)?, + } + } else { + style.set_pattern(style::LinePattern::Solid)?; + } + } + ':' => style.set_pattern(style::LinePattern::Dot)?, + 'b' => style.set_color(ColorU8::from_html(b"#0000ff").into())?, + 'g' => style.set_color(ColorU8::from_html(b"#008000").into())?, + 'r' => style.set_color(ColorU8::from_html(b"#ff0000").into())?, + 'c' => style.set_color(ColorU8::from_html(b"#00bfbf").into())?, + 'm' => style.set_color(ColorU8::from_html(b"#bf00bf").into())?, + 'y' => style.set_color(ColorU8::from_html(b"#bfbf00").into())?, + 'k' => style.set_color(ColorU8::from_html(b"#000000").into())?, + 'w' => style.set_color(ColorU8::from_html(b"#ffffff").into())?, + 'C' => { + let s: usize = mpl_style[i + 1..].parse().map_err(|_| { + MplStyleError(format!( + "Invalid color index after 'C' in Matplotlib style string: '{}'", + mpl_style + )) + })?; + style.set_color(style::series::IndexColor(s).into())?; + break; + } + _ => { + return Err(MplStyleError(format!( + "Unknown Matplotlib style character '{}'", + c + ))); + } + } + } + + Ok(style) + } + + fn set_marker_shape(&mut self, shape: style::MarkerShape) -> Result<(), MplStyleError> { + if self.marker_shape.is_some() { + Err(MplStyleError("Multiple marker shapes".to_string())) + } else { + self.marker_shape = Some(shape); + Ok(()) + } + } + + fn set_pattern(&mut self, pattern: style::LinePattern) -> Result<(), MplStyleError> { + if self.pattern.is_some() { + Err(MplStyleError("Multiple line patterns".to_string())) + } else { + self.pattern = Some(pattern); + Ok(()) + } + } + + fn set_color(&mut self, color: style::series::Color) -> Result<(), MplStyleError> { + if self.color.is_some() { + Err(MplStyleError("Multiple colors".to_string())) + } else { + self.color = Some(color); + Ok(()) + } + } +} diff --git a/tests/Cargo.toml b/tests/Cargo.toml index e2475c5..06af53b 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -10,7 +10,7 @@ keywords.workspace = true publish = false [dependencies] -plotive = { workspace = true, features=["time"] } +plotive = { workspace = true, features=["time", "utils"] } plotive-pxl.workspace = true plotive-svg.workspace = true tiny-skia.workspace = true diff --git a/tests/refs/style/dash-fat.png b/tests/refs/style/dash-fat.png new file mode 100644 index 0000000000000000000000000000000000000000..beb450a38a6c1f45b3a3a25d170cf7e974908fc2 GIT binary patch literal 14003 zcmeHOe{fXQ6^=!Xb;S6GXseL5KO9>{)2c+%K(@4YQi>zhcHKcpR*Rb25TcDCBqVu` z>68{Up<^`;kzJ+?MzSWWB3Us%ULl2`vOEF98bh)YN*1ytn`HB{*|+=N?LFshl1)hN zWv2hMzzmRKvwPpY=brPO?|kR%Uw{2b`s{0Oxi&5?ZuXL2KJ-{z+!dSY-}%>EO<(DA zp57N1mt3^up~b&lcSYYnXKnlb@;43)l7jwM4<4%i%I4V zw?DMj`G=ynH>75_Evt8#%o+J@CbR0ht)r*LUiWctja|#%oRddigI|!J9IW3%UxQx= z^PcWl32&yq5U>9r2j5KpvBbA0>#JnQju*b3ape8h!6~`Sxxc^ae-0(jd8zFALGe0gj@c83z04O|JfgQT4c95QLIb#{FkZj{0U9irN?&>6qz z%;Mr;f$)Gs{mS7IUgsV`732k$zyd_4hK7cPPG^zb+1((98*mS=U%(z*{r&gk29k4I z@D@&#&r3Z}AOs5n$8*)=kpezIkUNa(F{2Ak@m@p2XL;-G&Z4`T!wq89sn&{s=QJ0q zh~Zp+{}5b%WOy05U+Vkv{Z)7%gL=f^5}bS^Sb|`_MpsAUY%~J;9~hxc`{SkVc9}H;a+!s+a;f? zgc=G{apeZ%VRAFyF>GhhF>Jv?LsxHY*G7ld>*yhy_&&LeGJ-$~`f)H!vGDH9@NP>L zN#I_QY@C8h3BzB<8{ZTayVyNCcq(EEav_y`6P~gGX-p{ePKELVWF`=gLMNACXsPIu zQn)dJMwRHwTmN}k;=d3RzM{Ak5%|o4+(SXh(gGA|*ZG)0KH=}d-giDG05DQe%5{-+ z{>3?lkJ0F$pwyT!4KP|TXXvbI22LaHqrgZ($=|Y!3@-~HFv5WXM<^~ugbTQZ)o4Bd z#^-&t&Y=^OYi4djbl96v`XlpRz7=)>GNK>JW?_LOnM>NtgYf*7GCwma(vX|xvc^N7 zHC`w6?V#8++V9uaAXM0`$M{dbuLPcI2>jU4qEcK++obbiub+=YXQ_>0U4(l#?D!j;uK1SI};n~X4=js#(!VZ(Oct^&ODM>w=+CcV&v=iyf&ek z_h2G20coXd;TA;1!T=h?36KYTZS5BU6hvj_#!^a08oUP%OoA5S+#p2$(FgsdzYDxZkYu z3r_Mj0D_yctdqxA8oVz5c5<9ea91T+9J0Q_vNFnDV&faG6hT)j1X5k-{{ zx8|mQ_)jHEZl&S1~I)sh9584?4oj)4lBgKb*3x~IiyDMMF5|a+i4r^8sUbb z#|I8v(&a4~?el0k){e5o;oC~XPN7PVB+|mmT_lYONEfhsA26hu0g3Qb8^s|r0f}&{ zU-1AFkVpsD2*pf5%5UQ?uy95^%E%YF5uJip7Ni+%dO$Miq9QAs8oQu`|9Rrj4OW|^ zyenb-PZd8eCE#N!;q;ZGP>!CS-Q1J?kq7-psTQ_WmY%Jo(qm?<4mYiOK~mNh6kyV~ z4!)8#@Tft4bOM*`nQw9rUkHc4^(A*{pBo-{BQIR2_$ZX~RyAkH#*0?MT;!nS6beTw zq?>tMR$h&ugyIg=Z#HG8t(&Uuc5da-4W5G{Kz}g9BWE-Q*NYEO!&*2AP9~j`<)qI+ z#F{`v6ESfxmDoyL?&8_V=xAt5eYLda#iC*C)G>B4Y2 z)$7Sto)HYl@K6#%BVdmJDknX$m`EpYg-U?tY0Eas26%x+>^xK=uW_(lOG5DoGn>~t z!I5YL|AS|~1=M^CNB1W59Nn>WAuarAUW>scjVDW~7QOBUEiM?2l^($MuW_7Ta}@cU zrP)|r;H5`9He@!>GfYD1KpDbH6chwb$*K6)g8YF3FVs%K^>D|fz15`~?R5&_Xr!{|ETl-pL&$h* zL<%MQv&E5Ya3~uu5>lCEn_1;<-Z8>rNf|OZ!XYW*r!1C4?<$LvdC9dLmP878D?H4g zNo{j@pPrA;hfEgLu_me9(B8E2VBL0dJgH6QNp`E7V?X0j$ZD8VfbZ)~66_S9dO@3% za)bvf+b1;oStVbjm)wXMfu={;eCB%`OcXwZx)nnOK=I7t1njUB_PI;3J1(^Ij?jYY zDL4{#cd&M}*A*S%crWyO`L)x9#5?qRFIx82k6P@?#&v%~b)JqfD1M$sEgxTjLY7W~ zCrt-vKDIj%55=SAKZ6+hY(YU~coAevc=Cg^?FZd=p_b`TGC4*OVM@yln{P*{N5dch z+09OO#E9GmBJ%u}8_XoAZZI)-EjI`$54(O*2@<@ST5h-kTaQvb^^__?;0&}; zF=?TZDoK|8QIba0BzEIi^D9tz(*nF}0|cjc%qN&z?9mJN?AP94Z+9=f=6(2 za|XR9!13SpD9+>j%M^m2A&YF~LJ_^7e4q{tSsW1xoBo$aWTHbl)3lRF%9tskm zB`it~DM#agfjfNGragzjNS)f+v7)JM1(tm*w~0@$)Z4tI!v{oR2QykEuVJ^ z1}Q>kDzx9DkBMwV6khdceV(42uAZDPsY1M`>3DY2O(-oxcN|yna8{_JTZgx7z$6k1 z)i{(p)0FMTEpUK4Ut%A?<{fVn!&O^5mQrzBuYAV zmd(r_vr2{W6($~;B#FsVlnSKr=rNL%PFd0*xYF1S*4)ek1f`fj}{lC)m5hA<2uDN@1k1M;_`&D?Dhke0)eLPM|&I z{bNCC*Ys}EOpSPAr$LFhf~}fT9E*a-z5w&7GJCj)7*v=i!l*zYr=i)`u+ASW&4p9i zwNxhiCa+C;^qzUNHG%za7!1<=${#c2dH#GDNkSdRSy<(KilUo$?j2I0};- z)|r9&1NFQ-j?9FxfTs^7CGblM3C)`@#}Yrwv;ogE^hP0=PeP`@VsxiX`C%6wDo1)y zLnQWxPa>~@$q5?LdpuKwc?xBO+3V8oVvR%Tmu_Y{q+^IRBB`&y6rw^Bt6s~?{=&l zbqy4VgXCfHEA4xbHnA^(p*>}&E;|0dk6F1H_d(vocHen@5qg7i5|dyGf5IFJ6!|01 z`>R^9uQTDn_DnV&Rt(`l@F6L3U9S9i?tW0T@^rl&1Q$0G&xF6zWQvm}dUqcKPN-tp zJ8BV55s!Xz$xne@>fH=*r~itQV@O7O3VH_BU!i3rRzZ48WSc(!sAr2ptj@?!|KGnj og+!Ew(TrdF+8|u{)UB&NvOW8qGWun-xFx@M + + + + + + + + + \ No newline at end of file diff --git a/tests/refs/style/dash.png b/tests/refs/style/dash.png new file mode 100644 index 0000000000000000000000000000000000000000..c515845ed2b5a2b24cd834fcf269ba654f2197e8 GIT binary patch literal 13551 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 cD>-}hM_=uD + + + + + + + + + \ No newline at end of file diff --git a/tests/refs/style/dash_dot.png b/tests/refs/style/dash_dot.png new file mode 100644 index 0000000000000000000000000000000000000000..42d4fb153b6f3e45ba2b0e7d31449b016554779e GIT binary patch literal 12987 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 + + + + + + + + + \ No newline at end of file diff --git a/tests/refs/style/dot.png b/tests/refs/style/dot.png new file mode 100644 index 0000000000000000000000000000000000000000..f21abf7faf2096ff08edf8dfc3e879c332226e26 GIT binary patch literal 15066 zcmeHOZE#fAd0w}*@eqUBi4F0m+%&D*I<|_C0mHUsTpDfACRSkqNn8_3g$rVb53yhcEeInS327x(7isVA-qZJe z?%mZo!T0P;|M-Vzj6D`Qd(L@3p7(j5_w0}U=Gi4Prr$N)<#Nq<=9^DF?{eL=k^X%B z)?4T&!@ClvyXnpf1%#{a?djUW%>(k=KGcSW%`$A+PV4Gr+D%o|?6$9|z8#5gi(K689@;&5HDsURyYhz_B|Kn=<(J#8n)$8T9{lq=AwQp@@ zVWL&pD(*kBC!TkWPjj}vRY|mFb_~Ve4IP~COP%v|slWenPjVXlfBc@6v2*mJ^uar` zPN3ZSex8ra`ka_4^4*%6O}_X~eHS}=hdL&kvf}MCy3^}!kta| z1Ikf(lGPpTCB)dGnL}ECKp26?y~)SKxg4;d`lIvbFRv)(@VIBXm#fQ6=_h!1`5ZHcez~G^@{=a> z71IcaEt|YoHua13@|#xuvaRjt*rm?e;jp*TZM^5%!cg2Vw&bvMlx^A`O0?$@3~GmQ zTeVw?5at7I`kB&m1WO0=ynjJJnBP^R>Ltj!fFX~imnj4by6DXD;fPsjDmhqaE`=ZJ z3gLtA@UCF*|MHbb*XAD+)8q-~>jydw1-%tsW2d?$>>UpGi=W6XR5Vr?jTLg#+R^(u zBa4(dmiIG5G|L*nF@K z5;b3QCw6(P`j)27po9b@D!4^S$su)-|$(2CXOkDKQhm6 zu8`A#r^n6{>H}s7&eWSfAdJ~JsPsys(Y=|&=u&4>_0X>1HSWF|CTi3LxNZ)uT-{<~ zYPCoH^1fe{c668G5X*F5R2q(4y`JNZw#*8Eb6668G3^wm1#Zd6VsPCW06RYed@C|V{^&NNr zchF5CHT`B()|BWqblYHNxI3SJR7_bXBw;3fZYmzQ|lwC3>KQ;$_ z0=O(MXZB3dSRjqY1oaueGs=A!_D5MQ7K+qPQN5cQJ^(+Q}klr^CLpf%Wg^yx# zNrL4*>B7*&!a!_c`V&%XQEK}PabG%WZ7 z>4rm%p^1jPsP2v)hKj+_Y!O1gTyQkPaW)IdHN9^|jh1MiRpFY&H)WP}&-KLSc}Rz6 zx)3pkBmK7GFBco#d+nOf;stmiH`}DjtMAByu;6T!xXxuYHa~f~D0RD4`%6;gQb=qk zA_*l6LI8>@>J>N2Q`MPxjxskn!^n3u8B5u)|(_ zOJ?7ey#CnWJ}D(I$|t(P=*myB@&kXNO$p(p~fAc&x^a*2feXP^89*`<{@t3%Mb**W&I zQ?g}&9_ofNS|ta@T|cnbepz2P?Km5?i8I~eu^16>$4l4L zPDrk)w%U1)voZ<6`7ILCEOyB@N00qTLF^H*iBH!nbAcq|Q&r=T@JPLzhox{6=pu#W z%`$^o^F@ZEDZ!G8hxlg+LWx;0*4q2V??(jt%qu5bdDS|I}&%W32(N@Rif zK!OpRh9HG+VemRRb_oog$gM=#)s+(#gLi>#v1Hz5GYPR@cF(84domT7@K9t8Yi~3v z)s@4v9n6D?F~<_0)M~B$rXtY}Q$=I&Xjf1B42ZIrXOM~VWhQECJl5H8+a751R0crr z%5))fEv%`70tu=^hNj zO0=Ie$=2VLd88Fssm2zlmPScum5PPp1bSlt8282ds6Ybu$+XS0yCh(woCK+3IKe~= zn80TZ(10QReBef9+-Mr*NCgmL5zq_`J-_CtlUPY@vO$pFFkn)HPqK|{1Z6>AdY&3i zOl7H4@L($2YK=7rQT*nx3{qyH4Fve{nUU!>Ybw*D5}ncr*YOz4+h6W|3#^@$9)9$o z(kR36fyIVFR5uorU8wGG8@+llnsjG-*FDEE#Vj^M(r{CmvgF>3{*5*4Nz!>9$|O3S zcDv-XF*50wK0ttE%2e7zA=!CfrQ)Sk}^30lD;kt)_PFbQm#ZlfJWkoJ< z(koDs?AJ%X{q~2XLKa^5Oq0z4~NUR&VmeGzgPe1s%RZ8+gfh0SgwlKGZ5P$@< zJ4nAd;S6nJ0R(PW#wVKdY>PBS2*;b7;$NXsbznVfl`f0w>>rc}-MV%vJsE|4_*IO} zJIFlF^Mq6sa{QB*?hIXE8E-G+Zk6P6^`sXzhdQXH%raenteKoP2G4A-nmi;qk`M2jN^>dGmb>>$0^t%zVOb=C_hnb@{R+m9U` zWjV`kgNSA4JJ&|_ZHjKB7WfXcEC^}nQ?RkK;;1W~c>cg3`ef=#(Lg#mg#^IdgxL5M zs3uH#Gwl(!-RW$qNCi-7fsF3I6ocV1&G}+5$~7eNevJ* z{iuK;#qV;46m=V!nKh*)ji;C342)nUi-OL#^Z^1(L)s-XDT2gOsVedpdU(-Z|u`^G;RSgY#GLM)^Bb22e|p~Vs}&$*B1e%zm8K#G^8;zj2Kii~6s5z`yHSXgob z?HQEpIiK=?E!)l(0uI~l9|4@Cl54%lcfP34_?fSduDE|taehKqcOrExl)Md~$N>y9 zrFmfvtDzqJ#!QVPIaTiLn1WyPE35BcJv#?gDqE~1BZZvN*u9rec*_NJ9Pp&%{l@n2 zltjtuG55(<$H}Db4(D8LB~pm#CL0!-A4iZiRO3`P{m#%sO^Xa>6yCq*pAMzqqjhM`x`t zQ<{s#-Hw4oZ{+x0KG2^$^}Ba$&M)9J(>Tro`J6D(|)k zO!zA&M<@zjY264sj!&U4`?Tbpg4YRfTtS^s=t$_(U-t*kcJowfs~j4X+etg!{v@w4Ozg?Ob6+n$u#=rm7`8BP5|)BssBq)gEJ}ach7a) zw8`3(MSW~a7f$PrOa>euD8`#c@xJi4OBJ2ixmN8!ngCGJTyXzjq~9sez&A@$Uwo74 zi!XdBpNsFsUCfACsNpU2O+5UBHOs8;{*9-lM~HgR*!i1?RycnizdVU + + + + + + + + + \ No newline at end of file diff --git a/tests/src/tests.rs b/tests/src/tests.rs index 80e9ae7..d32e1b8 100644 --- a/tests/src/tests.rs +++ b/tests/src/tests.rs @@ -47,6 +47,7 @@ mod interp; mod legend; mod nulls; mod series; +mod style; mod subplots; #[test] diff --git a/tests/src/tests/style.rs b/tests/src/tests/style.rs new file mode 100644 index 0000000..d2a9fad --- /dev/null +++ b/tests/src/tests/style.rs @@ -0,0 +1,54 @@ +use plotive::des; +use plotive::utils::MplStyle; + +use super::{fig_small, line}; +use crate::{TestHarness, assert_fig_eq_ref}; + +#[test] +fn style_default() { + let series = line().into(); + let plot = des::Plot::new(vec![series]); + let fig = fig_small(plot); + + assert_fig_eq_ref!(&fig, "axes/default"); +} + +#[test] +fn style_dash() { + let series = line().with_mpl_style("--").unwrap().into(); + let plot = des::Plot::new(vec![series]); + let fig = fig_small(plot); + + assert_fig_eq_ref!(&fig, "style/dash"); +} + +#[test] +fn style_dash_dot() { + let series = line().with_mpl_style("-.").unwrap().into(); + let plot = des::Plot::new(vec![series]); + let fig = fig_small(plot); + + assert_fig_eq_ref!(&fig, "style/dash_dot"); +} + +#[test] +fn style_dot() { + let series = line().with_mpl_style(":").unwrap().into(); + let plot = des::Plot::new(vec![series]); + let fig = fig_small(plot); + + assert_fig_eq_ref!(&fig, "style/dot"); +} + +#[test] +fn style_dash_scales_with_width() { + let series = line() + .with_line(plotive::style::series::Stroke::default().with_width(4.0)) + .with_mpl_style("--") + .unwrap() + .into(); + let plot = des::Plot::new(vec![series]); + let fig = fig_small(plot); + + assert_fig_eq_ref!(&fig, "style/dash-fat"); +} From 33e52534d9b33dcc835886992f7286b749df047b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Thebault?= Date: Sun, 3 May 2026 18:05:45 +0200 Subject: [PATCH 05/13] series stroke and outline terminology --- examples/gauss.rs | 2 +- examples/polars_sine.rs | 2 +- examples/sine.rs | 2 +- iced/examples/scope.rs | 2 +- src/des/series.rs | 52 ++++++++++++++++++++-------------------- src/drawing/series.rs | 12 +++++----- src/utils.rs | 6 ++--- tests/src/tests/axes.rs | 2 +- tests/src/tests/style.rs | 2 +- 9 files changed, 41 insertions(+), 41 deletions(-) diff --git a/examples/gauss.rs b/examples/gauss.rs index da5c76e..1d40331 100644 --- a/examples/gauss.rs +++ b/examples/gauss.rs @@ -51,7 +51,7 @@ fn main() { let dist_series = des::Series::Line( des::series::Line::new(x.into(), y.into()) .with_name("distribution") - .with_line(style::series::Stroke { + .with_stroke(style::series::Stroke { width: 4.0, ..style::Stroke::default() }), diff --git a/examples/polars_sine.rs b/examples/polars_sine.rs index 1ef5d70..9ad1a94 100644 --- a/examples/polars_sine.rs +++ b/examples/polars_sine.rs @@ -27,7 +27,7 @@ fn main() { let series = des::Series::Line( des::series::Line::new(des::data_src_ref("x"), des::data_src_ref("y")) .with_name("y=sin(x)") - .with_line(style::series::Stroke::default().with_width(4.0)), + .with_stroke(style::series::Stroke::default().with_width(4.0)), ); let plot = des::Plot::new(vec![series]) diff --git a/examples/sine.rs b/examples/sine.rs index 64a58f2..e3fefa7 100644 --- a/examples/sine.rs +++ b/examples/sine.rs @@ -7,7 +7,7 @@ mod common; fn main() { let fig = des::series::Line::new(des::data_src_ref("x"), des::data_src_ref("y")) .with_name("y=sin(x)") - .with_line(style::series::Stroke::default().with_width(4.0)) + .with_stroke(style::series::Stroke::default().with_width(4.0)) .into_plot() .with_x_axis( des::Axis::new() diff --git a/iced/examples/scope.rs b/iced/examples/scope.rs index 8886a96..63c53d0 100644 --- a/iced/examples/scope.rs +++ b/iced/examples/scope.rs @@ -134,7 +134,7 @@ fn build_figure() -> des::Figure { des::DataCol::SrcRef("y".to_string()), ) .with_name("y=sin(x)".to_string()) - .with_line(style::series::Stroke::default().with_width(2.0)), + .with_stroke(style::series::Stroke::default().with_width(2.0)), ); let plot = des::Plot::new(vec![series]) diff --git a/src/des/series.rs b/src/des/series.rs index 17cfeb6..84d695d 100644 --- a/src/des/series.rs +++ b/src/des/series.rs @@ -225,8 +225,8 @@ impl Line { } /// Set the line style and return self for chaining - pub fn with_line(mut self, line: style::series::Stroke) -> Self { - self.stroke = line; + pub fn with_stroke(mut self, stroke: style::series::Stroke) -> Self { + self.stroke = stroke; self } @@ -585,7 +585,7 @@ pub struct Histogram { x_axis: axis::Ref, y_axis: axis::Ref, fill: style::series::Fill, - line: Option, + stroke: Option, bins: u32, density: bool, } @@ -600,7 +600,7 @@ impl Histogram { x_axis: Default::default(), y_axis: Default::default(), fill: style::series::Fill::default(), - line: None, + stroke: None, bins: 10, density: false, } @@ -631,9 +631,9 @@ impl Histogram { Self { fill, ..self } } - /// Set the line style for the histogram outline and return self for chaining - pub fn with_line(mut self, line: style::series::Stroke) -> Self { - self.line = Some(line); + /// Set the stroke style for the histogram outline and return self for chaining + pub fn with_outline(mut self, stroke: style::series::Stroke) -> Self { + self.stroke = Some(stroke); self } @@ -674,9 +674,9 @@ impl Histogram { &self.fill } - /// Get the line style, if any - pub fn line(&self) -> Option<&style::series::Stroke> { - self.line.as_ref() + /// Get the outline style, if any + pub fn outline(&self) -> Option<&style::series::Stroke> { + self.stroke.as_ref() } /// Get the number of bins @@ -726,7 +726,7 @@ pub struct Bars { x_axis: axis::Ref, y_axis: axis::Ref, fill: style::series::Fill, - line: Option, + stroke: Option, position: BarsPosition, } @@ -741,7 +741,7 @@ impl Bars { x_axis: Default::default(), y_axis: Default::default(), fill: style::series::Fill::default(), - line: None, + stroke: None, position: BarsPosition::default(), } } @@ -759,10 +759,10 @@ impl Bars { Self { fill, ..self } } - /// Set the line style for the bar outline and return self for chaining - pub fn with_line(self, line: style::series::Stroke) -> Self { + /// Set the stroke style for the bar outline and return self for chaining + pub fn with_outline(self, stroke: style::series::Stroke) -> Self { Self { - line: Some(line), + stroke: Some(stroke), ..self } } @@ -802,9 +802,9 @@ impl Bars { &self.fill } - /// Get the line style, if any - pub fn line(&self) -> Option<&style::series::Stroke> { - self.line.as_ref() + /// Get the outline style, if any + pub fn outline(&self) -> Option<&style::series::Stroke> { + self.stroke.as_ref() } /// Get the position configuration @@ -823,7 +823,7 @@ pub struct BarSeries { name: Option, fill: style::series::Fill, - line: Option, + stroke: Option, } impl BarSeries { @@ -834,7 +834,7 @@ impl BarSeries { name: None, fill: style::series::Fill::default(), - line: None, + stroke: None, } } @@ -851,10 +851,10 @@ impl BarSeries { Self { fill, ..self } } - /// Set the line style for the bar outline and return self for chaining - pub fn with_line(self, line: style::series::Stroke) -> Self { + /// Set the stroke style for the bar outline and return self for chaining + pub fn with_outline(self, stroke: style::series::Stroke) -> Self { Self { - line: Some(line), + stroke: Some(stroke), ..self } } @@ -874,9 +874,9 @@ impl BarSeries { &self.fill } - /// Get the line style, if any - pub fn line(&self) -> Option<&style::series::Stroke> { - self.line.as_ref() + /// Get the outline style, if any + pub fn outline(&self) -> Option<&style::series::Stroke> { + self.stroke.as_ref() } } diff --git a/src/drawing/series.rs b/src/drawing/series.rs index 8b25f58..012872b 100644 --- a/src/drawing/series.rs +++ b/src/drawing/series.rs @@ -54,7 +54,7 @@ impl SeriesExt for des::series::Histogram { self.name().map(|n| legend::Entry { label: n.as_ref(), font: None, - shape: legend::ShapeRef::Rect(Some(self.fill()), self.line()), + shape: legend::ShapeRef::Rect(Some(self.fill()), self.outline()), }) } } @@ -64,7 +64,7 @@ impl SeriesExt for des::series::Bars { self.name().map(|n| legend::Entry { label: n.as_ref(), font: None, - shape: legend::ShapeRef::Rect(Some(self.fill()), self.line()), + shape: legend::ShapeRef::Rect(Some(self.fill()), self.outline()), }) } } @@ -74,7 +74,7 @@ impl SeriesExt for des::series::BarSeries { self.name().map(|n| legend::Entry { label: n.as_ref(), font: None, - shape: legend::ShapeRef::Rect(Some(self.fill()), self.line()), + shape: legend::ShapeRef::Rect(Some(self.fill()), self.outline()), }) } } @@ -1018,7 +1018,7 @@ impl Histogram { bins, path: None, fill: hist.fill().clone(), - line: hist.line().cloned(), + line: hist.outline().cloned(), updated_once: false, }) } @@ -1168,7 +1168,7 @@ impl Bars { position: des.position().clone(), path: None, fill: des.fill().clone(), - line: des.line().cloned(), + line: des.outline().cloned(), }) } @@ -1492,7 +1492,7 @@ impl BarsGroup { let rpath = render::Path { path, fill: Some(series.fill().as_paint(&rc)), - stroke: series.line().map(|l| l.as_stroke(&rc)), + stroke: series.outline().map(|l| l.as_stroke(&rc)), transform: None, }; surface.draw_path(&rpath); diff --git a/src/utils.rs b/src/utils.rs index fb4aeb5..0526dbe 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -78,7 +78,7 @@ impl MplStyle for series::Line { if let Ok(c) = mpl_style.parse::() { let mut s = self.stroke().clone(); s.color = c.into(); - return Ok(self.with_line(s)); + return Ok(self.with_stroke(s)); } let style = match LineMplStyle::parse(mpl_style) { @@ -98,13 +98,13 @@ impl MplStyle for series::Line { if let Some(pattern) = style.pattern { let mut s = self.stroke().clone(); s.pattern = pattern; - self = self.with_line(s); + self = self.with_stroke(s); } if let Some(c) = style.color { let mut s = self.stroke().clone(); s.color = c; - self = self.with_line(s); + self = self.with_stroke(s); } Ok(self) diff --git a/tests/src/tests/axes.rs b/tests/src/tests/axes.rs index df51671..76d1160 100644 --- a/tests/src/tests/axes.rs +++ b/tests/src/tests/axes.rs @@ -212,7 +212,7 @@ fn axes_categories() { let y = vec![1.0, 1.4, 3.0]; let series = des::series::Bars::new(x.into(), y.into()) .with_fill(color::TRANSPARENT.into()) - .with_line(Default::default()); + .with_outline(Default::default()); let plot = des::Plot::new(vec![series.into()]) .with_x_axis(des::Axis::new().with_ticks(Default::default())); diff --git a/tests/src/tests/style.rs b/tests/src/tests/style.rs index d2a9fad..0e891fd 100644 --- a/tests/src/tests/style.rs +++ b/tests/src/tests/style.rs @@ -43,7 +43,7 @@ fn style_dot() { #[test] fn style_dash_scales_with_width() { let series = line() - .with_line(plotive::style::series::Stroke::default().with_width(4.0)) + .with_stroke(plotive::style::series::Stroke::default().with_width(4.0)) .with_mpl_style("--") .unwrap() .into(); From ab3a489dcb405879700843537bf527cf2b8afb8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Thebault?= Date: Sun, 3 May 2026 18:21:34 +0200 Subject: [PATCH 06/13] fix arabic text in text_line example --- text/examples/text_line.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/text/examples/text_line.rs b/text/examples/text_line.rs index 3ba585f..6ee3a65 100644 --- a/text/examples/text_line.rs +++ b/text/examples/text_line.rs @@ -3,7 +3,8 @@ use plotive_base::geom; use plotive_text::{bundled_font_db, font, line}; fn main() { - let db = bundled_font_db(); + let mut db = bundled_font_db(); + db.load_system_fonts(); let font = font::Font::default().with_families(vec![ font::Family::Named("Noto Sans".to_string()), From 8b80556a08d462be296149cd52ff1a601a3f6596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Thebault?= Date: Sun, 3 May 2026 18:14:04 +0200 Subject: [PATCH 07/13] run_all_examples include area --- run_all_examples.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/run_all_examples.sh b/run_all_examples.sh index 8b43e6a..5aa1dde 100755 --- a/run_all_examples.sh +++ b/run_all_examples.sh @@ -23,6 +23,7 @@ done cargo run --example text_line --package plotive-text --features noto-sans cargo run --example text_rich --package plotive-text --features noto-sans,noto-serif +cargo run --example area -- ${POS_ARGS[@]} cargo run --example bars -- ${POS_ARGS[@]} cargo run --example gauss -- ${POS_ARGS[@]} cargo run --example sine -- ${POS_ARGS[@]} From c89aba080d32910f45d39b5f772b5298d7cd8b11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Thebault?= Date: Sun, 3 May 2026 21:31:20 +0200 Subject: [PATCH 08/13] support line markers --- src/des/series.rs | 13 +++ src/drawing/series.rs | 117 +++++++++++++++++-------- src/utils.rs | 6 +- tests/refs/style/dash_dot-spline.png | Bin 0 -> 14441 bytes tests/refs/style/dash_dot-spline.svg | 10 +++ tests/refs/style/line-plus-markers.png | Bin 0 -> 19987 bytes tests/refs/style/line-plus-markers.svg | 16 ++++ tests/src/tests/style.rs | 40 ++++++--- 8 files changed, 153 insertions(+), 49 deletions(-) create mode 100644 tests/refs/style/dash_dot-spline.png create mode 100644 tests/refs/style/dash_dot-spline.svg create mode 100644 tests/refs/style/line-plus-markers.png create mode 100644 tests/refs/style/line-plus-markers.svg diff --git a/src/des/series.rs b/src/des/series.rs index 84d695d..391f663 100644 --- a/src/des/series.rs +++ b/src/des/series.rs @@ -184,6 +184,7 @@ pub struct Line { x_axis: axis::Ref, y_axis: axis::Ref, stroke: style::series::Stroke, + marker: Option, interpolation: Interpolation, } @@ -198,6 +199,7 @@ impl Line { x_axis: Default::default(), y_axis: Default::default(), stroke: style::series::Stroke::default().with_width(defaults::SERIES_LINE_WIDTH), + marker: None, interpolation: Interpolation::default(), } } @@ -230,6 +232,12 @@ impl Line { self } + /// Set the marker style and return self for chaining + pub fn with_marker(mut self, marker: style::series::Marker) -> Self { + self.marker = Some(marker); + self + } + /// Set the interpolation method and return self for chaining pub fn with_interpolation(mut self, interpolation: Interpolation) -> Self { self.interpolation = interpolation; @@ -266,6 +274,11 @@ impl Line { &self.stroke } + /// Get the marker style, if any + pub fn marker(&self) -> Option<&style::series::Marker> { + self.marker.as_ref() + } + /// Chaining helper to build a plot from this series /// This can only be used if your plot contains a single series. /// This is equivalent to `Plot::new(vec![self.into()])` diff --git a/src/drawing/series.rs b/src/drawing/series.rs index 012872b..d645341 100644 --- a/src/drawing/series.rs +++ b/src/drawing/series.rs @@ -585,6 +585,50 @@ fn calc_xy_line_path( } } +#[derive(Debug, Clone)] +struct MarkerData { + path: geom::Path, + points: Vec, + marker: style::series::Marker, +} + +impl MarkerData { + fn new(marker: style::series::Marker) -> Self { + let path = marker::marker_path(&marker); + Self { + path, + points: Vec::new(), + marker, + } + } + + fn clear(&mut self) { + self.points.clear(); + } + + fn draw(&self, surface: &mut S, style: &Style, index: usize) + where + S: render::Surface, + { + if self.points.is_empty() { + return; + } + + let rc = (style, index); + + for p in &self.points { + let transform = geom::Transform::from_translate(p.x, p.y); + let path = render::Path { + path: &self.path, + fill: self.marker.fill.as_ref().map(|f| f.as_paint(&rc)), + stroke: self.marker.stroke.as_ref().map(|l| l.as_stroke(&rc)), + transform: Some(&transform), + }; + surface.draw_path(&path); + } + } +} + #[derive(Debug, Clone)] struct Line { index: usize, @@ -594,6 +638,7 @@ struct Line { path: Option, stroke: style::series::Stroke, interpolation: des::series::Interpolation, + marker_data: Option, } impl Line { @@ -603,6 +648,7 @@ impl Line { { let cols = (des.x_data().clone(), des.y_data().clone()); let xy_bounds = calc_xy_bounds(data_source, &cols.0, &cols.1)?; + let marker_data = des.marker().cloned().map(MarkerData::new); Ok(Line { index, cols, @@ -611,6 +657,7 @@ impl Line { path: None, stroke: des.stroke().clone(), interpolation: des.interpolation(), + marker_data, }) } @@ -639,25 +686,42 @@ impl Line { let path = calc_xy_line_path(x_col, y_col, self.interpolation, rect, cm); self.path = Some(path); + + if let Some(marker_data) = self.marker_data.as_mut() { + let mut points = Vec::with_capacity(x_col.len()); + + for (x, y) in x_col.sample_iter().zip(y_col.sample_iter()) { + if x.is_null() || y.is_null() { + continue; + } + let (x, y) = cm.map_coord((x, y)).expect("Should be valid coordinates"); + let x = rect.left() + x; + let y = rect.bottom() - y; + points.push(geom::Point { x, y }); + } + marker_data.points = points; + } } fn draw(&self, surface: &mut S, style: &Style) where S: render::Surface, { - if self.path.is_none() { - return; - } - let rc = (style, self.index); - let path = render::Path { - path: self.path.as_ref().unwrap(), - fill: None, - stroke: Some(self.stroke.as_stroke(&rc)), - transform: None, - }; - surface.draw_path(&path); + if let Some(path) = self.path.as_ref() { + let rpath = render::Path { + path, + fill: None, + stroke: Some(self.stroke.as_stroke(&rc)), + transform: None, + }; + surface.draw_path(&rpath); + } + + if let Some(marker) = self.marker_data.as_ref() { + marker.draw(surface, style, self.index); + } } } @@ -667,9 +731,7 @@ struct Scatter { cols: (des::DataCol, des::DataCol), ab: Option<(axis::Bounds, axis::Bounds)>, axes: (des::axis::Ref, des::axis::Ref), - path: geom::Path, - points: Vec, - marker: style::series::Marker, + marker_data: MarkerData, } impl Scatter { @@ -679,15 +741,13 @@ impl Scatter { { let cols = (des.x_data().clone(), des.y_data().clone()); let xy_bounds = calc_xy_bounds(data_source, &cols.0, &cols.1)?; - let path = marker::marker_path(des.marker()); + let marker_data = MarkerData::new(des.marker().clone()); Ok(Scatter { index, cols, ab: xy_bounds, axes: (des.x_axis().clone(), des.y_axis().clone()), - path, - points: Vec::new(), - marker: des.marker().clone(), + marker_data, }) } @@ -700,7 +760,7 @@ impl Scatter { debug_assert!(x_col.len() == y_col.len()); if self.ab.is_none() && x_col.is_empty() { - self.points.clear(); + self.marker_data.clear(); return; } @@ -722,29 +782,14 @@ impl Scatter { let y = rect.bottom() - y; points.push(geom::Point { x, y }); } - self.points = points; + self.marker_data.points = points; } fn draw(&self, surface: &mut S, style: &Style) where S: render::Surface, { - if self.points.is_empty() { - return; - } - - let rc = (style, self.index); - - for p in &self.points { - let transform = geom::Transform::from_translate(p.x, p.y); - let path = render::Path { - path: &self.path, - fill: self.marker.fill.as_ref().map(|f| f.as_paint(&rc)), - stroke: self.marker.stroke.as_ref().map(|l| l.as_stroke(&rc)), - transform: Some(&transform), - }; - surface.draw_path(&path); - } + self.marker_data.draw(surface, style, self.index); } } diff --git a/src/utils.rs b/src/utils.rs index 0526dbe..37917a3 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -91,8 +91,10 @@ impl MplStyle for series::Line { } }; - if let Some(_) = style.marker_shape { - todo!("line marker not implemented yet"); + if let Some(shape) = style.marker_shape { + let mut marker = self.marker().cloned().unwrap_or_default(); + marker.shape = shape; + self = self.with_marker(marker); } if let Some(pattern) = style.pattern { diff --git a/tests/refs/style/dash_dot-spline.png b/tests/refs/style/dash_dot-spline.png new file mode 100644 index 0000000000000000000000000000000000000000..92ece4fb42ddc0fd6b5142fd8480f91ef048ee1e GIT binary patch literal 14441 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 + + + + + + + + + \ No newline at end of file diff --git a/tests/refs/style/line-plus-markers.png b/tests/refs/style/line-plus-markers.png new file mode 100644 index 0000000000000000000000000000000000000000..123c0504396ce20f3485b313b60151b707abbbc6 GIT binary patch literal 19987 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 literal 0 HcmV?d00001 diff --git a/tests/refs/style/line-plus-markers.svg b/tests/refs/style/line-plus-markers.svg new file mode 100644 index 0000000..b35baa0 --- /dev/null +++ b/tests/refs/style/line-plus-markers.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/src/tests/style.rs b/tests/src/tests/style.rs index 0e891fd..cdf672d 100644 --- a/tests/src/tests/style.rs +++ b/tests/src/tests/style.rs @@ -4,17 +4,15 @@ use plotive::utils::MplStyle; use super::{fig_small, line}; use crate::{TestHarness, assert_fig_eq_ref}; -#[test] -fn style_default() { - let series = line().into(); - let plot = des::Plot::new(vec![series]); - let fig = fig_small(plot); - - assert_fig_eq_ref!(&fig, "axes/default"); +fn line_spline() -> des::series::Line { + let x = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]; + let y = vec![0.0, 2.0, 3.0, 1.0, 4.0, 4.0]; + des::series::Line::new(des::data_inline(x), des::data_inline(y)) + .with_interpolation(des::series::Interpolation::Spline) } #[test] -fn style_dash() { +fn style_mpl_dash() { let series = line().with_mpl_style("--").unwrap().into(); let plot = des::Plot::new(vec![series]); let fig = fig_small(plot); @@ -23,7 +21,7 @@ fn style_dash() { } #[test] -fn style_dash_dot() { +fn style_mpl_dash_dot() { let series = line().with_mpl_style("-.").unwrap().into(); let plot = des::Plot::new(vec![series]); let fig = fig_small(plot); @@ -32,7 +30,19 @@ fn style_dash_dot() { } #[test] -fn style_dot() { +fn style_mpl_dash_dot_spline() { + let series = line_spline() + .with_mpl_style("-.") + .unwrap() + .into(); + let plot = des::Plot::new(vec![series]); + let fig = fig_small(plot); + + assert_fig_eq_ref!(&fig, "style/dash_dot-spline"); +} + +#[test] +fn style_mpl_dot() { let series = line().with_mpl_style(":").unwrap().into(); let plot = des::Plot::new(vec![series]); let fig = fig_small(plot); @@ -41,7 +51,7 @@ fn style_dot() { } #[test] -fn style_dash_scales_with_width() { +fn style_mpl_dash_scales_with_width() { let series = line() .with_stroke(plotive::style::series::Stroke::default().with_width(4.0)) .with_mpl_style("--") @@ -52,3 +62,11 @@ fn style_dash_scales_with_width() { assert_fig_eq_ref!(&fig, "style/dash-fat"); } + +#[test] +fn style_mpl_line_markers() { + let plot = line_spline().with_mpl_style("o").unwrap().into_plot(); + let fig = fig_small(plot); + + assert_fig_eq_ref!(&fig, "style/line-plus-markers"); +} From 21fd1d5ee71152bc5d730d73064d5a9cee458f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Thebault?= Date: Sun, 3 May 2026 22:16:06 +0200 Subject: [PATCH 09/13] markers default with stroke color identical as fill --- src/style.rs | 81 ++++++++++++++++++++++++-- src/style/defaults.rs | 2 +- src/style/series.rs | 10 +--- tests/refs/style/line-plus-markers.svg | 12 ++-- 4 files changed, 83 insertions(+), 22 deletions(-) diff --git a/src/style.rs b/src/style.rs index 909e9c4..a814f27 100644 --- a/src/style.rs +++ b/src/style.rs @@ -406,17 +406,86 @@ pub struct Marker { pub stroke: Option>, } +impl Marker { + /// Create a new marker with both fill and stroke set to the same color + pub fn new_with_color(color: C) -> Self { + Marker { + size: MarkerSize::default(), + shape: MarkerShape::default(), + fill: Some(Fill::Solid { + color, + opacity: None, + }), + stroke: Some(Stroke { + color, + width: defaults::SERIES_LINE_WIDTH, + pattern: LinePattern::default(), + opacity: None, + }), + } + } + + /// Set the marker size, returning self for chaining + pub fn with_size>(self, size: S) -> Self { + Self { + size: size.into(), + ..self + } + } + + /// Set the marker shape, returning self for chaining + pub fn with_shape(self, shape: MarkerShape) -> Self { + Self { shape, ..self } + } + + /// Set the marker fill style, returning self for chaining + pub fn with_fill>>(self, fill: F) -> Self { + Self { + fill: Some(fill.into()), + ..self + } + } + + /// Set the marker stroke style, returning self for chaining + pub fn with_stroke>>(self, stroke: S) -> Self { + Self { + stroke: Some(stroke.into()), + ..self + } + } + + /// Shorthand for setting both fill and stroke to the same color, returning self for chaining + pub fn with_color(self, color: C) -> Self { + let mut fill = self.fill.unwrap_or_else(|| Fill::Solid { + color, + opacity: None, + }); + match &mut fill { + Fill::Solid { color: col, .. } => *col = color, + } + + let mut stroke = self.stroke.unwrap_or_else(|| Stroke { + color, + width: defaults::SERIES_LINE_WIDTH, + opacity: None, + pattern: LinePattern::default(), + }); + stroke.color = color; + + Self { + fill: Some(fill), + stroke: Some(stroke), + ..self + } + } +} + impl Default for Marker where C: Color + Default, { fn default() -> Self { - Marker { - size: MarkerSize::default(), - shape: MarkerShape::default(), - fill: Some(Fill::default()), - stroke: None, - } + Marker::new_with_color(C::default()) } } diff --git a/src/style/defaults.rs b/src/style/defaults.rs index a498130..38a54a4 100644 --- a/src/style/defaults.rs +++ b/src/style/defaults.rs @@ -10,7 +10,7 @@ pub const AXIS_LABEL_FONT_SIZE: f32 = 16.0; pub const TICKS_LABEL_FONT_SIZE: f32 = 12.0; pub const SERIES_LINE_WIDTH: f32 = 1.5; -pub const MARKER_SIZE: f32 = 10.0; +pub const MARKER_SIZE: f32 = 8.5; pub const LEGEND_LABEL_FONT_SIZE: f32 = 13.0; pub const LEGEND_SHAPE_SPACING: f32 = 10.0; diff --git a/src/style/series.rs b/src/style/series.rs index ff6432d..20a8a0d 100644 --- a/src/style/series.rs +++ b/src/style/series.rs @@ -169,15 +169,7 @@ pub type Marker = style::Marker; impl From for Marker { fn from(color: ColorU8) -> Self { - Marker { - size: Default::default(), - shape: Default::default(), - fill: Some(Fill::Solid { - color: color.into(), - opacity: None, - }), - stroke: None, - } + Marker::new_with_color(color.into()) } } diff --git a/tests/refs/style/line-plus-markers.svg b/tests/refs/style/line-plus-markers.svg index b35baa0..fa3196c 100644 --- a/tests/refs/style/line-plus-markers.svg +++ b/tests/refs/style/line-plus-markers.svg @@ -5,12 +5,12 @@ - - - - - - + + + + + + \ No newline at end of file From 9f254449a5cdac0ebd3d8dd0fc98ebe26502923b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Thebault?= Date: Sun, 3 May 2026 22:43:59 +0200 Subject: [PATCH 10/13] fix markers rendering --- src/drawing/marker.rs | 43 ++++++++++++----- src/style.rs | 12 +++-- tests/refs/style/line-markers-plus.png | Bin 0 -> 20086 bytes tests/refs/style/line-markers-plus.svg | 16 +++++++ tests/refs/style/line-markers-triup-color.png | Bin 0 -> 20271 bytes tests/refs/style/line-markers-triup-color.svg | 16 +++++++ tests/refs/style/line-markers.png | Bin 0 -> 20084 bytes ...line-plus-markers.svg => line-markers.svg} | 0 tests/refs/style/line-plus-markers.png | Bin 19987 -> 0 bytes tests/src/tests/style.rs | 45 +++++++++++++----- 10 files changed, 105 insertions(+), 27 deletions(-) create mode 100644 tests/refs/style/line-markers-plus.png create mode 100644 tests/refs/style/line-markers-plus.svg create mode 100644 tests/refs/style/line-markers-triup-color.png create mode 100644 tests/refs/style/line-markers-triup-color.svg create mode 100644 tests/refs/style/line-markers.png rename tests/refs/style/{line-plus-markers.svg => line-markers.svg} (100%) delete mode 100644 tests/refs/style/line-plus-markers.png diff --git a/src/drawing/marker.rs b/src/drawing/marker.rs index b7b7a23..4a82030 100644 --- a/src/drawing/marker.rs +++ b/src/drawing/marker.rs @@ -1,6 +1,7 @@ use crate::{Color, geom, style}; -const SQRT2: f32 = 1.41421356237; +const SQRT2: f32 = 1.4142135623730951; +const TAN30: f32 = 0.5773502691896257; pub fn marker_path(marker: &style::Marker) -> geom::Path { match marker.shape { @@ -53,22 +54,42 @@ pub fn marker_path(marker: &style::Marker) -> geom::Path { builder.finish().expect("Should be a valid path") } style::MarkerShape::TriangleUp => { - let half_w = marker.size.0 / 2.0; - let half_h = marker.size.0 * SQRT2 / 2.0; + let height = marker.size.0; + let base = 2.0 * height * TAN30; let mut builder = geom::PathBuilder::new(); - builder.move_to(0.0, -half_h); - builder.line_to(half_w, half_h); - builder.line_to(-half_w, half_h); + builder.move_to(0.0, -2.0 * height / 3.0); + builder.line_to(base / 2.0, height / 3.0); + builder.line_to(-base / 2.0, height / 3.0); builder.close(); builder.finish().expect("Should be a valid path") } style::MarkerShape::TriangleDown => { - let half_w = marker.size.0 / 2.0; - let half_h = marker.size.0 * SQRT2 / 2.0; + let height = marker.size.0; + let base = 2.0 * height * TAN30; let mut builder = geom::PathBuilder::new(); - builder.move_to(0.0, half_h); - builder.line_to(half_w, -half_h); - builder.line_to(-half_w, -half_h); + builder.move_to(0.0, 2.0 * height / 3.0); + builder.line_to(base / 2.0, -height / 3.0); + builder.line_to(-base / 2.0, -height / 3.0); + builder.close(); + builder.finish().expect("Should be a valid path") + } + style::MarkerShape::TriangleRight => { + let height = marker.size.0; + let base = 2.0 * height * TAN30; + let mut builder = geom::PathBuilder::new(); + builder.move_to(2.0 * height / 3.0, 0.0); + builder.line_to(-height / 3.0, base / 2.0); + builder.line_to(-height / 3.0, -base / 2.0); + builder.close(); + builder.finish().expect("Should be a valid path") + } + style::MarkerShape::TriangleLeft => { + let height = marker.size.0; + let base = 2.0 * height * TAN30; + let mut builder = geom::PathBuilder::new(); + builder.move_to(-2.0 * height / 3.0, 0.0); + builder.line_to(height / 3.0, base / 2.0); + builder.line_to(height / 3.0, -base / 2.0); builder.close(); builder.finish().expect("Should be a valid path") } diff --git a/src/style.rs b/src/style.rs index a814f27..279f232 100644 --- a/src/style.rs +++ b/src/style.rs @@ -375,6 +375,10 @@ pub enum MarkerShape { TriangleUp, /// Downward pointing triangle marker TriangleDown, + /// Rightward pointing triangle marker + TriangleRight, + /// Leftward pointing triangle marker + TriangleLeft, } /// Size of a marker, used in scatter plots @@ -439,17 +443,17 @@ impl Marker { } /// Set the marker fill style, returning self for chaining - pub fn with_fill>>(self, fill: F) -> Self { + pub fn with_fill(self, fill: Fill) -> Self { Self { - fill: Some(fill.into()), + fill: Some(fill), ..self } } /// Set the marker stroke style, returning self for chaining - pub fn with_stroke>>(self, stroke: S) -> Self { + pub fn with_stroke(self, stroke: Stroke) -> Self { Self { - stroke: Some(stroke.into()), + stroke: Some(stroke), ..self } } diff --git a/tests/refs/style/line-markers-plus.png b/tests/refs/style/line-markers-plus.png new file mode 100644 index 0000000000000000000000000000000000000000..bc1cb2491484c68f072c636c25d1b688f882b5b7 GIT binary patch literal 20086 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 Fe*p+H?QZ}8 literal 0 HcmV?d00001 diff --git a/tests/refs/style/line-markers-plus.svg b/tests/refs/style/line-markers-plus.svg new file mode 100644 index 0000000..df4b812 --- /dev/null +++ b/tests/refs/style/line-markers-plus.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/refs/style/line-markers-triup-color.png b/tests/refs/style/line-markers-triup-color.png new file mode 100644 index 0000000000000000000000000000000000000000..d6c826bf09b621a74e886cf71a0363ffc7e2eb47 GIT binary patch literal 20271 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 literal 0 HcmV?d00001 diff --git a/tests/refs/style/line-markers-triup-color.svg b/tests/refs/style/line-markers-triup-color.svg new file mode 100644 index 0000000..2bcb3e3 --- /dev/null +++ b/tests/refs/style/line-markers-triup-color.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/refs/style/line-markers.png b/tests/refs/style/line-markers.png new file mode 100644 index 0000000000000000000000000000000000000000..031f5f11b5598b2702d0ddfddeee6ae3710abdf8 GIT binary patch literal 20084 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 SyiG3Y!G%j6sQtHP>;4y9_S?q* literal 0 HcmV?d00001 diff --git a/tests/refs/style/line-plus-markers.svg b/tests/refs/style/line-markers.svg similarity index 100% rename from tests/refs/style/line-plus-markers.svg rename to tests/refs/style/line-markers.svg diff --git a/tests/refs/style/line-plus-markers.png b/tests/refs/style/line-plus-markers.png deleted file mode 100644 index 123c0504396ce20f3485b313b60151b707abbbc6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19987 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 diff --git a/tests/src/tests/style.rs b/tests/src/tests/style.rs index cdf672d..3f13c0e 100644 --- a/tests/src/tests/style.rs +++ b/tests/src/tests/style.rs @@ -1,5 +1,5 @@ -use plotive::des; use plotive::utils::MplStyle; +use plotive::{ColorU8, des}; use super::{fig_small, line}; use crate::{TestHarness, assert_fig_eq_ref}; @@ -12,7 +12,7 @@ fn line_spline() -> des::series::Line { } #[test] -fn style_mpl_dash() { +fn style_dash_mpl() { let series = line().with_mpl_style("--").unwrap().into(); let plot = des::Plot::new(vec![series]); let fig = fig_small(plot); @@ -21,7 +21,7 @@ fn style_mpl_dash() { } #[test] -fn style_mpl_dash_dot() { +fn style_dash_dot_mpl() { let series = line().with_mpl_style("-.").unwrap().into(); let plot = des::Plot::new(vec![series]); let fig = fig_small(plot); @@ -30,11 +30,8 @@ fn style_mpl_dash_dot() { } #[test] -fn style_mpl_dash_dot_spline() { - let series = line_spline() - .with_mpl_style("-.") - .unwrap() - .into(); +fn style_dash_dot_spline_mpl() { + let series = line_spline().with_mpl_style("-.").unwrap().into(); let plot = des::Plot::new(vec![series]); let fig = fig_small(plot); @@ -42,7 +39,7 @@ fn style_mpl_dash_dot_spline() { } #[test] -fn style_mpl_dot() { +fn style_dot_mpl() { let series = line().with_mpl_style(":").unwrap().into(); let plot = des::Plot::new(vec![series]); let fig = fig_small(plot); @@ -51,7 +48,7 @@ fn style_mpl_dot() { } #[test] -fn style_mpl_dash_scales_with_width() { +fn style_dash_scales_with_width_mpl() { let series = line() .with_stroke(plotive::style::series::Stroke::default().with_width(4.0)) .with_mpl_style("--") @@ -64,9 +61,33 @@ fn style_mpl_dash_scales_with_width() { } #[test] -fn style_mpl_line_markers() { +fn style_line_markers_mpl() { let plot = line_spline().with_mpl_style("o").unwrap().into_plot(); let fig = fig_small(plot); - assert_fig_eq_ref!(&fig, "style/line-plus-markers"); + assert_fig_eq_ref!(&fig, "style/line-markers"); +} + +#[test] +fn style_line_markers_plus_mpl() { + let plot = line_spline().with_mpl_style("+").unwrap().into_plot(); + let fig = fig_small(plot); + + assert_fig_eq_ref!(&fig, "style/line-markers-plus"); +} + +#[test] +fn style_line_markers_triup_color() { + let plot = line_spline() + .with_marker( + plotive::style::series::Marker::new_with_color( + plotive::ColorU8::from_html(b"#000").into(), + ) + .with_stroke(ColorU8::from_html(b"#080").into()) + .with_shape(plotive::style::MarkerShape::TriangleUp), + ) + .into_plot(); + let fig = fig_small(plot); + + assert_fig_eq_ref!(&fig, "style/line-markers-triup-color"); } From 05d0fcc1ac272acb3a3fc235554776a9c43758c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Thebault?= Date: Mon, 4 May 2026 11:05:26 +0200 Subject: [PATCH 11/13] Test all marker shapes --- src/utils.rs | 2 + ...ne-markers.png => line-markers-circle.png} | Bin tests/refs/style/line-markers-circle.svg | 16 +++++ tests/refs/style/line-markers-cross.png | Bin 0 -> 20640 bytes tests/refs/style/line-markers-cross.svg | 16 +++++ tests/refs/style/line-markers-diamond.png | Bin 0 -> 20276 bytes tests/refs/style/line-markers-diamond.svg | 16 +++++ tests/refs/style/line-markers-plus.svg | 12 ++-- tests/refs/style/line-markers-square.png | Bin 0 -> 18952 bytes tests/refs/style/line-markers-square.svg | 16 +++++ tests/refs/style/line-markers-tridown.png | Bin 0 -> 20034 bytes tests/refs/style/line-markers-tridown.svg | 16 +++++ tests/refs/style/line-markers-trileft.png | Bin 0 -> 20368 bytes tests/refs/style/line-markers-trileft.svg | 16 +++++ tests/refs/style/line-markers-triright.png | Bin 0 -> 20514 bytes tests/refs/style/line-markers-triright.svg | 16 +++++ tests/refs/style/line-markers-triup-color.svg | 12 ++-- tests/refs/style/line-markers-triup.png | Bin 0 -> 20096 bytes tests/refs/style/line-markers-triup.svg | 16 +++++ tests/refs/style/line-markers.svg | 16 ----- tests/src/tests/style.rs | 60 +++++++++++++++++- 21 files changed, 200 insertions(+), 30 deletions(-) rename tests/refs/style/{line-markers.png => line-markers-circle.png} (100%) create mode 100644 tests/refs/style/line-markers-circle.svg create mode 100644 tests/refs/style/line-markers-cross.png create mode 100644 tests/refs/style/line-markers-cross.svg create mode 100644 tests/refs/style/line-markers-diamond.png create mode 100644 tests/refs/style/line-markers-diamond.svg create mode 100644 tests/refs/style/line-markers-square.png create mode 100644 tests/refs/style/line-markers-square.svg create mode 100644 tests/refs/style/line-markers-tridown.png create mode 100644 tests/refs/style/line-markers-tridown.svg create mode 100644 tests/refs/style/line-markers-trileft.png create mode 100644 tests/refs/style/line-markers-trileft.svg create mode 100644 tests/refs/style/line-markers-triright.png create mode 100644 tests/refs/style/line-markers-triright.svg create mode 100644 tests/refs/style/line-markers-triup.png create mode 100644 tests/refs/style/line-markers-triup.svg delete mode 100644 tests/refs/style/line-markers.svg diff --git a/src/utils.rs b/src/utils.rs index 37917a3..1711041 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -137,6 +137,8 @@ impl LineMplStyle { '+' => style.set_marker_shape(style::MarkerShape::Plus)?, 'v' => style.set_marker_shape(style::MarkerShape::TriangleDown)?, '^' => style.set_marker_shape(style::MarkerShape::TriangleUp)?, + '>' => style.set_marker_shape(style::MarkerShape::TriangleRight)?, + '<' => style.set_marker_shape(style::MarkerShape::TriangleLeft)?, '-' => { if let Some((_, next)) = chars.peek() { match next { diff --git a/tests/refs/style/line-markers.png b/tests/refs/style/line-markers-circle.png similarity index 100% rename from tests/refs/style/line-markers.png rename to tests/refs/style/line-markers-circle.png diff --git a/tests/refs/style/line-markers-circle.svg b/tests/refs/style/line-markers-circle.svg new file mode 100644 index 0000000..87bc805 --- /dev/null +++ b/tests/refs/style/line-markers-circle.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/refs/style/line-markers-cross.png b/tests/refs/style/line-markers-cross.png new file mode 100644 index 0000000000000000000000000000000000000000..6e1b3f98b7773b95bb51d999963111f6e9fddadc GIT binary patch literal 20640 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 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/refs/style/line-markers-diamond.png b/tests/refs/style/line-markers-diamond.png new file mode 100644 index 0000000000000000000000000000000000000000..4b9901307769a890623ec994ea0944e93853cb05 GIT binary patch literal 20276 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 literal 0 HcmV?d00001 diff --git a/tests/refs/style/line-markers-diamond.svg b/tests/refs/style/line-markers-diamond.svg new file mode 100644 index 0000000..d8aee57 --- /dev/null +++ b/tests/refs/style/line-markers-diamond.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/refs/style/line-markers-plus.svg b/tests/refs/style/line-markers-plus.svg index df4b812..3d04b3a 100644 --- a/tests/refs/style/line-markers-plus.svg +++ b/tests/refs/style/line-markers-plus.svg @@ -5,12 +5,12 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/tests/refs/style/line-markers-square.png b/tests/refs/style/line-markers-square.png new file mode 100644 index 0000000000000000000000000000000000000000..e599cd087fe9e3e9120e37b7ba5226e743d235fe GIT binary patch literal 18952 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 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/refs/style/line-markers-tridown.png b/tests/refs/style/line-markers-tridown.png new file mode 100644 index 0000000000000000000000000000000000000000..ba4d94720c0e4e8cd8ce2e1781d878fea7bf533c GIT binary patch literal 20034 zcmds9eRx#ml@HY#zc79_s|eAyyV_bzEeax#DO)L8x}>!mXBEiMM{P_dV}9zw{5o3eNxG=&|jq7d%s!+rDe`f6HE~e(gt&>hDea z`uWgW=dX(2-Y{!;3K9n*z(-xSiE<9** z<&>Sw@uyvVYC4;n;mOa~;11WiyLVb5J4d%Ez3dKcad+*^?%z3b&ZVqS%E2!?l09Eq2P*BHj3Gdo&sTJ9OuYz$y4BzVIWu6_~F6Ypzlb zD=NKVR|prW9!1!8R$D6r36|gSdBI)qhN@r*BeXhd8({=|> zE~?l4bE>1`3kM53>fH7aenGP06!v%qMIo3Y=qxYUUQrKH*U6w?@8;J!i?4gE+hUjr`^oEqdGs&jc zoz{M54<1-iZCmlwm(xtg*^{~rB(`$cF*pq#6&=iE51L{pOfB%}l;V%dN4~63ewiKK zX4#x<8reU4WPeJ}y!RR!nk_773A9PEw&+kEn+_|JTedBzOroWAg2!wgE_Mv7JFU8m zogT+E^hi@X?A#seaJP`rXGnn=>T}X!AE(iC zR>7ev_k=YMj3z@MORwmhlpsKQ&8-Z$d&)XJW#}cLzqrERQT_Y|^d7RZ?is@+@{F|L zPty*e;{;3}8oINxb>RGYik8x#S$c?0tJkJ1qtjy7C}9WN>`?j~J=>-5_WI|0ZwCKM zZ<#L*&JSdJgW1tcpRu1m2Cmh3yua}VIBBV6e%UAU{TOq1nyhcIWoruxLij-1RL<

4C4KdQ^ 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 literal 0 HcmV?d00001 diff --git a/tests/refs/style/line-markers-tridown.svg b/tests/refs/style/line-markers-tridown.svg new file mode 100644 index 0000000..9d6a0ac --- /dev/null +++ b/tests/refs/style/line-markers-tridown.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/refs/style/line-markers-trileft.png b/tests/refs/style/line-markers-trileft.png new file mode 100644 index 0000000000000000000000000000000000000000..efc7b84d22aeecfa0c05f8c9ad8d8499d75f9991 GIT binary patch literal 20368 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 NN1u44{=3hu`(NV>bg%#b literal 0 HcmV?d00001 diff --git a/tests/refs/style/line-markers-trileft.svg b/tests/refs/style/line-markers-trileft.svg new file mode 100644 index 0000000..f0cd8e2 --- /dev/null +++ b/tests/refs/style/line-markers-trileft.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/refs/style/line-markers-triright.png b/tests/refs/style/line-markers-triright.png new file mode 100644 index 0000000000000000000000000000000000000000..6c7d79bc1deb15acbd8b4369673081ad9c839a11 GIT binary patch literal 20514 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 literal 0 HcmV?d00001 diff --git a/tests/refs/style/line-markers-triright.svg b/tests/refs/style/line-markers-triright.svg new file mode 100644 index 0000000..d3de5da --- /dev/null +++ b/tests/refs/style/line-markers-triright.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/refs/style/line-markers-triup-color.svg b/tests/refs/style/line-markers-triup-color.svg index 2bcb3e3..fe166b8 100644 --- a/tests/refs/style/line-markers-triup-color.svg +++ b/tests/refs/style/line-markers-triup-color.svg @@ -5,12 +5,12 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/tests/refs/style/line-markers-triup.png b/tests/refs/style/line-markers-triup.png new file mode 100644 index 0000000000000000000000000000000000000000..a40071b92c101f0bae2829dd508e3eece56ee8e2 GIT binary patch literal 20096 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 T6W%Nr_t>hnKd$+gUv2(hUd!D$ literal 0 HcmV?d00001 diff --git a/tests/refs/style/line-markers-triup.svg b/tests/refs/style/line-markers-triup.svg new file mode 100644 index 0000000..441306d --- /dev/null +++ b/tests/refs/style/line-markers-triup.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/refs/style/line-markers.svg b/tests/refs/style/line-markers.svg deleted file mode 100644 index fa3196c..0000000 --- a/tests/refs/style/line-markers.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tests/src/tests/style.rs b/tests/src/tests/style.rs index 3f13c0e..06c8169 100644 --- a/tests/src/tests/style.rs +++ b/tests/src/tests/style.rs @@ -61,11 +61,35 @@ fn style_dash_scales_with_width_mpl() { } #[test] -fn style_line_markers_mpl() { +fn style_line_markers_circle_mpl() { let plot = line_spline().with_mpl_style("o").unwrap().into_plot(); let fig = fig_small(plot); - assert_fig_eq_ref!(&fig, "style/line-markers"); + assert_fig_eq_ref!(&fig, "style/line-markers-circle"); +} + +#[test] +fn style_line_markers_square_mpl() { + let plot = line_spline().with_mpl_style("s").unwrap().into_plot(); + let fig = fig_small(plot); + + assert_fig_eq_ref!(&fig, "style/line-markers-square"); +} + +#[test] +fn style_line_markers_diamond_mpl() { + let plot = line_spline().with_mpl_style("D").unwrap().into_plot(); + let fig = fig_small(plot); + + assert_fig_eq_ref!(&fig, "style/line-markers-diamond"); +} + +#[test] +fn style_line_markers_cross_mpl() { + let plot = line_spline().with_mpl_style("x").unwrap().into_plot(); + let fig = fig_small(plot); + + assert_fig_eq_ref!(&fig, "style/line-markers-cross"); } #[test] @@ -76,6 +100,38 @@ fn style_line_markers_plus_mpl() { assert_fig_eq_ref!(&fig, "style/line-markers-plus"); } +#[test] +fn style_line_markers_triup_mpl() { + let plot = line_spline().with_mpl_style("^").unwrap().into_plot(); + let fig = fig_small(plot); + + assert_fig_eq_ref!(&fig, "style/line-markers-triup"); +} + +#[test] +fn style_line_markers_tridown_mpl() { + let plot = line_spline().with_mpl_style("v").unwrap().into_plot(); + let fig = fig_small(plot); + + assert_fig_eq_ref!(&fig, "style/line-markers-tridown"); +} + +#[test] +fn style_line_markers_trileft_mpl() { + let plot = line_spline().with_mpl_style("<").unwrap().into_plot(); + let fig = fig_small(plot); + + assert_fig_eq_ref!(&fig, "style/line-markers-trileft"); +} + +#[test] +fn style_line_markers_triright_mpl() { + let plot = line_spline().with_mpl_style(">").unwrap().into_plot(); + let fig = fig_small(plot); + + assert_fig_eq_ref!(&fig, "style/line-markers-triright"); +} + #[test] fn style_line_markers_triup_color() { let plot = line_spline() From 49ff399db160414e3d6acbf1a9246471260c9985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Thebault?= Date: Mon, 4 May 2026 09:53:02 +0200 Subject: [PATCH 12/13] Support dynamic marker size --- src/des/series.rs | 20 +++++++++++++++ src/drawing/annot.rs | 15 ++++++++--- src/drawing/legend.rs | 11 +++++--- src/drawing/marker.rs | 33 ++++++++++++------------ src/drawing/series.rs | 38 +++++++++++++++++++++++----- src/render.rs | 8 ++++++ src/style.rs | 8 ++++++ src/style/defaults.rs | 2 +- tests/refs/series/scatter-sizes.png | Bin 0 -> 12477 bytes tests/refs/series/scatter-sizes.svg | 14 ++++++++++ tests/src/tests/series.rs | 31 +++++++++++++++++++++++ 11 files changed, 150 insertions(+), 30 deletions(-) create mode 100644 tests/refs/series/scatter-sizes.png create mode 100644 tests/refs/series/scatter-sizes.svg diff --git a/src/des/series.rs b/src/des/series.rs index 391f663..6d84273 100644 --- a/src/des/series.rs +++ b/src/des/series.rs @@ -311,6 +311,13 @@ impl Line { /// /// Plots data as individual scatter points without connecting them. /// Useful for visualizing correlations, distributions, and discrete data points. +/// +/// Optional sizes data column can be used to specify the size of each marker, for bubble charts. +/// If provided, the size field of the marker is ignored and the size of each marker +/// is determined by the corresponding value in the sizes data column. +/// Just like marker size, the size data is interpreted as an area. +/// (diameter = sqrt(size data) for circle marker). +/// The sizes data column must have the same length as the x and y data columns. #[derive(Debug, Clone)] pub struct Scatter { x_data: DataCol, @@ -320,6 +327,7 @@ pub struct Scatter { x_axis: axis::Ref, y_axis: axis::Ref, marker: style::series::Marker, + sizes_data: Option, } impl Scatter { @@ -333,6 +341,7 @@ impl Scatter { x_axis: Default::default(), y_axis: Default::default(), marker: style::series::Marker::default(), + sizes_data: None, } } @@ -364,6 +373,12 @@ impl Scatter { self } + /// Set the sizes data column and return self for chaining + pub fn with_sizes(mut self, sizes_data: DataCol) -> Self { + self.sizes_data = Some(sizes_data); + self + } + /// Get the x data column pub fn x_data(&self) -> &DataCol { &self.x_data @@ -393,6 +408,11 @@ impl Scatter { pub fn marker(&self) -> &style::series::Marker { &self.marker } + + /// Get the sizes data column, if any + pub fn sizes_data(&self) -> Option<&DataCol> { + self.sizes_data.as_ref() + } } /// Definition for the `y2_data` field of the Area plot. diff --git a/src/drawing/annot.rs b/src/drawing/annot.rs index 1a21362..3f1f78b 100644 --- a/src/drawing/annot.rs +++ b/src/drawing/annot.rs @@ -298,14 +298,21 @@ impl Annot { let (x, y) = marker.position(); let x = x_axis.coord_map().map_coord_num(x); let y = y_axis.coord_map().map_coord_num(y); - let path = marker::marker_path(marker.marker()); + let marker = marker.marker(); + let path = marker::marker_path(marker.shape); + let scale = marker.size.to_visual_size(); let transform = - geom::Transform::from_translate(plot_rect.left() + x, plot_rect.bottom() - y); + geom::Transform::from_translate(plot_rect.left() + x, plot_rect.bottom() - y) + .pre_scale(scale, scale); + let rpath = render::Path { path: &path, - fill: marker.marker().fill.as_ref().map(|f| f.as_paint(style)), - stroke: marker.marker().stroke.as_ref().map(|l| l.as_stroke(style)), + fill: marker.fill.as_ref().map(|f| f.as_paint(style)), + stroke: marker + .stroke + .as_ref() + .map(|l| l.as_stroke(style).with_multiplied_width(1.0 / scale)), transform: Some(&transform), }; surface.draw_path(&rpath); diff --git a/src/drawing/legend.rs b/src/drawing/legend.rs index 43e1b87..7dd3567 100644 --- a/src/drawing/legend.rs +++ b/src/drawing/legend.rs @@ -281,14 +281,19 @@ impl LegendEntry { surface.draw_path(&line); } Shape::Marker(marker) => { - let path = crate::drawing::marker::marker_path(&marker); + let path = crate::drawing::marker::marker_path(marker.shape); + let scale = style::MarkerSize::default().to_visual_size(); let transform = - geom::Transform::from_translate(shape_rect.center_x(), shape_rect.center_y()); + geom::Transform::from_translate(shape_rect.center_x(), shape_rect.center_y()) + .pre_scale(scale, scale); let path = render::Path { path: &path, fill: marker.fill.as_ref().map(|f| f.as_paint(&rc)), - stroke: marker.stroke.as_ref().map(|s| s.as_stroke(&rc)), + stroke: marker + .stroke + .as_ref() + .map(|s| s.as_stroke(&rc).with_multiplied_width(1.0 / scale)), transform: Some(&transform), }; surface.draw_path(&path); diff --git a/src/drawing/marker.rs b/src/drawing/marker.rs index 4a82030..74ea027 100644 --- a/src/drawing/marker.rs +++ b/src/drawing/marker.rs @@ -1,17 +1,18 @@ -use crate::{Color, geom, style}; +use crate::{geom, style}; const SQRT2: f32 = 1.4142135623730951; const TAN30: f32 = 0.5773502691896257; -pub fn marker_path(marker: &style::Marker) -> geom::Path { - match marker.shape { +/// Generate a path for a marker shape of size 1.0 and centered at (0, 0) +pub fn marker_path(shape: style::MarkerShape) -> geom::Path { + match shape { style::MarkerShape::Circle => { - let radius = marker.size.0 / 2.0; + let radius = 0.5; geom::PathBuilder::from_circle(0.0, 0.0, radius).expect("Should be a valid path") } style::MarkerShape::Square => { - let half_w = marker.size.0 / 2.0; - let half_h = marker.size.0 / 2.0; + let half_w = 0.5; + let half_h = 0.5; let mut builder = geom::PathBuilder::new(); builder.move_to(-half_w, -half_h); builder.line_to(half_w, -half_h); @@ -21,8 +22,8 @@ pub fn marker_path(marker: &style::Marker) -> geom::Path { builder.finish().expect("Should be a valid path") } style::MarkerShape::Diamond => { - let half_w = marker.size.0 / SQRT2; - let half_h = marker.size.0 / SQRT2; + let half_w = 1.0 / SQRT2; + let half_h = 1.0 / SQRT2; let mut builder = geom::PathBuilder::new(); builder.move_to(0.0, -half_h); builder.line_to(half_w, 0.0); @@ -32,8 +33,8 @@ pub fn marker_path(marker: &style::Marker) -> geom::Path { builder.finish().expect("Should be a valid path") } style::MarkerShape::Cross => { - let half_w = marker.size.0 / 2.0; - let half_h = marker.size.0 / 2.0; + let half_w = 0.5; + let half_h = 0.5; let mut builder = geom::PathBuilder::new(); builder.move_to(-half_w, -half_h); builder.line_to(half_w, half_h); @@ -43,8 +44,8 @@ pub fn marker_path(marker: &style::Marker) -> geom::Path { builder.finish().expect("Should be a valid path") } style::MarkerShape::Plus => { - let half_w = marker.size.0 / SQRT2; - let half_h = marker.size.0 / SQRT2; + let half_w = 1.0 / SQRT2; + let half_h = 1.0 / SQRT2; let mut builder = geom::PathBuilder::new(); builder.move_to(0.0, -half_h); builder.line_to(0.0, half_h); @@ -54,7 +55,7 @@ pub fn marker_path(marker: &style::Marker) -> geom::Path { builder.finish().expect("Should be a valid path") } style::MarkerShape::TriangleUp => { - let height = marker.size.0; + let height = 1.0; let base = 2.0 * height * TAN30; let mut builder = geom::PathBuilder::new(); builder.move_to(0.0, -2.0 * height / 3.0); @@ -64,7 +65,7 @@ pub fn marker_path(marker: &style::Marker) -> geom::Path { builder.finish().expect("Should be a valid path") } style::MarkerShape::TriangleDown => { - let height = marker.size.0; + let height = 1.0; let base = 2.0 * height * TAN30; let mut builder = geom::PathBuilder::new(); builder.move_to(0.0, 2.0 * height / 3.0); @@ -74,7 +75,7 @@ pub fn marker_path(marker: &style::Marker) -> geom::Path { builder.finish().expect("Should be a valid path") } style::MarkerShape::TriangleRight => { - let height = marker.size.0; + let height = 1.0; let base = 2.0 * height * TAN30; let mut builder = geom::PathBuilder::new(); builder.move_to(2.0 * height / 3.0, 0.0); @@ -84,7 +85,7 @@ pub fn marker_path(marker: &style::Marker) -> geom::Path { builder.finish().expect("Should be a valid path") } style::MarkerShape::TriangleLeft => { - let height = marker.size.0; + let height = 1.0; let base = 2.0 * height * TAN30; let mut builder = geom::PathBuilder::new(); builder.move_to(-2.0 * height / 3.0, 0.0); diff --git a/src/drawing/series.rs b/src/drawing/series.rs index d645341..42ee50f 100644 --- a/src/drawing/series.rs +++ b/src/drawing/series.rs @@ -588,13 +588,13 @@ fn calc_xy_line_path( #[derive(Debug, Clone)] struct MarkerData { path: geom::Path, - points: Vec, + points: Vec<(geom::Point, f32)>, // pos and scale marker: style::series::Marker, } impl MarkerData { fn new(marker: style::series::Marker) -> Self { - let path = marker::marker_path(&marker); + let path = marker::marker_path(marker.shape); Self { path, points: Vec::new(), @@ -617,11 +617,16 @@ impl MarkerData { let rc = (style, index); for p in &self.points { - let transform = geom::Transform::from_translate(p.x, p.y); + let transform = geom::Transform::from_translate(p.0.x, p.0.y).pre_scale(p.1, p.1); + let path = render::Path { path: &self.path, fill: self.marker.fill.as_ref().map(|f| f.as_paint(&rc)), - stroke: self.marker.stroke.as_ref().map(|l| l.as_stroke(&rc)), + stroke: self + .marker + .stroke + .as_ref() + .map(|l| l.as_stroke(&rc).with_multiplied_width(1.0 / p.1)), transform: Some(&transform), }; surface.draw_path(&path); @@ -688,6 +693,7 @@ impl Line { self.path = Some(path); if let Some(marker_data) = self.marker_data.as_mut() { + let size = marker_data.marker.size.to_visual_size(); let mut points = Vec::with_capacity(x_col.len()); for (x, y) in x_col.sample_iter().zip(y_col.sample_iter()) { @@ -697,7 +703,7 @@ impl Line { let (x, y) = cm.map_coord((x, y)).expect("Should be valid coordinates"); let x = rect.left() + x; let y = rect.bottom() - y; - points.push(geom::Point { x, y }); + points.push((geom::Point { x, y }, size)); } marker_data.points = points; } @@ -729,6 +735,7 @@ impl Line { struct Scatter { index: usize, cols: (des::DataCol, des::DataCol), + sizes_col: Option, ab: Option<(axis::Bounds, axis::Bounds)>, axes: (des::axis::Ref, des::axis::Ref), marker_data: MarkerData, @@ -740,11 +747,13 @@ impl Scatter { D: data::Source + ?Sized, { let cols = (des.x_data().clone(), des.y_data().clone()); + let sizes_col = des.sizes_data().cloned(); let xy_bounds = calc_xy_bounds(data_source, &cols.0, &cols.1)?; let marker_data = MarkerData::new(des.marker().clone()); Ok(Scatter { index, cols, + sizes_col, ab: xy_bounds, axes: (des.x_axis().clone(), des.y_axis().clone()), marker_data, @@ -759,6 +768,12 @@ impl Scatter { let y_col = get_column(&self.cols.1, data_source).unwrap(); debug_assert!(x_col.len() == y_col.len()); + let sizes_col = self + .sizes_col + .as_ref() + .map(|col| get_column(col, data_source).unwrap()); + debug_assert!(sizes_col.map_or(true, |sc| sc.len() == x_col.len())); + if self.ab.is_none() && x_col.is_empty() { self.marker_data.clear(); return; @@ -772,15 +787,26 @@ impl Scatter { } let mut points = Vec::with_capacity(x_col.len()); + let default_vs = self.marker_data.marker.size.to_visual_size(); + + let mut sizes_iter = sizes_col.map(|sc| sc.sample_iter()); for (x, y) in x_col.sample_iter().zip(y_col.sample_iter()) { if x.is_null() || y.is_null() { continue; } + let (x, y) = cm.map_coord((x, y)).expect("Should be valid coordinates"); let x = rect.left() + x; let y = rect.bottom() - y; - points.push(geom::Point { x, y }); + let vs = sizes_iter + .as_mut() + .and_then(|iter| iter.next()) + .and_then(|v| v.as_num()) + .map(|v| style::MarkerSize::from(v as f32).to_visual_size()) + .unwrap_or(default_vs); + + points.push((geom::Point { x, y }, vs)); } self.marker_data.points = points; } diff --git a/src/render.rs b/src/render.rs index 0e99648..4b4ebeb 100644 --- a/src/render.rs +++ b/src/render.rs @@ -73,6 +73,14 @@ pub struct Stroke<'a> { pub pattern: LinePattern<'a>, } +impl Stroke<'_> { + /// Multiply the line width by the given factor, useful for keeping visual width with scaled paths. + pub fn with_multiplied_width(mut self, factor: f32) -> Self { + self.width *= factor; + self + } +} + /// Rectangle to draw #[derive(Debug, Clone)] pub struct Rect<'a> { diff --git a/src/style.rs b/src/style.rs index 279f232..e1e4a63 100644 --- a/src/style.rs +++ b/src/style.rs @@ -382,9 +382,17 @@ pub enum MarkerShape { } /// Size of a marker, used in scatter plots +/// The size is interpreted as an area, so it scales quadratically with the visual size of the marker. #[derive(Debug, Clone, Copy)] pub struct MarkerSize(pub f32); +impl MarkerSize { + /// Convert the marker size to a visual size (e.g. diameter for circle marker) + pub fn to_visual_size(&self) -> f32 { + self.0.sqrt() + } +} + impl Default for MarkerSize { fn default() -> Self { MarkerSize(defaults::MARKER_SIZE) diff --git a/src/style/defaults.rs b/src/style/defaults.rs index 38a54a4..0efd912 100644 --- a/src/style/defaults.rs +++ b/src/style/defaults.rs @@ -10,7 +10,7 @@ pub const AXIS_LABEL_FONT_SIZE: f32 = 16.0; pub const TICKS_LABEL_FONT_SIZE: f32 = 12.0; pub const SERIES_LINE_WIDTH: f32 = 1.5; -pub const MARKER_SIZE: f32 = 8.5; +pub const MARKER_SIZE: f32 = 8.5 * 8.5; pub const LEGEND_LABEL_FONT_SIZE: f32 = 13.0; pub const LEGEND_SHAPE_SPACING: f32 = 10.0; diff --git a/tests/refs/series/scatter-sizes.png b/tests/refs/series/scatter-sizes.png new file mode 100644 index 0000000000000000000000000000000000000000..2011919aed237b8576668a5d04fc2f49b4e7a5c9 GIT binary patch literal 12477 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 literal 0 HcmV?d00001 diff --git a/tests/refs/series/scatter-sizes.svg b/tests/refs/series/scatter-sizes.svg new file mode 100644 index 0000000..eac205d --- /dev/null +++ b/tests/refs/series/scatter-sizes.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/src/tests/series.rs b/tests/src/tests/series.rs index fe89658..d901871 100644 --- a/tests/src/tests/series.rs +++ b/tests/src/tests/series.rs @@ -31,6 +31,37 @@ fn series_scatter_nodata() { assert_fig_eq_ref!(&fig, "series/scatter-nodata"); } +#[test] +fn series_scatter_sizes() { + let x = vec![1.0, 2.0, 3.0, 4.0, 5.0]; + let y = vec![1.0, 4.0, 9.0, 16.0, 25.0]; + let sizes = vec![300.0, 250.0, 200.0, 150.0, 100.0]; + + let color: plotive::ColorU8 = "light eggplant".parse().unwrap(); + + let plot = des::Plot::new(vec![ + des::series::Scatter::new(des::data_inline(x), des::data_inline(y)) + .with_sizes(des::data_inline(sizes)) + .with_marker( + style::series::Marker::default() + .with_fill(style::Fill::Solid { + color: color.into(), + opacity: Some(0.6), + }) + .with_stroke(style::series::Stroke { + color: color.into(), + width: 1.0, + pattern: style::LinePattern::Solid, + opacity: None, + }), + ) + .into(), + ]); + let fig = fig_small(plot); + + assert_fig_eq_ref!(&fig, "series/scatter-sizes"); +} + #[test] fn series_area_double() { let x = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0]; From 9d1615196874bdfbaa1348e14de62d2ed5862bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Thebault?= Date: Mon, 4 May 2026 11:35:02 +0200 Subject: [PATCH 13/13] marker shorthands --- src/style.rs | 30 ++++++++++++++++++++++++++++ tests/refs/series/scatter-sizes.png | Bin 12477 -> 13189 bytes tests/refs/series/scatter-sizes.svg | 10 +++++----- tests/src/tests/series.rs | 13 +++--------- 4 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/style.rs b/src/style.rs index e1e4a63..bc2d25b 100644 --- a/src/style.rs +++ b/src/style.rs @@ -490,6 +490,36 @@ impl Marker { ..self } } + + /// Shorthand for setting opacity of the fill, returning self for chaining + pub fn with_fill_opacity(self, opacity: f32) -> Self { + match self.fill { + Some(Fill::Solid { color, .. }) => self.with_fill(Fill::Solid { + color, + opacity: Some(opacity), + }), + None => self, + } + } + + /// Shorthand for setting stroke width, returning self for chaining + pub fn with_stroke_width(self, width: f32) -> Self { + let stroke = match self.stroke { + Some(Stroke { + color, + pattern, + opacity, + .. + }) => Some(Stroke { + color, + width, + pattern, + opacity, + }), + None => None, + }; + Self { stroke, ..self } + } } impl Default for Marker diff --git a/tests/refs/series/scatter-sizes.png b/tests/refs/series/scatter-sizes.png index 2011919aed237b8576668a5d04fc2f49b4e7a5c9..49ad4d0f9ff75421e878dcb7cf797581f07e7709 100644 GIT binary patch literal 13189 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 delta 3219 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 N#(cQF?vDpQ{};A)4$J@m diff --git a/tests/refs/series/scatter-sizes.svg b/tests/refs/series/scatter-sizes.svg index eac205d..46b3187 100644 --- a/tests/refs/series/scatter-sizes.svg +++ b/tests/refs/series/scatter-sizes.svg @@ -4,11 +4,11 @@ - - - - - + + + + + \ No newline at end of file diff --git a/tests/src/tests/series.rs b/tests/src/tests/series.rs index d901871..1031aa5 100644 --- a/tests/src/tests/series.rs +++ b/tests/src/tests/series.rs @@ -44,16 +44,9 @@ fn series_scatter_sizes() { .with_sizes(des::data_inline(sizes)) .with_marker( style::series::Marker::default() - .with_fill(style::Fill::Solid { - color: color.into(), - opacity: Some(0.6), - }) - .with_stroke(style::series::Stroke { - color: color.into(), - width: 1.0, - pattern: style::LinePattern::Solid, - opacity: None, - }), + .with_color(color.into()) + .with_fill_opacity(0.6) + .with_stroke_width(2.0), ) .into(), ]);