1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
/*!
Use [Plotters](https://crates.io/crates/plotters) to draw plots in [Druid](https://crates.io/crates/druid).
![recording of interactive example](https://raw.githubusercontent.com/Pascal-So/plotters-druid/main/examples/plotters-druid-interactive-example.gif)
All the features of plotters should just work. Additionally, transparency is also supportet, i.e. you don't
have to fill the background with a solid colour as is usually done in plotters examples, the background can
instead just be whatever background colour is given through druid.
Note that this is not directly a plotters backend in the sense described in
[plotters_backend](https://docs.rs/plotters-backend/latest/plotters_backend/), instead this uses
the plotters-piet backend and wraps it in a struct that implements [`druid::Widget`].
You'll mainly need [`Plot::new`] from this crate.
# Example
For more complete examples see [the GitHub repo](https://github.com/Pascal-So/plotters-druid#examples)
```rust
# use druid::{Widget, WindowDesc, AppLauncher};
# use plotters_druid::Plot;
# use plotters::prelude::*;
# #[derive(Clone, druid::Data)]
# struct AppState;
fn build_plot_widget() -> impl Widget<AppState> {
Plot::new(|(width, height), data: &AppState, root| {
root.fill(&WHITE).unwrap();
let mut chart = ChartBuilder::on(&root)
.build_cartesian_2d(-1f32..1f32, -0.1f32..1f32)
.unwrap();
// see the plotters documentation on how to use `chart`
})
}
# fn main() {
let main_window = WindowDesc::new(build_plot_widget());
# }
```
# Limitations
It's currently not possible to propagate errors that might be returned from the plotters API. Right now
this means you'll probably have to use `.unwrap()` a lot in the closure that you pass to [`Plot::new`],
or alternatively just log it and shrug.
The possible errors in there mostly come from the drawing backend, e.g. cairo / direct2d / whatever piet
uses on your platform. Just directly propagating these in the widget's draw function doesn't really make
sense because it's not clear what druid is supposed to do with these errors. Ideally we'd probably change
something in the data to notify the rest of the application of the error. If anyone has a good suggestion
for a possible API feel free to open an issue.
*/
use druid::{Data, Widget};
use plotters::{
coord::Shift,
prelude::{DrawingArea, IntoDrawingArea},
};
use plotters_piet::PietBackend;
/// The type of a plot widget.
///
/// See [`Plot::new`] for information on how to construct this.
///
/// This implements [`druid::Widget`] so it can be used like
/// any other widget type.
/// ```rust
/// # use druid::{Widget, WindowDesc, AppLauncher};
/// # use plotters_druid::Plot;
/// fn build_plot_widget() -> impl Widget<()> {
/// // ... construct and return widget using Plot::new()
/// # Plot::new(|_, _, _|{})
/// }
///
/// # fn main() {
/// let main_window = WindowDesc::new(build_plot_widget());
/// # }
/// ```
pub struct Plot<T: Data> {
#[allow(clippy::type_complexity)]
plot: Box<dyn Fn((u32, u32), &T, &DrawingArea<PietBackend, Shift>)>,
}
impl<T: Data> Plot<T> {
/// Create a plot widget
///
/// This takes a function that should draw the plot using the normal plotters API.
/// The function has access to the width and height of the plotting area, to the
/// [`Data`] of the rust widget, and to a plotters [`DrawingArea`].
///
/// ```rust
/// # use plotters_druid::Plot;
/// # use plotters::prelude::*;
/// # #[derive(Clone, druid::Data)]
/// # struct AppState;
/// Plot::new(|(width, height), data: &AppState, root| {
/// root.fill(&WHITE).unwrap();
/// let mut chart = ChartBuilder::on(&root)
/// .build_cartesian_2d(-1f32..1f32, -0.1f32..1f32)
/// .unwrap();
///
/// // see the plotters documentation on how to use `chart`
/// });
/// ```
pub fn new(f: impl Fn((u32, u32), &T, &DrawingArea<PietBackend, Shift>) + 'static) -> Plot<T> {
Plot { plot: Box::new(f) }
}
}
impl<T> Widget<T> for Plot<T>
where
T: Data,
{
fn event(&mut self, _: &mut druid::EventCtx, _: &druid::Event, _: &mut T, _: &druid::Env) {}
fn lifecycle(
&mut self,
_: &mut druid::LifeCycleCtx,
_: &druid::LifeCycle,
_: &T,
_: &druid::Env,
) {
}
fn update(&mut self, ctx: &mut druid::UpdateCtx, old_data: &T, data: &T, _env: &druid::Env) {
if !old_data.same(data) {
ctx.request_paint();
}
}
fn layout(
&mut self,
_: &mut druid::LayoutCtx,
bc: &druid::BoxConstraints,
_: &T,
_: &druid::Env,
) -> druid::Size {
bc.max()
}
fn paint(&mut self, ctx: &mut druid::PaintCtx, data: &T, _: &druid::Env) {
let druid::Size { width, height } = ctx.size();
let size = (width as u32, height as u32);
let backend = PietBackend {
size,
render_ctx: ctx.render_ctx,
};
(self.plot)(size, data, &backend.into_drawing_area());
}
}