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
Binary file modified gallery/bitcoin.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion gallery/bode_rlc.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified gallery/bode_rlc_macchiato.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified gallery/bode_rlc_mocha.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified gallery/bouncing_ball.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified gallery/gauss.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified gallery/iris.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified gallery/sine.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added gallery/stars.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/des.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub mod series;

pub use annot::Annotation;
pub use axis::Axis;
pub use colorbar::{ColorBar, ColorBarPos};
pub use colorbar::ColorBar;
pub use figure::{FigLegend, Figure};
pub use legend::Legend;
pub use plot::{Plot, PlotLegend, Subplots};
Expand Down
37 changes: 31 additions & 6 deletions src/des/cmap.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! A module for defining color maps that can be used in the design of plots to map scalar values to colors.

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

/// 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)]
Expand All @@ -21,6 +22,7 @@ pub struct LerpColorMap {
end: Rgb8,
stops: Vec<(f32, Rgb8)>,
data_range: Option<(f64, f64)>,
locator: Option<axis::ticks::Locator>,
}

impl LerpColorMap {
Expand All @@ -31,6 +33,7 @@ impl LerpColorMap {
start,
end,
data_range: None,
locator: None,
stops: Vec::new(),
}
}
Expand All @@ -45,8 +48,11 @@ impl LerpColorMap {
self
}

