plotters icon indicating copy to clipboard operation
plotters copied to clipboard

[Feature Request] Dynamic dispatch should be allows as long as the plot is accepting the same data points

Open mlange-42 opened this issue 5 years ago • 4 comments

If the chart is defined to accept same data type, even though the axis is using different types of decorator, those chart types should be cvt into a trait object.

==== Original issue (one of valid use case)

First, many thanks for providing this great library!

Learning Rust for 2 weeks now and not really grasping the type system yet, this may be more of a Rust question than a plotters question.

I try to create a chart context with optional log-scaled y axis:

let mut cc = if y_log {
    ChartBuilder::on(&root)
                .build_ranged(
                    (xlim.0)..(xlim.1),
                    LogRange((ylim.0)..(ylim.1)),
                ).unwrap()
} else {
    ChartBuilder::on(&root)
                .build_ranged(
                    (xlim.0)..(xlim.1),
                    (ylim.0)..(ylim.1),
                ).unwrap()
} 

Now, my problem is that this doesn't compile due to:

expected type `plotters::chart::context::ChartContext<'_, _, plotters::coord::ranged::RangedCoord<_, plotters::coord::numeric::RangedCoordf64>>`
 found struct `plotters::chart::context::ChartContext<'_, _, plotters::coord::ranged::RangedCoord<_, plotters::coord::logarithmic::LogCoord<f64>>>`

When I use the condition in build_ranged

if y_log { LogRange((ylim.0)..(ylim.1)) } else { (ylim.0)..(ylim.1) }

I get a type mismatch between std::ops::Range and plotters::coord::logarithmic::LogRange.

I understand that I probably have to specify an explicit type for cc, probably with some dyn Trait in ChartContext's generics. However, due to my still very limited Rust experience, I can't get it to work.

Many thanks for your help!

mlange-42 avatar Mar 23 '20 17:03 mlange-42

Hmm, this is something unsupported yet. You just spotted the root cause of this.

The cc variable doesn't have a valid type, since it can not be ChartContext<RangedCoord<_, :RangedCoordf64>> and ChartContext<'_, _, RangedCoord<_, LogCoord<f64>>> at the same time.

One way to address that (not only in Rust but also other programming languages) is using dynamic dispatch, which is, in Rust term, a trait object. However, Plotters doesn't have a trait for those two types, so the compiler don't know how to do that.

One work around is to use static dispatch instead (use template function).

Hope this is helpful, and dynamic dispatch on charts with same data type should be supported. Thanks again to come up with this problem, hopefully Plotters will support dynamic dispatch i the future release.

Thanks for trying Plotters and hope you enjoy both the crate and Rust language.

I am changing this issue to a feature request if you don't mind.

Cheers!

38 avatar Apr 02 '20 01:04 38

Many thanks for the reply!

My solution so far was complete doubling of the plotting code, as you can see here: https://github.com/mlange-42/easy_graph/blob/master/src/ui/chart.rs#L406

Not really nice, but good to know that my limited Rust experience was not the sole reason.

And yes, I really enjoy Rust as well as plotters.

mlange-42 avatar Apr 02 '20 11:04 mlange-42

doubling of the plotting code

Yep, you got that. To make it nicer, you can make duplicated code reusable with a template function.

That is very often used trick in both Rust and C++ I believe. Since in C++ it's also not recommended to frequently use dynamic dispatch, or AKA virtual method functions.

38 avatar Apr 02 '20 19:04 38

For anyone else running into this issue, the trait bounds I ended up with to get a linear or logarithmic Y-axis were:

use std::convert::identity;
use std::ops::Range;
use std::fmt::Debug;

use plotters::chart::ChartBuilder;
use plotters::coord::combinators::IntoLogRange;
use plotters::coord::ranged1d::AsRangedCoord;
use plotters::coord::ranged1d::DefaultFormatting;
use plotters::coord::ranged1d::Ranged;
use plotters::coord::ranged1d::ValueFormatter;

pub fn plot_linear() {
    plot(identity);
}

pub fn plot_logarithmic() {
    plot(IntoLogRange::log_scale);
}

fn plot<S>(
    scale: fn(Range<u64>) -> S,
)
where
    S: AsRangedCoord<Value = u64>,
    S::CoordDescType: ValueFormatter<u64> + Ranged<FormatOption = DefaultFormatting>,
    S::Value: Debug,
{
    ChartBuilder::on(...)
        .build_cartesian_2d(..., scale(0u64..100u64))?;   
}

nwtnni avatar Sep 06 '22 05:09 nwtnni