streamlit-react-components 1.0.5__py3-none-any.whl → 1.0.7__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.
- streamlit_react_components/__init__.py +5 -1
- streamlit_react_components/_frontend/index.css +1 -1
- streamlit_react_components/_frontend/index.js +143 -142
- streamlit_react_components/common/__init__.py +2 -0
- streamlit_react_components/common/button_group.py +10 -0
- streamlit_react_components/common/chart_legend.py +10 -0
- streamlit_react_components/common/data_table.py +10 -0
- streamlit_react_components/common/metric_row.py +10 -0
- streamlit_react_components/common/panel.py +10 -0
- streamlit_react_components/common/plotly_chart.py +10 -0
- streamlit_react_components/common/section_header.py +10 -0
- streamlit_react_components/common/smart_chart.py +584 -0
- streamlit_react_components/common/stat_card.py +10 -0
- streamlit_react_components/common/step_indicator.py +10 -0
- streamlit_react_components/form/__init__.py +2 -0
- streamlit_react_components/form/checkbox_group.py +10 -0
- streamlit_react_components/form/form_select.py +10 -0
- streamlit_react_components/form/form_slider.py +10 -0
- streamlit_react_components/form/radio_group.py +78 -0
- streamlit_react_components/themes.py +1203 -0
- {streamlit_react_components-1.0.5.dist-info → streamlit_react_components-1.0.7.dist-info}/METADATA +1 -1
- streamlit_react_components-1.0.7.dist-info/RECORD +25 -0
- {streamlit_react_components-1.0.5.dist-info → streamlit_react_components-1.0.7.dist-info}/WHEEL +1 -1
- streamlit_react_components-1.0.5.dist-info/RECORD +0 -22
- {streamlit_react_components-1.0.5.dist-info → streamlit_react_components-1.0.7.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
"""SmartChart component - Simplified chart creation for line, gauge, and waterfall charts."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Any, Optional, List, Union
|
|
4
|
+
from .plotly_chart import plotly_chart, _dataframe_to_figure
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _dataframe_to_gauge(
|
|
8
|
+
data: Any,
|
|
9
|
+
value_column: str,
|
|
10
|
+
min_value: Optional[float],
|
|
11
|
+
max_value: Optional[float],
|
|
12
|
+
threshold_low: Optional[float],
|
|
13
|
+
threshold_medium: Optional[float],
|
|
14
|
+
threshold_high: Optional[float],
|
|
15
|
+
title: Optional[str],
|
|
16
|
+
) -> Dict[str, Any]:
|
|
17
|
+
"""Convert a DataFrame to a Plotly gauge indicator figure."""
|
|
18
|
+
from ..themes import get_active_theme
|
|
19
|
+
|
|
20
|
+
# Validate required column exists
|
|
21
|
+
if value_column not in data.columns:
|
|
22
|
+
raise ValueError(f"Column '{value_column}' not found in DataFrame. Available columns: {list(data.columns)}")
|
|
23
|
+
|
|
24
|
+
# Extract value (first row if multiple)
|
|
25
|
+
value = float(data[value_column].iloc[0])
|
|
26
|
+
|
|
27
|
+
# Auto-calculate min/max if not provided
|
|
28
|
+
if min_value is None:
|
|
29
|
+
min_value = 0
|
|
30
|
+
if max_value is None:
|
|
31
|
+
max_value = float(data[value_column].max() * 1.2)
|
|
32
|
+
|
|
33
|
+
# Validate threshold ordering
|
|
34
|
+
if threshold_low is not None and threshold_medium is not None and threshold_low >= threshold_medium:
|
|
35
|
+
raise ValueError("threshold_low must be less than threshold_medium")
|
|
36
|
+
if threshold_medium is not None and threshold_high is not None and threshold_medium >= threshold_high:
|
|
37
|
+
raise ValueError("threshold_medium must be less than threshold_high")
|
|
38
|
+
|
|
39
|
+
# Get theme colors
|
|
40
|
+
theme = get_active_theme()
|
|
41
|
+
primary = theme['colors']['primary']
|
|
42
|
+
success = theme['colors']['success']
|
|
43
|
+
warning = theme['colors']['warning']
|
|
44
|
+
error = theme['colors']['error']
|
|
45
|
+
|
|
46
|
+
# Build gauge configuration
|
|
47
|
+
gauge_config = {
|
|
48
|
+
'axis': {'range': [min_value, max_value]},
|
|
49
|
+
'bar': {'color': primary}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Add colored threshold ranges
|
|
53
|
+
if threshold_low is not None or threshold_medium is not None or threshold_high is not None:
|
|
54
|
+
steps = []
|
|
55
|
+
if threshold_low is not None:
|
|
56
|
+
steps.append({'range': [min_value, threshold_low], 'color': error})
|
|
57
|
+
if threshold_medium is not None:
|
|
58
|
+
start = threshold_low if threshold_low is not None else min_value
|
|
59
|
+
steps.append({'range': [start, threshold_medium], 'color': warning})
|
|
60
|
+
if threshold_high is not None:
|
|
61
|
+
start = threshold_medium if threshold_medium is not None else min_value
|
|
62
|
+
steps.append({'range': [start, threshold_high], 'color': success})
|
|
63
|
+
# Add a final green band if there's space
|
|
64
|
+
if threshold_high < max_value:
|
|
65
|
+
steps.append({'range': [threshold_high, max_value], 'color': success})
|
|
66
|
+
|
|
67
|
+
gauge_config['steps'] = steps
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
'data': [{
|
|
71
|
+
'type': 'indicator',
|
|
72
|
+
'mode': 'gauge+number',
|
|
73
|
+
'value': value,
|
|
74
|
+
'title': {'text': title or ''},
|
|
75
|
+
'gauge': gauge_config,
|
|
76
|
+
'domain': {'x': [0, 1], 'y': [0, 1]}
|
|
77
|
+
}],
|
|
78
|
+
'layout': {}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _dataframe_to_waterfall(
|
|
83
|
+
data: Any,
|
|
84
|
+
category_column: str,
|
|
85
|
+
value_column: str,
|
|
86
|
+
measure_column: Optional[str],
|
|
87
|
+
title: Optional[str],
|
|
88
|
+
) -> Dict[str, Any]:
|
|
89
|
+
"""Convert a DataFrame to a Plotly waterfall figure."""
|
|
90
|
+
from ..themes import get_active_theme
|
|
91
|
+
|
|
92
|
+
# Validate required columns exist
|
|
93
|
+
if category_column not in data.columns:
|
|
94
|
+
raise ValueError(f"Column '{category_column}' not found in DataFrame. Available columns: {list(data.columns)}")
|
|
95
|
+
if value_column not in data.columns:
|
|
96
|
+
raise ValueError(f"Column '{value_column}' not found in DataFrame. Available columns: {list(data.columns)}")
|
|
97
|
+
|
|
98
|
+
categories = data[category_column].tolist()
|
|
99
|
+
values = data[value_column].tolist()
|
|
100
|
+
|
|
101
|
+
# Use measure_column if provided, otherwise auto-detect
|
|
102
|
+
if measure_column and measure_column in data.columns:
|
|
103
|
+
measures = data[measure_column].tolist()
|
|
104
|
+
else:
|
|
105
|
+
# Auto-detect: zero values are likely totals
|
|
106
|
+
measures = ['total' if v == 0 else 'relative' for v in values]
|
|
107
|
+
|
|
108
|
+
# Get theme colors
|
|
109
|
+
theme = get_active_theme()
|
|
110
|
+
success = theme['colors']['success']
|
|
111
|
+
error = theme['colors']['error']
|
|
112
|
+
info = theme['colors']['info']
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
'data': [{
|
|
116
|
+
'type': 'waterfall',
|
|
117
|
+
'x': categories,
|
|
118
|
+
'y': values,
|
|
119
|
+
'measure': measures,
|
|
120
|
+
'connector': {'line': {'color': '#475569'}}, # slate-600
|
|
121
|
+
'increasing': {'marker': {'color': success}},
|
|
122
|
+
'decreasing': {'marker': {'color': error}},
|
|
123
|
+
'totals': {'marker': {'color': info}},
|
|
124
|
+
}],
|
|
125
|
+
'layout': {
|
|
126
|
+
'title': title or '',
|
|
127
|
+
'showlegend': False,
|
|
128
|
+
'xaxis': {'title': category_column},
|
|
129
|
+
'yaxis': {'title': 'Value'}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _dataframe_to_scatter(
|
|
135
|
+
data: Any,
|
|
136
|
+
x: str,
|
|
137
|
+
y: str,
|
|
138
|
+
size: Optional[str] = None,
|
|
139
|
+
color_column: Optional[str] = None,
|
|
140
|
+
text: Optional[str] = None,
|
|
141
|
+
title: Optional[str] = None,
|
|
142
|
+
) -> Dict[str, Any]:
|
|
143
|
+
"""Convert DataFrame to Plotly scatter figure."""
|
|
144
|
+
from ..themes import get_active_theme
|
|
145
|
+
|
|
146
|
+
# Validate required columns
|
|
147
|
+
if x not in data.columns:
|
|
148
|
+
raise ValueError(f"Column '{x}' not found in DataFrame. Available columns: {list(data.columns)}")
|
|
149
|
+
if y not in data.columns:
|
|
150
|
+
raise ValueError(f"Column '{y}' not found in DataFrame. Available columns: {list(data.columns)}")
|
|
151
|
+
|
|
152
|
+
# Build marker config
|
|
153
|
+
marker_config = {}
|
|
154
|
+
if size and size in data.columns:
|
|
155
|
+
marker_config['size'] = data[size].tolist()
|
|
156
|
+
if color_column and color_column in data.columns:
|
|
157
|
+
marker_config['color'] = data[color_column].tolist()
|
|
158
|
+
marker_config['colorscale'] = 'Viridis'
|
|
159
|
+
marker_config['showscale'] = True
|
|
160
|
+
|
|
161
|
+
# Build hover text
|
|
162
|
+
hover_text = data[text].tolist() if text and text in data.columns else None
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
'data': [{
|
|
166
|
+
'type': 'scatter',
|
|
167
|
+
'mode': 'markers',
|
|
168
|
+
'x': data[x].tolist(),
|
|
169
|
+
'y': data[y].tolist(),
|
|
170
|
+
'marker': marker_config,
|
|
171
|
+
'text': hover_text,
|
|
172
|
+
'hovertemplate': f'{x}: %{{x}}<br>{y}: %{{y}}<extra></extra>'
|
|
173
|
+
}],
|
|
174
|
+
'layout': {
|
|
175
|
+
'title': title or '',
|
|
176
|
+
'xaxis': {'title': x},
|
|
177
|
+
'yaxis': {'title': y}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _dataframe_to_bar(
|
|
183
|
+
data: Any,
|
|
184
|
+
x: str,
|
|
185
|
+
y: Union[str, List[str]],
|
|
186
|
+
orientation: str = 'v',
|
|
187
|
+
barmode: str = 'group',
|
|
188
|
+
title: Optional[str] = None,
|
|
189
|
+
) -> Dict[str, Any]:
|
|
190
|
+
"""Convert DataFrame to Plotly bar figure."""
|
|
191
|
+
from ..themes import get_active_theme
|
|
192
|
+
|
|
193
|
+
theme = get_active_theme()
|
|
194
|
+
colors = [
|
|
195
|
+
theme['colors']['primary'],
|
|
196
|
+
theme['colors']['secondary'],
|
|
197
|
+
theme['colors']['success'],
|
|
198
|
+
theme['colors']['warning'],
|
|
199
|
+
theme['colors']['info']
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
# Validate columns
|
|
203
|
+
if x not in data.columns:
|
|
204
|
+
raise ValueError(f"Column '{x}' not found in DataFrame. Available columns: {list(data.columns)}")
|
|
205
|
+
|
|
206
|
+
y_cols = [y] if isinstance(y, str) else y
|
|
207
|
+
for y_col in y_cols:
|
|
208
|
+
if y_col not in data.columns:
|
|
209
|
+
raise ValueError(f"Column '{y_col}' not found in DataFrame. Available columns: {list(data.columns)}")
|
|
210
|
+
|
|
211
|
+
traces = []
|
|
212
|
+
for idx, y_col in enumerate(y_cols):
|
|
213
|
+
if orientation == 'v':
|
|
214
|
+
trace = {
|
|
215
|
+
'type': 'bar',
|
|
216
|
+
'x': data[x].tolist(),
|
|
217
|
+
'y': data[y_col].tolist(),
|
|
218
|
+
'name': y_col,
|
|
219
|
+
'marker': {'color': colors[idx % len(colors)]}
|
|
220
|
+
}
|
|
221
|
+
else: # horizontal
|
|
222
|
+
trace = {
|
|
223
|
+
'type': 'bar',
|
|
224
|
+
'x': data[y_col].tolist(),
|
|
225
|
+
'y': data[x].tolist(),
|
|
226
|
+
'orientation': 'h',
|
|
227
|
+
'name': y_col,
|
|
228
|
+
'marker': {'color': colors[idx % len(colors)]}
|
|
229
|
+
}
|
|
230
|
+
traces.append(trace)
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
'data': traces,
|
|
234
|
+
'layout': {
|
|
235
|
+
'title': title or '',
|
|
236
|
+
'barmode': barmode,
|
|
237
|
+
'xaxis': {'title': x if orientation == 'v' else 'Value'},
|
|
238
|
+
'yaxis': {'title': 'Value' if orientation == 'v' else x}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _dataframe_to_histogram(
|
|
244
|
+
data: Any,
|
|
245
|
+
x: str,
|
|
246
|
+
nbins: int = 30,
|
|
247
|
+
show_mean: bool = True,
|
|
248
|
+
title: Optional[str] = None,
|
|
249
|
+
) -> Dict[str, Any]:
|
|
250
|
+
"""Convert DataFrame to Plotly histogram figure."""
|
|
251
|
+
from ..themes import get_active_theme
|
|
252
|
+
|
|
253
|
+
if x not in data.columns:
|
|
254
|
+
raise ValueError(f"Column '{x}' not found in DataFrame. Available columns: {list(data.columns)}")
|
|
255
|
+
|
|
256
|
+
theme = get_active_theme()
|
|
257
|
+
primary = theme['colors']['primary']
|
|
258
|
+
success = theme['colors']['success']
|
|
259
|
+
|
|
260
|
+
figure = {
|
|
261
|
+
'data': [{
|
|
262
|
+
'type': 'histogram',
|
|
263
|
+
'x': data[x].tolist(),
|
|
264
|
+
'nbinsx': nbins,
|
|
265
|
+
'marker': {'color': primary, 'opacity': 0.75},
|
|
266
|
+
'name': 'Distribution'
|
|
267
|
+
}],
|
|
268
|
+
'layout': {
|
|
269
|
+
'title': title or f'{x} Distribution',
|
|
270
|
+
'xaxis': {'title': x},
|
|
271
|
+
'yaxis': {'title': 'Frequency'},
|
|
272
|
+
'showlegend': True
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
# Add mean line if requested
|
|
277
|
+
if show_mean:
|
|
278
|
+
mean_val = float(data[x].mean())
|
|
279
|
+
figure['layout']['shapes'] = [{
|
|
280
|
+
'type': 'line',
|
|
281
|
+
'x0': mean_val,
|
|
282
|
+
'x1': mean_val,
|
|
283
|
+
'y0': 0,
|
|
284
|
+
'y1': 1,
|
|
285
|
+
'yref': 'paper',
|
|
286
|
+
'line': {'color': success, 'dash': 'dash', 'width': 2}
|
|
287
|
+
}]
|
|
288
|
+
figure['layout']['annotations'] = [{
|
|
289
|
+
'x': mean_val,
|
|
290
|
+
'y': 1,
|
|
291
|
+
'yref': 'paper',
|
|
292
|
+
'text': f'Mean: {mean_val:.2f}',
|
|
293
|
+
'showarrow': False,
|
|
294
|
+
'yshift': 10
|
|
295
|
+
}]
|
|
296
|
+
|
|
297
|
+
return figure
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _dataframe_to_pie(
|
|
301
|
+
data: Any,
|
|
302
|
+
labels: str,
|
|
303
|
+
values: str,
|
|
304
|
+
hole: float = 0.0,
|
|
305
|
+
title: Optional[str] = None,
|
|
306
|
+
) -> Dict[str, Any]:
|
|
307
|
+
"""Convert DataFrame to Plotly pie/donut figure."""
|
|
308
|
+
from ..themes import get_active_theme
|
|
309
|
+
|
|
310
|
+
if labels not in data.columns:
|
|
311
|
+
raise ValueError(f"Column '{labels}' not found in DataFrame. Available columns: {list(data.columns)}")
|
|
312
|
+
if values not in data.columns:
|
|
313
|
+
raise ValueError(f"Column '{values}' not found in DataFrame. Available columns: {list(data.columns)}")
|
|
314
|
+
|
|
315
|
+
theme = get_active_theme()
|
|
316
|
+
colors = [
|
|
317
|
+
theme['colors']['primary'],
|
|
318
|
+
theme['colors']['secondary'],
|
|
319
|
+
theme['colors']['success'],
|
|
320
|
+
theme['colors']['warning'],
|
|
321
|
+
theme['colors']['info'],
|
|
322
|
+
theme['colors']['error']
|
|
323
|
+
]
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
'data': [{
|
|
327
|
+
'type': 'pie',
|
|
328
|
+
'labels': data[labels].tolist(),
|
|
329
|
+
'values': data[values].tolist(),
|
|
330
|
+
'hole': hole,
|
|
331
|
+
'marker': {'colors': colors}
|
|
332
|
+
}],
|
|
333
|
+
'layout': {
|
|
334
|
+
'title': title or ''
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def smart_chart(
|
|
340
|
+
data: Any,
|
|
341
|
+
chart_type: str,
|
|
342
|
+
# Common chart parameters
|
|
343
|
+
x: Optional[str] = None,
|
|
344
|
+
y: Optional[Union[str, List[str]]] = None,
|
|
345
|
+
# Scatter plot parameters
|
|
346
|
+
size: Optional[str] = None,
|
|
347
|
+
text: Optional[str] = None,
|
|
348
|
+
# Bar chart parameters
|
|
349
|
+
orientation: str = 'v',
|
|
350
|
+
barmode: str = 'group',
|
|
351
|
+
# Histogram parameters
|
|
352
|
+
nbins: int = 30,
|
|
353
|
+
show_mean: bool = True,
|
|
354
|
+
# Pie chart parameters
|
|
355
|
+
labels: Optional[str] = None,
|
|
356
|
+
values: Optional[str] = None,
|
|
357
|
+
hole: float = 0.0,
|
|
358
|
+
# Gauge chart parameters
|
|
359
|
+
value_column: Optional[str] = None,
|
|
360
|
+
min_value: Optional[float] = None,
|
|
361
|
+
max_value: Optional[float] = None,
|
|
362
|
+
threshold_low: Optional[float] = None,
|
|
363
|
+
threshold_medium: Optional[float] = None,
|
|
364
|
+
threshold_high: Optional[float] = None,
|
|
365
|
+
# Waterfall chart parameters
|
|
366
|
+
category_column: Optional[str] = None,
|
|
367
|
+
value_column_waterfall: Optional[str] = None,
|
|
368
|
+
measure_column: Optional[str] = None,
|
|
369
|
+
# Common parameters
|
|
370
|
+
title: Optional[str] = None,
|
|
371
|
+
color: Optional[str] = None,
|
|
372
|
+
# Pass-through to plotly_chart
|
|
373
|
+
on_click: bool = False,
|
|
374
|
+
on_select: bool = False,
|
|
375
|
+
on_hover: bool = False,
|
|
376
|
+
on_relayout: bool = False,
|
|
377
|
+
expandable: bool = False,
|
|
378
|
+
modal_title: str = "",
|
|
379
|
+
# Styling
|
|
380
|
+
style: Optional[Dict[str, Any]] = None,
|
|
381
|
+
class_name: str = "",
|
|
382
|
+
theme: Optional[Dict[str, Any]] = None,
|
|
383
|
+
key: Optional[str] = None,
|
|
384
|
+
) -> Optional[Dict[str, Any]]:
|
|
385
|
+
"""
|
|
386
|
+
Create a smart chart that automatically converts DataFrames to Plotly charts.
|
|
387
|
+
|
|
388
|
+
This is a wrapper component that simplifies chart creation by transforming DataFrames
|
|
389
|
+
into appropriate Plotly figure configurations based on the specified chart_type.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
data: pandas DataFrame containing the data to plot
|
|
393
|
+
chart_type: Type of chart to create - 'line', 'scatter', 'bar', 'bar_horizontal',
|
|
394
|
+
'histogram', 'pie', 'gauge', or 'waterfall'
|
|
395
|
+
|
|
396
|
+
# Common Chart Parameters
|
|
397
|
+
x: Column name for x-axis (required for line, scatter, bar, histogram charts)
|
|
398
|
+
y: Column name(s) for y-axis - string or list of strings (required for line, scatter, bar charts)
|
|
399
|
+
|
|
400
|
+
# Scatter Plot Parameters
|
|
401
|
+
size: Column name for marker sizes (optional)
|
|
402
|
+
text: Column name for hover text (optional)
|
|
403
|
+
|
|
404
|
+
# Bar Chart Parameters
|
|
405
|
+
orientation: 'v' for vertical (default) or 'h' for horizontal
|
|
406
|
+
barmode: 'group' (default), 'stack', or 'overlay'
|
|
407
|
+
|
|
408
|
+
# Histogram Parameters
|
|
409
|
+
nbins: Number of bins (default: 30)
|
|
410
|
+
show_mean: Show mean line on histogram (default: True)
|
|
411
|
+
|
|
412
|
+
# Pie Chart Parameters
|
|
413
|
+
labels: Column name for pie slice labels (required for pie charts)
|
|
414
|
+
values: Column name for pie slice values (required for pie charts)
|
|
415
|
+
hole: Hole size for donut chart (0.0-1.0, default: 0.0 for pie)
|
|
416
|
+
|
|
417
|
+
# Gauge Chart Parameters
|
|
418
|
+
value_column: Column name containing the value to display (required for gauge charts)
|
|
419
|
+
min_value: Minimum value for gauge range (auto-calculated if not provided)
|
|
420
|
+
max_value: Maximum value for gauge range (auto-calculated if not provided)
|
|
421
|
+
threshold_low: Low threshold value (red zone below this)
|
|
422
|
+
threshold_medium: Medium threshold value (amber zone)
|
|
423
|
+
threshold_high: High threshold value (green zone above this)
|
|
424
|
+
|
|
425
|
+
# Waterfall Chart Parameters
|
|
426
|
+
category_column: Column name for categories (required for waterfall charts)
|
|
427
|
+
value_column_waterfall: Column name for values (required for waterfall charts)
|
|
428
|
+
measure_column: Optional column specifying 'relative' or 'total' for each row.
|
|
429
|
+
If not provided, auto-detects: zeros are 'total', non-zeros are 'relative'
|
|
430
|
+
|
|
431
|
+
# Common Parameters
|
|
432
|
+
title: Chart title
|
|
433
|
+
color: Column name for color grouping (line charts only)
|
|
434
|
+
on_click: Enable click events
|
|
435
|
+
on_select: Enable selection events (box/lasso)
|
|
436
|
+
on_hover: Enable hover events
|
|
437
|
+
on_relayout: Enable relayout events (zoom/pan)
|
|
438
|
+
expandable: Show expand button to open chart in full-page dialog
|
|
439
|
+
modal_title: Title displayed in dialog header when expanded
|
|
440
|
+
style: Inline CSS styles as a dictionary
|
|
441
|
+
class_name: Tailwind CSS classes
|
|
442
|
+
theme: Optional theme dictionary. If None, uses active global theme.
|
|
443
|
+
key: Unique key for the component
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
Event dict with 'type' and event data, or None if no event.
|
|
447
|
+
|
|
448
|
+
Examples:
|
|
449
|
+
# Line Chart
|
|
450
|
+
import pandas as pd
|
|
451
|
+
|
|
452
|
+
df = pd.DataFrame({
|
|
453
|
+
'month': ['Jan', 'Feb', 'Mar', 'Apr'],
|
|
454
|
+
'revenue': [100, 150, 120, 180],
|
|
455
|
+
'costs': [60, 90, 70, 100]
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
smart_chart(
|
|
459
|
+
data=df,
|
|
460
|
+
chart_type='line',
|
|
461
|
+
x='month',
|
|
462
|
+
y=['revenue', 'costs'],
|
|
463
|
+
title='Monthly Performance'
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
# Gauge Chart
|
|
467
|
+
df_gauge = pd.DataFrame({'conversion_rate': [73.5]})
|
|
468
|
+
|
|
469
|
+
smart_chart(
|
|
470
|
+
data=df_gauge,
|
|
471
|
+
chart_type='gauge',
|
|
472
|
+
value_column='conversion_rate',
|
|
473
|
+
min_value=0,
|
|
474
|
+
max_value=100,
|
|
475
|
+
threshold_low=30,
|
|
476
|
+
threshold_medium=70,
|
|
477
|
+
threshold_high=90,
|
|
478
|
+
title='Conversion Rate'
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
# Waterfall Chart
|
|
482
|
+
df_waterfall = pd.DataFrame({
|
|
483
|
+
'category': ['Sales', 'Consulting', 'Net Revenue', 'Purchases', 'Other', 'Profit'],
|
|
484
|
+
'amount': [60, 80, 0, -40, -20, 0],
|
|
485
|
+
'measure': ['relative', 'relative', 'total', 'relative', 'relative', 'total']
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
smart_chart(
|
|
489
|
+
data=df_waterfall,
|
|
490
|
+
chart_type='waterfall',
|
|
491
|
+
category_column='category',
|
|
492
|
+
value_column_waterfall='amount',
|
|
493
|
+
measure_column='measure',
|
|
494
|
+
title='Revenue Breakdown'
|
|
495
|
+
)
|
|
496
|
+
"""
|
|
497
|
+
# Validate DataFrame
|
|
498
|
+
if data is None or (hasattr(data, 'empty') and data.empty):
|
|
499
|
+
raise ValueError("DataFrame cannot be None or empty")
|
|
500
|
+
|
|
501
|
+
# Validate chart_type
|
|
502
|
+
valid_types = ['line', 'scatter', 'bar', 'bar_horizontal', 'histogram', 'pie', 'gauge', 'waterfall']
|
|
503
|
+
if chart_type not in valid_types:
|
|
504
|
+
raise ValueError(f"Invalid chart_type: '{chart_type}'. Must be one of: {', '.join(valid_types)}")
|
|
505
|
+
|
|
506
|
+
# Route to appropriate transformer and validate required parameters
|
|
507
|
+
if chart_type == 'scatter':
|
|
508
|
+
if x is None or y is None:
|
|
509
|
+
raise ValueError("Scatter chart requires 'x' and 'y' parameters")
|
|
510
|
+
figure = _dataframe_to_scatter(data, x, y, size, color, text, title)
|
|
511
|
+
|
|
512
|
+
elif chart_type in ['bar', 'bar_horizontal']:
|
|
513
|
+
if x is None or y is None:
|
|
514
|
+
raise ValueError("Bar chart requires 'x' and 'y' parameters")
|
|
515
|
+
orient = 'h' if chart_type == 'bar_horizontal' else 'v'
|
|
516
|
+
figure = _dataframe_to_bar(data, x, y, orient, barmode, title)
|
|
517
|
+
|
|
518
|
+
elif chart_type == 'histogram':
|
|
519
|
+
if x is None:
|
|
520
|
+
raise ValueError("Histogram requires 'x' parameter")
|
|
521
|
+
figure = _dataframe_to_histogram(data, x, nbins, show_mean, title)
|
|
522
|
+
|
|
523
|
+
elif chart_type == 'pie':
|
|
524
|
+
if labels is None or values is None:
|
|
525
|
+
raise ValueError("Pie chart requires 'labels' and 'values' parameters")
|
|
526
|
+
figure = _dataframe_to_pie(data, labels, values, hole, title)
|
|
527
|
+
|
|
528
|
+
elif chart_type == 'line':
|
|
529
|
+
if x is None or y is None:
|
|
530
|
+
raise ValueError("Line chart requires 'x' and 'y' parameters")
|
|
531
|
+
|
|
532
|
+
# Validate columns exist
|
|
533
|
+
if x not in data.columns:
|
|
534
|
+
raise ValueError(f"Column '{x}' not found in DataFrame. Available columns: {list(data.columns)}")
|
|
535
|
+
|
|
536
|
+
y_cols = [y] if isinstance(y, str) else y
|
|
537
|
+
for y_col in y_cols:
|
|
538
|
+
if y_col not in data.columns:
|
|
539
|
+
raise ValueError(f"Column '{y_col}' not found in DataFrame. Available columns: {list(data.columns)}")
|
|
540
|
+
|
|
541
|
+
# Use existing _dataframe_to_figure for line charts
|
|
542
|
+
figure = _dataframe_to_figure(data, x, y, color, 'line', title)
|
|
543
|
+
|
|
544
|
+
elif chart_type == 'gauge':
|
|
545
|
+
if value_column is None:
|
|
546
|
+
raise ValueError("Gauge chart requires 'value_column' parameter")
|
|
547
|
+
|
|
548
|
+
figure = _dataframe_to_gauge(
|
|
549
|
+
data,
|
|
550
|
+
value_column,
|
|
551
|
+
min_value,
|
|
552
|
+
max_value,
|
|
553
|
+
threshold_low,
|
|
554
|
+
threshold_medium,
|
|
555
|
+
threshold_high,
|
|
556
|
+
title
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
elif chart_type == 'waterfall':
|
|
560
|
+
if category_column is None or value_column_waterfall is None:
|
|
561
|
+
raise ValueError("Waterfall chart requires 'category_column' and 'value_column_waterfall' parameters")
|
|
562
|
+
|
|
563
|
+
figure = _dataframe_to_waterfall(
|
|
564
|
+
data,
|
|
565
|
+
category_column,
|
|
566
|
+
value_column_waterfall,
|
|
567
|
+
measure_column,
|
|
568
|
+
title
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
# Delegate to plotly_chart
|
|
572
|
+
return plotly_chart(
|
|
573
|
+
figure=figure,
|
|
574
|
+
on_click=on_click,
|
|
575
|
+
on_select=on_select,
|
|
576
|
+
on_hover=on_hover,
|
|
577
|
+
on_relayout=on_relayout,
|
|
578
|
+
expandable=expandable,
|
|
579
|
+
modal_title=modal_title or title or "",
|
|
580
|
+
style=style,
|
|
581
|
+
class_name=class_name,
|
|
582
|
+
theme=theme,
|
|
583
|
+
key=key,
|
|
584
|
+
)
|
|
@@ -25,6 +25,7 @@ def stat_card(
|
|
|
25
25
|
action: Optional[Dict[str, str]] = None,
|
|
26
26
|
style: Optional[Dict[str, Any]] = None,
|
|
27
27
|
class_name: str = "",
|
|
28
|
+
theme: Optional[Dict[str, Any]] = None,
|
|
28
29
|
key: Optional[str] = None,
|
|
29
30
|
) -> Optional[str]:
|
|
30
31
|
"""
|
|
@@ -52,6 +53,8 @@ def stat_card(
|
|
|
52
53
|
- className: Optional Tailwind classes for custom styling
|
|
53
54
|
style: Inline CSS styles as a dictionary
|
|
54
55
|
class_name: Tailwind CSS classes
|
|
56
|
+
theme: Optional theme dictionary. If None, uses active global theme.
|
|
57
|
+
Set to False to disable theming for this component.
|
|
55
58
|
key: Unique key for the component
|
|
56
59
|
|
|
57
60
|
Returns:
|
|
@@ -116,6 +119,12 @@ def stat_card(
|
|
|
116
119
|
}
|
|
117
120
|
)
|
|
118
121
|
"""
|
|
122
|
+
# Resolve theme (None = use global, False = disable)
|
|
123
|
+
from ..themes import get_active_theme
|
|
124
|
+
resolved_theme = None
|
|
125
|
+
if theme is not False:
|
|
126
|
+
resolved_theme = theme if theme is not None else get_active_theme()
|
|
127
|
+
|
|
119
128
|
return _component(
|
|
120
129
|
component="stat_card",
|
|
121
130
|
label=label,
|
|
@@ -130,6 +139,7 @@ def stat_card(
|
|
|
130
139
|
action=action,
|
|
131
140
|
style=style,
|
|
132
141
|
className=class_name,
|
|
142
|
+
theme=resolved_theme,
|
|
133
143
|
key=key,
|
|
134
144
|
default=None,
|
|
135
145
|
)
|
|
@@ -17,6 +17,7 @@ def step_indicator(
|
|
|
17
17
|
current_step: int,
|
|
18
18
|
style: Optional[Dict[str, Any]] = None,
|
|
19
19
|
class_name: str = "",
|
|
20
|
+
theme: Optional[Dict[str, Any]] = None,
|
|
20
21
|
key: Optional[str] = None,
|
|
21
22
|
) -> Optional[int]:
|
|
22
23
|
"""
|
|
@@ -27,6 +28,8 @@ def step_indicator(
|
|
|
27
28
|
current_step: Current active step (1-indexed)
|
|
28
29
|
style: Inline CSS styles as a dictionary
|
|
29
30
|
class_name: Tailwind CSS classes
|
|
31
|
+
theme: Optional theme dictionary. If None, uses active global theme.
|
|
32
|
+
Set to False to disable theming for this component.
|
|
30
33
|
key: Unique key for the component
|
|
31
34
|
|
|
32
35
|
Returns:
|
|
@@ -40,12 +43,19 @@ def step_indicator(
|
|
|
40
43
|
if step:
|
|
41
44
|
st.session_state.step = step
|
|
42
45
|
"""
|
|
46
|
+
# Resolve theme (None = use global, False = disable)
|
|
47
|
+
from ..themes import get_active_theme
|
|
48
|
+
resolved_theme = None
|
|
49
|
+
if theme is not False:
|
|
50
|
+
resolved_theme = theme if theme is not None else get_active_theme()
|
|
51
|
+
|
|
43
52
|
return _component(
|
|
44
53
|
component="step_indicator",
|
|
45
54
|
steps=steps,
|
|
46
55
|
currentStep=current_step,
|
|
47
56
|
style=style,
|
|
48
57
|
className=class_name,
|
|
58
|
+
theme=resolved_theme,
|
|
49
59
|
key=key,
|
|
50
60
|
default=None,
|
|
51
61
|
)
|