figrecipe 0.6.0__py3-none-any.whl → 0.7.4__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 (177) hide show
  1. figrecipe/__init__.py +106 -973
  2. figrecipe/_api/__init__.py +48 -0
  3. figrecipe/_api/_extract.py +108 -0
  4. figrecipe/_api/_notebook.py +61 -0
  5. figrecipe/_api/_panel.py +46 -0
  6. figrecipe/_api/_save.py +191 -0
  7. figrecipe/_api/_seaborn_proxy.py +34 -0
  8. figrecipe/_api/_style_manager.py +153 -0
  9. figrecipe/_api/_subplots.py +333 -0
  10. figrecipe/_api/_validate.py +82 -0
  11. figrecipe/_dev/__init__.py +2 -93
  12. figrecipe/_dev/_plotters.py +76 -0
  13. figrecipe/_dev/_run_demos.py +56 -0
  14. figrecipe/_dev/demo_plotters/__init__.py +35 -166
  15. figrecipe/_dev/demo_plotters/_categories.py +81 -0
  16. figrecipe/_dev/demo_plotters/_figure_creators.py +119 -0
  17. figrecipe/_dev/demo_plotters/_helpers.py +31 -0
  18. figrecipe/_dev/demo_plotters/_registry.py +50 -0
  19. figrecipe/_dev/demo_plotters/bar_categorical/__init__.py +4 -0
  20. figrecipe/_dev/demo_plotters/contour_surface/__init__.py +4 -0
  21. figrecipe/_dev/demo_plotters/distribution/__init__.py +4 -0
  22. figrecipe/_dev/demo_plotters/image_matrix/__init__.py +4 -0
  23. figrecipe/_dev/demo_plotters/line_curve/__init__.py +4 -0
  24. figrecipe/_dev/demo_plotters/{plot_plot.py → line_curve/plot_plot.py} +3 -2
  25. figrecipe/_dev/demo_plotters/scatter_points/__init__.py +4 -0
  26. figrecipe/_dev/demo_plotters/special/__init__.py +4 -0
  27. figrecipe/_dev/demo_plotters/{plot_pie.py → special/plot_pie.py} +5 -1
  28. figrecipe/_dev/demo_plotters/spectral_signal/__init__.py +4 -0
  29. figrecipe/_dev/demo_plotters/vector_flow/__init__.py +4 -0
  30. figrecipe/_editor/__init__.py +57 -9
  31. figrecipe/_editor/_bbox/__init__.py +43 -0
  32. figrecipe/_editor/_bbox/_collections.py +177 -0
  33. figrecipe/_editor/_bbox/_elements.py +159 -0
  34. figrecipe/_editor/_bbox/_extract.py +256 -0
  35. figrecipe/_editor/_bbox/_extract_axes.py +370 -0
  36. figrecipe/_editor/_bbox/_extract_text.py +342 -0
  37. figrecipe/_editor/_bbox/_lines.py +173 -0
  38. figrecipe/_editor/_bbox/_transforms.py +146 -0
  39. figrecipe/_editor/_flask_app.py +68 -1039
  40. figrecipe/_editor/_helpers.py +242 -0
  41. figrecipe/_editor/_hitmap/__init__.py +76 -0
  42. figrecipe/_editor/_hitmap/_artists/__init__.py +21 -0
  43. figrecipe/_editor/_hitmap/_artists/_collections.py +345 -0
  44. figrecipe/_editor/_hitmap/_artists/_images.py +68 -0
  45. figrecipe/_editor/_hitmap/_artists/_lines.py +107 -0
  46. figrecipe/_editor/_hitmap/_artists/_patches.py +163 -0
  47. figrecipe/_editor/_hitmap/_artists/_text.py +190 -0
  48. figrecipe/_editor/_hitmap/_colors.py +181 -0
  49. figrecipe/_editor/_hitmap/_detect.py +137 -0
  50. figrecipe/_editor/_hitmap/_restore.py +154 -0
  51. figrecipe/_editor/_hitmap_main.py +182 -0
  52. figrecipe/_editor/_preferences.py +135 -0
  53. figrecipe/_editor/_render_overrides.py +480 -0
  54. figrecipe/_editor/_renderer.py +35 -185
  55. figrecipe/_editor/_routes_axis.py +453 -0
  56. figrecipe/_editor/_routes_core.py +284 -0
  57. figrecipe/_editor/_routes_element.py +317 -0
  58. figrecipe/_editor/_routes_style.py +223 -0
  59. figrecipe/_editor/_templates/__init__.py +78 -1
  60. figrecipe/_editor/_templates/_html.py +109 -13
  61. figrecipe/_editor/_templates/_scripts/__init__.py +120 -0
  62. figrecipe/_editor/_templates/_scripts/_api.py +228 -0
  63. figrecipe/_editor/_templates/_scripts/_colors.py +485 -0
  64. figrecipe/_editor/_templates/_scripts/_core.py +436 -0
  65. figrecipe/_editor/_templates/_scripts/_debug_snapshot.py +186 -0
  66. figrecipe/_editor/_templates/_scripts/_element_editor.py +310 -0
  67. figrecipe/_editor/_templates/_scripts/_files.py +195 -0
  68. figrecipe/_editor/_templates/_scripts/_hitmap.py +509 -0
  69. figrecipe/_editor/_templates/_scripts/_inspector.py +315 -0
  70. figrecipe/_editor/_templates/_scripts/_labels.py +464 -0
  71. figrecipe/_editor/_templates/_scripts/_legend_drag.py +265 -0
  72. figrecipe/_editor/_templates/_scripts/_modals.py +226 -0
  73. figrecipe/_editor/_templates/_scripts/_overlays.py +292 -0
  74. figrecipe/_editor/_templates/_scripts/_panel_drag.py +334 -0
  75. figrecipe/_editor/_templates/_scripts/_panel_position.py +279 -0
  76. figrecipe/_editor/_templates/_scripts/_selection.py +237 -0
  77. figrecipe/_editor/_templates/_scripts/_tabs.py +89 -0
  78. figrecipe/_editor/_templates/_scripts/_view_mode.py +107 -0
  79. figrecipe/_editor/_templates/_scripts/_zoom.py +179 -0
  80. figrecipe/_editor/_templates/_styles/__init__.py +69 -0
  81. figrecipe/_editor/_templates/_styles/_base.py +64 -0
  82. figrecipe/_editor/_templates/_styles/_buttons.py +206 -0
  83. figrecipe/_editor/_templates/_styles/_color_input.py +123 -0
  84. figrecipe/_editor/_templates/_styles/_controls.py +265 -0
  85. figrecipe/_editor/_templates/_styles/_dynamic_props.py +144 -0
  86. figrecipe/_editor/_templates/_styles/_forms.py +126 -0
  87. figrecipe/_editor/_templates/_styles/_hitmap.py +184 -0
  88. figrecipe/_editor/_templates/_styles/_inspector.py +90 -0
  89. figrecipe/_editor/_templates/_styles/_labels.py +118 -0
  90. figrecipe/_editor/_templates/_styles/_modals.py +98 -0
  91. figrecipe/_editor/_templates/_styles/_overlays.py +130 -0
  92. figrecipe/_editor/_templates/_styles/_preview.py +225 -0
  93. figrecipe/_editor/_templates/_styles/_selection.py +73 -0
  94. figrecipe/_params/_DECORATION_METHODS.py +6 -0
  95. figrecipe/_recorder.py +35 -106
  96. figrecipe/_recorder_utils.py +124 -0
  97. figrecipe/_reproducer/__init__.py +18 -0
  98. figrecipe/_reproducer/_core.py +498 -0
  99. figrecipe/_reproducer/_custom_plots.py +279 -0
  100. figrecipe/_reproducer/_seaborn.py +100 -0
  101. figrecipe/_reproducer/_violin.py +186 -0
  102. figrecipe/_signatures/_kwargs.py +273 -0
  103. figrecipe/_signatures/_loader.py +21 -423
  104. figrecipe/_signatures/_parsing.py +147 -0
  105. figrecipe/_wrappers/_axes.py +119 -910
  106. figrecipe/_wrappers/_axes_helpers.py +136 -0
  107. figrecipe/_wrappers/_axes_plots.py +418 -0
  108. figrecipe/_wrappers/_axes_seaborn.py +157 -0
  109. figrecipe/_wrappers/_figure.py +162 -0
  110. figrecipe/_wrappers/_panel_labels.py +127 -0
  111. figrecipe/_wrappers/_plot_helpers.py +143 -0
  112. figrecipe/_wrappers/_violin_helpers.py +180 -0
  113. figrecipe/styles/__init__.py +8 -6
  114. figrecipe/styles/_dotdict.py +72 -0
  115. figrecipe/styles/_finalize.py +134 -0
  116. figrecipe/styles/_fonts.py +77 -0
  117. figrecipe/styles/_kwargs_converter.py +178 -0
  118. figrecipe/styles/_plot_styles.py +209 -0
  119. figrecipe/styles/_style_applier.py +32 -478
  120. figrecipe/styles/_style_loader.py +16 -192
  121. figrecipe/styles/_themes.py +151 -0
  122. figrecipe/styles/presets/MATPLOTLIB.yaml +2 -1
  123. figrecipe/styles/presets/SCITEX.yaml +29 -24
  124. {figrecipe-0.6.0.dist-info → figrecipe-0.7.4.dist-info}/METADATA +37 -2
  125. figrecipe-0.7.4.dist-info/RECORD +188 -0
  126. figrecipe/_editor/_bbox.py +0 -978
  127. figrecipe/_editor/_hitmap.py +0 -937
  128. figrecipe/_editor/_templates/_scripts.py +0 -2778
  129. figrecipe/_editor/_templates/_styles.py +0 -1326
  130. figrecipe/_reproducer.py +0 -975
  131. figrecipe-0.6.0.dist-info/RECORD +0 -90
  132. /figrecipe/_dev/demo_plotters/{plot_bar.py → bar_categorical/plot_bar.py} +0 -0
  133. /figrecipe/_dev/demo_plotters/{plot_barh.py → bar_categorical/plot_barh.py} +0 -0
  134. /figrecipe/_dev/demo_plotters/{plot_contour.py → contour_surface/plot_contour.py} +0 -0
  135. /figrecipe/_dev/demo_plotters/{plot_contourf.py → contour_surface/plot_contourf.py} +0 -0
  136. /figrecipe/_dev/demo_plotters/{plot_tricontour.py → contour_surface/plot_tricontour.py} +0 -0
  137. /figrecipe/_dev/demo_plotters/{plot_tricontourf.py → contour_surface/plot_tricontourf.py} +0 -0
  138. /figrecipe/_dev/demo_plotters/{plot_tripcolor.py → contour_surface/plot_tripcolor.py} +0 -0
  139. /figrecipe/_dev/demo_plotters/{plot_triplot.py → contour_surface/plot_triplot.py} +0 -0
  140. /figrecipe/_dev/demo_plotters/{plot_boxplot.py → distribution/plot_boxplot.py} +0 -0
  141. /figrecipe/_dev/demo_plotters/{plot_ecdf.py → distribution/plot_ecdf.py} +0 -0
  142. /figrecipe/_dev/demo_plotters/{plot_hist.py → distribution/plot_hist.py} +0 -0
  143. /figrecipe/_dev/demo_plotters/{plot_hist2d.py → distribution/plot_hist2d.py} +0 -0
  144. /figrecipe/_dev/demo_plotters/{plot_violinplot.py → distribution/plot_violinplot.py} +0 -0
  145. /figrecipe/_dev/demo_plotters/{plot_hexbin.py → image_matrix/plot_hexbin.py} +0 -0
  146. /figrecipe/_dev/demo_plotters/{plot_imshow.py → image_matrix/plot_imshow.py} +0 -0
  147. /figrecipe/_dev/demo_plotters/{plot_matshow.py → image_matrix/plot_matshow.py} +0 -0
  148. /figrecipe/_dev/demo_plotters/{plot_pcolor.py → image_matrix/plot_pcolor.py} +0 -0
  149. /figrecipe/_dev/demo_plotters/{plot_pcolormesh.py → image_matrix/plot_pcolormesh.py} +0 -0
  150. /figrecipe/_dev/demo_plotters/{plot_spy.py → image_matrix/plot_spy.py} +0 -0
  151. /figrecipe/_dev/demo_plotters/{plot_errorbar.py → line_curve/plot_errorbar.py} +0 -0
  152. /figrecipe/_dev/demo_plotters/{plot_fill.py → line_curve/plot_fill.py} +0 -0
  153. /figrecipe/_dev/demo_plotters/{plot_fill_between.py → line_curve/plot_fill_between.py} +0 -0
  154. /figrecipe/_dev/demo_plotters/{plot_fill_betweenx.py → line_curve/plot_fill_betweenx.py} +0 -0
  155. /figrecipe/_dev/demo_plotters/{plot_stackplot.py → line_curve/plot_stackplot.py} +0 -0
  156. /figrecipe/_dev/demo_plotters/{plot_stairs.py → line_curve/plot_stairs.py} +0 -0
  157. /figrecipe/_dev/demo_plotters/{plot_step.py → line_curve/plot_step.py} +0 -0
  158. /figrecipe/_dev/demo_plotters/{plot_scatter.py → scatter_points/plot_scatter.py} +0 -0
  159. /figrecipe/_dev/demo_plotters/{plot_eventplot.py → special/plot_eventplot.py} +0 -0
  160. /figrecipe/_dev/demo_plotters/{plot_loglog.py → special/plot_loglog.py} +0 -0
  161. /figrecipe/_dev/demo_plotters/{plot_semilogx.py → special/plot_semilogx.py} +0 -0
  162. /figrecipe/_dev/demo_plotters/{plot_semilogy.py → special/plot_semilogy.py} +0 -0
  163. /figrecipe/_dev/demo_plotters/{plot_stem.py → special/plot_stem.py} +0 -0
  164. /figrecipe/_dev/demo_plotters/{plot_acorr.py → spectral_signal/plot_acorr.py} +0 -0
  165. /figrecipe/_dev/demo_plotters/{plot_angle_spectrum.py → spectral_signal/plot_angle_spectrum.py} +0 -0
  166. /figrecipe/_dev/demo_plotters/{plot_cohere.py → spectral_signal/plot_cohere.py} +0 -0
  167. /figrecipe/_dev/demo_plotters/{plot_csd.py → spectral_signal/plot_csd.py} +0 -0
  168. /figrecipe/_dev/demo_plotters/{plot_magnitude_spectrum.py → spectral_signal/plot_magnitude_spectrum.py} +0 -0
  169. /figrecipe/_dev/demo_plotters/{plot_phase_spectrum.py → spectral_signal/plot_phase_spectrum.py} +0 -0
  170. /figrecipe/_dev/demo_plotters/{plot_psd.py → spectral_signal/plot_psd.py} +0 -0
  171. /figrecipe/_dev/demo_plotters/{plot_specgram.py → spectral_signal/plot_specgram.py} +0 -0
  172. /figrecipe/_dev/demo_plotters/{plot_xcorr.py → spectral_signal/plot_xcorr.py} +0 -0
  173. /figrecipe/_dev/demo_plotters/{plot_barbs.py → vector_flow/plot_barbs.py} +0 -0
  174. /figrecipe/_dev/demo_plotters/{plot_quiver.py → vector_flow/plot_quiver.py} +0 -0
  175. /figrecipe/_dev/demo_plotters/{plot_streamplot.py → vector_flow/plot_streamplot.py} +0 -0
  176. {figrecipe-0.6.0.dist-info → figrecipe-0.7.4.dist-info}/WHEEL +0 -0
  177. {figrecipe-0.6.0.dist-info → figrecipe-0.7.4.dist-info}/licenses/LICENSE +0 -0
