hiplot-plus 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hiplot/__init__.py +38 -0
- hiplot/__main__.py +10 -0
- hiplot/compress.py +70 -0
- hiplot/csvvalues.py +83 -0
- hiplot/experiment.py +637 -0
- hiplot/fetchers.py +322 -0
- hiplot/fetchers_demo.py +459 -0
- hiplot/ipython.py +217 -0
- hiplot/pkginfo.py +15 -0
- hiplot/py.typed +0 -0
- hiplot/render.py +141 -0
- hiplot/server.py +76 -0
- hiplot/static/built/hiplot.bundle.js +1224 -0
- hiplot/static/built/streamlit_component/hiplot_streamlit.bundle.js +1220 -0
- hiplot/static/built/streamlit_component/index.html +16 -0
- hiplot/static/icon.png +0 -0
- hiplot/streamlit_helpers.py +127 -0
- hiplot/templates/index.html +68 -0
- hiplot_plus-0.1.0.dist-info/METADATA +197 -0
- hiplot_plus-0.1.0.dist-info/RECORD +24 -0
- hiplot_plus-0.1.0.dist-info/WHEEL +4 -0
- hiplot_plus-0.1.0.dist-info/entry_points.txt +4 -0
- hiplot_plus-0.1.0.dist-info/licenses/LICENSE +21 -0
- hiplot_plus-0.1.0.dist-info/licenses/NOTICE +207 -0
hiplot/fetchers_demo.py
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
|
2
|
+
# This source code is licensed under the MIT license found in the
|
|
3
|
+
# LICENSE file in the root directory of this source tree.
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
import random
|
|
7
|
+
import time
|
|
8
|
+
import typing as t
|
|
9
|
+
import uuid
|
|
10
|
+
|
|
11
|
+
from . import experiment as hip
|
|
12
|
+
|
|
13
|
+
# Demos from the README. If one of those is modified, please modify the readme as well
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def demo_change_column_properties() -> hip.Experiment:
|
|
17
|
+
data = [
|
|
18
|
+
{"param": 1, "loss": 10, "hidden_field": "value1", "c": "red"},
|
|
19
|
+
{"param": 2, "loss": 5, "hidden_field": "value2", "c": "black"},
|
|
20
|
+
]
|
|
21
|
+
exp = hip.Experiment.from_iterable(data)
|
|
22
|
+
exp.parameters_definition["c"].colors = {"red": "rgb(255, 0, 0)", "black": "rgb(0, 0, 0)"}
|
|
23
|
+
exp.parameters_definition["loss"].type = hip.ValueType.NUMERIC_LOG
|
|
24
|
+
exp.display_data(hip.Displays.PARALLEL_PLOT).update(
|
|
25
|
+
{
|
|
26
|
+
"hide": ["hidden_field"], # This column won't appear in the parallel plot
|
|
27
|
+
"order": ["c"], # Column `c` will be displayed first the in parallel plot
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
return exp
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def demo_basic_usage() -> hip.Experiment:
|
|
34
|
+
data = [
|
|
35
|
+
{"dropout": 0.1, "lr": 0.001, "loss": 10.0, "optimizer": "SGD"},
|
|
36
|
+
{"dropout": 0.15, "lr": 0.01, "loss": 3.5, "optimizer": "Adam"},
|
|
37
|
+
{"dropout": 0.3, "lr": 0.1, "loss": 4.5, "optimizer": "Adam"},
|
|
38
|
+
]
|
|
39
|
+
exp = hip.Experiment.from_iterable(data)
|
|
40
|
+
exp.colorby = "loss"
|
|
41
|
+
return exp
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def demo_line_xy() -> hip.Experiment:
|
|
45
|
+
# DEMO_LINE_XY_BEGIN
|
|
46
|
+
exp = hip.Experiment()
|
|
47
|
+
exp.display_data(hip.Displays.XY).update(
|
|
48
|
+
{
|
|
49
|
+
"axis_x": "generation",
|
|
50
|
+
"axis_y": "loss",
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
history: list[tuple[str, float, float]] = [] # (uid, loss, param) of every datapoint so far
|
|
54
|
+
for i in range(200):
|
|
55
|
+
param = 10 ** random.uniform(-1, 1)
|
|
56
|
+
loss = random.uniform(-5, 5)
|
|
57
|
+
from_uid = None
|
|
58
|
+
if i > 10:
|
|
59
|
+
parent_uid, parent_loss, parent_param = random.choice(history[-10:])
|
|
60
|
+
from_uid = parent_uid # <-- Connect the parent to the child
|
|
61
|
+
loss += parent_loss
|
|
62
|
+
param *= parent_param
|
|
63
|
+
history.append((str(i), loss, param))
|
|
64
|
+
values: dict[str, hip.DisplayableType] = {"generation": i, "param": param, "loss": loss}
|
|
65
|
+
exp.datapoints.append(hip.Datapoint(uid=str(i), from_uid=from_uid, values=values))
|
|
66
|
+
# DEMO_LINE_XY_END
|
|
67
|
+
return exp
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def demo_bug_uid() -> hip.Experiment:
|
|
71
|
+
return hip.Experiment.from_iterable([{"a": 1, "b": 2, "uid": 50.0}, {"a": 2, "b": 3, "uid": 49.33}])
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def demo(n: int = 100) -> hip.Experiment:
|
|
75
|
+
xp = hip.Experiment()
|
|
76
|
+
xp.display_data(hip.Displays.XY).update(
|
|
77
|
+
{
|
|
78
|
+
"axis_x": "time",
|
|
79
|
+
"axis_y": "exp_metric",
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Some fake PBT-ish data
|
|
84
|
+
def fake_params() -> dict[str, hip.DisplayableType]:
|
|
85
|
+
r = random.random()
|
|
86
|
+
p: dict[str, hip.DisplayableType] = {
|
|
87
|
+
"lr": 10 ** random.uniform(-5, 0),
|
|
88
|
+
"seed": random.uniform(0, 10),
|
|
89
|
+
"name": uuid.uuid4().hex[:6],
|
|
90
|
+
"optimizer": random.choice(["sgd", "adam", "adamw"]),
|
|
91
|
+
"r": r,
|
|
92
|
+
"c": random.choice(["red", "green", "black"]),
|
|
93
|
+
}
|
|
94
|
+
if r < 0.1:
|
|
95
|
+
del p["optimizer"]
|
|
96
|
+
if r > 0.3:
|
|
97
|
+
p["optionA"] = random.uniform(1, 5)
|
|
98
|
+
else:
|
|
99
|
+
p["optionB"] = random.uniform(1, 5)
|
|
100
|
+
|
|
101
|
+
if r < 0.2:
|
|
102
|
+
p["pctile"] = -1.0
|
|
103
|
+
elif r < 0.5:
|
|
104
|
+
p["pctile"] = random.uniform(-1.0, 10.0)
|
|
105
|
+
elif r < 0.8:
|
|
106
|
+
p["pctile"] = 10 ** random.uniform(1, 2)
|
|
107
|
+
else:
|
|
108
|
+
p["pctile"] = random.uniform(100, 101)
|
|
109
|
+
|
|
110
|
+
if random.random() > 0.3:
|
|
111
|
+
p["special_values"] = random.uniform(1, 5)
|
|
112
|
+
else:
|
|
113
|
+
p["special_values"] = random.choice([math.inf, -math.inf, math.nan])
|
|
114
|
+
return p
|
|
115
|
+
|
|
116
|
+
def fake_metrics(tm: float) -> dict[str, hip.DisplayableType]:
|
|
117
|
+
return {
|
|
118
|
+
"exp_metric": 10 ** random.uniform(-5, 0),
|
|
119
|
+
"pct_success": random.uniform(10, 90),
|
|
120
|
+
"large_numeric": random.uniform(50000.0, 150000.0),
|
|
121
|
+
"chkpt": uuid.uuid4().hex[:6],
|
|
122
|
+
"time": tm + random.uniform(-0.2, 0.2),
|
|
123
|
+
"force_numericlog": random.uniform(1, 100),
|
|
124
|
+
"timestamp": int(time.time() + (task_idx * 2000)),
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
current_pop: list[dict[str, t.Any]] = [
|
|
128
|
+
{"uid": f"init{i}", "params": fake_params(), "last_ckpt_uid": None} for i in range(10)
|
|
129
|
+
]
|
|
130
|
+
continue_num = 0
|
|
131
|
+
for task_idx in range(n):
|
|
132
|
+
# All drop checkpoints
|
|
133
|
+
for p in current_pop:
|
|
134
|
+
ckpt_uid = f"{p['uid']}_{uuid.uuid4().hex[:6]}"
|
|
135
|
+
xp.datapoints.append(
|
|
136
|
+
hip.Datapoint(
|
|
137
|
+
uid=ckpt_uid, from_uid=p["last_ckpt_uid"], values={**p["params"], **fake_metrics(task_idx)}
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
p["last_ckpt_uid"] = ckpt_uid
|
|
141
|
+
|
|
142
|
+
# Randomly drop some
|
|
143
|
+
current_pop = [p for p in current_pop if random.random() > 0.3]
|
|
144
|
+
|
|
145
|
+
# Respawn as needed
|
|
146
|
+
for _ in range(10 - len(current_pop)):
|
|
147
|
+
continue_num += 1
|
|
148
|
+
parent = random.choice(xp.datapoints[-10:])
|
|
149
|
+
current_pop.append({"uid": f"continue{continue_num}", "params": fake_params(), "last_ckpt_uid": parent.uid})
|
|
150
|
+
xp.parameters_definition["c"].colors = {"red": "rgb(255, 0, 0)", "green": "rgb(0, 255, 0)", "black": "rgb(0, 0, 0)"}
|
|
151
|
+
xp.parameters_definition["force_numericlog"].type = hip.ValueType.NUMERIC_LOG
|
|
152
|
+
xp.parameters_definition["pctile"].type = hip.ValueType.NUMERIC_PERCENTILE
|
|
153
|
+
xp.parameters_definition["timestamp"].type = hip.ValueType.TIMESTAMP
|
|
154
|
+
return xp
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def demo_customize() -> hip.Experiment:
|
|
158
|
+
exp = demo()
|
|
159
|
+
# EXPERIMENT_SETTINGS_SNIPPET2_BEGIN
|
|
160
|
+
# Provide configuration for the parallel plot
|
|
161
|
+
exp.display_data(hip.Displays.PARALLEL_PLOT).update(
|
|
162
|
+
{
|
|
163
|
+
# Hide some columns in the parallel plot
|
|
164
|
+
"hide": ["optionB"],
|
|
165
|
+
# Specify the order for others
|
|
166
|
+
"order": ["time"], # Put column time first on the left
|
|
167
|
+
}
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Provide configuration for the table with all the rows
|
|
171
|
+
exp.display_data(hip.Displays.TABLE).update(
|
|
172
|
+
{
|
|
173
|
+
# Don't display `uid` and `from_uid` columns to the user
|
|
174
|
+
"hide": ["uid", "from_uid"],
|
|
175
|
+
# In the table, order rows by default
|
|
176
|
+
"order_by": [["pct_success", "desc"]],
|
|
177
|
+
# Specify the order for columns
|
|
178
|
+
"order": ["time"], # Put column time first on the left
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Provide configuration for the XY graph
|
|
183
|
+
exp.display_data(hip.Displays.XY).update(
|
|
184
|
+
{
|
|
185
|
+
# Default X axis for the XY plot
|
|
186
|
+
"axis_x": "time",
|
|
187
|
+
# Default Y axis
|
|
188
|
+
"axis_y": "lr",
|
|
189
|
+
# Configure lines
|
|
190
|
+
"lines_thickness": 1.0,
|
|
191
|
+
"lines_opacity": 0.1,
|
|
192
|
+
# Configure dots
|
|
193
|
+
"dots_thickness": 2.0,
|
|
194
|
+
"dots_opacity": 0.3,
|
|
195
|
+
}
|
|
196
|
+
)
|
|
197
|
+
# EXPERIMENT_SETTINGS_SNIPPET2_END
|
|
198
|
+
return exp
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def demo_plotxy_large_numeric() -> hip.Experiment:
|
|
202
|
+
exp = demo()
|
|
203
|
+
exp.display_data(hip.Displays.XY).update(
|
|
204
|
+
{
|
|
205
|
+
"axis_x": "time",
|
|
206
|
+
"axis_y": "large_numeric",
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
return exp
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def demo_force_scale() -> hip.Experiment:
|
|
213
|
+
xp = hip.Experiment()
|
|
214
|
+
for _ in range(100):
|
|
215
|
+
values = [abs(random.gauss(0.0, 1.0)) for _ in range(4)]
|
|
216
|
+
xp.datapoints.append(hip.Datapoint({f"value{i}": v / sum(values) for i, v in enumerate(values)}))
|
|
217
|
+
for i in range(4):
|
|
218
|
+
xp.parameters_definition[f"value{i}"].force_range(0.0, 1.0)
|
|
219
|
+
return xp
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def demo_distribution(**kwargs: t.Any) -> hip.Experiment:
|
|
223
|
+
xp = hip.Experiment.from_iterable(
|
|
224
|
+
[
|
|
225
|
+
{
|
|
226
|
+
"cat": random.choice(["a", "b", "c", "d", "e", "f", "g", "h"]),
|
|
227
|
+
"numeric": random.uniform(0.0, 1.0),
|
|
228
|
+
}
|
|
229
|
+
for i in range(1000)
|
|
230
|
+
]
|
|
231
|
+
)
|
|
232
|
+
xp.display_data(hip.Displays.DISTRIBUTION).update(kwargs)
|
|
233
|
+
return xp
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def demo_distribution_colors_deterministic() -> hip.Experiment:
|
|
237
|
+
data = (
|
|
238
|
+
[{"c": "red", "x": i} for i in range(4)]
|
|
239
|
+
+ [{"c": "black", "x": i} for i in range(4, 10)]
|
|
240
|
+
+ [{"c": "green", "x": i} for i in range(10, 22)]
|
|
241
|
+
)
|
|
242
|
+
xp = hip.Experiment.from_iterable(data)
|
|
243
|
+
xp.parameters_definition["c"].colors = {
|
|
244
|
+
"red": "rgb(255, 0, 0)",
|
|
245
|
+
"green": "rgb(0, 255, 0)",
|
|
246
|
+
"black": "rgb(0, 0, 0)",
|
|
247
|
+
}
|
|
248
|
+
xp.display_data(hip.Displays.DISTRIBUTION).update({"axis": "c"})
|
|
249
|
+
return xp
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def demo_bool() -> hip.Experiment:
|
|
253
|
+
return hip.Experiment.from_iterable([{"bool": True}, {"bool": False}])
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def demo_color_interpolate() -> hip.Experiment:
|
|
257
|
+
exp = demo()
|
|
258
|
+
exp.parameters_definition["exp_metric"].colormap = "interpolateSinebow"
|
|
259
|
+
return exp
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def demo_color_scheme_ylrd() -> hip.Experiment:
|
|
263
|
+
exp = demo()
|
|
264
|
+
exp.parameters_definition["exp_metric"].colormap = "schemeYlOrRd"
|
|
265
|
+
return exp
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def demo_color_scheme_accent() -> hip.Experiment:
|
|
269
|
+
exp = demo()
|
|
270
|
+
exp.parameters_definition["exp_metric"].colormap = "schemeAccent"
|
|
271
|
+
return exp
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def demo_color_interpolate_inverse() -> hip.Experiment:
|
|
275
|
+
exp = demo_color_interpolate()
|
|
276
|
+
assert exp.parameters_definition["exp_metric"].colormap is not None
|
|
277
|
+
exp.parameters_definition["exp_metric"].colormap += "#inverse"
|
|
278
|
+
return exp
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def demo_axis_style() -> hip.Experiment:
|
|
282
|
+
data: list[dict[str, t.Any]] = [
|
|
283
|
+
{
|
|
284
|
+
**{f"param{i}": random.uniform(0, 1) for i in range(6)},
|
|
285
|
+
"loss": random.uniform(0, 100),
|
|
286
|
+
"metric": 10 ** random.uniform(0, 10),
|
|
287
|
+
}
|
|
288
|
+
for _ in range(100)
|
|
289
|
+
]
|
|
290
|
+
xp = hip.Experiment.from_iterable(data)
|
|
291
|
+
for i in range(6):
|
|
292
|
+
xp.parameters_definition[f"param{i}"].label_css = "badge badge-pill badge-secondary"
|
|
293
|
+
xp.parameters_definition["loss"].label_css = "badge badge-pill badge-primary"
|
|
294
|
+
xp.parameters_definition["metric"].label_css = "badge badge-pill badge-info"
|
|
295
|
+
return xp
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def demo_categorical() -> hip.Experiment:
|
|
299
|
+
data: list[dict[str, t.Any]] = [
|
|
300
|
+
{
|
|
301
|
+
"cat_num_05": random.randint(0, 5),
|
|
302
|
+
"cat_num_15": random.randint(0, 10),
|
|
303
|
+
"cat_num_25": random.randint(0, 25),
|
|
304
|
+
"cat_str_05": f"s{random.randint(0, 5)}",
|
|
305
|
+
"cat_str_15": f"s{random.randint(0, 15)}",
|
|
306
|
+
"cat_str_25": f"s{random.randint(0, 25)}",
|
|
307
|
+
}
|
|
308
|
+
for _ in range(100)
|
|
309
|
+
]
|
|
310
|
+
xp = hip.Experiment.from_iterable(data)
|
|
311
|
+
for param in ["cat_num_05", "cat_num_15", "cat_num_25"]:
|
|
312
|
+
xp.parameters_definition[param].type = hip.ValueType.CATEGORICAL
|
|
313
|
+
xp.colorby = "cat_num_25"
|
|
314
|
+
return xp
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def demo_long_names() -> hip.Experiment:
|
|
318
|
+
return hip.Experiment.from_iterable(
|
|
319
|
+
[
|
|
320
|
+
{
|
|
321
|
+
"some very very long name for a field": random.randint(0, 5),
|
|
322
|
+
"this one is also very long": random.randint(0, 10),
|
|
323
|
+
"another.long.one.but.with.dots": random.randint(0, 25),
|
|
324
|
+
}
|
|
325
|
+
for _ in range(100)
|
|
326
|
+
]
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def demo_force_constant_pplot() -> hip.Experiment:
|
|
331
|
+
exp = hip.Experiment.from_iterable([{"uid": 123, "a": 1, "b": 3}, {"uid": 345, "a": 2, "b": 3}])
|
|
332
|
+
exp.parameters_definition["b"].force_range(0, 100)
|
|
333
|
+
return exp
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def demo_first_value_nan() -> hip.Experiment:
|
|
337
|
+
return hip.Experiment.from_iterable(
|
|
338
|
+
[
|
|
339
|
+
{},
|
|
340
|
+
{"a": None},
|
|
341
|
+
{"a": 2},
|
|
342
|
+
{"a": 2.1},
|
|
343
|
+
{"a": 2.2},
|
|
344
|
+
{"a": 5.5},
|
|
345
|
+
{"a": math.nan},
|
|
346
|
+
]
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def demo_weighted_rows() -> hip.Experiment:
|
|
351
|
+
experiment = hip.Experiment.from_iterable(
|
|
352
|
+
[
|
|
353
|
+
{"w": 1.0, "a": 1, "b": 1},
|
|
354
|
+
{"w": 2.0, "a": 2, "b": 1},
|
|
355
|
+
{"w": -2.0, "a": 2, "b": 1},
|
|
356
|
+
{"w": math.inf, "a": 2, "b": 2},
|
|
357
|
+
{"w": "not_a_number", "a": 2, "b": 3},
|
|
358
|
+
{"w": None, "a": 3, "b": 3},
|
|
359
|
+
{"a": 4, "b": 3},
|
|
360
|
+
]
|
|
361
|
+
)
|
|
362
|
+
experiment.weightcolumn = "w"
|
|
363
|
+
return experiment
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def demo_3xcols() -> hip.Experiment:
|
|
367
|
+
xp = demo()
|
|
368
|
+
for i in range(2):
|
|
369
|
+
new_xp = demo()
|
|
370
|
+
for dp, new_dp in zip(xp.datapoints, new_xp.datapoints, strict=True):
|
|
371
|
+
dp.values.update({f"{k}{i}": v for k, v in new_dp.values.items()})
|
|
372
|
+
return xp
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def demo_col_html() -> hip.Experiment:
|
|
376
|
+
COL1 = "<h1>col1</h1>"
|
|
377
|
+
COL2 = "col_2"
|
|
378
|
+
experiment = hip.Experiment.from_iterable(
|
|
379
|
+
[
|
|
380
|
+
{COL1: 1.0, COL2: 1},
|
|
381
|
+
{COL1: 2.0, COL2: 2},
|
|
382
|
+
{COL1: 3.0, COL2: 3},
|
|
383
|
+
]
|
|
384
|
+
)
|
|
385
|
+
experiment.parameters_definition[COL2].label_html = "col<sub>2</sub>"
|
|
386
|
+
return experiment
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def demo_disable_table() -> hip.Experiment:
|
|
390
|
+
experiment = demo()
|
|
391
|
+
experiment.enabledDisplays.remove(hip.Displays.TABLE)
|
|
392
|
+
return experiment
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def demo_big_floats() -> hip.Experiment:
|
|
396
|
+
return hip.Experiment.from_iterable(
|
|
397
|
+
{
|
|
398
|
+
"bigfloat": math.nan if i < 10 else 10 ** random.uniform(15, 32),
|
|
399
|
+
}
|
|
400
|
+
for i in range(100)
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def demo_decay() -> hip.Experiment:
|
|
405
|
+
"""Simulated exponential decay: remaining = initial_mass * exp(-ln2 / half_life * elapsed).
|
|
406
|
+
Produces a wide numeric range in remaining_grams (spans many orders of magnitude),
|
|
407
|
+
useful for testing log-scale tick labels alongside plain numeric columns.
|
|
408
|
+
"""
|
|
409
|
+
rng = random.Random(42)
|
|
410
|
+
data: list[dict[str, t.Any]] = []
|
|
411
|
+
for _ in range(500):
|
|
412
|
+
initial_mass = rng.uniform(50, 500)
|
|
413
|
+
half_life = rng.uniform(1, 30)
|
|
414
|
+
elapsed = rng.uniform(1, 50)
|
|
415
|
+
remaining = initial_mass * math.exp(-math.log(2) / half_life * elapsed)
|
|
416
|
+
data.append(
|
|
417
|
+
{
|
|
418
|
+
"initial_mass": initial_mass,
|
|
419
|
+
"half_life": half_life,
|
|
420
|
+
"elapsed_years": elapsed,
|
|
421
|
+
"remaining_grams": remaining,
|
|
422
|
+
}
|
|
423
|
+
)
|
|
424
|
+
xp = hip.Experiment.from_iterable(data)
|
|
425
|
+
xp.parameters_definition["remaining_grams"].type = hip.ValueType.NUMERIC_LOG
|
|
426
|
+
return xp
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
README_DEMOS: dict[str, t.Callable[[], hip.Experiment]] = {
|
|
430
|
+
"demo": demo,
|
|
431
|
+
"demo_3xcols": demo_3xcols,
|
|
432
|
+
"demo_big": lambda: demo(1000),
|
|
433
|
+
"demo_change_column_properties": demo_change_column_properties,
|
|
434
|
+
"demo_basic_usage": demo_basic_usage,
|
|
435
|
+
"demo_line_xy": demo_line_xy,
|
|
436
|
+
"demo_bug_uid": demo_bug_uid,
|
|
437
|
+
"demo_force_scale": demo_force_scale,
|
|
438
|
+
"demo_distribution_cat": lambda: demo_distribution(axis="cat"),
|
|
439
|
+
"demo_distribution_num": lambda: demo_distribution(axis="numeric"),
|
|
440
|
+
"demo_distribution_num_100bins": lambda: demo_distribution(axis="numeric", nbins=100),
|
|
441
|
+
"demo_distribution_colors_deterministic": demo_distribution_colors_deterministic,
|
|
442
|
+
"demo_bool": demo_bool,
|
|
443
|
+
"demo_color_interpolate": demo_color_interpolate,
|
|
444
|
+
"demo_color_scheme_ylrd": demo_color_scheme_ylrd,
|
|
445
|
+
"demo_color_scheme_accent": demo_color_scheme_accent,
|
|
446
|
+
"demo_axis_style": demo_axis_style,
|
|
447
|
+
"demo_categorical": demo_categorical,
|
|
448
|
+
"demo_customize": demo_customize,
|
|
449
|
+
"demo_plotxy_large_numeric": demo_plotxy_large_numeric,
|
|
450
|
+
"demo_long_names": demo_long_names,
|
|
451
|
+
"demo_force_constant_pplot": demo_force_constant_pplot,
|
|
452
|
+
"demo_color_interpolate_inverse": demo_color_interpolate_inverse,
|
|
453
|
+
"demo_first_value_nan": demo_first_value_nan,
|
|
454
|
+
"demo_weighted_rows": demo_weighted_rows,
|
|
455
|
+
"demo_col_html": demo_col_html,
|
|
456
|
+
"demo_disable_table": demo_disable_table,
|
|
457
|
+
"demo_big_floats": demo_big_floats,
|
|
458
|
+
"demo_decay": demo_decay,
|
|
459
|
+
}
|
hiplot/ipython.py
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
|
2
|
+
# This source code is licensed under the MIT license found in the
|
|
3
|
+
# LICENSE file in the root directory of this source tree.
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import typing as t
|
|
7
|
+
import uuid
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from . import experiment as exp
|
|
11
|
+
from .render import escapejs, make_experiment_standalone_page
|
|
12
|
+
|
|
13
|
+
# Lazy imports for optional dependencies
|
|
14
|
+
if t.TYPE_CHECKING:
|
|
15
|
+
from ipykernel.comm import Comm
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _get_ipython_display() -> t.Any:
|
|
19
|
+
"""Lazy import of IPython.display with helpful error message."""
|
|
20
|
+
try:
|
|
21
|
+
import IPython.display
|
|
22
|
+
|
|
23
|
+
return IPython.display
|
|
24
|
+
except ImportError as e:
|
|
25
|
+
raise ImportError(
|
|
26
|
+
"IPython is required for notebook support. Install it with: pip install hiplot-plus[notebook]"
|
|
27
|
+
) from e
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _require_ipykernel() -> None:
|
|
31
|
+
"""Fail early with a helpful message when ipykernel is missing."""
|
|
32
|
+
try:
|
|
33
|
+
import ipykernel.comm # noqa: F401
|
|
34
|
+
except ImportError as e:
|
|
35
|
+
raise ImportError(
|
|
36
|
+
"ipykernel is required for notebook support. Install it with: pip install hiplot-plus[notebook]"
|
|
37
|
+
) from e
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class GetSelectedFailure(Exception):
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class NotebookJSBundleInjector:
|
|
45
|
+
"""
|
|
46
|
+
TODO: Maybe we should do something smart here. Like inject only once?
|
|
47
|
+
But how to be robust to the user clearing the output of the cell where we injected the bundle?
|
|
48
|
+
If he then refreshes the page, HiPlot bundle is no longer injected...
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def ensure_injected(cls) -> None:
|
|
53
|
+
IPython_display = _get_ipython_display()
|
|
54
|
+
bundle = Path(__file__).parent / "static" / "built" / "hiplot.bundle.js"
|
|
55
|
+
IPython_display.display(
|
|
56
|
+
IPython_display.Javascript(f"""
|
|
57
|
+
{bundle.read_text("utf-8")}
|
|
58
|
+
// Local variables can't be accessed in other cells, so let's
|
|
59
|
+
// manually create a global variable
|
|
60
|
+
Object.assign(window, {{'hiplot': hiplot}});
|
|
61
|
+
""")
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def jupyter_make_full_width(content: str) -> str:
|
|
66
|
+
w_id = f"wrap_html_{uuid.uuid4().hex[:6]}"
|
|
67
|
+
return f"""
|
|
68
|
+
<div id="{w_id}">{content}</div>
|
|
69
|
+
<script type="text/javascript">
|
|
70
|
+
(function() {{
|
|
71
|
+
const elem = document.getElementById({json.dumps(w_id)});
|
|
72
|
+
try {{
|
|
73
|
+
Jupyter.notebook.kernel; // `Jupyter` is not defined on Labs
|
|
74
|
+
}} catch(err) {{
|
|
75
|
+
console.warn('Option "force_full_width" is only supported on Jupyter Notebooks ' +
|
|
76
|
+
'(Labs is not supported as the display area is larger)');
|
|
77
|
+
return;
|
|
78
|
+
}}
|
|
79
|
+
elem.style.width = "100vw";
|
|
80
|
+
const removeElems = elem.parentElement.parentElement.getElementsByClassName("prompt");
|
|
81
|
+
for (var i = 0; i < removeElems.length; ++i) {{
|
|
82
|
+
removeElems[i].remove();
|
|
83
|
+
}}
|
|
84
|
+
elem.parentElement.style.overflowX = "visible";
|
|
85
|
+
|
|
86
|
+
const scale_to_100pct_screen = function() {{
|
|
87
|
+
elem.style.marginLeft = '0px';
|
|
88
|
+
elem.style.marginLeft = (- elem.getBoundingClientRect().x) + 'px';
|
|
89
|
+
}};
|
|
90
|
+
window.addEventListener('resize', function() {{
|
|
91
|
+
scale_to_100pct_screen();
|
|
92
|
+
}});
|
|
93
|
+
scale_to_100pct_screen();
|
|
94
|
+
}})();
|
|
95
|
+
</script>
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class IPythonExperimentDisplayed(exp.ExperimentDisplayed):
|
|
100
|
+
def __init__(self, xp: exp.Experiment, comm_name: str) -> None:
|
|
101
|
+
self._exp = xp
|
|
102
|
+
self._num_recv = 0
|
|
103
|
+
self._selected_ids: list[str] = []
|
|
104
|
+
self._last_data_per_type: dict[str, t.Any] = {}
|
|
105
|
+
|
|
106
|
+
_require_ipykernel()
|
|
107
|
+
|
|
108
|
+
def _recv(msg: dict[str, t.Any]) -> None:
|
|
109
|
+
self._num_recv += 1
|
|
110
|
+
msg_data = msg["content"]["data"]
|
|
111
|
+
self._last_data_per_type[msg_data["type"]] = msg_data["data"]
|
|
112
|
+
|
|
113
|
+
def target_func(comm: "Comm", _open_msg: dict[str, t.Any]) -> None:
|
|
114
|
+
"""Registers the handler for later messages on the comm the front-end opened."""
|
|
115
|
+
comm.on_msg(_recv)
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
ip: t.Any = get_ipython() # type: ignore[name-defined]
|
|
119
|
+
except NameError:
|
|
120
|
+
# Not in an ipython environment, for example in tests: there is no front-end to talk to.
|
|
121
|
+
return
|
|
122
|
+
ip.kernel.comm_manager.register_target(comm_name, target_func)
|
|
123
|
+
|
|
124
|
+
no_data_received_error = GetSelectedFailure(
|
|
125
|
+
"""No data received from the front-end. Please make sure that:
|
|
126
|
+
1. You don't call "get_selected" on the same cell
|
|
127
|
+
2. The interface has loaded
|
|
128
|
+
3. You are in a Jupyter notebook (Jupyter lab is *not* supported)"""
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def get_selected(self) -> list[exp.Datapoint]:
|
|
132
|
+
last_selected_uids = self._last_data_per_type.get("selected_uids")
|
|
133
|
+
if last_selected_uids is None:
|
|
134
|
+
raise self.no_data_received_error
|
|
135
|
+
selected_set = set(last_selected_uids)
|
|
136
|
+
datapoints = [i for i in self._exp.datapoints if i.uid in selected_set]
|
|
137
|
+
assert len(datapoints) == len(selected_set)
|
|
138
|
+
return datapoints
|
|
139
|
+
|
|
140
|
+
def get_brush_extents(self) -> dict[str, dict[str, t.Any]]:
|
|
141
|
+
last_msg = self._last_data_per_type.get("brush_extents")
|
|
142
|
+
if last_msg is None:
|
|
143
|
+
raise self.no_data_received_error
|
|
144
|
+
brush_extents: dict[str, dict[str, t.Any]] = last_msg
|
|
145
|
+
return brush_extents
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _should_embed_js_with_html() -> bool:
|
|
149
|
+
from IPython.core.getipython import get_ipython
|
|
150
|
+
|
|
151
|
+
ip = get_ipython()
|
|
152
|
+
if ip is None:
|
|
153
|
+
return True
|
|
154
|
+
return "BentoApp" in ip.config
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def display_exp(
|
|
158
|
+
xp: exp.Experiment,
|
|
159
|
+
force_full_width: bool = False,
|
|
160
|
+
store_state_url: str | None = None,
|
|
161
|
+
embed_js_with_html: bool | None = None,
|
|
162
|
+
**kwargs: t.Any,
|
|
163
|
+
) -> IPythonExperimentDisplayed:
|
|
164
|
+
IPython_display = _get_ipython_display()
|
|
165
|
+
|
|
166
|
+
if embed_js_with_html is None:
|
|
167
|
+
embed_js_with_html = _should_embed_js_with_html()
|
|
168
|
+
|
|
169
|
+
comm_id = f"comm_{uuid.uuid4().hex[:6]}"
|
|
170
|
+
displayed_xp = IPythonExperimentDisplayed(xp, comm_id)
|
|
171
|
+
options: dict[str, t.Any] = {**kwargs, "experiment": xp._payload()}
|
|
172
|
+
if store_state_url is not None:
|
|
173
|
+
options.update({"persistentStateUrlPrefix": store_state_url})
|
|
174
|
+
else:
|
|
175
|
+
options.update({"persistentState": None})
|
|
176
|
+
index_html = make_experiment_standalone_page(options=options)
|
|
177
|
+
# Remove line that references the script bundle - prevents an HTTP error in the notebook
|
|
178
|
+
index_html = index_html.replace('src="static/built/hiplot.bundle.js"', "")
|
|
179
|
+
index_html = index_html.replace(
|
|
180
|
+
"/*ON_LOAD_SCRIPT_INJECT*/",
|
|
181
|
+
f"""/*ON_LOAD_SCRIPT_INJECT*/
|
|
182
|
+
const comm_id = {escapejs(comm_id)};
|
|
183
|
+
try {{
|
|
184
|
+
console.log("Setting up communication channel with Jupyter: ", comm_id);
|
|
185
|
+
const comm = Jupyter.notebook.kernel.comm_manager.new_comm(comm_id, {{'type': 'hello'}});
|
|
186
|
+
var comm_message_id = 0;
|
|
187
|
+
function send_data_change(type, data) {{
|
|
188
|
+
comm.send({{
|
|
189
|
+
'type': type,
|
|
190
|
+
'message_id': comm_message_id,
|
|
191
|
+
'data': data,
|
|
192
|
+
}});
|
|
193
|
+
comm_message_id += 1;
|
|
194
|
+
}};
|
|
195
|
+
Object.assign(options, {{"onChange": {{
|
|
196
|
+
"selected_uids": send_data_change,
|
|
197
|
+
"brush_extents": send_data_change,
|
|
198
|
+
}}}});
|
|
199
|
+
}}
|
|
200
|
+
catch(err) {{
|
|
201
|
+
console.warn('Unable to create Javascript <-> Python communication channel' +
|
|
202
|
+
' (are you in a Jupyter notebook? Jupyter labs is *not* supported!)');
|
|
203
|
+
}}
|
|
204
|
+
""",
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
if force_full_width:
|
|
208
|
+
index_html = jupyter_make_full_width(index_html)
|
|
209
|
+
|
|
210
|
+
if embed_js_with_html:
|
|
211
|
+
bundle = Path(__file__).parent / "static" / "built" / "hiplot.bundle.js"
|
|
212
|
+
index_html = index_html.replace("/*BUNDLE_FILE*/", bundle.read_text("utf-8"))
|
|
213
|
+
else:
|
|
214
|
+
NotebookJSBundleInjector.ensure_injected()
|
|
215
|
+
|
|
216
|
+
IPython_display.display(IPython_display.HTML(index_html))
|
|
217
|
+
return displayed_xp
|
hiplot/pkginfo.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
|
2
|
+
# This source code is licensed under the MIT license found in the
|
|
3
|
+
# LICENSE file in the root directory of this source tree.
|
|
4
|
+
|
|
5
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
6
|
+
|
|
7
|
+
package_name = "hiplot-plus"
|
|
8
|
+
|
|
9
|
+
# Dynamic version from installed package metadata
|
|
10
|
+
# This ensures __version__ matches pyproject.toml when installed via pip
|
|
11
|
+
try:
|
|
12
|
+
__version__ = version(package_name)
|
|
13
|
+
except PackageNotFoundError:
|
|
14
|
+
# Package is not installed (e.g., running from source checkout)
|
|
15
|
+
__version__ = "0.0.0.dev0"
|
hiplot/py.typed
ADDED
|
File without changes
|