• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

morganjwilliams / pyrolite / 17569144994

09 Sep 2025 01:39AM UTC coverage: 90.14% (+0.06%) from 90.077%
17569144994

push

github

morganjwilliams
Update example for interactive plotting API

0 of 30 new or added lines in 2 files covered. (0.0%)

53 existing lines in 6 files now uncovered.

6226 of 6907 relevant lines covered (90.14%)

10.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.6
/pyrolite/plot/__init__.py
1
"""
2
Submodule with various plotting and visualisation functions.
3
"""
4

5
import warnings
12✔
6

7
import matplotlib
12✔
8
import matplotlib.pyplot as plt
12✔
9
import mpltern
12✔
10
import numpy as np
12✔
11
import pandas as pd
12✔
12

13
warnings.filterwarnings("ignore", "Unknown section")
12✔
14

15
from .. import geochem
12✔
16
from ..comp.codata import ILR, close
12✔
17
from ..util.distributions import get_scaler, sample_kde
12✔
18
from ..util.log import Handle
12✔
19
from ..util.meta import get_additional_params, subkwargs
12✔
20
from ..util.pd import to_frame, _check_components
12✔
21
from ..util.plot.axes import init_axes, label_axes
12✔
22
from ..util.plot.helpers import plot_cooccurence
12✔
23
from ..util.plot.style import linekwargs, scatterkwargs
12✔
24
from . import density, parallel, spider, stem
12✔
25
from .color import process_color
12✔
26

27
logger = Handle(__name__)
12✔
28

29
# pyroplot added to __all__ for docs
30
__all__ = ["density", "spider", "pyroplot"]
12✔
31

32

33
class pyroplot_matplotlib(object):
12✔
34
    def __init__(self, obj):
12✔
35
        """
36
        Custom dataframe accessor for pyrolite plotting.
37

38
        Notes
39
        -----
40
            This accessor enables the coexistence of array-based plotting functions and
41
            methods for pandas objects. This enables some separation of concerns.
42
        """
43
        self._validate(obj)
12✔
44
        self._obj = obj
12✔
45

46
    @staticmethod
12✔
47
    def _validate(obj):
12✔
48
        pass
12✔
49

50
    def cooccurence(self, ax=None, normalize=True, log=False, colorbar=False, **kwargs):
12✔
51
        """
52
        Plot the co-occurence frequency matrix for a given input.
53

54
        Parameters
55
        ----------
56
        ax : :class:`matplotlib.axes.Axes`, :code:`None`
57
            The subplot to draw on.
58
        normalize : :class:`bool`
59
            Whether to normalize the cooccurence to compare disparate variables.
60
        log : :class:`bool`
61
            Whether to take the log of the cooccurence.
62
        colorbar : :class:`bool`
63
            Whether to append a colorbar.
64

65
        Returns
66
        -------
67
        :class:`matplotlib.axes.Axes`
68
            Axes on which the cooccurence plot is added.
69

70
        """
71
        obj = to_frame(self._obj)
12✔
72
        ax = plot_cooccurence(
12✔
73
            obj.values, ax=ax, normalize=normalize, log=log, colorbar=colorbar, **kwargs
74
        )
75
        ax.set_xticklabels(obj.columns, minor=False, rotation=90)
12✔
76
        ax.set_yticklabels(obj.columns, minor=False)
12✔
77
        return ax
12✔
78

79
    def density(self, components: list = None, ax=None, axlabels=True, **kwargs):
12✔
80
        r"""
81
        Method for plotting histograms (mode='hist2d'|'hexbin') or kernel density
82
        esitimates from point data. Convenience access function to
83
        :func:`~pyrolite.plot.density.density` (see `Other Parameters`, below), where
84
        further parameters for relevant `matplotlib` functions are also listed.
85

86
        Parameters
87
        ----------
88
        components : :class:`list`, :code:`None`
89
            Elements or compositional components to plot.
90
        ax : :class:`matplotlib.axes.Axes`, :code:`None`
91
            The subplot to draw on.
92
        axlabels : :class:`bool`, True
93
            Whether to add x-y axis labels.
94

95
        {otherparams}
96

97
        Returns
98
        -------
99
        :class:`matplotlib.axes.Axes`
100
            Axes on which the density diagram is plotted.
101

102
        """
103
        obj = to_frame(self._obj)
