Skip to content

Commit

Permalink
Merge 332bec6 into ba63a09
Browse files Browse the repository at this point in the history
  • Loading branch information
ibgreen committed May 20, 2020
2 parents ba63a09 + 332bec6 commit 8d78371
Show file tree
Hide file tree
Showing 21 changed files with 546 additions and 249 deletions.
4 changes: 2 additions & 2 deletions bindings/pydeck/pydeck/widget/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ class DeckGLWidget(widgets.DOMWidget):
Whether the string message from deck.gl should be rendered, defaults to False
"""

_model_name = Unicode("DeckGLModel").tag(sync=True)
_model_name = Unicode("JupyterTransportModel").tag(sync=True)
_model_module = Unicode(module_name).tag(sync=True)
_model_module_version = Unicode(module_version).tag(sync=True)
_view_name = Unicode("DeckGLView").tag(sync=True)
_view_name = Unicode("JupyterTransportView").tag(sync=True)
_view_module = Unicode(module_name).tag(sync=True)
_view_module_version = Unicode(module_version).tag(sync=True)
mapbox_key = Unicode("", allow_none=True).tag(sync=True)
Expand Down
3 changes: 3 additions & 0 deletions modules/json/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
export {default as JSONConverter} from './json-converter';
export {default as JSONConfiguration} from './json-configuration';

// Transports
export {default as Transport} from './transports/transport';

// Helpers
export {default as _convertFunctions} from './helpers/convert-functions';
export {default as _parseExpressionString} from './helpers/parse-expression-string';
Expand Down
105 changes: 105 additions & 0 deletions modules/json/src/transports/transport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
const registeredTransports = [];

export default class Transport {
static registerTransport(transport) {
registeredTransports.push(transport);
}

static getDefaultTransport() {
if (registeredTransports.length === 0) {
throw new Error('No JSON transport registered');
}
return registeredTransports[0];
}

static getTransport(name, fallback = true) {
let transport = registeredTransports.find(transport_ => transport_.name.startsWith(name));
if (!transport && fallback) {
transport = Transport.getDefaultTransport();
}
return transport;
}

constructor(name = 'Transport') {
this.name = name;

// Set up an initialization promise, so that we can defer the call of onInitialize
this._initPromise = new Promise((resolve, reject) => {
this._initResolvers = {resolve, reject};
});
this._messageQueue = [];

this.userData = {};

this.onIninitialize = _ => _;
this.onFinalize = _ => _;
this.onMessage = null;
}

setCallbacks({onInitialize, onFinalize, onMessage}) {
if (onInitialize) {
this.onInitialize = onInitialize;
}
if (onFinalize) {
this._onFinalize = onFinalize;
}
if (onMessage) {
this._onMessage = onMessage;
}

if (onInitialize) {
this._initPromise.then(initArgs => {
onInitialize(initArgs);

if (this._onMessage) {
// Send any queued messages
let message;
while ((message = this._messageQueue.pop())) {
console.debug('Delivering queued transport message', message); // eslint-disable-line
this._onMessage(message);
}
}
});
}
}

// Back-channel messaging
sendJSONMessage() {
// eslint-disable-next-line
console.error('Back-channel not implemented for this transport');
}

sendBinaryMessage() {
// eslint-disable-next-line
console.error('Back-channel not implemented for this transport');
}

//
// API for transports (not intended for apps)
//

_initialize(options = {}) {
console.debug('Resolving init promise', options); // eslint-disable-line
this._initResolvers.resolve({transport: this, ...options});
}

_finalize(options = {}) {
// TODO - could potentially be called without Initialize being called
this.onFinalize({transport: this, ...options});
this._destroyed = true;
}

_messageReceived(message = {}) {
message = {transport: this, ...message};

// TODO - could potentially be called without Initialize being called
if (!this.onMessage) {
console.debug('Queueing transport message', message); // eslint-disable-line
this._messageQueue.push(message);
return;
}

console.debug('Delivering transport message', message); // eslint-disable-line
this.onMessage(message);
}
}
19 changes: 19 additions & 0 deletions modules/json/src/transports/transport.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Transport

The `Transport` base class is intended to abstract a number of client-host communication mechanisms, including:

- Jupyter Widget based communication between a browser and a notebook
- Browser: `postMessage` based communication between tabs and iframes
- Android: Communication between Java application and JavaScript running in a `WebView` component
- iOS: Communication between Swift and JavaScript running in a `UIWebView` component

Longer term goals:

- `WebSocket` based transport
- Possibly add additional transports such as Colab notebook specific APIs.

## Features

- Binary data
- Back-channel: Events
- Back-channel: Errors
41 changes: 26 additions & 15 deletions modules/jupyter-widget/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,44 @@
// Entry point for the Jupyter Notebook bundle containing custom Backbone model and view definitions.

// Some static assets may be required by the custom widget javascript. The base
// url for the notebook is not known at build time and is therefore computed
// dynamically.
// url for the notebook is not known at build time and is therefore computed dynamically.
const dataBaseUrl = document.body && document.body.getAttribute('data-base-url');
if (dataBaseUrl) {
window.__webpack_public_path__ = `${dataBaseUrl}nbextensions/pydeck/nb_extension`;
}

let DeckGLModel;
let DeckGLView;
const {createDeck, updateDeck, loadExternalClasses} = require('./create-deck');
const {MODULE_VERSION, MODULE_NAME} = require('./version');
// Initialize the transport
const {jupyterTransport} = require('./lib/jupyter-transport').default;

let TransportModel = null;
let TransportView = null;
try {
const widgetClasses = require('./widget');
DeckGLView = widgetClasses.DeckGLView;
DeckGLModel = widgetClasses.DeckGLModel;
TransportModel = require('./lib/jupyter-transport-model').default;
TransportView = require('./lib/jupyter-transport-view').default;
} catch (err) {
DeckGLModel = null;
DeckGLView = null;
// Note: Happens in the to_html() case
}

const {MODULE_VERSION, MODULE_NAME} = require('./version');

// TODO - this should be placed in a separate module `@deck.gl/playground`
const {createDeck, updateDeck} = require('./playground/create-deck');
const {initPlayground} = require('./playground');
initPlayground();

module.exports = {
DeckGLView,
DeckGLModel,
// Transports
jupyterTransport,

// Jupyter Hooks
MODULE_VERSION,
MODULE_NAME,
TransportModel,
TransportView,

// For to_html()...
initPlayground,
// TODO - use playground?
createDeck,
updateDeck,
loadExternalClasses
updateDeck
};
56 changes: 56 additions & 0 deletions modules/jupyter-widget/src/lib/jupyter-transport-model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {DOMWidgetModel} from '@jupyter-widgets/base';
import {MODULE_NAME, MODULE_VERSION} from '../version';
import {deserializeMatrix} from './utils/deserialize-matrix';
/**
*
* Note: Variables shared explictly between Python and JavaScript use snake_case
*/
export default class JupyterTransportModel extends DOMWidgetModel {
defaults() {
return {
...super.defaults(),
_model_name: JupyterTransportModel.model_name,
_model_module: JupyterTransportModel.model_module,
_model_module_version: JupyterTransportModel.model_module_version,
_view_name: JupyterTransportModel.view_name,
_view_module: JupyterTransportModel.view_module,
_view_module_version: JupyterTransportModel.view_module_version,
custom_libraries: [],
json_input: null,
mapbox_key: null,
selected_data: [],
data_buffer: null,
tooltip: null,
width: '100%',
height: 500,
js_warning: false
};
}

static get serializers() {
return {
...DOMWidgetModel.serializers,
// Add any extra serializers here
data_buffer: {deserialize: deserializeMatrix}
};
}

static get model_name() {
return 'JupyterTransportModel';
}
static get model_module() {
return MODULE_NAME;
}
static get model_module_version() {
return MODULE_VERSION;
}
static get view_name() {
return 'JupyterTransportView';
}
static get view_module() {
return MODULE_NAME;
}
static get view_module_version() {
return MODULE_VERSION;
}
}
55 changes: 55 additions & 0 deletions modules/jupyter-widget/src/lib/jupyter-transport-view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {DOMWidgetView} from '@jupyter-widgets/base';
import {jupyterTransport} from './jupyter-transport';

export default class JupyterTransportView extends DOMWidgetView {
initialize() {
this.listenTo(this.model, 'destroy', this.remove);

this.transport = jupyterTransport;

// Expose Jupyter internals to enable work-arounds
this.transport.jupyterModel = this.model;
this.transport.jupyterView = this;
this.transport._initialize();
}

remove() {
if (this.transport) {
this.transport._finalize();
this.transport.jupyterModel = null;
this.transport.jupyterView = null;
this.transport = null;
}
}

render() {
super.render();

// TODO - looks like bind(this) is not needed here, it is already passed as 3rd arg...
// TODO - remove and test
this.model.on('change:json_input', this.onJsonChanged.bind(this), this);
this.model.on('change:data_buffer', this.onDataBufferChanged.bind(this), this);

this.onDataBufferChanged();
}

onJsonChanged() {
const json = JSON.parse(this.model.get('json_input'));
this.transport._messageReceived({type: 'json', json});
}

onDataBufferChanged() {
const json = this.model.get('json_input');
const dataBuffer = this.model.get('data_buffer');

if (json && dataBuffer) {
this.transport._messageReceived({
type: 'json-with-binary',
json,
binary: dataBuffer
});
} else {
this.transport._messageReceived({type: 'json', json});
}
}
}
24 changes: 24 additions & 0 deletions modules/jupyter-widget/src/lib/jupyter-transport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Jupyter Widget based Transport implementation

import {Transport} from '@deck.gl/json';

/**
* A Transport subclass for communicating with Jupyter kernels
* via the Jupyter Widget API.
*/
export default class JupyterTransport extends Transport {
constructor() {
super('Jupyter Transport (JavaScript <=> Jupyter Kernel)');
this.jupyterModel = null; // Set manually by the Jupyter Widget View
this.jupyterView = null; // Set manually by the Jupyter Widget View
}

// TODO - implement back-channel messaging for event handling etc
sendJsonMessage({json, type}) {
// this.jupyterModel. ...
}
}

export const jupyterTransport = new JupyterTransport();

Transport.registerTransport(jupyterTransport);
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function dtypeToTypedArray(dtype) {
}
}

function deserializeMatrix(obj, manager) {
export function deserializeMatrix(obj, manager) {
if (!obj) {
return null;
}
Expand All @@ -47,16 +47,3 @@ function deserializeMatrix(obj, manager) {
// Becomes the data stored within the widget model at `model.get('data_buffer')`
return obj;
}

function processDataBuffer({dataBuffer, convertedJson}) {
// Takes JSON props and combines them with the binary data buffer
for (let i = 0; i < convertedJson.layers.length; i++) {
const layerId = convertedJson.layers[i].id;
const layer = convertedJson.layers[i];
// Replace data on every layer prop
convertedJson.layers[i] = layer.clone({data: dataBuffer[layerId]});
}
return convertedJson;
}

export {deserializeMatrix, processDataBuffer};
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
/* global window */
import makeTooltip from './widget-tooltip';

import mapboxgl from './ssr-safe-mapbox';

import {CSVLoader} from '@loaders.gl/csv';
import {registerLoaders} from '@loaders.gl/core';
import GL from '@luma.gl/constants';

import * as deck from './deck-bundle';
import makeTooltip from './widget-tooltip';

import {loadScript} from './script-utils';
import mapboxgl from './utils/mapbox-utils';
import {loadScript} from './utils/script-utils';

import GL from '@luma.gl/constants';
import * as deck from '../deck-bundle';

function extractClasses(library = {}) {
// Extracts exported class constructors as a dictionary from a library
Expand Down
4 changes: 4 additions & 0 deletions modules/jupyter-widget/src/playground/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export {initPlayground} from './playground';

// For to_html()...
export {createDeck, updateDeck} from './create-deck';

0 comments on commit 8d78371

Please sign in to comment.