mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-18 17:22:45 -05:00
Rework timeSeries in chart viewer (#2987)
* rework timeSeries in chart viewer * rework important to fix tests
This commit is contained in:
committed by
Karl Burtram
parent
724c49f5c4
commit
7dfcd89a04
@@ -10,10 +10,9 @@ import * as TelemetryUtils from 'sql/common/telemetryUtilities';
|
||||
import { IInsightsView, IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
|
||||
import { memoize, unmemoize } from 'sql/base/common/decorators';
|
||||
import { mixin } from 'sql/base/common/objects';
|
||||
import { LegendPosition, DataDirection, ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
import { LegendPosition, ChartType, defaultChartConfig, IChartConfig, IDataSet, IPointDataSet } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
|
||||
import * as colors from 'vs/platform/theme/common/colorRegistry';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
@@ -22,51 +21,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
declare var Chart: any;
|
||||
|
||||
export function customMixin(destination: any, source: any, overwrite?: boolean): any {
|
||||
if (types.isObject(source)) {
|
||||
mixin(destination, source, overwrite, customMixin);
|
||||
} else if (types.isArray(source)) {
|
||||
for (let i = 0; i < source.length; i++) {
|
||||
if (destination[i]) {
|
||||
mixin(destination[i], source[i], overwrite, customMixin);
|
||||
} else {
|
||||
destination[i] = source[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
destination = source;
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
export interface IDataSet {
|
||||
data: Array<number>;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface IPointDataSet {
|
||||
data: Array<{ x: number | string, y: number }>;
|
||||
label?: string;
|
||||
fill: boolean;
|
||||
backgroundColor?: Color;
|
||||
}
|
||||
|
||||
export interface IChartConfig {
|
||||
colorMap?: { [column: string]: string };
|
||||
labelFirstColumn?: boolean;
|
||||
legendPosition?: LegendPosition;
|
||||
dataDirection?: DataDirection;
|
||||
columnsAsLabels?: boolean;
|
||||
showTopNData?: number;
|
||||
}
|
||||
|
||||
export const defaultChartConfig: IChartConfig = {
|
||||
labelFirstColumn: true,
|
||||
columnsAsLabels: true,
|
||||
legendPosition: LegendPosition.Top,
|
||||
dataDirection: DataDirection.Vertical
|
||||
};
|
||||
|
||||
@Component({
|
||||
template: ` <div style="display: block; width: 100%; height: 100%; position: relative">
|
||||
<canvas #canvas *ngIf="_isDataAvailable && _hasInit"
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import * as types from 'vs/base/common/types';
|
||||
|
||||
import { mixin } from 'sql/base/common/objects';
|
||||
|
||||
export enum ChartType {
|
||||
Bar = 'bar',
|
||||
Doughnut = 'doughnut',
|
||||
@@ -29,4 +34,49 @@ export enum LegendPosition {
|
||||
export enum DataType {
|
||||
Number = 'number',
|
||||
Point = 'point'
|
||||
}
|
||||
}
|
||||
|
||||
export function customMixin(destination: any, source: any, overwrite?: boolean): any {
|
||||
if (types.isObject(source)) {
|
||||
mixin(destination, source, overwrite, customMixin);
|
||||
} else if (types.isArray(source)) {
|
||||
for (let i = 0; i < source.length; i++) {
|
||||
if (destination[i]) {
|
||||
mixin(destination[i], source[i], overwrite, customMixin);
|
||||
} else {
|
||||
destination[i] = source[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
destination = source;
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
export interface IDataSet {
|
||||
data: Array<number>;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface IPointDataSet {
|
||||
data: Array<{ x: number | string, y: number }>;
|
||||
label?: string;
|
||||
fill: boolean;
|
||||
backgroundColor?: Color;
|
||||
}
|
||||
|
||||
export interface IChartConfig {
|
||||
colorMap?: { [column: string]: string };
|
||||
labelFirstColumn?: boolean;
|
||||
legendPosition?: LegendPosition;
|
||||
dataDirection?: DataDirection;
|
||||
columnsAsLabels?: boolean;
|
||||
showTopNData?: number;
|
||||
}
|
||||
|
||||
export const defaultChartConfig: IChartConfig = {
|
||||
labelFirstColumn: true,
|
||||
columnsAsLabels: true,
|
||||
legendPosition: LegendPosition.Top,
|
||||
dataDirection: DataDirection.Vertical
|
||||
};
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ChartInsight, customMixin, IChartConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import { ChartInsight } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import { mixin } from 'sql/base/common/objects';
|
||||
import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
import { ChartType, IChartConfig, customMixin } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
|
||||
import { IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import * as colors from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
|
||||
import { defaultChartConfig, IDataSet, IPointDataSet } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import BarChart, { IBarChartConfig } from './barChart.component';
|
||||
import { memoize, unmemoize } from 'sql/base/common/decorators';
|
||||
import { clone } from 'sql/base/common/objects';
|
||||
import { ChartType, DataType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
import { ChartType, DataType, defaultChartConfig, IDataSet, IPointDataSet } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
|
||||
export interface ILineConfig extends IBarChartConfig {
|
||||
dataType?: DataType;
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { defaultChartConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import LineChart, { ILineConfig } from './lineChart.component';
|
||||
import { clone } from 'sql/base/common/objects';
|
||||
import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
import { ChartType, defaultChartConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { defaultChartConfig, IPointDataSet } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import LineChart, { ILineConfig } from './lineChart.component';
|
||||
import { clone } from 'sql/base/common/objects';
|
||||
import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
import { ChartType, defaultChartConfig, IPointDataSet } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
|
||||
@@ -18,7 +18,8 @@ export enum ControlType {
|
||||
combo,
|
||||
numberInput,
|
||||
input,
|
||||
checkbox
|
||||
checkbox,
|
||||
dateInput
|
||||
}
|
||||
|
||||
export interface IChartOption {
|
||||
@@ -115,6 +116,20 @@ const xAxisMaxInput: IChartOption = {
|
||||
default: undefined
|
||||
};
|
||||
|
||||
const xAxisMinDateInput: IChartOption = {
|
||||
label: localize('xAxisMinDate', 'X Axis Minimum Date'),
|
||||
type: ControlType.dateInput,
|
||||
configEntry: 'xAxisMin',
|
||||
default: undefined
|
||||
};
|
||||
|
||||
const xAxisMaxDateInput: IChartOption = {
|
||||
label: localize('xAxisMaxDate', 'X Axis Maximum Date'),
|
||||
type: ControlType.dateInput,
|
||||
configEntry: 'xAxisMax',
|
||||
default: undefined
|
||||
};
|
||||
|
||||
const dataTypeInput: IChartOption = {
|
||||
label: localize('dataTypeLabel', 'Data Type'),
|
||||
type: ControlType.combo,
|
||||
@@ -150,7 +165,11 @@ export const ChartOptions: IChartOptions = {
|
||||
[ChartType.TimeSeries]: [
|
||||
legendInput,
|
||||
yAxisLabelInput,
|
||||
xAxisLabelInput
|
||||
yAxisMinInput,
|
||||
yAxisMaxInput,
|
||||
xAxisLabelInput,
|
||||
xAxisMinDateInput,
|
||||
xAxisMaxDateInput,
|
||||
],
|
||||
[ChartType.Bar]: [
|
||||
dataDirectionOption,
|
||||
|
||||
@@ -275,7 +275,7 @@ export class ChartView implements IPanelView {
|
||||
dropdown.render(optionContainer);
|
||||
dropdown.onDidSelect(e => {
|
||||
if (this.options[option.configEntry] !== option.options[e.index]) {
|
||||
this.options[option.configEntry] = option.options[e.index] === 'timeSeries' ? 'line' : option.options[e.index];
|
||||
this.options[option.configEntry] = option.options[e.index];
|
||||
if (this.insight) {
|
||||
this.insight.options = this.options;
|
||||
}
|
||||
@@ -324,6 +324,24 @@ export class ChartView implements IPanelView {
|
||||
};
|
||||
this.optionDisposables.push(attachInputBoxStyler(numberInput, this._themeService));
|
||||
break;
|
||||
case ControlType.dateInput:
|
||||
let dateInput = new InputBox(optionContainer, this._contextViewService, { type: 'date' });
|
||||
dateInput.value = value || '';
|
||||
dateInput.onDidChange(e => {
|
||||
if (this.options[option.configEntry] !== e) {
|
||||
this.options[option.configEntry] = e;
|
||||
if (this.insight) {
|
||||
this.insight.options = this.options;
|
||||
}
|
||||
}
|
||||
});
|
||||
setFunc = (val: string) => {
|
||||
if (!isUndefinedOrNull(val)) {
|
||||
dateInput.value = val;
|
||||
}
|
||||
};
|
||||
this.optionDisposables.push(attachInputBoxStyler(dateInput, this._themeService));
|
||||
break;
|
||||
}
|
||||
this.optionMap[option.configEntry] = { element: optionContainer, set: setFunc };
|
||||
container.appendChild(optionContainer);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { Chart as ChartJs } from 'chart.js';
|
||||
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { mixin } from 'sql/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';
|
||||
@@ -15,10 +15,28 @@ import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
|
||||
import { IInsightOptions, IInsight } from './interfaces';
|
||||
import { ChartType, DataDirection, LegendPosition } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
import { ChartType, DataDirection, LegendPosition, DataType, IPointDataSet, customMixin } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
|
||||
const noneLineGraphs = [ChartType.Doughnut, ChartType.Pie];
|
||||
|
||||
const timeSeriesScales = {
|
||||
scales: {
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
display: true,
|
||||
ticks: {
|
||||
autoSkip: false,
|
||||
maxRotation: 45,
|
||||
minRotation: 45
|
||||
}
|
||||
}],
|
||||
|
||||
yAxes: [{
|
||||
display: true,
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
const defaultOptions: IInsightOptions = {
|
||||
type: ChartType.Bar,
|
||||
dataDirection: DataDirection.Horizontal
|
||||
@@ -30,6 +48,8 @@ export class Graph implements IInsight {
|
||||
private chartjs: ChartJs;
|
||||
private _data: IInsightData;
|
||||
|
||||
private originalType: ChartType;
|
||||
|
||||
public static readonly types = [ChartType.Bar, ChartType.Doughnut, ChartType.HorizontalBar, ChartType.Line, ChartType.Pie, ChartType.Scatter, ChartType.TimeSeries];
|
||||
public readonly types = Graph.types;
|
||||
|
||||
@@ -83,37 +103,51 @@ export class Graph implements IInsight {
|
||||
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)
|
||||
};
|
||||
});
|
||||
}
|
||||
if (this.originalType === ChartType.TimeSeries) {
|
||||
let dataSetMap: { [label: string]: IPointDataSet } = {};
|
||||
this._data.rows.map(row => {
|
||||
if (row && row.length >= 3) {
|
||||
let legend = row[0];
|
||||
if (!dataSetMap[legend]) {
|
||||
dataSetMap[legend] = { label: legend, data: [], fill: false };
|
||||
}
|
||||
dataSetMap[legend].data.push({ x: row[1], y: Number(row[2]) });
|
||||
}
|
||||
});
|
||||
chartData = Object.values(dataSetMap);
|
||||
} 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]
|
||||
};
|
||||
});
|
||||
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 {
|
||||
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)
|
||||
};
|
||||
});
|
||||
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)
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +221,35 @@ export class Graph implements IInsight {
|
||||
color: gridLines
|
||||
}
|
||||
}];
|
||||
|
||||
if (this.originalType === ChartType.TimeSeries) {
|
||||
retval = mixin(retval, timeSeriesScales, true, customMixin);
|
||||
if (options.xAxisMax) {
|
||||
retval = mixin(retval, {
|
||||
scales: {
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
time: {
|
||||
max: options.xAxisMax
|
||||
}
|
||||
}],
|
||||
}
|
||||
}, true, customMixin);
|
||||
}
|
||||
|
||||
if (options.xAxisMin) {
|
||||
retval = mixin(retval, {
|
||||
scales: {
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
time: {
|
||||
min: options.xAxisMin
|
||||
}
|
||||
}],
|
||||
}
|
||||
}, true, customMixin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
retval.legend = <ChartJs.ChartLegendOptions>{
|
||||
@@ -208,6 +271,12 @@ export class Graph implements IInsight {
|
||||
|
||||
public set options(options: IInsightOptions) {
|
||||
this._options = options;
|
||||
this.originalType = options.type as ChartType;
|
||||
if (this.options.type === ChartType.TimeSeries) {
|
||||
this.options.type = ChartType.Line;
|
||||
this.options.dataType = DataType.Point;
|
||||
this.options.dataDirection = DataDirection.Horizontal;
|
||||
}
|
||||
this.data = this._data;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { Graph } from './graphInsight';
|
||||
import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
|
||||
import { DataDirection, ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
import { DataDirection, ChartType, DataType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
import { ImageInsight } from './imageInsight';
|
||||
import { TableInsight } from './tableInsight';
|
||||
import { IInsightOptions, IInsight, InsightType, IInsightCtor } from './interfaces';
|
||||
@@ -16,6 +16,7 @@ import { CountInsight } from './countInsight';
|
||||
import { Builder } from 'vs/base/browser/builder';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Dimension } from 'vs/base/browser/dom';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
|
||||
const defaultOptions: IInsightOptions = {
|
||||
type: ChartType.Bar,
|
||||
@@ -47,13 +48,13 @@ export class Insight {
|
||||
}
|
||||
|
||||
public set options(val: IInsightOptions) {
|
||||
this._options = val;
|
||||
this._options = deepClone(val);
|
||||
if (this.insight) {
|
||||
// check to see if we need to change the insight type
|
||||
if (!this.insight.types.includes(val.type)) {
|
||||
if (!this.insight.types.includes(this.options.type)) {
|
||||
this.buildInsight();
|
||||
} else {
|
||||
this.insight.options = val;
|
||||
this.insight.options = this.options;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user