@@ -21,10 +21,12 @@ __all__ = [
21
21
  "load_preset",
22
22
  "unload_style",
23
23
  "get_style",
24
+ "get_current_style_dict",
24
25
  "reload_style",
25
26
  "list_presets",
26
27
  "STYLE",
27
28
  "to_subplots_kwargs",
29
+ "DotDict",
28
30
  ]
29
31
 
30
32
  from pathlib import Path
@@ -32,6 +34,9 @@ from typing import Any, Dict, List, Optional, Union
32
34
 
33
35
  from ruamel.yaml import YAML
34
36
 
37
+ from ._dotdict import DotDict
38
+ from ._kwargs_converter import to_subplots_kwargs
39
+
35
40
  # Path to presets directory
36
41
  _PRESETS_DIR = Path(__file__).parent / "presets"
37
42
 
@@ -44,7 +49,7 @@ _PRESET_ALIASES = {
44
49
  }
45
50
 
46
51
  # Global style cache
47
- _STYLE_CACHE: Optional["DotDict"] = None
52
+ _STYLE_CACHE: Optional[DotDict] = None
48
53
  _CURRENT_STYLE_NAME: Optional[str] = None
49
54
 
50
55
 
@@ -70,52 +75,6 @@ def list_presets() -> List[str]:
70
75
  return ["MATPLOTLIB", "SCITEX"]
