• 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

97.4
/pyrolite/util/plot/style.py
1
"""
2
Functions for automated plot styling and argument handling.
3

4
Attributes
5
----------
6
DEFAULT_CONT_COLORMAP : :class:`matplotlib.colors.ScalarMappable`
7
    Default continuous colormap.
8
DEFAULT_DISC_COLORMAP : :class:`matplotlib.colors.ScalarMappable`
9
    Default discrete colormap.
10
"""
11

12
import itertools
12✔
13
from pathlib import Path
12✔
14

15
import matplotlib
12✔
16
import matplotlib.axes
12✔
17
import matplotlib.collections
12✔
18
import matplotlib.colors
12✔
19
import matplotlib.lines
12✔
20
import matplotlib.patches
12✔
21
import matplotlib.pyplot as plt
12✔
22
import numpy as np
12✔
23
import pandas as pd
12✔
24
from matplotlib.legend_handler import HandlerTuple
12✔
25

26
from ...comp.codata import close
12✔
27
from ..general import copy_file
12✔
28
from ..log import Handle
12✔
29
from ..meta import pyrolite_datafolder, subkwargs
12✔
30
from .helpers import get_centroid
12✔
31
from .transform import xy_to_tlr
12✔
32

33
logger = Handle(__name__)
12✔
34

35
DEFAULT_CONT_COLORMAP = plt.cm.viridis
12✔
36
DEFAULT_DISC_COLORMAP = plt.cm.tab10
12✔
37

38

39
def _export_mplstyle(
12✔
40
    src=pyrolite_datafolder("_config") / "pyrolite.mplstyle", refresh=False
41
):
42
    """
43
    Export a matplotlib style file to the matplotlib style library such that one can
44
    use e.g. `matplotlib.style.use("pyrolite")`.
45

46
    Parameters
47
    -----------
48
    src : :class:`str` | :class:`pathlib.Path`
49
        File path for the style file to be exported.
50
    refresh : :class:`bool`
51
        Whether to re-export a style file (e.g. after updating) even if it
52
        already exists in the matplotlib style libary.
53
    """
54
    src_fn = Path(src)
12✔
55
    dest_dir = Path(matplotlib.get_configdir()) / "stylelib"
12✔
56
    dest_fn = dest_dir / src.name
12✔
57
    if (not dest_fn.exists()) or refresh:
12✔
58
        logger.debug("Exporting pyrolite.mplstyle to matplotlib config folder.")
12✔
59
        if not dest_dir.exists():
12✔
60
            dest_dir.mkdir(parents=True)
12✔
61
        copy_file(src_fn, dest_dir)  # copy to the destination DIR
12✔
62
        logger.debug("Reloading matplotlib")
12✔
63
    matplotlib.style.reload_library()  # needed to load in pyrolite style NOW
12✔
64

65

66
_export_mplstyle()
12✔
67
matplotlib.style.use("pyrolite")
12✔
68

69

70
def linekwargs(kwargs):
12✔
71
    """
72
    Get a subset of keyword arguments to pass to a matplotlib line-plot call.
73

74
    Parameters
75
    -----------
76
    kwargs : :class:`dict`
77
        Dictionary of keyword arguments to subset.
78

79
    Returns
80
    --------
81
    :class:`dict`
82
    """
83
    kw = subkwargs(
12✔
84
        kwargs,
85
        plt.plot,
86
        matplotlib.axes.Axes.plot,
87
        matplotlib.lines.Line2D,
88
        matplotlib.collections.Collection,
89
    )
90
    # could trim cmap and norm here, in case they get passed accidentally
91
    kw.update(
12✔
92
        **dict(
93
            alpha=kwargs.get("alpha"),
94
            label=kwargs.get("label"),
95
            clip_on=kwargs.get("clip_on", True),
96
        )
97
    )  # issues with introspection for alpha
98
    return kw
12✔
99

100

101
def scatterkwargs(kwargs):
12✔
102
    """
103
    Get a subset of keyword arguments to pass to a matplotlib scatter call.
104

105
    Parameters
106
    -----------
107
    kwargs : :class:`dict`
108
        Dictionary of keyword arguments to subset.
109

110
    Returns
111
    --------
112
    :class:`dict`
113
    """
114
    kw = subkwargs(
12✔
115
        kwargs,
116
        plt.scatter,
117
        matplotlib.axes.Axes.scatter,
118
        matplotlib.collections.Collection,
119
    )
120
    kw.update(
12✔
121
        **dict(
122
            alpha=kwargs.get("alpha"),
123
            label=kwargs.get("label"),
124
            clip_on=kwargs.get("clip_on", True),
125
        )
126
    )  # issues with introspection for alpha
127
    return kw
12✔
128

129

130
def patchkwargs(kwargs):
12✔
131
    kw = subkwargs(
12✔
132
        kwargs,
133
        matplotlib.axes.Axes.fill_between,
134
        matplotlib.collections.PolyCollection,
135
        matplotlib.patches.Patch,
136
    )
