mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Vislib] Removes the old implementation of the pie (#146990)
## Summary Closes https://github.com/elastic/kibana/issues/111246 Removes the implementation of the vislib pie. Specifically: - Removes the `visualization:visualize:legacyPieChartsLibrary` advanced setting which was used as a fallback to vislib pie, - Cleanups the vislib code from the pie
This commit is contained in:
parent
e771fc8e9f
commit
405eb89f35
70 changed files with 55 additions and 5918 deletions
|
@ -76,7 +76,6 @@ enabled:
|
|||
- test/functional/apps/dashboard/group3/config.ts
|
||||
- test/functional/apps/dashboard/group4/config.ts
|
||||
- test/functional/apps/dashboard/group5/config.ts
|
||||
- test/functional/apps/dashboard/group6/config.ts
|
||||
- test/functional/apps/discover/ccs_compatibility/config.ts
|
||||
- test/functional/apps/discover/classic/config.ts
|
||||
- test/functional/apps/discover/embeddable/config.ts
|
||||
|
|
|
@ -540,10 +540,6 @@ Enables the legacy time axis for charts in Lens, Discover, Visualize and TSVB
|
|||
[[visualization-heatmap-maxbuckets]]`visualization:heatmap:maxBuckets`::
|
||||
The maximum number of buckets a datasource can return. High numbers can have a negative impact on your browser rendering performance.
|
||||
|
||||
[[visualization-visualize-pieChartslibrary]]`visualization:visualize:legacyPieChartsLibrary`::
|
||||
**The legacy pie charts are deprecated and will not be supported in a future version.**
|
||||
The visualize editor uses new pie charts with improved performance, color palettes, label positioning, and more. Enable this option if you prefer to use the legacy charts library.
|
||||
|
||||
[[visualization-visualize-heatmapChartslibrary]]`visualization:visualize:legacyHeatmapChartsLibrary`::
|
||||
Disable this option if you prefer to use the new heatmap charts with improved performance, legend settings, and more..
|
||||
|
||||
|
|
|
@ -394,10 +394,6 @@ export const stackManagementSchema: MakeSchemaFrom<UsageStats> = {
|
|||
type: 'boolean',
|
||||
_meta: { description: 'Non-default value of setting.' },
|
||||
},
|
||||
'visualization:visualize:legacyPieChartsLibrary': {
|
||||
type: 'boolean',
|
||||
_meta: { description: 'Non-default value of setting.' },
|
||||
},
|
||||
'visualization:visualize:legacyHeatmapChartsLibrary': {
|
||||
type: 'boolean',
|
||||
_meta: { description: 'Non-default value of setting.' },
|
||||
|
|
|
@ -27,7 +27,6 @@ export interface UsageStats {
|
|||
'autocomplete:useTimeRange': boolean;
|
||||
'autocomplete:valueSuggestionMethod': string;
|
||||
'search:timeout': number;
|
||||
'visualization:visualize:legacyPieChartsLibrary': boolean;
|
||||
'visualization:visualize:legacyHeatmapChartsLibrary': boolean;
|
||||
'doc_table:legacy': boolean;
|
||||
'discover:modifyColumnsOnSwitch': boolean;
|
||||
|
|
|
@ -8844,12 +8844,6 @@
|
|||
"description": "Non-default value of setting."
|
||||
}
|
||||
},
|
||||
"visualization:visualize:legacyPieChartsLibrary": {
|
||||
"type": "boolean",
|
||||
"_meta": {
|
||||
"description": "Non-default value of setting."
|
||||
}
|
||||
},
|
||||
"visualization:visualize:legacyHeatmapChartsLibrary": {
|
||||
"type": "boolean",
|
||||
"_meta": {
|
||||
|
|
|
@ -7,4 +7,3 @@
|
|||
*/
|
||||
|
||||
export const DEFAULT_PERCENT_DECIMALS = 2;
|
||||
export const LEGACY_PIE_CHARTS_LIBRARY = 'visualization:visualize:legacyPieChartsLibrary';
|
||||
|
|
|
@ -12,7 +12,6 @@ import { ChartsPluginSetup } from '@kbn/charts-plugin/public';
|
|||
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
|
||||
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import { LEGACY_PIE_CHARTS_LIBRARY } from '../common';
|
||||
import { pieVisType } from './vis_type';
|
||||
import { setDataViewsStart } from './services';
|
||||
|
||||
|
@ -49,14 +48,12 @@ export class VisTypePiePlugin {
|
|||
core: CoreSetup<VisTypePiePluginStartDependencies>,
|
||||
{ visualizations, charts, usageCollection }: VisTypePieSetupDependencies
|
||||
) {
|
||||
if (!core.uiSettings.get(LEGACY_PIE_CHARTS_LIBRARY, false)) {
|
||||
visualizations.createBaseVisualization(
|
||||
pieVisType({
|
||||
showElasticChartsOptions: true,
|
||||
palettes: charts.palettes,
|
||||
})
|
||||
);
|
||||
}
|
||||
visualizations.createBaseVisualization(
|
||||
pieVisType({
|
||||
showElasticChartsOptions: true,
|
||||
palettes: charts.palettes,
|
||||
})
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
@ -6,47 +6,10 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { CoreSetup, Plugin, UiSettingsParams } from '@kbn/core/server';
|
||||
|
||||
import { LEGACY_PIE_CHARTS_LIBRARY } from '../common';
|
||||
|
||||
export const getUiSettingsConfig: () => Record<string, UiSettingsParams<boolean>> = () => ({
|
||||
// TODO: Remove this when vislib pie is removed
|
||||
// https://github.com/elastic/kibana/issues/111246
|
||||
[LEGACY_PIE_CHARTS_LIBRARY]: {
|
||||
name: i18n.translate('visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.name', {
|
||||
defaultMessage: 'Pie legacy charts library',
|
||||
}),
|
||||
requiresPageReload: true,
|
||||
value: false,
|
||||
description: i18n.translate(
|
||||
'visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.description',
|
||||
{
|
||||
defaultMessage: 'Enables legacy charts library for pie charts in visualize.',
|
||||
}
|
||||
),
|
||||
deprecation: {
|
||||
message: i18n.translate(
|
||||
'visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.deprecation',
|
||||
{
|
||||
defaultMessage:
|
||||
'The legacy charts library for pie in visualize is deprecated and will not be supported in a future version.',
|
||||
}
|
||||
),
|
||||
docLinksKey: 'visualizationSettings',
|
||||
},
|
||||
category: ['visualization'],
|
||||
schema: schema.boolean(),
|
||||
},
|
||||
});
|
||||
import { CoreSetup, Plugin } from '@kbn/core/server';
|
||||
|
||||
export class VisTypePieServerPlugin implements Plugin<object, object> {
|
||||
public setup(core: CoreSetup) {
|
||||
core.uiSettings.register(getUiSettingsConfig());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
"ui": true,
|
||||
"requiredPlugins": ["charts", "data", "expressions", "visualizations", "fieldFormats"],
|
||||
"optionalPlugins": ["usageCollection"],
|
||||
"requiredBundles": ["kibanaUtils", "visTypePie", "visTypeHeatmap", "visTypeGauge", "kibanaReact"],
|
||||
"requiredBundles": ["kibanaUtils", "visTypeHeatmap", "visTypeGauge", "kibanaReact"],
|
||||
"owner": {
|
||||
"name": "Vis Editors",
|
||||
"githubTeam": "kibana-visualizations"
|
||||
},
|
||||
"description": "Contains the vislib visualizations. These are the classical area/line/bar, pie, gauge/goal and heatmap charts. We want to replace them with elastic-charts."
|
||||
"description": "Contains the vislib visualizations. These are the classical area/line/bar, gauge/goal and heatmap charts. We want to replace them with elastic-charts."
|
||||
}
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`interpreter/functions#pie returns an object with the correct structure 1`] = `
|
||||
Object {
|
||||
"as": "vislib_vis",
|
||||
"type": "render",
|
||||
"value": Object {
|
||||
"visConfig": Object {
|
||||
"addTooltip": true,
|
||||
"dimensions": Object {
|
||||
"metric": Object {
|
||||
"accessor": 0,
|
||||
"aggType": "count",
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
},
|
||||
"params": Object {},
|
||||
},
|
||||
},
|
||||
"isDonut": true,
|
||||
"labels": Object {
|
||||
"last_level": true,
|
||||
"show": false,
|
||||
"truncate": 100,
|
||||
"values": true,
|
||||
},
|
||||
"legendDisplay": "show",
|
||||
"legendPosition": "right",
|
||||
"type": "pie",
|
||||
},
|
||||
"visData": Object {
|
||||
"hits": 1,
|
||||
"names": Array [
|
||||
"Count",
|
||||
],
|
||||
"raw": Object {
|
||||
"columns": Array [],
|
||||
"rows": Array [],
|
||||
},
|
||||
"slices": Object {
|
||||
"children": Array [],
|
||||
},
|
||||
"tooltipFormatter": Object {
|
||||
"id": "number",
|
||||
},
|
||||
},
|
||||
"visType": "pie",
|
||||
},
|
||||
}
|
||||
`;
|
|
@ -1,19 +0,0 @@
|
|||
// 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,\\"legendDisplay\\":\\"show\\",\\"legendPosition\\":\\"right\\",\\"legendSize\\":\\"large\\",\\"isDonut\\":true,\\"labels\\":{\\"show\\":true,\\"values\\":true,\\"last_level\\":true,\\"truncate\\":100},\\"dimensions\\":{\\"metric\\":{\\"accessor\\":0,\\"format\\":{\\"id\\":\\"number\\"},\\"params\\":{},\\"aggType\\":\\"count\\",\\"aggId\\":\\"1\\",\\"aggParams\\":{}},\\"buckets\\":[{\\"accessor\\":1,\\"format\\":{\\"id\\":\\"terms\\",\\"params\\":{\\"id\\":\\"string\\",\\"otherBucketLabel\\":\\"Other\\",\\"missingBucketLabel\\":\\"Missing\\",\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{},\\"aggType\\":\\"terms\\",\\"aggId\\":\\"2\\",\\"aggParams\\":{\\"field\\":\\"Carrier\\",\\"orderBy\\":\\"1\\",\\"order\\":\\"desc\\",\\"size\\":5,\\"otherBucket\\":false,\\"otherBucketLabel\\":\\"Other\\",\\"missingBucket\\":false,\\"missingBucketLabel\\":\\"Missing\\"}}]}}",
|
||||
],
|
||||
},
|
||||
"getArgument": [Function],
|
||||
"name": "vislib_pie_vis",
|
||||
"removeArgument": [Function],
|
||||
"replaceArgument": [Function],
|
||||
"toAst": [Function],
|
||||
"toString": [Function],
|
||||
"type": "expression_function_builder",
|
||||
}
|
||||
`;
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* 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 { pieVisType } from '@kbn/vis-type-pie-plugin/public';
|
||||
import { VisTypeDefinition } from '@kbn/visualizations-plugin/public';
|
||||
import { CommonVislibParams } from './types';
|
||||
import { toExpressionAst } from './to_ast_pie';
|
||||
|
||||
export enum LegendDisplay {
|
||||
SHOW = 'show',
|
||||
HIDE = 'hide',
|
||||
DEFAULT = 'default',
|
||||
}
|
||||
|
||||
export type PieVisParams = Omit<CommonVislibParams, 'addLegend'> & {
|
||||
type: 'pie';
|
||||
isDonut: boolean;
|
||||
labels: {
|
||||
show: boolean;
|
||||
values: boolean;
|
||||
last_level: boolean;
|
||||
truncate: number | null;
|
||||
};
|
||||
legendDisplay: LegendDisplay;
|
||||
};
|
||||
|
||||
export const pieVisTypeDefinition = {
|
||||
...pieVisType({}),
|
||||
toExpressionAst,
|
||||
} as VisTypeDefinition<PieVisParams>;
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* 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 type { Datatable } from '@kbn/expressions-plugin/common';
|
||||
import { functionWrapper } from '@kbn/expressions-plugin/common/expression_functions/specs/tests/utils';
|
||||
import { createPieVisFn } from './pie_fn';
|
||||
// @ts-ignore
|
||||
import { vislibSlicesResponseHandler } from './vislib/response_handler';
|
||||
|
||||
jest.mock('./vislib/response_handler', () => ({
|
||||
vislibSlicesResponseHandler: jest.fn().mockReturnValue({
|
||||
hits: 1,
|
||||
names: ['Count'],
|
||||
raw: {
|
||||
columns: [],
|
||||
rows: [],
|
||||
},
|
||||
slices: {
|
||||
children: [],
|
||||
},
|
||||
tooltipFormatter: {
|
||||
id: 'number',
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('interpreter/functions#pie', () => {
|
||||
const fn = functionWrapper(createPieVisFn());
|
||||
const context = {
|
||||
type: 'datatable',
|
||||
rows: [{ 'col-0-1': 0 }],
|
||||
columns: [{ id: 'col-0-1', name: 'Count' }],
|
||||
} as unknown as Datatable;
|
||||
const visConfig = {
|
||||
type: 'pie',
|
||||
addTooltip: true,
|
||||
legendDisplay: 'show',
|
||||
legendPosition: 'right',
|
||||
isDonut: true,
|
||||
labels: {
|
||||
show: false,
|
||||
values: true,
|
||||
last_level: true,
|
||||
truncate: 100,
|
||||
},
|
||||
dimensions: {
|
||||
metric: {
|
||||
accessor: 0,
|
||||
format: {
|
||||
id: 'number',
|
||||
},
|
||||
params: {},
|
||||
aggType: 'count',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('returns an object with the correct structure', async () => {
|
||||
const actual = await fn(context, { visConfig: JSON.stringify(visConfig) });
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('calls response handler with correct values', async () => {
|
||||
await fn(context, { visConfig: JSON.stringify(visConfig) });
|
||||
expect(vislibSlicesResponseHandler).toHaveBeenCalledTimes(1);
|
||||
expect(vislibSlicesResponseHandler).toHaveBeenCalledWith(context, visConfig.dimensions);
|
||||
});
|
||||
});
|
|
@ -1,70 +0,0 @@
|
|||
/*
|
||||
* 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 { ExpressionFunctionDefinition, Datatable, Render } from '@kbn/expressions-plugin/public';
|
||||
|
||||
// @ts-ignore
|
||||
import { vislibSlicesResponseHandler } from './vislib/response_handler';
|
||||
import { PieVisParams } from './pie';
|
||||
import { VislibChartType } from './types';
|
||||
import { vislibVisName } from './vis_type_vislib_vis_fn';
|
||||
|
||||
export const vislibPieName = 'vislib_pie_vis';
|
||||
|
||||
interface Arguments {
|
||||
visConfig: string;
|
||||
}
|
||||
|
||||
export interface PieRenderValue {
|
||||
visType: Extract<VislibChartType, 'pie'>;
|
||||
visData: unknown;
|
||||
visConfig: PieVisParams;
|
||||
}
|
||||
|
||||
export type VisTypeVislibPieExpressionFunctionDefinition = ExpressionFunctionDefinition<
|
||||
typeof vislibPieName,
|
||||
Datatable,
|
||||
Arguments,
|
||||
Render<PieRenderValue>
|
||||
>;
|
||||
|
||||
export const createPieVisFn = (): VisTypeVislibPieExpressionFunctionDefinition => ({
|
||||
name: vislibPieName,
|
||||
type: 'render',
|
||||
inputTypes: ['datatable'],
|
||||
help: i18n.translate('visTypeVislib.functions.pie.help', {
|
||||
defaultMessage: 'Pie visualization',
|
||||
}),
|
||||
args: {
|
||||
visConfig: {
|
||||
types: ['string'],
|
||||
default: '"{}"',
|
||||
help: 'vislib pie vis config',
|
||||
},
|
||||
},
|
||||
fn(input, args, handlers) {
|
||||
const visConfig = JSON.parse(args.visConfig) as PieVisParams;
|
||||
const visData = vislibSlicesResponseHandler(input, visConfig.dimensions);
|
||||
|
||||
if (handlers?.inspectorAdapters?.tables) {
|
||||
handlers.inspectorAdapters.tables.logDatatable('default', input);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'render',
|
||||
as: vislibVisName,
|
||||
value: {
|
||||
visData,
|
||||
visConfig,
|
||||
visType: VislibChartType.Pie,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
|
@ -14,15 +14,12 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
|||
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
|
||||
import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
|
||||
|
||||
import { LEGACY_PIE_CHARTS_LIBRARY } from '@kbn/vis-type-pie-plugin/common';
|
||||
import { LEGACY_HEATMAP_CHARTS_LIBRARY } from '@kbn/vis-type-heatmap-plugin/common';
|
||||
import { LEGACY_GAUGE_CHARTS_LIBRARY } from '@kbn/vis-type-gauge-plugin/common';
|
||||
import { setUsageCollectionStart } from './services';
|
||||
import { heatmapVisTypeDefinition } from './heatmap';
|
||||
|
||||
import { createVisTypeVislibVisFn } from './vis_type_vislib_vis_fn';
|
||||
import { createPieVisFn } from './pie_fn';
|
||||
import { pieVisTypeDefinition } from './pie';
|
||||
import { setFormatService, setDataActions, setTheme } from './services';
|
||||
import { getVislibVisRenderer } from './vis_renderer';
|
||||
import { gaugeVisTypeDefinition } from './gauge';
|
||||
|
@ -60,12 +57,6 @@ export class VisTypeVislibPlugin
|
|||
expressions.registerRenderer(getVislibVisRenderer(core, charts));
|
||||
expressions.registerFunction(createVisTypeVislibVisFn());
|
||||
|
||||
if (core.uiSettings.get(LEGACY_PIE_CHARTS_LIBRARY, false)) {
|
||||
// register vislib pie chart
|
||||
visualizations.createBaseVisualization(pieVisTypeDefinition);
|
||||
expressions.registerFunction(createPieVisFn());
|
||||
}
|
||||
|
||||
if (core.uiSettings.get(LEGACY_HEATMAP_CHARTS_LIBRARY)) {
|
||||
// register vislib heatmap chart
|
||||
visualizations.createBaseVisualization(heatmapVisTypeDefinition);
|
||||
|
|
|
@ -89,7 +89,7 @@ export const toExpressionAst = async <TVisParams extends VisParams>(
|
|||
const visTypeVislib = buildExpressionFunction<VisTypeVislibExpressionFunctionDefinition>(
|
||||
vislibVisName,
|
||||
{
|
||||
type: vis.type.name as Exclude<VislibChartType, 'pie'>,
|
||||
type: vis.type.name as VislibChartType,
|
||||
visConfig: JSON.stringify({ ...visConfig, dimensions }),
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* 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 '@kbn/visualizations-plugin/public';
|
||||
import { buildExpression } from '@kbn/expressions-plugin/public';
|
||||
|
||||
import { PieVisParams } from './pie';
|
||||
import { samplePieVis } from '@kbn/vis-type-pie-plugin/public/sample_vis.test.mocks';
|
||||
import { toExpressionAst } from './to_ast_pie';
|
||||
|
||||
jest.mock('@kbn/expressions-plugin/public', () => ({
|
||||
...(jest.requireActual('@kbn/expressions-plugin/public') as any),
|
||||
buildExpression: jest.fn().mockImplementation(() => ({
|
||||
toAst: () => ({
|
||||
type: 'expression',
|
||||
chain: [],
|
||||
}),
|
||||
})),
|
||||
}));
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* 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, VisToExpressionAst } from '@kbn/visualizations-plugin/public';
|
||||
import { buildExpression, buildExpressionFunction } from '@kbn/expressions-plugin/public';
|
||||
|
||||
import { PieVisParams } from './pie';
|
||||
import { vislibPieName, VisTypeVislibPieExpressionFunctionDefinition } from './pie_fn';
|
||||
|
||||
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 visTypePie = buildExpressionFunction<VisTypeVislibPieExpressionFunctionDefinition>(
|
||||
vislibPieName,
|
||||
{
|
||||
visConfig: JSON.stringify(visConfig),
|
||||
}
|
||||
);
|
||||
|
||||
const ast = buildExpression([visTypePie]);
|
||||
|
||||
return ast.toAst();
|
||||
};
|
|
@ -37,7 +37,6 @@ export const GaugeType = Object.freeze({
|
|||
export type GaugeType = $Values<typeof GaugeType>;
|
||||
|
||||
export const VislibChartType = Object.freeze({
|
||||
Pie: 'pie' as const,
|
||||
Heatmap: 'heatmap' as const,
|
||||
Gauge: 'gauge' as const,
|
||||
Goal: 'goal' as const,
|
||||
|
|
|
@ -16,7 +16,6 @@ import { IInterpreterRenderHandlers } from '@kbn/expressions-plugin/public';
|
|||
import { VisTypeVislibCoreSetup } from './plugin';
|
||||
import { VisLegend, CUSTOM_LEGEND_VIS_TYPES } from './vislib/components/legend';
|
||||
import { BasicVislibParams } from './types';
|
||||
import { LegendDisplay, PieVisParams } from './pie';
|
||||
|
||||
const legendClassName = {
|
||||
top: 'vislib--legend-top',
|
||||
|
@ -63,7 +62,7 @@ export const createVislibVisController = (
|
|||
|
||||
async render(
|
||||
esResponse: any,
|
||||
visParams: BasicVislibParams | PieVisParams,
|
||||
visParams: BasicVislibParams,
|
||||
handlers: IInterpreterRenderHandlers,
|
||||
renderComplete: (() => void) | undefined
|
||||
): Promise<void> {
|
||||
|
@ -123,7 +122,7 @@ export const createVislibVisController = (
|
|||
|
||||
mountLegend(
|
||||
visData: unknown,
|
||||
visParams: BasicVislibParams | PieVisParams,
|
||||
visParams: BasicVislibParams,
|
||||
fireEvent: IInterpreterRenderHandlers['event'],
|
||||
uiState?: PersistedState
|
||||
) {
|
||||
|
@ -155,15 +154,8 @@ export const createVislibVisController = (
|
|||
}
|
||||
}
|
||||
|
||||
showLegend(visParams: BasicVislibParams | PieVisParams) {
|
||||
if (this.arePieVisParams(visParams)) {
|
||||
return visParams.legendDisplay === LegendDisplay.SHOW;
|
||||
}
|
||||
showLegend(visParams: BasicVislibParams) {
|
||||
return visParams.addLegend ?? false;
|
||||
}
|
||||
|
||||
arePieVisParams(visParams: BasicVislibParams | PieVisParams): visParams is PieVisParams {
|
||||
return Object.values(LegendDisplay).includes((visParams as PieVisParams).legendDisplay);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -17,7 +17,6 @@ import { ChartsPluginSetup } from '@kbn/charts-plugin/public';
|
|||
import { VisTypeVislibCoreSetup } from './plugin';
|
||||
import { VislibRenderValue, vislibVisName } from './vis_type_vislib_vis_fn';
|
||||
import { VislibChartType } from './types';
|
||||
import { PieRenderValue } from './pie_fn';
|
||||
|
||||
const VislibWrapper = lazy(() => import('./vis_wrapper'));
|
||||
|
||||
|
@ -35,7 +34,7 @@ function shouldShowNoResultsMessage(visData: any, visType: VislibChartType): boo
|
|||
export const getVislibVisRenderer: (
|
||||
core: VisTypeVislibCoreSetup,
|
||||
charts: ChartsPluginSetup
|
||||
) => ExpressionRenderDefinition<VislibRenderValue | PieRenderValue> = (core, charts) => ({
|
||||
) => ExpressionRenderDefinition<VislibRenderValue> = (core, charts) => ({
|
||||
name: vislibVisName,
|
||||
displayName: 'Vislib visualization',
|
||||
reuseDomNode: true,
|
||||
|
|
|
@ -17,12 +17,12 @@ import { BasicVislibParams, VislibChartType } from './types';
|
|||
export const vislibVisName = 'vislib_vis';
|
||||
|
||||
interface Arguments {
|
||||
type: Exclude<VislibChartType, 'pie'>;
|
||||
type: VislibChartType;
|
||||
visConfig: string;
|
||||
}
|
||||
|
||||
export interface VislibRenderValue {
|
||||
visType: Exclude<VislibChartType, 'pie'>;
|
||||
visType: VislibChartType;
|
||||
visData: unknown;
|
||||
visConfig: BasicVislibParams;
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ export const createVisTypeVislibVisFn = (): VisTypeVislibExpressionFunctionDefin
|
|||
},
|
||||
},
|
||||
fn(context, args, handlers) {
|
||||
const visType = args.type as Exclude<VislibChartType, 'pie'>;
|
||||
const visType = args.type as VislibChartType;
|
||||
const visConfig = JSON.parse(args.visConfig) as BasicVislibParams;
|
||||
const visData = vislibSeriesResponseHandler(context, visConfig.dimensions);
|
||||
|
||||
|
|
|
@ -19,12 +19,11 @@ import { METRIC_TYPE } from '@kbn/analytics';
|
|||
import { VislibRenderValue } from './vis_type_vislib_vis_fn';
|
||||
import { createVislibVisController, VislibVisController } from './vis_controller';
|
||||
import { VisTypeVislibCoreSetup } from './plugin';
|
||||
import { PieRenderValue } from './pie_fn';
|
||||
|
||||
import './index.scss';
|
||||
import { getUsageCollectionStart } from './services';
|
||||
|
||||
type VislibWrapperProps = (VislibRenderValue | PieRenderValue) & {
|
||||
type VislibWrapperProps = VislibRenderValue & {
|
||||
core: VisTypeVislibCoreSetup;
|
||||
charts: ChartsPluginSetup;
|
||||
handlers: IInterpreterRenderHandlers;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Charts supported
|
||||
|
||||
Vislib supports the gauge/goal charts from the aggregation-based visualizations. It also contains the legacy implemementation of the pie and heatmap chart (enabled by the visualization:visualize:legacyPieChartsLibrary and visualization:visualize:legacyHeatmapChartsLibrary advanced setting respectively).
|
||||
Vislib supports the gauge/goal charts from the aggregation-based visualizations. It also contains the legacy implemementation of the heatmap chart (enabled by the visualization:visualize:legacyHeatmapChartsLibrary advanced setting).
|
||||
|
||||
# General overview
|
||||
|
||||
|
@ -12,11 +12,10 @@ Vislib supports the gauge/goal charts from the aggregation-based visualizations.
|
|||
|
||||
## Visualizations
|
||||
|
||||
Each base vis type (`lib/types`) can have a different layout defined (`lib/layout`) and different building blocks (pie charts dont have axes for example)
|
||||
Each base vis type (`lib/types`) can have a different layout defined (`lib/layout`) and different building blocks
|
||||
|
||||
All base visualizations extend from `visualizations/_chart`
|
||||
|
||||
### Pie chart
|
||||
|
||||
### Map
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ import { IInterpreterRenderHandlers } from '@kbn/expressions-plugin/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 {
|
||||
|
@ -160,7 +159,7 @@ export class VisLegend extends PureComponent<VisLegendProps, VisLegendState> {
|
|||
if (!data) return [];
|
||||
data = data.columns || data.rows || [data];
|
||||
|
||||
labels = type === 'pie' ? getPieNames(data) : this.getSeriesLabels(data);
|
||||
labels = this.getSeriesLabels(data);
|
||||
}
|
||||
|
||||
this.setFilterableLabels(labels);
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
* 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 _ from 'lodash';
|
||||
|
||||
/**
|
||||
* Returns an array of names ordered by appearance in the nested array
|
||||
* of objects
|
||||
*
|
||||
* > Duplicated utilty method from vislib Data class to decouple `vislib_vis_legend` from `vislib`
|
||||
*
|
||||
* @see src/plugins/vis_types/vislib/public/vislib/lib/data.js
|
||||
*
|
||||
* @returns {Array} Array of unique names (strings)
|
||||
*/
|
||||
export function getPieNames(data: any[]): string[] {
|
||||
const names: string[] = [];
|
||||
|
||||
_.forEach(data, function (obj) {
|
||||
const columns = obj.raw ? obj.raw.columns : undefined;
|
||||
_.forEach(getNames(obj, columns), function (name) {
|
||||
names.push(name);
|
||||
});
|
||||
});
|
||||
|
||||
return _.uniqBy(names, 'label');
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens hierarchical data into an array of objects with a name and index value.
|
||||
* The indexed value determines the order of nesting in the data.
|
||||
* Returns an array with names sorted by the index value.
|
||||
*
|
||||
* @param data {Object} Chart data object
|
||||
* @param columns {Object} Contains formatter information
|
||||
* @returns {Array} Array of names (strings)
|
||||
*/
|
||||
function getNames(data: any, columns: any): string[] {
|
||||
const slices = data.slices;
|
||||
|
||||
if (slices.children) {
|
||||
const namedObj = returnNames(slices.children, 0, columns);
|
||||
|
||||
return _(namedObj)
|
||||
.sortBy(function (obj) {
|
||||
return obj.index;
|
||||
})
|
||||
.uniqBy(function (d) {
|
||||
return d.label;
|
||||
})
|
||||
.value();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for getNames
|
||||
* Returns an array of objects with a name (key) value and an index value.
|
||||
* The index value allows us to sort the names in the correct nested order.
|
||||
*
|
||||
* @param array {Array} Array of data objects
|
||||
* @param index {Number} Number of times the object is nested
|
||||
* @param columns {Object} Contains name formatter information
|
||||
* @returns {Array} Array of labels (strings)
|
||||
*/
|
||||
function returnNames(array: any[], index: number, columns: any): any[] {
|
||||
const names: any[] = [];
|
||||
|
||||
_.forEach(array, function (obj) {
|
||||
names.push({
|
||||
label: obj.name,
|
||||
values: [obj.rawData],
|
||||
index,
|
||||
});
|
||||
|
||||
if (obj.children) {
|
||||
const plusIndex = index + 1;
|
||||
|
||||
_.forEach(returnNames(obj.children, plusIndex, columns), function (namedObj) {
|
||||
names.push(namedObj);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return names;
|
||||
}
|
|
@ -33,12 +33,6 @@ export class ContainerTooSmall extends VislibError {
|
|||
}
|
||||
}
|
||||
|
||||
export class PieContainsAllZeros extends VislibError {
|
||||
constructor() {
|
||||
super('No results displayed because all values equal 0.');
|
||||
}
|
||||
}
|
||||
|
||||
export class NoResults extends VislibError {
|
||||
constructor() {
|
||||
super(
|
||||
|
|
|
@ -1,377 +0,0 @@
|
|||
/*
|
||||
* 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 type { Dimensions, Dimension } from '@kbn/vis-type-pie-plugin/public';
|
||||
import { buildHierarchicalData } from './build_hierarchical_data';
|
||||
import { Table, TableParent } from '../../types';
|
||||
|
||||
function tableVisResponseHandler(table: Table, dimensions: Dimensions) {
|
||||
const converted: {
|
||||
tables: Array<TableParent | Table>;
|
||||
} = {
|
||||
tables: [],
|
||||
};
|
||||
|
||||
const split = dimensions.splitColumn || dimensions.splitRow;
|
||||
|
||||
if (split) {
|
||||
const splitColumnIndex = split[0].accessor;
|
||||
const splitColumn = table.columns[splitColumnIndex];
|
||||
const splitMap: { [key: string]: number } = {};
|
||||
let splitIndex = 0;
|
||||
|
||||
table.rows.forEach((row, rowIndex) => {
|
||||
const splitValue = row[splitColumn.id] as string;
|
||||
|
||||
if (!splitMap.hasOwnProperty(splitValue)) {
|
||||
splitMap[splitValue] = splitIndex++;
|
||||
const tableGroup = {
|
||||
$parent: converted,
|
||||
title: `splitValue: ${splitColumn.name}`,
|
||||
name: splitColumn.name,
|
||||
key: splitValue,
|
||||
column: splitColumnIndex,
|
||||
row: rowIndex,
|
||||
table,
|
||||
tables: [] as Table[],
|
||||
} as any;
|
||||
|
||||
tableGroup.tables.push({
|
||||
$parent: tableGroup,
|
||||
columns: table.columns,
|
||||
rows: [],
|
||||
});
|
||||
|
||||
converted.tables.push(tableGroup);
|
||||
}
|
||||
|
||||
const tableIndex = splitMap[splitValue];
|
||||
(converted.tables[tableIndex] as TableParent).tables![0].rows.push(row);
|
||||
});
|
||||
} else {
|
||||
converted.tables.push({
|
||||
columns: table.columns,
|
||||
rows: table.rows,
|
||||
} as Table);
|
||||
}
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
jest.mock('../../../services', () => ({
|
||||
getFormatService: jest.fn(() => ({
|
||||
deserialize: () => ({
|
||||
convert: jest.fn((v) => JSON.stringify(v)),
|
||||
}),
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('buildHierarchicalData convertTable', () => {
|
||||
describe('metric only', () => {
|
||||
let dimensions: Dimensions;
|
||||
let table: Table;
|
||||
|
||||
beforeEach(() => {
|
||||
const tabifyResponse = {
|
||||
columns: [{ id: 'col-0-agg_1', name: 'Average bytes' }],
|
||||
rows: [{ 'col-0-agg_1': 412032 }],
|
||||
};
|
||||
dimensions = {
|
||||
metric: { accessor: 0 } as Dimension,
|
||||
};
|
||||
|
||||
const tableGroup = tableVisResponseHandler(tabifyResponse, dimensions);
|
||||
table = tableGroup.tables[0] as Table;
|
||||
});
|
||||
|
||||
it('should set the slices with one child to a consistent label', () => {
|
||||
const results = buildHierarchicalData(table, dimensions);
|
||||
const checkLabel = 'Average bytes';
|
||||
expect(results).toHaveProperty('names');
|
||||
expect(results.names).toEqual([checkLabel]);
|
||||
expect(results).toHaveProperty('raw');
|
||||
expect(results.raw).toHaveProperty('rows');
|
||||
expect(results.raw.rows).toHaveLength(1);
|
||||
expect(results).toHaveProperty('slices');
|
||||
expect(results.slices).toHaveProperty('children');
|
||||
expect(results.slices.children).toHaveLength(1);
|
||||
expect(results.slices.children[0]).toHaveProperty('name', checkLabel);
|
||||
expect(results.slices.children[0]).toHaveProperty('size', 412032);
|
||||
});
|
||||
});
|
||||
|
||||
describe('threeTermBuckets', () => {
|
||||
let dimensions: Dimensions;
|
||||
let tables: TableParent[];
|
||||
|
||||
beforeEach(async () => {
|
||||
const tabifyResponse = {
|
||||
columns: [
|
||||
{ id: 'col-0-agg_2', name: 'extension: Descending' },
|
||||
{ id: 'col-1-agg_1', name: 'Average bytes' },
|
||||
{ id: 'col-2-agg_3', name: 'geo.src: Descending' },
|
||||
{ id: 'col-3-agg_1', name: 'Average bytes' },
|
||||
{ id: 'col-4-agg_4', name: 'machine.os: Descending' },
|
||||
{ id: 'col-5-agg_1', name: 'Average bytes' },
|
||||
],
|
||||
rows: [
|
||||
{
|
||||
'col-0-agg_2': 'png',
|
||||
'col-2-agg_3': 'IT',
|
||||
'col-4-agg_4': 'win',
|
||||
'col-1-agg_1': 412032,
|
||||
'col-3-agg_1': 9299,
|
||||
'col-5-agg_1': 0,
|
||||
},
|
||||
{
|
||||
'col-0-agg_2': 'png',
|
||||
'col-2-agg_3': 'IT',
|
||||
'col-4-agg_4': 'mac',
|
||||
'col-1-agg_1': 412032,
|
||||
'col-3-agg_1': 9299,
|
||||
'col-5-agg_1': 9299,
|
||||
},
|
||||
{
|
||||
'col-0-agg_2': 'png',
|
||||
'col-2-agg_3': 'US',
|
||||
'col-4-agg_4': 'linux',
|
||||
'col-1-agg_1': 412032,
|
||||
'col-3-agg_1': 8293,
|
||||
'col-5-agg_1': 3992,
|
||||
},
|
||||
{
|
||||
'col-0-agg_2': 'png',
|
||||
'col-2-agg_3': 'US',
|
||||
'col-4-agg_4': 'mac',
|
||||
'col-1-agg_1': 412032,
|
||||
'col-3-agg_1': 8293,
|
||||
'col-5-agg_1': 3029,
|
||||
},
|
||||
{
|
||||
'col-0-agg_2': 'css',
|
||||
'col-2-agg_3': 'MX',
|
||||
'col-4-agg_4': 'win',
|
||||
'col-1-agg_1': 412032,
|
||||
'col-3-agg_1': 9299,
|
||||
'col-5-agg_1': 4992,
|
||||
},
|
||||
{
|
||||
'col-0-agg_2': 'css',
|
||||
'col-2-agg_3': 'MX',
|
||||
'col-4-agg_4': 'mac',
|
||||
'col-1-agg_1': 412032,
|
||||
'col-3-agg_1': 9299,
|
||||
'col-5-agg_1': 5892,
|
||||
},
|
||||
{
|
||||
'col-0-agg_2': 'css',
|
||||
'col-2-agg_3': 'US',
|
||||
'col-4-agg_4': 'linux',
|
||||
'col-1-agg_1': 412032,
|
||||
'col-3-agg_1': 8293,
|
||||
'col-5-agg_1': 3992,
|
||||
},
|
||||
{
|
||||
'col-0-agg_2': 'css',
|
||||
'col-2-agg_3': 'US',
|
||||
'col-4-agg_4': 'mac',
|
||||
'col-1-agg_1': 412032,
|
||||
'col-3-agg_1': 8293,
|
||||
'col-5-agg_1': 3029,
|
||||
},
|
||||
{
|
||||
'col-0-agg_2': 'html',
|
||||
'col-2-agg_3': 'CN',
|
||||
'col-4-agg_4': 'win',
|
||||
'col-1-agg_1': 412032,
|
||||
'col-3-agg_1': 9299,
|
||||
'col-5-agg_1': 4992,
|
||||
},
|
||||
{
|
||||
'col-0-agg_2': 'html',
|
||||
'col-2-agg_3': 'CN',
|
||||
'col-4-agg_4': 'mac',
|
||||
'col-1-agg_1': 412032,
|
||||
'col-3-agg_1': 9299,
|
||||
'col-5-agg_1': 5892,
|
||||
},
|
||||
{
|
||||
'col-0-agg_2': 'html',
|
||||
'col-2-agg_3': 'FR',
|
||||
'col-4-agg_4': 'win',
|
||||
'col-1-agg_1': 412032,
|
||||
'col-3-agg_1': 8293,
|
||||
'col-5-agg_1': 3992,
|
||||
},
|
||||
{
|
||||
'col-0-agg_2': 'html',
|
||||
'col-2-agg_3': 'FR',
|
||||
'col-4-agg_4': 'mac',
|
||||
'col-1-agg_1': 412032,
|
||||
'col-3-agg_1': 8293,
|
||||
'col-5-agg_1': 3029,
|
||||
},
|
||||
],
|
||||
};
|
||||
dimensions = {
|
||||
splitRow: [{ accessor: 0 } as Dimension],
|
||||
metric: { accessor: 5 } as Dimension,
|
||||
buckets: [{ accessor: 2 }, { accessor: 4 }] as Dimension[],
|
||||
};
|
||||
const tableGroup = await tableVisResponseHandler(tabifyResponse, dimensions);
|
||||
tables = tableGroup.tables as TableParent[];
|
||||
});
|
||||
|
||||
it('should set the correct hits attribute for each of the results', () => {
|
||||
tables.forEach((t) => {
|
||||
const results = buildHierarchicalData(t.tables![0], dimensions);
|
||||
expect(results).toHaveProperty('hits');
|
||||
expect(results.hits).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
it('should set the correct names for each of the results', () => {
|
||||
const results0 = buildHierarchicalData(tables[0].tables![0], dimensions);
|
||||
expect(results0).toHaveProperty('names');
|
||||
expect(results0.names).toHaveLength(5);
|
||||
|
||||
const results1 = buildHierarchicalData(tables[1].tables![0], dimensions);
|
||||
expect(results1).toHaveProperty('names');
|
||||
expect(results1.names).toHaveLength(5);
|
||||
|
||||
const results2 = buildHierarchicalData(tables[2].tables![0], dimensions);
|
||||
expect(results2).toHaveProperty('names');
|
||||
expect(results2.names).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('should set the parent of the first item in the split', () => {
|
||||
const results0 = buildHierarchicalData(tables[0].tables![0], dimensions);
|
||||
expect(results0).toHaveProperty('slices');
|
||||
expect(results0.slices).toHaveProperty('children');
|
||||
expect(results0.slices.children).toHaveLength(2);
|
||||
expect(results0.slices.children[0].rawData!.table.$parent).toHaveProperty('key', 'png');
|
||||
|
||||
const results1 = buildHierarchicalData(tables[1].tables![0], dimensions);
|
||||
expect(results1).toHaveProperty('slices');
|
||||
expect(results1.slices).toHaveProperty('children');
|
||||
expect(results1.slices.children).toHaveLength(2);
|
||||
expect(results1.slices.children[0].rawData!.table.$parent).toHaveProperty('key', 'css');
|
||||
|
||||
const results2 = buildHierarchicalData(tables[2].tables![0], dimensions);
|
||||
expect(results2).toHaveProperty('slices');
|
||||
expect(results2.slices).toHaveProperty('children');
|
||||
expect(results2.slices.children).toHaveLength(2);
|
||||
expect(results2.slices.children[0].rawData!.table.$parent).toHaveProperty('key', 'html');
|
||||
});
|
||||
});
|
||||
|
||||
describe('oneHistogramBucket', () => {
|
||||
let dimensions: Dimensions;
|
||||
let table: Table;
|
||||
|
||||
beforeEach(async () => {
|
||||
const tabifyResponse = {
|
||||
columns: [
|
||||
{ id: 'col-0-agg_2', name: 'bytes' },
|
||||
{ id: 'col-1-1', name: 'Count' },
|
||||
],
|
||||
rows: [
|
||||
{ 'col-0-agg_2': 1411862400000, 'col-1-1': 8247 },
|
||||
{ 'col-0-agg_2': 1411948800000, 'col-1-1': 8184 },
|
||||
{ 'col-0-agg_2': 1412035200000, 'col-1-1': 8269 },
|
||||
{ 'col-0-agg_2': 1412121600000, 'col-1-1': 8141 },
|
||||
{ 'col-0-agg_2': 1412208000000, 'col-1-1': 8148 },
|
||||
{ 'col-0-agg_2': 1412294400000, 'col-1-1': 8219 },
|
||||
],
|
||||
};
|
||||
dimensions = {
|
||||
metric: { accessor: 1 } as Dimension,
|
||||
buckets: [{ accessor: 0 } as Dimension],
|
||||
};
|
||||
const tableGroup = await tableVisResponseHandler(tabifyResponse, dimensions);
|
||||
table = tableGroup.tables[0] as Table;
|
||||
});
|
||||
|
||||
it('should set the hits attribute for the results', () => {
|
||||
const results = buildHierarchicalData(table, dimensions);
|
||||
expect(results).toHaveProperty('raw');
|
||||
expect(results).toHaveProperty('slices');
|
||||
expect(results.slices).toHaveProperty('children');
|
||||
expect(results).toHaveProperty('names');
|
||||
expect(results.names).toHaveLength(6);
|
||||
});
|
||||
});
|
||||
|
||||
describe('oneRangeBucket', () => {
|
||||
let dimensions: Dimensions;
|
||||
let table: Table;
|
||||
|
||||
beforeEach(async () => {
|
||||
const tabifyResponse = {
|
||||
columns: [
|
||||
{ id: 'col-0-agg_2', name: 'bytes ranges' },
|
||||
{ id: 'col-1-1', name: 'Count' },
|
||||
],
|
||||
rows: [
|
||||
{ 'col-0-agg_2': { gte: 0, lt: 1000 }, 'col-1-1': 606 },
|
||||
{ 'col-0-agg_2': { gte: 1000, lt: 2000 }, 'col-1-1': 298 },
|
||||
],
|
||||
};
|
||||
dimensions = {
|
||||
metric: { accessor: 1 } as Dimension,
|
||||
buckets: [{ accessor: 0, format: { id: 'range', params: { id: 'agg_2' } } } as Dimension],
|
||||
};
|
||||
const tableGroup = await tableVisResponseHandler(tabifyResponse, dimensions);
|
||||
table = tableGroup.tables[0] as Table;
|
||||
});
|
||||
|
||||
it('should set the hits attribute for the results', () => {
|
||||
const results = buildHierarchicalData(table, dimensions);
|
||||
expect(results).toHaveProperty('raw');
|
||||
expect(results).toHaveProperty('slices');
|
||||
expect(results.slices).toHaveProperty('children');
|
||||
expect(results).toHaveProperty('names');
|
||||
expect(results.names).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('oneFilterBucket', () => {
|
||||
let dimensions: Dimensions;
|
||||
let table: Table;
|
||||
|
||||
beforeEach(async () => {
|
||||
const tabifyResponse = {
|
||||
columns: [
|
||||
{ id: 'col-0-agg_2', name: 'filters' },
|
||||
{ id: 'col-1-1', name: 'Count' },
|
||||
],
|
||||
rows: [
|
||||
{ 'col-0-agg_2': 'type:apache', 'col-1-1': 4844 },
|
||||
{ 'col-0-agg_2': 'type:nginx', 'col-1-1': 1161 },
|
||||
],
|
||||
};
|
||||
dimensions = {
|
||||
metric: { accessor: 1 } as Dimension,
|
||||
buckets: [
|
||||
{
|
||||
accessor: 0,
|
||||
},
|
||||
] as Dimension[],
|
||||
};
|
||||
const tableGroup = await tableVisResponseHandler(tabifyResponse, dimensions);
|
||||
table = tableGroup.tables[0] as Table;
|
||||
});
|
||||
|
||||
it('should set the hits attribute for the results', () => {
|
||||
const results = buildHierarchicalData(table, dimensions);
|
||||
expect(results).toHaveProperty('raw');
|
||||
expect(results).toHaveProperty('slices');
|
||||
expect(results).toHaveProperty('names');
|
||||
expect(results.names).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
* 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 { toArray } from 'lodash';
|
||||
import type { Dimensions } from '@kbn/vis-type-pie-plugin/public';
|
||||
import { getFormatService } from '../../../services';
|
||||
import { Table } from '../../types';
|
||||
|
||||
interface Slice {
|
||||
name: string;
|
||||
size: number;
|
||||
parent?: Slice;
|
||||
children?: [];
|
||||
rawData?: {
|
||||
table: Table;
|
||||
row: number;
|
||||
column: number;
|
||||
value: string | number | object;
|
||||
};
|
||||
}
|
||||
|
||||
export const buildHierarchicalData = (table: Table, { metric, buckets = [] }: Dimensions) => {
|
||||
let slices: Slice[];
|
||||
const names: { [key: string]: string } = {};
|
||||
const metricColumn = table.columns[metric.accessor];
|
||||
const metricFieldFormatter = metric.format;
|
||||
|
||||
if (!buckets.length) {
|
||||
slices = [
|
||||
{
|
||||
name: metricColumn.name,
|
||||
size: table.rows[0][metricColumn.id] as number,
|
||||
},
|
||||
];
|
||||
names[metricColumn.name] = metricColumn.name;
|
||||
} else {
|
||||
slices = [];
|
||||
table.rows.forEach((row, rowIndex) => {
|
||||
let parent: Slice;
|
||||
let dataLevel = slices;
|
||||
|
||||
buckets.forEach((bucket) => {
|
||||
const bucketColumn = table.columns[bucket.accessor];
|
||||
const bucketValueColumn = table.columns[bucket.accessor + 1];
|
||||
const bucketFormatter = getFormatService().deserialize(bucket.format);
|
||||
const name = bucketFormatter.convert(row[bucketColumn.id]);
|
||||
const size = row[bucketValueColumn.id] as number;
|
||||
names[name] = name;
|
||||
|
||||
let slice = dataLevel.find((dataLevelSlice) => dataLevelSlice.name === name);
|
||||
if (!slice) {
|
||||
slice = {
|
||||
name,
|
||||
size,
|
||||
parent,
|
||||
children: [],
|
||||
rawData: {
|
||||
table,
|
||||
row: rowIndex,
|
||||
column: bucket.accessor,
|
||||
value: row[bucketColumn.id],
|
||||
},
|
||||
};
|
||||
dataLevel.push(slice);
|
||||
}
|
||||
|
||||
parent = slice;
|
||||
dataLevel = slice.children as [];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
hits: table.rows.length,
|
||||
raw: table,
|
||||
names: toArray(names),
|
||||
tooltipFormatter: metricFieldFormatter,
|
||||
slices: {
|
||||
children: [...slices],
|
||||
},
|
||||
};
|
||||
};
|
|
@ -7,4 +7,3 @@
|
|||
*/
|
||||
|
||||
export { buildPointSeriesData } from './point_series';
|
||||
export { buildHierarchicalData } from './hierarchical/build_hierarchical_data';
|
||||
|
|
|
@ -49,7 +49,6 @@ export class Data {
|
|||
this.createColorLookupFunction = createColorLookupFunction;
|
||||
this.data = this.copyDataObj(data);
|
||||
this.type = this.getDataType();
|
||||
this._cleanVisData();
|
||||
this.labels = this._getLabels(this.data);
|
||||
this.color = this.labels
|
||||
? createColorLookupFunction(this.labels, uiState.get('vis.colors'))
|
||||
|
@ -248,19 +247,6 @@ export class Data {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array of chart data objects for pie data objects
|
||||
*
|
||||
* @method pieData
|
||||
* @returns {*} Array of chart data objects
|
||||
*/
|
||||
pieData() {
|
||||
if (!this.data.slices) {
|
||||
return this.data.rows ? this.data.rows : this.data.columns;
|
||||
}
|
||||
return [this.data];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attributes off the data, e.g. `tooltipFormatter` or `xAxisFormatter`
|
||||
* pulls the value off the first item in the array
|
||||
|
@ -379,71 +365,6 @@ export class Data {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean visualization data from missing/wrong values.
|
||||
* Currently used only to clean remove zero slices from
|
||||
* pie chart.
|
||||
*/
|
||||
_cleanVisData() {
|
||||
const visData = this.getVisData();
|
||||
if (this.type === 'slices') {
|
||||
this._cleanPieChartData(visData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutate the current pie chart vis data to remove slices with
|
||||
* zero values.
|
||||
* @param {Array} data
|
||||
*/
|
||||
_cleanPieChartData(data) {
|
||||
_.forEach(data, (obj) => {
|
||||
obj.slices = this._removeZeroSlices(obj.slices);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes zeros from pie chart data, mutating the passed values.
|
||||
* @param slices
|
||||
* @returns {*}
|
||||
*/
|
||||
_removeZeroSlices(slices) {
|
||||
if (!slices.children) {
|
||||
return slices;
|
||||
}
|
||||
|
||||
slices = _.clone(slices);
|
||||
slices.children = slices.children.reduce((children, child) => {
|
||||
if (child.size !== 0) {
|
||||
return [...children, this._removeZeroSlices(child)];
|
||||
}
|
||||
return children;
|
||||
}, []);
|
||||
|
||||
return slices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of names ordered by appearance in the nested array
|
||||
* of objects
|
||||
*
|
||||
* @method pieNames
|
||||
* @returns {Array} Array of unique names (strings)
|
||||
*/
|
||||
pieNames(data) {
|
||||
const self = this;
|
||||
const names = [];
|
||||
|
||||
_.forEach(data, function (obj) {
|
||||
const columns = obj.raw ? obj.raw.columns : undefined;
|
||||
_.forEach(self.getNames(obj, columns), function (name) {
|
||||
names.push(name);
|
||||
});
|
||||
});
|
||||
|
||||
return _.uniqBy(names, 'label');
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject zeros into the data
|
||||
*
|
||||
|
@ -483,30 +404,12 @@ export class Data {
|
|||
* @returns {Function} Performs lookup on string and returns hex color
|
||||
*/
|
||||
getColorFunc() {
|
||||
if (this.type === 'slices') {
|
||||
return this.getPieColorFunc();
|
||||
}
|
||||
const defaultColors = this.uiState.get('vis.defaultColors');
|
||||
const overwriteColors = this.uiState.get('vis.colors');
|
||||
const colors = defaultColors ? _.defaults({}, overwriteColors, defaultColors) : overwriteColors;
|
||||
return this.createColorLookupFunction(this.getLabels(), colors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function that does color lookup on names for pie charts
|
||||
*
|
||||
* @method getPieColorFunc
|
||||
* @returns {Function} Performs lookup on string and returns hex color
|
||||
*/
|
||||
getPieColorFunc() {
|
||||
return this.createColorLookupFunction(
|
||||
this.pieNames(this.getVisData()).map(function (d) {
|
||||
return d.label;
|
||||
}),
|
||||
this.uiState.get('vis.colors')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* ensure that the datas ordered property has a min and max
|
||||
* if the data represents an ordered date range.
|
||||
|
|
|
@ -157,24 +157,6 @@ describe('Vislib Data Class Test Suite', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('_removeZeroSlices', function () {
|
||||
let data;
|
||||
const pieData = {
|
||||
slices: {
|
||||
children: [{ size: 30 }, { size: 20 }, { size: 0 }],
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
data = new Data(pieData, mockUiState, () => undefined);
|
||||
});
|
||||
|
||||
it('should remove zero values', function () {
|
||||
const slices = data._removeZeroSlices(data.data.slices);
|
||||
expect(slices.children.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Data.flatten', function () {
|
||||
let serIn;
|
||||
let serOut;
|
||||
|
|
|
@ -123,23 +123,6 @@ export class Dispatch {
|
|||
return reduce(this._listeners, (count, handlers) => count + size(handlers), 0);
|
||||
}
|
||||
|
||||
_pieClickResponse(data) {
|
||||
const points = [];
|
||||
|
||||
let dataPointer = data;
|
||||
while (dataPointer && dataPointer.rawData) {
|
||||
points.push(dataPointer.rawData);
|
||||
dataPointer = dataPointer.parent;
|
||||
}
|
||||
|
||||
if (get(data, 'rawData.table.$parent')) {
|
||||
const { table, column, row, key } = get(data, 'rawData.table.$parent');
|
||||
points.push({ table, column, row, value: key });
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
_seriesClickResponse(data) {
|
||||
const points = [];
|
||||
|
||||
|
@ -170,7 +153,7 @@ export class Dispatch {
|
|||
const data = d.input || d;
|
||||
|
||||
return {
|
||||
data: isSlices ? this._pieClickResponse(data) : this._seriesClickResponse(data),
|
||||
data: this._seriesClickResponse(data),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -179,27 +179,6 @@ describe('Vislib Dispatch Class Test Suite', function () {
|
|||
});
|
||||
|
||||
describe('clickEvent handler', () => {
|
||||
describe('for pie chart', () => {
|
||||
test('prepares data points', () => {
|
||||
const expectedResponse = [{ column: 0, row: 0, table: {}, value: 0 }];
|
||||
const d = { rawData: { column: 0, row: 0, table: {}, value: 0 } };
|
||||
const chart = _.first(vis.handler.charts);
|
||||
const response = chart.events.clickEventResponse(d, { isSlices: true });
|
||||
expect(response.data).toEqual(expectedResponse);
|
||||
});
|
||||
|
||||
test('remove invalid points', () => {
|
||||
const expectedResponse = [{ column: 0, row: 0, table: {}, value: 0 }];
|
||||
const d = {
|
||||
rawData: { column: 0, row: 0, table: {}, value: 0 },
|
||||
yRaw: { table: {}, value: 0 },
|
||||
};
|
||||
const chart = _.first(vis.handler.charts);
|
||||
const response = chart.events.clickEventResponse(d, { isSlices: true });
|
||||
expect(response.data).toEqual(expectedResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('for xy charts', () => {
|
||||
test('prepares data points', () => {
|
||||
const expectedResponse = [{ column: 0, row: 0, table: {}, value: 0 }];
|
||||
|
|
|
@ -7,11 +7,9 @@
|
|||
*/
|
||||
|
||||
import { columnLayout } from './types/column_layout';
|
||||
import { pieLayout } from './types/pie_layout';
|
||||
import { gaugeLayout } from './types/gauge_layout';
|
||||
|
||||
export const layoutTypes = {
|
||||
pie: pieLayout,
|
||||
gauge: gaugeLayout,
|
||||
goal: gaugeLayout,
|
||||
metric: gaugeLayout,
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* 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 { chartSplit } from '../splits/pie_chart/chart_split';
|
||||
import { chartTitleSplit } from '../splits/pie_chart/chart_title_split';
|
||||
|
||||
/**
|
||||
* Specifies the visualization layout for column charts.
|
||||
*
|
||||
* This is done using an array of objects. The first object has
|
||||
* a `parent` DOM element, a DOM `type` (e.g. div, svg, etc),
|
||||
* and a `class` (required). Each child can omit the parent object,
|
||||
* but must include a type and class.
|
||||
*
|
||||
* Optionally, you can specify `datum` to be bound to the DOM
|
||||
* element, a `splits` function that divides the selected element
|
||||
* into more DOM elements based on a callback function provided, or
|
||||
* a children array which nests other layout objects.
|
||||
*
|
||||
* Objects in children arrays are children of the current object and return
|
||||
* DOM elements which are children of their respective parent element.
|
||||
*/
|
||||
|
||||
export function pieLayout(el, data) {
|
||||
if (!el || !data) {
|
||||
throw new Error('Both an el and data need to be specified');
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
parent: el,
|
||||
type: 'div',
|
||||
class: 'visWrapper',
|
||||
datum: data,
|
||||
children: [
|
||||
{
|
||||
type: 'div',
|
||||
class: 'visAxis__splitTitles--y',
|
||||
splits: chartTitleSplit,
|
||||
},
|
||||
{
|
||||
type: 'div',
|
||||
class: 'visWrapper__column',
|
||||
children: [
|
||||
{
|
||||
type: 'div',
|
||||
class: 'visWrapper__chart',
|
||||
splits: chartSplit,
|
||||
},
|
||||
{
|
||||
type: 'div',
|
||||
class: 'visAxis__splitTitles--x',
|
||||
splits: chartTitleSplit,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
|
@ -7,11 +7,9 @@
|
|||
*/
|
||||
|
||||
import { vislibPointSeriesTypes as pointSeries } from './point_series';
|
||||
import { vislibPieConfig } from './pie';
|
||||
import { vislibGaugeConfig } from './gauge';
|
||||
|
||||
export const vislibTypesConfig = {
|
||||
pie: vislibPieConfig,
|
||||
heatmap: pointSeries.heatmap,
|
||||
gauge: vislibGaugeConfig,
|
||||
goal: vislibGaugeConfig,
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
* 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 _ from 'lodash';
|
||||
|
||||
export function vislibPieConfig(config) {
|
||||
if (!config.chart) {
|
||||
config.chart = _.defaults({}, config, {
|
||||
type: 'pie',
|
||||
labels: {
|
||||
show: false,
|
||||
truncate: 100,
|
||||
},
|
||||
});
|
||||
}
|
||||
return config;
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import { getFormatService } from '../services';
|
||||
import { buildHierarchicalData, buildPointSeriesData } from './helpers';
|
||||
import { buildPointSeriesData } from './helpers';
|
||||
|
||||
function tableResponseHandler(table, dimensions) {
|
||||
const converted = { tables: [] };
|
||||
|
@ -112,5 +112,3 @@ function handlerFunction(convertTable) {
|
|||
}
|
||||
|
||||
export const vislibSeriesResponseHandler = handlerFunction(buildPointSeriesData);
|
||||
|
||||
export const vislibSlicesResponseHandler = handlerFunction(buildHierarchicalData);
|
||||
|
|
|
@ -9,41 +9,15 @@
|
|||
import { setFormatService } from '../services';
|
||||
|
||||
jest.mock('./helpers', () => ({
|
||||
buildHierarchicalData: jest.fn(() => ({})),
|
||||
buildPointSeriesData: jest.fn(() => ({})),
|
||||
}));
|
||||
|
||||
// @ts-ignore
|
||||
import { vislibSeriesResponseHandler, vislibSlicesResponseHandler } from './response_handler';
|
||||
import { buildHierarchicalData, buildPointSeriesData } from './helpers';
|
||||
import { Table } from './types';
|
||||
import { vislibSeriesResponseHandler } from './response_handler';
|
||||
import { buildPointSeriesData } from './helpers';
|
||||
|
||||
describe('response_handler', () => {
|
||||
describe('vislibSlicesResponseHandler', () => {
|
||||
test('should not call buildHierarchicalData when no columns', () => {
|
||||
vislibSlicesResponseHandler({ rows: [] }, {});
|
||||
expect(buildHierarchicalData).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should call buildHierarchicalData', () => {
|
||||
const response = {
|
||||
rows: [{ 'col-0-1': 1 }],
|
||||
columns: [{ id: 'col-0-1', name: 'Count' }],
|
||||
};
|
||||
const dimensions = { metric: { accessor: 0 } };
|
||||
vislibSlicesResponseHandler(response, dimensions);
|
||||
|
||||
expect(buildHierarchicalData).toHaveBeenCalledWith(
|
||||
{ columns: [...response.columns], rows: [...response.rows] },
|
||||
dimensions
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('vislibSeriesResponseHandler', () => {
|
||||
let resp: Table;
|
||||
let expected: any;
|
||||
|
||||
beforeAll(() => {
|
||||
setFormatService({
|
||||
deserialize: () => ({
|
||||
|
@ -52,27 +26,6 @@ describe('response_handler', () => {
|
|||
} as any);
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
resp = {
|
||||
rows: [
|
||||
{ 'col-0-3': 158599872, 'col-1-1': 1 },
|
||||
{ 'col-0-3': 158599893, 'col-1-1': 2 },
|
||||
{ 'col-0-3': 158599908, 'col-1-1': 1 },
|
||||
],
|
||||
columns: [
|
||||
{ id: 'col-0-3', name: 'timestamp per 30 seconds' },
|
||||
{ id: 'col-1-1', name: 'Count' },
|
||||
],
|
||||
} as Table;
|
||||
|
||||
const colId = resp.columns[0].id;
|
||||
expected = [
|
||||
{ label: `${resp.rows[0][colId]}: ${resp.columns[0].name}` },
|
||||
{ label: `${resp.rows[1][colId]}: ${resp.columns[0].name}` },
|
||||
{ label: `${resp.rows[2][colId]}: ${resp.columns[0].name}` },
|
||||
];
|
||||
});
|
||||
|
||||
test('should not call buildPointSeriesData when no columns', () => {
|
||||
vislibSeriesResponseHandler({ rows: [] }, {});
|
||||
expect(buildPointSeriesData).not.toHaveBeenCalled();
|
||||
|
@ -91,29 +44,5 @@ describe('response_handler', () => {
|
|||
dimensions
|
||||
);
|
||||
});
|
||||
|
||||
test('should split columns', () => {
|
||||
const dimensions = {
|
||||
x: null,
|
||||
y: [{ accessor: 1 }],
|
||||
splitColumn: [{ accessor: 0 }],
|
||||
};
|
||||
|
||||
const convertedResp = vislibSlicesResponseHandler(resp, dimensions);
|
||||
expect(convertedResp.columns).toHaveLength(resp.rows.length);
|
||||
expect(convertedResp.columns).toEqual(expected);
|
||||
});
|
||||
|
||||
test('should split rows', () => {
|
||||
const dimensions = {
|
||||
x: null,
|
||||
y: [{ accessor: 1 }],
|
||||
splitRow: [{ accessor: 0 }],
|
||||
};
|
||||
|
||||
const convertedResp = vislibSlicesResponseHandler(resp, dimensions);
|
||||
expect(convertedResp.rows).toHaveLength(resp.rows.length);
|
||||
expect(convertedResp.rows).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,11 +12,7 @@ import _ from 'lodash';
|
|||
import { dataLabel } from '../lib/_data_label';
|
||||
import { Dispatch } from '../lib/dispatch';
|
||||
import { getFormatService } from '../../services';
|
||||
import {
|
||||
Tooltip,
|
||||
hierarchicalTooltipFormatter,
|
||||
pointSeriesTooltipFormatter,
|
||||
} from '../components/tooltip';
|
||||
import { Tooltip, pointSeriesTooltipFormatter } from '../components/tooltip';
|
||||
|
||||
/**
|
||||
* The Base Class for all visualizations.
|
||||
|
@ -39,10 +35,7 @@ export class Chart {
|
|||
const fieldFormatter = getFormatService().deserialize(
|
||||
this.handler.data.get('tooltipFormatter')
|
||||
);
|
||||
const tooltipFormatterProvider =
|
||||
this.handler.visConfig.get('type') === 'pie'
|
||||
? hierarchicalTooltipFormatter
|
||||
: pointSeriesTooltipFormatter;
|
||||
const tooltipFormatterProvider = pointSeriesTooltipFormatter;
|
||||
const tooltipFormatter = tooltipFormatterProvider(fieldFormatter);
|
||||
|
||||
if (this.handler.visConfig && this.handler.visConfig.get('addTooltip', false)) {
|
||||
|
|
|
@ -1,383 +0,0 @@
|
|||
/*
|
||||
* 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 d3 from 'd3';
|
||||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import numeral from '@elastic/numeral';
|
||||
import { PieContainsAllZeros, ContainerTooSmall } from '../errors';
|
||||
import { Chart } from './_chart';
|
||||
import { truncateLabel } from '../components/labels/truncate_labels';
|
||||
|
||||
const defaults = {
|
||||
isDonut: false,
|
||||
showTooltip: true,
|
||||
color: undefined,
|
||||
fillColor: undefined,
|
||||
};
|
||||
/**
|
||||
* Pie Chart Visualization
|
||||
*
|
||||
* @class PieChart
|
||||
* @constructor
|
||||
* @extends Chart
|
||||
* @param handler {Object} Reference to the Handler Class Constructor
|
||||
* @param el {HTMLElement} HTML element to which the chart will be appended
|
||||
* @param chartData {Object} Elasticsearch query results for this specific chart
|
||||
*/
|
||||
export class PieChart extends Chart {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether pie slices have all zero values.
|
||||
* If so, an error is thrown.
|
||||
*/
|
||||
_validatePieData(charts) {
|
||||
const isAllZeros = charts.every((chart) => {
|
||||
return chart.slices.children.length === 0;
|
||||
});
|
||||
|
||||
if (isAllZeros) {
|
||||
throw new PieContainsAllZeros();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Events to SVG paths
|
||||
*
|
||||
* @method addPathEvents
|
||||
* @param element {D3.Selection} Reference to SVG path
|
||||
* @returns {D3.Selection} SVG path with event listeners attached
|
||||
*/
|
||||
addPathEvents(element) {
|
||||
const events = this.events;
|
||||
|
||||
return element
|
||||
.call(events.addHoverEvent())
|
||||
.call(events.addMouseoutEvent())
|
||||
.call(events.addClickEvent());
|
||||
}
|
||||
|
||||
convertToPercentage(slices) {
|
||||
(function assignPercentages(slices) {
|
||||
if (slices.sumOfChildren != null) return;
|
||||
|
||||
const parent = slices;
|
||||
const children = parent.children;
|
||||
const parentPercent = parent.percentOfParent;
|
||||
|
||||
const sum = (parent.sumOfChildren = Math.abs(
|
||||
children.reduce(function (sum, child) {
|
||||
return sum + Math.abs(child.size);
|
||||
}, 0)
|
||||
));
|
||||
|
||||
children.forEach(function (child) {
|
||||
child.percentOfGroup = Math.abs(child.size) / sum;
|
||||
child.percentOfParent = child.percentOfGroup;
|
||||
|
||||
if (parentPercent != null) {
|
||||
child.percentOfParent *= parentPercent;
|
||||
}
|
||||
|
||||
if (child.children) {
|
||||
assignPercentages(child);
|
||||
}
|
||||
});
|
||||
})(slices);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds pie paths to SVG
|
||||
*
|
||||
* @method addPath
|
||||
* @param width {Number} Width of SVG
|
||||
* @param height {Number} Height of SVG
|
||||
* @param svg {HTMLElement} Chart SVG
|
||||
* @param slices {Object} Chart data
|
||||
* @returns {D3.Selection} SVG with paths attached
|
||||
*/
|
||||
addPath(width, height, svg, slices) {
|
||||
const self = this;
|
||||
const marginFactor = 0.95;
|
||||
const isDonut = self._attr.isDonut;
|
||||
const radius = (Math.min(width, height) / 2) * marginFactor;
|
||||
const color = self.handler.data.getPieColorFunc();
|
||||
const tooltip = self.tooltip;
|
||||
const isTooltip = self._attr.addTooltip;
|
||||
|
||||
const arcs = svg.append('g').attr('class', 'arcs');
|
||||
const labels = svg.append('g').attr('class', 'labels');
|
||||
|
||||
const showLabels = self._attr.labels.show;
|
||||
const showValues = self._attr.labels.values;
|
||||
const truncateLabelLength = self._attr.labels.truncate;
|
||||
const showOnlyOnLastLevel = self._attr.labels.last_level;
|
||||
|
||||
const partition = d3.layout
|
||||
.partition()
|
||||
.sort(null)
|
||||
.value(function (d) {
|
||||
return d.percentOfParent * 100;
|
||||
});
|
||||
|
||||
const x = d3.scale.linear().range([0, 2 * Math.PI]);
|
||||
const y = d3.scale.sqrt().range([0, showLabels ? radius * 0.7 : radius]);
|
||||
|
||||
const startAngle = function (d) {
|
||||
return Math.max(0, Math.min(2 * Math.PI, x(d.x)));
|
||||
};
|
||||
|
||||
const endAngle = function (d) {
|
||||
if (d.dx < 1e-8) return x(d.x);
|
||||
return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
|
||||
};
|
||||
|
||||
const arc = d3.svg
|
||||
.arc()
|
||||
.startAngle(startAngle)
|
||||
.endAngle(endAngle)
|
||||
.innerRadius(function (d) {
|
||||
// option for a single layer, i.e pie chart
|
||||
if (d.depth === 1 && !isDonut) {
|
||||
// return no inner radius
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.max(0, y(d.y));
|
||||
})
|
||||
.outerRadius(function (d) {
|
||||
return Math.max(0, y(d.y + d.dy));
|
||||
});
|
||||
|
||||
const outerArc = d3.svg
|
||||
.arc()
|
||||
.startAngle(startAngle)
|
||||
.endAngle(endAngle)
|
||||
.innerRadius(radius * 0.8)
|
||||
.outerRadius(radius * 0.8);
|
||||
|
||||
let maxDepth = 0;
|
||||
const path = arcs
|
||||
.datum(slices)
|
||||
.selectAll('path')
|
||||
.data(partition.nodes)
|
||||
.enter()
|
||||
.append('path')
|
||||
.attr('d', arc)
|
||||
.attr('class', function (d) {
|
||||
if (d.depth === 0) {
|
||||
return;
|
||||
}
|
||||
if (d.depth > maxDepth) maxDepth = d.depth;
|
||||
return 'slice';
|
||||
})
|
||||
.attr('data-test-subj', function (d) {
|
||||
if (d.name) {
|
||||
return `pieSlice-${d.name.split(' ').join('-')}`;
|
||||
}
|
||||
})
|
||||
.call(self._addIdentifier, 'name')
|
||||
.style('fill', function (d) {
|
||||
if (d.depth === 0) {
|
||||
return 'none';
|
||||
}
|
||||
return color(d.name);
|
||||
});
|
||||
|
||||
// add labels
|
||||
if (showLabels) {
|
||||
const labelGroups = labels.datum(slices).selectAll('.label').data(partition.nodes);
|
||||
|
||||
// create an empty quadtree to hold label positions
|
||||
const svgParentNode = svg.node().parentNode.parentNode;
|
||||
const svgBBox = {
|
||||
width: svgParentNode.clientWidth,
|
||||
height: svgParentNode.clientHeight,
|
||||
};
|
||||
|
||||
const labelLayout = d3.geom
|
||||
.quadtree()
|
||||
.extent([
|
||||
[-svgBBox.width, -svgBBox.height],
|
||||
[svgBBox.width, svgBBox.height],
|
||||
])
|
||||
.x(function (d) {
|
||||
return d.position.x;
|
||||
})
|
||||
.y(function (d) {
|
||||
return d.position.y;
|
||||
})([]);
|
||||
|
||||
labelGroups
|
||||
.enter()
|
||||
.append('g')
|
||||
.attr('class', 'label')
|
||||
.append('text')
|
||||
.text(function (d) {
|
||||
if (d.depth === 0) {
|
||||
d3.select(this.parentNode).remove();
|
||||
return;
|
||||
}
|
||||
if (showValues) {
|
||||
const value = numeral(d.value / 100).format('0.[00]%');
|
||||
return `${d.name} (${value})`;
|
||||
}
|
||||
return d.name;
|
||||
})
|
||||
.text(function () {
|
||||
return truncateLabel(this, truncateLabelLength);
|
||||
})
|
||||
.attr('text-anchor', function (d) {
|
||||
const midAngle = startAngle(d) + (endAngle(d) - startAngle(d)) / 2;
|
||||
return midAngle < Math.PI ? 'start' : 'end';
|
||||
})
|
||||
.attr('class', 'label-text')
|
||||
.each(function resolveConflicts(d) {
|
||||
if (d.depth === 0) return;
|
||||
|
||||
const parentNode = this.parentNode;
|
||||
if (showOnlyOnLastLevel && maxDepth !== d.depth) {
|
||||
d3.select(parentNode).remove();
|
||||
return;
|
||||
}
|
||||
|
||||
const bbox = this.getBBox();
|
||||
const pos = outerArc.centroid(d);
|
||||
const midAngle = startAngle(d) + (endAngle(d) - startAngle(d)) / 2;
|
||||
pos[1] += 4;
|
||||
pos[0] = (0.7 + d.depth / 10) * radius * (midAngle < Math.PI ? 1 : -1);
|
||||
d.position = {
|
||||
x: pos[0],
|
||||
y: pos[1],
|
||||
left: midAngle < Math.PI ? pos[0] : pos[0] - bbox.width,
|
||||
right: midAngle > Math.PI ? pos[0] + bbox.width : pos[0],
|
||||
bottom: pos[1] + 5,
|
||||
top: pos[1] - bbox.height - 5,
|
||||
};
|
||||
|
||||
const conflicts = [];
|
||||
labelLayout.visit(function (node) {
|
||||
if (!node.point) return;
|
||||
if (conflicts.length) return true;
|
||||
|
||||
const point = node.point.position;
|
||||
const current = d.position;
|
||||
if (point) {
|
||||
const horizontalConflict =
|
||||
(point.left < 0 && current.left < 0) || (point.left > 0 && current.left > 0);
|
||||
const verticalConflict =
|
||||
(point.top >= current.top && point.top <= current.bottom) ||
|
||||
(point.top <= current.top && point.bottom >= current.top);
|
||||
|
||||
if (horizontalConflict && verticalConflict) {
|
||||
point.point = node.point;
|
||||
conflicts.push(point);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (conflicts.length) {
|
||||
d3.select(parentNode).remove();
|
||||
return;
|
||||
}
|
||||
|
||||
labelLayout.add(d);
|
||||
})
|
||||
.attr('x', function (d) {
|
||||
if (d.depth === 0 || !d.position) {
|
||||
return;
|
||||
}
|
||||
return d.position.x;
|
||||
})
|
||||
.attr('y', function (d) {
|
||||
if (d.depth === 0 || !d.position) {
|
||||
return;
|
||||
}
|
||||
return d.position.y;
|
||||
});
|
||||
|
||||
labelGroups
|
||||
.append('polyline')
|
||||
.attr('points', function (d) {
|
||||
if (d.depth === 0 || !d.position) {
|
||||
return;
|
||||
}
|
||||
const pos1 = outerArc.centroid(d);
|
||||
const x2 = d.position.x > 0 ? d.position.x - 10 : d.position.x + 10;
|
||||
const pos2 = [x2, d.position.y - 4];
|
||||
pos1[1] = pos2[1];
|
||||
return [arc.centroid(d), pos1, pos2];
|
||||
})
|
||||
.attr('class', 'label-line');
|
||||
}
|
||||
|
||||
if (isTooltip) {
|
||||
path.call(tooltip.render());
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
_validateContainerSize(width, height) {
|
||||
const minWidth = 20;
|
||||
const minHeight = 20;
|
||||
|
||||
if (width <= minWidth || height <= minHeight) {
|
||||
throw new ContainerTooSmall();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders d3 visualization
|
||||
*
|
||||
* @method draw
|
||||
* @returns {Function} Creates the pie chart
|
||||
*/
|
||||
draw() {
|
||||
const self = this;
|
||||
|
||||
return function (selection) {
|
||||
selection.each(function (data) {
|
||||
const slices = data.slices;
|
||||
const div = d3.select(this);
|
||||
const width = $(this).width();
|
||||
const height = $(this).height();
|
||||
|
||||
if (!slices.children.length) return;
|
||||
|
||||
self.convertToPercentage(slices);
|
||||
self._validateContainerSize(width, height);
|
||||
|
||||
const svg = div
|
||||
.append('svg')
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
.attr('focusable', 'false')
|
||||
.append('g')
|
||||
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
|
||||
|
||||
const path = self.addPath(width, height, svg, slices);
|
||||
self.addPathEvents(path);
|
||||
|
||||
self.events.emit('rendered', {
|
||||
chart: data,
|
||||
});
|
||||
|
||||
return svg;
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,270 +0,0 @@
|
|||
/*
|
||||
* 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 d3 from 'd3';
|
||||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import {
|
||||
setHTMLElementClientSizes,
|
||||
setSVGElementGetBBox,
|
||||
setSVGElementGetComputedTextLength,
|
||||
} from '@kbn/test-jest-helpers';
|
||||
import { getMockUiState } from '../../fixtures/mocks';
|
||||
import { getVis } from './_vis_fixture';
|
||||
import { pieChartMockData } from './pie_chart_mock_data';
|
||||
|
||||
const names = ['rows', 'columns', 'slices'];
|
||||
|
||||
const sizes = [0, 5, 15, 30, 60, 120];
|
||||
|
||||
let mockedHTMLElementClientSizes = {};
|
||||
let mockWidth;
|
||||
let mockHeight;
|
||||
let mockedSVGElementGetBBox;
|
||||
let mockedSVGElementGetComputedTextLength;
|
||||
|
||||
describe('No global chart settings', function () {
|
||||
const vislibParams1 = {
|
||||
el: '<div class=chart1></div>',
|
||||
type: 'pie',
|
||||
legendDisplay: 'show',
|
||||
addTooltip: true,
|
||||
};
|
||||
let chart1;
|
||||
let mockUiState;
|
||||
|
||||
beforeAll(() => {
|
||||
mockedHTMLElementClientSizes = setHTMLElementClientSizes(512, 512);
|
||||
mockedSVGElementGetBBox = setSVGElementGetBBox(100);
|
||||
mockedSVGElementGetComputedTextLength = setSVGElementGetComputedTextLength(100);
|
||||
mockWidth = jest.spyOn($.prototype, 'width').mockReturnValue(120);
|
||||
mockHeight = jest.spyOn($.prototype, 'height').mockReturnValue(120);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
chart1 = getVis(vislibParams1);
|
||||
mockUiState = getMockUiState();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
chart1.render(pieChartMockData.rowData, mockUiState);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
chart1.destroy();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mockedHTMLElementClientSizes.mockRestore();
|
||||
mockedSVGElementGetBBox.mockRestore();
|
||||
mockedSVGElementGetComputedTextLength.mockRestore();
|
||||
mockWidth.mockRestore();
|
||||
mockHeight.mockRestore();
|
||||
});
|
||||
|
||||
test('should render chart titles for all charts', function () {
|
||||
expect($(chart1.element).find('.visAxis__splitTitles--y').length).toBe(1);
|
||||
});
|
||||
|
||||
describe('_validatePieData method', function () {
|
||||
const allZeros = [
|
||||
{ slices: { children: [] } },
|
||||
{ slices: { children: [] } },
|
||||
{ slices: { children: [] } },
|
||||
];
|
||||
|
||||
const someZeros = [
|
||||
{ slices: { children: [{}] } },
|
||||
{ slices: { children: [{}] } },
|
||||
{ slices: { children: [] } },
|
||||
];
|
||||
|
||||
const noZeros = [
|
||||
{ slices: { children: [{}] } },
|
||||
{ slices: { children: [{}] } },
|
||||
{ slices: { children: [{}] } },
|
||||
];
|
||||
|
||||
test('should throw an error when all charts contain zeros', function () {
|
||||
expect(function () {
|
||||
chart1.handler.ChartClass.prototype._validatePieData(allZeros);
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
test('should not throw an error when only some or no charts contain zeros', function () {
|
||||
expect(function () {
|
||||
chart1.handler.ChartClass.prototype._validatePieData(someZeros);
|
||||
}).not.toThrowError();
|
||||
expect(function () {
|
||||
chart1.handler.ChartClass.prototype._validatePieData(noZeros);
|
||||
}).not.toThrowError();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Vislib PieChart Class Test Suite', function () {
|
||||
beforeAll(() => {
|
||||
mockedHTMLElementClientSizes = setHTMLElementClientSizes(512, 512);
|
||||
mockedSVGElementGetBBox = setSVGElementGetBBox(100);
|
||||
mockedSVGElementGetComputedTextLength = setSVGElementGetComputedTextLength(100);
|
||||
let width = 120;
|
||||
let height = 120;
|
||||
const mockWidth = jest.spyOn($.prototype, 'width');
|
||||
mockWidth.mockImplementation((size) => {
|
||||
if (size === undefined) {
|
||||
return width;
|
||||
}
|
||||
width = size;
|
||||
});
|
||||
const mockHeight = jest.spyOn($.prototype, 'height');
|
||||
mockHeight.mockImplementation((size) => {
|
||||
if (size === undefined) {
|
||||
return height;
|
||||
}
|
||||
height = size;
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mockedHTMLElementClientSizes.mockRestore();
|
||||
mockedSVGElementGetBBox.mockRestore();
|
||||
mockedSVGElementGetComputedTextLength.mockRestore();
|
||||
mockWidth.mockRestore();
|
||||
mockHeight.mockRestore();
|
||||
});
|
||||
|
||||
['rowData', 'columnData', 'sliceData'].forEach(function (aggItem, i) {
|
||||
describe('Vislib PieChart Class Test Suite for ' + names[i] + ' data', function () {
|
||||
const mockPieData = pieChartMockData[aggItem];
|
||||
|
||||
const vislibParams = {
|
||||
type: 'pie',
|
||||
legendDisplay: 'show',
|
||||
addTooltip: true,
|
||||
};
|
||||
let vis;
|
||||
|
||||
beforeEach(async () => {
|
||||
vis = getVis(vislibParams);
|
||||
const mockUiState = getMockUiState();
|
||||
vis.render(mockPieData, mockUiState);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
vis.destroy();
|
||||
});
|
||||
|
||||
describe('addPathEvents method', function () {
|
||||
let path;
|
||||
let d3selectedPath;
|
||||
let onClick;
|
||||
let onMouseOver;
|
||||
|
||||
beforeEach(function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
path = $(chart.chartEl).find('path')[0];
|
||||
d3selectedPath = d3.select(path)[0][0];
|
||||
|
||||
// d3 instance of click and hover
|
||||
onClick = !!d3selectedPath.__onclick;
|
||||
onMouseOver = !!d3selectedPath.__onmouseover;
|
||||
});
|
||||
});
|
||||
|
||||
test('should attach a click event', function () {
|
||||
vis.handler.charts.forEach(function () {
|
||||
expect(onClick).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('should attach a hover event', function () {
|
||||
vis.handler.charts.forEach(function () {
|
||||
expect(onMouseOver).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addPath method', function () {
|
||||
let width;
|
||||
let height;
|
||||
let svg;
|
||||
let slices;
|
||||
|
||||
test('should return an SVG object', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
$(chart.chartEl).find('svg').empty();
|
||||
width = $(chart.chartEl).width();
|
||||
height = $(chart.chartEl).height();
|
||||
svg = d3.select($(chart.chartEl).find('svg')[0]);
|
||||
slices = chart.chartData.slices;
|
||||
expect(_.isObject(chart.addPath(width, height, svg, slices))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('should draw path elements', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
// test whether path elements are drawn
|
||||
expect($(chart.chartEl).find('path').length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('should draw labels', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
$(chart.chartEl).find('svg').empty();
|
||||
width = $(chart.chartEl).width();
|
||||
height = $(chart.chartEl).height();
|
||||
svg = d3.select($(chart.chartEl).find('svg')[0]);
|
||||
slices = chart.chartData.slices;
|
||||
chart._attr.labels.show = true;
|
||||
chart.addPath(width, height, svg, slices);
|
||||
expect($(chart.chartEl).find('text.label-text').length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('draw method', function () {
|
||||
test('should return a function', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
expect(_.isFunction(chart.draw())).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
sizes.forEach(function (size) {
|
||||
describe('containerTooSmall error', function () {
|
||||
test('should throw an error', function () {
|
||||
// 20px is the minimum height and width
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
$(chart.chartEl).height(size);
|
||||
$(chart.chartEl).width(size);
|
||||
|
||||
if (size < 20) {
|
||||
expect(function () {
|
||||
chart.render();
|
||||
}).toThrowError();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('should not throw an error', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
$(chart.chartEl).height(size);
|
||||
$(chart.chartEl).width(size);
|
||||
|
||||
if (size > 20) {
|
||||
expect(function () {
|
||||
chart.render();
|
||||
}).not.toThrowError();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load diff
|
@ -7,11 +7,9 @@
|
|||
*/
|
||||
|
||||
import { PointSeries } from './point_series';
|
||||
import { PieChart } from './pie_chart';
|
||||
import { GaugeChart } from './gauge_chart';
|
||||
|
||||
export const visTypes = {
|
||||
pie: PieChart,
|
||||
point_series: PointSeries,
|
||||
gauge: GaugeChart,
|
||||
goal: GaugeChart,
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
{ "path": "../../kibana_utils/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" },
|
||||
]
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ jest.mock('@kbn/kibana-react-plugin/public', () => ({
|
|||
|
||||
jest.mock('../../services', () => ({
|
||||
getUISettings: jest.fn(() => ({
|
||||
get: jest.fn((token) => Boolean(token === 'visualization:visualize:legacyPieChartsLibrary')),
|
||||
get: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
|
@ -201,45 +201,4 @@ describe('VisualizeEditorCommon', () => {
|
|||
);
|
||||
expect(wrapper.find(VizChartWarning).length).toBe(0);
|
||||
});
|
||||
|
||||
it('should display a warning callout for old pie implementation', async () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<VisualizeEditorCommon
|
||||
appState={null}
|
||||
hasUnsavedChanges={false}
|
||||
setHasUnsavedChanges={() => {}}
|
||||
hasUnappliedChanges={false}
|
||||
isEmbeddableRendered={false}
|
||||
onAppLeave={() => {}}
|
||||
visEditorRef={React.createRef()}
|
||||
visInstance={
|
||||
{
|
||||
savedVis: {
|
||||
id: 'test',
|
||||
sharingSavedObjectProps: {
|
||||
outcome: 'conflict',
|
||||
aliasTargetId: 'alias_id',
|
||||
},
|
||||
},
|
||||
vis: {
|
||||
type: {
|
||||
title: 'pie',
|
||||
name: 'pie',
|
||||
},
|
||||
data: {
|
||||
aggs: {
|
||||
aggs: [
|
||||
{
|
||||
schema: 'buckets',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as VisualizeEditorVisInstance
|
||||
}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find(VizChartWarning).length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,4 +8,3 @@
|
|||
|
||||
export const NEW_HEATMAP_CHARTS_LIBRARY = 'visualization:visualize:legacyHeatmapChartsLibrary';
|
||||
export const NEW_GAUGE_CHARTS_LIBRARY = 'visualization:visualize:legacyGaugeChartsLibrary';
|
||||
export const NEW_PIE_CHARTS_LIBRARY = 'visualization:visualize:legacyPieChartsLibrary';
|
||||
|
|
|
@ -8,11 +8,7 @@
|
|||
|
||||
import { $Values } from '@kbn/utility-types';
|
||||
import { AggConfigs } from '@kbn/data-plugin/common';
|
||||
import {
|
||||
NEW_HEATMAP_CHARTS_LIBRARY,
|
||||
NEW_GAUGE_CHARTS_LIBRARY,
|
||||
NEW_PIE_CHARTS_LIBRARY,
|
||||
} from '../constants';
|
||||
import { NEW_HEATMAP_CHARTS_LIBRARY, NEW_GAUGE_CHARTS_LIBRARY } from '../constants';
|
||||
|
||||
export const CHARTS_WITHOUT_SMALL_MULTIPLES = {
|
||||
heatmap: 'heatmap',
|
||||
|
@ -30,7 +26,6 @@ export type CHARTS_TO_BE_DEPRECATED = $Values<typeof CHARTS_TO_BE_DEPRECATED>;
|
|||
export const CHARTS_CONFIG_TOKENS = {
|
||||
[CHARTS_WITHOUT_SMALL_MULTIPLES.heatmap]: NEW_HEATMAP_CHARTS_LIBRARY,
|
||||
[CHARTS_WITHOUT_SMALL_MULTIPLES.gauge]: NEW_GAUGE_CHARTS_LIBRARY,
|
||||
[CHARTS_TO_BE_DEPRECATED.pie]: NEW_PIE_CHARTS_LIBRARY,
|
||||
} as const;
|
||||
|
||||
export const isSplitChart = (chartType: string | undefined, aggs?: AggConfigs) => {
|
||||
|
|
|
@ -29,7 +29,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
const pieChart = getService('pieChart');
|
||||
const retry = getService('retry');
|
||||
const elasticChart = getService('elasticChart');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const dashboardAddPanel = getService('dashboardAddPanel');
|
||||
const xyChartSelector = 'xyVisChart';
|
||||
|
||||
|
@ -43,18 +42,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
// Failing: See https://github.com/elastic/kibana/issues/139762
|
||||
describe.skip('dashboard state', function describeIndexTests() {
|
||||
// Used to track flag before and after reset
|
||||
let isNewChartsLibraryEnabled = true;
|
||||
|
||||
before(async function () {
|
||||
isNewChartsLibraryEnabled = await PageObjects.visChart.isNewChartsLibraryEnabled();
|
||||
await PageObjects.dashboard.initTests();
|
||||
await PageObjects.dashboard.preserveCrossAppState();
|
||||
|
||||
if (!isNewChartsLibraryEnabled) {
|
||||
await kibanaServer.uiSettings.update({
|
||||
'visualization:visualize:legacyPieChartsLibrary': true,
|
||||
});
|
||||
}
|
||||
await browser.refresh();
|
||||
});
|
||||
|
||||
|
@ -288,9 +280,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
it('resets a pie slice color to the original when removed', async function () {
|
||||
const currentUrl = await getUrlFromShare();
|
||||
const newUrl = isNewChartsLibraryEnabled
|
||||
? currentUrl.replace(`'80000':%23FFFFFF`, '')
|
||||
: currentUrl.replace(`vis:(colors:('80,000':%23FFFFFF))`, '');
|
||||
const newUrl = currentUrl.replace(`'80000':%23FFFFFF`, '');
|
||||
|
||||
await hardRefresh(newUrl);
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
|
|
@ -11,38 +11,36 @@ import { FtrProviderContext } from '../../../ftr_provider_context';
|
|||
export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
||||
const browser = getService('browser');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
|
||||
async function loadLogstash() {
|
||||
await browser.setWindowSize(1200, 900);
|
||||
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional');
|
||||
}
|
||||
|
||||
async function unloadLogstash() {
|
||||
async function loadCurrentData() {
|
||||
await browser.setWindowSize(1300, 900);
|
||||
await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional');
|
||||
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/dashboard/current/data');
|
||||
}
|
||||
|
||||
describe('dashboard app - group 5', function () {
|
||||
// TODO: Remove when vislib is removed
|
||||
// https://github.com/elastic/kibana/issues/56143
|
||||
describe('old charts library', function () {
|
||||
before(async () => {
|
||||
await loadLogstash();
|
||||
await kibanaServer.uiSettings.update({
|
||||
'visualization:visualize:legacyPieChartsLibrary': true,
|
||||
});
|
||||
await browser.refresh();
|
||||
});
|
||||
async function unloadCurrentData() {
|
||||
await esArchiver.unload('test/functional/fixtures/es_archiver/dashboard/current/data');
|
||||
}
|
||||
|
||||
after(async () => {
|
||||
await unloadLogstash();
|
||||
await kibanaServer.uiSettings.update({
|
||||
'visualization:visualize:legacyPieChartsLibrary': false,
|
||||
});
|
||||
await browser.refresh();
|
||||
});
|
||||
describe('dashboard app - group 1', function () {
|
||||
before(loadCurrentData);
|
||||
after(unloadCurrentData);
|
||||
|
||||
loadTestFile(require.resolve('../group3/dashboard_state'));
|
||||
});
|
||||
// This has to be first since the other tests create some embeddables as side affects and our counting assumes
|
||||
// a fresh index.
|
||||
loadTestFile(require.resolve('./empty_dashboard'));
|
||||
loadTestFile(require.resolve('./dashboard_options'));
|
||||
loadTestFile(require.resolve('./data_shared_attributes'));
|
||||
loadTestFile(require.resolve('./share'));
|
||||
loadTestFile(require.resolve('./embed_mode'));
|
||||
loadTestFile(require.resolve('./dashboard_back_button'));
|
||||
loadTestFile(require.resolve('./dashboard_error_handling'));
|
||||
loadTestFile(require.resolve('./legacy_urls'));
|
||||
loadTestFile(require.resolve('./saved_search_embeddable'));
|
||||
|
||||
// Note: This one must be last because it unloads some data for one of its tests!
|
||||
// No, this isn't ideal, but loading/unloading takes so much time and these are all bunched
|
||||
// to improve efficiency...
|
||||
loadTestFile(require.resolve('./dashboard_query_bar'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
/*
|
||||
* 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 { FtrConfigProviderContext } from '@kbn/test';
|
||||
|
||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const functionalConfig = await readConfigFile(require.resolve('../../../config.base.js'));
|
||||
|
||||
return {
|
||||
...functionalConfig.getAll(),
|
||||
testFiles: [require.resolve('.')],
|
||||
};
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
* 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 { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
||||
const browser = getService('browser');
|
||||
const esArchiver = getService('esArchiver');
|
||||
|
||||
async function loadCurrentData() {
|
||||
await browser.setWindowSize(1300, 900);
|
||||
await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional');
|
||||
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/dashboard/current/data');
|
||||
}
|
||||
|
||||
async function unloadCurrentData() {
|
||||
await esArchiver.unload('test/functional/fixtures/es_archiver/dashboard/current/data');
|
||||
}
|
||||
|
||||
describe('dashboard app - group 1', function () {
|
||||
before(loadCurrentData);
|
||||
after(unloadCurrentData);
|
||||
|
||||
// This has to be first since the other tests create some embeddables as side affects and our counting assumes
|
||||
// a fresh index.
|
||||
loadTestFile(require.resolve('./empty_dashboard'));
|
||||
loadTestFile(require.resolve('./dashboard_options'));
|
||||
loadTestFile(require.resolve('./data_shared_attributes'));
|
||||
loadTestFile(require.resolve('./share'));
|
||||
loadTestFile(require.resolve('./embed_mode'));
|
||||
loadTestFile(require.resolve('./dashboard_back_button'));
|
||||
loadTestFile(require.resolve('./dashboard_error_handling'));
|
||||
loadTestFile(require.resolve('./legacy_urls'));
|
||||
loadTestFile(require.resolve('./saved_search_embeddable'));
|
||||
|
||||
// Note: This one must be last because it unloads some data for one of its tests!
|
||||
// No, this isn't ideal, but loading/unloading takes so much time and these are all bunched
|
||||
// to improve efficiency...
|
||||
loadTestFile(require.resolve('./dashboard_query_bar'));
|
||||
});
|
||||
}
|
|
@ -17,7 +17,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
const retry = getService('retry');
|
||||
const security = getService('security');
|
||||
const config = getService('config');
|
||||
const browser = getService('browser');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const PageObjects = getPageObjects([
|
||||
'console',
|
||||
|
@ -44,21 +43,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
// order they are added.
|
||||
let aggIndex = 1;
|
||||
// Used to track flag before and after reset
|
||||
let isNewChartsLibraryEnabled = true;
|
||||
|
||||
before(async function () {
|
||||
log.debug('https://www.elastic.co/guide/en/kibana/current/tutorial-load-dataset.html');
|
||||
isNewChartsLibraryEnabled = await PageObjects.visChart.isNewChartsLibraryEnabled();
|
||||
await security.testUser.setRoles(['kibana_admin', 'test_shakespeare_reader']);
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
log.debug('Load shakespeare data');
|
||||
|
||||
if (!isNewChartsLibraryEnabled) {
|
||||
await kibanaServer.uiSettings.update({
|
||||
'visualization:visualize:legacyPieChartsLibrary': true,
|
||||
});
|
||||
await browser.refresh();
|
||||
}
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
|
|
|
@ -10,7 +10,6 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
|||
|
||||
export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
||||
const browser = getService('browser');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const config = getService('config');
|
||||
const esNode = config.get('esTestCluster.ccs')
|
||||
? getService('remoteEsArchiver' as 'esArchiver')
|
||||
|
@ -26,25 +25,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
|||
await esNode.unload('test/functional/fixtures/es_archiver/getting_started/shakespeare');
|
||||
});
|
||||
|
||||
// TODO: Remove when vislib is removed
|
||||
describe('old charts library', function () {
|
||||
before(async () => {
|
||||
await kibanaServer.uiSettings.update({
|
||||
'visualization:visualize:legacyPieChartsLibrary': true,
|
||||
});
|
||||
await browser.refresh();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await kibanaServer.uiSettings.update({
|
||||
'visualization:visualize:legacyPieChartsLibrary': false,
|
||||
});
|
||||
await browser.refresh();
|
||||
});
|
||||
|
||||
loadTestFile(require.resolve('./_shakespeare'));
|
||||
});
|
||||
|
||||
describe('new charts library', () => {
|
||||
loadTestFile(require.resolve('./_shakespeare'));
|
||||
});
|
||||
|
|
|
@ -22,17 +22,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
|||
|
||||
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional');
|
||||
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/long_window_logstash');
|
||||
|
||||
await kibanaServer.uiSettings.update({
|
||||
'visualization:visualize:legacyPieChartsLibrary': true,
|
||||
});
|
||||
await browser.refresh();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await kibanaServer.uiSettings.update({
|
||||
'visualization:visualize:legacyPieChartsLibrary': false,
|
||||
});
|
||||
await browser.refresh();
|
||||
});
|
||||
|
||||
|
|
|
@ -59,7 +59,6 @@ export class VisualizePageObject extends FtrService {
|
|||
await this.kibanaServer.uiSettings.replace({
|
||||
defaultIndex: this.defaultIndexString,
|
||||
[FORMATS_UI_SETTINGS.FORMAT_BYTES_DEFAULT_PATTERN]: '0,0.[000]b',
|
||||
'visualization:visualize:legacyPieChartsLibrary': !isNewLibrary,
|
||||
'visualization:visualize:legacyHeatmapChartsLibrary': !isNewLibrary,
|
||||
'histogram:maxBars': 100,
|
||||
});
|
||||
|
|
|
@ -5966,7 +5966,6 @@
|
|||
"visTypeVislib.advancedSettings.visualization.heatmap.maxBucketsText": "Nombre maximal de groupes pouvant être renvoyés par une source de données unique. Un nombre plus élevé pourra impacter négativement les performances de rendu du navigateur",
|
||||
"visTypeVislib.advancedSettings.visualization.heatmap.maxBucketsTitle": "Nombre maximal de groupes pour la carte thermique",
|
||||
"visTypeVislib.aggResponse.allDocsTitle": "Tous les docs",
|
||||
"visTypeVislib.functions.pie.help": "Visualisation du camembert",
|
||||
"visTypeVislib.functions.vislib.help": "Visualisation Vislib",
|
||||
"visTypeVislib.vislib.errors.noResultsFoundTitle": "Résultat introuvable",
|
||||
"visTypeVislib.vislib.legend.loadingLabel": "chargement…",
|
||||
|
@ -33392,9 +33391,6 @@
|
|||
"visTypeMetric.params.style.styleTitle": "Style",
|
||||
"visTypeMetric.schemas.metricTitle": "Indicateur",
|
||||
"visTypeMetric.schemas.splitGroupTitle": "Diviser le groupe",
|
||||
"visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.deprecation": "La bibliothèque de graphiques héritée pour les camemberts dans Visualize est déclassée et ne sera plus compatible dans une prochaine version.",
|
||||
"visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.description": "Active la bibliothèque de graphiques héritée pour les camemberts dans Visualize.",
|
||||
"visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.name": "Bibliothèque de graphiques héritée pour les camemberts",
|
||||
"visTypePie.controls.truncateLabel": "Tronquer",
|
||||
"visTypePie.controls.truncateTooltip": "Nombre de caractères pour les étiquettes positionnées en dehors du graphique.",
|
||||
"visTypePie.editors.pie.decimalSliderLabel": "Nombre maximal de décimales pour les pourcentages",
|
||||
|
|
|
@ -5960,7 +5960,6 @@
|
|||
"visTypeVislib.advancedSettings.visualization.heatmap.maxBucketsText": "1つのデータソースが返せるバケットの最大数です。値が大きいとブラウザのレンダリング速度が下がる可能性があります。",
|
||||
"visTypeVislib.advancedSettings.visualization.heatmap.maxBucketsTitle": "ヒートマップの最大バケット数",
|
||||
"visTypeVislib.aggResponse.allDocsTitle": "すべてのドキュメント",
|
||||
"visTypeVislib.functions.pie.help": "パイビジュアライゼーション",
|
||||
"visTypeVislib.functions.vislib.help": "Vislib ビジュアライゼーション",
|
||||
"visTypeVislib.vislib.errors.noResultsFoundTitle": "結果が見つかりませんでした",
|
||||
"visTypeVislib.vislib.legend.loadingLabel": "読み込み中…",
|
||||
|
@ -33366,9 +33365,6 @@
|
|||
"visTypeMetric.params.style.styleTitle": "スタイル",
|
||||
"visTypeMetric.schemas.metricTitle": "メトリック",
|
||||
"visTypeMetric.schemas.splitGroupTitle": "グループを分割",
|
||||
"visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.deprecation": "Visualizeの円グラフのレガシーグラフライブラリは廃止予定であり、将来のバージョンではサポートされません。",
|
||||
"visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.description": "Visualizeで円グラフのレガシーグラフライブラリを有効にします。",
|
||||
"visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.name": "円グラフのレガシーグラフライブラリ",
|
||||
"visTypePie.controls.truncateLabel": "切り捨て",
|
||||
"visTypePie.controls.truncateTooltip": "グラフ外に配置されたラベルの文字数。",
|
||||
"visTypePie.editors.pie.decimalSliderLabel": "割合の最大小数点桁数",
|
||||
|
|
|
@ -5967,7 +5967,6 @@
|
|||
"visTypeVislib.advancedSettings.visualization.heatmap.maxBucketsText": "单个数据源可以返回的最大存储桶数目。较高的数目可能对浏览器呈现性能有负面影响",
|
||||
"visTypeVislib.advancedSettings.visualization.heatmap.maxBucketsTitle": "热图最大存储桶数",
|
||||
"visTypeVislib.aggResponse.allDocsTitle": "所有文档",
|
||||
"visTypeVislib.functions.pie.help": "饼图可视化",
|
||||
"visTypeVislib.functions.vislib.help": "Vislib 可视化",
|
||||
"visTypeVislib.vislib.errors.noResultsFoundTitle": "找不到结果",
|
||||
"visTypeVislib.vislib.legend.loadingLabel": "正在加载……",
|
||||
|
@ -33403,9 +33402,6 @@
|
|||
"visTypeMetric.params.style.styleTitle": "样式",
|
||||
"visTypeMetric.schemas.metricTitle": "指标",
|
||||
"visTypeMetric.schemas.splitGroupTitle": "拆分组",
|
||||
"visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.deprecation": "Visualize 中饼图的旧版图表库已过时,在未来版本中将不受支持。",
|
||||
"visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.description": "在 Visualize 中启用饼图的旧版图表库。",
|
||||
"visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.name": "饼图旧版图表库",
|
||||
"visTypePie.controls.truncateLabel": "截断",
|
||||
"visTypePie.controls.truncateTooltip": "标签位于图表之外的字符数。",
|
||||
"visTypePie.editors.pie.decimalSliderLabel": "百分比的最大小数位数",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue