ChartContext extension: Lost in trait bound hell

I have this wrapper object, which is doing its job:

use std::error::Error;
use std::time::Duration;

use plotters::backend::BitMapBackend;
use plotters::chart::ChartContext;
use plotters::coord::types::RangedCoordu64;
use plotters::element::EmptyElement;
use plotters::prelude::{Cartesian2d, Circle, LineSeries, PointSeries, RGBColor, Text, BLUE, RED};
use plotters::style::IntoFont;

use crate::BrightTemp;

pub struct Plotter<'a> {
    inner: ChartContext<'a, BitMapBackend<'a>, Cartesian2d<RangedCoordu64, RangedCoordu64>>,
}

impl Plotter<'_> {
    pub fn plot(&mut self, items: &[(Duration, BrightTemp)]) -> Result<(), Box<dyn Error>> {
        self.plot_series_and_dots(
            items
                .iter()
                .filter_map(|(t, b)| {
                    b.brightness()
                        .map(|brightness| (u64::from(brightness), t.as_secs()))
                })
                .collect(),
            RED,
            "Brightness",
        )?;

        self.plot_series_and_dots(
            items
                .iter()
                .filter_map(|(t, b)| {
                    b.color_temperature()
                        .map(|color_temperature| (u64::from(color_temperature), t.as_secs()))
                })
                .collect(),
            BLUE,
            "Color temperature",
        )?;

        Ok(())
    }

    fn plot_series_and_dots(
        &mut self,
        items: Vec<(u64, u64)>,
        style: RGBColor,
        label: &str,
    ) -> Result<(), Box<dyn Error>> {
        self.plot_series(items.clone(), style, label)?;
        self.plot_dots(items, style)
    }

    /// Draw a line series.
    fn plot_series(
        &mut self,
        items: Vec<(u64, u64)>,
        style: RGBColor,
        label: &str,
    ) -> Result<(), Box<dyn Error>> {
        self.inner
            .draw_series(LineSeries::new(items, style))?
            .label(label);
        Ok(())
    }

    /// Draw a point series.
    fn plot_dots(&mut self, items: Vec<(u64, u64)>, style: RGBColor) -> Result<(), Box<dyn Error>> {
        self.inner
            .draw_series(PointSeries::of_element(items, 5, style, &|c, s, st| {
                EmptyElement::at(c)    // We want to construct a composed element on-the-fly
                    + Circle::new((0, 0), s, st.filled()) // At this point, the new pixel coordinate is established
                    + Text::new(format!("{c:?}"), (10, 0), ("sans-serif", 10).into_font())
            }))?;
        Ok(())
    }
}

impl<'a> From<ChartContext<'a, BitMapBackend<'a>, Cartesian2d<RangedCoordu64, RangedCoordu64>>>
    for Plotter<'a>
{
    fn from(
        inner: ChartContext<'a, BitMapBackend<'a>, Cartesian2d<RangedCoordu64, RangedCoordu64>>,
    ) -> Self {
        Self { inner }
    }
}

impl<'a> From<Plotter<'a>>
    for ChartContext<'a, BitMapBackend<'a>, Cartesian2d<RangedCoordu64, RangedCoordu64>>
{
    fn from(plotter: Plotter<'a>) -> Self {
        plotter.inner
    }
}

However, I'd prefer implementing the respective methods on an extension trait of ChartContext<'_, DB, CT> instead.

However, I cannot find out, how to resolve the required trait bounds for plot_dots():

use plotters::element::{Circle, EmptyElement, PointCollection, Text};
use plotters::prelude::{
    ChartContext, CoordTranslate, DrawingBackend, DynElement, IntoFont, LineSeries, PointSeries,
    RGBColor,
};
use std::error::Error;

pub trait ChartContextExt {
    fn plot_series_and_dots(
        &mut self,
        items: Vec<(u64, u64)>,
        style: RGBColor,
        label: &str,
    ) -> Result<(), Box<dyn Error>> {
        self.plot_series(items.clone(), style, label)?;
        self.plot_dots(items, style)
    }

    /// Draw a line series.
    fn plot_series(
        &mut self,
        items: Vec<(u64, u64)>,
        style: RGBColor,
        label: &str,
    ) -> Result<(), Box<dyn Error>>;

    /// Draw a point series.
    fn plot_dots(&mut self, items: Vec<(u64, u64)>, style: RGBColor) -> Result<(), Box<dyn Error>>;
}

impl<DB, CT> ChartContextExt for ChartContext<'_, DB, CT>
where
    DB: DrawingBackend,
    CT: CoordTranslate,
    <DB as DrawingBackend>::ErrorType: 'static,
    for<'b> &'b DynElement<'static, DB, (u64, u64)>:
        PointCollection<'b, <CT as CoordTranslate>::From>,
{
    /// Draw a line series.
    fn plot_series(
        &mut self,
        items: Vec<(u64, u64)>,
        style: RGBColor,
        label: &str,
    ) -> Result<(), Box<dyn Error>> {
        self.draw_series(LineSeries::new(items, style))?
            .label(label);
        Ok(())
    }

    /// Draw a point series.
    fn plot_dots(&mut self, items: Vec<(u64, u64)>, style: RGBColor) -> Result<(), Box<dyn Error>> {
        self.draw_series(PointSeries::of_element(items, 5, style, &|c, s, st| {
            EmptyElement::at(c)    // We want to construct a composed element on-the-fly
                + Circle::new((0, 0), s, st.filled()) // At this point, the new pixel coordinate is established
                + Text::new(format!("{c:?}"), (10, 0), ("sans-serif", 10).into_font())
        }))?;
        Ok(())
    }
}

I cannot get rid of the error:

~/RustroverProjects/smik-testsuite> cargo build                                                                                                                                                                  2024-12-05T15:43:38
   Compiling smik-testsuite v0.1.0 (/home/neumann/RustroverProjects/smik-testsuite)
error[E0277]: the trait bound `ComposedElement<(u64, u64), _, Circle<(i32, i32), {integer}>, Text<'_, (i32, i32), String>>: Borrow<DynElement<'static, DB, (u64, u64)>>` is not satisfied
   --> src/plotting/chart_context_ext.rs:53:14
    |
53  |         self.draw_series(PointSeries::of_element(items, 5, style, &|c, s, st| {
    |              ^^^^^^^^^^^ the trait `Borrow<DynElement<'static, DB, (u64, u64)>>` is not implemented for `ComposedElement<(u64, u64), _, Circle<(i32, i32), {integer}>, Text<'_, (i32, i32), String>>`
    |
note: required by a bound in `ChartContext::<'a, DB, CT>::draw_series`
   --> /home/neumann/.cargo/registry/src/index.crates.io-6f17d22bba15001f/plotters-0.3.7/src/chart/context.rs:128:12
    |
120 |     pub fn draw_series<B, E, R, S>(
    |            ----------- required by a bound in this associated function
...
128 |         R: Borrow<E>,
    |            ^^^^^^^^^ required by this bound in `ChartContext::<'a, DB, CT>::draw_series`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `smik-testsuite` (lib) due to 1 previous error
~/RustroverProjects/smik-testsuite>    

Can somebody help me out here?

Looks like it might be the elided lifetime?

You might be able to implement the trait for ChartContext<'static, .... Or you could (should) figure out how these lifetimes relate, if they do.

But, I'm a little rusty (ha!) at the moment and not familiar at all with these types/traits, so I could be completely wrong.

1 Like

Alas, no dice with 'static. Also I would not even know where to begin to start resolving the lifetimes and trait bounds. I'll keep the wrapper object for now, since spending even more time on this is non-economic for me.