137
    kw.update(
12✔
138
        **dict(
139
            alpha=kwargs.get("alpha"),
140
            label=kwargs.get("label"),
141
            clip_on=kwargs.get("clip_on", True),
142
        )
143
    )  # issues with introspection for alpha
144
    return kw
12✔
145

146

147
def _mpl_sp_kw_split(kwargs):
12✔
148
    """
149
    Process keyword arguments supplied to a matplotlib plot function.
150

151
    Returns
152
    --------
153
    :class:`tuple` ( :class:`dict`, :class:`dict` )
154
    """
155
    sctr_kwargs = scatterkwargs(kwargs)
12✔
156
    # c kwarg is first priority, if it isn't present, use the color arg
157
    if sctr_kwargs.get("c") is None:
12✔
UNCOV
158
        sctr_kwargs = {**sctr_kwargs, **{"c": kwargs.get("color")}}
×
159

160
    line_kwargs = linekwargs(kwargs)
12✔
161
    return sctr_kwargs, line_kwargs
12✔
162

163

164
def marker_cycle(markers=["D", "s", "o", "+", "*"]):
12✔
165
    """
166
    Cycle through a set of markers.
167

168
    Parameters
169
    ----------
170
    markers : :class:`list`
171
        List of markers to provide to matplotlib.
172
    """
173
    return itertools.cycle(markers)
12✔
174

175

176
def mappable_from_values(values, cmap=DEFAULT_CONT_COLORMAP, norm=None, **kwargs):
12✔
177
    """
178
    Create a scalar mappable object from an array of values.
179

180
    Returns
181
    -------
182
    :class:`matplotlib.cm.ScalarMappable`
183
    """
184
    if isinstance(values, pd.Series):
12✔
185
        values = values.values
12✔
186
    sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm)
12✔
187
    sm.set_array(values[np.isfinite(values)])
12✔
188
    return sm
12✔
189

190

191
def ternary_color(
12✔
192
    tlr,
193
    alpha=1.0,
194
    colors=([1, 0, 0], [0, 1, 0], [0, 0, 1]),
195
    coefficients=(1, 1, 1),
196
):
197
    """
198
    Color a set of points by their ternary combinations of three specified colors.
199

200
    Parameters
201
    ----------
202
    tlr : :class:`numpy.ndarray`
203

204
    alpha : :class:`float`
205
        Alpha coefficient for the colors; to be applied *multiplicatively* with
206
        any existing alpha value (for RGBA colors specified).
207
    colors : :class:`tuple`
208
        Set of colours corresponding to the top, left and right verticies,
209
        respectively.
210
    coefficients : :class:`tuple`
211
        Coefficients for the ternary data to adjust the centre.
212

213
    Returns
214
    -------
215
    :class:`numpy.ndarray`
216
        Color array for the ternary points.
217
    """
218
    colors = np.array([matplotlib.colors.to_rgba(c) for c in colors], dtype=float)
12✔
219
    _tlr = close(np.array(tlr) * np.array(coefficients))
12✔
220
    color = np.atleast_2d(_tlr @ colors)
12✔
221
    color[:, -1] *= alpha * (1 - 10e-7)  # avoid 'greater than 1' issues
12✔
222
    return color
12✔
223

224

225
def color_ternary_polygons_by_centroid(
12✔
226
    ax=None,
227
    patches=None,
228
    alpha=1.0,
229
    colors=([1, 0, 0], [0, 1, 0], [0, 0, 1]),
230
    coefficients=(1, 1, 1),
231
):
232
    """
233
    Color a set of polygons within a ternary diagram by their centroid colors.
234

235
    Parameters
236
    ----------
237
    ax : :class:`matplotlib.axes.Axes`
238
        Ternary axes to check for patches, if patches is not supplied.
239
    patches : :class:`list`
240
        List of ternary-hosted patches to apply color to.
241
    alpha : :class:`float`
242
        Alpha coefficient for the colors; to be applied *multiplicatively* with
243
        any existing alpha value (for RGBA colors specified).
244
    colors : :class:`tuple`
245
        Set of colours corresponding to the top, left and right verticies,
246
        respectively.
247
    coefficients : :class:`tuple`
248
        Coefficients for the ternary data to adjust the centre.
249

250
    Returns
251
    -------
252
    patches : :class:`list`
253
        List of patches, with updated facecolors.
254
    """
255

256
    if patches is None:
12✔
257
        if ax is None:
12✔
UNCOV
258
            raise NotImplementedError("Either an axis or patches need to be supplied.")
×
259
        patches = ax.patches
12✔
260

261
    for poly in patches:
12✔
262
        xy = get_centroid(poly)
12✔
263
        tlr = xy_to_tlr(np.array([xy]))[0]
12✔
264
        color = ternary_color(
12✔
265
            tlr, alpha=alpha, colors=colors, coefficients=coefficients
266
        )
267
        poly.set_facecolor(color)
12✔
268

269
    return patches
12✔
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