ggstyle 0.1.0__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 (35) hide show
  1. ggstyle-0.1.0/.gitignore +16 -0
  2. ggstyle-0.1.0/CHANGELOG.md +23 -0
  3. ggstyle-0.1.0/CONTRIBUTING.md +44 -0
  4. ggstyle-0.1.0/LICENSE +21 -0
  5. ggstyle-0.1.0/PKG-INFO +268 -0
  6. ggstyle-0.1.0/README.md +225 -0
  7. ggstyle-0.1.0/SECURITY.md +9 -0
  8. ggstyle-0.1.0/docs/source/api.rst +33 -0
  9. ggstyle-0.1.0/docs/source/conf.py +39 -0
  10. ggstyle-0.1.0/docs/source/gettingstarted.rst +49 -0
  11. ggstyle-0.1.0/docs/source/index.rst +29 -0
  12. ggstyle-0.1.0/docs/source/pitfalls.rst +54 -0
  13. ggstyle-0.1.0/docs/source/principles.rst +49 -0
  14. ggstyle-0.1.0/docs/source/release.rst +11 -0
  15. ggstyle-0.1.0/docs/source/user-guide.rst +100 -0
  16. ggstyle-0.1.0/examples/smoke.png +0 -0
  17. ggstyle-0.1.0/examples/smoke.py +61 -0
  18. ggstyle-0.1.0/examples/theme-grey.png +0 -0
  19. ggstyle-0.1.0/examples/theme-minimal.png +0 -0
  20. ggstyle-0.1.0/examples/themes.py +48 -0
  21. ggstyle-0.1.0/pyproject.toml +88 -0
  22. ggstyle-0.1.0/src/ggstyle/__init__.py +35 -0
  23. ggstyle-0.1.0/src/ggstyle/_cadence.py +298 -0
  24. ggstyle-0.1.0/src/ggstyle/_formats.py +118 -0
  25. ggstyle-0.1.0/src/ggstyle/_frames.py +171 -0
  26. ggstyle-0.1.0/src/ggstyle/_parse.py +138 -0
  27. ggstyle-0.1.0/src/ggstyle/dates.py +1355 -0
  28. ggstyle-0.1.0/src/ggstyle/py.typed +1 -0
  29. ggstyle-0.1.0/src/ggstyle/theme.py +191 -0
  30. ggstyle-0.1.0/src/ggstyle/themes/ggstyle-grey.mplstyle +69 -0
  31. ggstyle-0.1.0/src/ggstyle/themes/ggstyle-minimal.mplstyle +71 -0
  32. ggstyle-0.1.0/tests/test_dates.py +542 -0
  33. ggstyle-0.1.0/tests/test_frames_themes.py +200 -0
  34. ggstyle-0.1.0/tests/test_parse_cadence.py +127 -0
  35. ggstyle-0.1.0/tools/validate_docstrings.py +60 -0
