streamlit-react-components 0.1.1__py3-none-any.whl → 1.0.1__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 +2 -0
- streamlit_react_components/_frontend/index.css +1 -1
- streamlit_react_components/_frontend/index.js +3877 -20
- streamlit_react_components/common/__init__.py +2 -0
- streamlit_react_components/common/button_group.py +13 -1
- streamlit_react_components/common/plotly_chart.py +284 -0
- streamlit_react_components/common/section_header.py +33 -3
- streamlit_react_components/common/stat_card.py +10 -1
- streamlit_react_components/form/form_slider.py +12 -1
- {streamlit_react_components-0.1.1.dist-info → streamlit_react_components-1.0.1.dist-info}/METADATA +222 -22
- streamlit_react_components-1.0.1.dist-info/RECORD +22 -0
- streamlit_react_components-0.1.1.dist-info/RECORD +0 -21
- {streamlit_react_components-0.1.1.dist-info → streamlit_react_components-1.0.1.dist-info}/WHEEL +0 -0
- {streamlit_react_components-0.1.1.dist-info → streamlit_react_components-1.0.1.dist-info}/top_level.txt +0 -0
|
@@ -8,6 +8,7 @@ from .data_table import data_table
|
|
|
8
8
|
from .step_indicator import step_indicator
|
|
9
9
|
from .button_group import button_group
|
|
10
10
|
from .chart_legend import chart_legend
|
|
11
|
+
from .plotly_chart import plotly_chart
|
|
11
12
|
|
|
12
13
|
__all__ = [
|
|
13
14
|
"panel",
|
|
@@ -18,4 +19,5 @@ __all__ = [
|
|
|
18
19
|
"step_indicator",
|
|
19
20
|
"button_group",
|
|
20
21
|
"chart_legend",
|
|
22
|
+
"plotly_chart",
|
|
21
23
|
]
|
|
@@ -26,8 +26,11 @@ def button_group(
|
|
|
26
26
|
- id: Unique identifier
|
|
27
27
|
- label: Button text (optional)
|
|
28
28
|
- icon: Button icon/emoji (optional)
|
|
29
|
-
- color:
|
|
29
|
+
- color: Preset name ("blue", "green", "red", "yellow", "purple",
|
|
30
|
+
"slate") or hex value like "#94a3b8" (optional)
|
|
30
31
|
- disabled: Whether button is disabled (optional)
|
|
32
|
+
- style: Inline CSS styles dict for this button (optional)
|
|
33
|
+
- className: Tailwind CSS classes for this button (optional)
|
|
31
34
|
style: Inline CSS styles as a dictionary
|
|
32
35
|
class_name: Tailwind CSS classes
|
|
33
36
|
key: Unique key for the component
|
|
@@ -36,6 +39,7 @@ def button_group(
|
|
|
36
39
|
The ID of the clicked button, or None if no click
|
|
37
40
|
|
|
38
41
|
Example:
|
|
42
|
+
# Using preset colors
|
|
39
43
|
clicked = button_group(
|
|
40
44
|
buttons=[
|
|
41
45
|
{"id": "view", "icon": "👁️"},
|
|
@@ -44,6 +48,14 @@ def button_group(
|
|
|
44
48
|
{"id": "reject", "icon": "✕", "color": "red"}
|
|
45
49
|
]
|
|
46
50
|
)
|
|
51
|
+
|
|
52
|
+
# Using hex colors and custom styling
|
|
53
|
+
clicked = button_group(
|
|
54
|
+
buttons=[
|
|
55
|
+
{"id": "custom", "icon": "🎨", "color": "#ff5733"},
|
|
56
|
+
{"id": "styled", "label": "Styled", "style": {"padding": "12px"}}
|
|
57
|
+
]
|
|
58
|
+
)
|
|
47
59
|
if clicked == "approve":
|
|
48
60
|
approve_item()
|
|
49
61
|
"""
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""PlotlyChart component - Render Plotly charts with full interactivity."""
|
|
2
|
+
|
|
3
|
+
import streamlit as st
|
|
4
|
+
import streamlit.components.v1 as components
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Dict, Any, Optional, List, Union
|
|
7
|
+
|
|
8
|
+
_FRONTEND_DIR = Path(__file__).parent.parent / "_frontend"
|
|
9
|
+
|
|
10
|
+
_component = components.declare_component(
|
|
11
|
+
"streamlit_react_components",
|
|
12
|
+
path=str(_FRONTEND_DIR),
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Global key for expanded chart dialog - only ONE dialog can be open at a time
|
|
17
|
+
_EXPANDED_DIALOG_KEY = "_plotly_expanded_chart_dialog"
|
|
18
|
+
_PROCESSED_EXPAND_KEY = "_plotly_processed_expand_ts"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _maybe_render_expanded_dialog() -> None:
|
|
22
|
+
"""Check if an expanded chart dialog should be rendered. Call at start of plotly_chart()."""
|
|
23
|
+
import plotly.graph_objects as go
|
|
24
|
+
|
|
25
|
+
if _EXPANDED_DIALOG_KEY not in st.session_state:
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
dialog_data = st.session_state[_EXPANDED_DIALOG_KEY]
|
|
29
|
+
if not dialog_data.get("open"):
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
# Clear the flag IMMEDIATELY to prevent re-rendering on subsequent plotly_chart calls
|
|
33
|
+
st.session_state[_EXPANDED_DIALOG_KEY] = {"open": False}
|
|
34
|
+
|
|
35
|
+
figure_dict = dialog_data["figure"]
|
|
36
|
+
title = dialog_data.get("title", "")
|
|
37
|
+
|
|
38
|
+
@st.dialog(title or "Chart View", width="large")
|
|
39
|
+
def _expanded_dialog():
|
|
40
|
+
fig = go.Figure(figure_dict)
|
|
41
|
+
st.plotly_chart(fig, width="stretch", key="_expanded_plotly_chart")
|
|
42
|
+
|
|
43
|
+
_expanded_dialog()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _dataframe_to_figure(
|
|
47
|
+
data: Any,
|
|
48
|
+
x: Optional[str],
|
|
49
|
+
y: Optional[Union[str, List[str]]],
|
|
50
|
+
color: Optional[str],
|
|
51
|
+
chart_type: str,
|
|
52
|
+
title: Optional[str],
|
|
53
|
+
) -> Dict[str, Any]:
|
|
54
|
+
"""Convert a DataFrame to a Plotly figure dict."""
|
|
55
|
+
traces = []
|
|
56
|
+
layout: Dict[str, Any] = {}
|
|
57
|
+
|
|
58
|
+
if title:
|
|
59
|
+
layout["title"] = title
|
|
60
|
+
|
|
61
|
+
# Determine y columns
|
|
62
|
+
y_cols = [y] if isinstance(y, str) else (y or [])
|
|
63
|
+
|
|
64
|
+
# Color grouping
|
|
65
|
+
if color and color in data.columns:
|
|
66
|
+
groups = data[color].unique()
|
|
67
|
+
for group in groups:
|
|
68
|
+
group_data = data[data[color] == group]
|
|
69
|
+
for y_col in y_cols:
|
|
70
|
+
trace = _create_trace(
|
|
71
|
+
chart_type,
|
|
72
|
+
group_data[x].tolist() if x else list(range(len(group_data))),
|
|
73
|
+
group_data[y_col].tolist(),
|
|
74
|
+
f"{group}" if len(y_cols) == 1 else f"{group} - {y_col}",
|
|
75
|
+
)
|
|
76
|
+
traces.append(trace)
|
|
77
|
+
else:
|
|
78
|
+
# No color grouping
|
|
79
|
+
x_data = data[x].tolist() if x else list(range(len(data)))
|
|
80
|
+
for y_col in y_cols:
|
|
81
|
+
trace = _create_trace(
|
|
82
|
+
chart_type,
|
|
83
|
+
x_data,
|
|
84
|
+
data[y_col].tolist(),
|
|
85
|
+
y_col if len(y_cols) > 1 else None,
|
|
86
|
+
)
|
|
87
|
+
traces.append(trace)
|
|
88
|
+
|
|
89
|
+
return {"data": traces, "layout": layout}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _create_trace(
|
|
93
|
+
chart_type: str,
|
|
94
|
+
x_data: List[Any],
|
|
95
|
+
y_data: List[Any],
|
|
96
|
+
name: Optional[str],
|
|
97
|
+
) -> Dict[str, Any]:
|
|
98
|
+
"""Create a single Plotly trace based on chart type."""
|
|
99
|
+
base: Dict[str, Any] = {"x": x_data, "y": y_data}
|
|
100
|
+
if name:
|
|
101
|
+
base["name"] = name
|
|
102
|
+
|
|
103
|
+
if chart_type == "line":
|
|
104
|
+
return {"type": "scatter", "mode": "lines", **base}
|
|
105
|
+
elif chart_type == "scatter":
|
|
106
|
+
return {"type": "scatter", "mode": "markers", **base}
|
|
107
|
+
elif chart_type == "bar":
|
|
108
|
+
return {"type": "bar", **base}
|
|
109
|
+
elif chart_type == "area":
|
|
110
|
+
return {"type": "scatter", "mode": "lines", "fill": "tozeroy", **base}
|
|
111
|
+
elif chart_type == "histogram":
|
|
112
|
+
return {"type": "histogram", "x": x_data}
|
|
113
|
+
elif chart_type == "pie":
|
|
114
|
+
return {"type": "pie", "labels": x_data, "values": y_data}
|
|
115
|
+
else:
|
|
116
|
+
# Default to scatter with lines
|
|
117
|
+
return {"type": "scatter", "mode": "lines", **base}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def plotly_chart(
|
|
121
|
+
figure: Optional[Any] = None,
|
|
122
|
+
data: Optional[Any] = None,
|
|
123
|
+
x: Optional[str] = None,
|
|
124
|
+
y: Optional[Union[str, List[str]]] = None,
|
|
125
|
+
color: Optional[str] = None,
|
|
126
|
+
chart_type: str = "line",
|
|
127
|
+
title: Optional[str] = None,
|
|
128
|
+
config: Optional[Dict[str, Any]] = None,
|
|
129
|
+
on_click: bool = False,
|
|
130
|
+
on_select: bool = False,
|
|
131
|
+
on_hover: bool = False,
|
|
132
|
+
on_relayout: bool = False,
|
|
133
|
+
expandable: bool = False,
|
|
134
|
+
modal_title: str = "",
|
|
135
|
+
style: Optional[Dict[str, Any]] = None,
|
|
136
|
+
class_name: str = "",
|
|
137
|
+
key: Optional[str] = None,
|
|
138
|
+
) -> Optional[Dict[str, Any]]:
|
|
139
|
+
"""
|
|
140
|
+
Render a Plotly chart with full interactivity and custom styling.
|
|
141
|
+
|
|
142
|
+
Supports two modes:
|
|
143
|
+
1. Pass a Plotly figure object directly
|
|
144
|
+
2. Pass a pandas DataFrame with chart configuration
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
figure: Plotly figure object (go.Figure) or dict with 'data' and 'layout'.
|
|
148
|
+
Takes precedence over `data` parameter.
|
|
149
|
+
data: pandas DataFrame for quick chart creation
|
|
150
|
+
x: Column name for x-axis (when using DataFrame)
|
|
151
|
+
y: Column name(s) for y-axis - string or list of strings (when using DataFrame)
|
|
152
|
+
color: Column name for color grouping (when using DataFrame)
|
|
153
|
+
chart_type: Chart type when using DataFrame - "line", "bar", "scatter",
|
|
154
|
+
"area", "pie", or "histogram" (default "line")
|
|
155
|
+
title: Chart title (when using DataFrame)
|
|
156
|
+
config: Plotly config options (displayModeBar, scrollZoom, etc.)
|
|
157
|
+
on_click: Enable click events (returns clicked points)
|
|
158
|
+
on_select: Enable selection events (box/lasso selection)
|
|
159
|
+
on_hover: Enable hover events (returns hovered points)
|
|
160
|
+
on_relayout: Enable relayout events (zoom/pan state)
|
|
161
|
+
expandable: Show expand button to open chart in full-page dialog
|
|
162
|
+
modal_title: Title displayed in dialog header when expanded
|
|
163
|
+
style: Inline CSS styles as a dictionary
|
|
164
|
+
class_name: Tailwind CSS classes
|
|
165
|
+
key: Unique key for the component
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Event dict with 'type' and event data, or None if no event.
|
|
169
|
+
Event types: 'click', 'select', 'hover', 'relayout'
|
|
170
|
+
|
|
171
|
+
Example using Plotly figure:
|
|
172
|
+
import plotly.graph_objects as go
|
|
173
|
+
|
|
174
|
+
fig = go.Figure(
|
|
175
|
+
data=[go.Scatter(x=[1,2,3], y=[4,5,6], mode='lines+markers')],
|
|
176
|
+
layout=go.Layout(title='My Chart')
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
event = plotly_chart(
|
|
180
|
+
figure=fig,
|
|
181
|
+
on_click=True,
|
|
182
|
+
on_select=True,
|
|
183
|
+
style={"height": "400px"}
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
if event and event['type'] == 'click':
|
|
187
|
+
st.write(f"Clicked: {event['points']}")
|
|
188
|
+
|
|
189
|
+
Example using DataFrame:
|
|
190
|
+
import pandas as pd
|
|
191
|
+
|
|
192
|
+
df = pd.DataFrame({
|
|
193
|
+
'month': ['Jan', 'Feb', 'Mar', 'Apr'],
|
|
194
|
+
'sales': [100, 150, 120, 180],
|
|
195
|
+
'orders': [50, 75, 60, 90]
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
# Simple line chart
|
|
199
|
+
event = plotly_chart(
|
|
200
|
+
data=df,
|
|
201
|
+
x='month',
|
|
202
|
+
y='sales',
|
|
203
|
+
chart_type='line',
|
|
204
|
+
title='Monthly Sales',
|
|
205
|
+
on_click=True
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Multiple y columns as bar chart
|
|
209
|
+
event = plotly_chart(
|
|
210
|
+
data=df,
|
|
211
|
+
x='month',
|
|
212
|
+
y=['sales', 'orders'],
|
|
213
|
+
chart_type='bar',
|
|
214
|
+
title='Sales vs Orders'
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# Scatter with color grouping
|
|
218
|
+
event = plotly_chart(
|
|
219
|
+
data=df,
|
|
220
|
+
x='sales',
|
|
221
|
+
y='orders',
|
|
222
|
+
color='month',
|
|
223
|
+
chart_type='scatter'
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
Example with expandable modal:
|
|
227
|
+
event = plotly_chart(
|
|
228
|
+
figure=fig,
|
|
229
|
+
expandable=True,
|
|
230
|
+
modal_title="Sales Dashboard",
|
|
231
|
+
style={"height": "300px"}
|
|
232
|
+
)
|
|
233
|
+
# Click the expand button to open chart in full-page dialog
|
|
234
|
+
"""
|
|
235
|
+
# Check if we should render an expanded dialog (from previous expand click)
|
|
236
|
+
# This MUST be called first, before any other plotly_chart processing
|
|
237
|
+
_maybe_render_expanded_dialog()
|
|
238
|
+
|
|
239
|
+
# Determine figure dict
|
|
240
|
+
if figure is not None:
|
|
241
|
+
# Convert Plotly figure to dict if needed
|
|
242
|
+
if hasattr(figure, "to_dict"):
|
|
243
|
+
figure_dict = figure.to_dict()
|
|
244
|
+
else:
|
|
245
|
+
figure_dict = figure
|
|
246
|
+
elif data is not None:
|
|
247
|
+
# Create figure from DataFrame
|
|
248
|
+
figure_dict = _dataframe_to_figure(data, x, y, color, chart_type, title)
|
|
249
|
+
else:
|
|
250
|
+
raise ValueError("Either 'figure' or 'data' parameter is required")
|
|
251
|
+
|
|
252
|
+
# Render the component
|
|
253
|
+
result = _component(
|
|
254
|
+
component="plotly_chart",
|
|
255
|
+
figure=figure_dict,
|
|
256
|
+
config=config,
|
|
257
|
+
onClickEnabled=on_click,
|
|
258
|
+
onSelectEnabled=on_select,
|
|
259
|
+
onHoverEnabled=on_hover,
|
|
260
|
+
onRelayoutEnabled=on_relayout,
|
|
261
|
+
expandable=expandable,
|
|
262
|
+
modalTitle=modal_title,
|
|
263
|
+
style=style,
|
|
264
|
+
className=class_name,
|
|
265
|
+
key=key,
|
|
266
|
+
default=None,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Handle expand event - store in session state and trigger rerun
|
|
270
|
+
if result and isinstance(result, dict) and result.get("type") == "expand":
|
|
271
|
+
event_ts = result.get("timestamp", 0)
|
|
272
|
+
last_processed = st.session_state.get(_PROCESSED_EXPAND_KEY, 0)
|
|
273
|
+
|
|
274
|
+
# Only process if this is a NEW expand click (different timestamp)
|
|
275
|
+
if event_ts > last_processed:
|
|
276
|
+
st.session_state[_PROCESSED_EXPAND_KEY] = event_ts
|
|
277
|
+
st.session_state[_EXPANDED_DIALOG_KEY] = {
|
|
278
|
+
"open": True,
|
|
279
|
+
"figure": result.get("figure", figure_dict),
|
|
280
|
+
"title": result.get("modalTitle", modal_title)
|
|
281
|
+
}
|
|
282
|
+
st.rerun()
|
|
283
|
+
|
|
284
|
+
return result
|
|
@@ -30,7 +30,13 @@ def section_header(
|
|
|
30
30
|
- id: Unique identifier for the action
|
|
31
31
|
- label: Button text (optional)
|
|
32
32
|
- icon: Button icon (optional)
|
|
33
|
-
- color:
|
|
33
|
+
- color: Preset name ("blue", "green", "red", "yellow", "purple",
|
|
34
|
+
"slate") or hex value like "#94a3b8" (optional)
|
|
35
|
+
- style: Inline CSS styles dict for this button (optional)
|
|
36
|
+
- className: Tailwind CSS classes for this button (optional)
|
|
37
|
+
- href: URL for link actions. External URLs (http/https) open
|
|
38
|
+
in a new tab. Internal paths return the ID for use with
|
|
39
|
+
st.switch_page() (optional)
|
|
34
40
|
style: Inline CSS styles as a dictionary
|
|
35
41
|
class_name: Tailwind CSS classes
|
|
36
42
|
key: Unique key for the component
|
|
@@ -39,13 +45,37 @@ def section_header(
|
|
|
39
45
|
The ID of the clicked action button, or None if no click
|
|
40
46
|
|
|
41
47
|
Example:
|
|
48
|
+
# Using preset colors
|
|
42
49
|
clicked = section_header(
|
|
43
50
|
title="Dashboard",
|
|
44
51
|
icon="📊",
|
|
45
52
|
actions=[{"id": "refresh", "label": "Refresh", "color": "blue"}]
|
|
46
53
|
)
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
|
|
55
|
+
# Using hex colors and custom styling
|
|
56
|
+
clicked = section_header(
|
|
57
|
+
title="Dashboard",
|
|
58
|
+
actions=[
|
|
59
|
+
{"id": "custom", "label": "Custom", "color": "#94a3b8"},
|
|
60
|
+
{"id": "styled", "label": "Styled", "style": {"padding": "12px"}}
|
|
61
|
+
]
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# External link (opens in new tab)
|
|
65
|
+
clicked = section_header(
|
|
66
|
+
title="Resources",
|
|
67
|
+
actions=[
|
|
68
|
+
{"id": "docs", "label": "Documentation", "href": "https://docs.example.com", "icon": "📚"}
|
|
69
|
+
]
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Internal navigation
|
|
73
|
+
clicked = section_header(
|
|
74
|
+
title="Settings",
|
|
75
|
+
actions=[{"id": "home", "label": "Home", "icon": "🏠"}]
|
|
76
|
+
)
|
|
77
|
+
if clicked == "home":
|
|
78
|
+
st.switch_page("pages/home.py")
|
|
49
79
|
"""
|
|
50
80
|
return _component(
|
|
51
81
|
component="section_header",
|
|
@@ -27,19 +27,28 @@ def stat_card(
|
|
|
27
27
|
Args:
|
|
28
28
|
label: The description label (e.g., "Total Users")
|
|
29
29
|
value: The statistic value to display
|
|
30
|
-
color: Accent color
|
|
30
|
+
color: Accent color - preset name ("blue", "green", "red", "yellow",
|
|
31
|
+
"purple", "slate") or hex value (e.g., "#94a3b8")
|
|
31
32
|
icon: Optional emoji or icon to display with the label
|
|
32
33
|
style: Inline CSS styles as a dictionary
|
|
33
34
|
class_name: Tailwind CSS classes
|
|
34
35
|
key: Unique key for the component
|
|
35
36
|
|
|
36
37
|
Example:
|
|
38
|
+
# Using preset color
|
|
37
39
|
stat_card(
|
|
38
40
|
label="Within Threshold",
|
|
39
41
|
value="4",
|
|
40
42
|
color="green",
|
|
41
43
|
style={"minWidth": "150px"}
|
|
42
44
|
)
|
|
45
|
+
|
|
46
|
+
# Using hex color
|
|
47
|
+
stat_card(
|
|
48
|
+
label="Custom Color",
|
|
49
|
+
value="42",
|
|
50
|
+
color="#ff5733"
|
|
51
|
+
)
|
|
43
52
|
"""
|
|
44
53
|
_component(
|
|
45
54
|
component="stat_card",
|
|
@@ -34,7 +34,8 @@ def form_slider(
|
|
|
34
34
|
max_val: Maximum value
|
|
35
35
|
step: Step increment (default 1)
|
|
36
36
|
unit: Unit suffix to display (e.g., "%", "hrs")
|
|
37
|
-
color: Accent color
|
|
37
|
+
color: Accent color - preset name ("blue", "green", "red", "yellow",
|
|
38
|
+
"purple", "slate") or hex value (e.g., "#94a3b8")
|
|
38
39
|
style: Inline CSS styles as a dictionary
|
|
39
40
|
class_name: Tailwind CSS classes
|
|
40
41
|
key: Unique key for the component
|
|
@@ -43,6 +44,7 @@ def form_slider(
|
|
|
43
44
|
The current slider value
|
|
44
45
|
|
|
45
46
|
Example:
|
|
47
|
+
# Using preset color
|
|
46
48
|
threshold = form_slider(
|
|
47
49
|
label="Upper Threshold",
|
|
48
50
|
value=90,
|
|
@@ -51,6 +53,15 @@ def form_slider(
|
|
|
51
53
|
unit="%",
|
|
52
54
|
color="red"
|
|
53
55
|
)
|
|
56
|
+
|
|
57
|
+
# Using hex color
|
|
58
|
+
threshold = form_slider(
|
|
59
|
+
label="Custom Slider",
|
|
60
|
+
value=50,
|
|
61
|
+
min_val=0,
|
|
62
|
+
max_val=100,
|
|
63
|
+
color="#ff5733"
|
|
64
|
+
)
|
|
54
65
|
"""
|
|
55
66
|
result = _component(
|
|
56
67
|
component="form_slider",
|