Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions base/src/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ impl Rgba8 {
Self(r, g, b, a)
}

pub const fn from_u32(rgba: u32) -> Self {
let r = ((rgba >> 24) & 0xff) as u8;
let g = ((rgba >> 16) & 0xff) as u8;
let b = ((rgba >> 8) & 0xff) as u8;
let a = (rgba & 0xff) as u8;
Self(r, g, b, a)
}

/// Get the red component of the color.
pub const fn r(&self) -> u8 {
self.0
Expand All @@ -36,6 +44,20 @@ impl Rgba8 {
self.3
}

/// Pack the color into a single u32 value in RGBA order.
pub const fn to_u32(&self) -> u32 {
((self.r() as u32) << 24)
| ((self.g() as u32) << 16)
| ((self.b() as u32) << 8)
| (self.a() as u32)
}

/// Pack the color to a single i64 value in RGBA order.
/// This is useful to send colors in data sources for plotting, as i64 is the only integer type supported in data sources.
pub const fn to_rgba_int(&self) -> i64 {
self.to_u32() as i64
}

/// Get the HTML hex string representation of the color, e.g. `#ff0000` for red.
/// If the alpha channel is not 255, the form "rgba(r, g, b, a)" is used instead (with alpha normalized to [0, 1]).
pub fn html(&self) -> String {
Expand Down Expand Up @@ -137,6 +159,14 @@ impl Rgb8 {
Self(r, g, b)
}

/// Create a Rgb8 from a u32 value in RGBA order, ignoring the alpha channel.
pub const fn from_u32(rgb: u32) -> Self {
let r = ((rgb >> 24) & 0xff) as u8;
let g = ((rgb >> 16) & 0xff) as u8;
let b = ((rgb >> 8) & 0xff) as u8;
Self(r, g, b)
}

/// Get the red component of the color.
pub const fn r(&self) -> u8 {
self.0
Expand All @@ -152,6 +182,17 @@ impl Rgb8 {
self.2
}

/// Pack the color into a single u32 value in RGBA order.
pub const fn to_u32(&self) -> u32 {
((self.r() as u32) << 24) | ((self.g() as u32) << 16) | ((self.b() as u32) << 8) | 0xff
}

/// Pack the color to a single i64 value in RGBA order.
/// This is useful to send colors in data sources for plotting, as i64 is the only integer type supported in data sources.
pub const fn to_rgba_int(&self) -> i64 {
self.to_u32() as i64
}

/// Get the HTML hex string representation of the color, e.g. `#ff0000` for red.
pub fn html(&self) -> String {
format!("#{:02x}{:02x}{:02x}", self.r(), self.g(), self.b())
Expand Down
2 changes: 1 addition & 1 deletion examples/stars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ fn main() {
des::Plot::new(vec![
des::series::Scatter::new("x".into(), "y".into())
.with_size_data("mag_sizes".into())
.with_color_data("temp".into(), cmap::stellar())
.with_color_data("temp".into(), cmap::stellar().into())
.with_marker(style::series::Marker::default().with_fill_opacity(0.85))
.into(),
])
Expand Down
80 changes: 68 additions & 12 deletions src/des/cmap.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,45 @@
//! A module for defining color maps that can be used in the design of plots to map scalar values to colors.

use std::collections::HashMap;

use crate::color::Rgb8;
use crate::des::axis;
use crate::style;

/// A generic color map that can be used to map scalar values to colors in a plot.
#[derive(Debug, Clone, PartialEq, Default)]
pub enum ColorMap {
/// A color map that automatically chooses a color map based on the data range and type.
/// When the data is floating point, it will use the viridis perceptual color map,
/// When the data is is string, it will use a categorical color map.
/// When the data is integer, a literal color map will be used, interpreting the integer values as RGBA 32 bit colors.
#[default]
Auto,
/// A color map that interpolates between colors in a specified color space.
Lerp(LerpColorMap),
/// A color map that uses a predefined set of colors for categorical data.
Cat(CatColorMap),
/// A color map that interprets string values as literal colors using `Rgb8::parse` and integer values as RGBA 32 bit colors.
Literal(LiteralColorMap),
}

impl From<LerpColorMap> for ColorMap {
fn from(cmap: LerpColorMap) -> Self {
ColorMap::Lerp(cmap)
}
}

impl From<CatColorMap> for ColorMap {
fn from(cmap: CatColorMap) -> Self {
ColorMap::Cat(cmap)
}
}

impl From<LiteralColorMap> for ColorMap {
fn from(cmap: LiteralColorMap) -> Self {
ColorMap::Literal(cmap)
}
}

/// Describes how to interpolate between colors in a color map, either in linear RGB or perceptual color space.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
Expand Down Expand Up @@ -177,12 +215,25 @@ impl From<(LerpMethod, &[Rgb8])> for LerpColorMap {
/// Returns None if the name is not recognized.
pub fn from_name(name: &str) -> Option<LerpColorMap> {
match name {
"stellar" => Some(stellar()),
"viridis" => Some(viridis()),
"stellar" => Some(stellar()),
_ => None,
}
}

/// The famous "viridis" color map from matplotlib
pub fn viridis() -> LerpColorMap {
const STOPS: &[Rgb8] = &[
Rgb8::from_hex(b"#440154"),
Rgb8::from_hex(b"#3b518a"),
Rgb8::from_hex(b"#208f8c"),
Rgb8::from_hex(b"#5bc862"),
Rgb8::from_hex(b"#fde724"),
];
let cmap: LerpColorMap = (LerpMethod::Perceptual, STOPS).into();
cmap.with_name("viridis")
}

/// A colormap that maps kelvin temperatures to black body color, with a range from 1000K to 15000K.
/// Based on the approximation from Tanner Helland:
/// https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html
Expand Down Expand Up @@ -244,15 +295,20 @@ pub fn stellar() -> LerpColorMap {
.with_name("stellar")
}

/// The famous "viridis" color map from matplotlib
pub fn viridis() -> LerpColorMap {
const STOPS: &[Rgb8] = &[
Rgb8::from_hex(b"#440154"),
Rgb8::from_hex(b"#3b518a"),
Rgb8::from_hex(b"#208f8c"),
Rgb8::from_hex(b"#5bc862"),
Rgb8::from_hex(b"#fde724"),
];
let cmap: LerpColorMap = (LerpMethod::Perceptual, STOPS).into();
cmap.with_name("viridis")
/// A categorical color map that maps a set of categories to a set of colors.
#[derive(Debug, Clone, PartialEq, Default)]
pub enum CatColorMap {
/// A categorical color map that pick colors based on the category type.
/// Each distinct category will be assigned a distinct color, in the order they are encoutered.
/// The colors are the one from the active series color palette.
#[default]
Auto,
/// A categorical color map that uses a predefined set of colors indexed by string categories
Strings(HashMap<String, style::series::Color>),
/// A categorical color map that uses a predefined set of colors indexed by integer categories
Integers(HashMap<i64, style::series::Color>),
}

/// A colormap that interpret string values as literal colors using `Rgb8::parse` and integer values as RGBA 32 bit colors.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct LiteralColorMap;
30 changes: 23 additions & 7 deletions src/des/series.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
//! Data series definitions for plots.
use crate::des::{axis, cmap};
use crate::des::axis;
use crate::des::cmap::ColorMap;
#[cfg(feature = "time")]
use crate::time;
use crate::{data, style};

/// A data column, either inline or a reference to a data source.
///
/// Data columns can contain either inline data (vectors of values) or references
/// to columns in a data source. This allows for flexible data handling in series.
/// to columns in a data source. This allows flexible data handling in series.
#[derive(Debug, Clone, PartialEq)]
pub enum DataCol {
/// The data is provided inline, directly in the series
Expand Down Expand Up @@ -328,6 +329,21 @@ impl Line {
/// Marker size is interpreted as an area, so the actual size of the marker will be proportional to the square root of the sizes data value
/// (e.g. for circle marker: diameter = sqrt(marker size * size column data)).
/// The sizes data column must have the same length as the x and y data columns.
///
/// Optional color data column can be used to specify the color of each marker, for colored scatter plots.
/// Interpretation of the color data depends on the type of data and on the type of the associated [`ColorMap`] field.
/// | data type | [`ColorMap`] variant | color interpretation |
/// |-------------|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
/// | f64 | [`ColorMap::Auto`] | Mapped to color by the [`cmap::viridis`] color map |
/// | f64 | [`ColorMap::Lerp`] | Mapped to color by the provided color map |
/// | String | [`ColorMap::Auto`] | Each unique string is interpreted as a category and assigned a color in order of series colors |
/// | String | [`ColorMap::Cat`] | Each unique string is interpreted as a category and mapped to color by the provided color map |
/// | String | [`ColorMap::Literal`] | Each string is parsed as a [`Rgba8`](crate::Rgba8) value |
/// | i64 | [`ColorMap::Auto`] | Cast to f64 and mapped to color by the [`cmap::viridis`] color map |
/// | i64 | [`ColorMap::Lerp`] | Cast to f64 and mapped to color by the provided color map |
/// | i64 | [`ColorMap::Literal`] | Interpreted as a color, where the integer is treated as a 32-bit RGBA value (e.g. 0xRRGGBBAA). See [`Rgba8::to_rgba_int`](crate::Rgba8::to_rgba_int). |
/// Other combinations of data type and color map will result in an error when rendering the plot.
/// If the plot's colorbar is set, the colorbar will be automatically configured based on the color data column and color map.
#[derive(Debug, Clone, PartialEq)]
pub struct Scatter {
x_data: DataCol,
Expand All @@ -338,7 +354,7 @@ pub struct Scatter {
y_axis: axis::Ref,
marker: style::series::Marker,
size_data: Option<DataCol>,
color_data: Option<(DataCol, cmap::LerpColorMap)>,
color_data: Option<(DataCol, ColorMap)>,
}

impl Scatter {
Expand Down Expand Up @@ -392,8 +408,8 @@ impl Scatter {
}

/// Set the color data column and color map, and return self for chaining
pub fn with_color_data(mut self, color_data: DataCol, color_map: cmap::LerpColorMap) -> Self {
self.color_data = Some((color_data, color_map));
pub fn with_color_data(mut self, color_data: DataCol, cmap: ColorMap) -> Self {
self.color_data = Some((color_data, cmap));
self
}

Expand Down Expand Up @@ -433,8 +449,8 @@ impl Scatter {
}

/// Get the color data column and color map, if any
pub fn color_data(&self) -> Option<&(DataCol, cmap::LerpColorMap)> {
self.color_data.as_ref()
pub fn color_data(&self) -> Option<(&DataCol, &ColorMap)> {
self.color_data.as_ref().map(|(data, cmap)| (data, cmap))
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/drawing/axis/bounds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ impl Bounds {
}
}

pub fn contains(&self, point: data::SampleRef<'_>) -> bool {
match (self, point) {
pub fn contains(&self, sample: data::SampleRef<'_>) -> bool {
match (self, sample) {
(Bounds::Num(nb), data::SampleRef::Num(n)) => nb.contains(n),
(Bounds::Cat(c), data::SampleRef::Cat(s)) => c.contains(s),
#[cfg(feature = "time")]
Expand Down
Loading
Loading