71
76
 
72
77
 
73
- class DotDict(dict):
74
- """Dictionary with dot-notation access to nested keys.
75
-
76
- Examples
77
- --------
78
- >>> d = DotDict({"axes": {"width_mm": 40}})
79
- >>> d.axes.width_mm
80
- 40
81
- """
82
-
83
- def __getattr__(self, key: str) -> Any:
84
- # Handle special methods first
85
- if key == "to_subplots_kwargs":
86
- return lambda: to_subplots_kwargs(self)
87
- try:
88
- value = self[key]
89
- if isinstance(value, dict) and not isinstance(value, DotDict):
90
- value = DotDict(value)
91
- self[key] = value
92
- return value
93
- except KeyError:
94
- raise AttributeError(f"'{type(self).__name__}' has no attribute '{key}'")
95
-
96
- def __setattr__(self, key: str, value: Any) -> None:
97
- self[key] = value
98
-
99
- def __delattr__(self, key: str) -> None:
100
- try:
101
- del self[key]
102
- except KeyError:
103
- raise AttributeError(f"'{type(self).__name__}' has no attribute '{key}'")
104
-
105
- def get(self, key: str, default: Any = None) -> Any:
106
- """Get value with default, supporting nested keys with dots."""
107
- if "." in key:
108
- parts = key.split(".")
109
- value = self
110
- for part in parts:
111
- if isinstance(value, dict) and part in value:
112
- value = value[part]
113
- else:
114
- return default
115
- return value
116
- return super().get(key, default)
117
-
118
-
119
78
  def _deep_merge(base: Dict, override: Dict) -> Dict:
