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