violit 0.0.5__py3-none-any.whl → 0.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.
- violit/component.py +38 -38
- violit/state.py +201 -0
- violit/theme.py +165 -0
- violit/widgets/chart_widgets.py +253 -253
- violit/widgets/data_widgets.py +27 -9
- violit/widgets/input_widgets.py +745 -745
- violit/widgets/layout_widgets.py +10 -2
- violit/widgets/status_widgets.py +308 -255
- violit/widgets/text_widgets.py +154 -109
- {violit-0.0.5.dist-info → violit-0.0.7.dist-info}/METADATA +1 -1
- violit-0.0.7.dist-info/RECORD +26 -0
- violit-0.0.5.dist-info/RECORD +0 -26
- {violit-0.0.5.dist-info → violit-0.0.7.dist-info}/WHEEL +0 -0
- {violit-0.0.5.dist-info → violit-0.0.7.dist-info}/licenses/LICENSE +0 -0
- {violit-0.0.5.dist-info → violit-0.0.7.dist-info}/top_level.txt +0 -0
violit/widgets/chart_widgets.py
CHANGED
|
@@ -1,253 +1,253 @@
|
|
|
1
|
-
"""Chart Widgets Mixin for Violit"""
|
|
2
|
-
|
|
3
|
-
from typing import Union, Optional, Any, Callable
|
|
4
|
-
import pandas as pd
|
|
5
|
-
import plotly.graph_objects as go
|
|
6
|
-
import plotly.io as pio
|
|
7
|
-
from ..component import Component
|
|
8
|
-
from ..context import rendering_ctx
|
|
9
|
-
from ..state import State
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class ChartWidgetsMixin:
|
|
13
|
-
"""Chart widgets (line, bar, area, scatter, plotly, pyplot, etc.)"""
|
|
14
|
-
|
|
15
|
-
def plotly_chart(self, fig: Union[go.Figure, Callable, State], use_container_width=True, render_mode="svg", **props):
|
|
16
|
-
"""Display Plotly chart with Signal/Lambda support"""
|
|
17
|
-
cid = self._get_next_cid("plot")
|
|
18
|
-
|
|
19
|
-
def builder():
|
|
20
|
-
# Handle Signal/Lambda/Direct Figure
|
|
21
|
-
current_fig = fig
|
|
22
|
-
if isinstance(fig, State):
|
|
23
|
-
token = rendering_ctx.set(cid)
|
|
24
|
-
current_fig = fig.value
|
|
25
|
-
rendering_ctx.reset(token)
|
|
26
|
-
elif callable(fig):
|
|
27
|
-
token = rendering_ctx.set(cid)
|
|
28
|
-
current_fig = fig()
|
|
29
|
-
rendering_ctx.reset(token)
|
|
30
|
-
|
|
31
|
-
if current_fig is None:
|
|
32
|
-
return Component("div", id=f"{cid}_wrapper", content="No data")
|
|
33
|
-
|
|
34
|
-
# Force render_mode if requested (default: svg)
|
|
35
|
-
# Convert scattergl to scatter for SVG rendering
|
|
36
|
-
if render_mode == "svg" and hasattr(current_fig, "data"):
|
|
37
|
-
has_scattergl = any(trace.type == 'scattergl' for trace in current_fig.data)
|
|
38
|
-
if has_scattergl:
|
|
39
|
-
# Create new figure with converted traces
|
|
40
|
-
new_traces = []
|
|
41
|
-
for trace in current_fig.data:
|
|
42
|
-
if trace.type == 'scattergl':
|
|
43
|
-
trace_dict = trace.to_plotly_json()
|
|
44
|
-
trace_dict['type'] = 'scatter'
|
|
45
|
-
new_traces.append(go.Scatter(trace_dict))
|
|
46
|
-
else:
|
|
47
|
-
new_traces.append(trace)
|
|
48
|
-
# Recreate figure with new traces and original layout
|
|
49
|
-
current_fig = go.Figure(data=new_traces, layout=current_fig.layout)
|
|
50
|
-
|
|
51
|
-
fj = pio.to_json(current_fig)
|
|
52
|
-
width_style = "width: 100%;" if use_container_width else ""
|
|
53
|
-
html = f'''
|
|
54
|
-
<div id="{cid}" style="{width_style} height: 500px;"></div>
|
|
55
|
-
<script>(function(){{
|
|
56
|
-
const d = {fj};
|
|
57
|
-
if (window.Plotly) {{
|
|
58
|
-
Plotly.newPlot('{cid}', d.data, d.layout, {{responsive: true}});
|
|
59
|
-
}} else {{
|
|
60
|
-
console.error("Plotly not found");
|
|
61
|
-
}}
|
|
62
|
-
}})();</script>
|
|
63
|
-
'''
|
|
64
|
-
return Component("div", id=f"{cid}_wrapper", content=html)
|
|
65
|
-
|
|
66
|
-
self._register_component(cid, builder)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def pyplot(self, fig=None, use_container_width=True, **props):
|
|
70
|
-
"""Display Matplotlib figure"""
|
|
71
|
-
import matplotlib
|
|
72
|
-
matplotlib.use('Agg')
|
|
73
|
-
import matplotlib.pyplot as plt
|
|
74
|
-
import io
|
|
75
|
-
import base64
|
|
76
|
-
|
|
77
|
-
cid = self._get_next_cid("pyplot")
|
|
78
|
-
|
|
79
|
-
def builder():
|
|
80
|
-
current_fig = fig if fig is not None else plt.gcf()
|
|
81
|
-
|
|
82
|
-
buf = io.BytesIO()
|
|
83
|
-
current_fig.savefig(buf, format='png', bbox_inches='tight', dpi=100)
|
|
84
|
-
buf.seek(0)
|
|
85
|
-
img_base64 = base64.b64encode(buf.read()).decode('utf-8')
|
|
86
|
-
buf.close()
|
|
87
|
-
|
|
88
|
-
width_style = "width: 100%;" if use_container_width else ""
|
|
89
|
-
html = f'<img src="data:image/png;base64,{img_base64}" style="{width_style} height: auto;" />'
|
|
90
|
-
return Component("div", id=cid, content=html, class_="pyplot-container")
|
|
91
|
-
|
|
92
|
-
self._register_component(cid, builder)
|
|
93
|
-
|
|
94
|
-
def line_chart(self, data, x=None, y=None, width=None, height=400, use_container_width=True, render_mode="svg", **props):
|
|
95
|
-
"""Display simple line chart"""
|
|
96
|
-
cid = self._get_next_cid("line_chart")
|
|
97
|
-
|
|
98
|
-
def builder():
|
|
99
|
-
x_data, y_data, trace_name = self._parse_chart_data(data, x, y)
|
|
100
|
-
|
|
101
|
-
fig = go.Figure()
|
|
102
|
-
cls = go.Scattergl if render_mode == "webgl" else go.Scatter
|
|
103
|
-
fig.add_trace(cls(x=x_data, y=y_data, mode='lines+markers', name=trace_name))
|
|
104
|
-
fig.update_layout(
|
|
105
|
-
height=height,
|
|
106
|
-
margin=dict(l=0, r=0, t=30, b=0),
|
|
107
|
-
template='plotly_white'
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
fj = pio.to_json(fig)
|
|
111
|
-
container_width = "width: 100%;" if use_container_width else f"width: {width}px;" if width else "width: 100%;"
|
|
112
|
-
html = f'''
|
|
113
|
-
<div id="{cid}" style="{container_width} height: {height}px;"></div>
|
|
114
|
-
<script>(function(){{
|
|
115
|
-
const d = {fj};
|
|
116
|
-
Plotly.newPlot('{cid}', d.data, d.layout, {{responsive: true}});
|
|
117
|
-
}})();</script>
|
|
118
|
-
'''
|
|
119
|
-
return Component("div", id=f"{cid}_wrapper", content=html)
|
|
120
|
-
|
|
121
|
-
self._register_component(cid, builder)
|
|
122
|
-
|
|
123
|
-
def bar_chart(self, data, x=None, y=None, width=None, height=400, use_container_width=True, render_mode="svg", **props):
|
|
124
|
-
"""Display simple bar chart"""
|
|
125
|
-
cid = self._get_next_cid("bar_chart")
|
|
126
|
-
|
|
127
|
-
def builder():
|
|
128
|
-
x_data, y_data, trace_name = self._parse_chart_data(data, x, y)
|
|
129
|
-
|
|
130
|
-
fig = go.Figure()
|
|
131
|
-
fig.add_trace(go.Bar(x=x_data, y=y_data, name=trace_name))
|
|
132
|
-
fig.update_layout(
|
|
133
|
-
height=height,
|
|
134
|
-
margin=dict(l=0, r=0, t=30, b=0),
|
|
135
|
-
template='plotly_white'
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
fj = pio.to_json(fig)
|
|
139
|
-
container_width = "width: 100%;" if use_container_width else f"width: {width}px;" if width else "width: 100%;"
|
|
140
|
-
html = f'''
|
|
141
|
-
<div id="{cid}" style="{container_width} height: {height}px;"></div>
|
|
142
|
-
<script>(function(){{
|
|
143
|
-
const d = {fj};
|
|
144
|
-
Plotly.newPlot('{cid}', d.data, d.layout, {{responsive: true}});
|
|
145
|
-
}})();</script>
|
|
146
|
-
'''
|
|
147
|
-
return Component("div", id=f"{cid}_wrapper", content=html)
|
|
148
|
-
|
|
149
|
-
self._register_component(cid, builder)
|
|
150
|
-
|
|
151
|
-
def area_chart(self, data, x=None, y=None, width=None, height=400, use_container_width=True, render_mode="svg", **props):
|
|
152
|
-
"""Display area chart"""
|
|
153
|
-
cid = self._get_next_cid("area_chart")
|
|
154
|
-
|
|
155
|
-
def builder():
|
|
156
|
-
x_data, y_data, trace_name = self._parse_chart_data(data, x, y)
|
|
157
|
-
|
|
158
|
-
fig = go.Figure()
|
|
159
|
-
cls = go.Scattergl if render_mode == "webgl" else go.Scatter
|
|
160
|
-
fig.add_trace(cls(x=x_data, y=y_data, fill='tozeroy', name=trace_name))
|
|
161
|
-
fig.update_layout(
|
|
162
|
-
height=height,
|
|
163
|
-
margin=dict(l=0, r=0, t=30, b=0),
|
|
164
|
-
template='plotly_white'
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
fj = pio.to_json(fig)
|
|
168
|
-
container_width = "width: 100%;" if use_container_width else f"width: {width}px;" if width else "width: 100%;"
|
|
169
|
-
html = f'''
|
|
170
|
-
<div id="{cid}" style="{container_width} height: {height}px;"></div>
|
|
171
|
-
<script>(function(){{
|
|
172
|
-
const d = {fj};
|
|
173
|
-
Plotly.newPlot('{cid}', d.data, d.layout, {{responsive: true}});
|
|
174
|
-
}})();</script>
|
|
175
|
-
'''
|
|
176
|
-
return Component("div", id=f"{cid}_wrapper", content=html)
|
|
177
|
-
|
|
178
|
-
self._register_component(cid, builder)
|
|
179
|
-
|
|
180
|
-
def scatter_chart(self, data, x=None, y=None, width=None, height=400, use_container_width=True, render_mode="svg", **props):
|
|
181
|
-
"""Display scatter chart"""
|
|
182
|
-
cid = self._get_next_cid("scatter_chart")
|
|
183
|
-
|
|
184
|
-
def builder():
|
|
185
|
-
x_data, y_data, trace_name = self._parse_chart_data(data, x, y)
|
|
186
|
-
|
|
187
|
-
fig = go.Figure()
|
|
188
|
-
cls = go.Scattergl if render_mode == "webgl" else go.Scatter
|
|
189
|
-
fig.add_trace(cls(x=x_data, y=y_data, mode='markers', name=trace_name))
|
|
190
|
-
fig.update_layout(
|
|
191
|
-
height=height,
|
|
192
|
-
margin=dict(l=0, r=0, t=30, b=0),
|
|
193
|
-
template='plotly_white'
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
fj = pio.to_json(fig)
|
|
197
|
-
container_width = "width: 100%;" if use_container_width else f"width: {width}px;" if width else "width: 100%;"
|
|
198
|
-
html = f'''
|
|
199
|
-
<div id="{cid}" style="{container_width} height: {height}px;"></div>
|
|
200
|
-
<script>(function(){{
|
|
201
|
-
const d = {fj};
|
|
202
|
-
Plotly.newPlot('{cid}', d.data, d.layout, {{responsive: true}});
|
|
203
|
-
}})();</script>
|
|
204
|
-
'''
|
|
205
|
-
return Component("div", id=f"{cid}_wrapper", content=html)
|
|
206
|
-
|
|
207
|
-
self._register_component(cid, builder)
|
|
208
|
-
|
|
209
|
-
def bokeh_chart(self, figure, use_container_width=True, **props):
|
|
210
|
-
"""Display Bokeh chart"""
|
|
211
|
-
from bokeh.embed import components
|
|
212
|
-
|
|
213
|
-
cid = self._get_next_cid("bokeh_chart")
|
|
214
|
-
|
|
215
|
-
def builder():
|
|
216
|
-
script, div = components(figure)
|
|
217
|
-
width_style = "width: 100%;" if use_container_width else ""
|
|
218
|
-
html = f'''
|
|
219
|
-
<div style="{width_style}">
|
|
220
|
-
{div}
|
|
221
|
-
{script}
|
|
222
|
-
</div>
|
|
223
|
-
'''
|
|
224
|
-
return Component("div", id=cid, content=html)
|
|
225
|
-
|
|
226
|
-
self._register_component(cid, builder)
|
|
227
|
-
|
|
228
|
-
def _parse_chart_data(self, data, x, y):
|
|
229
|
-
"""Parse chart data into x, y, and trace name"""
|
|
230
|
-
if isinstance(data, pd.DataFrame):
|
|
231
|
-
if x and y:
|
|
232
|
-
x_data = data[x].tolist()
|
|
233
|
-
y_data = data[y].tolist()
|
|
234
|
-
trace_name = y
|
|
235
|
-
elif y:
|
|
236
|
-
x_data = list(range(len(data)))
|
|
237
|
-
y_data = data[y].tolist()
|
|
238
|
-
trace_name = y
|
|
239
|
-
else:
|
|
240
|
-
cols = data.columns.tolist()
|
|
241
|
-
x_data = data[cols[0]].tolist()
|
|
242
|
-
y_data = data[cols[1]].tolist() if len(cols) > 1 else list(range(len(data)))
|
|
243
|
-
trace_name = cols[1] if len(cols) > 1 else 'Value'
|
|
244
|
-
elif isinstance(data, (list, tuple)):
|
|
245
|
-
x_data = list(range(len(data)))
|
|
246
|
-
y_data = list(data)
|
|
247
|
-
trace_name = 'Value'
|
|
248
|
-
else:
|
|
249
|
-
x_data = []
|
|
250
|
-
y_data = []
|
|
251
|
-
trace_name = 'Value'
|
|
252
|
-
|
|
253
|
-
return x_data, y_data, trace_name
|
|
1
|
+
"""Chart Widgets Mixin for Violit"""
|
|
2
|
+
|
|
3
|
+
from typing import Union, Optional, Any, Callable
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import plotly.graph_objects as go
|
|
6
|
+
import plotly.io as pio
|
|
7
|
+
from ..component import Component
|
|
8
|
+
from ..context import rendering_ctx
|
|
9
|
+
from ..state import State
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ChartWidgetsMixin:
|
|
13
|
+
"""Chart widgets (line, bar, area, scatter, plotly, pyplot, etc.)"""
|
|
14
|
+
|
|
15
|
+
def plotly_chart(self, fig: Union[go.Figure, Callable, State], use_container_width=True, render_mode="svg", **props):
|
|
16
|
+
"""Display Plotly chart with Signal/Lambda support"""
|
|
17
|
+
cid = self._get_next_cid("plot")
|
|
18
|
+
|
|
19
|
+
def builder():
|
|
20
|
+
# Handle Signal/Lambda/Direct Figure
|
|
21
|
+
current_fig = fig
|
|
22
|
+
if isinstance(fig, State):
|
|
23
|
+
token = rendering_ctx.set(cid)
|
|
24
|
+
current_fig = fig.value
|
|
25
|
+
rendering_ctx.reset(token)
|
|
26
|
+
elif callable(fig):
|
|
27
|
+
token = rendering_ctx.set(cid)
|
|
28
|
+
current_fig = fig()
|
|
29
|
+
rendering_ctx.reset(token)
|
|
30
|
+
|
|
31
|
+
if current_fig is None:
|
|
32
|
+
return Component("div", id=f"{cid}_wrapper", content="No data")
|
|
33
|
+
|
|
34
|
+
# Force render_mode if requested (default: svg)
|
|
35
|
+
# Convert scattergl to scatter for SVG rendering
|
|
36
|
+
if render_mode == "svg" and hasattr(current_fig, "data"):
|
|
37
|
+
has_scattergl = any(trace.type == 'scattergl' for trace in current_fig.data)
|
|
38
|
+
if has_scattergl:
|
|
39
|
+
# Create new figure with converted traces
|
|
40
|
+
new_traces = []
|
|
41
|
+
for trace in current_fig.data:
|
|
42
|
+
if trace.type == 'scattergl':
|
|
43
|
+
trace_dict = trace.to_plotly_json()
|
|
44
|
+
trace_dict['type'] = 'scatter'
|
|
45
|
+
new_traces.append(go.Scatter(trace_dict))
|
|
46
|
+
else:
|
|
47
|
+
new_traces.append(trace)
|
|
48
|
+
# Recreate figure with new traces and original layout
|
|
49
|
+
current_fig = go.Figure(data=new_traces, layout=current_fig.layout)
|
|
50
|
+
|
|
51
|
+
fj = pio.to_json(current_fig)
|
|
52
|
+
width_style = "width: 100%;" if use_container_width else ""
|
|
53
|
+
html = f'''
|
|
54
|
+
<div id="{cid}" style="{width_style} height: 500px;"></div>
|
|
55
|
+
<script>(function(){{
|
|
56
|
+
const d = {fj};
|
|
57
|
+
if (window.Plotly) {{
|
|
58
|
+
Plotly.newPlot('{cid}', d.data, d.layout, {{responsive: true}});
|
|
59
|
+
}} else {{
|
|
60
|
+
console.error("Plotly not found");
|
|
61
|
+
}}
|
|
62
|
+
}})();</script>
|
|
63
|
+
'''
|
|
64
|
+
return Component("div", id=f"{cid}_wrapper", content=html)
|
|
65
|
+
|
|
66
|
+
self._register_component(cid, builder)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def pyplot(self, fig=None, use_container_width=True, **props):
|
|
70
|
+
"""Display Matplotlib figure"""
|
|
71
|
+
import matplotlib
|
|
72
|
+
matplotlib.use('Agg')
|
|
73
|
+
import matplotlib.pyplot as plt
|
|
74
|
+
import io
|
|
75
|
+
import base64
|
|
76
|
+
|
|
77
|
+
cid = self._get_next_cid("pyplot")
|
|
78
|
+
|
|
79
|
+
def builder():
|
|
80
|
+
current_fig = fig if fig is not None else plt.gcf()
|
|
81
|
+
|
|
82
|
+
buf = io.BytesIO()
|
|
83
|
+
current_fig.savefig(buf, format='png', bbox_inches='tight', dpi=100)
|
|
84
|
+
buf.seek(0)
|
|
85
|
+
img_base64 = base64.b64encode(buf.read()).decode('utf-8')
|
|
86
|
+
buf.close()
|
|
87
|
+
|
|
88
|
+
width_style = "width: 100%;" if use_container_width else ""
|
|
89
|
+
html = f'<img src="data:image/png;base64,{img_base64}" style="{width_style} height: auto;" />'
|
|
90
|
+
return Component("div", id=cid, content=html, class_="pyplot-container")
|
|
91
|
+
|
|
92
|
+
self._register_component(cid, builder)
|
|
93
|
+
|
|
94
|
+
def line_chart(self, data, x=None, y=None, width=None, height=400, use_container_width=True, render_mode="svg", **props):
|
|
95
|
+
"""Display simple line chart"""
|
|
96
|
+
cid = self._get_next_cid("line_chart")
|
|
97
|
+
|
|
98
|
+
def builder():
|
|
99
|
+
x_data, y_data, trace_name = self._parse_chart_data(data, x, y)
|
|
100
|
+
|
|
101
|
+
fig = go.Figure()
|
|
102
|
+
cls = go.Scattergl if render_mode == "webgl" else go.Scatter
|
|
103
|
+
fig.add_trace(cls(x=x_data, y=y_data, mode='lines+markers', name=trace_name))
|
|
104
|
+
fig.update_layout(
|
|
105
|
+
height=height,
|
|
106
|
+
margin=dict(l=0, r=0, t=30, b=0),
|
|
107
|
+
template='plotly_white'
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
fj = pio.to_json(fig)
|
|
111
|
+
container_width = "width: 100%;" if use_container_width else f"width: {width}px;" if width else "width: 100%;"
|
|
112
|
+
html = f'''
|
|
113
|
+
<div id="{cid}" style="{container_width} height: {height}px;"></div>
|
|
114
|
+
<script>(function(){{
|
|
115
|
+
const d = {fj};
|
|
116
|
+
Plotly.newPlot('{cid}', d.data, d.layout, {{responsive: true}});
|
|
117
|
+
}})();</script>
|
|
118
|
+
'''
|
|
119
|
+
return Component("div", id=f"{cid}_wrapper", content=html)
|
|
120
|
+
|
|
121
|
+
self._register_component(cid, builder)
|
|
122
|
+
|
|
123
|
+
def bar_chart(self, data, x=None, y=None, width=None, height=400, use_container_width=True, render_mode="svg", **props):
|
|
124
|
+
"""Display simple bar chart"""
|
|
125
|
+
cid = self._get_next_cid("bar_chart")
|
|
126
|
+
|
|
127
|
+
def builder():
|
|
128
|
+
x_data, y_data, trace_name = self._parse_chart_data(data, x, y)
|
|
129
|
+
|
|
130
|
+
fig = go.Figure()
|
|
131
|
+
fig.add_trace(go.Bar(x=x_data, y=y_data, name=trace_name))
|
|
132
|
+
fig.update_layout(
|
|
133
|
+
height=height,
|
|
134
|
+
margin=dict(l=0, r=0, t=30, b=0),
|
|
135
|
+
template='plotly_white'
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
fj = pio.to_json(fig)
|
|
139
|
+
container_width = "width: 100%;" if use_container_width else f"width: {width}px;" if width else "width: 100%;"
|
|
140
|
+
html = f'''
|
|
141
|
+
<div id="{cid}" style="{container_width} height: {height}px;"></div>
|
|
142
|
+
<script>(function(){{
|
|
143
|
+
const d = {fj};
|
|
144
|
+
Plotly.newPlot('{cid}', d.data, d.layout, {{responsive: true}});
|
|
145
|
+
}})();</script>
|
|
146
|
+
'''
|
|
147
|
+
return Component("div", id=f"{cid}_wrapper", content=html)
|
|
148
|
+
|
|
149
|
+
self._register_component(cid, builder)
|
|
150
|
+
|
|
151
|
+
def area_chart(self, data, x=None, y=None, width=None, height=400, use_container_width=True, render_mode="svg", **props):
|
|
152
|
+
"""Display area chart"""
|
|
153
|
+
cid = self._get_next_cid("area_chart")
|
|
154
|
+
|
|
155
|
+
def builder():
|
|
156
|
+
x_data, y_data, trace_name = self._parse_chart_data(data, x, y)
|
|
157
|
+
|
|
158
|
+
fig = go.Figure()
|
|
159
|
+
cls = go.Scattergl if render_mode == "webgl" else go.Scatter
|
|
160
|
+
fig.add_trace(cls(x=x_data, y=y_data, fill='tozeroy', name=trace_name))
|
|
161
|
+
fig.update_layout(
|
|
162
|
+
height=height,
|
|
163
|
+
margin=dict(l=0, r=0, t=30, b=0),
|
|
164
|
+
template='plotly_white'
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
fj = pio.to_json(fig)
|
|
168
|
+
container_width = "width: 100%;" if use_container_width else f"width: {width}px;" if width else "width: 100%;"
|
|
169
|
+
html = f'''
|
|
170
|
+
<div id="{cid}" style="{container_width} height: {height}px;"></div>
|
|
171
|
+
<script>(function(){{
|
|
172
|
+
const d = {fj};
|
|
173
|
+
Plotly.newPlot('{cid}', d.data, d.layout, {{responsive: true}});
|
|
174
|
+
}})();</script>
|
|
175
|
+
'''
|
|
176
|
+
return Component("div", id=f"{cid}_wrapper", content=html)
|
|
177
|
+
|
|
178
|
+
self._register_component(cid, builder)
|
|
179
|
+
|
|
180
|
+
def scatter_chart(self, data, x=None, y=None, width=None, height=400, use_container_width=True, render_mode="svg", **props):
|
|
181
|
+
"""Display scatter chart"""
|
|
182
|
+
cid = self._get_next_cid("scatter_chart")
|
|
183
|
+
|
|
184
|
+
def builder():
|
|
185
|
+
x_data, y_data, trace_name = self._parse_chart_data(data, x, y)
|
|
186
|
+
|
|
187
|
+
fig = go.Figure()
|
|
188
|
+
cls = go.Scattergl if render_mode == "webgl" else go.Scatter
|
|
189
|
+
fig.add_trace(cls(x=x_data, y=y_data, mode='markers', name=trace_name))
|
|
190
|
+
fig.update_layout(
|
|
191
|
+
height=height,
|
|
192
|
+
margin=dict(l=0, r=0, t=30, b=0),
|
|
193
|
+
template='plotly_white'
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
fj = pio.to_json(fig)
|
|
197
|
+
container_width = "width: 100%;" if use_container_width else f"width: {width}px;" if width else "width: 100%;"
|
|
198
|
+
html = f'''
|
|
199
|
+
<div id="{cid}" style="{container_width} height: {height}px;"></div>
|
|
200
|
+
<script>(function(){{
|
|
201
|
+
const d = {fj};
|
|
202
|
+
Plotly.newPlot('{cid}', d.data, d.layout, {{responsive: true}});
|
|
203
|
+
}})();</script>
|
|
204
|
+
'''
|
|
205
|
+
return Component("div", id=f"{cid}_wrapper", content=html)
|
|
206
|
+
|
|
207
|
+
self._register_component(cid, builder)
|
|
208
|
+
|
|
209
|
+
def bokeh_chart(self, figure, use_container_width=True, **props):
|
|
210
|
+
"""Display Bokeh chart"""
|
|
211
|
+
from bokeh.embed import components
|
|
212
|
+
|
|
213
|
+
cid = self._get_next_cid("bokeh_chart")
|
|
214
|
+
|
|
215
|
+
def builder():
|
|
216
|
+
script, div = components(figure)
|
|
217
|
+
width_style = "width: 100%;" if use_container_width else ""
|
|
218
|
+
html = f'''
|
|
219
|
+
<div style="{width_style}">
|
|
220
|
+
{div}
|
|
221
|
+
{script}
|
|
222
|
+
</div>
|
|
223
|
+
'''
|
|
224
|
+
return Component("div", id=cid, content=html)
|
|
225
|
+
|
|
226
|
+
self._register_component(cid, builder)
|
|
227
|
+
|
|
228
|
+
def _parse_chart_data(self, data, x, y):
|
|
229
|
+
"""Parse chart data into x, y, and trace name"""
|
|
230
|
+
if isinstance(data, pd.DataFrame):
|
|
231
|
+
if x and y:
|
|
232
|
+
x_data = data[x].tolist()
|
|
233
|
+
y_data = data[y].tolist()
|
|
234
|
+
trace_name = y
|
|
235
|
+
elif y:
|
|
236
|
+
x_data = list(range(len(data)))
|
|
237
|
+
y_data = data[y].tolist()
|
|
238
|
+
trace_name = y
|
|
239
|
+
else:
|
|
240
|
+
cols = data.columns.tolist()
|
|
241
|
+
x_data = data[cols[0]].tolist()
|
|
242
|
+
y_data = data[cols[1]].tolist() if len(cols) > 1 else list(range(len(data)))
|
|
243
|
+
trace_name = cols[1] if len(cols) > 1 else 'Value'
|
|
244
|
+
elif isinstance(data, (list, tuple)):
|
|
245
|
+
x_data = list(range(len(data)))
|
|
246
|
+
y_data = list(data)
|
|
247
|
+
trace_name = 'Value'
|
|
248
|
+
else:
|
|
249
|
+
x_data = []
|
|
250
|
+
y_data = []
|
|
251
|
+
trace_name = 'Value'
|
|
252
|
+
|
|
253
|
+
return x_data, y_data, trace_name
|
violit/widgets/data_widgets.py
CHANGED
|
@@ -263,16 +263,34 @@ class DataWidgetsMixin:
|
|
|
263
263
|
self._register_component(cid, builder)
|
|
264
264
|
|
|
265
265
|
def json(self, body: Any, expanded=True):
|
|
266
|
-
"""Display JSON data"""
|
|
266
|
+
"""Display JSON data with Signal support"""
|
|
267
267
|
cid = self._get_next_cid("json")
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
268
|
+
|
|
269
|
+
def builder():
|
|
270
|
+
from ..state import State, ComputedState
|
|
271
|
+
import json as json_lib
|
|
272
|
+
|
|
273
|
+
# Handle Signal
|
|
274
|
+
current_body = body
|
|
275
|
+
if isinstance(body, (State, ComputedState)):
|
|
276
|
+
token = rendering_ctx.set(cid)
|
|
277
|
+
current_body = body.value
|
|
278
|
+
rendering_ctx.reset(token)
|
|
279
|
+
elif callable(body):
|
|
280
|
+
token = rendering_ctx.set(cid)
|
|
281
|
+
current_body = body()
|
|
282
|
+
rendering_ctx.reset(token)
|
|
283
|
+
|
|
284
|
+
json_str = json_lib.dumps(current_body, indent=2, default=str)
|
|
285
|
+
html = f'''
|
|
286
|
+
<details {"open" if expanded else ""} style="background:var(--sl-bg-card);border:1px solid var(--sl-border);border-radius:0.5rem;padding:0.5rem;">
|
|
287
|
+
<summary style="cursor:pointer;font-size:0.875rem;color:var(--sl-text-muted);">JSON Data</summary>
|
|
288
|
+
<pre style="margin:0.5rem 0 0 0;font-size:0.875rem;color:var(--sl-primary);overflow-x:auto;">{json_str}</pre>
|
|
289
|
+
</details>
|
|
290
|
+
'''
|
|
291
|
+
return Component("div", id=cid, content=html)
|
|
292
|
+
|
|
293
|
+
self._register_component(cid, builder)
|
|
276
294
|
|
|
277
295
|
def heatmap(self, data: Union[dict, State, Callable],
|
|
278
296
|
start_date=None, end_date=None,
|