Skip to content

Commit 00a9e7e

Browse files
swojitdomoritz
authored andcommitted
Add Vega2 extension
1 parent 236f4fe commit 00a9e7e

File tree

10 files changed

+697
-12
lines changed

10 files changed

+697
-12
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This is a [monorepo](https://github.com/lerna/lerna#what-does-a-lerna-repo-look-
1212
| [geojson-extension](packages/geojson-extension) | `application/geo+json` | `.geojson`, `.geo.json` |
1313
| [katex-extension](packages/katex-extension) | N/A | N/A |
1414
| [plotly-extension](packages/plotly-extension) | `application/vnd.plotly.v1+json` | `.plotly`, `.plotly.json` |
15+
| [vega2-extension](packages/vega2-extension) | `application/vnd.vega.v2+json`, `application/vnd.vegalite.v1+json`| `.vg`, `.vl`, `.vg.json`, `.vl.json`, `.vega`, `.vegalite` |
1516
| [vega3-extension](packages/vega3-extension) | `application/vnd.vega.v3+json`, `application/vnd.vegalite.v2+json`| `.vg`, `.vl`, `.vg.json`, `.vl.json`, `.vega`, `.vegalite` |
1617

1718
## Install
@@ -20,6 +21,7 @@ This is a [monorepo](https://github.com/lerna/lerna#what-does-a-lerna-repo-look-
2021
* geojson-extension: `jupyter labextension install @jupyterlab/geojson-extension`
2122
* katex-extension: `jupyter labextension install @jupyterlab/katex-extension`
2223
* plotly-extension: `jupyter labextension install @jupyterlab/plotly-extension`
24+
* vega2-extension: `jupyter labextension install @jupyterlab/vega2-extension`
2325
* vega3-extension: `jupyter labextension install @jupyterlab/vega3-extension`
2426

2527
## Contributing

packages/vega2-extension/README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# vega2-extension
2+
3+
A JupyterLab extension for rendering Vega 2 and Vega-lite 1.
4+
5+
**Vega 2 is deprecated. The latest version comes by default with JupyterLab. Only use this extension if you have specifications that do not work with the latest version.**
6+
7+
## Prerequisites
8+
9+
* JupyterLab ^0.30.0
10+
11+
## Usage
12+
13+
To render Vega 2 or Vega-lite 1 output in IPython:
14+
15+
```python
16+
from IPython.display import display
17+
18+
display({
19+
"application/vnd.vegalite.v1+json": {
20+
"$schema": "https://vega.github.io/schema/vega-lite/v1.json",
21+
"description": "A simple bar chart with embedded data.",
22+
"data": {
23+
"values": [
24+
{"a": "A", "b": 28}, {"a": "B", "b": 55}, {"a": "C", "b": 43},
25+
{"a": "D", "b": 91}, {"a": "E", "b": 81}, {"a": "F", "b": 53},
26+
{"a": "G", "b": 19}, {"a": "H", "b": 87}, {"a": "I", "b": 52}
27+
]
28+
},
29+
"mark": "bar",
30+
"encoding": {
31+
"x": {"field": "a", "type": "ordinal"},
32+
"y": {"field": "b", "type": "quantitative"}
33+
}
34+
}
35+
}, raw=True)
36+
```
37+
38+
To render a `.vg`, `.vl`, `.vg.json`, `.vl.json` file, simply open it:
39+
40+
## Install
41+
42+
```bash
43+
jupyter labextension install @jupyterlab/vega2-extension
44+
```
45+
46+
## Development
47+
48+
```bash
49+
# Clone the repo to your local environment
50+
git clone https://github.com/jupyterlab/jupyter-renderers.git
51+
cd jupyter-renderers
52+
# Install dependencies
53+
yarn install
54+
# Build Typescript source
55+
yarn run build
56+
# Link your development version of the extension with JupyterLab
57+
jupyter labextension link packages/vega2-extension
58+
# Rebuild Typescript source after making changes
59+
yarn run build
60+
# Rebuild JupyterLab after making any changes
61+
jupyter lab build
62+
```
63+
64+
You can watch the jupyter-renderers directory and run JupyterLab in watch mode to watch for changes in the extension's source and automatically rebuild the extension and application.
65+
66+
```bash
67+
# Run jupyterlab in watch mode in one terminal tab
68+
jupyter lab --watch
69+
# Watch the jupyter-renderers directory
70+
yarn run watch
71+
```
72+
73+
## Uninstall
74+
75+
```bash
76+
jupyter labextension uninstall @jupyterlab/vega2-extension
77+
```
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"name": "@jupyterlab/vega2-extension",
3+
"version": "0.15.5",
4+
"description": "JupyterLab - Vega 2 and Vega-Lite 1 Mime Renderer Extension",
5+
"homepage": "https://github.com/jupyterlab/jupyterlab",
6+
"bugs": {
7+
"url": "https://github.com/jupyterlab/jupyterlab/issues"
8+
},
9+
"license": "BSD-3-Clause",
10+
"author": "Project Jupyter",
11+
"files": [
12+
"lib/*.d.ts",
13+
"lib/*.js.map",
14+
"lib/*.js",
15+
"style/*.css"
16+
],
17+
"main": "lib/index.js",
18+
"types": "lib/index.d.ts",
19+
"directories": {
20+
"lib": "lib/"
21+
},
22+
"repository": {
23+
"type": "git",
24+
"url": "https://github.com/jupyterlab/jupyterlab.git"
25+
},
26+
"scripts": {
27+
"build": "tsc",
28+
"clean": "rimraf lib",
29+
"prepublishOnly": "npm run build",
30+
"watch": "tsc -w"
31+
},
32+
"dependencies": {
33+
"@jupyterlab/rendermime-interfaces": "^1.0.6",
34+
"@phosphor/coreutils": "^1.3.0",
35+
"@phosphor/widgets": "^1.5.0",
36+
"vega-embed-v2": "^0.0.3"
37+
},
38+
"devDependencies": {
39+
"@types/node": "~8.0.47",
40+
"rimraf": "~2.6.2",
41+
"typescript": "~2.7.2"
42+
},
43+
"jupyterlab": {
44+
"mimeExtension": true
45+
}
46+
}
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
/*-----------------------------------------------------------------------------
2+
| Copyright (c) Jupyter Development Team.
3+
| Distributed under the terms of the Modified BSD License.
4+
|----------------------------------------------------------------------------*/
5+
6+
import {
7+
JSONObject, ReadonlyJSONObject, JSONValue
8+
} from '@phosphor/coreutils';
9+
10+
import {
11+
Widget
12+
} from '@phosphor/widgets';
13+
14+
import {
15+
IRenderMime
16+
} from '@jupyterlab/rendermime-interfaces';
17+
18+
/**
19+
* Import vega-embed in this manner due to how it is exported.
20+
*/
21+
import embed = require('vega-embed-v2');
22+
23+
24+
import '../style/index.css';
25+
26+
27+
/**
28+
* The CSS class to add to the Vega and Vega-Lite widget.
29+
*/
30+
const VEGA_COMMON_CLASS = 'jp-RenderedVegaCommon';
31+
32+
/**
33+
* The CSS class to add to the Vega.
34+
*/
35+
const VEGA_CLASS = 'jp-RenderedVega';
36+
37+
/**
38+
* The CSS class to add to the Vega-Lite.
39+
*/
40+
const VEGALITE_CLASS = 'jp-RenderedVegaLite';
41+
42+
/**
43+
* The MIME type for Vega.
44+
*
45+
* #### Notes
46+
* The version of this follows the major version of Vega.
47+
*/
48+
export
49+
const VEGA_MIME_TYPE = 'application/vnd.vega.v2+json';
50+
51+
/**
52+
* The MIME type for Vega-Lite.
53+
*
54+
* #### Notes
55+
* The version of this follows the major version of Vega-Lite.
56+
*/
57+
export
58+
const VEGALITE_MIME_TYPE = 'application/vnd.vegalite.v1+json';
59+
60+
61+
/**
62+
* A widget for rendering Vega or Vega-Lite data, for usage with rendermime.
63+
*/
64+
export
65+
class RenderedVega extends Widget implements IRenderMime.IRenderer {
66+
/**
67+
* Create a new widget for rendering Vega/Vega-Lite.
68+
*/
69+
constructor(options: IRenderMime.IRendererOptions) {
70+
super();
71+
this.addClass(VEGA_COMMON_CLASS);
72+
73+
// Handle things related to the MIME type.
74+
let mimeType = this._mimeType = options.mimeType;
75+
if (mimeType === VEGA_MIME_TYPE) {
76+
this.addClass(VEGA_CLASS);
77+
this._mode = 'vega';
78+
} else {
79+
this.addClass(VEGALITE_CLASS);
80+
this._mode = 'vega-lite';
81+
}
82+
}
83+
84+
/**
85+
* Render Vega/Vega-Lite into this widget's node.
86+
*/
87+
renderModel(model: IRenderMime.IMimeModel): Promise<void> {
88+
89+
let data = model.data[this._mimeType] as ReadonlyJSONObject;
90+
let updatedData: JSONObject;
91+
if (this._mode === 'vega-lite') {
92+
updatedData = Private.updateVegaLiteDefaults(data);
93+
} else {
94+
updatedData = data as JSONObject;
95+
}
96+
97+
let embedSpec = {
98+
mode: this._mode,
99+
spec: updatedData
100+
};
101+
102+
return Private.ensureMod().then(embedFunc => {
103+
return new Promise<void>((resolve, reject) => {
104+
embedFunc(this.node, embedSpec, (error: any, result: any): any => {
105+
if (error) {
106+
return reject(error);
107+
}
108+
109+
// Save png data in MIME bundle along with original MIME data.
110+
if (!model.data['image/png']) {
111+
let imageData = result.view.toImageURL().split(',')[1] as JSONValue;
112+
let newData = {...(model.data), 'image/png': imageData};
113+
model.setData({ data: newData });
114+
}
115+
resolve(undefined);
116+
});
117+
});
118+
});
119+
}
120+
121+
private _mimeType: string;
122+
private _mode: string;
123+
}
124+
125+
126+
/**
127+
* A mime renderer factory for vega data.
128+
*/
129+
export
130+
const rendererFactory: IRenderMime.IRendererFactory = {
131+
safe: true,
132+
mimeTypes: [VEGA_MIME_TYPE, VEGALITE_MIME_TYPE],
133+
createRenderer: options => new RenderedVega(options)
134+
};
135+
136+
const extension: IRenderMime.IExtension = {
137+
id: '@jupyterlab/vega2-extension:factory',
138+
rendererFactory,
139+
rank: 60,
140+
dataType: 'json',
141+
documentWidgetFactoryOptions: [{
142+
name: 'Vega 2',
143+
primaryFileType: 'vega2',
144+
fileTypes: ['vega2', 'json'],
145+
defaultFor: ['vega2']
146+
},
147+
{
148+
name: 'Vega-Lite 1',
149+
primaryFileType: 'vega-lite1',
150+
fileTypes: ['vega-lite1', 'json'],
151+
defaultFor: ['vega-lite1']
152+
}],
153+
fileTypes: [{
154+
mimeTypes: [VEGA_MIME_TYPE],
155+
name: 'vega2',
156+
extensions: ['.vg', '.vg.json', '.vega'],
157+
iconClass: 'jp-MaterialIcon jp-VegaIcon',
158+
},
159+
{
160+
mimeTypes: [VEGALITE_MIME_TYPE],
161+
name: 'vega-lite1',
162+
extensions: ['.vl', '.vl.json', '.vegalite'],
163+
iconClass: 'jp-MaterialIcon jp-VegaIcon',
164+
}]
165+
};
166+
167+
export default extension;
168+
169+
170+
171+
/**
172+
* Namespace for module privates.
173+
*/
174+
namespace Private {
175+
176+
/**
177+
* Default cell config for Vega-Lite.
178+
*/
179+
const defaultCellConfig: JSONObject = {
180+
'width': 400,
181+
'height': 400 / 1.5
182+
};
183+
184+
/**
185+
* The embed module import.
186+
*/
187+
let mod: typeof embed;
188+
189+
/**
190+
* Initialize the vega-embed module.
191+
*/
192+
export
193+
function ensureMod(): Promise<typeof embed> {
194+
return new Promise((resolve, reject) => {
195+
if (mod !== undefined) {
196+
resolve(mod);
197+
return;
198+
}
199+
(require as any).ensure(['vega-embed-v2'], (require: NodeRequire) => {
200+
mod = require('vega-embed-v2');
201+
resolve(mod);
202+
},
203+
(err: any) => {
204+
reject(err);
205+
},
206+
'vega2'
207+
);
208+
});
209+
}
210+
211+
/**
212+
* Apply the default cell config to the spec in place.
213+
*
214+
* #### Notes
215+
* This carefully does a shallow copy to avoid copying the potentially
216+
* large data.
217+
*/
218+
export
219+
function updateVegaLiteDefaults(spec: ReadonlyJSONObject): JSONObject {
220+
let config = spec.config as JSONObject;
221+
if (!config) {
222+
return {...{'config': {'cell': defaultCellConfig}}, ...spec};
223+
}
224+
let cell = config.cell as JSONObject;
225+
if (cell) {
226+
return {
227+
...{'config': {...{'cell': {...defaultCellConfig, ...cell}}}, ...config},
228+
...spec
229+
};
230+
} else {
231+
return {...{'config': {...{'cell': {...defaultCellConfig}}}, ...config}, ...spec};
232+
}
233+
}
234+
}

0 commit comments

Comments
 (0)