index.ts 5.5 KB

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