mgplot 0.2.2__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mgplot-0.2.2 → mgplot-0.2.3}/CHANGELOG.md +7 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/PKG-INFO +1 -1
- {mgplot-0.2.2 → mgplot-0.2.3}/docs/mgplot.html +1 -1
- {mgplot-0.2.2 → mgplot-0.2.3}/pyproject.toml +1 -1
- {mgplot-0.2.2 → mgplot-0.2.3}/src/mgplot/__init__.py +27 -29
- {mgplot-0.2.2 → mgplot-0.2.3}/src/mgplot/axis_utils.py +25 -35
- {mgplot-0.2.2 → mgplot-0.2.3}/src/mgplot/bar_plot.py +21 -28
- {mgplot-0.2.2 → mgplot-0.2.3}/src/mgplot/colors.py +9 -20
- {mgplot-0.2.2 → mgplot-0.2.3}/src/mgplot/finalise_plot.py +15 -24
- {mgplot-0.2.2 → mgplot-0.2.3}/src/mgplot/finalisers.py +27 -44
- {mgplot-0.2.2 → mgplot-0.2.3}/src/mgplot/growth_plot.py +21 -25
- {mgplot-0.2.2 → mgplot-0.2.3}/src/mgplot/keyword_checking.py +46 -56
- {mgplot-0.2.2 → mgplot-0.2.3}/src/mgplot/line_plot.py +17 -20
- {mgplot-0.2.2 → mgplot-0.2.3}/src/mgplot/lint-all.sh +4 -1
- {mgplot-0.2.2 → mgplot-0.2.3}/src/mgplot/multi_plot.py +35 -36
- {mgplot-0.2.2 → mgplot-0.2.3}/src/mgplot/postcovid_plot.py +16 -21
- {mgplot-0.2.2 → mgplot-0.2.3}/src/mgplot/revision_plot.py +8 -12
- {mgplot-0.2.2 → mgplot-0.2.3}/src/mgplot/run_plot.py +18 -17
- {mgplot-0.2.2 → mgplot-0.2.3}/src/mgplot/seastrend_plot.py +8 -11
- {mgplot-0.2.2 → mgplot-0.2.3}/src/mgplot/settings.py +11 -18
- {mgplot-0.2.2 → mgplot-0.2.3}/src/mgplot/summary_plot.py +22 -23
- {mgplot-0.2.2 → mgplot-0.2.3}/src/mgplot/utilities.py +27 -36
- {mgplot-0.2.2 → mgplot-0.2.3}/uv.lock +1 -1
- {mgplot-0.2.2 → mgplot-0.2.3}/.gitignore +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/.pylintrc +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/LICENSE +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/README.md +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/build-docs.sh +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/build-test.sh +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/docs/index.html +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/docs/mgplot/bar_plot.html +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/docs/mgplot/finalise_plot.html +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/docs/mgplot/growth_plot.html +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/docs/mgplot/line_plot.html +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/docs/mgplot/postcovid_plot.html +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/docs/mgplot/revision_plot.html +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/docs/mgplot/run_plot.html +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/docs/mgplot/seastrend_plot.html +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/docs/mgplot/summary_plot.html +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/docs/search.js +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/src/mgplot/py.typed +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/test/test.ipynb +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/test/zz-test-data/ocr_rba.csv +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/test/zz-test-data/revisions.csv +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/test/zz-test-data/summary.csv +0 -0
- {mgplot-0.2.2 → mgplot-0.2.3}/uv-upgrade.sh +0 -0
|
@@ -649,7 +649,7 @@ with timeseries data that is indexed with a PeriodIndex.</p>
|
|
|
649
649
|
<section id="__version__">
|
|
650
650
|
<div class="attr variable">
|
|
651
651
|
<span class="name">__version__</span> =
|
|
652
|
-
<span class="default_value">'0.2.
|
|
652
|
+
<span class="default_value">'0.2.2'</span>
|
|
653
653
|
|
|
654
654
|
|
|
655
655
|
</div>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
mgplot
|
|
1
|
+
"""mgplot
|
|
3
2
|
------
|
|
4
3
|
|
|
5
4
|
Package to provide a frontend to matplotlib for working
|
|
@@ -11,49 +10,48 @@ import importlib.metadata
|
|
|
11
10
|
|
|
12
11
|
# --- local imports
|
|
13
12
|
# Do not import the utilities, axis_utils nor keyword_checking modules here.
|
|
14
|
-
from mgplot.bar_plot import
|
|
15
|
-
from mgplot.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
series_growth_plot,
|
|
24
|
-
SeriesGrowthKwargs,
|
|
25
|
-
calc_growth,
|
|
13
|
+
from mgplot.bar_plot import BarKwargs, bar_plot
|
|
14
|
+
from mgplot.colors import (
|
|
15
|
+
abbreviate_state,
|
|
16
|
+
colorise_list,
|
|
17
|
+
contrast,
|
|
18
|
+
get_color,
|
|
19
|
+
get_party_palette,
|
|
20
|
+
state_abbrs,
|
|
21
|
+
state_names,
|
|
26
22
|
)
|
|
27
|
-
from mgplot.
|
|
28
|
-
from mgplot.multi_plot import plot_then_finalise, multi_start, multi_column
|
|
23
|
+
from mgplot.finalise_plot import FinaliseKwargs, finalise_plot
|
|
29
24
|
from mgplot.finalisers import (
|
|
30
25
|
bar_plot_finalise,
|
|
26
|
+
growth_plot_finalise,
|
|
31
27
|
line_plot_finalise,
|
|
32
28
|
postcovid_plot_finalise,
|
|
33
|
-
growth_plot_finalise,
|
|
34
29
|
revision_plot_finalise,
|
|
35
30
|
run_plot_finalise,
|
|
36
31
|
seastrend_plot_finalise,
|
|
37
32
|
series_growth_plot_finalise,
|
|
38
33
|
summary_plot_finalise,
|
|
39
34
|
)
|
|
40
|
-
from mgplot.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
abbreviate_state,
|
|
47
|
-
state_names,
|
|
48
|
-
state_abbrs,
|
|
35
|
+
from mgplot.growth_plot import (
|
|
36
|
+
GrowthKwargs,
|
|
37
|
+
SeriesGrowthKwargs,
|
|
38
|
+
calc_growth,
|
|
39
|
+
growth_plot,
|
|
40
|
+
series_growth_plot,
|
|
49
41
|
)
|
|
42
|
+
from mgplot.line_plot import LineKwargs, line_plot
|
|
43
|
+
from mgplot.multi_plot import multi_column, multi_start, plot_then_finalise
|
|
44
|
+
from mgplot.postcovid_plot import PostcovidKwargs, postcovid_plot
|
|
45
|
+
from mgplot.revision_plot import revision_plot
|
|
46
|
+
from mgplot.run_plot import RunKwargs, run_plot
|
|
47
|
+
from mgplot.seastrend_plot import seastrend_plot
|
|
50
48
|
from mgplot.settings import (
|
|
49
|
+
clear_chart_dir,
|
|
51
50
|
get_setting,
|
|
52
|
-
set_setting,
|
|
53
51
|
set_chart_dir,
|
|
54
|
-
|
|
52
|
+
set_setting,
|
|
55
53
|
)
|
|
56
|
-
|
|
54
|
+
from mgplot.summary_plot import SummaryKwargs, summary_plot
|
|
57
55
|
|
|
58
56
|
# --- version and author
|
|
59
57
|
try:
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
axis_utils.py
|
|
1
|
+
"""axis_utils.py
|
|
3
2
|
|
|
4
3
|
This module contains functions to work with categorical
|
|
5
4
|
axis in Matplotlib, specifically:
|
|
@@ -10,21 +9,20 @@ axis in Matplotlib, specifically:
|
|
|
10
9
|
|
|
11
10
|
import calendar
|
|
12
11
|
from enum import Enum
|
|
13
|
-
|
|
14
|
-
from pandas.api.types import is_integer_dtype, is_string_dtype
|
|
12
|
+
|
|
15
13
|
from matplotlib.pyplot import Axes
|
|
14
|
+
from pandas import Period, PeriodIndex, RangeIndex, period_range
|
|
15
|
+
from pandas.api.types import is_integer_dtype, is_string_dtype
|
|
16
16
|
|
|
17
17
|
from mgplot.settings import DataT
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def is_categorical(data: DataT) -> bool:
|
|
21
|
-
"""
|
|
22
|
-
Check if the data.index is usefully categorical
|
|
21
|
+
"""Check if the data.index is usefully categorical
|
|
23
22
|
(index needs to be complete, and unique).
|
|
24
23
|
|
|
25
24
|
Note: we plot categoricals using bar plots.
|
|
26
25
|
"""
|
|
27
|
-
|
|
28
26
|
if data.index.has_duplicates or data.index.hasnans or data.index.empty:
|
|
29
27
|
return False
|
|
30
28
|
if is_string_dtype(data.index.dtype):
|
|
@@ -44,9 +42,7 @@ def is_categorical(data: DataT) -> bool:
|
|
|
44
42
|
|
|
45
43
|
|
|
46
44
|
def map_periodindex(data: DataT) -> None | tuple[DataT, PeriodIndex]:
|
|
47
|
-
"""
|
|
48
|
-
Map a PeriodIndex to an integer index.
|
|
49
|
-
"""
|
|
45
|
+
"""Map a PeriodIndex to an integer index."""
|
|
50
46
|
if not is_categorical(data):
|
|
51
47
|
return None
|
|
52
48
|
if not isinstance(data.index, PeriodIndex):
|
|
@@ -56,7 +52,8 @@ def map_periodindex(data: DataT) -> None | tuple[DataT, PeriodIndex]:
|
|
|
56
52
|
start=og_index[0].ordinal,
|
|
57
53
|
stop=og_index[-1].ordinal + (1 if og_index[0] < og_index[-1] else -1),
|
|
58
54
|
)
|
|
59
|
-
|
|
55
|
+
if len(data.index) != len(og_index):
|
|
56
|
+
raise ValueError("Mapped PeriodIndex to RangeIndex, but the lengths do not match.")
|
|
60
57
|
return data, og_index
|
|
61
58
|
|
|
62
59
|
|
|
@@ -89,11 +86,11 @@ intervals = {
|
|
|
89
86
|
|
|
90
87
|
|
|
91
88
|
def get_count(p: PeriodIndex, max_ticks: int) -> tuple[int, DateLike, int]:
|
|
92
|
-
"""
|
|
93
|
-
Work out the label frequency and interval for a date-like
|
|
89
|
+
"""Work out the label frequency and interval for a date-like
|
|
94
90
|
PeriodIndex.
|
|
95
91
|
|
|
96
92
|
Parameters
|
|
93
|
+
----------
|
|
97
94
|
- p: PeriodIndex - the PeriodIndex
|
|
98
95
|
- max_ticks - the maximum number of ticks [suggestive]
|
|
99
96
|
|
|
@@ -101,8 +98,8 @@ def get_count(p: PeriodIndex, max_ticks: int) -> tuple[int, DateLike, int]:
|
|
|
101
98
|
- the roughly anticipated number of ticks to highlight: int
|
|
102
99
|
- the type of ticks to highlight (eg. days/months/quarters/years): str
|
|
103
100
|
- the tick interval (ie. number of days/months/quarters/years): int
|
|
104
|
-
"""
|
|
105
101
|
|
|
102
|
+
"""
|
|
106
103
|
# --- sanity checks
|
|
107
104
|
error = (0, DateLike.BAD, 0)
|
|
108
105
|
if p.empty:
|
|
@@ -132,8 +129,7 @@ def day_labeller(labels: dict[Period, str]) -> dict[Period, str]:
|
|
|
132
129
|
|
|
133
130
|
def add_year(label: str, year: str) -> str:
|
|
134
131
|
label = label.replace("\n", " ") if len(label) > 2 else f"{label} {month}"
|
|
135
|
-
|
|
136
|
-
return label
|
|
132
|
+
return f"{label}\n{year}"
|
|
137
133
|
|
|
138
134
|
if not labels:
|
|
139
135
|
return labels
|
|
@@ -168,19 +164,17 @@ def day_labeller(labels: dict[Period, str]) -> dict[Period, str]:
|
|
|
168
164
|
|
|
169
165
|
def month_locator(p: PeriodIndex, interval: int) -> dict[Period, str]:
|
|
170
166
|
"""Select the months to label."""
|
|
171
|
-
|
|
172
167
|
subset = PeriodIndex([c for c in p if c.day == 1]) if p.freqstr[0] == "D" else p
|
|
173
168
|
|
|
174
169
|
start = 0
|
|
175
170
|
if interval > 1:
|
|
176
171
|
mod_months = [(c.month - 1) % interval for c in subset]
|
|
177
172
|
start = 0 if 0 not in mod_months else mod_months.index(0)
|
|
178
|
-
return
|
|
173
|
+
return dict.fromkeys(subset[start::interval], "")
|
|
179
174
|
|
|
180
175
|
|
|
181
176
|
def month_labeller(labels: dict[Period, str]) -> dict[Period, str]:
|
|
182
177
|
"""Label the selected months."""
|
|
183
|
-
|
|
184
178
|
if not labels:
|
|
185
179
|
return labels
|
|
186
180
|
|
|
@@ -211,17 +205,15 @@ def month_labeller(labels: dict[Period, str]) -> dict[Period, str]:
|
|
|
211
205
|
|
|
212
206
|
def qtr_locator(p: PeriodIndex, interval: int) -> dict[Period, str]:
|
|
213
207
|
"""Select the quarters to label."""
|
|
214
|
-
|
|
215
208
|
start = 0
|
|
216
209
|
if interval > 1:
|
|
217
210
|
mod_qtrs = [(c.quarter - 1) % interval for c in p]
|
|
218
211
|
start = 0 if 0 not in mod_qtrs else mod_qtrs.index(0)
|
|
219
|
-
return
|
|
212
|
+
return dict.fromkeys(p[start::interval], "")
|
|
220
213
|
|
|
221
214
|
|
|
222
215
|
def qtr_labeller(labels: dict[Period, str]) -> dict[Period, str]:
|
|
223
216
|
"""Label the selected quarters."""
|
|
224
|
-
|
|
225
217
|
if not labels:
|
|
226
218
|
return labels
|
|
227
219
|
|
|
@@ -246,7 +238,6 @@ def qtr_labeller(labels: dict[Period, str]) -> dict[Period, str]:
|
|
|
246
238
|
|
|
247
239
|
def year_locator(p: PeriodIndex, interval: int) -> dict[Period, str]:
|
|
248
240
|
"""Select the years to label."""
|
|
249
|
-
|
|
250
241
|
match p.freqstr[0]:
|
|
251
242
|
case "D":
|
|
252
243
|
subset = PeriodIndex([c for c in p if c.month == 1 and c.day == 1])
|
|
@@ -261,12 +252,11 @@ def year_locator(p: PeriodIndex, interval: int) -> dict[Period, str]:
|
|
|
261
252
|
if interval > 1:
|
|
262
253
|
mod_years = [(c.year) % interval for c in subset]
|
|
263
254
|
start = 0 if 0 not in mod_years else mod_years.index(0)
|
|
264
|
-
return
|
|
255
|
+
return dict.fromkeys(subset[start::interval], "")
|
|
265
256
|
|
|
266
257
|
|
|
267
258
|
def year_labeller(labels: dict[Period, str]) -> dict[Period, str]:
|
|
268
259
|
"""Label the selected years."""
|
|
269
|
-
|
|
270
260
|
if not labels:
|
|
271
261
|
return labels
|
|
272
262
|
|
|
@@ -277,18 +267,18 @@ def year_labeller(labels: dict[Period, str]) -> dict[Period, str]:
|
|
|
277
267
|
|
|
278
268
|
|
|
279
269
|
def make_labels(p: PeriodIndex, max_ticks: int) -> dict[Period, str]:
|
|
280
|
-
"""
|
|
281
|
-
Provide a dictionary of labels for the date-like PeriodIndex.
|
|
270
|
+
"""Provide a dictionary of labels for the date-like PeriodIndex.
|
|
282
271
|
|
|
283
272
|
Parameters
|
|
273
|
+
----------
|
|
284
274
|
- p: PeriodIndex - the PeriodIndex
|
|
285
275
|
- max_ticks - the maximum number of ticks [suggestive]
|
|
286
276
|
|
|
287
277
|
Returns a dictionary:
|
|
288
278
|
- keys are the Periods to label
|
|
289
279
|
- values are the labels to apply
|
|
290
|
-
"""
|
|
291
280
|
|
|
281
|
+
"""
|
|
292
282
|
labels: dict[Period, str] = {}
|
|
293
283
|
max_ticks = max(max_ticks, 4)
|
|
294
284
|
count, date_like, interval = get_count(p, max_ticks)
|
|
@@ -301,7 +291,7 @@ def make_labels(p: PeriodIndex, max_ticks: int) -> dict[Period, str]:
|
|
|
301
291
|
match target_freq:
|
|
302
292
|
case "D":
|
|
303
293
|
start = 0 if interval == 2 and count % 2 else interval // 2
|
|
304
|
-
labels =
|
|
294
|
+
labels = dict.fromkeys(complete[start::interval], "")
|
|
305
295
|
labels = day_labeller(labels)
|
|
306
296
|
|
|
307
297
|
case "M":
|
|
@@ -320,18 +310,18 @@ def make_labels(p: PeriodIndex, max_ticks: int) -> dict[Period, str]:
|
|
|
320
310
|
|
|
321
311
|
|
|
322
312
|
def make_ilabels(p: PeriodIndex, max_ticks: int) -> tuple[list[int], list[str]]:
|
|
323
|
-
"""
|
|
324
|
-
From a PeriodIndex, create a list of integer ticks and ticklabels
|
|
313
|
+
"""From a PeriodIndex, create a list of integer ticks and ticklabels
|
|
325
314
|
|
|
326
315
|
Parameters
|
|
316
|
+
----------
|
|
327
317
|
- p: PeriodIndex - the PeriodIndex
|
|
328
318
|
- max_ticks - the maximum number of ticks [suggestive]
|
|
329
319
|
|
|
330
320
|
Returns a tuple:
|
|
331
321
|
- list of integer ticks
|
|
332
322
|
- list of tick label strings
|
|
333
|
-
"""
|
|
334
323
|
|
|
324
|
+
"""
|
|
335
325
|
labels = make_labels(p, max_ticks)
|
|
336
326
|
ticks = [x.ordinal for x in sorted(labels.keys())]
|
|
337
327
|
ticklabels = [labels[x] for x in sorted(labels.keys())]
|
|
@@ -340,15 +330,15 @@ def make_ilabels(p: PeriodIndex, max_ticks: int) -> tuple[list[int], list[str]]:
|
|
|
340
330
|
|
|
341
331
|
|
|
342
332
|
def set_labels(axes: Axes, p: PeriodIndex, max_ticks: int = 10) -> None:
|
|
343
|
-
"""
|
|
344
|
-
Set the x-axis labels for a date-like PeriodIndex.
|
|
333
|
+
"""Set the x-axis labels for a date-like PeriodIndex.
|
|
345
334
|
|
|
346
335
|
Parameters
|
|
336
|
+
----------
|
|
347
337
|
- axes: Axes - the axes to set the labels on
|
|
348
338
|
- p: PeriodIndex - the PeriodIndex
|
|
349
339
|
- max_ticks: int - the maximum number of ticks [suggestive]
|
|
350
|
-
"""
|
|
351
340
|
|
|
341
|
+
"""
|
|
352
342
|
ticks, ticklabels = make_ilabels(p, max_ticks)
|
|
353
343
|
axes.set_xticks(ticks)
|
|
354
344
|
axes.set_xticklabels(ticklabels, rotation=0, ha="center")
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
bar_plot.py
|
|
1
|
+
"""bar_plot.py
|
|
3
2
|
This module contains functions to create bar plots using Matplotlib.
|
|
4
3
|
Note: bar plots in Matplotlib are not the same as bar charts in other
|
|
5
4
|
libraries. Bar plots are used to represent categorical data with
|
|
@@ -8,27 +7,25 @@ cannot be plotted on the same axes.
|
|
|
8
7
|
"""
|
|
9
8
|
|
|
10
9
|
# --- imports
|
|
11
|
-
from typing import Any, Final, Unpack, NotRequired
|
|
12
10
|
from collections.abc import Sequence
|
|
11
|
+
from typing import Any, Final, NotRequired, Unpack
|
|
13
12
|
|
|
14
|
-
import
|
|
15
|
-
from pandas import Series, DataFrame, Period
|
|
13
|
+
import matplotlib.patheffects as pe
|
|
16
14
|
import matplotlib.pyplot as plt
|
|
15
|
+
import numpy as np
|
|
17
16
|
from matplotlib.pyplot import Axes
|
|
18
|
-
import
|
|
19
|
-
|
|
17
|
+
from pandas import DataFrame, Period, Series
|
|
20
18
|
|
|
19
|
+
from mgplot.axis_utils import is_categorical, map_periodindex, set_labels
|
|
20
|
+
from mgplot.keyword_checking import BaseKwargs, report_kwargs, validate_kwargs
|
|
21
21
|
from mgplot.settings import DataT, get_setting
|
|
22
22
|
from mgplot.utilities import (
|
|
23
23
|
apply_defaults,
|
|
24
|
-
get_color_list,
|
|
25
|
-
get_axes,
|
|
26
24
|
constrain_data,
|
|
27
25
|
default_rounding,
|
|
26
|
+
get_axes,
|
|
27
|
+
get_color_list,
|
|
28
28
|
)
|
|
29
|
-
from mgplot.keyword_checking import validate_kwargs, report_kwargs, BaseKwargs
|
|
30
|
-
from mgplot.axis_utils import set_labels, map_periodindex, is_categorical
|
|
31
|
-
|
|
32
29
|
|
|
33
30
|
# --- constants
|
|
34
31
|
ME: Final[str] = "bar_plot"
|
|
@@ -68,7 +65,6 @@ def annotate_bars(
|
|
|
68
65
|
|
|
69
66
|
Note: "annotate", "fontsize", "fontname", "color", and "rotation" are expected in anno_kwargs.
|
|
70
67
|
"""
|
|
71
|
-
|
|
72
68
|
# --- only annotate in limited circumstances
|
|
73
69
|
if "annotate" not in anno_kwargs or not anno_kwargs["annotate"]:
|
|
74
70
|
return
|
|
@@ -89,12 +85,12 @@ def annotate_bars(
|
|
|
89
85
|
"color": anno_kwargs.get("color"),
|
|
90
86
|
"rotation": anno_kwargs.get("rotation"),
|
|
91
87
|
}
|
|
92
|
-
rounding = default_rounding(series=series, provided=anno_kwargs.get("rounding"
|
|
88
|
+
rounding = default_rounding(series=series, provided=anno_kwargs.get("rounding"))
|
|
93
89
|
adjustment = (series.max() - series.min()) * 0.02
|
|
94
90
|
zero_correction = series.index.min()
|
|
95
91
|
|
|
96
92
|
# --- annotate each bar
|
|
97
|
-
for index, value in zip(series.index.astype(int), series): # mypy syntactic sugar
|
|
93
|
+
for index, value in zip(series.index.astype(int), series, strict=False): # mypy syntactic sugar
|
|
98
94
|
position = base[index - zero_correction] + (adjustment if value >= 0 else -adjustment)
|
|
99
95
|
if above:
|
|
100
96
|
position += value
|
|
@@ -113,10 +109,7 @@ def annotate_bars(
|
|
|
113
109
|
|
|
114
110
|
|
|
115
111
|
def grouped(axes, df: DataFrame, anno_args, **kwargs) -> None:
|
|
116
|
-
"""
|
|
117
|
-
plot a grouped bar plot
|
|
118
|
-
"""
|
|
119
|
-
|
|
112
|
+
"""Plot a grouped bar plot"""
|
|
120
113
|
series_count = len(df.columns)
|
|
121
114
|
|
|
122
115
|
for i, col in enumerate(df.columns):
|
|
@@ -149,16 +142,15 @@ def grouped(axes, df: DataFrame, anno_args, **kwargs) -> None:
|
|
|
149
142
|
|
|
150
143
|
|
|
151
144
|
def stacked(axes, df: DataFrame, anno_args, **kwargs) -> None:
|
|
152
|
-
"""
|
|
153
|
-
plot a stacked bar plot
|
|
154
|
-
"""
|
|
155
|
-
|
|
145
|
+
"""Plot a stacked bar plot"""
|
|
156
146
|
series_count = len(df)
|
|
157
147
|
base_plus: np.ndarray[tuple[int, ...], np.dtype[np.float64]] = np.zeros(
|
|
158
|
-
shape=series_count,
|
|
148
|
+
shape=series_count,
|
|
149
|
+
dtype=np.float64,
|
|
159
150
|
)
|
|
160
151
|
base_minus: np.ndarray[tuple[int, ...], np.dtype[np.float64]] = np.zeros(
|
|
161
|
-
shape=series_count,
|
|
152
|
+
shape=series_count,
|
|
153
|
+
dtype=np.float64,
|
|
162
154
|
)
|
|
163
155
|
for i, col in enumerate(df.columns):
|
|
164
156
|
series = df[col]
|
|
@@ -185,12 +177,12 @@ def stacked(axes, df: DataFrame, anno_args, **kwargs) -> None:
|
|
|
185
177
|
|
|
186
178
|
|
|
187
179
|
def bar_plot(data: DataT, **kwargs: Unpack[BarKwargs]) -> Axes:
|
|
188
|
-
"""
|
|
189
|
-
Create a bar plot from the given data. Each column in the DataFrame
|
|
180
|
+
"""Create a bar plot from the given data. Each column in the DataFrame
|
|
190
181
|
will be stacked on top of each other, with positive values above
|
|
191
182
|
zero and negative values below zero.
|
|
192
183
|
|
|
193
184
|
Parameters
|
|
185
|
+
----------
|
|
194
186
|
- data: Series - The data to plot. Can be a DataFrame or a Series.
|
|
195
187
|
- **kwargs: BarKwargs - Additional keyword arguments for customization.
|
|
196
188
|
(see BarKwargs for details)
|
|
@@ -198,9 +190,10 @@ def bar_plot(data: DataT, **kwargs: Unpack[BarKwargs]) -> Axes:
|
|
|
198
190
|
Note: This function does not assume all data is timeseries with a PeriodIndex,
|
|
199
191
|
|
|
200
192
|
Returns
|
|
193
|
+
-------
|
|
201
194
|
- axes: Axes - The axes for the plot.
|
|
202
|
-
"""
|
|
203
195
|
|
|
196
|
+
"""
|
|
204
197
|
# --- check the kwargs
|
|
205
198
|
report_kwargs(caller=ME, **kwargs)
|
|
206
199
|
validate_kwargs(schema=BarKwargs, caller=ME, **kwargs)
|
|
@@ -1,21 +1,18 @@
|
|
|
1
|
-
"""
|
|
2
|
-
colors.py
|
|
1
|
+
"""colors.py
|
|
3
2
|
This module provides a set of color palettes and functions to generate colors
|
|
4
3
|
for Australian states and territories and major political parties.
|
|
5
4
|
It also provides Australian state names and their abbreviations.
|
|
6
5
|
"""
|
|
7
6
|
|
|
8
7
|
# --- Imports
|
|
9
|
-
from
|
|
8
|
+
from collections.abc import Iterable
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
# --- Functions
|
|
13
12
|
def get_party_palette(party_text: str) -> str:
|
|
14
|
-
"""
|
|
15
|
-
Return a matplotlib color-map name based on party_text.
|
|
13
|
+
"""Return a matplotlib color-map name based on party_text.
|
|
16
14
|
Works for Australian major political parties.
|
|
17
15
|
"""
|
|
18
|
-
|
|
19
16
|
# Note: light to dark maps work best
|
|
20
17
|
match party_text.lower():
|
|
21
18
|
case "alp" | "labor":
|
|
@@ -32,11 +29,9 @@ def get_party_palette(party_text: str) -> str:
|
|
|
32
29
|
|
|
33
30
|
|
|
34
31
|
def get_color(s: str) -> str:
|
|
35
|
-
"""
|
|
36
|
-
Return a matplotlib color for a party label
|
|
32
|
+
"""Return a matplotlib color for a party label
|
|
37
33
|
or an Australian state/territory.
|
|
38
34
|
"""
|
|
39
|
-
|
|
40
35
|
color_map = {
|
|
41
36
|
# --- Australian states and territories
|
|
42
37
|
("wa", "western australia"): "gold",
|
|
@@ -91,19 +86,14 @@ def get_color(s: str) -> str:
|
|
|
91
86
|
|
|
92
87
|
|
|
93
88
|
def colorise_list(party_list: Iterable) -> list[str]:
|
|
94
|
-
"""
|
|
95
|
-
Return a list of party/state colors for a party_list.
|
|
96
|
-
"""
|
|
97
|
-
|
|
89
|
+
"""Return a list of party/state colors for a party_list."""
|
|
98
90
|
return [get_color(x) for x in party_list]
|
|
99
91
|
|
|
100
92
|
|
|
101
93
|
def contrast(orig_color: str) -> str:
|
|
102
|
-
"""
|
|
103
|
-
Provide a constrasting color to any party color
|
|
94
|
+
"""Provide a constrasting color to any party color
|
|
104
95
|
generated by get_color() above.
|
|
105
96
|
"""
|
|
106
|
-
|
|
107
97
|
new_color = "black"
|
|
108
98
|
match orig_color:
|
|
109
99
|
case "royalblue":
|
|
@@ -154,14 +144,13 @@ for k, v in _state_names.items():
|
|
|
154
144
|
|
|
155
145
|
|
|
156
146
|
def abbreviate_state(state: str) -> str:
|
|
157
|
-
"""
|
|
158
|
-
A function to abbreviate long-form state
|
|
147
|
+
"""A function to abbreviate long-form state
|
|
159
148
|
names.
|
|
160
149
|
|
|
161
|
-
Arguments
|
|
150
|
+
Arguments:
|
|
162
151
|
- state: the long-form state name.
|
|
163
152
|
|
|
164
153
|
Return the abbreviation for a state name.
|
|
165
|
-
"""
|
|
166
154
|
|
|
155
|
+
"""
|
|
167
156
|
return _state_names_multi.get(state.lower(), state)
|
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
"""
|
|
2
|
-
finalise_plot.py:
|
|
1
|
+
"""finalise_plot.py:
|
|
3
2
|
This module provides a function to finalise and save plots to the
|
|
4
3
|
file system. It is used to publish plots.
|
|
5
4
|
"""
|
|
6
5
|
|
|
7
6
|
# --- imports
|
|
8
|
-
from typing import Any, Final, NotRequired, Unpack, Callable
|
|
9
|
-
from collections.abc import Sequence
|
|
10
7
|
import re
|
|
8
|
+
from collections.abc import Callable, Sequence
|
|
9
|
+
from typing import Any, Final, NotRequired, Unpack
|
|
10
|
+
|
|
11
11
|
import matplotlib as mpl
|
|
12
12
|
import matplotlib.pyplot as plt
|
|
13
13
|
from matplotlib.pyplot import Axes, Figure
|
|
14
14
|
|
|
15
|
+
from mgplot.keyword_checking import BaseKwargs, report_kwargs, validate_kwargs
|
|
15
16
|
from mgplot.settings import get_setting
|
|
16
|
-
from mgplot.keyword_checking import validate_kwargs, report_kwargs, BaseKwargs
|
|
17
|
-
|
|
18
17
|
|
|
19
18
|
# --- constants
|
|
20
19
|
ME: Final[str] = "finalise_plot"
|
|
@@ -93,9 +92,8 @@ _remove = re.compile(r"[^0-9A-Za-z]") # sensible file names from alphamum title
|
|
|
93
92
|
_reduce = re.compile(r"[-]+") # eliminate multiple hyphens
|
|
94
93
|
|
|
95
94
|
|
|
96
|
-
def make_legend(axes: Axes, legend: None | bool | dict[str, Any]) -> None:
|
|
95
|
+
def make_legend(axes: Axes, *, legend: None | bool | dict[str, Any]) -> None:
|
|
97
96
|
"""Create a legend for the plot."""
|
|
98
|
-
|
|
99
97
|
if legend is None or legend is False:
|
|
100
98
|
return
|
|
101
99
|
|
|
@@ -111,9 +109,8 @@ def make_legend(axes: Axes, legend: None | bool | dict[str, Any]) -> None:
|
|
|
111
109
|
|
|
112
110
|
def apply_value_kwargs(axes: Axes, settings: Sequence[str], **kwargs) -> None:
|
|
113
111
|
"""Set matplotlib elements by name using Axes.set()."""
|
|
114
|
-
|
|
115
112
|
for setting in settings:
|
|
116
|
-
value = kwargs.get(setting
|
|
113
|
+
value = kwargs.get(setting)
|
|
117
114
|
if value is None and setting not in ("title", "xlabel", "ylabel"):
|
|
118
115
|
continue
|
|
119
116
|
function: dict[str, Callable[[], str]] = {
|
|
@@ -132,17 +129,15 @@ def apply_value_kwargs(axes: Axes, settings: Sequence[str], **kwargs) -> None:
|
|
|
132
129
|
|
|
133
130
|
|
|
134
131
|
def apply_splat_kwargs(axes: Axes, settings: tuple, **kwargs) -> None:
|
|
135
|
-
"""
|
|
136
|
-
Set matplotlib elements dynamically using setting_name and splat.
|
|
132
|
+
"""Set matplotlib elements dynamically using setting_name and splat.
|
|
137
133
|
This is used for legend, axhspan, axvspan, axhline, and axvline.
|
|
138
134
|
These can be ignored if not in kwargs, or set to None in kwargs.
|
|
139
135
|
"""
|
|
140
|
-
|
|
141
136
|
for method_name in settings:
|
|
142
137
|
if method_name in kwargs:
|
|
143
138
|
if method_name == "legend":
|
|
144
139
|
# special case for legend
|
|
145
|
-
make_legend(axes, kwargs[method_name])
|
|
140
|
+
make_legend(axes, legend=kwargs[method_name])
|
|
146
141
|
continue
|
|
147
142
|
|
|
148
143
|
if kwargs[method_name] is None or kwargs[method_name] is False:
|
|
@@ -157,14 +152,12 @@ def apply_splat_kwargs(axes: Axes, settings: tuple, **kwargs) -> None:
|
|
|
157
152
|
method(**kwargs[method_name])
|
|
158
153
|
else:
|
|
159
154
|
print(
|
|
160
|
-
f"Warning expected dict argument for {method_name} but got "
|
|
161
|
-
+ f"{type(kwargs[method_name])}."
|
|
155
|
+
f"Warning expected dict argument for {method_name} but got {type(kwargs[method_name])}.",
|
|
162
156
|
)
|
|
163
157
|
|
|
164
158
|
|
|
165
159
|
def apply_annotations(axes: Axes, **kwargs) -> None:
|
|
166
160
|
"""Set figure size and apply chart annotations."""
|
|
167
|
-
|
|
168
161
|
fig = axes.figure
|
|
169
162
|
fig_size = kwargs.get("figsize", get_setting("figsize"))
|
|
170
163
|
if not isinstance(fig, mpl.figure.SubFigure):
|
|
@@ -227,7 +220,6 @@ def apply_kwargs(axes: Axes, **kwargs) -> None:
|
|
|
227
220
|
|
|
228
221
|
def save_to_file(fig: Figure, **kwargs) -> None:
|
|
229
222
|
"""Save the figure to file."""
|
|
230
|
-
|
|
231
223
|
saving = not kwargs.get("dont_save", False) # save by default
|
|
232
224
|
if saving:
|
|
233
225
|
chart_dir = kwargs.get("chart_dir", get_setting("chart_dir"))
|
|
@@ -250,8 +242,7 @@ def save_to_file(fig: Figure, **kwargs) -> None:
|
|
|
250
242
|
|
|
251
243
|
|
|
252
244
|
def finalise_plot(axes: Axes, **kwargs: Unpack[FinaliseKwargs]) -> None:
|
|
253
|
-
"""
|
|
254
|
-
A function to finalise and save plots to the file system. The filename
|
|
245
|
+
"""A function to finalise and save plots to the file system. The filename
|
|
255
246
|
for the saved plot is constructed from the global chart_dir, the plot's title,
|
|
256
247
|
any specified tag text, and the file_type for the plot.
|
|
257
248
|
|
|
@@ -259,10 +250,10 @@ def finalise_plot(axes: Axes, **kwargs: Unpack[FinaliseKwargs]) -> None:
|
|
|
259
250
|
- axes - matplotlib axes object - required
|
|
260
251
|
- kwargs: FinaliseKwargs
|
|
261
252
|
|
|
262
|
-
|
|
253
|
+
Returns:
|
|
263
254
|
- None
|
|
264
|
-
"""
|
|
265
255
|
|
|
256
|
+
"""
|
|
266
257
|
# --- check the kwargs
|
|
267
258
|
me = "finalise_plot"
|
|
268
259
|
report_kwargs(caller=me, **kwargs)
|
|
@@ -284,7 +275,7 @@ def finalise_plot(axes: Axes, **kwargs: Unpack[FinaliseKwargs]) -> None:
|
|
|
284
275
|
|
|
285
276
|
# tight layout and save the figure
|
|
286
277
|
fig = axes.figure
|
|
287
|
-
if
|
|
278
|
+
if kwargs.get("preserve_lims"):
|
|
288
279
|
# restore the original limits of the axes
|
|
289
280
|
axes.set_xlim(xlim)
|
|
290
281
|
axes.set_ylim(ylim)
|
|
@@ -298,7 +289,7 @@ def finalise_plot(axes: Axes, **kwargs: Unpack[FinaliseKwargs]) -> None:
|
|
|
298
289
|
save_to_file(fig, **kwargs)
|
|
299
290
|
|
|
300
291
|
# show the plot in Jupyter Lab
|
|
301
|
-
if
|
|
292
|
+
if kwargs.get("show"):
|
|
302
293
|
plt.show()
|
|
303
294
|
|
|
304
295
|
# And close
|