120
79
  """Deep merge two dictionaries, with override taking precedence."""
121
80
  result = base.copy()
@@ -391,156 +350,19 @@ def get_style() -> DotDict:
391
350
  return _STYLE_CACHE
392
351
 
393
352
 
394
- def to_subplots_kwargs(style: Optional[DotDict] = None) -> Dict[str, Any]:
395
- """Convert style DotDict to kwargs for ps.subplots().
396
-
397
- Uses YAML-compatible flattened key names as the single source of truth.
398
- For example, YAML `fonts.axis_label_pt` becomes `fonts_axis_label_pt`.
399
-
400
- Parameters
401
- ----------
402
- style : DotDict, optional
403
- Style configuration. If None, uses current loaded style.
353
+ def get_current_style_dict() -> Dict[str, Any]:
354
+ """Get current style as a flat dictionary for style applier functions.
404
355
 
405
356
  Returns
406
357
  -------
407
358
  dict
408
- Keyword arguments for ps.subplots() with YAML-compatible flattened keys.
409
-
410
- Examples
411
- --------
412
- >>> style = load_style()
413
- >>> kwargs = to_subplots_kwargs(style)
414
- >>> fig, ax = ps.subplots(**kwargs)
359
+ Flattened style dictionary with keys like 'pie_show_axes', 'imshow_show_axes'.
360
+ Returns empty dict if no style is loaded.
415
361
  """
416
- if style is None:
417
- style = get_style()
418
-
419
- # YAML-compatible flattened keys (single source of truth)
420
- result = {
421
- # Axes (axes.* in YAML)
422
- "axes_width_mm": style.axes.width_mm,
423
- "axes_height_mm": style.axes.height_mm,
424
- "axes_thickness_mm": style.axes.thickness_mm,
425
- # Margins (margins.* in YAML)
426
- "margins_left_mm": style.margins.left_mm,
427
- "margins_right_mm": style.margins.right_mm,
428
- "margins_bottom_mm": style.margins.bottom_mm,
429
- "margins_top_mm": style.margins.top_mm,
430
- # Spacing (spacing.* in YAML)
431
- "spacing_horizontal_mm": style.spacing.horizontal_mm,
432
- "spacing_vertical_mm": style.spacing.vertical_mm,
433
- # Ticks (ticks.* in YAML)
434
- "ticks_length_mm": style.ticks.length_mm,
435
- "ticks_thickness_mm": style.ticks.thickness_mm,
436
- "ticks_direction": style.ticks.get("direction", "out"),
437
- "ticks_n_ticks": style.ticks.n_ticks,
438
- # Lines (lines.* in YAML)
439
- "lines_trace_mm": style.lines.trace_mm,
440
- "lines_errorbar_mm": style.lines.get("errorbar_mm", 0.2),
441
- "lines_errorbar_cap_mm": style.lines.get("errorbar_cap_mm", 0.8),
442
- # Markers (markers.* in YAML)
443
- "markers_size_mm": style.markers.size_mm,
444
- "markers_scatter_mm": style.markers.get("scatter_mm", style.markers.size_mm),
445
- "markers_flier_mm": style.markers.get("flier_mm", style.markers.size_mm),
446
- "markers_edge_width_mm": style.markers.get("edge_width_mm"),
447
- # Boxplot (boxplot.* in YAML)
448
- "boxplot_line_mm": style.get("boxplot", {}).get("line_mm", 0.2),
449
- "boxplot_whisker_mm": style.get("boxplot", {}).get("whisker_mm", 0.2),
450
- "boxplot_cap_mm": style.get("boxplot", {}).get("cap_mm", 0.2),
451
- "boxplot_median_mm": style.get("boxplot", {}).get("median_mm", 0.2),
452
- "boxplot_median_color": style.get("boxplot", {}).get("median_color", "black"),
453
- "boxplot_flier_edge_mm": style.get("boxplot", {}).get("flier_edge_mm", 0.2),
454
- # Violinplot (violinplot.* in YAML)
455
- "violinplot_line_mm": style.get("violinplot", {}).get("line_mm", 0.2),
456
- "violinplot_inner": style.get("violinplot", {}).get("inner", "box"),
457
- "violinplot_box_width_mm": style.get("violinplot", {}).get("box_width_mm", 1.5),
458
- "violinplot_whisker_mm": style.get("violinplot", {}).get("whisker_mm", 0.2),
459
- "violinplot_median_mm": style.get("violinplot", {}).get("median_mm", 0.8),
460
- # Barplot (barplot.* in YAML)
461
- "barplot_edge_mm": style.get("barplot", {}).get("edge_mm", 0.2),
462
- # Histogram (histogram.* in YAML)
463
- "histogram_edge_mm": style.get("histogram", {}).get("edge_mm", 0.2),
464
- # Pie chart (pie.* in YAML)
465
- "pie_text_pt": style.get("pie", {}).get("text_pt", 6),
466
- "pie_show_axes": style.get("pie", {}).get("show_axes", False),
467
- # Imshow (imshow.* in YAML)
468
- "imshow_show_axes": style.get("imshow", {}).get("show_axes", False),
469
- "imshow_show_labels": style.get("imshow", {}).get("show_labels", False),
470
- # Fonts (fonts.* in YAML)
471
- "fonts_family": style.fonts.family,
472
- "fonts_axis_label_pt": style.fonts.axis_label_pt,
473
- "fonts_tick_label_pt": style.fonts.tick_label_pt,
474
- "fonts_title_pt": style.fonts.title_pt,
475
- "fonts_suptitle_pt": style.fonts.suptitle_pt,
476
- "fonts_legend_pt": style.fonts.legend_pt,
477
- "fonts_annotation_pt": style.fonts.get("annotation_pt", 6),
478
- # Padding (padding.* in YAML)
479
- "padding_label_pt": style.padding.label_pt,
480
- "padding_tick_pt": style.padding.tick_pt,
481
- "padding_title_pt": style.padding.title_pt,
482
- # Output (output.* in YAML)
483
- "output_dpi": style.output.dpi,
484
- "output_transparent": style.output.get("transparent", True),
485
- "output_format": style.output.get("format", "pdf"),
486
- # Theme (theme.* in YAML)
487
- "theme_mode": style.theme.mode,
488
- }
489
-
490
- # Add theme colors from preset if available
491
- theme_mode = style.theme.mode
492
- if "theme" in style and theme_mode in style.theme:
493
- result["theme_colors"] = dict(style.theme[theme_mode])
494
-
495
- # Add color palette if available
496
- if "colors" in style and "palette" in style.colors:
497
- result["color_palette"] = list(style.colors.palette)
498
-
499
- # Add behavior settings (behavior.* in YAML)
500
- if "behavior" in style:
501
- behavior = style.behavior
502
- if hasattr(behavior, "hide_top_spine"):
503
- result["behavior_hide_top_spine"] = behavior.hide_top_spine
504
- if hasattr(behavior, "hide_right_spine"):
505
- result["behavior_hide_right_spine"] = behavior.hide_right_spine
506
- if hasattr(behavior, "grid"):
507
- result["behavior_grid"] = behavior.grid
508
- if hasattr(behavior, "auto_scale_axes"):
509
- result["behavior_auto_scale_axes"] = behavior.auto_scale_axes
510
- if hasattr(behavior, "constrained_layout"):
511
- result["behavior_constrained_layout"] = behavior.constrained_layout
512
-
513
- # Legacy key aliases for backwards compatibility
514
- # (These allow existing code using old keys to still work)
515
- result["margin_left_mm"] = result["margins_left_mm"]
516
- result["margin_right_mm"] = result["margins_right_mm"]
517
- result["margin_bottom_mm"] = result["margins_bottom_mm"]
518
- result["margin_top_mm"] = result["margins_top_mm"]
519
- result["space_w_mm"] = result["spacing_horizontal_mm"]
520
- result["space_h_mm"] = result["spacing_vertical_mm"]
521
- result["tick_length_mm"] = result["ticks_length_mm"]
522
- result["tick_thickness_mm"] = result["ticks_thickness_mm"]
523
- result["n_ticks"] = result["ticks_n_ticks"]
524
- result["trace_thickness_mm"] = result["lines_trace_mm"]
525
- result["marker_size_mm"] = result["markers_size_mm"]
526
- result["font_family"] = result["fonts_family"]
527
- result["axis_font_size_pt"] = result["fonts_axis_label_pt"]
528
- result["tick_font_size_pt"] = result["fonts_tick_label_pt"]
529
- result["title_font_size_pt"] = result["fonts_title_pt"]
530
- result["suptitle_font_size_pt"] = result["fonts_suptitle_pt"]
531
- result["legend_font_size_pt"] = result["fonts_legend_pt"]
532
- result["label_pad_pt"] = result["padding_label_pt"]
533
- result["tick_pad_pt"] = result["padding_tick_pt"]
534
- result["title_pad_pt"] = result["padding_title_pt"]
535
- result["dpi"] = result["output_dpi"]
536
- result["theme"] = result["theme_mode"]
537
- result["hide_top_spine"] = result.get("behavior_hide_top_spine", True)
538
- result["hide_right_spine"] = result.get("behavior_hide_right_spine", True)
539
- result["grid"] = result.get("behavior_grid", False)
540
- result["auto_scale_axes"] = result.get("behavior_auto_scale_axes", True)
541
- result["constrained_layout"] = result.get("behavior_constrained_layout", False)
542
-
543
- return result
362
+ global _STYLE_CACHE
363
+ if _STYLE_CACHE is None:
364
+ return {}
365
+ return to_subplots_kwargs(_STYLE_CACHE)
544
366
 
545
367
 
546
368
  # Lazy-loaded global STYLE object
@@ -576,3 +398,5 @@ if __name__ == "__main__":
576
398
 
577
399
  print("\nUsing STYLE proxy...")
578
400
  print(f" STYLE.fonts.family: {STYLE.fonts.family}")
401
+
402
+ # EOF
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Theme color utilities for figrecipe styles."""
4
+
5
+ from typing import Dict, Optional
6
+
7
+ from matplotlib.axes import Axes
8
+
9
+ # Default theme color palettes (Monaco/VS Code style for dark)
10
+ THEME_COLORS = {
11
+ "dark": {
12
+ "figure_bg": "#1e1e1e", # VS Code main background
13
+ "axes_bg": "#252526", # VS Code panel background
14
+ "legend_bg": "#252526", # Same as axes
15
+ "text": "#d4d4d4", # VS Code default text
16
+ "spine": "#3c3c3c", # Subtle border color
17
+ "tick": "#d4d4d4", # Match text
18
+ "grid": "#3a3a3a", # Subtle grid
19
+ },
20
+ "light": {
21
+ "figure_bg": "none", # Transparent
22
+ "axes_bg": "none", # Transparent
23
+ "legend_bg": "none", # Transparent
24
+ "text": "black",
25
+ "spine": "black",
26
+ "tick": "black",
27
+ "grid": "#cccccc",
28
+ },
29
+ }
30
+
31
+
32
+ def apply_theme_colors(
33
+ ax: Axes,
34
+ theme: str = "light",
35
+ custom_colors: Optional[Dict[str, str]] = None,
36
+ ) -> None:
37
+ """Apply theme colors to axes for dark/light mode support.
38
+
39
+ Parameters
40
+ ----------
41
+ ax : matplotlib.axes.Axes
42
+ Target axes to apply theme to
43
+ theme : str or dict
44
+ Color theme: "light" or "dark" (default: "light")
45
+ If dict, extracts 'mode' key (for YAML-style theme dicts)
46
+ custom_colors : dict, optional
47
+ Custom color overrides. Keys: figure_bg, axes_bg, legend_bg, text, spine, tick, grid
48
+
49
+ Examples
50
+ --------
51
+ >>> fig, ax = plt.subplots()
52
+ >>> apply_theme_colors(ax, theme="dark") # Eye-friendly dark mode
53
+ """
54
+ # Handle dict-style theme (from YAML: {mode: "light", dark: {...}})
55
+ if isinstance(theme, dict):
56
+ theme = theme.get("mode", "light")
57
+
58
+ # Ensure theme is a string
59
+ if not isinstance(theme, str):
60
+ theme = "light"
61
+
62
+ # Get base theme colors
63
+ colors = THEME_COLORS.get(theme, THEME_COLORS["light"]).copy()
64
+
65
+ # Apply custom overrides
66
+ if custom_colors:
67
+ # Handle legacy key name (background -> figure_bg)
68
+ if "background" in custom_colors and "figure_bg" not in custom_colors:
69
+ custom_colors["figure_bg"] = custom_colors.pop("background")
70
+ colors.update(custom_colors)
71
+
72
+ # Helper to check for transparent/none
73
+ def is_transparent(color):
74
+ if color is None:
75
+ return False
76
+ return str(color).lower() in ("none", "transparent")
77
+
78
+ # Apply axes background (handle "none"/"transparent" for transparency)
79
+ axes_bg = colors.get("axes_bg", "none")
80
+ if is_transparent(axes_bg):
81
+ ax.set_facecolor("none")
82
+ ax.patch.set_alpha(0)
83
+ else:
84
+ ax.set_facecolor(axes_bg)
85
+
86
+ # Apply figure background if accessible
87
+ fig = ax.get_figure()
88
+ if fig is not None:
89
+ fig_bg = colors.get("figure_bg", "none")
90
+ if is_transparent(fig_bg):
91
+ fig.patch.set_facecolor("none")
92
+ fig.patch.set_alpha(0)
93
+ else:
94
+ fig.patch.set_facecolor(fig_bg)
95
+
96
+ # Apply text colors to figure-level text elements
97
+ if hasattr(fig, "_suptitle") and fig._suptitle is not None:
98
+ fig._suptitle.set_color(colors["text"])
99
+ if hasattr(fig, "_supxlabel") and fig._supxlabel is not None:
100
+ fig._supxlabel.set_color(colors["text"])
101
+ if hasattr(fig, "_supylabel") and fig._supylabel is not None:
102
+ fig._supylabel.set_color(colors["text"])
103
+
104
+ # Apply text colors (labels, titles)
105
+ ax.xaxis.label.set_color(colors["text"])
106
+ ax.yaxis.label.set_color(colors["text"])
107
+ ax.title.set_color(colors["text"])
108
+
109
+ # Update rcParams for dark mode support (pie charts, panel labels)
110
+ import matplotlib as mpl
111
+
112
+ mpl.rcParams["text.color"] = colors["text"]
113
+ mpl.rcParams["axes.labelcolor"] = colors["text"]
114
+ mpl.rcParams["xtick.color"] = colors["tick"]
115
+ mpl.rcParams["ytick.color"] = colors["tick"]
116
+
117
+ # Apply spine colors
118
+ for spine in ax.spines.values():
119
+ spine.set_color(colors["spine"])
120
+
121
+ # Apply tick colors (both marks and labels)
122
+ ax.tick_params(colors=colors["tick"], which="both")
123
+ for label in ax.get_xticklabels() + ax.get_yticklabels():
124
+ label.set_color(colors["tick"])
125
+
126
+ # Apply text colors to all text objects (panel labels, pie labels, annotations)
127
+ for text in ax.texts:
128
+ text.set_color(colors["text"])
129
+
130
+ # Apply legend colors if legend exists
131
+ legend = ax.get_legend()
132
+ if legend is not None:
133
+ for text in legend.get_texts():
134
+ text.set_color(colors["text"])
135
+ title = legend.get_title()
136
+ if title:
137
+ title.set_color(colors["text"])
138
+ frame = legend.get_frame()
139
+ if frame:
140
+ legend_bg = colors.get("legend_bg", colors.get("axes_bg", "none"))
141
+ if is_transparent(legend_bg):
142
+ frame.set_facecolor("none")
143
+ frame.set_alpha(0)
144
+ else:
145
+ frame.set_facecolor(legend_bg)
146
+ frame.set_edgecolor(colors["spine"])
147
+
148
+
149
+ __all__ = ["THEME_COLORS", "apply_theme_colors"]
150
+
151
+ # EOF
@@ -43,7 +43,8 @@ ticks:
43
43
  length_mm: null