12✔
104
        components = _check_components(obj, components=components)
12✔
105

106
        ax = density.density(
12✔
107
            obj.reindex(columns=components).astype(float).values, ax=ax, **kwargs
108
        )
109
        if axlabels:
12✔
110
            label_axes(ax, labels=components)
12✔
111

112
        return ax
12✔
113

114
    def heatscatter(
12✔
115
        self,
116
        components: list = None,
117
        ax=None,
118
        axlabels=True,
119
        logx=False,
120
        logy=False,
121
        **kwargs,
122
    ):
123
        r"""
124
        Heatmapped scatter plots using the pyroplot API. See further parameters
125
        for `matplotlib.pyplot.scatter` function below.
126

127
        Parameters
128
        ----------
129
        components : :class:`list`, :code:`None`
130
            Elements or compositional components to plot.
131
        ax : :class:`matplotlib.axes.Axes`, :code:`None`
132
            The subplot to draw on.
133
        axlabels : :class:`bool`, :code:`True`
134
            Whether to add x-y axis labels.
135
        logx : :class:`bool`, `False`
136
            Whether to log-transform x values before the KDE for bivariate plots.
137
        logy : :class:`bool`, `False`
138
            Whether to log-transform y values before the KDE for bivariate plots.
139

140
        {otherparams}
141

142
        Returns
143
        -------
144
        :class:`matplotlib.axes.Axes`
145
            Axes on which the heatmapped scatterplot is added.
146

147
        """
148
        obj = to_frame(self._obj)
12✔
149
        components = _check_components(obj, components=components)
12✔
150
        data, samples = (
12✔
151
            obj.reindex(columns=components).values,
152
            obj.reindex(columns=components).values,
153
        )
154
        kdetfm = [  # log transforms
12✔
155
            get_scaler([None, np.log][logx], [None, np.log][logy]),
156
            lambda x: ILR(close(x)),
157
        ][len(components) == 3]
158
        zi = sample_kde(
12✔
159
            data, samples, transform=kdetfm, **subkwargs(kwargs, sample_kde)
160
        )
161
        kwargs.update({"c": zi})
12✔
162
        ax = obj.reindex(columns=components).pyroplot.scatter(
12✔
163
            ax=ax, axlabels=axlabels, **kwargs
164
        )
165
        return ax
12✔
166

167
    def parallel(
12✔
168
        self,
169
        components=None,
170
        rescale=False,
171
        legend=False,
172
        ax=None,
173
        **kwargs,
174
    ):
175
        """
176
        Create a :func:`pyrolite.plot.parallel.parallel`. coordinate plot from
177
        the columns of the :class:`~pandas.DataFrame`.
178

179
        Parameters
180
        ----------
181
        components : :class:`list`, :code:`None`
182
            Components to use as axes for the plot.
183
        rescale : :class:`bool`
184
            Whether to rescale values to [-1, 1].
185
        legend : :class:`bool`, :code:`False`
186
            Whether to include or suppress the legend.
187
        ax : :class:`matplotlib.axes.Axes`, :code:`None`
188
            The subplot to draw on.
189
        {otherparams}
190

191
        Returns
192
        -------
193
        :class:`matplotlib.axes.Axes`
194
            Axes on which the parallel coordinates plot is added.
195

196
        Todo
197
        ----
198
        * Adapt figure size based on number of columns.
199

200
        """
201

202
        obj = to_frame(self._obj)
12✔
203
        ax = parallel.parallel(
12✔
204
            obj,
205
            components=components,
206
            rescale=rescale,
207
            legend=legend,
208
            ax=ax,
209
            **kwargs,
210
        )
211
        return ax
12✔
212

213
    def plot(self, components: list = None, ax=None, axlabels=True, **kwargs):
12✔
214
        r"""
215
        Convenience method for line plots using the pyroplot API. See
216
        further parameters for `matplotlib.pyplot.scatter` function below.
217

218
        Parameters
219
        ----------
220
        components : :class:`list`, :code:`None`
221
            Elements or compositional components to plot.
222
        ax : :class:`matplotlib.axes.Axes`, :code:`None`
223
            The subplot to draw on.
224
        axlabels : :class:`bool`, :code:`True`
225
            Whether to add x-y axis labels.
226
        {otherparams}
227

228
        Returns
229
        -------
230
        :class:`matplotlib.axes.Axes`
231
            Axes on which the plot is added.
232

233
        """
