Skip to main content

matplotlib/
commands.rs

1//! Commonly used plotting commands.
2//!
3//! This module contains types representing many common plotting commands,
4//! implementing [`Matplotlib`] and sometimes [`MatplotlibOpts`]. Each can be
5//! instantiated using their constructor methods or using a corresponding
6//! function from this module for convenience, e.g.
7//!
8//! ```
9//! # use matplotlib::commands::*;
10//! let p1 = Plot::new([0.0, 1.0, 2.0], [0.0, 2.0, 4.0]);
11//! let p2 =      plot([0.0, 1.0, 2.0], [0.0, 2.0, 4.0]);
12//!
13//! assert_eq!(p1, p2);
14//! ```
15//!
16//! **Note**: Several constructors take iterators of flat 3-, 4-, or 6-element
17//! tuples. This is inconvenient with respect to [`Iterator::zip`], so this
18//! module also provides [`Associator`] and [`assoc`] to help with
19//! rearrangement.
20
21use serde_json::{ Number, Value };
22use crate::core::{
23    Matplotlib,
24    MatplotlibOpts,
25    Opt,
26    GSPos,
27    PyValue,
28    AsPy,
29};
30
31/// Helper trait to coerce basic numerical types (and references thereof) to
32/// `f64`s for internal storage in the provided commands.
33pub trait Real {
34    /// Convert to a `f64`.
35    fn into_f64(self) -> f64;
36}
37
38impl Real for f64 { fn into_f64(self) -> f64 { self } }
39impl Real for &f64 { fn into_f64(self) -> f64 { *self } }
40
41macro_rules! impl_real {
42    ( $t:ty { into } ) => {
43        impl Real for $t { fn into_f64(self) -> f64 { self.into() } }
44    };
45    ( { * } $t:ty { into } ) => {
46        impl Real for $t { fn into_f64(self) -> f64 { (*self).into() } }
47    };
48    ( $t:ty { as } ) => {
49        impl Real for $t { fn into_f64(self) -> f64 { self as f64 } }
50    };
51    ( { * } $t:ty { as } ) => {
52        impl Real for $t { fn into_f64(self) -> f64 { *self as f64 } }
53    };
54}
55impl_real!(f32 { into });
56impl_real!({ * } &f32 { into });
57impl_real!(u8 { into });
58impl_real!({ * } &u8 { into });
59impl_real!(u16 { into });
60impl_real!({ * } &u16 { into });
61impl_real!(u32 { into });
62impl_real!({ * } &u32 { into });
63impl_real!(u64 { as });
64impl_real!({ * } &u64 { as });
65impl_real!(u128 { as });
66impl_real!({ * } &u128 { as });
67impl_real!(usize { as });
68impl_real!({ * } &usize { as });
69impl_real!(i8 { into });
70impl_real!({ * } &i8 { into });
71impl_real!(i16 { into });
72impl_real!({ * } &i16 { into });
73impl_real!(i32 { into });
74impl_real!({ * } &i32 { into });
75impl_real!(i64 { as });
76impl_real!({ * } &i64 { as });
77impl_real!(i128 { as });
78impl_real!({ * } &i128 { as });
79impl_real!(isize { as });
80impl_real!({ * } &isize { as });
81
82/// Direct injection of arbitrary Python.
83///
84/// See [`Prelude`] for prelude code.
85///
86/// Prelude: **No**
87///
88/// JSON data: **None**
89#[derive(Clone, Debug, PartialEq, Eq)]
90pub struct Raw(pub String);
91
92impl Raw {
93    /// Create a new [`Raw`].
94    pub fn new(s: &str) -> Self { Self(s.into()) }
95}
96
97/// Create a new [`Raw`].
98pub fn raw(s: &str) -> Raw { Raw::new(s) }
99
100impl Matplotlib for Raw {
101    fn is_prelude(&self) -> bool { false }
102
103    fn data(&self) -> Option<Value> { None }
104
105    fn py_cmd(&self) -> String { self.0.clone() }
106}
107
108/// Direct injection of arbitrary Python into the prelude.
109///
110/// See [`Raw`] for main body code.
111///
112/// Prelude: **Yes**
113///
114/// JSON data: **None**
115#[derive(Clone, Debug, PartialEq, Eq)]
116pub struct Prelude(pub String);
117
118impl Prelude {
119    /// Create a new `Prelude`.
120    pub fn new(s: &str) -> Self { Self(s.into()) }
121}
122
123/// Create a new [`Prelude`].
124pub fn prelude(s: &str) -> Prelude { Prelude::new(s) }
125
126impl Matplotlib for Prelude {
127    fn is_prelude(&self) -> bool { true }
128
129    fn data(&self) -> Option<Value> { None }
130
131    fn py_cmd(&self) -> String { self.0.clone() }
132}
133
134/// Specify a GUI backend.
135///
136/// ```python
137/// matplotlib.use({0})
138/// ```
139///
140/// Prelude: **Yes**
141///
142/// JSON data: **None**
143#[derive(Clone, Debug, PartialEq, Eq)]
144pub struct Backend(pub String);
145
146impl Backend {
147    /// Create a new `Backend`.
148    pub fn new(backend: &str) -> Self { Self(backend.into()) }
149}
150
151/// Create a new [`Backend`].
152pub fn backend(backend: &str) -> Backend { Backend::new(backend) }
153
154impl Matplotlib for Backend {
155    fn is_prelude(&self) -> bool { true }
156
157    fn data(&self) -> Option<Value> { None }
158
159    fn py_cmd(&self) -> String {
160        format!("matplotlib.use({})", self.0.as_py())
161    }
162}
163
164/// Close a figure.
165///
166/// ```python
167/// plt.close({fig})
168/// ```
169///
170/// Prelude: **No**
171///
172/// JSON data: **None**
173#[derive(Clone, Debug, PartialEq, Eq)]
174pub struct CloseFig(pub String);
175
176impl CloseFig {
177    /// Create a new `CloseFig`.
178    pub fn new(fig: &str) -> Self { Self(fig.into()) }
179}
180
181/// Create a new [`CloseFig`].
182pub fn close_fig(fig: &str) -> CloseFig { CloseFig::new(fig) }
183
184impl Matplotlib for CloseFig {
185    fn is_prelude(&self) -> bool { false }
186
187    fn data(&self) -> Option<Value> { None }
188
189    fn py_cmd(&self) -> String {
190        format!("plt.close({})", self.0)
191    }
192}
193
194/// Initialize to a figure with a single set of 3D axes.
195///
196/// The type of the axes object is `mpl_toolkits.mplot3d.axes3d.Axes3D`.
197///
198/// ```python
199/// fig = plt.figure()
200/// ax = axes3d.Axes3D(fig, auto_add_to_figure=False, **{opts})
201/// fig.add_axes(ax)
202/// ```
203///
204/// Prelude: **No**
205///
206/// JSON data: **None**
207#[derive(Clone, Debug, PartialEq, Default)]
208pub struct Init3D {
209    /// Optional keyword arguments.
210    pub opts: Vec<Opt>,
211}
212
213impl Init3D {
214    /// Create a new `Init3D` with no options.
215    pub fn new() -> Self { Self { opts: Vec::new() } }
216}
217
218impl Matplotlib for Init3D {
219    fn is_prelude(&self) -> bool { false }
220
221    fn data(&self) -> Option<Value> { None }
222
223    fn py_cmd(&self) -> String {
224        format!("\
225            fig = plt.figure()\n\
226            ax = axes3d.Axes3D(fig, auto_add_to_figure=False{}{})\n\
227            fig.add_axes(ax)",
228            if self.opts.is_empty() { "" } else { ", " },
229            self.opts.as_py(),
230        )
231    }
232}
233
234impl MatplotlibOpts for Init3D {
235    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
236        self.opts.push((key, val).into());
237        self
238    }
239}
240
241/// Initialize to a figure with a regular grid of plots.
242///
243/// All `Axes` objects will be stored in a 2D Numpy array under the local
244/// variable `AX`, and the script will be initially focused on the upper-left
245/// corner of the array, i.e. `ax = AX[0, 0]`.
246///
247/// ```python
248/// fig, AX = plt.subplots(nrows={nrows}, ncols={ncols}, **{opts})
249/// AX = AX.reshape(({nrows}, {ncols}))
250/// ax = AX[0, 0]
251/// ```
252///
253/// Prelude: **No**
254///
255/// JSON data: **None**
256#[derive(Clone, Debug, PartialEq)]
257pub struct InitGrid {
258    /// Number of rows.
259    pub nrows: usize,
260    /// Number of columns.
261    pub ncols: usize,
262    /// Optional keyword arguments.
263    pub opts: Vec<Opt>,
264}
265
266impl InitGrid {
267    /// Create a new `InitGrid` with no options.
268    pub fn new(nrows: usize, ncols: usize) -> Self {
269        Self { nrows, ncols, opts: Vec::new() }
270    }
271}
272
273/// Create a new [`InitGrid`] with no options.
274pub fn init_grid(nrows: usize, ncols: usize) -> InitGrid {
275    InitGrid::new(nrows, ncols)
276}
277
278impl Matplotlib for InitGrid {
279    fn is_prelude(&self) -> bool { false }
280
281    fn data(&self) -> Option<Value> { None }
282
283    fn py_cmd(&self) -> String {
284        format!("\
285            fig, AX = plt.subplots(nrows={}, ncols={}{}{})\n\
286            AX = AX.reshape(({}, {}))\n\
287            ax = AX[0, 0]",
288            self.nrows,
289            self.ncols,
290            if self.opts.is_empty() { "" } else { ", " },
291            self.opts.as_py(),
292            self.nrows,
293            self.ncols,
294        )
295    }
296}
297
298impl MatplotlibOpts for InitGrid {
299    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
300        self.opts.push((key, val).into());
301        self
302    }
303}
304
305/// Initialize a figure with Matplotlib's `gridspec`.
306///
307/// Keyword arguments are passed to `plt.Figure.add_gridspec`, and each
308/// subplot's position in the gridspec is specified using a [`GSPos`]. All
309/// `Axes` objects will be stored in a 1D Numpy array under the local variable
310/// `AX`, and the script will be initially focused to the subplot corresponding
311/// to the first `GSPos` encountered, i.e. `ax = AX[0]`.
312///
313/// ```python
314/// fig = plt.figure()
315/// gs = fig.add_gridspec(**{opts})
316/// AX = np.array([
317///     # sub-plots generated from {positions}...
318/// ])
319/// # share axes between sub-plots...
320/// ax = AX[0]
321/// ```
322///
323/// Prelude: **No**
324///
325/// JSON data: **None**
326#[derive(Clone, Debug, PartialEq)]
327pub struct InitGridSpec {
328    /// Keyword arguments.
329    pub gridspec_kw: Vec<Opt>,
330    /// Sub-plot positions and axis sharing.
331    pub positions: Vec<GSPos>,
332}
333
334impl InitGridSpec {
335    /// Create a new `InitGridSpec`.
336    pub fn new<I, P>(gridspec_kw: I, positions: P) -> Self
337    where
338        I: IntoIterator<Item = Opt>,
339        P: IntoIterator<Item = GSPos>,
340    {
341        Self {
342            gridspec_kw: gridspec_kw.into_iter().collect(),
343            positions: positions.into_iter().collect(),
344        }
345    }
346}
347
348/// Create a new [`InitGridSpec`].
349pub fn init_gridspec<I, P>(gridspec_kw: I, positions: P) -> InitGridSpec
350where
351    I: IntoIterator<Item = Opt>,
352    P: IntoIterator<Item = GSPos>,
353{
354    InitGridSpec::new(gridspec_kw, positions)
355}
356
357impl Matplotlib for InitGridSpec {
358    fn is_prelude(&self) -> bool { false }
359
360    fn data(&self) -> Option<Value> { None }
361
362    fn py_cmd(&self) -> String {
363        let mut code =
364            format!("\
365                fig = plt.figure()\n\
366                gs = fig.add_gridspec({})\n\
367                AX = np.array([\n",
368                self.gridspec_kw.as_py(),
369            );
370        for GSPos { i, j, sharex: _, sharey: _ } in self.positions.iter() {
371            code.push_str(
372                &format!("    fig.add_subplot(gs[{}:{}, {}:{}]),\n",
373                    i.start, i.end, j.start, j.end,
374                )
375            );
376        }
377        code.push_str("])\n");
378        let iter = self.positions.iter().enumerate();
379        for (k, GSPos { i: _, j: _, sharex, sharey }) in iter {
380            if let Some(x) = sharex {
381                code.push_str(&format!("AX[{}].sharex(AX[{}])\n", k, x));
382            }
383            if let Some(y) = sharey {
384                code.push_str(&format!("AX[{}].sharey(AX[{}])\n", k, y));
385            }
386        }
387        code.push_str("ax = AX[0]\n");
388        code
389    }
390}
391
392impl MatplotlibOpts for InitGridSpec {
393    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
394        self.gridspec_kw.push((key, val).into());
395        self
396    }
397}
398
399/// Set the value of an RC parameter.
400///
401/// **Note**: This type limits values to basic Python types; this is fine for
402/// all but a few of the RC parameters; e.g. `axes.prop_cycle`. For the
403/// remainder, use [`Raw`] or [`Prelude`].
404///
405/// ```python
406/// plt.rcParams["{key}"] = {val}
407/// ```
408///
409/// Prelude: **Yes**
410///
411/// JSON data: **None**
412#[derive(Clone, Debug, PartialEq)]
413pub struct RcParam {
414    /// Key in `matplotlib.pyplot.rcParams`.
415    pub key: String,
416    /// Value setting.
417    pub val: PyValue,
418}
419
420impl RcParam {
421    /// Create a new `RcParam`.
422    pub fn new<T: Into<PyValue>>(key: &str, val: T) -> Self {
423        Self { key: key.into(), val: val.into() }
424    }
425}
426
427/// Create a new [`RcParam`].
428pub fn rcparam<T: Into<PyValue>>(key: &str, val: T) -> RcParam {
429    RcParam::new(key, val)
430}
431
432impl Matplotlib for RcParam {
433    fn is_prelude(&self) -> bool { true }
434
435    fn data(&self) -> Option<Value> { None }
436
437    fn py_cmd(&self) -> String {
438        format!("plt.rcParams[{}] = {}", self.key.as_py(), self.val.as_py())
439    }
440}
441
442/// Activate or deactivate TeX text.
443///
444/// ```python
445/// plt.rcParams["text.usetex"] = {0}
446/// ```
447///
448/// Prelude: **Yes**
449///
450/// JSON data: **None**
451#[derive(Copy, Clone, Debug, PartialEq, Eq)]
452pub struct TeX(pub bool);
453
454impl TeX {
455    /// Turn TeX text on.
456    pub fn on() -> Self { Self(true) }
457
458    /// Turn TeX text off.
459    pub fn off() -> Self { Self(false) }
460}
461
462/// Turn TeX text on.
463pub fn tex_on() -> TeX { TeX(true) }
464
465/// Turn TeX text off.
466pub fn tex_off() -> TeX { TeX(false) }
467
468impl Matplotlib for TeX {
469    fn is_prelude(&self) -> bool { true }
470
471    fn data(&self) -> Option<Value> { None }
472
473    fn py_cmd(&self) -> String {
474        format!("plt.rcParams[\"text.usetex\"] = {}", self.0.as_py())
475    }
476}
477
478/// Set the local variable `ax` to a different set of axes.
479///
480/// ```python
481/// ax = {0}
482/// ```
483///
484/// Prelude: **No**
485///
486/// JSON data: **None**
487#[derive(Clone, Debug, PartialEq, Eq)]
488pub struct FocusAx(pub String);
489
490impl FocusAx {
491    /// Create a new `FocusAx`.
492    pub fn new(expr: &str) -> Self { Self(expr.into()) }
493}
494
495/// Create a new [`FocusAx`].
496pub fn focus_ax(expr: &str) -> FocusAx { FocusAx::new(expr) }
497
498impl Matplotlib for FocusAx {
499    fn is_prelude(&self) -> bool { false }
500
501    fn data(&self) -> Option<Value> { None }
502
503    fn py_cmd(&self) -> String { format!("ax = {}", self.0) }
504}
505
506/// Set the local variable `fig` to a different figure.
507///
508/// ```python
509/// fig = {0}
510/// ```
511///
512/// Prelude: **No**
513///
514/// JSON data: **None**
515#[derive(Clone, Debug, PartialEq, Eq)]
516pub struct FocusFig(pub String);
517
518impl FocusFig {
519    /// Create a new `FocusFig`.
520    pub fn new(expr: &str) -> Self { Self(expr.into()) }
521}
522
523/// Create a new [`FocusFig`].
524pub fn focus_fig(expr: &str) -> FocusFig { FocusFig::new(expr) }
525
526impl Matplotlib for FocusFig {
527    fn is_prelude(&self) -> bool { false }
528
529    fn data(&self) -> Option<Value> { None }
530
531    fn py_cmd(&self) -> String { format!("fig = {}", self.0) }
532}
533
534/// Set the local variable `cbar` to a different colorbar.
535///
536/// ```python
537/// cbar = {0}
538/// ```
539///
540/// Prelude: **No**
541///
542/// JSON data: **None**
543#[derive(Clone, Debug, PartialEq, Eq)]
544pub struct FocusCBar(pub String);
545
546impl FocusCBar {
547    /// Create a new `FocusCBar`.
548    pub fn new(expr: &str) -> Self { Self(expr.into()) }
549}
550
551/// Create a new [`FocusCBar`].
552pub fn focus_cbar(expr: &str) -> FocusCBar { FocusCBar::new(expr) }
553
554impl Matplotlib for FocusCBar {
555    fn is_prelude(&self) -> bool { false }
556
557    fn data(&self) -> Option<Value> { None }
558
559    fn py_cmd(&self) -> String { format!("cbar = {}", self.0) }
560}
561
562/// Set the local variable `im` to a different image or mappable object.
563///
564/// ```python
565/// im = {0}
566/// ```
567///
568/// Prelude: **No**
569///
570/// JSON data: **None**
571#[derive(Clone, Debug, PartialEq, Eq)]
572pub struct FocusIm(pub String);
573
574impl FocusIm {
575    /// Create a new `FocusIm`.
576    pub fn new(expr: &str) -> Self { Self(expr.into()) }
577}
578
579/// Create a new [`FocusIm`].
580pub fn focus_im(expr: &str) -> FocusIm { FocusIm::new(expr) }
581
582impl Matplotlib for FocusIm {
583    fn is_prelude(&self) -> bool { false }
584
585    fn data(&self) -> Option<Value> { None }
586
587    fn py_cmd(&self) -> String { format!("im = {}", self.0) }
588}
589
590/// A (*x*, *y*) plot.
591///
592/// ```python
593/// ax.plot({x}, {y}, **{opts})
594/// ```
595///
596/// Prelude: **No**
597///
598/// JSON data: `[list[float], list[float]]`
599#[derive(Clone, Debug, PartialEq)]
600pub struct Plot {
601    /// X-coordinates.
602    pub x: Vec<f64>,
603    /// Y-coordinates.
604    pub y: Vec<f64>,
605    /// Optional keyword arguments.
606    pub opts: Vec<Opt>,
607}
608
609impl Plot {
610    /// Create a new `Plot` with no options.
611    pub fn new<X, XE, Y, YE>(x: X, y: Y) -> Self
612    where
613        X: IntoIterator<Item = XE>,
614        XE: Real,
615        Y: IntoIterator<Item = YE>,
616        YE: Real,
617    {
618        Self {
619            x: x.into_iter().map(Real::into_f64).collect(),
620            y: y.into_iter().map(Real::into_f64).collect(),
621            opts: Vec::new(),
622        }
623    }
624
625    /// Create a new `Plot` with no options from a single iterator.
626    pub fn new_pairs<I, XE, YE>(data: I) -> Self
627    where
628        I: IntoIterator<Item = (XE, YE)>,
629        XE: Real,
630        YE: Real,
631    {
632        let (x, y): (Vec<f64>, Vec<f64>) =
633            data.into_iter()
634            .map(|(x, y)| (x.into_f64(), y.into_f64()))
635            .unzip();
636        Self { x, y, opts: Vec::new() }
637    }
638}
639
640/// Create a new [`Plot`] with no options.
641pub fn plot<X, XE, Y, YE>(x: X, y: Y) -> Plot
642where
643    X: IntoIterator<Item = XE>,
644    XE: Real,
645    Y: IntoIterator<Item = YE>,
646    YE: Real,
647{
648    Plot::new(x, y)
649}
650
651/// Create a new [`Plot`] with no options from a single iterator.
652pub fn plot_pairs<I, XE, YE>(data: I) -> Plot
653where
654    I: IntoIterator<Item = (XE, YE)>,
655    XE: Real,
656    YE: Real,
657{
658    Plot::new_pairs(data)
659}
660
661impl Matplotlib for Plot {
662    fn is_prelude(&self) -> bool { false }
663
664    fn data(&self) -> Option<Value> {
665        let x: Vec<Value> = self.x.iter().copied().map(Value::from).collect();
666        let y: Vec<Value> = self.y.iter().copied().map(Value::from).collect();
667        Some(Value::Array(vec![x.into(), y.into()]))
668    }
669
670    fn py_cmd(&self) -> String {
671        format!("ax.plot(data[0], data[1]{}{})",
672            if self.opts.is_empty() { "" } else { ", " },
673            self.opts.as_py(),
674        )
675    }
676}
677
678impl MatplotlibOpts for Plot {
679    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
680        self.opts.push((key, val).into());
681        self
682    }
683}
684
685/// A histogram of a data set.
686///
687/// ```python
688/// ax.hist({data}, **{opts})
689/// ```
690///
691/// Prelude: **No**
692///
693/// JSON data: `list[float]`
694#[derive(Clone, Debug, PartialEq)]
695pub struct Hist {
696    /// Data set.
697    pub data: Vec<f64>,
698    /// Optional keyword arguments.
699    pub opts: Vec<Opt>,
700}
701
702impl Hist {
703    /// Create a new `Hist` with no options.
704    pub fn new<I, E>(data: I) -> Self
705    where
706        I: IntoIterator<Item = E>,
707        E: Real,
708    {
709        let data: Vec<f64> = data.into_iter().map(Real::into_f64).collect();
710        Self { data, opts: Vec::new() }
711    }
712}
713
714/// Create a new [`Hist`] with no options.
715pub fn hist<I, E>(data: I) -> Hist
716where
717    I: IntoIterator<Item = E>,
718    E: Real,
719{
720    Hist::new(data)
721}
722
723impl Matplotlib for Hist {
724    fn is_prelude(&self) -> bool { false }
725
726    fn data(&self) -> Option<Value> {
727        let data: Vec<Value> =
728            self.data.iter().copied().map(Value::from).collect();
729        Some(Value::Array(data))
730    }
731
732    fn py_cmd(&self) -> String {
733        format!("ax.hist(data{}{})",
734            if self.opts.is_empty() { "" } else { ", " },
735            self.opts.as_py(),
736        )
737    }
738}
739
740impl MatplotlibOpts for Hist {
741    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
742        self.opts.push((key, val).into());
743        self
744    }
745}
746
747/// A histogram of two variables.
748///
749/// ```python
750/// ax.hist2d({data}, **{opts})
751/// ```
752///
753/// Prelude: **No**
754///
755/// JSON data: `[list[float], list[float]]`
756#[derive(Clone, Debug, PartialEq)]
757pub struct Hist2d {
758    /// X data set.
759    pub x: Vec<f64>,
760    /// Y data set.
761    pub y: Vec<f64>,
762    /// Optional keyword arguments.
763    pub opts: Vec<Opt>,
764}
765
766impl Hist2d {
767    /// Create a new `Hist2d` with no options.
768    pub fn new<X, XE, Y, YE>(x: X, y: Y) -> Self
769    where
770        X: IntoIterator<Item = XE>,
771        XE: Real,
772        Y: IntoIterator<Item = YE>,
773        YE: Real,
774    {
775        let x: Vec<f64> = x.into_iter().map(Real::into_f64).collect();
776        let y: Vec<f64> = y.into_iter().map(Real::into_f64).collect();
777        Self { x, y, opts: Vec::new() }
778    }
779
780    /// Create a new `Hist2d` with no options from a single iterator.
781    pub fn new_pairs<I, XE, YE>(data: I) -> Self
782    where
783        I: IntoIterator<Item = (XE, YE)>,
784        XE: Real,
785        YE: Real,
786    {
787        let (x, y): (Vec<f64>, Vec<f64>) =
788            data.into_iter()
789            .map(|(x, y)| (x.into_f64(), y.into_f64()))
790            .unzip();
791        Self { x, y, opts: Vec::new() }
792    }
793}
794
795/// Create a new [`Hist2d`] with no options.
796pub fn hist2d<X, XE, Y, YE>(x: X, y: Y) -> Hist2d
797where
798    X: IntoIterator<Item = XE>,
799    XE: Real,
800    Y: IntoIterator<Item = YE>,
801    YE: Real,
802{
803    Hist2d::new(x, y)
804}
805
806/// Create a new [`Hist2d`] with no options from a single iterator.
807pub fn hist2d_pairs<I, XE, YE>(data: I) -> Hist2d
808where
809    I: IntoIterator<Item = (XE, YE)>,
810    XE: Real,
811    YE: Real,
812{
813    Hist2d::new_pairs(data)
814}
815
816impl Matplotlib for Hist2d {
817    fn is_prelude(&self) -> bool { false }
818
819    fn data(&self) -> Option<Value> {
820        let x: Vec<Value> = self.x.iter().copied().map(Value::from).collect();
821        let y: Vec<Value> = self.y.iter().copied().map(Value::from).collect();
822        Some(Value::Array(vec![x.into(), y.into()]))
823    }
824
825    fn py_cmd(&self) -> String {
826        format!("ax.hist2d(data[0], data[1]{}{})",
827            if self.opts.is_empty() { "" } else { ", " },
828            self.opts.as_py(),
829        )
830    }
831}
832
833impl MatplotlibOpts for Hist2d {
834    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
835        self.opts.push((key, val).into());
836        self
837    }
838}
839
840/// A (*x*, *y*) scatter plot.
841///
842/// ```python
843/// ax.scatter({x}, {y}, **{opts})
844/// ```
845///
846/// Prelude: **No**
847///
848/// JSON data: `[list[float], list[float]]`
849#[derive(Clone, Debug, PartialEq)]
850pub struct Scatter {
851    /// X-coordinates.
852    pub x: Vec<f64>,
853    /// Y-coordinates.
854    pub y: Vec<f64>,
855    /// Optional keyword arguments.
856    pub opts: Vec<Opt>,
857}
858
859impl Scatter {
860    /// Create a new `Scatter` with no options.
861    pub fn new<X, XE, Y, YE>(x: X, y: Y) -> Self
862    where
863        X: IntoIterator<Item = XE>,
864        XE: Real,
865        Y: IntoIterator<Item = YE>,
866        YE: Real,
867    {
868        Self {
869            x: x.into_iter().map(Real::into_f64).collect(),
870            y: y.into_iter().map(Real::into_f64).collect(),
871            opts: Vec::new(),
872        }
873    }
874
875    /// Create a new `Scatter` with no options from a single iterator.
876    pub fn new_pairs<I, XE, YE>(data: I) -> Self
877    where
878        I: IntoIterator<Item = (XE, YE)>,
879        XE: Real,
880        YE: Real,
881    {
882        let (x, y): (Vec<f64>, Vec<f64>) =
883            data.into_iter()
884            .map(|(x, y)| (x.into_f64(), y.into_f64()))
885            .unzip();
886        Self { x, y, opts: Vec::new() }
887    }
888}
889
890/// Create a new [`Scatter`] with no options.
891pub fn scatter<X, XE, Y, YE>(x: X, y: Y) -> Scatter
892where
893    X: IntoIterator<Item = XE>,
894    XE: Real,
895    Y: IntoIterator<Item = YE>,
896    YE: Real,
897{
898    Scatter::new(x, y)
899}
900
901/// Create a new [`Scatter`] with no options from a single iterator.
902pub fn scatter_pairs<I, XE, YE>(data: I) -> Scatter
903where
904    I: IntoIterator<Item = (XE, YE)>,
905    XE: Real,
906    YE: Real,
907{
908    Scatter::new_pairs(data)
909}
910
911impl Matplotlib for Scatter {
912    fn is_prelude(&self) -> bool { false }
913
914    fn data(&self) -> Option<Value> {
915        let x: Vec<Value> = self.x.iter().copied().map(Value::from).collect();
916        let y: Vec<Value> = self.y.iter().copied().map(Value::from).collect();
917        Some(Value::Array(vec![x.into(), y.into()]))
918    }
919
920    fn py_cmd(&self) -> String {
921        format!("ax.scatter(data[0], data[1]{}{})",
922            if self.opts.is_empty() { "" } else { ", " },
923            self.opts.as_py(),
924        )
925    }
926}
927
928impl MatplotlibOpts for Scatter {
929    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
930        self.opts.push((key, val).into());
931        self
932    }
933}
934
935/// A stem plot.
936///
937/// ```python
938/// ax.stem({x}, {y}, **{opts})
939/// ```
940///
941/// Prelude: **No**
942///
943/// JSON data: `[list[float], list[float]]`
944#[derive(Clone, Debug, PartialEq)]
945pub struct Stem {
946    /// X-coordinates.
947    pub x: Vec<f64>,
948    /// Y-coordinates.
949    pub y: Vec<f64>,
950    /// Optional keyword arguments.
951    pub opts: Vec<Opt>,
952}
953
954impl Stem {
955    /// Create a new `Stem` with no options.
956    pub fn new<X, XE, Y, YE>(x: X, y: Y) -> Self
957    where
958        X: IntoIterator<Item = XE>,
959        XE: Real,
960        Y: IntoIterator<Item = YE>,
961        YE: Real,
962    {
963        Self {
964            x: x.into_iter().map(Real::into_f64).collect(),
965            y: y.into_iter().map(Real::into_f64).collect(),
966            opts: Vec::new(),
967        }
968    }
969
970    /// Create a new `Stem` with no options from a single iterator.
971    pub fn new_pairs<I, XE, YE>(data: I) -> Self
972    where
973        I: IntoIterator<Item = (XE, YE)>,
974        XE: Real,
975        YE: Real,
976    {
977        let (x, y): (Vec<f64>, Vec<f64>) =
978            data.into_iter()
979            .map(|(x, y)| (x.into_f64(), y.into_f64()))
980            .unzip();
981        Self { x, y, opts: Vec::new() }
982    }
983}
984
985/// Create a new [`Stem`] with no options.
986pub fn stem<X, XE, Y, YE>(x: X, y: Y) -> Stem
987where
988    X: IntoIterator<Item = XE>,
989    XE: Real,
990    Y: IntoIterator<Item = YE>,
991    YE: Real,
992{
993    Stem::new(x, y)
994}
995
996/// Create a new [`Stem`] with no options from a single iterator.
997pub fn stem_pairs<I, XE, YE>(data: I) -> Stem
998where
999    I: IntoIterator<Item = (XE, YE)>,
1000    XE: Real,
1001    YE: Real,
1002{
1003    Stem::new_pairs(data)
1004}
1005
1006impl Matplotlib for Stem {
1007    fn is_prelude(&self) -> bool { false }
1008
1009    fn data(&self) -> Option<Value> {
1010        let x: Vec<Value> = self.x.iter().copied().map(Value::from).collect();
1011        let y: Vec<Value> = self.y.iter().copied().map(Value::from).collect();
1012        Some(Value::Array(vec![x.into(), y.into()]))
1013    }
1014
1015    fn py_cmd(&self) -> String {
1016        format!("ax.stem(data[0], data[1]{}{})",
1017            if self.opts.is_empty() { "" } else { ", " },
1018            self.opts.as_py(),
1019        )
1020    }
1021}
1022
1023impl MatplotlibOpts for Stem {
1024    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
1025        self.opts.push((key, val).into());
1026        self
1027    }
1028}
1029
1030/// A stair-step plot.
1031///
1032/// ```python
1033/// ax.stairs({y}, {x}, **{opts})
1034/// ```
1035///
1036/// Prelude: **No**
1037///
1038/// JSON data: `[list[float], list[float]]`
1039#[derive(Clone, Debug, PartialEq)]
1040pub struct Stairs {
1041    /// X-coordinates of each stair edge.
1042    pub x: Vec<f64>,
1043    /// Y-coordinates of each stair step height.
1044    pub y: Vec<f64>,
1045    /// Optional keyword arguments.
1046    pub opts: Vec<Opt>,
1047}
1048
1049impl Stairs {
1050    /// Create a new `Stairs` with no options.
1051    pub fn new<X, XE, Y, YE>(x: X, y: Y) -> Self
1052    where
1053        X: IntoIterator<Item = XE>,
1054        XE: Real,
1055        Y: IntoIterator<Item = YE>,
1056        YE: Real,
1057    {
1058        Self {
1059            x: x.into_iter().map(Real::into_f64).collect(),
1060            y: y.into_iter().map(Real::into_f64).collect(),
1061            opts: Vec::new(),
1062        }
1063    }
1064
1065    /// Create a new `Stairs` with no options from a single iterator.
1066    pub fn new_pairs<I, XE, YE>(data: I) -> Self
1067    where
1068        I: IntoIterator<Item = (XE, YE)>,
1069        XE: Real,
1070        YE: Real,
1071    {
1072        let (x, y): (Vec<f64>, Vec<f64>) =
1073            data.into_iter()
1074            .map(|(x, y)| (x.into_f64(), y.into_f64()))
1075            .unzip();
1076        Self { x, y, opts: Vec::new() }
1077    }
1078}
1079
1080/// Create a new [`Stairs`] with no options.
1081pub fn stairs<X, XE, Y, YE>(x: X, y: Y) -> Stairs
1082where
1083    X: IntoIterator<Item = XE>,
1084    XE: Real,
1085    Y: IntoIterator<Item = YE>,
1086    YE: Real,
1087{
1088    Stairs::new(x, y)
1089}
1090
1091/// Create a new [`Stairs`] with no options from a single iterator.
1092pub fn stairs_pairs<I, XE, YE>(data: I) -> Stairs
1093where
1094    I: IntoIterator<Item = (XE, YE)>,
1095    XE: Real,
1096    YE: Real,
1097{
1098    Stairs::new_pairs(data)
1099}
1100
1101impl Matplotlib for Stairs {
1102    fn is_prelude(&self) -> bool { false }
1103
1104    fn data(&self) -> Option<Value> {
1105        let x: Vec<Value> = self.x.iter().copied().map(Value::from).collect();
1106        let y: Vec<Value> = self.y.iter().copied().map(Value::from).collect();
1107        Some(Value::Array(vec![x.into(), y.into()]))
1108    }
1109
1110    fn py_cmd(&self) -> String {
1111        format!("ax.stairs(data[1], data[0]{}{})",
1112            if self.opts.is_empty() { "" } else { ", " },
1113            self.opts.as_py(),
1114        )
1115    }
1116}
1117
1118impl MatplotlibOpts for Stairs {
1119    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
1120        self.opts.push((key, val).into());
1121        self
1122    }
1123}
1124
1125/// A step plot.
1126///
1127/// ```python
1128/// ax.step({x}, {y}, **{opts})
1129/// ```
1130///
1131/// Prelude: **No**
1132///
1133/// JSON data: `[list[float], list[float]]`
1134#[derive(Clone, Debug, PartialEq)]
1135pub struct Step {
1136    /// X-coordinates.
1137    pub x: Vec<f64>,
1138    /// Y-coordinates.
1139    pub y: Vec<f64>,
1140    /// Optional keyword arguments.
1141    pub opts: Vec<Opt>,
1142}
1143
1144impl Step {
1145    /// Create a new `Step` with no options.
1146    pub fn new<X, XE, Y, YE>(x: X, y: Y) -> Self
1147    where
1148        X: IntoIterator<Item = XE>,
1149        XE: Real,
1150        Y: IntoIterator<Item = YE>,
1151        YE: Real,
1152    {
1153        Self {
1154            x: x.into_iter().map(Real::into_f64).collect(),
1155            y: y.into_iter().map(Real::into_f64).collect(),
1156            opts: Vec::new(),
1157        }
1158    }
1159
1160    /// Create a new `Step` with no options from a single iterator.
1161    pub fn new_pairs<I, XE, YE>(data: I) -> Self
1162    where
1163        I: IntoIterator<Item = (XE, YE)>,
1164        XE: Real,
1165        YE: Real,
1166    {
1167        let (x, y): (Vec<f64>, Vec<f64>) =
1168            data.into_iter()
1169            .map(|(x, y)| (x.into_f64(), y.into_f64()))
1170            .unzip();
1171        Self { x, y, opts: Vec::new() }
1172    }
1173}
1174
1175/// Create a new [`Step`] with no options.
1176pub fn step<X, XE, Y, YE>(x: X, y: Y) -> Step
1177where
1178    X: IntoIterator<Item = XE>,
1179    XE: Real,
1180    Y: IntoIterator<Item = YE>,
1181    YE: Real,
1182{
1183    Step::new(x, y)
1184}
1185
1186/// Create a new [`Step`] with no options from a single iterator.
1187pub fn step_pairs<I, XE, YE>(data: I) -> Step
1188where
1189    I: IntoIterator<Item = (XE, YE)>,
1190    XE: Real,
1191    YE: Real,
1192{
1193    Step::new_pairs(data)
1194}
1195
1196impl Matplotlib for Step {
1197    fn is_prelude(&self) -> bool { false }
1198
1199    fn data(&self) -> Option<Value> {
1200        let x: Vec<Value> = self.x.iter().copied().map(Value::from).collect();
1201        let y: Vec<Value> = self.y.iter().copied().map(Value::from).collect();
1202        Some(Value::Array(vec![x.into(), y.into()]))
1203    }
1204
1205    fn py_cmd(&self) -> String {
1206        format!("ax.step(data[0], data[1]{}{})",
1207            if self.opts.is_empty() { "" } else { ", " },
1208            self.opts.as_py(),
1209        )
1210    }
1211}
1212
1213impl MatplotlibOpts for Step {
1214    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
1215        self.opts.push((key, val).into());
1216        self
1217    }
1218}
1219
1220/// A vector field plot.
1221///
1222/// ```python
1223/// ax.quiver({x}, {y}, {vx}, {vy}, **{ops})
1224/// ```
1225///
1226/// Prelude: **No**
1227///
1228/// JSON data: `[list[float], list[float], list[float], list[float]]`
1229#[derive(Clone, Debug, PartialEq)]
1230pub struct Quiver {
1231    /// X-coordinates.
1232    pub x: Vec<f64>,
1233    /// Y-coordinates.
1234    pub y: Vec<f64>,
1235    /// Vector X-components.
1236    pub vx: Vec<f64>,
1237    /// Vector Y-components.
1238    pub vy: Vec<f64>,
1239    /// Optional keyword arguments.
1240    pub opts: Vec<Opt>,
1241}
1242
1243impl Quiver {
1244    /// Create a new `Quiver` with no options.
1245    pub fn new<X, XE, Y, YE, VX, VXE, VY, VYE>(
1246        x: X,
1247        y: Y,
1248        vx: VX,
1249        vy: VY,
1250    ) -> Self
1251    where
1252        X: IntoIterator<Item = XE>,
1253        XE: Real,
1254        Y: IntoIterator<Item = YE>,
1255        YE: Real,
1256        VX: IntoIterator<Item = VXE>,
1257        VXE: Real,
1258        VY: IntoIterator<Item = VYE>,
1259        VYE: Real,
1260    {
1261        Self {
1262            x: x.into_iter().map(Real::into_f64).collect(),
1263            y: y.into_iter().map(Real::into_f64).collect(),
1264            vx: vx.into_iter().map(Real::into_f64).collect(),
1265            vy: vy.into_iter().map(Real::into_f64).collect(),
1266            opts: Vec::new(),
1267        }
1268    }
1269
1270    /// Create a new `Quiver` with no options from iterators over coordinate
1271    /// pairs.
1272    pub fn new_pairs<I, XE, YE, VI, VXE, VYE>(xy: I, vxy: VI) -> Self
1273    where
1274        I: IntoIterator<Item = (XE, YE)>,
1275        XE: Real,
1276        YE: Real,
1277        VI: IntoIterator<Item = (VXE, VYE)>,
1278        VXE: Real,
1279        VYE: Real,
1280    {
1281        let (x, y): (Vec<f64>, Vec<f64>) =
1282            xy.into_iter()
1283            .map(|(x, y)| (x.into_f64(), y.into_f64()))
1284            .unzip();
1285        let (vx, vy): (Vec<f64>, Vec<f64>) =
1286            vxy.into_iter()
1287            .map(|(x, y)| (x.into_f64(), y.into_f64()))
1288            .unzip();
1289        Self { x, y, vx, vy, opts: Vec::new() }
1290    }
1291
1292    /// Create a new `Quiver` with no options from a single iterator. The first
1293    /// two elements of each iterator item should be spatial coordinates and the
1294    /// last two should be vector components.
1295    pub fn new_data<I, XE, YE, VXE, VYE>(data: I) -> Self
1296    where
1297        I: IntoIterator<Item = (XE, YE, VXE, VYE)>,
1298        XE: Real,
1299        YE: Real,
1300        VXE: Real,
1301        VYE: Real,
1302    {
1303        let (((x, y), vx), vy) =
1304            data.into_iter()
1305            .map(|(a, b, c, d)| {
1306                (a.into_f64(), b.into_f64(), c.into_f64(), d.into_f64())
1307            })
1308            .map(assoc)
1309            .unzip();
1310        Self { x, y, vx, vy, opts: Vec::new() }
1311    }
1312}
1313
1314/// Create a new [`Quiver`] with no options.
1315pub fn quiver<X, XE, Y, YE, VX, VXE, VY, VYE>(
1316    x: X,
1317    y: Y,
1318    vx: VX,
1319    vy: VY,
1320) -> Quiver
1321where
1322    X: IntoIterator<Item = XE>,
1323    XE: Real,
1324    Y: IntoIterator<Item = YE>,
1325    YE: Real,
1326    VX: IntoIterator<Item = VXE>,
1327    VXE: Real,
1328    VY: IntoIterator<Item = VYE>,
1329    VYE: Real,
1330{
1331    Quiver::new(x, y, vx, vy)
1332}
1333
1334/// Create a new [`Quiver`] with no options from iterators over coordinate
1335/// pairs.
1336pub fn quiver_pairs<I, XE, YE, VI, VXE, VYE>(xy: I, vxy: VI) -> Quiver
1337where
1338    I: IntoIterator<Item = (XE, YE)>,
1339    XE: Real,
1340    YE: Real,
1341    VI: IntoIterator<Item = (VXE, VYE)>,
1342    VXE: Real,
1343    VYE: Real,
1344{
1345    Quiver::new_pairs(xy, vxy)
1346}
1347
1348/// Create a new [`Quiver`] with no options from a single iterator. The first
1349/// two elements of each iterator item should be spatial coordinates and
1350/// the last two should be vector components.
1351pub fn quiver_data<I, XE, YE, VXE, VYE>(data: I) -> Quiver
1352where
1353    I: IntoIterator<Item = (XE, YE, VXE, VYE)>,
1354    XE: Real,
1355    YE: Real,
1356    VXE: Real,
1357    VYE: Real,
1358{
1359    Quiver::new_data(data)
1360}
1361
1362impl Matplotlib for Quiver {
1363    fn is_prelude(&self) -> bool { false }
1364
1365    fn data(&self) -> Option<Value> {
1366        let x: Vec<Value> = self.x.iter().copied().map(Value::from).collect();
1367        let y: Vec<Value> = self.y.iter().copied().map(Value::from).collect();
1368        let vx: Vec<Value> = self.vx.iter().copied().map(Value::from).collect();
1369        let vy: Vec<Value> = self.vy.iter().copied().map(Value::from).collect();
1370        Some(Value::Array(vec![x.into(), y.into(), vx.into(), vy.into()]))
1371    }
1372
1373    fn py_cmd(&self) -> String {
1374        format!("ax.quiver(data[0], data[1], data[2], data[3]{}{})",
1375            if self.opts.is_empty() { "" } else { ", " },
1376            self.opts.as_py(),
1377        )
1378    }
1379}
1380
1381impl MatplotlibOpts for Quiver {
1382    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
1383        self.opts.push((key, val).into());
1384        self
1385    }
1386}
1387
1388/// A bar plot.
1389///
1390/// ```python
1391/// ax.bar({x}, {y}, **{opts})
1392/// ```
1393///
1394/// Prelude: **No**
1395///
1396/// JSON data: `[list[float], list[float]]`
1397#[derive(Clone, Debug, PartialEq)]
1398pub struct Bar {
1399    /// X-coordinates.
1400    pub x: Vec<f64>,
1401    /// Y-coordinates.
1402    pub y: Vec<f64>,
1403    /// Optional keyword arguments.
1404    pub opts: Vec<Opt>,
1405}
1406
1407impl Bar {
1408    /// Create a new `Bar` with no options.
1409    pub fn new<X, XE, Y, YE>(x: X, y: Y) -> Self
1410    where
1411        X: IntoIterator<Item = XE>,
1412        XE: Real,
1413        Y: IntoIterator<Item = YE>,
1414        YE: Real,
1415    {
1416        Self {
1417            x: x.into_iter().map(Real::into_f64).collect(),
1418            y: y.into_iter().map(Real::into_f64).collect(),
1419            opts: Vec::new(),
1420        }
1421    }
1422
1423    /// Create a new `Bar` with options from a single iterator.
1424    pub fn new_pairs<I, XE, YE>(data: I) -> Self
1425    where
1426        I: IntoIterator<Item = (XE, YE)>,
1427        XE: Real,
1428        YE: Real,
1429    {
1430        let (x, y): (Vec<f64>, Vec<f64>) =
1431            data.into_iter()
1432            .map(|(x, y)| (x.into_f64(), y.into_f64()))
1433            .unzip();
1434        Self { x, y, opts: Vec::new() }
1435    }
1436}
1437
1438/// Create a new [`Bar`] with no options.
1439pub fn bar<X, XE, Y, YE>(x: X, y: Y) -> Bar
1440where
1441    X: IntoIterator<Item = XE>,
1442    XE: Real,
1443    Y: IntoIterator<Item = YE>,
1444    YE: Real,
1445{
1446    Bar::new(x, y)
1447}
1448
1449/// Create a new [`Bar`] with options from a single iterator.
1450pub fn bar_pairs<I, XE, YE>(data: I) -> Bar
1451where
1452    I: IntoIterator<Item = (XE, YE)>,
1453    XE: Real,
1454    YE: Real,
1455{
1456    Bar::new_pairs(data)
1457}
1458
1459impl Matplotlib for Bar {
1460    fn is_prelude(&self) -> bool { false }
1461
1462    fn data(&self) -> Option<Value> {
1463        let x: Vec<Value> = self.x.iter().copied().map(Value::from).collect();
1464        let y: Vec<Value> = self.y.iter().copied().map(Value::from).collect();
1465        Some(Value::Array(vec![x.into(), y.into()]))
1466    }
1467
1468    fn py_cmd(&self) -> String {
1469        format!("ax.bar(data[0], data[1]{}{})",
1470            if self.opts.is_empty() { "" } else { ", " },
1471            self.opts.as_py(),
1472        )
1473    }
1474}
1475
1476impl MatplotlibOpts for Bar {
1477    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
1478        self.opts.push((key, val).into());
1479        self
1480    }
1481}
1482
1483/// A horizontal bar plot.
1484///
1485/// ```python
1486/// ax.barh({y}, {w}, **{opts})
1487/// ```
1488///
1489/// Prelude: **No**
1490///
1491/// JSON data: `[list[float], list[float]]`
1492#[derive(Clone, Debug, PartialEq)]
1493pub struct BarH {
1494    /// Y-coordinates.
1495    pub y: Vec<f64>,
1496    /// Bar widths.
1497    pub w: Vec<f64>,
1498    /// Optional keyword arguments.
1499    pub opts: Vec<Opt>,
1500}
1501
1502impl BarH {
1503    /// Create a new `BarH` with no options.
1504    pub fn new<Y, YE, W, WE>(y: Y, w: W) -> Self
1505    where
1506        Y: IntoIterator<Item = YE>,
1507        YE: Real,
1508        W: IntoIterator<Item = WE>,
1509        WE: Real,
1510    {
1511        Self {
1512            y: y.into_iter().map(Real::into_f64).collect(),
1513            w: w.into_iter().map(Real::into_f64).collect(),
1514            opts: Vec::new(),
1515        }
1516    }
1517
1518    /// Create a new `BarH` with options from a single iterator.
1519    pub fn new_pairs<I, YE, WE>(data: I) -> Self
1520    where
1521        I: IntoIterator<Item = (YE, WE)>,
1522        YE: Real,
1523        WE: Real,
1524    {
1525        let (y, w): (Vec<f64>, Vec<f64>) =
1526            data.into_iter()
1527            .map(|(y, w)| (y.into_f64(), w.into_f64()))
1528            .unzip();
1529        Self { y, w, opts: Vec::new() }
1530    }
1531}
1532
1533/// Create a new [`BarH`] with no options.
1534pub fn barh<Y, YE, W, WE>(y: Y, w: W) -> BarH
1535where
1536    Y: IntoIterator<Item = YE>,
1537    YE: Real,
1538    W: IntoIterator<Item = WE>,
1539    WE: Real,
1540{
1541    BarH::new(y, w)
1542}
1543
1544/// Create a new [`BarH`] with options from a single iterator.
1545pub fn barh_pairs<I, YE, WE>(data: I) -> BarH
1546where
1547    I: IntoIterator<Item = (YE, WE)>,
1548    YE: Real,
1549    WE: Real,
1550{
1551    BarH::new_pairs(data)
1552}
1553
1554impl Matplotlib for BarH {
1555    fn is_prelude(&self) -> bool { false }
1556
1557    fn data(&self) -> Option<Value> {
1558        let y: Vec<Value> = self.y.iter().copied().map(Value::from).collect();
1559        let w: Vec<Value> = self.w.iter().copied().map(Value::from).collect();
1560        Some(Value::Array(vec![y.into(), w.into()]))
1561    }
1562
1563    fn py_cmd(&self) -> String {
1564        format!("ax.barh(data[0], data[1]{}{})",
1565            if self.opts.is_empty() { "" } else { ", " },
1566            self.opts.as_py(),
1567        )
1568    }
1569}
1570
1571impl MatplotlibOpts for BarH {
1572    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
1573        self.opts.push((key, val).into());
1574        self
1575    }
1576}
1577
1578/// Plot with error bars.
1579///
1580/// ```python
1581/// ax.errorbar({x}, {y}, {e}, **{opts})
1582/// ```
1583///
1584/// Prelude: **No**
1585///
1586/// JSON data: `[list[float], list[float], list[float]]`
1587#[derive(Clone, Debug, PartialEq)]
1588pub struct Errorbar {
1589    /// X-coordinates.
1590    pub x: Vec<f64>,
1591    /// Y-coordinates.
1592    pub y: Vec<f64>,
1593    /// Symmetric error bar sizes on Y-coordinates.
1594    pub e: Vec<f64>,
1595    /// Optional keyword arguments.
1596    pub opts: Vec<Opt>,
1597}
1598
1599impl Errorbar {
1600    /// Create a new `Errorbar` with no options.
1601    pub fn new<X, XE, Y, YE, E, EE>(x: X, y: Y, e: E) -> Self
1602    where
1603        X: IntoIterator<Item = XE>,
1604        XE: Real,
1605        Y: IntoIterator<Item = YE>,
1606        YE: Real,
1607        E: IntoIterator<Item = EE>,
1608        EE: Real,
1609    {
1610        Self {
1611            x: x.into_iter().map(Real::into_f64).collect(),
1612            y: y.into_iter().map(Real::into_f64).collect(),
1613            e: e.into_iter().map(Real::into_f64).collect(),
1614            opts: Vec::new(),
1615        }
1616    }
1617
1618    /// Create a new `Errorbar` with no options from a single iterator.
1619    pub fn new_data<I, XE, YE, EE>(data: I) -> Self
1620    where
1621        I: IntoIterator<Item = (XE, YE, EE)>,
1622        XE: Real,
1623        YE: Real,
1624        EE: Real,
1625    {
1626        let ((x, y), e) =
1627            data.into_iter()
1628            .map(|(a, b, c)| (a.into_f64(), b.into_f64(), c.into_f64()))
1629            .map(assoc)
1630            .unzip();
1631        Self { x, y, e, opts: Vec::new() }
1632    }
1633}
1634
1635/// Create a new [`Errorbar`] with no options.
1636pub fn errorbar<X, XE, Y, YE, E, EE>(x: X, y: Y, e: E) -> Errorbar
1637where
1638    X: IntoIterator<Item = XE>,
1639    XE: Real,
1640    Y: IntoIterator<Item = YE>,
1641    YE: Real,
1642    E: IntoIterator<Item = EE>,
1643    EE: Real,
1644{
1645    Errorbar::new(x, y, e)
1646}
1647
1648/// Create a new [`Errorbar`] with no options from a single iterator.
1649pub fn errorbar_data<I, XE, YE, EE>(data: I) -> Errorbar
1650where
1651    I: IntoIterator<Item = (XE, YE, EE)>,
1652    XE: Real,
1653    YE: Real,
1654    EE: Real,
1655{
1656    Errorbar::new_data(data)
1657}
1658
1659impl Matplotlib for Errorbar {
1660    fn is_prelude(&self) -> bool { false }
1661
1662    fn data(&self) -> Option<Value> {
1663        let x: Vec<Value> = self.x.iter().copied().map(Value::from).collect();
1664        let y: Vec<Value> = self.y.iter().copied().map(Value::from).collect();
1665        let e: Vec<Value> = self.e.iter().copied().map(Value::from).collect();
1666        Some(Value::Array(vec![x.into(), y.into(), e.into()]))
1667    }
1668
1669    fn py_cmd(&self) -> String {
1670        format!("ax.errorbar(data[0], data[1], data[2]{}{})",
1671            if self.opts.is_empty() { "" } else { ", " },
1672            self.opts.as_py(),
1673        )
1674    }
1675}
1676
1677impl MatplotlibOpts for Errorbar {
1678    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
1679        self.opts.push((key, val).into());
1680        self
1681    }
1682}
1683
1684/// Convert a `FillBetween` to an `Errorbar`, maintaining all options.
1685impl From<FillBetween> for Errorbar {
1686    fn from(fill_between: FillBetween) -> Self {
1687        let FillBetween { x, mut y1, mut y2, opts } = fill_between;
1688        y1.iter_mut()
1689            .zip(y2.iter_mut())
1690            .for_each(|(y1k, y2k)| {
1691                let y1 = *y1k;
1692                let y2 = *y2k;
1693                *y1k = 0.5 * (y1 + y2);
1694                *y2k = 0.5 * (y1 - y2).abs();
1695            });
1696        Self { x, y: y1, e: y2, opts }
1697    }
1698}
1699
1700/// Plot with asymmetric error bars.
1701///
1702/// ```python
1703/// ax.errorbar({x}, {y}, [{e_neg}, {e_pos}], **{opts})
1704/// ```
1705///
1706/// Prelude: **No**
1707///
1708/// JSON data: `[list[float], list[float], list[float], list[float]]`
1709#[derive(Clone, Debug, PartialEq)]
1710pub struct Errorbar2 {
1711    /// X-coordinates.
1712    pub x: Vec<f64>,
1713    /// Y-coordinates.
1714    pub y: Vec<f64>,
1715    /// Negative-sided error bar sizes on Y-coordinates.
1716    pub e_neg: Vec<f64>,
1717    /// Positive-sided error bar sizes on Y-coordinates.
1718    pub e_pos: Vec<f64>,
1719    /// Optional keyword arguments.
1720    pub opts: Vec<Opt>,
1721}
1722
1723impl Errorbar2 {
1724    /// Create a new `Errorbar2` with no options.
1725    pub fn new<X, XE, Y, YE, E1, E1E, E2, E2E>(
1726        x: X,
1727        y: Y,
1728        e_neg: E1,
1729        e_pos: E2,
1730    ) -> Self
1731    where
1732        X: IntoIterator<Item = XE>,
1733        XE: Real,
1734        Y: IntoIterator<Item = YE>,
1735        YE: Real,
1736        E1: IntoIterator<Item = E1E>,
1737        E1E: Real,
1738        E2: IntoIterator<Item = E2E>,
1739        E2E: Real,
1740    {
1741        Self {
1742            x: x.into_iter().map(Real::into_f64).collect(),
1743            y: y.into_iter().map(Real::into_f64).collect(),
1744            e_neg: e_neg.into_iter().map(Real::into_f64).collect(),
1745            e_pos: e_pos.into_iter().map(Real::into_f64).collect(),
1746            opts: Vec::new(),
1747        }
1748    }
1749
1750    /// Create a new `Errorbar2` with no options from a single iterator.
1751    pub fn new_data<I, XE, YE, E1E, E2E>(data: I) -> Self
1752    where
1753        I: IntoIterator<Item = (XE, YE, E1E, E2E)>,
1754        XE: Real,
1755        YE: Real,
1756        E1E: Real,
1757        E2E: Real,
1758    {
1759        let (((x, y), e_neg), e_pos) =
1760            data.into_iter()
1761            .map(|(a, b, c, d)| {
1762                (a.into_f64(), b.into_f64(), c.into_f64(), d.into_f64())
1763            })
1764            .map(assoc)
1765            .unzip();
1766        Self { x, y, e_neg, e_pos, opts: Vec::new() }
1767    }
1768}
1769
1770/// Create a new [`Errorbar2`] with no options.
1771pub fn errorbar2<X, XE, Y, YE, E1, E1E, E2, E2E>(
1772    x: X,
1773    y: Y,
1774    e_neg: E1,
1775    e_pos: E2,
1776) -> Errorbar2
1777where
1778    X: IntoIterator<Item = XE>,
1779    XE: Real,
1780    Y: IntoIterator<Item = YE>,
1781    YE: Real,
1782    E1: IntoIterator<Item = E1E>,
1783    E1E: Real,
1784    E2: IntoIterator<Item = E2E>,
1785    E2E: Real,
1786{
1787    Errorbar2::new(x, y, e_neg, e_pos)
1788}
1789
1790/// Create a new [`Errorbar2`] with no options from a single iterator.
1791pub fn errorbar2_data<I, XE, YE, E1E, E2E>(data: I) -> Errorbar2
1792where
1793    I: IntoIterator<Item = (XE, YE, E1E, E2E)>,
1794    XE: Real,
1795    YE: Real,
1796    E1E: Real,
1797    E2E: Real,
1798{
1799    Errorbar2::new_data(data)
1800}
1801
1802impl Matplotlib for Errorbar2 {
1803    fn is_prelude(&self) -> bool { false }
1804
1805    fn data(&self) -> Option<Value> {
1806        let x: Vec<Value> = self.x.iter().copied().map(Value::from).collect();
1807        let y: Vec<Value> = self.y.iter().copied().map(Value::from).collect();
1808        let e_neg: Vec<Value> =
1809            self.e_neg.iter().copied().map(Value::from).collect();
1810        let e_pos: Vec<Value> =
1811            self.e_pos.iter().copied().map(Value::from).collect();
1812        Some(Value::Array(
1813            vec![x.into(), y.into(), e_neg.into(), e_pos.into()]))
1814    }
1815
1816    fn py_cmd(&self) -> String {
1817        format!("ax.errorbar(data[0], data[1], [data[2], data[3]]{}{})",
1818            if self.opts.is_empty() { "" } else { ", " },
1819            self.opts.as_py(),
1820        )
1821    }
1822}
1823
1824impl MatplotlibOpts for Errorbar2 {
1825    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
1826        self.opts.push((key, val).into());
1827        self
1828    }
1829}
1830
1831struct Chunks<I, T>
1832where I: Iterator<Item = T>
1833{
1834    chunksize: usize,
1835    buflen: usize,
1836    buf: Vec<T>,
1837    iter: I,
1838}
1839
1840impl<I, T> Chunks<I, T>
1841where I: Iterator<Item = T>
1842{
1843    fn new(iter: I, chunksize: usize) -> Self {
1844        if chunksize == 0 { panic!("chunk size cannot be zero"); }
1845        Self {
1846            chunksize,
1847            buflen: 0,
1848            buf: Vec::with_capacity(chunksize),
1849            iter,
1850        }
1851    }
1852}
1853
1854impl<I, T> Iterator for Chunks<I, T>
1855where I: Iterator<Item = T>
1856{
1857    type Item = Vec<T>;
1858
1859    fn next(&mut self) -> Option<Self::Item> {
1860        loop {
1861            if let Some(item) = self.iter.next() {
1862                self.buf.push(item);
1863                self.buflen += 1;
1864                if self.buflen == self.chunksize {
1865                    let mut bufswap = Vec::with_capacity(self.chunksize);
1866                    std::mem::swap(&mut bufswap, &mut self.buf);
1867                    self.buflen = 0;
1868                    return Some(bufswap);
1869                } else {
1870                    continue;
1871                }
1872            } else if self.buflen > 0 {
1873                let mut bufswap = Vec::with_capacity(0);
1874                std::mem::swap(&mut bufswap, &mut self.buf);
1875                self.buflen = 0;
1876                return Some(bufswap);
1877            } else {
1878                return None;
1879            }
1880        }
1881    }
1882}
1883
1884
1885/// Box(-and-whisker) plots for a number of data sets.
1886///
1887/// ```python
1888/// ax.boxplot({data}, **{opts})
1889/// ```
1890///
1891/// Prelude: **No**
1892///
1893/// JSON data: `list[list[float]]`
1894#[derive(Clone, Debug, PartialEq)]
1895pub struct Boxplot {
1896    /// List of data sets.
1897    pub data: Vec<Vec<f64>>,
1898    /// Optional keyword arguments.
1899    pub opts: Vec<Opt>,
1900}
1901
1902impl Boxplot {
1903    /// Create a new `Boxplot` with no options.
1904    pub fn new<I, J, E>(data: I) -> Self
1905    where
1906        I: IntoIterator<Item = J>,
1907        J: IntoIterator<Item = E>,
1908        E: Real,
1909    {
1910        let data: Vec<Vec<f64>> =
1911            data.into_iter()
1912            .map(|row| row.into_iter().map(Real::into_f64).collect())
1913            .collect();
1914        Self { data, opts: Vec::new() }
1915    }
1916
1917    /// Create a new `Boxplot` from a flattened iterator over a number of data
1918    /// set of size `size`.
1919    ///
1920    /// The last data set is truncated if `size` does not evenly divide the
1921    /// length of the iterator.
1922    ///
1923    /// *Panics if `size == 0`*.
1924    pub fn new_flat<I, E>(data: I, size: usize) -> Self
1925    where
1926        I: IntoIterator<Item = E>,
1927        E: Real,
1928    {
1929        if size == 0 { panic!("data set size cannot be zero"); }
1930        let data: Vec<Vec<f64>> =
1931            Chunks::new(data.into_iter().map(Real::into_f64), size)
1932            .collect();
1933        Self { data, opts: Vec::new() }
1934    }
1935}
1936
1937/// Create a new [`Boxplot`] with no options.
1938pub fn boxplot<I, J, E>(data: I) -> Boxplot
1939where
1940    I: IntoIterator<Item = J>,
1941    J: IntoIterator<Item = E>,
1942    E: Real,
1943{
1944    Boxplot::new(data)
1945}
1946
1947/// Create a new [`Boxplot`] from a flattened iterator over a number of data
1948/// set of size `size`.
1949///
1950/// The last data set is truncated if `size` does not evenly divide the
1951/// length of the iterator.
1952///
1953/// *Panics if `size == 0`*.
1954pub fn boxplot_flat<I, E>(data: I, size: usize) -> Boxplot
1955where
1956    I: IntoIterator<Item = E>,
1957    E: Real,
1958{
1959    Boxplot::new_flat(data, size)
1960}
1961
1962impl Matplotlib for Boxplot {
1963    fn is_prelude(&self) -> bool { false }
1964
1965    fn data(&self) -> Option<Value> {
1966        let data: Vec<Value> =
1967            self.data.iter()
1968            .map(|row| {
1969                let row: Vec<Value> =
1970                    row.iter().copied().map(Value::from).collect();
1971                Value::Array(row)
1972            })
1973            .collect();
1974        Some(Value::Array(data))
1975    }
1976
1977    fn py_cmd(&self) -> String {
1978        format!("ax.boxplot(data{}{})",
1979            if self.opts.is_empty() { "" } else { ", " },
1980            self.opts.as_py(),
1981        )
1982    }
1983}
1984
1985impl MatplotlibOpts for Boxplot {
1986    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
1987        self.opts.push((key, val).into());
1988        self
1989    }
1990}
1991
1992/// Violin plots for a number of data sets.
1993///
1994/// ```python
1995/// ax.violinplot({data}, **{opts})
1996/// ```
1997///
1998/// Prelude: **No**
1999///
2000/// JSON data: `list[list[float]]`
2001#[derive(Clone, Debug, PartialEq)]
2002pub struct Violinplot {
2003    /// List of data sets.
2004    pub data: Vec<Vec<f64>>,
2005    /// Optional keyword arguments.
2006    pub opts: Vec<Opt>,
2007}
2008
2009impl Violinplot {
2010    /// Create a new `Violinplot` with no options.
2011    pub fn new<I, J, E>(data: I) -> Self
2012    where
2013        I: IntoIterator<Item = J>,
2014        J: IntoIterator<Item = E>,
2015        E: Real,
2016    {
2017        let data: Vec<Vec<f64>> =
2018            data.into_iter()
2019            .map(|row| row.into_iter().map(Real::into_f64).collect())
2020            .collect();
2021        Self { data, opts: Vec::new() }
2022    }
2023
2024    /// Create a new `Violinplot` from a flattened iterator over a number of
2025    /// data set of size `size`.
2026    ///
2027    /// The last data set is truncated if `size` does not evenly divide the
2028    /// length of the iterator.
2029    ///
2030    /// *Panics if `size == 0`*.
2031    pub fn new_flat<I, E>(data: I, size: usize) -> Self
2032    where
2033        I: IntoIterator<Item = E>,
2034        E: Real,
2035    {
2036        if size == 0 { panic!("data set size cannot be zero"); }
2037        let data: Vec<Vec<f64>> =
2038            Chunks::new(data.into_iter().map(Real::into_f64), size)
2039            .collect();
2040        Self { data, opts: Vec::new() }
2041    }
2042}
2043
2044/// Create a new [`Violinplot`] with no options.
2045pub fn violinplot<I, J, E>(data: I) -> Violinplot
2046where
2047    I: IntoIterator<Item = J>,
2048    J: IntoIterator<Item = E>,
2049    E: Real,
2050{
2051    Violinplot::new(data)
2052}
2053
2054/// Create a new [`Violinplot`] from a flattened iterator over a number of
2055/// data set of size `size`.
2056///
2057/// The last data set is truncated if `size` does not evenly divide the
2058/// length of the iterator.
2059///
2060/// *Panics if `size == 0`*.
2061pub fn violinplot_flat<I, E>(data: I, size: usize) -> Violinplot
2062where
2063    I: IntoIterator<Item = E>,
2064    E: Real,
2065{
2066    Violinplot::new_flat(data, size)
2067}
2068
2069impl Matplotlib for Violinplot {
2070    fn is_prelude(&self) -> bool { false }
2071
2072    fn data(&self) -> Option<Value> {
2073        let data: Vec<Value> =
2074            self.data.iter()
2075            .map(|row| {
2076                let row: Vec<Value> =
2077                    row.iter().copied().map(Value::from).collect();
2078                Value::Array(row)
2079            })
2080            .collect();
2081        Some(Value::Array(data))
2082    }
2083
2084    fn py_cmd(&self) -> String {
2085        format!("ax.violinplot(data{}{})",
2086            if self.opts.is_empty() { "" } else { ", " },
2087            self.opts.as_py(),
2088        )
2089    }
2090}
2091
2092impl MatplotlibOpts for Violinplot {
2093    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
2094        self.opts.push((key, val).into());
2095        self
2096    }
2097}
2098
2099/// A contour plot for a (*x*, *y*, *z*) surface.
2100///
2101/// This command sets a local variable `im` to the output of the call to
2102/// `contour` for use with [`Colorbar`]
2103///
2104/// ```python
2105/// im = ax.contour({x}, {y}, {z}, **{opts})
2106/// ```
2107///
2108/// Prelude: **No**
2109///
2110/// JSON data: `[list[float], list[float], list[list[float]]]`
2111///
2112/// **Note**: No checking is performed for the shapes/sizes of the data arrays.
2113#[derive(Clone, Debug, PartialEq)]
2114pub struct Contour {
2115    /// X-coordinates.
2116    pub x: Vec<f64>,
2117    /// Y-coordinates.
2118    pub y: Vec<f64>,
2119    /// Z-coordinates.
2120    ///
2121    /// Columns correspond to x-coordinates.
2122    pub z: Vec<Vec<f64>>,
2123    /// Optional keyword arguments.
2124    pub opts: Vec<Opt>,
2125}
2126
2127impl Contour {
2128    /// Create a new `Contour` with no options.
2129    pub fn new<X, XE, Y, YE, ZI, ZJ, ZE>(x: X, y: Y, z: ZI) -> Self
2130    where
2131        X: IntoIterator<Item = XE>,
2132        XE: Real,
2133        Y: IntoIterator<Item = YE>,
2134        YE: Real,
2135        ZI: IntoIterator<Item = ZJ>,
2136        ZJ: IntoIterator<Item = ZE>,
2137        ZE: Real,
2138    {
2139        let x: Vec<f64> = x.into_iter().map(Real::into_f64).collect();
2140        let y: Vec<f64> = y.into_iter().map(Real::into_f64).collect();
2141        let z: Vec<Vec<f64>> =
2142            z.into_iter()
2143            .map(|row| row.into_iter().map(Real::into_f64).collect())
2144            .collect();
2145        Self { x, y, z, opts: Vec::new() }
2146    }
2147
2148    /// Create a new `Contour` with no options using a flattened iterator over
2149    /// z-coordinates.
2150    ///
2151    /// *Panics if the number of x-coordinates is zero*.
2152    pub fn new_flat<X, XE, Y, YE, Z, ZE>(x: X, y: Y, z: Z) -> Self
2153    where
2154        X: IntoIterator<Item = XE>,
2155        XE: Real,
2156        Y: IntoIterator<Item = YE>,
2157        YE: Real,
2158        Z: IntoIterator<Item = ZE>,
2159        ZE: Real,
2160    {
2161        let x: Vec<f64> = x.into_iter().map(Real::into_f64).collect();
2162        if x.is_empty() { panic!("x-coordinate array cannot be empty"); }
2163        let y: Vec<f64> = y.into_iter().map(Real::into_f64).collect();
2164        let z: Vec<Vec<f64>> =
2165            Chunks::new(z.into_iter().map(Real::into_f64), x.len())
2166            .collect();
2167        Self { x, y, z, opts: Vec::new() }
2168    }
2169}
2170
2171/// Create a new [`Contour`] with no options.
2172pub fn contour<X, XE, Y, YE, ZI, ZJ, ZE>(x: X, y: Y, z: ZI) -> Contour
2173where
2174    X: IntoIterator<Item = XE>,
2175    XE: Real,
2176    Y: IntoIterator<Item = YE>,
2177    YE: Real,
2178    ZI: IntoIterator<Item = ZJ>,
2179    ZJ: IntoIterator<Item = ZE>,
2180    ZE: Real,
2181{
2182    Contour::new(x, y, z)
2183}
2184
2185/// Create a new [`Contour`] with no options using a flattened iterator over
2186/// z-coordinates.
2187///
2188/// *Panics if the number of x-coordinates is zero*.
2189pub fn contour_flat<X, XE, Y, YE, Z, ZE>(x: X, y: Y, z: Z) -> Contour
2190where
2191    X: IntoIterator<Item = XE>,
2192    XE: Real,
2193    Y: IntoIterator<Item = YE>,
2194    YE: Real,
2195    Z: IntoIterator<Item = ZE>,
2196    ZE: Real,
2197{
2198    Contour::new_flat(x, y, z)
2199}
2200
2201impl Matplotlib for Contour {
2202    fn is_prelude(&self) -> bool { false }
2203
2204    fn data(&self) -> Option<Value> {
2205        let x: Vec<Value> = self.x.iter().copied().map(Value::from).collect();
2206        let y: Vec<Value> = self.y.iter().copied().map(Value::from).collect();
2207        let z: Vec<Value> =
2208            self.z.iter()
2209            .map(|row| {
2210                let row: Vec<Value> =
2211                    row.iter().copied().map(Value::from).collect();
2212                Value::Array(row)
2213            })
2214            .collect();
2215        Some(Value::Array(vec![x.into(), y.into(), z.into()]))
2216    }
2217
2218    fn py_cmd(&self) -> String {
2219        format!("im = ax.contour(data[0], data[1], data[2]{}{})",
2220            if self.opts.is_empty() { "" } else { ", " },
2221            self.opts.as_py(),
2222        )
2223    }
2224}
2225
2226impl MatplotlibOpts for Contour {
2227    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
2228        self.opts.push((key, val).into());
2229        self
2230    }
2231}
2232
2233/// Turn on labels for the contour in a [`Contour`] plot.
2234///
2235/// ```python
2236/// ax.clabel(im, {levels?}, fmt={fmt?}, **{opts})
2237/// ```
2238///
2239/// Prelude: **No**
2240///
2241/// JSON data: **None**
2242#[derive(Clone, Debug, PartialEq, Default)]
2243pub struct ContourLabels {
2244    /// Optionally specify a subset of level to label.
2245    ///
2246    /// If `None`, all levels are labeled.
2247    pub levels: Option<Vec<f64>>,
2248    /// Optionally specify a formatter as a `lambda` function.
2249    ///
2250    /// The first element of the tuple is the function's argument name, and the
2251    /// second is its body as raw Python source.
2252    pub fmt: Option<(String, Raw)>,
2253    /// Optional keyword arguments.
2254    pub opts: Vec<Opt>,
2255}
2256
2257impl ContourLabels {
2258    /// Create a new `ContourLabels` with no options and no formatter.
2259    pub fn new() -> Self {
2260        Self { levels: None, fmt: None, opts: Vec::new() }
2261    }
2262
2263    /// Create a new `ContourLabels` labeling only a subset of levels with no
2264    /// options and no formatter.
2265    pub fn new_levels<I, E>(levels: I) -> Self
2266    where
2267        I: IntoIterator<Item = E>,
2268        E: Real,
2269    {
2270        Self {
2271            levels: Some(levels.into_iter().map(Real::into_f64).collect()),
2272            fmt: None,
2273            opts: Vec::new(),
2274        }
2275    }
2276
2277    /// Specify a subset of levels to label.
2278    pub fn on_levels<I, E>(mut self, levels: I) -> Self
2279    where
2280        I: IntoIterator<Item = E>,
2281        E: Real,
2282    {
2283        self.levels = Some(levels.into_iter().map(Real::into_f64).collect());
2284        self
2285    }
2286
2287    /// Specify a formatting `lambda` function.
2288    ///
2289    /// No checks are performed to ensure that `arg` is a valid Python
2290    /// identifier.
2291    pub fn with_fmt(mut self, arg: &str, body: &str) -> Self {
2292        self.fmt = Some((arg.to_string(), Raw(body.to_string())));
2293        self
2294    }
2295}
2296
2297/// Create a new [`ContourLabels`] with no options and no formatter.
2298pub fn contour_labels() -> ContourLabels {
2299    ContourLabels::new()
2300}
2301
2302impl Matplotlib for ContourLabels {
2303    fn is_prelude(&self) -> bool { false }
2304
2305    fn data(&self) -> Option<Value> { None }
2306
2307    fn py_cmd(&self) -> String {
2308        let levels_str =
2309            if let Some(levels) = self.levels.as_ref() {
2310                let mut acc = ", [".to_string();
2311                let n = levels.len();
2312                for (k, level) in levels.iter().enumerate() {
2313                    acc += &level.as_py();
2314                    if k < n - 1 { acc += ", "; }
2315                }
2316                acc += "]";
2317                acc
2318            } else {
2319                ", im.levels".to_string()
2320            };
2321        let fmt_str =
2322            if let Some((arg, Raw(body))) = self.fmt.as_ref() {
2323                format!(", fmt=lambda {}: {}", arg, body)
2324            } else {
2325                "".to_string()
2326            };
2327        format!("ax.clabel(im{}{}{}{})",
2328            levels_str,
2329            fmt_str,
2330            if self.opts.is_empty() { "" } else { ", " },
2331            self.opts.as_py(),
2332        )
2333    }
2334}
2335
2336impl MatplotlibOpts for ContourLabels {
2337    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
2338        self.opts.push((key, val).into());
2339        self
2340    }
2341}
2342
2343/// A filled contour plot for a (*x*, *y*, *z*) surface.
2344///
2345/// This command sets a local variable `im` to the output of the call to
2346/// `contourf` for use with [`Colorbar`].
2347///
2348/// ```python
2349/// im = ax.contourf({x}, {y}, {z}, **{opts})
2350/// ```
2351///
2352/// Prelude: **No**
2353///
2354/// JSON data: `[list[float], list[float], list[list[float]]]`
2355///
2356/// **Note**: No checking is performed for the shapes/sizes of the data arrays.
2357#[derive(Clone, Debug, PartialEq)]
2358pub struct Contourf {
2359    /// X-coordinates.
2360    pub x: Vec<f64>,
2361    /// Y-coordinates.
2362    pub y: Vec<f64>,
2363    /// Z-coordinates.
2364    ///
2365    /// Columns correspond to x-coordinates.
2366    pub z: Vec<Vec<f64>>,
2367    /// Optional keyword arguments.
2368    pub opts: Vec<Opt>,
2369}
2370
2371impl Contourf {
2372    /// Create a new `Contourf` with no options.
2373    pub fn new<X, XE, Y, YE, ZI, ZJ, ZE>(x: X, y: Y, z: ZI) -> Self
2374    where
2375        X: IntoIterator<Item = XE>,
2376        XE: Real,
2377        Y: IntoIterator<Item = YE>,
2378        YE: Real,
2379        ZI: IntoIterator<Item = ZJ>,
2380        ZJ: IntoIterator<Item = ZE>,
2381        ZE: Real,
2382    {
2383        let x: Vec<f64> = x.into_iter().map(Real::into_f64).collect();
2384        let y: Vec<f64> = y.into_iter().map(Real::into_f64).collect();
2385        let z: Vec<Vec<f64>> =
2386            z.into_iter()
2387            .map(|row| row.into_iter().map(Real::into_f64).collect())
2388            .collect();
2389        Self { x, y, z, opts: Vec::new() }
2390    }
2391
2392    /// Create a new `Contourf` with no options using a flattened iterator over
2393    /// z-coordinates.
2394    ///
2395    /// *Panics if the number of x-coordinates is zero*.
2396    pub fn new_flat<X, XE, Y, YE, Z, ZE>(x: X, y: Y, z: Z) -> Self
2397    where
2398        X: IntoIterator<Item = XE>,
2399        XE: Real,
2400        Y: IntoIterator<Item = YE>,
2401        YE: Real,
2402        Z: IntoIterator<Item = ZE>,
2403        ZE: Real,
2404    {
2405        let x: Vec<f64> = x.into_iter().map(Real::into_f64).collect();
2406        if x.is_empty() { panic!("x-coordinate array cannot be empty"); }
2407        let y: Vec<f64> = y.into_iter().map(Real::into_f64).collect();
2408        let z: Vec<Vec<f64>> =
2409            Chunks::new(z.into_iter().map(Real::into_f64), x.len())
2410            .collect();
2411        Self { x, y, z, opts: Vec::new() }
2412    }
2413}
2414
2415/// Create a new [`Contourf`] with no options.
2416pub fn contourf<X, XE, Y, YE, ZI, ZJ, ZE>(x: X, y: Y, z: ZI) -> Contourf
2417where
2418    X: IntoIterator<Item = XE>,
2419    XE: Real,
2420    Y: IntoIterator<Item = YE>,
2421    YE: Real,
2422    ZI: IntoIterator<Item = ZJ>,
2423    ZJ: IntoIterator<Item = ZE>,
2424    ZE: Real,
2425{
2426    Contourf::new(x, y, z)
2427}
2428
2429/// Create a new [`Contourf`] with no options using a flattened iterator over
2430/// z-coordinates.
2431///
2432/// *Panics if the number of x-coordinates is zero*.
2433pub fn contourf_flat<X, XE, Y, YE, Z, ZE>(x: X, y: Y, z: Z) -> Contourf
2434where
2435    X: IntoIterator<Item = XE>,
2436    XE: Real,
2437    Y: IntoIterator<Item = YE>,
2438    YE: Real,
2439    Z: IntoIterator<Item = ZE>,
2440    ZE: Real,
2441{
2442    Contourf::new_flat(x, y, z)
2443}
2444
2445impl Matplotlib for Contourf {
2446    fn is_prelude(&self) -> bool { false }
2447
2448    fn data(&self) -> Option<Value> {
2449        let x: Vec<Value> = self.x.iter().copied().map(Value::from).collect();
2450        let y: Vec<Value> = self.y.iter().copied().map(Value::from).collect();
2451        let z: Vec<Value> =
2452            self.z.iter()
2453            .map(|row| {
2454                let row: Vec<Value> =
2455                    row.iter().copied().map(Value::from).collect();
2456                Value::Array(row)
2457            })
2458            .collect();
2459        Some(Value::Array(vec![x.into(), y.into(), z.into()]))
2460    }
2461
2462    fn py_cmd(&self) -> String {
2463        format!("im = ax.contourf(data[0], data[1], data[2]{}{})",
2464            if self.opts.is_empty() { "" } else { ", " },
2465            self.opts.as_py(),
2466        )
2467    }
2468}
2469
2470impl MatplotlibOpts for Contourf {
2471    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
2472        self.opts.push((key, val).into());
2473        self
2474    }
2475}
2476
2477/// A single color cell in an image plot.
2478///
2479/// Can be a single scalar value to be color-mapped, a RGB triple (0-1 float or
2480/// 0-255 integer), or a RGBA 4-tuple.
2481#[derive(Copy, Clone, Debug, PartialEq)]
2482pub enum ColorCell {
2483    Scalar(f64),
2484    Rgb(u8, u8, u8),
2485    RgbFloat(f64, f64, f64),
2486    Rgba(u8, u8, u8, u8),
2487    RgbaFloat(f64, f64, f64, f64),
2488}
2489
2490impl From<f64> for ColorCell {
2491    fn from(x: f64) -> Self { Self::Scalar(x) }
2492}
2493
2494impl From<&f64> for ColorCell {
2495    fn from(x: &f64) -> Self { Self::Scalar(*x) }
2496}
2497
2498impl From<(u8, u8, u8)> for ColorCell {
2499    fn from((r, g, b): (u8, u8, u8)) -> Self { Self::Rgb(r, g, b) }
2500}
2501
2502impl From<&(u8, u8, u8)> for ColorCell {
2503    fn from((r, g, b): &(u8, u8, u8)) -> Self { Self::Rgb(*r, *g, *b) }
2504}
2505
2506impl From<(&u8, &u8, &u8)> for ColorCell {
2507    fn from((r, g, b): (&u8, &u8, &u8)) -> Self { Self::Rgb(*r, *g, *b) }
2508}
2509
2510impl From<(f64, f64, f64)> for ColorCell {
2511    fn from((r, g, b): (f64, f64, f64)) -> Self { Self::RgbFloat(r, g, b) }
2512}
2513
2514impl From<&(f64, f64, f64)> for ColorCell {
2515    fn from((r, g, b): &(f64, f64, f64)) -> Self { Self::RgbFloat(*r, *g, *b) }
2516}
2517
2518impl From<(&f64, &f64, &f64)> for ColorCell {
2519    fn from((r, g, b): (&f64, &f64, &f64)) -> Self {
2520        Self::RgbFloat(*r, *g, *b)
2521    }
2522}
2523
2524impl From<(u8, u8, u8, u8)> for ColorCell {
2525    fn from((r, g, b, a): (u8, u8, u8, u8)) -> Self { Self::Rgba(r, g, b, a) }
2526}
2527
2528impl From<&(u8, u8, u8, u8)> for ColorCell {
2529    fn from((r, g, b, a): &(u8, u8, u8, u8)) -> Self {
2530        Self::Rgba(*r, *g, *b, *a)
2531    }
2532}
2533
2534impl From<(&u8, &u8, &u8, &u8)> for ColorCell {
2535    fn from((r, g, b, a): (&u8, &u8, &u8, &u8)) -> Self {
2536        Self::Rgba(*r, *g, *b, *a)
2537    }
2538}
2539
2540impl From<(f64, f64, f64, f64)> for ColorCell {
2541    fn from((r, g, b, a): (f64, f64, f64, f64)) -> Self {
2542        Self::RgbaFloat(r, g, b, a)
2543    }
2544}
2545
2546impl From<&(f64, f64, f64, f64)> for ColorCell {
2547    fn from((r, g, b, a): &(f64, f64, f64, f64)) -> Self {
2548        Self::RgbaFloat(*r, *g, *b, *a)
2549    }
2550}
2551
2552impl From<(&f64, &f64, &f64, &f64)> for ColorCell {
2553    fn from((r, g, b, a): (&f64, &f64, &f64, &f64)) -> Self {
2554        Self::RgbaFloat(*r, *g, *b, *a)
2555    }
2556}
2557
2558impl From<ColorCell> for Value {
2559    fn from(cell: ColorCell) -> Self {
2560        match cell {
2561            ColorCell::Scalar(x) => Self::from(x),
2562            ColorCell::Rgb(r, g, b) => {
2563                let r = Number::from(r);
2564                let g = Number::from(g);
2565                let b = Number::from(b);
2566                Self::Array(vec![r.into(), g.into(), b.into()])
2567            },
2568            ColorCell::RgbFloat(r, g, b) => {
2569                let r = Number::from_f64(r)
2570                    .expect("encountered infinity or NaN");
2571                let g = Number::from_f64(g)
2572                    .expect("encountered infinity or NaN");
2573                let b = Number::from_f64(b)
2574                    .expect("encountered infinity or NaN");
2575                Self::Array(vec![r.into(), g.into(), b.into()])
2576            },
2577            ColorCell::Rgba(r, g, b, a) => {
2578                let r = Number::from(r);
2579                let g = Number::from(g);
2580                let b = Number::from(b);
2581                let a = Number::from(a);
2582                Self::Array(vec![r.into(), g.into(), b.into(), a.into()])
2583            },
2584            ColorCell::RgbaFloat(r, g, b, a) => {
2585                let r = Number::from_f64(r)
2586                    .expect("encountered infinity or NaN");
2587                let g = Number::from_f64(g)
2588                    .expect("encountered infinity or NaN");
2589                let b = Number::from_f64(b)
2590                    .expect("encountered infinity or NaN");
2591                let a = Number::from_f64(a)
2592                    .expect("encountered infinity or NaN");
2593                Self::Array(vec![r.into(), g.into(), b.into(), a.into()])
2594            },
2595        }
2596    }
2597}
2598
2599impl From<ColorCell> for PyValue {
2600    fn from(cell: ColorCell) -> Self {
2601        match cell {
2602            ColorCell::Scalar(x) => Self::Float(x),
2603            ColorCell::Rgb(r, g, b) =>
2604                Self::list([r as i32, g as i32, b as i32]),
2605            ColorCell::RgbFloat(r, g, b) => Self::list([r, g, b]),
2606            ColorCell::Rgba(r, g, b, a) =>
2607                Self::list([r as i32, g as i32, b as i32, a as i32]),
2608            ColorCell::RgbaFloat(r, g, b, a) => Self::list([r, g, b, a]),
2609        }
2610    }
2611}
2612
2613/// A 2D data set as an image.
2614///
2615/// This command sets a local variable `im` to the output of the call to
2616/// `imshow` for use with [`Colorbar`].
2617///
2618/// ```python
2619/// im = ax.imshow({data}, **{opts})
2620/// ```
2621///
2622/// Prelude: **No**
2623///
2624/// JSON data: `list[list[float | list[float]]]`
2625#[derive(Clone, Debug, PartialEq)]
2626pub struct Imshow {
2627    /// Image data.
2628    pub data: Vec<Vec<ColorCell>>,
2629    /// Optional keyword arguments.
2630    pub opts: Vec<Opt>,
2631}
2632
2633impl Imshow {
2634    /// Create a new `Imshow` with no options.
2635    pub fn new<I, J, C>(data: I) -> Self
2636    where
2637        I: IntoIterator<Item = J>,
2638        J: IntoIterator<Item = C>,
2639        C: Into<ColorCell>,
2640    {
2641        let data: Vec<Vec<ColorCell>> =
2642            data.into_iter()
2643            .map(|row| row.into_iter().map(|c| c.into()).collect())
2644            .collect();
2645        Self { data, opts: Vec::new() }
2646    }
2647
2648    /// Create a new `Imshow` from a flattened, row-major iterator over image
2649    /// data with row length `rowlen`.
2650    ///
2651    /// *Panics if `rowlen == 0`*.
2652    pub fn new_flat<I, C>(data: I, rowlen: usize) -> Self
2653    where
2654        I: IntoIterator<Item = C>,
2655        C: Into<ColorCell>,
2656    {
2657        if rowlen == 0 { panic!("row length cannot be zero"); }
2658        let data: Vec<Vec<ColorCell>> =
2659            Chunks::new(data.into_iter().map(|c| c.into()), rowlen)
2660            .collect();
2661        Self { data, opts: Vec::new() }
2662    }
2663
2664    /// Create a new `Imshow` from a flattened, column-major iterator over image
2665    /// data with column length `collen`.
2666    ///
2667    /// *Panics of `collen == 0`*.
2668    pub fn new_flat_c<I, C>(data: I, collen: usize) -> Self
2669    where
2670        I: IntoIterator<Item = C>,
2671        C: Into<ColorCell>,
2672    {
2673        if collen == 0 { panic!("column length cannot be zero"); }
2674        let mut cells: Vec<Vec<ColorCell>> =
2675            (0 .. collen).map(|_| Vec::new()).collect();
2676        Chunks::new(data.into_iter().map(|c| c.into()), collen)
2677            .for_each(|chunk| {
2678                cells.iter_mut().zip(chunk)
2679                    .for_each(|(cell, c)| { cell.push(c); });
2680            });
2681        Self { data: cells, opts: Vec::new() }
2682    }
2683}
2684
2685/// Create a new [`Imshow`] with no options.
2686pub fn imshow<I, J, C>(data: I) -> Imshow
2687where
2688    I: IntoIterator<Item = J>,
2689    J: IntoIterator<Item = C>,
2690    C: Into<ColorCell>,
2691{
2692    Imshow::new(data)
2693}
2694
2695/// Create a new [`Imshow`] from a flattened, row-major iterator over image
2696/// data with row length `rowlen`.
2697///
2698/// *Panics if `rowlen == 0`*.
2699pub fn imshow_flat<I, C>(data: I, rowlen: usize) -> Imshow
2700where
2701    I: IntoIterator<Item = C>,
2702    C: Into<ColorCell>,
2703{
2704    Imshow::new_flat(data, rowlen)
2705}
2706
2707/// Create a new [`Imshow`] from a flattened, column-major iterator over image
2708/// data with column length `collen`.
2709///
2710/// *Panics if `collen == 0`*.
2711pub fn imshow_flat_c<I, C>(data: I, collen: usize) -> Imshow
2712where
2713    I: IntoIterator<Item = C>,
2714    C: Into<ColorCell>,
2715{
2716    Imshow::new_flat_c(data, collen)
2717}
2718
2719impl Matplotlib for Imshow {
2720    fn is_prelude(&self) -> bool { false }
2721
2722    fn data(&self) -> Option<Value> {
2723        let data: Vec<Value> =
2724            self.data.iter()
2725            .map(|row| {
2726                let row: Vec<Value> =
2727                    row.iter().copied().map(Value::from).collect();
2728                Value::Array(row)
2729            })
2730            .collect();
2731        Some(Value::Array(data))
2732    }
2733
2734    fn py_cmd(&self) -> String {
2735        format!("im = ax.imshow(data{}{})",
2736            if self.opts.is_empty() { "" } else { ", " },
2737            self.opts.as_py(),
2738        )
2739    }
2740}
2741
2742impl MatplotlibOpts for Imshow {
2743    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
2744        self.opts.push((key, val).into());
2745        self
2746    }
2747}
2748
2749/// A 2D, rectangular data set as an image over non-uniformly spaced
2750/// coordinates.
2751///
2752/// This command sets a local variable `im` to the output of a call to
2753/// `mimage.NonUniformImage` for use with [`Colorbar`]. The interpolation method
2754/// for the plot defaults to `"nearest"`, but may be overridden.
2755///
2756/// ```python
2757/// _dx = ({x}[1] - {x}[0], {x}[-1] - {x}[-2])
2758/// _dy = ({y}[1] - {y}[0], {y}[-1] - {y}[-2])
2759/// _extent = [
2760///     {x}[0] - _dx[0] / 2.0, {x}[-1] + _dx[1] / 2.0,
2761///     {y}[0] - _dy[0] / 2.0, {y}[-1] + _dy[1] / 2.0,
2762/// ]
2763/// im = mimage.NonUniformImage(
2764///     ax,
2765///     extent=_extent, **{dict(interpolation="nearest") | opts}
2766/// )
2767/// im.set_data({x}, {y}, {z})
2768/// ax.add_image(im)
2769/// ax.set_xlim(_extent[0], _extent[1])
2770/// ax.set_ylim(_extent[2], _extent[3])
2771/// del _dx, _dy, _extent
2772/// ```
2773///
2774/// Prelude: **No**
2775///
2776/// JSON data: `list[list[float], list[float], list[list[float | list[float]]]]`
2777#[derive(Clone, Debug, PartialEq)]
2778pub struct Colorplot {
2779    /// X-coordinates.
2780    pub x: Vec<f64>,
2781    /// Y-coordinates.
2782    pub y: Vec<f64>,
2783    /// Z-values.
2784    pub z: Vec<Vec<ColorCell>>,
2785    /// Optional keyword arguments.
2786    pub opts: Vec<Opt>,
2787}
2788
2789impl Colorplot {
2790    /// Create a new `Colorplot` with no options.
2791    pub fn new<X, XE, Y, YE, I, J, C>(x: X, y: Y, z: I) -> Self
2792    where
2793        X: IntoIterator<Item = XE>,
2794        XE: Real,
2795        Y: IntoIterator<Item = YE>,
2796        YE: Real,
2797        I: IntoIterator<Item = J>,
2798        J: IntoIterator<Item = C>,
2799        C: Into<ColorCell>,
2800    {
2801        let x: Vec<f64> = x.into_iter().map(Real::into_f64).collect();
2802        let y: Vec<f64> = y.into_iter().map(Real::into_f64).collect();
2803        let z: Vec<Vec<ColorCell>> =
2804            z.into_iter()
2805            .map(|row| row.into_iter().map(|c| c.into()).collect())
2806            .collect();
2807        Self { x, y, z, opts: Vec::new() }
2808    }
2809
2810    /// Create a new `Colorplot` from a flattened, row-major iterator over image
2811    /// data.
2812    pub fn new_flat<X, XE, Y, YE, Z, C>(x: X, y: Y, z: Z) -> Self
2813    where
2814        X: IntoIterator<Item = XE>,
2815        XE: Real,
2816        Y: IntoIterator<Item = YE>,
2817        YE: Real,
2818        Z: IntoIterator<Item = C>,
2819        C: Into<ColorCell>,
2820    {
2821        let x: Vec<f64> = x.into_iter().map(Real::into_f64).collect();
2822        let y: Vec<f64> = y.into_iter().map(Real::into_f64).collect();
2823        if x.is_empty() { panic!("x-coordinate array cannot be empty"); }
2824        let z: Vec<Vec<ColorCell>> =
2825            Chunks::new(z.into_iter().map(|c| c.into()), x.len())
2826            .collect();
2827        Self { x, y, z, opts: Vec::new() }
2828    }
2829
2830    /// Create a new `Colorplot` from a flattened, column-major iterator over
2831    /// image data.
2832    pub fn new_flat_c<X, XE, Y, YE, Z, C>(x: X, y: Y, z: Z) -> Self
2833    where
2834        X: IntoIterator<Item = XE>,
2835        XE: Real,
2836        Y: IntoIterator<Item = YE>,
2837        YE: Real,
2838        Z: IntoIterator<Item = C>,
2839        C: Into<ColorCell>,
2840    {
2841        let x: Vec<f64> = x.into_iter().map(Real::into_f64).collect();
2842        let y: Vec<f64> = y.into_iter().map(Real::into_f64).collect();
2843        if y.is_empty() { panic!("y-coordinate array cannot be empty"); }
2844        let mut cells: Vec<Vec<ColorCell>> =
2845            (0 .. y.len()).map(|_| Vec::new()).collect();
2846        Chunks::new(z.into_iter().map(|c| c.into()), y.len())
2847            .for_each(|chunk| {
2848                cells.iter_mut().zip(chunk)
2849                    .for_each(|(cell, c)| { cell.push(c); });
2850            });
2851        Self { x, y, z: cells, opts: Vec::new() }
2852    }
2853}
2854
2855/// Create a new [`Colorplot`] with no options.
2856pub fn colorplot<X, XE, Y, YE, I, J, C>(x: X, y: Y, z: I) -> Colorplot
2857where
2858    X: IntoIterator<Item = XE>,
2859    XE: Real,
2860    Y: IntoIterator<Item = YE>,
2861    YE: Real,
2862    I: IntoIterator<Item = J>,
2863    J: IntoIterator<Item = C>,
2864    C: Into<ColorCell>,
2865{
2866    Colorplot::new(x, y, z)
2867}
2868
2869/// Create a new [`Colorplot`] from a flattened, row-major iterator over image
2870/// data.
2871pub fn colorplot_flat<X, XE, Y, YE, Z, C>(x: X, y: Y, z: Z) -> Colorplot
2872where
2873    X: IntoIterator<Item = XE>,
2874    XE: Real,
2875    Y: IntoIterator<Item = YE>,
2876    YE: Real,
2877    Z: IntoIterator<Item = C>,
2878    C: Into<ColorCell>,
2879{
2880    Colorplot::new_flat(x, y, z)
2881}
2882
2883/// Create a new [`Colorplot`] from a flattened, column-major iterator over
2884/// image data.
2885pub fn colorplot_flat_c<X, XE, Y, YE, Z, C>(x: X, y: Y, z: Z) -> Colorplot
2886where
2887    X: IntoIterator<Item = XE>,
2888    XE: Real,
2889    Y: IntoIterator<Item = YE>,
2890    YE: Real,
2891    Z: IntoIterator<Item = C>,
2892    C: Into<ColorCell>,
2893{
2894    Colorplot::new_flat_c(x, y, z)
2895}
2896
2897impl Matplotlib for Colorplot {
2898    fn is_prelude(&self) -> bool { false }
2899
2900    fn data(&self) -> Option<Value> {
2901        let x: Vec<Value> = self.x.iter().copied().map(Value::from).collect();
2902        let y: Vec<Value> = self.y.iter().copied().map(Value::from).collect();
2903        let z: Vec<Value> =
2904            self.z.iter()
2905            .map(|row| {
2906                let row: Vec<Value> =
2907                    row.iter().copied().map(Value::from).collect();
2908                Value::Array(row)
2909            })
2910            .collect();
2911        Some(Value::Array(vec![x.into(), y.into(), z.into()]))
2912    }
2913
2914    fn py_cmd(&self) -> String {
2915        format!("\
2916_dx = (data[0][1] - data[0][0], data[0][-1] - data[0][-2])
2917_dy = (data[1][1] - data[1][0], data[1][-1] - data[1][-2])
2918_extent = [
2919    data[0][0] - _dx[0] / 2.0, data[0][-1] + _dx[1] / 2.0,
2920    data[1][0] - _dy[0] / 2.0, data[1][-1] + _dy[1] / 2.0,
2921]
2922_opts = dict(interpolation=\"nearest\") | dict({})
2923im = mimage.NonUniformImage(ax, extent=_extent, **_opts)
2924im.set_data(data[0], data[1], data[2])
2925ax.add_image(im)
2926ax.set_xlim(_extent[0], _extent[1])
2927ax.set_ylim(_extent[2], _extent[3])
2928del _dx, _dy, _extent, _opts",
2929            self.opts.as_py(),
2930        )
2931    }
2932}
2933
2934impl MatplotlibOpts for Colorplot {
2935    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
2936        self.opts.push((key, val).into());
2937        self
2938    }
2939}
2940
2941/// A filled area between two horizontal curves.
2942///
2943/// ```python
2944/// ax.fill_between({x}, {y1}, {y2}, **{opts})
2945/// ```
2946///
2947/// Prelude: **No**
2948///
2949/// JSON data: `[list[float], list[float], list[float]]`
2950#[derive(Clone, Debug, PartialEq)]
2951pub struct FillBetween {
2952    /// X-coordinates.
2953    pub x: Vec<f64>,
2954    /// Y-coordinates of the first curve.
2955    pub y1: Vec<f64>,
2956    /// Y-coordinates of the second curve.
2957    pub y2: Vec<f64>,
2958    /// Optional keyword arguments.
2959    pub opts: Vec<Opt>,
2960}
2961
2962impl FillBetween {
2963    /// Create a new `FillBetween` with no options.
2964    pub fn new<X, XE, Y1, Y1E, Y2, Y2E>(x: X, y1: Y1, y2: Y2) -> Self
2965    where
2966        X: IntoIterator<Item = XE>,
2967        XE: Real,
2968        Y1: IntoIterator<Item = Y1E>,
2969        Y1E: Real,
2970        Y2: IntoIterator<Item = Y2E>,
2971        Y2E: Real,
2972    {
2973        Self {
2974            x: x.into_iter().map(Real::into_f64).collect(),
2975            y1: y1.into_iter().map(Real::into_f64).collect(),
2976            y2: y2.into_iter().map(Real::into_f64).collect(),
2977            opts: Vec::new(),
2978        }
2979    }
2980
2981    /// Create a new `FillBetween` with no options from a single iterator.
2982    pub fn new_data<I, XE, Y1E, Y2E>(data: I) -> Self
2983    where
2984        I: IntoIterator<Item = (XE, Y1E, Y2E)>,
2985        XE: Real,
2986        Y1E: Real,
2987        Y2E: Real,
2988    {
2989        let ((x, y1), y2) =
2990            data.into_iter()
2991            .map(|(a, b, c)| (a.into_f64(), b.into_f64(), c.into_f64()))
2992            .map(assoc)
2993            .unzip();
2994        Self { x, y1, y2, opts: Vec::new() }
2995    }
2996}
2997
2998/// Create a new [`FillBetween`] with no options.
2999pub fn fill_between<X, XE, Y1, Y1E, Y2, Y2E>(
3000    x: X,
3001    y1: Y1,
3002    y2: Y2,
3003) -> FillBetween
3004where
3005    X: IntoIterator<Item = XE>,
3006    XE: Real,
3007    Y1: IntoIterator<Item = Y1E>,
3008    Y1E: Real,
3009    Y2: IntoIterator<Item = Y2E>,
3010    Y2E: Real,
3011{
3012    FillBetween::new(x, y1, y2)
3013}
3014
3015/// Create a new [`FillBetween`] with no options from a single iterator.
3016pub fn fill_between_data<I, XE, Y1E, Y2E>(data: I) -> FillBetween
3017where
3018    I: IntoIterator<Item = (XE, Y1E, Y2E)>,
3019    XE: Real,
3020    Y1E: Real,
3021    Y2E: Real,
3022{
3023    FillBetween::new_data(data)
3024}
3025
3026impl Matplotlib for FillBetween {
3027    fn is_prelude(&self) -> bool { false }
3028
3029    fn data(&self) -> Option<Value> {
3030        let x: Vec<Value> = self.x.iter().copied().map(Value::from).collect();
3031        let y1: Vec<Value> = self.y1.iter().copied().map(Value::from).collect();
3032        let y2: Vec<Value> = self.y2.iter().copied().map(Value::from).collect();
3033        Some(Value::Array(vec![x.into(), y1.into(), y2.into()]))
3034    }
3035
3036    fn py_cmd(&self) -> String {
3037        format!("ax.fill_between(data[0], data[1], data[2]{}{})",
3038            if self.opts.is_empty() { "" } else { ", " },
3039            self.opts.as_py(),
3040        )
3041    }
3042}
3043
3044impl MatplotlibOpts for FillBetween {
3045    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
3046        self.opts.push((key, val).into());
3047        self
3048    }
3049}
3050
3051/// Convert an `Errorbar` to a `FillBetween`, maintaining all options.
3052impl From<Errorbar> for FillBetween {
3053    fn from(errorbar: Errorbar) -> Self {
3054        let Errorbar { x, mut y, mut e, opts } = errorbar;
3055        y.iter_mut()
3056            .zip(e.iter_mut())
3057            .for_each(|(yk, ek)| {
3058                let y = *yk;
3059                let e = *ek;
3060                *yk -= e;
3061                *ek += y;
3062            });
3063        Self { x, y1: y, y2: e, opts }
3064    }
3065}
3066
3067/// Convert an `Errorbar2` to a `FillBetween`, maintaining all options.
3068impl From<Errorbar2> for FillBetween {
3069    fn from(errorbar2: Errorbar2) -> Self {
3070        let Errorbar2 { x, mut y, mut e_neg, e_pos, opts } = errorbar2;
3071        y.iter_mut()
3072            .zip(e_neg.iter_mut().zip(e_pos.iter()))
3073            .for_each(|(yk, (emk, epk))| {
3074                let y = *yk;
3075                let em = *emk;
3076                let ep = *epk;
3077                *yk -= em;
3078                *emk = y + ep;
3079            });
3080        Self { x, y1: y, y2: e_neg, opts }
3081    }
3082}
3083
3084/// A filled area between two vertical curves.
3085///
3086/// ```python
3087/// ax.fill_betweenx({y}, {x1}, {x2}, **{opts})
3088/// ```
3089///
3090/// Prelude: **No**
3091///
3092/// JSON data: `[list[float], list[float], list[float]]`
3093#[derive(Clone, Debug, PartialEq)]
3094pub struct FillBetweenX {
3095    /// Y-coordinates.
3096    pub y: Vec<f64>,
3097    /// X-coordinates of the first curve.
3098    pub x1: Vec<f64>,
3099    /// X-coordinates of the second curve.
3100    pub x2: Vec<f64>,
3101    /// Optional keyword arguments.
3102    pub opts: Vec<Opt>,
3103}
3104
3105impl FillBetweenX {
3106    /// Create a new `FillBetweenX` with no options.
3107    pub fn new<Y, YE, X1, X1E, X2, X2E>(y: Y, x1: X1, x2: X2) -> Self
3108    where
3109        Y: IntoIterator<Item = YE>,
3110        YE: Real,
3111        X1: IntoIterator<Item = X1E>,
3112        X1E: Real,
3113        X2: IntoIterator<Item = X2E>,
3114        X2E: Real,
3115    {
3116        Self {
3117            y: y.into_iter().map(Real::into_f64).collect(),
3118            x1: x1.into_iter().map(Real::into_f64).collect(),
3119            x2: x2.into_iter().map(Real::into_f64).collect(),
3120            opts: Vec::new(),
3121        }
3122    }
3123
3124    /// Create a new `FillBetweenX` with no options from a single iterator.
3125    pub fn new_data<I, YE, X1E, X2E>(data: I) -> Self
3126    where
3127        I: IntoIterator<Item = (YE, X1E, X2E)>,
3128        YE: Real,
3129        X1E: Real,
3130        X2E: Real,
3131    {
3132        let ((y, x1), x2) =
3133            data.into_iter()
3134            .map(|(a, b, c)| (a.into_f64(), b.into_f64(), c.into_f64()))
3135            .map(assoc)
3136            .unzip();
3137        Self { y, x1, x2, opts: Vec::new() }
3138    }
3139}
3140
3141/// Create a new [`FillBetweenX`] with no options.
3142pub fn fill_betweenx<Y, YE, X1, X1E, X2, X2E>(
3143    y: Y,
3144    x1: X1,
3145    x2: X2,
3146) -> FillBetweenX
3147where
3148    Y: IntoIterator<Item = YE>,
3149    YE: Real,
3150    X1: IntoIterator<Item = X1E>,
3151    X1E: Real,
3152    X2: IntoIterator<Item = X2E>,
3153    X2E: Real,
3154{
3155    FillBetweenX::new(y, x1, x2)
3156}
3157
3158/// Create a new [`FillBetweenX`] with no options from a single iterator.
3159pub fn fill_betweenx_data<I, YE, X1E, X2E>(data: I) -> FillBetweenX
3160where
3161    I: IntoIterator<Item = (YE, X1E, X2E)>,
3162    YE: Real,
3163    X1E: Real,
3164    X2E: Real,
3165{
3166    FillBetweenX::new_data(data)
3167}
3168
3169impl Matplotlib for FillBetweenX {
3170    fn is_prelude(&self) -> bool { false }
3171
3172    fn data(&self) -> Option<Value> {
3173        let y: Vec<Value> = self.y.iter().copied().map(Value::from).collect();
3174        let x1: Vec<Value> = self.x1.iter().copied().map(Value::from).collect();
3175        let x2: Vec<Value> = self.x2.iter().copied().map(Value::from).collect();
3176        Some(Value::Array(vec![y.into(), x1.into(), x2.into()]))
3177    }
3178
3179    fn py_cmd(&self) -> String {
3180        format!("ax.fill_betweenx(data[0], data[1], data[2]{}{})",
3181            if self.opts.is_empty() { "" } else { ", " },
3182            self.opts.as_py(),
3183        )
3184    }
3185}
3186
3187impl MatplotlibOpts for FillBetweenX {
3188    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
3189        self.opts.push((key, val).into());
3190        self
3191    }
3192}
3193
3194/// A horizontal line.
3195///
3196/// ```python
3197/// ax.axhline({y}, **{opts})
3198/// ```
3199///
3200/// Prelude: **No**
3201///
3202/// JSON data: **None**
3203#[derive(Clone, Debug, PartialEq)]
3204pub struct AxHLine {
3205    /// Y-coordinate of the line.
3206    pub y: f64,
3207    /// Optional keyword arguments.
3208    pub opts: Vec<Opt>,
3209}
3210
3211impl AxHLine {
3212    /// Create a new `AxHLine` with no options.
3213    pub fn new(y: f64) -> Self {
3214        Self { y, opts: Vec::new() }
3215    }
3216}
3217
3218/// Create a new [`AxHLine`] with no options.
3219pub fn axhline(y: f64) -> AxHLine { AxHLine::new(y) }
3220
3221impl Matplotlib for AxHLine {
3222    fn is_prelude(&self) -> bool { false }
3223
3224    fn data(&self) -> Option<Value> { None }
3225
3226    fn py_cmd(&self) -> String {
3227        format!("ax.axhline({}{}{})",
3228            self.y.as_py(),
3229            if self.opts.is_empty() { "" } else { ", " },
3230            self.opts.as_py(),
3231        )
3232    }
3233}
3234
3235impl MatplotlibOpts for AxHLine {
3236    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
3237        self.opts.push((key, val).into());
3238        self
3239    }
3240}
3241
3242/// A vertical line.
3243///
3244/// ```python
3245/// ax.axvline({x}, **{opts})
3246/// ```
3247///
3248/// Prelude: **No**
3249///
3250/// JSON data: **None**
3251#[derive(Clone, Debug, PartialEq)]
3252pub struct AxVLine {
3253    /// X-coordinate of the line.
3254    pub x: f64,
3255    /// Optional keyword arguments.
3256    pub opts: Vec<Opt>,
3257}
3258
3259impl AxVLine {
3260    /// Create a new `AxVLine` with no options.
3261    pub fn new(x: f64) -> Self {
3262        Self { x, opts: Vec::new() }
3263    }
3264}
3265
3266/// Create a new [`AxVLine`] with no options.
3267pub fn axvline(x: f64) -> AxVLine { AxVLine::new(x) }
3268
3269impl Matplotlib for AxVLine {
3270    fn is_prelude(&self) -> bool { false }
3271
3272    fn data(&self) -> Option<Value> { None }
3273
3274    fn py_cmd(&self) -> String {
3275        format!("ax.axvline({}{}{})",
3276            self.x.as_py(),
3277            if self.opts.is_empty() { "" } else { ", " },
3278            self.opts.as_py(),
3279        )
3280    }
3281}
3282
3283impl MatplotlibOpts for AxVLine {
3284    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
3285        self.opts.push((key, val).into());
3286        self
3287    }
3288}
3289
3290/// A line passing through two points.
3291///
3292/// ```python
3293/// ax.axline({xy1}, {xy2}, **{opts})
3294/// ```
3295#[derive(Clone, Debug, PartialEq)]
3296pub struct AxLine {
3297    /// First (*x*, *y*) point.
3298    pub xy1: (f64, f64),
3299    /// Second (*x*, *y*) point.
3300    pub xy2: (f64, f64),
3301    /// Optional keyword arguments.
3302    pub opts: Vec<Opt>,
3303}
3304
3305impl AxLine {
3306    /// Create a new `AxLine` with no options.
3307    pub fn new(xy1: (f64, f64), xy2: (f64, f64)) -> Self {
3308        Self { xy1, xy2, opts: Vec::new() }
3309    }
3310}
3311
3312/// Create a new [`AxLine`] with no options.
3313pub fn axline(xy1: (f64, f64), xy2: (f64, f64)) -> AxLine {
3314    AxLine::new(xy1, xy2)
3315}
3316
3317impl Matplotlib for AxLine {
3318    fn is_prelude(&self) -> bool { false }
3319
3320    fn data(&self) -> Option<Value> { None }
3321
3322    fn py_cmd(&self) -> String {
3323        format!("ax.axline({}, {}{}{})",
3324            self.xy1.as_py(),
3325            self.xy2.as_py(),
3326            if self.opts.is_empty() { "" } else { ", " },
3327            self.opts.as_py(),
3328        )
3329    }
3330}
3331
3332impl MatplotlibOpts for AxLine {
3333    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
3334        self.opts.push((key, val).into());
3335        self
3336    }
3337}
3338
3339/// A line passing through one point with a slope.
3340///
3341/// ```python
3342/// ax.axline({xy}, xy2=None, slope={m}, **{opts})
3343/// ```
3344#[derive(Clone, Debug, PartialEq)]
3345pub struct AxLineM {
3346    /// (*x*, *y*) point.
3347    pub xy: (f64, f64),
3348    /// Slope.
3349    pub m: f64,
3350    /// Optional keyword arguments.
3351    pub opts: Vec<Opt>,
3352}
3353
3354impl AxLineM {
3355    /// Create a new `AxLineM` with no options.
3356    pub fn new(xy: (f64, f64), m: f64) -> Self {
3357        Self { xy, m, opts: Vec::new() }
3358    }
3359}
3360
3361/// Create a new [`AxLineM`] with no options.
3362pub fn axlinem(xy: (f64, f64), m: f64) -> AxLineM { AxLineM::new(xy, m) }
3363
3364impl Matplotlib for AxLineM {
3365    fn is_prelude(&self) -> bool { false }
3366
3367    fn data(&self) -> Option<Value> { None }
3368
3369    fn py_cmd(&self) -> String {
3370        format!("ax.axline({}, xy2=None, slope={}{}{})",
3371            self.xy.as_py(),
3372            self.m.as_py(),
3373            if self.opts.is_empty() { "" } else { ", " },
3374            self.opts.as_py(),
3375        )
3376    }
3377}
3378
3379impl MatplotlibOpts for AxLineM {
3380    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
3381        self.opts.push((key, val).into());
3382        self
3383    }
3384}
3385
3386/// A pie chart for a single data set.
3387///
3388/// ```python
3389/// ax.pie({data}, **{opts})
3390/// ```
3391///
3392/// Prelude: **No**
3393///
3394/// JSON data: `list[float]`
3395#[derive(Clone, Debug, PartialEq)]
3396pub struct Pie {
3397    /// Data values.
3398    pub data: Vec<f64>,
3399    /// Optional keyword arguments.
3400    pub opts: Vec<Opt>,
3401}
3402
3403impl Pie {
3404    /// Create a new `Pie` with no options.
3405    pub fn new<I, E>(data: I) -> Self
3406    where
3407        I: IntoIterator<Item = E>,
3408        E: Real,
3409    {
3410        Self {
3411            data: data.into_iter().map(Real::into_f64).collect(),
3412            opts: Vec::new(),
3413        }
3414    }
3415}
3416
3417/// Create a new [`Pie`] with no options.
3418pub fn pie<I, E>(data: I) -> Pie
3419where
3420    I: IntoIterator<Item = E>,
3421    E: Real,
3422{
3423    Pie::new(data)
3424}
3425
3426impl Matplotlib for Pie {
3427    fn is_prelude(&self) -> bool { false }
3428
3429    fn data(&self) -> Option<Value> {
3430        Some(Value::Array(
3431            self.data.iter().copied().map(Value::from).collect()))
3432    }
3433
3434    fn py_cmd(&self) -> String {
3435        format!("ax.pie(data{}{})",
3436            if self.opts.is_empty() { "" } else { ", " },
3437            self.opts.as_py(),
3438        )
3439    }
3440}
3441
3442impl MatplotlibOpts for Pie {
3443    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
3444        self.opts.push((key, val).into());
3445        self
3446    }
3447}
3448
3449/// Some text placed in a plot via data coordinates.
3450///
3451/// ```python
3452/// ax.text({x}, {y}, {s}, **{opts})
3453/// ```
3454///
3455/// Prelude: **No**
3456///
3457/// JSON data: `[float, float, str]`
3458///
3459/// See also [`AxText`].
3460#[derive(Clone, Debug, PartialEq)]
3461pub struct Text {
3462    /// X-coordinate.
3463    pub x: f64,
3464    /// Y-coordinate.
3465    pub y: f64,
3466    /// Text to place.
3467    pub s: String,
3468    /// Optional keyword arguments.
3469    pub opts: Vec<Opt>,
3470}
3471
3472impl Text {
3473    /// Create a new `Text` with no options.
3474    pub fn new(x: f64, y: f64, s: &str) -> Self {
3475        Self { x, y, s: s.into(), opts: Vec::new() }
3476    }
3477}
3478
3479/// Create a new [`Text`] with no options.
3480pub fn text(x: f64, y: f64, s: &str) -> Text { Text::new(x, y, s) }
3481
3482impl Matplotlib for Text {
3483    fn is_prelude(&self) -> bool { false }
3484
3485    fn data(&self) -> Option<Value> {
3486        Some(Value::Array(vec![
3487            self.x.into(),
3488            self.y.into(),
3489            (&*self.s).into(),
3490        ]))
3491    }
3492
3493    fn py_cmd(&self) -> String {
3494        format!("ax.text(data[0], data[1], data[2]{}{})",
3495            if self.opts.is_empty() { "" } else { ", " },
3496            self.opts.as_py(),
3497        )
3498    }
3499}
3500
3501impl MatplotlibOpts for Text {
3502    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
3503        self.opts.push((key, val).into());
3504        self
3505    }
3506}
3507
3508/// Some text placed in a plot via axes [0, 1] coordinates.
3509///
3510/// ```python
3511/// ax.text({x}, {y}, {s}, transform=ax.transAxes, **{opts})
3512/// ```
3513///
3514/// Prelude: **No**
3515///
3516/// JSON data: `[float, float, str]`
3517#[derive(Clone, Debug, PartialEq)]
3518pub struct AxText {
3519    /// X-coordinate.
3520    pub x: f64,
3521    /// Y-coordinate.
3522    pub y: f64,
3523    /// Text to place.
3524    pub s: String,
3525    /// Option keyword arguments.
3526    pub opts: Vec<Opt>,
3527}
3528
3529impl AxText {
3530    /// Create a new `AxText` with no options.
3531    pub fn new(x: f64, y: f64, s: &str) -> Self {
3532        Self { x, y, s: s.into(), opts: Vec::new() }
3533    }
3534}
3535
3536/// Create a new [`AxText`] with no options.
3537pub fn axtext(x: f64, y: f64, s: &str) -> AxText { AxText::new(x, y, s) }
3538
3539impl Matplotlib for AxText {
3540    fn is_prelude(&self) -> bool { false }
3541
3542    fn data(&self) -> Option<Value> {
3543        Some(Value::Array(vec![
3544            self.x.into(),
3545            self.y.into(),
3546            (&*self.s).into(),
3547        ]))
3548    }
3549
3550    fn py_cmd(&self) -> String {
3551        format!(
3552            "ax.text(data[0], data[1], data[2], transform=ax.transAxes{}{})",
3553            if self.opts.is_empty() { "" } else { ", " },
3554            self.opts.as_py(),
3555        )
3556    }
3557}
3558
3559impl MatplotlibOpts for AxText {
3560    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
3561        self.opts.push((key, val).into());
3562        self
3563    }
3564}
3565
3566/// Some text placed in a figure via figure [0, 1] coordinates.
3567///
3568/// ```python
3569/// fig.text({x}, {y}, {s}, **{opts})
3570/// ```
3571///
3572/// Prelude: **No**
3573///
3574/// JSON data: `[float, float, str]`
3575///
3576/// **Note** that this python command calls a method of the `fig` variable,
3577/// rather than `ax`.
3578#[derive(Clone, Debug, PartialEq)]
3579pub struct FigText {
3580    /// X-coordinate.
3581    pub x: f64,
3582    /// Y-coordinate.
3583    pub y: f64,
3584    /// Text to place.
3585    pub s: String,
3586    /// Option keyword arguments.
3587    pub opts: Vec<Opt>,
3588}
3589
3590impl FigText {
3591    /// Create a new `FigText` with no options.
3592    pub fn new(x: f64, y: f64, s: &str) -> Self {
3593        Self { x, y, s: s.into(), opts: Vec::new() }
3594    }
3595}
3596
3597/// Create a new [`FigText`] with no options.
3598pub fn figtext(x: f64, y: f64, s: &str) -> FigText { FigText::new(x, y, s) }
3599
3600impl Matplotlib for FigText {
3601    fn is_prelude(&self) -> bool { false }
3602
3603    fn data(&self) -> Option<Value> {
3604        Some(Value::Array(vec![
3605            self.x.into(),
3606            self.y.into(),
3607            (&*self.s).into(),
3608        ]))
3609    }
3610
3611    fn py_cmd(&self) -> String {
3612        format!("fig.text(data[0], data[1], data[2]{}{})",
3613            if self.opts.is_empty() { "" } else { ", " },
3614            self.opts.as_py(),
3615        )
3616    }
3617}
3618
3619impl MatplotlibOpts for FigText {
3620    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
3621        self.opts.push((key, val).into());
3622        self
3623    }
3624}
3625
3626/// Add a colorbar to the figure.
3627///
3628/// This command relies on the local variable `im` being defined and set equal
3629/// to the output of a plotting command to which a color map can be applied
3630/// (e.g. [`Imshow`]). The output of this command is stored in a local variable
3631/// `cbar`.
3632///
3633/// ```python
3634/// cbar = fig.colorbar(im, ax=ax, **{opts})
3635/// ```
3636///
3637/// Prelude: **No**
3638///
3639/// JSON data: **None**
3640#[derive(Clone, Debug, PartialEq)]
3641pub struct Colorbar {
3642    /// Optional keyword arguments.
3643    pub opts: Vec<Opt>,
3644}
3645
3646impl Default for Colorbar {
3647    fn default() -> Self { Self::new() }
3648}
3649
3650impl Colorbar {
3651    /// Create a new `Colorbar` with no options.
3652    pub fn new() -> Self { Self { opts: Vec::new() } }
3653}
3654
3655/// Create a new [`Colorbar`] with no options.
3656pub fn colorbar() -> Colorbar { Colorbar::new() }
3657
3658impl Matplotlib for Colorbar {
3659    fn is_prelude(&self) -> bool { false }
3660
3661    fn data(&self) -> Option<Value> { None }
3662
3663    fn py_cmd(&self) -> String {
3664        format!("cbar = fig.colorbar(im, ax=ax{}{})",
3665            if self.opts.is_empty() { "" } else { ", " },
3666            self.opts.as_py(),
3667        )
3668    }
3669}
3670
3671impl MatplotlibOpts for Colorbar {
3672    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
3673        self.opts.push((key, val).into());
3674        self
3675    }
3676}
3677
3678/// Set the scaling of an axis.
3679///
3680/// ```python
3681/// ax.set_{axis}scale("{scale}")
3682/// ```
3683///
3684/// Prelude: **No**
3685///
3686/// JSON data: **None**
3687#[derive(Copy, Clone, Debug, PartialEq, Eq)]
3688pub struct Scale {
3689    /// Which axis to scale.
3690    pub axis: Axis,
3691    /// What scaling to use.
3692    pub scale: AxisScale,
3693}
3694
3695impl Scale {
3696    /// Create a new `Scale`.
3697    pub fn new(axis: Axis, scale: AxisScale) -> Self { Self { axis, scale } }
3698}
3699
3700/// Create a new [`Scale`].
3701pub fn scale(axis: Axis, scale: AxisScale) -> Scale { Scale::new(axis, scale) }
3702
3703/// Create a new [`Scale`] for the X-axis.
3704pub fn xscale(scale: AxisScale) -> Scale { Scale::new(Axis::X, scale) }
3705
3706/// Create a new [`Scale`] for the Y-axis.
3707pub fn yscale(scale: AxisScale) -> Scale { Scale::new(Axis::Y, scale) }
3708
3709/// Create a new [`Scale`] for the Z-axis.
3710pub fn zscale(scale: AxisScale) -> Scale { Scale::new(Axis::Z, scale) }
3711
3712impl Matplotlib for Scale {
3713    fn is_prelude(&self) -> bool { false }
3714
3715    fn data(&self) -> Option<Value> { None }
3716
3717    fn py_cmd(&self) -> String {
3718        let ax = format!("{:?}", self.axis).to_lowercase();
3719        let sc = format!("{:?}", self.scale).to_lowercase();
3720        format!("ax.set_{}scale(\"{}\")", ax, sc)
3721    }
3722}
3723
3724/// An axis of a Matplotlib `Axes` or `Axes3D` object.
3725#[derive(Copy, Clone, Debug, PartialEq, Eq)]
3726pub enum Axis {
3727    /// The X-axis.
3728    X,
3729    /// The Y-axis.
3730    Y,
3731    /// The Z-axis.
3732    Z,
3733}
3734
3735/// An axis scaling.
3736#[derive(Copy, Clone, Debug, PartialEq, Eq)]
3737pub enum AxisScale {
3738    /// Linear scaling.
3739    Linear,
3740    /// Logarithmic scaling.
3741    Log,
3742    /// Symmetric logarithmic scaling.
3743    ///
3744    /// Allows for negative values by scaling their absolute values.
3745    SymLog,
3746    /// Scaling through the logit function.
3747    ///
3748    /// ```python
3749    /// logit(x) = log(x / (1 - x))
3750    /// ```
3751    ///
3752    /// Specifically designed for values in the (0, 1) range.
3753    Logit,
3754}
3755
3756/// Set the plotting limits of an axis.
3757///
3758/// ```python
3759/// ax.set_{axis}lim({min}, {max})
3760/// ```
3761///
3762/// Prelude: **No**
3763///
3764/// JSON data: **None**
3765#[derive(Copy, Clone, Debug, PartialEq)]
3766pub struct Lim {
3767    /// Which axis.
3768    pub axis: Axis,
3769    /// Minimum value.
3770    ///
3771    /// Pass `None` to auto-set.
3772    pub min: Option<f64>,
3773    /// Maximum value.
3774    ///
3775    /// Pass `None` to auto-set.
3776    pub max: Option<f64>,
3777}
3778
3779impl Lim {
3780    /// Create a new `Lim`.
3781    pub fn new(axis: Axis, min: Option<f64>, max: Option<f64>) -> Self {
3782        Self { axis, min, max }
3783    }
3784}
3785
3786/// Create a new [`Lim`].
3787pub fn lim(axis: Axis, min: Option<f64>, max: Option<f64>) -> Lim {
3788    Lim::new(axis, min, max)
3789}
3790
3791/// Create a new [`Lim`] for the X-axis.
3792pub fn xlim(min: Option<f64>, max: Option<f64>) -> Lim {
3793    Lim::new(Axis::X, min, max)
3794}
3795
3796/// Create a new [`Lim`] for the Y-axis.
3797pub fn ylim(min: Option<f64>, max: Option<f64>) -> Lim {
3798    Lim::new(Axis::Y, min, max)
3799}
3800
3801/// Create a new [`Lim`] for the Z-axis.
3802pub fn zlim(min: Option<f64>, max: Option<f64>) -> Lim {
3803    Lim::new(Axis::Z, min, max)
3804}
3805
3806impl Matplotlib for Lim {
3807    fn is_prelude(&self) -> bool { false }
3808
3809    fn data(&self) -> Option<Value> { None }
3810
3811    fn py_cmd(&self) -> String {
3812        let ax = format!("{:?}", self.axis).to_lowercase();
3813        let min = self.min.as_ref().map(|x| x.as_py()).unwrap_or("None".into());
3814        let max = self.max.as_ref().map(|x| x.as_py()).unwrap_or("None".into());
3815        format!("ax.set_{}lim({}, {})", ax, min, max)
3816    }
3817}
3818
3819/// Set the plotting limits of the colorbar.
3820///
3821/// This relies on an existing local variable `im` produced by e.g. [`Imshow`].
3822///
3823/// ```python
3824/// im.set_clim({min}, {max})
3825/// ```
3826///
3827/// Prelude: **No**
3828///
3829/// JSON data: **None**
3830#[derive(Copy, Clone, Debug, PartialEq)]
3831pub struct CLim {
3832    /// Minimum value.
3833    ///
3834    /// Pass `None` to auto-set.
3835    pub min: Option<f64>,
3836    /// Maximum value.
3837    ///
3838    /// Pass `None` to auto-set.
3839    pub max: Option<f64>,
3840}
3841
3842impl CLim {
3843    /// Create a new `CLim`.
3844    pub fn new(min: Option<f64>, max: Option<f64>) -> Self {
3845        Self { min, max }
3846    }
3847}
3848
3849/// Create a new [`CLim`].
3850pub fn clim(min: Option<f64>, max: Option<f64>) -> CLim { CLim::new(min, max) }
3851
3852impl Matplotlib for CLim {
3853    fn is_prelude(&self) -> bool { false }
3854
3855    fn data(&self) -> Option<Value> { None }
3856
3857    fn py_cmd(&self) -> String {
3858        let min = self.min.as_ref().map(|x| x.as_py()).unwrap_or("None".into());
3859        let max = self.max.as_ref().map(|x| x.as_py()).unwrap_or("None".into());
3860        format!("im.set_clim({}, {})", min, max)
3861    }
3862}
3863
3864/// Set the title of a set of axes.
3865///
3866/// ```python
3867/// ax.set_title("{s}", **{opts})
3868/// ```
3869///
3870/// Prelude: **No**
3871///
3872/// JSON data: **None**
3873#[derive(Clone, Debug, PartialEq)]
3874pub struct Title {
3875    /// Axes title.
3876    pub s: String,
3877    /// Optional keyword arguments.
3878    pub opts: Vec<Opt>,
3879}
3880
3881impl Title {
3882    /// Create a new `Title` with no options.
3883    pub fn new(s: &str) -> Self {
3884        Self { s: s.into(), opts: Vec::new() }
3885    }
3886}
3887
3888/// Create a new [`Title`] with no options.
3889pub fn title(s: &str) -> Title { Title::new(s) }
3890
3891impl Matplotlib for Title {
3892    fn is_prelude(&self) -> bool { false }
3893
3894    fn data(&self) -> Option<Value> { None }
3895
3896    fn py_cmd(&self) -> String {
3897        format!("ax.set_title({}{}{})",
3898            self.s.as_py(),
3899            if self.opts.is_empty() { "" } else { ", " },
3900            self.opts.as_py(),
3901        )
3902    }
3903}
3904
3905impl MatplotlibOpts for Title {
3906    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
3907        self.opts.push((key, val).into());
3908        self
3909    }
3910}
3911
3912/// Set a label on a set of axes.
3913///
3914/// ```python
3915/// ax.set_{axis}label("{s}", **{opts})
3916/// ```
3917///
3918/// Prelude: **No**
3919///
3920/// JSON data: **None**
3921#[derive(Clone, Debug, PartialEq)]
3922pub struct Label {
3923    /// Which axis to label.
3924    pub axis: Axis,
3925    /// Axis label.
3926    pub s: String,
3927    /// Optional keyword arguments.
3928    pub opts: Vec<Opt>,
3929}
3930
3931impl Label {
3932    /// Create a new `Label` with no options.
3933    pub fn new(axis: Axis, s: &str) -> Self {
3934        Self { axis, s: s.into(), opts: Vec::new() }
3935    }
3936}
3937
3938/// Create a new [`Label`] with no options.
3939pub fn label(axis: Axis, s: &str) -> Label { Label::new(axis, s) }
3940
3941/// Create a new [`Label`] for the X-axis with no options.
3942pub fn xlabel(s: &str) -> Label { Label::new(Axis::X, s) }
3943
3944/// Create a new [`Label`] for the Y-axis with no options.
3945pub fn ylabel(s: &str) -> Label { Label::new(Axis::Y, s) }
3946
3947/// Create a new [`Label`] for the Z-axis with no options.
3948pub fn zlabel(s: &str) -> Label { Label::new(Axis::Z, s) }
3949
3950impl Matplotlib for Label {
3951    fn is_prelude(&self) -> bool { false }
3952
3953    fn data(&self) -> Option<Value> { None }
3954
3955    fn py_cmd(&self) -> String {
3956        let ax = format!("{:?}", self.axis).to_lowercase();
3957        format!("ax.set_{}label({}{}{})",
3958            ax,
3959            self.s.as_py(),
3960            if self.opts.is_empty() { "" } else { ", " },
3961            self.opts.as_py(),
3962        )
3963    }
3964}
3965
3966impl MatplotlibOpts for Label {
3967    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
3968        self.opts.push((key, val).into());
3969        self
3970    }
3971}
3972
3973/// Set a label on a colorbar.
3974///
3975/// This relies on an existing local variable `cbar` produced by e.g.
3976/// [`Colorbar`].
3977///
3978/// ```python
3979/// cbar.set_label("{s}", **{opts})
3980/// ```
3981///
3982/// Prelude: **No**
3983///
3984/// JSON data: **None**
3985#[derive(Clone, Debug, PartialEq)]
3986pub struct CLabel {
3987    /// Colorbar label.
3988    pub s: String,
3989    /// Optional keyword arguments.
3990    pub opts: Vec<Opt>,
3991}
3992
3993impl CLabel {
3994    /// Create a new `CLabel` with no options.
3995    pub fn new(s: &str) -> Self {
3996        Self { s: s.into(), opts: Vec::new() }
3997    }
3998}
3999
4000/// Create a new [`CLabel`] with no options.
4001pub fn clabel(s: &str) -> CLabel { CLabel::new(s) }
4002
4003impl Matplotlib for CLabel {
4004    fn is_prelude(&self) -> bool { false }
4005
4006    fn data(&self) -> Option<Value> { None }
4007
4008    fn py_cmd(&self) -> String {
4009        format!("cbar.set_label({}{}{})",
4010            self.s.as_py(),
4011            if self.opts.is_empty() { "" } else { ", " },
4012            self.opts.as_py(),
4013        )
4014    }
4015}
4016
4017impl MatplotlibOpts for CLabel {
4018    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
4019        self.opts.push((key, val).into());
4020        self
4021    }
4022}
4023
4024/// Set the values for which ticks are placed on an axis.
4025///
4026/// ```python
4027/// ax.set_{axis}ticks({v}, **{opts})
4028/// ```
4029///
4030/// Prelude: **No**
4031///
4032/// JSON data: `list[float]`
4033#[derive(Clone, Debug, PartialEq)]
4034pub struct Ticks {
4035    /// Which axis.
4036    pub axis: Axis,
4037    /// Tick values.
4038    pub v: Vec<f64>,
4039    /// Optional keyword arguments.
4040    pub opts: Vec<Opt>,
4041}
4042
4043impl Ticks {
4044    /// Create a new `Ticks` with no options.
4045    pub fn new<I, VE>(axis: Axis, v: I) -> Self
4046    where
4047        I: IntoIterator<Item = VE>,
4048        VE: Real,
4049    {
4050        Self {
4051            axis,
4052            v: v.into_iter().map(Real::into_f64).collect(),
4053            opts: Vec::new(),
4054        }
4055    }
4056}
4057
4058/// Create a new [`Ticks`] with no options.
4059pub fn ticks<I, VE>(axis: Axis, v: I) -> Ticks
4060where
4061    I: IntoIterator<Item = VE>,
4062    VE: Real,
4063{
4064    Ticks::new(axis, v)
4065}
4066
4067/// Create a new [`Ticks`] for the X-axis with no options.
4068pub fn xticks<I, VE>(v: I) -> Ticks
4069where
4070    I: IntoIterator<Item = VE>,
4071    VE: Real,
4072{
4073    Ticks::new(Axis::X, v)
4074}
4075
4076/// Create a new [`Ticks`] for the Y-axis with no options.
4077pub fn yticks<I, VE>(v: I) -> Ticks
4078where
4079    I: IntoIterator<Item = VE>,
4080    VE: Real,
4081{
4082    Ticks::new(Axis::Y, v)
4083}
4084
4085/// Create a new [`Ticks`] for the Z-axis with no options.
4086pub fn zticks<I, VE>(v: I) -> Ticks
4087where
4088    I: IntoIterator<Item = VE>,
4089    VE: Real,
4090{
4091    Ticks::new(Axis::Z, v)
4092}
4093
4094impl Matplotlib for Ticks {
4095    fn is_prelude(&self) -> bool { false }
4096
4097    fn data(&self) -> Option<Value> {
4098        let v: Vec<Value> = self.v.iter().copied().map(Value::from).collect();
4099        Some(Value::Array(v))
4100    }
4101
4102    fn py_cmd(&self) -> String {
4103        format!("ax.set_{}ticks(data{}{})",
4104            format!("{:?}", self.axis).to_lowercase(),
4105            if self.opts.is_empty() { "" } else { ", " },
4106            self.opts.as_py(),
4107        )
4108    }
4109}
4110
4111impl MatplotlibOpts for Ticks {
4112    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
4113        self.opts.push((key, val).into());
4114        self
4115    }
4116}
4117
4118/// Set the values for which ticks are placed on a colorbar.
4119///
4120/// This relies on an existing local variable `cbar` produced by e.g.
4121/// [`Colorbar`].
4122///
4123/// ```python
4124/// cbar.set_ticks({v}, **{opts})
4125/// ```
4126///
4127/// Prelude: **No**
4128///
4129/// JSON data: `list[float]`
4130#[derive(Clone, Debug, PartialEq)]
4131pub struct CTicks {
4132    /// Tick values.
4133    pub v: Vec<f64>,
4134    /// Optional keyword arguments.
4135    pub opts: Vec<Opt>,
4136}
4137
4138impl CTicks {
4139    /// Create a new `CTicks` with no options.
4140    pub fn new<I, VE>(v: I) -> Self
4141    where
4142        I: IntoIterator<Item = VE>,
4143        VE: Real,
4144    {
4145        Self {
4146            v: v.into_iter().map(Real::into_f64).collect(),
4147            opts: Vec::new(),
4148        }
4149    }
4150}
4151
4152/// Create a new [`CTicks`] with no options.
4153pub fn cticks<I, VE>(v: I) -> CTicks
4154where
4155    I: IntoIterator<Item = VE>,
4156    VE: Real,
4157{
4158    CTicks::new(v)
4159}
4160
4161impl Matplotlib for CTicks {
4162    fn is_prelude(&self) -> bool { false }
4163
4164    fn data(&self) -> Option<Value> {
4165        let v: Vec<Value> = self.v.iter().copied().map(Value::from).collect();
4166        Some(Value::Array(v))
4167    }
4168
4169    fn py_cmd(&self) -> String {
4170        format!("cbar.set_ticks(data{}{})",
4171            if self.opts.is_empty() { "" } else { ", " },
4172            self.opts.as_py(),
4173        )
4174    }
4175}
4176
4177impl MatplotlibOpts for CTicks {
4178    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
4179        self.opts.push((key, val).into());
4180        self
4181    }
4182}
4183
4184/// Set the values and labels for which ticks are placed on an axis.
4185///
4186/// ```python
4187/// ax.set_{axis}ticks({v}, labels={s}, **{opts})
4188/// ```
4189///
4190/// Prelude: **No**
4191///
4192/// JSON data: `[list[float], list[str]]`.
4193#[derive(Clone, Debug, PartialEq)]
4194pub struct TickLabels {
4195    /// Which axis.
4196    pub axis: Axis,
4197    /// Tick values.
4198    pub v: Vec<f64>,
4199    /// Tick labels.
4200    pub s: Vec<String>,
4201    /// Optional keyword arguments.
4202    pub opts: Vec<Opt>,
4203}
4204
4205impl TickLabels {
4206    /// Create a new `TickLabels` with no options.
4207    pub fn new<I, VE, J, S>(axis: Axis, v: I, s: J) -> Self
4208    where
4209        I: IntoIterator<Item = VE>,
4210        VE: Real,
4211        J: IntoIterator<Item = S>,
4212        S: Into<String>,
4213    {
4214        Self {
4215            axis,
4216            v: v.into_iter().map(Real::into_f64).collect(),
4217            s: s.into_iter().map(|sk| sk.into()).collect(),
4218            opts: Vec::new(),
4219        }
4220    }
4221
4222    /// Create a new `TickLabels` with no options from a single iterator.
4223    pub fn new_data<I, VE, S>(axis: Axis, ticklabels: I) -> Self
4224    where
4225        I: IntoIterator<Item = (VE, S)>,
4226        VE: Real,
4227        S: Into<String>,
4228    {
4229        let (v, s): (Vec<f64>, Vec<String>) =
4230            ticklabels.into_iter()
4231            .map(|(vk, sk)| (vk.into_f64(), sk.into()))
4232            .unzip();
4233        Self { axis, v, s, opts: Vec::new() }
4234    }
4235}
4236
4237/// Create a new [`TickLabels`] with no options.
4238pub fn ticklabels<I, VE, J, S>(axis: Axis, v: I, s: J) -> TickLabels
4239where
4240    I: IntoIterator<Item = VE>,
4241    VE: Real,
4242    J: IntoIterator<Item = S>,
4243    S: Into<String>,
4244{
4245    TickLabels::new(axis, v, s)
4246}
4247
4248/// Create a new [`TickLabels`] with no options from a single iterator.
4249pub fn ticklabels_data<I, VE, S>(axis: Axis, ticklabels: I) -> TickLabels
4250where
4251    I: IntoIterator<Item = (VE, S)>,
4252    VE: Real,
4253    S: Into<String>,
4254{
4255    TickLabels::new_data(axis, ticklabels)
4256}
4257
4258/// Create a new [`TickLabels`] for the X-axis with no options.
4259pub fn xticklabels<I, VE, J, S>(v: I, s: J) -> TickLabels
4260where
4261    I: IntoIterator<Item = VE>,
4262    VE: Real,
4263    J: IntoIterator<Item = S>,
4264    S: Into<String>,
4265{
4266    TickLabels::new(Axis::X, v, s)
4267}
4268
4269/// Create a new [`TickLabels`] for the X-axis with no options from a single
4270/// iterator.
4271pub fn xticklabels_data<I, VE, S>(ticklabels: I) -> TickLabels
4272where
4273    I: IntoIterator<Item = (VE, S)>,
4274    VE: Real,
4275    S: Into<String>,
4276{
4277    TickLabels::new_data(Axis::X, ticklabels)
4278}
4279
4280/// Create a new [`TickLabels`] for the Y-axis with no options.
4281pub fn yticklabels<I, VE, J, S>(v: I, s: J) -> TickLabels
4282where
4283    I: IntoIterator<Item = VE>,
4284    VE: Real,
4285    J: IntoIterator<Item = S>,
4286    S: Into<String>,
4287{
4288    TickLabels::new(Axis::Y, v, s)
4289}
4290
4291/// Create a new [`TickLabels`] for the Y-axis with no options from a single
4292/// iterator.
4293pub fn yticklabels_data<I, VE, S>(ticklabels: I) -> TickLabels
4294where
4295    I: IntoIterator<Item = (VE, S)>,
4296    VE: Real,
4297    S: Into<String>,
4298{
4299    TickLabels::new_data(Axis::Y, ticklabels)
4300}
4301
4302/// Create a new [`TickLabels`] for the Z-axis with no options.
4303pub fn zticklabels<I, VE, J, S>(v: I, s: J) -> TickLabels
4304where
4305    I: IntoIterator<Item = VE>,
4306    VE: Real,
4307    J: IntoIterator<Item = S>,
4308    S: Into<String>,
4309{
4310    TickLabels::new(Axis::Z, v, s)
4311}
4312
4313/// Create a new [`TickLabels`] for the Z-axis with no options from a single
4314/// iterator.
4315pub fn zticklabels_data<I, VE, S>(ticklabels: I) -> TickLabels
4316where
4317    I: IntoIterator<Item = (VE, S)>,
4318    VE: Real,
4319    S: Into<String>,
4320{
4321    TickLabels::new_data(Axis::Z, ticklabels)
4322}
4323
4324impl Matplotlib for TickLabels {
4325    fn is_prelude(&self) -> bool { false }
4326
4327    fn data(&self) -> Option<Value> {
4328        let v: Vec<Value> = self.v.iter().copied().map(Value::from).collect();
4329        let s: Vec<Value> = self.s.iter().cloned().map(Value::from).collect();
4330        Some(Value::Array(vec![v.into(), s.into()]))
4331    }
4332
4333    fn py_cmd(&self) -> String {
4334        format!("ax.set_{}ticks(data[0], labels=data[1]{}{})",
4335            format!("{:?}", self.axis).to_lowercase(),
4336            if self.opts.is_empty() { "" } else { ", " },
4337            self.opts.as_py(),
4338        )
4339    }
4340}
4341
4342impl MatplotlibOpts for TickLabels {
4343    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
4344        self.opts.push((key, val).into());
4345        self
4346    }
4347}
4348
4349/// Set the values and labels for which ticks are placed on a colorbar.
4350///
4351/// This relies on an existing local variable `cbar` produced by e.g.
4352/// [`Colorbar`].
4353///
4354/// ```python
4355/// cbar.set_ticks({v}, labels={s}, **{opts})
4356/// ```
4357///
4358/// Prelude: **No**
4359///
4360/// JSON data: `[list[float], list[str]]`
4361#[derive(Clone, Debug, PartialEq)]
4362pub struct CTickLabels {
4363    /// Tick values.
4364    pub v: Vec<f64>,
4365    /// Tick labels.
4366    pub s: Vec<String>,
4367    /// Optional keyword arguments.
4368    pub opts: Vec<Opt>,
4369}
4370
4371impl CTickLabels {
4372    /// Create a new `CTickLabels` with no options.
4373    pub fn new<I, VE, J, S>(v: I, s: J) -> Self
4374    where
4375        I: IntoIterator<Item = VE>,
4376        VE: Real,
4377        J: IntoIterator<Item = S>,
4378        S: Into<String>,
4379    {
4380        Self {
4381            v: v.into_iter().map(Real::into_f64).collect(),
4382            s: s.into_iter().map(|sk| sk.into()).collect(),
4383            opts: Vec::new(),
4384        }
4385    }
4386
4387    /// Create a new `CTickLabels` with no options from a single iterator.
4388    pub fn new_data<I, VE, S>(ticklabels: I) -> Self
4389    where
4390        I: IntoIterator<Item = (VE, S)>,
4391        VE: Real,
4392        S: Into<String>,
4393    {
4394        let (v, s): (Vec<f64>, Vec<String>) =
4395            ticklabels.into_iter()
4396            .map(|(vk, sk)| (vk.into_f64(), sk.into()))
4397            .unzip();
4398        Self { v, s, opts: Vec::new() }
4399    }
4400}
4401
4402/// Create a new [`CTickLabels`] with no options.
4403pub fn cticklabels<I, VE, J, S>(v: I, s: J) -> CTickLabels
4404where
4405    I: IntoIterator<Item = VE>,
4406    VE: Real,
4407    J: IntoIterator<Item = S>,
4408    S: Into<String>,
4409{
4410    CTickLabels::new(v, s)
4411}
4412
4413/// Create a new [`CTickLabels`] with no options from a single iterator.
4414pub fn cticklabels_data<I, VE, S>(ticklabels: I) -> CTickLabels
4415where
4416    I: IntoIterator<Item = (VE, S)>,
4417    VE: Real,
4418    S: Into<String>,
4419{
4420    CTickLabels::new_data(ticklabels)
4421}
4422
4423impl Matplotlib for CTickLabels {
4424    fn is_prelude(&self) -> bool { false }
4425
4426    fn data(&self) -> Option<Value> {
4427        let v: Vec<Value> = self.v.iter().copied().map(Value::from).collect();
4428        let s: Vec<Value> = self.s.iter().cloned().map(Value::from).collect();
4429        Some(Value::Array(vec![v.into(), s.into()]))
4430    }
4431
4432    fn py_cmd(&self) -> String {
4433        format!("cbar.set_ticks(data[0], labels=data[1]{}{})",
4434            if self.opts.is_empty() { "" } else { ", " },
4435            self.opts.as_py(),
4436        )
4437    }
4438}
4439
4440impl MatplotlibOpts for CTickLabels {
4441    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
4442        self.opts.push((key, val).into());
4443        self
4444    }
4445}
4446
4447/// Set the appearance of ticks, tick labels, and gridlines.
4448///
4449/// ```python
4450/// ax.tick_params({axis}, **{opts})
4451/// ```
4452///
4453/// Prelude: **No**
4454///
4455/// JSON data: **None**
4456#[derive(Clone, Debug, PartialEq)]
4457pub struct TickParams {
4458    /// Which axis.
4459    pub axis: Axis2,
4460    /// Optional keyword arguments.
4461    pub opts: Vec<Opt>,
4462}
4463
4464impl TickParams {
4465    /// Create a new `TickParams` with no options.
4466    pub fn new(axis: Axis2) -> Self {
4467        Self { axis, opts: Vec::new() }
4468    }
4469}
4470
4471/// Create a new [`TickParams`] with no options.
4472pub fn tick_params(axis: Axis2) -> TickParams { TickParams::new(axis) }
4473
4474/// Create a new [`TickParams`] for the X-axis with no options.
4475pub fn xtick_params() -> TickParams { TickParams::new(Axis2::X) }
4476
4477/// Create a new [`TickParams`] for the Y-axis with no options.
4478pub fn ytick_params() -> TickParams { TickParams::new(Axis2::Y) }
4479
4480impl Matplotlib for TickParams {
4481    fn is_prelude(&self) -> bool { false }
4482
4483    fn data(&self) -> Option<Value> { None }
4484
4485    fn py_cmd(&self) -> String {
4486        format!("ax.tick_params(\"{}\"{}{})",
4487            format!("{:?}", self.axis).to_lowercase(),
4488            if self.opts.is_empty() { "" } else { ", " },
4489            self.opts.as_py(),
4490        )
4491    }
4492}
4493
4494impl MatplotlibOpts for TickParams {
4495    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
4496        self.opts.push((key, val).into());
4497        self
4498    }
4499}
4500
4501/// Like [`Axis`], but limited to X or Y and with the option of both.
4502#[derive(Copy, Clone, Debug, PartialEq, Eq)]
4503pub enum Axis2 {
4504    /// The X-axis.
4505    X,
4506    /// The Y-axis.
4507    Y,
4508    /// Both the X- and Y-axes.
4509    Both,
4510}
4511
4512/// Invert an axis.
4513///
4514/// ```python
4515/// ax.invert_{axis}axis()
4516/// ```
4517///
4518/// Prelude: **No**
4519///
4520/// JSON data: **None**
4521#[derive(Clone, Debug, PartialEq)]
4522pub struct InvertAx {
4523    /// The axis to invert.
4524    pub axis: Axis,
4525}
4526
4527impl InvertAx {
4528    /// Create a new `InvertAx`.
4529    pub fn new(axis: Axis) -> Self {
4530        Self { axis }
4531    }
4532}
4533
4534/// Create a new [`InvertAx`].
4535pub fn invert_ax(axis: Axis) -> InvertAx { InvertAx::new(axis) }
4536
4537/// Create a new [`InvertAx`] for the X-axis.
4538pub fn invert_x() -> InvertAx { InvertAx::new(Axis::X) }
4539
4540/// Create a new [`InvertAx`] for the Y-axis.
4541pub fn invert_y() -> InvertAx { InvertAx::new(Axis::Y) }
4542
4543/// Create a new [`InvertAx`] for the Z-axis.
4544pub fn invert_z() -> InvertAx { InvertAx::new(Axis::Z) }
4545
4546impl Matplotlib for InvertAx {
4547    fn is_prelude(&self) -> bool { false }
4548
4549    fn data(&self) -> Option<Value> { None }
4550
4551    fn py_cmd(&self) -> String {
4552        let ax = format!("{:?}", self.axis).to_lowercase();
4553        format!("ax.invert_{}axis()", ax)
4554    }
4555}
4556
4557/// Set the axis aspect ratio, i.e. y/x-scale.
4558///
4559/// ```python
4560/// ax.set_aspect({asp}, **{opts})
4561/// ```
4562///
4563/// Prelude: **No**
4564///
4565/// JSON data: **None**
4566#[derive(Clone, Debug, PartialEq)]
4567pub struct Aspect {
4568    /// Aspect ratio, y/x.
4569    pub asp: f64,
4570    /// Optional keyword arguments.
4571    pub opts: Vec<Opt>,
4572}
4573
4574impl Aspect {
4575    /// Create a new `Aspect` with no options.
4576    pub fn new(asp: f64) -> Self {
4577        Self { asp, opts: Vec::new() }
4578    }
4579}
4580
4581/// Create a new [`Aspect`] with no options.
4582pub fn aspect(asp: f64) -> Aspect { Aspect::new(asp) }
4583
4584impl Matplotlib for Aspect {
4585    fn is_prelude(&self) -> bool { false }
4586
4587    fn data(&self) -> Option<Value> { None }
4588
4589    fn py_cmd(&self) -> String {
4590        format!("ax.set_aspect({}{}{})",
4591            self.asp.as_py(),
4592            if self.opts.is_empty() { "" } else { ", " },
4593            self.opts.as_py(),
4594        )
4595    }
4596}
4597
4598impl MatplotlibOpts for Aspect {
4599    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
4600        self.opts.push((key, val).into());
4601        self
4602    }
4603}
4604
4605/// Set the title of the figure.
4606///
4607/// ```python
4608/// fig.suptitle({s}, **{opts})
4609/// ```
4610///
4611/// Prelude: **No**
4612///
4613/// JSON data: **None**
4614#[derive(Clone, Debug, PartialEq)]
4615pub struct SupTitle {
4616    /// Figure title.
4617    pub s: String,
4618    /// Optional keyword arguments.
4619    pub opts: Vec<Opt>,
4620}
4621
4622impl SupTitle {
4623    /// Create a new `SupTitle` with no options.
4624    pub fn new(s: &str) -> Self {
4625        Self { s: s.into(), opts: Vec::new() }
4626    }
4627}
4628
4629/// Create a new [`SupTitle`] with no options.
4630pub fn suptitle(s: &str) -> SupTitle { SupTitle::new(s) }
4631
4632impl Matplotlib for SupTitle {
4633    fn is_prelude(&self) -> bool { false }
4634
4635    fn data(&self) -> Option<Value> { None }
4636
4637    fn py_cmd(&self) -> String {
4638        format!("fig.suptitle({}{}{})",
4639            self.s.as_py(),
4640            if self.opts.is_empty() { "" } else { ", " },
4641            self.opts.as_py(),
4642        )
4643    }
4644}
4645
4646impl MatplotlibOpts for SupTitle {
4647    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
4648        self.opts.push((key, val).into());
4649        self
4650    }
4651}
4652
4653/// Set the X label of the figure.
4654///
4655/// ```python
4656/// fig.supxlabel({s}, **{opts})
4657/// ```
4658///
4659/// Prelude: **No**
4660///
4661/// JSON data: **None**
4662#[derive(Clone, Debug, PartialEq)]
4663pub struct SupXLabel {
4664    /// Figure X label.
4665    pub s: String,
4666    /// Optional keyword arguments.
4667    pub opts: Vec<Opt>,
4668}
4669
4670impl SupXLabel {
4671    /// Create a new `SupXLabel` with no options.
4672    pub fn new(s: &str) -> Self {
4673        Self { s: s.into(), opts: Vec::new() }
4674    }
4675}
4676
4677/// Create a new [`SupXLabel`] with no options.
4678pub fn supxlabel(s: &str) -> SupXLabel { SupXLabel::new(s) }
4679
4680impl Matplotlib for SupXLabel {
4681    fn is_prelude(&self) -> bool { false }
4682
4683    fn data(&self) -> Option<Value> { None }
4684
4685    fn py_cmd(&self) -> String {
4686        format!("fig.supxlabel({}{}{})",
4687            self.s.as_py(),
4688            if self.opts.is_empty() { "" } else { ", " },
4689            self.opts.as_py(),
4690        )
4691    }
4692}
4693
4694impl MatplotlibOpts for SupXLabel {
4695    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
4696        self.opts.push((key, val).into());
4697        self
4698    }
4699}
4700
4701/// Set the Y label of the figure.
4702///
4703/// ```python
4704/// fig.supylabel({s}, **{opts})
4705/// ```
4706///
4707/// Prelude: **No**
4708///
4709/// JSON data: **None**
4710#[derive(Clone, Debug, PartialEq)]
4711pub struct SupYLabel {
4712    /// Figure title.
4713    pub s: String,
4714    /// Optional keyword arguments.
4715    pub opts: Vec<Opt>,
4716}
4717
4718impl SupYLabel {
4719    /// Create a new `SupYLabel` with no options.
4720    pub fn new(s: &str) -> Self {
4721        Self { s: s.into(), opts: Vec::new() }
4722    }
4723}
4724
4725/// Create a new [`SupYLabel`] with no options.
4726pub fn supylabel(s: &str) -> SupYLabel { SupYLabel::new(s) }
4727
4728impl Matplotlib for SupYLabel {
4729    fn is_prelude(&self) -> bool { false }
4730
4731    fn data(&self) -> Option<Value> { None }
4732
4733    fn py_cmd(&self) -> String {
4734        format!("fig.supylabel({}{}{})",
4735            self.s.as_py(),
4736            if self.opts.is_empty() { "" } else { ", " },
4737            self.opts.as_py(),
4738        )
4739    }
4740}
4741
4742impl MatplotlibOpts for SupYLabel {
4743    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
4744        self.opts.push((key, val).into());
4745        self
4746    }
4747}
4748
4749/// Place a legend on a set of axes.
4750///
4751/// ```python
4752/// ax.legend(**{opts})
4753/// ```
4754///
4755/// Prelude: **No**
4756///
4757/// JSON data: **None**
4758#[derive(Clone, Debug, PartialEq)]
4759pub struct Legend {
4760    /// Optional keyword arguments.
4761    pub opts: Vec<Opt>,
4762}
4763
4764impl Default for Legend {
4765    fn default() -> Self { Self::new() }
4766}
4767
4768impl Legend {
4769    /// Create a new `Legend` with no options.
4770    pub fn new() -> Self {
4771        Self { opts: Vec::new() }
4772    }
4773}
4774
4775/// Create a new [`Legend`] with no options.
4776pub fn legend() -> Legend { Legend::new() }
4777
4778impl Matplotlib for Legend {
4779    fn is_prelude(&self) -> bool { false }
4780
4781    fn data(&self) -> Option<Value> { None }
4782
4783    fn py_cmd(&self) -> String {
4784        format!("ax.legend({})", self.opts.as_py())
4785    }
4786}
4787
4788impl MatplotlibOpts for Legend {
4789    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
4790        self.opts.push((key, val).into());
4791        self
4792    }
4793}
4794
4795/// Activate or modify the coordinate grid.
4796///
4797/// ```python
4798/// ax.grid({onoff}, **{opts})
4799/// ```
4800///
4801/// Prelude: **No**
4802///
4803/// JSON data: **None**
4804#[derive(Clone, Debug, PartialEq)]
4805pub struct Grid {
4806    /// On/off setting.
4807    pub onoff: bool,
4808    /// Optional keyword arguments.
4809    pub opts: Vec<Opt>,
4810}
4811
4812impl Grid {
4813    /// Create a new `Grid` with no options.
4814    pub fn new(onoff: bool) -> Self { Self { onoff, opts: Vec::new() } }
4815}
4816
4817/// Create a new [`Grid`] with no options.
4818pub fn grid(onoff: bool) -> Grid { Grid::new(onoff) }
4819
4820impl Matplotlib for Grid {
4821    fn is_prelude(&self) -> bool { false }
4822
4823    fn data(&self) -> Option<Value> { None }
4824
4825    fn py_cmd(&self) -> String {
4826        format!("ax.grid({}{}{})",
4827            self.onoff.as_py(),
4828            if self.opts.is_empty() { "" } else { ", " },
4829            self.opts.as_py(),
4830        )
4831    }
4832}
4833
4834impl MatplotlibOpts for Grid {
4835    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
4836        self.opts.push((key, val).into());
4837        self
4838    }
4839}
4840
4841/// Adjust the padding between and around subplots.
4842///
4843/// ```python
4844/// fig.tight_layout(**{opts})
4845/// ```
4846///
4847/// Prelude: **No**
4848///
4849/// JSON data: **None**
4850#[derive(Clone, Debug, PartialEq)]
4851pub struct TightLayout {
4852    /// Optional keyword arguments.
4853    pub opts: Vec<Opt>,
4854}
4855
4856impl Default for TightLayout {
4857    fn default() -> Self { Self::new() }
4858}
4859
4860impl TightLayout {
4861    /// Create a new `TightLayout` with no options.
4862    pub fn new() -> Self { Self { opts: Vec::new() } }
4863}
4864
4865/// Create a new [`TightLayout`] with no options.
4866pub fn tight_layout() -> TightLayout { TightLayout::new() }
4867
4868impl Matplotlib for TightLayout {
4869    fn is_prelude(&self) -> bool { false }
4870
4871    fn data(&self) -> Option<Value> { None }
4872
4873    fn py_cmd(&self) -> String {
4874        format!("fig.tight_layout({})", self.opts.as_py())
4875    }
4876}
4877
4878impl MatplotlibOpts for TightLayout {
4879    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
4880        self.opts.push((key, val).into());
4881        self
4882    }
4883}
4884
4885/// Create and refocus to a set of axes inset to `ax`.
4886///
4887/// Coordinates and sizes are in axis [0, 1] units.
4888///
4889/// ```python
4890/// ax = ax.inset_axes([{x}, {y}, {w}, {h}], **{opts})
4891/// ```
4892///
4893/// Prelude: **No**
4894///
4895/// JSON data: **None**
4896#[derive(Clone, Debug, PartialEq)]
4897pub struct InsetAxes {
4898    /// X-coordinate of the lower-left corner of the inset.
4899    pub x: f64,
4900    /// Y-coordinate of the lower-left corner of the inset.
4901    pub y: f64,
4902    /// Width of the inset.
4903    pub w: f64,
4904    /// Height of the inset.
4905    pub h: f64,
4906    /// Optional keyword arguments.
4907    pub opts: Vec<Opt>,
4908}
4909
4910impl InsetAxes {
4911    /// Create a new `InsetAxes` with no options.
4912    pub fn new(x: f64, y: f64, w: f64, h: f64) -> Self {
4913        Self { x, y, w, h, opts: Vec::new() }
4914    }
4915
4916    /// Create a new `InsetAxes` with no options from (*x*, *y*) and (*width*,
4917    /// *height*) pairs.
4918    pub fn new_pairs(xy: (f64, f64), wh: (f64, f64)) -> Self {
4919        Self { x: xy.0, y: xy.1, w: wh.0, h: wh.1, opts: Vec::new() }
4920    }
4921}
4922
4923/// Create a new [`InsetAxes`] with no options.
4924pub fn inset_axes(x: f64, y: f64, w: f64, h: f64) -> InsetAxes {
4925    InsetAxes::new(x, y, w, h)
4926}
4927
4928/// Create a new [`InsetAxes`] with no options from (*x*, *y*) and (*width*,
4929/// *height*) pairs.
4930pub fn inset_axes_pairs(xy: (f64, f64), wh: (f64, f64)) -> InsetAxes {
4931    InsetAxes::new_pairs(xy, wh)
4932}
4933
4934impl Matplotlib for InsetAxes {
4935    fn is_prelude(&self) -> bool { false }
4936
4937    fn data(&self) -> Option<Value> { None }
4938
4939    fn py_cmd(&self) -> String {
4940        format!("ax = ax.inset_axes([{}, {}, {}, {}]{}{})",
4941            self.x.as_py(),
4942            self.y.as_py(),
4943            self.w.as_py(),
4944            self.h.as_py(),
4945            if self.opts.is_empty() { "" } else { ", " },
4946            self.opts.as_py(),
4947        )
4948    }
4949}
4950
4951impl MatplotlibOpts for InsetAxes {
4952    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
4953        self.opts.push((key, val).into());
4954        self
4955    }
4956}
4957
4958/// A (*x*, *y*, *z*) plot.
4959///
4960/// ```python
4961/// ax.plot({x}, {y}, {z}, **{opts})
4962/// ```
4963///
4964/// Prelude: **No**
4965///
4966/// JSON data: `[list[float], list[float], list[float]]`
4967#[derive(Clone, Debug, PartialEq)]
4968pub struct Plot3 {
4969    /// X-coordinates.
4970    pub x: Vec<f64>,
4971    /// Y-coordinates.
4972    pub y: Vec<f64>,
4973    /// Z-coordinates.
4974    pub z: Vec<f64>,
4975    /// Optional keyword arguments.
4976    pub opts: Vec<Opt>,
4977}
4978
4979impl Plot3 {
4980    /// Create a new `Plot3` with no options.
4981    pub fn new<X, XE, Y, YE, Z, ZE>(x: X, y: Y, z: Z) -> Self
4982    where
4983        X: IntoIterator<Item = XE>,
4984        XE: Real,
4985        Y: IntoIterator<Item = YE>,
4986        YE: Real,
4987        Z: IntoIterator<Item = ZE>,
4988        ZE: Real,
4989    {
4990        Self {
4991            x: x.into_iter().map(Real::into_f64).collect(),
4992            y: y.into_iter().map(Real::into_f64).collect(),
4993            z: z.into_iter().map(Real::into_f64).collect(),
4994            opts: Vec::new(),
4995        }
4996    }
4997
4998    /// Create a new `Plot3` with no options from a single iterator.
4999    pub fn new_data<I, XE, YE, ZE>(data: I) -> Self
5000    where
5001        I: IntoIterator<Item = (XE, YE, ZE)>,
5002        XE: Real,
5003        YE: Real,
5004        ZE: Real,
5005    {
5006        let ((x, y), z) =
5007            data.into_iter()
5008            .map(|(a, b, c)| (a.into_f64(), b.into_f64(), c.into_f64()))
5009            .map(assoc)
5010            .unzip();
5011        Self { x, y, z, opts: Vec::new() }
5012    }
5013}
5014
5015/// Create a new [`Plot3`] with no options.
5016pub fn plot3<X, XE, Y, YE, Z, ZE>(x: X, y: Y, z: Z) -> Plot3
5017where
5018    X: IntoIterator<Item = XE>,
5019    XE: Real,
5020    Y: IntoIterator<Item = YE>,
5021    YE: Real,
5022    Z: IntoIterator<Item = ZE>,
5023    ZE: Real,
5024{
5025    Plot3::new(x, y, z)
5026}
5027
5028/// Create a new [`Plot3`] with no options from a single iterator.
5029pub fn plot3_data<I, XE, YE, ZE>(data: I) -> Plot3
5030where
5031    I: IntoIterator<Item = (XE, YE, ZE)>,
5032    XE: Real,
5033    YE: Real,
5034    ZE: Real,
5035{
5036    Plot3::new_data(data)
5037}
5038
5039impl Matplotlib for Plot3 {
5040    fn is_prelude(&self) -> bool { false }
5041
5042    fn data(&self) -> Option<Value> {
5043        let x: Vec<Value> = self.x.iter().copied().map(Value::from).collect();
5044        let y: Vec<Value> = self.y.iter().copied().map(Value::from).collect();
5045        let z: Vec<Value> = self.z.iter().copied().map(Value::from).collect();
5046        Some(Value::Array(vec![x.into(), y.into(), z.into()]))
5047    }
5048
5049    fn py_cmd(&self) -> String {
5050        format!("ax.plot(data[0], data[1], data[2]{}{})",
5051            if self.opts.is_empty() { "" } else { ", " },
5052            self.opts.as_py(),
5053        )
5054    }
5055}
5056
5057impl MatplotlibOpts for Plot3 {
5058    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
5059        self.opts.push((key, val).into());
5060        self
5061    }
5062}
5063
5064/// A (*x*, *y*, *z*) scatter plot.
5065///
5066/// ```python
5067/// ax.scatter({x}, {y}, {z}, **{opts})
5068/// ```
5069///
5070/// Prelude: **No**
5071///
5072/// JSON data: `[list[float], list[float], list[float]]`
5073#[derive(Clone, Debug, PartialEq)]
5074pub struct Scatter3 {
5075    /// X-coordinates.
5076    pub x: Vec<f64>,
5077    /// Y-coordinates.
5078    pub y: Vec<f64>,
5079    /// Z-coordinates.
5080    pub z: Vec<f64>,
5081    /// Optional keyword arguments.
5082    pub opts: Vec<Opt>,
5083}
5084
5085impl Scatter3 {
5086    /// Create a new `Scatter3` with no options.
5087    pub fn new<X, XE, Y, YE, Z, ZE>(x: X, y: Y, z: Z) -> Self
5088    where
5089        X: IntoIterator<Item = XE>,
5090        XE: Real,
5091        Y: IntoIterator<Item = YE>,
5092        YE: Real,
5093        Z: IntoIterator<Item = ZE>,
5094        ZE: Real,
5095    {
5096        Self {
5097            x: x.into_iter().map(Real::into_f64).collect(),
5098            y: y.into_iter().map(Real::into_f64).collect(),
5099            z: z.into_iter().map(Real::into_f64).collect(),
5100            opts: Vec::new(),
5101        }
5102    }
5103
5104    /// Create a new `Scatter3` with no options from a single iterator.
5105    pub fn new_data<I, XE, YE, ZE>(data: I) -> Self
5106    where
5107        I: IntoIterator<Item = (XE, YE, ZE)>,
5108        XE: Real,
5109        YE: Real,
5110        ZE: Real,
5111    {
5112        let ((x, y), z) =
5113            data.into_iter()
5114            .map(|(a, b, c)| (a.into_f64(), b.into_f64(), c.into_f64()))
5115            .map(assoc)
5116            .unzip();
5117        Self { x, y, z, opts: Vec::new() }
5118    }
5119}
5120
5121/// Create a new [`Scatter3`] with no options.
5122pub fn scatter3<X, XE, Y, YE, Z, ZE>(x: X, y: Y, z: Z) -> Scatter3
5123where
5124    X: IntoIterator<Item = XE>,
5125    XE: Real,
5126    Y: IntoIterator<Item = YE>,
5127    YE: Real,
5128    Z: IntoIterator<Item = ZE>,
5129    ZE: Real,
5130{
5131    Scatter3::new(x, y, z)
5132}
5133
5134/// Create a new [`Scatter3`] with no options from a single iterator.
5135pub fn scatter3_data<I, XE, YE, ZE>(data: I) -> Scatter3
5136where
5137    I: IntoIterator<Item = (XE, YE, ZE)>,
5138    XE: Real,
5139    YE: Real,
5140    ZE: Real,
5141{
5142    Scatter3::new_data(data)
5143}
5144
5145impl Matplotlib for Scatter3 {
5146    fn is_prelude(&self) -> bool { false }
5147
5148    fn data(&self) -> Option<Value> {
5149        let x: Vec<Value> = self.x.iter().copied().map(Value::from).collect();
5150        let y: Vec<Value> = self.y.iter().copied().map(Value::from).collect();
5151        let z: Vec<Value> = self.z.iter().copied().map(Value::from).collect();
5152        Some(Value::Array(vec![x.into(), y.into(), z.into()]))
5153    }
5154
5155    fn py_cmd(&self) -> String {
5156        format!("ax.scatter(data[0], data[1], data[2]{}{})",
5157            if self.opts.is_empty() { "" } else { ", " },
5158            self.opts.as_py(),
5159        )
5160    }
5161}
5162
5163impl MatplotlibOpts for Scatter3 {
5164    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
5165        self.opts.push((key, val).into());
5166        self
5167    }
5168}
5169
5170/// A 3D vector field plot.
5171///
5172/// ```python
5173/// ax.quiver({x}, {y}, {z}, {vx}, {vy}, {vz}, **{ops})
5174/// ```
5175///
5176/// Prelude: **No**
5177///
5178/// JSON data: `[list[float], list[float], list[float], list[float], list[float], list[float]]`
5179#[derive(Clone, Debug, PartialEq)]
5180pub struct Quiver3 {
5181    /// X-coordinates.
5182    pub x: Vec<f64>,
5183    /// Y-coordinates.
5184    pub y: Vec<f64>,
5185    /// Z-coordinates.
5186    pub z: Vec<f64>,
5187    /// Vector X-components.
5188    pub vx: Vec<f64>,
5189    /// Vector Y-components.
5190    pub vy: Vec<f64>,
5191    /// Vector Z-components.
5192    pub vz: Vec<f64>,
5193    /// Optional keyword arguments.
5194    pub opts: Vec<Opt>,
5195}
5196
5197impl Quiver3 {
5198    /// Create a new `Quiver3` with no options.
5199    pub fn new<X, XE, Y, YE, Z, ZE, VX, VXE, VY, VYE, VZ, VZE>(
5200        x: X,
5201        y: Y,
5202        z: Z,
5203        vx: VX,
5204        vy: VY,
5205        vz: VZ,
5206    ) -> Self
5207    where
5208        X: IntoIterator<Item = XE>,
5209        XE: Real,
5210        Y: IntoIterator<Item = YE>,
5211        YE: Real,
5212        Z: IntoIterator<Item = ZE>,
5213        ZE: Real,
5214        VX: IntoIterator<Item = VXE>,
5215        VXE: Real,
5216        VY: IntoIterator<Item = VYE>,
5217        VYE: Real,
5218        VZ: IntoIterator<Item = VZE>,
5219        VZE: Real,
5220    {
5221        Self {
5222            x: x.into_iter().map(Real::into_f64).collect(),
5223            y: y.into_iter().map(Real::into_f64).collect(),
5224            z: z.into_iter().map(Real::into_f64).collect(),
5225            vx: vx.into_iter().map(Real::into_f64).collect(),
5226            vy: vy.into_iter().map(Real::into_f64).collect(),
5227            vz: vz.into_iter().map(Real::into_f64).collect(),
5228            opts: Vec::new(),
5229        }
5230    }
5231
5232    /// Create a new `Quiver3` with no options from iterators over coordinate
5233    /// triples.
5234    pub fn new_triples<I, XE, YE, ZE, VI, VXE, VYE, VZE>(xyz: I, vxyz: VI) -> Self
5235    where
5236        I: IntoIterator<Item = (XE, YE, ZE)>,
5237        XE: Real,
5238        YE: Real,
5239        ZE: Real,
5240        VI: IntoIterator<Item = (VXE, VYE, VZE)>,
5241        VXE: Real,
5242        VYE: Real,
5243        VZE: Real,
5244    {
5245        let ((x, y), z): ((Vec<f64>, Vec<f64>), Vec<f64>) =
5246            xyz.into_iter()
5247            .map(|(x, y, z)| (x.into_f64(), y.into_f64(), z.into_f64()))
5248            .map(assoc)
5249            .unzip();
5250        let ((vx, vy), vz): ((Vec<f64>, Vec<f64>), Vec<f64>) =
5251            vxyz.into_iter()
5252            .map(|(x, y, z)| (x.into_f64(), y.into_f64(), z.into_f64()))
5253            .map(assoc)
5254            .unzip();
5255        Self { x, y, z, vx, vy, vz, opts: Vec::new() }
5256    }
5257
5258    /// Create a new `Quiver3` with no options from a single iterator. The first
5259    /// three elements of each iterator item should be spatial coordinates and
5260    /// the last three should be vector components.
5261    pub fn new_data<I, XE, YE, ZE, VXE, VYE, VZE>(data: I) -> Self
5262    where
5263        I: IntoIterator<Item = (XE, YE, ZE, VXE, VYE, VZE)>,
5264        XE: Real,
5265        YE: Real,
5266        ZE: Real,
5267        VXE: Real,
5268        VYE: Real,
5269        VZE: Real,
5270    {
5271        let (((((x, y), z), vx), vy), vz) =
5272            data.into_iter()
5273            .map(|(a, b, c, d, e, f)| {
5274                let a = a.into_f64();
5275                let b = b.into_f64();
5276                let c = c.into_f64();
5277                let d = d.into_f64();
5278                let e = e.into_f64();
5279                let f = f.into_f64();
5280                (a, b, c, d, e, f)
5281            })
5282            .map(assoc)
5283            .unzip();
5284        Self { x, y, z, vx, vy, vz, opts: Vec::new() }
5285    }
5286}
5287
5288/// Create a new [`Quiver3`] with no options.
5289pub fn quiver3<X, XE, Y, YE, Z, ZE, VX, VXE, VY, VYE, VZ, VZE>(
5290    x: X,
5291    y: Y,
5292    z: Z,
5293    vx: VX,
5294    vy: VY,
5295    vz: VZ,
5296) -> Quiver3
5297where
5298    X: IntoIterator<Item = XE>,
5299    XE: Real,
5300    Y: IntoIterator<Item = YE>,
5301    YE: Real,
5302    Z: IntoIterator<Item = ZE>,
5303    ZE: Real,
5304    VX: IntoIterator<Item = VXE>,
5305    VXE: Real,
5306    VY: IntoIterator<Item = VYE>,
5307    VYE: Real,
5308    VZ: IntoIterator<Item = VZE>,
5309    VZE: Real,
5310{
5311    Quiver3::new(x, y, z, vx, vy, vz)
5312}
5313
5314/// Create a new [`Quiver3`] with no options from iterators over coordinate
5315/// triples.
5316pub fn quiver3_triples<I, XE, YE, ZE, VI, VXE, VYE, VZE>(xyz: I, vxyz: VI)
5317    -> Quiver3
5318where
5319    I: IntoIterator<Item = (XE, YE, ZE)>,
5320    XE: Real,
5321    YE: Real,
5322    ZE: Real,
5323    VI: IntoIterator<Item = (VXE, VYE, VZE)>,
5324    VXE: Real,
5325    VYE: Real,
5326    VZE: Real,
5327{
5328    Quiver3::new_triples(xyz, vxyz)
5329}
5330
5331/// Create a new [`Quiver3`] with no options from a single iterator.
5332///
5333/// The first three elements of each iterator item should be spatial coordinates
5334/// and the last three should be vector components.
5335pub fn quiver3_data<I, XE, YE, ZE, VXE, VYE, VZE>(data: I) -> Quiver3
5336where
5337    I: IntoIterator<Item = (XE, YE, ZE, VXE, VYE, VZE)>,
5338    XE: Real,
5339    YE: Real,
5340    ZE: Real,
5341    VXE: Real,
5342    VYE: Real,
5343    VZE: Real,
5344{
5345    Quiver3::new_data(data)
5346}
5347
5348impl Matplotlib for Quiver3 {
5349    fn is_prelude(&self) -> bool { false }
5350
5351    fn data(&self) -> Option<Value> {
5352        let x: Vec<Value> = self.x.iter().copied().map(Value::from).collect();
5353        let y: Vec<Value> = self.y.iter().copied().map(Value::from).collect();
5354        let z: Vec<Value> = self.z.iter().copied().map(Value::from).collect();
5355        let vx: Vec<Value> = self.vx.iter().copied().map(Value::from).collect();
5356        let vy: Vec<Value> = self.vy.iter().copied().map(Value::from).collect();
5357        let vz: Vec<Value> = self.vz.iter().copied().map(Value::from).collect();
5358        Some(Value::Array(vec![
5359            x.into(),
5360            y.into(),
5361            z.into(),
5362            vx.into(),
5363            vy.into(),
5364            vz.into(),
5365        ]))
5366    }
5367
5368    fn py_cmd(&self) -> String {
5369        format!(
5370            "ax.quiver(\
5371            data[0], data[1], data[2], data[3], data[4], data[5]{}{})",
5372            if self.opts.is_empty() { "" } else { ", " },
5373            self.opts.as_py(),
5374        )
5375    }
5376}
5377
5378impl MatplotlibOpts for Quiver3 {
5379    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
5380        self.opts.push((key, val).into());
5381        self
5382    }
5383}
5384
5385/// A 3D surface plot.
5386///
5387/// ```python
5388/// ax.plot_surface({x}, {y}, {z}, **{opts})
5389/// ```
5390///
5391/// **Note**: `plot_surface` requires input to be in the form of a NumPy array.
5392/// Therefore, this command requires that NumPy be imported under the usual
5393/// name, `np`.
5394///
5395/// Prelude: **No**
5396///
5397/// JSON data: `[list[list[float]], list[list[float]], list[list[float]]]`
5398#[derive(Clone, Debug, PartialEq)]
5399pub struct Surface {
5400    /// X-coordinates.
5401    pub x: Vec<Vec<f64>>,
5402    /// Y-coordinates.
5403    pub y: Vec<Vec<f64>>,
5404    /// Z-coordinates.
5405    pub z: Vec<Vec<f64>>,
5406    /// Optional keyword arguments.
5407    pub opts: Vec<Opt>,
5408}
5409
5410impl Surface {
5411    /// Create a new `Surface` with no options.
5412    pub fn new<XI, XJ, XE, YI, YJ, YE, ZI, ZJ, ZE>(x: XI, y: YI, z: ZI) -> Self
5413    where
5414        XI: IntoIterator<Item = XJ>,
5415        XJ: IntoIterator<Item = XE>,
5416        XE: Real,
5417        YI: IntoIterator<Item = YJ>,
5418        YJ: IntoIterator<Item = YE>,
5419        YE: Real,
5420        ZI: IntoIterator<Item = ZJ>,
5421        ZJ: IntoIterator<Item = ZE>,
5422        ZE: Real,
5423    {
5424        let x: Vec<Vec<f64>> =
5425            x.into_iter()
5426            .map(|row| row.into_iter().map(Real::into_f64).collect())
5427            .collect();
5428        let y: Vec<Vec<f64>> =
5429            y.into_iter()
5430            .map(|row| row.into_iter().map(Real::into_f64).collect())
5431            .collect();
5432        let z: Vec<Vec<f64>> =
5433            z.into_iter()
5434            .map(|row| row.into_iter().map(Real::into_f64).collect())
5435            .collect();
5436        Self { x, y, z, opts: Vec::new() }
5437    }
5438
5439    /// Create a new `Surface` from flattened, row-major iterators over
5440    /// coordinate data with row length `rowlen`.
5441    ///
5442    /// *Panics if `rowlen == 0`*.
5443    pub fn new_flat<X, XE, Y, YE, Z, ZE>(x: X, y: Y, z: Z, rowlen: usize) -> Self
5444    where
5445        X: IntoIterator<Item = XE>,
5446        XE: Real,
5447        Y: IntoIterator<Item = YE>,
5448        YE: Real,
5449        Z: IntoIterator<Item = ZE>,
5450        ZE: Real,
5451    {
5452        if rowlen == 0 { panic!("row length cannot be zero"); }
5453        let x: Vec<Vec<f64>> =
5454            Chunks::new(x.into_iter().map(Real::into_f64), rowlen)
5455            .collect();
5456        let y: Vec<Vec<f64>> =
5457            Chunks::new(y.into_iter().map(Real::into_f64), rowlen)
5458            .collect();
5459        let z: Vec<Vec<f64>> =
5460            Chunks::new(z.into_iter().map(Real::into_f64), rowlen)
5461            .collect();
5462        Self { x, y, z, opts: Vec::new() }
5463    }
5464
5465    /// Create a new `Surface` from a single flattened, row-major iterator over
5466    /// coordinate data with row length `rowlen`.
5467    ///
5468    /// *Panics if `rowlen == 0`*.
5469    pub fn new_data<I, XE, YE, ZE>(data: I, rowlen: usize) -> Self
5470    where
5471        I: IntoIterator<Item = (XE, YE, ZE)>,
5472        XE: Real,
5473        YE: Real,
5474        ZE: Real,
5475    {
5476        if rowlen == 0 { panic!("row length cannot be zero"); }
5477        let mut x: Vec<Vec<f64>> = Vec::new();
5478        let mut y: Vec<Vec<f64>> = Vec::new();
5479        let mut z: Vec<Vec<f64>> = Vec::new();
5480        Chunks::new(data.into_iter(), rowlen)
5481            .for_each(|points| {
5482                let mut xi: Vec<f64> = Vec::with_capacity(rowlen);
5483                let mut yi: Vec<f64> = Vec::with_capacity(rowlen);
5484                let mut zi: Vec<f64> = Vec::with_capacity(rowlen);
5485                points.into_iter()
5486                    .for_each(|(xij, yij, zij)| {
5487                        xi.push(xij.into_f64());
5488                        yi.push(yij.into_f64());
5489                        zi.push(zij.into_f64());
5490                    });
5491                x.push(xi);
5492                y.push(yi);
5493                z.push(zi);
5494            });
5495        Self { x, y, z, opts: Vec::new() }
5496    }
5497}
5498
5499/// Create a new [`Surface`] with no options.
5500pub fn surface<XI, XJ, XE, YI, YJ, YE, ZI, ZJ, ZE>(x: XI, y: YI, z: ZI)
5501    -> Surface
5502where
5503    XI: IntoIterator<Item = XJ>,
5504    XJ: IntoIterator<Item = XE>,
5505    XE: Real,
5506    YI: IntoIterator<Item = YJ>,
5507    YJ: IntoIterator<Item = YE>,
5508    YE: Real,
5509    ZI: IntoIterator<Item = ZJ>,
5510    ZJ: IntoIterator<Item = ZE>,
5511    ZE: Real,
5512{
5513    Surface::new(x, y, z)
5514}
5515
5516/// Create a new [`Surface`] from flattened, row-major iterators over coordinate
5517/// data with row length `rowlen`.
5518///
5519/// *Panics if `rowlen == 0`*.
5520pub fn surface_flat<X, XE, Y, YE, Z, ZE>(x: X, y: Y, z: Z, rowlen: usize) -> Surface
5521where
5522    X: IntoIterator<Item = XE>,
5523    XE: Real,
5524    Y: IntoIterator<Item = YE>,
5525    YE: Real,
5526    Z: IntoIterator<Item = ZE>,
5527    ZE: Real,
5528{
5529    Surface::new_flat(x, y, z, rowlen)
5530}
5531
5532/// Create a new [`Surface`] from a single flattened, row-major iterator over
5533/// coordinate data with row length `rowlen`.
5534///
5535/// *Panics if `rowlen == 0`*.
5536pub fn surface_data<I, XE, YE, ZE>(data: I, rowlen: usize) -> Surface
5537where
5538    I: IntoIterator<Item = (XE, YE, ZE)>,
5539    XE: Real,
5540    YE: Real,
5541    ZE: Real,
5542{
5543    Surface::new_data(data, rowlen)
5544}
5545
5546impl Matplotlib for Surface {
5547    fn is_prelude(&self) -> bool { false }
5548
5549    fn data(&self) -> Option<Value> {
5550        let x: Vec<Value> =
5551            self.x.iter()
5552            .map(|row| {
5553                let row: Vec<Value> =
5554                    row.iter().copied().map(Value::from).collect();
5555                Value::Array(row)
5556            })
5557            .collect();
5558        let y: Vec<Value> =
5559            self.y.iter()
5560            .map(|row| {
5561                let row: Vec<Value> =
5562                    row.iter().copied().map(Value::from).collect();
5563                Value::Array(row)
5564            })
5565            .collect();
5566        let z: Vec<Value> =
5567            self.z.iter()
5568            .map(|row| {
5569                let row: Vec<Value> =
5570                    row.iter().copied().map(Value::from).collect();
5571                Value::Array(row)
5572            })
5573            .collect();
5574        Some(Value::Array(vec![x.into(), y.into(), z.into()]))
5575    }
5576
5577    fn py_cmd(&self) -> String {
5578        format!("\
5579            ax.plot_surface(\
5580            np.array(data[0]), \
5581            np.array(data[1]), \
5582            np.array(data[2])\
5583            {}{})",
5584            if self.opts.is_empty() { "" } else { ", " },
5585            self.opts.as_py(),
5586        )
5587    }
5588}
5589
5590impl MatplotlibOpts for Surface {
5591    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
5592        self.opts.push((key, val).into());
5593        self
5594    }
5595}
5596
5597/// A 3D surface plot using triangulation.
5598///
5599/// ```python
5600/// ax.plot_trisurf({x}, {y}, {z}, **{opts})
5601/// ```
5602///
5603/// Prelude: **No**
5604///
5605/// JSON data: `[list[float], list[float], list[float]]`
5606#[derive(Clone, Debug, PartialEq)]
5607pub struct Trisurf {
5608    /// X-coordinates.
5609    pub x: Vec<f64>,
5610    /// Y-coordinates.
5611    pub y: Vec<f64>,
5612    /// Z-coordinates.
5613    pub z: Vec<f64>,
5614    /// Optional keyword arguments.
5615    pub opts: Vec<Opt>,
5616}
5617
5618impl Trisurf {
5619    /// Create a new `Trisurf` with no options.
5620    pub fn new<X, XE, Y, YE, Z, ZE>(x: X, y: Y, z: Z) -> Self
5621    where
5622        X: IntoIterator<Item = XE>,
5623        XE: Real,
5624        Y: IntoIterator<Item = YE>,
5625        YE: Real,
5626        Z: IntoIterator<Item = ZE>,
5627        ZE: Real,
5628    {
5629        Self {
5630            x: x.into_iter().map(Real::into_f64).collect(),
5631            y: y.into_iter().map(Real::into_f64).collect(),
5632            z: z.into_iter().map(Real::into_f64).collect(),
5633            opts: Vec::new(),
5634        }
5635    }
5636
5637    /// Create a new `Trisurf` with no options from a single iterator.
5638    pub fn new_data<I, XE, YE, ZE>(data: I) -> Self
5639    where
5640        I: IntoIterator<Item = (XE, YE, ZE)>,
5641        XE: Real,
5642        YE: Real,
5643        ZE: Real,
5644    {
5645        let ((x, y), z) =
5646            data.into_iter()
5647            .map(|(a, b, c)| (a.into_f64(), b.into_f64(), c.into_f64()))
5648            .map(assoc)
5649            .unzip();
5650        Self { x, y, z, opts: Vec::new() }
5651    }
5652}
5653
5654/// Create a new [`Trisurf`] with no options.
5655pub fn trisurf<X, XE, Y, YE, Z, ZE>(x: X, y: Y, z: Z) -> Trisurf
5656where
5657    X: IntoIterator<Item = XE>,
5658    XE: Real,
5659    Y: IntoIterator<Item = YE>,
5660    YE: Real,
5661    Z: IntoIterator<Item = ZE>,
5662    ZE: Real,
5663{
5664    Trisurf::new(x, y, z)
5665}
5666
5667/// Create a new [`Trisurf`] with no options from a single iterator.
5668pub fn trisurf_data<I, XE, YE, ZE>(data: I) -> Trisurf
5669where
5670    I: IntoIterator<Item = (XE, YE, ZE)>,
5671    XE: Real,
5672    YE: Real,
5673    ZE: Real,
5674{
5675    Trisurf::new_data(data)
5676}
5677
5678impl Matplotlib for Trisurf {
5679    fn is_prelude(&self) -> bool { false }
5680
5681    fn data(&self) -> Option<Value> {
5682        let x: Vec<Value> = self.x.iter().copied().map(Value::from).collect();
5683        let y: Vec<Value> = self.y.iter().copied().map(Value::from).collect();
5684        let z: Vec<Value> = self.z.iter().copied().map(Value::from).collect();
5685        Some(Value::Array(vec![x.into(), y.into(), z.into()]))
5686    }
5687
5688    fn py_cmd(&self) -> String {
5689        format!("ax.plot_trisurf(data[0], data[1], data[2]{}{})",
5690            if self.opts.is_empty() { "" } else { ", " },
5691            self.opts.as_py(),
5692        )
5693    }
5694}
5695
5696impl MatplotlibOpts for Trisurf {
5697    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
5698        self.opts.push((key, val).into());
5699        self
5700    }
5701}
5702
5703/// Set the view on a set of 3D axes.
5704///
5705/// Angles are in degrees.
5706///
5707/// ```python
5708/// ax.view_init(azim={azim}, elev={elev}, **{opts})
5709/// ```
5710///
5711/// Prelude: **No**
5712///
5713/// JSON data: **None**
5714#[derive(Clone, Debug, PartialEq)]
5715pub struct ViewInit {
5716    /// Azimuthal angle.
5717    pub azim: f64,
5718    /// Elevational angle.
5719    pub elev: f64,
5720    /// Optional keyword arguments.
5721    pub opts: Vec<Opt>,
5722}
5723
5724impl ViewInit {
5725    /// Create a new `ViewInit` with no options.
5726    pub fn new(azim: f64, elev: f64) -> Self {
5727        Self { azim, elev, opts: Vec::new() }
5728    }
5729}
5730
5731/// Create a new [`ViewInit`] with no options.
5732pub fn view_init(azim: f64, elev: f64) -> ViewInit { ViewInit::new(azim, elev) }
5733
5734impl Matplotlib for ViewInit {
5735    fn is_prelude(&self) -> bool { false }
5736
5737    fn data(&self) -> Option<Value> { None }
5738
5739    fn py_cmd(&self) -> String {
5740        format!("ax.view_init(azim={}, elev={}{}{})",
5741            self.azim.as_py(),
5742            self.elev.as_py(),
5743            if self.opts.is_empty() { "" } else { ", " },
5744            self.opts.as_py(),
5745        )
5746    }
5747}
5748
5749impl MatplotlibOpts for ViewInit {
5750    fn kwarg<T: Into<PyValue>>(&mut self, key: &str, val: T) -> &mut Self {
5751        self.opts.push((key, val).into());
5752        self
5753    }
5754}
5755
5756/// Rearrange the grouping of tuples.
5757///
5758/// Although this trait can in principle describe any effective isomorphism
5759/// between two types, the implementations in this crate focus on those
5760/// describing how tuples can be rearranged trivially. That is, this crate
5761/// implements `Associator` to perform "flattening" (or "unflattening")
5762/// operations on tuples of few to several elements.
5763///
5764/// This is helpful in interfacing chains of calls to [`Iterator::zip`] with
5765/// several constructors in this module that require iterators over "flat"
5766/// tuples.
5767/// ```
5768/// use matplotlib::commands::assoc;
5769///
5770/// let x = vec![1,    2,     3_usize];
5771/// let y = vec!['a',  'b',   'c'    ];
5772/// let z = vec![true, false, true   ];
5773///
5774/// let flat: Vec<(usize, char, bool)>
5775///     = x.iter().copied()
5776///     .zip(y.iter().copied())
5777///     .zip(z.iter().copied()) // element type is ((usize, char), bool)
5778///     .map(assoc) // ((A, B), C) -> (A, B, C)
5779///     .collect();
5780///
5781/// assert_eq!(flat, vec![(1, 'a', true), (2, 'b', false), (3, 'c', true)]);
5782///
5783/// // can also be used for unzipping
5784/// let ((x2, y2), z2): ((Vec<usize>, Vec<char>), Vec<bool>)
5785///     = flat.into_iter().map(assoc).unzip();
5786///
5787/// assert_eq!(x2, x);
5788/// assert_eq!(y2, y);
5789/// assert_eq!(z2, z);
5790/// ```
5791pub trait Associator<P> {
5792    /// Rearrange the elements of `self`.
5793    fn assoc(self) -> P;
5794}
5795
5796// there may be a way to do all these with recursive macros, but I'm too dumb
5797// for it; instead, we'll bootstrap with four base impls:
5798
5799impl<A, B, C> Associator<((A, B), C)> for (A, B, C) {
5800    fn assoc(self) -> ((A, B), C) { ((self.0, self.1), self.2) }
5801}
5802
5803impl<A, B, C> Associator<(A, B, C)> for ((A, B), C) {
5804    fn assoc(self) -> (A, B, C) { (self.0.0, self.0.1, self.1) }
5805}
5806
5807impl<A, B, C> Associator<(A, (B, C))> for (A, B, C) {
5808    fn assoc(self) -> (A, (B, C)) { (self.0, (self.1, self.2)) }
5809}
5810
5811impl<A, B, C> Associator<(A, B, C)> for (A, (B, C)) {
5812    fn assoc(self) -> (A, B, C) { (self.0, self.1.0, self.1.1) }
5813}
5814
5815// now use the base impls to cover cases with more elements
5816
5817macro_rules! impl_biassoc {
5818    (
5819        <$( $gen:ident ),+>,
5820        $pair:ty,
5821        ($( $l:ident ),+),
5822        $r:ident $(,)?
5823    ) => {
5824        impl<$( $gen ),+> Associator<$pair> for ($( $gen ),+) {
5825            fn assoc(self) -> $pair {
5826                let ($( $l ),+, $r) = self;
5827                (($( $l ),+).assoc(), $r)
5828            }
5829        }
5830
5831        impl<$( $gen ),+> Associator<($( $gen ),+)> for $pair {
5832            fn assoc(self) -> ($( $gen ),+) {
5833                let ($( $l ),+) = self.0.assoc();
5834                ($( $l ),+, self.1)
5835            }
5836        }
5837    };
5838    (
5839        <$( $gen:ident ),+>,
5840        $pair:ty,
5841        $l:ident,
5842        ($( $r:ident ),+) $(,)?
5843    ) => {
5844        impl<$( $gen ),+> Associator<$pair> for ($( $gen ),+) {
5845            fn assoc(self) -> $pair {
5846                let ($l, $( $r ),+) = self;
5847                ($l, ($( $r ),+).assoc())
5848            }
5849        }
5850
5851        impl<$( $gen ),+> Associator<($( $gen ),+)> for $pair {
5852            fn assoc(self) -> ($( $gen ),+) {
5853                let ($( $r ),+) = self.1.assoc();
5854                (self.0, $( $r ),+)
5855            }
5856        }
5857    };
5858}
5859
5860impl_biassoc!(<A, B, C, D>, (((A, B), C), D), (a, b, c), d);
5861impl_biassoc!(<A, B, C, D>, ((A, (B, C)), D), (a, b, c), d);
5862impl_biassoc!(<A, B, C, D>, (A, ((B, C), D)), a, (b, c, d));
5863impl_biassoc!(<A, B, C, D>, (A, (B, (C, D))), a, (b, c, d));
5864impl_biassoc!(<A, B, C, D, E>, ((((A, B), C), D), E), (a, b, c, d), e);
5865impl_biassoc!(<A, B, C, D, E>, (((A, (B, C)), D), E), (a, b, c, d), e);
5866impl_biassoc!(<A, B, C, D, E>, ((A, ((B, C), D)), E), (a, b, c, d), e);
5867impl_biassoc!(<A, B, C, D, E>, ((A, (B, (C, D))), E), (a, b, c, d), e);
5868impl_biassoc!(<A, B, C, D, E>, (A, (((B, C), D), E)), a, (b, c, d, e));
5869impl_biassoc!(<A, B, C, D, E>, (A, ((B, (C, D)), E)), a, (b, c, d, e));
5870impl_biassoc!(<A, B, C, D, E>, (A, (B, ((C, D), E))), a, (b, c, d, e));
5871impl_biassoc!(<A, B, C, D, E>, (A, (B, (C, (D, E)))), a, (b, c, d, e));
5872impl_biassoc!(<A, B, C, D, E, F>, (((((A, B), C), D), E), F), (a, b, c, d, e), f);
5873impl_biassoc!(<A, B, C, D, E, F>, ((((A, (B, C)), D), E), F), (a, b, c, d, e), f);
5874impl_biassoc!(<A, B, C, D, E, F>, (((A, ((B, C), D)), E), F), (a, b, c, d, e), f);
5875impl_biassoc!(<A, B, C, D, E, F>, (((A, (B, (C, D))), E), F), (a, b, c, d, e), f);
5876impl_biassoc!(<A, B, C, D, E, F>, ((A, (((B, C), D), E)), F), (a, b, c, d, e), f);
5877impl_biassoc!(<A, B, C, D, E, F>, ((A, ((B, (C, D)), E)), F), (a, b, c, d, e), f);
5878impl_biassoc!(<A, B, C, D, E, F>, ((A, (B, ((C, D), E))), F), (a, b, c, d, e), f);
5879impl_biassoc!(<A, B, C, D, E, F>, ((A, (B, (C, (D, E)))), F), (a, b, c, d, e), f);
5880impl_biassoc!(<A, B, C, D, E, F>, (A, ((((B, C), D), E), F)), a, (b, c, d, e, f));
5881impl_biassoc!(<A, B, C, D, E, F>, (A, (((B, (C, D)), E), F)), a, (b, c, d, e, f));
5882impl_biassoc!(<A, B, C, D, E, F>, (A, ((B, ((C, D), E)), F)), a, (b, c, d, e, f));
5883impl_biassoc!(<A, B, C, D, E, F>, (A, ((B, (C, (D, E))), F)), a, (b, c, d, e, f));
5884impl_biassoc!(<A, B, C, D, E, F>, (A, (B, (((C, D), E), F))), a, (b, c, d, e, f));
5885impl_biassoc!(<A, B, C, D, E, F>, (A, (B, ((C, (D, E)), F))), a, (b, c, d, e, f));
5886impl_biassoc!(<A, B, C, D, E, F>, (A, (B, (C, ((D, E), F)))), a, (b, c, d, e, f));
5887impl_biassoc!(<A, B, C, D, E, F>, (A, (B, (C, (D, (E, F))))), a, (b, c, d, e, f));
5888
5889/// Quick shortcut to [`Associator::assoc`] that doesn't require importing the
5890/// trait.
5891pub fn assoc<A, B>(a: A) -> B
5892where A: Associator<B>
5893{
5894    a.assoc()
5895}
5896
5897/// Quick shortcut to calling `.map` on an iterator with [`assoc`].
5898pub fn assoc_iter<I, J, A, B>(iter: I) -> std::iter::Map<J, fn(A) -> B>
5899where
5900    I: IntoIterator<IntoIter = J, Item = A>,
5901    J: Iterator<Item = A>,
5902    A: Associator<B>,
5903{
5904    iter.into_iter().map(assoc)
5905}
5906
5907#[cfg(test)]
5908mod tests {
5909    use crate::{ Mpl, Run, MatplotlibOpts, opt, GSPos };
5910    use super::*;
5911
5912    fn runner() -> Run { Run::Debug }
5913
5914    #[test]
5915    fn test_prelude_init() {
5916        Mpl::default()
5917            | runner()
5918    }
5919
5920    #[test]
5921    fn test_axhline() {
5922        Mpl::default()
5923            & axhline(10.0).o("linestyle", "-")
5924            | runner()
5925    }
5926
5927    #[test]
5928    fn test_axline() {
5929        Mpl::default()
5930            & axline((0.0, 0.0), (10.0, 10.0)).o("linestyle", "-")
5931            | runner()
5932    }
5933
5934    #[test]
5935    fn test_axlinem() {
5936        Mpl::default()
5937            & axlinem((0.0, 0.0), 1.0).o("linestyle", "-")
5938            | runner()
5939    }
5940
5941    #[test]
5942    fn test_axtext() {
5943        Mpl::default()
5944            & axtext(0.5, 0.5, "hello world").o("ha", "left").o("va", "bottom")
5945            | runner()
5946    }
5947
5948    #[test]
5949    fn test_axvline() {
5950        Mpl::default()
5951            & axvline(10.0).o("linestyle", "-")
5952            | runner()
5953    }
5954
5955    #[test]
5956    fn test_bar() {
5957        Mpl::default()
5958            & bar([0.0, 1.0], [0.5, 0.5]).o("color", "C0")
5959            | runner()
5960    }
5961
5962    #[test]
5963    fn test_bar_pairs() {
5964        Mpl::default()
5965            & bar_pairs([(0.0, 0.5), (1.0, 0.5)]).o("color", "C0")
5966            | runner()
5967    }
5968
5969    #[test]
5970    fn test_bar_eq() {
5971        assert_eq!(
5972            bar([0.0, 1.0], [0.5, 0.5]).o("color", "C0"),
5973            bar_pairs([(0.0, 0.5), (1.0, 0.5)]).o("color", "C0"),
5974        )
5975    }
5976
5977    #[test]
5978    fn test_barh() {
5979        Mpl::default()
5980            & barh([0.0, 1.0], [0.5, 0.5]).o("color", "C0")
5981            | runner()
5982    }
5983
5984    #[test]
5985    fn test_barh_pairs() {
5986        Mpl::default()
5987            & barh_pairs([(0.0, 0.5), (1.0, 0.5)]).o("color", "C0")
5988            | runner()
5989    }
5990
5991    #[test]
5992    fn test_barh_eq() {
5993        assert_eq!(
5994            barh([0.0, 1.0], [0.5, 0.5]).o("color", "C0"),
5995            barh_pairs([(0.0, 0.5), (1.0, 0.5)]).o("color", "C0"),
5996        )
5997    }
5998
5999    #[test]
6000    fn test_boxplot() {
6001        Mpl::default()
6002            & boxplot([[0.0, 1.0, 2.0], [2.0, 3.0, 4.0]]).o("notch", true)
6003            | runner()
6004    }
6005
6006    #[test]
6007    fn test_boxplot_flat() {
6008        Mpl::default()
6009            & boxplot_flat([0.0, 1.0, 2.0, 2.0, 3.0, 4.0], 3).o("notch", true)
6010            | runner()
6011    }
6012
6013    #[test]
6014    fn test_boxplot_eq() {
6015        assert_eq!(
6016            boxplot([[0.0, 1.0, 2.0], [2.0, 3.0, 4.0]]).o("notch", true),
6017            boxplot_flat([0.0, 1.0, 2.0, 2.0, 3.0, 4.0], 3).o("notch", true),
6018        )
6019    }
6020
6021    #[test]
6022    fn test_clabel() {
6023        Mpl::default()
6024            & imshow([[0.0, 1.0], [2.0, 3.0]])
6025            & colorbar()
6026            & clabel("hello world").o("fontsize", "medium")
6027            | runner()
6028    }
6029
6030    #[test]
6031    fn test_clim() {
6032        Mpl::default()
6033            & imshow([[0.0, 1.0], [2.0, 3.0]])
6034            & colorbar()
6035            & clim(Some(0.0), Some(1.0))
6036            | runner()
6037    }
6038
6039    #[test]
6040    fn test_colorbar() {
6041        Mpl::default()
6042            & imshow([[0.0, 1.0], [2.0, 3.0]])
6043            & colorbar().o("location", "top")
6044            | runner()
6045    }
6046
6047    #[test]
6048    fn test_contour() {
6049        Mpl::default()
6050            & contour([0.0, 1.0], [0.0, 1.0], [[0.0, 1.0], [2.0, 3.0]])
6051                .o("cmap", "bone")
6052            & colorbar()
6053            | runner()
6054    }
6055
6056    #[test]
6057    fn test_contour_flat() {
6058        Mpl::default()
6059            & contour_flat([0.0, 1.0], [0.0, 1.0], [0.0, 1.0, 2.0, 3.0])
6060                .o("cmap", "bone")
6061            & colorbar()
6062            | runner()
6063    }
6064
6065    #[test]
6066    fn test_contour_eq() {
6067        assert_eq!(
6068            contour([0.0, 1.0], [0.0, 1.0], [[0.0, 1.0], [2.0, 3.0]])
6069                .o("cmap", "bone"),
6070            contour_flat([0.0, 1.0], [0.0, 1.0], [0.0, 1.0, 2.0, 3.0])
6071                .o("cmap", "bone"),
6072        )
6073    }
6074
6075    #[test]
6076    fn test_contour_labels() {
6077        Mpl::default()
6078            & contour([0.0, 1.0], [0.0, 1.0], [[0.0, 1.0], [2.0, 3.0]])
6079                .o("cmap", "bone")
6080                .o("levels", [0.5, 1.0, 1.5, 2.0, 2.5])
6081            & colorbar()
6082            & contour_labels()
6083                .on_levels([1.0, 2.0])
6084                .with_fmt("x", "f\"{x:.6f}\"")
6085                .o("fontsize", "medium")
6086            | runner()
6087    }
6088
6089    #[test]
6090    fn test_contourf() {
6091        Mpl::default()
6092            & contour([0.0, 1.0], [0.0, 1.0], [[0.0, 1.0], [2.0, 3.0]])
6093                .o("cmap", "bone")
6094            & colorbar()
6095            | runner()
6096    }
6097
6098    #[test]
6099    fn test_contourf_flat() {
6100        Mpl::default()
6101            & contour_flat([0.0, 1.0], [0.0, 1.0], [0.0, 1.0, 2.0, 3.0])
6102                .o("cmap", "bone")
6103            & colorbar()
6104            | runner()
6105    }
6106
6107    #[test]
6108    fn test_contourf_eq() {
6109        assert_eq!(
6110            contourf([0.0, 1.0], [0.0, 1.0], [[0.0, 1.0], [2.0, 3.0]])
6111                .o("cmap", "bone"),
6112            contourf_flat([0.0, 1.0], [0.0, 1.0], [0.0, 1.0, 2.0, 3.0])
6113                .o("cmap", "bone"),
6114        )
6115    }
6116
6117    #[test]
6118    fn test_cticklabels() {
6119        Mpl::default()
6120            & imshow([[0.0, 1.0], [2.0, 3.0]])
6121            & colorbar()
6122            & cticklabels([0.0, 1.0], ["zero", "one"]).o("minor", true)
6123            | runner()
6124    }
6125
6126    #[test]
6127    fn test_cticklabels_data() {
6128        Mpl::default()
6129            & imshow([[0.0, 1.0], [2.0, 3.0]])
6130            & colorbar()
6131            & cticklabels_data([(0.0, "zero"), (1.0, "one")]).o("minor", true)
6132            | runner()
6133    }
6134
6135    #[test]
6136    fn test_cticklabels_eq() {
6137        assert_eq!(
6138            cticklabels([0.0, 1.0], ["zero", "one"]).o("minor", true),
6139            cticklabels_data([(0.0, "zero"), (1.0, "one")]).o("minor", true),
6140        )
6141    }
6142
6143    #[test]
6144    fn test_cticks() {
6145        Mpl::default()
6146            & imshow([[0.0, 1.0], [2.0, 3.0]])
6147            & colorbar()
6148            & cticks([0.0, 1.0])
6149                .o("labels", PyValue::list(["zero", "one"]))
6150            | runner()
6151    }
6152
6153    #[test]
6154    fn test_errorbar() {
6155        Mpl::default()
6156            & errorbar([0.0, 1.0], [0.0, 1.0], [0.5, 1.0]).o("color", "C0")
6157            | runner()
6158    }
6159
6160    #[test]
6161    fn test_errorbar_data() {
6162        Mpl::default()
6163            & errorbar_data([(0.0, 0.0, 0.5), (1.0, 1.0, 1.0)]).o("color", "C0")
6164            | runner()
6165    }
6166
6167    #[test]
6168    fn test_errorbar_eq() {
6169        assert_eq!(
6170            errorbar([0.0, 1.0], [0.0, 1.0], [0.5, 1.0]).o("color", "C0"),
6171            errorbar_data([(0.0, 0.0, 0.5), (1.0, 1.0, 1.0)]).o("color", "C0"),
6172        )
6173    }
6174
6175    #[test]
6176    fn test_errorbar2() {
6177        Mpl::default()
6178            & errorbar2([0.0, 1.0], [0.0, 1.0], [1.0, 0.5], [0.5, 1.0])
6179                .o("color", "C0")
6180            | runner()
6181    }
6182
6183    #[test]
6184    fn test_errorbar2_data() {
6185        Mpl::default()
6186            & errorbar2_data([(0.0, 0.0, 1.0, 0.5), (1.0, 1.0, 0.5, 1.0)])
6187                .o("color", "C0")
6188            | runner()
6189    }
6190
6191    #[test]
6192    fn test_errorbar2_eq() {
6193        assert_eq!(
6194            errorbar2([0.0, 1.0], [0.0, 1.0], [1.0, 0.5], [0.5, 1.0])
6195                .o("color", "C0"),
6196            errorbar2_data([(0.0, 0.0, 1.0, 0.5), (1.0, 1.0, 0.5, 1.0)])
6197                .o("color", "C0"),
6198        )
6199    }
6200
6201    #[test]
6202    fn test_figtext() {
6203        Mpl::default()
6204            & figtext(0.5, 0.5, "hello world").o("ha", "left").o("va", "bottom")
6205            | runner()
6206    }
6207
6208    #[test]
6209    fn test_fill_between() {
6210        Mpl::default()
6211            & fill_between([0.0, 1.0], [-0.5, 0.0], [0.5, 2.0]).o("color", "C0")
6212            | runner()
6213    }
6214
6215    #[test]
6216    fn test_fill_between_data() {
6217        Mpl::default()
6218            & fill_between_data([(0.0, -0.5, 0.5), (1.0, 0.0, 2.0)])
6219                .o("color", "C0")
6220            | runner()
6221    }
6222
6223    #[test]
6224    fn test_fill_between_eq() {
6225        assert_eq!(
6226            fill_between([0.0, 1.0], [-0.5, 0.0], [0.5, 2.0]).o("color", "C0"),
6227            fill_between_data([(0.0, -0.5, 0.5), (1.0, 0.0, 2.0)])
6228                .o("color", "C0"),
6229        )
6230    }
6231
6232    #[test]
6233    fn test_fillbetween_from_errorbar() {
6234        let ebar =
6235            errorbar_data([(0.0, 0.0, 0.5), (1.0, 1.0, 1.0)]);
6236        let ebar2 =
6237            errorbar2_data([(0.0, 0.25, 0.75, 0.25), (1.0, 1.0, 1.0, 1.0)]);
6238        let fbetw =
6239            fill_between_data([(0.0, -0.5, 0.5), (1.0, 0.0, 2.0)]);
6240        assert_eq!(FillBetween::from(ebar),  fbetw);
6241        assert_eq!(FillBetween::from(ebar2), fbetw);
6242    }
6243
6244    #[test]
6245    fn test_errorbar_from_fillbetween() {
6246        let fbetw = fill_between_data([(0.0, -0.5, 0.5), (1.0, 0.0, 2.0)]);
6247        let ebar = errorbar_data([(0.0, 0.0, 0.5), (1.0, 1.0, 1.0)]);
6248        assert_eq!(Errorbar::from(fbetw), ebar);
6249    }
6250
6251    #[test]
6252    fn test_fill_betweenx() {
6253        Mpl::default()
6254            & fill_betweenx([0.0, 1.0], [-0.5, 0.0], [0.5, 2.0])
6255                .o("color", "C0")
6256            | runner()
6257    }
6258
6259    #[test]
6260    fn test_fill_betweenx_data() {
6261        Mpl::default()
6262            & fill_betweenx_data([(0.0, -0.5, 0.5), (1.0, 0.0, 2.0)])
6263                .o("color", "C0")
6264            | runner()
6265    }
6266
6267    #[test]
6268    fn test_fill_betweenx_eq() {
6269        assert_eq!(
6270            fill_betweenx([0.0, 1.0], [-0.5, 0.0], [0.5, 2.0]).o("color", "C0"),
6271            fill_betweenx_data([(0.0, -0.5, 0.5), (1.0, 0.0, 2.0)])
6272                .o("color", "C0"),
6273        )
6274    }
6275
6276    #[test]
6277    fn test_grid() {
6278        Mpl::default()
6279            & grid(true).o("which", "both")
6280            | runner()
6281    }
6282
6283    #[test]
6284    fn test_hist() {
6285        Mpl::default()
6286            & hist([0.0, 1.0, 2.0])
6287                .o("bins", PyValue::list([-0.5, 0.5, 1.5, 2.5]))
6288            | runner()
6289    }
6290
6291    #[test]
6292    fn test_hist2d() {
6293        Mpl::default()
6294            & hist2d([0.0, 1.0, 2.0], [0.0, 2.0, 4.0]).o("cmap", "bone")
6295            | runner()
6296    }
6297
6298    #[test]
6299    fn test_hist2d_pairs() {
6300        Mpl::default()
6301            & hist2d_pairs([(0.0, 0.0), (1.0, 2.0), (2.0, 4.0)])
6302                .o("cmap", "bone")
6303            | runner()
6304    }
6305
6306    #[test]
6307    fn test_hist2d_eq() {
6308        assert_eq!(
6309            hist2d([0.0, 1.0, 2.0], [0.0, 2.0, 4.0]).o("cmap", "bone"),
6310            hist2d_pairs([(0.0, 0.0), (1.0, 2.0), (2.0, 4.0)]).o("cmap", "bone"),
6311        )
6312    }
6313
6314    #[test]
6315    fn test_violinplot() {
6316        Mpl::default()
6317            & violinplot([[0.0, 1.0], [2.0, 3.0]]).o("vert", false)
6318            | runner()
6319    }
6320
6321    #[test]
6322    fn test_violinplot_flat() {
6323        Mpl::default()
6324            & violinplot_flat([0.0, 1.0, 2.0, 3.0], 2).o("vert", false)
6325            | runner()
6326    }
6327
6328    #[test]
6329    fn test_violinplot_eq() {
6330        assert_eq!(
6331            violinplot([[0.0, 1.0], [2.0, 3.0]]).o("vert", false),
6332            violinplot_flat([0.0, 1.0, 2.0, 3.0], 2).o("vert", false),
6333        )
6334    }
6335
6336    #[test]
6337    fn test_imshow() {
6338        Mpl::default()
6339            & imshow([[0.0, 1.0], [2.0, 3.0]]).o("cmap", "bone")
6340            | runner()
6341    }
6342
6343    #[test]
6344    fn test_colorplot() {
6345        Mpl::default()
6346            & colorplot(
6347                [0.0, 1.0, 1.5],
6348                [2.0, 3.0, 3.5],
6349                [[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]]
6350            )
6351                .o("cmap", "bone")
6352            | runner()
6353    }
6354
6355    #[test]
6356    fn test_colorplot_flat() {
6357        Mpl::default()
6358            & colorplot_flat(
6359                [0.0, 1.0, 1.5],
6360                [2.0, 3.0, 3.5],
6361                [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]
6362            )
6363                .o("cmap", "bone")
6364            | runner()
6365    }
6366
6367    #[test]
6368    fn test_colorplot_eq() {
6369        assert_eq!(
6370            colorplot(
6371                [0.0, 1.0, 1.5],
6372                [2.0, 3.0, 3.5],
6373                [[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]]
6374            ).o("cmap", "bone"),
6375            colorplot_flat(
6376                [0.0, 1.0, 1.5],
6377                [2.0, 3.0, 3.5],
6378                [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]
6379            ).o("cmap", "bone"),
6380        )
6381    }
6382
6383    #[test]
6384    fn test_imshow_flat() {
6385        Mpl::default()
6386            & imshow_flat([0.0, 1.0, 2.0, 3.0], 2).o("cmap", "bone")
6387            | runner()
6388    }
6389
6390    #[test]
6391    fn test_imshow_eq() {
6392        assert_eq!(
6393            imshow([[0.0, 1.0], [2.0, 3.0]]).o("cmap", "bone"),
6394            imshow_flat([0.0, 1.0, 2.0, 3.0], 2).o("cmap", "bone"),
6395        )
6396    }
6397
6398    #[test]
6399    fn test_inset_axes() {
6400        Mpl::default()
6401            & inset_axes(0.5, 0.5, 0.25, 0.25).o("polar", true)
6402            | runner()
6403    }
6404
6405    #[test]
6406    fn test_inset_axes_pairs() {
6407        Mpl::default()
6408            & inset_axes_pairs((0.5, 0.5), (0.25, 0.25)).o("polar", true)
6409            | runner()
6410    }
6411
6412    #[test]
6413    fn test_label() {
6414        Mpl::default()
6415            & label(Axis::X, "xlabel").o("fontsize", "large")
6416            & label(Axis::Y, "ylabel").o("fontsize", "large")
6417            | runner()
6418    }
6419
6420    #[test]
6421    fn test_xlabel() {
6422        Mpl::default()
6423            & xlabel("xlabel").o("fontsize", "large")
6424            | runner()
6425    }
6426
6427    #[test]
6428    fn test_ylabel() {
6429        Mpl::default()
6430            & ylabel("ylabel").o("fontsize", "large")
6431            | runner()
6432    }
6433
6434    #[test]
6435    fn test_label_eq() {
6436        assert_eq!(label(Axis::X, "xlabel"), xlabel("xlabel"));
6437        assert_eq!(label(Axis::Y, "ylabel"), ylabel("ylabel"));
6438    }
6439
6440    #[test]
6441    fn test_legend() {
6442        Mpl::default()
6443            & plot([0.0], [0.0]).o("label", "hello world")
6444            & legend().o("loc", "lower left")
6445            | runner()
6446    }
6447
6448    #[test]
6449    fn test_lim() {
6450        Mpl::default()
6451            & lim(Axis::X, Some(-10.0), Some(10.0))
6452            & lim(Axis::Y, Some(-10.0), Some(10.0))
6453            | runner()
6454    }
6455
6456    #[test]
6457    fn test_xlim() {
6458        Mpl::default()
6459            & xlim(Some(-10.0), Some(10.0))
6460            | runner()
6461    }
6462
6463    #[test]
6464    fn test_ylim() {
6465        Mpl::default()
6466            & ylim(Some(-10.0), Some(10.0))
6467            | runner()
6468    }
6469
6470    #[test]
6471    fn test_lim_eq() {
6472        assert_eq!(
6473            lim(Axis::X, Some(-10.0), Some(15.0)),
6474            xlim(Some(-10.0), Some(15.0)),
6475        );
6476        assert_eq!(
6477            lim(Axis::Y, Some(-10.0), Some(15.0)),
6478            ylim(Some(-10.0), Some(15.0)),
6479        );
6480        assert_eq!(
6481            lim(Axis::Z, Some(-10.0), Some(15.0)),
6482            zlim(Some(-10.0), Some(15.0)),
6483        )
6484    }
6485
6486    #[test]
6487    fn test_pie() {
6488        Mpl::default()
6489            & pie([1.0, 2.0]).o("radius", 2)
6490            | runner()
6491    }
6492
6493    #[test]
6494    fn test_plot() {
6495        Mpl::default()
6496            & plot([0.0, 1.0], [0.0, 1.0]).o("color", "C0")
6497            | runner()
6498    }
6499
6500    #[test]
6501    fn test_plot_empty() {
6502        Mpl::default()
6503            & plot::<_, f64, _, f64>([], []).o("color", "C0")
6504            | runner()
6505    }
6506
6507    #[test]
6508    fn test_plot_borrowed() {
6509        Mpl::default()
6510            & plot(&[0.0, 1.0], &[0.0, 1.0]).o("color", "C0")
6511            | runner()
6512    }
6513
6514    #[test]
6515    fn test_plot_pairs() {
6516        Mpl::default()
6517            & plot_pairs([(0.0, 0.0), (1.0, 1.0)]).o("color", "C0")
6518            | runner()
6519    }
6520
6521    #[test]
6522    fn test_plot_eq() {
6523        assert_eq!(
6524            plot([0.0, 1.0], [0.0, 1.0]).o("color", "C0"),
6525            plot_pairs([(0.0, 0.0), (1.0, 1.0)]).o("color", "C0"),
6526        )
6527    }
6528
6529    #[test]
6530    fn test_quiver() {
6531        Mpl::default()
6532            & quiver([0.0, 1.0], [0.0, 1.0], [0.0, 1.0], [0.0, 1.0])
6533                .o("pivot", "middle")
6534            | runner()
6535    }
6536
6537    #[test]
6538    fn test_quiver_data() {
6539        Mpl::default()
6540            & quiver_data([(0.0, 0.0, 0.0, 0.0), (1.0, 1.0, 1.0, 1.0)])
6541                .o("pivot", "middle")
6542            | runner()
6543    }
6544
6545    #[test]
6546    fn test_quiver_pairs() {
6547        Mpl::default()
6548            & quiver_pairs([(0.0, 0.0), (1.0, 1.0)], [(0.0, 0.0), (1.0, 1.0)])
6549                .o("pivot", "middle")
6550            | runner()
6551    }
6552
6553    #[test]
6554    fn test_quiver_eq() {
6555        let norm =
6556            quiver([0.0, 1.0], [0.0, 1.0], [0.0, 1.0], [0.0, 1.0])
6557            .o("pivot", "middle");
6558        let data =
6559            quiver_data([(0.0, 0.0, 0.0, 0.0), (1.0, 1.0, 1.0, 1.0)])
6560                .o("pivot", "middle");
6561        let pairs =
6562            quiver_pairs([(0.0, 0.0), (1.0, 1.0)], [(0.0, 0.0), (1.0, 1.0)])
6563                .o("pivot", "middle");
6564        assert_eq!(norm, data);
6565        assert_eq!(norm, pairs);
6566    }
6567
6568    #[test]
6569    fn test_rcparam() {
6570        Mpl::default()
6571            & rcparam("figure.figsize", PyValue::list([2.5, 3.5]))
6572            | runner()
6573    }
6574
6575    #[test]
6576    fn test_scale() {
6577        Mpl::default()
6578            & scale(Axis::X, AxisScale::Log)
6579            & scale(Axis::Y, AxisScale::Logit)
6580            | runner()
6581    }
6582
6583    #[test]
6584    fn test_xscale() {
6585        Mpl::default()
6586            & xscale(AxisScale::Log)
6587            | runner()
6588    }
6589
6590    #[test]
6591    fn test_yscale() {
6592        Mpl::default()
6593            & yscale(AxisScale::Logit)
6594            | runner()
6595    }
6596
6597    #[test]
6598    fn test_scale_eq() {
6599        assert_eq!(scale(Axis::X, AxisScale::Log), xscale(AxisScale::Log));
6600        assert_eq!(scale(Axis::Y, AxisScale::Logit), yscale(AxisScale::Logit));
6601        assert_eq!(scale(Axis::Z, AxisScale::SymLog), zscale(AxisScale::SymLog));
6602    }
6603
6604    #[test]
6605    fn test_scatter() {
6606        Mpl::default()
6607            & scatter([0.0, 1.0], [0.0, 1.0]).o("marker", "D")
6608            | runner()
6609    }
6610
6611    #[test]
6612    fn test_scatter_pairs() {
6613        Mpl::default()
6614            & scatter_pairs([(0.0, 0.0), (1.0, 1.0)]).o("marker", "D")
6615            | runner()
6616    }
6617
6618    #[test]
6619    fn test_scatter_eq() {
6620        assert_eq!(
6621            scatter([0.0, 1.0], [0.0, 1.0]).o("marker", "D"),
6622            scatter_pairs([(0.0, 0.0), (1.0, 1.0)]).o("marker", "D"),
6623        )
6624    }
6625
6626    #[test]
6627    fn test_suptitle() {
6628        Mpl::default()
6629            & suptitle("hello world").o("fontsize", "xx-small")
6630            | runner()
6631    }
6632
6633    #[test]
6634    fn test_supxlabel() {
6635        Mpl::default()
6636            & supxlabel("hello world").o("fontsize", "xx-small")
6637            | runner()
6638    }
6639
6640    #[test]
6641    fn test_supylabel() {
6642        Mpl::default()
6643            & supylabel("hello world").o("fontsize", "xx-small")
6644            | runner()
6645    }
6646
6647    #[test]
6648    fn test_make_grid() {
6649        Mpl::new_grid(3, 3, [opt("sharex", true), opt("sharey", true)])
6650            & focus_ax("AX[1, 1]")
6651            & plot([0.0, 1.0], [0.0, 1.0]).o("color", "C1")
6652            | runner()
6653    }
6654
6655    #[test]
6656    fn test_make_gridspec() {
6657        //        0   1   2
6658        //       |--||--||----|
6659        //
6660        //   -   +------++----+
6661        // 0 |   | 0    || 2  |
6662        //   -   |      ||    |
6663        //   -   |      ||    |
6664        // 1 |   |      ||    |
6665        //   -   +------+|    |
6666        //   -   +------+|    |
6667        // 2 |   | 1    ||    |
6668        //   -   +------++----+
6669        //       <sharex>
6670        Mpl::new_gridspec(
6671                [
6672                    opt("nrows", 3),
6673                    opt("ncols", 3),
6674                    opt("width_ratios", PyValue::list([1, 1, 2])),
6675                ],
6676                [
6677                    GSPos::new(0..2, 0..2),
6678                    GSPos::new(2..3, 0..2).sharex(Some(0)),
6679                    GSPos::new(0..3, 2..3),
6680                ],
6681            )
6682            & focus_ax("AX[1]")
6683            & plot([0.0, 1.0], [0.0, 1.0]).o("color", "C1")
6684            | runner()
6685    }
6686
6687    #[test]
6688    fn test_tex_off() {
6689        Mpl::default()
6690            & tex_off()
6691            | runner()
6692    }
6693
6694    #[test]
6695    fn test_tex_on() {
6696        Mpl::default()
6697            & tex_on()
6698            | runner()
6699    }
6700
6701    #[test]
6702    fn test_text() {
6703        Mpl::default()
6704            & text(0.5, 0.5, "hello world").o("fontsize", "large")
6705            | runner()
6706    }
6707
6708    #[test]
6709    fn test_tick_params() {
6710        Mpl::default()
6711            & tick_params(Axis2::Both).o("color", "r")
6712            | runner()
6713    }
6714
6715    #[test]
6716    fn test_xtick_params() {
6717        Mpl::default()
6718            & xtick_params().o("color", "r")
6719            | runner()
6720    }
6721
6722    #[test]
6723    fn test_ytick_params() {
6724        Mpl::default()
6725            & xtick_params().o("color", "r")
6726            | runner()
6727    }
6728
6729    #[test]
6730    fn test_tick_params_eq() {
6731        assert_eq!(
6732            tick_params(Axis2::X).o("color", "r"),
6733            xtick_params().o("color", "r"),
6734        );
6735        assert_eq!(
6736            tick_params(Axis2::Y).o("color", "r"),
6737            ytick_params().o("color", "r"),
6738        );
6739    }
6740
6741    #[test]
6742    fn test_ticklabels() {
6743        Mpl::default()
6744            & ticklabels(Axis::X, [0.0, 1.0], ["x:zero", "x:one"])
6745                .o("fontsize", "small")
6746            & ticklabels(Axis::Y, [0.0, 1.0], ["y:zero", "y:one"])
6747                .o("fontsize", "small")
6748            | runner()
6749    }
6750
6751    #[test]
6752    fn test_ticklabels_data() {
6753        Mpl::default()
6754            & ticklabels_data(Axis::X, [(0.0, "x:zero"), (1.0, "x:one")])
6755                .o("fontsize", "small")
6756            & ticklabels_data(Axis::Y, [(0.0, "y:zero"), (1.0, "y:one")])
6757                .o("fontsize", "small")
6758            | runner()
6759    }
6760
6761    #[test]
6762    fn test_xticklabels() {
6763        Mpl::default()
6764            & xticklabels([0.0, 1.0], ["x:zero", "x:one"])
6765                .o("fontsize", "small")
6766            | runner()
6767    }
6768
6769    #[test]
6770    fn test_xticklabels_data() {
6771        Mpl::default()
6772            & xticklabels_data([(0.0, "x:zero"), (1.0, "x:one")])
6773                .o("fontsize", "small")
6774            | runner()
6775    }
6776
6777    #[test]
6778    fn test_yticklabels() {
6779        Mpl::default()
6780            & yticklabels([0.0, 1.0], ["y:zero", "y:one"])
6781                .o("fontsize", "small")
6782            | runner()
6783    }
6784
6785    #[test]
6786    fn test_yticklabels_data() {
6787        Mpl::default()
6788            & yticklabels_data([(0.0, "y:zero"), (1.0, "y:one")])
6789                .o("fontsize", "small")
6790            | runner()
6791    }
6792
6793    #[test]
6794    fn test_ticklabels_eq() {
6795        let normx =
6796            ticklabels(Axis::X, [0.0, 1.0], ["x:zero", "x:one"]);
6797        let normx_data =
6798            ticklabels_data(Axis::X, [(0.0, "x:zero"), (1.0, "x:one")]);
6799        let aliasx =
6800            xticklabels([0.0, 1.0], ["x:zero", "x:one"]);
6801        let aliasx_data =
6802            xticklabels_data([(0.0, "x:zero"), (1.0, "x:one")]);
6803        let normy =
6804            ticklabels(Axis::Y, [0.0, 1.0], ["y:zero", "y:one"]);
6805        let normy_data =
6806            ticklabels_data(Axis::Y, [(0.0, "y:zero"), (1.0, "y:one")]);
6807        let aliasy =
6808            yticklabels([0.0, 1.0], ["y:zero", "y:one"]);
6809        let aliasy_data =
6810            yticklabels_data([(0.0, "y:zero"), (1.0, "y:one")]);
6811        assert_eq!(normx, normx_data);
6812        assert_eq!(aliasx, aliasx_data);
6813        assert_eq!(normx, aliasx);
6814        assert_eq!(normy, normy_data);
6815        assert_eq!(aliasy, aliasy_data);
6816        assert_eq!(normy, aliasy);
6817    }
6818
6819    #[test]
6820    fn test_ticks() {
6821        Mpl::default()
6822            & ticks(Axis::X, [0.0, 1.0]).o("minor", true)
6823            & ticks(Axis::Y, [0.0, 2.0]).o("minor", true)
6824            | runner()
6825    }
6826
6827    #[test]
6828    fn test_xticks() {
6829        Mpl::default()
6830            & xticks([0.0, 1.0]).o("minor", true)
6831            | runner()
6832    }
6833
6834    #[test]
6835    fn test_yticks() {
6836        Mpl::default()
6837            & yticks([0.0, 2.0]).o("minor", true)
6838            | runner()
6839    }
6840
6841    #[test]
6842    fn test_ticks_eq() {
6843        let normx = ticks(Axis::X, [0.0, 1.0]);
6844        let aliasx = xticks([0.0, 1.0]);
6845        let normy = ticks(Axis::Y, [0.0, 2.0]);
6846        let aliasy = yticks([0.0, 2.0]);
6847        assert_eq!(normx, aliasx);
6848        assert_eq!(normy, aliasy);
6849    }
6850
6851    #[test]
6852    fn test_title() {
6853        Mpl::default()
6854            & title("hello world").o("fontsize", "large")
6855            | runner()
6856    }
6857
6858    #[test]
6859    fn test_tight_layout() {
6860        Mpl::new_grid(3, 3, [])
6861            & tight_layout().o("h_pad", 1.0).o("w_pad", 0.5)
6862            | runner()
6863    }
6864
6865    #[test]
6866    fn test_make_3d() {
6867        Mpl::new_3d([opt("elev", 50.0)])
6868            | runner()
6869    }
6870
6871    #[test]
6872    fn test_plot3() {
6873        Mpl::new_3d([])
6874            & plot3([0.0, 1.0], [0.0, 1.0], [0.0, 1.0]).o("marker", "D")
6875            | runner()
6876    }
6877
6878    #[test]
6879    fn test_plot3_data() {
6880        Mpl::new_3d([])
6881            & plot3_data([(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)]).o("marker", "D")
6882            | runner()
6883    }
6884
6885    #[test]
6886    fn test_plot3_eq() {
6887        assert_eq!(
6888            plot3([0.0, 1.0], [0.0, 1.0], [0.0, 1.0]).o("marker", "D"),
6889            plot3_data([(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)]).o("marker", "D"),
6890        )
6891    }
6892
6893    #[test]
6894    fn test_scatter3() {
6895        Mpl::new_3d([])
6896            & scatter3([0.0, 1.0], [0.0, 1.0], [0.0, 1.0]).o("marker", "D")
6897            | runner()
6898    }
6899
6900    #[test]
6901    fn test_scatter3_data() {
6902        Mpl::new_3d([])
6903            & scatter3_data([(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)]).o("marker", "D")
6904            | runner()
6905    }
6906
6907    #[test]
6908    fn test_scatter3_eq() {
6909        assert_eq!(
6910            scatter3([0.0, 1.0], [0.0, 1.0], [0.0, 1.0]).o("marker", "D"),
6911            scatter3_data([(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)]).o("marker", "D"),
6912        )
6913    }
6914
6915    #[test]
6916    fn test_quiver3() {
6917        Mpl::new_3d([])
6918            & quiver3(
6919                [0.0, 1.0],
6920                [0.0, 1.0],
6921                [0.0, 1.0],
6922                [1.0, 2.0],
6923                [1.0, 2.0],
6924                [1.0, 2.0],
6925            ).o("pivot", "middle")
6926            | runner()
6927    }
6928
6929    #[test]
6930    fn test_quiver3_data() {
6931        Mpl::new_3d([])
6932            & quiver3_data([
6933                (0.0, 0.0, 0.0, 1.0, 1.0, 1.0),
6934                (1.0, 1.0, 1.0, 2.0, 2.0, 2.0),
6935            ]).o("pivot", "middle")
6936            | runner()
6937    }
6938
6939    #[test]
6940    fn test_quiver3_triples() {
6941        Mpl::new_3d([])
6942            & quiver3_triples(
6943                [(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)],
6944                [(1.0, 1.0, 1.0), (2.0, 2.0, 2.0)],
6945            ).o("pivot", "middle")
6946            | runner()
6947    }
6948
6949    #[test]
6950    fn test_quiver3_eq() {
6951        let norm = quiver3(
6952            [0.0, 1.0],
6953            [0.0, 1.0],
6954            [0.0, 1.0],
6955            [1.0, 2.0],
6956            [1.0, 2.0],
6957            [1.0, 2.0],
6958        ).o("pivot", "middle");
6959        let data = quiver3_data([
6960            (0.0, 0.0, 0.0, 1.0, 1.0, 1.0),
6961            (1.0, 1.0, 1.0, 2.0, 2.0, 2.0),
6962        ]).o("pivot", "middle");
6963        let triples = quiver3_triples(
6964            [(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)],
6965            [(1.0, 1.0, 1.0), (2.0, 2.0, 2.0)],
6966        ).o("pivot", "middle");
6967        assert_eq!(norm, data);
6968        assert_eq!(norm, triples);
6969    }
6970
6971    #[test]
6972    fn test_surface() {
6973        Mpl::new_3d([])
6974            & surface(
6975                [[0.0, 1.0], [0.0, 1.0]],
6976                [[0.0, 0.0], [1.0, 1.0]],
6977                [[0.0, 1.0], [2.0, 3.0]],
6978            ).o("cmap", "rainbow")
6979            | runner()
6980    }
6981
6982    #[test]
6983    fn test_surface_data() {
6984        Mpl::new_3d([])
6985            & surface_data(
6986                [
6987                    (0.0, 0.0, 0.0),
6988                    (1.0, 0.0, 1.0),
6989                    (0.0, 1.0, 2.0),
6990                    (1.0, 1.0, 3.0),
6991                ],
6992                2,
6993            ).o("cmap", "rainbow")
6994            | runner()
6995    }
6996
6997    #[test]
6998    fn test_surface_flat() {
6999        Mpl::new_3d([])
7000            & surface_flat(
7001                [0.0, 1.0, 0.0, 1.0],
7002                [0.0, 0.0, 1.0, 1.0],
7003                [0.0, 1.0, 2.0, 3.0],
7004                2,
7005            ).o("cmap", "rainbow")
7006            | runner()
7007    }
7008
7009    #[test]
7010    fn test_surface_eq() {
7011        let norm = surface(
7012            [[0.0, 1.0], [0.0, 1.0]],
7013            [[0.0, 0.0], [1.0, 1.0]],
7014            [[0.0, 1.0], [2.0, 3.0]],
7015        ).o("cmap", "rainbow");
7016        let data = surface_data(
7017            [
7018                (0.0, 0.0, 0.0),
7019                (1.0, 0.0, 1.0),
7020                (0.0, 1.0, 2.0),
7021                (1.0, 1.0, 3.0),
7022            ],
7023            2,
7024        ).o("cmap", "rainbow");
7025        let flat = surface_flat(
7026            [0.0, 1.0, 0.0, 1.0],
7027            [0.0, 0.0, 1.0, 1.0],
7028            [0.0, 1.0, 2.0, 3.0],
7029            2,
7030        ).o("cmap", "rainbow");
7031        assert_eq!(norm, data);
7032        assert_eq!(norm, flat);
7033    }
7034
7035    #[test]
7036    fn test_trisurf() {
7037        Mpl::new_3d([])
7038            & trisurf(
7039                [0.0, 1.0, 0.0, 1.0],
7040                [0.0, 0.0, 1.0, 1.0],
7041                [0.0, 1.0, 2.0, 3.0],
7042            ).o("cmap", "rainbow")
7043            | runner()
7044    }
7045
7046    #[test]
7047    fn test_trisurf_data() {
7048        Mpl::new_3d([])
7049            & trisurf_data([
7050                (0.0, 0.0, 0.0),
7051                (1.0, 0.0, 1.0),
7052                (0.0, 1.0, 2.0),
7053                (1.0, 1.0, 3.0),
7054            ]).o("cmap", "rainbow")
7055            | runner()
7056    }
7057
7058    #[test]
7059    fn test_trisurf_eq() {
7060        let norm = trisurf(
7061            [0.0, 1.0, 0.0, 1.0],
7062            [0.0, 0.0, 1.0, 1.0],
7063            [0.0, 1.0, 2.0, 3.0],
7064        ).o("cmap", "rainbow");
7065        let data = trisurf_data([
7066            (0.0, 0.0, 0.0),
7067            (1.0, 0.0, 1.0),
7068            (0.0, 1.0, 2.0),
7069            (1.0, 1.0, 3.0),
7070        ]).o("cmap", "rainbow");
7071        assert_eq!(norm, data);
7072    }
7073
7074    #[test]
7075    fn test_view_init() {
7076        Mpl::new_3d([])
7077            & view_init(90.0, 0.0).o("roll", 45.0)
7078            | runner()
7079    }
7080
7081    #[test]
7082    fn test_zlabel() {
7083        Mpl::new_3d([])
7084            & zlabel("zlabel")
7085            | runner()
7086    }
7087
7088    #[test]
7089    fn test_zlim() {
7090        Mpl::new_3d([])
7091            & zlim(Some(-10.0), Some(15.0))
7092            | runner()
7093    }
7094
7095    #[test]
7096    fn test_zscale() {
7097        Mpl::new_3d([])
7098            & zscale(AxisScale::Log)
7099            | runner()
7100    }
7101
7102    #[test]
7103    fn test_zticklabels() {
7104        Mpl::new_3d([])
7105            & zticklabels([0.0, 1.0], ["zero", "one"]).o("minor", true)
7106            | runner()
7107    }
7108
7109    #[test]
7110    fn test_zticklabels_data() {
7111        Mpl::new_3d([])
7112            & zticklabels_data([(0.0, "zero"), (1.0, "one")]).o("minor", true)
7113            | runner()
7114    }
7115
7116    #[test]
7117    fn test_zticklabels_eq() {
7118        assert_eq!(
7119            zticklabels([0.0, 1.0], ["zero", "one"]).o("minor", true),
7120            zticklabels_data([(0.0, "zero"), (1.0, "one")]).o("minor", true),
7121        )
7122    }
7123
7124    #[test]
7125    fn test_zticks() {
7126        Mpl::new_3d([])
7127            & zticks([0.0, 1.0]).o("minor", true)
7128            | runner()
7129    }
7130}
7131