44
44
  thickness_mm: null
45
45
  direction: null
46
- n_ticks: null
46
+ n_ticks_min: null
47
+ n_ticks_max: null
47
48
 
48
49
  markers:
49
50
  size_mm: null
@@ -1,4 +1,4 @@
1
- # Timestamp: "2025-12-22 13:49:08 (ywatanabe)"
1
+ # Timestamp: "2025-12-24 14:11:46 (ywatanabe)"
2
2
  # File: ./src/figrecipe/styles/presets/SCITEX.yaml
3
3
  # FIGRECIPE Style Preset
4
4
  # ======================
@@ -49,7 +49,8 @@ ticks:
49
49
  length_mm: 0.8
50
50
  thickness_mm: 0.2
51
51
  direction: "out"
52
- n_ticks: 4
52
+ n_ticks_min: 3
53
+ n_ticks_max: 4
53
54
 
54
55
  markers:
55
56
  size_mm: 0.8
@@ -85,6 +86,8 @@ histogram:
85
86
  pie:
86
87
  text_pt: 6 # Pie chart text size (labels, autopct)
87
88
  show_axes: false # Hide axes for pie charts
89
+ edge_color: "black" # Wedge edge color (black for contrast)
90
+ edge_mm: 0.2 # Wedge edge thickness
88
91
 
