index.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. /*-----------------------------------------------------------------------------
  2. | Copyright (c) Jupyter Development Team.
  3. | Distributed under the terms of the Modified BSD License.
  4. |----------------------------------------------------------------------------*/
  5. import { JSONObject } from '@phosphor/coreutils';
  6. import { Widget } from '@phosphor/widgets';
  7. import { IRenderMime } from '@jupyterlab/rendermime-interfaces';
  8. import * as VegaModuleType from 'vega-embed';
  9. import '../style/index.css';
  10. /**
  11. * The CSS class to add to the Vega and Vega-Lite widget.
  12. */
  13. const VEGA_COMMON_CLASS = 'jp-RenderedVegaCommon4';
  14. /**
  15. * The CSS class to add to the Vega.
  16. */
  17. const VEGA_CLASS = 'jp-RenderedVega4';
  18. /**
  19. * The CSS class to add to the Vega-Lite.
  20. */
  21. const VEGALITE_CLASS = 'jp-RenderedVegaLite2';
  22. /**
  23. * The MIME type for Vega.
  24. *
  25. * #### Notes
  26. * The version of this follows the major version of Vega.
  27. */
  28. export const VEGA_MIME_TYPE = 'application/vnd.vega.v4+json';
  29. /**
  30. * The MIME type for Vega-Lite.
  31. *
  32. * #### Notes
  33. * The version of this follows the major version of Vega-Lite.
  34. */
  35. export const VEGALITE_MIME_TYPE = 'application/vnd.vegalite.v2+json';
  36. /**
  37. * A widget for rendering Vega or Vega-Lite data, for usage with rendermime.
  38. */
  39. export class RenderedVega extends Widget implements IRenderMime.IRenderer {
  40. private _result: VegaModuleType.Result;
  41. /**
  42. * Create a new widget for rendering Vega/Vega-Lite.
  43. */
  44. constructor(options: IRenderMime.IRendererOptions) {
  45. super();
  46. this._mimeType = options.mimeType;
  47. this._resolver = options.resolver;
  48. this.addClass(VEGA_COMMON_CLASS);
  49. this.addClass(
  50. this._mimeType === VEGA_MIME_TYPE ? VEGA_CLASS : VEGALITE_CLASS
  51. );
  52. }
  53. /**
  54. * Render Vega/Vega-Lite into this widget's node.
  55. */
  56. async renderModel(model: IRenderMime.IMimeModel): Promise<void> {
  57. const spec = model.data[this._mimeType] as JSONObject;
  58. const metadata = model.metadata[this._mimeType] as {
  59. embed_options?: VegaModuleType.EmbedOptions;
  60. };
  61. const embedOptions =
  62. metadata && metadata.embed_options ? metadata.embed_options : {};
  63. const mode: VegaModuleType.Mode =
  64. this._mimeType === VEGA_MIME_TYPE ? 'vega' : 'vega-lite';
  65. const vega =
  66. Private.vega != null ? Private.vega : await Private.ensureVega();
  67. const path = await this._resolver.resolveUrl('');
  68. const baseURL = await this._resolver.getDownloadUrl(path);
  69. const el = document.createElement('div');
  70. // clear the output before attaching a chart
  71. this.node.innerHTML = '';
  72. this.node.appendChild(el);
  73. this._result = await vega.default(el, spec, {
  74. actions: true,
  75. defaultStyle: true,
  76. ...embedOptions,
  77. mode,
  78. loader: {
  79. baseURL,
  80. http: { credentials: 'same-origin' }
  81. }
  82. });
  83. if (model.data['image/png']) {
  84. return;
  85. }
  86. // Add png representation of vega chart to output
  87. const imageURL = await this._result.view.toImageURL('png');
  88. model.setData({
  89. data: { ...model.data, 'image/png': imageURL.split(',')[1] }
  90. });
  91. }
  92. dispose(): void {
  93. if (this._result) {
  94. this._result.view.finalize();
  95. }
  96. super.dispose();
  97. }
  98. private _mimeType: string;
  99. private _resolver: IRenderMime.IResolver;
  100. }
  101. /**
  102. * A mime renderer factory for vega data.
  103. */
  104. export const rendererFactory: IRenderMime.IRendererFactory = {
  105. safe: true,
  106. mimeTypes: [VEGA_MIME_TYPE, VEGALITE_MIME_TYPE],
  107. createRenderer: options => new RenderedVega(options)
  108. };
  109. const extension: IRenderMime.IExtension = {
  110. id: '@jupyterlab/vega-extension:factory',
  111. rendererFactory,
  112. rank: 50, // prefer over vega 2 extension
  113. dataType: 'json',
  114. documentWidgetFactoryOptions: [
  115. {
  116. name: 'Vega',
  117. primaryFileType: 'vega4',
  118. fileTypes: ['vega4', 'json'],
  119. defaultFor: ['vega4']
  120. },
  121. {
  122. name: 'Vega-Lite',
  123. primaryFileType: 'vega-lite2',
  124. fileTypes: ['vega-lite2', 'json'],
  125. defaultFor: ['vega-lite2']
  126. }
  127. ],
  128. fileTypes: [
  129. {
  130. mimeTypes: [VEGA_MIME_TYPE],
  131. name: 'vega4',
  132. extensions: ['.vg', '.vg.json', '.vega'],
  133. iconClass: 'jp-MaterialIcon jp-VegaIcon'
  134. },
  135. {
  136. mimeTypes: [VEGALITE_MIME_TYPE],
  137. name: 'vega-lite2',
  138. extensions: ['.vl', '.vl.json', '.vegalite'],
  139. iconClass: 'jp-MaterialIcon jp-VegaIcon'
  140. }
  141. ]
  142. };
  143. export default extension;
  144. /**
  145. * A namespace for private module data.
  146. */
  147. namespace Private {
  148. /**
  149. * A cached reference to the vega library.
  150. */
  151. export let vega: typeof VegaModuleType;
  152. /**
  153. * A Promise for the initial load of vega.
  154. */
  155. export let vegaReady: Promise<typeof VegaModuleType>;
  156. /**
  157. * Lazy-load and cache the vega-embed library
  158. */
  159. export function ensureVega(): Promise<typeof VegaModuleType> {
  160. if (vegaReady) {
  161. return vegaReady;
  162. }
  163. vegaReady = new Promise((resolve, reject) => {
  164. require.ensure(
  165. ['vega-embed'],
  166. // see https://webpack.js.org/api/module-methods/#require-ensure
  167. // this argument MUST be named `require` for the WebPack parser
  168. require => {
  169. vega = require('vega-embed') as typeof VegaModuleType;
  170. resolve(vega);
  171. },
  172. (error: any) => {
  173. console.error(error);
  174. reject();
  175. },
  176. 'vega'
  177. );
  178. });
  179. return vegaReady;
  180. }
  181. }