Coverage for src / tracekit / visualization / presets.py: 100%
60 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 23:04 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 23:04 +0000
1"""Comprehensive visualization presets combining styles, colors, and rendering.
3This module provides integrated presets that combine style settings, color
4palettes, and rendering configuration for different use cases.
7Example:
8 >>> from tracekit.visualization.presets import apply_preset
9 >>> with apply_preset("ieee_publication"):
10 ... plot_waveform(signal)
12References:
13 - IEEE publication standards
14 - Presentation best practices
15 - Colorblind-safe palette design
16"""
18from __future__ import annotations
20from contextlib import contextmanager
21from dataclasses import dataclass
22from typing import TYPE_CHECKING, Any
24if TYPE_CHECKING:
25 from collections.abc import Iterator
27try:
28 import matplotlib.pyplot as plt
30 HAS_MATPLOTLIB = True
31except ImportError:
32 HAS_MATPLOTLIB = False
35@dataclass
36class VisualizationPreset:
37 """Complete visualization preset configuration.
39 Attributes:
40 name: Preset name
41 description: Preset description
42 style_params: Matplotlib rcParams
43 color_palette: List of colors for multi-channel plots
44 dpi: Target DPI
45 figure_size: Default figure size (width, height) in inches
46 font_family: Font family
47 colorblind_safe: Whether palette is colorblind-safe
48 print_optimized: Whether optimized for print output
49 """
51 name: str
52 description: str
53 style_params: dict[str, Any]
54 color_palette: list[str]
55 dpi: int = 96
56 figure_size: tuple[float, float] = (10, 6)
57 font_family: str = "sans-serif"
58 colorblind_safe: bool = True
59 print_optimized: bool = False
62# IEEE Publication Preset (VIS-020)
63IEEE_PUBLICATION_PRESET = VisualizationPreset(
64 name="ieee_publication",
65 description="IEEE publication quality (single-column, grayscale-friendly)",
66 dpi=600,
67 figure_size=(3.5, 2.5), # IEEE single-column width
68 font_family="serif",
69 colorblind_safe=True,
70 print_optimized=True,
71 color_palette=[
72 "#000000", # Black
73 "#555555", # Dark gray
74 "#AAAAAA", # Light gray
75 "#0173B2", # Blue (grayscale-safe)
76 "#DE8F05", # Orange (grayscale-safe)
77 "#029E73", # Green (grayscale-safe)
78 ],
79 style_params={
80 "figure.dpi": 600,
81 "savefig.dpi": 600,
82 "savefig.format": "pdf",
83 "savefig.bbox": "tight",
84 "font.family": "serif",
85 "font.size": 8,
86 "axes.titlesize": 9,
87 "axes.labelsize": 8,
88 "xtick.labelsize": 7,
89 "ytick.labelsize": 7,
90 "legend.fontsize": 7,
91 "lines.linewidth": 0.8,
92 "lines.markersize": 3.0,
93 "axes.linewidth": 0.6,
94 "grid.linewidth": 0.4,
95 "grid.alpha": 0.3,
96 "grid.linestyle": ":",
97 "axes.grid": True,
98 "axes.axisbelow": True,
99 "xtick.major.width": 0.6,
100 "ytick.major.width": 0.6,
101 "xtick.minor.width": 0.4,
102 "ytick.minor.width": 0.4,
103 "lines.antialiased": False, # Sharper for print
104 "patch.antialiased": False,
105 "mathtext.fontset": "cm", # Computer Modern (LaTeX-like)
106 },
107)
109# IEEE Double-Column Preset
110IEEE_DOUBLE_COLUMN_PRESET = VisualizationPreset(
111 name="ieee_double_column",
112 description="IEEE publication quality (double-column width)",
113 dpi=600,
114 figure_size=(7.0, 2.5), # IEEE double-column width
115 font_family="serif",
116 colorblind_safe=True,
117 print_optimized=True,
118 color_palette=IEEE_PUBLICATION_PRESET.color_palette,
119 style_params=IEEE_PUBLICATION_PRESET.style_params.copy(),
120)
122# Presentation Preset
123PRESENTATION_PRESET = VisualizationPreset(
124 name="presentation",
125 description="Presentation slides (high contrast, large fonts, bold lines)",
126 dpi=96,
127 figure_size=(12, 7),
128 font_family="sans-serif",
129 colorblind_safe=True,
130 print_optimized=False,
131 color_palette=[
132 "#0173B2", # Blue
133 "#DE8F05", # Orange
134 "#029E73", # Green
135 "#CC78BC", # Purple
136 "#CA9161", # Brown
137 "#ECE133", # Yellow
138 ],
139 style_params={
140 "figure.dpi": 96,
141 "font.family": "sans-serif",
142 "font.size": 18,
143 "axes.titlesize": 22,
144 "axes.labelsize": 20,
145 "xtick.labelsize": 16,
146 "ytick.labelsize": 16,
147 "legend.fontsize": 16,
148 "lines.linewidth": 3.0,
149 "lines.markersize": 10.0,
150 "axes.linewidth": 2.0,
151 "grid.linewidth": 1.0,
152 "grid.alpha": 0.4,
153 "axes.grid": True,
154 "xtick.major.width": 2.0,
155 "ytick.major.width": 2.0,
156 "xtick.major.size": 8,
157 "ytick.major.size": 8,
158 "lines.antialiased": True,
159 "patch.antialiased": True,
160 },
161)
163# Screen/Interactive Preset
164SCREEN_PRESET = VisualizationPreset(
165 name="screen",
166 description="Screen viewing (vibrant colors, medium fonts, anti-aliased)",
167 dpi=96,
168 figure_size=(10, 6),
169 font_family="sans-serif",
170 colorblind_safe=True,
171 print_optimized=False,
172 color_palette=[
173 "#1F77B4", # Blue
174 "#FF7F0E", # Orange
175 "#2CA02C", # Green
176 "#D62728", # Red
177 "#9467BD", # Purple
178 "#8C564B", # Brown
179 "#E377C2", # Pink
180 "#7F7F7F", # Gray
181 ],
182 style_params={
183 "figure.dpi": 96,
184 "font.family": "sans-serif",
185 "font.size": 10,
186 "axes.titlesize": 12,
187 "axes.labelsize": 10,
188 "xtick.labelsize": 9,
189 "ytick.labelsize": 9,
190 "legend.fontsize": 9,
191 "lines.linewidth": 1.5,
192 "lines.markersize": 6.0,
193 "axes.linewidth": 1.0,
194 "grid.linewidth": 0.6,
195 "grid.alpha": 0.3,
196 "axes.grid": True,
197 "lines.antialiased": True,
198 "patch.antialiased": True,
199 },
200)
202# Print Preset
203PRINT_PRESET = VisualizationPreset(
204 name="print",
205 description="Print output (300 DPI, CMYK-safe colors, optimized file size)",
206 dpi=300,
207 figure_size=(8, 5),
208 font_family="serif",
209 colorblind_safe=True,
210 print_optimized=True,
211 color_palette=[
212 "#0173B2", # Blue (CMYK-safe)
213 "#DE8F05", # Orange (CMYK-safe)
214 "#029E73", # Green (CMYK-safe)
215 "#CC78BC", # Purple (CMYK-safe)
216 "#555555", # Gray (CMYK-safe)
217 ],
218 style_params={
219 "figure.dpi": 300,
220 "savefig.dpi": 300,
221 "savefig.format": "pdf",
222 "font.family": "serif",
223 "font.size": 11,
224 "axes.titlesize": 13,
225 "axes.labelsize": 11,
226 "xtick.labelsize": 10,
227 "ytick.labelsize": 10,
228 "legend.fontsize": 10,
229 "lines.linewidth": 1.2,
230 "lines.markersize": 5.0,
231 "axes.linewidth": 1.0,
232 "grid.linewidth": 0.6,
233 "grid.alpha": 0.3,
234 "axes.grid": True,
235 "lines.antialiased": False, # Cleaner for print
236 "patch.antialiased": False,
237 },
238)
240# Dark Theme Preset
241DARK_THEME_PRESET = VisualizationPreset(
242 name="dark",
243 description="Dark theme (dark background, high-contrast colors)",
244 dpi=96,
245 figure_size=(10, 6),
246 font_family="sans-serif",
247 colorblind_safe=True,
248 print_optimized=False,
249 color_palette=[
250 "#56B4E9", # Light blue
251 "#E69F00", # Orange
252 "#009E73", # Green
253 "#F0E442", # Yellow
254 "#CC79A7", # Pink
255 "#0072B2", # Blue
256 ],
257 style_params={
258 "figure.dpi": 96,
259 "figure.facecolor": "#1E1E1E",
260 "axes.facecolor": "#2D2D2D",
261 "axes.edgecolor": "#CCCCCC",
262 "axes.labelcolor": "#CCCCCC",
263 "text.color": "#CCCCCC",
264 "xtick.color": "#CCCCCC",
265 "ytick.color": "#CCCCCC",
266 "grid.color": "#555555",
267 "grid.alpha": 0.5,
268 "font.family": "sans-serif",
269 "font.size": 10,
270 "lines.linewidth": 1.5,
271 "axes.grid": True,
272 "lines.antialiased": True,
273 },
274)
276# Preset registry
277PRESETS: dict[str, VisualizationPreset] = {
278 "ieee_publication": IEEE_PUBLICATION_PRESET,
279 "ieee_double_column": IEEE_DOUBLE_COLUMN_PRESET,
280 "presentation": PRESENTATION_PRESET,
281 "screen": SCREEN_PRESET,
282 "print": PRINT_PRESET,
283 "dark": DARK_THEME_PRESET,
284}
287@contextmanager
288def apply_preset(
289 preset: str | VisualizationPreset,
290 *,
291 overrides: dict[str, Any] | None = None,
292) -> Iterator[VisualizationPreset]:
293 """Apply visualization preset as context manager./VIS-024.
295 Combines style settings, color palette, and rendering configuration.
297 Args:
298 preset: Preset name or VisualizationPreset object.
299 overrides: Dictionary of rcParams to override.
301 Yields:
302 VisualizationPreset object for access to color palette.
304 Raises:
305 ValueError: If preset name is unknown.
306 ImportError: If matplotlib is not available.
308 Example:
309 >>> with apply_preset("ieee_publication") as preset:
310 ... fig, ax = plt.subplots(figsize=preset.figure_size)
311 ... ax.plot(x, y, color=preset.color_palette[0])
312 ... plt.savefig("figure.pdf")
314 >>> # With custom overrides
315 >>> with apply_preset("screen", overrides={"font.size": 14}):
316 ... plot_waveform(signal)
318 References:
319 VIS-020: IEEE Publication Style Preset
320 VIS-024: Plot Style Presets
321 """
322 if not HAS_MATPLOTLIB:
323 raise ImportError("matplotlib is required for visualization presets")
325 # Get preset object
326 if isinstance(preset, str):
327 if preset not in PRESETS:
328 raise ValueError(f"Unknown preset: {preset}. Available: {list(PRESETS.keys())}")
329 preset_obj = PRESETS[preset]
330 else:
331 preset_obj = preset
333 # Build rcParams dictionary
334 rc_dict = preset_obj.style_params.copy()
336 # Apply overrides
337 if overrides:
338 rc_dict.update(overrides)
340 # Apply as context
341 with plt.rc_context(rc_dict):
342 yield preset_obj
345def get_preset_colors(
346 preset: str | VisualizationPreset,
347 n_colors: int | None = None,
348) -> list[str]:
349 """Get color palette from preset.
351 Args:
352 preset: Preset name or object.
353 n_colors: Number of colors to return (None = all).
355 Returns:
356 List of color hex codes.
358 Raises:
359 ValueError: If unknown preset name.
361 Example:
362 >>> colors = get_preset_colors("ieee_publication", n_colors=3)
363 >>> # Use colors for multi-channel plot
365 References:
366 VIS-023: Data-Driven Color Palette
367 VIS-024: Plot Style Presets
368 """
369 if isinstance(preset, str):
370 if preset not in PRESETS:
371 raise ValueError(f"Unknown preset: {preset}")
372 preset_obj = PRESETS[preset]
373 else:
374 preset_obj = preset
376 colors = preset_obj.color_palette
378 if n_colors is not None:
379 if n_colors <= len(colors):
380 return colors[:n_colors]
381 else:
382 # Cycle colors if more needed
383 return [colors[i % len(colors)] for i in range(n_colors)]
385 return colors
388def list_presets() -> list[str]:
389 """Get list of available preset names.
391 Returns:
392 List of preset names.
394 Example:
395 >>> presets = list_presets()
396 >>> print(presets)
397 ['ieee_publication', 'presentation', 'screen', 'print', 'dark']
398 """
399 return list(PRESETS.keys())
402def create_custom_preset(
403 name: str,
404 base_preset: str = "screen",
405 **kwargs: Any,
406) -> VisualizationPreset:
407 """Create custom preset by inheriting from base.
409 Args:
410 name: Name for custom preset.
411 base_preset: Base preset to inherit from.
412 **kwargs: Attributes to override.
414 Returns:
415 Custom VisualizationPreset object.
417 Raises:
418 ValueError: If base_preset is unknown.
420 Example:
421 >>> custom = create_custom_preset(
422 ... "my_preset",
423 ... base_preset="ieee_publication",
424 ... figure_size=(5, 3),
425 ... dpi=300,
426 ... )
427 >>> with apply_preset(custom):
428 ... plot_data()
430 References:
431 VIS-024: Plot Style Presets (custom preset creation)
432 """
433 if base_preset not in PRESETS:
434 raise ValueError(f"Unknown base_preset: {base_preset}")
436 base = PRESETS[base_preset]
438 # Create copy with overrides
439 preset_dict = {
440 "name": name,
441 "description": kwargs.get("description", f"Custom preset based on {base_preset}"),
442 "style_params": kwargs.get("style_params", base.style_params.copy()),
443 "color_palette": kwargs.get("color_palette", base.color_palette.copy()),
444 "dpi": kwargs.get("dpi", base.dpi),
445 "figure_size": kwargs.get("figure_size", base.figure_size),
446 "font_family": kwargs.get("font_family", base.font_family),
447 "colorblind_safe": kwargs.get("colorblind_safe", base.colorblind_safe),
448 "print_optimized": kwargs.get("print_optimized", base.print_optimized),
449 }
451 return VisualizationPreset(**preset_dict)
454__all__ = [
455 "DARK_THEME_PRESET",
456 "IEEE_DOUBLE_COLUMN_PRESET",
457 "IEEE_PUBLICATION_PRESET",
458 "PRESENTATION_PRESET",
459 "PRESETS",
460 "PRINT_PRESET",
461 "SCREEN_PRESET",
462 "VisualizationPreset",
463 "apply_preset",
464 "create_custom_preset",
465 "get_preset_colors",
466 "list_presets",
467]