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

1"""Comprehensive visualization presets combining styles, colors, and rendering. 

2 

3This module provides integrated presets that combine style settings, color 

4palettes, and rendering configuration for different use cases. 

5 

6 

7Example: 

8 >>> from tracekit.visualization.presets import apply_preset 

9 >>> with apply_preset("ieee_publication"): 

10 ... plot_waveform(signal) 

11 

12References: 

13 - IEEE publication standards 

14 - Presentation best practices 

15 - Colorblind-safe palette design 

16""" 

17 

18from __future__ import annotations 

19 

20from contextlib import contextmanager 

21from dataclasses import dataclass 

22from typing import TYPE_CHECKING, Any 

23 

24if TYPE_CHECKING: 

25 from collections.abc import Iterator 

26 

27try: 

28 import matplotlib.pyplot as plt 

29 

30 HAS_MATPLOTLIB = True 

31except ImportError: 

32 HAS_MATPLOTLIB = False 

33 

34 

35@dataclass 

36class VisualizationPreset: 

37 """Complete visualization preset configuration. 

38 

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 """ 

50 

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 

60 

61 

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) 

108 

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) 

121 

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) 

162 

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) 

201 

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) 

239 

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) 

275 

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} 

285 

286 

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. 

294 

295 Combines style settings, color palette, and rendering configuration. 

296 

297 Args: 

298 preset: Preset name or VisualizationPreset object. 

299 overrides: Dictionary of rcParams to override. 

300 

301 Yields: 

302 VisualizationPreset object for access to color palette. 

303 

304 Raises: 

305 ValueError: If preset name is unknown. 

306 ImportError: If matplotlib is not available. 

307 

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") 

313 

314 >>> # With custom overrides 

315 >>> with apply_preset("screen", overrides={"font.size": 14}): 

316 ... plot_waveform(signal) 

317 

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") 

324 

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 

332 

333 # Build rcParams dictionary 

334 rc_dict = preset_obj.style_params.copy() 

335 

336 # Apply overrides 

337 if overrides: 

338 rc_dict.update(overrides) 

339 

340 # Apply as context 

341 with plt.rc_context(rc_dict): 

342 yield preset_obj 

343 

344 

345def get_preset_colors( 

346 preset: str | VisualizationPreset, 

347 n_colors: int | None = None, 

348) -> list[str]: 

349 """Get color palette from preset. 

350 

351 Args: 

352 preset: Preset name or object. 

353 n_colors: Number of colors to return (None = all). 

354 

355 Returns: 

356 List of color hex codes. 

357 

358 Raises: 

359 ValueError: If unknown preset name. 

360 

361 Example: 

362 >>> colors = get_preset_colors("ieee_publication", n_colors=3) 

363 >>> # Use colors for multi-channel plot 

364 

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 

375 

376 colors = preset_obj.color_palette 

377 

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)] 

384 

385 return colors 

386 

387 

388def list_presets() -> list[str]: 

389 """Get list of available preset names. 

390 

391 Returns: 

392 List of preset names. 

393 

394 Example: 

395 >>> presets = list_presets() 

396 >>> print(presets) 

397 ['ieee_publication', 'presentation', 'screen', 'print', 'dark'] 

398 """ 

399 return list(PRESETS.keys()) 

400 

401 

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. 

408 

409 Args: 

410 name: Name for custom preset. 

411 base_preset: Base preset to inherit from. 

412 **kwargs: Attributes to override. 

413 

414 Returns: 

415 Custom VisualizationPreset object. 

416 

417 Raises: 

418 ValueError: If base_preset is unknown. 

419 

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() 

429 

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}") 

435 

436 base = PRESETS[base_preset] 

437 

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 } 

450 

451 return VisualizationPreset(**preset_dict) 

452 

453 

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]