plotpress 0.23.2__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.
plotpress/style.py ADDED
@@ -0,0 +1,91 @@
1
+ """Per-figure styling. Replaces matplotlib's global ``rcParams``.
2
+
3
+ Every :class:`~plotpress.figure.Figure` owns its own :class:`Style` instance, so
4
+ nothing here is global. Mutating one figure's style never affects another.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field, replace
10
+ from typing import List
11
+
12
+ # Default categorical color cycle (matplotlib's "tab10").
13
+ DEFAULT_COLOR_CYCLE: List[str] = [
14
+ "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd",
15
+ "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf",
16
+ ]
17
+
18
+
19
+ @dataclass
20
+ class Style:
21
+ """Visual configuration for a single figure.
22
+
23
+ Create a variant without mutating the original via :meth:`copy`.
24
+ """
25
+
26
+ # Figure
27
+ facecolor: str = "#ffffff"
28
+ dpi: float = 100.0
29
+
30
+ # Axes
31
+ axes_facecolor: str = "#ffffff"
32
+ spine_color: str = "#000000"
33
+ spine_width: float = 0.8
34
+
35
+ # Text
36
+ #
37
+ # Layout reserves space for text using bundled advance widths (see
38
+ # plotpress.fonts), because an SVG figure is laid out before anything draws
39
+ # the glyphs. Helvetica, Times, Courier and DejaVu Sans are bundled, along
40
+ # with their metric-compatible clones, so those families are accurate. A
41
+ # family outside them -- Verdana, Tahoma, Arial Black, Arial Narrow -- still
42
+ # renders, but is measured as Helvetica, so its legend boxes and axis
43
+ # margins will not fit it. Set measure_installed_fonts for those.
44
+ font_family: str = "Helvetica, Arial, sans-serif"
45
+ font_size: float = 10.0
46
+ title_size: float = 12.0
47
+ label_size: float = 11.0
48
+ text_color: str = "#000000"
49
+
50
+ # Measure the font files installed on this machine instead of the bundled
51
+ # tables. More faithful for families plotpress cannot measure, at the cost
52
+ # of the guarantee that the same script lays out identically everywhere --
53
+ # which is why it is off. See plotpress.fonts.installed.
54
+ measure_installed_fonts: bool = False
55
+
56
+ # Ticks
57
+ tick_size: float = 3.5
58
+ tick_width: float = 0.8
59
+ tick_label_size: float = 9.0
60
+
61
+ # Grid
62
+ grid_color: str = "#b0b0b0"
63
+ grid_width: float = 0.6
64
+ grid_alpha: float = 0.6
65
+
66
+ # Lines / markers
67
+ line_width: float = 1.5
68
+ marker_size: float = 6.0 # diameter in points
69
+
70
+ color_cycle: List[str] = field(default_factory=lambda: list(DEFAULT_COLOR_CYCLE))
71
+
72
+ def text_width(self, text: str, size: float, bold: bool = False,
73
+ italic: bool = False) -> float:
74
+ """Predicted width of ``text`` in pixels, per this style's font settings.
75
+
76
+ The measuring entry point layout should use: it carries ``font_family``
77
+ and ``measure_installed_fonts`` with it, so no caller has to remember to
78
+ pass either.
79
+ """
80
+ from .fonts import text_width
81
+
82
+ return text_width(text, size, self.font_family, bold, italic,
83
+ measure_installed=self.measure_installed_fonts)
84
+
85
+ def copy(self, **overrides) -> "Style":
86
+ """Return a modified copy, leaving this instance untouched.
87
+
88
+ Mutable fields are duplicated so two figures never share a list.
89
+ """
90
+ overrides.setdefault("color_cycle", list(self.color_cycle))
91
+ return replace(self, **overrides)