@perspective-dev/jupyterlab 4.0.0
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.
- package/LICENSE.md +193 -0
- package/README.md +21 -0
- package/package.json +66 -0
- package/src/js/index.js +44 -0
- package/src/js/model.js +43 -0
- package/src/js/notebook/css.js +20 -0
- package/src/js/notebook/extension.js +32 -0
- package/src/js/notebook/index.js +27 -0
- package/src/js/plugin.js +38 -0
- package/src/js/psp_widget.js +199 -0
- package/src/js/renderer.js +374 -0
- package/src/js/utils.js +15 -0
- package/src/js/version.js +14 -0
- package/src/js/view.js +420 -0
- package/src/js/widget.js +54 -0
- package/src/less/index.less +53 -0
package/src/js/view.js
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
import { DOMWidgetView } from "@jupyter-widgets/base";
|
|
14
|
+
import { PerspectiveJupyterWidget } from "./widget";
|
|
15
|
+
|
|
16
|
+
import perspective from "@perspective-dev/client";
|
|
17
|
+
|
|
18
|
+
function isEqual(a, b) {
|
|
19
|
+
if (a === b) return true;
|
|
20
|
+
if (typeof a != "object" || typeof b != "object" || a == null || b == null)
|
|
21
|
+
return false;
|
|
22
|
+
|
|
23
|
+
let keysA = Object.keys(a),
|
|
24
|
+
keysB = Object.keys(b);
|
|
25
|
+
|
|
26
|
+
if (keysA.length != keysB.length) return false;
|
|
27
|
+
for (let key of keysA) {
|
|
28
|
+
if (!keysB.includes(key)) return false;
|
|
29
|
+
if (typeof a[key] === "function" || typeof b[key] === "function") {
|
|
30
|
+
if (a[key].toString() != b[key].toString()) return false;
|
|
31
|
+
} else {
|
|
32
|
+
if (!isEqual(a[key], b[key])) return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function get_psp_wasm_module() {
|
|
40
|
+
let elem = customElements.get("perspective-viewer");
|
|
41
|
+
if (!elem) {
|
|
42
|
+
await customElements.whenDefined("perspective-viewer");
|
|
43
|
+
elem = customElements.get("perspective-viewer");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return elem.__wasm_module__;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* `PerspectiveView` defines the plugin's DOM and how the plugin interacts with
|
|
51
|
+
* the DOM.
|
|
52
|
+
*/
|
|
53
|
+
export class PerspectiveView extends DOMWidgetView {
|
|
54
|
+
#psp_client_id = `${Math.random()}`;
|
|
55
|
+
|
|
56
|
+
_createElement() {
|
|
57
|
+
const bindingMode = this.model.get("binding_mode");
|
|
58
|
+
this.luminoWidget = new PerspectiveJupyterWidget(
|
|
59
|
+
undefined,
|
|
60
|
+
this,
|
|
61
|
+
bindingMode,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// set up perspective_client
|
|
65
|
+
get_psp_wasm_module().then(async (wasm_module) => {
|
|
66
|
+
this.send({
|
|
67
|
+
type: "connect",
|
|
68
|
+
client_id: this.psp_client_id,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const { Client } = wasm_module;
|
|
72
|
+
// Responses are fed to the client in the widget's msg:custom handler
|
|
73
|
+
this.perspective_client = new Client(
|
|
74
|
+
async (binary_msg) => {
|
|
75
|
+
const buffer = binary_msg.slice().buffer;
|
|
76
|
+
this.send(
|
|
77
|
+
{ type: "binary_msg", client_id: this.psp_client_id },
|
|
78
|
+
[buffer],
|
|
79
|
+
);
|
|
80
|
+
},
|
|
81
|
+
() => {
|
|
82
|
+
this.send({
|
|
83
|
+
type: "hangup",
|
|
84
|
+
client_id: this.psp_client_id,
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const tableName = this.model.get("table_name");
|
|
90
|
+
if (!tableName) throw new Error("table_name not set in model");
|
|
91
|
+
const table = this.perspective_client
|
|
92
|
+
.open_table(tableName)
|
|
93
|
+
.then(async (table) => {
|
|
94
|
+
if (bindingMode === "client-server") {
|
|
95
|
+
// TODO make this a global lazy singleton
|
|
96
|
+
const client = await perspective.worker();
|
|
97
|
+
const remote_view = await table.view();
|
|
98
|
+
const local_table = await client.table(remote_view);
|
|
99
|
+
return local_table;
|
|
100
|
+
} else if (bindingMode === "server") {
|
|
101
|
+
return table;
|
|
102
|
+
} else {
|
|
103
|
+
throw new Error(`unknown binding mode: ${bindingMode}`);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
this.luminoWidget.load(table);
|
|
108
|
+
this._restore_from_model();
|
|
109
|
+
});
|
|
110
|
+
this._synchronize_state_dbg = (event) => {
|
|
111
|
+
console.log("perspective-config-update event", event);
|
|
112
|
+
this._synchronize_state();
|
|
113
|
+
};
|
|
114
|
+
this._synchronize_state = this._synchronize_state.bind(this);
|
|
115
|
+
|
|
116
|
+
// add event handler to synchronize traitlet values
|
|
117
|
+
this.luminoWidget.viewer.addEventListener(
|
|
118
|
+
"perspective-config-update",
|
|
119
|
+
this._synchronize_state_dbg,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// bind toggle_editable to this
|
|
123
|
+
this._toggle_editable = this._toggle_editable.bind(this);
|
|
124
|
+
|
|
125
|
+
// return the node against witch pWidget is bound
|
|
126
|
+
return this.luminoWidget.node;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
_setElement(el) {
|
|
130
|
+
if (this.el || el !== this.luminoWidget.node) {
|
|
131
|
+
// Do not allow the view to be reassigned to a different element.
|
|
132
|
+
throw new Error("Cannot reset the DOM element.");
|
|
133
|
+
}
|
|
134
|
+
this.el = this.luminoWidget.node;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* When state changes on the viewer DOM, apply it to the widget state.
|
|
139
|
+
*
|
|
140
|
+
* @param mutations
|
|
141
|
+
*/
|
|
142
|
+
|
|
143
|
+
async _synchronize_state(event) {
|
|
144
|
+
if (!this.luminoWidget._load_complete) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const config = await this.luminoWidget.viewer.save();
|
|
149
|
+
|
|
150
|
+
for (const name of Object.keys(config)) {
|
|
151
|
+
let new_value = config[name];
|
|
152
|
+
|
|
153
|
+
const current_value = this.model.get(name);
|
|
154
|
+
if (typeof new_value === "undefined") {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (
|
|
159
|
+
new_value &&
|
|
160
|
+
typeof new_value === "string" &&
|
|
161
|
+
name !== "plugin" &&
|
|
162
|
+
name !== "theme" &&
|
|
163
|
+
name !== "title" &&
|
|
164
|
+
name !== "version"
|
|
165
|
+
) {
|
|
166
|
+
new_value = JSON.parse(new_value);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (new_value === null && name === "plugin_config") {
|
|
170
|
+
new_value = {};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!isEqual(new_value, current_value)) {
|
|
174
|
+
this.model.set(name, new_value);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// propagate changes back to Python
|
|
179
|
+
this.touch();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
get psp_client_id() {
|
|
183
|
+
return this.#psp_client_id;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Attach event handlers to the model for state changes in order to
|
|
188
|
+
* reflect them back to the DOM.
|
|
189
|
+
*/
|
|
190
|
+
|
|
191
|
+
render() {
|
|
192
|
+
super.render();
|
|
193
|
+
this.model.on("msg:custom", this._handle_message, this);
|
|
194
|
+
this.model.on("change:plugin", this.plugin_changed, this);
|
|
195
|
+
this.model.on("change:columns", this.columns_changed, this);
|
|
196
|
+
this.model.on("change:group_by", this.group_by_changed, this);
|
|
197
|
+
this.model.on("change:split_by", this.split_by_changed, this);
|
|
198
|
+
this.model.on("change:aggregates", this.aggregates_changed, this);
|
|
199
|
+
this.model.on("change:sort", this.sort_changed, this);
|
|
200
|
+
this.model.on("change:filter", this.filter_changed, this);
|
|
201
|
+
this.model.on("change:expressions", this.expressions_changed, this);
|
|
202
|
+
this.model.on("change:plugin_config", this.plugin_config_changed, this);
|
|
203
|
+
this.model.on("change:theme", this.theme_changed, this);
|
|
204
|
+
this.model.on("change:settings", this.settings_changed, this);
|
|
205
|
+
this.model.on("change:title", this.title_changed, this);
|
|
206
|
+
this.model.on("change:table_name", this.table_name_changed, this);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Handle messages from the Python PerspectiveViewer instance.
|
|
211
|
+
*/
|
|
212
|
+
_handle_message(msg, buffers) {
|
|
213
|
+
if (msg.type === "binary_msg" && msg.client_id === this.psp_client_id) {
|
|
214
|
+
const [dataview] = buffers;
|
|
215
|
+
this.perspective_client.handle_response(dataview.buffer);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
get client_worker() {
|
|
220
|
+
if (!this._client_worker) {
|
|
221
|
+
this._client_worker = perspective.worker();
|
|
222
|
+
}
|
|
223
|
+
return this._client_worker;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async _restore_from_model() {
|
|
227
|
+
await this.luminoWidget.restore({
|
|
228
|
+
plugin: this.model.get("plugin"),
|
|
229
|
+
columns: this.model.get("columns"),
|
|
230
|
+
group_by: this.model.get("group_by"),
|
|
231
|
+
split_by: this.model.get("split_by"),
|
|
232
|
+
aggregates: this.model.get("aggregates"),
|
|
233
|
+
sort: this.model.get("sort"),
|
|
234
|
+
filter: this.model.get("filter"),
|
|
235
|
+
expressions: this.model.get("expressions"),
|
|
236
|
+
plugin_config: this.model.get("plugin_config"),
|
|
237
|
+
theme: this.model.get("theme"),
|
|
238
|
+
settings: this.model.get("settings"),
|
|
239
|
+
title: this.model.get("title"),
|
|
240
|
+
version: this.model.get("version"),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// XXX(tom): haven't looked at this, needs testing. used in client-server mode
|
|
245
|
+
async _toggle_editable() {
|
|
246
|
+
// Need to await the table and get the instance
|
|
247
|
+
// separately as load() only takes a promise
|
|
248
|
+
// to a table and not the instance itself.
|
|
249
|
+
const table = await this.luminoWidget.getTable();
|
|
250
|
+
|
|
251
|
+
// Setup ports in advance
|
|
252
|
+
if (!this._client_edit_port) {
|
|
253
|
+
this._client_edit_port = await this.luminoWidget.getEditPort();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// if (!this._kernel_edit_port) {
|
|
257
|
+
// this._kernel_edit_port = await this._kernel_table.make_port();
|
|
258
|
+
// }
|
|
259
|
+
|
|
260
|
+
const { plugin_config } = await this.luminoWidget.viewer.save();
|
|
261
|
+
if (plugin_config?.editable) {
|
|
262
|
+
// TODO only evaluated during initial load.
|
|
263
|
+
// Toggling from python after initial load won't
|
|
264
|
+
// cause edits to propagate
|
|
265
|
+
|
|
266
|
+
// Allow edits from the client Perspective to
|
|
267
|
+
// feed back to the kernel.
|
|
268
|
+
|
|
269
|
+
// When the client updates, if the update
|
|
270
|
+
// comes through the edit port then forward
|
|
271
|
+
// it to the server.
|
|
272
|
+
this._client_view_update_callback = (updated) => {
|
|
273
|
+
if (updated.port_id === this._client_edit_port) {
|
|
274
|
+
this._kernel_table.update(updated.delta, {
|
|
275
|
+
port_id: this._kernel_edit_port,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// If the server updates, and the edit is
|
|
281
|
+
// not coming from the server edit port,
|
|
282
|
+
// then synchronize state with the client.
|
|
283
|
+
this._kernel_view_update_callback = (updated) => {
|
|
284
|
+
if (updated.port_id !== this._kernel_edit_port) {
|
|
285
|
+
table.update(updated.delta); // any port, we dont care
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
} else {
|
|
289
|
+
// ignore
|
|
290
|
+
this._client_view_update_callback = () => {};
|
|
291
|
+
|
|
292
|
+
// Load the table and mirror updates from the
|
|
293
|
+
// kernel.
|
|
294
|
+
this._kernel_view_update_callback = (updated) =>
|
|
295
|
+
table.update(updated.delta);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (this._client_view) {
|
|
299
|
+
// NOTE: if `plugin_config_changed` called before
|
|
300
|
+
// `_handle_load_message`, this will be undefined
|
|
301
|
+
// Ignore, as `_handle_load_message` is sure to
|
|
302
|
+
// follow.
|
|
303
|
+
this._client_view.on_update(
|
|
304
|
+
(updated) => this._client_view_update_callback(updated),
|
|
305
|
+
{ mode: "row" },
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// this._kernel_view.on_update(
|
|
310
|
+
// (updated) => this._kernel_view_update_callback(updated),
|
|
311
|
+
// { mode: "row" }
|
|
312
|
+
// );
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* When the View is removed after the widget terminates, clean up the
|
|
317
|
+
* client viewer and Web Worker.
|
|
318
|
+
*/
|
|
319
|
+
|
|
320
|
+
remove() {
|
|
321
|
+
// Delete the <perspective-viewer> but do not terminate the shared
|
|
322
|
+
// worker as it is shared across other widgets.
|
|
323
|
+
this.perspective_client.terminate(); // invokes the close callback we wired up in constructor
|
|
324
|
+
this.luminoWidget.delete();
|
|
325
|
+
this.luminoWidget.viewer.removeEventListener(
|
|
326
|
+
"perspective-config-update",
|
|
327
|
+
this._synchronize_state_dbg,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* When traitlets are updated in python, update the corresponding value on
|
|
333
|
+
* the front-end viewer. `client` and `server` are not included, as they
|
|
334
|
+
* are not properties in `<perspective-viewer>`.
|
|
335
|
+
*/
|
|
336
|
+
|
|
337
|
+
plugin_changed() {
|
|
338
|
+
this.luminoWidget.restore({
|
|
339
|
+
plugin: this.model.get("plugin"),
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
columns_changed() {
|
|
344
|
+
this.luminoWidget.restore({
|
|
345
|
+
columns: this.model.get("columns"),
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
group_by_changed() {
|
|
350
|
+
this.luminoWidget.restore({
|
|
351
|
+
group_by: this.model.get("group_by"),
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
split_by_changed() {
|
|
356
|
+
this.luminoWidget.restore({
|
|
357
|
+
split_by: this.model.get("split_by"),
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
aggregates_changed() {
|
|
362
|
+
this.luminoWidget.restore({
|
|
363
|
+
aggregates: this.model.get("aggregates"),
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
sort_changed() {
|
|
368
|
+
this.luminoWidget.restore({
|
|
369
|
+
sort: this.model.get("sort"),
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
filter_changed() {
|
|
374
|
+
this.luminoWidget.restore({
|
|
375
|
+
filter: this.model.get("filter"),
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
expressions_changed() {
|
|
380
|
+
this.luminoWidget.restore({
|
|
381
|
+
expressions: this.model.get("expressions"),
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
plugin_config_changed() {
|
|
386
|
+
this.luminoWidget.restore({
|
|
387
|
+
plugin_config: this.model.get("plugin_config"),
|
|
388
|
+
});
|
|
389
|
+
this._toggle_editable();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
theme_changed() {
|
|
393
|
+
this.luminoWidget.restore({
|
|
394
|
+
theme: this.model.get("theme"),
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
settings_changed() {
|
|
399
|
+
this.luminoWidget.restore({
|
|
400
|
+
settings: this.model.get("settings"),
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
title_changed() {
|
|
405
|
+
this.luminoWidget.restore({
|
|
406
|
+
title: this.model.get("title"),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
version_changed() {
|
|
411
|
+
this.luminoWidget.restore({
|
|
412
|
+
version: this.model.get("version"),
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
table_name_changed() {
|
|
417
|
+
// nop
|
|
418
|
+
// XXX(tom): we may want to re-load the viewer in this instance
|
|
419
|
+
}
|
|
420
|
+
}
|
package/src/js/widget.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
import { PerspectiveWidget } from "./psp_widget";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* PerspectiveJupyterWidget is the ipywidgets front-end for the Perspective Jupyterlab plugin.
|
|
17
|
+
*/
|
|
18
|
+
export class PerspectiveJupyterWidget extends PerspectiveWidget {
|
|
19
|
+
constructor(name = "Perspective", view, bindingMode) {
|
|
20
|
+
super(name, view.el, bindingMode);
|
|
21
|
+
this._view = view;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Process the lumino message.
|
|
26
|
+
*
|
|
27
|
+
* Any custom lumino widget used inside a Jupyter widget should override
|
|
28
|
+
* the processMessage function like this.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
processMessage(msg) {
|
|
32
|
+
super.processMessage(msg);
|
|
33
|
+
this._view.processLuminoMessage(msg);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Dispose the widget.
|
|
38
|
+
*
|
|
39
|
+
* This causes the view to be destroyed as well with 'remove'
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
dispose() {
|
|
43
|
+
if (this.isDisposed) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
super.dispose();
|
|
48
|
+
if (this._view) {
|
|
49
|
+
this._view.remove();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this._view = null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
@import "@perspective-dev/viewer/dist/css/themes.css";
|
|
14
|
+
|
|
15
|
+
div.PSPContainer {
|
|
16
|
+
overflow: auto;
|
|
17
|
+
padding-right: 5px;
|
|
18
|
+
padding-bottom: 5px;
|
|
19
|
+
height: 100%;
|
|
20
|
+
width: 100%;
|
|
21
|
+
flex: 1;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.jp-Notebook div.PSPContainer {
|
|
25
|
+
resize: vertical;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Widget height for Jupyterlab
|
|
29
|
+
.jp-NotebookPanel-notebook div.PSPContainer {
|
|
30
|
+
height: 520px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Widget height for Jupyter Notebook
|
|
34
|
+
.jupyter-widgets-view div.PSPContainer {
|
|
35
|
+
height: 520px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Widget height for Voila
|
|
39
|
+
// Widget height for VS Code
|
|
40
|
+
body[data-voila="voila"] .jp-OutputArea-output div.PSPContainer,
|
|
41
|
+
body[data-vscode-theme-id] .cell-output-ipywidget-background div.PSPContainer {
|
|
42
|
+
min-height: 520px;
|
|
43
|
+
height: 520px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
div.PSPContainer perspective-viewer[theme="Pro Light"] {
|
|
47
|
+
--plugin--border: 1px solid #e0e0e0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
div.PSPContainer perspective-viewer {
|
|
51
|
+
display: block;
|
|
52
|
+
height: 100%;
|
|
53
|
+
}
|