UNCOV
234
        obj = to_frame(self._obj)
×
UNCOV
235
        components = _check_components(obj, components=components)
×
UNCOV
236
        projection = [None, "ternary"][len(components) == 3]
×
237
        ax = init_axes(ax=ax, projection=projection, **kwargs)
×
238
        kw = linekwargs(kwargs)
×
239
        ax.plot(*obj.reindex(columns=components).values.T, **kw)
×
240
        # if color is multi, could update line colors here
241
        if axlabels:
×
242
            label_axes(ax, labels=components)
×
243

244
        ax.tick_params("both")
×
245
        # ax.grid()
246
        # ax.set_aspect("equal")
247
        return ax
×
248

249
    def REE(
12✔
250
        self,
251
        index="elements",
252
        ax=None,
253
        mode="plot",
254
        dropPm=True,
255
        scatter_kw={},
256
        line_kw={},
257
        **kwargs,
258
    ):
259
        """Pass the pandas object to :func:`pyrolite.plot.spider.REE_v_radii`.
260

261
        Parameters
262
        ----------
263
        ax : :class:`matplotlib.axes.Axes`, :code:`None`
264
            The subplot to draw on.
265
        index : :class:`str`
266
            Whether to plot radii ('radii') on the principal x-axis, or elements
267
            ('elements').
268
        mode : :class:`str`, :code`["plot", "fill", "binkde", "ckde", "kde", "hist"]`
269
            Mode for plot. Plot will produce a line-scatter diagram. Fill will return
270
            a filled range. Density will return a conditional density diagram.
271
        dropPm : :class:`bool`
272
            Whether to exclude the (almost) non-existent element Promethium from the REE
273
            list.
274
        scatter_kw : :class:`dict`
275
            Keyword parameters to be passed to the scatter plotting function.
276
        line_kw : :class:`dict`
277
            Keyword parameters to be passed to the line plotting function.
278
        {otherparams}
279

280
        Returns
281
        -------
282
        :class:`matplotlib.axes.Axes`
283
            Axes on which the REE plot is added.
284

285
        """
286
        obj = to_frame(self._obj)
12✔
287
        ree = [i for i in geochem.ind.REE(dropPm=dropPm) if i in obj.columns]
12✔
288

289
        ax = spider.REE_v_radii(
12✔
290
            obj.reindex(columns=ree).astype(float).values,
291
            index=index,
292
            ree=ree,
293
            mode=mode,
294
            ax=ax,
295
            scatter_kw=scatter_kw,
296
            line_kw=line_kw,
297
            **kwargs,
298
        )
299
        ax.set_ylabel(r"$\mathrm{X / X_{Reference}}$")
12✔
300
        return ax
12✔
301

302
    def scatter(self, components: list = None, ax=None, axlabels=True, **kwargs):
12✔
303
        r"""
304
        Convenience method for scatter plots using the pyroplot API. See
305
        further parameters for `matplotlib.pyplot.scatter` function below.
306

307
        Parameters
308
        ----------
309
        components : :class:`list`, :code:`None`
310
            Elements or compositional components to plot.
311
        ax : :class:`matplotlib.axes.Axes`, :code:`None`
312
            The subplot to draw on.
313
        axlabels : :class:`bool`, :code:`True`
314
            Whether to add x-y axis labels.
315
        {otherparams}
316

317
        Returns
318
        -------
319
        :class:`matplotlib.axes.Axes`
320
            Axes on which the scatterplot is added.
321

322
        """
323
        obj = to_frame(self._obj)
12✔
324
        components = _check_components(obj, components=components)
12✔
325

326
        projection = [None, "ternary"][len(components) == 3]
12✔
327
        ax = init_axes(ax=ax, projection=projection, **kwargs)
12✔
328
        size = obj.index.size
12✔
329
        kw = process_color(size=size, **kwargs)
12✔
330
        with warnings.catch_warnings():
12✔
331
            # ternary transform where points add to zero will give an unnecessary
332
            # warning; here we supress it
333
            warnings.filterwarnings(
12✔
334
                "ignore", message="invalid value encountered in divide"
335
            )
336
            ax.scatter(*obj.reindex(columns=components).values.T, **scatterkwargs(kw))
12✔
337

338
        if axlabels:
