supervisely 6.73.288__py3-none-any.whl → 6.73.289__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.
- supervisely/app/widgets/bokeh/bokeh.py +213 -244
- {supervisely-6.73.288.dist-info → supervisely-6.73.289.dist-info}/METADATA +1 -1
- {supervisely-6.73.288.dist-info → supervisely-6.73.289.dist-info}/RECORD +7 -7
- {supervisely-6.73.288.dist-info → supervisely-6.73.289.dist-info}/LICENSE +0 -0
- {supervisely-6.73.288.dist-info → supervisely-6.73.289.dist-info}/WHEEL +0 -0
- {supervisely-6.73.288.dist-info → supervisely-6.73.289.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.288.dist-info → supervisely-6.73.289.dist-info}/top_level.txt +0 -0
|
@@ -4,17 +4,13 @@ import asyncio
|
|
|
4
4
|
import re
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
6
|
from datetime import datetime
|
|
7
|
-
from typing import Any, Callable,
|
|
7
|
+
from typing import Any, Callable, List, Literal, Optional
|
|
8
8
|
from uuid import uuid4
|
|
9
9
|
|
|
10
10
|
from fastapi.responses import HTMLResponse
|
|
11
11
|
from pydantic import BaseModel
|
|
12
12
|
|
|
13
|
-
from supervisely._utils import is_production
|
|
14
|
-
from supervisely.api.api import Api
|
|
15
13
|
from supervisely.app.widgets import Widget
|
|
16
|
-
from supervisely.io.env import task_id
|
|
17
|
-
from supervisely.sly_logger import logger
|
|
18
14
|
|
|
19
15
|
|
|
20
16
|
class DebouncedEventHandler:
|
|
@@ -37,7 +33,6 @@ class DebouncedEventHandler:
|
|
|
37
33
|
|
|
38
34
|
class SelectedIds(BaseModel):
|
|
39
35
|
selected_ids: List[int]
|
|
40
|
-
plot_id: Optional[Union[str, int]] = None
|
|
41
36
|
|
|
42
37
|
|
|
43
38
|
class Bokeh(Widget):
|
|
@@ -77,14 +72,25 @@ class Bokeh(Widget):
|
|
|
77
72
|
|
|
78
73
|
from supervisely.app.widgets import Bokeh, IFrame
|
|
79
74
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
colors
|
|
85
|
-
|
|
75
|
+
data = {
|
|
76
|
+
"x": [1, 2, 3, 4, 5],
|
|
77
|
+
"y": [1, 2, 3, 4, 5],
|
|
78
|
+
"radius": [10, 20, 30, 40, 50],
|
|
79
|
+
"colors": ["red", "green", "blue", "yellow", "purple"],
|
|
80
|
+
"ids": [1, 2, 3, 4, 5],
|
|
81
|
+
"names": ["kiwi", "kiwi", "lemon", "lemon", "lemon"],
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
plot_lemon = Bokeh.Circle(name="lemon")
|
|
85
|
+
plot_kiwi = Bokeh.Circle(name="kiwi")
|
|
86
|
+
bokeh = Bokeh(
|
|
87
|
+
x_axis_visible=True,
|
|
88
|
+
y_axis_visible=True,
|
|
89
|
+
grid_visible=True,
|
|
90
|
+
show_legend=True,
|
|
86
91
|
)
|
|
87
|
-
bokeh
|
|
92
|
+
bokeh.add_data(**data)
|
|
93
|
+
bokeh.add_plots([plot_lemon, plot_kiwi])
|
|
88
94
|
|
|
89
95
|
# To allow the widget to be interacted with, you need to add it to the IFrame widget.
|
|
90
96
|
iframe = IFrame()
|
|
@@ -96,126 +102,72 @@ class Bokeh(Widget):
|
|
|
96
102
|
HTML_ROUTE = "bokeh.html"
|
|
97
103
|
|
|
98
104
|
class Plot(ABC):
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
def __init__(self, name: Optional[str] = None, **kwargs):
|
|
106
|
+
|
|
107
|
+
self._name = name or str(uuid4())
|
|
108
|
+
self.kwargs = kwargs
|
|
102
109
|
|
|
103
110
|
@abstractmethod
|
|
104
|
-
def
|
|
111
|
+
def add(self, plot, source) -> None:
|
|
105
112
|
pass
|
|
106
113
|
|
|
114
|
+
@property
|
|
115
|
+
def name(self) -> str:
|
|
116
|
+
return self._name
|
|
117
|
+
|
|
107
118
|
class Circle(Plot):
|
|
108
|
-
def
|
|
109
|
-
self,
|
|
110
|
-
x_coordinates: List[Union[int, float]],
|
|
111
|
-
y_coordinates: List[Union[int, float]],
|
|
112
|
-
radii: Optional[Union[Union[int, float], List[Union[int, float]]]] = None,
|
|
113
|
-
colors: Optional[Union[str, List[str]]] = None,
|
|
114
|
-
data: Optional[List[Any]] = None,
|
|
115
|
-
dynamic_selection: bool = False,
|
|
116
|
-
fill_alpha: float = 0.5,
|
|
117
|
-
line_color: Optional[str] = None,
|
|
118
|
-
legend_label: Optional[str] = None,
|
|
119
|
-
plot_id: Optional[Union[str, int]] = None,
|
|
120
|
-
):
|
|
121
|
-
if not colors:
|
|
122
|
-
colors = Bokeh._generate_colors(x_coordinates, y_coordinates)
|
|
123
|
-
elif isinstance(colors, str):
|
|
124
|
-
colors = [colors] * len(x_coordinates)
|
|
125
|
-
|
|
126
|
-
if not radii:
|
|
127
|
-
radii = Bokeh._generate_radii(x_coordinates, y_coordinates)
|
|
128
|
-
elif isinstance(radii, (int, float)):
|
|
129
|
-
radii = [radii] * len(x_coordinates)
|
|
130
|
-
|
|
131
|
-
if not len(x_coordinates) == len(y_coordinates) == len(radii) == len(colors):
|
|
132
|
-
raise ValueError(
|
|
133
|
-
"x_coordinates, y_coordinates, radii, and colors must have the same length"
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
if data is not None and len(data) != len(x_coordinates):
|
|
137
|
-
raise ValueError("data must have the same length as x_coordinates")
|
|
138
|
-
|
|
139
|
-
if data is None:
|
|
140
|
-
data = list(range(len(x_coordinates)))
|
|
141
|
-
|
|
142
|
-
self._x_coordinates = x_coordinates
|
|
143
|
-
self._y_coordinates = y_coordinates
|
|
144
|
-
self._radii = radii
|
|
145
|
-
self._colors = colors
|
|
146
|
-
self._data = data
|
|
147
|
-
self._source = None
|
|
148
|
-
self._dynamic_selection = dynamic_selection
|
|
149
|
-
self._fill_alpha = fill_alpha
|
|
150
|
-
self._line_color = line_color
|
|
151
|
-
self._plot_id = plot_id or uuid4().hex
|
|
152
|
-
self._legend_label = legend_label or str(self._plot_id)
|
|
153
|
-
|
|
154
|
-
def add(self, plot) -> None:
|
|
119
|
+
def add(self, plot, source) -> None:
|
|
155
120
|
from bokeh.models import ( # pylint: disable=import-error
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
data = dict(
|
|
161
|
-
x=self._x_coordinates,
|
|
162
|
-
y=self._y_coordinates,
|
|
163
|
-
radius=self._radii,
|
|
164
|
-
colors=self._colors,
|
|
165
|
-
ids=self._data,
|
|
121
|
+
CDSView,
|
|
122
|
+
GroupFilter,
|
|
166
123
|
)
|
|
167
|
-
self._source = ColumnDataSource(data=data)
|
|
168
124
|
|
|
169
|
-
|
|
125
|
+
filters = [GroupFilter(column_name="names", group=self.name, name=self.name)]
|
|
126
|
+
view = CDSView(source=source, filters=filters)
|
|
127
|
+
return plot.circle(
|
|
170
128
|
"x",
|
|
171
129
|
"y",
|
|
172
130
|
radius="radius",
|
|
173
131
|
fill_color="colors",
|
|
174
|
-
fill_alpha=
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
132
|
+
fill_alpha=0.5,
|
|
133
|
+
source=source,
|
|
134
|
+
line_color=None,
|
|
135
|
+
view=view,
|
|
178
136
|
)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}}
|
|
205
|
-
var xhr = new XMLHttpRequest();
|
|
206
|
-
xhr.open("POST", "{route_path}", true);
|
|
207
|
-
xhr.setRequestHeader("Content-Type", "application/json");
|
|
208
|
-
xhr.send(JSON.stringify({{selected_ids: selected_ids, plot_id: '{plot_id}'}}));
|
|
209
|
-
""".format(
|
|
210
|
-
route_path=route_path,
|
|
211
|
-
plot_id=self._plot_id,
|
|
212
|
-
),
|
|
137
|
+
|
|
138
|
+
class Scatter(Plot):
|
|
139
|
+
def add(self, plot, source) -> None:
|
|
140
|
+
from bokeh.models import ( # pylint: disable=import-error
|
|
141
|
+
CDSView,
|
|
142
|
+
GroupFilter,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
filters = [GroupFilter(column_name="names", group=self.name, name=self.name)]
|
|
146
|
+
view = CDSView(source=source, filters=filters)
|
|
147
|
+
return plot.scatter(
|
|
148
|
+
"x",
|
|
149
|
+
"y",
|
|
150
|
+
size="radius",
|
|
151
|
+
color="colors",
|
|
152
|
+
fill_alpha=0.5,
|
|
153
|
+
source=source,
|
|
154
|
+
view=view,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
class Line(Plot):
|
|
158
|
+
def add(self, plot, source) -> None:
|
|
159
|
+
from bokeh.models import ( # pylint: disable=import-error
|
|
160
|
+
CDSView,
|
|
161
|
+
GroupFilter,
|
|
213
162
|
)
|
|
214
|
-
|
|
163
|
+
|
|
164
|
+
filters = [GroupFilter(column_name="names", group=self.name, name=self.name)]
|
|
165
|
+
view = CDSView(source=source, filters=filters)
|
|
166
|
+
return plot.line("x", "y", source=source, view=view, line_width=2)
|
|
215
167
|
|
|
216
168
|
def __init__(
|
|
217
169
|
self,
|
|
218
|
-
plots: List[Plot],
|
|
170
|
+
plots: List[Plot] = None,
|
|
219
171
|
width: int = 1000,
|
|
220
172
|
height: int = 600,
|
|
221
173
|
tools: List[str] = [
|
|
@@ -223,7 +175,7 @@ class Bokeh(Widget):
|
|
|
223
175
|
"wheel_zoom",
|
|
224
176
|
"box_zoom",
|
|
225
177
|
"reset",
|
|
226
|
-
"save",
|
|
178
|
+
# "save",
|
|
227
179
|
"poly_select",
|
|
228
180
|
"tap",
|
|
229
181
|
"lasso_select",
|
|
@@ -244,8 +196,11 @@ class Bokeh(Widget):
|
|
|
244
196
|
if bokeh.__version__ != "3.1.1":
|
|
245
197
|
raise RuntimeError(f"Bokeh version {bokeh.__version__} is not supported. Use 3.1.1")
|
|
246
198
|
|
|
199
|
+
self._source_data = {"x": [], "y": [], "radius": [], "colors": [], "ids": [], "names": []}
|
|
200
|
+
self._source = None
|
|
247
201
|
self.widget_id = widget_id
|
|
248
|
-
self._plots = plots
|
|
202
|
+
self._plots = plots or []
|
|
203
|
+
self._view = None
|
|
249
204
|
|
|
250
205
|
self._width = width
|
|
251
206
|
self._height = height
|
|
@@ -259,56 +214,14 @@ class Bokeh(Widget):
|
|
|
259
214
|
self._legend_click_policy = legend_click_policy
|
|
260
215
|
|
|
261
216
|
super().__init__(widget_id=widget_id, file_path=__file__)
|
|
262
|
-
|
|
217
|
+
|
|
218
|
+
self._update()
|
|
263
219
|
|
|
264
220
|
server = self._sly_app.get_server()
|
|
265
221
|
|
|
266
222
|
@server.get(self.html_route)
|
|
267
223
|
def _html_response() -> None:
|
|
268
|
-
return HTMLResponse(content=self.
|
|
269
|
-
|
|
270
|
-
# TODO: support for offline mode
|
|
271
|
-
# JinjaWidgets().context.pop(self.widget_id, None) # remove the widget from index.html
|
|
272
|
-
|
|
273
|
-
def _load_chart(self, **kwargs):
|
|
274
|
-
from bokeh.models import Legend # pylint: disable=import-error
|
|
275
|
-
from bokeh.plotting import figure # pylint: disable=import-error
|
|
276
|
-
|
|
277
|
-
self._width = kwargs.get("width", self._width)
|
|
278
|
-
self._height = kwargs.get("height", self._height)
|
|
279
|
-
self._tools = kwargs.get("tools", self._tools)
|
|
280
|
-
self._toolbar_location = kwargs.get("toolbar_location", self._toolbar_location)
|
|
281
|
-
self._show_legend = kwargs.get("show_legend", self._show_legend)
|
|
282
|
-
self._legend_location = kwargs.get("legend_location", self._legend_location)
|
|
283
|
-
self._legend_click_policy = kwargs.get("legend_click_policy", self._legend_click_policy)
|
|
284
|
-
self._x_axis_visible = kwargs.get("x_axis_visible", self._x_axis_visible)
|
|
285
|
-
self._y_axis_visible = kwargs.get("y_axis_visible", self._y_axis_visible)
|
|
286
|
-
self._grid_visible = kwargs.get("grid_visible", self._grid_visible)
|
|
287
|
-
|
|
288
|
-
self._plot = figure(
|
|
289
|
-
width=self._width,
|
|
290
|
-
height=self._height,
|
|
291
|
-
tools=self._tools,
|
|
292
|
-
toolbar_location=self._toolbar_location,
|
|
293
|
-
)
|
|
294
|
-
|
|
295
|
-
if self._show_legend:
|
|
296
|
-
self._plot.add_layout(
|
|
297
|
-
Legend(click_policy=self._legend_click_policy),
|
|
298
|
-
self._legend_location,
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
self._plot.xaxis.visible = self._x_axis_visible
|
|
302
|
-
self._plot.yaxis.visible = self._y_axis_visible
|
|
303
|
-
self._plot.grid.visible = self._grid_visible
|
|
304
|
-
|
|
305
|
-
self._renderers = []
|
|
306
|
-
self._process_plots(self._plots)
|
|
307
|
-
self._update_html()
|
|
308
|
-
|
|
309
|
-
@property
|
|
310
|
-
def route_path(self) -> str:
|
|
311
|
-
return self.get_route_path(Bokeh.Routes.VALUE_CHANGED)
|
|
224
|
+
return HTMLResponse(content=self._get_html())
|
|
312
225
|
|
|
313
226
|
@property
|
|
314
227
|
def html_route(self) -> str:
|
|
@@ -318,48 +231,75 @@ class Bokeh(Widget):
|
|
|
318
231
|
def html_route_with_timestamp(self) -> str:
|
|
319
232
|
return f".{self.html_route}?t={datetime.now().timestamp()}"
|
|
320
233
|
|
|
321
|
-
def
|
|
322
|
-
|
|
323
|
-
self._process_plots(plots)
|
|
324
|
-
self._update_html()
|
|
234
|
+
def get_json_data(self):
|
|
235
|
+
return {}
|
|
325
236
|
|
|
326
|
-
def
|
|
327
|
-
|
|
328
|
-
self._renderers = []
|
|
329
|
-
self._plot.renderers = []
|
|
330
|
-
self._update_html()
|
|
237
|
+
def get_json_state(self):
|
|
238
|
+
return {}
|
|
331
239
|
|
|
332
|
-
def
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
self.
|
|
240
|
+
def _add_callbacks(self):
|
|
241
|
+
from bokeh.models import CustomJS # pylint: disable=import-error
|
|
242
|
+
|
|
243
|
+
route_path = self.get_route_path(Bokeh.Routes.VALUE_CHANGED)
|
|
244
|
+
callback = CustomJS(
|
|
245
|
+
args=dict(source=self._source),
|
|
246
|
+
code=f"""
|
|
247
|
+
var indices = source.selected.indices;
|
|
248
|
+
var selected_ids = [];
|
|
249
|
+
for (var i = 0; i < indices.length; i++) {{
|
|
250
|
+
selected_ids.push(source.data['ids'][indices[i]]);
|
|
251
|
+
}}
|
|
252
|
+
var xhr = new XMLHttpRequest();
|
|
253
|
+
xhr.open("POST", "{route_path}", true);
|
|
254
|
+
xhr.setRequestHeader("Content-Type", "application/json");
|
|
255
|
+
xhr.send(JSON.stringify({{selected_ids: selected_ids}}));
|
|
256
|
+
""",
|
|
257
|
+
)
|
|
258
|
+
self._source.selected.js_on_change("indices", callback)
|
|
336
259
|
|
|
337
|
-
def
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
self.
|
|
260
|
+
def _get_html(self) -> str:
|
|
261
|
+
return f"""<div>
|
|
262
|
+
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-3.1.1.min.js"></script>
|
|
263
|
+
{self._script}
|
|
264
|
+
{self._div}
|
|
265
|
+
</div>"""
|
|
266
|
+
|
|
267
|
+
def _create_figure(self):
|
|
268
|
+
from bokeh.models import ( # pylint: disable=import-error
|
|
269
|
+
ColumnDataSource,
|
|
270
|
+
Legend,
|
|
271
|
+
)
|
|
272
|
+
from bokeh.plotting import figure # pylint: disable=import-error
|
|
273
|
+
|
|
274
|
+
self._plot = figure(width=self._width, height=self._height, tools=self._tools)
|
|
275
|
+
|
|
276
|
+
self._plot.xaxis.visible = self._x_axis_visible
|
|
277
|
+
self._plot.yaxis.visible = self._y_axis_visible
|
|
278
|
+
self._plot.grid.visible = self._grid_visible
|
|
342
279
|
|
|
343
|
-
|
|
280
|
+
self._source = ColumnDataSource(data=self._source_data)
|
|
281
|
+
self._add_callbacks()
|
|
282
|
+
legend_items = []
|
|
283
|
+
for plot in self._plots:
|
|
284
|
+
r = plot.add(self._plot, self._source)
|
|
285
|
+
legend_items.append((plot.name, [r]))
|
|
286
|
+
|
|
287
|
+
if self._show_legend:
|
|
288
|
+
self._plot.add_layout(
|
|
289
|
+
Legend(items=legend_items, click_policy=self._legend_click_policy),
|
|
290
|
+
self._legend_location,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
def _update_html(self):
|
|
344
294
|
from bokeh.embed import components # pylint: disable=import-error
|
|
345
295
|
|
|
346
|
-
script, self._div = components(self._plot
|
|
296
|
+
script, self._div = components(self._plot)
|
|
347
297
|
self._div_id = self._get_div_id(self._div)
|
|
348
298
|
self._script = self._update_script(script)
|
|
349
299
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
"#%02x%02x%02x" % (int(r), int(g), 150)
|
|
354
|
-
for r, g in zip(
|
|
355
|
-
[50 + 2 * xi for xi in x_coordinates], [30 + 2 * yi for yi in y_coordinates]
|
|
356
|
-
)
|
|
357
|
-
]
|
|
358
|
-
return colors
|
|
359
|
-
|
|
360
|
-
@staticmethod
|
|
361
|
-
def _generate_radii(x_coordinates: List[int], y_coordinates: List[int]) -> List[int]:
|
|
362
|
-
return [1] * len(x_coordinates)
|
|
300
|
+
def _update(self):
|
|
301
|
+
self._create_figure()
|
|
302
|
+
self._update_html()
|
|
363
303
|
|
|
364
304
|
def _get_div_id(self, div: str) -> str:
|
|
365
305
|
match = re.search(r'id="([^"]+)"', div)
|
|
@@ -368,69 +308,98 @@ class Bokeh(Widget):
|
|
|
368
308
|
raise ValueError(f"No div id found in {div}")
|
|
369
309
|
|
|
370
310
|
def _update_script(self, script: str) -> str:
|
|
371
|
-
# TODO: Reimplement using regex.
|
|
372
311
|
insert_after = "const fn = function() {"
|
|
373
312
|
updated_script = ""
|
|
374
313
|
for line in script.split("\n"):
|
|
375
314
|
if line.strip().startswith(insert_after):
|
|
376
|
-
line
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
315
|
+
line += f"""
|
|
316
|
+
const el = document.querySelector('#{self._div_id}');
|
|
317
|
+
if (el === null) {{
|
|
318
|
+
setTimeout(fn, 200);
|
|
319
|
+
return;
|
|
320
|
+
}}
|
|
321
|
+
"""
|
|
381
322
|
updated_script += line + "\n"
|
|
382
323
|
return updated_script
|
|
383
324
|
|
|
384
|
-
def get_json_data(self):
|
|
385
|
-
return {}
|
|
386
|
-
|
|
387
|
-
def get_json_state(self):
|
|
388
|
-
return {}
|
|
389
|
-
|
|
390
325
|
def value_changed(self, func: Callable) -> Callable:
|
|
326
|
+
"""Registers a callback function that will be called when the chart is clicked."""
|
|
327
|
+
|
|
391
328
|
server = self._sly_app.get_server()
|
|
329
|
+
route_path = self.get_route_path(Bokeh.Routes.VALUE_CHANGED)
|
|
392
330
|
self._changes_handled = True
|
|
393
|
-
debounced_handler = DebouncedEventHandler(debounce_time=0.2)
|
|
331
|
+
debounced_handler = DebouncedEventHandler(debounce_time=0.2)
|
|
394
332
|
|
|
395
|
-
@server.post(
|
|
333
|
+
@server.post(route_path)
|
|
396
334
|
async def _click(data: SelectedIds) -> None:
|
|
397
|
-
debounced_handler.handle_event(data, func)
|
|
335
|
+
debounced_handler.handle_event(data.selected_ids, func)
|
|
398
336
|
|
|
399
337
|
return _click
|
|
400
338
|
|
|
401
|
-
def
|
|
402
|
-
|
|
403
|
-
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-3.1.1.min.js"></script>
|
|
404
|
-
<script type="text/javascript"> {self._script} </script>
|
|
405
|
-
{self._div}
|
|
406
|
-
</div>"""
|
|
339
|
+
def clear(self) -> None:
|
|
340
|
+
"""Clears all data in the ColumnDataSource and removes plots."""
|
|
407
341
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
342
|
+
self._source_data = {key: [] for key in self._source_data.keys()}
|
|
343
|
+
self._plots = []
|
|
344
|
+
self._update()
|
|
345
|
+
|
|
346
|
+
def refresh(self) -> None:
|
|
347
|
+
"""Refreshes the chart by reloading the existing data and updating the HTML."""
|
|
348
|
+
|
|
349
|
+
self._update()
|
|
350
|
+
|
|
351
|
+
def update_chart_size(self, width: Optional[int] = None, height: Optional[int] = None):
|
|
352
|
+
"""Updates the size of the chart."""
|
|
353
|
+
|
|
354
|
+
if width:
|
|
355
|
+
self._width = width
|
|
356
|
+
if height:
|
|
357
|
+
self._height = height
|
|
358
|
+
self._update()
|
|
359
|
+
|
|
360
|
+
def add_data(
|
|
361
|
+
self,
|
|
362
|
+
x: List[float],
|
|
363
|
+
y: List[float],
|
|
364
|
+
radius: List[float],
|
|
365
|
+
colors: List[str],
|
|
366
|
+
ids: List[Any],
|
|
367
|
+
names: List[str],
|
|
368
|
+
append: bool = True,
|
|
369
|
+
):
|
|
370
|
+
"""Adds data to the chart."""
|
|
371
|
+
|
|
372
|
+
if append:
|
|
373
|
+
self._source.data["x"] += x
|
|
374
|
+
self._source.data["y"] += y
|
|
375
|
+
self._source.data["radius"] += radius
|
|
376
|
+
self._source.data["colors"] += colors
|
|
377
|
+
self._source.data["ids"] += ids
|
|
378
|
+
self._source.data["names"] += names
|
|
379
|
+
else:
|
|
380
|
+
self._source.data = {
|
|
381
|
+
"x": x,
|
|
382
|
+
"y": y,
|
|
383
|
+
"radius": radius,
|
|
384
|
+
"colors": colors,
|
|
385
|
+
"ids": ids,
|
|
386
|
+
"names": names,
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
def add_plot(self, plot: Plot):
|
|
390
|
+
"""Adds a plot to the chart."""
|
|
391
|
+
|
|
392
|
+
self._plots.append(plot)
|
|
393
|
+
self._update()
|
|
394
|
+
|
|
395
|
+
def add_plots(self, plots: List[Plot]):
|
|
396
|
+
"""Adds multiple plots to the chart."""
|
|
397
|
+
|
|
398
|
+
self._plots.extend(plots)
|
|
399
|
+
self._update()
|
|
428
400
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
self._load_chart()
|
|
401
|
+
def update_point_size(self, size: float):
|
|
402
|
+
"""Updates the size of the points in the chart."""
|
|
432
403
|
|
|
433
|
-
|
|
434
|
-
self.
|
|
435
|
-
self._height = height or self._height
|
|
436
|
-
self._load_chart()
|
|
404
|
+
self._source_data["radius"] = [size] * len(self._source_data["x"])
|
|
405
|
+
self._update()
|
|
@@ -129,7 +129,7 @@ supervisely/app/widgets/binded_input_number/__init__.py,sha256=47DEQpj8HBSa-_TIm
|
|
|
129
129
|
supervisely/app/widgets/binded_input_number/binded_input_number.py,sha256=RXTAGaMXtGOl4pprVLyQosr61t2rI_U_U8VNHhSyBhA,6251
|
|
130
130
|
supervisely/app/widgets/binded_input_number/template.html,sha256=uCEZ54BNC2itr39wxxThXw62WlJ9659cuz8osCm0WZE,162
|
|
131
131
|
supervisely/app/widgets/bokeh/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
132
|
-
supervisely/app/widgets/bokeh/bokeh.py,sha256=
|
|
132
|
+
supervisely/app/widgets/bokeh/bokeh.py,sha256=H53AqKc3sGJU3aoYeIdFfJoeIquyxbIHCDV8CRBjrUs,13262
|
|
133
133
|
supervisely/app/widgets/bokeh/template.html,sha256=ntsh7xx4q9OHG62sa_r3INDxsXgvdPFIWTtYaWn_0t8,12
|
|
134
134
|
supervisely/app/widgets/button/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
135
135
|
supervisely/app/widgets/button/button.py,sha256=zDQinhOOjNNxdP2GIFrwTmVfGAeJJoKV6CT6C8KzQNI,10405
|
|
@@ -1071,9 +1071,9 @@ supervisely/worker_proto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
|
|
|
1071
1071
|
supervisely/worker_proto/worker_api_pb2.py,sha256=VQfi5JRBHs2pFCK1snec3JECgGnua3Xjqw_-b3aFxuM,59142
|
|
1072
1072
|
supervisely/worker_proto/worker_api_pb2_grpc.py,sha256=3BwQXOaP9qpdi0Dt9EKG--Lm8KGN0C5AgmUfRv77_Jk,28940
|
|
1073
1073
|
supervisely_lib/__init__.py,sha256=7-3QnN8Zf0wj8NCr2oJmqoQWMKKPKTECvjH9pd2S5vY,159
|
|
1074
|
-
supervisely-6.73.
|
|
1075
|
-
supervisely-6.73.
|
|
1076
|
-
supervisely-6.73.
|
|
1077
|
-
supervisely-6.73.
|
|
1078
|
-
supervisely-6.73.
|
|
1079
|
-
supervisely-6.73.
|
|
1074
|
+
supervisely-6.73.289.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
1075
|
+
supervisely-6.73.289.dist-info/METADATA,sha256=RMgZIP_bwLgos0vF9tP2PFNzmKVI5h1VFkdK_bX7WKE,33573
|
|
1076
|
+
supervisely-6.73.289.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
|
1077
|
+
supervisely-6.73.289.dist-info/entry_points.txt,sha256=U96-5Hxrp2ApRjnCoUiUhWMqijqh8zLR03sEhWtAcms,102
|
|
1078
|
+
supervisely-6.73.289.dist-info/top_level.txt,sha256=kcFVwb7SXtfqZifrZaSE3owHExX4gcNYe7Q2uoby084,28
|
|
1079
|
+
supervisely-6.73.289.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|