[Gauge] Vis Type (#126048)

* Added transparent background

* Added gauge/goal visType.

* Fixed palette, scale, and types.

* Set legacy chart as default.

* Removed deprecation message.

* Added percent format params, coming from visdimensions.

* Added support of labels/sublabels.

* Updated i18n label.

* Added support of showElasticChartsOptions

* Added autoextend ranges elastic charts tooltip.

* The outline elastic-charts message added.

* outline renaming and metric/buckets limitations

* reverted mistaken change of sample_vis.test.mocks.

* Warning message added to gauge split chart.

* Added warning message to the splitChart button at goal/gauge.

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Yaroslav Kuznietsov 2022-03-04 11:06:06 +02:00 committed by GitHub
parent 6c0526b67c
commit 2b6885a74c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 1228 additions and 378 deletions

View file

@ -68,6 +68,7 @@
"usageCollection": "src/plugins/usage_collection",
"utils": "packages/kbn-securitysolution-utils/src",
"visDefaultEditor": "src/plugins/vis_default_editor",
"visTypeGauge": "src/plugins/vis_types/gauge",
"visTypeHeatmap": "src/plugins/vis_types/heatmap",
"visTypeMarkdown": "src/plugins/vis_type_markdown",
"visTypeMetric": "src/plugins/vis_types/metric",

View file

@ -297,6 +297,10 @@ It acts as a container for a particular visualization and options tabs. Contains
The plugin exposes the static DefaultEditorController class to consume.
|{kib-repo}blob/{branch}/src/plugins/vis_types/gauge[visTypeGauge]
|WARNING: Missing README.
|{kib-repo}blob/{branch}/src/plugins/vis_types/heatmap[visTypeHeatmap]
|WARNING: Missing README.

View file

@ -121,4 +121,5 @@ pageLoadAssetSize:
expressionPartitionVis: 26338
sharedUX: 16225
ux: 20784
visTypeGauge: 24113
cloudSecurityPosture: 19109

View file

@ -10,6 +10,7 @@ export const PLUGIN_ID = 'expressionGauge';
export const PLUGIN_NAME = 'expressionGauge';
export type {
GaugeExpressionFunctionDefinition,
GaugeExpressionProps,
FormatFactory,
GaugeRenderProps,

View file

@ -10,6 +10,7 @@ import { Chart, Goal, Settings } from '@elastic/charts';
import { FormattedMessage } from '@kbn/i18n-react';
import type { CustomPaletteState } from '../../../../charts/public';
import { EmptyPlaceholder } from '../../../../charts/public';
import { isVisDimension } from '../../../../visualizations/common/utils';
import {
GaugeRenderProps,
GaugeLabelMajorMode,
@ -234,17 +235,22 @@ export const GaugeComponent: FC<GaugeRenderProps> = memo(
/>
);
}
const tickFormatter = formatFactory(
metricColumn?.meta?.params?.params
const customMetricFormatParams = isVisDimension(args.metric) ? args.metric.format : undefined;
const tableMetricFormatParams = metricColumn?.meta?.params?.params
? metricColumn?.meta?.params
: {
: undefined;
const defaultMetricFormatParams = {
id: 'number',
params: {
pattern: max - min > 5 ? `0,0` : `0,0.0`,
},
}
};
const tickFormatter = formatFactory(
customMetricFormatParams ?? tableMetricFormatParams ?? defaultMetricFormatParams
);
const colors = palette?.params?.colors ? normalizeColors(palette.params, min, max) : undefined;
const bands: number[] = (palette?.params as CustomPaletteState)
? normalizeBands(args.palette?.params as CustomPaletteState, { min, max })

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export const LEGACY_GAUGE_CHARTS_LIBRARY = 'visualization:visualize:legacyGaugeChartsLibrary';

View file

@ -6,13 +6,10 @@
* Side Public License, v 1.
*/
import { VisTypeDefinition } from 'src/plugins/visualizations/public';
import { gaugeVisTypeDefinition } from './gauge';
import { goalVisTypeDefinition } from './goal';
import { schema, TypeOf } from '@kbn/config-schema';
export { pieVisTypeDefinition } from './pie';
export const configSchema = schema.object({
enabled: schema.boolean({ defaultValue: true }),
});
export const visLibVisTypeDefinitions: Array<VisTypeDefinition<any>> = [
gaugeVisTypeDefinition,
goalVisTypeDefinition,
];
export type ConfigSchema = TypeOf<typeof configSchema>;

View file

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../../..',
roots: ['<rootDir>/src/plugins/vis_types/gauge'],
coverageDirectory: '<rootDir>/target/kibana-coverage/jest/src/plugins/vis_types/gauge',
coverageReporters: ['text', 'html'],
collectCoverageFrom: [
'<rootDir>/src/plugins/vis_types/gauge/{common,public,server}/**/*.{ts,tsx}',
],
};

View file

@ -0,0 +1,16 @@
{
"id": "visTypeGauge",
"version": "1.0.0",
"kibanaVersion": "kibana",
"server": true,
"ui": true,
"requiredPlugins": ["charts", "data", "expressions", "visualizations"],
"requiredBundles": ["visDefaultEditor"],
"optionalPlugins": ["expressionGauge"],
"extraPublicDirs": ["common/index"],
"owner": {
"name": "Vis Editors",
"githubTeam": "kibana-vis-editors"
},
"description": "Contains the gauge chart implementation using the elastic-charts library. The goal is to eventually deprecate the old implementation and keep only this. Until then, the library used is defined by the Legacy charts library advanced setting."
}

View file

@ -0,0 +1,63 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`gauge vis toExpressionAst function with minimal params 1`] = `
Object {
"chain": Array [
Object {
"arguments": Object {
"aggs": Array [],
"index": Array [
Object {
"chain": Array [
Object {
"arguments": Object {
"id": Array [
"123",
],
},
"function": "indexPatternLoad",
"type": "function",
},
],
"type": "expression",
},
],
"metricsAtAllLevels": Array [
false,
],
"partialRows": Array [
false,
],
},
"function": "esaggs",
"type": "function",
},
Object {
"arguments": Object {
"centralMajorMode": Array [
"custom",
],
"colorMode": Array [
"palette",
],
"labelMajorMode": Array [
"auto",
],
"labelMinor": Array [
"some custom sublabel",
],
"metric": Array [],
"shape": Array [
"circle",
],
"ticksPosition": Array [
"hidden",
],
},
"function": "gauge",
"type": "function",
},
],
"type": "expression",
}
`;

View file

@ -7,21 +7,18 @@
*/
import { i18n } from '@kbn/i18n';
import { colorSchemas } from '../../../../charts/public';
import { getPositions, getScaleTypes } from '../../../xy/public';
import { Alignment, GaugeType } from '../types';
export const getGaugeTypes = () => [
{
text: i18n.translate('visTypeVislib.gauge.gaugeTypes.arcText', {
text: i18n.translate('visTypeGauge.gauge.gaugeTypes.arcText', {
defaultMessage: 'Arc',
}),
value: GaugeType.Arc,
},
{
text: i18n.translate('visTypeVislib.gauge.gaugeTypes.circleText', {
text: i18n.translate('visTypeGauge.gauge.gaugeTypes.circleText', {
defaultMessage: 'Circle',
}),
value: GaugeType.Circle,
@ -30,19 +27,19 @@ export const getGaugeTypes = () => [
export const getAlignments = () => [
{
text: i18n.translate('visTypeVislib.gauge.alignmentAutomaticTitle', {
text: i18n.translate('visTypeGauge.gauge.alignmentAutomaticTitle', {
defaultMessage: 'Automatic',
}),
value: Alignment.Automatic,
},
{
text: i18n.translate('visTypeVislib.gauge.alignmentHorizontalTitle', {
text: i18n.translate('visTypeGauge.gauge.alignmentHorizontalTitle', {
defaultMessage: 'Horizontal',
}),
value: Alignment.Horizontal,
},
{
text: i18n.translate('visTypeVislib.gauge.alignmentVerticalTitle', {
text: i18n.translate('visTypeGauge.gauge.alignmentVerticalTitle', {
defaultMessage: 'Vertical',
}),
value: Alignment.Vertical,
@ -54,9 +51,3 @@ export const getGaugeCollections = () => ({
alignments: getAlignments(),
colorSchemas,
});
export const getHeatmapCollections = () => ({
legendPositions: getPositions(),
scales: getScaleTypes(),
colorSchemas,
});

View file

@ -10,19 +10,21 @@ import React, { useCallback } from 'react';
import { EuiSpacer } from '@elastic/eui';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { GaugeVisParams } from '../../../gauge';
import { GaugeTypeProps, GaugeVisParams } from '../../../types';
import { RangesPanel } from './ranges_panel';
import { StylePanel } from './style_panel';
import { LabelsPanel } from './labels_panel';
export type GaugeOptionsInternalProps = VisEditorOptionsProps<GaugeVisParams> & {
export interface GaugeOptionsProps extends VisEditorOptionsProps<GaugeVisParams>, GaugeTypeProps {}
export type GaugeOptionsInternalProps = GaugeOptionsProps & {
setGaugeValue: <T extends keyof GaugeVisParams['gauge']>(
paramName: T,
value: GaugeVisParams['gauge'][T]
) => void;
};
function GaugeOptions(props: VisEditorOptionsProps<GaugeVisParams>) {
function GaugeOptions(props: GaugeOptionsProps) {
const { stateParams, setValue } = props;
const setGaugeValue: GaugeOptionsInternalProps['setGaugeValue'] = useCallback(
@ -37,13 +39,9 @@ function GaugeOptions(props: VisEditorOptionsProps<GaugeVisParams>) {
return (
<>
<StylePanel {...props} setGaugeValue={setGaugeValue} />
<EuiSpacer size="s" />
<RangesPanel {...props} setGaugeValue={setGaugeValue} />
<EuiSpacer size="s" />
<LabelsPanel {...props} setGaugeValue={setGaugeValue} />
</>
);

View file

@ -19,7 +19,7 @@ function LabelsPanel({ stateParams, setValue, setGaugeValue }: GaugeOptionsInter
<EuiTitle size="xs">
<h3>
<FormattedMessage
id="visTypeVislib.controls.gaugeOptions.labelsTitle"
id="visTypeGauge.controls.gaugeOptions.labelsTitle"
defaultMessage="Labels"
/>
</h3>
@ -27,7 +27,7 @@ function LabelsPanel({ stateParams, setValue, setGaugeValue }: GaugeOptionsInter
<EuiSpacer size="s" />
<SwitchOption
label={i18n.translate('visTypeVislib.controls.gaugeOptions.showLabelsLabel', {
label={i18n.translate('visTypeGauge.controls.gaugeOptions.showLabelsLabel', {
defaultMessage: 'Show labels',
})}
paramName="show"
@ -40,7 +40,7 @@ function LabelsPanel({ stateParams, setValue, setGaugeValue }: GaugeOptionsInter
<TextInputOption
disabled={!stateParams.gauge.labels.show}
label={i18n.translate('visTypeVislib.controls.gaugeOptions.subTextLabel', {
label={i18n.translate('visTypeGauge.controls.gaugeOptions.subTextLabel', {
defaultMessage: 'Sub label',
})}
paramName="subText"

View file

@ -19,9 +19,10 @@ import {
} from '../../../../../../vis_default_editor/public';
import { ColorSchemaParams, ColorSchemas, colorSchemas } from '../../../../../../charts/public';
import { GaugeOptionsInternalProps } from '../gauge';
import { Gauge } from '../../../gauge';
import { Gauge } from '../../../types';
function RangesPanel({
showElasticChartsOptions,
setGaugeValue,
setTouched,
setValidity,
@ -50,7 +51,7 @@ function RangesPanel({
<EuiTitle size="xs">
<h3>
<FormattedMessage
id="visTypeVislib.controls.gaugeOptions.rangesTitle"
id="visTypeGauge.controls.gaugeOptions.rangesTitle"
defaultMessage="Ranges"
/>
</h3>
@ -66,13 +67,20 @@ function RangesPanel({
/>
<SwitchOption
disabled={stateParams.gauge.colorsRange.length < 2}
label={i18n.translate('visTypeVislib.controls.gaugeOptions.autoExtendRangeLabel', {
disabled={showElasticChartsOptions || stateParams.gauge.colorsRange.length < 2}
label={i18n.translate('visTypeGauge.controls.gaugeOptions.autoExtendRangeLabel', {
defaultMessage: 'Auto extend range',
})}
tooltip={i18n.translate('visTypeVislib.controls.gaugeOptions.extendRangeTooltip', {
tooltip={
showElasticChartsOptions
? i18n.translate('visTypeGauge.controls.gaugeOptions.extendRangeTooltipNotAvailable', {
defaultMessage:
'The new charts library supports only extended ranges. To disable it, please, enable the gauge legacy charts library advanced setting.',
})
: i18n.translate('visTypeGauge.controls.gaugeOptions.extendRangeTooltip', {
defaultMessage: 'Extends range to the maximum value in your data.',
})}
})
}
paramName="extendRange"
value={stateParams.gauge.extendRange}
setValue={setGaugeValue}
@ -95,25 +103,46 @@ function RangesPanel({
/>
<SwitchOption
label={i18n.translate('visTypeVislib.controls.gaugeOptions.showOutline', {
label={i18n.translate('visTypeGauge.controls.gaugeOptions.showOutline', {
defaultMessage: 'Show outline',
})}
paramName="outline"
value={stateParams.gauge.outline}
setValue={setGaugeValue}
disabled={showElasticChartsOptions}
{...(showElasticChartsOptions
? {
tooltip: i18n.translate(
'visTypeGauge.controls.gaugeOptions.showOutlineNotAvailable',
{
defaultMessage:
'The outline is not supported with the new charts library. Please, enable the gauge legacy charts library advanced setting.',
}
),
}
: {})}
/>
<SwitchOption
label={i18n.translate('visTypeVislib.controls.gaugeOptions.showLegendLabel', {
label={i18n.translate('visTypeGauge.controls.gaugeOptions.showLegendLabel', {
defaultMessage: 'Show legend',
})}
paramName="addLegend"
value={stateParams.addLegend}
setValue={setValue}
disabled={showElasticChartsOptions}
{...(showElasticChartsOptions
? {
tooltip: i18n.translate('visTypeGauge.controls.gaugeOptions.showLegendNotAvailable', {
defaultMessage:
'The legend is not supported with the new charts library. Please, enable the gauge legacy charts library advanced setting.',
}),
}
: {})}
/>
<SwitchOption
label={i18n.translate('visTypeVislib.controls.gaugeOptions.showScaleLabel', {
label={i18n.translate('visTypeGauge.controls.gaugeOptions.showScaleLabel', {
defaultMessage: 'Show scale',
})}
paramName="show"

View file

@ -7,7 +7,7 @@
*/
import React from 'react';
import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui';
import { EuiPanel, EuiSpacer, EuiTitle, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
@ -18,35 +18,19 @@ import { getGaugeCollections } from './../../collections';
const gaugeCollections = getGaugeCollections();
function StylePanel({ aggs, setGaugeValue, stateParams }: GaugeOptionsInternalProps) {
const diasableAlignment =
function StylePanel({
aggs,
setGaugeValue,
stateParams,
showElasticChartsOptions,
}: GaugeOptionsInternalProps) {
const disableAlignment =
aggs.byType(AggGroupNames.Metrics).length === 1 && !aggs.byType(AggGroupNames.Buckets);
return (
<EuiPanel paddingSize="s">
<EuiTitle size="xs">
<h3>
<FormattedMessage
id="visTypeVislib.controls.gaugeOptions.styleTitle"
defaultMessage="Style"
/>
</h3>
</EuiTitle>
<EuiSpacer size="s" />
const alignmentSelect = (
<SelectOption
label={i18n.translate('visTypeVislib.controls.gaugeOptions.gaugeTypeLabel', {
defaultMessage: 'Gauge type',
})}
options={gaugeCollections.gaugeTypes}
paramName="gaugeType"
value={stateParams.gauge.gaugeType}
setValue={setGaugeValue}
/>
<SelectOption
disabled={diasableAlignment}
label={i18n.translate('visTypeVislib.controls.gaugeOptions.alignmentLabel', {
disabled={showElasticChartsOptions || disableAlignment}
label={i18n.translate('visTypeGauge.controls.gaugeOptions.alignmentLabel', {
defaultMessage: 'Alignment',
})}
options={gaugeCollections.alignments}
@ -54,6 +38,47 @@ function StylePanel({ aggs, setGaugeValue, stateParams }: GaugeOptionsInternalPr
value={stateParams.gauge.alignment}
setValue={setGaugeValue}
/>
);
return (
<EuiPanel paddingSize="s">
<EuiTitle size="xs">
<h3>
<FormattedMessage
id="visTypeGauge.controls.gaugeOptions.styleTitle"
defaultMessage="Style"
/>
</h3>
</EuiTitle>
<EuiSpacer size="s" />
<SelectOption
label={i18n.translate('visTypeGauge.controls.gaugeOptions.gaugeTypeLabel', {
defaultMessage: 'Gauge type',
})}
options={gaugeCollections.gaugeTypes}
paramName="gaugeType"
value={stateParams.gauge.gaugeType}
setValue={setGaugeValue}
/>
{showElasticChartsOptions ? (
<>
<EuiSpacer size="s" />
<EuiToolTip
content={i18n.translate('visTypeGauge.editors.gauge.alignmentNotAvailable', {
defaultMessage:
'The alignment is not yet supported with the new charts library. Please, enable the gauge legacy charts library advanced setting.',
})}
delay="long"
position="right"
>
{alignmentSelect}
</EuiToolTip>
<EuiSpacer size="s" />
</>
) : (
alignmentSelect
)}
</EuiPanel>
);
}

View file

@ -9,10 +9,11 @@
import React, { lazy } from 'react';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { GaugeVisParams } from '../../gauge';
import { GaugeTypeProps, GaugeVisParams } from '../../types';
const GaugeOptionsLazy = lazy(() => import('./gauge'));
export const GaugeOptions = (props: VisEditorOptionsProps<GaugeVisParams>) => (
<GaugeOptionsLazy {...props} />
);
export const getGaugeOptions =
({ showElasticChartsOptions }: GaugeTypeProps) =>
(props: VisEditorOptionsProps<GaugeVisParams>) =>
<GaugeOptionsLazy {...props} showElasticChartsOptions={showElasticChartsOptions} />;

View file

@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { VisTypeGaugePlugin } from './plugin';
export function plugin() {
return new VisTypeGaugePlugin();
}
export type { VisTypeGaugePluginSetup, VisTypeGaugePluginStart } from './types';
export { gaugeVisType, goalVisType } from './vis_type';

View file

@ -0,0 +1,41 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { VisualizationsSetup } from '../../../../plugins/visualizations/public';
import { DataPublicPluginStart } from '../../../../plugins/data/public';
import { CoreSetup } from '../../../../core/public';
import { LEGACY_GAUGE_CHARTS_LIBRARY } from '../common';
import { VisTypeGaugePluginSetup } from './types';
import { gaugeVisType, goalVisType } from './vis_type';
/** @internal */
export interface VisTypeGaugeSetupDependencies {
visualizations: VisualizationsSetup;
}
/** @internal */
export interface VisTypePiePluginStartDependencies {
data: DataPublicPluginStart;
}
export class VisTypeGaugePlugin {
public setup(
core: CoreSetup<VisTypePiePluginStartDependencies>,
{ visualizations }: VisTypeGaugeSetupDependencies
): VisTypeGaugePluginSetup {
if (!core.uiSettings.get(LEGACY_GAUGE_CHARTS_LIBRARY)) {
const visTypeProps = { showElasticChartsOptions: true };
visualizations.createBaseVisualization(gaugeVisType(visTypeProps));
visualizations.createBaseVisualization(goalVisType(visTypeProps));
}
return {};
}
public start() {}
}

View file

@ -0,0 +1,53 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { TimefilterContract } from 'src/plugins/data/public';
import { Vis } from 'src/plugins/visualizations/public';
import { toExpressionAst } from './to_ast';
import { GaugeVisParams } from './types';
describe('gauge vis toExpressionAst function', () => {
let vis: Vis<GaugeVisParams>;
beforeEach(() => {
vis = {
isHierarchical: () => false,
type: {},
params: {
gauge: {
gaugeType: 'Circle',
scale: {
show: false,
labels: false,
color: 'rgba(105,112,125,0.2)',
},
labels: {
show: true,
},
style: {
subText: 'some custom sublabel',
},
},
},
data: {
indexPattern: { id: '123' } as any,
aggs: {
getResponseAggs: () => [],
aggs: [],
} as any,
},
} as unknown as Vis<GaugeVisParams>;
});
it('with minimal params', () => {
const actual = toExpressionAst(vis, {
timefilter: {} as TimefilterContract,
});
expect(actual).toMatchSnapshot();
});
});

View file

@ -0,0 +1,93 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { getVisSchemas, SchemaConfig, VisToExpressionAst } from '../../../visualizations/public';
import { buildExpression, buildExpressionFunction } from '../../../expressions/public';
import type {
GaugeExpressionFunctionDefinition,
GaugeShape,
} from '../../../chart_expressions/expression_gauge/common';
import { GaugeType, GaugeVisParams } from './types';
import { getStopsWithColorsFromRanges } from './utils';
import { getEsaggsFn } from './to_ast_esaggs';
const prepareDimension = (params: SchemaConfig) => {
const visdimension = buildExpressionFunction('visdimension', { accessor: params.accessor });
if (params.format) {
visdimension.addArgument('format', params.format.id);
visdimension.addArgument('formatParams', JSON.stringify(params.format.params));
}
return buildExpression([visdimension]);
};
const gaugeTypeToShape = (type: GaugeType): GaugeShape => {
const arc: GaugeShape = 'arc';
const circle: GaugeShape = 'circle';
return {
[GaugeType.Arc]: arc,
[GaugeType.Circle]: circle,
}[type];
};
export const toExpressionAst: VisToExpressionAst<GaugeVisParams> = (vis, params) => {
const schemas = getVisSchemas(vis, params);
const {
gaugeType,
percentageMode,
percentageFormatPattern,
colorSchema,
colorsRange,
invertColors,
scale,
style,
labels,
} = vis.params.gauge;
// fix formatter for percentage mode
if (percentageMode === true) {
schemas.metric.forEach((metric: SchemaConfig) => {
metric.format = {
id: 'percent',
params: { pattern: percentageFormatPattern },
};
});
}
const centralMajorMode = labels.show ? (style.subText ? 'custom' : 'auto') : 'none';
const gauge = buildExpressionFunction<GaugeExpressionFunctionDefinition>('gauge', {
shape: gaugeTypeToShape(gaugeType),
metric: schemas.metric.map(prepareDimension),
ticksPosition: scale.show ? 'auto' : 'hidden',
labelMajorMode: 'auto',
colorMode: 'palette',
centralMajorMode,
...(centralMajorMode === 'custom' ? { labelMinor: style.subText } : {}),
});
if (colorsRange && colorsRange.length) {
const stopsWithColors = getStopsWithColorsFromRanges(colorsRange, colorSchema, invertColors);
const palette = buildExpressionFunction('palette', {
...stopsWithColors,
range: percentageMode ? 'percent' : 'number',
continuity: 'none',
gradient: true,
rangeMax: percentageMode ? 100 : Infinity,
rangeMin: 0,
});
gauge.addArgument('palette', buildExpression([palette]));
}
const ast = buildExpression([getEsaggsFn(vis), gauge]);
return ast.toAst();
};

View file

@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { Vis } from '../../../visualizations/public';
import { buildExpression, buildExpressionFunction } from '../../../expressions/public';
import {
EsaggsExpressionFunctionDefinition,
IndexPatternLoadExpressionFunctionDefinition,
} from '../../../data/public';
import { GaugeVisParams } from './types';
/**
* Get esaggs expressions function
* @param vis
*/
export function getEsaggsFn(vis: Vis<GaugeVisParams>) {
return buildExpressionFunction<EsaggsExpressionFunctionDefinition>('esaggs', {
index: buildExpression([
buildExpressionFunction<IndexPatternLoadExpressionFunctionDefinition>('indexPatternLoad', {
id: vis.data.indexPattern!.id!,
}),
]),
metricsAtAllLevels: vis.isHierarchical(),
partialRows: false,
aggs: vis.data.aggs!.aggs.map((agg) => buildExpression(agg.toExpressionAst())),
});
}

View file

@ -0,0 +1,68 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { $Values } from '@kbn/utility-types';
import { Range } from '../../../expressions/public';
import { ColorSchemaParams, Labels, Style } from '../../../charts/public';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface VisTypeGaugePluginSetup {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface VisTypeGaugePluginStart {}
/**
* Gauge title alignment
*/
export const Alignment = {
Automatic: 'automatic',
Horizontal: 'horizontal',
Vertical: 'vertical',
} as const;
export type Alignment = $Values<typeof Alignment>;
export const GaugeType = {
Arc: 'Arc',
Circle: 'Circle',
} as const;
export type GaugeType = $Values<typeof GaugeType>;
export interface Gauge extends ColorSchemaParams {
backStyle: 'Full';
gaugeStyle: 'Full';
orientation: 'vertical';
type: 'meter';
alignment: Alignment;
colorsRange: Range[];
extendRange: boolean;
gaugeType: GaugeType;
labels: Labels;
percentageMode: boolean;
percentageFormatPattern?: string;
outline?: boolean;
scale: {
show: boolean;
labels: false;
color: 'rgba(105,112,125,0.2)';
};
style: Style;
}
export interface GaugeVisParams {
type: 'gauge';
addTooltip: boolean;
addLegend: boolean;
isDisplayWarning: boolean;
gauge: Gauge;
}
export interface GaugeTypeProps {
showElasticChartsOptions?: boolean;
}

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export { getStopsWithColorsFromRanges } from './palette';

View file

@ -0,0 +1,49 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { ColorSchemas, getHeatmapColors } from '../../../../charts/common';
import { Range } from '../../../../expressions';
export interface PaletteConfig {
color: Array<string | undefined>;
stop: number[];
}
const TRANSPARENT = 'rgb(0, 0, 0, 0)';
const getColor = (
index: number,
elementsCount: number,
colorSchema: ColorSchemas,
invertColors: boolean = false
) => {
const divider = Math.max(elementsCount - 1, 1);
const value = invertColors ? 1 - index / divider : index / divider;
return getHeatmapColors(value, colorSchema);
};
export const getStopsWithColorsFromRanges = (
ranges: Range[],
colorSchema: ColorSchemas,
invertColors: boolean = false
) => {
return ranges.reduce<PaletteConfig>(
(acc, range, index, rangesArr) => {
if (index && range.from !== rangesArr[index - 1].to) {
acc.color.push(TRANSPARENT);
acc.stop.push(range.from);
}
acc.color.push(getColor(index, rangesArr.length, colorSchema, invertColors));
acc.stop.push(range.to);
return acc;
},
{ color: [], stop: [] }
);
};

View file

@ -0,0 +1,130 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React from 'react';
import { i18n } from '@kbn/i18n';
import { ColorMode, ColorSchemas } from '../../../../charts/public';
import { AggGroupNames } from '../../../../data/public';
import { VisTypeDefinition, VIS_EVENT_TO_TRIGGER } from '../../../../visualizations/public';
import { Alignment, GaugeType, GaugeTypeProps } from '../types';
import { toExpressionAst } from '../to_ast';
import { getGaugeOptions } from '../editor/components';
import { GaugeVisParams } from '../types';
import { SplitTooltip } from './split_tooltip';
export const getGaugeVisTypeDefinition = (
props: GaugeTypeProps
): VisTypeDefinition<GaugeVisParams> => ({
name: 'gauge',
title: i18n.translate('visTypeGauge.gauge.gaugeTitle', { defaultMessage: 'Gauge' }),
icon: 'visGauge',
description: i18n.translate('visTypeGauge.gauge.gaugeDescription', {
defaultMessage: 'Show the status of a metric.',
}),
getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter],
toExpressionAst,
visConfig: {
defaults: {
type: 'gauge',
addTooltip: true,
addLegend: true,
isDisplayWarning: false,
gauge: {
alignment: Alignment.Automatic,
extendRange: true,
percentageMode: false,
gaugeType: GaugeType.Arc,
gaugeStyle: 'Full',
backStyle: 'Full',
orientation: 'vertical',
colorSchema: ColorSchemas.GreenToRed,
gaugeColorMode: ColorMode.Labels,
colorsRange: [
{ from: 0, to: 50 },
{ from: 50, to: 75 },
{ from: 75, to: 100 },
],
invertColors: false,
labels: {
show: true,
color: 'black',
},
scale: {
show: true,
labels: false,
color: 'rgba(105,112,125,0.2)',
},
type: 'meter',
style: {
bgWidth: 0.9,
width: 0.9,
mask: false,
bgMask: false,
maskBars: 50,
bgFill: 'rgba(105,112,125,0.2)',
bgColor: true,
subText: '',
fontSize: 60,
},
},
},
},
editorConfig: {
optionsTemplate: getGaugeOptions(props),
schemas: [
{
group: AggGroupNames.Metrics,
name: 'metric',
title: i18n.translate('visTypeGauge.gauge.metricTitle', { defaultMessage: 'Metric' }),
min: 1,
...(props.showElasticChartsOptions ? { max: 1 } : {}),
aggFilter: [
'!std_dev',
'!geo_centroid',
'!percentiles',
'!percentile_ranks',
'!derivative',
'!serial_diff',
'!moving_avg',
'!cumulative_sum',
'!geo_bounds',
'!filtered_metric',
'!single_percentile',
],
defaults: [{ schema: 'metric', type: 'count' }],
},
{
group: AggGroupNames.Buckets,
name: 'group',
// TODO: Remove when split chart aggs are supported
...(props.showElasticChartsOptions && {
disabled: true,
tooltip: <SplitTooltip />,
}),
title: i18n.translate('visTypeGauge.gauge.groupTitle', {
defaultMessage: 'Split group',
}),
min: 0,
max: 1,
aggFilter: [
'!geohash_grid',
'!geotile_grid',
'!filter',
'!sampler',
'!diversified_sampler',
'!rare_terms',
'!multi_terms',
'!significant_text',
],
},
],
},
requiresSearch: true,
});

View file

@ -0,0 +1,122 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React from 'react';
import { i18n } from '@kbn/i18n';
import { AggGroupNames } from '../../../../data/public';
import { ColorMode, ColorSchemas } from '../../../../charts/public';
import { VisTypeDefinition } from '../../../../visualizations/public';
import { getGaugeOptions } from '../editor/components';
import { toExpressionAst } from '../to_ast';
import { GaugeVisParams, GaugeType, GaugeTypeProps } from '../types';
import { SplitTooltip } from './split_tooltip';
export const getGoalVisTypeDefinition = (
props: GaugeTypeProps
): VisTypeDefinition<GaugeVisParams> => ({
name: 'goal',
title: i18n.translate('visTypeGauge.goal.goalTitle', { defaultMessage: 'Goal' }),
icon: 'visGoal',
description: i18n.translate('visTypeGauge.goal.goalDescription', {
defaultMessage: 'Track how a metric progresses to a goal.',
}),
toExpressionAst,
visConfig: {
defaults: {
addTooltip: true,
addLegend: false,
isDisplayWarning: false,
type: 'gauge',
gauge: {
verticalSplit: false,
autoExtend: false,
percentageMode: true,
gaugeType: GaugeType.Arc,
gaugeStyle: 'Full',
backStyle: 'Full',
orientation: 'vertical',
useRanges: false,
colorSchema: ColorSchemas.GreenToRed,
gaugeColorMode: ColorMode.None,
colorsRange: [{ from: 0, to: 10000 }],
invertColors: false,
labels: {
show: true,
color: 'black',
},
scale: {
show: false,
labels: false,
color: 'rgba(105,112,125,0.2)',
width: 2,
},
type: 'meter',
style: {
bgFill: 'rgba(105,112,125,0.2)',
bgColor: false,
labelColor: false,
subText: '',
fontSize: 60,
},
},
},
},
editorConfig: {
optionsTemplate: getGaugeOptions(props),
schemas: [
{
group: AggGroupNames.Metrics,
name: 'metric',
title: i18n.translate('visTypeGauge.goal.metricTitle', { defaultMessage: 'Metric' }),
min: 1,
...(props.showElasticChartsOptions ? { max: 1 } : {}),
aggFilter: [
'!std_dev',
'!geo_centroid',
'!percentiles',
'!percentile_ranks',
'!derivative',
'!serial_diff',
'!moving_avg',
'!cumulative_sum',
'!geo_bounds',
'!filtered_metric',
'!single_percentile',
],
defaults: [{ schema: 'metric', type: 'count' }],
},
{
group: AggGroupNames.Buckets,
name: 'group',
// TODO: Remove when split chart aggs are supported
...(props.showElasticChartsOptions && {
disabled: true,
tooltip: <SplitTooltip />,
}),
title: i18n.translate('visTypeGauge.goal.groupTitle', {
defaultMessage: 'Split group',
}),
min: 0,
max: 1,
aggFilter: [
'!geohash_grid',
'!geotile_grid',
'!filter',
'!sampler',
'!diversified_sampler',
'!rare_terms',
'!multi_terms',
'!significant_text',
],
},
],
},
requiresSearch: true,
});

View file

@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { GaugeTypeProps } from '../types';
import { getGaugeVisTypeDefinition } from './gauge';
import { getGoalVisTypeDefinition } from './goal';
export const gaugeVisType = (props: GaugeTypeProps) => {
return getGaugeVisTypeDefinition(props);
};
export const goalVisType = (props: GaugeTypeProps) => {
return getGoalVisTypeDefinition(props);
};

View file

@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
export function SplitTooltip() {
return (
<FormattedMessage
id="visTypeGauge.splitTitle.tooltip"
defaultMessage="Split chart aggregation is not yet supported with the new charts library. Please, enable the gauge legacy charts library advanced setting to use split chart aggregation."
/>
);
}

View file

@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { PluginConfigDescriptor } from 'src/core/server';
import { configSchema, ConfigSchema } from '../config';
import { VisTypeGaugeServerPlugin } from './plugin';
export const config: PluginConfigDescriptor<ConfigSchema> = {
schema: configSchema,
};
export const plugin = () => new VisTypeGaugeServerPlugin();

View file

@ -0,0 +1,47 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { i18n } from '@kbn/i18n';
import { schema } from '@kbn/config-schema';
import { CoreSetup, Plugin, UiSettingsParams } from 'kibana/server';
import { LEGACY_GAUGE_CHARTS_LIBRARY } from '../common';
export const getUiSettingsConfig: () => Record<string, UiSettingsParams<boolean>> = () => ({
[LEGACY_GAUGE_CHARTS_LIBRARY]: {
name: i18n.translate(
'visTypeGauge.advancedSettings.visualization.legacyGaugeChartsLibrary.name',
{
defaultMessage: 'Gauge legacy charts library',
}
),
requiresPageReload: true,
value: true,
description: i18n.translate(
'visTypeGauge.advancedSettings.visualization.legacyGaugeChartsLibrary.description',
{
defaultMessage: 'Enables legacy charts library for gauge charts in visualize.',
}
),
category: ['visualization'],
schema: schema.boolean(),
},
});
export class VisTypeGaugeServerPlugin implements Plugin<object, object> {
public setup(core: CoreSetup) {
core.uiSettings.register(getUiSettingsConfig());
return {};
}
public start() {
return {};
}
}

View file

@ -0,0 +1,27 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./target/types",
"emitDeclarationOnly": true,
"declaration": true,
"declarationMap": true
},
"include": [
"common/**/*",
"public/**/*",
"server/**/*",
"*.ts"
],
"references": [
{ "path": "../../../core/tsconfig.json" },
{ "path": "../../charts/tsconfig.json" },
{ "path": "../../data/tsconfig.json" },
{ "path": "../../expressions/tsconfig.json" },
{ "path": "../../chart_expressions/expression_gauge/tsconfig.json" },
{ "path": "../../visualizations/tsconfig.json" },
{ "path": "../../usage_collection/tsconfig.json" },
{ "path": "../../vis_default_editor/tsconfig.json" },
{ "path": "../../field_formats/tsconfig.json" },
{ "path": "../../chart_expressions/expression_partition_vis/tsconfig.json" }
]
}

View file

@ -4,7 +4,7 @@
"server": true,
"ui": true,
"requiredPlugins": ["charts", "data", "expressions", "visualizations"],
"requiredBundles": ["kibanaUtils", "visDefaultEditor", "visTypeXy", "visTypePie", "visTypeHeatmap", "fieldFormats", "kibanaReact"],
"requiredBundles": ["kibanaUtils", "visTypeXy", "visTypePie", "visTypeHeatmap", "visTypeGauge", "fieldFormats", "kibanaReact"],
"owner": {
"name": "Vis Editors",
"githubTeam": "kibana-vis-editors"

View file

@ -6,16 +6,13 @@
* Side Public License, v 1.
*/
import { i18n } from '@kbn/i18n';
import { ColorMode, ColorSchemas, ColorSchemaParams, Labels, Style } from '../../../charts/public';
import { ColorSchemaParams, Labels, Style } from '../../../charts/public';
import { RangeValues } from '../../../vis_default_editor/public';
import { AggGroupNames } from '../../../data/public';
import { VisTypeDefinition, VIS_EVENT_TO_TRIGGER } from '../../../visualizations/public';
import { gaugeVisType } from '../../gauge/public';
import { VisTypeDefinition } from '../../../visualizations/public';
import { Alignment, GaugeType, VislibChartType } from './types';
import { Alignment, GaugeType } from './types';
import { toExpressionAst } from './to_ast';
import { GaugeOptions } from './editor/components';
export interface Gauge extends ColorSchemaParams {
backStyle: 'Full';
@ -46,104 +43,7 @@ export interface GaugeVisParams {
gauge: Gauge;
}
export const gaugeVisTypeDefinition: VisTypeDefinition<GaugeVisParams> = {
name: 'gauge',
title: i18n.translate('visTypeVislib.gauge.gaugeTitle', { defaultMessage: 'Gauge' }),
icon: 'visGauge',
description: i18n.translate('visTypeVislib.gauge.gaugeDescription', {
defaultMessage: 'Show the status of a metric.',
}),
getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter],
export const gaugeVisTypeDefinition = {
...gaugeVisType({}),
toExpressionAst,
visConfig: {
defaults: {
type: VislibChartType.Gauge,
addTooltip: true,
addLegend: true,
isDisplayWarning: false,
gauge: {
alignment: Alignment.Automatic,
extendRange: true,
percentageMode: false,
gaugeType: GaugeType.Arc,
gaugeStyle: 'Full',
backStyle: 'Full',
orientation: 'vertical',
colorSchema: ColorSchemas.GreenToRed,
gaugeColorMode: ColorMode.Labels,
colorsRange: [
{ from: 0, to: 50 },
{ from: 50, to: 75 },
{ from: 75, to: 100 },
],
invertColors: false,
labels: {
show: true,
color: 'black',
},
scale: {
show: true,
labels: false,
color: 'rgba(105,112,125,0.2)',
},
type: 'meter',
style: {
bgWidth: 0.9,
width: 0.9,
mask: false,
bgMask: false,
maskBars: 50,
bgFill: 'rgba(105,112,125,0.2)',
bgColor: true,
subText: '',
fontSize: 60,
},
},
},
},
editorConfig: {
optionsTemplate: GaugeOptions,
schemas: [
{
group: AggGroupNames.Metrics,
name: 'metric',
title: i18n.translate('visTypeVislib.gauge.metricTitle', { defaultMessage: 'Metric' }),
min: 1,
aggFilter: [
'!std_dev',
'!geo_centroid',
'!percentiles',
'!percentile_ranks',
'!derivative',
'!serial_diff',
'!moving_avg',
'!cumulative_sum',
'!geo_bounds',
'!filtered_metric',
'!single_percentile',
],
defaults: [{ schema: 'metric', type: 'count' }],
},
{
group: AggGroupNames.Buckets,
name: 'group',
title: i18n.translate('visTypeVislib.gauge.groupTitle', {
defaultMessage: 'Split group',
}),
min: 0,
max: 1,
aggFilter: [
'!geohash_grid',
'!geotile_grid',
'!filter',
'!sampler',
'!diversified_sampler',
'!rare_terms',
'!multi_terms',
'!significant_text',
],
},
],
},
requiresSearch: true,
};
} as VisTypeDefinition<GaugeVisParams>;

View file

@ -6,108 +6,13 @@
* Side Public License, v 1.
*/
import { i18n } from '@kbn/i18n';
import { AggGroupNames } from '../../../data/public';
import { ColorMode, ColorSchemas } from '../../../charts/public';
import { VisTypeDefinition } from '../../../visualizations/public';
import { goalVisType } from '../../gauge/public';
import { GaugeOptions } from './editor';
import { toExpressionAst } from './to_ast';
import { GaugeType } from './types';
import { GaugeVisParams } from './gauge';
export const goalVisTypeDefinition: VisTypeDefinition<GaugeVisParams> = {
name: 'goal',
title: i18n.translate('visTypeVislib.goal.goalTitle', { defaultMessage: 'Goal' }),
icon: 'visGoal',
description: i18n.translate('visTypeVislib.goal.goalDescription', {
defaultMessage: 'Track how a metric progresses to a goal.',
}),
export const goalVisTypeDefinition = {
...goalVisType({}),
toExpressionAst,
visConfig: {
defaults: {
addTooltip: true,
addLegend: false,
isDisplayWarning: false,
type: 'gauge',
gauge: {
verticalSplit: false,
autoExtend: false,
percentageMode: true,
gaugeType: GaugeType.Arc,
gaugeStyle: 'Full',
backStyle: 'Full',
orientation: 'vertical',
useRanges: false,
colorSchema: ColorSchemas.GreenToRed,
gaugeColorMode: ColorMode.None,
colorsRange: [{ from: 0, to: 10000 }],
invertColors: false,
labels: {
show: true,
color: 'black',
},
scale: {
show: false,
labels: false,
color: 'rgba(105,112,125,0.2)',
width: 2,
},
type: 'meter',
style: {
bgFill: 'rgba(105,112,125,0.2)',
bgColor: false,
labelColor: false,
subText: '',
fontSize: 60,
},
},
},
},
editorConfig: {
optionsTemplate: GaugeOptions,
schemas: [
{
group: AggGroupNames.Metrics,
name: 'metric',
title: i18n.translate('visTypeVislib.goal.metricTitle', { defaultMessage: 'Metric' }),
min: 1,
aggFilter: [
'!std_dev',
'!geo_centroid',
'!percentiles',
'!percentile_ranks',
'!derivative',
'!serial_diff',
'!moving_avg',
'!cumulative_sum',
'!geo_bounds',
'!filtered_metric',
'!single_percentile',
],
defaults: [{ schema: 'metric', type: 'count' }],
},
{
group: AggGroupNames.Buckets,
name: 'group',
title: i18n.translate('visTypeVislib.goal.groupTitle', {
defaultMessage: 'Split group',
}),
min: 0,
max: 1,
aggFilter: [
'!geohash_grid',
'!geotile_grid',
'!filter',
'!sampler',
'!diversified_sampler',
'!rare_terms',
'!multi_terms',
'!significant_text',
],
},
],
},
requiresSearch: true,
};
} as VisTypeDefinition<GaugeVisParams>;

View file

@ -14,13 +14,16 @@ import { ChartsPluginSetup } from '../../../charts/public';
import { DataPublicPluginStart } from '../../../data/public';
import { LEGACY_PIE_CHARTS_LIBRARY } from '../../pie/common/index';
import { LEGACY_HEATMAP_CHARTS_LIBRARY } from '../../heatmap/common/index';
import { LEGACY_GAUGE_CHARTS_LIBRARY } from '../../gauge/common/index';
import { heatmapVisTypeDefinition } from './heatmap';
import { createVisTypeVislibVisFn } from './vis_type_vislib_vis_fn';
import { createPieVisFn } from './pie_fn';
import { visLibVisTypeDefinitions, pieVisTypeDefinition } from './vis_type_vislib_vis_types';
import { pieVisTypeDefinition } from './pie';
import { setFormatService, setDataActions, setTheme } from './services';
import { getVislibVisRenderer } from './vis_renderer';
import { gaugeVisTypeDefinition } from './gauge';
import { goalVisTypeDefinition } from './goal';
/** @internal */
export interface VisTypeVislibPluginSetupDependencies {
@ -48,7 +51,7 @@ export class VisTypeVislibPlugin
{ expressions, visualizations, charts }: VisTypeVislibPluginSetupDependencies
) {
// register vislib XY axis charts
visLibVisTypeDefinitions.forEach(visualizations.createBaseVisualization);
expressions.registerRenderer(getVislibVisRenderer(core, charts));
expressions.registerFunction(createVisTypeVislibVisFn());
@ -57,10 +60,16 @@ export class VisTypeVislibPlugin
visualizations.createBaseVisualization(pieVisTypeDefinition);
expressions.registerFunction(createPieVisFn());
}
if (core.uiSettings.get(LEGACY_HEATMAP_CHARTS_LIBRARY)) {
// register vislib heatmap chart
visualizations.createBaseVisualization(heatmapVisTypeDefinition);
expressions.registerFunction(createVisTypeVislibVisFn());
}
if (core.uiSettings.get(LEGACY_GAUGE_CHARTS_LIBRARY)) {
// register vislib gauge and goal charts
visualizations.createBaseVisualization(gaugeVisTypeDefinition);
visualizations.createBaseVisualization(goalVisTypeDefinition);
}
}

View file

@ -18,7 +18,7 @@
{ "path": "../../expressions/tsconfig.json" },
{ "path": "../../visualizations/tsconfig.json" },
{ "path": "../../kibana_utils/tsconfig.json" },
{ "path": "../../vis_default_editor/tsconfig.json" },
{ "path": "../../vis_types/gauge/tsconfig.json" },
{ "path": "../../vis_types/xy/tsconfig.json" },
{ "path": "../../vis_types/pie/tsconfig.json" },
{ "path": "../../vis_types/heatmap/tsconfig.json" },

View file

@ -37,7 +37,7 @@ export const getAccessorByDimension = (
dimension: string | ExpressionValueVisDimension,
columns: DatatableColumn[]
) => {
if (typeof dimension === 'string') {
if (!isVisDimension(dimension)) {
return dimension;
}
@ -48,3 +48,13 @@ export const getAccessorByDimension = (
return accessor.id;
};
export function isVisDimension(
accessor: string | ExpressionValueVisDimension | undefined
): accessor is ExpressionValueVisDimension {
if (typeof accessor === 'string' || accessor === undefined) {
return false;
}
return true;
}

View file

@ -8,4 +8,4 @@
export { prepareLogTable } from './prepare_log_table';
export type { Dimension } from './prepare_log_table';
export { findAccessorOrFail, getAccessorByDimension } from './accessors';
export { findAccessorOrFail, getAccessorByDimension, isVisDimension } from './accessors';

View file

@ -6,40 +6,39 @@
* Side Public License, v 1.
*/
import React from 'react';
import React, { FC } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiCallOut, EuiLink } from '@elastic/eui';
import { useKibana } from '../../../../kibana_react/public';
import { VisualizeServices } from '../types';
import { CHARTS_WITHOUT_SMALL_MULTIPLES } from '../utils/split_chart_warning_helpers';
import type { CHARTS_WITHOUT_SMALL_MULTIPLES as CHART_WITHOUT_SMALL_MULTIPLES } from '../utils/split_chart_warning_helpers';
export const NEW_HEATMAP_CHARTS_LIBRARY = 'visualization:visualize:legacyHeatmapChartsLibrary';
interface Props {
chartType: CHART_WITHOUT_SMALL_MULTIPLES;
chartConfigToken: string;
}
export const SplitChartWarning = () => {
const { services } = useKibana<VisualizeServices>();
const canEditAdvancedSettings = services.application.capabilities.advancedSettings.save;
const advancedSettingsLink = services.application.getUrlForApp('management', {
path: `/kibana/settings?query=${NEW_HEATMAP_CHARTS_LIBRARY}`,
});
interface WarningMessageProps {
canEditAdvancedSettings: boolean | Readonly<{ [x: string]: boolean }>;
advancedSettingsLink: string;
}
const SwitchToOldLibraryMessage: FC<WarningMessageProps> = ({
canEditAdvancedSettings,
advancedSettingsLink,
}) => {
return (
<EuiCallOut
data-test-subj="vizSplitChartWarning"
title={
<FormattedMessage
id="visualizations.newHeatmapChart.notificationMessage"
defaultMessage="The new heatmap charts library does not yet support split chart aggregation. {conditionalMessage}"
values={{
conditionalMessage: (
<>
{canEditAdvancedSettings && (
<FormattedMessage
id="visualizations.newHeatmapChart.conditionalMessage.newLibrary"
id="visualizations.newChart.conditionalMessage.newLibrary"
defaultMessage="Switch to the old library in {link}"
values={{
link: (
<EuiLink href={advancedSettingsLink}>
<FormattedMessage
id="visualizations.newHeatmapChart.conditionalMessage.advancedSettingsLink"
id="visualizations.newChart.conditionalMessage.advancedSettingsLink"
defaultMessage="Advanced Settings."
/>
</EuiLink>
@ -47,6 +46,13 @@ export const SplitChartWarning = () => {
}}
/>
)}
</>
);
};
const ContactAdminMessage: FC<WarningMessageProps> = ({ canEditAdvancedSettings }) => {
return (
<>
{!canEditAdvancedSettings && (
<FormattedMessage
id="visualizations.legacyCharts.conditionalMessage.noPermissions"
@ -54,9 +60,64 @@ export const SplitChartWarning = () => {
/>
)}
</>
);
};
const GaugeWarningFormatMessage: FC<WarningMessageProps> = (props) => {
return (
<FormattedMessage
id="visualizations.newGaugeChart.notificationMessage"
defaultMessage="The new gauge charts library does not yet support split chart aggregation. {conditionalMessage}"
values={{
conditionalMessage: (
<>
<SwitchToOldLibraryMessage {...props} />
<ContactAdminMessage {...props} />
</>
),
}}
/>
);
};
const HeatmapWarningFormatMessage: FC<WarningMessageProps> = (props) => {
return (
<FormattedMessage
id="visualizations.newHeatmapChart.notificationMessage"
defaultMessage="The new heatmap charts library does not yet support split chart aggregation. {conditionalMessage}"
values={{
conditionalMessage: (
<>
<SwitchToOldLibraryMessage {...props} />
<ContactAdminMessage {...props} />
</>
),
}}
/>
);
};
const warningMessages = {
[CHARTS_WITHOUT_SMALL_MULTIPLES.heatmap]: HeatmapWarningFormatMessage,
[CHARTS_WITHOUT_SMALL_MULTIPLES.gauge]: GaugeWarningFormatMessage,
};
export const SplitChartWarning: FC<Props> = ({ chartType, chartConfigToken }) => {
const { services } = useKibana<VisualizeServices>();
const canEditAdvancedSettings = services.application.capabilities.advancedSettings.save;
const advancedSettingsLink = services.application.getUrlForApp('management', {
path: `/kibana/settings?query=${chartConfigToken}`,
});
const WarningMessage = warningMessages[chartType];
return (
<EuiCallOut
data-test-subj="vizSplitChartWarning"
title={
<WarningMessage
advancedSettingsLink={advancedSettingsLink}
canEditAdvancedSettings={canEditAdvancedSettings}
/>
}
iconType="alert"
color="warning"

View file

@ -17,7 +17,7 @@ import { ExperimentalVisInfo } from './experimental_vis_info';
import { useKibana } from '../../../../kibana_react/public';
import { urlFor } from '../../../../visualizations/public';
import { getUISettings } from '../../services';
import { SplitChartWarning, NEW_HEATMAP_CHARTS_LIBRARY } from './split_chart_warning';
import { SplitChartWarning } from './split_chart_warning';
import {
SavedVisInstance,
VisualizeAppState,
@ -25,6 +25,11 @@ import {
VisualizeAppStateContainer,
VisualizeEditorVisInstance,
} from '../types';
import {
CHARTS_CONFIG_TOKENS,
CHARTS_WITHOUT_SMALL_MULTIPLES,
isSplitChart as isSplitChartFn,
} from '../utils/split_chart_warning_helpers';
interface VisualizeEditorCommonProps {
visInstance?: VisualizeEditorVisInstance;
@ -110,8 +115,17 @@ export const VisualizeEditorCommon = ({
return null;
}, [visInstance?.savedVis, services, visInstance?.vis?.type.title]);
// Adds a notification for split chart on the new implementation as it is not supported yet
const isSplitChart = visInstance?.vis?.data?.aggs?.aggs.some((agg) => agg.schema === 'split');
const hasHeatmapLegacyhartsEnabled = getUISettings().get(NEW_HEATMAP_CHARTS_LIBRARY);
const chartName = visInstance?.vis.type.name;
const isSplitChart = isSplitChartFn(chartName, visInstance?.vis?.data?.aggs);
const chartsWithoutSmallMultiples: string[] = Object.values(CHARTS_WITHOUT_SMALL_MULTIPLES);
const chartNeedsWarning = chartName ? chartsWithoutSmallMultiples.includes(chartName) : false;
const chartToken =
chartName && chartNeedsWarning
? CHARTS_CONFIG_TOKENS[chartName as CHARTS_WITHOUT_SMALL_MULTIPLES]
: undefined;
const hasLegacyChartsEnabled = chartToken ? getUISettings().get(chartToken) : true;
return (
<div className={`app-container visEditor visEditor--${visInstance?.vis.type.name}`}>
@ -134,9 +148,12 @@ export const VisualizeEditorCommon = ({
/>
)}
{visInstance?.vis?.type?.stage === 'experimental' && <ExperimentalVisInfo />}
{!hasHeatmapLegacyhartsEnabled &&
isSplitChart &&
visInstance?.vis.type.name === 'heatmap' && <SplitChartWarning />}
{!hasLegacyChartsEnabled && isSplitChart && chartNeedsWarning && chartToken && chartName && (
<SplitChartWarning
chartType={chartName as CHARTS_WITHOUT_SMALL_MULTIPLES}
chartConfigToken={chartToken}
/>
)}
{visInstance?.vis?.type?.getInfoMessage?.(visInstance.vis)}
{getLegacyUrlConflictCallout()}
{visInstance && (

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export const NEW_HEATMAP_CHARTS_LIBRARY = 'visualization:visualize:legacyHeatmapChartsLibrary';
export const NEW_GAUGE_CHARTS_LIBRARY = 'visualization:visualize:legacyGaugeChartsLibrary';

View file

@ -0,0 +1,34 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { $Values } from '@kbn/utility-types';
import { AggConfigs } from '../../../../data/common';
import { NEW_HEATMAP_CHARTS_LIBRARY, NEW_GAUGE_CHARTS_LIBRARY } from '../constants';
export const CHARTS_WITHOUT_SMALL_MULTIPLES = {
heatmap: 'heatmap',
gauge: 'gauge',
} as const;
export type CHARTS_WITHOUT_SMALL_MULTIPLES = $Values<typeof CHARTS_WITHOUT_SMALL_MULTIPLES>;
export const CHARTS_CONFIG_TOKENS = {
[CHARTS_WITHOUT_SMALL_MULTIPLES.heatmap]: NEW_HEATMAP_CHARTS_LIBRARY,
[CHARTS_WITHOUT_SMALL_MULTIPLES.gauge]: NEW_GAUGE_CHARTS_LIBRARY,
} as const;
export const isSplitChart = (chartType: string | undefined, aggs?: AggConfigs) => {
const defaultIsSplitChart = () => aggs?.aggs.some((agg) => agg.schema === 'split');
const knownCheckers = {
[CHARTS_WITHOUT_SMALL_MULTIPLES.heatmap]: defaultIsSplitChart,
[CHARTS_WITHOUT_SMALL_MULTIPLES.gauge]: () => aggs?.aggs.some((agg) => agg.schema === 'group'),
};
return (knownCheckers[chartType as CHARTS_WITHOUT_SMALL_MULTIPLES] ?? defaultIsSplitChart)();
};

View file

@ -6285,33 +6285,33 @@
"visTypeVislib.advancedSettings.visualization.heatmap.maxBucketsText": "1つのデータソースが返せるバケットの最大数です。値が大きいとブラウザのレンダリング速度が下がる可能性があります。",
"visTypeVislib.advancedSettings.visualization.heatmap.maxBucketsTitle": "ヒートマップの最大バケット数",
"visTypeVislib.aggResponse.allDocsTitle": "すべてのドキュメント",
"visTypeVislib.controls.gaugeOptions.alignmentLabel": "アラインメント",
"visTypeVislib.controls.gaugeOptions.autoExtendRangeLabel": "範囲を自動拡張",
"visTypeVislib.controls.gaugeOptions.extendRangeTooltip": "範囲をデータの最高値に広げます。",
"visTypeVislib.controls.gaugeOptions.gaugeTypeLabel": "ゲージタイプ",
"visTypeVislib.controls.gaugeOptions.labelsTitle": "ラベル",
"visTypeVislib.controls.gaugeOptions.rangesTitle": "範囲",
"visTypeVislib.controls.gaugeOptions.showLabelsLabel": "ラベルを表示",
"visTypeVislib.controls.gaugeOptions.showLegendLabel": "凡例を表示",
"visTypeVislib.controls.gaugeOptions.showOutline": "アウトラインを表示",
"visTypeVislib.controls.gaugeOptions.showScaleLabel": "縮尺を表示",
"visTypeVislib.controls.gaugeOptions.styleTitle": "スタイル",
"visTypeVislib.controls.gaugeOptions.subTextLabel": "サブラベル",
"visTypeGauge.controls.gaugeOptions.alignmentLabel": "アラインメント",
"visTypeGauge.controls.gaugeOptions.autoExtendRangeLabel": "範囲を自動拡張",
"visTypeGauge.controls.gaugeOptions.extendRangeTooltip": "範囲をデータの最高値に広げます。",
"visTypeGauge.controls.gaugeOptions.gaugeTypeLabel": "ゲージタイプ",
"visTypeGauge.controls.gaugeOptions.labelsTitle": "ラベル",
"visTypeGauge.controls.gaugeOptions.rangesTitle": "範囲",
"visTypeGauge.controls.gaugeOptions.showLabelsLabel": "ラベルを表示",
"visTypeGauge.controls.gaugeOptions.showLegendLabel": "凡例を表示",
"visTypeGauge.controls.gaugeOptions.showOutline": "アウトラインを表示",
"visTypeGauge.controls.gaugeOptions.showScaleLabel": "縮尺を表示",
"visTypeGauge.controls.gaugeOptions.styleTitle": "スタイル",
"visTypeGauge.controls.gaugeOptions.subTextLabel": "サブラベル",
"visTypeVislib.functions.pie.help": "パイビジュアライゼーション",
"visTypeVislib.functions.vislib.help": "Vislib ビジュアライゼーション",
"visTypeVislib.gauge.alignmentAutomaticTitle": "自動",
"visTypeVislib.gauge.alignmentHorizontalTitle": "横",
"visTypeVislib.gauge.alignmentVerticalTitle": "縦",
"visTypeVislib.gauge.gaugeDescription": "メトリックのステータスを示します。",
"visTypeVislib.gauge.gaugeTitle": "ゲージ",
"visTypeVislib.gauge.gaugeTypes.arcText": "弧形",
"visTypeVislib.gauge.gaugeTypes.circleText": "円",
"visTypeVislib.gauge.groupTitle": "グループを分割",
"visTypeVislib.gauge.metricTitle": "メトリック",
"visTypeVislib.goal.goalDescription": "メトリックがどのように目標まで進むのかを追跡します。",
"visTypeVislib.goal.goalTitle": "ゴール",
"visTypeVislib.goal.groupTitle": "グループを分割",
"visTypeVislib.goal.metricTitle": "メトリック",
"visTypeGauge.gauge.alignmentAutomaticTitle": "自動",
"visTypeGauge.gauge.alignmentHorizontalTitle": "横",
"visTypeGauge.gauge.alignmentVerticalTitle": "縦",
"visTypeGauge.gauge.gaugeDescription": "メトリックのステータスを示します。",
"visTypeGauge.gauge.gaugeTitle": "ゲージ",
"visTypeGauge.gauge.gaugeTypes.arcText": "弧形",
"visTypeGauge.gauge.gaugeTypes.circleText": "円",
"visTypeGauge.gauge.groupTitle": "グループを分割",
"visTypeGauge.gauge.metricTitle": "メトリック",
"visTypeGauge.goal.goalDescription": "メトリックがどのように目標まで進むのかを追跡します。",
"visTypeGauge.goal.goalTitle": "ゴール",
"visTypeGauge.goal.groupTitle": "グループを分割",
"visTypeGauge.goal.metricTitle": "メトリック",
"visTypeVislib.vislib.errors.noResultsFoundTitle": "結果が見つかりませんでした",
"visTypeVislib.vislib.heatmap.maxBucketsText": "定義された数列が多すぎます({nr})。構成されている最大値は {max} です。",
"visTypeVislib.vislib.legend.filterForValueButtonAriaLabel": "値 {legendDataLabel} でフィルタリング",
@ -6587,8 +6587,8 @@
"visualizations.listing.table.titleColumnName": "タイトル",
"visualizations.listing.table.typeColumnName": "型",
"visualizations.listingPageTitle": "Visualizeライブラリ",
"visualizations.newHeatmapChart.conditionalMessage.advancedSettingsLink": "高度な設定",
"visualizations.newHeatmapChart.conditionalMessage.newLibrary": "{link}で古いライブラリに切り替える",
"visualizations.newChart.conditionalMessage.advancedSettingsLink": "高度な設定",
"visualizations.newChart.conditionalMessage.newLibrary": "{link}で古いライブラリに切り替える",
"visualizations.newHeatmapChart.notificationMessage": "新しいヒートマップグラフライブラリはまだ分割グラフアグリゲーションをサポートしていません。{conditionalMessage}",
"visualizations.newVisWizard.aggBasedGroupDescription": "クラシック Visualize ライブラリを使用して、アグリゲーションに基づいてグラフを作成します。",
"visualizations.newVisWizard.aggBasedGroupTitle": "アグリゲーションに基づく",

View file

@ -6296,33 +6296,33 @@
"visTypeVislib.advancedSettings.visualization.heatmap.maxBucketsText": "单个数据源可以返回的最大存储桶数目。较高的数目可能对浏览器呈现性能有负面影响",
"visTypeVislib.advancedSettings.visualization.heatmap.maxBucketsTitle": "热图最大存储桶数",
"visTypeVislib.aggResponse.allDocsTitle": "所有文档",
"visTypeVislib.controls.gaugeOptions.alignmentLabel": "对齐方式",
"visTypeVislib.controls.gaugeOptions.autoExtendRangeLabel": "自动扩展范围",
"visTypeVislib.controls.gaugeOptions.extendRangeTooltip": "将数据范围扩展到数据中的最大值。",
"visTypeVislib.controls.gaugeOptions.gaugeTypeLabel": "仪表类型",
"visTypeVislib.controls.gaugeOptions.labelsTitle": "标签",
"visTypeVislib.controls.gaugeOptions.rangesTitle": "范围",
"visTypeVislib.controls.gaugeOptions.showLabelsLabel": "显示标签",
"visTypeVislib.controls.gaugeOptions.showLegendLabel": "显示图例",
"visTypeVislib.controls.gaugeOptions.showOutline": "显示轮廓",
"visTypeVislib.controls.gaugeOptions.showScaleLabel": "显示比例",
"visTypeVislib.controls.gaugeOptions.styleTitle": "样式",
"visTypeVislib.controls.gaugeOptions.subTextLabel": "子标签",
"visTypeGauge.controls.gaugeOptions.alignmentLabel": "对齐方式",
"visTypeGauge.controls.gaugeOptions.autoExtendRangeLabel": "自动扩展范围",
"visTypeGauge.controls.gaugeOptions.extendRangeTooltip": "将数据范围扩展到数据中的最大值。",
"visTypeGauge.controls.gaugeOptions.gaugeTypeLabel": "仪表类型",
"visTypeGauge.controls.gaugeOptions.labelsTitle": "标签",
"visTypeGauge.controls.gaugeOptions.rangesTitle": "范围",
"visTypeGauge.controls.gaugeOptions.showLabelsLabel": "显示标签",
"visTypeGauge.controls.gaugeOptions.showLegendLabel": "显示图例",
"visTypeGauge.controls.gaugeOptions.showOutline": "显示轮廓",
"visTypeGauge.controls.gaugeOptions.showScaleLabel": "显示比例",
"visTypeGauge.controls.gaugeOptions.styleTitle": "样式",
"visTypeGauge.controls.gaugeOptions.subTextLabel": "子标签",
"visTypeVislib.functions.pie.help": "饼图可视化",
"visTypeVislib.functions.vislib.help": "Vislib 可视化",
"visTypeVislib.gauge.alignmentAutomaticTitle": "自动",
"visTypeVislib.gauge.alignmentHorizontalTitle": "水平",
"visTypeVislib.gauge.alignmentVerticalTitle": "垂直",
"visTypeVislib.gauge.gaugeDescription": "显示指标的状态。",
"visTypeVislib.gauge.gaugeTitle": "仪表盘",
"visTypeVislib.gauge.gaugeTypes.arcText": "弧形",
"visTypeVislib.gauge.gaugeTypes.circleText": "圆形",
"visTypeVislib.gauge.groupTitle": "拆分组",
"visTypeVislib.gauge.metricTitle": "指标",
"visTypeVislib.goal.goalDescription": "跟踪指标如何达到目标。",
"visTypeVislib.goal.goalTitle": "目标图",
"visTypeVislib.goal.groupTitle": "拆分组",
"visTypeVislib.goal.metricTitle": "指标",
"visTypeGauge.gauge.alignmentAutomaticTitle": "自动",
"visTypeGauge.gauge.alignmentHorizontalTitle": "水平",
"visTypeGauge.gauge.alignmentVerticalTitle": "垂直",
"visTypeGauge.gauge.gaugeDescription": "显示指标的状态。",
"visTypeGauge.gauge.gaugeTitle": "仪表盘",
"visTypeGauge.gauge.gaugeTypes.arcText": "弧形",
"visTypeGauge.gauge.gaugeTypes.circleText": "圆形",
"visTypeGauge.gauge.groupTitle": "拆分组",
"visTypeGauge.gauge.metricTitle": "指标",
"visTypeGauge.goal.goalDescription": "跟踪指标如何达到目标。",
"visTypeGauge.goal.goalTitle": "目标图",
"visTypeGauge.goal.groupTitle": "拆分组",
"visTypeGauge.goal.metricTitle": "指标",
"visTypeVislib.vislib.errors.noResultsFoundTitle": "找不到结果",
"visTypeVislib.vislib.heatmap.maxBucketsText": "定义了过多的序列 ({nr})。配置的最大值为 {max}。",
"visTypeVislib.vislib.legend.filterForValueButtonAriaLabel": "筛留值 {legendDataLabel}",
@ -6598,8 +6598,8 @@
"visualizations.listing.table.titleColumnName": "标题",
"visualizations.listing.table.typeColumnName": "类型",
"visualizations.listingPageTitle": "Visualize 库",
"visualizations.newHeatmapChart.conditionalMessage.advancedSettingsLink": "免费的 API 密钥。",
"visualizations.newHeatmapChart.conditionalMessage.newLibrary": "切换到{link}中的旧库",
"visualizations.newChart.conditionalMessage.advancedSettingsLink": "免费的 API 密钥。",
"visualizations.newChart.conditionalMessage.newLibrary": "切换到{link}中的旧库",
"visualizations.newHeatmapChart.notificationMessage": "新的热图图表库尚不支持拆分图表聚合。{conditionalMessage}",
"visualizations.newVisWizard.aggBasedGroupDescription": "使用我们的经典可视化库,基于聚合创建图表。",
"visualizations.newVisWizard.aggBasedGroupTitle": "基于聚合",