12✔
339
            label_axes(ax, labels=components)
12✔
340

341
        ax.tick_params("both")
12✔
342
        # ax.grid()
343
        # ax.set_aspect("equal")
344
        return ax
12✔
345

346
    def spider(
12✔
347
        self,
348
        components: list = None,
349
        indexes: list = None,
350
        ax=None,
351
        mode="plot",
352
        index_order=None,
353
        autoscale=True,
354
        scatter_kw={},
355
        line_kw={},
356
        **kwargs,
357
    ):
358
        r"""
359
        Method for spider plots. Convenience access function to
360
        :func:`~pyrolite.plot.spider.spider` (see `Other Parameters`, below), where
361
        further parameters for relevant `matplotlib` functions are also listed.
362

363
        Parameters
364
        ----------
365
        components : :class:`list`, `None`
366
            Elements or compositional components to plot.
367
        indexes :  :class:`list`, `None`
368
            Elements or compositional components to plot.
369
        ax : :class:`matplotlib.axes.Axes`, :code:`None`
370
            The subplot to draw on.
371
        index_order
372
            Function to order spider plot indexes (e.g. by incompatibility).
373
        autoscale : :class:`bool`
374
            Whether to autoscale the y-axis limits for standard spider plots.
375
        mode : :class:`str`, :code`["plot", "fill", "binkde", "ckde", "kde", "hist"]`
376
            Mode for plot. Plot will produce a line-scatter diagram. Fill will return
377
            a filled range. Density will return a conditional density diagram.
378
        scatter_kw : :class:`dict`
379
            Keyword parameters to be passed to the scatter plotting function.
380
        line_kw : :class:`dict`
381
            Keyword parameters to be passed to the line plotting function.
382
        {otherparams}
383

384
        Returns
385
        -------
386
        :class:`matplotlib.axes.Axes`
387
            Axes on which the spider diagram is plotted.
388

389
        Todo
390
        ----
391
            * Add 'compositional data' filter for default components if None is given
392

393
        """
394
        obj = to_frame(self._obj)
12✔
395

396
        if components is None:  # default to plotting elemental data
12✔
397
            components = [
12✔
398
                el for el in obj.columns if el in geochem.ind.common_elements()
399
            ]
400

401
        assert len(components) != 0
12✔
402

403
        if index_order is not None:
12✔
UNCOV
404
            if isinstance(index_order, str):
×
UNCOV
405
                try:
×
UNCOV
406
                    index_order = geochem.ind.ordering[index_order]
×
407
                except KeyError:
×
408
                    msg = (
×
409
                        "Ordering not applied, as parameter '{}' not recognized."
410
                        " Select from: {}"
411
                    ).format(index_order, ", ".join(list(geochem.ind.ordering.keys())))
UNCOV
412
                    logger.warning(msg)
×
UNCOV
413
                components = index_order(components)
×
414
            else:
415
                components = index_order(components)
×
416

417
        ax = init_axes(ax=ax, **kwargs)
12✔
418

419
        if hasattr(ax, "_pyrolite_components"):
12✔
420
            # TODO: handle spider diagrams which have specified components
421
            pass
422

423
        ax = spider.spider(
12✔
424
            obj.reindex(columns=components).astype(float).values,
425
            indexes=indexes,
426
            ax=ax,
427
            mode=mode,
428
            autoscale=autoscale,
429
            scatter_kw=scatter_kw,
430
            line_kw=line_kw,
431
            **kwargs,
432
        )
433
        ax._pyrolite_components = components
12✔
434
        ax.set_xticklabels(components, rotation=60)
12✔
435
        return ax
12✔
436

437
    def stem(
12✔
438
        self,
439
        components: list = None,
440
        ax=None,
441
        orientation="horizontal",
442
        axlabels=True,
443
        **kwargs,
444
    ):
445
        r"""
446
        Method for creating stem plots. Convenience access function to
447
        :func:`~pyrolite.plot.stem.stem` (see `Other Parameters`, below), where
448
        further parameters for relevant `matplotlib` functions are also listed.
449

450
        Parameters
451
        ----------
452
        components : :class:`list`, :code:`None`
453
            Elements or compositional components to plot.
454
        ax : :class:`matplotlib.axes.Axes`, :code:`None`
455
            The subplot to draw on.
456
        orientation : :class:`str`
457
            Orientation of the plot (horizontal or vertical).
458
        axlabels : :class:`bool`, True
459
            Whether to add x-y axis labels.
460
        {otherparams}
461

462
        Returns
463
        -------
464
        :class:`matplotlib.axes.Axes`
465
            Axes on which the stem diagram is plotted.
466
        """