89
92
  imshow:
90
93
  show_axes: false # Hide ticks, ticklabels, spines for imshow
@@ -120,6 +123,7 @@ behavior:
120
123
  hide_right_spine: true
121
124
  grid: false
122
125
  constrained_layout: true # Auto-spacing for suptitle/supxlabel/supylabel
126
+ panel_labels: true # Auto-add A, B, C labels to multi-panel figures
123
127
 
124
128
  theme:
125
129
  mode: "light"
@@ -152,25 +156,26 @@ colors:
152
156
  - [228, 94, 50] # orange
153
157
  - [255, 150, 200] # pink
154
158
 
155
- # Named colors
156
- white: [255, 255, 255]
157
- black: [0, 0, 0]
158
- blue: [0, 128, 192]
159
- red: [255, 70, 50]
160
- pink: [255, 150, 200]
161
- green: [20, 180, 20]
162
- yellow: [230, 160, 20]
163
- gray: [128, 128, 128]
164
- grey: [128, 128, 128]
165
- purple: [200, 50, 255]
166
- lightblue: [20, 200, 200]
167
- brown: [128, 0, 0]
168
- navy: [0, 0, 100]
169
- orange: [228, 94, 50]
170
-
171
- # Semantic
172
- primary: [0, 128, 192]
173
- secondary: [255, 70, 50]
174
- accent: [20, 180, 20]
175
-
176
- # EOF
159
+ rgb:
160
+ # Named colors
161
+ - white: [255, 255, 255]
162
+ - black: [0, 0, 0]
163
+ - blue: [0, 128, 192]
164
+ - red: [255, 70, 50]
165
+ - pink: [255, 150, 200]
166
+ - green: [20, 180, 20]
167
+ - yellow: [230, 160, 20]
168
+ - gray: [128, 128, 128]
169
+ - grey: [128, 128, 128]
170
+ - purple: [200, 50, 255]
171
+ - lightblue: [20, 200, 200]
172
+ - brown: [128, 0, 0]
173
+ - navy: [0, 0, 100]
174
+ - orange: [228, 94, 50]
175
+
176
+ # Semantic
177
+ - primary: [0, 128, 192]
178
+ - secondary: [255, 70, 50]
179
+ - accent: [20, 180, 20]
180
+
181
+ # EOF
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: figrecipe
3
- Version: 0.6.0
3
+ Version: 0.7.4
4
4
  Summary: Reproducible matplotlib wrapper with mm-precision layouts
