Vislib visualization renderer (#80744)

This commit is contained in:
Nick Partridge 2020-10-29 13:34:41 -05:00 committed by GitHub
parent 5752b7a8dd
commit e0b6b0ba5c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 4227 additions and 679 deletions

View file

@ -45,7 +45,9 @@ export const TagCloudChart = ({
const visController = useRef<any>(null);
useEffect(() => {
visController.current = new TagCloudVisualization(chartDiv.current, colors, fireEvent);
if (chartDiv.current) {
visController.current = new TagCloudVisualization(chartDiv.current, colors, fireEvent);
}
return () => {
visController.current.destroy();
visController.current = null;

View file

@ -42,12 +42,6 @@ export const getTimelionVisRenderer: (
const [seriesList] = visData.sheet;
const showNoResult = !seriesList || !seriesList.list.length;
if (showNoResult) {
// send the render complete event when there is no data to show
// to notify that a chart is updated
handlers.done();
}
render(
<VisualizationContainer handlers={handlers} showNoResult={showNoResult}>
<KibanaContextProvider services={{ ...deps }}>

View file

@ -2,12 +2,9 @@
exports[`interpreter/functions#pie returns an object with the correct structure 1`] = `
Object {
"as": "visualization",
"as": "vislib_vis",
"type": "render",
"value": Object {
"params": Object {
"listenOnChange": true,
},
"visConfig": Object {
"addLegend": true,
"addTooltip": true,

View file

@ -0,0 +1,22 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`vislib vis toExpressionAst function should match basic snapshot 1`] = `
Object {
"addArgument": [Function],
"arguments": Object {
"type": Array [
"area",
],
"visConfig": Array [
"{\\"type\\":\\"area\\",\\"grid\\":{\\"categoryLines\\":false,\\"style\\":{\\"color\\":\\"#eee\\"}},\\"categoryAxes\\":[{\\"id\\":\\"CategoryAxis-1\\",\\"type\\":\\"category\\",\\"position\\":\\"bottom\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\"},\\"labels\\":{\\"show\\":true,\\"truncate\\":100},\\"title\\":{}}],\\"valueAxes\\":[{\\"id\\":\\"ValueAxis-1\\",\\"name\\":\\"LeftAxis-1\\",\\"type\\":\\"value\\",\\"position\\":\\"left\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\",\\"mode\\":\\"normal\\"},\\"labels\\":{\\"show\\":true,\\"rotate\\":0,\\"filter\\":false,\\"truncate\\":100},\\"title\\":{\\"text\\":\\"Sum of total_quantity\\"}}],\\"seriesParams\\":[{\\"show\\":\\"true\\",\\"type\\":\\"area\\",\\"mode\\":\\"stacked\\",\\"data\\":{\\"label\\":\\"Sum of total_quantity\\",\\"id\\":\\"1\\"},\\"drawLinesBetweenPoints\\":true,\\"showCircles\\":true,\\"interpolate\\":\\"linear\\",\\"valueAxis\\":\\"ValueAxis-1\\"}],\\"addTooltip\\":true,\\"addLegend\\":true,\\"legendPosition\\":\\"top\\",\\"times\\":[],\\"addTimeMarker\\":false,\\"thresholdLine\\":{\\"show\\":false,\\"value\\":10,\\"width\\":1,\\"style\\":\\"full\\",\\"color\\":\\"#E7664C\\"},\\"labels\\":{},\\"dimensions\\":{\\"x\\":{\\"accessor\\":1,\\"format\\":{\\"id\\":\\"date\\",\\"params\\":{\\"pattern\\":\\"HH:mm:ss.SSS\\"}},\\"params\\":{}},\\"y\\":[{\\"accessor\\":0,\\"format\\":{\\"id\\":\\"number\\",\\"params\\":{\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}],\\"series\\":[{\\"accessor\\":2,\\"format\\":{\\"id\\":\\"terms\\",\\"params\\":{\\"id\\":\\"string\\",\\"otherBucketLabel\\":\\"Other\\",\\"missingBucketLabel\\":\\"Missing\\",\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}]}}",
],
},
"getArgument": [Function],
"name": "vislib_vis",
"removeArgument": [Function],
"replaceArgument": [Function],
"toAst": [Function],
"toString": [Function],
"type": "expression_function_builder",
}
`;

View file

@ -0,0 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`vislib pie vis toExpressionAst function should match basic snapshot 1`] = `
Object {
"addArgument": [Function],
"arguments": Object {
"visConfig": Array [
"{\\"type\\":\\"pie\\",\\"addTooltip\\":true,\\"addLegend\\":true,\\"legendPosition\\":\\"right\\",\\"isDonut\\":true,\\"labels\\":{\\"show\\":true,\\"values\\":true,\\"last_level\\":true,\\"truncate\\":100},\\"dimensions\\":{\\"metric\\":{\\"accessor\\":0,\\"format\\":{\\"id\\":\\"number\\"},\\"params\\":{}},\\"buckets\\":[{\\"accessor\\":1,\\"format\\":{\\"id\\":\\"terms\\",\\"params\\":{\\"id\\":\\"string\\",\\"otherBucketLabel\\":\\"Other\\",\\"missingBucketLabel\\":\\"Missing\\",\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}]}}",
],
},
"getArgument": [Function],
"name": "vislib_pie_vis",
"removeArgument": [Function],
"replaceArgument": [Function],
"toAst": [Function],
"toString": [Function],
"type": "expression_function_builder",
}
`;

View file

@ -37,22 +37,20 @@ import {
getConfigCollections,
} from './utils/collections';
import { getAreaOptionTabs, countLabel } from './utils/common_config';
import { createVislibVisController } from './vis_controller';
import { VisTypeVislibDependencies } from './plugin';
import { Rotates } from '../../charts/public';
import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
import { toExpressionAst } from './to_ast';
import { BasicVislibParams } from './types';
export const createAreaVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({
export const areaVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
name: 'area',
title: i18n.translate('visTypeVislib.area.areaTitle', { defaultMessage: 'Area' }),
icon: 'visArea',
description: i18n.translate('visTypeVislib.area.areaDescription', {
defaultMessage: 'Emphasize the quantity beneath a line chart',
}),
visualization: createVislibVisController(deps),
getSupportedTriggers: () => {
return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush];
},
getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush],
toExpressionAst,
visConfig: {
defaults: {
type: 'area',
@ -131,9 +129,6 @@ export const createAreaVisTypeDefinition = (deps: VisTypeVislibDependencies) =>
labels: {},
},
},
events: {
brush: { disabled: false },
},
editorConfig: {
collections: getConfigCollections(),
optionTabs: getAreaOptionTabs(),
@ -190,4 +185,4 @@ export const createAreaVisTypeDefinition = (deps: VisTypeVislibDependencies) =>
},
]),
},
});
};

View file

@ -60,4 +60,6 @@ function GaugeOptions(props: VisOptionsProps<GaugeVisParams>) {
);
}
export { GaugeOptions };
// default export required for React.Lazy
// eslint-disable-next-line import/no-default-export
export { GaugeOptions as default };

View file

@ -185,4 +185,6 @@ function HeatmapOptions(props: VisOptionsProps<HeatmapVisParams>) {
);
}
export { HeatmapOptions };
// default export required for React.Lazy
// eslint-disable-next-line import/no-default-export
export { HeatmapOptions as default };

View file

@ -0,0 +1,51 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { lazy } from 'react';
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
import { ValidationVisOptionsProps } from '../common';
import { GaugeVisParams } from '../../gauge';
import { PieVisParams } from '../../pie';
import { BasicVislibParams } from '../../types';
import { HeatmapVisParams } from '../../heatmap';
const GaugeOptionsLazy = lazy(() => import('./gauge'));
const PieOptionsLazy = lazy(() => import('./pie'));
const PointSeriesOptionsLazy = lazy(() => import('./point_series'));
const HeatmapOptionsLazy = lazy(() => import('./heatmap'));
const MetricsAxisOptionsLazy = lazy(() => import('./metrics_axes'));
export const GaugeOptions = (props: VisOptionsProps<GaugeVisParams>) => (
<GaugeOptionsLazy {...props} />
);
export const PieOptions = (props: VisOptionsProps<PieVisParams>) => <PieOptionsLazy {...props} />;
export const PointSeriesOptions = (props: ValidationVisOptionsProps<BasicVislibParams>) => (
<PointSeriesOptionsLazy {...props} />
);
export const HeatmapOptions = (props: VisOptionsProps<HeatmapVisParams>) => (
<HeatmapOptionsLazy {...props} />
);
export const MetricsAxisOptions = (props: ValidationVisOptionsProps<BasicVislibParams>) => (
<MetricsAxisOptionsLazy {...props} />
);

View file

@ -21,7 +21,7 @@ import React from 'react';
import { mount, shallow } from 'enzyme';
import { IAggConfig, IAggType } from 'src/plugins/data/public';
import { MetricsAxisOptions } from './index';
import MetricsAxisOptions from './index';
import { BasicVislibParams, SeriesParam, ValueAxis } from '../../../types';
import { ValidationVisOptionsProps } from '../../common';
import { Positions } from '../../../utils/collections';

View file

@ -325,4 +325,6 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps<BasicVislibParams>)
) : null;
}
export { MetricsAxisOptions };
// default export required for React.Lazy
// eslint-disable-next-line import/no-default-export
export { MetricsAxisOptions as default };

View file

@ -99,4 +99,6 @@ function PieOptions(props: VisOptionsProps<PieVisParams>) {
);
}
export { PieOptions };
// default export required for React.Lazy
// eslint-disable-next-line import/no-default-export
export { PieOptions as default };

View file

@ -17,4 +17,6 @@
* under the License.
*/
export { PointSeriesOptions } from './point_series';
// default export required for React.Lazy
// eslint-disable-next-line import/no-default-export
export { PointSeriesOptions as default } from './point_series';

View file

@ -24,8 +24,9 @@ import { AggGroupNames } from '../../data/public';
import { GaugeOptions } from './components/options';
import { getGaugeCollections, Alignments, GaugeTypes } from './utils/collections';
import { ColorModes, ColorSchemas, ColorSchemaParams, Labels, Style } from '../../charts/public';
import { createVislibVisController } from './vis_controller';
import { VisTypeVislibDependencies } from './plugin';
import { toExpressionAst } from './to_ast';
import { BaseVisTypeOptions } from '../../visualizations/public';
import { BasicVislibParams } from './types';
export interface Gauge extends ColorSchemaParams {
backStyle: 'Full';
@ -55,7 +56,7 @@ export interface GaugeVisParams {
gauge: Gauge;
}
export const createGaugeVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({
export const gaugeVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
name: 'gauge',
title: i18n.translate('visTypeVislib.gauge.gaugeTitle', { defaultMessage: 'Gauge' }),
icon: 'visGauge',
@ -63,6 +64,7 @@ export const createGaugeVisTypeDefinition = (deps: VisTypeVislibDependencies) =>
defaultMessage:
"Gauges indicate the status of a metric. Use it to show how a metric's value relates to reference threshold values.",
}),
toExpressionAst,
visConfig: {
defaults: {
type: 'gauge',
@ -109,7 +111,6 @@ export const createGaugeVisTypeDefinition = (deps: VisTypeVislibDependencies) =>
},
},
},
visualization: createVislibVisController(deps),
editorConfig: {
collections: getGaugeCollections(),
optionsTemplate: GaugeOptions,
@ -145,4 +146,4 @@ export const createGaugeVisTypeDefinition = (deps: VisTypeVislibDependencies) =>
]),
},
useCustomNoDataScreen: true,
});
};

View file

@ -21,20 +21,21 @@ import { i18n } from '@kbn/i18n';
import { GaugeOptions } from './components/options';
import { getGaugeCollections, GaugeTypes } from './utils/collections';
import { createVislibVisController } from './vis_controller';
import { VisTypeVislibDependencies } from './plugin';
import { ColorModes, ColorSchemas } from '../../charts/public';
import { AggGroupNames } from '../../data/public';
import { Schemas } from '../../vis_default_editor/public';
import { toExpressionAst } from './to_ast';
import { BaseVisTypeOptions } from '../../visualizations/public';
import { BasicVislibParams } from './types';
export const createGoalVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({
export const goalVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
name: 'goal',
title: i18n.translate('visTypeVislib.goal.goalTitle', { defaultMessage: 'Goal' }),
icon: 'visGoal',
description: i18n.translate('visTypeVislib.goal.goalDescription', {
defaultMessage: 'A goal chart indicates how close you are to your final goal.',
}),
visualization: createVislibVisController(deps),
toExpressionAst,
visConfig: {
defaults: {
addTooltip: true,
@ -110,4 +111,4 @@ export const createGoalVisTypeDefinition = (deps: VisTypeVislibDependencies) =>
]),
},
useCustomNoDataScreen: true,
});
};

View file

@ -23,12 +23,11 @@ import { RangeValues, Schemas } from '../../vis_default_editor/public';
import { AggGroupNames } from '../../data/public';
import { AxisTypes, getHeatmapCollections, Positions, ScaleTypes } from './utils/collections';
import { HeatmapOptions } from './components/options';
import { createVislibVisController } from './vis_controller';
import { TimeMarker } from './vislib/visualizations/time_marker';
import { CommonVislibParams, ValueAxis } from './types';
import { VisTypeVislibDependencies } from './plugin';
import { BasicVislibParams, CommonVislibParams, ValueAxis } from './types';
import { ColorSchemas, ColorSchemaParams } from '../../charts/public';
import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
import { toExpressionAst } from './to_ast';
export interface HeatmapVisParams extends CommonVislibParams, ColorSchemaParams {
type: 'heatmap';
@ -42,17 +41,15 @@ export interface HeatmapVisParams extends CommonVislibParams, ColorSchemaParams
times: TimeMarker[];
}
export const createHeatmapVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({
export const heatmapVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
name: 'heatmap',
title: i18n.translate('visTypeVislib.heatmap.heatmapTitle', { defaultMessage: 'Heat Map' }),
icon: 'heatmap',
description: i18n.translate('visTypeVislib.heatmap.heatmapDescription', {
defaultMessage: 'Shade cells within a matrix',
}),
getSupportedTriggers: () => {
return [VIS_EVENT_TO_TRIGGER.filter];
},
visualization: createVislibVisController(deps),
getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter],
toExpressionAst,
visConfig: {
defaults: {
type: 'heatmap',
@ -86,9 +83,6 @@ export const createHeatmapVisTypeDefinition = (deps: VisTypeVislibDependencies)
],
},
},
events: {
brush: { disabled: false },
},
editorConfig: {
collections: getHeatmapCollections(),
optionsTemplate: HeatmapOptions,
@ -142,4 +136,4 @@ export const createHeatmapVisTypeDefinition = (deps: VisTypeVislibDependencies)
},
]),
},
});
};

View file

@ -36,12 +36,12 @@ import {
getConfigCollections,
} from './utils/collections';
import { getAreaOptionTabs, countLabel } from './utils/common_config';
import { createVislibVisController } from './vis_controller';
import { VisTypeVislibDependencies } from './plugin';
import { Rotates } from '../../charts/public';
import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
import { BasicVislibParams } from './types';
import { toExpressionAst } from './to_ast';
export const createHistogramVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({
export const histogramVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
name: 'histogram',
title: i18n.translate('visTypeVislib.histogram.histogramTitle', {
defaultMessage: 'Vertical Bar',
@ -50,10 +50,8 @@ export const createHistogramVisTypeDefinition = (deps: VisTypeVislibDependencies
description: i18n.translate('visTypeVislib.histogram.histogramDescription', {
defaultMessage: 'Assign a continuous variable to each axis',
}),
visualization: createVislibVisController(deps),
getSupportedTriggers: () => {
return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush];
},
getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush],
toExpressionAst,
visConfig: {
defaults: {
type: 'histogram',
@ -133,9 +131,6 @@ export const createHistogramVisTypeDefinition = (deps: VisTypeVislibDependencies
},
},
},
events: {
brush: { disabled: false },
},
editorConfig: {
collections: getConfigCollections(),
optionTabs: getAreaOptionTabs(),
@ -192,4 +187,4 @@ export const createHistogramVisTypeDefinition = (deps: VisTypeVislibDependencies
},
]),
},
});
};

View file

@ -34,12 +34,12 @@ import {
getConfigCollections,
} from './utils/collections';
import { getAreaOptionTabs, countLabel } from './utils/common_config';
import { createVislibVisController } from './vis_controller';
import { VisTypeVislibDependencies } from './plugin';
import { Rotates } from '../../charts/public';
import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
import { BasicVislibParams } from './types';
import { toExpressionAst } from './to_ast';
export const createHorizontalBarVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({
export const horizontalBarVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
name: 'horizontal_bar',
title: i18n.translate('visTypeVislib.horizontalBar.horizontalBarTitle', {
defaultMessage: 'Horizontal Bar',
@ -48,10 +48,8 @@ export const createHorizontalBarVisTypeDefinition = (deps: VisTypeVislibDependen
description: i18n.translate('visTypeVislib.horizontalBar.horizontalBarDescription', {
defaultMessage: 'Assign a continuous variable to each axis',
}),
visualization: createVislibVisController(deps),
getSupportedTriggers: () => {
return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush];
},
getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush],
toExpressionAst,
visConfig: {
defaults: {
type: 'histogram',
@ -130,9 +128,6 @@ export const createHorizontalBarVisTypeDefinition = (deps: VisTypeVislibDependen
},
},
},
events: {
brush: { disabled: false },
},
editorConfig: {
collections: getConfigCollections(),
optionTabs: getAreaOptionTabs(),
@ -189,4 +184,4 @@ export const createHorizontalBarVisTypeDefinition = (deps: VisTypeVislibDependen
},
]),
},
});
};

View file

@ -1 +1 @@
@import './vislib/index'
@import './vislib/index';

View file

@ -35,22 +35,20 @@ import {
getConfigCollections,
} from './utils/collections';
import { getAreaOptionTabs, countLabel } from './utils/common_config';
import { createVislibVisController } from './vis_controller';
import { VisTypeVislibDependencies } from './plugin';
import { Rotates } from '../../charts/public';
import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
import { toExpressionAst } from './to_ast';
import { BasicVislibParams } from './types';
export const createLineVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({
export const lineVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
name: 'line',
title: i18n.translate('visTypeVislib.line.lineTitle', { defaultMessage: 'Line' }),
icon: 'visLine',
description: i18n.translate('visTypeVislib.line.lineDescription', {
defaultMessage: 'Emphasize trends',
}),
visualization: createVislibVisController(deps),
getSupportedTriggers: () => {
return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush];
},
getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush],
toExpressionAst,
visConfig: {
defaults: {
type: 'line',
@ -129,9 +127,6 @@ export const createLineVisTypeDefinition = (deps: VisTypeVislibDependencies) =>
},
},
},
events: {
brush: { disabled: false },
},
editorConfig: {
collections: getConfigCollections(),
optionTabs: getAreaOptionTabs(),
@ -182,4 +177,4 @@ export const createLineVisTypeDefinition = (deps: VisTypeVislibDependencies) =>
},
]),
},
});
};

View file

@ -23,14 +23,12 @@ import { AggGroupNames } from '../../data/public';
import { Schemas } from '../../vis_default_editor/public';
import { PieOptions } from './components/options';
import { getPositions, Positions } from './utils/collections';
import { createVislibVisController } from './vis_controller';
import { CommonVislibParams } from './types';
import { VisTypeVislibDependencies } from './plugin';
import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
import { toExpressionAst } from './to_ast_pie';
export interface PieVisParams extends CommonVislibParams {
type: 'pie';
addLegend: boolean;
isDonut: boolean;
labels: {
show: boolean;
@ -40,17 +38,15 @@ export interface PieVisParams extends CommonVislibParams {
};
}
export const createPieVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({
export const pieVisTypeDefinition: BaseVisTypeOptions<PieVisParams> = {
name: 'pie',
title: i18n.translate('visTypeVislib.pie.pieTitle', { defaultMessage: 'Pie' }),
icon: 'visPie',
description: i18n.translate('visTypeVislib.pie.pieDescription', {
defaultMessage: 'Compare parts of a whole',
}),
visualization: createVislibVisController(deps),
getSupportedTriggers: () => {
return [VIS_EVENT_TO_TRIGGER.filter];
},
getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter],
toExpressionAst,
visConfig: {
defaults: {
type: 'pie',
@ -108,4 +104,4 @@ export const createPieVisTypeDefinition = (deps: VisTypeVislibDependencies) => (
},
hierarchicalData: true,
responseHandler: 'vislib_slices',
});
};

View file

@ -18,27 +18,35 @@
*/
import { i18n } from '@kbn/i18n';
import { ExpressionFunctionDefinition, Datatable, Render } from '../../expressions/public';
// @ts-ignore
import { vislibSlicesResponseHandler } from './vislib/response_handler';
import { PieVisParams } from './pie';
import { vislibVisName } from './vis_type_vislib_vis_fn';
export const vislibPieName = 'vislib_pie_vis';
interface Arguments {
visConfig: string;
}
type VisParams = Required<Arguments>;
interface RenderValue {
visConfig: VisParams;
visData: unknown;
visType: string;
visConfig: PieVisParams;
}
export const createPieVisFn = (): ExpressionFunctionDefinition<
'kibana_pie',
export type VisTypeVislibPieExpressionFunctionDefinition = ExpressionFunctionDefinition<
typeof vislibPieName,
Datatable,
Arguments,
Render<RenderValue>
> => ({
name: 'kibana_pie',
>;
export const createPieVisFn = (): VisTypeVislibPieExpressionFunctionDefinition => ({
name: vislibPieName,
type: 'render',
inputTypes: ['datatable'],
help: i18n.translate('visTypeVislib.functions.pie.help', {
@ -48,23 +56,20 @@ export const createPieVisFn = (): ExpressionFunctionDefinition<
visConfig: {
types: ['string'],
default: '"{}"',
help: '',
help: 'vislib pie vis config',
},
},
fn(input, args) {
const visConfig = JSON.parse(args.visConfig);
const convertedData = vislibSlicesResponseHandler(input, visConfig.dimensions);
const visConfig = JSON.parse(args.visConfig) as PieVisParams;
const visData = vislibSlicesResponseHandler(input, visConfig.dimensions);
return {
type: 'render',
as: 'visualization',
as: vislibVisName,
value: {
visData: convertedData,
visType: 'pie',
visData,
visConfig,
params: {
listenOnChange: true,
},
visType: 'pie',
},
};
},

View file

@ -17,40 +17,20 @@
* under the License.
*/
import './index.scss';
import {
CoreSetup,
CoreStart,
Plugin,
IUiSettingsClient,
PluginInitializerContext,
} from 'kibana/public';
import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'kibana/public';
import { VisTypeXyPluginSetup } from 'src/plugins/vis_type_xy/public';
import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public';
import { VisualizationsSetup } from '../../visualizations/public';
import { BaseVisTypeOptions, VisualizationsSetup } from '../../visualizations/public';
import { createVisTypeVislibVisFn } from './vis_type_vislib_vis_fn';
import { createPieVisFn } from './pie_fn';
import {
createHistogramVisTypeDefinition,
createLineVisTypeDefinition,
createPieVisTypeDefinition,
createAreaVisTypeDefinition,
createHeatmapVisTypeDefinition,
createHorizontalBarVisTypeDefinition,
createGaugeVisTypeDefinition,
createGoalVisTypeDefinition,
} from './vis_type_vislib_vis_types';
import { visLibVisTypeDefinitions, pieVisTypeDefinition } from './vis_type_vislib_vis_types';
import { ChartsPluginSetup } from '../../charts/public';
import { DataPublicPluginStart } from '../../data/public';
import { setFormatService, setDataActions, setKibanaLegacy } from './services';
import { KibanaLegacyStart } from '../../kibana_legacy/public';
export interface VisTypeVislibDependencies {
uiSettings: IUiSettingsClient;
charts: ChartsPluginSetup;
}
import { setFormatService, setDataActions } from './services';
import { getVislibVisRenderer } from './vis_renderer';
import { BasicVislibParams } from './types';
/** @internal */
export interface VisTypeVislibPluginSetupDependencies {
@ -66,54 +46,37 @@ export interface VisTypeVislibPluginStartDependencies {
kibanaLegacy: KibanaLegacyStart;
}
type VisTypeVislibCoreSetup = CoreSetup<VisTypeVislibPluginStartDependencies, void>;
export type VisTypeVislibCoreSetup = CoreSetup<VisTypeVislibPluginStartDependencies, void>;
/** @internal */
export class VisTypeVislibPlugin implements Plugin<void, void> {
export class VisTypeVislibPlugin
implements
Plugin<void, void, VisTypeVislibPluginSetupDependencies, VisTypeVislibPluginStartDependencies> {
constructor(public initializerContext: PluginInitializerContext) {}
public async setup(
core: VisTypeVislibCoreSetup,
{ expressions, visualizations, charts, visTypeXy }: VisTypeVislibPluginSetupDependencies
) {
const visualizationDependencies: Readonly<VisTypeVislibDependencies> = {
uiSettings: core.uiSettings,
charts,
};
const vislibTypes = [
createHistogramVisTypeDefinition,
createLineVisTypeDefinition,
createPieVisTypeDefinition,
createAreaVisTypeDefinition,
createHeatmapVisTypeDefinition,
createHorizontalBarVisTypeDefinition,
createGaugeVisTypeDefinition,
createGoalVisTypeDefinition,
];
const vislibFns = [createVisTypeVislibVisFn(), createPieVisFn()];
// if visTypeXy plugin is disabled it's config will be undefined
if (!visTypeXy) {
const convertedTypes: any[] = [];
const convertedTypes: Array<BaseVisTypeOptions<BasicVislibParams>> = [];
const convertedFns: any[] = [];
// Register legacy vislib types that have been converted
convertedFns.forEach(expressions.registerFunction);
convertedTypes.forEach((vis) =>
visualizations.createBaseVisualization(vis(visualizationDependencies))
);
convertedTypes.forEach(visualizations.createBaseVisualization);
expressions.registerRenderer(getVislibVisRenderer(core, charts));
}
// Register non-converted types
vislibFns.forEach(expressions.registerFunction);
vislibTypes.forEach((vis) =>
visualizations.createBaseVisualization(vis(visualizationDependencies))
);
visLibVisTypeDefinitions.forEach(visualizations.createBaseVisualization);
visualizations.createBaseVisualization(pieVisTypeDefinition);
expressions.registerRenderer(getVislibVisRenderer(core, charts));
[createVisTypeVislibVisFn(), createPieVisFn()].forEach(expressions.registerFunction);
}
public start(core: CoreStart, { data, kibanaLegacy }: VisTypeVislibPluginStartDependencies) {
public start(core: CoreStart, { data }: VisTypeVislibPluginStartDependencies) {
setFormatService(data.fieldFormats);
setDataActions(data.actions);
setKibanaLegacy(kibanaLegacy);
}
}

File diff suppressed because one or more lines are too long

View file

@ -19,7 +19,6 @@
import { createGetterSetter } from '../../kibana_utils/public';
import { DataPublicPluginStart } from '../../data/public';
import { KibanaLegacyStart } from '../../kibana_legacy/public';
export const [getDataActions, setDataActions] = createGetterSetter<
DataPublicPluginStart['actions']
@ -28,7 +27,3 @@ export const [getDataActions, setDataActions] = createGetterSetter<
export const [getFormatService, setFormatService] = createGetterSetter<
DataPublicPluginStart['fieldFormats']
>('vislib data.fieldFormats');
export const [getKibanaLegacy, setKibanaLegacy] = createGetterSetter<KibanaLegacyStart>(
'vislib kibanalegacy'
);

View file

@ -0,0 +1,60 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { Vis } from '../../visualizations/public';
import { buildExpression } from '../../expressions/public';
import { BasicVislibParams } from './types';
import { toExpressionAst } from './to_ast';
import { sampleAreaVis } from './sample_vis.test.mocks';
jest.mock('../../expressions/public', () => ({
...(jest.requireActual('../../expressions/public') as any),
buildExpression: jest.fn().mockImplementation(() => ({
toAst: () => ({
type: 'expression',
chain: [],
}),
})),
}));
jest.mock('./to_ast_esaggs', () => ({
getEsaggsFn: jest.fn(),
}));
describe('vislib vis toExpressionAst function', () => {
let vis: Vis<BasicVislibParams>;
const params = {
timefilter: {},
timeRange: {},
abortSignal: {},
} as any;
beforeEach(() => {
vis = sampleAreaVis as any;
});
it('should match basic snapshot', () => {
toExpressionAst(vis, params);
const [, builtExpression] = (buildExpression as jest.Mock).mock.calls[0][0];
expect(builtExpression).toMatchSnapshot();
});
});

View file

@ -0,0 +1,103 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import moment from 'moment';
import { VisToExpressionAst, getVisSchemas } from '../../visualizations/public';
import { buildExpression, buildExpressionFunction } from '../../expressions/public';
import { vislibVisName, VisTypeVislibExpressionFunctionDefinition } from './vis_type_vislib_vis_fn';
import { BasicVislibParams } from './types';
import {
DateHistogramParams,
Dimensions,
HistogramParams,
} from './vislib/helpers/point_series/point_series';
import { getEsaggsFn } from './to_ast_esaggs';
export const toExpressionAst: VisToExpressionAst<BasicVislibParams> = async (vis, params) => {
const schemas = getVisSchemas(vis, params);
const dimensions: Dimensions = {
x: schemas.segment ? schemas.segment[0] : null,
y: schemas.metric,
z: schemas.radius,
width: schemas.width,
series: schemas.group,
splitRow: schemas.split_row,
splitColumn: schemas.split_column,
};
const responseAggs = vis.data.aggs?.getResponseAggs() ?? [];
if (dimensions.x) {
const xAgg = responseAggs[dimensions.x.accessor] as any;
if (xAgg.type.name === 'date_histogram') {
(dimensions.x.params as DateHistogramParams).date = true;
const { esUnit, esValue } = xAgg.buckets.getInterval();
(dimensions.x.params as DateHistogramParams).intervalESUnit = esUnit;
(dimensions.x.params as DateHistogramParams).intervalESValue = esValue;
(dimensions.x.params as DateHistogramParams).interval = moment
.duration(esValue, esUnit)
.asMilliseconds();
(dimensions.x.params as DateHistogramParams).format = xAgg.buckets.getScaledDateFormat();
(dimensions.x.params as DateHistogramParams).bounds = xAgg.buckets.getBounds();
} else if (xAgg.type.name === 'histogram') {
const intervalParam = xAgg.type.paramByName('interval');
const output = { params: {} as any };
await intervalParam.modifyAggConfigOnSearchRequestStart(xAgg, vis.data.searchSource, {
abortSignal: params.abortSignal,
});
intervalParam.write(xAgg, output);
(dimensions.x.params as HistogramParams).interval = output.params.interval;
}
}
const visConfig = { ...vis.params };
(dimensions.y || []).forEach((yDimension) => {
const yAgg = responseAggs.filter(({ enabled }) => enabled)[yDimension.accessor];
const seriesParam = (visConfig.seriesParams || []).find((param) => param.data.id === yAgg.id);
if (seriesParam) {
const usedValueAxis = (visConfig.valueAxes || []).find(
(valueAxis) => valueAxis.id === seriesParam.valueAxis
);
if (usedValueAxis?.scale.mode === 'percentage') {
yDimension.format = { id: 'percent' };
}
}
if (visConfig?.gauge?.percentageMode === true) {
yDimension.format = { id: 'percent' };
}
});
visConfig.dimensions = dimensions;
const configStr = JSON.stringify(visConfig).replace(/\\/g, `\\\\`).replace(/'/g, `\\'`);
const visTypeXy = buildExpressionFunction<VisTypeVislibExpressionFunctionDefinition>(
vislibVisName,
{
type: vis.type.name,
visConfig: configStr,
}
);
const ast = buildExpression([getEsaggsFn(vis), visTypeXy]);
return ast.toAst();
};

View file

@ -17,8 +17,24 @@
* under the License.
*/
export { GaugeOptions } from './gauge';
export { PieOptions } from './pie';
export { PointSeriesOptions } from './point_series';
export { HeatmapOptions } from './heatmap';
export { MetricsAxisOptions } from './metrics_axes';
import { Vis } from '../../visualizations/public';
import { buildExpressionFunction } from '../../expressions/public';
import { EsaggsExpressionFunctionDefinition } from '../../data/public';
import { PieVisParams } from './pie';
import { BasicVislibParams } from './types';
/**
* Get esaggs expressions function
* TODO: replace this with vis.data.aggs!.toExpressionAst();
* @param vis
*/
export function getEsaggsFn(vis: Vis<PieVisParams> | Vis<BasicVislibParams>) {
return buildExpressionFunction<EsaggsExpressionFunctionDefinition>('esaggs', {
index: vis.data.indexPattern!.id!,
metricsAtAllLevels: vis.isHierarchical(),
partialRows: false,
aggConfigs: JSON.stringify(vis.data.aggs!.aggs),
includeFormatHints: false,
});
}

View file

@ -0,0 +1,60 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { Vis } from '../../visualizations/public';
import { buildExpression } from '../../expressions/public';
import { PieVisParams } from './pie';
import { samplePieVis } from './sample_vis.test.mocks';
import { toExpressionAst } from './to_ast_pie';
jest.mock('../../expressions/public', () => ({
...(jest.requireActual('../../expressions/public') as any),
buildExpression: jest.fn().mockImplementation(() => ({
toAst: () => ({
type: 'expression',
chain: [],
}),
})),
}));
jest.mock('./to_ast_esaggs', () => ({
getEsaggsFn: jest.fn(),
}));
describe('vislib pie vis toExpressionAst function', () => {
let vis: Vis<PieVisParams>;
const params = {
timefilter: {},
timeRange: {},
abortSignal: {},
} as any;
beforeEach(() => {
vis = samplePieVis as any;
});
it('should match basic snapshot', () => {
toExpressionAst(vis, params);
const [, builtExpression] = (buildExpression as jest.Mock).mock.calls[0][0];
expect(builtExpression).toMatchSnapshot();
});
});

View file

@ -0,0 +1,50 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { getVisSchemas, VisToExpressionAst } from '../../visualizations/public';
import { buildExpression, buildExpressionFunction } from '../../expressions/public';
import { PieVisParams } from './pie';
import { vislibPieName, VisTypeVislibPieExpressionFunctionDefinition } from './pie_fn';
import { getEsaggsFn } from './to_ast_esaggs';
export const toExpressionAst: VisToExpressionAst<PieVisParams> = async (vis, params) => {
const schemas = getVisSchemas(vis, params);
const visConfig = {
...vis.params,
dimensions: {
metric: schemas.metric[0],
buckets: schemas.segment,
splitRow: schemas.split_row,
splitColumn: schemas.split_column,
},
};
const configStr = JSON.stringify(visConfig).replace(/\\/g, `\\\\`).replace(/'/g, `\\'`);
const visTypePie = buildExpressionFunction<VisTypeVislibPieExpressionFunctionDefinition>(
vislibPieName,
{
visConfig: configStr,
}
);
const ast = buildExpression([getEsaggsFn(vis), visTypePie]);
return ast.toAst();
};

View file

@ -29,10 +29,13 @@ import {
ThresholdLineStyles,
} from './utils/collections';
import { Labels, Style } from '../../charts/public';
import { Dimensions } from './vislib/helpers/point_series/point_series';
export interface CommonVislibParams {
addTooltip: boolean;
addLegend: boolean;
legendPosition: Positions;
dimensions: Dimensions;
}
export interface Scale {
@ -87,6 +90,9 @@ export interface BasicVislibParams extends CommonVislibParams {
labels: Labels;
thresholdLine: ThresholdLine;
valueAxes: ValueAxis[];
gauge?: {
percentageMode: boolean;
};
grid: {
categoryLines: boolean;
valueAxis?: string;

View file

@ -20,127 +20,147 @@
import $ from 'jquery';
import React, { RefObject } from 'react';
import { Positions } from './utils/collections';
import { VisTypeVislibDependencies } from './plugin';
import { mountReactNode } from '../../../core/public/utils';
import { ChartsPluginSetup } from '../../charts/public';
import { PersistedState } from '../../visualizations/public';
import { IInterpreterRenderHandlers } from '../../expressions/public';
import { VisTypeVislibCoreSetup } from './plugin';
import { VisLegend, CUSTOM_LEGEND_VIS_TYPES } from './vislib/components/legend';
import { VisParams, ExprVis } from '../../visualizations/public';
import { getKibanaLegacy } from './services';
import { BasicVislibParams } from './types';
import { PieVisParams } from './pie';
const legendClassName = {
top: 'visLib--legend-top',
bottom: 'visLib--legend-bottom',
left: 'visLib--legend-left',
right: 'visLib--legend-right',
top: 'vislib--legend-top',
bottom: 'vislib--legend-bottom',
left: 'vislib--legend-left',
right: 'vislib--legend-right',
};
export const createVislibVisController = (deps: VisTypeVislibDependencies) => {
export type VislibVisController = InstanceType<ReturnType<typeof createVislibVisController>>;
export const createVislibVisController = (
core: VisTypeVislibCoreSetup,
charts: ChartsPluginSetup
) => {
return class VislibVisController {
unmount: (() => void) | null = null;
visParams?: VisParams;
private removeListeners?: () => void;
private unmountLegend?: () => void;
legendRef: RefObject<VisLegend>;
container: HTMLDivElement;
chartEl: HTMLDivElement;
legendEl: HTMLDivElement;
vislibVis: any;
vislibVis?: any;
constructor(public el: Element, public vis: ExprVis) {
constructor(public el: HTMLDivElement) {
this.el = el;
this.vis = vis;
this.unmount = null;
this.legendRef = React.createRef();
// vis mount point
this.container = document.createElement('div');
this.container.className = 'visLib';
this.container.className = 'vislib';
this.el.appendChild(this.container);
// chart mount point
this.chartEl = document.createElement('div');
this.chartEl.className = 'visLib__chart';
this.chartEl.className = 'vislib__chart';
this.container.appendChild(this.chartEl);
// legend mount point
this.legendEl = document.createElement('div');
this.legendEl.className = 'visLib__legend';
this.legendEl.className = 'vislib__legend';
this.container.appendChild(this.legendEl);
}
render(esResponse: any, visParams: VisParams): Promise<void> {
async render(
esResponse: any,
visParams: BasicVislibParams | PieVisParams,
handlers: IInterpreterRenderHandlers
): Promise<void> {
if (this.vislibVis) {
this.destroy();
this.destroy(false);
}
getKibanaLegacy().loadFontAwesome();
// Used in functional tests to know when chart is loaded by type
this.chartEl.dataset.vislibChartType = visParams.type;
return new Promise(async (resolve) => {
if (this.el.clientWidth === 0 || this.el.clientHeight === 0) {
return resolve();
}
if (this.el.clientWidth === 0 || this.el.clientHeight === 0) {
handlers.done();
return;
}
// @ts-expect-error
const { Vis: Vislib } = await import('./vislib/vis');
const [, { kibanaLegacy }] = await core.getStartServices();
kibanaLegacy.loadFontAwesome();
this.vislibVis = new Vislib(this.chartEl, visParams, deps);
this.vislibVis.on('brush', this.vis.API.events.brush);
this.vislibVis.on('click', this.vis.API.events.filter);
this.vislibVis.on('renderComplete', resolve);
// @ts-expect-error
const { Vis: Vislib } = await import('./vislib/vis');
const { uiState, event: fireEvent } = handlers;
this.vislibVis.initVisConfig(esResponse, this.vis.getUiState());
this.vislibVis = new Vislib(this.chartEl, visParams, core, charts);
this.vislibVis.on('brush', fireEvent);
this.vislibVis.on('click', fireEvent);
this.vislibVis.on('renderComplete', handlers.done);
this.removeListeners = () => {
this.vislibVis.off('brush', fireEvent);
this.vislibVis.off('click', fireEvent);
};
if (visParams.addLegend) {
$(this.container)
.attr('class', (i, cls) => {
return cls.replace(/visLib--legend-\S+/g, '');
})
.addClass((legendClassName as any)[visParams.legendPosition]);
this.vislibVis.initVisConfig(esResponse, uiState);
this.mountLegend(esResponse, visParams.legendPosition);
}
if (visParams.addLegend) {
$(this.container)
.attr('class', (i, cls) => {
return cls.replace(/vislib--legend-\S+/g, '');
})
.addClass((legendClassName as any)[visParams.legendPosition]);
this.vislibVis.render(esResponse, this.vis.getUiState());
this.mountLegend(esResponse, visParams, fireEvent, uiState);
}
// refreshing the legend after the chart is rendered.
// this is necessary because some visualizations
// provide data necessary for the legend only after a render cycle.
if (
visParams.addLegend &&
CUSTOM_LEGEND_VIS_TYPES.includes(this.vislibVis.visConfigArgs.type)
) {
this.unmountLegend();
this.mountLegend(esResponse, visParams.legendPosition);
this.vislibVis.render(esResponse, this.vis.getUiState());
}
});
this.vislibVis.render(esResponse, uiState);
// refreshing the legend after the chart is rendered.
// this is necessary because some visualizations
// provide data necessary for the legend only after a render cycle.
if (
visParams.addLegend &&
CUSTOM_LEGEND_VIS_TYPES.includes(this.vislibVis.visConfigArgs.type)
) {
this.unmountLegend?.();
this.mountLegend(esResponse, visParams, fireEvent, uiState);
this.vislibVis.render(esResponse, uiState);
}
}
mountLegend(visData: any, position: Positions) {
this.unmount = mountReactNode(
mountLegend(
visData: unknown,
{ legendPosition, addLegend }: BasicVislibParams | PieVisParams,
fireEvent: IInterpreterRenderHandlers['event'],
uiState?: PersistedState
) {
this.unmountLegend = mountReactNode(
<VisLegend
ref={this.legendRef}
vis={this.vis}
vislibVis={this.vislibVis}
visData={visData}
position={position}
uiState={this.vis.getUiState()}
uiState={uiState}
fireEvent={fireEvent}
addLegend={addLegend}
position={legendPosition}
/>
)(this.legendEl);
}
unmountLegend() {
if (this.unmount) {
this.unmount();
}
}
destroy(clearElement = true) {
this.unmountLegend?.();
destroy() {
if (this.unmount) {
this.unmount();
if (clearElement) {
this.el.innerHTML = '';
}
if (this.vislibVis) {
this.vislibVis.off('brush', this.vis.API.events.brush);
this.vislibVis.off('click', this.vis.API.events.filter);
this.removeListeners?.();
this.vislibVis.destroy();
delete this.vislibVis;
}

View file

@ -0,0 +1,61 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { lazy } from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { ExpressionRenderDefinition } from '../../expressions/public';
import { VisualizationContainer } from '../../visualizations/public';
import { ChartsPluginSetup } from '../../charts/public';
import { VisTypeVislibCoreSetup } from './plugin';
import { VislibRenderValue, vislibVisName } from './vis_type_vislib_vis_fn';
function shouldShowNoResultsMessage(visData: any, visType: string): boolean {
if (['goal', 'gauge'].includes(visType)) {
return false;
}
const rows: object[] | undefined = visData?.rows;
const isZeroHits = visData?.hits === 0 || (rows && !rows.length);
return Boolean(isZeroHits);
}
const VislibWrapper = lazy(() => import('./vis_wrapper'));
export const getVislibVisRenderer: (
core: VisTypeVislibCoreSetup,
charts: ChartsPluginSetup
) => ExpressionRenderDefinition<VislibRenderValue> = (core, charts) => ({
name: vislibVisName,
reuseDomNode: true,
render: async (domNode, config, handlers) => {
const showNoResult = shouldShowNoResultsMessage(config.visData, config.visType);
handlers.onDestroy(() => unmountComponentAtNode(domNode));
render(
<VisualizationContainer handlers={handlers} showNoResult={showNoResult}>
<VislibWrapper {...config} core={core} charts={charts} handlers={handlers} />
</VisualizationContainer>,
domNode
);
},
});

View file

@ -18,29 +18,35 @@
*/
import { i18n } from '@kbn/i18n';
import { ExpressionFunctionDefinition, Datatable, Render } from '../../expressions/public';
// @ts-ignore
import { vislibSeriesResponseHandler } from './vislib/response_handler';
import { BasicVislibParams } from './types';
export const vislibVisName = 'vislib_vis';
interface Arguments {
type: string;
visConfig: string;
}
type VisParams = Required<Arguments>;
interface RenderValue {
export interface VislibRenderValue {
visData: any;
visType: string;
visConfig: VisParams;
visConfig: BasicVislibParams;
}
export const createVisTypeVislibVisFn = (): ExpressionFunctionDefinition<
'vislib',
export type VisTypeVislibExpressionFunctionDefinition = ExpressionFunctionDefinition<
typeof vislibVisName,
Datatable,
Arguments,
Render<RenderValue>
> => ({
name: 'vislib',
Render<VislibRenderValue>
>;
export const createVisTypeVislibVisFn = (): VisTypeVislibExpressionFunctionDefinition => ({
name: vislibVisName,
type: 'render',
inputTypes: ['datatable'],
help: i18n.translate('visTypeVislib.functions.vislib.help', {
@ -55,23 +61,21 @@ export const createVisTypeVislibVisFn = (): ExpressionFunctionDefinition<
visConfig: {
types: ['string'],
default: '"{}"',
help: '',
help: 'vislib vis config',
},
},
fn(context, args) {
const visConfigParams = JSON.parse(args.visConfig);
const convertedData = vislibSeriesResponseHandler(context, visConfigParams.dimensions);
const visType = args.type;
const visConfig = JSON.parse(args.visConfig) as BasicVislibParams;
const visData = vislibSeriesResponseHandler(context, visConfig.dimensions);
return {
type: 'render',
as: 'visualization',
as: vislibVisName,
value: {
visData: convertedData,
visType: args.type,
visConfig: visConfigParams,
params: {
listenOnChange: true,
},
visData,
visConfig,
visType,
},
};
},

View file

@ -17,11 +17,22 @@
* under the License.
*/
export { createHistogramVisTypeDefinition } from './histogram';
export { createLineVisTypeDefinition } from './line';
export { createPieVisTypeDefinition } from './pie';
export { createAreaVisTypeDefinition } from './area';
export { createHeatmapVisTypeDefinition } from './heatmap';
export { createHorizontalBarVisTypeDefinition } from './horizontal_bar';
export { createGaugeVisTypeDefinition } from './gauge';
export { createGoalVisTypeDefinition } from './goal';
import { histogramVisTypeDefinition } from './histogram';
import { lineVisTypeDefinition } from './line';
import { areaVisTypeDefinition } from './area';
import { heatmapVisTypeDefinition } from './heatmap';
import { horizontalBarVisTypeDefinition } from './horizontal_bar';
import { gaugeVisTypeDefinition } from './gauge';
import { goalVisTypeDefinition } from './goal';
export { pieVisTypeDefinition } from './pie';
export const visLibVisTypeDefinitions = [
histogramVisTypeDefinition,
lineVisTypeDefinition,
areaVisTypeDefinition,
heatmapVisTypeDefinition,
horizontalBarVisTypeDefinition,
gaugeVisTypeDefinition,
goalVisTypeDefinition,
];

View file

@ -0,0 +1,89 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { useEffect, useMemo, useRef } from 'react';
import { EuiResizeObserver } from '@elastic/eui';
import { debounce } from 'lodash';
import { IInterpreterRenderHandlers } from '../../expressions/public';
import { ChartsPluginSetup } from '../../charts/public';
import { VislibRenderValue } from './vis_type_vislib_vis_fn';
import { createVislibVisController, VislibVisController } from './vis_controller';
import { VisTypeVislibCoreSetup } from './plugin';
import './index.scss';
type VislibWrapperProps = VislibRenderValue & {
core: VisTypeVislibCoreSetup;
charts: ChartsPluginSetup;
handlers: IInterpreterRenderHandlers;
};
const VislibWrapper = ({ core, charts, visData, visConfig, handlers }: VislibWrapperProps) => {
const chartDiv = useRef<HTMLDivElement>(null);
const visController = useRef<VislibVisController | null>(null);
const updateChart = useMemo(
() =>
debounce(() => {
if (visController.current) {
visController.current.render(visData, visConfig, handlers);
}
}, 100),
[visConfig, visData, handlers]
);
useEffect(() => {
if (chartDiv.current) {
const Controller = createVislibVisController(core, charts);
visController.current = new Controller(chartDiv.current);
}
return () => {
visController.current?.destroy();
visController.current = null;
};
}, [core, charts, handlers]);
useEffect(updateChart, [updateChart]);
useEffect(() => {
if (handlers.uiState) {
handlers.uiState.on('change', updateChart);
return () => {
handlers.uiState?.off('change', updateChart);
};
}
}, [handlers.uiState, updateChart]);
return (
<EuiResizeObserver onResize={updateChart}>
{(resizeRef) => (
<div className="vislib__wrapper" ref={resizeRef}>
<div className="vislib__container" ref={chartDiv} />
</div>
)}
</EuiResizeObserver>
);
};
// default export required for React.Lazy
// eslint-disable-next-line import/no-default-export
export { VislibWrapper as default };

View file

@ -1,27 +1,29 @@
.visLib {
.vislib {
flex: 1 1 0;
display: flex;
flex-direction: row;
overflow: auto;
&.visLib--legend-left {
&.vislib--legend-left {
flex-direction: row-reverse;
}
&.visLib--legend-right {
&.vislib--legend-right {
flex-direction: row;
}
&.visLib--legend-top {
&.vislib--legend-top {
flex-direction: column-reverse;
}
&.visLib--legend-bottom {
&.vislib--legend-bottom {
flex-direction: column;
}
}
.visLib__chart {
.vislib__chart,
.vislib__wrapper,
.vislib__container {
display: flex;
flex: 1 1 auto;
min-height: 0;

View file

@ -13,6 +13,7 @@ $visLegendLineHeight: $euiSize;
left: 0;
display: flex;
padding: $euiSizeXS;
margin: $euiSizeS;
background-color: $euiColorEmptyShade;
transition: opacity $euiAnimSpeedFast $euiAnimSlightResistance, background-color $euiAnimSpeedFast $euiAnimSlightResistance $euiAnimSpeedExtraSlow;
@ -33,13 +34,13 @@ $visLegendLineHeight: $euiSize;
height: 100%;
}
.visLib--legend-left {
.vislib--legend-left {
.visLegend__list {
margin-bottom: $euiSizeL;
}
}
.visLib--legend-bottom {
.vislib--legend-bottom {
.visLegend__list {
margin-left: $euiSizeL;
}
@ -64,8 +65,8 @@ $visLegendLineHeight: $euiSize;
}
}
.visLib--legend-top &,
.visLib--legend-bottom & {
.vislib--legend-top &,
.vislib--legend-bottom & {
width: auto;
flex-direction: row;
flex-wrap: wrap;

View file

@ -41,16 +41,8 @@ jest.mock('../../../services', () => ({
}),
}));
const vis = {
params: {
addLegend: true,
},
API: {
events: {
filter: jest.fn(),
},
},
};
const fireEvent = jest.fn();
const vislibVis = {
handler: {
highlight: jest.fn(),
@ -96,14 +88,15 @@ const uiState = {
set: jest.fn().mockImplementation((key, value) => mockState.set(key, value)),
emit: jest.fn(),
setSilent: jest.fn(),
};
} as any;
const getWrapper = async (props?: Partial<VisLegendProps>) => {
const wrapper = mount(
<I18nProvider>
<VisLegend
addLegend
position="top"
vis={vis}
fireEvent={fireEvent}
vislibVis={vislibVis}
visData={visData}
uiState={uiState}
@ -188,8 +181,7 @@ describe('VisLegend Component', () => {
});
it('should work with no handlers set', () => {
const newVis = {
...vis,
const newProps = {
vislibVis: {
...vislibVis,
handler: null,
@ -197,7 +189,7 @@ describe('VisLegend Component', () => {
};
expect(async () => {
wrapper = await getWrapper({ vis: newVis });
wrapper = await getWrapper(newProps);
const first = getLegendItems(wrapper).first();
first.simulate('focus');
first.simulate('blur');
@ -216,8 +208,11 @@ describe('VisLegend Component', () => {
const filterGroup = wrapper.find(EuiButtonGroup).first();
filterGroup.getElement().props.onChange('filterIn');
expect(vis.API.events.filter).toHaveBeenCalledWith({ data: ['valuesA'], negate: false });
expect(vis.API.events.filter).toHaveBeenCalledTimes(1);
expect(fireEvent).toHaveBeenCalledWith({
name: 'filterBucket',
data: { data: ['valuesA'], negate: false },
});
expect(fireEvent).toHaveBeenCalledTimes(1);
});
it('should filter in when clicked', () => {
@ -226,8 +221,11 @@ describe('VisLegend Component', () => {
const filterGroup = wrapper.find(EuiButtonGroup).first();
filterGroup.getElement().props.onChange('filterOut');
expect(vis.API.events.filter).toHaveBeenCalledWith({ data: ['valuesA'], negate: true });
expect(vis.API.events.filter).toHaveBeenCalledTimes(1);
expect(fireEvent).toHaveBeenCalledWith({
name: 'filterBucket',
data: { data: ['valuesA'], negate: true },
});
expect(fireEvent).toHaveBeenCalledTimes(1);
});
});

View file

@ -23,16 +23,21 @@ import { compact, uniqBy, map, every, isUndefined } from 'lodash';
import { i18n } from '@kbn/i18n';
import { EuiPopoverProps, EuiIcon, keys, htmlIdGenerator } from '@elastic/eui';
import { PersistedState } from '../../../../../visualizations/public';
import { IInterpreterRenderHandlers } from '../../../../../expressions/public';
import { getDataActions } from '../../../services';
import { CUSTOM_LEGEND_VIS_TYPES, LegendItem } from './models';
import { VisLegendItem } from './legend_item';
import { getPieNames } from './pie_utils';
import { BasicVislibParams } from '../../../types';
export interface VisLegendProps {
vis: any;
vislibVis: any;
visData: any;
uiState: any;
visData: unknown;
uiState?: PersistedState;
fireEvent: IInterpreterRenderHandlers['event'];
addLegend: BasicVislibParams['addLegend'];
position: 'top' | 'bottom' | 'left' | 'right';
}
@ -49,7 +54,10 @@ export class VisLegend extends PureComponent<VisLegendProps, VisLegendState> {
constructor(props: VisLegendProps) {
super(props);
const open = props.uiState.get('vis.legendOpen', true);
// TODO: Check when this bwc can safely be removed
const bwcLegendStateDefault = props.addLegend ?? true;
const open = props.uiState?.get('vis.legendOpen', bwcLegendStateDefault) as boolean;
this.state = {
open,
@ -64,13 +72,9 @@ export class VisLegend extends PureComponent<VisLegendProps, VisLegendState> {
}
toggleLegend = () => {
const bwcAddLegend = this.props.vis.params.addLegend;
const bwcLegendStateDefault = bwcAddLegend == null ? true : bwcAddLegend;
const newOpen = !this.props.uiState.get('vis.legendOpen', bwcLegendStateDefault);
this.setState({ open: newOpen });
// open should be applied on template before we update uiState
setTimeout(() => {
this.props.uiState.set('vis.legendOpen', newOpen);
const newOpen = !this.state.open;
this.setState({ open: newOpen }, () => {
this.props.uiState?.set('vis.legendOpen', newOpen);
});
};
@ -79,17 +83,23 @@ export class VisLegend extends PureComponent<VisLegendProps, VisLegendState> {
return;
}
const colors = this.props.uiState.get('vis.colors') || {};
const colors = this.props.uiState?.get('vis.colors') || {};
if (colors[label] === color) delete colors[label];
else colors[label] = color;
this.props.uiState.setSilent('vis.colors', null);
this.props.uiState.set('vis.colors', colors);
this.props.uiState.emit('colorChanged');
this.props.uiState?.setSilent('vis.colors', null);
this.props.uiState?.set('vis.colors', colors);
this.props.uiState?.emit('colorChanged');
this.refresh();
};
filter = ({ values: data }: LegendItem, negate: boolean) => {
this.props.vis.API.events.filter({ data, negate });
this.props.fireEvent({
name: 'filterBucket',
data: {
data,
negate,
},
});
};
canFilter = async (item: LegendItem): Promise<boolean> => {
@ -172,11 +182,8 @@ export class VisLegend extends PureComponent<VisLegendProps, VisLegendState> {
return;
} // make sure vislib is defined at this point
if (
this.props.uiState.get('vis.legendOpen') == null &&
this.props.vis.params.addLegend != null
) {
this.setState({ open: this.props.vis.params.addLegend });
if (this.props.uiState?.get('vis.legendOpen') == null && this.props.addLegend != null) {
this.setState({ open: this.props.addLegend });
}
if (vislibVis.visConfig) {

View file

@ -33,11 +33,11 @@ export function initXAxis(chart: Chart, table: Table) {
chart.xAxisLabel = title;
if ('interval' in params) {
const { interval } = params;
if ('date' in params) {
const { intervalESUnit, intervalESValue } = params;
chart.ordered = {
interval: moment.duration(interval),
interval: moment.duration(intervalESValue, intervalESUnit as any),
intervalESUnit,
intervalESValue,
};

View file

@ -28,7 +28,7 @@ import { Column, Table } from '../../types';
export interface DateHistogramParams {
date: boolean;
interval: string;
interval: number | string;
intervalESValue: number;
intervalESUnit: string;
format: string;
@ -57,6 +57,9 @@ export interface Dimensions {
y: Dimension[];
z?: Dimension[];
series?: Dimension | Dimension[];
width?: Dimension[];
splitRow?: Dimension[];
splitColumn?: Dimension[];
}
export interface Aspect {
accessor: Column['id'];

View file

@ -1,17 +0,0 @@
.visError {
flex: 1 1 0;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
// From ML
.top { align-self: flex-start; }
.bottom { align-self: flex-end; }
}
// Prevent large request errors from overflowing the container
.visError--request {
max-width: 100%;
max-height: 100%;
}

View file

@ -1,4 +1,3 @@
@import './alerts';
@import './handler';
@import './layout/index';

View file

@ -182,7 +182,6 @@ export class Dispatch {
const data = d.input || d;
return {
e: d3.event,
data: isSlices ? this._pieClickResponse(data) : this._seriesClickResponse(data),
};
}
@ -423,7 +422,6 @@ export class Dispatch {
*/
createBrush(xScale, svg) {
const self = this;
const visConfig = self.handler.visConfig;
const { width, height } = svg.node().getBBox();
const isHorizontal = self.handler.categoryAxes[0].axisConfig.isHorizontal();
@ -449,8 +447,6 @@ export class Dispatch {
return self.emit('brush', {
range,
config: visConfig,
e: d3.event,
data,
});
});

View file

@ -46,10 +46,10 @@ const markdownIt = new MarkdownIt({
* create the visualization
*/
export class Handler {
constructor(vis, visConfig, deps) {
constructor(vis, visConfig, uiSettings) {
this.el = visConfig.get('el');
this.ChartClass = chartTypes[visConfig.get('type')];
this.deps = deps;
this.uiSettings = uiSettings;
this.charts = [];
this.vis = vis;
@ -91,12 +91,18 @@ export class Handler {
const xRaw = _.get(eventPayload.data, 'series[0].values[0].xRaw');
if (!xRaw) return; // not sure if this is possible?
return self.vis.emit(eventType, {
table: xRaw.table,
range: eventPayload.range,
column: xRaw.column,
name: 'brush',
data: {
table: xRaw.table,
range: eventPayload.range,
column: xRaw.column,
},
});
case 'click':
return self.vis.emit(eventType, eventPayload);
return self.vis.emit(eventType, {
name: 'filterBucket',
data: eventPayload,
});
}
};
});
@ -164,7 +170,7 @@ export class Handler {
let loadedCount = 0;
const chartSelection = selection.selectAll('.chart');
chartSelection.each(function (chartData) {
const chart = new self.ChartClass(self, this, chartData, self.deps);
const chart = new self.ChartClass(self, this, chartData, self.uiSettings);
self.vis.eventNames().forEach(function (event) {
self.enable(event, chart);
@ -222,7 +228,7 @@ export class Handler {
// class name needs `chart` in it for the polling checkSize function
// to continuously call render on resize
.attr('class', 'visError chart error')
.attr('data-test-subj', 'visLibVisualizeError');
.attr('data-test-subj', 'vislibVisualizeError');
div.append('h4').text(markdownIt.renderInline(message));

View file

@ -157,7 +157,7 @@ dateHistogramArray.forEach(function (data, i) {
const args = Array.from(arguments);
expect(args.length).toBe(2);
expect(args[0]).toBe('click');
expect(args[1]).toBe(event);
expect(args[1].data).toBe(event);
done();
};

View file

@ -50,7 +50,6 @@ export class VisConfig {
return _.get(this._values, property, defaults);
} else {
throw new Error(`Accessing invalid config property: ${property}`);
return defaults;
}
}

View file

@ -35,13 +35,14 @@ import { DIMMING_OPACITY_SETTING, HEATMAP_MAX_BUCKETS_SETTING } from '../../comm
* @param config {Object} Parameters that define the chart type and chart options
*/
export class Vis extends EventEmitter {
constructor(element, visConfigArgs, deps) {
constructor(element, visConfigArgs, core, charts) {
super();
this.element = element.get ? element.get(0) : element;
this.visConfigArgs = _.cloneDeep(visConfigArgs);
this.visConfigArgs.dimmingOpacity = deps.uiSettings.get(DIMMING_OPACITY_SETTING);
this.visConfigArgs.heatmapMaxBuckets = deps.uiSettings.get(HEATMAP_MAX_BUCKETS_SETTING);
this.deps = deps;
this.visConfigArgs.dimmingOpacity = core.uiSettings.get(DIMMING_OPACITY_SETTING);
this.visConfigArgs.heatmapMaxBuckets = core.uiSettings.get(HEATMAP_MAX_BUCKETS_SETTING);
this.charts = charts;
this.uiSettings = core.uiSettings;
}
hasLegend() {
@ -56,7 +57,7 @@ export class Vis extends EventEmitter {
this.data,
this.uiState,
this.element,
this.deps.charts.colors.createColorLookupFunction.bind(this.deps.charts.colors)
this.charts.colors.createColorLookupFunction.bind(this.charts.colors)
);
}
@ -78,7 +79,7 @@ export class Vis extends EventEmitter {
this.initVisConfig(data, uiState);
this.handler = new Handler(this, this.visConfig, this.deps);
this.handler = new Handler(this, this.visConfig, this.uiSettings);
this._runOnHandler('render');
}

View file

@ -39,13 +39,13 @@ import {
* @param chartData {Object} Elasticsearch query results for this specific chart
*/
export class Chart {
constructor(handler, element, chartData, deps) {
constructor(handler, element, chartData, uiSettings) {
this.handler = handler;
this.chartEl = element;
this.chartData = chartData;
this.tooltips = [];
const events = (this.events = new Dispatch(handler, deps.uiSettings));
const events = (this.events = new Dispatch(handler, uiSettings));
const fieldFormatter = getFormatService().deserialize(
this.handler.data.get('tooltipFormatter')

View file

@ -53,20 +53,10 @@ afterEach(function () {
count = 0;
});
const getDeps = () => {
const mockUiSettings = coreMock.createSetup().uiSettings;
const charts = chartPluginMock.createStartContract();
return {
uiSettings: mockUiSettings,
charts: charts,
};
};
export function getVis(visLibParams, element) {
export function getVis(vislibParams, element) {
return new Vis(
element || $visCanvas.new(),
_.defaults({}, visLibParams || {}, {
_.defaults({}, vislibParams || {}, {
addTooltip: true,
addLegend: true,
defaultYExtents: false,
@ -74,6 +64,7 @@ export function getVis(visLibParams, element) {
yAxis: {},
type: 'histogram',
}),
getDeps()
coreMock.createSetup(),
chartPluginMock.createStartContract()
);
}

View file

@ -28,7 +28,7 @@ import { getVis } from './_vis_fixture';
describe('Vislib Gauge Chart Test Suite', function () {
let vis;
let chartEl;
const visLibParams = {
const vislibParams = {
type: 'gauge',
addTooltip: true,
addLegend: false,
@ -71,7 +71,7 @@ describe('Vislib Gauge Chart Test Suite', function () {
};
function generateVis(opts = {}) {
const config = _.defaultsDeep({}, opts, visLibParams);
const config = _.defaultsDeep({}, opts, vislibParams);
if (vis) {
vis.destroy();
$('.visChart').remove();

View file

@ -42,8 +42,8 @@ const defaults = {
* @param chartData {Object} Elasticsearch query results for this specific chart
*/
export class PieChart extends Chart {
constructor(handler, chartEl, chartData, deps) {
super(handler, chartEl, chartData, deps);
constructor(handler, chartEl, chartData, uiSettings) {
super(handler, chartEl, chartData, uiSettings);
const charts = this.handler.data.getVisData();
this._validatePieData(charts);
this._attr = _.defaults(handler.visConfig.get('chart', {}), defaults);

View file

@ -40,7 +40,7 @@ let mockedSVGElementGetBBox;
let mockedSVGElementGetComputedTextLength;
describe('No global chart settings', function () {
const visLibParams1 = {
const vislibParams1 = {
el: '<div class=chart1></div>',
type: 'pie',
addLegend: true,
@ -58,7 +58,7 @@ describe('No global chart settings', function () {
});
beforeEach(() => {
chart1 = getVis(visLibParams1);
chart1 = getVis(vislibParams1);
mockUiState = getMockUiState();
});
@ -153,7 +153,7 @@ describe('Vislib PieChart Class Test Suite', function () {
describe('Vislib PieChart Class Test Suite for ' + names[i] + ' data', function () {
const mockPieData = pieChartMockData[aggItem];
const visLibParams = {
const vislibParams = {
type: 'pie',
addLegend: true,
addTooltip: true,
@ -161,7 +161,7 @@ describe('Vislib PieChart Class Test Suite', function () {
let vis;
beforeEach(async () => {
vis = getVis(visLibParams);
vis = getVis(vislibParams);
const mockUiState = getMockUiState();
vis.render(mockPieData, mockUiState);
});

View file

@ -40,10 +40,10 @@ const touchdownTmpl = _.template(touchdownTmplHtml);
* @param chartData {Object} Elasticsearch query results for this specific chart
*/
export class PointSeries extends Chart {
constructor(handler, chartEl, chartData, deps) {
super(handler, chartEl, chartData, deps);
constructor(handler, chartEl, chartData, uiSettings) {
super(handler, chartEl, chartData, uiSettings);
this.deps = deps;
this.uiSettings = uiSettings;
this.handler = handler;
this.chartData = chartData;
this.chartEl = chartEl;
@ -246,7 +246,13 @@ export class PointSeries extends Chart {
if (!seriArgs.show) return;
const SeriClass =
seriTypes[seriArgs.type || self.handler.visConfig.get('chart.type')] || seriTypes.line;
const series = new SeriClass(self.handler, svg, data.series[i], seriArgs, self.deps);
const series = new SeriClass(
self.handler,
svg,
data.series[i],
seriArgs,
self.uiSettings
);
series.events = self.events;
svg.call(series.draw());
self.series.push(series);

View file

@ -43,8 +43,8 @@ const defaults = {
* chart
*/
export class AreaChart extends PointSeries {
constructor(handler, chartEl, chartData, seriesConfigArgs, deps) {
super(handler, chartEl, chartData, seriesConfigArgs, deps);
constructor(handler, chartEl, chartData, seriesConfigArgs, core) {
super(handler, chartEl, chartData, seriesConfigArgs, core);
this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults);
this.isOverlapping = this.seriesConfig.mode !== 'stacked';

View file

@ -38,7 +38,7 @@ const dataTypesArray = {
stackedSeries: import('../../../fixtures/mock_data/date_histogram/_stacked_series'),
};
const visLibParams = {
const vislibParams = {
type: 'area',
addLegend: true,
addTooltip: true,
@ -61,7 +61,7 @@ _.forOwn(dataTypesArray, function (dataType, dataTypeName) {
});
beforeEach(async () => {
vis = getVis(visLibParams);
vis = getVis(vislibParams);
mockUiState = getMockUiState();
vis.on('brush', _.noop);
vis.render(await dataType, mockUiState);

View file

@ -57,8 +57,8 @@ function datumWidth(defaultWidth, datum, nextDatum, scale, gutterWidth, groupCou
* @param chartData {Object} Elasticsearch query results for this specific chart
*/
export class ColumnChart extends PointSeries {
constructor(handler, chartEl, chartData, seriesConfigArgs, deps) {
super(handler, chartEl, chartData, seriesConfigArgs, deps);
constructor(handler, chartEl, chartData, seriesConfigArgs, core) {
super(handler, chartEl, chartData, seriesConfigArgs, core);
this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults);
this.labelOptions = _.defaults(handler.visConfig.get('labels', {}), defaults.showLabel);
}

View file

@ -62,7 +62,7 @@ dataTypesArray.forEach(function (dataType) {
describe('Vislib Column Chart Test Suite for ' + name + ' Data', function () {
let vis;
let mockUiState;
const visLibParams = {
const vislibParams = {
type: 'histogram',
addLegend: true,
addTooltip: true,
@ -81,7 +81,7 @@ dataTypesArray.forEach(function (dataType) {
});
beforeEach(() => {
vis = getVis(visLibParams);
vis = getVis(vislibParams);
mockUiState = getMockUiState();
vis.on('brush', _.noop);
vis.render(data, mockUiState);
@ -261,7 +261,7 @@ dataTypesArray.forEach(function (dataType) {
describe('stackData method - data set with zeros in percentage mode', function () {
let vis;
let mockUiState;
const visLibParams = {
const vislibParams = {
type: 'histogram',
addLegend: true,
addTooltip: true,
@ -276,7 +276,7 @@ describe('stackData method - data set with zeros in percentage mode', function (
});
beforeEach(() => {
vis = getVis(visLibParams);
vis = getVis(vislibParams);
mockUiState = getMockUiState();
vis.on('brush', _.noop);
});
@ -320,7 +320,7 @@ describe('stackData method - data set with zeros in percentage mode', function (
describe('datumWidth - split chart data set with holes', function () {
let vis;
let mockUiState;
const visLibParams = {
const vislibParams = {
type: 'histogram',
addLegend: true,
addTooltip: true,
@ -335,7 +335,7 @@ describe('datumWidth - split chart data set with holes', function () {
});
beforeEach(() => {
vis = getVis(visLibParams);
vis = getVis(vislibParams);
mockUiState = getMockUiState();
vis.on('brush', _.noop);
vis.render(rowsSeriesWithHoles, mockUiState);
@ -366,7 +366,7 @@ describe('datumWidth - split chart data set with holes', function () {
describe('datumWidth - monthly interval', function () {
let vis;
let mockUiState;
const visLibParams = {
const vislibParams = {
type: 'histogram',
addLegend: true,
addTooltip: true,
@ -384,7 +384,7 @@ describe('datumWidth - monthly interval', function () {
});
beforeEach(() => {
vis = getVis(visLibParams);
vis = getVis(vislibParams);
mockUiState = getMockUiState();
vis.on('brush', _.noop);
vis.render(seriesMonthlyInterval, mockUiState);

View file

@ -71,7 +71,7 @@ describe('Vislib Heatmap Chart Test Suite', function () {
describe('for ' + name + ' Data', function () {
let vis;
let mockUiState;
const visLibParams = {
const vislibParams = {
type: 'heatmap',
addLegend: true,
addTooltip: true,
@ -84,7 +84,7 @@ describe('Vislib Heatmap Chart Test Suite', function () {
};
function generateVis(opts = {}) {
const config = _.defaultsDeep({}, opts, visLibParams);
const config = _.defaultsDeep({}, opts, vislibParams);
vis = getVis(config);
mockUiState = getMockUiState();
vis.on('brush', _.noop);

View file

@ -42,8 +42,8 @@ const defaults = {
* @param chartData {Object} Elasticsearch query results for this specific chart
*/
export class LineChart extends PointSeries {
constructor(handler, chartEl, chartData, seriesConfigArgs, deps) {
super(handler, chartEl, chartData, seriesConfigArgs, deps);
constructor(handler, chartEl, chartData, seriesConfigArgs, core) {
super(handler, chartEl, chartData, seriesConfigArgs, core);
this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults);
}

View file

@ -71,14 +71,14 @@ describe('Vislib Line Chart', function () {
let mockUiState;
beforeEach(() => {
const visLibParams = {
const vislibParams = {
type: 'line',
addLegend: true,
addTooltip: true,
drawLinesBetweenPoints: true,
};
vis = getVis(visLibParams);
vis = getVis(vislibParams);
mockUiState = getMockUiState();
vis.render(data, mockUiState);
vis.on('brush', _.noop);

View file

@ -70,10 +70,10 @@
flex-direction: column;
}
.visChart__spinner {
.visChart__spinner, .visError {
display: flex;
flex: 1 1 auto;
justify-content: center;
align-items: center;
text-align: center;
}

View file

@ -52,6 +52,7 @@ export {
ISavedVis,
VisSavedObject,
VisResponseValue,
VisToExpressionAst,
} from './types';
export { ExprVisAPIEvents } from './expressions/vis';
export { VisualizationListItem } from './vis_types/vis_type_alias_registry';

View file

@ -6,8 +6,6 @@ exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunct
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metrics/tsvb function 1`] = `"tsvb params='{\\"foo\\":\\"bar\\"}' uiState='{}' "`;
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles pie function 1`] = `"kibana_pie visConfig='{\\"dimensions\\":{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},\\"buckets\\":[1,2]}}' "`;
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function with buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},\\"bucket\\":1}' "`;
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function without buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}}' "`;

View file

@ -21,14 +21,12 @@ import {
prepareJson,
prepareString,
buildPipelineVisFunction,
buildVislibDimensions,
buildPipeline,
SchemaConfig,
Schemas,
} from './build_pipeline';
import { Vis } from '..';
import { dataPluginMock } from '../../../../plugins/data/public/mocks';
import { IndexPattern, IAggConfigs } from '../../../../plugins/data/public';
import { parseExpression } from '../../../expressions/common';
describe('visualize loader pipeline helpers: build pipeline', () => {
@ -136,15 +134,6 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
const actual = buildPipelineVisFunction.tile_map(params, schemas, uiState);
expect(actual).toMatchSnapshot();
});
it('handles pie function', () => {
const schemas = {
...schemasDef,
segment: [1, 2],
};
const actual = buildPipelineVisFunction.pie({}, schemas, uiState);
expect(actual).toMatchSnapshot();
});
});
describe('buildPipeline', () => {
@ -174,157 +163,4 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
expect(expression).toMatchSnapshot();
});
});
describe('buildVislibDimensions', () => {
const dataStart = dataPluginMock.createStartContract();
let aggs: IAggConfigs;
let vis: Vis;
let params: any;
beforeEach(() => {
aggs = dataStart.search.aggs.createAggConfigs({} as IndexPattern, [
{
id: '0',
enabled: true,
type: 'count',
schema: 'metric',
params: {},
},
]);
params = {
searchSource: null,
timefilter: dataStart.query.timefilter.timefilter,
timeRange: null,
};
});
describe('test y dimension format for histogram chart', () => {
beforeEach(() => {
vis = {
// @ts-ignore
type: {
name: 'histogram',
},
params: {
seriesParams: [
{
data: { id: '0' },
valueAxis: 'axis-y',
},
],
valueAxes: [
{
id: 'axis-y',
scale: {
mode: 'normal',
},
},
],
},
data: {
aggs,
searchSource: {} as any,
},
isHierarchical: () => {
return false;
},
};
});
it('with one numeric metric in regular moder', async () => {
const dimensions = await buildVislibDimensions(vis, params);
const expected = { id: 'number' };
const actual = dimensions.y[0].format;
expect(actual).toEqual(expected);
});
it('with one numeric metric in percentage mode', async () => {
vis.params.valueAxes[0].scale.mode = 'percentage';
const dimensions = await buildVislibDimensions(vis, params);
const expected = { id: 'percent' };
const actual = dimensions.y[0].format;
expect(actual).toEqual(expected);
});
it('with two numeric metrics, mixed normal and percent mode should have corresponding formatters', async () => {
aggs.createAggConfig({
id: '5',
enabled: true,
type: 'count',
schema: 'metric',
params: {},
});
vis.params = {
seriesParams: [
{
data: { id: '0' },
valueAxis: 'axis-y-1',
},
{
data: { id: '5' },
valueAxis: 'axis-y-2',
},
],
valueAxes: [
{
id: 'axis-y-1',
scale: {
mode: 'normal',
},
},
{
id: 'axis-y-2',
scale: {
mode: 'percentage',
},
},
],
};
const dimensions = await buildVislibDimensions(vis, params);
const expectedY1 = { id: 'number' };
const expectedY2 = { id: 'percent' };
expect(dimensions.y[0].format).toEqual(expectedY1);
expect(dimensions.y[1].format).toEqual(expectedY2);
});
});
describe('test y dimension format for gauge chart', () => {
beforeEach(() => {
vis = {
// @ts-ignore
type: {
name: 'gauge',
},
params: { gauge: {} },
data: {
aggs,
searchSource: {} as any,
},
isHierarchical: () => {
return false;
},
};
});
it('with percentageMode = false', async () => {
vis.params.gauge.percentageMode = false;
const dimensions = await buildVislibDimensions(vis, params);
const expected = { id: 'number' };
const actual = dimensions.y[0].format;
expect(actual).toEqual(expected);
});
it('with percentageMode = true', async () => {
vis.params.gauge.percentageMode = true;
const dimensions = await buildVislibDimensions(vis, params);
const expected = { id: 'percent' };
const actual = dimensions.y[0].format;
expect(actual).toEqual(expected);
});
});
});
});

View file

@ -17,8 +17,6 @@
* under the License.
*/
import { get } from 'lodash';
import moment from 'moment';
import { formatExpression, SerializedFieldFormat } from '../../../../plugins/expressions/public';
import { IAggConfig, search, TimefilterContract } from '../../../../plugins/data/public';
import { Vis, VisParams } from '../types';
@ -76,16 +74,6 @@ export interface BuildPipelineParams {
abortSignal?: AbortSignal;
}
const vislibCharts: string[] = [
'area',
'gauge',
'goal',
'heatmap',
'histogram',
'horizontal_bar',
'line',
];
export const getSchemas = <TVisParams>(
vis: Vis<TVisParams>,
{ timeRange, timefilter }: BuildPipelineParams
@ -230,29 +218,6 @@ export const prepareDimension = (variable: string, data: any) => {
return expr;
};
const adjustVislibDimensionFormmaters = (vis: Vis, dimensions: { y: any[] }): void => {
const visConfig = vis.params;
const responseAggs = vis.data.aggs!.getResponseAggs().filter((agg: IAggConfig) => agg.enabled);
(dimensions.y || []).forEach((yDimension) => {
const yAgg = responseAggs[yDimension.accessor];
const seriesParam = (visConfig.seriesParams || []).find(
(param: any) => param.data.id === yAgg.id
);
if (seriesParam) {
const usedValueAxis = (visConfig.valueAxes || []).find(
(valueAxis: any) => valueAxis.id === seriesParam.valueAxis
);
if (get(usedValueAxis, 'scale.mode') === 'percentage') {
yDimension.format = { id: 'percent' };
}
}
if (get(visConfig, 'gauge.percentageMode') === true) {
yDimension.format = { id: 'percent' };
}
});
};
export const buildPipelineVisFunction: BuildPipelineVisFunction = {
input_control_vis: (params) => {
return `input_control_vis ${prepareJson('visConfig', params)}`;
@ -278,13 +243,6 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = {
};
return `tilemap ${prepareJson('visConfig', visConfig)}`;
},
pie: (params, schemas) => {
const visConfig = {
...params,
...buildVisConfig.pie(schemas),
};
return `kibana_pie ${prepareJson('visConfig', visConfig)}`;
},
};
const buildVisConfig: BuildVisConfigFunction = {
@ -305,55 +263,6 @@ const buildVisConfig: BuildVisConfigFunction = {
};
return visConfig;
},
pie: (schemas) => {
const visConfig = {} as any;
visConfig.dimensions = {
metric: schemas.metric[0],
buckets: schemas.segment,
splitRow: schemas.split_row,
splitColumn: schemas.split_column,
};
return visConfig;
},
};
export const buildVislibDimensions = async (vis: any, params: BuildPipelineParams) => {
const schemas = getSchemas(vis, {
timeRange: params.timeRange,
timefilter: params.timefilter,
});
const dimensions = {
x: schemas.segment ? schemas.segment[0] : null,
y: schemas.metric,
z: schemas.radius,
width: schemas.width,
series: schemas.group,
splitRow: schemas.split_row,
splitColumn: schemas.split_column,
};
if (schemas.segment) {
const xAgg = vis.data.aggs.getResponseAggs()[dimensions.x.accessor];
if (xAgg.type.name === 'date_histogram') {
dimensions.x.params.date = true;
const { esUnit, esValue } = xAgg.buckets.getInterval();
dimensions.x.params.interval = moment.duration(esValue, esUnit);
dimensions.x.params.intervalESValue = esValue;
dimensions.x.params.intervalESUnit = esUnit;
dimensions.x.params.format = xAgg.buckets.getScaledDateFormat();
dimensions.x.params.bounds = xAgg.buckets.getBounds();
} else if (xAgg.type.name === 'histogram') {
const intervalParam = xAgg.type.paramByName('interval');
const output = { params: {} as any };
await intervalParam.modifyAggConfigOnSearchRequestStart(xAgg, vis.data.searchSource, {
abortSignal: params.abortSignal,
});
intervalParam.write(xAgg, output);
dimensions.x.params.interval = output.params.interval;
}
}
adjustVislibDimensionFormmaters(vis, dimensions);
return dimensions;
};
export const buildPipeline = async (vis: Vis, params: BuildPipelineParams) => {
@ -396,11 +305,6 @@ export const buildPipeline = async (vis: Vis, params: BuildPipelineParams) => {
schemas,
uiState
);
} else if (vislibCharts.includes(vis.type.name)) {
const visConfig = { ...vis.params };
visConfig.dimensions = await buildVislibDimensions(vis, params);
pipeline += `vislib type='${vis.type.name}' ${prepareJson('visConfig', visConfig)}`;
} else {
const visConfig = { ...vis.params };
visConfig.dimensions = schemas;

View file

@ -76,4 +76,4 @@ export interface VisToExpressionAstParams {
export type VisToExpressionAst<TVisParams = VisParams> = (
vis: Vis<TVisParams>,
params: VisToExpressionAstParams
) => ExpressionAstExpression;
) => Promise<ExpressionAstExpression> | ExpressionAstExpression;

View file

@ -218,7 +218,7 @@ export function VisualizeChartPageProvider({ getService, getPageObjects }: FtrPr
}
public async expectError() {
await testSubjects.existOrFail('visLibVisualizeError');
await testSubjects.existOrFail('vislibVisualizeError');
}
public async getVisualizationRenderingCount() {