mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-14 01:25:37 -05:00
Add Plotly output support to notebooks
With this change, Plotly types will be successfully rendered in a Notebook. Currently they have a default width of 700px with a scrollbar if the window size is smaller (this matches other notebook viewers). The Plotly library is dynamically required to avoid startup time perf hits. This is something we could look at for other components too.
This commit is contained in:
@@ -39,6 +39,7 @@
|
||||
"@angular/platform-browser-dynamic": "~4.1.3",
|
||||
"@angular/router": "~4.1.3",
|
||||
"@angular/upgrade": "~4.1.3",
|
||||
"@types/plotly.js": "^1.44.9",
|
||||
"angular2-grid": "2.0.6",
|
||||
"angular2-slickgrid": "github:Microsoft/angular2-slickgrid#1.4.6",
|
||||
"ansi_up": "^3.0.0",
|
||||
@@ -60,6 +61,7 @@
|
||||
"native-watchdog": "1.0.0",
|
||||
"ng2-charts": "^1.6.0",
|
||||
"node-pty": "0.9.0-beta9",
|
||||
"plotly.js-dist": "^1.48.3",
|
||||
"reflect-metadata": "^0.1.8",
|
||||
"rxjs": "5.4.0",
|
||||
"sanitize-html": "^1.19.1",
|
||||
|
||||
3
src/bootstrap-window.js
vendored
3
src/bootstrap-window.js
vendored
@@ -121,7 +121,8 @@ exports.load = function (modulePaths, resultCallback, options) {
|
||||
'rxjs/Subject',
|
||||
'rxjs/Observer',
|
||||
'htmlparser2',
|
||||
'sanitize'
|
||||
'sanitize',
|
||||
'plotly.js-dist'
|
||||
]);
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
|
||||
|
||||
@@ -463,3 +463,8 @@ output-component .jp-RenderedHTMLCommon > *:last-child {
|
||||
.jp-RenderedPDF {
|
||||
font-size: var(--jp-ui-font-size1);
|
||||
}
|
||||
|
||||
plotly-output .plotly-wrapper {
|
||||
display: block;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import product from 'vs/platform/product/node/product';
|
||||
import { registerComponentType } from 'sql/workbench/parts/notebook/outputs/mimeRegistry';
|
||||
import { MimeRendererComponent as MimeRendererComponent } from 'sql/workbench/parts/notebook/outputs/mimeRenderer.component';
|
||||
import { MarkdownOutputComponent } from 'sql/workbench/parts/notebook/outputs/markdownOutput.component';
|
||||
import { PlotlyOutputComponent } from 'sql/workbench/parts/notebook/outputs/plotlyOutput.component';
|
||||
|
||||
// Model View editor registration
|
||||
const viewModelEditorDescriptor = new EditorDescriptor(
|
||||
@@ -138,7 +139,6 @@ registerComponentType({
|
||||
|
||||
/**
|
||||
* A mime renderer component for LaTeX.
|
||||
* This will be replaced by a dedicated component in the future
|
||||
*/
|
||||
registerComponentType({
|
||||
mimeTypes: ['text/latex'],
|
||||
@@ -150,7 +150,6 @@ registerComponentType({
|
||||
|
||||
/**
|
||||
* A mime renderer component for Markdown.
|
||||
* This will be replaced by a dedicated component in the future
|
||||
*/
|
||||
registerComponentType({
|
||||
mimeTypes: ['text/markdown'],
|
||||
@@ -159,3 +158,26 @@ registerComponentType({
|
||||
ctor: MarkdownOutputComponent,
|
||||
selector: MarkdownOutputComponent.SELECTOR
|
||||
});
|
||||
|
||||
/**
|
||||
* A mime renderer component for Plotly graphs.
|
||||
*/
|
||||
registerComponentType({
|
||||
mimeTypes: ['application/vnd.plotly.v1+json'],
|
||||
rank: 45,
|
||||
safe: true,
|
||||
ctor: PlotlyOutputComponent,
|
||||
selector: PlotlyOutputComponent.SELECTOR
|
||||
});
|
||||
/**
|
||||
* A mime renderer component for Plotly HTML output
|
||||
* that will ensure this gets ignored if possible since it's only output
|
||||
* on offline init and adds a <script> tag which does what we've done (add Plotly support into the app)
|
||||
*/
|
||||
registerComponentType({
|
||||
mimeTypes: ['text/vnd.plotly.v1+html'],
|
||||
rank: 46,
|
||||
safe: true,
|
||||
ctor: PlotlyOutputComponent,
|
||||
selector: PlotlyOutputComponent.SELECTOR
|
||||
});
|
||||
@@ -0,0 +1,157 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
import { OnInit, Component, Input, Inject, ElementRef, ViewChild } from '@angular/core';
|
||||
import { AngularDisposable } from 'sql/base/node/lifecycle';
|
||||
import { IMimeComponent } from 'sql/workbench/parts/notebook/outputs/mimeRegistry';
|
||||
import { MimeModel } from 'sql/workbench/parts/notebook/outputs/common/mimemodel';
|
||||
import { ICellModel } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { getErrorMessage } from 'sql/workbench/parts/notebook/notebookUtils';
|
||||
import { localize } from 'vs/nls';
|
||||
import * as types from 'vs/base/common/types';
|
||||
|
||||
type ObjectType = object;
|
||||
|
||||
interface FigureLayout extends ObjectType {
|
||||
width?: string | number;
|
||||
height?: string;
|
||||
autosize?: boolean;
|
||||
}
|
||||
|
||||
interface Figure extends ObjectType {
|
||||
data: object[];
|
||||
layout: Partial<FigureLayout>;
|
||||
}
|
||||
|
||||
declare class PlotlyHTMLElement extends HTMLDivElement {
|
||||
data: object;
|
||||
layout: object;
|
||||
newPlot: () => void;
|
||||
redraw: () => void;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: PlotlyOutputComponent.SELECTOR,
|
||||
template: `<div #output class="plotly-wrapper"></div>
|
||||
<pre *ngIf="hasError" class="p-Widget jp-RenderedText">{{errorText}}</pre>
|
||||
`
|
||||
})
|
||||
export class PlotlyOutputComponent extends AngularDisposable implements IMimeComponent, OnInit {
|
||||
public static readonly SELECTOR: string = 'plotly-output';
|
||||
|
||||
Plotly!: {
|
||||
newPlot: (
|
||||
div: PlotlyHTMLElement | null | undefined,
|
||||
data: object,
|
||||
layout: FigureLayout
|
||||
) => void;
|
||||
redraw: (div?: PlotlyHTMLElement) => void;
|
||||
};
|
||||
|
||||
@ViewChild('output', { read: ElementRef }) private output: ElementRef;
|
||||
|
||||
private _initialized: boolean = false;
|
||||
private _rendered: boolean = false;
|
||||
private _cellModel: ICellModel;
|
||||
private _bundleOptions: MimeModel.IOptions;
|
||||
private _plotDiv: PlotlyHTMLElement;
|
||||
public errorText: string;
|
||||
|
||||
constructor(
|
||||
@Inject(IThemeService) private readonly themeService: IThemeService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@Input() set bundleOptions(value: MimeModel.IOptions) {
|
||||
this._bundleOptions = value;
|
||||
if (this._initialized) {
|
||||
this.renderPlotly();
|
||||
}
|
||||
}
|
||||
|
||||
@Input() mimeType: string;
|
||||
|
||||
get cellModel(): ICellModel {
|
||||
return this._cellModel;
|
||||
}
|
||||
|
||||
@Input() set cellModel(value: ICellModel) {
|
||||
this._cellModel = value;
|
||||
if (this._initialized) {
|
||||
this.renderPlotly();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.Plotly = require.__$__nodeRequire('plotly.js-dist');
|
||||
this._plotDiv = this.output.nativeElement;
|
||||
this.renderPlotly();
|
||||
this._initialized = true;
|
||||
}
|
||||
|
||||
renderPlotly(): void {
|
||||
if (this._rendered) {
|
||||
// just re-layout
|
||||
this.layout();
|
||||
return;
|
||||
}
|
||||
if (!this._bundleOptions || !this._cellModel || !this.mimeType) {
|
||||
return;
|
||||
}
|
||||
if (this.mimeType === 'text/vnd.plotly.v1+html') {
|
||||
// Do nothing - this is our way to ignore the offline init Plotly attempts to do via a <script> tag.
|
||||
// We have "handled" it by pulling in the plotly library into this component instead
|
||||
return;
|
||||
}
|
||||
this.errorText = undefined;
|
||||
const figure = this.getFigure(true);
|
||||
if (figure) {
|
||||
figure.layout = figure.layout || {};
|
||||
if (!figure.layout.width && !figure.layout.autosize) {
|
||||
// Workaround: to avoid filling up the entire cell, use plotly's default
|
||||
figure.layout.width = Math.min(700, this._plotDiv.clientWidth);
|
||||
}
|
||||
try {
|
||||
this.Plotly.newPlot(this._plotDiv, figure.data, figure.layout);
|
||||
} catch (error) {
|
||||
this.displayError(error);
|
||||
}
|
||||
}
|
||||
this._rendered = true;
|
||||
}
|
||||
|
||||
getFigure(showError: boolean): Figure {
|
||||
const figure = <Figure><any>this._bundleOptions.data[this.mimeType];
|
||||
if (typeof figure === 'string') {
|
||||
try {
|
||||
JSON.parse(figure);
|
||||
} catch (error) {
|
||||
if (showError) {
|
||||
this.displayError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { data = [], layout = {} } = figure;
|
||||
|
||||
return { data, layout };
|
||||
}
|
||||
|
||||
private displayError(error: Error | string): void {
|
||||
this.errorText = localize('plotlyError', 'Error displaying Plotly graph: {0}', getErrorMessage(error));
|
||||
}
|
||||
|
||||
layout(): void {
|
||||
// No need to re-layout for now as Plotly is doing its own resize handling.
|
||||
}
|
||||
|
||||
public hasError(): boolean {
|
||||
return !types.isUndefinedOrNull(this.errorText);
|
||||
}
|
||||
|
||||
}
|
||||
1
src/typings/index.d.ts
vendored
1
src/typings/index.d.ts
vendored
@@ -19,3 +19,4 @@
|
||||
/// <reference path="modules/html-query-plan/index.d.ts" />
|
||||
/// <reference path="modules/ng2-charts/index.d.ts" />
|
||||
/// <reference path="modules/rxjs/index.d.ts" />
|
||||
/// <reference path="modules/@types/plotly.js-dist/index.d.ts" />
|
||||
|
||||
1423
src/typings/modules/@types/plotly.js-dist/index.d.ts
vendored
Normal file
1423
src/typings/modules/@types/plotly.js-dist/index.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
17
yarn.lock
17
yarn.lock
@@ -85,6 +85,11 @@
|
||||
dependencies:
|
||||
commander "*"
|
||||
|
||||
"@types/d3@^3":
|
||||
version "3.5.42"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3/-/d3-3.5.42.tgz#6a782b44bb7f5c48165cb166886b5d53cb84455f"
|
||||
integrity sha512-jKnkXluwSAzkvR19zjCHvLYgsWuDqpeE79NrhWrqhKqrx3sgTRqqt4SKaxSy+N7mt1J04Xy4L0/cKdfIgnjzVQ==
|
||||
|
||||
"@types/fancy-log@1.3.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/fancy-log/-/fancy-log-1.3.0.tgz#a61ab476e5e628cd07a846330df53b85e05c8ce0"
|
||||
@@ -117,6 +122,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.12.tgz#e15a9d034d9210f00320ef718a50c4a799417c47"
|
||||
integrity sha512-Pr+6JRiKkfsFvmU/LK68oBRCQeEg36TyAbPhc2xpez24OOZZCuoIhWGTd39VZy6nGafSbxzGouFPTFD/rR1A0A==
|
||||
|
||||
"@types/plotly.js@^1.44.9":
|
||||
version "1.44.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/plotly.js/-/plotly.js-1.44.9.tgz#d20bd229b409f83b5e9bc06df0c948a27b8fbc0d"
|
||||
integrity sha512-YZlxjspeO7FEmlR56m2RQpohSWeQ4MRysT1Ghln/DFLS29Fy2YGMeASnhlYRiFxYcz12Jh0pXM0YYWbFNS4YYA==
|
||||
dependencies:
|
||||
"@types/d3" "^3"
|
||||
|
||||
"@types/sanitize-html@^1.18.2":
|
||||
version "1.18.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-1.18.2.tgz#14e9971064d0f29aa4feaa8421122ced9e8346d9"
|
||||
@@ -6763,6 +6775,11 @@ plist@^3.0.1:
|
||||
xmlbuilder "^9.0.7"
|
||||
xmldom "0.1.x"
|
||||
|
||||
plotly.js-dist@^1.48.3:
|
||||
version "1.48.3"
|
||||
resolved "https://registry.yarnpkg.com/plotly.js-dist/-/plotly.js-dist-1.48.3.tgz#b160b2d080ad87720121c9119f28036f35a301dc"
|
||||
integrity sha512-Ocy2WWjzh4Ofk293AgTvP0ga053nTV7TvUbYEzmq1E9Eh7wc6HCPBxo55YcZThHw3hVycEnhktLQK94en1bYSw==
|
||||
|
||||
plugin-error@0.1.2, plugin-error@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-0.1.2.tgz#3b9bb3335ccf00f425e07437e19276967da47ace"
|
||||
|
||||
Reference in New Issue
Block a user