5
5
  Project-URL: Homepage, https://github.com/ywatanabe1989/figrecipe
6
6
  Project-URL: Documentation, https://github.com/ywatanabe1989/figrecipe#readme
@@ -53,7 +53,9 @@ Description-Content-Type: text/markdown
53
53
  !-- --- -->
54
54
 
55
55
  <p align="center">
56
- <img src="docs/figrecipe_logo.png" alt="figrecipe logo" width="200"/>
56
+ <a href="https://scitex.ai" target="_blank">
57
+ <img src="docs/figrecipe_logo.png" alt="figrecipe logo" width="200"/>
58
+ </a>
57
59
  </p>
58
60
 
59
61
  # FigRecipe
@@ -112,6 +114,31 @@ The notebook includes side-by-side comparisons of original and reproduced figure
112
114
 
113
115
  Source: [examples/figrecipe_demo.ipynb](examples/figrecipe_demo.ipynb)
114
116
 
117
+ ### Supported Plot Types
118
+
119
+ FigRecipe supports 46 matplotlib plot types, organized into 9 categories:
120
+
121
+ <details>
122
+ <summary><b>All Plot Types</b> (click to expand)</summary>
123
+ <p align="center">
124
+ <img src="docs/images/plot_types/all_plot_types.png" alt="All Plot Types" width="100%"/>
125
+ </p>
126
+ </details>
127
+
128
+ | Line & Curve | Scatter | Distribution |
129
+ |:---:|:---:|:---:|
130
+ | ![Line](docs/images/plot_types/category_line_curve.png) | ![Scatter](docs/images/plot_types/category_scatter_points.png) | ![Distribution](docs/images/plot_types/category_distribution.png) |
131
+
132
+ | Bar & Categorical | Contour & Surface | 2D/Image/Matrix |
133
+ |:---:|:---:|:---:|
134
+ | ![Bar](docs/images/plot_types/category_bar_categorical.png) | ![Contour](docs/images/plot_types/category_contour_surface.png) | ![Image](docs/images/plot_types/category_2d_image_matrix.png) |
135
+
136
+ | Vector & Flow | Spectral & Signal | Special |
137
+ |:---:|:---:|:---:|
138
+ | ![Vector](docs/images/plot_types/category_vector_flow.png) | ![Spectral](docs/images/plot_types/category_spectral_signal.png) | ![Special](docs/images/plot_types/category_special.png) |
139
+
140
+ Generate all plots: `python examples/demo_plot_all.py`
141
+
115
142
  ## Installation
116
143
 
117
144
  ```bash
@@ -216,6 +243,10 @@ overrides = fr.edit(fig, port=5050)
216
243
  # Apply overrides to future figures or save to custom YAML
217
244
  ```
218
245
 
246
+ <p align="center">
247
+ <img src="docs/images/gui_editor_demo.png" alt="FigRecipe GUI Editor" width="100%"/>
248
+ </p>
249
+
219
250
  The editor provides:
220
251
  - **Live preview** with real-time style updates
221
252
  - **Theme switching** between SCITEX/MATPLOTLIB presets
@@ -224,6 +255,10 @@ The editor provides:
224
255
  - **Download** in PNG, SVG, PDF formats
225
256
  - **Export** updated recipe YAML
226
257
 
258
+ | Axis Properties | Download Options | Dark Mode |
259
+ |:---:|:---:|:---:|
260
+ | ![Axis Panel](docs/images/editor_axis_panel.png) | ![Download](docs/images/editor_download_options.png) | ![Dark Mode](docs/images/editor_dark_mode.png) |
261
+
227
262
  ### Style Format (YAML)
228
263
 
229
264
  ``` yaml