Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create switch to render line charts using vega-lite #3106

Large diffs are not rendered by default.

196 changes: 126 additions & 70 deletions src/plugins/vis_type_vega/public/expressions/line_vega_spec_fn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,36 @@ interface Arguments {
dimensions: string;
}

export type VegaSpecExpressionFunctionDefinition = ExpressionFunctionDefinition<
export type LineVegaSpecExpressionFunctionDefinition = ExpressionFunctionDefinition<
'line_vega_spec',
Input,
Arguments,
Output
>;

// TODO: move this to the visualization plugin that has VisParams once all of these parameters have been better defined
interface ValueAxis {
id: string;
labels: {
filter: boolean;
rotate: number;
show: boolean;
truncate: number;
};
name: string;
position: string;
scale: {
mode: string;
type: string;
};
show: true;
style: any;
title: {
text: string;
};
type: string;
}

// Get the first xaxis field as only 1 setup of X Axis will be supported and
// there won't be support for split series and split chart
const getXAxisId = (dimensions: any, columns: OpenSearchDashboardsDatatableColumn[]): string => {
Expand All @@ -49,6 +72,80 @@ export const formatDataTable = (
return datatable;
};

export const setupConfig = (visParams: VisParams) => {
const legendPosition = visParams.legendPosition;
return {
view: {
stroke: null,
},
concat: {
spacing: 0,
},
legend: {
orient: legendPosition,
},
};
};

export const buildLayerMark = (seriesParams: {
type: string;
interpolate: string;
lineWidth: number;
showCircles: boolean;
}) => {
return {
// Possible types are: line, area, histogram. The eligibility checker will
// prevent area and histogram (though area works in vega-lite)
type: seriesParams.type,
// Possible types: linear, cardinal, step-after. All of these types work in vega-lite
interpolate: seriesParams.interpolate,
// The possible values is any number, which matches what vega-lite supports
strokeWidth: seriesParams.lineWidth,
// this corresponds to showing the dots in the visbuilder for each data point
point: seriesParams.showCircles,
};
};

export const buildXAxis = (
xAxisTitle: string,
xAxisId: string,
startTime: number,
endTime: number,
visParams: VisParams
) => {
return {
axis: {
title: xAxisTitle,
grid: visParams.grid.categoryLines,
},
field: xAxisId,
// Right now, the line charts can only set the x-axis value to be a date attribute, so
// this should always be of type temporal
type: 'temporal',
scale: {
domain: [startTime, endTime],
},
};
};

export const buildYAxis = (
column: OpenSearchDashboardsDatatableColumn,
valueAxis: ValueAxis,
visParams: VisParams
) => {
return {
axis: {
title: cleanString(valueAxis.title.text) || column.name,
grid: visParams.grid.valueAxis,
orient: valueAxis.position,
labels: valueAxis.labels.show,
labelAngle: valueAxis.labels.rotate,
},
field: column.id,
type: 'quantitative',
};
};

export const createSpecFromDatatable = (
datatable: OpenSearchDashboardsDatatable,
visParams: VisParams,
Expand All @@ -59,56 +156,34 @@ export const createSpecFromDatatable = (
// For now, we make this more loosely-typed by just specifying it as a generic object.
const spec = {} as any;

const legendPosition = visParams.legendPosition;

spec.$schema = 'https://vega.github.io/schema/vega-lite/v5.json';
spec.data = {
values: datatable.rows,
};
spec.config = {
view: {
stroke: null,
},
concat: {
spacing: 0,
},
// the circle timeline representing annotations
circle: {
color: 'blue',
},
// the vertical line when user hovers over an annotation circle
rule: {
color: 'red',
},
legend: {
orient: legendPosition,
},
};
spec.config = setupConfig(visParams);

// Get the valueAxes data and generate a map to easily fetch the different valueAxes data
const valueAxis = {};
visParams?.valueAxes.forEach((yAxis: { id: { toString: () => string | number } }) => {
// @ts-ignore
valueAxis[yAxis.id.toString()] = yAxis;
const valueAxis = new Map();
visParams?.valueAxes?.forEach((yAxis: ValueAxis) => {
valueAxis.set(yAxis.id, yAxis);
});

spec.layer = [] as any[];

if (datatable.rows.length > 0 && dimensions.x != null) {
if (datatable.rows.length > 0 && dimensions.x !== null) {
const xAxisId = getXAxisId(dimensions, datatable.columns);
const xAxisTitle = cleanString(dimensions.x.label);
// get x-axis bounds for the chart
const startTime = new Date(dimensions.x.params.bounds.min).valueOf();
const endTime = new Date(dimensions.x.params.bounds.max).valueOf();
let skip = 0;
datatable.columns.forEach((column, index) => {
// Check if its not xAxis column data
if (column.meta?.aggConfigParams?.interval != null) {
// Check if it's not xAxis column data
if (column.meta?.aggConfigParams?.interval !== undefined) {
skip++;
} else {
const currentSeriesParams = visParams.seriesParams[index - skip];
const currentValueAxis =
// @ts-ignore
valueAxis[currentSeriesParams.valueAxis.toString()];
const currentValueAxis = valueAxis.get(currentSeriesParams.valueAxis.toString());
let tooltip: Array<{ field: string; type: string; title: string }> = [];
lezzago marked this conversation as resolved.
Show resolved Hide resolved
if (visParams.addTooltip) {
tooltip = [
Expand All @@ -117,42 +192,13 @@ export const createSpecFromDatatable = (
];
}
spec.layer.push({
mark: {
// Possible types are: line, area, histogram. The eligibility checker will
// prevent area and histogram (though area works in vega-lite)
type: currentSeriesParams.type,
// Possible types: linear, cardinal, step-after. All of these types work in vega-lite
interpolate: currentSeriesParams.interpolate,
// The possible values is any number, which matches what vega-lite supports
strokeWidth: currentSeriesParams.lineWidth,
// this corresponds to showing the dots in the visbuilder for each data point
point: currentSeriesParams.showCircles,
},
mark: buildLayerMark(currentSeriesParams),
encoding: {
x: {
axis: {
title: xAxisTitle,
grid: visParams.grid.categoryLines,
},
field: xAxisId,
type: 'temporal',
scale: {
domain: [startTime, endTime],
},
},
y: {
axis: {
title: cleanString(currentValueAxis.title.text) || column.name,
grid: visParams.grid.valueAxis !== '',
orient: currentValueAxis.position,
labels: currentValueAxis.labels.show,
labelAngle: currentValueAxis.labels.rotate,
},
field: column.id,
type: 'quantitative',
},
x: buildXAxis(xAxisTitle, xAxisId, startTime, endTime, visParams),
y: buildYAxis(column, currentValueAxis, visParams),
tooltip,
color: {
// This ensures all the different metrics have their own distinct and unique color
datum: column.name,
},
},
Expand All @@ -176,6 +222,7 @@ export const createSpecFromDatatable = (
type: 'temporal',
field: 'now_field',
},
// The time marker on vislib is red, so keeping this consistent
color: {
value: 'red',
},
Expand All @@ -187,30 +234,39 @@ export const createSpecFromDatatable = (
}

if (visParams.thresholdLine.show as boolean) {
spec.layer.push({
const layer = {
mark: {
type: 'rule',
color: visParams.thresholdLine.color,
strokeDash: [1, 0],
},
encoding: {
y: {
datum: visParams.thresholdLine.value,
},
},
});
};

// Can only support making a threshold line with full or dashed style, but not dot-dashed
// due to vega-lite limitations
if (visParams.thresholdLine.style !== 'full') {
layer.mark.strokeDash = [8, 8];
}

spec.layer.push(layer);
}

return spec;
};

export const createVegaSpecFn = (
export const createLineVegaSpecFn = (
dependencies: VegaVisualizationDependencies
): VegaSpecExpressionFunctionDefinition => ({
): LineVegaSpecExpressionFunctionDefinition => ({
name: 'line_vega_spec',
type: 'string',
inputTypes: ['opensearch_dashboards_datatable'],
help: i18n.translate('visTypeVega.function.help', {
defaultMessage: 'Construct vega spec',
defaultMessage: 'Construct line vega spec',
}),
args: {
visLayers: {
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/vis_type_vega/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ export function plugin(initializerContext: PluginInitializerContext<ConfigSchema
}

export { VegaExpressionFunctionDefinition } from './expressions/vega_fn';
export { VegaSpecExpressionFunctionDefinition } from './expressions/line_vega_spec_fn';
export { LineVegaSpecExpressionFunctionDefinition } from './expressions/line_vega_spec_fn';
4 changes: 2 additions & 2 deletions src/plugins/vis_type_vega/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import './index.scss';
import { ConfigSchema } from '../config';

import { getVegaInspectorView } from './vega_inspector';
import { createVegaSpecFn } from './expressions/line_vega_spec_fn';
import { createLineVegaSpecFn } from './expressions/line_vega_spec_fn';

/** @internal */
export interface VegaVisualizationDependencies {
Expand Down Expand Up @@ -105,7 +105,7 @@ export class VegaPlugin implements Plugin<Promise<void>, void> {
inspector.registerView(getVegaInspectorView({ uiSettings: core.uiSettings }));

expressions.registerFunction(() => createVegaFn(visualizationDependencies));
expressions.registerFunction(() => createVegaSpecFn(visualizationDependencies));
expressions.registerFunction(() => createLineVegaSpecFn(visualizationDependencies));

visualizations.createBaseVisualization(createVegaTypeDefinition(visualizationDependencies));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,10 @@ export class VegaBaseView {
* Set global debug variable to simplify vega debugging in console. Show info message first time
*/
setDebugValues(view, spec, vlspec) {
// The vega inspector can now be null when rendering line charts using vega for the overlay visualization feature
// The vega inspector can now be null when rendering line charts using vega for the overlay visualization feature.
// This is because the inspectors get added at bootstrap to the different chart types and visualize embeddable
// thinks the line chart is vislib line chart and uses that inspector adapter and has no way of knowing it's
// actually a vega-lite chart and needs to use the vega inspector adapter without hacky code.
this._parser.searchAPI.inspectorAdapters?.vega?.bindInspectValues({
lezzago marked this conversation as resolved.
Show resolved Hide resolved
view,
spec: vlspec || spec,
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/vis_type_vislib/public/line_to_expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { buildExpression, buildExpressionFunction } from '../../expressions/publ
import { OpenSearchaggsExpressionFunctionDefinition } from '../../data/common/search/expressions';
import {
VegaExpressionFunctionDefinition,
VegaSpecExpressionFunctionDefinition,
LineVegaSpecExpressionFunctionDefinition,
} from '../../vis_type_vega/public';

export const toExpressionAst = async (vis: Vis, params: any) => {
Expand Down Expand Up @@ -39,7 +39,7 @@ export const toExpressionAst = async (vis: Vis, params: any) => {
} else {
const dimensions = await buildVislibDimensions(vis, params);
// adding the new expr fn here that takes the datatable and converts to a vega spec
const vegaSpecFn = buildExpressionFunction<VegaSpecExpressionFunctionDefinition>(
const vegaSpecFn = buildExpressionFunction<LineVegaSpecExpressionFunctionDefinition>(
'line_vega_spec',
{
visLayers: JSON.stringify([]),
Expand Down
10 changes: 5 additions & 5 deletions src/plugins/visualizations/public/legacy/build_pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,11 +334,11 @@ const buildVisConfig: BuildVisConfigFunction = {
export interface VislibDimensions {
x: any;
y: SchemaConfig[];
z: any[] | undefined;
width: any[] | undefined;
series: any[] | undefined;
splitRow: any[] | undefined;
splitColumn: any[] | undefined;
z?: any[];
width?: any[];
series?: any[];
splitRow?: any[];
splitColumn?: any[];
}

export const buildVislibDimensions = async (
Expand Down