diff --git a/.cookiecutter.yaml b/.cookiecutter.yaml new file mode 100644 index 0000000..4260d4d --- /dev/null +++ b/.cookiecutter.yaml @@ -0,0 +1,7 @@ +default_context: + author_name: "Grant Nestor" + author_email: "grantnestor@gmail.com" + mime_type: "application/vnd.vega.v2+json" + file_extension: "vg" + mime_short_name: "Vega" + extension_name: "jupyterlab_vega" diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 7829bbb..387726a --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,15 @@ -jupyterlab_vega/static/ -jupyterlab_vega/__pycache__/ -jupyterlab_vega.egg-info -node_modules/ +labextension/lib/ +nbextension/lib/ nbextension/embed/ +*/node_modules/ +npm-debug.log +*.egg-info/ +build/ +dist/ + +# Compiled javascript +jupyterlab_vega/__pycache__/ +jupyterlab_vega/static/ + +# OS X +.DS_Store diff --git a/README.md b/README.md index ad6ccae..ef2b704 100755 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A JupyterLab and Jupyter Notebook extension for rendering Vega and Vega-lite ## Prerequisites -* JupyterLab ^0.16.0 and/or Notebook >=4.3.0 +* JupyterLab ^0.17.0 and/or Notebook >=4.3.0 ## Usage diff --git a/RELEASE.md b/RELEASE.md index 690e134..f6a56e3 100755 --- a/RELEASE.md +++ b/RELEASE.md @@ -4,15 +4,14 @@ This document guides an extension maintainer through creating and publishing a r ## Update version number -Update the version number in `setup.py` and in `package.json`. +Update the version number in `setup.py`, `labextension/package.json`, and `nbextension/package.json`. ## Remove generated files -Remove old Javascript bundle builds and delete the `dist/` folder to remove old Python package builds: +Remove old Javascript bundle and Python package builds: ```bash -npm run clean -rm -rf dist/ +rm -rf jupyterlab_vega/static ``` ## Build the package @@ -20,7 +19,7 @@ rm -rf dist/ Build the Javascript extension bundle, then build the Python package and wheel: ```bash -npm run build +bash build.js python setup.py sdist python setup.py bdist_wheel --universal ``` diff --git a/jupyterlab_vega/__init__.py b/jupyterlab_vega/__init__.py index 81b4f17..82905ca 100755 --- a/jupyterlab_vega/__init__.py +++ b/jupyterlab_vega/__init__.py @@ -138,8 +138,48 @@ def _ipython_display_(self): 'application/vnd.vega.v2+json': prepare_vega_spec(self.spec, self.data), 'text/plain': '' } - metadata = { - 'application/vnd.vega.v2+json': self.metadata + display(bundle, raw=True) + + +class VegaLite(Vega): + """VegaLite expects a spec (a JSON-able dict) and data (JSON-able list or pandas DataFrame) argument + + not already-serialized JSON strings. + + Scalar types (None, number, string) are not allowed, only dict containers. + """ + + def __init__(self, spec=None, data=None, url=None, filename=None, metadata=None): + """Create a VegaLite display object given raw data. + + Parameters + ---------- + spec : dict + VegaLite spec. Not an already-serialized JSON string. + data : dict or list + VegaLite data. Not an already-serialized JSON string. + Scalar types (None, number, string) are not allowed, only dict + or list containers. + url : unicode + A URL to download the data from. + filename : unicode + Path to a local file to load the data from. + metadata: dict + Specify extra metadata to attach to the json display object. + """ + + super(VegaLite, self).__init__(spec=spec, data=data, url=url, filename=filename) + + def _check_data(self): + if self.spec is not None and not isinstance(self.spec, dict): + raise TypeError("%s expects a JSONable dict, not %r" % (self.__class__.__name__, self.spec)) + if self.data is not None and not isinstance(self.data, (list, pd.DataFrame)): + raise TypeError("%s expects a JSONable list or pandas DataFrame, not %r" % (self.__class__.__name__, self.data)) + + def _ipython_display_(self): + bundle = { + 'application/vnd.vegalite.v1+json': prepare_vegalite_spec(self.spec, self.data), + 'text/plain': '' } display(bundle, metadata=metadata, raw=True) diff --git a/labextension/README.md b/labextension/README.md index c5ce783..ee20368 100644 --- a/labextension/README.md +++ b/labextension/README.md @@ -4,9 +4,7 @@ A JupyterLab extension for rendering Vega ## Prerequisites -* `jupyterlab@^0.16.0` - -![file renderer](http://g.recordit.co/cbf0xnQHKn.gif) +* `jupyterlab@^0.17.0` ## Development diff --git a/labextension/build_extension.js b/labextension/build_extension.js index bdff53b..574914e 100644 --- a/labextension/build_extension.js +++ b/labextension/build_extension.js @@ -3,26 +3,45 @@ var path = require('path'); buildExtension({ name: 'jupyterlab_vega', - entry: './src/plugin.js', - outputDir: '../jupyterlab_vega/static', + entry: path.join(__dirname, 'src', 'plugin.js'), + outputDir: path.join( + __dirname, + '..', + 'jupyterlab_vega', + 'static' + ), useDefaultLoaders: false, config: { module: { loaders: [ { test: /\.html$/, loader: 'file-loader' }, { test: /\.(jpg|png|gif)$/, loader: 'file-loader' }, - { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' }, - { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' }, - { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream' }, + { + test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, + loader: 'url-loader?limit=10000&mimetype=application/font-woff' + }, + { + test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, + loader: 'url-loader?limit=10000&mimetype=application/font-woff' + }, + { + test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, + loader: 'url-loader?limit=10000&mimetype=application/octet-stream' + }, { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader' }, - { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml' }, + { + test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, + loader: 'url-loader?limit=10000&mimetype=image/svg+xml' + }, { test: /\.json$/, loader: 'json-loader' }, - { test: /\.js$/, - exclude: /node_modules(?!\/jupyterlab_vega_react)/, + { + test: /\.js$/, + include: [ + path.join(__dirname, 'src'), + path.join(__dirname, 'node_modules', 'jupyterlab_vega_react') + ], loader: 'babel-loader', - query: { - presets: ['latest', 'stage-0', 'react'] - } + query: { presets: [ 'latest', 'stage-0', 'react' ] } } ] } diff --git a/labextension/package.json b/labextension/package.json index bf70dcd..59209f3 100644 --- a/labextension/package.json +++ b/labextension/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "jupyterlab_vega_labextension", - "version": "0.16.0", + "version": "0.17.0", "description": "A JupyterLab extension for rendering Vega", "author": "Grant Nestor ", "main": "lib/plugin.js", @@ -12,7 +12,7 @@ ], "scripts": { "build": "node build_extension.js", - "watch": "watch \"npm run build\" src ../component --wait 10 --ignoreDotFiles", + "watch": "watch \"npm install\" src ../component --wait 10 --ignoreDotFiles", "preinstall": "npm install ../component", "prepublish": "npm run build", "extension:install": "jupyter labextension install --symlink --py --sys-prefix jupyterlab_vega", @@ -21,20 +21,19 @@ "extension:disable": "jupyter labextension disable --py --sys-prefix jupyterlab_vega" }, "dependencies": { - "jupyterlab": "^0.16.0", - "phosphor": "^0.7.0", + "jupyterlab": "^0.17.0", + "@phosphor/algorithm": "^0.1.0", + "@phosphor/widgets": "^0.1.3", "react": "^15.3.2", "react-dom": "^15.3.2" }, "devDependencies": { "@jupyterlab/extension-builder": ">=0.10.0", - "@jupyterlab/services": ">=0.25.0", "babel-core": "^6.18.2", "babel-loader": "^6.2.7", "babel-preset-latest": "^6.16.0", "babel-preset-react": "^6.16.0", "babel-preset-stage-0": "^6.16.0", - "rimraf": "^2.5.4", "watch": "^1.0.1" } } diff --git a/labextension/src/doc.js b/labextension/src/doc.js index 8063d60..c5f87f0 100755 --- a/labextension/src/doc.js +++ b/labextension/src/doc.js @@ -1,4 +1,4 @@ -import { Widget } from 'phosphor/lib/ui/widget'; +import { Widget } from '@phosphor/widgets'; import { ABCWidgetFactory } from 'jupyterlab/lib/docregistry'; import { ActivityMonitor } from 'jupyterlab/lib/common/activitymonitor'; import React from 'react'; @@ -6,7 +6,7 @@ import ReactDOM from 'react-dom'; import Vega from 'jupyterlab_vega_react'; /** - * The class name added to this DocWidget. + * The class name added to a DocWidget. */ const CLASS_NAME = 'jp-DocWidgetVega'; @@ -18,8 +18,7 @@ const RENDER_TIMEOUT = 1000; /** * A widget for rendering jupyterlab_vega files. */ -class DocWidget extends Widget { - +export class DocWidget extends Widget { constructor(context) { super(); this._context = context; @@ -55,14 +54,51 @@ class DocWidget extends Widget { onUpdateRequest(msg) { this.title.label = this._context.path.split('/').pop(); if (this.isAttached) { - let content = this._context.model.toString(); - const json = content ? JSON.parse(content) : {}; - const path = this._context._path; - const props = { - data: json, - embedMode: path.includes('.vl') ? 'vega-lite' : 'vega' - }; - ReactDOM.render(, this.node); + const content = this._context.model.toString(); + try { + const data = JSON.parse(content); + const path = this._context._path; + const props = { + data, + embedMode: path.includes('.vl') ? 'vega-lite' : 'vega' + }; + ReactDOM.render(, this.node); + } catch (error) { + const ErrorDisplay = props => ( +
+ {props.message} +
{props.content}
+
+ ); + + ReactDOM.render( + , + this.node + ); + } } } @@ -72,14 +108,12 @@ class DocWidget extends Widget { onAfterAttach(msg) { this.update(); } - } /** * A widget factory for DocWidget. */ export class VegaDoc extends ABCWidgetFactory { - constructor(options) { super(options); } @@ -88,9 +122,8 @@ export class VegaDoc extends ABCWidgetFactory { * Create a new widget given a context. */ createNewWidget(context, kernel) { - let widget = new DocWidget(context); + const widget = new DocWidget(context); this.widgetCreated.emit(widget); return widget; } - } diff --git a/labextension/src/index.css b/labextension/src/index.css index 136a735..725355e 100755 --- a/labextension/src/index.css +++ b/labextension/src/index.css @@ -4,8 +4,7 @@ */ .jp-OutputWidgetVega, .jp-DocWidgetVega { - padding: 0; - overflow-y: scroll; + padding: 5px 0; } .jp-OutputWidgetVega { @@ -13,5 +12,6 @@ } .jp-DocWidgetVega { - + padding: 5px; + overflow: auto; } diff --git a/labextension/src/output.js b/labextension/src/output.js index eabe1bf..0a990b8 100755 --- a/labextension/src/output.js +++ b/labextension/src/output.js @@ -1,4 +1,4 @@ -import { Widget } from 'phosphor/lib/ui/widget'; +import { Widget } from '@phosphor/widgets'; import React from 'react'; import ReactDOM from 'react-dom'; import Vega from 'jupyterlab_vega_react'; @@ -11,14 +11,13 @@ const CLASS_NAME = 'jp-OutputWidgetVega'; /** * A widget for rendering Vega. */ -class OutputWidget extends Widget { - +export class OutputWidget extends Widget { constructor(options) { super(); this.addClass(CLASS_NAME); - this._source = options.source; - this._mimetype = options.mimetype; - this._injector = options.injector; + this._data = options.model.data; + // this._metadata = options.model.metadata.get(options.mimeType); + this._mimeType = options.mimeType; } /** @@ -39,42 +38,36 @@ class OutputWidget extends Widget { * A render function given the widget's DOM node. */ _render() { - const json = this._source; - const mimetype = this._mimetype; + const data = this._data.get(this._mimeType); + // const metadata = this._metadata.get(this._mimeType); const props = { - data: json, - embedMode: mimetype === 'application/vnd.vegalite.v1+json' ? 'vega-lite' : 'vega', + data, + // metadata, + embedMode: this._mimeType === 'application/vnd.vegalite.v1+json' + ? 'vega-lite' + : 'vega', renderedCallback: (error, result) => { if (error) return console.log(error); // Add a static image output to mime bundle const imageData = result.view.toImageURL().split(',')[1]; - if (!this._injector.has('image/png')) this._injector.add('image/png', imageData); + this._data.set('image/png', imageData) } }; ReactDOM.render(, this.node); } - } export class VegaOutput { - /** * The mime types this OutputRenderer accepts. */ - mimetypes = [ 'application/vnd.vega.v2+json' ]; - - /** - * Whether the input can safely sanitized for a given mime type. - */ - isSanitizable(mimetype) { - return this.mimetypes.indexOf(mimetype) !== -1; - } + mimeTypes = ['application/vnd.vega.v2+json']; /** - * Whether the input is safe without sanitization. + * Whether the renderer can render given the render options. */ - isSafe(mimetype) { - return false; + canRender(options) { + return this.mimeTypes.indexOf(options.mimeType) !== -1; } /** @@ -83,28 +76,19 @@ export class VegaOutput { render(options) { return new OutputWidget(options); } - } export class VegaLiteOutput { - /** * The mime types this OutputRenderer accepts. */ - mimetypes = [ 'application/vnd.vegalite.v1+json' ]; + mimeTypes = ['application/vnd.vegalite.v1+json']; /** - * Whether the input can safely sanitized for a given mime type. + * Whether the renderer can render given the render options. */ - isSanitizable(mimetype) { - return this.mimetypes.indexOf(mimetype) !== -1; - } - - /** - * Whether the input is safe without sanitization. - */ - isSafe(mimetype) { - return false; + canRender(options) { + return this.mimeTypes.indexOf(options.mimeType) !== -1; } /** @@ -113,5 +97,4 @@ export class VegaLiteOutput { render(options) { return new OutputWidget(options); } - } diff --git a/labextension/src/plugin.js b/labextension/src/plugin.js index e037e56..aee47f0 100644 --- a/labextension/src/plugin.js +++ b/labextension/src/plugin.js @@ -1,7 +1,6 @@ import { IRenderMime } from 'jupyterlab/lib/rendermime'; import { IDocumentRegistry } from 'jupyterlab/lib/docregistry'; -import { toArray } from 'phosphor/lib/algorithm/iteration'; -import { findLastIndex } from 'phosphor/lib/algorithm/searching'; +import { toArray, ArrayExt } from '@phosphor/algorithm'; import { VegaOutput, VegaLiteOutput } from './output'; import { VegaDoc } from './doc'; import './index.css'; @@ -10,20 +9,34 @@ import './index.css'; * Activate the extension. */ function activatePlugin(app, rendermime, registry) { - /** * Calculate the index of the renderer in the array renderers (e.g. Insert * this renderer after any renderers with mime type that matches "+json") * or simply pass an integer such as 0. */ - // const index = findLastIndex(toArray(rendermime.mimetypes()), mimetype => mimetype.endsWith('+json')) + 1; + // const index = ArrayExt.findLastIndex( + // toArray(rendermime.mimeTypes()), + // mime => mime.endsWith('+json') + // ) + 1; const index = 0; - + /** * Add the renderer to the registry of renderers. */ - rendermime.addRenderer('application/vnd.vega.v2+json', new VegaOutput(), index); - rendermime.addRenderer('application/vnd.vegalite.v1+json', new VegaLiteOutput(), index); + rendermime.addRenderer( + { + mimeType: 'application/vnd.vega.v2+json', + renderer: new VegaOutput() + }, + index + ); + rendermime.addRenderer( + { + mimeType: 'application/vnd.vegalite.v1+json', + renderer: new VegaLiteOutput() + }, + index + ); /** * Set the extensions associated with Vega. @@ -37,7 +50,7 @@ function activatePlugin(app, rendermime, registry) { registry.addWidgetFactory(new VegaDoc({ fileExtensions: VEGA_EXTENSIONS, - defaultFor: VEGA_EXTENSIONS, + defaultFor: VEGA_EXTENSIONS.slice(0,2), name: 'Vega', displayName: 'Vega', modelName: 'text', diff --git a/nbextension/package.json b/nbextension/package.json index 94d6106..5a6a0bc 100644 --- a/nbextension/package.json +++ b/nbextension/package.json @@ -1,6 +1,6 @@ { "name": "jupyterlab_vega_nbextension", - "version": "0.16.0", + "version": "0.17.0", "description": "A Jupyter Notebook extension for rendering Vega", "author": "Grant Nestor ", "main": "lib/index.js", @@ -11,7 +11,7 @@ ], "scripts": { "build": "webpack", - "watch": "watch \"npm run build\" src ../component --wait 10 --ignoreDotFiles", + "watch": "watch \"npm install\" src ../component --wait 10 --ignoreDotFiles", "preinstall": "npm install ../component", "prepublish": "npm run build", "extension:install": "jupyter nbextension install --symlink --py --sys-prefix notebook_json", diff --git a/nbextension/src/embed.js b/nbextension/src/embed.js index 0a14add..07a23a8 100644 --- a/nbextension/src/embed.js +++ b/nbextension/src/embed.js @@ -1,7 +1,9 @@ -// Entry point for the unpkg bundle containing custom model definitions. -// -// It differs from the notebook bundle in that it does not need to define a -// dynamic baseURL for the static assets and may load some css that would -// already be loaded by the notebook otherwise. +/** + * Entry point for the unpkg bundle containing custom model definitions. + * + * It differs from the notebook bundle in that it does not need to define a + * dynamic baseURL for the static assets and may load some css that would + * already be loaded by the notebook otherwise. + */ export { version } from '../package.json'; diff --git a/nbextension/src/extension.js b/nbextension/src/extension.js index e9b1770..1af09ad 100644 --- a/nbextension/src/extension.js +++ b/nbextension/src/extension.js @@ -1,8 +1,12 @@ -// This file contains the javascript that is run when the notebook is loaded. -// It contains some requirejs configuration and the `load_ipython_extension` -// which is required for any notebook extension. +/** + * This file contains the javascript that is run when the notebook is loaded. + * It contains some requirejs configuration and the `load_ipython_extension` + * which is required for any notebook extension. + */ -// Configure requirejs +/** + * Configure requirejs. + */ if (window.require) { window.require.config({ map: { @@ -13,13 +17,19 @@ if (window.require) { }); } -// Export the required load_ipython_extention +/** + * Export the required load_ipython_extention. + */ export function load_ipython_extension() { - define([ - 'nbextensions/jupyterlab_vega/index', - 'jquery' - ], (Extension, $) => { - Extension.register_renderer($); - Extension.render_cells($); - }); -}; + define( + [ + 'nbextensions/jupyterlab_vega/index', + 'base/js/namespace' + ], + (Extension, Jupyter) => { + const { notebook } = Jupyter; + Extension.register_renderer(notebook); + Extension.render_cells(notebook); + } + ); +} diff --git a/nbextension/src/index.css b/nbextension/src/index.css index 2bec908..908e768 100644 --- a/nbextension/src/index.css +++ b/nbextension/src/index.css @@ -1,4 +1,4 @@ -div.output_subarea .output_Vega { +div.output_subarea.output_Vega { padding: 0.4em 0; max-width: 100%; } diff --git a/nbextension/src/index.js b/nbextension/src/index.js index fb5e4ac..e32c919 100644 --- a/nbextension/src/index.js +++ b/nbextension/src/index.js @@ -1,12 +1,18 @@ -// Entry point for the notebook bundle containing custom model definitions. -// -// Setup notebook base URL -// -// 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. -__webpack_public_path__ = document.querySelector('body').getAttribute('data-base-url') + 'nbextensions/jupyterlab_vega/'; +/** + * Entry point for the notebook bundle containing custom model definitions. + * Setup notebook base URL + * 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. + */ -// Export widget models and views, and the npm package version number. +__webpack_public_path__ = document + .querySelector('body') + .getAttribute('data-base-url') + + 'nbextensions/jupyterlab_vega/'; + +/** + * Export widget models and views, and the npm package version number. + */ export { register_renderer, render_cells } from './renderer.js'; export { version } from '../package.json'; diff --git a/nbextension/src/renderer.js b/nbextension/src/renderer.js index a63bec4..d49a15b 100644 --- a/nbextension/src/renderer.js +++ b/nbextension/src/renderer.js @@ -7,22 +7,27 @@ const VEGA_MIME_TYPE = 'application/vnd.vega.v2+json'; const VEGALITE_MIME_TYPE = 'application/vnd.vegalite.v1+json'; const CLASS_NAME = 'output_Vega rendered_html'; -// -// Render data to the output area -// +/** + * Render data to the output area + */ function render(props, node) { ReactDOM.render(, node); } -// -// Register the mime type and append_mime_type function with the notebook's OutputArea -// -export function register_renderer($) { - // Get an instance of the OutputArea object from the first CodeCellebook_ - const OutputArea = $('#notebook-container').find('.code_cell').eq(0).data('cell').output_area; - // A function to render output of Vega mime type +/** + * Register the mime type and append_mime_type function with the notebook's + * output_area + */ +export function register_renderer(notebook) { + // Get an instance of output_area from the notebook object + const { output_area } = notebook + .get_cells() + .reduce((result, cell) => cell.output_area ? cell : result, {}); + // A function to render output of 'application/vnd.vega.v2+json' mime type function append_mime(mimetype) { - const embedMode = mimetype === 'application/vnd.vegalite.v1+json' ? 'vega-lite' : 'vega' + const embedMode = mimetype === 'application/vnd.vegalite.v1+json' + ? 'vega-lite' + : 'vega'; return function(json, md, element) { const toinsert = this.create_output_subarea(md, CLASS_NAME, mimetype); this.keyboard_manager.register_events(toinsert); @@ -33,47 +38,61 @@ export function register_renderer($) { if (error) return console.log(error); // Add a static image output to mime bundle const imageData = result.view.toImageURL().split(',')[1]; - } }; - render(props, toinsert[(0)]); + render(props, toinsert[0]); element.append(toinsert); return toinsert; - } + }; } - // Calculate the index of this renderer in `OutputArea.display_order` + // Calculate the index of this renderer in `output_area.display_order` // e.g. Insert this renderer after any renderers with mime type that matches "+json" - // const mime_types = OutputArea.mime_types(); + // const mime_types = output_area.mime_types(); // const json_types = mime_types.filter(mimetype => mimetype.includes('+json')); // const index = mime_types.lastIndexOf(json_types.pop() + 1); - // ...or just insert it at the top + // // ...or just insert it at the top const index = 0; - // Register the mime type and append_mime_type function with the notebook's OutputArea - OutputArea.register_mime_type(VEGA_MIME_TYPE, append_mime(VEGA_MIME_TYPE), { - // Is output safe? - safe: true, - // Index of renderer in `OutputArea.display_order` - index: index - }); - OutputArea.register_mime_type(VEGALITE_MIME_TYPE, append_mime(VEGALITE_MIME_TYPE), { + // Register the mime type and append_mime_type function with the notebook's output_area + output_area.register_mime_type( + VEGA_MIME_TYPE, + append_mime(VEGA_MIME_TYPE), + { + // Is output safe? + safe: true, + // Index of renderer in `output_area.display_order` + index: index + } +); +output_area.register_mime_type( + VEGALITE_MIME_TYPE, + append_mime(VEGALITE_MIME_TYPE), + { // Is output safe? safe: true, - // Index of renderer in `OutputArea.display_order` + // Index of renderer in `output_area.display_order` index: index - }); -} + } +); -// -// Re-render cells with output data of 'application/vnd.vega.v2+json' mime type -// -export function render_cells($) { - // Get all cells in notebook - $('#notebook-container').find('.cell').toArray().forEach(item => { - const CodeCell = $(item).data('cell'); - // If a cell has output data of 'application/vnd.vega+json' mime type - if (CodeCell.output_area && CodeCell.output_area.outputs.find(output => output.data && (output.data[VEGA_MIME_TYPE] || output.data[VEGALITE_MIME_TYPE]))) { - // Re-render the cell by executing it - CodeCell.notebook.render_cell_output(CodeCell); - } - }); } + +/** + * Re-render cells with output data of 'application/vnd.vega.v2+json' mime type + * on load notebook + */ + export function render_cells(notebook) { + // Get all cells in notebook + notebook.get_cells().forEach(cell => { + // If a cell has output data of 'application/geo+json' mime type + if ( + cell.output_area && + cell.output_area.outputs.find(output => + output.data && + (output.data[VEGA_MIME_TYPE] || output.data[VEGALITE_MIME_TYPE]) + ) + ) { + // Re-render the cell by executing it + notebook.render_cell_output(cell); + } + }); + } diff --git a/nbextension/webpack.config.js b/nbextension/webpack.config.js index a74232d..b47342d 100644 --- a/nbextension/webpack.config.js +++ b/nbextension/webpack.config.js @@ -1,112 +1,121 @@ var version = require('./package.json').version; var path = require('path'); -// Custom webpack loaders are generally the same for all webpack bundles, hence -// stored in a separate local variable. +/** + * Custom webpack loaders are generally the same for all webpack bundles, hence + * stored in a separate local variable. + */ var loaders = [ { test: /\.js$/, - exclude: /node_modules(?!\/jupyterlab_vega_react)/, + include: [ + path.join(__dirname, 'src'), + path.join(__dirname, 'node_modules', 'jupyterlab_vega_react') + ], loader: 'babel-loader', - query: { - presets: ['latest', 'stage-0', 'react'] - } - }, { - test: /\.json$/, - loader: 'json-loader' - }, { - test: /\.css$/, - loader: 'style-loader!css-loader' - }, { - test: /\.html$/, - loader: 'file-loader' - }, { - test: /\.(jpg|png|gif)$/, - loader: 'file-loader' - }, { + query: { presets: [ 'latest', 'stage-0', 'react' ] } + }, + { test: /\.json$/, loader: 'json-loader' }, + { test: /\.css$/, loader: 'style-loader!css-loader' }, + { test: /\.html$/, loader: 'file-loader' }, + { test: /\.(jpg|png|gif)$/, loader: 'file-loader' }, + { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' - }, { + }, + { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' - }, { + }, + { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream' - }, { - test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, - loader: 'file-loader' - }, { + }, + { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader' }, + { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml' } ]; - module.exports = [ - // Notebook extension - // - // This bundle only contains the part of the JavaScript that is run on - // load of the notebook. This section generally only performs - // some configuration for requirejs, and provides the legacy - // "load_ipython_extension" function which is required for any notebook - // extension. + /** + * Notebook extension + * + * This bundle only contains the part of the JavaScript that is run on + * load of the notebook. This section generally only performs + * some configuration for requirejs, and provides the legacy + * "load_ipython_extension" function which is required for any notebook + * extension. + */ { - entry: './src/extension.js', + entry: path.join(__dirname, 'src', 'extension.js'), output: { filename: 'extension.js', - path: '../jupyterlab_vega/static', + path: path.join( + __dirname, + '..', + 'jupyterlab_vega', + 'static' + ), libraryTarget: 'amd' }, devtool: 'source-map', - module: { - loaders: loaders, - }, + module: { loaders }, externals: [ - 'nbextensions/jupyterlab_vega/index', - 'jquery' + 'nbextensions/jupyterlab_vega/index', + 'base/js/namespace' ] }, - // Bundle for the notebook containing the custom widget views and models - // - // This bundle contains the implementation for the custom widget views and - // custom widget. - // It must be an amd module + /** + * Bundle for the notebook containing the custom widget views and models + * + * This bundle contains the implementation for the custom widget views and + * custom widget. + * + * It must be an amd module + */ { - entry: './src/index.js', + entry: path.join(__dirname, 'src', 'index.js'), output: { filename: 'index.js', - path: '../jupyterlab_vega/static', + path: path.join( + __dirname, + '..', + 'jupyterlab_vega', + 'static' + ), libraryTarget: 'amd' }, devtool: 'source-map', - module: { - loaders: loaders, - } + module: { loaders } }, - // Embeddable jupyterlab_vega bundle - // - // This bundle is generally almost identical to the notebook bundle - // containing the custom widget views and models. - // - // The only difference is in the configuration of the webpack public path - // for the static assets. - // - // It will be automatically distributed by unpkg to work with the static - // widget embedder. - // - // The target bundle is always `lib/index.js`, which is the path required - // by the custom widget embedder. + /** + * Embeddable jupyterlab_vega bundle + * + * This bundle is generally almost identical to the notebook bundle + * containing the custom widget views and models. + * + * The only difference is in the configuration of the webpack public path + * for the static assets. + * + * It will be automatically distributed by unpkg to work with the static + * widget embedder. + * + * The target bundle is always `lib/index.js`, which is the path required + * by the custom widget embedder. + */ { entry: './src/embed.js', output: { filename: 'index.js', - path: './embed/', + path: path.join(__dirname, 'embed'), libraryTarget: 'amd', - publicPath: 'https://unpkg.com/jupyterlab_vega@' + version + '/lib/' + publicPath: ( + 'https://unpkg.com/jupyterlab_vega@' + version + '/lib/' + ) }, devtool: 'source-map', - module: { - loaders: loaders, - } + module: { loaders } } ]; diff --git a/setup.py b/setup.py index 15c5d66..14998d9 100755 --- a/setup.py +++ b/setup.py @@ -22,16 +22,15 @@ class NodeModulesMissing(Exception): setup_args = dict( name = 'jupyterlab_vega', - version = '0.1.0', + version = '0.17.0', packages = ['jupyterlab_vega'], author = 'Grant Nestor', author_email = 'grantnestor@gmail.com', keywords = ['jupyter', 'jupyterlab', 'labextension', 'notebook', 'nbextension'], include_package_data = True, install_requires = [ - 'jupyterlab>=0.16.0', - 'ipython>=1.0.0', - 'pandas' + 'jupyterlab>=0.17.0', + 'ipython>=1.0.0' ] )