/// Set the range of scalar values that this color map maps to, as (min, max).
pub fn with_data_range(mut self, range: (f64, f64)) -> Self {
/// Force the range of scalar data values that this color map maps to, as (min, max).
///
/// By default, the colormap will map the range of data values in the plot, but this can be overridden with this method.
/// Use this if only a specific range of data are meaningful to map to colors.
pub fn force_data_range(mut self, range: (f64, f64)) -> Self {
assert!(
range.0.is_finite() && range.1.is_finite(),
"Color map data range must be finite"
Expand All @@ -59,6 +65,14 @@ impl LerpColorMap {
self
}

/// Force the ticks of colorbar mapping this colormap to be located according to the given locator.
/// By default, the locator is automatic, but this can be overridden with this method.
/// Use this if you want to have specific control over the ticks of the colorbar, for example to place them at specific data values.
pub fn force_ticks_locator(mut self, locator: axis::ticks::Locator) -> Self {
self.locator = Some(locator);
self
}

/// Get the interpolation method used by this color map.
pub fn method(&self) -> LerpMethod {
self.method
Expand All @@ -79,11 +93,16 @@ impl LerpColorMap {
&self.stops
}

/// Get the range of scalar values that this color map maps to, if it has one.
/// Get the range of scalar values that this colormap is forced to map to, if it has one.
/// If None, the color map is assumed to map the range of data values in the plot.
pub fn data_range(&self) -> Option<(f64, f64)> {
pub fn forced_data_range(&self) -> Option<(f64, f64)> {
self.data_range
}

/// Get the ticks locator that this colormap is forced to use for its colorbar, if it has one.
pub fn forced_ticks_locator(&self) -> Option<&axis::ticks::Locator> {
self.locator.as_ref()
}
}

impl From<(LerpMethod, &[Rgb8])> for LerpColorMap {
Expand Down Expand Up @@ -154,8 +173,14 @@ pub fn stellar() -> LerpColorMap {
.with_stop(stop_for_temp(8000.0))
.with_stop(stop_for_temp(9000.0))
.with_stop(stop_for_temp(10000.0))
.with_stop(stop_for_temp(12000.0))
.with_data_range((MIN_TEMP, MAX_TEMP))
.with_stop(stop_for_temp(12500.0))
.force_data_range((MIN_TEMP, MAX_TEMP))
.force_ticks_locator(axis::ticks::Locator::List(
vec![
1000.0, 2000.0, 3000.0, 4000.0, 5000.0, 6500.0, 8000.0, 10000.0, 12500.0, 15000.0,
]
.into(),
))
}

/// The famous "viridis" color map from matplotlib
Expand Down
28 changes: 21 additions & 7 deletions src/des/colorbar.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! Color bar configuration
use crate::des::axis;
use crate::style::{defaults, theme};
use crate::text;

Expand All @@ -12,7 +13,7 @@ impl Default for TitleProps {

/// Position of a color bar relatively to the plot
#[derive(Debug, Default, Clone, Copy)]
pub enum ColorBarPos {
pub enum Pos {
/// Position the color bar above the plot area
Top,
/// Position the color bar to the right of the plot area (default)
Expand Down Expand Up @@ -48,18 +49,19 @@ impl Default for TicksFont {
/// ColorBar configuration for a plot
#[derive(Debug, Clone)]
pub struct ColorBar {
pos: ColorBarPos,
pos: Pos,
width: f32,
title: Option<Title>,
ticks_font: TicksFont,
border: Option<theme::Stroke>,
locator: axis::ticks::Locator,
margin: f32,
}

impl Default for ColorBar {
fn default() -> Self {
Self {
pos: ColorBarPos::default(),
pos: Pos::default(),
width: defaults::COLORBAR_WIDTH,
title: None,
ticks_font: TicksFont::default(),
Expand All @@ -69,14 +71,15 @@ impl Default for ColorBar {
pattern: Default::default(),
opacity: None,
}),
locator: axis::ticks::Locator::Auto,
margin: defaults::COLORBAR_MARGIN,
}
}
}

impl ColorBar {
/// Create a new color bar with the specified position
pub fn new(pos: ColorBarPos) -> Self {
pub fn new(pos: Pos) -> Self {
Self {
pos,
..Default::default()
Expand Down Expand Up @@ -107,14 +110,20 @@ impl ColorBar {
self
}

/// Set the ticks locator and return self for chaining
pub fn with_ticks_locator(mut self, locator: axis::ticks::Locator) -> Self {
self.locator = locator;
self
}

/// Set the margin between the color bar and the plot area and return self for chaining
pub fn with_margin(mut self, margin: f32) -> Self {
self.margin = margin;
self
}

/// Get the position of the color bar
pub fn pos(&self) -> ColorBarPos {
pub fn pos(&self) -> Pos {
self.pos
}

Expand All @@ -138,14 +147,19 @@ impl ColorBar {
self.border.as_ref()
}

/// Get the ticks locator
pub fn ticks_locator(&self) -> &axis::ticks::Locator {
&self.locator
}

/// Get the margin between the color bar and the plot area
pub fn margin(&self) -> f32 {
self.margin
}
}

impl From<ColorBarPos> for ColorBar {
fn from(pos: ColorBarPos) -> Self {
impl From<Pos> for ColorBar {
fn from(pos: Pos) -> Self {
Self::new(pos)
}
}
15 changes: 11 additions & 4 deletions src/drawing/cmap.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::sync::Arc;

use crate::color::{Lerp, LinRgb, OkLab, Rgb8, Xyz};
use crate::des;
use crate::des::cmap::{LerpColorMap, LerpMethod};
use crate::drawing::axis;

Expand All @@ -14,7 +15,8 @@ pub trait ColorMap {
pub trait AsColorMap {
fn hash(&self) -> u64;

fn data_range(&self) -> Option<axis::Bounds>;
fn forced_data_range(&self) -> Option<axis::Bounds>;
fn forced_ticks_locator(&self) -> Option<&des::axis::ticks::Locator>;

/// Convert this type to a `ColorMap` implementation that can be used for color mapping.
fn as_color_map(&self) -> Arc<dyn ColorMap>;
Expand All @@ -36,18 +38,23 @@ impl AsColorMap for LerpColorMap {
pos_bits.hash(&mut hasher);
stop.1.hash(&mut hasher);
}
if let Some(range) = self.data_range() {
if let Some(range) = self.forced_data_range() {
range.0.to_bits().hash(&mut hasher);
range.1.to_bits().hash(&mut hasher);
}
// TODO: hash the locator
hasher.finish()
}

fn data_range(&self) -> Option<axis::Bounds> {
self.data_range()
fn forced_data_range(&self) -> Option<axis::Bounds> {
self.forced_data_range()
.map(|rng| axis::NumBounds::from(rng).into())
}

fn forced_ticks_locator(&self) -> Option<&des::axis::ticks::Locator> {
self.forced_ticks_locator()
}

fn as_color_map(&self) -> Arc<dyn ColorMap> {
let start = self.start();
let end = self.end();
Expand Down
28 changes: 18 additions & 10 deletions src/drawing/colorbar.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::fmt;
use std::sync::Arc;

use crate::des::{self, ColorBarPos};
use crate::des::axis::ticks::Locator;
use crate::des::{self, colorbar};
use crate::drawing::axis::{self, AsBoundRef};
use crate::drawing::cmap::{AsColorMap, ColorMap};
use crate::drawing::{Ctx, Text, ticks};
Expand All @@ -27,14 +28,21 @@ pub struct ColorBarBuilder {
hash: u64,
cmap: Arc<dyn ColorMap>,
data_bounds: axis::Bounds,
locator: Locator,
}

impl ColorBarBuilder {
pub fn new(hash: u64, cmap: Arc<dyn ColorMap>, data_bounds: axis::Bounds) -> Self {
pub fn new(
hash: u64,
cmap: Arc<dyn ColorMap>,
data_bounds: axis::Bounds,
locator: Locator,
) -> Self {
Self {
hash,
cmap,
data_bounds,
locator,
}
}

Expand All @@ -55,10 +63,10 @@ impl ColorBarBuilder {
D: data::Source + ?Sized,
{
let side = match des.pos() {
ColorBarPos::Right => axis::Side::Right,
ColorBarPos::Left => axis::Side::Left,
ColorBarPos::Top => axis::Side::Top,
ColorBarPos::Bottom => axis::Side::Bottom,
colorbar::Pos::Right => axis::Side::Right,
colorbar::Pos::Left => axis::Side::Left,
colorbar::Pos::Top => axis::Side::Top,
colorbar::Pos::Bottom => axis::Side::Bottom,
};

let title = des
Expand All @@ -74,10 +82,10 @@ impl ColorBarBuilder {
let font = des.ticks_font().clone();
let scale: des::axis::Scale =
des::axis::Range::new(Some(nb.start()), Some(nb.end())).into();
let locator = des::axis::ticks::Locator::Auto;
let formatter = des::axis::ticks::Formatter::Auto;
let ticks = ticks::locate_num(&locator, *nb, &scale)?;
let formatter = ticks::num_label_formatter(&locator, Some(&formatter), *nb, &scale);
let ticks = ticks::locate_num(&self.locator, *nb, &scale)?;
let formatter =
ticks::num_label_formatter(&self.locator, Some(&formatter), *nb, &scale);
ticks
.into_iter()
.map(|t| -> Result<_, super::Error> {
Expand Down Expand Up @@ -166,7 +174,7 @@ impl ColorDataMap for ColorBar {
}

impl ColorBar {
pub fn pos(&self) -> ColorBarPos {
pub fn pos(&self) -> colorbar::Pos {
self.des.pos()
}

Expand Down
21 changes: 13 additions & 8 deletions src/drawing/plot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::cell::RefCell;
use std::f32;
use std::rc::Rc;

use crate::des::{PlotIdx, annot};
use crate::des::{PlotIdx, annot, colorbar};
use crate::drawing::annot::Annot;
use crate::drawing::axis::{AsBoundRef, Axis, AxisScale, Bounds, Side};
use crate::drawing::colorbar::{ColorBar, ColorBarBuilder};
Expand Down Expand Up @@ -515,7 +515,7 @@ where

for_each_series(des_plot, |s| {
if let Some(entry) = s.colorbar_entry() {
let forced_bounds = entry.cmap.data_range();
let forced_bounds = entry.cmap.forced_data_range();
let has_forced_bounds = forced_bounds.is_some();
let bounds = if let Some(range) = forced_bounds {
range
Expand All @@ -525,6 +525,10 @@ where
.expect("Should get bounds for colormap data column")
};

let locator = entry
.cmap
.forced_ticks_locator()
.unwrap_or(des_colorbar.ticks_locator());
let hash = entry.cmap.hash();

if let Some(cbb) = builders.iter_mut().find(|b| b.hash() == hash) {
Expand All @@ -541,6 +545,7 @@ where
hash,
entry.cmap.as_color_map(),
bounds,
locator.clone(),
));
}
}
Expand Down Expand Up @@ -912,18 +917,18 @@ fn y_side_matches_out_legend_pos(side: des::axis::Side, legend_pos: des::plot::L
}
}

fn x_side_matches_colorbar_pos(side: des::axis::Side, pos: des::ColorBarPos) -> bool {
fn x_side_matches_colorbar_pos(side: des::axis::Side, pos: colorbar::Pos) -> bool {
match (side, pos) {
(des::axis::Side::Main, des::ColorBarPos::Bottom) => true,
(des::axis::Side::Opposite, des::ColorBarPos::Top) => true,
(des::axis::Side::Main, colorbar::Pos::Bottom) => true,
(des::axis::Side::Opposite, colorbar::Pos::Top) => true,
_ => false,
}
}

fn y_side_matches_colorbar_pos(side: des::axis::Side, pos: des::ColorBarPos) -> bool {
fn y_side_matches_colorbar_pos(side: des::axis::Side, pos: colorbar::Pos) -> bool {
match (side, pos) {
(des::axis::Side::Main, des::ColorBarPos::Left) => true,
(des::axis::Side::Opposite, des::ColorBarPos::Right) => true,
(des::axis::Side::Main, colorbar::Pos::Left) => true,
(des::axis::Side::Opposite, colorbar::Pos::Right) => true,
_ => false,
}
}
Expand Down
Binary file added tests/refs/colorbar/auto-range.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading