mgplot 0.1.0__py3-none-any.whl

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/__init__.py ADDED
@@ -0,0 +1,121 @@
1
+ """
2
+ mgplot
3
+ ------
4
+
5
+ Package to provide a frontend to matplotlib for working
6
+ with timeseries data that is indexed with a PeriodIndex.
7
+ """
8
+
9
+ # --- version and author
10
+ # NOTE: update version number here (below) and in pyproject.toml
11
+ __version__ = "0.1.0"
12
+ __author__ = "Bryan Palmer"
13
+
14
+
15
+ # --- local imports
16
+ # Do not import the utilities, test nor type-checking modules here.
17
+ from mgplot.finalise_plot import finalise_plot
18
+ from mgplot.bar_plot import bar_plot
19
+ from mgplot.line_plot import line_plot
20
+ from mgplot.seastrend_plot import seastrend_plot
21
+ from mgplot.postcovid_plot import postcovid_plot
22
+ from mgplot.revision_plot import revision_plot
23
+ from mgplot.run_plot import run_plot
24
+ from mgplot.summary_plot import summary_plot
25
+ from mgplot.growth_plot import (
26
+ calc_growth,
27
+ raw_growth_plot,
28
+ series_growth_plot,
29
+ )
30
+ from mgplot.multi_plot import (
31
+ multi_start,
32
+ multi_column,
33
+ plot_then_finalise,
34
+ )
35
+ from mgplot.colors import (
36
+ get_color,
37
+ get_party_palette,
38
+ colorise_list,
39
+ contrast,
40
+ abbreviate_state,
41
+ state_names,
42
+ state_abbrs,
43
+ )
44
+ from mgplot.settings import (
45
+ get_setting,
46
+ set_setting,
47
+ set_chart_dir,
48
+ clear_chart_dir,
49
+ )
50
+ from mgplot.finalisers import (
51
+ line_plot_finalise,
52
+ bar_plot_finalise,
53
+ seastrend_plot_finalise,
54
+ postcovid_plot_finalise,
55
+ revision_plot_finalise,
56
+ summary_plot_finalise,
57
+ raw_growth_plot_finalise,
58
+ series_growth_plot_finalise,
59
+ run_plot_finalise,
60
+ )
61
+
62
+
63
+ # --- version and author
64
+ __version__ = "0.0.1"
65
+ __author__ = "Bryan Palmer"
66
+
67
+
68
+ # --- public API
69
+ __all__ = (
70
+ "__version__",
71
+ "__author__",
72
+ # --- settings
73
+ "get_setting",
74
+ "set_setting",
75
+ "set_chart_dir",
76
+ "clear_chart_dir",
77
+ # --- colors
78
+ "get_color",
79
+ "get_party_palette",
80
+ "colorise_list",
81
+ "contrast",
82
+ "abbreviate_state",
83
+ "state_names",
84
+ "state_abbrs",
85
+ # --- finalise_plot
86
+ "finalise_plot",
87
+ # --- line_plot
88
+ "line_plot",
89
+ # --- bar plot
90
+ "bar_plot",
91
+ # --- seastrend_plot
92
+ "seastrend_plot",
93
+ # --- postcovid_plot
94
+ "postcovid_plot",
95
+ # --- revision_plot
96
+ "revision_plot",
97
+ # --- run_plot
98
+ "run_plot",
99
+ # --- summary_plot
100
+ "summary_plot",
101
+ # --- growth_plot
102
+ "calc_growth",
103
+ "raw_growth_plot",
104
+ "series_growth_plot",
105
+ # --- multi_plot
106
+ "multi_start",
107
+ "multi_column",
108
+ "plot_then_finalise",
109
+ # --- finaliser functions
110
+ "line_plot_finalise",
111
+ "bar_plot_finalise",
112
+ "seastrend_plot_finalise",
113
+ "postcovid_plot_finalise",
114
+ "revision_plot_finalise",
115
+ "summary_plot_finalise",
116
+ "raw_growth_plot_finalise",
117
+ "series_growth_plot_finalise",
118
+ "run_plot_finalise",
119
+ # --- The rest are internal use only
120
+ )
121
+ # __pdoc__: dict[str, Any] = {"test": False} # hide submodules from documentation
mgplot/bar_plot.py ADDED
@@ -0,0 +1,107 @@
1
+ """
2
+ bar_plot.py
3
+ This module contains functions to create bar plots using Matplotlib.
4
+ Note: bar plots in Matplotlib are not the same as bar charts in other
5
+ libraries. Bar plots are used to represent categorical data with
6
+ rectangular bars. As a result, bar plots and line plots typically
7
+ cannot be plotted on the same axes.
8
+ """
9
+
10
+ # --- imports
11
+ from typing import Any
12
+ from collections.abc import Sequence
13
+ from pandas import DataFrame, period_range, PeriodIndex
14
+ import matplotlib.pyplot as plt
15
+ from matplotlib.pyplot import Axes
16
+
17
+ from mgplot.settings import DataT, get_setting
18
+ from mgplot.utilities import apply_defaults, get_color_list, get_axes, constrain_data
19
+ from mgplot.kw_type_checking import validate_kwargs, validate_expected, ExpectedTypeDict
20
+ from mgplot.date_utils import set_labels
21
+
22
+
23
+ # --- constants
24
+ BAR_PLOT_KW_TYPES: ExpectedTypeDict = {
25
+ "color": (str, Sequence, (str,)),
26
+ "width": float,
27
+ "stacked": bool,
28
+ "rotation": (int, float),
29
+ "bar_legend": bool,
30
+ "max_ticks": int,
31
+ "plot_from": (int, PeriodIndex, type(None)),
32
+ }
33
+ validate_expected(BAR_PLOT_KW_TYPES, "bar_plot")
34
+
35
+
36
+ # --- functions
37
+ def bar_plot(
38
+ data: DataT,
39
+ **kwargs,
40
+ ) -> Axes:
41
+ """
42
+ Create a bar plot from the given data. Each column in the DataFrame
43
+ will be stacked on top of each other, with positive values above
44
+ zero and negative values below zero.
45
+
46
+ Parameters
47
+ - data: Series - The data to plot. Can be a DataFrame or a Series.
48
+ - **kwargs: dict Additional keyword arguments for customization.
49
+ - color: list - A list of colors for the each series (column) in the DataFrame.
50
+ - width: float - The width of the bars.
51
+ - stacked: bool - If True, the bars will be stacked.
52
+ - rotation: int - The rotation angle in degrees for the x-axis labels.
53
+ - bar_legend: bool - If True, show the legend. Defaults to True
54
+ if more than one bar being plotted for each category.
55
+ - "max_ticks": int - The maximum number of ticks on the x-axis,
56
+ (this option only applies to PeriodIndex data.).
57
+
58
+ Note: This function does not assume all data is timeseries with a PeriodIndex,
59
+
60
+ Returns
61
+ - axes: Axes - The axes for the plot.
62
+ """
63
+
64
+ # --- validate the kwargs
65
+ validate_kwargs(BAR_PLOT_KW_TYPES, "bar_plot", **kwargs)
66
+ # note data may not be time-series or have a period index.
67
+
68
+ # --- get the data
69
+ df = DataFrame(data) # really we are only plotting DataFrames
70
+ df, kwargs = constrain_data(df, **kwargs)
71
+ item_count = len(df.columns)
72
+
73
+ defaults: dict[str, Any] = {
74
+ "color": get_color_list(item_count),
75
+ "width": get_setting("bar_width"),
76
+ "stacked": False,
77
+ "rotation": 90,
78
+ "bar_legend": (item_count > 1),
79
+ "max_ticks": 10,
80
+ }
81
+ bar_args, remaining_kwargs = apply_defaults(item_count, defaults, kwargs)
82
+
83
+ # --- plot the data
84
+ axes, _rkwargs = get_axes(**remaining_kwargs)
85
+
86
+ df.plot.bar(
87
+ ax=axes,
88
+ color=bar_args["color"],
89
+ stacked=bar_args["stacked"][0],
90
+ width=bar_args["width"][0],
91
+ legend=bar_args["bar_legend"][0],
92
+ )
93
+
94
+ rotate_labels = True
95
+ if isinstance(df.index, PeriodIndex):
96
+ complete = period_range(
97
+ start=df.index.min(), end=df.index.max(), freq=df.index.freqstr
98
+ )
99
+ if complete.equals(df.index):
100
+ # if the index is complete, we can set the labels
101
+ set_labels(axes, df.index, bar_args["max_ticks"][0])
102
+ rotate_labels = False
103
+
104
+ if rotate_labels:
105
+ plt.xticks(rotation=bar_args["rotation"][0])
106
+
107
+ return axes
mgplot/colors.py ADDED
@@ -0,0 +1,199 @@
1
+ """
2
+ colors.py
3
+ This module provides a set of color palettes and functions to generate colors
4
+ for Australian states and territories and major political parties.
5
+ It also provides Australian state names and their abbreviations.
6
+ """
7
+
8
+ # --- Imports
9
+ from typing import Iterable
10
+
11
+
12
+ # --- Functions
13
+ def get_party_palette(party_text: str) -> str:
14
+ """
15
+ Return a matplotlib color-map name based on party_text.
16
+ Works for Australian major political parties.
17
+ """
18
+
19
+ # Note: light to dark maps work best
20
+ match party_text.lower():
21
+ case "alp" | "labor":
22
+ return "Reds"
23
+ case "l/np" | "coalition":
24
+ return "Blues"
25
+ case "grn" | "green" | "greens":
26
+ return "Greens"
27
+ case "oth" | "other":
28
+ return "YlOrBr"
29
+ case "onp" | "one nation":
30
+ return "YlGnBu"
31
+ return "Purples"
32
+
33
+
34
+ def get_color(s: str) -> str:
35
+ """
36
+ Return a matplotlib color for a party label
37
+ or an Australian state/territory.
38
+ """
39
+
40
+ color_map = {
41
+ # --- Australian states and territories
42
+ ("wa", "western australia"): "gold",
43
+ ("sa", "south australia"): "red",
44
+ ("nt", "northern territory"): "#CC7722", # ochre
45
+ ("nsw", "new south wales"): "deepskyblue",
46
+ ("act", "australian capital territory"): "blue",
47
+ ("vic", "victoria"): "navy",
48
+ ("tas", "tasmania"): "seagreen", # bottle green #006A4E?
49
+ ("qld", "queensland"): "#c32148", # a lighter maroon
50
+ ("australia", "aus"): "grey",
51
+ # --- political parties
52
+ ("dissatisfied",): "darkorange", # must be before satisfied
53
+ ("satisfied",): "mediumblue",
54
+ (
55
+ "lnp",
56
+ "l/np",
57
+ "coalition",
58
+ "dutton",
59
+ "ley",
60
+ ): "royalblue",
61
+ (
62
+ "alp",
63
+ "labor",
64
+ "albanese",
65
+ ): "indianred",
66
+ (
67
+ "grn",
68
+ "green",
69
+ "greens",
70
+ ): "mediumseagreen",
71
+ (
72
+ "other",
73
+ "oth",
74
+ ): "darkorange",
75
+ }
76
+
77
+ for find_me, return_me in color_map.items():
78
+ if any(x == s.lower() for x in find_me):
79
+ return return_me
80
+
81
+ return "darkgrey"
82
+
83
+
84
+ def colorise_list(party_list: Iterable) -> list[str]:
85
+ """
86
+ Return a list of party/state colors for a party_list.
87
+ """
88
+
89
+ return [get_color(x) for x in party_list]
90
+
91
+
92
+ def contrast(orig_color: str) -> str:
93
+ """
94
+ Provide a constrasting color to any party color
95
+ generated by get_color() above.
96
+ """
97
+
98
+ new_color = "black"
99
+ match orig_color:
100
+ case "royalblue":
101
+ new_color = "indianred"
102
+ case "indianred":
103
+ new_color = "mediumblue"
104
+
105
+ case "darkorange":
106
+ new_color = "mediumblue"
107
+ case "mediumblue":
108
+ new_color = "darkorange"
109
+
110
+ case "mediumseagreen":
111
+ new_color = "darkblue"
112
+
113
+ case "darkgrey":
114
+ new_color = "hotpink"
115
+
116
+ return new_color
117
+
118
+
119
+ # --- Australian state names
120
+ _state_names: dict[str, str] = {
121
+ "New South Wales": "NSW",
122
+ "Victoria": "Vic",
123
+ "Queensland": "Qld",
124
+ "South Australia": "SA",
125
+ "Western Australia": "WA",
126
+ "Tasmania": "Tas",
127
+ "Northern Territory": "NT",
128
+ "Australian Capital Territory": "ACT",
129
+ }
130
+
131
+ # a tuple of standard state names
132
+ state_names = tuple(_state_names.keys())
133
+
134
+ # a tuple of standard state abbreviations
135
+ state_abbrs = tuple(_state_names.values())
136
+
137
+ # a map of state name to their abbreviation
138
+ # including upper and lower case mappings
139
+ _state_names_multi: dict[str, str] = {}
140
+ for k, v in _state_names.items():
141
+ # allow for fast different case matches
142
+ _state_names_multi[k.lower()] = v
143
+ _state_names_multi[k.lower()] = v
144
+ _state_names_multi[v.lower()] = v
145
+
146
+
147
+ def abbreviate_state(state: str) -> str:
148
+ """
149
+ A function to abbreviate long-form state
150
+ names.
151
+
152
+ Arguments
153
+ - state: the long-form state name.
154
+
155
+ Return the abbreviation for a state name.
156
+ """
157
+
158
+ return _state_names_multi.get(state.lower(), state)
159
+
160
+
161
+ # --- test code
162
+ if __name__ == "__main__":
163
+ # Test the color functions
164
+ check = ["Western Australia", "L/NP", "ALP", "Greens", "Dissatisfied", "Ley"]
165
+ for party in check:
166
+ print(f"{party} -> {get_color(party)}")
167
+
168
+ # Test the party palette function
169
+ print()
170
+ check = ["ALP", "L/NP", "Greens"]
171
+ for party in check:
172
+ print(f"{party} -> {get_party_palette(party)}")
173
+
174
+ # Test the abbreviate_state function
175
+ print()
176
+ check = [
177
+ "New South Wales",
178
+ "nsw",
179
+ "Victoria",
180
+ "victoria",
181
+ "VICTORIA",
182
+ "VIC",
183
+ "rubbish",
184
+ "Queensland",
185
+ "South Australia",
186
+ "Western Australia",
187
+ "Tasmania",
188
+ "Northern Territory",
189
+ "Australian Capital Territory",
190
+ "ACT",
191
+ ]
192
+
193
+ for state_ in check:
194
+ print(f"{state_} -> {abbreviate_state(state_)}")
195
+
196
+ # Test the get_color function for states
197
+ print()
198
+ for state_ in check:
199
+ print(f"{state_} -> {get_color(state_)}")