@@ -0,0 +1,16 @@
1
+ .DS_Store
2
+ .venv/
3
+ __pycache__/
4
+ *.py[cod]
5
+ *.egg-info/
6
+ .pytest_cache/
7
+ .ruff_cache/
8
+ .mypy_cache/
9
+ .coverage
10
+ htmlcov/
11
+ build/
12
+ dist/
13
+ docs/build/
14
+ docs/source/generated/
15
+ *.tar.gz
16
+ .release-inputs/
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ This project follows [Semantic Versioning](https://semver.org/).
4
+
5
+ ## 0.1.0 - 2026-08-19
6
+
7
+ Initial public release.
8
+
9
+ - Add a date-axis handle that adopts existing matplotlib axes.
10
+ - Add independent tick cadence, label formatting, range, and grid controls.
11
+ - Add observation-based gap collapsing and date-space annotations.
12
+ - Add pandas, polars, pyarrow, NumPy, and plain-sequence date extraction.
13
+ - Add opt-in minimal and grey themes without import-time global state changes.
14
+ - Add NumPy-style API documentation and a warning-free Sphinx user guide modeled on the
15
+ documentation structure used by statsmodels.
16
+ - Replace loosely typed annotation dictionaries with explicit internal state objects and
17
+ broaden automated clean-code checks.
18
+ - Add structured ``AxisSummary`` metadata and captions generated from the same source.
19
+ - Add explicit ``missing="raise"`` and ``missing="drop"`` date policies.
20
+ - Add ``sync_dates`` for comparable date coordinates and limits across multiple panels.
21
+
22
+ Collapsed mode currently remaps line artists only. Collection remapping remains planned
23
+ for a later release.
@@ -0,0 +1,44 @@
1
+ # Contributing
2
+
3
+ Bug reports and focused pull requests are welcome. For behavior changes, open an issue
4
+ first so the public API and date-axis semantics can be agreed before implementation.
5
+
6
+ ## Development
7
+
8
+ Use Python 3.10 or newer:
9
+
10
+ ```bash
11
+ python -m venv .venv
12
+ .venv/bin/python -m pip install -e ".[dev]"
13
+ .venv/bin/python -m pytest -q --cov=ggstyle --cov-report=term-missing
14
+ .venv/bin/ruff check .
15
+ .venv/bin/mypy src
16
+ .venv/bin/python tools/validate_docstrings.py
17
+ .venv/bin/python -m sphinx -W --keep-going -b html docs/source docs/build/html
18
+ ```
19
+
20
+ Public functions, classes, methods, and attributes use the NumPy docstring standard, as
21
+ in statsmodels. New functionality also belongs in the appropriate page under
22
+ `docs/source` and in the changelog. Documentation must build without warnings.
23
+
24
+ New behavior needs tests. Changes to collapsed coordinates should test both collapsed and
25
+ expanded modes, including switching between them. Do not commit generated distributions,
26
+ virtual environments, caches, or platform metadata.
27
+
28
+ By contributing, you agree that your contributions are licensed under the MIT License.
29
+
30
+ ## Releasing
31
+
32
+ Releases use PyPI trusted publishing; maintainers must not store a long-lived PyPI token
33
+ in GitHub. Before the first release, register a pending publisher for project ``ggstyle``
34
+ on PyPI with these values:
35
+
36
+ - Owner: ``joshuamyers22``
37
+ - Repository: ``ggstyle``
38
+ - Workflow: ``publish.yml``
39
+ - Environment: ``pypi``
40
+
41
+ After the release commit passes CI, create and push a tag matching the package version,
42
+ for example ``v0.1.0``. The publish workflow independently repeats the test, type,
43
+ documentation, and package checks; publishes the distributions to PyPI; and creates the
44
+ GitHub release only after publication succeeds.
ggstyle-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Joshua Myers
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
ggstyle-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,268 @@
1
+ Metadata-Version: 2.5
2
+ Name: ggstyle
3
+ Version: 0.1.0
4
+ Summary: A date axis for matplotlib that is easy to use and manipulate
5
+ Project-URL: Homepage, https://github.com/joshuamyers22/ggstyle
6
+ Project-URL: Repository, https://github.com/joshuamyers22/ggstyle
7
+ Project-URL: Issues, https://github.com/joshuamyers22/ggstyle/issues
8
+ Project-URL: Changelog, https://github.com/joshuamyers22/ggstyle/blob/main/CHANGELOG.md
9
+ Author-email: Joshua Myers <joshua.myers22@gmail.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: dates,ggplot2,matplotlib,visualization
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering :: Visualization
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: matplotlib>=3.7
25
+ Requires-Dist: numpy>=1.24
26
+ Requires-Dist: pandas>=2.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: build; extra == 'dev'
29
+ Requires-Dist: mypy; extra == 'dev'
30
+ Requires-Dist: pandas-stubs; extra == 'dev'
31
+ Requires-Dist: polars>=1.0; extra == 'dev'
32
+ Requires-Dist: pytest-cov; extra == 'dev'
33
+ Requires-Dist: pytest>=7.0; extra == 'dev'
34
+ Requires-Dist: ruff; extra == 'dev'
35
+ Requires-Dist: twine; extra == 'dev'
36
+ Provides-Extra: docs
37
+ Requires-Dist: numpydoc>=1.6; extra == 'docs'
38
+ Requires-Dist: pydata-sphinx-theme>=0.15; extra == 'docs'
39
+ Requires-Dist: sphinx>=7; extra == 'docs'
40
+ Provides-Extra: polars
41
+ Requires-Dist: polars>=1.0; extra == 'polars'
42
+ Description-Content-Type: text/markdown
43
+
44
+ # ggstyle
45
+
46
+ A date axis for matplotlib that is easy to use and easy to manipulate.
47
+
48
+ **v0.1 is the date axis plus themes.** No palettes module and no `line()` yet — those
49
+ remain future additions once the axis ergonomics have real usage behind them.
50
+
51
+ This is the first public release. The date-axis behavior is tested, but the project is
52
+ still young and follows semantic versioning. See the [known limits](#known-limits-in-v01)
53
+ before using collapsed mode in production.
54
+
55
+ ## Why
56
+
57
+ Most of the pain in Python time-series plotting is not the grammar, it's the axis: ticks in
58
+ the wrong places, labels rotated to hide the fact that there are too many of them, weekend
59
+ gaps shredding an intraday chart, and annotation code that quietly puts your vertical line
60
+ three days off. `ggstyle` fixes the axis first.
61
+
62
+ ## Install
63
+
64
+ ```bash
65
+ pip install ggstyle
66
+ ```
67
+
68
+ For development from a clone:
69
+
70
+ ```bash
71
+ pip install -e ".[dev]"
72
+ ```
73
+
74
+ ## Use
75
+
76
+ It adopts any Axes, including plots it never made:
77
+
78
+ ```python
79
+ import matplotlib.pyplot as plt
80
+ import ggstyle as gs
81
+
82
+ gs.use_theme() # "minimal" is the default
83
+
84
+ fig, ax = plt.subplots()
85
+ ax.plot(df["date"], df["close"]) # plain matplotlib, seaborn, or df.plot()
86
+
87
+ gs.dates(ax).ticks("quarterly").fmt("month-year").zoom("2020", "2022")
88
+ ```
89
+
90
+ Configuration and drawing methods return the handle, so calls chain.
91
+
92
+ Axis semantics are also available as structured data rather than only rendered output:
93
+
94
+ ```python
95
+ summary = gs.dates(ax).summary()
96
+ caption = gs.dates(ax).caption(add=True)
97
+ ```
98
+
99
+ ### Ticks — where they go
100
+
101
+ ```python
102
+ .ticks("monthly") # daily | weekly | monthly | quarterly | yearly
103
+ .ticks("month-end") # anchored: month-start, quarter-end, year-start, ...
104
+ .ticks(every="3M") # any offset alias; legacy M/Q/Y/H accepted
105
+ .ticks(n=6) # about six ticks, snapped to a natural cadence
106
+ .ticks(at=["2020-01-01", "2021-07-01"])
107
+ .ticks(major="yearly", minor="monthly")
108
+ ```
109
+
110
+ Anchoring is not cosmetic: month-start vs. month-end is the difference between labels that
111
+ line up with your observations and labels that float between them.
112
+
113
+ ### Labels — what they say
114
+
115
+ ```python
116
+ .fmt("concise") # default: year shown once, not on every label
117
+ .fmt("month-year") # Jun 2020
118
+ .fmt("quarter") # Q2 2020
119
+ .fmt("year") / .fmt("month") / .fmt("day") / .fmt("iso") / .fmt("time")
120
+ .fmt("%b '%y") # any strftime string
121
+ .fmt(lambda d: f"week {d.isocalendar().week}")
122
+ ```
123
+
124
+ Changing the format never moves a tick, and changing the cadence never changes the format.
125
+ That orthogonality is a test, not an aspiration.
126
+
127
+ ### Range
128
+
129
+ Partial strings expand to whole periods, pandas-style:
130
+
131
+ ```python
132
+ .zoom("2020", "2022") # three complete years
133
+ .zoom("2020-03", None) # open-ended
134
+ .zoom(last="6M") # trailing window from the last observation, not from today
135
+ .zoom(ytd=True)
136
+ .pad(left="1M", right="1M")
137
+ ```
138
+
139
+ ### Gaps
140
+
141
+ ```python
142
+ .collapse() # unobserved dates get no space
143
+ .expand() # true datetime axis, gaps restored
144
+ ```
145
+
146
+ Collapsed mode is defined by **the dates present in your data**, not by a holiday calendar.
147
+ Anything not observed is not allocated space. That is correct for any market or region and
148
+ needs no extra dependency. With several series, the axis uses the union of observed dates.
149
+
150
+ ### Annotation in date space
151
+
152
+ Every one of these is correct in both modes — that is the whole point of the handle:
153
+
154
+ ```python
155
+ .loc("2020-03-23") # -> axis position; the escape-hatch primitive
156
+ .vline("2020-03-23", label="trough")
157
+ .span("2020-02-19", "2020-03-23", label="drawdown")
158
+ .spans(events_df, start="begin", end="end", label="name")
159
+ .grid("yearly") # gridline cadence, independent of ticks
160
+ ```
161
+
162
+ In collapsed mode a date that falls inside a gap (a Sunday, a holiday) is placed by linear
163
+ interpolation between its neighbours. `loc(date, snap=True)` rounds to the nearest
164
+ observation instead; `loc(date, strict=True)` raises if the date was never observed.
165
+
166
+ ### Escape hatch
167
+
168
+ `.loc()` is the primitive that keeps raw matplotlib correct:
169
+
170
+ ```python
171
+ handle = gs.dates(ax).collapse()
172
+ ax.axvline(handle.loc("2020-03-23")) # lands in the right place
173
+ ax.set_xlim(handle.loc("2020-01"), handle.loc("2021-01"))
174
+ ```
175
+
176
+ ## Themes
177
+
178
+ Two ship. `minimal` is the default; `grey` is the ggplot2 `theme_grey` analogue.
179
+
180
+ ```python
181
+ gs.use_theme() # minimal, process-wide
182
+ gs.use_theme("grey") # "gray" also accepted
183
+
184
+ with gs.theme("grey"): # scoped; restores every rcParam on exit
185
+ ...
186
+
187
+ plt.style.use(gs.stylesheet()) # the .mplstyle on its own, no ggstyle import needed
188
+ ```
189
+
190
+ Both spell out the same type scale, colour cycle, and layout, so switching changes the
191
+ panel surface and nothing else — the same separation ggplot2 makes. The colour cycle is
192
+ Okabe–Ito-derived and capped at eight; past eight, direct labelling or faceting is the
193
+ right answer, not a ninth colour.
194
+
195
+ Importing `ggstyle` never mutates `rcParams`. Theming is always something you ask for.
196
+
197
+ Almost all of it is plain rcParams in a `.mplstyle` file, including spine removal
198
+ (`axes.spines.left: False`), which an earlier draft of the design wrongly assumed needed
199
+ Python.
200
+
201
+ ## Data frames
202
+
203
+ pandas and polars both work, as do pyarrow arrays, numpy `datetime64`, and plain lists:
204
+
205
+ ```python
206
+ gs.dates(ax, data=frame["date"]) # pandas Series, polars Series, or Index
207
+ ```
208
+
209
+ Polars is detected by module name rather than imported, so installing `ggstyle` never
210
+ pulls it in and pandas-only users pay nothing for the support. Timezone-aware input from
211
+ either library is converted to UTC instants for positioning; display timezones stay a
212
+ separate concern handled by `.tz()`.
213
+
214
+ Two things are deliberately *not* guessed: a whole DataFrame passed where a column was
215
+ meant, and a string column that might be dates. Both raise.
216
+
217
+ Missing values in explicit date data also raise unless exclusion is requested with
218
+ ``missing="drop"``. The number excluded remains available through ``.summary()`` and in
219
+ generated captions.
220
+
221
+ ## Multiple panels
222
+
223
+ Synchronize comparable axes with a common observation registry and limits:
224
+
225
+ ```python
226
+ handles = gs.sync_dates(axes, mode="collapse", limits="union")
227
+ ```
228
+
229
+ This prevents the same date from receiving different ordinal positions in independently
230
+ collapsed panels.
231
+
232
+ ## Design rules
233
+
234
+ - The date axis is a standalone object, not a side effect of plotting.
235
+ - Importing the package is inert; theming is opt-in.
236
+ - Placement, labels, gridline cadence, and range are four independent knobs.
237
+ - Fail loudly: a non-date axis raises, and mixed tz-aware/naive input raises rather than
238
+ guessing UTC.
239
+ - Never resample or interpolate the data silently.
240
+ - Never rotate tick labels by default. Rotation is a symptom of bad tick selection.
241
+
242
+ ## Known limits in v0.1
243
+
244
+ - Collapsed mode remaps `Line2D` artists only. Collections (`fill_between`, `scatter`) are
245
+ not yet remapped; annotate through the handle instead.
246
+ - Native `ax.axvline(timestamp)` is still wrong in collapsed mode — go through `.loc()`.
247
+ A registered matplotlib scale would remove that caveat and is the v0.2 candidate.
248
+ - `.tz()` assumes naive data is UTC when converting for display.
249
+ - No palettes module yet: the colour cycle lives in the stylesheets.
250
+
251
+ ## Tests
252
+
253
+ ```bash
254
+ python -m pytest -q
255
+ ruff check .
256
+ mypy src
257
+ ```
258
+
259
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the complete development workflow and
260
+ [SECURITY.md](SECURITY.md) for vulnerability reporting.
261
+
262
+ The structured documentation follows the same user-guide, API-reference, pitfalls, and
263
+ release-note separation used by statsmodels. Build it locally with:
264
+
265
+ ```bash
266
+ pip install -e ".[docs]"
267
+ python -m sphinx -W --keep-going -b html docs/source docs/build/html
268
+ ```
@@ -0,0 +1,225 @@
1
+ # ggstyle
2
+
3
+ A date axis for matplotlib that is easy to use and easy to manipulate.
4
+
5
+ **v0.1 is the date axis plus themes.** No palettes module and no `line()` yet — those
6
+ remain future additions once the axis ergonomics have real usage behind them.
7
+
8
+ This is the first public release. The date-axis behavior is tested, but the project is
9
+ still young and follows semantic versioning. See the [known limits](#known-limits-in-v01)
10
+ before using collapsed mode in production.
11
+
12
+ ## Why
13
+
14
+ Most of the pain in Python time-series plotting is not the grammar, it's the axis: ticks in
15
+ the wrong places, labels rotated to hide the fact that there are too many of them, weekend
16
+ gaps shredding an intraday chart, and annotation code that quietly puts your vertical line
17
+ three days off. `ggstyle` fixes the axis first.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ pip install ggstyle
23
+ ```
24
+
25
+ For development from a clone:
26
+
27
+ ```bash
28
+ pip install -e ".[dev]"
29
+ ```
30
+
31
+ ## Use
32
+
33
+ It adopts any Axes, including plots it never made:
34
+
35
+ ```python
36
+ import matplotlib.pyplot as plt
37
+ import ggstyle as gs
38
+
39
+ gs.use_theme() # "minimal" is the default
40
+
41
+ fig, ax = plt.subplots()
42
+ ax.plot(df["date"], df["close"]) # plain matplotlib, seaborn, or df.plot()
43
+
44
+ gs.dates(ax).ticks("quarterly").fmt("month-year").zoom("2020", "2022")
45
+ ```
46
+
47
+ Configuration and drawing methods return the handle, so calls chain.
48
+
49
+ Axis semantics are also available as structured data rather than only rendered output:
50
+
51
+ ```python
52
+ summary = gs.dates(ax).summary()
53
+ caption = gs.dates(ax).caption(add=True)
54
+ ```
55
+
56
+ ### Ticks — where they go
57
+
58
+ ```python
59
+ .ticks("monthly") # daily | weekly | monthly | quarterly | yearly
60
+ .ticks("month-end") # anchored: month-start, quarter-end, year-start, ...
61
+ .ticks(every="3M") # any offset alias; legacy M/Q/Y/H accepted
62
+ .ticks(n=6) # about six ticks, snapped to a natural cadence
63
+ .ticks(at=["2020-01-01", "2021-07-01"])
64
+ .ticks(major="yearly", minor="monthly")
65
+ ```
66
+
67
+ Anchoring is not cosmetic: month-start vs. month-end is the difference between labels that
68
+ line up with your observations and labels that float between them.
69
+
70
+ ### Labels — what they say
71
+
72
+ ```python
73
+ .fmt("concise") # default: year shown once, not on every label
74
+ .fmt("month-year") # Jun 2020
75
+ .fmt("quarter") # Q2 2020
76
+ .fmt("year") / .fmt("month") / .fmt("day") / .fmt("iso") / .fmt("time")
77
+ .fmt("%b '%y") # any strftime string
78
+ .fmt(lambda d: f"week {d.isocalendar().week}")
79
+ ```
80
+
81
+ Changing the format never moves a tick, and changing the cadence never changes the format.
82
+ That orthogonality is a test, not an aspiration.
83
+
84
+ ### Range
85
+
86
+ Partial strings expand to whole periods, pandas-style:
87
+
88
+ ```python
89
+ .zoom("2020", "2022") # three complete years
90
+ .zoom("2020-03", None) # open-ended
91
+ .zoom(last="6M") # trailing window from the last observation, not from today
92
+ .zoom(ytd=True)
93
+ .pad(left="1M", right="1M")
94
+ ```
95
+
96
+ ### Gaps
97
+
98
+ ```python
99
+ .collapse() # unobserved dates get no space
100
+ .expand() # true datetime axis, gaps restored
101
+ ```
102
+
103
+ Collapsed mode is defined by **the dates present in your data**, not by a holiday calendar.
104
+ Anything not observed is not allocated space. That is correct for any market or region and
105
+ needs no extra dependency. With several series, the axis uses the union of observed dates.
106
+
107
+ ### Annotation in date space
108
+
109
+ Every one of these is correct in both modes — that is the whole point of the handle:
110
+
111
+ ```python
112
+ .loc("2020-03-23") # -> axis position; the escape-hatch primitive
113
+ .vline("2020-03-23", label="trough")
114
+ .span("2020-02-19", "2020-03-23", label="drawdown")
115
+ .spans(events_df, start="begin", end="end", label="name")
116
+ .grid("yearly") # gridline cadence, independent of ticks
117
+ ```
118
+
119
+ In collapsed mode a date that falls inside a gap (a Sunday, a holiday) is placed by linear
120
+ interpolation between its neighbours. `loc(date, snap=True)` rounds to the nearest
121
+ observation instead; `loc(date, strict=True)` raises if the date was never observed.
122
+
123
+ ### Escape hatch
124
+
125
+ `.loc()` is the primitive that keeps raw matplotlib correct:
126
+
127
+ ```python
128
+ handle = gs.dates(ax).collapse()
129
+ ax.axvline(handle.loc("2020-03-23")) # lands in the right place
130
+ ax.set_xlim(handle.loc("2020-01"), handle.loc("2021-01"))
131
+ ```
132
+
133
+ ## Themes
134
+
135
+ Two ship. `minimal` is the default; `grey` is the ggplot2 `theme_grey` analogue.
136
+
137
+ ```python
138
+ gs.use_theme() # minimal, process-wide
139
+ gs.use_theme("grey") # "gray" also accepted
140
+
141
+ with gs.theme("grey"): # scoped; restores every rcParam on exit
142
+ ...
143
+
144
+ plt.style.use(gs.stylesheet()) # the .mplstyle on its own, no ggstyle import needed
145
+ ```
146
+
147
+ Both spell out the same type scale, colour cycle, and layout, so switching changes the
148
+ panel surface and nothing else — the same separation ggplot2 makes. The colour cycle is
149
+ Okabe–Ito-derived and capped at eight; past eight, direct labelling or faceting is the
150
+ right answer, not a ninth colour.
151
+
152
+ Importing `ggstyle` never mutates `rcParams`. Theming is always something you ask for.
153
+
154
+ Almost all of it is plain rcParams in a `.mplstyle` file, including spine removal
155
+ (`axes.spines.left: False`), which an earlier draft of the design wrongly assumed needed
156
+ Python.
157
+
158
+ ## Data frames
159
+
160
+ pandas and polars both work, as do pyarrow arrays, numpy `datetime64`, and plain lists:
161
+
162
+ ```python
163
+ gs.dates(ax, data=frame["date"]) # pandas Series, polars Series, or Index
164
+ ```
165
+
166
+ Polars is detected by module name rather than imported, so installing `ggstyle` never
167
+ pulls it in and pandas-only users pay nothing for the support. Timezone-aware input from
168
+ either library is converted to UTC instants for positioning; display timezones stay a
169
+ separate concern handled by `.tz()`.
170
+
171
+ Two things are deliberately *not* guessed: a whole DataFrame passed where a column was
172
+ meant, and a string column that might be dates. Both raise.
173
+
174
+ Missing values in explicit date data also raise unless exclusion is requested with
175
+ ``missing="drop"``. The number excluded remains available through ``.summary()`` and in
176
+ generated captions.
177
+
178
+ ## Multiple panels
179
+
180
+ Synchronize comparable axes with a common observation registry and limits:
181
+
182
+ ```python
183
+ handles = gs.sync_dates(axes, mode="collapse", limits="union")
184
+ ```
185
+
186
+ This prevents the same date from receiving different ordinal positions in independently
187
+ collapsed panels.
188
+
189
+ ## Design rules
190
+
191
+ - The date axis is a standalone object, not a side effect of plotting.
192
+ - Importing the package is inert; theming is opt-in.
193
+ - Placement, labels, gridline cadence, and range are four independent knobs.
194
+ - Fail loudly: a non-date axis raises, and mixed tz-aware/naive input raises rather than
195
+ guessing UTC.
196
+ - Never resample or interpolate the data silently.
197
+ - Never rotate tick labels by default. Rotation is a symptom of bad tick selection.
198
+
199
+ ## Known limits in v0.1
200
+
201
+ - Collapsed mode remaps `Line2D` artists only. Collections (`fill_between`, `scatter`) are
202
+ not yet remapped; annotate through the handle instead.
203
+ - Native `ax.axvline(timestamp)` is still wrong in collapsed mode — go through `.loc()`.
204
+ A registered matplotlib scale would remove that caveat and is the v0.2 candidate.
205
+ - `.tz()` assumes naive data is UTC when converting for display.
206
+ - No palettes module yet: the colour cycle lives in the stylesheets.
207
+
208
+ ## Tests
209
+
210
+ ```bash
211
+ python -m pytest -q
212
+ ruff check .
213
+ mypy src
214
+ ```
215
+
216
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the complete development workflow and
217
+ [SECURITY.md](SECURITY.md) for vulnerability reporting.
218
+
219
+ The structured documentation follows the same user-guide, API-reference, pitfalls, and
220
+ release-note separation used by statsmodels. Build it locally with:
221
+
222
+ ```bash
223
+ pip install -e ".[docs]"
224
+ python -m sphinx -W --keep-going -b html docs/source docs/build/html
225
+ ```
@@ -0,0 +1,9 @@
1
+ # Security policy
2
+
3
+ `ggstyle` is a local plotting library: it does not make network requests, execute input,
4
+ or deserialize untrusted objects. Treat custom matplotlib formatters and user-provided
5
+ callables as trusted Python code.
6
+
7
+ Please report a suspected vulnerability privately to `joshua.myers22@gmail.com` rather
8
+ than opening a public issue. Include the affected version, a minimal reproducer, and the
9
+ impact. Security fixes are made on the latest public release only.
@@ -0,0 +1,33 @@
1
+ API reference
2
+ =============
3
+
4
+ The top-level API contains the date-axis handle, cadence specification, and opt-in theme
5
+ helpers. Internal modules are not compatibility guarantees.
6
+
7
+ Date axes
8
+ ---------
9
+
10
+ .. autofunction:: ggstyle.dates
11
+
12
+ .. autoclass:: ggstyle.DateAxis
13
+ :members:
14
+
15
+ .. autoclass:: ggstyle.AxisSummary
16
+ :members:
17
+
18
+ .. autoclass:: ggstyle.Cadence
19
+ :members:
20
+
21
+ .. autofunction:: ggstyle.sync_dates
22
+
23
+ Themes
24
+ ------
25
+
26
+ .. autofunction:: ggstyle.use_theme
27
+
28
+ .. autoclass:: ggstyle.theme
29
+ :members:
30
+
31
+ .. autofunction:: ggstyle.stylesheet
32
+
33
+ .. autofunction:: ggstyle.available_themes
@@ -0,0 +1,39 @@
1
+ """Sphinx configuration for the ggstyle documentation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib.metadata import version as metadata_version
6
+
7
+ project = "ggstyle"
8
+ author = "Joshua Myers"
9
+ copyright = "2026, Joshua Myers"
10
+ release = metadata_version("ggstyle")
11
+
12
+ extensions = [
13
+ "sphinx.ext.autodoc",
14
+ "sphinx.ext.doctest",
15
+ "sphinx.ext.viewcode",
16
+ "numpydoc",
17
+ ]
18
+
19
+ autodoc_typehints = "none"
20
+ numpydoc_class_members_toctree = False
21
+ numpydoc_show_class_members = False
22
+ numpydoc_xref_param_type = False
23
+
24
+ doctest_global_setup = """
25
+ from ggstyle import Cadence, available_themes, dates, use_theme
26
+ """
27
+ doctest_global_cleanup = """
28
+ import matplotlib.pyplot as plt
29
+ plt.close("all")
30
+ """
31
+
32
+ html_theme = "pydata_sphinx_theme"
33
+ html_title = f"ggstyle {release}"
34
+ html_theme_options = {
35
+ "github_url": "https://github.com/joshuamyers22/ggstyle",
36
+ "show_toc_level": 2,
37
+ }
38
+
39
+ exclude_patterns = ["_build", "generated"]