plotly-renderer.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  4. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  5. return new (P || (P = Promise))(function (resolve, reject) {
  6. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  7. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  8. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  9. step((generator = generator.apply(thisArg, _arguments || [])).next());
  10. });
  11. };
  12. import { Widget } from "@lumino/widgets";
  13. import "../style/index.css";
  14. /**
  15. * The CSS class to add to the Plotly Widget.
  16. */
  17. const CSS_CLASS = "jp-RenderedPlotly";
  18. /**
  19. * The CSS class for a Plotly icon.
  20. */
  21. const CSS_ICON_CLASS = "jp-MaterialIcon jp-PlotlyIcon";
  22. /**
  23. * The MIME type for Plotly.
  24. * The version of this follows the major version of Plotly.
  25. */
  26. export const MIME_TYPE = "application/vnd.plotly.v1+json";
  27. export class RenderedPlotly extends Widget {
  28. /**
  29. * Create a new widget for rendering Plotly.
  30. */
  31. constructor(options) {
  32. super();
  33. this.addClass(CSS_CLASS);
  34. this._mimeType = options.mimeType;
  35. // Create image element
  36. this._img_el = document.createElement("img");
  37. this._img_el.className = "plot-img";
  38. this.node.appendChild(this._img_el);
  39. // Install image hover callback
  40. this._img_el.addEventListener("mouseenter", (event) => {
  41. this.createGraph(this._model);
  42. });
  43. }
  44. /**
  45. * Render Plotly into this widget's node.
  46. */
  47. renderModel(model) {
  48. if (this.hasGraphElement()) {
  49. // We already have a graph, don't overwrite it
  50. return Promise.resolve();
  51. }
  52. // Save off reference to model so that we can regenerate the plot later
  53. this._model = model;
  54. // Check for PNG data in mime bundle
  55. const png_data = model.data["image/png"];
  56. if (png_data !== undefined && png_data !== null) {
  57. // We have PNG data, use it
  58. this.updateImage(png_data);
  59. return Promise.resolve();
  60. }
  61. else {
  62. // Create a new graph
  63. return this.createGraph(model);
  64. }
  65. }
  66. hasGraphElement() {
  67. // Check for the presence of the .plot-container element that plotly.js
  68. // places at the top of the figure structure
  69. return this.node.querySelector(".plot-container") !== null;
  70. }
  71. updateImage(png_data) {
  72. this.hideGraph();
  73. this._img_el.src = "data:image/png;base64," + png_data;
  74. this.showImage();
  75. }
  76. hideGraph() {
  77. // Hide the graph if there is one
  78. let el = this.node.querySelector(".plot-container");
  79. if (el !== null && el !== undefined) {
  80. el.style.display = "none";
  81. }
  82. }
  83. showGraph() {
  84. // Show the graph if there is one
  85. let el = this.node.querySelector(".plot-container");
  86. if (el !== null && el !== undefined) {
  87. el.style.display = "block";
  88. }
  89. }
  90. hideImage() {
  91. // Hide the image element
  92. let el = this.node.querySelector(".plot-img");
  93. if (el !== null && el !== undefined) {
  94. el.style.display = "none";
  95. }
  96. }
  97. showImage() {
  98. // Show the image element
  99. let el = this.node.querySelector(".plot-img");
  100. if (el !== null && el !== undefined) {
  101. el.style.display = "block";
  102. }
  103. }
  104. createGraph(model) {
  105. const { data, layout, frames, config } = model.data[this._mimeType];
  106. // Load plotly asynchronously
  107. const loadPlotly = () => __awaiter(this, void 0, void 0, function* () {
  108. if (RenderedPlotly.Plotly === null) {
  109. RenderedPlotly.Plotly = yield import("plotly.js/dist/plotly");
  110. RenderedPlotly._resolveLoadingPlotly();
  111. }
  112. return RenderedPlotly.loadingPlotly;
  113. });
  114. return loadPlotly()
  115. .then(() => RenderedPlotly.Plotly.react(this.node, data, layout, config))
  116. .then((plot) => {
  117. this.showGraph();
  118. this.hideImage();
  119. this.update();
  120. if (frames) {
  121. RenderedPlotly.Plotly.addFrames(this.node, frames);
  122. }
  123. if (this.node.offsetWidth > 0 && this.node.offsetHeight > 0) {
  124. RenderedPlotly.Plotly.toImage(plot, {
  125. format: "png",
  126. width: this.node.offsetWidth,
  127. height: this.node.offsetHeight,
  128. }).then((url) => {
  129. const imageData = url.split(",")[1];
  130. if (model.data["image/png"] !== imageData) {
  131. model.setData({
  132. data: Object.assign(Object.assign({}, model.data), { "image/png": imageData }),
  133. });
  134. }
  135. });
  136. }
  137. // Handle webgl context lost events
  138. this.node.on("plotly_webglcontextlost", () => {
  139. const png_data = model.data["image/png"];
  140. if (png_data !== undefined && png_data !== null) {
  141. // We have PNG data, use it
  142. this.updateImage(png_data);
  143. return Promise.resolve();
  144. }
  145. });
  146. });
  147. }
  148. /**
  149. * A message handler invoked on an `'after-show'` message.
  150. */
  151. onAfterShow(msg) {
  152. this.update();
  153. }
  154. /**
  155. * A message handler invoked on a `'resize'` message.
  156. */
  157. onResize(msg) {
  158. this.update();
  159. }
  160. /**
  161. * A message handler invoked on an `'update-request'` message.
  162. */
  163. onUpdateRequest(msg) {
  164. if (RenderedPlotly.Plotly && this.isVisible && this.hasGraphElement()) {
  165. RenderedPlotly.Plotly.redraw(this.node).then(() => {
  166. RenderedPlotly.Plotly.Plots.resize(this.node);
  167. });
  168. }
  169. }
  170. }
  171. RenderedPlotly.Plotly = null;
  172. RenderedPlotly.loadingPlotly = new Promise((resolve) => {
  173. RenderedPlotly._resolveLoadingPlotly = resolve;
  174. });
  175. /**
  176. * A mime renderer factory for Plotly data.
  177. */
  178. export const rendererFactory = {
  179. safe: true,
  180. mimeTypes: [MIME_TYPE],
  181. createRenderer: (options) => new RenderedPlotly(options),
  182. };
  183. const extensions = [
  184. {
  185. id: "@jupyterlab/plotly-extension:factory",
  186. rendererFactory,
  187. rank: 0,
  188. dataType: "json",
  189. fileTypes: [
  190. {
  191. name: "plotly",
  192. mimeTypes: [MIME_TYPE],
  193. extensions: [".plotly", ".plotly.json"],
  194. iconClass: CSS_ICON_CLASS,
  195. },
  196. ],
  197. documentWidgetFactoryOptions: {
  198. name: "Plotly",
  199. primaryFileType: "plotly",
  200. fileTypes: ["plotly", "json"],
  201. defaultFor: ["plotly"],
  202. },
  203. },
  204. ];
  205. export default extensions;