qmlmathplot 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. qmlmathplot/__init__.py +84 -0
  2. qmlmathplot/app.py +79 -0
  3. qmlmathplot/camera.py +366 -0
  4. qmlmathplot/curve.py +296 -0
  5. qmlmathplot/model.py +541 -0
  6. qmlmathplot/plot.py +213 -0
  7. qmlmathplot/qml/PlotView.qml +247 -0
  8. qmlmathplot/qsb.py +96 -0
  9. qmlmathplot/themes/LICENSE.matplotlib +99 -0
  10. qmlmathplot/themes/README.md +15 -0
  11. qmlmathplot/themes/Solarize_Light2.mplstyle +53 -0
  12. qmlmathplot/themes/bmh.mplstyle +27 -0
  13. qmlmathplot/themes/classic.mplstyle +491 -0
  14. qmlmathplot/themes/dark_background.mplstyle +26 -0
  15. qmlmathplot/themes/fast.mplstyle +11 -0
  16. qmlmathplot/themes/fivethirtyeight.mplstyle +37 -0
  17. qmlmathplot/themes/ggplot.mplstyle +39 -0
  18. qmlmathplot/themes/grayscale.mplstyle +29 -0
  19. qmlmathplot/themes/petroff10.mplstyle +5 -0
  20. qmlmathplot/themes/seaborn-v0_8-bright.mplstyle +3 -0
  21. qmlmathplot/themes/seaborn-v0_8-colorblind.mplstyle +3 -0
  22. qmlmathplot/themes/seaborn-v0_8-dark-palette.mplstyle +3 -0
  23. qmlmathplot/themes/seaborn-v0_8-dark.mplstyle +30 -0
  24. qmlmathplot/themes/seaborn-v0_8-darkgrid.mplstyle +30 -0
  25. qmlmathplot/themes/seaborn-v0_8-deep.mplstyle +3 -0
  26. qmlmathplot/themes/seaborn-v0_8-muted.mplstyle +3 -0
  27. qmlmathplot/themes/seaborn-v0_8-notebook.mplstyle +21 -0
  28. qmlmathplot/themes/seaborn-v0_8-paper.mplstyle +21 -0
  29. qmlmathplot/themes/seaborn-v0_8-pastel.mplstyle +3 -0
  30. qmlmathplot/themes/seaborn-v0_8-poster.mplstyle +21 -0
  31. qmlmathplot/themes/seaborn-v0_8-talk.mplstyle +21 -0
  32. qmlmathplot/themes/seaborn-v0_8-ticks.mplstyle +30 -0
  33. qmlmathplot/themes/seaborn-v0_8-white.mplstyle +30 -0
  34. qmlmathplot/themes/seaborn-v0_8-whitegrid.mplstyle +30 -0
  35. qmlmathplot/themes/seaborn-v0_8.mplstyle +57 -0
  36. qmlmathplot/themes/tableau-colorblind10.mplstyle +3 -0
  37. qmlmathplot/themes.py +236 -0
  38. qmlmathplot/view.py +55 -0
  39. qmlmathplot/widget.py +183 -0
  40. qmlmathplot-0.1.0.dist-info/METADATA +383 -0
  41. qmlmathplot-0.1.0.dist-info/RECORD +43 -0
  42. qmlmathplot-0.1.0.dist-info/WHEEL +4 -0
  43. qmlmathplot-0.1.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,39 @@
1
+ # from https://everyhue.me/posts/sane-color-scheme-for-matplotlib/
2
+
3
+ patch.linewidth: 0.5
4
+ patch.facecolor: 348ABD # blue
5
+ patch.edgecolor: EEEEEE
6
+ patch.antialiased: True
7
+
8
+ font.size: 10.0
9
+
10
+ axes.facecolor: E5E5E5
11
+ axes.edgecolor: white
12
+ axes.linewidth: 1
13
+ axes.grid: True
14
+ axes.titlesize: x-large
15
+ axes.labelsize: large
16
+ axes.labelcolor: 555555
17
+ axes.axisbelow: True # grid/ticks are below elements (e.g., lines, text)
18
+
19
+ axes.prop_cycle: cycler('color', ['E24A33', '348ABD', '988ED5', '777777', 'FBC15E', '8EBA42', 'FFB5B8'])
20
+ # E24A33 : red
21
+ # 348ABD : blue
22
+ # 988ED5 : purple
23
+ # 777777 : gray
24
+ # FBC15E : yellow
25
+ # 8EBA42 : green
26
+ # FFB5B8 : pink
27
+
28
+ xtick.color: 555555
29
+ xtick.direction: out
30
+
31
+ ytick.color: 555555
32
+ ytick.direction: out
33
+
34
+ grid.color: white
35
+ grid.linestyle: - # solid line
36
+
37
+ figure.facecolor: white
38
+ figure.edgecolor: 0.50
39
+
@@ -0,0 +1,29 @@
1
+ # Set all colors to grayscale
2
+ # Note: strings of float values are interpreted by matplotlib as gray values.
3
+
4
+
5
+ lines.color: black
6
+ patch.facecolor: gray
7
+ patch.edgecolor: black
8
+
9
+ text.color: black
10
+
11
+ axes.facecolor: white
12
+ axes.edgecolor: black
13
+ axes.labelcolor: black
14
+ # black to light gray
15
+ axes.prop_cycle: cycler('color', ['0.00', '0.40', '0.60', '0.70'])
16
+
17
+ xtick.color: black
18
+ ytick.color: black
19
+
20
+ grid.color: black
21
+
22
+ figure.facecolor: 0.75
23
+ figure.edgecolor: white
24
+
25
+ image.cmap: gray
26
+
27
+ savefig.facecolor: white
28
+ savefig.edgecolor: white
29
+
@@ -0,0 +1,5 @@
1
+ # Color cycle survey palette from Petroff (2021):
2
+ # https://arxiv.org/abs/2107.02270
3
+ # https://github.com/mpetroff/accessible-color-cycles
4
+ axes.prop_cycle: cycler('color', ['3f90da', 'ffa90e', 'bd1f01', '94a4a2', '832db6', 'a96b59', 'e76300', 'b9ac70', '717581', '92dadd'])
5
+ patch.facecolor: 3f90da
@@ -0,0 +1,3 @@
1
+ # Seaborn bright palette
2
+ axes.prop_cycle: cycler('color', ['003FFF', '03ED3A', 'E8000B', '8A2BE2', 'FFC400', '00D7FF'])
3
+ patch.facecolor: 003FFF
@@ -0,0 +1,3 @@
1
+ # Seaborn colorblind palette
2
+ axes.prop_cycle: cycler('color', ['0072B2', '009E73', 'D55E00', 'CC79A7', 'F0E442', '56B4E9'])
3
+ patch.facecolor: 0072B2
@@ -0,0 +1,3 @@
1
+ # Seaborn dark palette
2
+ axes.prop_cycle: cycler('color', ['001C7F', '017517', '8C0900', '7600A1', 'B8860B', '006374'])
3
+ patch.facecolor: 001C7F
@@ -0,0 +1,30 @@
1
+ # Seaborn common parameters
2
+ # .15 = dark_gray
3
+ # .8 = light_gray
4
+ figure.facecolor: white
5
+ text.color: .15
6
+ axes.labelcolor: .15
7
+ legend.frameon: False
8
+ legend.numpoints: 1
9
+ legend.scatterpoints: 1
10
+ xtick.direction: out
11
+ ytick.direction: out
12
+ xtick.color: .15
13
+ ytick.color: .15
14
+ axes.axisbelow: True
15
+ image.cmap: Greys
16
+ font.family: sans-serif
17
+ font.sans-serif: Arial, Liberation Sans, DejaVu Sans, Bitstream Vera Sans, sans-serif
18
+ grid.linestyle: -
19
+ lines.solid_capstyle: round
20
+
21
+ # Seaborn dark parameters
22
+ axes.grid: False
23
+ axes.facecolor: EAEAF2
24
+ axes.edgecolor: white
25
+ axes.linewidth: 0
26
+ grid.color: white
27
+ xtick.major.size: 0
28
+ ytick.major.size: 0
29
+ xtick.minor.size: 0
30
+ ytick.minor.size: 0
@@ -0,0 +1,30 @@
1
+ # Seaborn common parameters
2
+ # .15 = dark_gray
3
+ # .8 = light_gray
4
+ figure.facecolor: white
5
+ text.color: .15
6
+ axes.labelcolor: .15
7
+ legend.frameon: False
8
+ legend.numpoints: 1
9
+ legend.scatterpoints: 1
10
+ xtick.direction: out
11
+ ytick.direction: out
12
+ xtick.color: .15
13
+ ytick.color: .15
14
+ axes.axisbelow: True
15
+ image.cmap: Greys
16
+ font.family: sans-serif
17
+ font.sans-serif: Arial, Liberation Sans, DejaVu Sans, Bitstream Vera Sans, sans-serif
18
+ grid.linestyle: -
19
+ lines.solid_capstyle: round
20
+
21
+ # Seaborn darkgrid parameters
22
+ axes.grid: True
23
+ axes.facecolor: EAEAF2
24
+ axes.edgecolor: white
25
+ axes.linewidth: 0
26
+ grid.color: white
27
+ xtick.major.size: 0
28
+ ytick.major.size: 0
29
+ xtick.minor.size: 0
30
+ ytick.minor.size: 0
@@ -0,0 +1,3 @@
1
+ # Seaborn deep palette
2
+ axes.prop_cycle: cycler('color', ['4C72B0', '55A868', 'C44E52', '8172B2', 'CCB974', '64B5CD'])
3
+ patch.facecolor: 4C72B0
@@ -0,0 +1,3 @@
1
+ # Seaborn muted palette
2
+ axes.prop_cycle: cycler('color', ['4878CF', '6ACC65', 'D65F5F', 'B47CC7', 'C4AD66', '77BEDB'])
3
+ patch.facecolor: 4878CF
@@ -0,0 +1,21 @@
1
+ # Seaborn notebook context
2
+ figure.figsize: 8.0, 5.5
3
+ axes.labelsize: 11
4
+ axes.titlesize: 12
5
+ xtick.labelsize: 10
6
+ ytick.labelsize: 10
7
+ legend.fontsize: 10
8
+
9
+ grid.linewidth: 1
10
+ lines.linewidth: 1.75
11
+ patch.linewidth: .3
12
+ lines.markersize: 7
13
+ lines.markeredgewidth: 0
14
+
15
+ xtick.major.width: 1
16
+ ytick.major.width: 1
17
+ xtick.minor.width: .5
18
+ ytick.minor.width: .5
19
+
20
+ xtick.major.pad: 7
21
+ ytick.major.pad: 7
@@ -0,0 +1,21 @@
1
+ # Seaborn paper context
2
+ figure.figsize: 6.4, 4.4
3
+ axes.labelsize: 8.8
4
+ axes.titlesize: 9.6
5
+ xtick.labelsize: 8
6
+ ytick.labelsize: 8
7
+ legend.fontsize: 8
8
+
9
+ grid.linewidth: 0.8
10
+ lines.linewidth: 1.4
11
+ patch.linewidth: 0.24
12
+ lines.markersize: 5.6
13
+ lines.markeredgewidth: 0
14
+
15
+ xtick.major.width: 0.8
16
+ ytick.major.width: 0.8
17
+ xtick.minor.width: 0.4
18
+ ytick.minor.width: 0.4
19
+
20
+ xtick.major.pad: 5.6
21
+ ytick.major.pad: 5.6
@@ -0,0 +1,3 @@
1
+ # Seaborn pastel palette
2
+ axes.prop_cycle: cycler('color', ['92C6FF', '97F0AA', 'FF9F9A', 'D0BBFF', 'FFFEA3', 'B0E0E6'])
3
+ patch.facecolor: 92C6FF
@@ -0,0 +1,21 @@
1
+ # Seaborn poster context
2
+ figure.figsize: 12.8, 8.8
3
+ axes.labelsize: 17.6
4
+ axes.titlesize: 19.2
5
+ xtick.labelsize: 16
6
+ ytick.labelsize: 16
7
+ legend.fontsize: 16
8
+
9
+ grid.linewidth: 1.6
10
+ lines.linewidth: 2.8
11
+ patch.linewidth: 0.48
12
+ lines.markersize: 11.2
13
+ lines.markeredgewidth: 0
14
+
15
+ xtick.major.width: 1.6
16
+ ytick.major.width: 1.6
17
+ xtick.minor.width: 0.8
18
+ ytick.minor.width: 0.8
19
+
20
+ xtick.major.pad: 11.2
21
+ ytick.major.pad: 11.2
@@ -0,0 +1,21 @@
1
+ # Seaborn talk context
2
+ figure.figsize: 10.4, 7.15
3
+ axes.labelsize: 14.3
4
+ axes.titlesize: 15.6
5
+ xtick.labelsize: 13
6
+ ytick.labelsize: 13
7
+ legend.fontsize: 13
8
+
9
+ grid.linewidth: 1.3
10
+ lines.linewidth: 2.275
11
+ patch.linewidth: 0.39
12
+ lines.markersize: 9.1
13
+ lines.markeredgewidth: 0
14
+
15
+ xtick.major.width: 1.3
16
+ ytick.major.width: 1.3
17
+ xtick.minor.width: 0.65
18
+ ytick.minor.width: 0.65
19
+
20
+ xtick.major.pad: 9.1
21
+ ytick.major.pad: 9.1
@@ -0,0 +1,30 @@
1
+ # Seaborn common parameters
2
+ # .15 = dark_gray
3
+ # .8 = light_gray
4
+ figure.facecolor: white
5
+ text.color: .15
6
+ axes.labelcolor: .15
7
+ legend.frameon: False
8
+ legend.numpoints: 1
9
+ legend.scatterpoints: 1
10
+ xtick.direction: out
11
+ ytick.direction: out
12
+ xtick.color: .15
13
+ ytick.color: .15
14
+ axes.axisbelow: True
15
+ image.cmap: Greys
16
+ font.family: sans-serif
17
+ font.sans-serif: Arial, Liberation Sans, DejaVu Sans, Bitstream Vera Sans, sans-serif
18
+ grid.linestyle: -
19
+ lines.solid_capstyle: round
20
+
21
+ # Seaborn white parameters
22
+ axes.grid: False
23
+ axes.facecolor: white
24
+ axes.edgecolor: .15
25
+ axes.linewidth: 1.25
26
+ grid.color: .8
27
+ xtick.major.size: 6
28
+ ytick.major.size: 6
29
+ xtick.minor.size: 3
30
+ ytick.minor.size: 3
@@ -0,0 +1,30 @@
1
+ # Seaborn common parameters
2
+ # .15 = dark_gray
3
+ # .8 = light_gray
4
+ figure.facecolor: white
5
+ text.color: .15
6
+ axes.labelcolor: .15
7
+ legend.frameon: False
8
+ legend.numpoints: 1
9
+ legend.scatterpoints: 1
10
+ xtick.direction: out
11
+ ytick.direction: out
12
+ xtick.color: .15
13
+ ytick.color: .15
14
+ axes.axisbelow: True
15
+ image.cmap: Greys
16
+ font.family: sans-serif
17
+ font.sans-serif: Arial, Liberation Sans, DejaVu Sans, Bitstream Vera Sans, sans-serif
18
+ grid.linestyle: -
19
+ lines.solid_capstyle: round
20
+
21
+ # Seaborn white parameters
22
+ axes.grid: False
23
+ axes.facecolor: white
24
+ axes.edgecolor: .15
25
+ axes.linewidth: 1.25
26
+ grid.color: .8
27
+ xtick.major.size: 0
28
+ ytick.major.size: 0
29
+ xtick.minor.size: 0
30
+ ytick.minor.size: 0
@@ -0,0 +1,30 @@
1
+ # Seaborn common parameters
2
+ # .15 = dark_gray
3
+ # .8 = light_gray
4
+ figure.facecolor: white
5
+ text.color: .15
6
+ axes.labelcolor: .15
7
+ legend.frameon: False
8
+ legend.numpoints: 1
9
+ legend.scatterpoints: 1
10
+ xtick.direction: out
11
+ ytick.direction: out
12
+ xtick.color: .15
13
+ ytick.color: .15
14
+ axes.axisbelow: True
15
+ image.cmap: Greys
16
+ font.family: sans-serif
17
+ font.sans-serif: Arial, Liberation Sans, DejaVu Sans, Bitstream Vera Sans, sans-serif
18
+ grid.linestyle: -
19
+ lines.solid_capstyle: round
20
+
21
+ # Seaborn whitegrid parameters
22
+ axes.grid: True
23
+ axes.facecolor: white
24
+ axes.edgecolor: .8
25
+ axes.linewidth: 1
26
+ grid.color: .8
27
+ xtick.major.size: 0
28
+ ytick.major.size: 0
29
+ xtick.minor.size: 0
30
+ ytick.minor.size: 0
@@ -0,0 +1,57 @@
1
+ # default seaborn aesthetic
2
+ # darkgrid + deep palette + notebook context
3
+
4
+ axes.axisbelow: True
5
+ axes.edgecolor: white
6
+ axes.facecolor: EAEAF2
7
+ axes.grid: True
8
+ axes.labelcolor: .15
9
+ axes.labelsize: 11
10
+ axes.linewidth: 0
11
+ axes.prop_cycle: cycler('color', ['4C72B0', '55A868', 'C44E52', '8172B2', 'CCB974', '64B5CD'])
12
+ axes.titlesize: 12
13
+
14
+ figure.facecolor: white
15
+ figure.figsize: 8.0, 5.5
16
+
17
+ font.family: sans-serif
18
+ font.sans-serif: Arial, Liberation Sans, DejaVu Sans, Bitstream Vera Sans, sans-serif
19
+
20
+ grid.color: white
21
+ grid.linestyle: -
22
+ grid.linewidth: 1
23
+
24
+ image.cmap: Greys
25
+
26
+ legend.fontsize: 10
27
+ legend.frameon: False
28
+ legend.numpoints: 1
29
+ legend.scatterpoints: 1
30
+
31
+ lines.linewidth: 1.75
32
+ lines.markeredgewidth: 0
33
+ lines.markersize: 7
34
+ lines.solid_capstyle: round
35
+
36
+ patch.facecolor: 4C72B0
37
+ patch.linewidth: .3
38
+
39
+ text.color: .15
40
+
41
+ xtick.color: .15
42
+ xtick.direction: out
43
+ xtick.labelsize: 10
44
+ xtick.major.pad: 7
45
+ xtick.major.size: 0
46
+ xtick.major.width: 1
47
+ xtick.minor.size: 0
48
+ xtick.minor.width: .5
49
+
50
+ ytick.color: .15
51
+ ytick.direction: out
52
+ ytick.labelsize: 10
53
+ ytick.major.pad: 7
54
+ ytick.major.size: 0
55
+ ytick.major.width: 1
56
+ ytick.minor.size: 0
57
+ ytick.minor.width: .5
@@ -0,0 +1,3 @@
1
+ # Tableau colorblind 10 palette
2
+ axes.prop_cycle: cycler('color', ['006BA4', 'FF800E', 'ABABAB', '595959', '5F9ED1', 'C85200', '898989', 'A2C8EC', 'FFBC79', 'CFCFCF'])
3
+ patch.facecolor: 006BA4
qmlmathplot/themes.py ADDED
@@ -0,0 +1,236 @@
1
+ """Matplotlib-flavoured themes.
2
+
3
+ A theme is a set of overrides for a :class:`~qmlmathplot.plot.Plot`'s style properties.
4
+ The bundled ones are Matplotlib's own style sheets, vendored verbatim into
5
+ ``qmlmathplot/themes/*.mplstyle`` and redistributed under Matplotlib's BSD license (see
6
+ ``themes/LICENSE.matplotlib``). They are converted at load time: Matplotlib's ``rcParams``
7
+ keys are mapped onto this library's property names, and keys that describe things this
8
+ library does not have (spines, figure size, dpi, legend, marker styles, …) are ignored.
9
+
10
+ plot.theme = "ggplot"
11
+ plot.theme = "dark_background"
12
+ plot.theme = "seaborn-v0_8-darkgrid"
13
+ plot.theme = "my-style" # themes/my-style.mplstyle
14
+ plot.theme = "path/to/any.mplstyle" # any file with the same format
15
+
16
+ The file format is Matplotlib's: ``key: value`` lines, ``#`` starts a comment. Only the
17
+ keys listed in ``_KEY_MAP`` are converted; the rest are dropped silently, so a style sheet
18
+ can be dropped in without editing.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import re
24
+ from pathlib import Path
25
+
26
+ __all__ = ["names", "raw", "resolve"]
27
+
28
+ THEME_DIR = Path(__file__).with_name("themes")
29
+
30
+ #: Matplotlib sizes are in points; this library's properties are logical pixels.
31
+ PT_TO_PX = 4.0 / 3.0
32
+
33
+ #: Matplotlib's named font sizes, in points.
34
+ _NAMED_SIZES = {
35
+ "xx-small": 5.79, "x-small": 6.94, "small": 8.33, "medium": 10.0,
36
+ "large": 12.0, "x-large": 14.4, "xx-large": 17.28,
37
+ }
38
+
39
+ _LINE_STYLES = {
40
+ "solid": "-", "dashed": "--", "dotted": ":", "dashdot": "-.", "none": "", "": "",
41
+ "-": "-", "--": "--", ":": ":", "-.": "-.",
42
+ }
43
+
44
+ #: matplotlib's single-letter colour names.
45
+ _SHORT_COLORS = {
46
+ "b": "blue", "g": "green", "r": "red", "c": "cyan",
47
+ "m": "magenta", "y": "yellow", "k": "black", "w": "white",
48
+ }
49
+
50
+ #: Font weights that mean "bold" (matplotlib's ``axes.titleweight``).
51
+ _BOLD_WEIGHTS = frozenset({"bold", "heavy", "semibold", "black"})
52
+
53
+ #: For every Plot property, the style-sheet keys that can feed it, most specific first.
54
+ #: A key that is absent is skipped; unknown keys are dropped entirely.
55
+ _SOURCES: dict[str, tuple[str, ...]] = {
56
+ "background": ("axes.facecolor", "figure.facecolor"),
57
+ "grid": ("axes.grid",),
58
+ "grid_color": ("grid.color",),
59
+ "grid_width": ("grid.linewidth",),
60
+ "grid_alpha": ("grid.alpha",),
61
+ "grid_style": ("grid.linestyle",),
62
+ "color_cycle": ("axes.prop_cycle",),
63
+ "line_width": ("lines.linewidth",),
64
+ "text_color": ("text.color", "axes.labelcolor"),
65
+ "tick_color": ("xtick.color", "ytick.color"),
66
+ "tick_length": ("xtick.major.size", "ytick.major.size"),
67
+ "font_size": ("font.size",),
68
+ "tick_font_size": ("xtick.labelsize", "ytick.labelsize"),
69
+ "title_color": ("axes.titlecolor",),
70
+ "title_font_size": ("axes.titlesize",),
71
+ "title_bold": ("axes.titleweight",),
72
+ }
73
+
74
+ #: Keys whose value is a boolean rather than text.
75
+ _BOOL_KEYS = frozenset({"axes.grid", "axes.titleweight"})
76
+ #: Keys whose value is a size in points (converted to logical pixels).
77
+ _PT_KEYS = frozenset({
78
+ "grid.linewidth", "lines.linewidth", "xtick.major.size", "ytick.major.size",
79
+ "font.size", "xtick.labelsize", "ytick.labelsize", "axes.titlesize",
80
+ })
81
+ #: Keys whose value is a colour.
82
+ _COLOR_KEYS = frozenset({
83
+ "xtick.color", "ytick.color", "text.color", "axes.labelcolor", "axes.titlecolor",
84
+ "grid.color", "axes.facecolor", "figure.facecolor",
85
+ })
86
+
87
+
88
+ def names() -> list[str]:
89
+ """Names of the bundled themes (file stems), plus ``"default"`` (no overrides)."""
90
+ stems = sorted(p.stem for p in THEME_DIR.glob("*.mplstyle")) if THEME_DIR.is_dir() else []
91
+ return ["default", *stems]
92
+
93
+
94
+ def raw(name: str) -> dict[str, str]:
95
+ """The theme's raw ``rcParams`` (key -> text value), before conversion."""
96
+ if name == "default":
97
+ return {}
98
+ path = Path(name)
99
+ if not path.is_file():
100
+ path = THEME_DIR / f"{name}.mplstyle"
101
+ if not path.is_file():
102
+ raise KeyError(f"unknown theme {name!r}; available: {', '.join(names())}")
103
+ return _parse(path.read_text(encoding="utf-8"))
104
+
105
+
106
+ def resolve(name: str) -> dict[str, object]:
107
+ """Convert a theme into ``{plot property: value}`` overrides.
108
+
109
+ Only keys this library understands are returned; everything else in the style sheet
110
+ (spines, figure size, dpi, legend, marker styles, …) is dropped, because there is no
111
+ figure, no axes box and no legend here.
112
+ """
113
+ values = raw(name)
114
+ cycle = _parse_cycle(values.get("axes.prop_cycle", ""))
115
+ out: dict[str, object] = {}
116
+ for prop, keys in _SOURCES.items():
117
+ for key in keys:
118
+ if key not in values:
119
+ continue
120
+ value = _convert(key, values[key], cycle)
121
+ if value is not None:
122
+ out[prop] = value
123
+ break
124
+ return out
125
+
126
+
127
+ def _convert(key: str, text: str, cycle: list[str]) -> object | None:
128
+ """One style-sheet value -> the value a Plot property expects (``None`` = skip)."""
129
+ if key == "axes.prop_cycle":
130
+ return cycle or None
131
+ if key == "axes.grid":
132
+ return text.strip().lower() in ("true", "1", "yes", "on")
133
+ if key == "axes.titleweight":
134
+ return text.strip().lower() in _BOLD_WEIGHTS
135
+ if key == "grid.linestyle":
136
+ return _LINE_STYLES.get(text.strip().lower())
137
+ if key in _PT_KEYS:
138
+ return _size(text) * PT_TO_PX if key.endswith("size") else _number(text) * PT_TO_PX
139
+ if key in _COLOR_KEYS:
140
+ return _color(text, cycle)
141
+ return None
142
+
143
+
144
+ # --------------------------------------------------------------------------- parsing
145
+
146
+
147
+ def _parse(text: str) -> dict[str, str]:
148
+ """Parse ``key: value`` lines; later keys win (Matplotlib semantics)."""
149
+ out: dict[str, str] = {}
150
+ for line in text.splitlines():
151
+ line = line.split("#", 1)[0].strip() if not line.lstrip().startswith("#") else ""
152
+ if not line or ":" not in line:
153
+ continue
154
+ key, _, value = line.partition(":")
155
+ out[key.strip()] = value.strip()
156
+ return out
157
+
158
+
159
+ def _number(text: str) -> float:
160
+ try:
161
+ return float(text.strip())
162
+ except ValueError:
163
+ return 0.0
164
+
165
+
166
+ def _size(text: str) -> float:
167
+ """A font size: a number in points, a named size, or a relative one."""
168
+ text = text.strip().lower()
169
+ if text in _NAMED_SIZES:
170
+ return _NAMED_SIZES[text]
171
+ try:
172
+ return float(text)
173
+ except ValueError:
174
+ return _NAMED_SIZES["medium"]
175
+
176
+
177
+ _CYCLE_LIST = re.compile(r"\[([^\]]*)\]")
178
+ _QUOTED = re.compile(r"['\"]([^'\"]+)['\"]")
179
+
180
+
181
+ def _parse_cycle(text: str) -> list[str]:
182
+ """``cycler('color', ['#1f77b4', …])`` -> the colour list.
183
+
184
+ Only the quoted strings *inside the list* count: the first argument of ``cycler`` is the
185
+ property name (``'color'``) and must not be mistaken for a colour.
186
+ """
187
+ if "cycler" not in text:
188
+ return []
189
+ inside = _CYCLE_LIST.search(text)
190
+ if not inside:
191
+ return []
192
+ out: list[str] = []
193
+ for item in _QUOTED.findall(inside.group(1)):
194
+ value = _color(item, [])
195
+ if isinstance(value, str):
196
+ out.append(value)
197
+ return out
198
+
199
+
200
+ def _gray(value: float) -> str:
201
+ """A 0..1 grayscale value as ``#rrggbb`` (matplotlib's float colour shorthand)."""
202
+ level = int(round(max(0.0, min(1.0, value)) * 255))
203
+ return f"#{level:02x}{level:02x}{level:02x}"
204
+
205
+
206
+ def _normalise_hex(text: str) -> str:
207
+ """Matplotlib accepts hex colours without the leading ``#`` (``E24A33``)."""
208
+ text = text.strip()
209
+ if re.fullmatch(r"[0-9a-fA-F]{6}", text) or re.fullmatch(r"[0-9a-fA-F]{3}", text):
210
+ return f"#{text}"
211
+ return text
212
+
213
+
214
+ def _color(text: str, cycle: list[str]) -> str | float | None:
215
+ """A colour value: ``#rrggbb``, a name, a grayscale float, or ``C0``… from the cycle.
216
+
217
+ Returns ``None`` for values that mean "nothing" (``none``), which have no equivalent
218
+ here because there is no frame or spine to hide.
219
+ """
220
+ text = text.strip()
221
+ low = text.lower()
222
+ if low in ("none", ""):
223
+ return None
224
+ match = re.fullmatch(r"c(\d+)", low)
225
+ if match:
226
+ index = int(match.group(1))
227
+ return cycle[index % len(cycle)] if cycle else None
228
+ if text in _SHORT_COLORS: # matplotlib's single-letter names ("w", "k")
229
+ return _SHORT_COLORS[text]
230
+ if re.fullmatch(r"[0-9a-fA-F]{6}", text) or re.fullmatch(r"[0-9a-fA-F]{3}", text):
231
+ return f"#{text}" # matplotlib accepts hex without the leading '#'
232
+ try:
233
+ value = float(text)
234
+ except ValueError:
235
+ return text # a colour name ("white", "steelblue", …)
236
+ return _gray(value) if 0.0 <= value <= 1.0 else None # grayscale shorthand ("0.8")