processbehavior 0.2.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.
- processbehavior/__init__.py +133 -0
- processbehavior/analysis.py +2944 -0
- processbehavior/analysis_dataset.py +361 -0
- processbehavior/analysis_result.py +1276 -0
- processbehavior/calibration.py +70 -0
- processbehavior/capability.py +720 -0
- processbehavior/data_preparation.py +792 -0
- processbehavior/datasets/__init__.py +37 -0
- processbehavior/datasets/data/coffee_shop_demo_long.csv +385 -0
- processbehavior/datasets/loaders.py +66 -0
- processbehavior/datasets/synthetic.py +1711 -0
- processbehavior/derivations.py +602 -0
- processbehavior/effects_calculator.py +603 -0
- processbehavior/exceptions.py +216 -0
- processbehavior/exporters/__init__.py +5 -0
- processbehavior/exporters/excel_exporter.py +708 -0
- processbehavior/focused_result.py +329 -0
- processbehavior/formulation_spec.py +144 -0
- processbehavior/loss_function.py +517 -0
- processbehavior/maximum_information.py +285 -0
- processbehavior/plotting/__init__.py +46 -0
- processbehavior/plotting/_math.py +121 -0
- processbehavior/plotting/capability_chart.py +858 -0
- processbehavior/plotting/contracts.py +176 -0
- processbehavior/plotting/control_chart.py +206 -0
- processbehavior/plotting/effects_charts.py +640 -0
- processbehavior/plotting/lane_boundaries.py +78 -0
- processbehavior/plotting/limits.py +157 -0
- processbehavior/plotting/maximum_information_chart.py +395 -0
- processbehavior/plotting/plotter.py +1469 -0
- processbehavior/plotting/renderers.py +660 -0
- processbehavior/plotting/report.py +211 -0
- processbehavior/plotting/residuals.py +282 -0
- processbehavior/plotting/run_rules_viz.py +150 -0
- processbehavior/plotting/stats_box.py +183 -0
- processbehavior/plotting/themes.py +544 -0
- processbehavior/plotting/x_axis_layout.py +474 -0
- processbehavior/plotting/zones.py +127 -0
- processbehavior/process_behavior.py +1290 -0
- processbehavior/py.typed +0 -0
- processbehavior/residual_calculator.py +572 -0
- processbehavior/result_signals.py +149 -0
- processbehavior/result_tabular.py +296 -0
- processbehavior/sds_detector.py +1432 -0
- processbehavior/signals/__init__.py +14 -0
- processbehavior/signals/config.py +435 -0
- processbehavior/signals/detector.py +328 -0
- processbehavior/signals/detectors.py +368 -0
- processbehavior/signals/result.py +232 -0
- processbehavior/spc_constants.py +692 -0
- processbehavior/study.py +2733 -0
- processbehavior/types.py +154 -0
- processbehavior-0.2.0.dist-info/METADATA +279 -0
- processbehavior-0.2.0.dist-info/RECORD +56 -0
- processbehavior-0.2.0.dist-info/WHEEL +4 -0
- processbehavior-0.2.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Theme definitions for control chart plotting.
|
|
3
|
+
|
|
4
|
+
Provides a comprehensive theme system with full customization of colors,
|
|
5
|
+
sizes, fonts, and layout properties. Inspired by ggplot2's theme system.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
from ..exceptions import ValidationError
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
import plotly.graph_objects as go
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class ChartTheme:
|
|
21
|
+
"""
|
|
22
|
+
Complete theme specification for control charts.
|
|
23
|
+
|
|
24
|
+
This dataclass defines all visual properties for control chart rendering,
|
|
25
|
+
following ggplot2's philosophy of sensible defaults with full customization.
|
|
26
|
+
|
|
27
|
+
Attributes
|
|
28
|
+
----------
|
|
29
|
+
name : str
|
|
30
|
+
Theme identifier
|
|
31
|
+
|
|
32
|
+
# Data appearance
|
|
33
|
+
data_color : str
|
|
34
|
+
Color for data points and lines
|
|
35
|
+
data_marker_size : int
|
|
36
|
+
Size of data point markers
|
|
37
|
+
data_line_width : float
|
|
38
|
+
Width of data connecting lines
|
|
39
|
+
|
|
40
|
+
# Control limits
|
|
41
|
+
ucl_color : str
|
|
42
|
+
Upper control limit line color
|
|
43
|
+
lcl_color : str
|
|
44
|
+
Lower control limit line color
|
|
45
|
+
center_color : str
|
|
46
|
+
Centerline color
|
|
47
|
+
limit_line_dash : str
|
|
48
|
+
Dash style for control limits ('dash', 'dot', 'solid')
|
|
49
|
+
limit_line_width : float
|
|
50
|
+
Width of control limit lines
|
|
51
|
+
center_line_width : float
|
|
52
|
+
Width of centerline
|
|
53
|
+
|
|
54
|
+
# Signal/out-of-control markers
|
|
55
|
+
signal_color : str
|
|
56
|
+
Color for out-of-control points
|
|
57
|
+
signal_marker_size : int
|
|
58
|
+
Size of signal markers
|
|
59
|
+
signal_marker_symbol : str
|
|
60
|
+
Plotly marker symbol ('x', 'circle', 'diamond', etc.)
|
|
61
|
+
signal_marker_line_width : float
|
|
62
|
+
Line width of signal marker border
|
|
63
|
+
signal_marker_line_color : str
|
|
64
|
+
Border color of signal markers
|
|
65
|
+
|
|
66
|
+
# Zone shading (for Western Electric rules visualization)
|
|
67
|
+
zone_a_color : str
|
|
68
|
+
Zone A (±2σ to ±3σ) fill color
|
|
69
|
+
zone_b_color : str
|
|
70
|
+
Zone B (±1σ to ±2σ) fill color
|
|
71
|
+
zone_c_color : str
|
|
72
|
+
Zone C (±0σ to ±1σ) fill color
|
|
73
|
+
zone_opacity : float
|
|
74
|
+
Opacity for zone shading (0-1)
|
|
75
|
+
|
|
76
|
+
# Layout
|
|
77
|
+
plot_bgcolor : str
|
|
78
|
+
Plot area background color
|
|
79
|
+
paper_bgcolor : str
|
|
80
|
+
Figure background color
|
|
81
|
+
grid_color : str
|
|
82
|
+
Grid line color
|
|
83
|
+
grid_width : float
|
|
84
|
+
Grid line width
|
|
85
|
+
show_grid : bool
|
|
86
|
+
Whether to show grid lines
|
|
87
|
+
axis_line_color : str
|
|
88
|
+
Axis line color
|
|
89
|
+
axis_line_width : float
|
|
90
|
+
Axis line width
|
|
91
|
+
|
|
92
|
+
# Typography
|
|
93
|
+
font_family : str
|
|
94
|
+
Font family for all text
|
|
95
|
+
font_size : int
|
|
96
|
+
Base font size
|
|
97
|
+
font_color : str
|
|
98
|
+
Default text color
|
|
99
|
+
title_font_size : int
|
|
100
|
+
Title font size
|
|
101
|
+
title_font_color : str
|
|
102
|
+
Title color
|
|
103
|
+
axis_title_font_size : int
|
|
104
|
+
Axis title font size
|
|
105
|
+
annotation_font_size : int
|
|
106
|
+
Annotation (limit labels) font size
|
|
107
|
+
data_opacity : float
|
|
108
|
+
Opacity for data points and lines (0-1)
|
|
109
|
+
show_axis_line : bool
|
|
110
|
+
Whether to show axis lines
|
|
111
|
+
facet_col_spacing : float
|
|
112
|
+
Horizontal spacing between facet columns (fraction of figure width)
|
|
113
|
+
facet_row_spacing : float
|
|
114
|
+
Vertical spacing between facet rows (fraction of figure height)
|
|
115
|
+
lane_boundary_color : str
|
|
116
|
+
Color for lane boundary lines (vertical separators for collapsed factors)
|
|
117
|
+
lane_boundary_width : float
|
|
118
|
+
Width of lane boundary lines
|
|
119
|
+
lane_label_font_size : int
|
|
120
|
+
Font size for lane boundary annotations
|
|
121
|
+
stats_box_bgcolor : str
|
|
122
|
+
Background color for statistics box annotations
|
|
123
|
+
stats_box_bordercolor : str
|
|
124
|
+
Border color for statistics box
|
|
125
|
+
stats_box_borderwidth : int
|
|
126
|
+
Border width for statistics box
|
|
127
|
+
stats_box_font_size : int
|
|
128
|
+
Font size for statistics box text
|
|
129
|
+
stats_box_font_color : str
|
|
130
|
+
Font color for statistics box text
|
|
131
|
+
pattern_signal_color : str
|
|
132
|
+
Color for pattern-based signal markers (Rules 2-8)
|
|
133
|
+
|
|
134
|
+
Examples
|
|
135
|
+
--------
|
|
136
|
+
Create custom theme:
|
|
137
|
+
|
|
138
|
+
>>> theme = ChartTheme(
|
|
139
|
+
... name='custom',
|
|
140
|
+
... data_color='navy',
|
|
141
|
+
... signal_color='orange',
|
|
142
|
+
... center_color='darkgreen'
|
|
143
|
+
... )
|
|
144
|
+
|
|
145
|
+
Modify existing theme:
|
|
146
|
+
|
|
147
|
+
>>> theme = get_theme('processbehavior')
|
|
148
|
+
>>> theme.data_color = 'purple'
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
# Theme name
|
|
152
|
+
name: str = 'processbehavior'
|
|
153
|
+
|
|
154
|
+
# Data appearance
|
|
155
|
+
data_color: str = 'steelblue'
|
|
156
|
+
data_marker_size: int = 5
|
|
157
|
+
data_line_width: float = 1.0
|
|
158
|
+
data_opacity: float = 1.0
|
|
159
|
+
|
|
160
|
+
# Control limits
|
|
161
|
+
ucl_color: str = 'red'
|
|
162
|
+
lcl_color: str = 'red'
|
|
163
|
+
center_color: str = '#2E8B57' # SeaGreen - professional green
|
|
164
|
+
limit_line_dash: str = 'dash'
|
|
165
|
+
limit_line_width: float = 1.5
|
|
166
|
+
center_line_width: float = 1.5
|
|
167
|
+
|
|
168
|
+
# Signal markers - Two-tier system:
|
|
169
|
+
# Both tiers use same marker style/size as data (circle, size 5), differentiated only by color
|
|
170
|
+
# Tier 1 (Rule 1): Red - points outside control limits
|
|
171
|
+
signal_color: str = 'red'
|
|
172
|
+
signal_marker_size: int = 5 # Same size as data points
|
|
173
|
+
signal_marker_symbol: str = 'circle' # Same as data points
|
|
174
|
+
signal_marker_line_width: float = 0.5
|
|
175
|
+
signal_marker_line_color: str = 'darkred'
|
|
176
|
+
# Tier 2 (Rules 2-8): Orange - pattern-based signals
|
|
177
|
+
pattern_signal_color: str = '#FF8C00' # Dark Orange
|
|
178
|
+
|
|
179
|
+
# Limit summary annotation (above subplot)
|
|
180
|
+
limit_summary_color: str = '#333333'
|
|
181
|
+
limit_summary_bgcolor: str = 'rgba(255,255,255,0.7)'
|
|
182
|
+
|
|
183
|
+
# Zone shading
|
|
184
|
+
zone_a_color: str = '#FFB3B3' # Light red
|
|
185
|
+
zone_b_color: str = '#FFFFB3' # Light yellow
|
|
186
|
+
zone_c_color: str = '#B3FFB3' # Light green
|
|
187
|
+
zone_opacity: float = 0.15
|
|
188
|
+
|
|
189
|
+
# Layout
|
|
190
|
+
plot_bgcolor: str = 'white'
|
|
191
|
+
paper_bgcolor: str = 'white'
|
|
192
|
+
grid_color: str = '#E5E5E5'
|
|
193
|
+
grid_width: float = 1.0
|
|
194
|
+
show_grid: bool = True
|
|
195
|
+
axis_line_color: str = '#999999'
|
|
196
|
+
axis_line_width: float = 1.0
|
|
197
|
+
show_axis_line: bool = True
|
|
198
|
+
|
|
199
|
+
# Typography
|
|
200
|
+
font_family: str = 'Arial, sans-serif'
|
|
201
|
+
font_size: int = 12
|
|
202
|
+
font_color: str = '#333333'
|
|
203
|
+
title_font_size: int = 16
|
|
204
|
+
title_font_color: str = '#222222'
|
|
205
|
+
axis_title_font_size: int = 12
|
|
206
|
+
annotation_font_size: int = 10
|
|
207
|
+
|
|
208
|
+
# Faceted plot settings
|
|
209
|
+
facet_marker_size: int = 5
|
|
210
|
+
facet_line_width: float = 1.5
|
|
211
|
+
|
|
212
|
+
# Lane boundaries (vertical separators for collapsed factors)
|
|
213
|
+
lane_boundary_color: str = '#888888' # Medium gray
|
|
214
|
+
lane_boundary_dash: str = 'dot'
|
|
215
|
+
lane_boundary_width: float = 1.0
|
|
216
|
+
lane_boundary_annotation_size: int = 8
|
|
217
|
+
|
|
218
|
+
# Stats box
|
|
219
|
+
stats_box_bgcolor: str = 'rgba(255, 255, 255, 0.9)'
|
|
220
|
+
stats_box_bordercolor: str = '#CCCCCC'
|
|
221
|
+
stats_box_borderwidth: int = 1
|
|
222
|
+
stats_box_font_size: int = 10
|
|
223
|
+
stats_box_font_color: str = '#333333'
|
|
224
|
+
|
|
225
|
+
def to_layout_dict(self) -> dict:
|
|
226
|
+
"""
|
|
227
|
+
Convert theme to Plotly layout dictionary.
|
|
228
|
+
|
|
229
|
+
Returns
|
|
230
|
+
-------
|
|
231
|
+
dict
|
|
232
|
+
Layout properties for fig.update_layout()
|
|
233
|
+
"""
|
|
234
|
+
return {
|
|
235
|
+
'plot_bgcolor': self.plot_bgcolor,
|
|
236
|
+
'paper_bgcolor': self.paper_bgcolor,
|
|
237
|
+
'font': {'family': self.font_family, 'size': self.font_size, 'color': self.font_color},
|
|
238
|
+
'title': {
|
|
239
|
+
'font': {'size': self.title_font_size, 'color': self.title_font_color},
|
|
240
|
+
'x': 0.5,
|
|
241
|
+
'xanchor': 'center',
|
|
242
|
+
},
|
|
243
|
+
'xaxis': {
|
|
244
|
+
'showgrid': self.show_grid,
|
|
245
|
+
'gridcolor': self.grid_color,
|
|
246
|
+
'gridwidth': self.grid_width,
|
|
247
|
+
'showline': self.show_axis_line,
|
|
248
|
+
'linecolor': self.axis_line_color,
|
|
249
|
+
'linewidth': self.axis_line_width,
|
|
250
|
+
},
|
|
251
|
+
'yaxis': {
|
|
252
|
+
'showgrid': self.show_grid,
|
|
253
|
+
'gridcolor': self.grid_color,
|
|
254
|
+
'gridwidth': self.grid_width,
|
|
255
|
+
'showline': self.show_axis_line,
|
|
256
|
+
'linecolor': self.axis_line_color,
|
|
257
|
+
'linewidth': self.axis_line_width,
|
|
258
|
+
'zeroline': False,
|
|
259
|
+
},
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
# =============================================================================
|
|
264
|
+
# Built-in Themes
|
|
265
|
+
# =============================================================================
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _create_processbehavior_theme() -> ChartTheme:
|
|
269
|
+
"""Default theme with professional, balanced appearance."""
|
|
270
|
+
return ChartTheme(
|
|
271
|
+
name='processbehavior',
|
|
272
|
+
data_opacity=0.8,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _create_ggplot_theme() -> ChartTheme:
|
|
277
|
+
"""Theme inspired by ggplot2's default appearance."""
|
|
278
|
+
return ChartTheme(
|
|
279
|
+
name='ggplot',
|
|
280
|
+
# Data - ggplot2's default blue
|
|
281
|
+
data_color='#3366CC',
|
|
282
|
+
# Control limits
|
|
283
|
+
ucl_color='#E31A1C', # ggplot2 red
|
|
284
|
+
lcl_color='#E31A1C',
|
|
285
|
+
center_color='#33A02C', # ggplot2 green
|
|
286
|
+
limit_line_width=1.2,
|
|
287
|
+
center_line_width=1.2,
|
|
288
|
+
# Signals - same marker as data, just red fill
|
|
289
|
+
signal_color='#E31A1C',
|
|
290
|
+
signal_marker_symbol='circle',
|
|
291
|
+
signal_marker_line_color='#B2182B',
|
|
292
|
+
# Zones
|
|
293
|
+
zone_a_color='#FDAE61',
|
|
294
|
+
zone_b_color='#FEE08B',
|
|
295
|
+
zone_c_color='#D9EF8B',
|
|
296
|
+
zone_opacity=0.2,
|
|
297
|
+
# Layout - ggplot2's gray panel background
|
|
298
|
+
plot_bgcolor='#EBEBEB',
|
|
299
|
+
paper_bgcolor='white',
|
|
300
|
+
grid_color='white',
|
|
301
|
+
grid_width=1.0,
|
|
302
|
+
axis_line_color='#EBEBEB',
|
|
303
|
+
# Typography
|
|
304
|
+
font_family='sans-serif',
|
|
305
|
+
font_size=11,
|
|
306
|
+
font_color='#333333',
|
|
307
|
+
title_font_size=14,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _create_minimal_theme() -> ChartTheme:
|
|
312
|
+
"""Clean, minimalist theme with reduced visual elements."""
|
|
313
|
+
return ChartTheme(
|
|
314
|
+
name='minimal',
|
|
315
|
+
# Data
|
|
316
|
+
data_color='#2C3E50', # Dark slate
|
|
317
|
+
# Control limits - subtle
|
|
318
|
+
ucl_color='#95A5A6', # Gray
|
|
319
|
+
lcl_color='#95A5A6',
|
|
320
|
+
center_color='#7F8C8D',
|
|
321
|
+
limit_line_dash='dot',
|
|
322
|
+
limit_line_width=1.0,
|
|
323
|
+
center_line_width=1.0,
|
|
324
|
+
# Signals - same marker as data, just red fill
|
|
325
|
+
signal_color='#E74C3C', # Flat red
|
|
326
|
+
signal_marker_symbol='circle',
|
|
327
|
+
signal_marker_line_color='#C0392B',
|
|
328
|
+
# Zones - very subtle
|
|
329
|
+
zone_a_color='#FADBD8',
|
|
330
|
+
zone_b_color='#FCF3CF',
|
|
331
|
+
zone_c_color='#D5F5E3',
|
|
332
|
+
zone_opacity=0.1,
|
|
333
|
+
# Layout - minimal grid
|
|
334
|
+
plot_bgcolor='white',
|
|
335
|
+
paper_bgcolor='white',
|
|
336
|
+
grid_color='#F5F5F5',
|
|
337
|
+
show_grid=True, # Only y-grid
|
|
338
|
+
axis_line_color='#CCCCCC',
|
|
339
|
+
# Typography
|
|
340
|
+
font_family='Helvetica, sans-serif',
|
|
341
|
+
font_size=11,
|
|
342
|
+
font_color='#2C3E50',
|
|
343
|
+
title_font_size=14,
|
|
344
|
+
title_font_color='#2C3E50',
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _create_dark_theme() -> ChartTheme:
|
|
349
|
+
"""Dark theme for presentations and dark mode UIs."""
|
|
350
|
+
return ChartTheme(
|
|
351
|
+
name='dark',
|
|
352
|
+
# Data - bright for contrast
|
|
353
|
+
data_color='#00D4AA', # Teal/cyan
|
|
354
|
+
# Control limits
|
|
355
|
+
ucl_color='#FF6B6B', # Soft red
|
|
356
|
+
lcl_color='#FF6B6B',
|
|
357
|
+
center_color='#4ECDC4', # Bright teal
|
|
358
|
+
limit_line_width=1.5,
|
|
359
|
+
center_line_width=1.5,
|
|
360
|
+
# Signals - same marker as data, just red fill
|
|
361
|
+
signal_color='#FF4757', # Bright red
|
|
362
|
+
signal_marker_symbol='circle',
|
|
363
|
+
signal_marker_line_color='#FF6B81',
|
|
364
|
+
# Zones - semi-transparent on dark
|
|
365
|
+
zone_a_color='#FF6B6B',
|
|
366
|
+
zone_b_color='#FFA502',
|
|
367
|
+
zone_c_color='#2ED573',
|
|
368
|
+
zone_opacity=0.15,
|
|
369
|
+
# Layout
|
|
370
|
+
plot_bgcolor='#1E1E1E',
|
|
371
|
+
paper_bgcolor='#2D2D2D',
|
|
372
|
+
grid_color='#444444',
|
|
373
|
+
axis_line_color='#666666',
|
|
374
|
+
# Typography
|
|
375
|
+
font_family='Arial, sans-serif',
|
|
376
|
+
font_size=12,
|
|
377
|
+
font_color='#E0E0E0',
|
|
378
|
+
title_font_size=16,
|
|
379
|
+
title_font_color='#FFFFFF',
|
|
380
|
+
# Limit summary
|
|
381
|
+
limit_summary_color='#FFFFFF',
|
|
382
|
+
limit_summary_bgcolor='rgba(30,30,30,0.7)',
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _create_publication_theme() -> ChartTheme:
|
|
387
|
+
"""Theme optimized for academic publications and print."""
|
|
388
|
+
return ChartTheme(
|
|
389
|
+
name='publication',
|
|
390
|
+
# Data - high contrast black
|
|
391
|
+
data_color='#000000',
|
|
392
|
+
data_marker_size=5,
|
|
393
|
+
# Control limits - grayscale for B&W printing
|
|
394
|
+
ucl_color='#666666',
|
|
395
|
+
lcl_color='#666666',
|
|
396
|
+
center_color='#333333',
|
|
397
|
+
limit_line_dash='dash',
|
|
398
|
+
limit_line_width=0.8,
|
|
399
|
+
center_line_width=1.0,
|
|
400
|
+
# Signals - red to stand out against black data
|
|
401
|
+
signal_color='red',
|
|
402
|
+
signal_marker_size=5, # Same as data_marker_size
|
|
403
|
+
signal_marker_symbol='circle',
|
|
404
|
+
signal_marker_line_width=0.5,
|
|
405
|
+
signal_marker_line_color='darkred',
|
|
406
|
+
# Zones - not used in print (set opacity to 0)
|
|
407
|
+
zone_opacity=0.0,
|
|
408
|
+
# Layout - clean white
|
|
409
|
+
plot_bgcolor='white',
|
|
410
|
+
paper_bgcolor='white',
|
|
411
|
+
grid_color='#CCCCCC',
|
|
412
|
+
grid_width=0.5,
|
|
413
|
+
axis_line_color='#000000',
|
|
414
|
+
axis_line_width=1.0,
|
|
415
|
+
# Typography - serif for academic
|
|
416
|
+
font_family='Times New Roman, serif',
|
|
417
|
+
font_size=10,
|
|
418
|
+
font_color='#000000',
|
|
419
|
+
title_font_size=12,
|
|
420
|
+
title_font_color='#000000',
|
|
421
|
+
axis_title_font_size=10,
|
|
422
|
+
annotation_font_size=9,
|
|
423
|
+
facet_line_width=0.8,
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
# =============================================================================
|
|
428
|
+
# Theme Registry and Access Functions
|
|
429
|
+
# =============================================================================
|
|
430
|
+
|
|
431
|
+
# Registry of built-in themes (lazily populated)
|
|
432
|
+
_THEME_REGISTRY: dict[str, ChartTheme] = {}
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def _ensure_registry():
|
|
436
|
+
"""Populate theme registry if empty."""
|
|
437
|
+
global _THEME_REGISTRY
|
|
438
|
+
if not _THEME_REGISTRY:
|
|
439
|
+
_THEME_REGISTRY = {
|
|
440
|
+
'processbehavior': _create_processbehavior_theme(),
|
|
441
|
+
'ggplot': _create_ggplot_theme(),
|
|
442
|
+
'minimal': _create_minimal_theme(),
|
|
443
|
+
'dark': _create_dark_theme(),
|
|
444
|
+
'publication': _create_publication_theme(),
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def get_theme(name: str) -> ChartTheme:
|
|
449
|
+
"""
|
|
450
|
+
Get a theme by name.
|
|
451
|
+
|
|
452
|
+
Parameters
|
|
453
|
+
----------
|
|
454
|
+
name : str
|
|
455
|
+
Theme name ('processbehavior', 'ggplot', 'minimal', 'dark', 'publication')
|
|
456
|
+
|
|
457
|
+
Returns
|
|
458
|
+
-------
|
|
459
|
+
ChartTheme
|
|
460
|
+
Copy of the requested theme (safe to modify)
|
|
461
|
+
|
|
462
|
+
Raises
|
|
463
|
+
------
|
|
464
|
+
ValueError
|
|
465
|
+
If theme name is not found
|
|
466
|
+
|
|
467
|
+
Examples
|
|
468
|
+
--------
|
|
469
|
+
>>> theme = get_theme('ggplot')
|
|
470
|
+
>>> theme.data_color = 'purple' # Safe to modify
|
|
471
|
+
"""
|
|
472
|
+
_ensure_registry()
|
|
473
|
+
|
|
474
|
+
if name not in _THEME_REGISTRY:
|
|
475
|
+
available = list(_THEME_REGISTRY.keys())
|
|
476
|
+
raise ValidationError(f"Unknown theme: '{name}'.\nAvailable themes: {available}")
|
|
477
|
+
|
|
478
|
+
# Return a copy so modifications don't affect the registry
|
|
479
|
+
import copy
|
|
480
|
+
|
|
481
|
+
return copy.copy(_THEME_REGISTRY[name])
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def list_themes() -> list[str]:
|
|
485
|
+
"""
|
|
486
|
+
List all available theme names.
|
|
487
|
+
|
|
488
|
+
Returns
|
|
489
|
+
-------
|
|
490
|
+
list of str
|
|
491
|
+
Available theme names
|
|
492
|
+
"""
|
|
493
|
+
_ensure_registry()
|
|
494
|
+
return list(_THEME_REGISTRY.keys())
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def register_theme(theme: ChartTheme):
|
|
498
|
+
"""
|
|
499
|
+
Register a custom theme.
|
|
500
|
+
|
|
501
|
+
Parameters
|
|
502
|
+
----------
|
|
503
|
+
theme : ChartTheme
|
|
504
|
+
Theme to register (uses theme.name as key)
|
|
505
|
+
|
|
506
|
+
Examples
|
|
507
|
+
--------
|
|
508
|
+
>>> custom = ChartTheme(name='corporate', data_color='#003366')
|
|
509
|
+
>>> register_theme(custom)
|
|
510
|
+
>>> fig = plotter.plot(theme='corporate')
|
|
511
|
+
"""
|
|
512
|
+
_ensure_registry()
|
|
513
|
+
_THEME_REGISTRY[theme.name] = theme
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def apply_theme(fig: go.Figure, theme: str | ChartTheme = 'processbehavior') -> go.Figure:
|
|
517
|
+
"""
|
|
518
|
+
Apply a theme to a Plotly figure.
|
|
519
|
+
|
|
520
|
+
Parameters
|
|
521
|
+
----------
|
|
522
|
+
fig : go.Figure
|
|
523
|
+
Plotly figure to apply theme to
|
|
524
|
+
theme : str or ChartTheme
|
|
525
|
+
Theme name or ChartTheme instance
|
|
526
|
+
|
|
527
|
+
Returns
|
|
528
|
+
-------
|
|
529
|
+
go.Figure
|
|
530
|
+
Figure with theme applied
|
|
531
|
+
|
|
532
|
+
Examples
|
|
533
|
+
--------
|
|
534
|
+
>>> fig = go.Figure()
|
|
535
|
+
>>> fig = apply_theme(fig, 'dark')
|
|
536
|
+
|
|
537
|
+
>>> custom_theme = ChartTheme(name='custom', plot_bgcolor='#F0F0F0')
|
|
538
|
+
>>> fig = apply_theme(fig, custom_theme)
|
|
539
|
+
"""
|
|
540
|
+
theme = get_theme(theme) if isinstance(theme, str) else theme
|
|
541
|
+
|
|
542
|
+
fig.update_layout(**theme.to_layout_dict())
|
|
543
|
+
|
|
544
|
+
return fig
|