charite-plot 0.1.1__tar.gz → 0.2.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.
- {charite_plot-0.1.1 → charite_plot-0.2.0}/CHANGELOG.md +11 -0
- charite_plot-0.2.0/CITATION.cff +19 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/LICENSE.md +4 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/PKG-INFO +26 -1
- {charite_plot-0.1.1 → charite_plot-0.2.0}/README.md +21 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/charite_plot/__init__.py +1 -1
- charite_plot-0.2.0/charite_plot/_theme_common.py +55 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/charite_plot/altair_themes.py +61 -38
- charite_plot-0.2.0/charite_plot/mpl_themes.py +188 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/docs/altair.md +14 -0
- charite_plot-0.2.0/docs/assets/theme_example.png +0 -0
- charite_plot-0.2.0/docs/citing.md +20 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/docs/matplotlib.md +14 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/examples/mpl_showcase.py +3 -1
- charite_plot-0.2.0/examples/palette_preview.py +61 -0
- charite_plot-0.2.0/examples/poly_showcase.py +107 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/mkdocs.yml +1 -0
- charite_plot-0.1.1/charite_plot/mpl_themes.py +0 -129
- charite_plot-0.1.1/docs/assets/theme_example.png +0 -0
- charite_plot-0.1.1/examples/palette_preview.py +0 -47
- charite_plot-0.1.1/examples/poly_showcase.py +0 -108
- {charite_plot-0.1.1 → charite_plot-0.2.0}/.github/workflows/ci.yml +0 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/.gitignore +0 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/charite_plot/colors.py +0 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/charite_plot/fonts.py +0 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/charite_plot/palettes.py +0 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/charite_plot/py.typed +0 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/docs/api/altair_themes.md +0 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/docs/api/colors.md +0 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/docs/api/fonts.md +0 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/docs/api/mpl_themes.md +0 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/docs/api/palettes.md +0 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/docs/assets/palette_preview.png +0 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/docs/colors.md +0 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/docs/index.md +0 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/docs/installation.md +0 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/docs/palettes.md +0 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/docs/stylesheets/extra.css +0 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/examples/altair_showcase.py +0 -0
- {charite_plot-0.1.1 → charite_plot-0.2.0}/pyproject.toml +0 -0
|
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.2.0] - 2026-07-01
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `theme_charite` (matplotlib) gained `interactive` and `tiny_margins` parameters.
|
|
13
|
+
- `theme_charite` (Altair) gained `thickness` and `tiny_margins` parameters, bringing it to parity with the matplotlib backend.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Font-size ramp is now shared between both backends: labels scale at 1× and legend text at 0.8× the base size. Altair legend labels are slightly smaller and axis labels slightly larger as a result.
|
|
18
|
+
|
|
8
19
|
## [0.1.1] - 2026-06-13
|
|
9
20
|
|
|
10
21
|
### Fixed
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use this software, please cite it as below."
|
|
3
|
+
type: software
|
|
4
|
+
title: "charite-plot: Matplotlib and Altair Themes for Charité – Universitätsmedizin Berlin"
|
|
5
|
+
authors:
|
|
6
|
+
- family-names: Ramezani
|
|
7
|
+
given-names: Pedram
|
|
8
|
+
email: pedram.ramezani@charite.de
|
|
9
|
+
version: 0.2.0
|
|
10
|
+
date-released: 2026-07-01
|
|
11
|
+
license: MIT
|
|
12
|
+
url: "https://github.com/pedramezani/charite-plot"
|
|
13
|
+
repository-code: "https://github.com/pedramezani/charite-plot"
|
|
14
|
+
keywords:
|
|
15
|
+
- matplotlib
|
|
16
|
+
- altair
|
|
17
|
+
- theme
|
|
18
|
+
- visualization
|
|
19
|
+
- charite
|
|
@@ -7,3 +7,7 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
|
7
7
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
8
|
|
|
9
9
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
This package is based on the original `charite` R package developed by Johannes Julius Mohn (https://github.com/johannesjuliusm/charite), which is also distributed under the MIT License.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: charite-plot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Matplotlib and Altair themes for Charité – Universitätsmedizin Berlin
|
|
5
5
|
Project-URL: Homepage, https://github.com/pedramezani/charite-plot
|
|
6
6
|
Project-URL: Documentation, https://pedramezani.github.io/charite-plot
|
|
@@ -17,6 +17,10 @@ License: # MIT License
|
|
|
17
17
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
18
18
|
|
|
19
19
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
This package is based on the original `charite` R package developed by Johannes Julius Mohn (https://github.com/johannesjuliusm/charite), which is also distributed under the MIT License.
|
|
20
24
|
License-File: LICENSE.md
|
|
21
25
|
Keywords: altair,charite,matplotlib,theme,visualization
|
|
22
26
|
Classifier: Development Status :: 3 - Alpha
|
|
@@ -140,6 +144,27 @@ The fallback chain follows the official Charité brand guidelines:
|
|
|
140
144
|
apply_theme(theme_charite(font="Arial"))
|
|
141
145
|
```
|
|
142
146
|
|
|
147
|
+
## How to cite
|
|
148
|
+
|
|
149
|
+
If you use charite-plot in your work, please cite it as:
|
|
150
|
+
|
|
151
|
+
```bibtex
|
|
152
|
+
@software{ramezani2026chariteplot,
|
|
153
|
+
author = {Ramezani, Pedram},
|
|
154
|
+
title = {charite-plot: Matplotlib and Altair Themes for Charité – Universitätsmedizin Berlin},
|
|
155
|
+
year = {2026},
|
|
156
|
+
version = {0.2.0},
|
|
157
|
+
url = {https://github.com/pedramezani/charite-plot},
|
|
158
|
+
license = {MIT},
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Acknowledgements
|
|
163
|
+
|
|
164
|
+
This package is based on the original [`charite` R package](https://github.com/johannesjuliusm/charite) developed by Johannes Julius Mohn.
|
|
165
|
+
|
|
143
166
|
## License
|
|
144
167
|
|
|
145
168
|
MIT © 2026 Pedram Ramezani
|
|
169
|
+
|
|
170
|
+
The original `charite` R package by Johannes Julius Mohn is likewise MIT licensed.
|
|
@@ -91,6 +91,27 @@ The fallback chain follows the official Charité brand guidelines:
|
|
|
91
91
|
apply_theme(theme_charite(font="Arial"))
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
+
## How to cite
|
|
95
|
+
|
|
96
|
+
If you use charite-plot in your work, please cite it as:
|
|
97
|
+
|
|
98
|
+
```bibtex
|
|
99
|
+
@software{ramezani2026chariteplot,
|
|
100
|
+
author = {Ramezani, Pedram},
|
|
101
|
+
title = {charite-plot: Matplotlib and Altair Themes for Charité – Universitätsmedizin Berlin},
|
|
102
|
+
year = {2026},
|
|
103
|
+
version = {0.2.0},
|
|
104
|
+
url = {https://github.com/pedramezani/charite-plot},
|
|
105
|
+
license = {MIT},
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Acknowledgements
|
|
110
|
+
|
|
111
|
+
This package is based on the original [`charite` R package](https://github.com/johannesjuliusm/charite) developed by Johannes Julius Mohn.
|
|
112
|
+
|
|
94
113
|
## License
|
|
95
114
|
|
|
96
115
|
MIT © 2026 Pedram Ramezani
|
|
116
|
+
|
|
117
|
+
The original `charite` R package by Johannes Julius Mohn is likewise MIT licensed.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Shared theme primitives for the matplotlib and Altair backends.
|
|
2
|
+
|
|
3
|
+
These small ``NamedTuple`` structures keep the per-backend theme builders
|
|
4
|
+
readable by giving names to the derived size/margin values. The font-size
|
|
5
|
+
ratios are shared so both backends scale identically from their base size
|
|
6
|
+
(matplotlib works in points, Altair in pixels, but the ratios are the same).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import NamedTuple
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FontSizes(NamedTuple):
|
|
13
|
+
"""Derived font sizes, all scaled from the base ``font_size``."""
|
|
14
|
+
title: float
|
|
15
|
+
subtitle: float
|
|
16
|
+
label: float
|
|
17
|
+
legend: float
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Margins(NamedTuple):
|
|
21
|
+
"""Derived margins/paddings around the plot panel.
|
|
22
|
+
|
|
23
|
+
``wspace`` / ``hspace`` are subplot-spacing fractions used only by the
|
|
24
|
+
matplotlib backend; they default to ``0`` so the Altair backend can build
|
|
25
|
+
a ``Margins`` without supplying them.
|
|
26
|
+
"""
|
|
27
|
+
title: float
|
|
28
|
+
label: float
|
|
29
|
+
legend: float
|
|
30
|
+
wspace: float = 0.0
|
|
31
|
+
hspace: float = 0.0
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Thickness(NamedTuple):
|
|
35
|
+
"""Derived line widths, scaled from the base ``thickness``.
|
|
36
|
+
|
|
37
|
+
``lines`` / ``patches`` are matplotlib-specific defaults; they carry
|
|
38
|
+
defaults so the Altair backend can build a ``Thickness`` from just the
|
|
39
|
+
axis-related widths.
|
|
40
|
+
"""
|
|
41
|
+
axes: float
|
|
42
|
+
grid: float
|
|
43
|
+
ticks: float
|
|
44
|
+
lines: float = 1.5
|
|
45
|
+
patches: float = 0.0
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def resolve_font_sizes(font_size: float) -> FontSizes:
|
|
49
|
+
"""Return the standard Charité font-size ramp derived from *font_size*."""
|
|
50
|
+
return FontSizes(
|
|
51
|
+
title=round(font_size * 1.2),
|
|
52
|
+
subtitle=font_size,
|
|
53
|
+
label=font_size,
|
|
54
|
+
legend=round(font_size * 0.8),
|
|
55
|
+
)
|
|
@@ -8,17 +8,20 @@ Register and enable with::
|
|
|
8
8
|
enable(font="Arial", font_size=13) # font override
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
11
13
|
from .colors import (
|
|
12
14
|
BLACK, WHITE, TEXT_GREY, PRIME_BLUE, PRIME_LGREY,
|
|
13
15
|
SECOND_DBLUE, KORALL,
|
|
14
16
|
)
|
|
15
17
|
from .palettes import PALETTES
|
|
16
18
|
from .fonts import build_font_stack
|
|
19
|
+
from ._theme_common import Margins, Thickness, resolve_font_sizes
|
|
17
20
|
|
|
18
21
|
from typing import TYPE_CHECKING
|
|
19
22
|
|
|
20
23
|
if TYPE_CHECKING:
|
|
21
|
-
from altair.theme import ThemeConfig
|
|
24
|
+
from altair.theme import AxisConfigKwds, ThemeConfig
|
|
22
25
|
|
|
23
26
|
|
|
24
27
|
def _css_font_stack(preferred: str | None) -> str:
|
|
@@ -32,10 +35,12 @@ def _css_font_stack(preferred: str | None) -> str:
|
|
|
32
35
|
def theme_charite(
|
|
33
36
|
font: str | None = None,
|
|
34
37
|
font_size: int = 12,
|
|
38
|
+
thickness: float = 0.5,
|
|
35
39
|
grid: bool = False,
|
|
36
40
|
palette: str | list[str] = "primary",
|
|
37
41
|
background: str = "white",
|
|
38
|
-
|
|
42
|
+
tiny_margins: bool = False,
|
|
43
|
+
) -> ThemeConfig:
|
|
39
44
|
"""Return an Altair theme config dict for the Charité corporate theme.
|
|
40
45
|
|
|
41
46
|
Parameters
|
|
@@ -44,78 +49,95 @@ def theme_charite(
|
|
|
44
49
|
Preferred font. Falls back through Charité Text Office → Charit? Text Office → Calibri → DejaVu Sans → sans-serif.
|
|
45
50
|
font_size:
|
|
46
51
|
Base font size in pixels.
|
|
52
|
+
thickness:
|
|
53
|
+
Axis line and tick width.
|
|
47
54
|
grid:
|
|
48
55
|
Show axis grid lines.
|
|
49
56
|
palette:
|
|
50
57
|
Built-in palette name or list of hex colors for the categorical range.
|
|
51
58
|
background:
|
|
52
59
|
Chart background color.
|
|
60
|
+
tiny_margins:
|
|
61
|
+
Minimise all paddings around the chart.
|
|
53
62
|
"""
|
|
54
63
|
colors = PALETTES[palette] if isinstance(palette, str) else list(palette)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
font_stack = _css_font_stack(preferred=font)
|
|
65
|
+
|
|
66
|
+
FONT_SIZES = resolve_font_sizes(font_size)
|
|
67
|
+
MARGINS = Margins(
|
|
68
|
+
title=8 if tiny_margins else 12, # chart title offset
|
|
69
|
+
label=3 if tiny_margins else 6, # axis / legend title padding
|
|
70
|
+
legend=2 if tiny_margins else 4, # legend inner padding
|
|
71
|
+
)
|
|
72
|
+
THICKNESS = Thickness(
|
|
73
|
+
axes=thickness, # domain line width
|
|
74
|
+
grid=thickness * 0.8, # grid line width
|
|
75
|
+
ticks=thickness, # tick mark width
|
|
76
|
+
lines=2, # line-mark stroke width (independent of thickness)
|
|
77
|
+
patches=0.4, # geoshape stroke width
|
|
78
|
+
)
|
|
79
|
+
CHART_PAD = 6 if tiny_margins else 10
|
|
80
|
+
|
|
81
|
+
axis: AxisConfigKwds = {
|
|
82
|
+
"labelFont": font_stack,
|
|
83
|
+
"titleFont": font_stack,
|
|
84
|
+
"labelFontSize": FONT_SIZES.label,
|
|
85
|
+
"titleFontSize": FONT_SIZES.subtitle,
|
|
64
86
|
"labelColor": TEXT_GREY,
|
|
65
87
|
"titleColor": TEXT_GREY,
|
|
66
|
-
"titlePadding":
|
|
88
|
+
"titlePadding": MARGINS.label,
|
|
67
89
|
"domain": True,
|
|
68
90
|
"domainColor": BLACK,
|
|
69
|
-
"domainWidth":
|
|
91
|
+
"domainWidth": THICKNESS.axes,
|
|
70
92
|
"ticks": True,
|
|
71
93
|
"tickColor": BLACK,
|
|
72
|
-
"tickWidth":
|
|
94
|
+
"tickWidth": THICKNESS.ticks,
|
|
73
95
|
"tickSize": 4,
|
|
74
96
|
"grid": grid,
|
|
75
97
|
"gridColor": PRIME_LGREY,
|
|
76
|
-
"gridWidth":
|
|
77
|
-
"gridOpacity": 0.
|
|
98
|
+
"gridWidth": THICKNESS.grid,
|
|
99
|
+
"gridOpacity": 0.85,
|
|
78
100
|
}
|
|
79
101
|
|
|
80
102
|
return {
|
|
81
103
|
"config": {
|
|
82
104
|
"background": background,
|
|
83
|
-
"font":
|
|
84
|
-
"padding": {"top":
|
|
105
|
+
"font": font_stack,
|
|
106
|
+
"padding": {"top": CHART_PAD, "bottom": CHART_PAD, "left": CHART_PAD, "right": CHART_PAD},
|
|
85
107
|
"view": {"stroke": None},
|
|
86
108
|
"axis": axis,
|
|
87
109
|
"axisX": axis,
|
|
88
110
|
"axisY": axis,
|
|
89
111
|
"legend": {
|
|
90
|
-
"labelFont":
|
|
91
|
-
"titleFont":
|
|
92
|
-
"labelFontSize":
|
|
93
|
-
"titleFontSize":
|
|
112
|
+
"labelFont": font_stack,
|
|
113
|
+
"titleFont": font_stack,
|
|
114
|
+
"labelFontSize": FONT_SIZES.legend,
|
|
115
|
+
"titleFontSize": FONT_SIZES.subtitle,
|
|
94
116
|
"labelColor": TEXT_GREY,
|
|
95
117
|
"titleColor": TEXT_GREY,
|
|
96
|
-
"titlePadding":
|
|
97
|
-
"padding":
|
|
98
|
-
"rowPadding":
|
|
118
|
+
"titlePadding": MARGINS.label,
|
|
119
|
+
"padding": MARGINS.legend,
|
|
120
|
+
"rowPadding": FONT_SIZES.legend / 4,
|
|
99
121
|
"symbolSize": 100,
|
|
100
122
|
},
|
|
101
123
|
"header": {
|
|
102
|
-
"labelFont":
|
|
103
|
-
"titleFont":
|
|
104
|
-
"labelFontSize":
|
|
105
|
-
"titleFontSize":
|
|
124
|
+
"labelFont": font_stack,
|
|
125
|
+
"titleFont": font_stack,
|
|
126
|
+
"labelFontSize": FONT_SIZES.label,
|
|
127
|
+
"titleFontSize": FONT_SIZES.subtitle,
|
|
106
128
|
"labelColor": WHITE,
|
|
107
129
|
"titleColor": PRIME_BLUE,
|
|
108
130
|
"labelBackground": PRIME_BLUE,
|
|
109
131
|
},
|
|
110
132
|
"title": {
|
|
111
|
-
"font":
|
|
112
|
-
"subtitleFont":
|
|
113
|
-
"fontSize":
|
|
114
|
-
"subtitleFontSize":
|
|
133
|
+
"font": font_stack,
|
|
134
|
+
"subtitleFont": font_stack,
|
|
135
|
+
"fontSize": FONT_SIZES.title,
|
|
136
|
+
"subtitleFontSize": FONT_SIZES.subtitle,
|
|
115
137
|
"color": PRIME_BLUE,
|
|
116
138
|
"subtitleColor": TEXT_GREY,
|
|
117
139
|
"anchor": "middle",
|
|
118
|
-
"offset":
|
|
140
|
+
"offset": MARGINS.title,
|
|
119
141
|
},
|
|
120
142
|
"range": {
|
|
121
143
|
"category": colors,
|
|
@@ -125,13 +147,13 @@ def theme_charite(
|
|
|
125
147
|
"ramp": ["#ffffff", PRIME_BLUE],
|
|
126
148
|
},
|
|
127
149
|
"bar": {"color": colors[0], "binSpacing": 1},
|
|
128
|
-
"line": {"color": colors[0], "strokeWidth":
|
|
150
|
+
"line": {"color": colors[0], "strokeWidth": THICKNESS.lines},
|
|
129
151
|
"point": {"color": colors[0], "size": 60, "opacity": 0.85, "filled": True},
|
|
130
152
|
"area": {"color": colors[0], "opacity": 0.8},
|
|
131
153
|
"arc": {"color": colors[0]},
|
|
132
154
|
"rect": {"color": colors[0]},
|
|
133
|
-
"geoshape": {"stroke": WHITE, "strokeWidth":
|
|
134
|
-
"text": {"font":
|
|
155
|
+
"geoshape": {"stroke": WHITE, "strokeWidth": THICKNESS.patches},
|
|
156
|
+
"text": {"font": font_stack, "fontSize": FONT_SIZES.label, "color": TEXT_GREY},
|
|
135
157
|
}
|
|
136
158
|
}
|
|
137
159
|
|
|
@@ -149,7 +171,8 @@ def enable(**kwargs) -> None:
|
|
|
149
171
|
----------
|
|
150
172
|
**kwargs:
|
|
151
173
|
Any parameter accepted by ``theme_charite``
|
|
152
|
-
(``font``, ``font_size``, ``grid``, ``palette``,
|
|
174
|
+
(``font``, ``font_size``, ``thickness``, ``grid``, ``palette``,
|
|
175
|
+
``background``, ``tiny_margins``).
|
|
153
176
|
|
|
154
177
|
Examples
|
|
155
178
|
--------
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Matplotlib theme for Charité – Universitätsmedizin Berlin."""
|
|
2
|
+
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
|
|
5
|
+
from cycler import cycler
|
|
6
|
+
|
|
7
|
+
from .colors import TEXT_GREY, PRIME_BLUE, PRIME_LGREY, BLACK, WHITE
|
|
8
|
+
from .palettes import PALETTES
|
|
9
|
+
from .fonts import build_font_stack
|
|
10
|
+
from ._theme_common import Margins, Thickness, resolve_font_sizes
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def theme_charite(
|
|
14
|
+
font: str | None = None,
|
|
15
|
+
font_size: int = 10,
|
|
16
|
+
thickness: float = 0.5,
|
|
17
|
+
grid: bool = False,
|
|
18
|
+
palette: str | list[str] = "primary",
|
|
19
|
+
interactive: bool = False,
|
|
20
|
+
tiny_margins: bool = False,
|
|
21
|
+
) -> dict:
|
|
22
|
+
"""Return matplotlib rcParams dict for the Charité corporate identity theme.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
font:
|
|
27
|
+
Preferred font family. Falls back through Charité Text Office →
|
|
28
|
+
Charit? Text Office → Calibri → DejaVu Sans → sans-serif if the requested font is not installed.
|
|
29
|
+
font_size:
|
|
30
|
+
Base font size in points. Defaults to 10 for screen; use 8 for print.
|
|
31
|
+
thickness:
|
|
32
|
+
Axis line and tick width. Mirrors the R ``thickness`` parameter.
|
|
33
|
+
grid:
|
|
34
|
+
Show major grid lines. Disabled by default.
|
|
35
|
+
palette:
|
|
36
|
+
Name of a built-in palette or a list of hex color strings used for the
|
|
37
|
+
``axes.prop_cycle``.
|
|
38
|
+
interactive:
|
|
39
|
+
Enable matplotlib interactive mode (equivalent to ``plt.ion()``).
|
|
40
|
+
Useful in notebooks or scripts where you want plots to display
|
|
41
|
+
without blocking.
|
|
42
|
+
tiny_margins:
|
|
43
|
+
Minimise all margins around the plot panel. Useful for dense layouts or
|
|
44
|
+
when saving figures with little surrounding whitespace.
|
|
45
|
+
"""
|
|
46
|
+
sans_font_stack = build_font_stack(preferred=font)
|
|
47
|
+
monospace_font_stack = ["Andale Mono", "Nimbus Mono L", "Courier New", "Courier", "Fixed", "Terminal", "monospace"]
|
|
48
|
+
colors = PALETTES[palette] if isinstance(palette, str) else list(palette)
|
|
49
|
+
|
|
50
|
+
FONT_SIZES = resolve_font_sizes(font_size)
|
|
51
|
+
MARGINS = Margins(
|
|
52
|
+
title=4 if tiny_margins else 8,
|
|
53
|
+
label=2 if tiny_margins else 4,
|
|
54
|
+
legend=2 if tiny_margins else 4,
|
|
55
|
+
wspace=0.1 if tiny_margins else 0.2,
|
|
56
|
+
hspace=0.125 if tiny_margins else 0.25
|
|
57
|
+
)
|
|
58
|
+
THICKNESS = Thickness(
|
|
59
|
+
axes=thickness,
|
|
60
|
+
grid=thickness * 0.6,
|
|
61
|
+
ticks=thickness,
|
|
62
|
+
lines=1.5,
|
|
63
|
+
patches=0
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
# Font — monospace is a fallback for latex mathtt, code blocks, etc.
|
|
68
|
+
"font.family": "sans-serif",
|
|
69
|
+
"font.sans-serif": sans_font_stack,
|
|
70
|
+
"font.monospace": monospace_font_stack,
|
|
71
|
+
"font.size": FONT_SIZES.label,
|
|
72
|
+
|
|
73
|
+
# Axes
|
|
74
|
+
"axes.linewidth": THICKNESS.axes,
|
|
75
|
+
"axes.titlelocation": "center",
|
|
76
|
+
"xaxis.labellocation": "center",
|
|
77
|
+
"yaxis.labellocation": "center",
|
|
78
|
+
"axes.titlesize": FONT_SIZES.title,
|
|
79
|
+
"axes.titlecolor": PRIME_BLUE,
|
|
80
|
+
"axes.titlepad": MARGINS.title,
|
|
81
|
+
"axes.labelsize": FONT_SIZES.label,
|
|
82
|
+
"axes.labelcolor": TEXT_GREY,
|
|
83
|
+
"axes.labelpad": MARGINS.label,
|
|
84
|
+
"axes.edgecolor": BLACK,
|
|
85
|
+
"axes.facecolor": WHITE,
|
|
86
|
+
"axes.grid": grid,
|
|
87
|
+
"axes.axisbelow": True,
|
|
88
|
+
"axes.spines.top": False,
|
|
89
|
+
"axes.spines.right": False,
|
|
90
|
+
|
|
91
|
+
# Axes 3D (only visible when projection='3d')
|
|
92
|
+
"axes3d.grid": grid,
|
|
93
|
+
"axes3d.xaxis.panecolor": WHITE,
|
|
94
|
+
"axes3d.yaxis.panecolor": WHITE,
|
|
95
|
+
"axes3d.zaxis.panecolor": WHITE,
|
|
96
|
+
|
|
97
|
+
# Polar axes (only visible when projection='polar')
|
|
98
|
+
"polaraxes.grid": grid,
|
|
99
|
+
|
|
100
|
+
# Grid (only visible when grid=True)
|
|
101
|
+
"grid.color": PRIME_LGREY,
|
|
102
|
+
"grid.linewidth": THICKNESS.grid,
|
|
103
|
+
# "grid.minor.alpha": 0.48,
|
|
104
|
+
|
|
105
|
+
# Ticks
|
|
106
|
+
"xtick.color": BLACK,
|
|
107
|
+
"ytick.color": BLACK,
|
|
108
|
+
"xtick.labelsize": FONT_SIZES.label,
|
|
109
|
+
"ytick.labelsize": FONT_SIZES.label,
|
|
110
|
+
"xtick.labelcolor": TEXT_GREY,
|
|
111
|
+
"ytick.labelcolor": TEXT_GREY,
|
|
112
|
+
"xtick.major.size": 2,
|
|
113
|
+
"ytick.major.size": 2,
|
|
114
|
+
"xtick.major.pad": 2,
|
|
115
|
+
"ytick.major.pad": 2,
|
|
116
|
+
"xtick.major.width": THICKNESS.ticks,
|
|
117
|
+
"ytick.major.width": THICKNESS.ticks,
|
|
118
|
+
"xtick.minor.visible": False,
|
|
119
|
+
"ytick.minor.visible": False,
|
|
120
|
+
|
|
121
|
+
# Text
|
|
122
|
+
"text.color": TEXT_GREY,
|
|
123
|
+
"text.antialiased": True,
|
|
124
|
+
|
|
125
|
+
# Figure
|
|
126
|
+
"figure.facecolor": WHITE,
|
|
127
|
+
"figure.edgecolor": WHITE,
|
|
128
|
+
"figure.dpi": 100,
|
|
129
|
+
"figure.figsize": (11, 8),
|
|
130
|
+
"figure.subplot.hspace": MARGINS.hspace,
|
|
131
|
+
"figure.subplot.wspace": MARGINS.wspace,
|
|
132
|
+
"figure.titlesize": FONT_SIZES.title,
|
|
133
|
+
"figure.labelsize": FONT_SIZES.label,
|
|
134
|
+
|
|
135
|
+
# Lines / markers
|
|
136
|
+
"lines.linewidth": THICKNESS.lines,
|
|
137
|
+
"lines.markersize": 6,
|
|
138
|
+
"lines.antialiased": True,
|
|
139
|
+
|
|
140
|
+
# Patches (bars, etc.)
|
|
141
|
+
"patch.linewidth": THICKNESS.patches,
|
|
142
|
+
"patch.force_edgecolor": False,
|
|
143
|
+
"patch.antialiased": True,
|
|
144
|
+
|
|
145
|
+
# Histograms
|
|
146
|
+
"hist.bins": "auto",
|
|
147
|
+
|
|
148
|
+
# Legend
|
|
149
|
+
"legend.fancybox": True,
|
|
150
|
+
"legend.frameon": False,
|
|
151
|
+
"legend.fontsize": FONT_SIZES.legend,
|
|
152
|
+
"legend.title_fontsize": FONT_SIZES.title,
|
|
153
|
+
"legend.labelcolor": TEXT_GREY,
|
|
154
|
+
|
|
155
|
+
# Interactive mode
|
|
156
|
+
"interactive": interactive,
|
|
157
|
+
|
|
158
|
+
# Save
|
|
159
|
+
"savefig.dpi": 300,
|
|
160
|
+
"savefig.bbox": "tight",
|
|
161
|
+
"savefig.transparent": True,
|
|
162
|
+
|
|
163
|
+
# Color cycle
|
|
164
|
+
"axes.prop_cycle": cycler("color", colors),
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def apply_theme(params: dict) -> None:
|
|
169
|
+
"""Apply a theme dict as the active matplotlib rcParams (permanent until reset)."""
|
|
170
|
+
import matplotlib as mpl
|
|
171
|
+
mpl.rcParams.update(params)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@contextmanager
|
|
175
|
+
def using(params: dict):
|
|
176
|
+
"""Context manager: temporarily apply *params* then restore previous rcParams.
|
|
177
|
+
|
|
178
|
+
Examples
|
|
179
|
+
--------
|
|
180
|
+
```python
|
|
181
|
+
with using(theme_charite(palette="goldelse")):
|
|
182
|
+
fig, ax = plt.subplots()
|
|
183
|
+
ax.plot([1, 2, 3])
|
|
184
|
+
```
|
|
185
|
+
"""
|
|
186
|
+
import matplotlib as mpl
|
|
187
|
+
with mpl.rc_context(params):
|
|
188
|
+
yield
|
|
@@ -44,9 +44,11 @@ alt.theme.enable("charite") # enable later
|
|
|
44
44
|
|-----------|------|---------|-------------|
|
|
45
45
|
| `font` | `str | None` | `None` | Preferred font. CSS fallback: Charité Text Office → Charit? Text Office → Calibri → DejaVu Sans → sans-serif. |
|
|
46
46
|
| `font_size` | `int` | `12` | Base font size in pixels. |
|
|
47
|
+
| `thickness` | `float` | `0.5` | Axis line and tick width. |
|
|
47
48
|
| `grid` | `bool` | `False` | Show axis grid lines. |
|
|
48
49
|
| `palette` | `str | list[str]` | `"primary"` | Named palette or list of hex strings for the categorical color range. |
|
|
49
50
|
| `background` | `str` | `"white"` | Chart background color. |
|
|
51
|
+
| `tiny_margins` | `bool` | `False` | Minimise all paddings around the chart. Useful for dense layouts. |
|
|
50
52
|
|
|
51
53
|
---
|
|
52
54
|
|
|
@@ -95,3 +97,15 @@ alt.Chart(df).mark_point().encode(
|
|
|
95
97
|
```python
|
|
96
98
|
enable(background="transparent")
|
|
97
99
|
```
|
|
100
|
+
|
|
101
|
+
### Dense layout with minimal margins
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
enable(tiny_margins=True)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Thicker axis lines
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
enable(thickness=1.0)
|
|
111
|
+
```
|
|
Binary file
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Citing charite-plot
|
|
2
|
+
|
|
3
|
+
If you use charite-plot in your research or work, please cite it. A `CITATION.cff` file is included in the repository and GitHub will surface a **Cite this repository** button on the sidebar automatically.
|
|
4
|
+
|
|
5
|
+
## BibTeX
|
|
6
|
+
|
|
7
|
+
```bibtex
|
|
8
|
+
@software{ramezani2026chariteplot,
|
|
9
|
+
author = {Ramezani, Pedram},
|
|
10
|
+
title = {charite-plot: Matplotlib and Altair Themes for Charité – Universitätsmedizin Berlin},
|
|
11
|
+
year = {2026},
|
|
12
|
+
version = {0.2.0},
|
|
13
|
+
url = {https://github.com/pedramezani/charite-plot},
|
|
14
|
+
license = {MIT},
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Acknowledgements
|
|
19
|
+
|
|
20
|
+
charite-plot is based on the original [`charite` R package](https://github.com/johannesjuliusm/charite) developed by Johannes Julius Mohn.
|
|
@@ -57,6 +57,8 @@ mpl.rcParams.update(params)
|
|
|
57
57
|
| `thickness` | `float` | `0.5` | Axis line and tick width. |
|
|
58
58
|
| `grid` | `bool` | `False` | Show major grid lines. |
|
|
59
59
|
| `palette` | `str | list[str]` | `"primary"` | Named palette or list of hex strings for `axes.prop_cycle`. |
|
|
60
|
+
| `interactive` | `bool` | `False` | Enable interactive mode (equivalent to `plt.ion()`). Useful in notebooks or scripts where plots should display without blocking. |
|
|
61
|
+
| `tiny_margins` | `bool` | `False` | Minimise all margins and paddings around the plot panel. Useful for dense layouts. |
|
|
60
62
|
|
|
61
63
|
---
|
|
62
64
|
|
|
@@ -87,3 +89,15 @@ apply_theme(theme_charite(palette=["#004d9b", "#ea5451", "#fab600"]))
|
|
|
87
89
|
```python
|
|
88
90
|
apply_theme(theme_charite(grid=True))
|
|
89
91
|
```
|
|
92
|
+
|
|
93
|
+
### Dense layout with minimal margins
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
apply_theme(theme_charite(tiny_margins=True))
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Interactive mode (notebooks / REPL)
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
apply_theme(theme_charite(interactive=True)) # equivalent to plt.ion()
|
|
103
|
+
```
|
|
@@ -41,7 +41,7 @@ fig.suptitle(
|
|
|
41
41
|
fontsize=16,
|
|
42
42
|
y=1.01,
|
|
43
43
|
)
|
|
44
|
-
sub = fig.add_gridspec(3, 3, hspace=0.
|
|
44
|
+
sub = fig.add_gridspec(3, 3, hspace=0.125, wspace=0.1)
|
|
45
45
|
|
|
46
46
|
cats = ["A", "B", "C"]
|
|
47
47
|
|
|
@@ -178,7 +178,9 @@ ax9.set_yticks([n_rows - i - 0.6 for i in range(n_rows)])
|
|
|
178
178
|
ax9.set_yticklabels(palette_names, fontsize=7)
|
|
179
179
|
ax9.set_xticks([])
|
|
180
180
|
ax9.spines["left"].set_visible(False)
|
|
181
|
+
ax9.spines["bottom"].set_visible(False)
|
|
181
182
|
ax9.tick_params(left=False)
|
|
183
|
+
ax9.tick_params(bottom=False)
|
|
182
184
|
ax9.set_title("Color Palettes")
|
|
183
185
|
|
|
184
186
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Palette preview — generates swatch strips for every built-in palette.
|
|
2
|
+
|
|
3
|
+
Produces one asset in docs/assets/:
|
|
4
|
+
palette_preview.png
|
|
5
|
+
|
|
6
|
+
Run with:
|
|
7
|
+
python examples/palette_preview.py
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
import pathlib
|
|
12
|
+
sys.path.insert(0, str(pathlib.Path(__file__).parent.parent))
|
|
13
|
+
|
|
14
|
+
import matplotlib.pyplot as plt
|
|
15
|
+
import matplotlib.patches as mpatches
|
|
16
|
+
|
|
17
|
+
from charite_plot.mpl_themes import theme_charite, apply_theme
|
|
18
|
+
from charite_plot.palettes import PALETTES
|
|
19
|
+
|
|
20
|
+
apply_theme(theme_charite())
|
|
21
|
+
|
|
22
|
+
MAX_COLS = max(len(p) for p in PALETTES.values())
|
|
23
|
+
names = list(PALETTES.keys())
|
|
24
|
+
LABEL_PAD = 0.4
|
|
25
|
+
ROW_H = 0.58
|
|
26
|
+
FIG_W = 7.5
|
|
27
|
+
|
|
28
|
+
ASSETS = pathlib.Path(__file__).parent.parent / "docs" / "assets"
|
|
29
|
+
ASSETS.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
OUT = ASSETS / "palette_preview.png"
|
|
31
|
+
SAVE_KW = dict(dpi=300, bbox_inches="tight", facecolor="white", transparent=False)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _draw_row(ax: plt.Axes, name: str) -> None:
|
|
35
|
+
for i, color in enumerate(PALETTES[name]):
|
|
36
|
+
ax.add_patch(
|
|
37
|
+
mpatches.Rectangle(
|
|
38
|
+
(i, 0),
|
|
39
|
+
1.0,
|
|
40
|
+
0.82,
|
|
41
|
+
facecolor=color,
|
|
42
|
+
edgecolor="none",
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
ax.set_xlim(-LABEL_PAD, MAX_COLS)
|
|
46
|
+
ax.set_ylim(-0.1, 1.0)
|
|
47
|
+
ax.text(-LABEL_PAD, 0.41, name, ha="right", va="center", fontsize=12)
|
|
48
|
+
ax.axis("off")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
fig, axes = plt.subplots(
|
|
52
|
+
len(names),
|
|
53
|
+
1,
|
|
54
|
+
figsize=(FIG_W, len(names) * ROW_H + 0.3),
|
|
55
|
+
gridspec_kw={"hspace": 0},
|
|
56
|
+
)
|
|
57
|
+
for ax, name in zip(axes, names):
|
|
58
|
+
_draw_row(ax, name)
|
|
59
|
+
fig.savefig(OUT, **SAVE_KW)
|
|
60
|
+
print(f"Saved → {OUT}")
|
|
61
|
+
plt.show()
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Synthetic time-series showcase — generates the charite-plot theme example image.
|
|
2
|
+
|
|
3
|
+
Replicates the curves from ``tools/data/simulate_timeseries_data.R`` in the
|
|
4
|
+
charite R package: three conditions (wiggly normal, cubic polynomial, square
|
|
5
|
+
root) z-scored and plotted with uniform ±SE bands.
|
|
6
|
+
|
|
7
|
+
Produces one asset in docs/assets/:
|
|
8
|
+
theme_example.png
|
|
9
|
+
|
|
10
|
+
Run with:
|
|
11
|
+
python examples/poly_showcase.py
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
import pathlib
|
|
16
|
+
sys.path.insert(0, str(pathlib.Path(__file__).parent.parent))
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
import matplotlib.pyplot as plt
|
|
20
|
+
|
|
21
|
+
from charite_plot.mpl_themes import theme_charite, apply_theme
|
|
22
|
+
from charite_plot.colors import PRIME_BLUE, PRIME_DGREY, KORALL, TEXT_GREY
|
|
23
|
+
|
|
24
|
+
COLORS = [KORALL, PRIME_BLUE, PRIME_DGREY] # C1, C2, C3
|
|
25
|
+
ASSETS = pathlib.Path(__file__).parent.parent / "docs" / "assets"
|
|
26
|
+
ASSETS.mkdir(parents=True, exist_ok=True)
|
|
27
|
+
OUT = ASSETS / "theme_example.png"
|
|
28
|
+
SAVE_KW = dict(dpi=300, bbox_inches="tight", facecolor="white")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ── data helpers ────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
def _dnorm(x: np.ndarray, mean: float, sd: float) -> np.ndarray:
|
|
34
|
+
return np.exp(-0.5 * ((x - mean) / sd) ** 2) / (sd * np.sqrt(2 * np.pi))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _scale(x: np.ndarray) -> np.ndarray:
|
|
38
|
+
return (x - x.mean()) / x.std(ddof=1)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _generate_data():
|
|
42
|
+
# R and numpy use different RNGs so the tiny noise terms differ slightly,
|
|
43
|
+
# but they are visually negligible after z-scoring (sd ≤ 0.01).
|
|
44
|
+
rng = np.random.default_rng(42)
|
|
45
|
+
n = 100
|
|
46
|
+
time = np.linspace(0, 1, n)
|
|
47
|
+
t_axis = np.arange(1, n + 1)
|
|
48
|
+
|
|
49
|
+
c1_core = _dnorm(time, mean=0.5, sd=0.15)
|
|
50
|
+
c1 = (c1_core
|
|
51
|
+
+ 0.1 * np.sin(25 * time)
|
|
52
|
+
+ 0.05 * np.cos(40 * time)
|
|
53
|
+
+ rng.normal(0, 0.005, n))
|
|
54
|
+
c2 = 3.9 * time**3 - 6 * time**2 + 3 * time + rng.normal(0, 0.01, n)
|
|
55
|
+
c3 = time**0.5 + rng.normal(0, 0.005, n)
|
|
56
|
+
|
|
57
|
+
means = {"C1": _scale(c1) + 2.2, "C2": _scale(c2) + 2.8, "C3": _scale(c3) + 2.8}
|
|
58
|
+
se_all = rng.uniform(0.4, 0.6, n * 3)
|
|
59
|
+
ses = {"C1": se_all[:n], "C2": se_all[n:2*n], "C3": se_all[2*n:]}
|
|
60
|
+
return t_axis, means, ses
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# ── plot helpers ────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
def _plot_curves(ax: plt.Axes, t_axis, means, ses, frameon: bool = False) -> None:
|
|
66
|
+
for (label, mean), se, color in zip(means.items(), ses.values(), COLORS):
|
|
67
|
+
ax.fill_between(t_axis, mean - se, mean + se, alpha=0.25, color=color, linewidth=0)
|
|
68
|
+
ax.plot(t_axis, mean, lw=2, label=label, color=color)
|
|
69
|
+
ax.set_xlim(left=0)
|
|
70
|
+
ax.set_ylim(bottom=0)
|
|
71
|
+
ax.set_xlabel("Time")
|
|
72
|
+
ax.set_ylabel("Signal")
|
|
73
|
+
ax.legend(frameon=frameon)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _despine(ax: plt.Axes) -> None:
|
|
77
|
+
"""Seaborn-style despine: trim spines to tick extent."""
|
|
78
|
+
for side in ("top", "right"):
|
|
79
|
+
ax.spines[side].set_visible(False)
|
|
80
|
+
for side in ("left", "bottom"):
|
|
81
|
+
ax.spines[side].set_position(("outward", 0))
|
|
82
|
+
xt = np.asarray(ax.get_xticks())
|
|
83
|
+
xt = xt[(xt >= ax.get_xlim()[0]) & (xt <= ax.get_xlim()[1])]
|
|
84
|
+
if xt.size:
|
|
85
|
+
ax.spines["bottom"].set_bounds(xt[0], xt[-1])
|
|
86
|
+
yt = np.asarray(ax.get_yticks())
|
|
87
|
+
yt = yt[(yt >= ax.get_ylim()[0]) & (yt <= ax.get_ylim()[1])]
|
|
88
|
+
if yt.size:
|
|
89
|
+
ax.spines["left"].set_bounds(yt[0], yt[-1])
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# ── generate data once ──────────────────────────────────────────────────────
|
|
93
|
+
t_axis, means, ses = _generate_data()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ── single: Charité theme ───────────────────────────────────────────────────
|
|
97
|
+
apply_theme(theme_charite(font_size=14, thickness=1))
|
|
98
|
+
|
|
99
|
+
fig, ax = plt.subplots(figsize=(6, 4.5))
|
|
100
|
+
fig.subplots_adjust(top=0.88)
|
|
101
|
+
fig.suptitle("Charité", fontsize=16, y=0.95, color=PRIME_BLUE)
|
|
102
|
+
fig.text(0.5, 0.90, "mpl Theme", ha="center", va="top", fontsize=9.5, color=TEXT_GREY)
|
|
103
|
+
_plot_curves(ax, t_axis, means, ses)
|
|
104
|
+
_despine(ax)
|
|
105
|
+
fig.savefig(OUT, **SAVE_KW)
|
|
106
|
+
print(f"Saved → {OUT}")
|
|
107
|
+
plt.show()
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
"""Matplotlib theme for Charité – Universitätsmedizin Berlin."""
|
|
2
|
-
|
|
3
|
-
from contextlib import contextmanager
|
|
4
|
-
|
|
5
|
-
from cycler import cycler
|
|
6
|
-
|
|
7
|
-
from .colors import TEXT_GREY, PRIME_BLUE, PRIME_LGREY, BLACK
|
|
8
|
-
from .palettes import PALETTES
|
|
9
|
-
from .fonts import build_font_stack
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def theme_charite(
|
|
13
|
-
font: str | None = None,
|
|
14
|
-
font_size: float = 10,
|
|
15
|
-
thickness: float = 0.5,
|
|
16
|
-
grid: bool = False,
|
|
17
|
-
palette: str | list[str] = "primary",
|
|
18
|
-
) -> dict:
|
|
19
|
-
"""Return matplotlib rcParams dict for the Charité corporate identity theme.
|
|
20
|
-
|
|
21
|
-
Parameters
|
|
22
|
-
----------
|
|
23
|
-
font:
|
|
24
|
-
Preferred font family. Falls back through Charité Text Office →
|
|
25
|
-
Charit? Text Office → Calibri → DejaVu Sans → sans-serif if the requested font is not installed.
|
|
26
|
-
font_size:
|
|
27
|
-
Base font size in points. Defaults to 10 for screen; use 8 for print.
|
|
28
|
-
thickness:
|
|
29
|
-
Axis line and tick width. Mirrors the R ``thickness`` parameter.
|
|
30
|
-
grid:
|
|
31
|
-
Show major grid lines. Disabled by default.
|
|
32
|
-
palette:
|
|
33
|
-
Name of a built-in palette or a list of hex color strings used for the
|
|
34
|
-
``axes.prop_cycle``.
|
|
35
|
-
"""
|
|
36
|
-
colors = PALETTES[palette] if isinstance(palette, str) else list(palette)
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
# Font
|
|
40
|
-
"font.family": "sans-serif",
|
|
41
|
-
"font.sans-serif": build_font_stack(preferred=font),
|
|
42
|
-
"font.size": font_size,
|
|
43
|
-
|
|
44
|
-
# Spines — only bottom and left visible (mirrors theme_classic in R)
|
|
45
|
-
"axes.spines.top": False,
|
|
46
|
-
"axes.spines.right": False,
|
|
47
|
-
|
|
48
|
-
# Axes
|
|
49
|
-
"axes.linewidth": thickness,
|
|
50
|
-
"axes.titlesize": round(font_size * 1.2),
|
|
51
|
-
"axes.titlecolor": PRIME_BLUE,
|
|
52
|
-
"axes.titlepad": 8,
|
|
53
|
-
"axes.labelsize": font_size,
|
|
54
|
-
"axes.labelcolor": TEXT_GREY,
|
|
55
|
-
"axes.edgecolor": BLACK,
|
|
56
|
-
"axes.facecolor": "white",
|
|
57
|
-
"axes.grid": grid,
|
|
58
|
-
|
|
59
|
-
# Grid (only visible when grid=True)
|
|
60
|
-
"grid.color": PRIME_LGREY,
|
|
61
|
-
"grid.linewidth": thickness * 0.6,
|
|
62
|
-
"grid.alpha": 0.8,
|
|
63
|
-
|
|
64
|
-
# Ticks
|
|
65
|
-
"xtick.color": BLACK,
|
|
66
|
-
"ytick.color": BLACK,
|
|
67
|
-
"xtick.labelsize": font_size - 1,
|
|
68
|
-
"ytick.labelsize": font_size - 1,
|
|
69
|
-
"xtick.labelcolor": TEXT_GREY,
|
|
70
|
-
"ytick.labelcolor": TEXT_GREY,
|
|
71
|
-
"xtick.major.size": 3,
|
|
72
|
-
"ytick.major.size": 3,
|
|
73
|
-
"xtick.major.width": thickness,
|
|
74
|
-
"ytick.major.width": thickness,
|
|
75
|
-
"xtick.minor.visible": False,
|
|
76
|
-
"ytick.minor.visible": False,
|
|
77
|
-
|
|
78
|
-
# Text
|
|
79
|
-
"text.color": TEXT_GREY,
|
|
80
|
-
|
|
81
|
-
# Figure
|
|
82
|
-
"figure.facecolor": "white",
|
|
83
|
-
"figure.edgecolor": "white",
|
|
84
|
-
"figure.dpi": 100,
|
|
85
|
-
|
|
86
|
-
# Lines / markers
|
|
87
|
-
"lines.linewidth": 1.5,
|
|
88
|
-
"lines.markersize": 6,
|
|
89
|
-
|
|
90
|
-
# Patches (bars, etc.)
|
|
91
|
-
"patch.linewidth": 0,
|
|
92
|
-
|
|
93
|
-
# Legend
|
|
94
|
-
"legend.frameon": False,
|
|
95
|
-
"legend.fontsize": round(font_size * 0.9),
|
|
96
|
-
"legend.title_fontsize": font_size,
|
|
97
|
-
"legend.labelcolor": TEXT_GREY,
|
|
98
|
-
|
|
99
|
-
# Save
|
|
100
|
-
"savefig.dpi": 300,
|
|
101
|
-
"savefig.bbox": "tight",
|
|
102
|
-
"savefig.transparent": True,
|
|
103
|
-
|
|
104
|
-
# Color cycle
|
|
105
|
-
"axes.prop_cycle": cycler("color", colors),
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def apply_theme(params: dict) -> None:
|
|
110
|
-
"""Apply a theme dict as the active matplotlib rcParams (permanent until reset)."""
|
|
111
|
-
import matplotlib as mpl
|
|
112
|
-
mpl.rcParams.update(params)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
@contextmanager
|
|
116
|
-
def using(params: dict):
|
|
117
|
-
"""Context manager: temporarily apply *params* then restore previous rcParams.
|
|
118
|
-
|
|
119
|
-
Examples
|
|
120
|
-
--------
|
|
121
|
-
```python
|
|
122
|
-
with using(theme_charite(palette="goldelse")):
|
|
123
|
-
fig, ax = plt.subplots()
|
|
124
|
-
ax.plot([1, 2, 3])
|
|
125
|
-
```
|
|
126
|
-
"""
|
|
127
|
-
import matplotlib as mpl
|
|
128
|
-
with mpl.rc_context(params):
|
|
129
|
-
yield
|
|
Binary file
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
"""Palette preview — generates a swatch strip for every built-in palette.
|
|
2
|
-
|
|
3
|
-
Run with:
|
|
4
|
-
python examples/palette_preview.py
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import sys
|
|
8
|
-
import pathlib
|
|
9
|
-
sys.path.insert(0, str(pathlib.Path(__file__).parent.parent))
|
|
10
|
-
|
|
11
|
-
import matplotlib.pyplot as plt
|
|
12
|
-
import matplotlib.patches as mpatches
|
|
13
|
-
|
|
14
|
-
from charite_plot.mpl_themes import theme_charite, apply_theme
|
|
15
|
-
from charite_plot.palettes import PALETTES
|
|
16
|
-
|
|
17
|
-
apply_theme(theme_charite())
|
|
18
|
-
|
|
19
|
-
MAX_COLS = max(len(p) for p in PALETTES.values())
|
|
20
|
-
names = list(PALETTES.keys())
|
|
21
|
-
|
|
22
|
-
LABEL_PAD = 0.4 # data-units reserved to the left for labels at font size 12
|
|
23
|
-
|
|
24
|
-
fig, axes = plt.subplots(
|
|
25
|
-
len(names), 1,
|
|
26
|
-
figsize=(7.5, len(names) * 0.58 + 0.3),
|
|
27
|
-
gridspec_kw={"hspace": 0},
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
for ax, name in zip(axes, names):
|
|
31
|
-
palette = PALETTES[name]
|
|
32
|
-
for i, color in enumerate(palette):
|
|
33
|
-
ax.add_patch(mpatches.Rectangle(
|
|
34
|
-
(i, 0), 1.0, 0.82,
|
|
35
|
-
facecolor=color, edgecolor="none",
|
|
36
|
-
))
|
|
37
|
-
ax.set_xlim(-LABEL_PAD, MAX_COLS)
|
|
38
|
-
ax.set_ylim(-0.1, 1.0)
|
|
39
|
-
ax.text(-LABEL_PAD, 0.41, name, ha="right", va="center", fontsize=12)
|
|
40
|
-
ax.axis("off")
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
out = pathlib.Path(__file__).parent.parent / "docs" / "assets" / "palette_preview.png"
|
|
44
|
-
out.parent.mkdir(parents=True, exist_ok=True)
|
|
45
|
-
fig.savefig(out, dpi=300, bbox_inches="tight", facecolor="white")
|
|
46
|
-
print(f"Saved → {out}")
|
|
47
|
-
plt.show()
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
"""Synthetic time-series showcase — generates the charite-plot theme example image.
|
|
2
|
-
|
|
3
|
-
Replicates the curves from ``tools/data/simulate_timeseries_data.R`` in the
|
|
4
|
-
charite R package: three conditions (wiggly normal, cubic polynomial, square
|
|
5
|
-
root) z-scored and plotted with uniform ±SE bands.
|
|
6
|
-
|
|
7
|
-
Run with:
|
|
8
|
-
python examples/poly_showcase.py
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import sys
|
|
12
|
-
import pathlib
|
|
13
|
-
sys.path.insert(0, str(pathlib.Path(__file__).parent.parent))
|
|
14
|
-
|
|
15
|
-
import numpy as np
|
|
16
|
-
import matplotlib.pyplot as plt
|
|
17
|
-
|
|
18
|
-
from charite_plot.mpl_themes import theme_charite, apply_theme
|
|
19
|
-
from charite_plot.colors import PRIME_BLUE, PRIME_DGREY, KORALL, TEXT_GREY
|
|
20
|
-
|
|
21
|
-
apply_theme(theme_charite(font_size=10))
|
|
22
|
-
|
|
23
|
-
# ── reproduce R's simulate_timeseries_data.R (set.seed(42)) ────────────────
|
|
24
|
-
# R and numpy use different RNGs so the tiny noise terms differ slightly,
|
|
25
|
-
# but they are visually negligible after z-scoring (sd ≤ 0.01).
|
|
26
|
-
rng = np.random.default_rng(42)
|
|
27
|
-
|
|
28
|
-
n_time = 100
|
|
29
|
-
time = np.linspace(0, 1, n_time) # seq(0, 1, length.out = 100)
|
|
30
|
-
t_axis = np.arange(1, n_time + 1) # seq(1, 100) — x-axis
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def _dnorm(x: np.ndarray, mean: float, sd: float) -> np.ndarray:
|
|
34
|
-
"""Normal PDF — equivalent to R's dnorm()."""
|
|
35
|
-
return np.exp(-0.5 * ((x - mean) / sd) ** 2) / (sd * np.sqrt(2 * np.pi))
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def _scale(x: np.ndarray) -> np.ndarray:
|
|
39
|
-
"""Center and unit-scale — equivalent to R's scale()."""
|
|
40
|
-
return (x - x.mean()) / x.std(ddof=1)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
# C1: wiggly normal
|
|
44
|
-
c1_core = _dnorm(time, mean=0.5, sd=0.15)
|
|
45
|
-
c1 = (c1_core
|
|
46
|
-
+ 0.1 * np.sin(25 * time)
|
|
47
|
-
+ 0.05 * np.cos(40 * time)
|
|
48
|
-
+ rng.normal(0, 0.005, n_time))
|
|
49
|
-
|
|
50
|
-
# C2: 3rd-order polynomial
|
|
51
|
-
c2 = 3.9 * time**3 - 6 * time**2 + 3 * time + rng.normal(0, 0.01, n_time)
|
|
52
|
-
|
|
53
|
-
# C3: square root
|
|
54
|
-
c3 = time**0.5 + rng.normal(0, 0.005, n_time)
|
|
55
|
-
|
|
56
|
-
# z-score and shift (matching the R data frame construction)
|
|
57
|
-
means = {
|
|
58
|
-
"C1": _scale(c1) + 2.2,
|
|
59
|
-
"C2": _scale(c2) + 2.8,
|
|
60
|
-
"C3": _scale(c3) + 2.8,
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
# SE: runif(n_time * 3, min = 0.4, max = 0.6), sliced per condition
|
|
64
|
-
se_all = rng.uniform(0.4, 0.6, n_time * 3)
|
|
65
|
-
ses = {
|
|
66
|
-
"C1": se_all[0 : n_time],
|
|
67
|
-
"C2": se_all[n_time : 2 * n_time],
|
|
68
|
-
"C3": se_all[2 * n_time :],
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
# ── plot ───────────────────────────────────────────────────────────────────
|
|
72
|
-
colors = [KORALL, PRIME_BLUE, PRIME_DGREY] # C1, C2, C3
|
|
73
|
-
|
|
74
|
-
fig, ax = plt.subplots(figsize=(6, 4.5)) # 4:3
|
|
75
|
-
fig.subplots_adjust(top=0.88)
|
|
76
|
-
fig.suptitle("Charité", fontsize=16, y=0.95, color=PRIME_BLUE)
|
|
77
|
-
fig.text(0.5, 0.90, "mpl Theme", ha="center", va="top", fontsize=9.5, color=TEXT_GREY)
|
|
78
|
-
|
|
79
|
-
for (label, mean), se, color in zip(means.items(), ses.values(), colors):
|
|
80
|
-
ax.fill_between(t_axis, mean - se, mean + se, alpha=0.25, color=color, linewidth=0)
|
|
81
|
-
ax.plot(t_axis, mean, lw=2, label=label, color=color)
|
|
82
|
-
|
|
83
|
-
ax.set_xlim(left=0)
|
|
84
|
-
ax.set_ylim(bottom=0)
|
|
85
|
-
ax.set_xlabel("Time")
|
|
86
|
-
ax.set_ylabel("Signal")
|
|
87
|
-
ax.legend(frameon=False)
|
|
88
|
-
|
|
89
|
-
# despine: offset spines outward and trim to tick extent (seaborn-style)
|
|
90
|
-
for side in ("left", "bottom"):
|
|
91
|
-
ax.spines[side].set_position(("outward", 0))
|
|
92
|
-
for side in ("top", "right"):
|
|
93
|
-
ax.spines[side].set_visible(False)
|
|
94
|
-
xt = np.asarray(ax.get_xticks())
|
|
95
|
-
xt = xt[(xt >= ax.get_xlim()[0]) & (xt <= ax.get_xlim()[1])]
|
|
96
|
-
if xt.size:
|
|
97
|
-
ax.spines["bottom"].set_bounds(xt[0], xt[-1])
|
|
98
|
-
yt = np.asarray(ax.get_yticks())
|
|
99
|
-
yt = yt[(yt >= ax.get_ylim()[0]) & (yt <= ax.get_ylim()[1])]
|
|
100
|
-
if yt.size:
|
|
101
|
-
ax.spines["left"].set_bounds(yt[0], yt[-1])
|
|
102
|
-
|
|
103
|
-
# ── save ───────────────────────────────────────────────────────────────────
|
|
104
|
-
out = pathlib.Path(__file__).parent.parent / "docs" / "assets" / "theme_example.png"
|
|
105
|
-
out.parent.mkdir(parents=True, exist_ok=True)
|
|
106
|
-
fig.savefig(out, dpi=300, bbox_inches="tight", facecolor="white")
|
|
107
|
-
print(f"Saved → {out}")
|
|
108
|
-
plt.show()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|