467
        obj = to_frame(self._obj)
12✔
468
        components = _check_components(obj, components=components, valid_sizes=[2])
12✔
469

470
        ax = stem.stem(
12✔
471
            *obj.reindex(columns=components).values.T,
472
            ax=ax,
473
            orientation=orientation,
474
            **process_color(**kwargs),
475
        )
476

477
        if axlabels:
12✔
478
            if "h" not in orientation.lower():
12✔
479
                components = components[::-1]
12✔
480
            label_axes(ax, labels=components)
12✔
481

482
        return ax
12✔
483

484

485
pyroplot = pyroplot_matplotlib
12✔
486

487

488
# note that only some of these methods will be valid for series
489
pd.api.extensions.register_series_accessor("pyroplot")(pyroplot)
12✔
490
pd.api.extensions.register_dataframe_accessor("pyroplot")(pyroplot)
12✔
491

492

493
# ideally we would i) check for the same params and ii) aggregate all others across
494
# inherited or chained functions. This simply imports the params from another docstring
495
_add_additional_parameters = True
12✔
496

497
pyroplot.density.__doc__ = pyroplot.density.__doc__.format(
12✔
498
    otherparams=[
499
        "",
500
        get_additional_params(
501
            pyroplot.density,
502
            density.density,
503
            header="Other Parameters",
504
            indent=8,
505
            subsections=True,
506
        ),
507
    ][_add_additional_parameters]
508
)
509

510
pyroplot.parallel.__doc__ = pyroplot.parallel.__doc__.format(
12✔
511
    otherparams=[
512
        "",
513
        get_additional_params(
514
            pyroplot.parallel,
515
            parallel.parallel,
516
            header="Other Parameters",
517
            indent=8,
518
            subsections=True,
519
        ),
520
    ][_add_additional_parameters]
521
)
522

523

524
pyroplot.REE.__doc__ = pyroplot.REE.__doc__.format(
12✔
525
    otherparams=[
526
        "",
527
        get_additional_params(
528
            pyroplot.REE,
529
            spider.REE_v_radii,
530
            header="Other Parameters",
531
            indent=8,
532
            subsections=True,
533
        ),
534
    ][_add_additional_parameters]
535
)
536

537

538
pyroplot.scatter.__doc__ = pyroplot.scatter.__doc__.format(
12✔
539
    otherparams=[
540
        "",
541
        get_additional_params(
542
            pyroplot.scatter,
543
            plt.scatter,
544
            header="Other Parameters",
545
            indent=8,
546
            subsections=True,
547
        ),
548
    ][_add_additional_parameters]
549
)
550

551
pyroplot.plot.__doc__ = pyroplot.plot.__doc__.format(
12✔
552
    otherparams=[
553
        "",
554
        get_additional_params(
555
            pyroplot.plot,
556
            plt.plot,
557
            header="Other Parameters",
558
            indent=8,
559
            subsections=True,
560
        ),
561
    ][_add_additional_parameters]
562
)
563

564
pyroplot.spider.__doc__ = pyroplot.spider.__doc__.format(
12✔
565
    otherparams=[
566
        "",
567
        get_additional_params(
568
            pyroplot.spider,
569
            spider.spider,
570
            header="Other Parameters",
571
            indent=8,
572
            subsections=True,
573
        ),
574
    ][_add_additional_parameters]
575
)
576

577

578
pyroplot.stem.__doc__ = pyroplot.stem.__doc__.format(
12✔
579
    otherparams=[
580
        "",
581
        get_additional_params(
582
            pyroplot.stem,
583
            stem.stem,
584
            header="Other Parameters",
585
            indent=8,
586
            subsections=True,
587
        ),
588
    ][_add_additional_parameters]
589
)
590

591
pyroplot.heatscatter.__doc__ = pyroplot.heatscatter.__doc__.format(
12✔
592
    otherparams=[
593
        "",
594
        get_additional_params(
595
            pyroplot.scatter, header="Other Parameters", indent=8, subsections=True
596
        ),
597
    ][_add_additional_parameters]
598
)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc