mgplot 0.1.3__tar.gz → 0.1.5__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.
Files changed (38) hide show
  1. mgplot-0.1.5/.gitignore +27 -0
  2. mgplot-0.1.5/CHANGELOG.md +49 -0
  3. {mgplot-0.1.3/src/mgplot.egg-info → mgplot-0.1.5}/PKG-INFO +6 -17
  4. mgplot-0.1.5/build-docs.sh +6 -0
  5. mgplot-0.1.5/build-test.sh +33 -0
  6. {mgplot-0.1.3 → mgplot-0.1.5}/pyproject.toml +11 -8
  7. {mgplot-0.1.3 → mgplot-0.1.5}/src/mgplot/__init__.py +9 -6
  8. {mgplot-0.1.3 → mgplot-0.1.5}/src/mgplot/finalisers.py +1 -10
  9. {mgplot-0.1.3 → mgplot-0.1.5}/src/mgplot/growth_plot.py +57 -45
  10. {mgplot-0.1.3 → mgplot-0.1.5}/src/mgplot/kw_type_checking.py +1 -1
  11. {mgplot-0.1.3 → mgplot-0.1.5}/src/mgplot/line_plot.py +8 -0
  12. mgplot-0.1.5/src/mgplot/lint-all.sh +19 -0
  13. {mgplot-0.1.3 → mgplot-0.1.5}/src/mgplot/multi_plot.py +44 -25
  14. {mgplot-0.1.3 → mgplot-0.1.5}/src/mgplot/utilities.py +31 -15
  15. mgplot-0.1.5/src/mgplot/zz-test-data/ocr_rba.csv +419 -0
  16. mgplot-0.1.5/src/mgplot/zz-test-data/revisions.csv +568 -0
  17. mgplot-0.1.5/src/mgplot/zz-test-data/summary.csv +568 -0
  18. mgplot-0.1.5/uv.lock +718 -0
  19. mgplot-0.1.3/PKG-INFO +0 -99
  20. mgplot-0.1.3/setup.cfg +0 -4
  21. mgplot-0.1.3/src/mgplot.egg-info/SOURCES.txt +0 -27
  22. mgplot-0.1.3/src/mgplot.egg-info/dependency_links.txt +0 -1
  23. mgplot-0.1.3/src/mgplot.egg-info/requires.txt +0 -18
  24. mgplot-0.1.3/src/mgplot.egg-info/top_level.txt +0 -1
  25. {mgplot-0.1.3 → mgplot-0.1.5}/LICENSE +0 -0
  26. {mgplot-0.1.3 → mgplot-0.1.5}/README.md +0 -0
  27. {mgplot-0.1.3 → mgplot-0.1.5}/src/mgplot/bar_plot.py +0 -0
  28. {mgplot-0.1.3 → mgplot-0.1.5}/src/mgplot/colors.py +0 -0
  29. {mgplot-0.1.3 → mgplot-0.1.5}/src/mgplot/date_utils.py +0 -0
  30. {mgplot-0.1.3 → mgplot-0.1.5}/src/mgplot/finalise_plot.py +0 -0
  31. {mgplot-0.1.3 → mgplot-0.1.5}/src/mgplot/postcovid_plot.py +0 -0
  32. {mgplot-0.1.3 → mgplot-0.1.5}/src/mgplot/py.typed +0 -0
  33. {mgplot-0.1.3 → mgplot-0.1.5}/src/mgplot/revision_plot.py +0 -0
  34. {mgplot-0.1.3 → mgplot-0.1.5}/src/mgplot/run_plot.py +0 -0
  35. {mgplot-0.1.3 → mgplot-0.1.5}/src/mgplot/seastrend_plot.py +0 -0
  36. {mgplot-0.1.3 → mgplot-0.1.5}/src/mgplot/settings.py +0 -0
  37. {mgplot-0.1.3 → mgplot-0.1.5}/src/mgplot/summary_plot.py +0 -0
  38. {mgplot-0.1.3 → mgplot-0.1.5}/src/mgplot/test.py +0 -0
@@ -0,0 +1,27 @@
1
+ # python stuff to be ignored
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # jupyter notebooks stuff to be ignored
7
+ .ipynb_checkpoints/
8
+
9
+ # vscode stuff
10
+ .vscode/
11
+
12
+ # MacOS stuff to be ignored
13
+ .DS_Store
14
+
15
+
16
+ # build files
17
+ build/
18
+ dist/
19
+ wheels/
20
+ *.egg-info
21
+
22
+ # Test
23
+ src/mgplot/zz-test-charts/**
24
+ src/mgplot/zz-unused-code-snippets/**
25
+
26
+ # Virtual environments
27
+ .venv
@@ -0,0 +1,49 @@
1
+ Version 0.1.4 - released 01-Jun-2025 (Canberra, Australia)
2
+
3
+ * minor changes
4
+ - changed the build-system
5
+ - added dynamic version numbering to __init__.py
6
+ - reworked annotations in the growth_plot.py module
7
+ and the utilities module,
8
+ - reworked kwargs validation in plot_then_finalise()
9
+ - typo in kw_type_checking.py
10
+ - tightened up function chaining in the multi-plot modules
11
+ - moved some default arguments from the finalisers module
12
+ to the line_plot module.
13
+
14
+ ---
15
+
16
+ Version 0.1.3 - released 31-May-2025 (Canberra, Australia)
17
+
18
+ * minor changes
19
+ - changed defaults for postcovid_plot() to annotate series
20
+ - changed line_plot() to bail early if nothing to plot
21
+ - added a test to ignore empty axes objects in finalise_plot()
22
+ - reduced the text size for runs in run_plot()
23
+ - added "legend" to line_plot() and the growth plots.
24
+ - if the plot function and the finalise_plot() function have
25
+ kwargs in common, they will be handled by plot() and not
26
+ sent to finalise plot (done by plot_then_finalise())
27
+ ---
28
+
29
+ Version 0.1.2 - released 30-May-2025 (Canberra, Australia)
30
+
31
+ * minor changes
32
+ - fixed an incorrect typing-gate in run_plot()
33
+ - removed repeated version code in __init__.py
34
+ - added "numpy-typing" to pyproject.toml
35
+ - added a warning if ylabel set in series_growth_plot_finalise()
36
+ - added legend=True default argument to raw_growth_plot()
37
+ ---
38
+
39
+ Version 0.1.1 - released 29-May-2025 (Canberra, Australia)
40
+
41
+ * minor changes
42
+ - added additional documentation
43
+ - disclosed additional variables in the API
44
+ - standardised the naming of the internal ExpectedTypeDicts
45
+ ---
46
+
47
+ Version 0.1.0 - released 28-May-2025 (Canberra, Australia)
48
+
49
+ ---
@@ -1,29 +1,18 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mgplot
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: mgplot is a time-series/PeriodIndex frontend for matplotlib
5
+ Project-URL: Repository, https://github.com/bpalmer4/mgplot
5
6
  Project-URL: Homepage, https://github.com/bpalmer4/mgplot
6
- Requires-Python: >=3.11
7
- Description-Content-Type: text/markdown
8
7
  License-File: LICENSE
9
- Requires-Dist: typing
10
- Requires-Dist: pathlib
8
+ Requires-Python: >=3.12
11
9
  Requires-Dist: matplotlib
12
10
  Requires-Dist: numpy
13
11
  Requires-Dist: pandas
12
+ Requires-Dist: pathlib
14
13
  Requires-Dist: tabulate
15
- Requires-Dist: black
16
- Requires-Dist: mypy
17
- Requires-Dist: pdoc
18
- Requires-Dist: pylint
19
- Requires-Dist: ruff
20
- Requires-Dist: pandas-stubs
21
- Requires-Dist: numpy-typing
22
- Requires-Dist: types-tabulate
23
- Provides-Extra: build
24
- Requires-Dist: setuptools; extra == "build"
25
- Requires-Dist: cython; extra == "build"
26
- Dynamic: license-file
14
+ Requires-Dist: typing
15
+ Description-Content-Type: text/markdown
27
16
 
28
17
  mgplot
29
18
  ======
@@ -0,0 +1,6 @@
1
+ echo " "
2
+ echo "About to build the documentation ..."
3
+ cd ~/mgplot
4
+ rm -rf ./docs
5
+ pdoc ./src/mgplot -o ./docs
6
+
@@ -0,0 +1,33 @@
1
+ #!/bin/zsh
2
+ # make sure the version number is correct:
3
+ # ~/mgplot/pyproject.toml
4
+
5
+ # --- cd mgplot home and get ready
6
+ cd ~/mgplot
7
+ uv pip uninstall mgplot
8
+ deactivate
9
+
10
+ # --- clean out the dist folder
11
+ if [ ! -d ./dist ]; then
12
+ mkdir dist
13
+ fi
14
+ if [ -n "$(ls -A ./dist 2>/dev/null)" ]; then
15
+ rm ./dist/*
16
+ fi
17
+
18
+ # --- sync and build
19
+ uv lock --upgrade # --upgrade to get the latest dependencies
20
+ uv sync --no-dev # --no-dev to avoid installing dev dependencies
21
+ uv build
22
+
23
+ # --- install new mgplot locally
24
+ uv sync # install with the development dependencies
25
+ uv pip install dist/mgplot*gz # add in the new package
26
+
27
+ # --- build documentation
28
+ ~/mgplot/build-docs.sh
29
+
30
+ # --- if everything is good publish and git
31
+ echo "\nAnd if everything is okay ..."
32
+ echo "uv publish --token MY_TOKEN_HERE"
33
+ echo "And don't forget to upload to github"
@@ -1,9 +1,9 @@
1
1
  [project]
2
2
  name = "mgplot"
3
- version = "0.1.3"
3
+ version = "0.1.5"
4
4
  description = "mgplot is a time-series/PeriodIndex frontend for matplotlib"
5
5
  readme = "README.md"
6
- requires-python = ">=3.11"
6
+ requires-python = ">=3.12"
7
7
 
8
8
  dependencies = [
9
9
  # - system
@@ -15,24 +15,27 @@ dependencies = [
15
15
  "numpy",
16
16
  "pandas",
17
17
  "tabulate",
18
+ ]
18
19
 
19
- # - development environment
20
+ [dependency-groups]
21
+ dev = [
22
+ # - tools
20
23
  "black",
21
24
  "mypy",
22
25
  "pdoc",
23
26
  "pylint",
24
27
  "ruff",
25
-
28
+
26
29
  # - typing
27
30
  "pandas-stubs",
28
31
  "numpy-typing",
29
32
  "types-tabulate",
30
33
  ]
31
34
 
32
- [project.optional-dependencies]
33
- build = ["setuptools", "cython"]
34
-
35
35
  [project.urls]
36
+ Repository = "https://github.com/bpalmer4/mgplot"
36
37
  Homepage = "https://github.com/bpalmer4/mgplot"
37
38
 
38
-
39
+ [build-system]
40
+ requires = ["hatchling"]
41
+ build-backend = "hatchling.build"
@@ -7,14 +7,10 @@ with timeseries data that is indexed with a PeriodIndex.
7
7
  """
8
8
 
9
9
  # --- version and author
10
- # NOTE: update version number here (below) and in pyproject.toml
11
- __version__ = "0.1.3"
12
- __author__ = "Bryan Palmer"
13
-
10
+ import importlib.metadata
14
11
 
15
12
  # --- local imports
16
13
  # Do not import the utilities, test nor type-checking modules here.
17
- from mgplot.kw_type_checking import ExpectedTypeDict # typing for the kwarg types
18
14
  from mgplot.finalise_plot import finalise_plot, FINALISE_KW_TYPES
19
15
  from mgplot.bar_plot import bar_plot, BAR_KW_TYPES
20
16
  from mgplot.line_plot import line_plot, LINE_KW_TYPES
@@ -63,6 +59,14 @@ from mgplot.finalisers import (
63
59
  )
64
60
 
65
61
 
62
+ # --- version and author
63
+ try:
64
+ __version__ = importlib.metadata.version(__name__)
65
+ except importlib.metadata.PackageNotFoundError:
66
+ __version__ = "0.0.0" # Fallback for development mode
67
+ __author__ = "Bryan Palmer"
68
+
69
+
66
70
  # --- public API
67
71
  __all__ = (
68
72
  "__version__",
@@ -115,7 +119,6 @@ __all__ = (
115
119
  "series_growth_plot_finalise",
116
120
  "run_plot_finalise",
117
121
  # --- typing information
118
- "ExpectedTypeDict",
119
122
  "FINALISE_KW_TYPES",
120
123
  "BAR_KW_TYPES",
121
124
  "LINE_KW_TYPES",
@@ -46,15 +46,6 @@ def line_plot_finalise(
46
46
  wraps calls to line_plot() and finalise_plot().
47
47
  """
48
48
 
49
- if isinstance(data, DataFrame):
50
- if len(data.columns) > 1:
51
- # default to displaying a legend
52
- kwargs["legend"] = kwargs.get("legend", True)
53
- if len(data.columns) > 4:
54
- # default to using a style for the lines
55
- kwargs["style"] = kwargs.get(
56
- "style", ["solid", "dashed", "dashdot", "dotted"]
57
- )
58
49
  plot_then_finalise(
59
50
  data,
60
51
  function=line_plot,
@@ -249,6 +240,7 @@ if __name__ == "__main__":
249
240
  data=test_frame,
250
241
  title="Test Line Plot",
251
242
  ylabel="Value",
243
+ annotate=True,
252
244
  xlabel=None,
253
245
  )
254
246
 
@@ -306,7 +298,6 @@ if __name__ == "__main__":
306
298
  function=series_growth_plot_finalise,
307
299
  starts=[0, -19],
308
300
  title="Test Multi Start Series Growth Plot: ",
309
- ylabel="Per cent Growth",
310
301
  xlabel=None,
311
302
  )
312
303
 
@@ -18,7 +18,7 @@ from mgplot.finalise_plot import make_legend
18
18
  from mgplot.test import prepare_for_test
19
19
  from mgplot.settings import get_setting, DataT
20
20
  from mgplot.date_utils import set_labels
21
- from mgplot.utilities import annotate_series, check_clean_timeseries
21
+ from mgplot.utilities import annotate_series, check_clean_timeseries, default_rounding
22
22
  from mgplot.kw_type_checking import (
23
23
  validate_kwargs,
24
24
  report_kwargs,
@@ -31,15 +31,22 @@ from mgplot.kw_type_checking import (
31
31
  ANNUAL = "annual"
32
32
  PERIODIC = "periodic"
33
33
 
34
+ ANNOTATE_BAR = "annotate_bar"
35
+ ANNOTATE_LINE = "annotate_line"
36
+ BAR_ROUNDING = "bar_rounding"
37
+ ROUNDING = "rounding"
38
+
39
+
34
40
  RAW_GROWTH_KW_TYPES: Final[ExpectedTypeDict] = {
35
41
  "line_width": (float, int),
36
42
  "line_color": str,
37
43
  "line_style": str,
38
- "annotate_line": (type(None), int, str),
44
+ ANNOTATE_LINE: (type(None), bool, int, str), # None, True or fontsize
45
+ ROUNDING: (type(None), bool, int), # None, True or rounding
39
46
  "bar_width": float,
40
47
  "bar_color": str,
41
- "annotate_bar": (type(None), int, str),
42
- "annotation_rounding": int,
48
+ ANNOTATE_BAR: (type(None), bool, int, str), # None, True or fontsize
49
+ BAR_ROUNDING: (type(None), bool, int), # None, True or rounding
43
50
  "plot_from": (type(None), Period, int),
44
51
  "max_ticks": int,
45
52
  "legend": (type(None), bool, dict, (str, object)),
@@ -108,44 +115,45 @@ def _annotations(
108
115
  ) -> None:
109
116
  """Apply annotations the annual and periodic growth series."""
110
117
 
111
- annotate_line = kwargs.get("annotate_line", "small")
112
- if annotate_line is not None:
113
- annotate_series(
114
- annual,
115
- axes,
116
- rounding=kwargs.get("annotation_rounding", True),
117
- fontsize=annotate_line,
118
- color=kwargs.get("line_color", "darkblue"),
118
+ # --- annotate the end of the line
119
+ annotate_line = kwargs.get(ANNOTATE_LINE, True) #
120
+ if annotate_line is not None and annotate_line is not False:
121
+ fontsize = kwargs.pop("fontsize", annotate_line)
122
+ annotate_series(annual, axes, fontsize=fontsize, **kwargs)
123
+
124
+ # --- annotate each bar
125
+ annotate_bar = kwargs.get(ANNOTATE_BAR, True) # really fontsize
126
+ if annotate_bar is None or annotate_bar is False:
127
+ return
128
+ if annotate_bar is True:
129
+ annotate_bar = "small"
130
+ max_annotations = 25
131
+ if len(periodic) > max_annotations:
132
+ return
133
+
134
+ rounding = kwargs.get(BAR_ROUNDING, True)
135
+ if rounding is None or isinstance(rounding, bool):
136
+ value = periodic.abs().max()
137
+ rounding = default_rounding(value)
138
+ annotate_style = {
139
+ "fontsize": annotate_bar,
140
+ "fontname": "Helvetica",
141
+ }
142
+ adjustment = (periodic.max() - periodic.min()) * 0.01
143
+ for i, value in enumerate(periodic):
144
+ va = "bottom" if value >= 0 else "top"
145
+ text = axes.text(
146
+ periodic.index[i],
147
+ adjustment if value >= 0 else -adjustment,
148
+ f"{value:.{rounding}f}",
149
+ ha="center",
150
+ va=va,
151
+ **annotate_style,
152
+ color="white",
153
+ )
154
+ text.set_path_effects(
155
+ [pe.withStroke(linewidth=2, foreground=kwargs.get("bar_color", "#dd0000"))]
119
156
  )
120
-
121
- annotate_bar = kwargs.get("annotate_bar", "small")
122
- max_annotations = 30
123
- if annotate_bar is not None and len(periodic) < max_annotations:
124
- annotation_rounding = kwargs.get("annotation_rounding", 1)
125
- annotate_style = {
126
- "fontsize": annotate_bar,
127
- "fontname": "Helvetica",
128
- }
129
- adjustment = (periodic.max() - periodic.min()) * 0.005
130
- for i, value in enumerate(periodic):
131
- va = "bottom" if value >= 0 else "top"
132
- text = axes.text(
133
- periodic.index[i],
134
- adjustment if value >= 0 else -adjustment,
135
- f"{value:.{annotation_rounding}f}",
136
- ha="center",
137
- va=va,
138
- **annotate_style,
139
- fontdict=None,
140
- color="white",
141
- )
142
- text.set_path_effects(
143
- [
144
- pe.withStroke(
145
- linewidth=2, foreground=kwargs.get("bar_color", "indianred")
146
- )
147
- ]
148
- )
149
157
 
150
158
 
151
159
  def raw_growth_plot(
@@ -162,15 +170,17 @@ def raw_growth_plot(
162
170
  - line_width: The width of the line (default is 2).
163
171
  - line_color: The color of the line (default is "darkblue").
164
172
  - line_style: The style of the line (default is "-").
165
- - annotate_line: None | int | str - fontsize to annotate the line
166
- (default is "small", which means the line is annotated with
173
+ - annotate_line: None | bool | int | str - fontsize to annotate
174
+ the line (default is "small", which means the line is annotated with
167
175
  small text).
176
+ - rounding: None | bool | int - the number of decimal places to round
177
+ the line (default is 0).
168
178
  - bar_width: The width of the bars (default is 0.8).
169
179
  - bar_color: The color of the bars (default is "indianred").
170
180
  - annotate_bar: None | int | str - fontsize to annotate the bars
171
181
  (default is "small", which means the bars are annotated with
172
182
  small text).
173
- - annotation_rounding: The number of decimal places to round the
183
+ - bar_rounding: The number of decimal places to round the
174
184
  annotations to (default is 1).
175
185
  - plot_from: None | Period | int -- if:
176
186
  - None: the entire series is plotted
@@ -217,8 +227,10 @@ def raw_growth_plot(
217
227
  periodic.name = {"M": "Monthly", "Q": "Quarterly", "D": "Daily"}[
218
228
  PeriodIndex(save_index).freqstr[:1]
219
229
  ] + " Growth"
230
+ color = kwargs.get("bar_color", "#dd0000")
231
+ kwargs["bar_color"] = color # for annotations
220
232
  axes = periodic.plot.bar(
221
- color=kwargs.get("bar_color", "indianred"),
233
+ color=color,
222
234
  width=kwargs.get("bar_width}", 0.8),
223
235
  )
224
236
  thin_threshold = 180
@@ -379,7 +379,7 @@ def validate_kwargs(
379
379
  continue
380
380
  if key not in expected:
381
381
  problems += (
382
- f"{key}: unexpected keyword argument with {value=}in {called_from}.\n"
382
+ f"{key}: keyword not recognised. Has {value=} " + f"in {called_from}.\n"
383
383
  )
384
384
  continue
385
385
  if not _type_check_kwargs(value, expected[key]):
@@ -130,6 +130,14 @@ def line_plot(data: DataT, **kwargs) -> plt.Axes:
130
130
  df = DataFrame(data) # really we are only plotting DataFrames
131
131
  df, kwargs = constrain_data(df, **kwargs)
132
132
 
133
+ # --- some special defaults
134
+ if len(df.columns) > 1:
135
+ # default to displaying a legend
136
+ kwargs["legend"] = kwargs.get("legend", True)
137
+ if len(df.columns) > 4:
138
+ # default to using a style for the lines
139
+ kwargs["style"] = kwargs.get("style", ["solid", "dashed", "dashdot", "dotted"])
140
+
133
141
  # --- Let's plot
134
142
  axes, kwargs = get_axes(**kwargs) # get the axes to plot on
135
143
  if df.empty or df.isna().all().all():
@@ -0,0 +1,19 @@
1
+ echo "black"
2
+ black *.py
3
+
4
+ echo "pylint"
5
+ pylint *.py
6
+
7
+ echo "mypy"
8
+ mypy *.py
9
+
10
+ echo "ruff"
11
+ ruff check *.py
12
+
13
+ # report any lint overrides
14
+ echo " "
15
+ echo "Check linting overrides ..."
16
+ grep "# type" *.py
17
+ grep "# pylint" *.py
18
+ grep --regexp="from typing import .*cast" *.py
19
+ grep "cast(" *.py
@@ -36,7 +36,7 @@ Note: rather than pass the kwargs dict directly, we will re-pack-it
36
36
  """
37
37
 
38
38
  # --- imports
39
- from typing import Any, Callable, Final
39
+ from typing import Callable, Final
40
40
  from collections.abc import Iterable
41
41
  from pandas import Period, DataFrame, Series, period_range
42
42
  from numpy import random
@@ -85,8 +85,7 @@ EXPECTED_CALLABLES: Final[dict[Callable, ExpectedTypeDict]] = {
85
85
  # --- private functions
86
86
  def first_unchain(
87
87
  function: Callable | list[Callable],
88
- **kwargs,
89
- ) -> tuple[Callable, dict[str, Any]]:
88
+ ) -> tuple[Callable, list[Callable]]:
90
89
  """
91
90
  Extract the first Callable from function (which may be
92
91
  a stand alone Callable or a nonr-empty list of Callables).
@@ -94,12 +93,10 @@ def first_unchain(
94
93
  This allows for chaining multiple functions together.
95
94
 
96
95
  Parameters
97
- - kwargs - keyword arguments
96
+ - function - a Callable or a non-empty list of Callables
98
97
 
99
- Returns a tuple containing the first function and the updated kwargs.
100
- if function is a list of Callables, the first function will be removed
101
- from the the list, and the remaining functions will be stored in a
102
- list under the key "function" in kwargs.
98
+ Returns a tuple containing the first function and a list of the remaining
99
+ functions (which may be empty if there are no remaining functions).
103
100
 
104
101
  Raises ValueError if function is an empty list.
105
102
 
@@ -117,10 +114,7 @@ def first_unchain(
117
114
  else:
118
115
  raise ValueError(error_msg)
119
116
 
120
- if rest:
121
- kwargs["function"] = rest
122
-
123
- return first, kwargs
117
+ return first, rest
124
118
 
125
119
 
126
120
  # --- public functions
@@ -151,8 +145,17 @@ def plot_then_finalise(
151
145
  # data is not checked here, assume it is checked by the called
152
146
  # plot function.
153
147
 
154
- # --- check the function argument
155
- first, kwargs_ = first_unchain(function, **kwargs)
148
+ first, kwargs["function"] = first_unchain(function)
149
+ if not kwargs["function"]:
150
+ del kwargs["function"] # remove the function key if it is empty
151
+
152
+ bad_next = (multi_start, multi_column)
153
+ if first in bad_next:
154
+ # these functions should not be called by plot_then_finalise()
155
+ raise ValueError(
156
+ f"[{', '.join(k.__name__ for k in bad_next)}] should not be called by {me}. "
157
+ "Call them before calling {me}. "
158
+ )
156
159
 
157
160
  if first in EXPECTED_CALLABLES:
158
161
  expected = EXPECTED_CALLABLES[first]
@@ -161,10 +164,12 @@ def plot_then_finalise(
161
164
  # this is an unexpected Callable, so we will give it a try
162
165
  print(f"Unknown proposed function: {first}; nonetheless, will give it a try.")
163
166
  expected = {}
164
- plot_kwargs = kwargs_
165
- validate_kwargs(expected | FINALISE_KW_TYPES, me, **plot_kwargs)
167
+ plot_kwargs = kwargs.copy()
166
168
 
167
- # --- call the first function with the data and kwargs
169
+ # --- validate the original kwargs (could not do before now)
170
+ validate_kwargs(FINALISE_KW_TYPES | expected, me, **kwargs)
171
+
172
+ # --- call the first function with the data and selected plot kwargs
168
173
  axes = first(data, **plot_kwargs)
169
174
 
170
175
  # --- remove potentially overlapping kwargs
@@ -215,7 +220,9 @@ def multi_start(
215
220
 
216
221
  # --- check the function argument
217
222
  original_tag: Final[str] = kwargs.get("tag", "")
218
- first, kwargs = first_unchain(function, **kwargs)
223
+ first, kwargs["function"] = first_unchain(function)
224
+ if not kwargs["function"]:
225
+ del kwargs["function"] # remove the function key if it is empty
219
226
 
220
227
  # --- iterate over the starts
221
228
  for i, start in enumerate(starts):
@@ -255,7 +262,9 @@ def multi_column(
255
262
  # --- check the function argument
256
263
  title_stem = kwargs.get("title", "")
257
264
  tag: Final[str] = kwargs.get("tag", "")
258
- first, kwargs = first_unchain(function, **kwargs)
265
+ first, kwargs["function"] = first_unchain(function)
266
+ if not kwargs["function"]:
267
+ del kwargs["function"] # remove the function key if it is empty
259
268
 
260
269
  # --- iterate over the columns
261
270
  for i, col in enumerate(data.columns):
@@ -304,7 +313,8 @@ if __name__ == "__main__":
304
313
  [multi_start, plot_then_finalise, line_plot],
305
314
  title="Test Multi Column / Multi start: ",
306
315
  starts=[None, 180],
307
- verbose=False,
316
+ garbage=True, # this is an unexpected argument - should trigger a warning
317
+ annotate=True, # this is an expected argument
308
318
  )
309
319
 
310
320
  # bar plot
@@ -327,10 +337,6 @@ if __name__ == "__main__":
327
337
  ylabel="Y-axis Label",
328
338
  )
329
339
 
330
-
331
- # --- test
332
- if __name__ == "__main__":
333
-
334
340
  # --- check that this fails
335
341
  try:
336
342
  multi_start(
@@ -341,7 +347,7 @@ if __name__ == "__main__":
341
347
  except (ValueError, TypeError) as e:
342
348
  print(f"Expected error: {e}")
343
349
 
344
- # --- check that tjis fails
350
+ # --- check that this fails
345
351
  try:
346
352
  multi_column(
347
353
  data=Series([1, 2, 3]), # type: ignore # Series is not a DataFrame
@@ -358,3 +364,16 @@ if __name__ == "__main__":
358
364
  )
359
365
  except (ValueError, TypeError) as e:
360
366
  print(f"Expected error: {e}")
367
+
368
+ # --- check this fails
369
+ try:
370
+ plot_then_finalise(
371
+ data=Series([1, 2, 3]),
372
+ function=[multi_start, bar_plot],
373
+ starts=[0, 1],
374
+ title="Test Multi Start with Bar Plot",
375
+ xlabel="X-axis Label",
376
+ ylabel="Y-axis Label",
377
+ )
378
+ except (ValueError, TypeError) as e:
379
+ print(f"Expected error: {e}")