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