├── .cookiecutter.yaml
├── .gitignore
├── MANIFEST.in
├── README.md
├── RELEASE.md
├── build.sh
├── component
├── README.md
├── index.css
├── index.js
└── package.json
├── install.sh
├── jupyterlab_plotly
├── README.md
├── __init__.py
└── utils.py
├── labextension
├── README.md
├── build_extension.js
├── package.json
└── src
│ ├── doc.js
│ ├── index.css
│ ├── output.js
│ └── plugin.js
├── nbextension
├── README.md
├── package.json
├── src
│ ├── embed.js
│ ├── extension.js
│ ├── index.css
│ ├── index.js
│ └── renderer.js
└── webpack.config.js
├── setup.py
├── setupbase.py
└── uninstall.sh
/.cookiecutter.yaml:
--------------------------------------------------------------------------------
1 | default_context:
2 | author_name: "Grant Nestor"
3 | author_email: "grantnestor@gmail.com"
4 | mime_type: "application/vnd.plotly.v1+json"
5 | file_extension: "plotly"
6 | mime_short_name: "Plotly"
7 | extension_name: "jupyterlab_plotly"
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | labextension/lib/
2 | nbextension/lib/
3 | nbextension/embed/
4 | node_modules/
5 | npm-debug.log
6 | *.egg-info/
7 | __pycache__/
8 | build/
9 | dist/
10 |
11 | # Compiled javascript
12 | jupyterlab_plotly/static/
13 |
14 | # OS X
15 | .DS_Store
16 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | graft jupyterlab_plotly/static
2 | graft labextension/src
3 | graft nbextension/src
4 | include labextension/package.json
5 | include nbextension/package.json
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # jupyterlab_plotly
2 |
3 | A JupyterLab and Jupyter Notebook extension for rendering [Plotly](https://plot.ly/python/) charts
4 |
5 | 
6 |
7 | 
8 |
9 | ## Prerequisites
10 |
11 | * JupyterLab ^0.18.0 and/or Notebook >=4.3.0
12 |
13 | ## Usage
14 |
15 | To render Plotly JSON using IPython:
16 |
17 | ```python
18 | from jupyterlab_plotly import Plotly
19 |
20 | data = [
21 | {'x': [1999, 2000, 2001, 2002], 'y': [10, 15, 13, 17], 'type': 'scatter'},
22 | {'x': [1999, 2000, 2001, 2002], 'y': [16, 5, 11, 9], 'type': 'scatter'}
23 | ]
24 |
25 | layout = {
26 | 'title': 'Sales Growth',
27 | 'xaxis': { 'title': 'Year', 'showgrid': False, 'zeroline': False },
28 | 'yaxis': { 'title': 'Percent', 'showline': False }
29 | }
30 |
31 | Plotly(data, layout)
32 | ```
33 |
34 | To render a Plotly JSON (`.plotly` or `.plotly.json`) file in JupyterLab, simply open it.
35 |
36 | ## Install
37 |
38 | ```bash
39 | pip install jupyterlab_plotly
40 | # For JupyterLab
41 | jupyter labextension install --symlink --py --sys-prefix jupyterlab_plotly
42 | jupyter labextension enable --py --sys-prefix jupyterlab_plotly
43 | # For Notebook
44 | jupyter nbextension install --symlink --py --sys-prefix jupyterlab_plotly
45 | jupyter nbextension enable --py --sys-prefix jupyterlab_plotly
46 | ```
47 |
48 | ## Development
49 |
50 | ```bash
51 | pip install -e .
52 | # For JupyterLab
53 | jupyter labextension install --symlink --py --sys-prefix jupyterlab_plotly
54 | jupyter labextension enable --py --sys-prefix jupyterlab_plotly
55 | # For Notebook
56 | jupyter nbextension install --symlink --py --sys-prefix jupyterlab_plotly
57 | jupyter nbextension enable --py --sys-prefix jupyterlab_plotly
58 | ```
59 |
--------------------------------------------------------------------------------
/RELEASE.md:
--------------------------------------------------------------------------------
1 | # Making a jupyterlab_plotly release
2 |
3 | This document guides an extension maintainer through creating and publishing a release of jupyterlab_plotly. This process creates a Python source package and a Python universal wheel and uploads them to PyPI.
4 |
5 | ## Update version number
6 |
7 | Update the version number in `setup.py`, `labextension/package.json`, and `nbextension/package.json`.
8 |
9 | Commit your changes, add git tag for this version, and push both commit and tag to your origin/remote repo.
10 |
11 | ## Remove generated files
12 |
13 | Remove old Javascript bundle and Python package builds:
14 |
15 | ```bash
16 | rm -rf jupyterlab_plotly/static
17 | ```
18 |
19 | ## Build the package
20 |
21 | Build the Javascript extension bundle, then build the Python package and wheel:
22 |
23 | ```bash
24 | bash build.js
25 | python setup.py sdist
26 | python setup.py bdist_wheel --universal
27 | ```
28 |
29 | ## Upload the package
30 |
31 | Upload the Python package and wheel with [twine](https://github.com/pypa/twine). See the Python documentation on [package uploading](https://packaging.python.org/distributing/#uploading-your-project-to-pypi)
32 | for [twine](https://github.com/pypa/twine) setup instructions and for why twine is the recommended uploading method.
33 |
34 | ```bash
35 | twine upload dist/*
36 | ```
37 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | npm -v
4 | if [ $? -eq 0 ]; then
5 | echo npm is installed
6 | else
7 | echo "'npm -v' failed, therefore npm is not installed. In order to perform a
8 | developer install of jupyterlab_plotly you must have npm installed
9 | on your machine! See http://blog.npmjs.org/post/85484771375/how-to-install-npm
10 | for installation instructions."
11 | exit 1
12 | fi
13 |
14 | cd labextension
15 | npm install
16 | cd ..
17 |
18 | cd nbextension
19 | npm install
20 | cd ..
21 |
--------------------------------------------------------------------------------
/component/README.md:
--------------------------------------------------------------------------------
1 | # component
2 |
3 | A React component for rendering Plotly
4 |
5 | ## Structure
6 |
7 | * `index.js`: Entry point for React component(s)
8 | * `index.css`: Optional CSS file for styling React component(s)
9 | * `package.json`: Node package configuration, use this to add npm depedencies
10 |
--------------------------------------------------------------------------------
/component/index.css:
--------------------------------------------------------------------------------
1 | .Plotly {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/component/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Plotly from 'plotly.js/lib/core';
3 |
4 | const DEFAULT_WIDTH = 840;
5 | const DEFAULT_HEIGHT = 336;
6 |
7 | export default class PlotlyComponent extends React.Component {
8 | componentDidMount() {
9 | // window.addEventListener('resize', this.handleResize);
10 | const { data, layout } = this.props;
11 | Plotly.newPlot(this.element, data, layout)
12 | .then(gd => {
13 | Plotly.Plots.resize(this.element);
14 | });
15 | // .then(gd =>
16 | // Plotly.toImage(gd, {
17 | // format: 'png',
18 | // width: this.props.width || DEFAULT_WIDTH,
19 | // height: this.props.height || DEFAULT_HEIGHT
20 | // }))
21 | // .then(url => {
22 | // const data = url.split(',')[1];
23 | // this.props.callback(null, data);
24 | // this.handleResize();
25 | // });
26 | }
27 |
28 | componentDidUpdate() {
29 | const { data, layout } = this.props;
30 | Plotly.redraw(this.element)
31 | .then(gd => {
32 | Plotly.Plots.resize(this.element);
33 | });
34 | // .then(gd =>
35 | // Plotly.toImage(gd, {
36 | // format: 'png',
37 | // width: this.props.width || DEFAULT_WIDTH,
38 | // height: this.props.height || DEFAULT_HEIGHT
39 | // }))
40 | // .then(url => {
41 | // const data = url.split(',')[1];
42 | // this.props.callback(null, data);
43 | // this.handleResize();
44 | // });
45 | }
46 |
47 | // componentWillUnmount() {
48 | // window.removeEventListener('resize', this.handleResize);
49 | // }
50 |
51 | // handleResize = event => {
52 | // Plotly.Plots.resize(this.element);
53 | // };
54 |
55 | render() {
56 | const { layout } = this.props;
57 | const style = {
58 | width: '100%',
59 | height: layout && layout.height && !layout.autosize
60 | ? layout.height
61 | : DEFAULT_HEIGHT
62 | };
63 | return (
64 |
{
67 | this.element = el;
68 | }}
69 | />
70 | );
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/component/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jupyterlab_plotly_react",
3 | "version": "1.0.0",
4 | "description": "A React component for rendering Plotly",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [
10 | "jupyter",
11 | "react"
12 | ],
13 | "author": "Grant Nestor
",
14 | "license": "ISC",
15 | "dependencies": {
16 | "plotly.js": "^1.22.0",
17 | "react": "^15.3.2"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 | nbExtFlags=$1
4 |
5 | bash build.sh
6 |
7 | pip --version
8 | if [ $? -eq 0 ]; then
9 | echo pip is installed
10 | else
11 | echo "'pip --version' failed, therefore pip is not installed. In order to perform
12 | a developer install of jupyterlab_plotly you must have pip installed on
13 | your machine! See https://packaging.python.org/installing/ for installation instructions."
14 | exit 1
15 | fi
16 |
17 | pip install -v -e .
18 |
19 | jupyter lab --version
20 | if [ $? -eq 0 ]; then
21 | echo jupyter lab is installed
22 | if [[ "$OSTYPE" == "msys" ]]; then
23 | jupyter labextension install --py $nbExtFlags jupyterlab_plotly
24 | else
25 | jupyter labextension install --py --symlink $nbExtFlags jupyterlab_plotly
26 | fi
27 | jupyter labextension enable --py $nbExtFlags jupyterlab_plotly
28 | else
29 | echo "'jupyter lab --version' failed, therefore jupyter lab is not installed. In
30 | order to perform a developer install of jupyterlab_plotly you must
31 | have jupyter lab on your machine! Install using 'pip install jupyterlab' or
32 | follow instructions at https://github.com/jupyterlab/jupyterlab/blob/master/CONTRIBUTING.md#installing-jupyterlab for developer install."
33 | fi
34 |
35 | jupyter notebook --version
36 | if [ $? -eq 0 ]; then
37 | echo jupyter notebook is installed
38 | if [[ "$OSTYPE" == "msys" ]]; then
39 | jupyter nbextension install --py $nbExtFlags jupyterlab_plotly
40 | else
41 | jupyter nbextension install --py --symlink $nbExtFlags jupyterlab_plotly
42 | fi
43 | jupyter nbextension enable --py $nbExtFlags jupyterlab_plotly
44 | else
45 | echo "'jupyter notebook --version' failed, therefore jupyter notebook is not
46 | installed. In order to perform a developer install of
47 | jupyterlab_plotly you must have jupyter notebook on your machine!
48 | Install using 'pip install notebook' or follow instructions at
49 | https://github.com/jupyter/notebook/blob/master/CONTRIBUTING.rst#installing-the-jupyter-notebook
50 | for developer install."
51 | fi
52 |
--------------------------------------------------------------------------------
/jupyterlab_plotly/README.md:
--------------------------------------------------------------------------------
1 | # jupyterlab_plotly
2 |
3 | Single Python package for lab and notebook extensions
4 |
5 | ## Structure
6 |
7 | * `static`: Built Javascript from `../labextension/` and `../nbextension/`
8 | * `__init__.py`: Exports paths and metadata of lab and notebook extensions and exports an optional `display` method that can be imported into a notebook and used to easily display data using this renderer
9 |
--------------------------------------------------------------------------------
/jupyterlab_plotly/__init__.py:
--------------------------------------------------------------------------------
1 | from IPython.display import display, DisplayObject
2 | import json
3 | import pandas as pd
4 |
5 |
6 | # Running `npm run build` will create static resources in the static
7 | # directory of this Python package (and create that directory if necessary).
8 |
9 | def _jupyter_labextension_paths():
10 | return [{
11 | 'name': 'jupyterlab_plotly',
12 | 'src': 'static',
13 | }]
14 |
15 | def _jupyter_nbextension_paths():
16 | return [{
17 | 'section': 'notebook',
18 | 'src': 'static',
19 | 'dest': 'jupyterlab_plotly',
20 | 'require': 'jupyterlab_plotly/extension'
21 | }]
22 |
23 | def prepare_data(data=None):
24 | """Prepare JSONTable data from Pandas DataFrame."""
25 |
26 | if isinstance(data, pd.DataFrame):
27 | data = data.to_json(orient='records')
28 | return json.loads(data)
29 | return data
30 |
31 | # A display class that can be used within a notebook. E.g.:
32 | # from jupyterlab_plotly import Plotly
33 | # Plotly(data)
34 |
35 | class Plotly(DisplayObject):
36 | """Plotly expects a data (JSON-able dict or list) and layout (a JSON-able dict) argument
37 |
38 | not an already-serialized JSON string.
39 |
40 | Scalar types (None, number, string) are not allowed, only dict containers.
41 | """
42 | # wrap data in a property, which warns about passing already-serialized JSON
43 | _data = None
44 | _layout = None
45 | def __init__(self, data=None, layout=None, url=None, filename=None, metadata=None):
46 | """Create a Plotly display object given raw data.
47 |
48 | Parameters
49 | ----------
50 | data : list
51 | Not an already-serialized JSON string.
52 | Scalar types (None, number, string) are not allowed, only list containers.
53 | layout : dict
54 | Plotly layout. Not an already-serialized JSON string.
55 | url : unicode
56 | A URL to download the data from.
57 | filename : unicode
58 | Path to a local file to load the data from.
59 | metadata: dict
60 | Specify extra metadata to attach to the json display object.
61 | """
62 | self.layout = layout
63 | self.metadata = metadata
64 | super(Plotly, self).__init__(data=data, url=url, filename=filename)
65 |
66 | def _check_data(self):
67 | if self.layout is not None and not isinstance(self.layout, dict):
68 | raise TypeError("%s expects a JSONable dict, not %r" % (self.__class__.__name__, self.layout))
69 | if self.data is not None and not isinstance(self.data, (list, pd.DataFrame)):
70 | raise TypeError("%s expects a JSONable list or pandas DataFrame, not %r" % (self.__class__.__name__, self.data))
71 |
72 | @property
73 | def layout(self):
74 | return self._layout
75 |
76 | @property
77 | def data(self):
78 | return self._data
79 |
80 | @layout.setter
81 | def layout(self, layout):
82 | if isinstance(layout, str):
83 | # warnings.warn("Plotly expects a JSONable dict, not JSON strings")
84 | layout = json.loads(layout)
85 | self._layout = layout
86 |
87 | @data.setter
88 | def data(self, data):
89 | if isinstance(data, str):
90 | # warnings.warn("Plotly expects JSONable dict or list, not JSON strings")
91 | data = json.loads(data)
92 | self._data = data
93 |
94 | def _ipython_display_(self):
95 | bundle = {
96 | 'application/vnd.plotly.v1+json': {
97 | 'layout': self.layout,
98 | 'data': prepare_data(self.data)
99 | },
100 | 'text/plain': ''
101 | }
102 | metadata = {
103 | 'application/vnd.plotly.v1+json': self.metadata
104 | }
105 | display(bundle, metadata=metadata, raw=True)
106 |
--------------------------------------------------------------------------------
/jupyterlab_plotly/utils.py:
--------------------------------------------------------------------------------
1 | import cgi
2 | import codecs
3 | import collections
4 | import os.path
5 | import pandas as pd
6 |
7 |
8 | def nested_update(d, u):
9 | """Update nested dictionary d (in-place) with keys from u."""
10 | for k, v in u.items():
11 | if isinstance(v, collections.Mapping):
12 | d[k] = nested_update(d.get(k, {}), v)
13 | else:
14 | d[k] = v
15 | return d
16 |
17 |
18 | def abs_path(path):
19 | """Make path absolute."""
20 | return os.path.join(
21 | os.path.dirname(os.path.abspath(__file__)),
22 | path)
23 |
24 |
25 | def get_content(path):
26 | """Get content of file."""
27 | with codecs.open(abs_path(path), encoding='utf-8') as f:
28 | return f.read()
29 |
30 |
31 | def escape(string):
32 | """Escape the string."""
33 | return cgi.escape(string, quote=True)
34 |
35 |
36 | def sanitize_dataframe(df):
37 | """Sanitize a DataFrame to prepare it for serialization.
38 |
39 | * Make a copy
40 | * Raise ValueError if it has a hierarchical index.
41 | * Convert categoricals to strings.
42 | * Convert np.int dtypes to Python int objects
43 | * Convert floats to objects and replace NaNs by None.
44 | * Convert DateTime dtypes into appropriate string representations
45 | """
46 | import pandas as pd
47 | import numpy as np
48 |
49 | df = df.copy()
50 |
51 | if isinstance(df.index, pd.core.index.MultiIndex):
52 | raise ValueError('Hierarchical indices not supported')
53 | if isinstance(df.columns, pd.core.index.MultiIndex):
54 | raise ValueError('Hierarchical indices not supported')
55 |
56 | for col_name, dtype in df.dtypes.iteritems():
57 | if str(dtype) == 'category':
58 | # XXXX: work around bug in to_json for categorical types
59 | # https://github.com/pydata/pandas/issues/10778
60 | df[col_name] = df[col_name].astype(str)
61 | elif np.issubdtype(dtype, np.integer):
62 | # convert integers to objects; np.int is not JSON serializable
63 | df[col_name] = df[col_name].astype(object)
64 | elif np.issubdtype(dtype, np.floating):
65 | # For floats, convert nan->None: np.float is not JSON serializable
66 | col = df[col_name].astype(object)
67 | df[col_name] = col.where(col.notnull(), None)
68 | elif str(dtype).startswith('datetime'):
69 | # Convert datetimes to strings
70 | # astype(str) will choose the appropriate resolution
71 | df[col_name] = df[col_name].astype(str).replace('NaT', '')
72 | return df
73 |
74 |
75 | def prepare_plotly_data(data=None):
76 | """Prepare Plotly data from Pandas DataFrame."""
77 |
78 | if isinstance(data, list):
79 | return data
80 | data = sanitize_dataframe(data)
81 | return data.to_dict(orient='records')
82 |
--------------------------------------------------------------------------------
/labextension/README.md:
--------------------------------------------------------------------------------
1 | # labextension
2 |
3 | A JupyterLab extension for rendering Plotly
4 |
5 | ## Prerequisites
6 |
7 | * `jupyterlab@^0.18.0`
8 |
9 | ## Development
10 |
11 | Install dependencies and build Javascript:
12 |
13 | ```bash
14 | npm install
15 | ```
16 |
17 | Re-build Javascript:
18 |
19 | ```bash
20 | npm run build
21 | ```
22 |
23 | Watch `/src` directory and re-build on changes:
24 |
25 | ```bash
26 | npm run watch
27 | ```
28 |
29 | Manage extension
30 |
31 | ```bash
32 | # Install
33 | npm run extension:install
34 | # Enable
35 | npm run extension:enable
36 | # Disable
37 | npm run extension:disable
38 | # Uninstall
39 | npm run extension:uninstall
40 | ```
41 |
--------------------------------------------------------------------------------
/labextension/build_extension.js:
--------------------------------------------------------------------------------
1 | var buildExtension = require('@jupyterlab/extension-builder').buildExtension;
2 | var path = require('path');
3 |
4 | buildExtension({
5 | name: 'jupyterlab_plotly',
6 | entry: path.join(__dirname, 'src', 'plugin.js'),
7 | outputDir: path.join(__dirname, '..', 'jupyterlab_plotly', 'static'),
8 | useDefaultLoaders: false,
9 | config: {
10 | module: {
11 | loaders: [
12 | { test: /\.html$/, loader: 'file-loader' },
13 | { test: /\.(jpg|png|gif)$/, loader: 'file-loader' },
14 | {
15 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
16 | loader: 'url-loader?limit=10000&mimetype=application/font-woff'
17 | },
18 | {
19 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
20 | loader: 'url-loader?limit=10000&mimetype=application/font-woff'
21 | },
22 | {
23 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
24 | loader: 'url-loader?limit=10000&mimetype=application/octet-stream'
25 | },
26 | { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader' },
27 | {
28 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
29 | loader: 'url-loader?limit=10000&mimetype=image/svg+xml'
30 | },
31 | { test: /\.json$/, loader: 'json-loader' },
32 | {
33 | test: /\.js$/,
34 | include: [
35 | path.join(__dirname, 'src'),
36 | path.join(
37 | __dirname,
38 | 'node_modules',
39 | 'jupyterlab_plotly_react'
40 | )
41 | ],
42 | loader: 'babel-loader',
43 | query: { presets: ['latest', 'stage-0', 'react'] }
44 | }
45 | ]
46 | }
47 | }
48 | });
49 |
--------------------------------------------------------------------------------
/labextension/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "jupyterlab_plotly_labextension",
4 | "version": "0.18.0",
5 | "description": "A JupyterLab extension for rendering Plotly",
6 | "author": "Grant Nestor ",
7 | "main": "src/plugin.js",
8 | "keywords": [
9 | "jupyter",
10 | "jupyterlab",
11 | "jupyterlab extension"
12 | ],
13 | "scripts": {
14 | "build": "node build_extension.js",
15 | "watch": "watch \"npm install\" src ../component --wait 10 --ignoreDotFiles",
16 | "preinstall": "npm install ../component",
17 | "prepublish": "npm run build",
18 | "extension:install": "jupyter labextension install --symlink --py --sys-prefix jupyterlab_plotly",
19 | "extension:uninstall": "jupyter labextension uninstall --py --sys-prefix jupyterlab_plotly",
20 | "extension:enable": "jupyter labextension enable --py --sys-prefix jupyterlab_plotly",
21 | "extension:disable": "jupyter labextension disable --py --sys-prefix jupyterlab_plotly"
22 | },
23 | "dependencies": {
24 | "@jupyterlab/apputils": "^0.1.3",
25 | "@jupyterlab/codemirror": "^0.1.3",
26 | "@jupyterlab/docregistry": "^0.1.3",
27 | "@jupyterlab/rendermime": "^0.1.3",
28 | "@phosphor/algorithm": "^0.1.1",
29 | "@phosphor/virtualdom": "^0.1.1",
30 | "@phosphor/widgets": "^0.3.0",
31 | "react": "^15.3.2",
32 | "react-dom": "^15.3.2"
33 | },
34 | "devDependencies": {
35 | "@jupyterlab/extension-builder": "^0.10.0",
36 | "babel-core": "^6.18.2",
37 | "babel-loader": "^6.2.7",
38 | "babel-preset-latest": "^6.16.0",
39 | "babel-preset-react": "^6.16.0",
40 | "babel-preset-stage-0": "^6.16.0",
41 | "watch": "^1.0.1"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/labextension/src/doc.js:
--------------------------------------------------------------------------------
1 | import { Widget } from '@phosphor/widgets';
2 | import { ABCWidgetFactory } from '@jupyterlab/docregistry';
3 | import { ActivityMonitor } from '@jupyterlab/coreutils';
4 | import { runMode } from '@jupyterlab/codemirror';
5 | import React from 'react';
6 | import ReactDOM from 'react-dom';
7 | import PlotlyComponent from 'jupyterlab_plotly_react';
8 |
9 | const CLASS_NAME = 'jp-DocWidgetPlotly';
10 | const RENDER_TIMEOUT = 1000;
11 |
12 | /**
13 | * A component for rendering error messages
14 | */
15 | class ErrorDisplay extends React.Component {
16 | componentDidUpdate() {
17 | runMode(this.props.content, { name: 'javascript', json: true }, this.ref);
18 | }
19 | render() {
20 | return (
21 |
22 |
{this.props.message}
23 |
{
25 | this.ref = ref;
26 | }}
27 | className="CodeMirror cm-s-jupyter CodeMirror-wrap"
28 | />
29 |
30 | );
31 | }
32 | }
33 |
34 | /**
35 | * A widget for rendering jupyterlab_plotly files
36 | */
37 | export class DocWidget extends Widget {
38 | constructor(context) {
39 | super();
40 | this._context = context;
41 | this._onPathChanged();
42 | this.addClass(CLASS_NAME);
43 | context.ready.then(() => {
44 | this.update();
45 | /* Re-render when the document content changes */
46 | context.model.contentChanged.connect(this.update, this);
47 | /* Re-render when the document path changes */
48 | context.fileChanged.connect(this.update, this);
49 | });
50 | /* Update title when path changes */
51 | context.pathChanged.connect(this._onPathChanged, this);
52 | /* Throttle re-renders until changes have stopped */
53 | this._monitor = new ActivityMonitor({
54 | signal: context.model.contentChanged,
55 | timeout: RENDER_TIMEOUT
56 | });
57 | this._monitor.activityStopped.connect(this.update, this);
58 | }
59 |
60 | /**
61 | * The widget's context
62 | */
63 | get context() {
64 | return this._context;
65 | }
66 |
67 | /**
68 | * Dispose of the resources used by the widget
69 | */
70 | dispose() {
71 | if (!this.isDisposed) {
72 | this._context = null;
73 | this._monitor.dispose();
74 | super.dispose();
75 | }
76 | }
77 |
78 | /**
79 | * A message handler invoked on an `'after-attach'` message
80 | */
81 | onAfterAttach(msg) {
82 | /* Render initial data */
83 | this.update();
84 | }
85 |
86 | /**
87 | * A message handler invoked on an `'before-detach'` message
88 | */
89 | onBeforeDetach(msg) {
90 | /* Dispose of resources used by widget */
91 | ReactDOM.unmountComponentAtNode(this.node);
92 | }
93 |
94 | /**
95 | * A message handler invoked on a `'resize'` message
96 | */
97 | onResize(msg) {
98 | /* Re-render on resize */
99 | this.update();
100 | }
101 |
102 | /**
103 | * A message handler invoked on an `'update-request'` message
104 | */
105 | onUpdateRequest(msg) {
106 | if (this.isAttached && this._context.isReady) this._render();
107 | }
108 |
109 | _render() {
110 | const content = this._context.model.toString();
111 | try {
112 | const props = {
113 | data: JSON.parse(content),
114 | width: this.node.offsetWidth,
115 | height: this.node.offsetHeight
116 | };
117 | ReactDOM.render(, this.node);
118 | } catch (error) {
119 | ReactDOM.render(
120 | ,
121 | this.node
122 | );
123 | }
124 | }
125 |
126 | _onPathChanged() {
127 | this.title.label = this._context.path.split('/').pop();
128 | }
129 | }
130 |
131 | /**
132 | * A widget factory for DocWidget
133 | */
134 | export class DocWidgetFactory extends ABCWidgetFactory {
135 | /**
136 | * Create a new widget instance
137 | */
138 | createNewWidget(context) {
139 | return new DocWidget(context);
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/labextension/src/index.css:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Jupyter Development Team.
3 | Distributed under the terms of the Modified BSD License.
4 | */
5 |
6 | .jp-OutputWidgetPlotly, .jp-DocWidgetPlotly {
7 | width: 100%;
8 | padding: 0;
9 | }
10 |
11 | .jp-OutputWidgetPlotly {
12 | padding-top: 5px;
13 | height: 360px;
14 | }
15 |
16 | .jp-DocWidgetPlotly {
17 | overflow: auto;
18 | }
19 |
20 | .jp-DocWidgetPlotly .jp-mod-error {
21 | width: 100%;
22 | min-height: 100%;
23 | text-align: center;
24 | padding: 10px;
25 | box-sizing: border-box;
26 | }
27 |
28 | .jp-DocWidgetPlotly .jp-mod-error h2 {
29 | font-size: 18px;
30 | font-weight: 500;
31 | padding-bottom: 10px;
32 | }
33 |
34 | .jp-DocWidgetPlotly .jp-mod-error pre {
35 | text-align: left;
36 | padding: 10px;
37 | overflow: hidden;
38 | }
39 |
--------------------------------------------------------------------------------
/labextension/src/output.js:
--------------------------------------------------------------------------------
1 | import { Widget } from '@phosphor/widgets';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import PlotlyComponent from 'jupyterlab_plotly_react';
5 |
6 | const MIME_TYPE = 'application/vnd.plotly.v1+json';
7 | const CLASS_NAME = 'jp-OutputWidgetPlotly';
8 |
9 | /**
10 | * A Phosphor widget for rendering Plotly
11 | */
12 | export class OutputWidget extends Widget {
13 | constructor(options) {
14 | super();
15 | this._mimeType = options.mimeType;
16 | this._data = options.model.data;
17 | this._metadata = options.model.metadata;
18 | this.addClass(CLASS_NAME);
19 | }
20 |
21 | /**
22 | * A message handler invoked on an `'after-attach'` message
23 | */
24 | onAfterAttach(msg) {
25 | /* Render initial data */
26 | this._render();
27 | }
28 |
29 | /**
30 | * A message handler invoked on an `'before-detach'` message
31 | */
32 | onBeforeDetach(msg) {
33 | /* Dispose of resources used by this widget */
34 | ReactDOM.unmountComponentAtNode(this.node);
35 | }
36 |
37 | /**
38 | * A message handler invoked on a `'child-added'` message
39 | */
40 | onChildAdded(msg) {
41 | /* e.g. Inject a static image representation into the mime bundle for
42 | * endering on Github, etc.
43 | */
44 | // renderLibrary.toPng(this.node).then(url => {
45 | // const data = url.split(',')[1];
46 | // this._data.set('image/png', data);
47 | // })
48 | }
49 |
50 | /**
51 | * A message handler invoked on a `'resize'` message
52 | */
53 | onResize(msg) {
54 | /* Re-render on resize */
55 | this._render();
56 | }
57 |
58 | /**
59 | * Render data to DOM node
60 | */
61 | _render() {
62 | const props = {
63 | data: this._data.get(this._mimeType),
64 | metadata: this._metadata.get(this._mimeType),
65 | width: this.node.offsetWidth,
66 | height: this.node.offsetHeight
67 | };
68 | ReactDOM.render(, this.node);
69 | }
70 | }
71 |
72 | export class OutputRenderer {
73 | /**
74 | * The mime types that this OutputRenderer accepts
75 | */
76 | mimeTypes = [MIME_TYPE];
77 |
78 | /**
79 | * Whether the renderer can render given the render options
80 | */
81 | canRender(options) {
82 | return this.mimeTypes.indexOf(options.mimeType) !== -1;
83 | }
84 |
85 | /**
86 | * Render the transformed mime bundle
87 | */
88 | render(options) {
89 | return new OutputWidget(options);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/labextension/src/plugin.js:
--------------------------------------------------------------------------------
1 | import { IRenderMime } from '@jupyterlab/rendermime';
2 | import { IDocumentRegistry } from '@jupyterlab/docregistry';
3 | import { ILayoutRestorer, InstanceTracker } from '@jupyterlab/apputils';
4 | import { toArray, ArrayExt } from '@phosphor/algorithm';
5 | import { OutputRenderer } from './output';
6 | import { DocWidgetFactory } from './doc';
7 | import './index.css';
8 |
9 | /**
10 | * The name of the factory
11 | */
12 | const FACTORY = 'Plotly';
13 |
14 | /**
15 | * Set the extensions associated with application/vnd.plotly.v1+json
16 | */
17 | const EXTENSIONS = ['.plotly', '.plotly.json'];
18 | const DEFAULT_EXTENSIONS = ['.plotly', '.plotly.json'];
19 |
20 | /**
21 | * Activate the extension.
22 | */
23 | function activatePlugin(app, rendermime, registry, restorer) {
24 | /**
25 | * Calculate the index of the renderer in the array renderers
26 | * e.g. Insert this renderer after any renderers with mime type that matches
27 | * "+json"
28 | */
29 | // const index = ArrayExt.findLastIndex(
30 | // toArray(rendermime.mimeTypes()),
31 | // mime => mime.endsWith('+json')
32 | // ) + 1;
33 | /* ...or just insert it at the top */
34 | const index = 0;
35 |
36 | /**
37 | * Add output renderer for application/vnd.plotly.v1+json data
38 | */
39 | rendermime.addRenderer(
40 | {
41 | mimeType: 'application/vnd.plotly.v1+json',
42 | renderer: new OutputRenderer()
43 | },
44 | index
45 | );
46 |
47 | const factory = new DocWidgetFactory({
48 | fileExtensions: EXTENSIONS,
49 | defaultFor: DEFAULT_EXTENSIONS,
50 | name: FACTORY
51 | });
52 |
53 | /**
54 | * Add document renderer for .plotly files
55 | */
56 | registry.addWidgetFactory(factory);
57 |
58 | const tracker = new InstanceTracker({
59 | namespace: 'Plotly',
60 | shell: app.shell
61 | });
62 |
63 | /**
64 | * Handle widget state deserialization
65 | */
66 | restorer.restore(tracker, {
67 | command: 'file-operations:open',
68 | args: widget => ({ path: widget.context.path, factory: FACTORY }),
69 | name: widget => widget.context.path
70 | });
71 |
72 | /**
73 | * Serialize widget state
74 | */
75 | factory.widgetCreated.connect((sender, widget) => {
76 | tracker.add(widget);
77 | /* Notify the instance tracker if restore data needs to update */
78 | widget.context.pathChanged.connect(() => {
79 | tracker.save(widget);
80 | });
81 | });
82 | }
83 |
84 | /**
85 | * Configure jupyterlab plugin
86 | */
87 | const Plugin = {
88 | id: 'jupyter.extensions.Plotly',
89 | requires: [IRenderMime, IDocumentRegistry, ILayoutRestorer],
90 | activate: activatePlugin,
91 | autoStart: true
92 | };
93 |
94 | export default Plugin;
95 |
--------------------------------------------------------------------------------
/nbextension/README.md:
--------------------------------------------------------------------------------
1 | # nbextension
2 |
3 | A Jupyter Notebook extension for rendering Plotly
4 |
5 | ## Prerequisites
6 |
7 | * `notebook@>=4.3.0`
8 |
9 | ## Development
10 |
11 | Install dependencies and build Javascript:
12 |
13 | ```bash
14 | npm install
15 | ```
16 |
17 | Re-build Javascript:
18 |
19 | ```bash
20 | npm run build
21 | ```
22 |
23 | Watch `/src` directory and re-build on changes:
24 |
25 | ```bash
26 | npm run watch
27 | ```
28 |
29 | Manage extension
30 |
31 | ```bash
32 | # Install
33 | npm run extension:install
34 | # Enable
35 | npm run extension:enable
36 | # Disable
37 | npm run extension:disable
38 | # Uninstall
39 | npm run extension:uninstall
40 | ```
41 |
--------------------------------------------------------------------------------
/nbextension/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "jupyterlab_plotly_nbextension",
4 | "version": "0.18.0",
5 | "description": "A Jupyter Notebook extension for rendering Plotly",
6 | "author": "Grant Nestor ",
7 | "main": "src/index.js",
8 | "keywords": [
9 | "jupyter",
10 | "jupyterlab",
11 | "jupyterlab extension"
12 | ],
13 | "scripts": {
14 | "build": "webpack",
15 | "watch": "watch \"npm install\" src ../component --wait 10 --ignoreDotFiles",
16 | "preinstall": "npm install ../component",
17 | "prepublish": "npm run build",
18 | "extension:install": "jupyter nbextension install --symlink --py --sys-prefix jupyterlab_plotly",
19 | "extension:uninstall": "jupyter nbextension uninstall --py --sys-prefix jupyterlab_plotly",
20 | "extension:enable": "jupyter nbextension enable --py --sys-prefix jupyterlab_plotly",
21 | "extension:disable": "jupyter nbextension disable --py --sys-prefix jupyterlab_plotly"
22 | },
23 | "dependencies": {
24 | "react": "^15.3.2",
25 | "react-dom": "^15.3.2"
26 | },
27 | "devDependencies": {
28 | "babel-core": "^6.18.2",
29 | "babel-loader": "^6.4.0",
30 | "babel-preset-latest": "^6.16.0",
31 | "babel-preset-react": "^6.16.0",
32 | "babel-preset-stage-0": "^6.22.0",
33 | "css-loader": "^0.25.0",
34 | "file-loader": "^0.9.0",
35 | "json-loader": "^0.5.4",
36 | "style-loader": "^0.13.1",
37 | "url-loader": "^0.5.7",
38 | "watch": "^1.0.1",
39 | "webpack": "^2.2.0"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/nbextension/src/embed.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Entry point for the unpkg bundle containing custom model definitions.
3 | *
4 | * It differs from the notebook bundle in that it does not need to define a
5 | * dynamic baseURL for the static assets and may load some css that would
6 | * already be loaded by the notebook otherwise.
7 | */
8 |
9 | export { version } from '../package.json';
10 |
--------------------------------------------------------------------------------
/nbextension/src/extension.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This file contains the javascript that is run when the notebook is loaded.
3 | * It contains some requirejs configuration and the `load_ipython_extension`
4 | * which is required for any notebook extension.
5 | */
6 |
7 | /**
8 | * Configure requirejs.
9 | */
10 | if (window.require) {
11 | window.require.config({
12 | map: {
13 | '*': {
14 | jupyterlab_plotly: 'nbextensions/jupyterlab_plotly/index'
15 | }
16 | }
17 | });
18 | }
19 |
20 | /**
21 | * Export the required load_ipython_extention.
22 | */
23 | export function load_ipython_extension() {
24 | define(
25 | [
26 | 'nbextensions/jupyterlab_plotly/index',
27 | 'base/js/namespace',
28 | 'base/js/events',
29 | 'notebook/js/outputarea'
30 | ],
31 | (Extension, Jupyter, events, outputarea) => {
32 | const { notebook } = Jupyter;
33 | const { OutputArea } = outputarea;
34 | Extension.register_renderer(notebook, events, OutputArea);
35 | Extension.render_cells(notebook);
36 | }
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/nbextension/src/index.css:
--------------------------------------------------------------------------------
1 | div.output_subarea.output_Plotly {
2 | max-width: 100%;
3 | padding: 0;
4 | padding-top: 0.4em;
5 | }
6 |
--------------------------------------------------------------------------------
/nbextension/src/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Entry point for the notebook bundle containing custom model definitions.
3 | * Setup notebook base URL
4 | * Some static assets may be required by the custom widget javascript. The base
5 | * url for the notebook is not known at build time and is therefore computed
6 | * dynamically.
7 | */
8 |
9 | __webpack_public_path__ = document
10 | .querySelector('body')
11 | .getAttribute('data-base-url') +
12 | 'nbextensions/jupyterlab_plotly/';
13 |
14 | /**
15 | * Export widget models and views, and the npm package version number.
16 | */
17 | export { register_renderer, render_cells } from './renderer.js';
18 | export { version } from '../package.json';
19 |
--------------------------------------------------------------------------------
/nbextension/src/renderer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import PlotlyComponent from 'jupyterlab_plotly_react';
4 | import './index.css';
5 |
6 | const MIME_TYPE = 'application/vnd.plotly.v1+json';
7 | const CLASS_NAME = 'output_Plotly rendered_html';
8 | const DEFAULT_WIDTH = 840;
9 | const DEFAULT_HEIGHT = 360;
10 |
11 | /**
12 | * Render data to the DOM node
13 | */
14 | function render(props, node) {
15 | ReactDOM.render(, node);
16 | }
17 |
18 | /**
19 | * Handle when an output is cleared or removed
20 | */
21 | function handleClearOutput(event, { cell: { output_area } }) {
22 | /* Get rendered DOM node */
23 | const toinsert = output_area.element.find(`.${CLASS_NAME.split(' ')[0]}`);
24 | /* e.g. Dispose of resources used by renderer library */
25 | if (toinsert[0]) ReactDOM.unmountComponentAtNode(toinsert[0]);
26 | }
27 |
28 | /**
29 | * Handle when a new output is added
30 | */
31 | function handleAddOutput(event, { output, output_area }) {
32 | /* Get rendered DOM node */
33 | const toinsert = output_area.element.find(`.${CLASS_NAME.split(' ')[0]}`);
34 | /** e.g. Inject a static image representation into the mime bundle for
35 | * endering on Github, etc.
36 | */
37 | // if (toinsert[0]) {
38 | // renderLibrary.toPng(toinsert[0]).then(url => {
39 | // const data = url.split(',')[1];
40 | // output_area.outputs
41 | // .filter(output => output.data[MIME_TYPE])
42 | // .forEach(output => {
43 | // output.data['image/png'] = data;
44 | // });
45 | // });
46 | // }
47 | }
48 |
49 | /**
50 | * Register the mime type and append_mime function with the notebook's
51 | * output area
52 | */
53 | export function register_renderer(notebook, events, OutputArea) {
54 | /* A function to render output of 'application/vnd.plotly.v1+json' mime type */
55 | const append_mime = function(data, metadata, element) {
56 | /* Create a DOM node to render to */
57 | const toinsert = this.create_output_subarea(
58 | metadata,
59 | CLASS_NAME,
60 | MIME_TYPE
61 | );
62 | this.keyboard_manager.register_events(toinsert);
63 | /* Render data to DOM node */
64 | const props = {
65 | data,
66 | metadata: metadata[MIME_TYPE],
67 | width: element.width(),
68 | height: DEFAULT_HEIGHT
69 | };
70 | render(props, toinsert[0]);
71 | element.append(toinsert);
72 | const output_area = this;
73 | this.element.on('changed', () => {
74 | if (output_area.outputs.length > 0) ReactDOM.unmountComponentAtNode(toinsert[0]);
75 | });
76 | return toinsert;
77 | };
78 |
79 | /* Handle when an output is cleared or removed */
80 | events.on('clear_output.CodeCell', handleClearOutput);
81 | events.on('delete.Cell', handleClearOutput);
82 |
83 | /* Handle when a new output is added */
84 | events.on('output_added.OutputArea', handleAddOutput);
85 |
86 | /**
87 | * Calculate the index of this renderer in `output_area.display_order`
88 | * e.g. Insert this renderer after any renderers with mime type that matches
89 | * "+json"
90 | */
91 | // const mime_types = output_area.mime_types();
92 | // const json_types = mime_types.filter(mimetype => mimetype.includes('+json'));
93 | // const index = mime_types.lastIndexOf(json_types.pop() + 1);
94 |
95 | /* ...or just insert it at the top */
96 | const index = 0;
97 |
98 | /**
99 | * Register the mime type and append_mime function with output_area
100 | */
101 | OutputArea.prototype.register_mime_type(MIME_TYPE, append_mime, {
102 | /* Is output safe? */
103 | safe: true,
104 | /* Index of renderer in `output_area.display_order` */
105 | index: index
106 | });
107 | }
108 |
109 | /**
110 | * Re-render cells with output data of 'application/vnd.plotly.v1+json' mime type
111 | * on load notebook
112 | */
113 | export function render_cells(notebook) {
114 | /* Get all cells in notebook */
115 | notebook.get_cells().forEach(cell => {
116 | /* If a cell has output data of 'application/vnd.plotly.v1+json' mime type */
117 | if (
118 | cell.output_area &&
119 | cell.output_area.outputs.find(
120 | output => output.data && output.data[MIME_TYPE]
121 | )
122 | ) {
123 | /* Re-render the cell */
124 | notebook.render_cell_output(cell);
125 | }
126 | });
127 | }
128 |
--------------------------------------------------------------------------------
/nbextension/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var version = require('./package.json').version;
3 |
4 | /**
5 | * Custom webpack loaders are generally the same for all webpack bundles, hence
6 | * stored in a separate local variable.
7 | */
8 | var loaders = [
9 | {
10 | test: /\.js$/,
11 | include: [
12 | path.join(__dirname, 'src'),
13 | path.join(__dirname, 'node_modules', 'jupyterlab_plotly_react')
14 | ],
15 | loader: 'babel-loader',
16 | query: { presets: ['latest', 'stage-0', 'react'] }
17 | },
18 | { test: /\.json$/, loader: 'json-loader' },
19 | { test: /\.css$/, loader: 'style-loader!css-loader' },
20 | { test: /\.html$/, loader: 'file-loader' },
21 | { test: /\.(jpg|png|gif)$/, loader: 'file-loader' },
22 | {
23 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
24 | loader: 'url-loader?limit=10000&mimetype=application/font-woff'
25 | },
26 | {
27 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
28 | loader: 'url-loader?limit=10000&mimetype=application/font-woff'
29 | },
30 | {
31 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
32 | loader: 'url-loader?limit=10000&mimetype=application/octet-stream'
33 | },
34 | { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader' },
35 | {
36 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
37 | loader: 'url-loader?limit=10000&mimetype=image/svg+xml'
38 | }
39 | ];
40 |
41 | var base = {
42 | output: {
43 | libraryTarget: 'amd',
44 | devtoolModuleFilenameTemplate: 'webpack:///[absolute-resource-path]'
45 | },
46 | devtool: 'source-map',
47 | module: { loaders },
48 | externals: [
49 | 'nbextensions/jupyterlab_plotly/index',
50 | 'base/js/namespace',
51 | 'base/js/events',
52 | 'notebook/js/outputarea'
53 | ]
54 | };
55 |
56 | module.exports = [
57 | /**
58 | * Notebook extension
59 | *
60 | * This bundle only contains the part of the JavaScript that is run on
61 | * load of the notebook. This section generally only performs
62 | * some configuration for requirejs, and provides the legacy
63 | * "load_ipython_extension" function which is required for any notebook
64 | * extension.
65 | */
66 | Object.assign({}, base, {
67 | entry: path.join(__dirname, 'src', 'extension.js'),
68 | output: Object.assign({}, base.output, {
69 | filename: 'extension.js',
70 | path: path.join(
71 | __dirname,
72 | '..',
73 | 'jupyterlab_plotly',
74 | 'static'
75 | )
76 | })
77 | }),
78 | /**
79 | * Bundle for the notebook containing the custom widget views and models
80 | *
81 | * This bundle contains the implementation for the custom widget views and
82 | * custom widget.
83 | *
84 | * It must be an amd module
85 | */
86 | Object.assign({}, base, {
87 | entry: path.join(__dirname, 'src', 'index.js'),
88 | output: Object.assign({}, base.output, {
89 | filename: 'index.js',
90 | path: path.join(
91 | __dirname,
92 | '..',
93 | 'jupyterlab_plotly',
94 | 'static'
95 | )
96 | })
97 | }),
98 | /**
99 | * Embeddable jupyterlab_plotly bundle
100 | *
101 | * This bundle is generally almost identical to the notebook bundle
102 | * containing the custom widget views and models.
103 | *
104 | * The only difference is in the configuration of the webpack public path
105 | * for the static assets.
106 | *
107 | * It will be automatically distributed by unpkg to work with the static
108 | * widget embedder.
109 | *
110 | * The target bundle is always `lib/index.js`, which is the path required
111 | * by the custom widget embedder.
112 | */
113 | Object.assign({}, base, {
114 | entry: './src/embed.js',
115 | output: Object.assign({}, base.output, {
116 | filename: 'index.js',
117 | path: path.join(__dirname, 'embed'),
118 | publicPath: 'https://unpkg.com/jupyterlab_plotly@' +
119 | version +
120 | '/lib/'
121 | })
122 | })
123 | ];
124 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 | from setupbase import create_cmdclass, install_npm
3 |
4 | cmdclass = create_cmdclass(['labextension', 'nbextension'])
5 | cmdclass['labextension'] = install_npm('labextension')
6 | cmdclass['nbextension'] = install_npm('nbextension')
7 |
8 | setup_args = dict(
9 | name = 'jupyterlab_plotly',
10 | version = '0.18.0',
11 | packages = ['jupyterlab_plotly'],
12 | author = 'Grant Nestor',
13 | author_email = 'grantnestor@gmail.com',
14 | url = 'http://jupyter.org',
15 | license = 'BSD',
16 | platforms = "Linux, Mac OS X, Windows",
17 | keywords = [
18 | 'ipython',
19 | 'jupyter',
20 | 'jupyterlab',
21 | 'extension',
22 | 'renderer',
23 | 'plotly'
24 | ],
25 | classifiers = [
26 | 'Intended Audience :: Developers',
27 | 'Intended Audience :: System Administrators',
28 | 'Intended Audience :: Science/Research',
29 | 'License :: OSI Approved :: BSD License',
30 | 'Programming Language :: Python',
31 | 'Programming Language :: Python :: 2.7',
32 | 'Programming Language :: Python :: 3',
33 | 'Programming Language :: Python :: 3.3',
34 | 'Programming Language :: Python :: 3.4',
35 | 'Programming Language :: Python :: 3.5',
36 | ],
37 | cmdclass = cmdclass,
38 | install_requires = [
39 | 'jupyterlab>=0.18.0',
40 | 'notebook>=4.3.0',
41 | 'ipython>=1.0.0'
42 | ]
43 | )
44 |
45 | if __name__ == '__main__':
46 | setup(**setup_args)
47 |
--------------------------------------------------------------------------------
/setupbase.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf-8
3 |
4 | # Copyright (c) Jupyter Development Team.
5 | # Distributed under the terms of the Modified BSD License.
6 |
7 | import os
8 | from os.path import join as pjoin
9 | import functools
10 | import pipes
11 |
12 | from setuptools import Command
13 | from setuptools.command.build_py import build_py
14 | from setuptools.command.sdist import sdist
15 | from setuptools.command.develop import develop
16 | from setuptools.command.bdist_egg import bdist_egg
17 | from distutils import log
18 | from subprocess import check_call
19 | import sys
20 |
21 | try:
22 | from wheel.bdist_wheel import bdist_wheel
23 | except ImportError:
24 | bdist_wheel = None
25 |
26 | if sys.platform == 'win32':
27 | from subprocess import list2cmdline
28 | else:
29 | def list2cmdline(cmd_list):
30 | return ' '.join(map(pipes.quote, cmd_list))
31 |
32 |
33 | __version__ = '0.1.0'
34 |
35 | # ---------------------------------------------------------------------------
36 | # Top Level Variables
37 | # ---------------------------------------------------------------------------
38 |
39 |
40 | here = os.path.abspath(os.path.dirname(sys.argv[0]))
41 | is_repo = os.path.exists(pjoin(here, '.git'))
42 | node_modules = pjoin(here, 'node_modules')
43 | npm_path = ':'.join([
44 | pjoin(here, 'node_modules', '.bin'),
45 | os.environ.get('PATH', os.defpath),
46 | ])
47 |
48 | if "--skip-npm" in sys.argv:
49 | print("Skipping npm install as requested.")
50 | skip_npm = True
51 | sys.argv.remove("--skip-npm")
52 | else:
53 | skip_npm = False
54 |
55 | # ---------------------------------------------------------------------------
56 | # Public Functions
57 | # ---------------------------------------------------------------------------
58 |
59 |
60 | def get_data_files(top):
61 | """Get data files"""
62 |
63 | data_files = []
64 | ntrim = len(here + os.path.sep)
65 |
66 | for (d, dirs, filenames) in os.walk(top):
67 | data_files.append((
68 | d[ntrim:],
69 | [pjoin(d, f) for f in filenames]
70 | ))
71 | return data_files
72 |
73 |
74 | def find_packages(top):
75 | """
76 | Find all of the packages.
77 | """
78 | packages = []
79 | for d, _, _ in os.walk(top):
80 | if os.path.exists(pjoin(d, '__init__.py')):
81 | packages.append(d.replace(os.path.sep, '.'))
82 |
83 |
84 | def create_cmdclass(wrappers=None, data_dirs=None):
85 | """Create a command class with the given optional wrappers.
86 | Parameters
87 | ----------
88 | wrappers: list(str), optional
89 | The cmdclass names to run before running other commands
90 | data_dirs: list(str), optional.
91 | The directories containing static data.
92 | """
93 | egg = bdist_egg if 'bdist_egg' in sys.argv else bdist_egg_disabled
94 | wrappers = wrappers or []
95 | data_dirs = data_dirs or []
96 | wrapper = functools.partial(wrap_command, wrappers, data_dirs)
97 | cmdclass = dict(
98 | build_py=wrapper(build_py, strict=is_repo),
99 | sdist=wrapper(sdist, strict=True),
100 | bdist_egg=egg,
101 | develop=wrapper(develop, strict=True)
102 | )
103 | if bdist_wheel:
104 | cmdclass['bdist_wheel'] = wrapper(bdist_wheel, strict=True)
105 | return cmdclass
106 |
107 |
108 | def run(cmd, *args, **kwargs):
109 | """Echo a command before running it. Defaults to repo as cwd"""
110 | log.info('> ' + list2cmdline(cmd))
111 | kwargs.setdefault('cwd', here)
112 | kwargs.setdefault('shell', sys.platform == 'win32')
113 | if not isinstance(cmd, list):
114 | cmd = cmd.split()
115 | return check_call(cmd, *args, **kwargs)
116 |
117 |
118 | def is_stale(target, source):
119 | """Test whether the target file/directory is stale based on the source
120 | file/directory.
121 | """
122 | if not os.path.exists(target):
123 | return True
124 | return mtime(target) < mtime(source)
125 |
126 |
127 | class BaseCommand(Command):
128 | """Empty command because Command needs subclasses to override too much"""
129 | user_options = []
130 |
131 | def initialize_options(self):
132 | pass
133 |
134 | def finalize_options(self):
135 | pass
136 |
137 | def get_inputs(self):
138 | return []
139 |
140 | def get_outputs(self):
141 | return []
142 |
143 |
144 | def combine_commands(*commands):
145 | """Return a Command that combines several commands."""
146 |
147 | class CombinedCommand(Command):
148 |
149 | def initialize_options(self):
150 | self.commands = []
151 | for C in commands:
152 | self.commands.append(C(self.distribution))
153 | for c in self.commands:
154 | c.initialize_options()
155 |
156 | def finalize_options(self):
157 | for c in self.commands:
158 | c.finalize_options()
159 |
160 | def run(self):
161 | for c in self.commands:
162 | c.run()
163 | return CombinedCommand
164 |
165 |
166 | def mtime(path):
167 | """shorthand for mtime"""
168 | return os.stat(path).st_mtime
169 |
170 |
171 | def install_npm(path=None, build_dir=None, source_dir=None, build_cmd='build'):
172 | """Return a Command for managing an npm installation.
173 | Parameters
174 | ----------
175 | path: str, optional
176 | The base path of the node package. Defaults to the repo root.
177 | build_dir: str, optional
178 | The target build directory. If this and source_dir are given,
179 | the JavaScript will only be build if necessary.
180 | source_dir: str, optional
181 | The source code directory.
182 | build_cmd: str, optional
183 | The npm command to build assets to the build_dir.
184 | """
185 |
186 | class NPM(BaseCommand):
187 | description = 'install package.json dependencies using npm'
188 |
189 | def run(self):
190 | if skip_npm:
191 | log.info('Skipping npm-installation')
192 | return
193 | node_package = path or here
194 | node_modules = pjoin(node_package, 'node_modules')
195 |
196 | if not which("npm"):
197 | log.error("`npm` unavailable. If you're running this command "
198 | "using sudo, make sure `npm` is availble to sudo")
199 | return
200 | if is_stale(node_modules, pjoin(node_package, 'package.json')):
201 | log.info('Installing build dependencies with npm. This may '
202 | 'take a while...')
203 | run(['npm', 'install'], cwd=node_package)
204 | if build_dir and source_dir:
205 | should_build = is_stale(build_dir, source_dir)
206 | else:
207 | should_build = True
208 | if should_build:
209 | run(['npm', 'run', build_cmd], cwd=node_package)
210 |
211 | return NPM
212 |
213 |
214 | # `shutils.which` function copied verbatim from the Python-3.3 source.
215 | def which(cmd, mode=os.F_OK | os.X_OK, path=None):
216 | """Given a command, mode, and a PATH string, return the path which
217 | conforms to the given mode on the PATH, or None if there is no such
218 | file.
219 | `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
220 | of os.environ.get("PATH"), or can be overridden with a custom search
221 | path.
222 | """
223 |
224 | # Check that a given file can be accessed with the correct mode.
225 | # Additionally check that `file` is not a directory, as on Windows
226 | # directories pass the os.access check.
227 | def _access_check(fn, mode):
228 | return (os.path.exists(fn) and os.access(fn, mode) and
229 | not os.path.isdir(fn))
230 |
231 | # Short circuit. If we're given a full path which matches the mode
232 | # and it exists, we're done here.
233 | if _access_check(cmd, mode):
234 | return cmd
235 |
236 | path = (path or os.environ.get("PATH", os.defpath)).split(os.pathsep)
237 |
238 | if sys.platform == "win32":
239 | # The current directory takes precedence on Windows.
240 | if os.curdir not in path:
241 | path.insert(0, os.curdir)
242 |
243 | # PATHEXT is necessary to check on Windows.
244 | pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
245 | # See if the given file matches any of the expected path extensions.
246 | # This will allow us to short circuit when given "python.exe".
247 | matches = [cmd for ext in pathext if cmd.lower().endswith(ext.lower())]
248 | # If it does match, only test that one, otherwise we have to try
249 | # others.
250 | files = [cmd] if matches else [cmd + ext.lower() for ext in pathext]
251 | else:
252 | # On other platforms you don't have things like PATHEXT to tell you
253 | # what file suffixes are executable, so just pass on cmd as-is.
254 | files = [cmd]
255 |
256 | seen = set()
257 | for dir in path:
258 | dir = os.path.normcase(dir)
259 | if dir not in seen:
260 | seen.add(dir)
261 | for thefile in files:
262 | name = os.path.join(dir, thefile)
263 | if _access_check(name, mode):
264 | return name
265 | return None
266 |
267 |
268 | # ---------------------------------------------------------------------------
269 | # Private Functions
270 | # ---------------------------------------------------------------------------
271 |
272 |
273 | def wrap_command(cmds, data_dirs, cls, strict=True):
274 | """Wrap a setup command
275 | Parameters
276 | ----------
277 | cmds: list(str)
278 | The names of the other commands to run prior to the command.
279 | strict: boolean, optional
280 | Wether to raise errors when a pre-command fails.
281 | """
282 | class WrappedCommand(cls):
283 |
284 | def run(self):
285 | if not getattr(self, 'uninstall', None):
286 | try:
287 | [self.run_command(cmd) for cmd in cmds]
288 | except Exception:
289 | if strict:
290 | raise
291 | else:
292 | pass
293 |
294 | result = cls.run(self)
295 | data_files = []
296 | for dname in data_dirs:
297 | data_files.extend(get_data_files(dname))
298 | # update data-files in case this created new files
299 | self.distribution.data_files = data_files
300 | return result
301 | return WrappedCommand
302 |
303 |
304 | class bdist_egg_disabled(bdist_egg):
305 | """Disabled version of bdist_egg
306 | Prevents setup.py install performing setuptools' default easy_install,
307 | which it should never ever do.
308 | """
309 |
310 | def run(self):
311 | sys.exit("Aborting implicit building of eggs. Use `pip install .` " +
312 | " to install from source.")
313 |
--------------------------------------------------------------------------------
/uninstall.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | nbExtFlags=$1
3 |
4 | jupyter lab --version
5 | if [ $? -eq 0 ]; then
6 | jupyter labextension uninstall --py $nbExtFlags jupyterlab_test
7 | fi
8 |
9 | jupyter notebook --version
10 | if [ $? -eq 0 ]; then
11 | jupyter nbextension uninstall --py $nbExtFlags jupyterlab_test
12 | fi
13 |
14 | pip --version
15 | if [ $? -eq 0 ]; then
16 | pip uninstall -v .
17 | else
18 | echo "'pip --version' failed, therefore pip is not installed. In order to perform
19 | an install of jupyterlab_plotly you must have both pip and npm installed on
20 | your machine! See https://packaging.python.org/installing/ for installation instructions."
21 | fi
22 |
--------------------------------------------------------------------------------