Skip to content

Commit 437d477

Browse files
committed
Cleanup the chart context code
1 parent 14cf15d commit 437d477

File tree

8 files changed

+265
-272
lines changed

8 files changed

+265
-272
lines changed

plotters-doc-data

src/chart/builder.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,18 @@ impl<'a, 'b, DB: DrawingBackend> ChartBuilder<'a, 'b, DB> {
206206
actual_drawing_area_pos[idx] += split_point;
207207
}
208208

209+
// Now the root drawing area is to be split into
210+
//
211+
// +----------+------------------------------+------+
212+
// | 0 | 1 (Top Label Area) | 2 |
213+
// +----------+------------------------------+------+
214+
// | 3 | | 5 |
215+
// | Left | 4 (Plotting Area) | Right|
216+
// | Labels | | Label|
217+
// +----------+------------------------------+------+
218+
// | 6 | 7 (Bottom Labels) | 8 |
219+
// +----------+------------------------------+------+
220+
209221
let mut split: Vec<_> = drawing_area
210222
.split_by_breakpoints(
211223
&actual_drawing_area_pos[2..4],
@@ -215,8 +227,11 @@ impl<'a, 'b, DB: DrawingBackend> ChartBuilder<'a, 'b, DB> {
215227
.map(Some)
216228
.collect();
217229

230+
// Take out the plotting area
218231
std::mem::swap(&mut drawing_area, split[4].as_mut().unwrap());
219232

233+
// Initialize the label areas - since the label area might be overlapping
234+
// with the plotting area, in this case, we need handle them differently
220235
for (src_idx, dst_idx) in [1, 7, 3, 5].iter().zip(0..4) {
221236
if !self.overlap_plotting_area[dst_idx] {
222237
let (h, w) = split[*src_idx].as_ref().unwrap().dim_in_pixel();

src/chart/context.rs

Lines changed: 26 additions & 263 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,26 @@
11
use std::borrow::Borrow;
2-
use std::marker::PhantomData;
32
use std::ops::Range;
4-
use std::sync::Arc;
53

6-
use super::dual_coord::DualCoordChartContext;
7-
use super::mesh::MeshStyle;
8-
use super::series::SeriesLabelStyle;
4+
use super::{DualCoordChartContext, MeshStyle, SeriesAnno, SeriesLabelStyle};
95

106
use crate::coord::{
117
AsRangedCoord, CoordTranslate, KeyPointHint, MeshLine, Ranged, RangedCoord,
128
ReverseCoordTranslate, Shift, ValueFormatter,
139
};
1410
use crate::drawing::{DrawingArea, DrawingAreaErrorKind};
15-
use crate::element::{Drawable, DynElement, IntoDynElement, PathElement, PointCollection};
11+
use crate::element::{Drawable, PathElement, PointCollection};
1612
use crate::style::text_anchor::{HPos, Pos, VPos};
17-
use crate::style::{AsRelative, ShapeStyle, SizeDesc, TextStyle};
13+
use crate::style::{ShapeStyle, TextStyle};
1814

1915
use plotters_backend::{BackendCoord, DrawingBackend, FontTransform};
2016

21-
/// The annotations (such as the label of the series, the legend element, etc)
22-
/// When a series is drawn onto a drawing area, an series annotation object
23-
/// is created and a mutable reference is returned.
24-
#[allow(clippy::type_complexity)]
25-
pub struct SeriesAnno<'a, DB: DrawingBackend> {
26-
label: Option<String>,
27-
draw_func: Option<Box<dyn Fn(BackendCoord) -> DynElement<'a, DB, BackendCoord> + 'a>>,
28-
phantom_data: PhantomData<DB>,
29-
}
30-
31-
impl<'a, DB: DrawingBackend> SeriesAnno<'a, DB> {
32-
pub(crate) fn get_label(&self) -> &str {
33-
self.label.as_ref().map(|x| x.as_str()).unwrap_or("")
34-
}
35-
36-
pub(crate) fn get_draw_func(
37-
&self,
38-
) -> Option<&dyn Fn(BackendCoord) -> DynElement<'a, DB, BackendCoord>> {
39-
self.draw_func.as_ref().map(|x| x.borrow())
40-
}
41-
42-
fn new() -> Self {
43-
Self {
44-
label: None,
45-
draw_func: None,
46-
phantom_data: PhantomData,
47-
}
48-
}
49-
50-
/// Set the series label
51-
/// - `label`: The string would be use as label for current series
52-
pub fn label<L: Into<String>>(&mut self, label: L) -> &mut Self {
53-
self.label = Some(label.into());
54-
self
55-
}
56-
57-
/// Set the legend element creator function
58-
/// - `func`: The function use to create the element
59-
/// *Note*: The creation function uses a shifted pixel-based coordinate system. And place the
60-
/// point (0,0) to the mid-right point of the shape
61-
pub fn legend<E: IntoDynElement<'a, DB, BackendCoord>, T: Fn(BackendCoord) -> E + 'a>(
62-
&mut self,
63-
func: T,
64-
) -> &mut Self {
65-
self.draw_func = Some(Box::new(move |p| func(p).into_dyn()));
66-
self
67-
}
68-
}
69-
7017
/// The context of the chart. This is the core object of Plotters.
7118
/// Any plot/chart is abstracted as this type, and any data series can be placed to the chart
7219
/// context.
20+
///
21+
/// - To draw a series on a chart context, use [ChartContext::draw_series](struct.ChartContext.html#method.draw_series)
22+
/// - To draw a single element to the chart, you may want to use [ChartContext::plotting_area](struct.ChartContext.html#method.plotting_area)
23+
///
7324
pub struct ChartContext<'a, DB: DrawingBackend, CT: CoordTranslate> {
7425
pub(super) x_label_area: [Option<DrawingArea<DB, Shift>>; 2],
7526
pub(super) y_label_area: [Option<DrawingArea<DB, Shift>>; 2],
@@ -78,131 +29,16 @@ pub struct ChartContext<'a, DB: DrawingBackend, CT: CoordTranslate> {
7829
pub(super) drawing_area_pos: (i32, i32),
7930
}
8031

81-
/// A chart context state - This is the data that is needed to reconstruct the chart context
82-
/// without actually drawing the chart. This is useful when we want to do realtime rendering and
83-
/// want to incrementally update the chart.
84-
///
85-
/// For each frame, instead of updating the entire backend, we are able to keep the keep the figure
86-
/// component like axis, labels untouched and make updates only in the plotting drawing area.
87-
/// This is very useful for incremental render.
88-
/// ```rust
89-
/// use plotters::prelude::*;
90-
/// let mut buffer = vec![0u8;1024*768*3];
91-
/// let area = BitMapBackend::with_buffer(&mut buffer[..], (1024, 768))
92-
/// .into_drawing_area()
93-
/// .split_evenly((1,2));
94-
/// let chart = ChartBuilder::on(&area[0])
95-
/// .caption("Incremental Example", ("sans-serif", 20))
96-
/// .set_all_label_area_size(30)
97-
/// .build_ranged(0..10, 0..10)
98-
/// .expect("Unable to build ChartContext");
99-
/// // Draw the first frame at this point
100-
/// area[0].present().expect("Present");
101-
/// let state = chart.into_chart_state();
102-
/// // Let's draw the second frame
103-
/// let chart = state.restore(&area[0]);
104-
/// chart.plotting_area().fill(&WHITE).unwrap(); // Clear the previously drawn graph
105-
/// // At this point, you are able to draw next frame
106-
///```
107-
pub struct ChartState<CT: CoordTranslate> {
108-
drawing_area_pos: (i32, i32),
109-
drawing_area_size: (u32, u32),
110-
coord: CT,
111-
}
112-
113-
impl<'a, CT: CoordTranslate + Clone> Clone for ChartState<CT> {
114-
fn clone(&self) -> Self {
115-
Self {
116-
drawing_area_size: self.drawing_area_size,
117-
drawing_area_pos: self.drawing_area_pos,
118-
coord: self.coord.clone(),
119-
}
120-
}
121-
}
122-
123-
impl<'a, DB: DrawingBackend, CT: CoordTranslate> From<ChartContext<'a, DB, CT>> for ChartState<CT> {
124-
fn from(chart: ChartContext<'a, DB, CT>) -> ChartState<CT> {
125-
ChartState {
126-
drawing_area_pos: chart.drawing_area_pos,
127-
drawing_area_size: chart.drawing_area.dim_in_pixel(),
128-
coord: chart.drawing_area.into_coord_spec(),
129-
}
130-
}
131-
}
132-
133-
impl<'a, DB: DrawingBackend, CT: CoordTranslate> ChartContext<'a, DB, CT> {
134-
/// Convert a chart context into a chart state, by doing so, the chart context is consumed and
135-
/// a saved chart state is created for later use. This is typically used in incrmental rendering. See documentation of `ChartState` for more detailed example.
136-
pub fn into_chart_state(self) -> ChartState<CT> {
137-
self.into()
138-
}
139-
140-
/// Convert the chart context into a sharable chart state.
141-
/// Normally a chart state can not be clone, since the coordinate spec may not be able to be
142-
/// cloned. In this case, we can use an `Arc` get the coordinate wrapped thus the state can be
143-
/// cloned and shared by multiple chart context
144-
pub fn into_shared_chart_state(self) -> ChartState<Arc<CT>> {
145-
ChartState {
146-
drawing_area_pos: self.drawing_area_pos,
147-
drawing_area_size: self.drawing_area.dim_in_pixel(),
148-
coord: Arc::new(self.drawing_area.into_coord_spec()),
149-
}
150-
}
151-
}
152-
153-
impl<'a, 'b, DB, CT> From<&ChartContext<'a, DB, CT>> for ChartState<CT>
32+
impl<'a, DB, XT, YT, X, Y> ChartContext<'a, DB, RangedCoord<X, Y>>
15433
where
15534
DB: DrawingBackend,
156-
CT: CoordTranslate + Clone,
157-
{
158-
fn from(chart: &ChartContext<'a, DB, CT>) -> ChartState<CT> {
159-
ChartState {
160-
drawing_area_pos: chart.drawing_area_pos,
161-
drawing_area_size: chart.drawing_area.dim_in_pixel(),
162-
coord: chart.drawing_area.as_coord_spec().clone(),
163-
}
164-
}
165-
}
166-
167-
impl<'a, DB: DrawingBackend, CT: CoordTranslate + Clone> ChartContext<'a, DB, CT> {
168-
/// Make the chart context, do not consume the chart context and clone the coordinate spec
169-
pub fn to_chart_state(&self) -> ChartState<CT> {
170-
self.into()
171-
}
172-
}
173-
174-
impl<CT: CoordTranslate> ChartState<CT> {
175-
/// Restore the chart context on the given drawing area
176-
///
177-
/// - `area`: The given drawing area where we want to restore the chart context
178-
/// - **returns** The newly created chart context
179-
pub fn restore<'a, DB: DrawingBackend>(
180-
self,
181-
area: &DrawingArea<DB, Shift>,
182-
) -> ChartContext<'a, DB, CT> {
183-
let area = area
184-
.clone()
185-
.shrink(self.drawing_area_pos, self.drawing_area_size);
186-
ChartContext {
187-
x_label_area: [None, None],
188-
y_label_area: [None, None],
189-
drawing_area: area.apply_coord_spec(self.coord),
190-
series_anno: vec![],
191-
drawing_area_pos: self.drawing_area_pos,
192-
}
193-
}
194-
}
195-
196-
impl<
197-
'a,
198-
DB: DrawingBackend,
199-
XT,
200-
YT,
201-
X: Ranged<ValueType = XT> + ValueFormatter<XT>,
202-
Y: Ranged<ValueType = YT> + ValueFormatter<YT>,
203-
> ChartContext<'a, DB, RangedCoord<X, Y>>
35+
X: Ranged<ValueType = XT> + ValueFormatter<XT>,
36+
Y: Ranged<ValueType = YT> + ValueFormatter<YT>,
20437
{
205-
fn is_overlapping_drawing_area(&self, area: Option<&DrawingArea<DB, Shift>>) -> bool {
38+
pub(crate) fn is_overlapping_drawing_area(
39+
&self,
40+
area: Option<&DrawingArea<DB, Shift>>,
41+
) -> bool {
20642
if let Some(area) = area {
20743
let (x0, y0) = area.get_base_pixel();
20844
let (w, h) = area.dim_in_pixel();
@@ -222,52 +58,17 @@ impl<
22258

22359
/// Initialize a mesh configuration object and mesh drawing can be finalized by calling
22460
/// the function `MeshStyle::draw`.
225-
pub fn configure_mesh<'b>(&'b mut self) -> MeshStyle<'a, 'b, X, Y, DB> {
226-
let base_tick_size = (5u32).percent().max(5).in_pixels(&self.drawing_area);
227-
228-
let mut x_tick_size = [base_tick_size, base_tick_size];
229-
let mut y_tick_size = [base_tick_size, base_tick_size];
230-
231-
for idx in 0..2 {
232-
if self.is_overlapping_drawing_area(self.x_label_area[idx].as_ref()) {
233-
x_tick_size[idx] = -x_tick_size[idx];
234-
}
235-
if self.is_overlapping_drawing_area(self.y_label_area[idx].as_ref()) {
236-
y_tick_size[idx] = -y_tick_size[idx];
237-
}
238-
}
239-
240-
MeshStyle {
241-
parent_size: self.drawing_area.dim_in_pixel(),
242-
axis_style: None,
243-
x_label_offset: 0,
244-
y_label_offset: 0,
245-
draw_x_mesh: true,
246-
draw_y_mesh: true,
247-
draw_x_axis: true,
248-
draw_y_axis: true,
249-
n_x_labels: 10,
250-
n_y_labels: 10,
251-
bold_line_style: None,
252-
light_line_style: None,
253-
x_label_style: None,
254-
y_label_style: None,
255-
format_x: &X::format,
256-
format_y: &Y::format,
257-
target: Some(self),
258-
_phantom_data: PhantomData,
259-
x_desc: None,
260-
y_desc: None,
261-
axis_desc_style: None,
262-
x_tick_size,
263-
y_tick_size,
264-
}
61+
pub fn configure_mesh(&mut self) -> MeshStyle<'a, '_, X, Y, DB> {
62+
MeshStyle::new(self)
26563
}
26664
}
26765

268-
impl<'a, DB: DrawingBackend + 'a, CT: CoordTranslate> ChartContext<'a, DB, CT> {
66+
impl<'a, DB: DrawingBackend, CT: CoordTranslate> ChartContext<'a, DB, CT> {
26967
/// Configure the styles for drawing series labels in the chart
270-
pub fn configure_series_labels<'b>(&'b mut self) -> SeriesLabelStyle<'a, 'b, DB, CT> {
68+
pub fn configure_series_labels<'b>(&'b mut self) -> SeriesLabelStyle<'a, 'b, DB, CT>
69+
where
70+
DB: 'a,
71+
{
27172
SeriesLabelStyle::new(self)
27273
}
27374

@@ -292,18 +93,18 @@ impl<'a, DB: DrawingBackend, CT: ReverseCoordTranslate> ChartContext<'a, DB, CT>
29293
}
29394
}
29495

295-
impl<'a, DB: DrawingBackend, X: Ranged, Y: Ranged> ChartContext<'a, DB, Arc<RangedCoord<X, Y>>> {
296-
// TODO: All draw_series_impl is over strict on lifetime, because we don't have stable HKT,
96+
impl<'a, DB: DrawingBackend, CT: CoordTranslate> ChartContext<'a, DB, CT> {
97+
// TODO: All draw_series_impl is overly strict about lifetime, because we don't have stable HKT,
29798
// what we can ensure is for all lifetime 'b the element reference &'b E is a iterator
29899
// of points reference with the same lifetime.
299100
// However, this doesn't work if the coordinate doesn't live longer than the backend,
300-
// this is unneccessarily strct
101+
// this is unnecessarily strict
301102
pub(super) fn draw_series_impl<E, R, S>(
302103
&mut self,
303104
series: S,
304105
) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>>
305106
where
306-
for<'b> &'b E: PointCollection<'b, (X::ValueType, Y::ValueType)>,
107+
for<'b> &'b E: PointCollection<'b, CT::From>,
307108
E: Drawable<DB>,
308109
R: Borrow<E>,
309110
S: IntoIterator<Item = R>,
@@ -326,7 +127,7 @@ impl<'a, DB: DrawingBackend, X: Ranged, Y: Ranged> ChartContext<'a, DB, Arc<Rang
326127
series: S,
327128
) -> Result<&mut SeriesAnno<'a, DB>, DrawingAreaErrorKind<DB::ErrorType>>
328129
where
329-
for<'b> &'b E: PointCollection<'b, (X::ValueType, Y::ValueType)>,
130+
for<'b> &'b E: PointCollection<'b, CT::From>,
330131
E: Drawable<DB>,
331132
R: Borrow<E>,
332133
S: IntoIterator<Item = R>,
@@ -353,44 +154,6 @@ impl<'a, DB: DrawingBackend, X: Ranged, Y: Ranged> ChartContext<'a, DB, RangedCo
353154
self.drawing_area.map_coordinate(coord)
354155
}
355156

356-
pub(super) fn draw_series_impl<E, R, S>(
357-
&mut self,
358-
series: S,
359-
) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>>
360-
where
361-
for<'b> &'b E: PointCollection<'b, (X::ValueType, Y::ValueType)>,
362-
E: Drawable<DB>,
363-
R: Borrow<E>,
364-
S: IntoIterator<Item = R>,
365-
{
366-
for element in series {
367-
self.drawing_area.draw(element.borrow())?;
368-
}
369-
Ok(())
370-
}
371-
372-
pub(super) fn alloc_series_anno(&mut self) -> &mut SeriesAnno<'a, DB> {
373-
let idx = self.series_anno.len();
374-
self.series_anno.push(SeriesAnno::new());
375-
&mut self.series_anno[idx]
376-
}
377-
378-
/// Draw a data series. A data series in Plotters is abstracted as an iterator of elements.
379-
/// - **Returns**: Either drawing error or a series annotation object thus we can put annotation to current series (e.g. legend)
380-
pub fn draw_series<E, R, S>(
381-
&mut self,
382-
series: S,
383-
) -> Result<&mut SeriesAnno<'a, DB>, DrawingAreaErrorKind<DB::ErrorType>>
384-
where
385-
for<'b> &'b E: PointCollection<'b, (X::ValueType, Y::ValueType)>,
386-
E: Drawable<DB>,
387-
R: Borrow<E>,
388-
S: IntoIterator<Item = R>,
389-
{
390-
self.draw_series_impl(series)?;
391-
Ok(self.alloc_series_anno())
392-
}
393-
394157
/// The actual function that draws the mesh lines.
395158
/// It also returns the label that suppose to be there.
396159
#[allow(clippy::type_complexity)]

0 commit comments

Comments
 (0)