Refactor chart viewer (#2381)

* working on adding charts

* working on chart options

* adding image and table insight

* add chart viewing and handle a bunch of small bugs

* formatting

* remove unused code
This commit is contained in:
Anthony Dresser
2018-08-31 12:55:34 -07:00
committed by GitHub
parent 54ee1c23f0
commit 8e0c19fc8d
25 changed files with 1919 additions and 40 deletions

View File

@@ -33,6 +33,7 @@
"@angular/platform-browser-dynamic": "~4.1.3",
"@angular/router": "~4.1.3",
"@angular/upgrade": "~4.1.3",
"@types/chart.js": "^2.7.31",
"angular2-grid": "2.0.6",
"angular2-slickgrid": "github:Microsoft/angular2-slickgrid#1.4.4",
"applicationinsights": "0.18.0",

View File

@@ -142,3 +142,8 @@ panel {
.visibility.hidden {
visibility: hidden;
}
.tabBody {
width: 100%;
height: 100%;
}

View File

@@ -78,12 +78,16 @@ export class TabbedPanel extends Disposable implements IThemable {
} else {
this._headerVisible = false;
}
this.$body = $('tabBody');
this.$body = $('.tabBody');
this.$body.attr('role', 'tabpanel');
this.$body.attr('tabindex', '0');
this.$parent.append(this.$body);
}
public contains(tab: IPanelTab): boolean {
return this._tabMap.has(tab.identifier);
}
public pushTab(tab: IPanelTab): PanelTabIdentifier {
let internalTab = tab as IInternalPanelTab;
this._tabMap.set(tab.identifier, internalTab);
@@ -94,6 +98,7 @@ export class TabbedPanel extends Disposable implements IThemable {
if (this._tabMap.size > 1 && !this._headerVisible) {
this.$parent.append(this.$header, 0);
this._headerVisible = true;
this.layout(this._currentDimensions);
}
return tab.identifier as PanelTabIdentifier;
}
@@ -170,6 +175,8 @@ export class TabbedPanel extends Disposable implements IThemable {
public layout(dimension: Dimension): void {
this._currentDimensions = dimension;
this.$parent.style('height', dimension.height + 'px');
this.$parent.style('width', dimension.width + 'px');
this.$header.style('width', dimension.width + 'px');
this.$body.style('width', dimension.width + 'px');
const bodyHeight = dimension.height - (this._headerVisible ? this.headersize : 0);

View File

@@ -103,8 +103,8 @@ export const defaultChartConfig: IChartConfig = {
})
export abstract class ChartInsight extends Disposable implements IInsightsView {
private _isDataAvailable: boolean = false;
private _hasInit: boolean = false;
private _hasError: boolean = false;
protected _hasInit: boolean = false;
protected _hasError: boolean = false;
private _options: any = {};
@ViewChild(BaseChartDirective) private _chart: BaseChartDirective;
@@ -113,7 +113,7 @@ export abstract class ChartInsight extends Disposable implements IInsightsView {
protected _config: IChartConfig;
protected _data: IInsightData;
private readonly CHART_ERROR_MESSAGE = nls.localize('chartErrorMessage', 'Chart cannot be displayed with the given data');
protected readonly CHART_ERROR_MESSAGE = nls.localize('chartErrorMessage', 'Chart cannot be displayed with the given data');
protected abstract get chartType(): ChartType;

View File

@@ -5,9 +5,9 @@
import { defaultChartConfig, IPointDataSet, ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
import LineChart, { ILineConfig } from './lineChart.component';
import { clone } from 'sql/base/common/objects';
import { mixin } from 'vs/base/common/objects';
import { clone } from 'sql/base/common/objects';
import { Color } from 'vs/base/common/color';
const defaultTimeSeriesConfig = mixin(clone(defaultChartConfig), { dataType: 'point', dataDirection: 'horizontal' }) as ILineConfig;

View File

@@ -15,8 +15,8 @@ import { IInsightsView, IInsightData } from 'sql/parts/dashboard/widgets/insight
`
})
export default class CountInsight implements IInsightsView {
private _labels: Array<string>;
private _values: Array<string>;
protected _labels: Array<string>;
protected _values: Array<string>;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef) { }

View File

@@ -250,8 +250,7 @@
}
.grid-panel .action-label.icon {
height: 35px;
line-height: 35px;
height: 16px;
min-width: 28px;
background-size: 16px;
background-position: center center;

View File

@@ -73,9 +73,6 @@
-->
<ng-template #lineInput>
<ng-container *ngTemplateOutlet="dataTypeInput"></ng-container>
<ng-template [ngIf]="showDataDirection">
<ng-container *ngTemplateOutlet="dataDirectionInput"></ng-container>
</ng-template>
<ng-template [ngIf]="showColumnsAsLabels">
<ng-container *ngTemplateOutlet="columnsAsLabelsInput"></ng-container>
</ng-template>

View File

@@ -16,6 +16,11 @@ import QueryRunner from 'sql/parts/query/execution/queryRunner';
import { SaveFormat } from 'sql/parts/grid/common/interfaces';
import { Table } from 'sql/base/browser/ui/table/table';
import { GridTableState } from 'sql/parts/query/editor/gridPanel';
import { IEditorService } from 'vs/platform/editor/common/editor';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { QueryEditor } from './queryEditor';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
export interface IGridActionContext {
cell: { row: number; cell: number; };
@@ -158,3 +163,23 @@ export class MinimizeTableAction extends Action {
return TPromise.as(true);
}
}
export class ChartDataAction extends Action {
public static ID = 'grid.chart';
public static LABEL = localize('chart', 'Chart');
public static ICON = 'viewChart';
constructor(@IWorkbenchEditorService private editorService: IWorkbenchEditorService) {
super(ChartDataAction.ID, ChartDataAction.LABEL, ChartDataAction.ICON);
}
public run(context: IGridActionContext): TPromise<boolean> {
let activeEditor = this.editorService.getActiveEditor();
if (activeEditor instanceof QueryEditor) {
activeEditor.resultsEditor.chart({ batchId: context.batchId, resultId: context.resultId });
return TPromise.as(true);
} else {
return TPromise.as(false);
}
}
}

View File

@@ -0,0 +1,204 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { localize } from 'vs/nls';
import { Registry } from 'vs/platform/registry/common/platform';
import { ChartType, DataDirection, LegendPosition } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
import { Extensions, IInsightRegistry } from 'sql/platform/dashboard/common/insightRegistry';
import { DataType } from 'sql/parts/dashboard/widgets/insights/views/charts/types/lineChart.component';
import { InsightType, IInsightOptions } from './insights/interfaces';
const insightRegistry = Registry.as<IInsightRegistry>(Extensions.InsightContribution);
export enum ControlType {
combo,
numberInput,
input,
checkbox
}
export interface IChartOption {
label: string;
type: ControlType;
configEntry: string;
default: any;
options?: any[];
displayableOptions?: string[];
if?: (options: IInsightOptions) => boolean;
}
export interface IChartOptions {
general: Array<IChartOption>;
[x: string]: Array<IChartOption>;
}
const dataDirectionOption: IChartOption = {
label: localize('dataDirectionLabel', 'Data Direction'),
type: ControlType.combo,
displayableOptions: [localize('verticalLabel', 'Vertical'), localize('horizontalLabel', 'Horizontal')],
options: [DataDirection.Vertical, DataDirection.Horizontal],
configEntry: 'dataDirection',
default: DataDirection.Horizontal
};
const columnsAsLabelsInput: IChartOption = {
label: localize('columnsAsLabelsLabel', 'Use column names as labels'),
type: ControlType.checkbox,
configEntry: 'columnsAsLabels',
default: false,
if: (options: IInsightOptions) => {
return options.dataDirection === DataDirection.Vertical && options.dataType !== DataType.Point;
}
};
const labelFirstColumnInput: IChartOption = {
label: localize('labelFirstColumnLabel', 'Use first column as row label'),
type: ControlType.checkbox,
configEntry: 'labelFirstColumn',
default: false,
if: (options: IInsightOptions) => {
return options.dataDirection === DataDirection.Horizontal && options.dataType !== DataType.Point;
}
};
const legendInput: IChartOption = {
label: localize('legendLabel', 'Legend Position'),
type: ControlType.combo,
options: Object.values(LegendPosition),
configEntry: 'legendPosition',
default: LegendPosition.Top
};
const yAxisLabelInput: IChartOption = {
label: localize('yAxisLabel', 'Y Axis Label'),
type: ControlType.input,
configEntry: 'yAxisLabel',
default: undefined
};
const yAxisMinInput: IChartOption = {
label: localize('yAxisMinVal', 'Y Axis Minimum Value'),
type: ControlType.numberInput,
configEntry: 'yAxisMin',
default: undefined
};
const yAxisMaxInput: IChartOption = {
label: localize('yAxisMaxVal', 'Y Axis Maximum Value'),
type: ControlType.numberInput,
configEntry: 'yAxisMax',
default: undefined
};
const xAxisLabelInput: IChartOption = {
label: localize('xAxisLabel', 'X Axis Label'),
type: ControlType.input,
configEntry: 'xAxisLabel',
default: undefined
};
const xAxisMinInput: IChartOption = {
label: localize('xAxisMinVal', 'X Axis Minimum Value'),
type: ControlType.numberInput,
configEntry: 'xAxisMin',
default: undefined
};
const xAxisMaxInput: IChartOption = {
label: localize('xAxisMaxVal', 'X Axis Maximum Value'),
type: ControlType.numberInput,
configEntry: 'xAxisMax',
default: undefined
};
const dataTypeInput: IChartOption = {
label: localize('dataTypeLabel', 'Data Type'),
type: ControlType.combo,
options: [DataType.Number, DataType.Point],
displayableOptions: [localize('numberLabel', 'Number'), localize('pointLabel', 'Point')],
configEntry: 'dataType',
default: DataType.Number
};
export const ChartOptions: IChartOptions = {
general: [
{
label: localize('chartTypeLabel', 'Chart Type'),
type: ControlType.combo,
options: insightRegistry.getAllIds(),
configEntry: 'type',
default: ChartType.Bar
}
],
[ChartType.Line]: [
dataTypeInput,
columnsAsLabelsInput,
labelFirstColumnInput,
yAxisLabelInput,
xAxisLabelInput,
legendInput
],
[ChartType.Scatter]: [
legendInput,
yAxisLabelInput,
xAxisLabelInput
],
[ChartType.TimeSeries]: [
legendInput,
yAxisLabelInput,
xAxisLabelInput
],
[ChartType.Bar]: [
dataDirectionOption,
columnsAsLabelsInput,
labelFirstColumnInput,
legendInput,
yAxisLabelInput,
yAxisMinInput,
yAxisMaxInput,
xAxisLabelInput
],
[ChartType.HorizontalBar]: [
dataDirectionOption,
columnsAsLabelsInput,
labelFirstColumnInput,
legendInput,
xAxisLabelInput,
xAxisMinInput,
xAxisMaxInput,
yAxisLabelInput
],
[ChartType.Pie]: [
dataDirectionOption,
columnsAsLabelsInput,
labelFirstColumnInput,
legendInput
],
[ChartType.Doughnut]: [
dataDirectionOption,
columnsAsLabelsInput,
labelFirstColumnInput,
legendInput
],
[InsightType.Table]: [],
[InsightType.Count]: [],
[InsightType.Image]: [
{
configEntry: 'encoding',
label: localize('encodingOption', 'Encoding'),
type: ControlType.input,
default: 'hex'
},
{
configEntry: 'imageFormat',
label: localize('imageFormatOption', 'Image Format'),
type: ControlType.input,
default: 'jpeg'
}
]
};

View File

@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IPanelTab } from 'sql/base/browser/ui/panel/panel';
import { ChartView } from './chartView';
import QueryRunner from 'sql/parts/query/execution/queryRunner';
import { localize } from 'vs/nls';
import { generateUuid } from 'vs/base/common/uuid';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export class ChartTab implements IPanelTab {
public readonly title = localize('chartTabTitle', 'Chart');
public readonly identifier = generateUuid();
public readonly view: ChartView;
constructor(@IInstantiationService instantiationService: IInstantiationService) {
this.view = instantiationService.createInstance(ChartView);
}
public set queryRunner(runner: QueryRunner) {
this.view.queryRunner = runner;
}
public chart(dataId: { batchId: number, resultId: number}): void {
this.view.chart(dataId);
}
}

View File

@@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.chart-view-container {
display: flex;
width: 100%;
height: 100%;
}
.graph-container {
flex: 1 1 0;
}
.options-container {
width: 250px;
padding-right: 10px;
}

View File

@@ -0,0 +1,240 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./chartView';
import { IPanelView } from 'sql/base/browser/ui/panel/panel';
import { Insight } from './insights/insight';
import QueryRunner from 'sql/parts/query/execution/queryRunner';
import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
import { ChartOptions, IChartOption, ControlType } from './chartOptions';
import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox';
import { IInsightOptions } from './insights/interfaces';
import { Dimension, $, getContentHeight, getContentWidth } from 'vs/base/browser/dom';
import { SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { Builder } from 'vs/base/browser/builder';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { attachSelectBoxStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
declare class Proxy {
constructor(object, handler);
}
export class ChartView implements IPanelView {
private insight: Insight;
private _queryRunner: QueryRunner;
private _data: IInsightData;
private _currentData: { batchId: number, resultId: number };
private optionsControl: HTMLElement;
private options: IInsightOptions = {
type: ChartType.Bar
};
private container: HTMLElement;
private typeControls: HTMLElement;
private graphContainer: HTMLElement;
private optionDisposables: IDisposable[] = [];
private optionMap: { [x: string]: HTMLElement } = {};
constructor(
@IContextViewService private _contextViewService: IContextViewService,
@IThemeService private _themeService: IThemeService,
@IInstantiationService private _instantiationService: IInstantiationService
) {
this.optionsControl = $('div.options-container');
let generalControls = $('div.general-controls');
this.optionsControl.appendChild(generalControls);
this.typeControls = $('div.type-controls');
this.optionsControl.appendChild(this.typeControls);
let self = this;
this.options = new Proxy(this.options, {
get: function (target, key, receiver) {
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
let change = false;
if (target[key] !== value) {
change = true;
}
let result = Reflect.set(target, key, value, receiver);
if (change) {
if (key === 'type') {
self.buildOptions();
} else {
self.verifyOptions();
}
}
return result;
}
}) as IInsightOptions;
ChartOptions.general.map(o => {
this.createOption(o, generalControls);
});
}
render(container: HTMLElement): void {
if (!this.container) {
this.container = $('div.chart-view-container');
this.graphContainer = $('div.graph-container');
this.container.appendChild(this.graphContainer);
this.container.appendChild(this.optionsControl);
this.insight = new Insight(this.graphContainer, this.options, this._instantiationService);
}
container.appendChild(this.container);
if (this._data) {
this.insight.data = this._data;
} else {
this.queryRunner = this._queryRunner;
}
}
public chart(dataId: { batchId: number, resultId: number }) {
this._currentData = dataId;
this.shouldGraph();
}
layout(dimension: Dimension): void {
if (this.insight) {
this.insight.layout(new Dimension(getContentWidth(this.graphContainer), getContentHeight(this.graphContainer)));
}
}
public set queryRunner(runner: QueryRunner) {
this._queryRunner = runner;
this.shouldGraph();
}
private shouldGraph() {
// Check if we have the necessary information
if (this._currentData && this._queryRunner) {
// check if we are being asked to graph something that is available
let batch = this._queryRunner.batchSets[this._currentData.batchId];
if (batch) {
let summary = batch.resultSetSummaries[this._currentData.resultId];
if (summary) {
this._queryRunner.getQueryRows(0, summary.rowCount, 0, 0).then(d => {
this._data = {
columns: summary.columnInfo.map(c => c.columnName),
rows: d.resultSubset.rows.map(r => r.map(c => c.displayValue))
};
if (this.insight) {
this.insight.data = this._data;
}
});
}
}
// if we have the necessary information but the information isn't avaiable yet,
// we should be smart and retrying when the information might be available
}
}
private buildOptions() {
dispose(this.optionDisposables);
this.optionDisposables = [];
this.optionMap = {};
new Builder(this.typeControls).clearChildren();
ChartOptions[this.options.type].map(o => {
this.createOption(o, this.typeControls);
});
if (this.insight) {
this.insight.options = this.options;
}
this.verifyOptions();
}
private verifyOptions() {
for (let key in this.optionMap) {
if (this.optionMap.hasOwnProperty(key)) {
let option = ChartOptions[this.options.type].find(e => e.configEntry === key);
if (option && option.if) {
if (option.if(this.options)) {
new Builder(this.optionMap[key]).show();
} else {
new Builder(this.optionMap[key]).hide();
}
}
}
}
}
private createOption(option: IChartOption, container: HTMLElement) {
let label = $('div');
label.innerText = option.label;
let optionContainer = $('div.option-container');
optionContainer.appendChild(label);
switch (option.type) {
case ControlType.checkbox:
let checkbox = new Checkbox(optionContainer, {
label: '',
ariaLabel: option.label,
checked: option.default,
onChange: () => {
if (this.options[option.configEntry] !== checkbox.checked) {
this.options[option.configEntry] = checkbox.checked;
this.insight.options = this.options;
}
}
});
break;
case ControlType.combo:
let dropdown = new SelectBox(option.displayableOptions || option.options, 0, this._contextViewService);
dropdown.select(option.options.indexOf(option.default));
dropdown.render(optionContainer);
dropdown.onDidSelect(e => {
if (this.options[option.configEntry] !== option.options[e.index]) {
this.options[option.configEntry] = option.options[e.index];
this.insight.options = this.options;
}
});
this.optionDisposables.push(attachSelectBoxStyler(dropdown, this._themeService));
break;
case ControlType.input:
let input = new InputBox(optionContainer, this._contextViewService);
input.value = option.default || '';
input.onDidChange(e => {
if (this.options[option.configEntry] !== e) {
this.options[option.configEntry] = e;
this.insight.options = this.options;
}
});
this.optionDisposables.push(attachInputBoxStyler(input, this._themeService));
break;
case ControlType.numberInput:
let numberInput = new InputBox(optionContainer, this._contextViewService, { type: 'number' });
numberInput.value = option.default || '';
numberInput.onDidChange(e => {
if (this.options[option.configEntry] !== Number(e)) {
this.options[option.configEntry] = Number(e);
this.insight.options = this.options;
}
});
this.optionDisposables.push(attachInputBoxStyler(numberInput, this._themeService));
break;
}
this.optionMap[option.configEntry] = optionContainer;
container.appendChild(optionContainer);
this.options[option.configEntry] = option.default;
}
}

View File

@@ -0,0 +1,16 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.count-label-container {
margin-left: 5px;
}
.label-container {
font-size: 20px
}
.value-container {
}

View File

@@ -0,0 +1,45 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./countInsight';
import { IInsight, InsightType } from './interfaces';
import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
import { $ } from 'vs/base/browser/dom';
import { Builder } from 'vs/base/browser/builder';
export class CountInsight implements IInsight {
public options;
public static readonly types = [InsightType.Count];
public readonly types = CountInsight.types;
private countImage: HTMLElement;
constructor(container: HTMLElement, options: any) {
this.countImage = $('div');
container.appendChild(this.countImage);
}
public layout() { }
set data(data: IInsightData) {
new Builder(this.countImage).empty();
for (let i = 0; i < data.columns.length; i++) {
let container = $('div.count-label-container');
let label = $('span.label-container');
label.innerText = data.columns[i];
let value = $('span.value-container');
value.innerText = data.rows[0][i];
container.appendChild(label);
container.appendChild(value);
this.countImage.appendChild(container);
}
}
dispose() {
}
}

View File

@@ -0,0 +1,321 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Chart as ChartJs } from 'chart.js';
import { mixin } from 'vs/base/common/objects';
import { localize } from 'vs/nls';
import * as colors from 'vs/platform/theme/common/colorRegistry';
import { editorLineNumbers } from 'vs/editor/common/view/editorColorRegistry';
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
import { ChartType, DataDirection, LegendPosition } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
import { IInsightOptions, IInsight } from './interfaces';
const noneLineGraphs = [ChartType.Doughnut, ChartType.Pie];
const defaultOptions: IInsightOptions = {
type: ChartType.Bar,
dataDirection: DataDirection.Horizontal
};
export class Graph implements IInsight {
private _options: IInsightOptions;
private canvas: HTMLCanvasElement;
private chartjs: ChartJs;
private _data: IInsightData;
public static readonly types = [ChartType.Bar, ChartType.Doughnut, ChartType.HorizontalBar, ChartType.Line, ChartType.Pie, ChartType.Scatter, ChartType.TimeSeries];
public readonly types = Graph.types;
private _theme: ITheme;
constructor(
container: HTMLElement, options: IInsightOptions = defaultOptions,
@IThemeService themeService: IThemeService
) {
this._theme = themeService.getTheme();
themeService.onThemeChange(e => {
this._theme = e;
this.data = this._data;
});
this._options = mixin(options, defaultOptions, false);
let canvasContainer = document.createElement('div');
canvasContainer.style.width = '100%';
canvasContainer.style.height = '100%';
this.canvas = document.createElement('canvas');
canvasContainer.appendChild(this.canvas);
container.appendChild(canvasContainer);
}
public dispose() {
}
public layout() {
}
public set data(data: IInsightData) {
this._data = data;
let chartData: Array<ChartJs.ChartDataSets>;
let labels: Array<string>;
if (this.options.dataDirection === DataDirection.Horizontal) {
if (this.options.labelFirstColumn) {
labels = data.columns.slice(1);
} else {
labels = data.columns;
}
} else {
labels = data.rows.map(row => row[0]);
}
if (this.options.dataDirection === DataDirection.Horizontal) {
if (this.options.labelFirstColumn) {
chartData = data.rows.map((row) => {
return {
data: row.map(item => Number(item)).slice(1),
label: row[0]
};
});
} else {
chartData = data.rows.map((row, i) => {
return {
data: row.map(item => Number(item)),
label: localize('series', 'Series {0}', i)
};
});
}
} else {
if (this.options.columnsAsLabels) {
chartData = data.rows[0].slice(1).map((row, i) => {
return {
data: data.rows.map(row => Number(row[i + 1])),
label: data.columns[i + 1]
};
});
} else {
chartData = data.rows[0].slice(1).map((row, i) => {
return {
data: data.rows.map(row => Number(row[i + 1])),
label: localize('series', 'Series {0}', i + 1)
};
});
}
}
chartData = chartData.map((c, i) => {
return mixin(c, getColors(this.options.type, i, c.data.length), false);
});
if (this.chartjs) {
this.chartjs.data.datasets = chartData;
this.chartjs.config.type = this.options.type;
this.chartjs.data.labels = labels;
this.chartjs.options = this.transformOptions(this.options);
this.chartjs.update(0);
} else {
this.chartjs = new ChartJs(this.canvas.getContext('2d'), {
data: {
labels: labels,
datasets: chartData
},
type: this.options.type,
options: {
maintainAspectRatio: false
}
});
}
}
private transformOptions(options: IInsightOptions): ChartJs.ChartOptions {
let retval: ChartJs.ChartOptions = {};
retval.maintainAspectRatio = false;
let foregroundColor = this._theme.getColor(colors.editorForeground);
let foreground = foregroundColor ? foregroundColor.toString() : null;
let gridLinesColor = this._theme.getColor(editorLineNumbers);
let gridLines = gridLinesColor ? gridLinesColor.toString() : null;
if (options) {
retval.scales = {};
// we only want to include axis if it is a axis based graph type
if (!noneLineGraphs.includes(options.type as ChartType)) {
retval.scales.xAxes = [{
scaleLabel: {
fontColor: foreground,
labelString: options.xAxisLabel,
display: options.xAxisLabel ? true : false
},
ticks: {
fontColor: foreground,
max: options.xAxisMax,
min: options.xAxisMin
},
gridLines: {
color: gridLines
}
}];
retval.scales.yAxes = [{
scaleLabel: {
fontColor: foreground,
labelString: options.yAxisLabel,
display: options.yAxisLabel ? true : false
},
ticks: {
fontColor: foreground,
max: options.yAxisMax,
min: options.yAxisMin
},
gridLines: {
color: gridLines
}
}];
}
retval.legend = {
position: options.legendPosition as ChartJs.PositionType,
display: options.legendPosition !== LegendPosition.None
};
}
return retval;
}
public set options(options: IInsightOptions) {
this._options = options;
this.data = this._data;
}
public get options(): IInsightOptions {
return this._options;
}
}
/**
* The Following code is pulled from ng2-charting in order to keep the same
* color functionality
*/
const defaultColors = [
[255, 99, 132],
[54, 162, 235],
[255, 206, 86],
[231, 233, 237],
[75, 192, 192],
[151, 187, 205],
[220, 220, 220],
[247, 70, 74],
[70, 191, 189],
[253, 180, 92],
[148, 159, 177],
[77, 83, 96]
];
function rgba(colour, alpha) {
return 'rgba(' + colour.concat(alpha).join(',') + ')';
}
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function getRandomColor() {
return [getRandomInt(0, 255), getRandomInt(0, 255), getRandomInt(0, 255)];
}
/**
* Generate colors for line|bar charts
* @param index
* @returns {number[]|Color}
*/
function generateColor(index) {
return defaultColors[index] || getRandomColor();
}
/**
* Generate colors for pie|doughnut charts
* @param count
* @returns {Colors}
*/
function generateColors(count) {
var colorsArr = new Array(count);
for (var i = 0; i < count; i++) {
colorsArr[i] = defaultColors[i] || getRandomColor();
}
return colorsArr;
}
function formatLineColor(colors) {
return {
backgroundColor: rgba(colors, 0.4),
borderColor: rgba(colors, 1),
pointBackgroundColor: rgba(colors, 1),
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: rgba(colors, 0.8)
};
}
function formatBarColor(colors) {
return {
backgroundColor: rgba(colors, 0.6),
borderColor: rgba(colors, 1),
hoverBackgroundColor: rgba(colors, 0.8),
hoverBorderColor: rgba(colors, 1)
};
}
function formatPieColors(colors) {
return {
backgroundColor: colors.map(function (color) { return rgba(color, 0.6); }),
borderColor: colors.map(function () { return '#fff'; }),
pointBackgroundColor: colors.map(function (color) { return rgba(color, 1); }),
pointBorderColor: colors.map(function () { return '#fff'; }),
pointHoverBackgroundColor: colors.map(function (color) { return rgba(color, 1); }),
pointHoverBorderColor: colors.map(function (color) { return rgba(color, 1); })
};
}
function formatPolarAreaColors(colors) {
return {
backgroundColor: colors.map(function (color) { return rgba(color, 0.6); }),
borderColor: colors.map(function (color) { return rgba(color, 1); }),
hoverBackgroundColor: colors.map(function (color) { return rgba(color, 0.8); }),
hoverBorderColor: colors.map(function (color) { return rgba(color, 1); })
};
}
/**
* Generate colors by chart type
* @param chartType
* @param index
* @param count
* @returns {Color}
*/
function getColors(chartType, index, count) {
if (chartType === 'pie' || chartType === 'doughnut') {
return formatPieColors(generateColors(count));
}
if (chartType === 'polarArea') {
return formatPolarAreaColors(generateColors(count));
}
if (chartType === 'line' || chartType === 'radar') {
return formatLineColor(generateColor(index));
}
if (chartType === 'bar' || chartType === 'horizontalBar') {
return formatBarColor(generateColor(index));
}
return generateColor(index);
}

View File

@@ -0,0 +1,71 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IInsight, IInsightOptions, InsightType } from './interfaces';
import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
import { $ } from 'vs/base/browser/dom';
import { mixin } from 'vs/base/common/objects';
export interface IConfig extends IInsightOptions {
encoding?: string;
imageFormat?: string;
}
const defaultConfig: IConfig = {
type: InsightType.Image,
encoding: 'hex',
imageFormat: 'jpeg'
};
export class ImageInsight implements IInsight {
public static readonly types = [InsightType.Image];
public readonly types = ImageInsight.types;
private _options: IConfig;
private imageEle: HTMLImageElement;
constructor(container: HTMLElement, options: IConfig) {
this.imageEle = $('img');
container.appendChild(this.imageEle);
}
public layout() {
}
public dispose() {
}
set options(config: IConfig) {
this._options = mixin(config, defaultConfig, false);
}
get options(): IConfig {
return this._options;
}
set data(data: IInsightData) {
if (data.rows && data.rows.length > 0 && data.rows[0].length > 0) {
let img = data.rows[0][0];
if (this._options.encoding === 'hex') {
img = ImageInsight._hexToBase64(img);
}
this.imageEle.src = `data:image/${this._options.imageFormat};base64,${img}`;
}
}
private static _hexToBase64(hexVal: string) {
if (hexVal.startsWith('0x')) {
hexVal = hexVal.slice(2);
}
// should be able to be replaced with new Buffer(hexVal, 'hex').toString('base64')
return btoa(String.fromCharCode.apply(null, hexVal.replace(/\r|\n/g, '').replace(/([\da-fA-F]{2}) ?/g, '0x$1 ').replace(/ +$/, '').split(' ')));
}
}

View File

@@ -0,0 +1,98 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Graph } from './graphInsight';
import { ChartType, DataDirection } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
import { Builder } from 'vs/base/browser/builder';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ImageInsight } from './imageInsight';
import { TableInsight } from './tableInsight';
import { IInsightOptions, IInsight, InsightType, IInsightCtor } from './interfaces';
import { CountInsight } from './countInsight';
import { Dimension } from 'vs/base/browser/dom';
const defaultOptions: IInsightOptions = {
type: ChartType.Bar,
dataDirection: DataDirection.Horizontal
};
export class Insight {
private insight: IInsight;
private _options: IInsightOptions;
private _data: IInsightData;
private dim: Dimension
constructor(
private container: HTMLElement, options: IInsightOptions = defaultOptions,
@IInstantiationService private _instantiationService: IInstantiationService
) {
this.options = options;
this.buildInsight();
}
public layout(dim: Dimension) {
this.dim = dim;
this.insight.layout(dim);
}
public set options(val: IInsightOptions) {
this._options = val;
if (this.insight) {
// check to see if we need to change the insight type
if (!this.insight.types.includes(val.type)) {
this.buildInsight();
} else {
this.insight.options = val;
}
}
}
public get options(): IInsightOptions {
return this._options;
}
public set data(val: IInsightData) {
this._data = val;
if (this.insight) {
this.insight.data = val;
}
}
private buildInsight() {
if (this.insight) {
this.insight.dispose();
}
new Builder(this.container).empty();
let ctor = this.findctor(this.options.type);
if (ctor) {
this.insight = this._instantiationService.createInstance(ctor, this.container, this.options);
this.insight.layout(this.dim);
if (this._data) {
this.insight.data = this._data;
}
}
}
private findctor(type: ChartType | InsightType): IInsightCtor {
if (Graph.types.includes(type as ChartType)) {
return Graph;
} else if (ImageInsight.types.includes(type as InsightType)) {
return ImageInsight;
} else if (TableInsight.types.includes(type as InsightType)) {
return TableInsight;
} else if (CountInsight.types.includes(type as InsightType)) {
return CountInsight;
}
return undefined;
}
}

View File

@@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
import { ChartType, LegendPosition, DataDirection } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
import { Dimension } from 'vs/base/browser/dom';
import { DataType } from 'sql/parts/dashboard/widgets/insights/views/charts/types/lineChart.component';
export interface IInsightOptions {
type: InsightType | ChartType;
dataDirection?: DataDirection;
dataType?: DataType;
labelFirstColumn?: boolean;
columnsAsLabels?: boolean;
legendPosition?: LegendPosition;
yAxisLabel?: string;
yAxisMin?: number;
yAxisMax?: number;
xAxisLabel?: string;
xAxisMin?: number;
xAxisMax?: number;
encoding?: string;
imageFormat?: string;
}
export interface IInsight {
options: IInsightOptions;
data: IInsightData;
readonly types: Array<InsightType | ChartType>;
layout(dim: Dimension);
dispose();
}
export interface IInsightCtor {
new (container: HTMLElement, options: IInsightOptions, ...services: { _serviceBrand: any; }[]): IInsight;
readonly types: Array<InsightType | ChartType>;
}
export enum InsightType {
Image = 'image',
Table = 'table',
Count = 'count'
}

View File

@@ -0,0 +1,74 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IInsight, InsightType } from './interfaces';
import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
import { Table } from 'sql/base/browser/ui/table/table';
import { attachTableStyler } from 'sql/common/theme/styler';
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
import { $, Dimension } from 'vs/base/browser/dom';
import { Disposable } from 'vs/base/common/lifecycle';
import { IThemeService } from 'vs/platform/theme/common/themeService';
export class TableInsight extends Disposable implements IInsight {
public static readonly types = [InsightType.Table];
public readonly types = TableInsight.types;
private table: Table<any>;
private dataView: TableDataView<any>;
private columns: Slick.Column<any>[];
constructor(container: HTMLElement, options: any,
@IThemeService themeService: IThemeService
) {
super();
let tableContainer = $('div');
tableContainer.style.width = '100%';
tableContainer.style.height = '100%';
container.appendChild(tableContainer);
this.dataView = new TableDataView();
this.table = new Table(tableContainer, { dataProvider: this.dataView }, { showRowNumber: true });
this.table.setSelectionModel(new CellSelectionModel());
this._register(attachTableStyler(this.table, themeService));
}
set data(data: IInsightData) {
this.dataView.clear();
this.dataView.push(transformData(data.rows, data.columns));
this.columns = transformColumns(data.columns);
this.table.columns = this.columns;
}
layout(dim: Dimension) {
this.table.layout(dim);
}
public options;
}
function transformData(rows: string[][], columns: string[]): { [key: string]: string }[] {
return rows.map(row => {
let object: { [key: string]: string } = {};
row.forEach((val, index) => {
object[columns[index]] = val;
});
return object;
});
}
function transformColumns(columns: string[]): Slick.Column<any>[] {
return columns.map(col => {
return <Slick.Column<any>>{
name: col,
id: col,
field: col
};
});
}

View File

@@ -12,7 +12,7 @@ import { ScrollableSplitView } from 'sql/base/browser/ui/scrollableSplitview/scr
import { MouseWheelSupport } from 'sql/base/browser/ui/table/plugins/mousewheelTableScroll.plugin';
import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin';
import { SaveFormat } from 'sql/parts/grid/common/interfaces';
import { IGridActionContext, SaveResultAction, CopyResultAction, SelectAllGridAction, MaximizeTableAction, MinimizeTableAction } from 'sql/parts/query/editor/actions';
import { IGridActionContext, SaveResultAction, CopyResultAction, SelectAllGridAction, MaximizeTableAction, MinimizeTableAction, ChartDataAction } from 'sql/parts/query/editor/actions';
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColumn.plugin';
@@ -33,6 +33,7 @@ import { generateUuid } from 'vs/base/common/uuid';
import { TPromise } from 'vs/base/common/winjs.base';
import { Separator, ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { Dimension, getContentWidth } from 'vs/base/browser/dom';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
const rowHeight = 29;
const columnHeight = 26;
@@ -93,7 +94,8 @@ export class GridPanel extends ViewletPanel {
@IKeybindingService keybindingService: IKeybindingService,
@IContextMenuService contextMenuService: IContextMenuService,
@IConfigurationService configurationService: IConfigurationService,
@IThemeService private themeService: IThemeService
@IThemeService private themeService: IThemeService,
@IInstantiationService private instantiationService: IInstantiationService
) {
super(title, options, keybindingService, contextMenuService, configurationService);
this.splitView = new ScrollableSplitView(this.container, { enableResizing: false });
@@ -143,7 +145,7 @@ export class GridPanel extends ViewletPanel {
for (let set of resultsToAdd) {
let tableState = new GridTableState();
let table = new GridTable(this.runner, tableState, set, this.contextMenuService);
let table = new GridTable(this.runner, tableState, set, this.contextMenuService, this.instantiationService);
tableState.onMaximizedChange(e => {
if (e) {
this.maximizeTable(table.id);
@@ -222,7 +224,8 @@ class GridTable<T> extends Disposable implements IView {
private runner: QueryRunner,
public state: GridTableState,
private resultSet: sqlops.ResultSetSummary,
private contextMenuService: IContextMenuService
private contextMenuService: IContextMenuService,
private instantiationService: IInstantiationService
) {
super();
this.container.style.width = '100%';
@@ -283,7 +286,8 @@ class GridTable<T> extends Disposable implements IView {
actions.push(
new SaveResultAction(SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveResultAction.SAVECSV_ICON, SaveFormat.CSV),
new SaveResultAction(SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveResultAction.SAVEEXCEL_ICON, SaveFormat.EXCEL),
new SaveResultAction(SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveResultAction.SAVEJSON_ICON, SaveFormat.JSON)
new SaveResultAction(SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveResultAction.SAVEJSON_ICON, SaveFormat.JSON),
this.instantiationService.createInstance(ChartDataAction)
);
let actionBarContainer = document.createElement('div');

View File

@@ -151,6 +151,10 @@ export class QueryResultsEditor extends BaseEditor {
return TPromise.wrap<void>(null);
}
public chart(dataId: { batchId: number, resultId: number }) {
this.resultsView.chartData(dataId);
}
public dispose(): void {
super.dispose();
}

View File

@@ -16,8 +16,7 @@ import * as UUID from 'vs/base/common/uuid';
import { PanelViewlet } from 'vs/workbench/browser/parts/views/panelViewlet';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import * as DOM from 'vs/base/browser/dom';
import { Emitter } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ChartTab } from './charting/chartTab';
class ResultsView implements IPanelView {
private panelViewlet: PanelViewlet;
@@ -25,14 +24,6 @@ class ResultsView implements IPanelView {
private messagePanel: MessagePanel;
private container = document.createElement('div');
private _onRemove = new Emitter<void>();
public readonly onRemove = this._onRemove.event;
private _onLayout = new Emitter<void>();
public readonly onLayout = this._onLayout.event;
private queryRunnerDisposable: IDisposable[] = [];
constructor(instantiationService: IInstantiationService) {
this.panelViewlet = instantiationService.createInstance(PanelViewlet, 'resultsView', { showHeaderInTitleWhenSingleView: false });
this.gridPanel = instantiationService.createInstance(GridPanel, nls.localize('gridPanel', 'Results'), {});
@@ -61,6 +52,10 @@ class ResultsView implements IPanelView {
this.gridPanel.queryRunner = runner;
this.messagePanel.queryRunner = runner;
}
public hideResultHeader() {
this.gridPanel.headerVisible = false;
}
}
class ResultsTab implements IPanelTab {
@@ -68,17 +63,8 @@ class ResultsTab implements IPanelTab {
public readonly identifier = UUID.generateUuid();
public readonly view: ResultsView;
private _isAttached = false;
constructor(instantiationService: IInstantiationService) {
this.view = new ResultsView(instantiationService);
this.view.onLayout(() => this._isAttached = true, this);
this.view.onRemove(() => this._isAttached = false, this);
}
public isAttached(): boolean {
return this._isAttached;
}
public set queryRunner(runner: QueryRunner) {
@@ -90,6 +76,7 @@ export class QueryResultsView {
private _panelView: TabbedPanel;
private _input: QueryResultsInput;
private resultsTab: ResultsTab;
private chartTab: ChartTab;
constructor(
container: HTMLElement,
@@ -97,19 +84,20 @@ export class QueryResultsView {
@IQueryModelService private queryModelService: IQueryModelService
) {
this.resultsTab = new ResultsTab(instantiationService);
this.chartTab = new ChartTab(instantiationService);
this._panelView = new TabbedPanel(container, { showHeaderWhenSingleView: false });
}
public style() {
}
public set input(input: QueryResultsInput) {
this._input = input;
this.resultsTab.queryRunner = this.queryModelService._getQueryInfo(input.uri).queryRunner;
// if (!this.resultsTab.isAttached) {
let queryRunner = this.queryModelService._getQueryInfo(input.uri).queryRunner;
this.resultsTab.queryRunner = queryRunner;
this.chartTab.queryRunner = queryRunner;
this._panelView.pushTab(this.resultsTab);
// }
}
public get input(): QueryResultsInput {
@@ -119,4 +107,14 @@ export class QueryResultsView {
public layout(dimension: DOM.Dimension) {
this._panelView.layout(dimension);
}
public chartData(dataId: { resultId: number, batchId: number }): void {
if (!this._panelView.contains(this.chartTab)) {
this._panelView.pushTab(this.chartTab);
this.resultsTab.view.hideResultHeader();
}
this._panelView.showTab(this.chartTab.identifier);
this.chartTab.chart(dataId);
}
}

668
src/typings/modules/chartjs/index.d.ts vendored Normal file
View File

@@ -0,0 +1,668 @@
// Type definitions for Chart.js 2.7
// Project: https://github.com/nnnick/Chart.js
// Definitions by: Alberto Nuti <https://github.com/anuti>
// Fabien Lavocat <https://github.com/FabienLavocat>
// KentarouTakeda <https://github.com/KentarouTakeda>
// Larry Bahr <https://github.com/larrybahr>
// Daniel Luz <https://github.com/mernen>
// Joseph Page <https://github.com/josefpaij>
// Dan Manastireanu <https://github.com/danmana>
// Guillaume Rodriguez <https://github.com/guillaume-ro-fr>
// Sergey Rubanov <https://github.com/chicoxyzzy>
// Simon Archer <https://github.com/archy-bold>
// Ken Elkabany <https://github.com/braincore>
// Slavik Nychkalo <https://github.com/gebeto>
// Francesco Benedetto <https://github.com/frabnt>
// Alexandros Dorodoulis <https://github.com/alexdor>
// Manuel Heidrich <https://github.com/mahnuh>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.3
declare module 'chart.js' {
export class Chart {
static readonly Chart: typeof Chart;
constructor(
context: string | CanvasRenderingContext2D | HTMLCanvasElement | ArrayLike<CanvasRenderingContext2D | HTMLCanvasElement>,
options: Chart.ChartConfiguration
);
config: Chart.ChartConfiguration;
data: Chart.ChartData;
destroy: () => {};
update: (duration?: any, lazy?: any) => {};
render: (duration?: any, lazy?: any) => {};
stop: () => {};
resize: () => {};
clear: () => {};
options: Chart.ChartOptions;
toBase64Image: () => string;
generateLegend: () => {};
getElementAtEvent: (e: any) => {};
getElementsAtEvent: (e: any) => Array<{}>;
getDatasetAtEvent: (e: any) => Array<{}>;
getDatasetMeta: (index: number) => Meta;
ctx: CanvasRenderingContext2D | null;
canvas: HTMLCanvasElement | null;
chartArea: Chart.ChartArea;
static pluginService: PluginServiceStatic;
static plugins: PluginServiceStatic;
static defaults: {
global: Chart.ChartOptions & Chart.ChartFontOptions;
[key: string]: any;
};
static controllers: {
[key: string]: any;
};
static helpers: {
[key: string]: any;
};
// Tooltip Static Options
static Tooltip: Chart.ChartTooltipsStaticConfiguration;
}
export class PluginServiceStatic {
register(plugin: PluginServiceGlobalRegistration & PluginServiceRegistrationOptions): void;
unregister(plugin: PluginServiceGlobalRegistration & PluginServiceRegistrationOptions): void;
}
interface PluginServiceGlobalRegistration {
id?: string;
}
interface PluginServiceRegistrationOptions {
beforeInit?(chartInstance: Chart, options?: any): void;
afterInit?(chartInstance: Chart, options?: any): void;
beforeUpdate?(chartInstance: Chart, options?: any): void;
afterUpdate?(chartInstance: Chart, options?: any): void;
beforeLayout?(chartInstance: Chart, options?: any): void;
afterLayout?(chartInstance: Chart, options?: any): void;
beforeDatasetsUpdate?(chartInstance: Chart, options?: any): void;
afterDatasetsUpdate?(chartInstance: Chart, options?: any): void;
beforeDatasetUpdate?(chartInstance: Chart, options?: any): void;
afterDatasetUpdate?(chartInstance: Chart, options?: any): void;
// This is called at the start of a render. It is only called once, even if the animation will run for a number of frames. Use beforeDraw or afterDraw
// to do something on each animation frame
beforeRender?(chartInstance: Chart, options?: any): void;
afterRender?(chartInstance: Chart, options?: any): void;
// Easing is for animation
beforeDraw?(chartInstance: Chart, easing: string, options?: any): void;
afterDraw?(chartInstance: Chart, easing: string, options?: any): void;
// Before the datasets are drawn but after scales are drawn
beforeDatasetsDraw?(chartInstance: Chart, easing: string, options?: any): void;
afterDatasetsDraw?(chartInstance: Chart, easing: string, options?: any): void;
beforeDatasetDraw?(chartInstance: Chart, easing: string, options?: any): void;
afterDatasetDraw?(chartInstance: Chart, easing: string, options?: any): void;
// Called before drawing the `tooltip`. If any plugin returns `false`,
// the tooltip drawing is cancelled until another `render` is triggered.
beforeTooltipDraw?(chartInstance: Chart, tooltipData?: any, options?: any): void;
// Called after drawing the `tooltip`. Note that this hook will not,
// be called if the tooltip drawing has been previously cancelled.
afterTooltipDraw?(chartInstance: Chart, tooltipData?: any, options?: any): void;
// Called when an event occurs on the chart
beforeEvent?(chartInstance: Chart, event: Event, options?: any): void;
afterEvent?(chartInstance: Chart, event: Event, options?: any): void;
resize?(chartInstance: Chart, newChartSize: Chart.ChartSize, options?: any): void;
destroy?(chartInstance: Chart): void;
/** @deprecated since version 2.5.0. Use `afterLayout` instead. */
afterScaleUpdate?(chartInstance: Chart, options?: any): void;
}
interface Meta {
type: Chart.ChartType;
data: MetaData[];
dataset?: Chart.ChartDataSets;
controller: { [key: string]: any; };
hidden?: boolean;
total?: string;
xAxisID?: string;
yAxisID?: string;
"$filler"?: { [key: string]: any; };
}
interface MetaData {
_chart: Chart;
_datasetIndex: number;
_index: number;
_model: Model;
_start?: any;
_view: Model;
_xScale: Chart.ChartScales;
_yScale: Chart.ChartScales;
hidden?: boolean;
}
interface Model {
backgroundColor: string;
borderColor: string;
borderWidth?: number;
controlPointNextX: number;
controlPointNextY: number;
controlPointPreviousX: number;
controlPointPreviousY: number;
hitRadius: number;
pointStyle: string;
radius: string;
skip?: boolean;
steppedLine?: undefined;
tension: number;
x: number;
y: number;
}
export namespace Chart {
type ChartType = 'line' | 'bar' | 'horizontalBar' | 'radar' | 'doughnut' | 'polarArea' | 'bubble' | 'pie';
type TimeUnit = 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
type ScaleType = 'category' | 'linear' | 'logarithmic' | 'time' | 'radialLinear';
type PointStyle = 'circle' | 'cross' | 'crossRot' | 'dash' | 'line' | 'rect' | 'rectRounded' | 'rectRot' | 'star' | 'triangle';
type PositionType = 'left' | 'right' | 'top' | 'bottom';
interface ChartArea {
top: number;
right: number;
bottom: number;
left: number;
}
interface ChartLegendItem {
text?: string;
fillStyle?: string;
hidden?: boolean;
lineCap?: string;
lineDash?: number[];
lineDashOffset?: number;
lineJoin?: string;
lineWidth?: number;
strokeStyle?: string;
pointStyle?: PointStyle;
}
interface ChartLegendLabelItem extends ChartLegendItem {
datasetIndex: number;
}
interface ChartTooltipItem {
xLabel?: string;
yLabel?: string;
datasetIndex?: number;
index?: number;
}
interface ChartTooltipLabelColor {
borderColor: ChartColor;
backgroundColor: ChartColor;
}
interface ChartTooltipCallback {
beforeTitle?(item: ChartTooltipItem[], data: ChartData): string | string[];
title?(item: ChartTooltipItem[], data: ChartData): string | string[];
afterTitle?(item: ChartTooltipItem[], data: ChartData): string | string[];
beforeBody?(item: ChartTooltipItem[], data: ChartData): string | string[];
beforeLabel?(tooltipItem: ChartTooltipItem, data: ChartData): string | string[];
label?(tooltipItem: ChartTooltipItem, data: ChartData): string | string[];
labelColor?(tooltipItem: ChartTooltipItem, chart: Chart): ChartTooltipLabelColor;
labelTextColor?(tooltipItem: ChartTooltipItem, chart: Chart): string;
afterLabel?(tooltipItem: ChartTooltipItem, data: ChartData): string | string[];
afterBody?(item: ChartTooltipItem[], data: ChartData): string | string[];
beforeFooter?(item: ChartTooltipItem[], data: ChartData): string | string[];
footer?(item: ChartTooltipItem[], data: ChartData): string | string[];
afterFooter?(item: ChartTooltipItem[], data: ChartData): string | string[];
}
interface ChartAnimationParameter {
chartInstance?: any;
animationObject?: any;
}
interface ChartPoint {
x?: number | string | Date;
y?: number | string | Date;
r?: number;
t?: number | string | Date;
}
interface ChartConfiguration {
type?: ChartType | string;
data?: ChartData;
options?: ChartOptions;
plugins?: PluginServiceRegistrationOptions[];
}
interface ChartData {
labels?: Array<string | string[]>;
datasets?: ChartDataSets[];
}
interface RadialChartOptions extends ChartOptions {
scale?: RadialLinearScale;
}
interface ChartSize {
height: number;
width: number;
}
interface ChartOptions {
responsive?: boolean;
responsiveAnimationDuration?: number;
aspectRatio?: number;
maintainAspectRatio?: boolean;
events?: string[];
onHover?(this: Chart, event: MouseEvent, activeElements: Array<{}>): any;
onClick?(event?: MouseEvent, activeElements?: Array<{}>): any;
onResize?(this: Chart, newSize: ChartSize): void;
title?: ChartTitleOptions;
legend?: ChartLegendOptions;
tooltips?: ChartTooltipOptions;
hover?: ChartHoverOptions;
animation?: ChartAnimationOptions;
elements?: ChartElementsOptions;
layout?: ChartLayoutOptions;
scales?: ChartScales;
showLines?: boolean;
spanGaps?: boolean;
cutoutPercentage?: number;
circumference?: number;
rotation?: number;
devicePixelRatio?: number;
// Plugins can require any options
plugins?: { [pluginId: string]: any };
}
interface ChartFontOptions {
defaultFontColor?: ChartColor;
defaultFontFamily?: string;
defaultFontSize?: number;
defaultFontStyle?: string;
}
interface ChartTitleOptions {
display?: boolean;
position?: PositionType;
fullWidth?: boolean;
fontSize?: number;
fontFamily?: string;
fontColor?: ChartColor;
fontStyle?: string;
padding?: number;
text?: string | string[];
}
interface ChartLegendOptions {
display?: boolean;
position?: PositionType;
fullWidth?: boolean;
onClick?(event: MouseEvent, legendItem: ChartLegendLabelItem): void;
onHover?(event: MouseEvent, legendItem: ChartLegendLabelItem): void;
labels?: ChartLegendLabelOptions;
reverse?: boolean;
}
interface ChartLegendLabelOptions {
boxWidth?: number;
fontSize?: number;
fontStyle?: string;
fontColor?: ChartColor;
fontFamily?: string;
padding?: number;
generateLabels?(chart: any): any;
filter?(legendItem: ChartLegendLabelItem, data: ChartData): any;
usePointStyle?: boolean;
}
interface ChartTooltipOptions {
enabled?: boolean;
custom?(a: any): void;
mode?: string;
intersect?: boolean;
backgroundColor?: ChartColor;
titleFontFamily?: string;
titleFontSize?: number;
titleFontStyle?: string;
titleFontColor?: ChartColor;
titleSpacing?: number;
titleMarginBottom?: number;
bodyFontFamily?: string;
bodyFontSize?: number;
bodyFontStyle?: string;
bodyFontColor?: ChartColor;
bodySpacing?: number;
footerFontFamily?: string;
footerFontSize?: number;
footerFontStyle?: string;
footerFontColor?: ChartColor;
footerSpacing?: number;
footerMarginTop?: number;
xPadding?: number;
yPadding?: number;
caretSize?: number;
cornerRadius?: number;
multiKeyBackground?: string;
callbacks?: ChartTooltipCallback;
filter?(item: ChartTooltipItem): boolean;
itemSort?(itemA: ChartTooltipItem, itemB: ChartTooltipItem): number;
position?: string;
caretPadding?: number;
displayColors?: boolean;
borderColor?: ChartColor;
borderWidth?: number;
}
interface ChartTooltipsStaticConfiguration {
positioners: { [mode: string]: ChartTooltipPositioner };
}
type ChartTooltipPositioner = (elements: any[], eventPosition: Point) => Point;
interface ChartHoverOptions {
mode?: string;
animationDuration?: number;
intersect?: boolean;
onHover?(this: Chart, event: MouseEvent, activeElements: Array<{}>): any;
}
interface ChartAnimationObject {
currentStep?: number;
numSteps?: number;
easing?: string;
render?(arg: any): void;
onAnimationProgress?(arg: any): void;
onAnimationComplete?(arg: any): void;
}
interface ChartAnimationOptions {
duration?: number;
easing?: string;
onProgress?(chart: any): void;
onComplete?(chart: any): void;
}
interface ChartElementsOptions {
point?: ChartPointOptions;
line?: ChartLineOptions;
arc?: ChartArcOptions;
rectangle?: ChartRectangleOptions;
}
interface ChartArcOptions {
backgroundColor?: ChartColor;
borderColor?: ChartColor;
borderWidth?: number;
}
interface ChartLineOptions {
tension?: number;
backgroundColor?: ChartColor;
borderWidth?: number;
borderColor?: ChartColor;
borderCapStyle?: string;
borderDash?: any[];
borderDashOffset?: number;
borderJoinStyle?: string;
capBezierPoints?: boolean;
fill?: 'zero' | 'top' | 'bottom' | boolean;
stepped?: boolean;
}
interface ChartPointOptions {
radius?: number;
pointStyle?: PointStyle;
backgroundColor?: ChartColor;
borderWidth?: number;
borderColor?: ChartColor;
hitRadius?: number;
hoverRadius?: number;
hoverBorderWidth?: number;
}
interface ChartRectangleOptions {
backgroundColor?: ChartColor;
borderWidth?: number;
borderColor?: ChartColor;
borderSkipped?: string;
}
interface ChartLayoutOptions {
padding?: ChartLayoutPaddingObject | number;
}
interface ChartLayoutPaddingObject {
top?: number;
right?: number;
bottom?: number;
left?: number;
}
interface GridLineOptions {
display?: boolean;
color?: ChartColor;
borderDash?: number[];
borderDashOffset?: number;
lineWidth?: number;
drawBorder?: boolean;
drawOnChartArea?: boolean;
drawTicks?: boolean;
tickMarkLength?: number;
zeroLineWidth?: number;
zeroLineColor?: ChartColor;
zeroLineBorderDash?: number[];
zeroLineBorderDashOffset?: number;
offsetGridLines?: boolean;
}
interface ScaleTitleOptions {
display?: boolean;
labelString?: string;
fontColor?: ChartColor;
fontFamily?: string;
fontSize?: number;
fontStyle?: string;
}
interface TickOptions {
autoSkip?: boolean;
autoSkipPadding?: number;
backdropColor?: ChartColor;
backdropPaddingX?: number;
backdropPaddingY?: number;
beginAtZero?: boolean;
callback?(value: any, index: any, values: any): string | number;
display?: boolean;
fontColor?: ChartColor;
fontFamily?: string;
fontSize?: number;
fontStyle?: string;
labelOffset?: number;
max?: any;
maxRotation?: number;
maxTicksLimit?: number;
min?: any;
minRotation?: number;
mirror?: boolean;
padding?: number;
reverse?: boolean;
showLabelBackdrop?: boolean;
source?: 'auto' | 'data' | 'labels';
}
interface AngleLineOptions {
display?: boolean;
color?: ChartColor;
lineWidth?: number;
}
interface PointLabelOptions {
callback?(arg: any): any;
fontColor?: ChartColor;
fontFamily?: string;
fontSize?: number;
fontStyle?: string;
}
interface LinearTickOptions extends TickOptions {
maxTicksLimit?: number;
stepSize?: number;
suggestedMin?: number;
suggestedMax?: number;
}
// tslint:disable-next-line no-empty-interface
interface LogarithmicTickOptions extends TickOptions {
}
type ChartColor = string | CanvasGradient | CanvasPattern | string[];
interface ChartDataSets {
cubicInterpolationMode?: 'default' | 'monotone';
backgroundColor?: ChartColor | ChartColor[];
borderWidth?: number | number[];
borderColor?: ChartColor | ChartColor[];
borderCapStyle?: string;
borderDash?: number[];
borderDashOffset?: number;
borderJoinStyle?: string;
borderSkipped?: PositionType;
data?: number[] | ChartPoint[];
fill?: boolean | number | string;
hoverBackgroundColor?: string | string[];
hoverBorderColor?: string | string[];
hoverBorderWidth?: number | number[];
label?: string;
lineTension?: number;
steppedLine?: 'before' | 'after' | boolean;
pointBorderColor?: ChartColor | ChartColor[];
pointBackgroundColor?: ChartColor | ChartColor[];
pointBorderWidth?: number | number[];
pointRadius?: number | number[];
pointHoverRadius?: number | number[];
pointHitRadius?: number | number[];
pointHoverBackgroundColor?: ChartColor | ChartColor[];
pointHoverBorderColor?: ChartColor | ChartColor[];
pointHoverBorderWidth?: number | number[];
pointStyle?: PointStyle | HTMLImageElement | HTMLCanvasElement | Array<PointStyle | HTMLImageElement | HTMLCanvasElement>;
xAxisID?: string;
yAxisID?: string;
type?: string;
hidden?: boolean;
hideInLegendAndTooltip?: boolean;
showLine?: boolean;
stack?: string;
spanGaps?: boolean;
}
interface ChartScales {
type?: ScaleType | string;
display?: boolean;
position?: PositionType | string;
gridLines?: GridLineOptions;
scaleLabel?: ScaleTitleOptions;
ticks?: TickOptions;
xAxes?: ChartXAxe[];
yAxes?: ChartYAxe[];
}
interface CommonAxe {
bounds?: string;
type?: ScaleType | string;
display?: boolean;
id?: string;
stacked?: boolean;
position?: string;
ticks?: TickOptions;
gridLines?: GridLineOptions;
barThickness?: number | "flex";
maxBarThickness?: number;
scaleLabel?: ScaleTitleOptions;
time?: TimeScale;
offset?: boolean;
beforeUpdate?(scale?: any): void;
beforeSetDimension?(scale?: any): void;
beforeDataLimits?(scale?: any): void;
beforeBuildTicks?(scale?: any): void;
beforeTickToLabelConversion?(scale?: any): void;
beforeCalculateTickRotation?(scale?: any): void;
beforeFit?(scale?: any): void;
afterUpdate?(scale?: any): void;
afterSetDimension?(scale?: any): void;
afterDataLimits?(scale?: any): void;
afterBuildTicks?(scale?: any): void;
afterTickToLabelConversion?(scale?: any): void;
afterCalculateTickRotation?(scale?: any): void;
afterFit?(scale?: any): void;
}
interface ChartXAxe extends CommonAxe {
categoryPercentage?: number;
barPercentage?: number;
distribution?: 'linear' | 'series';
}
// tslint:disable-next-line no-empty-interface
interface ChartYAxe extends CommonAxe {
}
interface LinearScale extends ChartScales {
ticks?: LinearTickOptions;
}
interface LogarithmicScale extends ChartScales {
ticks?: LogarithmicTickOptions;
}
interface TimeDisplayFormat {
millisecond?: string;
second?: string;
minute?: string;
hour?: string;
day?: string;
week?: string;
month?: string;
quarter?: string;
year?: string;
}
interface TimeScale extends ChartScales {
displayFormats?: TimeDisplayFormat;
isoWeekday?: boolean;
max?: string;
min?: string;
parser?: string | ((arg: any) => any);
round?: TimeUnit;
tooltipFormat?: string;
unit?: TimeUnit;
unitStepSize?: number;
stepSize?: number;
minUnit?: TimeUnit;
}
interface RadialLinearScale extends LinearScale {
lineArc?: boolean;
angleLines?: AngleLineOptions;
pointLabels?: PointLabelOptions;
ticks?: TickOptions;
}
interface Point {
x: number;
y: number;
}
}
}

View File

@@ -53,6 +53,10 @@
normalize-path "^2.0.1"
through2 "^2.0.3"
"@types/chart.js@^2.7.31":
version "2.7.31"
resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.7.31.tgz#fe2c28d3defa461f5d5cd01f1fac635df649472b"
"@types/commander@^2.11.0":
version "2.12.2"
resolved "https://registry.yarnpkg.com/@types/commander/-/commander-2.12.2.tgz#183041a23842d4281478fa5d23c5ca78e6fd08ae"