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