Add charting functionality to SQL notebooks. (#9306)

This commit is contained in:
Cory Rivera
2020-02-26 10:52:19 -08:00
committed by GitHub
parent ff207859d6
commit 412214e193
6 changed files with 107 additions and 63 deletions

View File

@@ -247,16 +247,6 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean, conf
`);
}
// Styling for all links in notebooks
const linkForeground = theme.getColor(textLinkForeground);
if (linkForeground) {
collector.addRule(`
.notebookEditor .scrollable a {
color: ${linkForeground};
}
`);
}
// Styling for tables in notebooks
const borderColor = theme.getColor(SIDE_BAR_BACKGROUND);
if (borderColor) {
@@ -269,6 +259,7 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean, conf
// Styling for markdown cells & links in notebooks.
// This matches the values used by default in all web views
const linkForeground = theme.getColor(textLinkForeground);
if (linkForeground) {
collector.addRule(`
.notebookEditor a:link {

View File

@@ -28,16 +28,19 @@ import { GridTableState } from 'sql/workbench/common/editor/query/gridPanelState
import { GridTableBase } from 'sql/workbench/contrib/query/browser/gridPanel';
import { getErrorMessage } from 'vs/base/common/errors';
import { ISerializationService, SerializeDataParams } from 'sql/platform/serialization/common/serializationService';
import { SaveResultAction } from 'sql/workbench/contrib/query/browser/actions';
import { SaveResultAction, IGridActionContext } from 'sql/workbench/contrib/query/browser/actions';
import { ResultSerializer, SaveResultsResponse, SaveFormat } from 'sql/workbench/services/query/common/resultSerializer';
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { values } from 'vs/base/common/collections';
import { assign } from 'vs/base/common/objects';
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
import { ChartView } from 'sql/workbench/contrib/charts/browser/chartView';
import { Orientation } from 'vs/base/browser/ui/splitview/splitview';
import { ToggleableAction } from 'sql/workbench/contrib/notebook/browser/notebookActions';
@Component({
selector: GridOutputComponent.SELECTOR,
template: `<div #output class="notebook-cellTable" (mouseover)="hover=true" (mouseleave)="hover=false"></div>`
template: `<div #output class="notebook-cellTable"></div>`
})
export class GridOutputComponent extends AngularDisposable implements IMimeComponent, OnInit {
public static readonly SELECTOR: string = 'grid-output';
@@ -48,7 +51,7 @@ export class GridOutputComponent extends AngularDisposable implements IMimeCompo
private _cellModel: ICellModel;
private _bundleOptions: MimeModel.IOptions;
private _table: DataResourceTable;
private _hover: boolean;
constructor(
@Inject(IInstantiationService) private instantiationService: IInstantiationService,
@Inject(IThemeService) private readonly themeService: IThemeService
@@ -76,14 +79,6 @@ export class GridOutputComponent extends AngularDisposable implements IMimeCompo
}
}
@Input() set hover(value: boolean) {
// only reaction on hover changes
if (this._hover !== value) {
this.toggleActionbar(value);
this._hover = value;
}
}
ngOnInit() {
this.renderGrid();
}
@@ -100,8 +95,7 @@ export class GridOutputComponent extends AngularDisposable implements IMimeCompo
outputElement.appendChild(this._table.element);
this._register(attachTableStyler(this._table, this.themeService));
this.layout();
// By default, do not show the actions
this.toggleActionbar(false);
this._table.onAdd();
this._initialized = true;
}
@@ -113,36 +107,26 @@ export class GridOutputComponent extends AngularDisposable implements IMimeCompo
this._table.layout(maxSize, undefined, ActionsOrientation.HORIZONTAL);
}
}
private toggleActionbar(visible: boolean) {
let outputElement = <HTMLElement>this.output.nativeElement;
let actionsContainers: HTMLElement[] = Array.prototype.slice.call(outputElement.getElementsByClassName('actions-container'));
if (actionsContainers && actionsContainers.length) {
if (visible) {
actionsContainers.forEach(container => container.style.visibility = 'visible');
} else {
actionsContainers.forEach(container => container.style.visibility = 'hidden');
}
}
}
}
class DataResourceTable extends GridTableBase<any> {
private _gridDataProvider: IGridDataProvider;
private _chart: ChartView;
private _chartContainer: HTMLElement;
constructor(source: IDataResource,
documentUri: string,
public readonly documentUri: string,
state: GridTableState,
@IContextMenuService contextMenuService: IContextMenuService,
@IInstantiationService instantiationService: IInstantiationService,
@IInstantiationService protected instantiationService: IInstantiationService,
@IEditorService editorService: IEditorService,
@IUntitledTextEditorService untitledEditorService: IUntitledTextEditorService,
@IConfigurationService configurationService: IConfigurationService,
@ISerializationService private _serializationService: ISerializationService
@IConfigurationService configurationService: IConfigurationService
) {
super(state, createResultSet(source), contextMenuService, instantiationService, editorService, untitledEditorService, configurationService);
this._gridDataProvider = this.instantiationService.createInstance(DataResourceDataProvider, source, this.resultSet, documentUri);
this._gridDataProvider = this.instantiationService.createInstance(DataResourceDataProvider, source, this.resultSet, this.documentUri);
this._chart = this.instantiationService.createInstance(ChartView);
}
get gridDataProvider(): IGridDataProvider {
@@ -154,14 +138,12 @@ class DataResourceTable extends GridTableBase<any> {
}
protected getContextActions(): IAction[] {
if (!this._serializationService.hasProvider()) {
return [];
}
return [
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveResultAction.SAVECSV_ICON, SaveFormat.CSV),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveResultAction.SAVEEXCEL_ICON, SaveFormat.EXCEL),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveResultAction.SAVEJSON_ICON, SaveFormat.JSON),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEXML_ID, SaveResultAction.SAVEXML_LABEL, SaveResultAction.SAVEXML_ICON, SaveFormat.XML),
this.instantiationService.createInstance(NotebookChartAction, this)
];
}
@@ -170,6 +152,33 @@ class DataResourceTable extends GridTableBase<any> {
// When we add this back in, we should update this calculation
return Math.max(this.maxSize, /* ACTIONBAR_HEIGHT + BOTTOM_PADDING */ 0);
}
public layout(size?: number, orientation?: Orientation, actionsOrientation?: ActionsOrientation): void {
super.layout(size, orientation, actionsOrientation);
if (!this._chartContainer) {
this._chartContainer = document.createElement('div');
this._chartContainer.style.display = 'none';
this._chartContainer.style.width = '100%';
this.element.appendChild(this._chartContainer);
this._chart.render(this._chartContainer);
}
}
public toggleChartVisibility(): void {
if (this.tableContainer.style.display !== 'none') {
this.tableContainer.style.display = 'none';
this._chartContainer.style.display = 'inline-block';
} else {
this.tableContainer.style.display = 'inline-block';
this._chartContainer.style.display = 'none';
}
}
public get chart(): ChartView {
return this._chart;
}
}
class DataResourceDataProvider implements IGridDataProvider {
@@ -252,7 +261,6 @@ class DataResourceDataProvider implements IGridDataProvider {
return this._serializationService.hasProvider();
}
serializeResults(format: SaveFormat, selection: Slick.Range[]): Thenable<void> {
let serializer = this._instantiationService.createInstance(ResultSerializer);
return serializer.handleSerialization(this.documentUri, format, (filePath) => this.doSerialize(serializer, filePath, format, selection));
@@ -375,3 +383,37 @@ class SimpleDbColumn implements azdata.IDbColumn {
udtAssemblyQualifiedName: string;
dataTypeName: string;
}
export class NotebookChartAction extends ToggleableAction {
public static ID = 'notebook.showChart';
public static SHOWCHART_LABEL = localize('notebook.showChart', "Show chart");
public static SHOWCHART_ICON = 'viewChart';
public static HIDECHART_LABEL = localize('notebook.hideChart', "Hide chart");
public static HIDECHART_ICON = 'close';
constructor(private resourceTable: DataResourceTable) {
super(NotebookChartAction.ID, {
toggleOnLabel: NotebookChartAction.HIDECHART_LABEL,
toggleOnClass: NotebookChartAction.HIDECHART_ICON,
toggleOffLabel: NotebookChartAction.SHOWCHART_LABEL,
toggleOffClass: NotebookChartAction.SHOWCHART_ICON,
isOn: false
});
}
public async run(context: IGridActionContext): Promise<boolean> {
this.resourceTable.toggleChartVisibility();
this.toggle(!this.state.isOn);
if (this.state.isOn) {
let rowCount = context.table.getData().getLength();
let range = new Slick.Range(0, 0, rowCount - 1, context.table.columns.length - 1);
let columns = context.gridDataProvider.getColumnHeaders(range);
context.gridDataProvider.getRowData(0, rowCount).then(result => {
this.resourceTable.chart.setData(result.resultSubset.rows, columns);
});
}
return true;
}
}