mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[XY] Remove vis type xy renderer (#138735)
* Remove vis type xy renderer * Fix check * Remove unused translations * Some * Updated limits * Update limits Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Joe Reuter <johannes.reuter@elastic.co>
This commit is contained in:
parent
5b993bed5c
commit
bb705c8ba5
73 changed files with 28 additions and 6417 deletions
|
@ -70,7 +70,7 @@ pageLoadAssetSize:
|
|||
visTypeTimeseries: 55203
|
||||
visTypeVega: 153573
|
||||
visTypeVislib: 242838
|
||||
visTypeXy: 113478
|
||||
visTypeXy: 30000
|
||||
visualizations: 90000
|
||||
watcher: 43598
|
||||
runtimeFields: 41752
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
// TODO: https://github.com/elastic/kibana/issues/110891
|
||||
/* eslint-disable @kbn/eslint/no_export_all */
|
||||
|
||||
import { RangeSelectContext, ValueClickContext } from '@kbn/embeddable-plugin/public';
|
||||
import { ChartsPlugin } from './plugin';
|
||||
|
||||
export const plugin = () => new ChartsPlugin();
|
||||
|
@ -19,6 +20,16 @@ export * from './static';
|
|||
export { lightenColor } from './services/palettes/lighten_color';
|
||||
export { useActiveCursor } from './services/active_cursor';
|
||||
|
||||
export interface ClickTriggerEvent {
|
||||
name: 'filter';
|
||||
data: ValueClickContext['data'];
|
||||
}
|
||||
|
||||
export interface BrushTriggerEvent {
|
||||
name: 'brush';
|
||||
data: RangeSelectContext['data'];
|
||||
}
|
||||
|
||||
export type {
|
||||
CustomPaletteArguments,
|
||||
CustomPaletteState,
|
||||
|
|
|
@ -8,5 +8,4 @@
|
|||
|
||||
export * from './colors';
|
||||
export * from './components';
|
||||
export * from './utils';
|
||||
export * from '../../common/static/styles';
|
||||
|
|
|
@ -1,9 +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.
|
||||
*/
|
||||
|
||||
export * from './transform_click_event';
|
|
@ -1,282 +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 {
|
||||
XYChartSeriesIdentifier,
|
||||
GeometryValue,
|
||||
XYBrushEvent,
|
||||
Accessor,
|
||||
AccessorFn,
|
||||
Datum,
|
||||
} from '@elastic/charts';
|
||||
|
||||
import { RangeSelectContext, ValueClickContext } from '@kbn/embeddable-plugin/public';
|
||||
import { Datatable } from '@kbn/expressions-plugin/public';
|
||||
|
||||
export interface ClickTriggerEvent {
|
||||
name: 'filter';
|
||||
data: ValueClickContext['data'];
|
||||
}
|
||||
|
||||
export interface BrushTriggerEvent {
|
||||
name: 'brush';
|
||||
data: RangeSelectContext['data'];
|
||||
}
|
||||
|
||||
type AllSeriesAccessors<D = any> = Array<
|
||||
[accessor: Accessor<D> | AccessorFn<D>, value: string | number]
|
||||
>;
|
||||
|
||||
/**
|
||||
* returns accessor value from string or function accessor
|
||||
* @param datum
|
||||
* @param accessor
|
||||
*/
|
||||
function getAccessorValue<D>(datum: D, accessor: Accessor<D> | AccessorFn<D>) {
|
||||
if (typeof accessor === 'function') {
|
||||
return accessor(datum);
|
||||
}
|
||||
|
||||
return (datum as Datum)[accessor];
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a little unorthodox, but using functional accessors makes it
|
||||
* difficult to match the correct column. This creates a test object to throw
|
||||
* an error when the target id is accessed, thus matcing the target column.
|
||||
*/
|
||||
function validateAccessorId(id: string, accessor: Accessor | AccessorFn) {
|
||||
if (typeof accessor !== 'function') {
|
||||
return id === accessor;
|
||||
}
|
||||
|
||||
const matchedMessage = 'validateAccessorId matched';
|
||||
|
||||
try {
|
||||
accessor({
|
||||
get [id]() {
|
||||
throw new Error(matchedMessage);
|
||||
},
|
||||
});
|
||||
return false;
|
||||
} catch ({ message }) {
|
||||
return message === matchedMessage;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups split accessors by their accessor string or function and related value
|
||||
*
|
||||
* @param splitAccessors
|
||||
* @param splitSeriesAccessorFnMap
|
||||
*/
|
||||
const getAllSplitAccessors = (
|
||||
splitAccessors: Map<string | number, string | number>,
|
||||
splitSeriesAccessorFnMap?: Map<string | number, AccessorFn>
|
||||
): Array<[accessor: Accessor | AccessorFn, value: string | number]> =>
|
||||
[...splitAccessors.entries()].map(([key, value]) => [
|
||||
splitSeriesAccessorFnMap?.get?.(key) ?? key,
|
||||
value,
|
||||
]);
|
||||
|
||||
/**
|
||||
* Gets value from small multiple accessors
|
||||
*
|
||||
* Only handles single small multiple accessor
|
||||
*/
|
||||
function getSplitChartValue({
|
||||
smHorizontalAccessorValue,
|
||||
smVerticalAccessorValue,
|
||||
}: Pick<XYChartSeriesIdentifier, 'smHorizontalAccessorValue' | 'smVerticalAccessorValue'>):
|
||||
| string
|
||||
| number
|
||||
| undefined {
|
||||
if (smHorizontalAccessorValue !== undefined) {
|
||||
return smHorizontalAccessorValue;
|
||||
}
|
||||
|
||||
if (smVerticalAccessorValue !== undefined) {
|
||||
return smVerticalAccessorValue;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces matching column indexes
|
||||
*
|
||||
* @param xAccessor
|
||||
* @param yAccessor
|
||||
* @param splitAccessors
|
||||
*/
|
||||
const columnReducer =
|
||||
(
|
||||
xAccessor: Accessor | AccessorFn | null,
|
||||
yAccessor: Accessor | AccessorFn | null,
|
||||
splitAccessors: AllSeriesAccessors,
|
||||
splitChartAccessor?: Accessor | AccessorFn
|
||||
) =>
|
||||
(
|
||||
acc: Array<[index: number, id: string]>,
|
||||
{ id }: Datatable['columns'][number],
|
||||
index: number
|
||||
): Array<[index: number, id: string]> => {
|
||||
if (
|
||||
(xAccessor !== null && validateAccessorId(id, xAccessor)) ||
|
||||
(yAccessor !== null && validateAccessorId(id, yAccessor)) ||
|
||||
(splitChartAccessor !== undefined && validateAccessorId(id, splitChartAccessor)) ||
|
||||
splitAccessors.some(([accessor]) => validateAccessorId(id, accessor))
|
||||
) {
|
||||
acc.push([index, id]);
|
||||
}
|
||||
|
||||
return acc;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds matching row index for given accessors and geometry values
|
||||
*
|
||||
* @param geometry
|
||||
* @param xAccessor
|
||||
* @param yAccessor
|
||||
* @param splitAccessors
|
||||
*/
|
||||
const rowFindPredicate =
|
||||
(
|
||||
geometry: GeometryValue | null,
|
||||
xAccessor: Accessor | AccessorFn | null,
|
||||
yAccessor: Accessor | AccessorFn | null,
|
||||
splitAccessors: AllSeriesAccessors,
|
||||
splitChartAccessor?: Accessor | AccessorFn,
|
||||
splitChartValue?: string | number
|
||||
) =>
|
||||
(row: Datatable['rows'][number]): boolean =>
|
||||
(geometry === null ||
|
||||
(xAccessor !== null &&
|
||||
getAccessorValue(row, xAccessor) === getAccessorValue(geometry.datum, xAccessor) &&
|
||||
yAccessor !== null &&
|
||||
getAccessorValue(row, yAccessor) === getAccessorValue(geometry.datum, yAccessor) &&
|
||||
(splitChartAccessor === undefined ||
|
||||
(splitChartValue !== undefined &&
|
||||
getAccessorValue(row, splitChartAccessor) === splitChartValue)))) &&
|
||||
[...splitAccessors].every(([accessor, value]) => getAccessorValue(row, accessor) === value);
|
||||
|
||||
/**
|
||||
* Helper function to transform `@elastic/charts` click event into filter action event
|
||||
*
|
||||
* @param table
|
||||
* @param xAccessor
|
||||
* @param splitSeriesAccessorFnMap needed when using `splitSeriesAccessors` as `AccessorFn`
|
||||
* @param negate
|
||||
*/
|
||||
export const getFilterFromChartClickEventFn =
|
||||
(
|
||||
table: Datatable,
|
||||
xAccessor: Accessor | AccessorFn,
|
||||
splitSeriesAccessorFnMap?: Map<string | number, AccessorFn>,
|
||||
splitChartAccessor?: Accessor | AccessorFn,
|
||||
negate: boolean = false
|
||||
) =>
|
||||
(points: Array<[GeometryValue, XYChartSeriesIdentifier]>): ClickTriggerEvent => {
|
||||
const data: ValueClickContext['data']['data'] = [];
|
||||
|
||||
points.forEach((point) => {
|
||||
const [geometry, { yAccessor, splitAccessors }] = point;
|
||||
const splitChartValue = getSplitChartValue(point[1]);
|
||||
const allSplitAccessors = getAllSplitAccessors(splitAccessors, splitSeriesAccessorFnMap);
|
||||
const columns = table.columns.reduce<Array<[index: number, id: string]>>(
|
||||
columnReducer(xAccessor, yAccessor, allSplitAccessors, splitChartAccessor),
|
||||
[]
|
||||
);
|
||||
const row = table.rows.findIndex(
|
||||
rowFindPredicate(
|
||||
geometry,
|
||||
xAccessor,
|
||||
yAccessor,
|
||||
allSplitAccessors,
|
||||
splitChartAccessor,
|
||||
splitChartValue
|
||||
)
|
||||
);
|
||||
const newData = columns.map(([column, id]) => ({
|
||||
table,
|
||||
column,
|
||||
row,
|
||||
value: table.rows?.[row]?.[id] ?? null,
|
||||
}));
|
||||
|
||||
data.push(...newData);
|
||||
});
|
||||
|
||||
return {
|
||||
name: 'filter',
|
||||
data: {
|
||||
negate,
|
||||
data,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to get filter action event from series
|
||||
*/
|
||||
export const getFilterFromSeriesFn =
|
||||
(table: Datatable) =>
|
||||
(
|
||||
{ splitAccessors, ...rest }: XYChartSeriesIdentifier,
|
||||
splitSeriesAccessorFnMap?: Map<string | number, AccessorFn>,
|
||||
splitChartAccessor?: Accessor | AccessorFn,
|
||||
negate = false
|
||||
): ClickTriggerEvent => {
|
||||
const splitChartValue = getSplitChartValue(rest);
|
||||
const allSplitAccessors = getAllSplitAccessors(splitAccessors, splitSeriesAccessorFnMap);
|
||||
const columns = table.columns.reduce<Array<[index: number, id: string]>>(
|
||||
columnReducer(null, null, allSplitAccessors, splitChartAccessor),
|
||||
[]
|
||||
);
|
||||
const row = table.rows.findIndex(
|
||||
rowFindPredicate(null, null, null, allSplitAccessors, splitChartAccessor, splitChartValue)
|
||||
);
|
||||
const data: ValueClickContext['data']['data'] = columns.map(([column, id]) => ({
|
||||
table,
|
||||
column,
|
||||
row,
|
||||
value: table.rows?.[row]?.[id] ?? null,
|
||||
}));
|
||||
|
||||
return {
|
||||
name: 'filter',
|
||||
data: {
|
||||
negate,
|
||||
data,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to transform `@elastic/charts` brush event into brush action event
|
||||
*/
|
||||
export function getBrushFromChartBrushEventFn<D = never>(
|
||||
table: Datatable,
|
||||
xAccessor: Accessor<D> | AccessorFn<D>
|
||||
) {
|
||||
return ({ x: selectedRange }: XYBrushEvent): BrushTriggerEvent => {
|
||||
const [start, end] = selectedRange ?? [0, 0];
|
||||
const range: [number, number] = [start, end];
|
||||
const column = table.columns.findIndex(({ id }) => validateAccessorId(id, xAccessor));
|
||||
|
||||
return {
|
||||
data: {
|
||||
table,
|
||||
column,
|
||||
range,
|
||||
},
|
||||
name: 'brush',
|
||||
};
|
||||
};
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
"ui": true,
|
||||
"requiredPlugins": ["charts", "data", "expressions", "visualizations", "fieldFormats"],
|
||||
"optionalPlugins": ["usageCollection"],
|
||||
"requiredBundles": ["kibanaUtils", "visTypeXy", "visTypePie", "visTypeHeatmap", "visTypeGauge", "kibanaReact"],
|
||||
"requiredBundles": ["kibanaUtils", "visTypePie", "visTypeHeatmap", "visTypeGauge", "kibanaReact"],
|
||||
"owner": {
|
||||
"name": "Vis Editors",
|
||||
"githubTeam": "kibana-vis-editors"
|
||||
|
|
|
@ -6,11 +6,12 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { getAggId } from '@kbn/vis-type-xy-plugin/public';
|
||||
import type { Dimension } from '@kbn/vis-type-xy-plugin/public';
|
||||
|
||||
import { Point } from './_get_point';
|
||||
|
||||
const getAggId = (accessor: string) => (accessor ?? '').split('-').pop() ?? '';
|
||||
|
||||
export interface Serie {
|
||||
id: string;
|
||||
rawId: string;
|
||||
|
|
|
@ -14,8 +14,3 @@ export enum ChartType {
|
|||
Area = 'area',
|
||||
Histogram = 'histogram',
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of xy visualizations
|
||||
*/
|
||||
export type XyVisType = ChartType | 'horizontal_bar';
|
||||
|
|
|
@ -3,9 +3,8 @@
|
|||
"version": "kibana",
|
||||
"ui": true,
|
||||
"server": true,
|
||||
"requiredPlugins": ["charts", "data", "expressions", "visualizations", "fieldFormats"],
|
||||
"requiredBundles": ["kibanaUtils", "visDefaultEditor", "kibanaReact"],
|
||||
"optionalPlugins": ["usageCollection"],
|
||||
"requiredPlugins": ["charts", "visualizations", "data", "expressions"],
|
||||
"requiredBundles": ["kibanaUtils", "visDefaultEditor"],
|
||||
"extraPublicDirs": ["common/index"],
|
||||
"owner": {
|
||||
"name": "Vis Editors",
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
.xyChart__container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
|
@ -1,43 +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 React from 'react';
|
||||
import { Accessor, AccessorFn, GroupBy, SmallMultiples, Predicate } from '@elastic/charts';
|
||||
|
||||
interface ChartSplitProps {
|
||||
splitColumnAccessor?: Accessor | AccessorFn;
|
||||
splitRowAccessor?: Accessor | AccessorFn;
|
||||
}
|
||||
|
||||
const CHART_SPLIT_ID = '__chart_split__';
|
||||
|
||||
export const ChartSplit = ({ splitColumnAccessor, splitRowAccessor }: ChartSplitProps) => {
|
||||
if (!splitColumnAccessor && !splitRowAccessor) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<GroupBy
|
||||
id={CHART_SPLIT_ID}
|
||||
by={(spec, datum) => {
|
||||
const splitTypeAccessor = splitColumnAccessor || splitRowAccessor;
|
||||
if (splitTypeAccessor) {
|
||||
return typeof splitTypeAccessor === 'function'
|
||||
? splitTypeAccessor(datum)
|
||||
: datum[splitTypeAccessor];
|
||||
}
|
||||
return spec.id;
|
||||
}}
|
||||
sort={Predicate.DataIndex}
|
||||
/>
|
||||
<SmallMultiples
|
||||
splitVertically={splitRowAccessor ? CHART_SPLIT_ID : undefined}
|
||||
splitHorizontally={splitColumnAccessor ? CHART_SPLIT_ID : undefined}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,34 +0,0 @@
|
|||
.detailedTooltip {
|
||||
@include euiToolTipStyle('s');
|
||||
pointer-events: none;
|
||||
max-width: $euiSizeXL * 10;
|
||||
overflow: hidden;
|
||||
padding: $euiSizeS;
|
||||
|
||||
table {
|
||||
td,
|
||||
th {
|
||||
text-align: left;
|
||||
padding: $euiSizeXS;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.detailedTooltip__header {
|
||||
> :last-child {
|
||||
margin-bottom: $euiSizeS;
|
||||
}
|
||||
}
|
||||
|
||||
.detailedTooltip__labelContainer,
|
||||
.detailedTooltip__valueContainer {
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.detailedTooltip__label {
|
||||
font-weight: $euiFontWeightMedium;
|
||||
color: shade($euiColorGhost, 20%);
|
||||
}
|
|
@ -1,187 +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.
|
||||
*/
|
||||
|
||||
export const aspects = {
|
||||
x: {
|
||||
accessor: 'col-0-3',
|
||||
column: 0,
|
||||
title: 'timestamp per 3 hours',
|
||||
format: {
|
||||
id: 'date',
|
||||
params: {
|
||||
pattern: 'YYYY-MM-DD HH:mm',
|
||||
},
|
||||
},
|
||||
aggType: 'date_histogram',
|
||||
aggId: '3',
|
||||
params: {
|
||||
date: true,
|
||||
intervalESUnit: 'h',
|
||||
intervalESValue: 3,
|
||||
interval: 10800000,
|
||||
format: 'YYYY-MM-DD HH:mm',
|
||||
},
|
||||
},
|
||||
y: [
|
||||
{
|
||||
accessor: 'col-1-1',
|
||||
column: 1,
|
||||
title: 'Count',
|
||||
format: {
|
||||
id: 'number',
|
||||
},
|
||||
aggType: 'count',
|
||||
aggId: '1',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const aspectsWithSplitColumn = {
|
||||
x: {
|
||||
accessor: 'col-0-3',
|
||||
column: 0,
|
||||
title: 'timestamp per 3 hours',
|
||||
format: {
|
||||
id: 'date',
|
||||
params: {
|
||||
pattern: 'YYYY-MM-DD HH:mm',
|
||||
},
|
||||
},
|
||||
aggType: 'date_histogram',
|
||||
aggId: '3',
|
||||
params: {
|
||||
date: true,
|
||||
intervalESUnit: 'h',
|
||||
intervalESValue: 3,
|
||||
interval: 10800000,
|
||||
format: 'YYYY-MM-DD HH:mm',
|
||||
},
|
||||
},
|
||||
y: [
|
||||
{
|
||||
accessor: 'col-2-1',
|
||||
column: 2,
|
||||
title: 'Count',
|
||||
format: {
|
||||
id: 'number',
|
||||
},
|
||||
aggType: 'count',
|
||||
aggId: '1',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
splitColumn: {
|
||||
accessor: 'col-1-4',
|
||||
column: 1,
|
||||
title: 'Cancelled: Descending',
|
||||
format: {
|
||||
id: 'terms',
|
||||
params: {
|
||||
id: 'boolean',
|
||||
otherBucketLabel: 'Other',
|
||||
missingBucketLabel: 'Missing',
|
||||
},
|
||||
},
|
||||
aggType: 'terms',
|
||||
aggId: '4',
|
||||
params: {},
|
||||
},
|
||||
};
|
||||
|
||||
export const aspectsWithSplitRow = {
|
||||
x: {
|
||||
accessor: 'col-0-3',
|
||||
column: 0,
|
||||
title: 'timestamp per 3 hours',
|
||||
format: {
|
||||
id: 'date',
|
||||
params: {
|
||||
pattern: 'YYYY-MM-DD HH:mm',
|
||||
},
|
||||
},
|
||||
aggType: 'date_histogram',
|
||||
aggId: '3',
|
||||
params: {
|
||||
date: true,
|
||||
intervalESUnit: 'h',
|
||||
intervalESValue: 3,
|
||||
interval: 10800000,
|
||||
format: 'YYYY-MM-DD HH:mm',
|
||||
},
|
||||
},
|
||||
y: [
|
||||
{
|
||||
accessor: 'col-3-1',
|
||||
column: 2,
|
||||
title: 'Count',
|
||||
format: {
|
||||
id: 'number',
|
||||
},
|
||||
aggType: 'count',
|
||||
aggId: '1',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
splitRow: {
|
||||
accessor: 'col-1-5',
|
||||
column: 1,
|
||||
title: 'Carrier: Descending',
|
||||
format: {
|
||||
id: 'terms',
|
||||
params: {
|
||||
id: 'string',
|
||||
otherBucketLabel: 'Other',
|
||||
missingBucketLabel: 'Missing',
|
||||
},
|
||||
},
|
||||
aggType: 'terms',
|
||||
aggId: '4',
|
||||
params: {},
|
||||
},
|
||||
};
|
||||
|
||||
export const header = {
|
||||
seriesIdentifier: {
|
||||
key: 'groupId{__pseudo_stacked_group-ValueAxis-1__}spec{area-col-1-1}yAccessor{col-1-1}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}',
|
||||
specId: 'area-col-1-1',
|
||||
yAccessor: 'col-1-1',
|
||||
splitAccessors: {},
|
||||
seriesKeys: ['col-1-1'],
|
||||
smVerticalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__',
|
||||
smHorizontalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__',
|
||||
},
|
||||
valueAccessor: 'y1',
|
||||
label: 'Count',
|
||||
value: 1611817200000,
|
||||
formattedValue: '1611817200000',
|
||||
markValue: null,
|
||||
color: '#54b399',
|
||||
isHighlighted: false,
|
||||
isVisible: true,
|
||||
};
|
||||
|
||||
export const value = {
|
||||
seriesIdentifier: {
|
||||
key: 'groupId{__pseudo_stacked_group-ValueAxis-1__}spec{area-col-1-1}yAccessor{col-1-1}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}',
|
||||
specId: 'area-col-1-1',
|
||||
yAccessor: 'col-1-1',
|
||||
splitAccessors: [],
|
||||
seriesKeys: ['col-1-1'],
|
||||
smVerticalAccessorValue: 'kibana',
|
||||
smHorizontalAccessorValue: 'false',
|
||||
},
|
||||
valueAccessor: 'y1',
|
||||
label: 'Count',
|
||||
value: 52,
|
||||
formattedValue: '52',
|
||||
markValue: null,
|
||||
color: '#54b399',
|
||||
isHighlighted: true,
|
||||
isVisible: true,
|
||||
};
|
|
@ -1,62 +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 { getTooltipData } from './detailed_tooltip';
|
||||
import {
|
||||
aspects,
|
||||
aspectsWithSplitColumn,
|
||||
aspectsWithSplitRow,
|
||||
header,
|
||||
value,
|
||||
} from './detailed_tooltip.mock';
|
||||
|
||||
describe('getTooltipData', () => {
|
||||
it('returns an array with the header and data information', () => {
|
||||
const tooltipData = getTooltipData(aspects, header, value);
|
||||
expect(tooltipData).toStrictEqual([
|
||||
{
|
||||
label: 'timestamp per 3 hours',
|
||||
value: '1611817200000',
|
||||
},
|
||||
{
|
||||
label: 'Count',
|
||||
value: '52',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns an array with the data information if the header is not applied', () => {
|
||||
const tooltipData = getTooltipData(aspects, null, value);
|
||||
expect(tooltipData).toStrictEqual([
|
||||
{
|
||||
label: 'Count',
|
||||
value: '52',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns an array with the split column information if it is provided', () => {
|
||||
const tooltipData = getTooltipData(aspectsWithSplitColumn, null, value);
|
||||
expect(tooltipData).toStrictEqual([
|
||||
{
|
||||
label: 'Cancelled: Descending',
|
||||
value: 'false',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns an array with the split row information if it is provided', () => {
|
||||
const tooltipData = getTooltipData(aspectsWithSplitRow, null, value);
|
||||
expect(tooltipData).toStrictEqual([
|
||||
{
|
||||
label: 'Carrier: Descending',
|
||||
value: 'kibana',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -1,146 +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 React from 'react';
|
||||
import { isNil } from 'lodash';
|
||||
|
||||
import {
|
||||
CustomTooltip,
|
||||
TooltipValue,
|
||||
TooltipValueFormatter,
|
||||
XYChartSeriesIdentifier,
|
||||
} from '@elastic/charts';
|
||||
|
||||
import { Aspects } from '../types';
|
||||
|
||||
import './_detailed_tooltip.scss';
|
||||
import { COMPLEX_SPLIT_ACCESSOR, isRangeAggType } from '../utils/accessors';
|
||||
|
||||
interface TooltipData {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export const getTooltipData = (
|
||||
aspects: Aspects,
|
||||
header: TooltipValue | null,
|
||||
value: TooltipValue
|
||||
): TooltipData[] => {
|
||||
const data: TooltipData[] = [];
|
||||
|
||||
if (header) {
|
||||
const xFormatter = isRangeAggType(aspects.x.aggType) ? null : aspects.x.formatter;
|
||||
data.push({
|
||||
label: aspects.x.title,
|
||||
value: xFormatter ? xFormatter(header.value) : `${header.value}`,
|
||||
});
|
||||
}
|
||||
|
||||
const valueSeries = value.seriesIdentifier as XYChartSeriesIdentifier;
|
||||
const yAccessor = aspects.y.find(({ accessor }) => accessor === valueSeries.yAccessor) ?? null;
|
||||
|
||||
if (yAccessor) {
|
||||
data.push({
|
||||
label: yAccessor.title,
|
||||
value: yAccessor.formatter ? yAccessor.formatter(value.value) : `${value.value}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (aspects.z && !isNil(value.markValue)) {
|
||||
data.push({
|
||||
label: aspects.z.title,
|
||||
value: aspects.z.formatter ? aspects.z.formatter(value.markValue) : `${value.markValue}`,
|
||||
});
|
||||
}
|
||||
|
||||
valueSeries.splitAccessors.forEach((splitValue, key) => {
|
||||
const split = (aspects.series ?? []).find(({ accessor }, i) => {
|
||||
return accessor === key || key === `${COMPLEX_SPLIT_ACCESSOR}::${i}`;
|
||||
});
|
||||
|
||||
if (split) {
|
||||
data.push({
|
||||
label: split?.title,
|
||||
value:
|
||||
split?.formatter && !key.toString().startsWith(COMPLEX_SPLIT_ACCESSOR)
|
||||
? split?.formatter(splitValue)
|
||||
: `${splitValue}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (
|
||||
aspects.splitColumn &&
|
||||
valueSeries.smHorizontalAccessorValue !== undefined &&
|
||||
valueSeries.smHorizontalAccessorValue !== undefined
|
||||
) {
|
||||
data.push({
|
||||
label: aspects.splitColumn.title,
|
||||
value: `${valueSeries.smHorizontalAccessorValue}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
aspects.splitRow &&
|
||||
valueSeries.smVerticalAccessorValue !== undefined &&
|
||||
valueSeries.smVerticalAccessorValue !== undefined
|
||||
) {
|
||||
data.push({
|
||||
label: aspects.splitRow.title,
|
||||
value: `${valueSeries.smVerticalAccessorValue}`,
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const renderData = ({ label, value }: TooltipData, index: number) => {
|
||||
return label && value ? (
|
||||
<tr key={label + value + index}>
|
||||
<td className="detailedTooltip__label">
|
||||
<div className="detailedTooltip__labelContainer">{label}</div>
|
||||
</td>
|
||||
|
||||
<td className="detailedTooltip__value">
|
||||
<div className="detailedTooltip__valueContainer">{value}</div>
|
||||
</td>
|
||||
</tr>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export const getDetailedTooltip =
|
||||
(aspects: Aspects) =>
|
||||
(headerFormatter?: TooltipValueFormatter): CustomTooltip => {
|
||||
return function DetailedTooltip({ header, values }) {
|
||||
// Note: first value is not necessarily the closest value
|
||||
// To be fixed with https://github.com/elastic/elastic-charts/issues/835
|
||||
// TODO: Allow multiple values to be displayed in tooltip
|
||||
const highlightedValue = values.find(({ isHighlighted }) => isHighlighted);
|
||||
|
||||
if (!highlightedValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tooltipData = getTooltipData(aspects, header, highlightedValue);
|
||||
|
||||
if (tooltipData.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="detailedTooltip">
|
||||
{headerFormatter && header && (
|
||||
<div className="detailedTooltip__header">{headerFormatter(header)}</div>
|
||||
)}
|
||||
<table>
|
||||
<tbody>{tooltipData.map(renderData)}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
|
@ -1,13 +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.
|
||||
*/
|
||||
|
||||
export { XYAxis } from './xy_axis';
|
||||
export { XYEndzones } from './xy_endzones';
|
||||
export { XYCurrentTime } from './xy_current_time';
|
||||
export { XYSettings } from './xy_settings';
|
||||
export { XYThresholdLine } from './xy_threshold_line';
|
|
@ -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 React, { FC } from 'react';
|
||||
|
||||
import { Axis } from '@elastic/charts';
|
||||
|
||||
import { AxisConfig } from '../types';
|
||||
|
||||
type XYAxisPros = AxisConfig<any>;
|
||||
|
||||
export const XYAxis: FC<XYAxisPros> = ({
|
||||
id,
|
||||
title,
|
||||
show,
|
||||
position,
|
||||
groupId,
|
||||
grid,
|
||||
ticks,
|
||||
domain,
|
||||
style,
|
||||
integersOnly,
|
||||
timeAxisLayerCount,
|
||||
}) => (
|
||||
<Axis
|
||||
id={`${id}__axis`}
|
||||
groupId={groupId}
|
||||
hide={!show}
|
||||
title={title}
|
||||
style={style}
|
||||
domain={domain}
|
||||
position={position}
|
||||
integersOnly={integersOnly}
|
||||
showGridLines={grid?.show}
|
||||
tickFormat={ticks?.formatter}
|
||||
labelFormat={ticks?.labelFormatter}
|
||||
showOverlappingLabels={ticks?.showOverlappingLabels}
|
||||
showDuplicatedTicks={ticks?.showDuplicates}
|
||||
timeAxisLayerCount={timeAxisLayerCount}
|
||||
/>
|
||||
);
|
|
@ -1,26 +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 React, { FC } from 'react';
|
||||
import { DomainRange } from '@elastic/charts';
|
||||
import { CurrentTime } from '@kbn/charts-plugin/public';
|
||||
|
||||
interface XYCurrentTime {
|
||||
enabled: boolean;
|
||||
isDarkMode: boolean;
|
||||
domain?: DomainRange;
|
||||
}
|
||||
|
||||
export const XYCurrentTime: FC<XYCurrentTime> = ({ enabled, isDarkMode, domain }) => {
|
||||
if (!enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const domainEnd = domain && 'max' in domain ? domain.max : undefined;
|
||||
return <CurrentTime isDarkMode={isDarkMode} domainEnd={domainEnd} />;
|
||||
};
|
|
@ -1,57 +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 React, { FC } from 'react';
|
||||
|
||||
import { DomainRange } from '@elastic/charts';
|
||||
|
||||
import { Endzones } from '@kbn/charts-plugin/public';
|
||||
|
||||
interface XYEndzones {
|
||||
enabled: boolean;
|
||||
isDarkMode: boolean;
|
||||
isFullBin: boolean;
|
||||
hideTooltips?: boolean;
|
||||
domain?: DomainRange;
|
||||
adjustedDomain?: DomainRange;
|
||||
}
|
||||
|
||||
export const XYEndzones: FC<XYEndzones> = ({
|
||||
enabled,
|
||||
isDarkMode,
|
||||
isFullBin,
|
||||
hideTooltips,
|
||||
domain,
|
||||
adjustedDomain,
|
||||
}) => {
|
||||
if (
|
||||
enabled &&
|
||||
domain &&
|
||||
adjustedDomain &&
|
||||
'min' in domain &&
|
||||
'max' in domain &&
|
||||
domain.minInterval !== undefined &&
|
||||
'min' in adjustedDomain &&
|
||||
'max' in adjustedDomain
|
||||
) {
|
||||
return (
|
||||
<Endzones
|
||||
isFullBin={isFullBin}
|
||||
isDarkMode={isDarkMode}
|
||||
domainStart={domain.min}
|
||||
domainEnd={domain.max}
|
||||
interval={domain.minInterval}
|
||||
domainMin={adjustedDomain.min}
|
||||
domainMax={adjustedDomain.max}
|
||||
hideTooltips={hideTooltips}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
|
@ -1,195 +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 React, { FC } from 'react';
|
||||
|
||||
import {
|
||||
Direction,
|
||||
Settings,
|
||||
SettingsProps,
|
||||
DomainRange,
|
||||
Position,
|
||||
PartialTheme,
|
||||
ElementClickListener,
|
||||
BrushEndListener,
|
||||
RenderChangeListener,
|
||||
LegendAction,
|
||||
LegendColorPicker,
|
||||
TooltipProps,
|
||||
TickFormatter,
|
||||
VerticalAlignment,
|
||||
HorizontalAlignment,
|
||||
} from '@elastic/charts';
|
||||
|
||||
import { renderEndzoneTooltip } from '@kbn/charts-plugin/public';
|
||||
|
||||
import { getThemeService } from '../services';
|
||||
import { VisConfig } from '../types';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
/**
|
||||
* Flag used to enable debugState on elastic charts
|
||||
*/
|
||||
_echDebugStateFlag?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
type XYSettingsProps = Pick<
|
||||
VisConfig,
|
||||
| 'markSizeRatio'
|
||||
| 'rotation'
|
||||
| 'enableHistogramMode'
|
||||
| 'tooltip'
|
||||
| 'isTimeChart'
|
||||
| 'xAxis'
|
||||
| 'orderBucketsBySum'
|
||||
> & {
|
||||
onPointerUpdate: SettingsProps['onPointerUpdate'];
|
||||
externalPointerEvents: SettingsProps['externalPointerEvents'];
|
||||
xDomain?: DomainRange;
|
||||
adjustedXDomain?: DomainRange;
|
||||
showLegend: boolean;
|
||||
onElementClick: ElementClickListener;
|
||||
onBrushEnd?: BrushEndListener;
|
||||
onRenderChange: RenderChangeListener;
|
||||
legendAction?: LegendAction;
|
||||
legendColorPicker: LegendColorPicker;
|
||||
legendPosition: Position;
|
||||
truncateLegend: boolean;
|
||||
maxLegendLines: number;
|
||||
legendSize?: number;
|
||||
ariaLabel?: string;
|
||||
};
|
||||
|
||||
function getValueLabelsStyling() {
|
||||
const VALUE_LABELS_MAX_FONTSIZE = 12;
|
||||
const VALUE_LABELS_MIN_FONTSIZE = 10;
|
||||
|
||||
return {
|
||||
displayValue: {
|
||||
fontSize: { min: VALUE_LABELS_MIN_FONTSIZE, max: VALUE_LABELS_MAX_FONTSIZE },
|
||||
alignment: { horizontal: HorizontalAlignment.Center, vertical: VerticalAlignment.Middle },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const XYSettings: FC<XYSettingsProps> = ({
|
||||
markSizeRatio,
|
||||
rotation,
|
||||
enableHistogramMode,
|
||||
tooltip,
|
||||
isTimeChart,
|
||||
xAxis,
|
||||
orderBucketsBySum,
|
||||
xDomain,
|
||||
adjustedXDomain,
|
||||
showLegend,
|
||||
onElementClick,
|
||||
onPointerUpdate,
|
||||
externalPointerEvents,
|
||||
onBrushEnd,
|
||||
onRenderChange,
|
||||
legendAction,
|
||||
legendColorPicker,
|
||||
legendPosition,
|
||||
maxLegendLines,
|
||||
truncateLegend,
|
||||
legendSize,
|
||||
ariaLabel,
|
||||
}) => {
|
||||
const themeService = getThemeService();
|
||||
const theme = themeService.useChartsTheme();
|
||||
const baseTheme = themeService.useChartsBaseTheme();
|
||||
const valueLabelsStyling = getValueLabelsStyling();
|
||||
|
||||
const themeOverrides: PartialTheme = {
|
||||
markSizeRatio,
|
||||
barSeriesStyle: {
|
||||
...valueLabelsStyling,
|
||||
},
|
||||
crosshair: {
|
||||
...theme.crosshair,
|
||||
},
|
||||
legend: {
|
||||
labelOptions: { maxLines: truncateLegend ? maxLegendLines ?? 1 : 0 },
|
||||
},
|
||||
axes: {
|
||||
axisTitle: {
|
||||
padding: {
|
||||
outer: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
chartMargins:
|
||||
legendPosition === Position.Top || legendPosition === Position.Right
|
||||
? {
|
||||
bottom: (theme.chartMargins?.bottom ?? 0) + 10,
|
||||
}
|
||||
: {
|
||||
right: (theme.chartMargins?.right ?? 0) + 10,
|
||||
},
|
||||
};
|
||||
|
||||
const headerValueFormatter: TickFormatter<any> | undefined = xAxis.ticks?.formatter
|
||||
? (value) => xAxis.ticks?.formatter?.(value) ?? ''
|
||||
: undefined;
|
||||
const headerFormatter =
|
||||
isTimeChart && xDomain && adjustedXDomain
|
||||
? renderEndzoneTooltip(
|
||||
xDomain.minInterval,
|
||||
'min' in xDomain ? xDomain.min : undefined,
|
||||
'max' in xDomain ? xDomain.max : undefined,
|
||||
headerValueFormatter,
|
||||
!tooltip.detailedTooltip
|
||||
)
|
||||
: headerValueFormatter &&
|
||||
(tooltip.detailedTooltip ? undefined : ({ value }: any) => headerValueFormatter(value));
|
||||
|
||||
const boundary = document.getElementById('app-fixed-viewport') ?? undefined;
|
||||
const tooltipProps: TooltipProps = tooltip.detailedTooltip
|
||||
? {
|
||||
...tooltip,
|
||||
boundary,
|
||||
customTooltip: tooltip.detailedTooltip(headerFormatter),
|
||||
headerFormatter: undefined,
|
||||
}
|
||||
: { ...tooltip, boundary, headerFormatter };
|
||||
|
||||
return (
|
||||
<Settings
|
||||
debugState={window._echDebugStateFlag ?? false}
|
||||
onPointerUpdate={onPointerUpdate}
|
||||
externalPointerEvents={externalPointerEvents}
|
||||
xDomain={adjustedXDomain}
|
||||
rotation={rotation}
|
||||
theme={[themeOverrides, theme]}
|
||||
baseTheme={baseTheme}
|
||||
showLegend={showLegend}
|
||||
legendPosition={legendPosition}
|
||||
legendSize={legendSize}
|
||||
allowBrushingLastHistogramBin={isTimeChart}
|
||||
roundHistogramBrushValues={enableHistogramMode && !isTimeChart}
|
||||
legendColorPicker={legendColorPicker}
|
||||
onElementClick={onElementClick}
|
||||
onBrushEnd={onBrushEnd}
|
||||
onRenderChange={onRenderChange}
|
||||
legendAction={legendAction}
|
||||
tooltip={tooltipProps}
|
||||
ariaLabel={ariaLabel}
|
||||
ariaUseDefaultSummary={!ariaLabel}
|
||||
orderOrdinalBinsBy={
|
||||
orderBucketsBySum
|
||||
? {
|
||||
direction: Direction.Descending,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -1,47 +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 React, { FC } from 'react';
|
||||
|
||||
import { AnnotationDomainType, LineAnnotation } from '@elastic/charts';
|
||||
|
||||
import { ThresholdLineConfig } from '../types';
|
||||
|
||||
type XYThresholdLineProps = ThresholdLineConfig & {
|
||||
groupId?: string;
|
||||
};
|
||||
|
||||
export const XYThresholdLine: FC<XYThresholdLineProps> = ({
|
||||
show,
|
||||
value: dataValue,
|
||||
color,
|
||||
width,
|
||||
groupId,
|
||||
dash,
|
||||
}) => {
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<LineAnnotation
|
||||
id="__threshold_line__"
|
||||
groupId={groupId}
|
||||
domainType={AnnotationDomainType.YDomain}
|
||||
dataValues={[{ dataValue }]}
|
||||
style={{
|
||||
line: {
|
||||
stroke: color,
|
||||
strokeWidth: width ?? 2,
|
||||
opacity: 1,
|
||||
dash,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -1,14 +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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get agg id from accessor
|
||||
*
|
||||
* For now this is determined by the esaggs column name. Could be cleaned up in the future.
|
||||
*/
|
||||
export const getAggId = (accessor: string) => (accessor ?? '').split('-').pop() ?? '';
|
|
@ -1,89 +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 { compact } from 'lodash';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { DatatableColumn } from '@kbn/expressions-plugin/public';
|
||||
|
||||
import { Aspect, Dimension, Aspects, Dimensions } from '../types';
|
||||
import { getFormatService } from '../services';
|
||||
import { getAggId } from './get_agg_id';
|
||||
|
||||
export function getEmptyAspect(): Aspect {
|
||||
return {
|
||||
accessor: null,
|
||||
aggId: null,
|
||||
aggType: null,
|
||||
title: i18n.translate('visTypeXy.aggResponse.allDocsTitle', {
|
||||
defaultMessage: 'All docs',
|
||||
}),
|
||||
params: {
|
||||
defaultValue: '_all',
|
||||
},
|
||||
};
|
||||
}
|
||||
export function getAspects(
|
||||
columns: DatatableColumn[],
|
||||
{ x, y, z, series, splitColumn, splitRow }: Dimensions
|
||||
): Aspects {
|
||||
const seriesDimensions = Array.isArray(series) || series === undefined ? series : [series];
|
||||
|
||||
return {
|
||||
x: getAspectsFromDimension(columns, x) ?? getEmptyAspect(),
|
||||
y: getAspectsFromDimension(columns, y) ?? [],
|
||||
z: z && z?.length > 0 ? getAspectsFromDimension(columns, z[0]) : undefined,
|
||||
series: getAspectsFromDimension(columns, seriesDimensions),
|
||||
splitColumn: splitColumn?.length ? getAspectsFromDimension(columns, splitColumn[0]) : undefined,
|
||||
splitRow: splitRow?.length ? getAspectsFromDimension(columns, splitRow[0]) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function getAspectsFromDimension(
|
||||
columns: DatatableColumn[],
|
||||
dimension?: Dimension | null
|
||||
): Aspect | undefined;
|
||||
function getAspectsFromDimension(
|
||||
columns: DatatableColumn[],
|
||||
dimensions?: Dimension[] | null
|
||||
): Aspect[] | undefined;
|
||||
function getAspectsFromDimension(
|
||||
columns: DatatableColumn[],
|
||||
dimensions?: Dimension | Dimension[] | null
|
||||
): Aspect[] | Aspect | undefined {
|
||||
if (!dimensions) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(dimensions)) {
|
||||
return compact(
|
||||
dimensions.map((d) => {
|
||||
const column = d && columns[d.accessor];
|
||||
return column && getAspect(column, d);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const column = columns[dimensions.accessor];
|
||||
return column && getAspect(column, dimensions);
|
||||
}
|
||||
|
||||
const getAspect = (
|
||||
{ id: accessor, name: title }: DatatableColumn,
|
||||
{ accessor: column, format, params, aggType }: Dimension
|
||||
): Aspect => ({
|
||||
accessor,
|
||||
column,
|
||||
title,
|
||||
format,
|
||||
aggType,
|
||||
aggId: getAggId(accessor),
|
||||
formatter: (value: any) => getFormatService().deserialize(format).convert(value),
|
||||
params,
|
||||
});
|
|
@ -1,78 +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 { getScale } from './get_axis';
|
||||
import type { Scale } from '../types';
|
||||
|
||||
describe('getScale', () => {
|
||||
const axisScale = {
|
||||
type: 'linear',
|
||||
mode: 'normal',
|
||||
scaleType: 'linear',
|
||||
} as Scale;
|
||||
|
||||
it('returns linear type for a number', () => {
|
||||
const format = { id: 'number' };
|
||||
const scale = getScale(axisScale, {}, format, true);
|
||||
expect(scale.type).toBe('linear');
|
||||
});
|
||||
|
||||
it('returns ordinal type for a terms aggregation on a number field', () => {
|
||||
const format = {
|
||||
id: 'terms',
|
||||
params: {
|
||||
id: 'number',
|
||||
otherBucketLabel: 'Other',
|
||||
missingBucketLabel: 'Missing',
|
||||
},
|
||||
};
|
||||
const scale = getScale(axisScale, {}, format, true);
|
||||
expect(scale.type).toBe('ordinal');
|
||||
});
|
||||
|
||||
it('returns ordinal type for a terms aggregation on a string field', () => {
|
||||
const format = {
|
||||
id: 'terms',
|
||||
params: {
|
||||
id: 'string',
|
||||
otherBucketLabel: 'Other',
|
||||
missingBucketLabel: 'Missing',
|
||||
},
|
||||
};
|
||||
const scale = getScale(axisScale, {}, format, true);
|
||||
expect(scale.type).toBe('ordinal');
|
||||
});
|
||||
|
||||
it('returns ordinal type for a range aggregation on a number field', () => {
|
||||
const format = {
|
||||
id: 'range',
|
||||
params: {
|
||||
id: 'number',
|
||||
},
|
||||
};
|
||||
const scale = getScale(axisScale, {}, format, true);
|
||||
expect(scale.type).toBe('ordinal');
|
||||
});
|
||||
|
||||
it('returns time type for a date histogram aggregation', () => {
|
||||
const format = {
|
||||
id: 'date',
|
||||
params: {
|
||||
pattern: 'HH:mm',
|
||||
},
|
||||
};
|
||||
const scale = getScale(axisScale, { date: true }, format, true);
|
||||
expect(scale.type).toBe('time');
|
||||
});
|
||||
|
||||
it('returns linear type for an histogram aggregation', () => {
|
||||
const format = { id: 'number' };
|
||||
const scale = getScale(axisScale, { interval: 1 }, format, true);
|
||||
expect(scale.type).toBe('linear');
|
||||
});
|
||||
});
|
|
@ -1,199 +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 { identity } from 'lodash';
|
||||
|
||||
import { AxisSpec, TickFormatter, YDomainRange, ScaleType as ECScaleType } from '@elastic/charts';
|
||||
|
||||
import { LabelRotation } from '@kbn/charts-plugin/public';
|
||||
import { BUCKET_TYPES } from '@kbn/data-plugin/public';
|
||||
import { MULTILAYER_TIME_AXIS_STYLE } from '@kbn/charts-plugin/common';
|
||||
|
||||
import {
|
||||
Aspect,
|
||||
CategoryAxis,
|
||||
Grid,
|
||||
AxisConfig,
|
||||
TickOptions,
|
||||
ScaleConfig,
|
||||
Scale,
|
||||
ScaleType,
|
||||
AxisType,
|
||||
XScaleType,
|
||||
YScaleType,
|
||||
SeriesParam,
|
||||
} from '../types';
|
||||
|
||||
export function getAxis<S extends XScaleType | YScaleType>(
|
||||
{ type, title: axisTitle, labels, scale: axisScale, ...axis }: CategoryAxis,
|
||||
{ categoryLines, valueAxis }: Grid,
|
||||
{ params, format, formatter, title: fallbackTitle = '', aggType }: Aspect,
|
||||
seriesParams: SeriesParam[],
|
||||
isDateHistogram = false,
|
||||
useMultiLayerTimeAxis = false,
|
||||
darkMode = false
|
||||
): AxisConfig<S> {
|
||||
const isCategoryAxis = type === AxisType.Category;
|
||||
// Hide unassigned axis, not supported in elastic charts
|
||||
// TODO: refactor when disallowing unassigned axes
|
||||
// https://github.com/elastic/kibana/issues/82752
|
||||
const show =
|
||||
(isCategoryAxis || seriesParams.some(({ valueAxis: id }) => id === axis.id)) && axis.show;
|
||||
const groupId = axis.id;
|
||||
|
||||
const grid = isCategoryAxis
|
||||
? {
|
||||
show: categoryLines,
|
||||
}
|
||||
: {
|
||||
show: valueAxis === axis.id,
|
||||
};
|
||||
// Date range formatter applied on xAccessor
|
||||
const tickFormatter =
|
||||
aggType === BUCKET_TYPES.DATE_RANGE || aggType === BUCKET_TYPES.RANGE ? identity : formatter;
|
||||
const ticks: TickOptions = {
|
||||
formatter: tickFormatter,
|
||||
labelFormatter: getLabelFormatter(labels.truncate, tickFormatter),
|
||||
show: labels.show,
|
||||
rotation: labels.rotate,
|
||||
showOverlappingLabels: !labels.filter,
|
||||
showDuplicates: !labels.filter,
|
||||
};
|
||||
const scale = getScale<S>(axisScale, params, format, isCategoryAxis);
|
||||
const title = axisTitle.text || fallbackTitle;
|
||||
const fallbackRotation =
|
||||
isCategoryAxis && isDateHistogram ? LabelRotation.Horizontal : LabelRotation.Vertical;
|
||||
|
||||
return {
|
||||
...axis,
|
||||
show,
|
||||
groupId,
|
||||
title,
|
||||
ticks,
|
||||
grid,
|
||||
scale,
|
||||
style: getAxisStyle(useMultiLayerTimeAxis, darkMode, ticks, title, fallbackRotation),
|
||||
domain: getAxisDomain(scale, isCategoryAxis),
|
||||
integersOnly: aggType === 'count',
|
||||
timeAxisLayerCount: useMultiLayerTimeAxis ? 3 : 0,
|
||||
};
|
||||
}
|
||||
|
||||
function getLabelFormatter(
|
||||
truncate?: number | null,
|
||||
formatter?: TickFormatter
|
||||
): TickFormatter | undefined {
|
||||
if (truncate === null || truncate === undefined) {
|
||||
return formatter;
|
||||
}
|
||||
|
||||
return (value: any) => {
|
||||
const finalValue = `${formatter ? formatter(value) : value}`;
|
||||
|
||||
if (finalValue.length > truncate) {
|
||||
return `${finalValue.slice(0, truncate)}...`;
|
||||
}
|
||||
|
||||
return finalValue;
|
||||
};
|
||||
}
|
||||
|
||||
function getScaleType(
|
||||
scale?: Scale,
|
||||
isNumber?: boolean,
|
||||
isTime = false,
|
||||
isHistogram = false
|
||||
): ECScaleType | undefined {
|
||||
if (isTime) return ECScaleType.Time;
|
||||
if (isHistogram) return ECScaleType.Linear;
|
||||
|
||||
if (!isNumber) {
|
||||
return ECScaleType.Ordinal;
|
||||
}
|
||||
|
||||
const type = scale?.type;
|
||||
if (type === ScaleType.SquareRoot) {
|
||||
return ECScaleType.Sqrt;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
export function getScale<S extends XScaleType | YScaleType>(
|
||||
scale: Scale,
|
||||
params: Aspect['params'],
|
||||
format: Aspect['format'],
|
||||
isCategoryAxis: boolean
|
||||
): ScaleConfig<S> {
|
||||
const type = (
|
||||
isCategoryAxis
|
||||
? getScaleType(
|
||||
scale,
|
||||
format?.id === 'number' ||
|
||||
(format?.params?.id === 'number' &&
|
||||
format?.id !== BUCKET_TYPES.RANGE &&
|
||||
format?.id !== BUCKET_TYPES.TERMS),
|
||||
'date' in params,
|
||||
'interval' in params
|
||||
)
|
||||
: getScaleType(scale, true)
|
||||
) as S;
|
||||
|
||||
return {
|
||||
...scale,
|
||||
type,
|
||||
};
|
||||
}
|
||||
|
||||
function getAxisStyle(
|
||||
isMultiLayerTimeAxis: boolean,
|
||||
darkMode: boolean,
|
||||
ticks?: TickOptions,
|
||||
title?: string,
|
||||
rotationFallback: LabelRotation = LabelRotation.Vertical
|
||||
): AxisSpec['style'] {
|
||||
return isMultiLayerTimeAxis
|
||||
? {
|
||||
...MULTILAYER_TIME_AXIS_STYLE,
|
||||
tickLabel: {
|
||||
...MULTILAYER_TIME_AXIS_STYLE.tickLabel,
|
||||
visible: Boolean(ticks?.show),
|
||||
},
|
||||
tickLine: {
|
||||
...MULTILAYER_TIME_AXIS_STYLE.tickLine,
|
||||
visible: Boolean(ticks?.show),
|
||||
},
|
||||
axisTitle: {
|
||||
visible: (title ?? '').trim().length > 0,
|
||||
},
|
||||
}
|
||||
: {
|
||||
axisTitle: {
|
||||
visible: (title ?? '').trim().length > 0,
|
||||
},
|
||||
tickLabel: {
|
||||
visible: Boolean(ticks?.show),
|
||||
rotation: -(ticks?.rotation ?? rotationFallback),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getAxisDomain<S extends XScaleType | YScaleType>(
|
||||
scale: ScaleConfig<S>,
|
||||
isCategoryAxis: boolean
|
||||
): YDomainRange | undefined {
|
||||
if (isCategoryAxis || !scale) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { min, max, defaultYExtents, boundsMargin } = scale;
|
||||
const fit = defaultYExtents;
|
||||
const padding = boundsMargin || undefined;
|
||||
|
||||
return { fit, padding, min: min ?? NaN, max: max ?? NaN };
|
||||
}
|
|
@ -1,107 +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 { getConfig } from './get_config';
|
||||
import { visData, visDataPercentile, visParamsWithTwoYAxes } from '../mocks';
|
||||
import { VisParams } from '../types';
|
||||
|
||||
// ToDo: add more tests for all the config properties
|
||||
describe('getConfig', () => {
|
||||
it('identifies it as a timeChart if the x axis has a date field', () => {
|
||||
const config = getConfig(visData, visParamsWithTwoYAxes);
|
||||
expect(config.isTimeChart).toBe(true);
|
||||
});
|
||||
|
||||
it('not adds the current time marker if the param is set to false', () => {
|
||||
const config = getConfig(visData, visParamsWithTwoYAxes);
|
||||
expect(config.showCurrentTime).toBe(false);
|
||||
});
|
||||
|
||||
it('adds the current time marker if the param is set to false', () => {
|
||||
const newVisParams = {
|
||||
...visParamsWithTwoYAxes,
|
||||
addTimeMarker: true,
|
||||
};
|
||||
const config = getConfig(visData, newVisParams);
|
||||
expect(config.showCurrentTime).toBe(true);
|
||||
});
|
||||
|
||||
it('enables the histogram mode for a date_histogram', () => {
|
||||
const config = getConfig(visData, visParamsWithTwoYAxes);
|
||||
expect(config.enableHistogramMode).toBe(true);
|
||||
});
|
||||
|
||||
it('assigns the correct formatter per y axis', () => {
|
||||
const config = getConfig(visData, visParamsWithTwoYAxes);
|
||||
expect(config.yAxes.length).toBe(2);
|
||||
expect(config.yAxes[0].ticks?.formatter).toStrictEqual(config.aspects.y[0].formatter);
|
||||
expect(config.yAxes[1].ticks?.formatter).toStrictEqual(config.aspects.y[1].formatter);
|
||||
});
|
||||
|
||||
it('assigns the correct number of yAxes if the agg is hidden', () => {
|
||||
// We have two axes but the one y dimension is hidden
|
||||
const newVisParams = {
|
||||
...visParamsWithTwoYAxes,
|
||||
dimensions: {
|
||||
...visParamsWithTwoYAxes.dimensions,
|
||||
y: [
|
||||
{
|
||||
label: 'Average memory',
|
||||
aggType: 'avg',
|
||||
params: {},
|
||||
accessor: 1,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const config = getConfig(visData, newVisParams);
|
||||
expect(config.yAxes.length).toBe(1);
|
||||
});
|
||||
|
||||
it('assigns the correct number of yAxes if the agg is Percentile', () => {
|
||||
const newVisParams = {
|
||||
...visParamsWithTwoYAxes,
|
||||
seriesParams: [
|
||||
{
|
||||
type: 'line',
|
||||
data: {
|
||||
label: 'Percentiles of bytes',
|
||||
id: '1',
|
||||
},
|
||||
drawLinesBetweenPoints: true,
|
||||
interpolate: 'linear',
|
||||
lineWidth: 2,
|
||||
mode: 'normal',
|
||||
show: true,
|
||||
showCircles: true,
|
||||
circlesRadius: 3,
|
||||
valueAxis: 'ValueAxis-1',
|
||||
},
|
||||
],
|
||||
dimensions: {
|
||||
...visParamsWithTwoYAxes.dimensions,
|
||||
y: ['1st', '5th', '25th', '50th', '75th', '95th', '99th'].map((prefix, accessor) => ({
|
||||
label: `${prefix} percentile of bytes`,
|
||||
aggType: 'percentiles',
|
||||
params: {},
|
||||
accessor,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
})),
|
||||
},
|
||||
} as VisParams;
|
||||
const config = getConfig(visDataPercentile, newVisParams);
|
||||
expect(config.yAxes.length).toBe(1);
|
||||
});
|
||||
});
|
|
@ -1,146 +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 { Fit, ScaleContinuousType } from '@elastic/charts';
|
||||
|
||||
import { Datatable } from '@kbn/expressions-plugin/public';
|
||||
import { BUCKET_TYPES } from '@kbn/data-plugin/public';
|
||||
import { DateHistogramParams } from '@kbn/visualizations-plugin/public';
|
||||
|
||||
import {
|
||||
Aspect,
|
||||
AxisConfig,
|
||||
SeriesParam,
|
||||
VisConfig,
|
||||
VisParams,
|
||||
XScaleType,
|
||||
YScaleType,
|
||||
} from '../types';
|
||||
import { getThresholdLine } from './get_threshold_line';
|
||||
import { getRotation } from './get_rotation';
|
||||
import { getTooltip } from './get_tooltip';
|
||||
import { getLegend } from './get_legend';
|
||||
import { getAxis } from './get_axis';
|
||||
import { getAspects } from './get_aspects';
|
||||
import { ChartType } from '..';
|
||||
import { getSafeId } from '../utils/accessors';
|
||||
|
||||
export function getConfig(
|
||||
table: Datatable,
|
||||
params: VisParams,
|
||||
useLegacyTimeAxis = false,
|
||||
darkMode = false
|
||||
): VisConfig {
|
||||
const {
|
||||
thresholdLine,
|
||||
orderBucketsBySum,
|
||||
addTimeMarker,
|
||||
radiusRatio,
|
||||
labels,
|
||||
fittingFunction,
|
||||
detailedTooltip,
|
||||
isVislibVis,
|
||||
fillOpacity,
|
||||
} = params;
|
||||
const aspects = getAspects(table.columns, params.dimensions);
|
||||
const tooltip = getTooltip(aspects, params);
|
||||
|
||||
const yAxes: Array<AxisConfig<ScaleContinuousType>> = [];
|
||||
|
||||
// avoid duplicates based on aggId
|
||||
const aspectVisited = new Set();
|
||||
params.dimensions.y.forEach((y) => {
|
||||
const accessor = y.accessor;
|
||||
const aspect = aspects.y.find(({ column }) => column === accessor);
|
||||
const aggId = getSafeId(aspect?.aggId);
|
||||
const serie = params.seriesParams.find(({ data: { id } }) => id === aggId);
|
||||
const valueAxis = params.valueAxes.find(({ id }) => id === serie?.valueAxis);
|
||||
if (aspect && valueAxis && !aspectVisited.has(aggId)) {
|
||||
yAxes.push(getAxis<YScaleType>(valueAxis, params.grid, aspect, params.seriesParams));
|
||||
aspectVisited.add(aggId);
|
||||
}
|
||||
});
|
||||
|
||||
const rotation = getRotation(params.categoryAxes[0]);
|
||||
|
||||
const isDateHistogram = params.dimensions.x?.aggType === BUCKET_TYPES.DATE_HISTOGRAM;
|
||||
const isHistogram = params.dimensions.x?.aggType === BUCKET_TYPES.HISTOGRAM;
|
||||
const enableHistogramMode =
|
||||
(isDateHistogram || isHistogram) &&
|
||||
shouldEnableHistogramMode(params.seriesParams, aspects.y, yAxes);
|
||||
|
||||
const useMultiLayerTimeAxis =
|
||||
enableHistogramMode && isDateHistogram && !useLegacyTimeAxis && rotation === 0;
|
||||
|
||||
const xAxis = getAxis<XScaleType>(
|
||||
params.categoryAxes[0],
|
||||
params.grid,
|
||||
aspects.x,
|
||||
params.seriesParams,
|
||||
isDateHistogram,
|
||||
useMultiLayerTimeAxis,
|
||||
darkMode
|
||||
);
|
||||
|
||||
const isTimeChart = (aspects.x.params as DateHistogramParams).date ?? false;
|
||||
|
||||
return {
|
||||
// NOTE: downscale ratio to match current vislib implementation
|
||||
markSizeRatio: radiusRatio * 0.6,
|
||||
fittingFunction: fittingFunction ?? Fit.Linear,
|
||||
fillOpacity,
|
||||
detailedTooltip,
|
||||
orderBucketsBySum,
|
||||
isTimeChart,
|
||||
isVislibVis,
|
||||
showCurrentTime: addTimeMarker && isTimeChart,
|
||||
showValueLabel: labels.show ?? false,
|
||||
enableHistogramMode,
|
||||
tooltip,
|
||||
aspects,
|
||||
xAxis,
|
||||
yAxes,
|
||||
legend: getLegend(params),
|
||||
rotation,
|
||||
thresholdLine: getThresholdLine(thresholdLine, yAxes, params.seriesParams),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* disables histogram mode for any config that has non-stacked clustered bars
|
||||
*
|
||||
* @param seriesParams
|
||||
* @param yAspects
|
||||
* @param yAxes
|
||||
*/
|
||||
const shouldEnableHistogramMode = (
|
||||
seriesParams: SeriesParam[],
|
||||
yAspects: Aspect[],
|
||||
yAxes: Array<AxisConfig<ScaleContinuousType>>
|
||||
): boolean => {
|
||||
const bars = seriesParams.filter(({ type, data: { id: paramId } }) => {
|
||||
return (
|
||||
type === ChartType.Histogram && yAspects.find(({ aggId }) => aggId === paramId) !== undefined
|
||||
);
|
||||
});
|
||||
|
||||
const groupIds = [
|
||||
...bars.reduce<Set<string>>((acc, { valueAxis: groupId, mode }) => {
|
||||
acc.add(groupId);
|
||||
return acc;
|
||||
}, new Set()),
|
||||
];
|
||||
|
||||
if (groupIds.length > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return bars.every(({ valueAxis: groupId, mode }) => {
|
||||
return mode === 'stacked';
|
||||
});
|
||||
};
|
|
@ -1,16 +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 { LegendOptions, VisParams } from '../types';
|
||||
|
||||
export function getLegend({ addLegend, legendPosition }: VisParams): LegendOptions {
|
||||
return {
|
||||
show: addLegend,
|
||||
position: legendPosition,
|
||||
};
|
||||
}
|
|
@ -1,19 +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 { Rotation } from '@elastic/charts';
|
||||
|
||||
import { CategoryAxis } from '../types';
|
||||
|
||||
export function getRotation({ position }: CategoryAxis): Rotation {
|
||||
if (position === 'left' || position === 'right') {
|
||||
return 90;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,44 +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 {
|
||||
ThresholdLineConfig,
|
||||
ThresholdLine,
|
||||
ThresholdLineStyle,
|
||||
AxisConfig,
|
||||
SeriesParam,
|
||||
YScaleType,
|
||||
} from '../types';
|
||||
|
||||
export function getThresholdLine(
|
||||
{ style, ...rest }: ThresholdLine,
|
||||
yAxes: Array<AxisConfig<YScaleType>>,
|
||||
seriesParams: SeriesParam[]
|
||||
): ThresholdLineConfig {
|
||||
const groupId = yAxes.find(({ id }) =>
|
||||
seriesParams.some(({ valueAxis }) => id === valueAxis)
|
||||
)?.groupId;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
dash: getDash(style),
|
||||
groupId,
|
||||
};
|
||||
}
|
||||
|
||||
function getDash(style: ThresholdLineStyle): number[] | undefined {
|
||||
switch (style) {
|
||||
case ThresholdLineStyle.Dashed:
|
||||
return [10, 5];
|
||||
case ThresholdLineStyle.DotDashed:
|
||||
return [20, 5, 5, 5];
|
||||
case ThresholdLineStyle.Full:
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -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 { TooltipType } from '@elastic/charts';
|
||||
|
||||
import { Aspects, VisParams, TooltipConfig } from '../types';
|
||||
import { getDetailedTooltip } from '../components/detailed_tooltip';
|
||||
|
||||
export function getTooltip(
|
||||
aspects: Aspects,
|
||||
{ addTooltip, detailedTooltip }: VisParams
|
||||
): TooltipConfig {
|
||||
return {
|
||||
type: addTooltip ? TooltipType.VerticalCursor : TooltipType.None,
|
||||
detailedTooltip: detailedTooltip ? getDetailedTooltip(aspects) : undefined,
|
||||
};
|
||||
}
|
|
@ -1,10 +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.
|
||||
*/
|
||||
|
||||
export { getConfig } from './get_config';
|
||||
export { getAggId } from './get_agg_id';
|
|
@ -1,116 +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 type {
|
||||
ExpressionFunctionDefinition,
|
||||
Datatable,
|
||||
ExpressionValueBoxed,
|
||||
} from '@kbn/expressions-plugin/public';
|
||||
import type { CategoryAxis } from '../types';
|
||||
import type { ExpressionValueScale } from './vis_scale';
|
||||
import type { ExpressionValueLabel } from './label';
|
||||
|
||||
export interface Arguments extends Omit<CategoryAxis, 'title' | 'scale' | 'labels'> {
|
||||
title?: string;
|
||||
scale: ExpressionValueScale;
|
||||
labels: ExpressionValueLabel;
|
||||
}
|
||||
|
||||
export type ExpressionValueCategoryAxis = ExpressionValueBoxed<
|
||||
'category_axis',
|
||||
{
|
||||
id: CategoryAxis['id'];
|
||||
show: CategoryAxis['show'];
|
||||
position: CategoryAxis['position'];
|
||||
axisType: CategoryAxis['type'];
|
||||
title: {
|
||||
text?: string;
|
||||
};
|
||||
labels: CategoryAxis['labels'];
|
||||
scale: CategoryAxis['scale'];
|
||||
}
|
||||
>;
|
||||
|
||||
export const categoryAxis = (): ExpressionFunctionDefinition<
|
||||
'categoryaxis',
|
||||
Datatable | null,
|
||||
Arguments,
|
||||
ExpressionValueCategoryAxis
|
||||
> => ({
|
||||
name: 'categoryaxis',
|
||||
help: i18n.translate('visTypeXy.function.categoryAxis.help', {
|
||||
defaultMessage: 'Generates category axis object',
|
||||
}),
|
||||
type: 'category_axis',
|
||||
args: {
|
||||
id: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.categoryAxis.id.help', {
|
||||
defaultMessage: 'Id of category axis',
|
||||
}),
|
||||
required: true,
|
||||
},
|
||||
show: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('visTypeXy.function.categoryAxis.show.help', {
|
||||
defaultMessage: 'Show the category axis',
|
||||
}),
|
||||
required: true,
|
||||
},
|
||||
position: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.categoryAxis.position.help', {
|
||||
defaultMessage: 'Position of the category axis',
|
||||
}),
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.categoryAxis.type.help', {
|
||||
defaultMessage: 'Type of the category axis. Can be category or value',
|
||||
}),
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.categoryAxis.title.help', {
|
||||
defaultMessage: 'Title of the category axis',
|
||||
}),
|
||||
},
|
||||
scale: {
|
||||
types: ['vis_scale'],
|
||||
help: i18n.translate('visTypeXy.function.categoryAxis.scale.help', {
|
||||
defaultMessage: 'Scale config',
|
||||
}),
|
||||
},
|
||||
labels: {
|
||||
types: ['label'],
|
||||
help: i18n.translate('visTypeXy.function.categoryAxis.labels.help', {
|
||||
defaultMessage: 'Axis label config',
|
||||
}),
|
||||
},
|
||||
},
|
||||
fn: (context, args) => {
|
||||
return {
|
||||
type: 'category_axis',
|
||||
id: args.id,
|
||||
show: args.show,
|
||||
position: args.position,
|
||||
axisType: args.type,
|
||||
title: {
|
||||
text: args.title,
|
||||
},
|
||||
scale: {
|
||||
...args.scale,
|
||||
type: args.scale.scaleType,
|
||||
},
|
||||
labels: args.labels,
|
||||
};
|
||||
},
|
||||
});
|
|
@ -1,24 +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.
|
||||
*/
|
||||
|
||||
export { visTypeXyVisFn } from './xy_vis_fn';
|
||||
|
||||
export type { ExpressionValueCategoryAxis } from './category_axis';
|
||||
export { categoryAxis } from './category_axis';
|
||||
export type { ExpressionValueTimeMarker } from './time_marker';
|
||||
export { timeMarker } from './time_marker';
|
||||
export type { ExpressionValueValueAxis } from './value_axis';
|
||||
export { valueAxis } from './value_axis';
|
||||
export type { ExpressionValueSeriesParam } from './series_param';
|
||||
export { seriesParam } from './series_param';
|
||||
export type { ExpressionValueThresholdLine } from './threshold_line';
|
||||
export { thresholdLine } from './threshold_line';
|
||||
export type { ExpressionValueLabel } from './label';
|
||||
export { label } from './label';
|
||||
export type { ExpressionValueScale } from './vis_scale';
|
||||
export { visScale } from './vis_scale';
|
|
@ -1,89 +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 type { Labels } from '@kbn/charts-plugin/public';
|
||||
import type {
|
||||
ExpressionFunctionDefinition,
|
||||
Datatable,
|
||||
ExpressionValueBoxed,
|
||||
} from '@kbn/expressions-plugin/public';
|
||||
|
||||
export type ExpressionValueLabel = ExpressionValueBoxed<
|
||||
'label',
|
||||
{
|
||||
color?: Labels['color'];
|
||||
filter?: Labels['filter'];
|
||||
overwriteColor?: Labels['overwriteColor'];
|
||||
rotate?: Labels['rotate'];
|
||||
show?: Labels['show'];
|
||||
truncate?: Labels['truncate'];
|
||||
}
|
||||
>;
|
||||
|
||||
export const label = (): ExpressionFunctionDefinition<
|
||||
'label',
|
||||
Datatable | null,
|
||||
Labels,
|
||||
ExpressionValueLabel
|
||||
> => ({
|
||||
name: 'label',
|
||||
help: i18n.translate('visTypeXy.function.label.help', {
|
||||
defaultMessage: 'Generates label object',
|
||||
}),
|
||||
type: 'label',
|
||||
args: {
|
||||
color: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.label.color.help', {
|
||||
defaultMessage: 'Color of label',
|
||||
}),
|
||||
},
|
||||
filter: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('visTypeXy.function.label.filter.help', {
|
||||
defaultMessage: 'Hides overlapping labels and duplicates on axis',
|
||||
}),
|
||||
},
|
||||
overwriteColor: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('visTypeXy.function.label.overwriteColor.help', {
|
||||
defaultMessage: 'Overwrite color',
|
||||
}),
|
||||
},
|
||||
rotate: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('visTypeXy.function.label.rotate.help', {
|
||||
defaultMessage: 'Rotate angle',
|
||||
}),
|
||||
},
|
||||
show: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('visTypeXy.function.label.show.help', {
|
||||
defaultMessage: 'Show label',
|
||||
}),
|
||||
},
|
||||
truncate: {
|
||||
types: ['number', 'null'],
|
||||
help: i18n.translate('visTypeXy.function.label.truncate.help', {
|
||||
defaultMessage: 'The number of symbols before truncating',
|
||||
}),
|
||||
},
|
||||
},
|
||||
fn: (context, args) => {
|
||||
return {
|
||||
type: 'label',
|
||||
color: args.color,
|
||||
filter: args.hasOwnProperty('filter') ? args.filter : undefined,
|
||||
overwriteColor: args.overwriteColor,
|
||||
rotate: args.rotate,
|
||||
show: args.show,
|
||||
truncate: args.truncate,
|
||||
};
|
||||
},
|
||||
});
|
|
@ -1,136 +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 type {
|
||||
ExpressionFunctionDefinition,
|
||||
Datatable,
|
||||
ExpressionValueBoxed,
|
||||
} from '@kbn/expressions-plugin/public';
|
||||
import type { SeriesParam } from '../types';
|
||||
|
||||
export interface Arguments extends Omit<SeriesParam, 'data'> {
|
||||
label: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export type ExpressionValueSeriesParam = ExpressionValueBoxed<
|
||||
'series_param',
|
||||
{
|
||||
data: { label: string; id: string };
|
||||
drawLinesBetweenPoints?: boolean;
|
||||
interpolate?: SeriesParam['interpolate'];
|
||||
lineWidth?: number;
|
||||
mode: SeriesParam['mode'];
|
||||
show: boolean;
|
||||
showCircles: boolean;
|
||||
circlesRadius: number;
|
||||
seriesParamType: SeriesParam['type'];
|
||||
valueAxis: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export const seriesParam = (): ExpressionFunctionDefinition<
|
||||
'seriesparam',
|
||||
Datatable,
|
||||
Arguments,
|
||||
ExpressionValueSeriesParam
|
||||
> => ({
|
||||
name: 'seriesparam',
|
||||
help: i18n.translate('visTypeXy.function.seriesparam.help', {
|
||||
defaultMessage: 'Generates series param object',
|
||||
}),
|
||||
type: 'series_param',
|
||||
inputTypes: ['datatable'],
|
||||
args: {
|
||||
label: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.seriesParam.label.help', {
|
||||
defaultMessage: 'Name of series param',
|
||||
}),
|
||||
required: true,
|
||||
},
|
||||
id: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.seriesParam.id.help', {
|
||||
defaultMessage: 'Id of series param',
|
||||
}),
|
||||
required: true,
|
||||
},
|
||||
drawLinesBetweenPoints: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('visTypeXy.function.seriesParam.drawLinesBetweenPoints.help', {
|
||||
defaultMessage: 'Draw lines between points',
|
||||
}),
|
||||
},
|
||||
interpolate: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.seriesParam.interpolate.help', {
|
||||
defaultMessage: 'Interpolate mode. Can be linear, cardinal or step-after',
|
||||
}),
|
||||
},
|
||||
show: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('visTypeXy.function.seriesParam.show.help', {
|
||||
defaultMessage: 'Show param',
|
||||
}),
|
||||
required: true,
|
||||
},
|
||||
lineWidth: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('visTypeXy.function.seriesParam.lineWidth.help', {
|
||||
defaultMessage: 'Width of line',
|
||||
}),
|
||||
},
|
||||
mode: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.seriesParam.mode.help', {
|
||||
defaultMessage: 'Chart mode. Can be stacked or percentage',
|
||||
}),
|
||||
},
|
||||
showCircles: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('visTypeXy.function.seriesParam.showCircles.help', {
|
||||
defaultMessage: 'Show circles',
|
||||
}),
|
||||
},
|
||||
circlesRadius: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('visTypeXy.function.seriesParam.circlesRadius.help', {
|
||||
defaultMessage: 'Defines the circles size (radius)',
|
||||
}),
|
||||
},
|
||||
type: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.seriesParam.type.help', {
|
||||
defaultMessage: 'Chart type. Can be line, area or histogram',
|
||||
}),
|
||||
},
|
||||
valueAxis: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.seriesParam.valueAxis.help', {
|
||||
defaultMessage: 'Name of value axis',
|
||||
}),
|
||||
},
|
||||
},
|
||||
fn: (context, args) => {
|
||||
return {
|
||||
type: 'series_param',
|
||||
data: { label: args.label, id: args.id },
|
||||
drawLinesBetweenPoints: args.drawLinesBetweenPoints,
|
||||
interpolate: args.interpolate,
|
||||
lineWidth: args.lineWidth,
|
||||
mode: args.mode,
|
||||
show: args.show,
|
||||
showCircles: args.showCircles,
|
||||
circlesRadius: args.circlesRadius,
|
||||
seriesParamType: args.type,
|
||||
valueAxis: args.valueAxis,
|
||||
};
|
||||
},
|
||||
});
|
|
@ -1,86 +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 type {
|
||||
ExpressionFunctionDefinition,
|
||||
Datatable,
|
||||
ExpressionValueBoxed,
|
||||
} from '@kbn/expressions-plugin/public';
|
||||
import type { ThresholdLine } from '../types';
|
||||
|
||||
export type ExpressionValueThresholdLine = ExpressionValueBoxed<
|
||||
'threshold_line',
|
||||
{
|
||||
show: ThresholdLine['show'];
|
||||
value: ThresholdLine['value'];
|
||||
width: ThresholdLine['width'];
|
||||
style: ThresholdLine['style'];
|
||||
color: ThresholdLine['color'];
|
||||
}
|
||||
>;
|
||||
|
||||
export const thresholdLine = (): ExpressionFunctionDefinition<
|
||||
'thresholdline',
|
||||
Datatable | null,
|
||||
ThresholdLine,
|
||||
ExpressionValueThresholdLine
|
||||
> => ({
|
||||
name: 'thresholdline',
|
||||
help: i18n.translate('visTypeXy.function.thresholdLine.help', {
|
||||
defaultMessage: 'Generates threshold line object',
|
||||
}),
|
||||
type: 'threshold_line',
|
||||
args: {
|
||||
show: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('visTypeXy.function.thresholdLine.show.help', {
|
||||
defaultMessage: 'Show threshould line',
|
||||
}),
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
types: ['number', 'null'],
|
||||
help: i18n.translate('visTypeXy.function.thresholdLine.value.help', {
|
||||
defaultMessage: 'Threshold value',
|
||||
}),
|
||||
required: true,
|
||||
},
|
||||
width: {
|
||||
types: ['number', 'null'],
|
||||
help: i18n.translate('visTypeXy.function.thresholdLine.width.help', {
|
||||
defaultMessage: 'Width of threshold line',
|
||||
}),
|
||||
required: true,
|
||||
},
|
||||
style: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.thresholdLine.style.help', {
|
||||
defaultMessage: 'Style of threshold line. Can be full, dashed or dot-dashed',
|
||||
}),
|
||||
required: true,
|
||||
},
|
||||
color: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.thresholdLine.color.help', {
|
||||
defaultMessage: 'Color of threshold line',
|
||||
}),
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
fn: (context, args) => {
|
||||
return {
|
||||
type: 'threshold_line',
|
||||
show: args.show,
|
||||
value: args.value,
|
||||
width: args.width,
|
||||
style: args.style,
|
||||
color: args.color,
|
||||
};
|
||||
},
|
||||
});
|
|
@ -1,82 +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 type {
|
||||
ExpressionFunctionDefinition,
|
||||
Datatable,
|
||||
ExpressionValueBoxed,
|
||||
} from '@kbn/expressions-plugin/public';
|
||||
import type { TimeMarker } from '../types';
|
||||
|
||||
export type ExpressionValueTimeMarker = ExpressionValueBoxed<
|
||||
'time_marker',
|
||||
{
|
||||
time: string;
|
||||
class?: string;
|
||||
color?: string;
|
||||
opacity?: number;
|
||||
width?: number;
|
||||
}
|
||||
>;
|
||||
|
||||
export const timeMarker = (): ExpressionFunctionDefinition<
|
||||
'timemarker',
|
||||
Datatable | null,
|
||||
TimeMarker,
|
||||
ExpressionValueTimeMarker
|
||||
> => ({
|
||||
name: 'timemarker',
|
||||
help: i18n.translate('visTypeXy.function.timemarker.help', {
|
||||
defaultMessage: 'Generates time marker object',
|
||||
}),
|
||||
type: 'time_marker',
|
||||
args: {
|
||||
time: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.timeMarker.time.help', {
|
||||
defaultMessage: 'Exact Time',
|
||||
}),
|
||||
required: true,
|
||||
},
|
||||
class: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.timeMarker.class.help', {
|
||||
defaultMessage: 'Css class name',
|
||||
}),
|
||||
},
|
||||
color: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.timeMarker.color.help', {
|
||||
defaultMessage: 'Color of time marker',
|
||||
}),
|
||||
},
|
||||
opacity: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('visTypeXy.function.timeMarker.opacity.help', {
|
||||
defaultMessage: 'Opacity of time marker',
|
||||
}),
|
||||
},
|
||||
width: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('visTypeXy.function.timeMarker.width.help', {
|
||||
defaultMessage: 'Width of time marker',
|
||||
}),
|
||||
},
|
||||
},
|
||||
fn: (context, args) => {
|
||||
return {
|
||||
type: 'time_marker',
|
||||
time: args.time,
|
||||
class: args.class,
|
||||
color: args.color,
|
||||
opacity: args.opacity,
|
||||
width: args.width,
|
||||
};
|
||||
},
|
||||
});
|
|
@ -1,79 +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 type {
|
||||
ExpressionFunctionDefinition,
|
||||
Datatable,
|
||||
ExpressionValueBoxed,
|
||||
} from '@kbn/expressions-plugin/public';
|
||||
import type { ExpressionValueCategoryAxis } from './category_axis';
|
||||
import type { CategoryAxis } from '../types';
|
||||
|
||||
interface Arguments {
|
||||
name: string;
|
||||
axisParams: ExpressionValueCategoryAxis;
|
||||
}
|
||||
|
||||
export type ExpressionValueValueAxis = ExpressionValueBoxed<
|
||||
'value_axis',
|
||||
{
|
||||
name: string;
|
||||
id: string;
|
||||
show: boolean;
|
||||
position: CategoryAxis['position'];
|
||||
axisType: CategoryAxis['type'];
|
||||
title: {
|
||||
text?: string;
|
||||
};
|
||||
labels: CategoryAxis['labels'];
|
||||
scale: CategoryAxis['scale'];
|
||||
}
|
||||
>;
|
||||
|
||||
export const valueAxis = (): ExpressionFunctionDefinition<
|
||||
'valueaxis',
|
||||
Datatable | null,
|
||||
Arguments,
|
||||
ExpressionValueValueAxis
|
||||
> => ({
|
||||
name: 'valueaxis',
|
||||
help: i18n.translate('visTypeXy.function.valueaxis.help', {
|
||||
defaultMessage: 'Generates value axis object',
|
||||
}),
|
||||
type: 'value_axis',
|
||||
args: {
|
||||
name: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.valueAxis.name.help', {
|
||||
defaultMessage: 'Name of value axis',
|
||||
}),
|
||||
required: true,
|
||||
},
|
||||
axisParams: {
|
||||
types: ['category_axis'],
|
||||
help: i18n.translate('visTypeXy.function.valueAxis.axisParams.help', {
|
||||
defaultMessage: 'Value axis params',
|
||||
}),
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
fn: (context, args) => {
|
||||
return {
|
||||
type: 'value_axis',
|
||||
name: args.name,
|
||||
id: args.axisParams.id,
|
||||
show: args.axisParams.show,
|
||||
position: args.axisParams.position,
|
||||
axisType: args.axisParams.axisType,
|
||||
title: args.axisParams.title,
|
||||
scale: args.axisParams.scale,
|
||||
labels: args.axisParams.labels,
|
||||
};
|
||||
},
|
||||
});
|
|
@ -1,98 +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 type {
|
||||
ExpressionFunctionDefinition,
|
||||
Datatable,
|
||||
ExpressionValueBoxed,
|
||||
} from '@kbn/expressions-plugin/public';
|
||||
import type { Scale } from '../types';
|
||||
|
||||
export type ExpressionValueScale = ExpressionValueBoxed<
|
||||
'vis_scale',
|
||||
{
|
||||
boundsMargin?: Scale['boundsMargin'];
|
||||
defaultYExtents?: Scale['defaultYExtents'];
|
||||
max?: Scale['max'];
|
||||
min?: Scale['min'];
|
||||
mode?: Scale['mode'];
|
||||
setYExtents?: Scale['setYExtents'];
|
||||
scaleType: Scale['type'];
|
||||
}
|
||||
>;
|
||||
|
||||
export const visScale = (): ExpressionFunctionDefinition<
|
||||
'visscale',
|
||||
Datatable | null,
|
||||
Scale,
|
||||
ExpressionValueScale
|
||||
> => ({
|
||||
name: 'visscale',
|
||||
help: i18n.translate('visTypeXy.function.scale.help', {
|
||||
defaultMessage: 'Generates scale object',
|
||||
}),
|
||||
type: 'vis_scale',
|
||||
args: {
|
||||
boundsMargin: {
|
||||
types: ['number', 'string'],
|
||||
help: i18n.translate('visTypeXy.function.scale.boundsMargin.help', {
|
||||
defaultMessage: 'Margin of bounds',
|
||||
}),
|
||||
},
|
||||
defaultYExtents: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('visTypeXy.function.scale.defaultYExtents.help', {
|
||||
defaultMessage: 'Flag which allows to scale to data bounds',
|
||||
}),
|
||||
},
|
||||
setYExtents: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('visTypeXy.function.scale.setYExtents.help', {
|
||||
defaultMessage: 'Flag which allows to set your own extents',
|
||||
}),
|
||||
},
|
||||
max: {
|
||||
types: ['number', 'null'],
|
||||
help: i18n.translate('visTypeXy.function.scale.max.help', {
|
||||
defaultMessage: 'Max value',
|
||||
}),
|
||||
},
|
||||
min: {
|
||||
types: ['number', 'null'],
|
||||
help: i18n.translate('visTypeXy.function.scale.min.help', {
|
||||
defaultMessage: 'Min value',
|
||||
}),
|
||||
},
|
||||
mode: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.scale.mode.help', {
|
||||
defaultMessage: 'Scale mode. Can be normal, percentage, wiggle or silhouette',
|
||||
}),
|
||||
},
|
||||
type: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.scale.type.help', {
|
||||
defaultMessage: 'Scale type. Can be linear, log or square root',
|
||||
}),
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
fn: (context, args) => {
|
||||
return {
|
||||
type: 'vis_scale',
|
||||
boundsMargin: args.boundsMargin,
|
||||
defaultYExtents: args.defaultYExtents,
|
||||
setYExtents: args.setYExtents,
|
||||
max: args.max,
|
||||
min: args.min,
|
||||
mode: args.mode,
|
||||
scaleType: args.type,
|
||||
};
|
||||
},
|
||||
});
|
|
@ -1,370 +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 type {
|
||||
ExpressionFunctionDefinition,
|
||||
Datatable,
|
||||
Render,
|
||||
} from '@kbn/expressions-plugin/common';
|
||||
import {
|
||||
prepareLogTable,
|
||||
Dimension,
|
||||
DEFAULT_LEGEND_SIZE,
|
||||
LegendSize,
|
||||
} from '@kbn/visualizations-plugin/public';
|
||||
import type { VisParams, XYVisConfig } from '../types';
|
||||
import type { XyVisType } from '../../common';
|
||||
|
||||
export const visName = 'xy_vis';
|
||||
export interface RenderValue {
|
||||
visData: Datatable;
|
||||
visType: XyVisType;
|
||||
visConfig: VisParams;
|
||||
syncColors: boolean;
|
||||
syncTooltips: boolean;
|
||||
}
|
||||
|
||||
export type VisTypeXyExpressionFunctionDefinition = ExpressionFunctionDefinition<
|
||||
typeof visName,
|
||||
Datatable,
|
||||
XYVisConfig,
|
||||
Render<RenderValue>
|
||||
>;
|
||||
|
||||
export const visTypeXyVisFn = (): VisTypeXyExpressionFunctionDefinition => ({
|
||||
name: visName,
|
||||
type: 'render',
|
||||
context: {
|
||||
types: ['datatable'],
|
||||
},
|
||||
help: i18n.translate('visTypeXy.functions.help', {
|
||||
defaultMessage: 'XY visualization',
|
||||
}),
|
||||
args: {
|
||||
type: {
|
||||
types: ['string'],
|
||||
default: '""',
|
||||
help: 'xy vis type',
|
||||
},
|
||||
chartType: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.args.args.chartType.help', {
|
||||
defaultMessage: 'Type of a chart. Can be line, area or histogram',
|
||||
}),
|
||||
},
|
||||
addTimeMarker: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('visTypeXy.function.args.addTimeMarker.help', {
|
||||
defaultMessage: 'Show time marker',
|
||||
}),
|
||||
},
|
||||
truncateLegend: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('visTypeXy.function.args.truncateLegend.help', {
|
||||
defaultMessage: 'Defines if the legend will be truncated or not',
|
||||
}),
|
||||
},
|
||||
maxLegendLines: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('visTypeXy.function.args.args.maxLegendLines.help', {
|
||||
defaultMessage: 'Defines the maximum lines per legend item',
|
||||
}),
|
||||
},
|
||||
legendSize: {
|
||||
types: ['string'],
|
||||
default: DEFAULT_LEGEND_SIZE,
|
||||
help: i18n.translate('visTypeXy.function.args.args.legendSize.help', {
|
||||
defaultMessage: 'Specifies the legend size.',
|
||||
}),
|
||||
options: [
|
||||
LegendSize.AUTO,
|
||||
LegendSize.SMALL,
|
||||
LegendSize.MEDIUM,
|
||||
LegendSize.LARGE,
|
||||
LegendSize.EXTRA_LARGE,
|
||||
],
|
||||
strict: true,
|
||||
},
|
||||
addLegend: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('visTypeXy.function.args.addLegend.help', {
|
||||
defaultMessage: 'Show chart legend',
|
||||
}),
|
||||
},
|
||||
addTooltip: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('visTypeXy.function.args.addTooltip.help', {
|
||||
defaultMessage: 'Show tooltip on hover',
|
||||
}),
|
||||
},
|
||||
legendPosition: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.args.legendPosition.help', {
|
||||
defaultMessage: 'Position the legend on top, bottom, left, right of the chart',
|
||||
}),
|
||||
},
|
||||
categoryAxes: {
|
||||
types: ['category_axis'],
|
||||
help: i18n.translate('visTypeXy.function.args.categoryAxes.help', {
|
||||
defaultMessage: 'Category axis config',
|
||||
}),
|
||||
multi: true,
|
||||
},
|
||||
thresholdLine: {
|
||||
types: ['threshold_line'],
|
||||
help: i18n.translate('visTypeXy.function.args.thresholdLine.help', {
|
||||
defaultMessage: 'Threshold line config',
|
||||
}),
|
||||
},
|
||||
labels: {
|
||||
types: ['label'],
|
||||
help: i18n.translate('visTypeXy.function.args.labels.help', {
|
||||
defaultMessage: 'Chart labels config',
|
||||
}),
|
||||
},
|
||||
orderBucketsBySum: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('visTypeXy.function.args.orderBucketsBySum.help', {
|
||||
defaultMessage: 'Order buckets by sum',
|
||||
}),
|
||||
},
|
||||
seriesParams: {
|
||||
types: ['series_param'],
|
||||
help: i18n.translate('visTypeXy.function.args.seriesParams.help', {
|
||||
defaultMessage: 'Series param config',
|
||||
}),
|
||||
multi: true,
|
||||
},
|
||||
valueAxes: {
|
||||
types: ['value_axis'],
|
||||
help: i18n.translate('visTypeXy.function.args.valueAxes.help', {
|
||||
defaultMessage: 'Value axis config',
|
||||
}),
|
||||
multi: true,
|
||||
},
|
||||
radiusRatio: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('visTypeXy.function.args.radiusRatio.help', {
|
||||
defaultMessage: 'Dot size ratio',
|
||||
}),
|
||||
},
|
||||
gridCategoryLines: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('visTypeXy.function.args.gridCategoryLines.help', {
|
||||
defaultMessage: 'Show grid category lines in chart',
|
||||
}),
|
||||
},
|
||||
gridValueAxis: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.args.gridValueAxis.help', {
|
||||
defaultMessage: 'Name of value axis for which we show grid',
|
||||
}),
|
||||
},
|
||||
isVislibVis: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('visTypeXy.function.args.isVislibVis.help', {
|
||||
defaultMessage:
|
||||
'Flag to indicate old vislib visualizations. Used for backwards compatibility including colors',
|
||||
}),
|
||||
},
|
||||
detailedTooltip: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('visTypeXy.function.args.detailedTooltip.help', {
|
||||
defaultMessage: 'Show detailed tooltip',
|
||||
}),
|
||||
},
|
||||
fittingFunction: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.args.fittingFunction.help', {
|
||||
defaultMessage: 'Name of fitting function',
|
||||
}),
|
||||
},
|
||||
times: {
|
||||
types: ['time_marker'],
|
||||
help: i18n.translate('visTypeXy.function.args.times.help', {
|
||||
defaultMessage: 'Time marker config',
|
||||
}),
|
||||
multi: true,
|
||||
},
|
||||
palette: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.args.palette.help', {
|
||||
defaultMessage: 'Defines the chart palette name',
|
||||
}),
|
||||
},
|
||||
fillOpacity: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('visTypeXy.function.args.fillOpacity.help', {
|
||||
defaultMessage: 'Defines the area chart fill opacity',
|
||||
}),
|
||||
},
|
||||
xDimension: {
|
||||
types: ['xy_dimension', 'null'],
|
||||
help: i18n.translate('visTypeXy.function.args.xDimension.help', {
|
||||
defaultMessage: 'X axis dimension config',
|
||||
}),
|
||||
},
|
||||
yDimension: {
|
||||
types: ['xy_dimension'],
|
||||
help: i18n.translate('visTypeXy.function.args.yDimension.help', {
|
||||
defaultMessage: 'Y axis dimension config',
|
||||
}),
|
||||
multi: true,
|
||||
},
|
||||
zDimension: {
|
||||
types: ['xy_dimension'],
|
||||
help: i18n.translate('visTypeXy.function.args.zDimension.help', {
|
||||
defaultMessage: 'Z axis dimension config',
|
||||
}),
|
||||
multi: true,
|
||||
},
|
||||
widthDimension: {
|
||||
types: ['xy_dimension'],
|
||||
help: i18n.translate('visTypeXy.function.args.widthDimension.help', {
|
||||
defaultMessage: 'Width dimension config',
|
||||
}),
|
||||
multi: true,
|
||||
},
|
||||
seriesDimension: {
|
||||
types: ['xy_dimension'],
|
||||
help: i18n.translate('visTypeXy.function.args.seriesDimension.help', {
|
||||
defaultMessage: 'Series dimension config',
|
||||
}),
|
||||
multi: true,
|
||||
},
|
||||
splitRowDimension: {
|
||||
types: ['xy_dimension'],
|
||||
help: i18n.translate('visTypeXy.function.args.splitRowDimension.help', {
|
||||
defaultMessage: 'Split by row dimension config',
|
||||
}),
|
||||
multi: true,
|
||||
},
|
||||
splitColumnDimension: {
|
||||
types: ['xy_dimension'],
|
||||
help: i18n.translate('visTypeXy.function.args.splitColumnDimension.help', {
|
||||
defaultMessage: 'Split by column dimension config',
|
||||
}),
|
||||
multi: true,
|
||||
},
|
||||
ariaLabel: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('visTypeXy.function.args.ariaLabel.help', {
|
||||
defaultMessage: 'Specifies the aria label of the xy chart',
|
||||
}),
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
fn(context, args, handlers) {
|
||||
const visType = args.type;
|
||||
const visConfig = {
|
||||
ariaLabel:
|
||||
args.ariaLabel ??
|
||||
(handlers.variables?.embeddableTitle as string) ??
|
||||
handlers.getExecutionContext?.()?.description,
|
||||
type: args.chartType,
|
||||
addLegend: args.addLegend,
|
||||
addTooltip: args.addTooltip,
|
||||
legendPosition: args.legendPosition,
|
||||
addTimeMarker: args.addTimeMarker,
|
||||
maxLegendLines: args.maxLegendLines,
|
||||
truncateLegend: args.truncateLegend,
|
||||
legendSize: args.legendSize,
|
||||
categoryAxes: args.categoryAxes.map((categoryAxis) => ({
|
||||
...categoryAxis,
|
||||
type: categoryAxis.axisType,
|
||||
})),
|
||||
orderBucketsBySum: args.orderBucketsBySum,
|
||||
labels: args.labels,
|
||||
thresholdLine: args.thresholdLine,
|
||||
valueAxes: args.valueAxes.map((valueAxis) => ({ ...valueAxis, type: valueAxis.axisType })),
|
||||
grid: {
|
||||
categoryLines: args.gridCategoryLines,
|
||||
valueAxis: args.gridValueAxis,
|
||||
},
|
||||
seriesParams: args.seriesParams.map((seriesParam) => ({
|
||||
...seriesParam,
|
||||
type: seriesParam.seriesParamType,
|
||||
})),
|
||||
radiusRatio: args.radiusRatio,
|
||||
times: args.times,
|
||||
isVislibVis: args.isVislibVis,
|
||||
detailedTooltip: args.detailedTooltip,
|
||||
palette: {
|
||||
type: 'palette',
|
||||
name: args.palette,
|
||||
},
|
||||
fillOpacity: args.fillOpacity,
|
||||
fittingFunction: args.fittingFunction,
|
||||
dimensions: {
|
||||
x: args.xDimension,
|
||||
y: args.yDimension,
|
||||
z: args.zDimension,
|
||||
width: args.widthDimension,
|
||||
series: args.seriesDimension,
|
||||
splitRow: args.splitRowDimension,
|
||||
splitColumn: args.splitColumnDimension,
|
||||
},
|
||||
} as VisParams;
|
||||
|
||||
if (handlers?.inspectorAdapters?.tables) {
|
||||
const argsTable: Dimension[] = [
|
||||
[
|
||||
args.yDimension,
|
||||
i18n.translate('visTypeXy.function.dimension.metric', {
|
||||
defaultMessage: 'Metric',
|
||||
}),
|
||||
],
|
||||
[
|
||||
args.zDimension,
|
||||
i18n.translate('visTypeXy.function.adimension.dotSize', {
|
||||
defaultMessage: 'Dot size',
|
||||
}),
|
||||
],
|
||||
[
|
||||
args.splitColumnDimension,
|
||||
i18n.translate('visTypeXy.function.dimension.splitcolumn', {
|
||||
defaultMessage: 'Column split',
|
||||
}),
|
||||
],
|
||||
[
|
||||
args.splitRowDimension,
|
||||
i18n.translate('visTypeXy.function.dimension.splitrow', {
|
||||
defaultMessage: 'Row split',
|
||||
}),
|
||||
],
|
||||
];
|
||||
|
||||
if (args.xDimension) {
|
||||
argsTable.push([
|
||||
[args.xDimension],
|
||||
i18n.translate('visTypeXy.function.adimension.bucket', {
|
||||
defaultMessage: 'Bucket',
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
const logTable = prepareLogTable(context, argsTable);
|
||||
handlers.inspectorAdapters.tables.logDatatable('default', logTable);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'render',
|
||||
as: visName,
|
||||
value: {
|
||||
context,
|
||||
visType,
|
||||
visConfig,
|
||||
visData: context,
|
||||
syncColors: handlers?.isSyncColorsEnabled?.() ?? false,
|
||||
syncTooltips: handlers?.isSyncTooltipsEnabled?.() ?? false,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
|
@ -29,7 +29,6 @@ export type { ValidationVisOptionsProps } from './editor/components/common/valid
|
|||
export { TruncateLabelsOption } from './editor/components/common/truncate_labels';
|
||||
export { getPositions } from './editor/positions';
|
||||
export { getScaleTypes } from './editor/scale_types';
|
||||
export { getAggId } from './config/get_agg_id';
|
||||
|
||||
// Export common types
|
||||
export * from '../common';
|
||||
|
|
|
@ -6,31 +6,12 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
|
||||
import type { Plugin as ExpressionsPublicPlugin } from '@kbn/expressions-plugin/public';
|
||||
import type { VisualizationsSetup, VisualizationsStart } from '@kbn/visualizations-plugin/public';
|
||||
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
|
||||
import type { ChartsPluginSetup, ChartsPluginStart } from '@kbn/charts-plugin/public';
|
||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type {
|
||||
UsageCollectionSetup,
|
||||
UsageCollectionStart,
|
||||
} from '@kbn/usage-collection-plugin/public';
|
||||
import { createStartServicesGetter } from '@kbn/kibana-utils-plugin/public';
|
||||
import {
|
||||
setDataActions,
|
||||
setFormatService,
|
||||
setThemeService,
|
||||
setUISettings,
|
||||
setDocLinks,
|
||||
setPalettesService,
|
||||
setActiveCursor,
|
||||
} from './services';
|
||||
import type { CoreSetup, Plugin } from '@kbn/core/public';
|
||||
import type { VisualizationsSetup } from '@kbn/visualizations-plugin/public';
|
||||
import type { ChartsPluginSetup } from '@kbn/charts-plugin/public';
|
||||
import { setUISettings, setPalettesService } from './services';
|
||||
|
||||
import { visTypesDefinitions } from './vis_types';
|
||||
import { getXYVisRenderer } from './vis_renderer';
|
||||
|
||||
import * as expressionFunctions from './expression_functions';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface VisTypeXyPluginSetup {}
|
||||
|
@ -39,70 +20,29 @@ export interface VisTypeXyPluginStart {}
|
|||
|
||||
/** @internal */
|
||||
export interface VisTypeXyPluginSetupDependencies {
|
||||
expressions: ReturnType<ExpressionsPublicPlugin['setup']>;
|
||||
visualizations: VisualizationsSetup;
|
||||
charts: ChartsPluginSetup;
|
||||
usageCollection: UsageCollectionSetup;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface VisTypeXyPluginStartDependencies {
|
||||
expressions: ReturnType<ExpressionsPublicPlugin['start']>;
|
||||
visualizations: VisualizationsStart;
|
||||
data: DataPublicPluginStart;
|
||||
fieldFormats: FieldFormatsStart;
|
||||
charts: ChartsPluginStart;
|
||||
usageCollection?: UsageCollectionStart;
|
||||
}
|
||||
|
||||
type VisTypeXyCoreSetup = CoreSetup<VisTypeXyPluginStartDependencies, VisTypeXyPluginStart>;
|
||||
type VisTypeXyCoreSetup = CoreSetup<{}, VisTypeXyPluginStart>;
|
||||
|
||||
/** @internal */
|
||||
export class VisTypeXyPlugin
|
||||
implements
|
||||
Plugin<
|
||||
VisTypeXyPluginSetup,
|
||||
VisTypeXyPluginStart,
|
||||
VisTypeXyPluginSetupDependencies,
|
||||
VisTypeXyPluginStartDependencies
|
||||
>
|
||||
Plugin<VisTypeXyPluginSetup, VisTypeXyPluginStart, VisTypeXyPluginSetupDependencies, {}>
|
||||
{
|
||||
public setup(
|
||||
core: VisTypeXyCoreSetup,
|
||||
{ expressions, visualizations, charts, usageCollection }: VisTypeXyPluginSetupDependencies
|
||||
{ visualizations, charts }: VisTypeXyPluginSetupDependencies
|
||||
) {
|
||||
setUISettings(core.uiSettings);
|
||||
setThemeService(charts.theme);
|
||||
setPalettesService(charts.palettes);
|
||||
|
||||
const getStartDeps = createStartServicesGetter<
|
||||
VisTypeXyPluginStartDependencies,
|
||||
VisTypeXyPluginStart
|
||||
>(core.getStartServices);
|
||||
|
||||
expressions.registerRenderer(
|
||||
getXYVisRenderer({
|
||||
getStartDeps,
|
||||
})
|
||||
);
|
||||
expressions.registerFunction(expressionFunctions.visTypeXyVisFn);
|
||||
expressions.registerFunction(expressionFunctions.categoryAxis);
|
||||
expressions.registerFunction(expressionFunctions.timeMarker);
|
||||
expressions.registerFunction(expressionFunctions.valueAxis);
|
||||
expressions.registerFunction(expressionFunctions.seriesParam);
|
||||
expressions.registerFunction(expressionFunctions.thresholdLine);
|
||||
expressions.registerFunction(expressionFunctions.label);
|
||||
expressions.registerFunction(expressionFunctions.visScale);
|
||||
|
||||
visTypesDefinitions.forEach(visualizations.createBaseVisualization);
|
||||
return {};
|
||||
}
|
||||
|
||||
public start(core: CoreStart, { data, charts, fieldFormats }: VisTypeXyPluginStartDependencies) {
|
||||
setFormatService(fieldFormats);
|
||||
setDataActions(data.actions);
|
||||
setDocLinks(core.docLinks);
|
||||
setActiveCursor(charts.activeCursor);
|
||||
public start() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,29 +6,13 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { CoreSetup, DocLinksStart } from '@kbn/core/public';
|
||||
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
|
||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type { ChartsPluginSetup, ChartsPluginStart } from '@kbn/charts-plugin/public';
|
||||
import type { CoreSetup } from '@kbn/core/public';
|
||||
import type { ChartsPluginSetup } from '@kbn/charts-plugin/public';
|
||||
|
||||
import { createGetterSetter } from '@kbn/kibana-utils-plugin/public';
|
||||
|
||||
export const [getUISettings, setUISettings] =
|
||||
createGetterSetter<CoreSetup['uiSettings']>('xy core.uiSettings');
|
||||
|
||||
export const [getDataActions, setDataActions] =
|
||||
createGetterSetter<DataPublicPluginStart['actions']>('xy data.actions');
|
||||
|
||||
export const [getFormatService, setFormatService] =
|
||||
createGetterSetter<FieldFormatsStart>('xy fieldFormats');
|
||||
|
||||
export const [getThemeService, setThemeService] =
|
||||
createGetterSetter<ChartsPluginSetup['theme']>('xy charts.theme');
|
||||
|
||||
export const [getActiveCursor, setActiveCursor] =
|
||||
createGetterSetter<ChartsPluginStart['activeCursor']>('xy charts.activeCursor');
|
||||
|
||||
export const [getPalettesService, setPalettesService] =
|
||||
createGetterSetter<ChartsPluginSetup['palettes']>('xy charts.palette');
|
||||
|
||||
export const [getDocLinks, setDocLinks] = createGetterSetter<DocLinksStart>('DocLinks');
|
||||
|
|
|
@ -1,123 +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 {
|
||||
AxisSpec,
|
||||
CustomTooltip,
|
||||
Fit,
|
||||
GridLineStyle,
|
||||
Position,
|
||||
Rotation,
|
||||
SeriesScales,
|
||||
TickFormatter,
|
||||
TooltipProps,
|
||||
TooltipValueFormatter,
|
||||
YDomainRange,
|
||||
} from '@elastic/charts';
|
||||
|
||||
import type { Dimension, Scale, ThresholdLine } from './param';
|
||||
|
||||
export interface Column {
|
||||
id: string | null;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Aspect {
|
||||
accessor: Column['id'];
|
||||
aggType: string | null;
|
||||
aggId: string | null;
|
||||
column?: Dimension['accessor'];
|
||||
title: Column['name'];
|
||||
format?: Dimension['format'];
|
||||
formatter?: TickFormatter;
|
||||
params: Dimension['params'];
|
||||
}
|
||||
|
||||
export interface Aspects {
|
||||
x: Aspect;
|
||||
y: Aspect[];
|
||||
z?: Aspect;
|
||||
series?: Aspect[];
|
||||
splitColumn?: Aspect;
|
||||
splitRow?: Aspect;
|
||||
}
|
||||
|
||||
export interface AxisGrid {
|
||||
show?: boolean;
|
||||
styles?: GridLineStyle;
|
||||
}
|
||||
|
||||
export interface TickOptions {
|
||||
show?: boolean;
|
||||
size?: number;
|
||||
count?: number;
|
||||
padding?: number;
|
||||
formatter?: TickFormatter;
|
||||
labelFormatter?: TickFormatter;
|
||||
rotation?: number;
|
||||
showDuplicates?: boolean;
|
||||
integersOnly?: boolean;
|
||||
showOverlappingTicks?: boolean;
|
||||
showOverlappingLabels?: boolean;
|
||||
}
|
||||
|
||||
export type YScaleType = SeriesScales['yScaleType'];
|
||||
export type XScaleType = SeriesScales['xScaleType'];
|
||||
|
||||
export type ScaleConfig<S extends XScaleType | YScaleType> = Omit<Scale, 'type'> & {
|
||||
type?: S;
|
||||
};
|
||||
|
||||
export interface AxisConfig<S extends XScaleType | YScaleType> {
|
||||
id: string;
|
||||
groupId?: string;
|
||||
position: Position;
|
||||
ticks?: TickOptions;
|
||||
show: boolean;
|
||||
style: AxisSpec['style'];
|
||||
scale: ScaleConfig<S>;
|
||||
domain?: YDomainRange;
|
||||
title?: string;
|
||||
grid?: AxisGrid;
|
||||
integersOnly: boolean;
|
||||
timeAxisLayerCount?: number;
|
||||
}
|
||||
|
||||
export interface LegendOptions {
|
||||
show: boolean;
|
||||
position?: Position;
|
||||
}
|
||||
|
||||
export type ThresholdLineConfig = Omit<ThresholdLine, 'style'> & {
|
||||
dash?: number[];
|
||||
groupId?: string;
|
||||
};
|
||||
|
||||
export type TooltipConfig = Omit<TooltipProps, 'customTooltip'> & {
|
||||
detailedTooltip?: (headerFormatter?: TooltipValueFormatter) => CustomTooltip;
|
||||
};
|
||||
|
||||
export interface VisConfig {
|
||||
legend: LegendOptions;
|
||||
tooltip: TooltipConfig;
|
||||
xAxis: AxisConfig<XScaleType>;
|
||||
yAxes: Array<AxisConfig<YScaleType>>;
|
||||
aspects: Aspects;
|
||||
rotation: Rotation;
|
||||
thresholdLine: ThresholdLineConfig;
|
||||
orderBucketsBySum?: boolean;
|
||||
showCurrentTime: boolean;
|
||||
isTimeChart: boolean;
|
||||
markSizeRatio: number;
|
||||
showValueLabel: boolean;
|
||||
enableHistogramMode: boolean;
|
||||
fittingFunction?: Exclude<Fit, 'explicit'>;
|
||||
fillOpacity?: number;
|
||||
detailedTooltip?: boolean;
|
||||
isVislibVis?: boolean;
|
||||
}
|
|
@ -40,9 +40,3 @@ export enum ThresholdLineStyle {
|
|||
Dashed = 'dashed',
|
||||
DotDashed = 'dot-dashed',
|
||||
}
|
||||
|
||||
export enum ColorMode {
|
||||
Background = 'Background',
|
||||
Labels = 'Labels',
|
||||
None = 'None',
|
||||
}
|
||||
|
|
|
@ -7,6 +7,4 @@
|
|||
*/
|
||||
|
||||
export * from './constants';
|
||||
export * from './config';
|
||||
export * from './param';
|
||||
export type { VisTypeNames, XyVisTypeDefinition } from './vis_type';
|
||||
|
|
|
@ -11,21 +11,12 @@ import type { PaletteOutput } from '@kbn/coloring';
|
|||
import type { Style, Labels } from '@kbn/charts-plugin/public';
|
||||
import type {
|
||||
SchemaConfig,
|
||||
ExpressionValueXYDimension,
|
||||
FakeParams,
|
||||
HistogramParams,
|
||||
DateHistogramParams,
|
||||
LegendSize,
|
||||
} from '@kbn/visualizations-plugin/public';
|
||||
import type { ChartType, XyVisType } from '../../common';
|
||||
import type {
|
||||
ExpressionValueCategoryAxis,
|
||||
ExpressionValueSeriesParam,
|
||||
ExpressionValueValueAxis,
|
||||
ExpressionValueLabel,
|
||||
ExpressionValueThresholdLine,
|
||||
ExpressionValueTimeMarker,
|
||||
} from '../expression_functions';
|
||||
import type { ChartType } from '../../common';
|
||||
|
||||
import type {
|
||||
ChartMode,
|
||||
|
@ -150,45 +141,3 @@ export interface VisParams {
|
|||
fittingFunction?: Exclude<Fit, 'explicit'>;
|
||||
ariaLabel?: string;
|
||||
}
|
||||
|
||||
export interface XYVisConfig {
|
||||
type: XyVisType;
|
||||
chartType: ChartType;
|
||||
gridCategoryLines: boolean;
|
||||
gridValueAxis?: string;
|
||||
categoryAxes: ExpressionValueCategoryAxis[];
|
||||
valueAxes: ExpressionValueValueAxis[];
|
||||
seriesParams: ExpressionValueSeriesParam[];
|
||||
palette: string;
|
||||
addLegend: boolean;
|
||||
addTooltip: boolean;
|
||||
legendPosition: Position;
|
||||
addTimeMarker: boolean;
|
||||
truncateLegend: boolean;
|
||||
maxLegendLines: number;
|
||||
legendSize?: LegendSize;
|
||||
orderBucketsBySum?: boolean;
|
||||
labels: ExpressionValueLabel;
|
||||
thresholdLine: ExpressionValueThresholdLine;
|
||||
radiusRatio: number;
|
||||
times: ExpressionValueTimeMarker[]; // For compatibility with vislib
|
||||
/**
|
||||
* flag to indicate old vislib visualizations
|
||||
* used for backwards compatibility including colors
|
||||
*/
|
||||
isVislibVis?: boolean;
|
||||
/**
|
||||
* Add for detailed tooltip option
|
||||
*/
|
||||
detailedTooltip?: boolean;
|
||||
fittingFunction?: Exclude<Fit, 'explicit'>;
|
||||
fillOpacity?: number;
|
||||
xDimension: ExpressionValueXYDimension | null;
|
||||
yDimension: ExpressionValueXYDimension[];
|
||||
zDimension?: ExpressionValueXYDimension[];
|
||||
widthDimension?: ExpressionValueXYDimension[];
|
||||
seriesDimension?: ExpressionValueXYDimension[];
|
||||
splitRowDimension?: ExpressionValueXYDimension[];
|
||||
splitColumnDimension?: ExpressionValueXYDimension[];
|
||||
ariaLabel?: string;
|
||||
}
|
||||
|
|
|
@ -1,16 +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 { VisTypeDefinition } from '@kbn/visualizations-plugin/public';
|
||||
import { ChartType } from '../../common';
|
||||
|
||||
import { VisParams } from './param';
|
||||
|
||||
export type VisTypeNames = ChartType | 'horizontal_bar';
|
||||
|
||||
export type XyVisTypeDefinition = VisTypeDefinition<VisParams>;
|
|
@ -1,155 +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 {
|
||||
COMPLEX_SPLIT_ACCESSOR,
|
||||
getComplexAccessor,
|
||||
isPercentileIdEqualToSeriesId,
|
||||
} from './accessors';
|
||||
import { BUCKET_TYPES } from '@kbn/data-plugin/common';
|
||||
import { AccessorFn, Datum } from '@elastic/charts';
|
||||
|
||||
describe('XY chart datum accessors', () => {
|
||||
const aspectBase = {
|
||||
accessor: 'col-0-2',
|
||||
formatter: (value: Datum) => value,
|
||||
aggId: '',
|
||||
title: '',
|
||||
params: {},
|
||||
};
|
||||
|
||||
it('should return complex accessor for IP range aggregation', () => {
|
||||
const aspect = {
|
||||
aggType: BUCKET_TYPES.IP_RANGE,
|
||||
...aspectBase,
|
||||
};
|
||||
const accessor = getComplexAccessor(COMPLEX_SPLIT_ACCESSOR)(aspect);
|
||||
const datum = {
|
||||
'col-0-2': { type: 'range', from: '0.0.0.0', to: '127.255.255.255' },
|
||||
};
|
||||
|
||||
expect(typeof accessor).toBe('function');
|
||||
expect((accessor as AccessorFn)(datum)).toStrictEqual({
|
||||
type: 'range',
|
||||
from: '0.0.0.0',
|
||||
to: '127.255.255.255',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return complex accessor for date range aggregation', () => {
|
||||
const aspect = {
|
||||
aggType: BUCKET_TYPES.DATE_RANGE,
|
||||
...aspectBase,
|
||||
};
|
||||
const accessor = getComplexAccessor(COMPLEX_SPLIT_ACCESSOR)(aspect);
|
||||
const datum = {
|
||||
'col-0-2': { from: '1613941200000', to: '1614685113537' },
|
||||
};
|
||||
|
||||
expect(typeof accessor).toBe('function');
|
||||
expect((accessor as AccessorFn)(datum)).toStrictEqual({
|
||||
from: '1613941200000',
|
||||
to: '1614685113537',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return complex accessor when isComplex option set to true', () => {
|
||||
const aspect = {
|
||||
aggType: BUCKET_TYPES.TERMS,
|
||||
...aspectBase,
|
||||
};
|
||||
const accessor = getComplexAccessor(COMPLEX_SPLIT_ACCESSOR, true)(aspect);
|
||||
|
||||
expect(typeof accessor).toBe('function');
|
||||
expect((accessor as AccessorFn)({ 'col-0-2': 'some value' })).toBe('some value');
|
||||
});
|
||||
|
||||
it('should return simple string accessor for not range (date histogram) aggregation', () => {
|
||||
const aspect = {
|
||||
aggType: BUCKET_TYPES.DATE_HISTOGRAM,
|
||||
...aspectBase,
|
||||
};
|
||||
const accessor = getComplexAccessor(COMPLEX_SPLIT_ACCESSOR)(aspect);
|
||||
|
||||
expect(typeof accessor).toBe('string');
|
||||
expect(accessor).toBe('col-0-2');
|
||||
});
|
||||
|
||||
it('should return simple string accessor when aspect has no formatter', () => {
|
||||
const aspect = {
|
||||
aggType: BUCKET_TYPES.RANGE,
|
||||
...aspectBase,
|
||||
formatter: undefined,
|
||||
};
|
||||
const accessor = getComplexAccessor(COMPLEX_SPLIT_ACCESSOR)(aspect);
|
||||
|
||||
expect(typeof accessor).toBe('string');
|
||||
expect(accessor).toBe('col-0-2');
|
||||
});
|
||||
|
||||
it('should return undefined when aspect has no accessor', () => {
|
||||
const aspect = {
|
||||
aggType: BUCKET_TYPES.RANGE,
|
||||
...aspectBase,
|
||||
accessor: null,
|
||||
};
|
||||
const accessor = getComplexAccessor(COMPLEX_SPLIT_ACCESSOR)(aspect);
|
||||
|
||||
expect(accessor).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isPercentileIdEqualToSeriesId', () => {
|
||||
it('should be equal for plain column ids', () => {
|
||||
const seriesColumnId = 'col-0-1';
|
||||
const columnId = `${seriesColumnId}`;
|
||||
|
||||
const isEqual = isPercentileIdEqualToSeriesId(columnId, seriesColumnId);
|
||||
expect(isEqual).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should be equal for column with percentile', () => {
|
||||
const seriesColumnId = '1';
|
||||
const columnId = `${seriesColumnId}.95`;
|
||||
|
||||
const isEqual = isPercentileIdEqualToSeriesId(columnId, seriesColumnId);
|
||||
expect(isEqual).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not be equal for column with percentile equal to seriesColumnId', () => {
|
||||
const seriesColumnId = '1';
|
||||
const columnId = `2.1`;
|
||||
|
||||
const isEqual = isPercentileIdEqualToSeriesId(columnId, seriesColumnId);
|
||||
expect(isEqual).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should be equal for column with percentile with decimal points', () => {
|
||||
const seriesColumnId = '1';
|
||||
const columnId = `${seriesColumnId}['95.5']`;
|
||||
|
||||
const isEqual = isPercentileIdEqualToSeriesId(columnId, seriesColumnId);
|
||||
expect(isEqual).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not be equal for column with percentile with decimal points equal to seriesColumnId', () => {
|
||||
const seriesColumnId = '1';
|
||||
const columnId = `2['1.3']`;
|
||||
|
||||
const isEqual = isPercentileIdEqualToSeriesId(columnId, seriesColumnId);
|
||||
expect(isEqual).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should not be equal for column with percentile, where columnId contains seriesColumnId', () => {
|
||||
const seriesColumnId = '1';
|
||||
const columnId = `${seriesColumnId}2.1`;
|
||||
|
||||
const isEqual = isPercentileIdEqualToSeriesId(columnId, seriesColumnId);
|
||||
expect(isEqual).toBeFalsy();
|
||||
});
|
||||
});
|
|
@ -6,78 +6,6 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { AccessorFn, Accessor } from '@elastic/charts';
|
||||
import { BUCKET_TYPES } from '@kbn/data-plugin/public';
|
||||
import { FakeParams } from '@kbn/visualizations-plugin/public';
|
||||
import type { Aspect } from '../types';
|
||||
|
||||
export const COMPLEX_X_ACCESSOR = '__customXAccessor__';
|
||||
export const COMPLEX_SPLIT_ACCESSOR = '__complexSplitAccessor__';
|
||||
const SHARD_DELAY = 'shard_delay';
|
||||
|
||||
export const getXAccessor = (aspect: Aspect): Accessor | AccessorFn => {
|
||||
return (
|
||||
getComplexAccessor(COMPLEX_X_ACCESSOR)(aspect) ??
|
||||
(() => (aspect.params as FakeParams)?.defaultValue)
|
||||
);
|
||||
};
|
||||
|
||||
const getFieldName = (fieldName: string, index?: number) => {
|
||||
const indexStr = index !== undefined ? `::${index}` : '';
|
||||
|
||||
return `${fieldName}${indexStr}`;
|
||||
};
|
||||
|
||||
export const isRangeAggType = (type: string | null) =>
|
||||
type === BUCKET_TYPES.DATE_RANGE || type === BUCKET_TYPES.RANGE || type === BUCKET_TYPES.IP_RANGE;
|
||||
|
||||
/**
|
||||
* Returns accessor function for complex accessor types
|
||||
* @param aspect
|
||||
* @param isComplex - forces to be functional/complex accessor
|
||||
*/
|
||||
export const getComplexAccessor =
|
||||
(fieldName: string, isComplex: boolean = false) =>
|
||||
(aspect: Aspect, index?: number): Accessor | AccessorFn | undefined => {
|
||||
if (!aspect.accessor || aspect.aggType === SHARD_DELAY) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!((isComplex || isRangeAggType(aspect.aggType)) && aspect.formatter)) {
|
||||
return aspect.accessor;
|
||||
}
|
||||
|
||||
const formatter = aspect.formatter;
|
||||
const accessor = aspect.accessor;
|
||||
const fn: AccessorFn = (d) => {
|
||||
const v = d[accessor];
|
||||
if (v === undefined) {
|
||||
return;
|
||||
}
|
||||
const f = formatter(v);
|
||||
return f;
|
||||
};
|
||||
|
||||
fn.fieldName = getFieldName(fieldName, index);
|
||||
|
||||
return fn;
|
||||
};
|
||||
|
||||
export const getSplitSeriesAccessorFnMap = (
|
||||
splitSeriesAccessors: Array<Accessor | AccessorFn>
|
||||
): Map<string | number, AccessorFn> => {
|
||||
const m = new Map<string | number, AccessorFn>();
|
||||
|
||||
splitSeriesAccessors.forEach((accessor, index) => {
|
||||
if (typeof accessor === 'function') {
|
||||
const fieldName = getFieldName(COMPLEX_SPLIT_ACCESSOR, index);
|
||||
m.set(fieldName, accessor);
|
||||
}
|
||||
});
|
||||
|
||||
return m;
|
||||
};
|
||||
|
||||
// For percentile, the aggregation id is coming in the form %s.%d, where %s is agg_id and %d - percents
|
||||
export const getSafeId = (columnId?: number | string | null) => {
|
||||
const id = String(columnId);
|
||||
|
@ -89,12 +17,3 @@ export const getSafeId = (columnId?: number | string | null) => {
|
|||
const baseId = id.substring(0, id.indexOf('[') !== -1 ? id.indexOf('[') : id.indexOf('.'));
|
||||
return baseId;
|
||||
};
|
||||
|
||||
export const isPercentileIdEqualToSeriesId = (
|
||||
columnId: number | string | null | undefined,
|
||||
seriesColumnId: string
|
||||
) => getSafeId(columnId) === seriesColumnId;
|
||||
|
||||
export const isValidSeriesForDimension = (seriesColumnId: string, { aggId, accessor }: Aspect) =>
|
||||
(aggId === seriesColumnId || isPercentileIdEqualToSeriesId(aggId ?? '', seriesColumnId)) &&
|
||||
accessor !== null;
|
||||
|
|
|
@ -1,216 +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 { computePercentageData } from './compute_percentage_data';
|
||||
|
||||
const rowsOneMetric = [
|
||||
{
|
||||
'col-0-4': 'Kibana Airlines',
|
||||
'col-1-1': 85,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'ES-Air',
|
||||
'col-1-1': 84,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'Logstash Airways',
|
||||
'col-1-1': 82,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'JetBeats',
|
||||
'col-1-1': 81,
|
||||
},
|
||||
];
|
||||
|
||||
const twoMetricsRows = [
|
||||
{
|
||||
'col-0-4': 'ES-Air',
|
||||
'col-1-5': 10,
|
||||
'col-2-1': 71,
|
||||
'col-3-1': 1,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'ES-Air',
|
||||
'col-1-5': 9,
|
||||
'col-2-1': 14,
|
||||
'col-3-1': 1,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'Kibana Airlines',
|
||||
'col-1-5': 5,
|
||||
'col-2-1': 71,
|
||||
'col-3-1': 0,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'Kibana Airlines',
|
||||
'col-1-5': 8,
|
||||
'col-2-1': 13,
|
||||
'col-3-1': 1,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'JetBeats',
|
||||
'col-1-5': 11,
|
||||
'col-2-1': 72,
|
||||
'col-3-1': 0,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'JetBeats',
|
||||
'col-1-5': 12,
|
||||
'col-2-1': 9,
|
||||
'col-3-1': 0,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'Logstash Airways',
|
||||
'col-1-5': 5,
|
||||
'col-2-1': 71,
|
||||
'col-3-1': 1,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'Logstash Airways',
|
||||
'col-1-5': 7,
|
||||
'col-2-1': 10,
|
||||
'col-3-1': 0,
|
||||
},
|
||||
];
|
||||
|
||||
describe('computePercentageData', () => {
|
||||
it('returns ratio 1 if there is only one metric in the axis', () => {
|
||||
const data = computePercentageData(rowsOneMetric, 'col-0-4', ['col-1-1']);
|
||||
expect(data).toStrictEqual([
|
||||
{
|
||||
'col-0-4': 'Kibana Airlines',
|
||||
'col-1-1': 1,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'ES-Air',
|
||||
'col-1-1': 1,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'Logstash Airways',
|
||||
'col-1-1': 1,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'JetBeats',
|
||||
'col-1-1': 1,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns correct ratio if there are two metrics in the same axis with no small multiples', () => {
|
||||
const data = computePercentageData(twoMetricsRows, 'col-0-4', ['col-1-5', 'col-2-1']);
|
||||
expect(data).toStrictEqual([
|
||||
{
|
||||
'col-0-4': 'ES-Air',
|
||||
'col-1-5': 0.09615384615384616,
|
||||
'col-2-1': 0.6826923076923077,
|
||||
'col-3-1': 1,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'ES-Air',
|
||||
'col-1-5': 0.08653846153846154,
|
||||
'col-2-1': 0.1346153846153846,
|
||||
'col-3-1': 1,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'Kibana Airlines',
|
||||
'col-1-5': 0.05154639175257732,
|
||||
'col-2-1': 0.7319587628865979,
|
||||
'col-3-1': 0,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'Kibana Airlines',
|
||||
'col-1-5': 0.08247422680412371,
|
||||
'col-2-1': 0.13402061855670103,
|
||||
'col-3-1': 1,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'JetBeats',
|
||||
'col-1-5': 0.10576923076923077,
|
||||
'col-2-1': 0.6923076923076923,
|
||||
'col-3-1': 0,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'JetBeats',
|
||||
'col-1-5': 0.11538461538461539,
|
||||
'col-2-1': 0.08653846153846154,
|
||||
'col-3-1': 0,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'Logstash Airways',
|
||||
'col-1-5': 0.053763440860215055,
|
||||
'col-2-1': 0.7634408602150538,
|
||||
'col-3-1': 1,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'Logstash Airways',
|
||||
'col-1-5': 0.07526881720430108,
|
||||
'col-2-1': 0.10752688172043011,
|
||||
'col-3-1': 0,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns correct ratio if there are two metrics in the same axis with small multiples', () => {
|
||||
const data = computePercentageData(
|
||||
twoMetricsRows,
|
||||
'col-0-4',
|
||||
['col-1-5', 'col-2-1'],
|
||||
'col-3-1'
|
||||
);
|
||||
expect(data).toStrictEqual([
|
||||
{
|
||||
'col-0-4': 'ES-Air',
|
||||
'col-1-5': 0.09615384615384616,
|
||||
'col-2-1': 0.6826923076923077,
|
||||
'col-3-1': 1,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'ES-Air',
|
||||
'col-1-5': 0.08653846153846154,
|
||||
'col-2-1': 0.1346153846153846,
|
||||
'col-3-1': 1,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'Kibana Airlines',
|
||||
'col-1-5': 0.06578947368421052,
|
||||
'col-2-1': 0.9342105263157895,
|
||||
'col-3-1': 0,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'Kibana Airlines',
|
||||
'col-1-5': 0.38095238095238093,
|
||||
'col-2-1': 0.6190476190476191,
|
||||
'col-3-1': 1,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'JetBeats',
|
||||
'col-1-5': 0.10576923076923077,
|
||||
'col-2-1': 0.6923076923076923,
|
||||
'col-3-1': 0,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'JetBeats',
|
||||
'col-1-5': 0.11538461538461539,
|
||||
'col-2-1': 0.08653846153846154,
|
||||
'col-3-1': 0,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'Logstash Airways',
|
||||
'col-1-5': 0.06578947368421052,
|
||||
'col-2-1': 0.9342105263157895,
|
||||
'col-3-1': 1,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'Logstash Airways',
|
||||
'col-1-5': 0.4117647058823529,
|
||||
'col-2-1': 0.5882352941176471,
|
||||
'col-3-1': 0,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -1,31 +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 { Accessor, AccessorFn } from '@elastic/charts';
|
||||
import { computeRatioByGroups } from '@elastic/charts';
|
||||
import type { DatatableRow } from '@kbn/expressions-plugin/public';
|
||||
|
||||
export const computePercentageData = (
|
||||
rows: DatatableRow[],
|
||||
xAccessor: Accessor | AccessorFn,
|
||||
yAccessors: string[],
|
||||
splitChartAccessor?: string | null
|
||||
) => {
|
||||
// compute percentage mode data
|
||||
const groupAccessors = [String(xAccessor)];
|
||||
if (splitChartAccessor) {
|
||||
groupAccessors.push(splitChartAccessor);
|
||||
}
|
||||
|
||||
return computeRatioByGroups(
|
||||
rows,
|
||||
groupAccessors,
|
||||
yAccessors.map((accessor) => {
|
||||
return [(d) => d[accessor], (d, v) => ({ ...d, [accessor]: v })];
|
||||
})
|
||||
);
|
||||
};
|
|
@ -1,84 +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 { uniq } from 'lodash';
|
||||
import { unitOfTime } from 'moment';
|
||||
|
||||
import { DomainRange } from '@elastic/charts';
|
||||
|
||||
import { getAdjustedInterval } from '@kbn/charts-plugin/public';
|
||||
import { Datatable } from '@kbn/expressions-plugin/public';
|
||||
import { DateHistogramParams, HistogramParams } from '@kbn/visualizations-plugin/public';
|
||||
|
||||
import { Aspect } from '../types';
|
||||
|
||||
export const getXDomain = (params: Aspect['params']): DomainRange => {
|
||||
const minInterval = (params as DateHistogramParams | HistogramParams)?.interval ?? undefined;
|
||||
const bounds = (params as DateHistogramParams).date
|
||||
? (params as DateHistogramParams).bounds
|
||||
: null;
|
||||
|
||||
if (bounds) {
|
||||
return {
|
||||
min: bounds.min as number,
|
||||
max: bounds.max as number,
|
||||
minInterval,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
minInterval,
|
||||
min: NaN,
|
||||
max: NaN,
|
||||
};
|
||||
};
|
||||
|
||||
export const getAdjustedDomain = (
|
||||
data: Datatable['rows'],
|
||||
{ accessor, params }: Aspect,
|
||||
timeZone: string,
|
||||
domain: DomainRange | undefined,
|
||||
hasBars?: boolean
|
||||
): DomainRange => {
|
||||
if (
|
||||
accessor &&
|
||||
domain &&
|
||||
'min' in domain &&
|
||||
'max' in domain &&
|
||||
'intervalESValue' in params &&
|
||||
'intervalESUnit' in params
|
||||
) {
|
||||
const { interval, intervalESValue, intervalESUnit } = params;
|
||||
const xValues = uniq(data.map((d) => d[accessor]).sort());
|
||||
|
||||
const [firstXValue] = xValues;
|
||||
const lastXValue = xValues[xValues.length - 1];
|
||||
|
||||
const domainMin = Math.min(firstXValue, domain.min);
|
||||
const domainMaxValue = Math.max(domain.max - interval, lastXValue);
|
||||
const domainMax = hasBars ? domainMaxValue : domainMaxValue + interval;
|
||||
const minInterval = getAdjustedInterval(
|
||||
xValues,
|
||||
intervalESValue,
|
||||
intervalESUnit as unitOfTime.Base,
|
||||
timeZone
|
||||
);
|
||||
|
||||
return {
|
||||
min: domainMin,
|
||||
max: domainMax,
|
||||
minInterval,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
minInterval: 'interval' in params ? params.interval : undefined,
|
||||
min: NaN,
|
||||
max: NaN,
|
||||
};
|
||||
};
|
|
@ -1,183 +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 { getAllSeries } from './get_all_series';
|
||||
|
||||
const rowsNoSplitSeries = [
|
||||
{
|
||||
'col-0-4': 'Kibana Airlines',
|
||||
'col-1-1': 85,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'ES-Air',
|
||||
'col-1-1': 84,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'Logstash Airways',
|
||||
'col-1-1': 82,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'JetBeats',
|
||||
'col-1-1': 81,
|
||||
},
|
||||
];
|
||||
|
||||
const rowsWithSplitSeries = [
|
||||
{
|
||||
'col-0-4': 'ES-Air',
|
||||
'col-1-5': 0,
|
||||
'col-2-1': 71,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'ES-Air',
|
||||
'col-1-5': 1,
|
||||
'col-2-1': 14,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'Kibana Airlines',
|
||||
'col-1-5': 0,
|
||||
'col-2-1': 71,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'Kibana Airlines',
|
||||
'col-1-5': 1,
|
||||
'col-2-1': 13,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'JetBeats',
|
||||
'col-1-5': 0,
|
||||
'col-2-1': 72,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'JetBeats',
|
||||
'col-1-5': 1,
|
||||
'col-2-1': 9,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'Logstash Airways',
|
||||
'col-1-5': 0,
|
||||
'col-2-1': 71,
|
||||
},
|
||||
{
|
||||
'col-0-4': 'Logstash Airways',
|
||||
'col-1-5': 1,
|
||||
'col-2-1': 10,
|
||||
},
|
||||
];
|
||||
|
||||
const yAspects = [
|
||||
{
|
||||
accessor: 'col-2-1',
|
||||
column: 2,
|
||||
title: 'Count',
|
||||
format: {
|
||||
id: 'number',
|
||||
},
|
||||
aggType: 'count',
|
||||
aggId: '1',
|
||||
params: {},
|
||||
},
|
||||
];
|
||||
|
||||
const myltipleYAspects = [
|
||||
{
|
||||
accessor: 'col-2-1',
|
||||
column: 2,
|
||||
title: 'Count',
|
||||
format: {
|
||||
id: 'number',
|
||||
},
|
||||
aggType: 'count',
|
||||
aggId: '1',
|
||||
params: {},
|
||||
},
|
||||
{
|
||||
accessor: 'col-3-4',
|
||||
column: 3,
|
||||
title: 'Average AvgTicketPrice',
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {
|
||||
pattern: '$0,0.[00]',
|
||||
},
|
||||
},
|
||||
aggType: 'avg',
|
||||
aggId: '4',
|
||||
params: {},
|
||||
},
|
||||
];
|
||||
|
||||
describe('getAllSeries', () => {
|
||||
it('returns empty array if splitAccessors is undefined', () => {
|
||||
const splitAccessors = undefined;
|
||||
const series = getAllSeries(rowsNoSplitSeries, splitAccessors, yAspects);
|
||||
expect(series).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('returns an array of series names if splitAccessors is an array', () => {
|
||||
const splitAccessors = [
|
||||
{
|
||||
accessor: 'col-1-5',
|
||||
},
|
||||
];
|
||||
const series = getAllSeries(rowsWithSplitSeries, splitAccessors, yAspects);
|
||||
expect(series).toStrictEqual([0, 1]);
|
||||
});
|
||||
|
||||
it('returns the correct array of series names for two splitAccessors without duplicates', () => {
|
||||
const splitAccessors = [
|
||||
{
|
||||
accessor: 'col-0-4',
|
||||
},
|
||||
{
|
||||
accessor: 'col-1-5',
|
||||
},
|
||||
];
|
||||
const series = getAllSeries(rowsWithSplitSeries, splitAccessors, yAspects);
|
||||
expect(series).toStrictEqual([
|
||||
'ES-Air - 0',
|
||||
'ES-Air - 1',
|
||||
'Kibana Airlines - 0',
|
||||
'Kibana Airlines - 1',
|
||||
'JetBeats - 0',
|
||||
'JetBeats - 1',
|
||||
'Logstash Airways - 0',
|
||||
'Logstash Airways - 1',
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns the correct array of series names for two splitAccessors and two y axis', () => {
|
||||
const splitAccessors = [
|
||||
{
|
||||
accessor: 'col-0-4',
|
||||
},
|
||||
{
|
||||
accessor: 'col-1-5',
|
||||
},
|
||||
];
|
||||
const series = getAllSeries(rowsWithSplitSeries, splitAccessors, myltipleYAspects);
|
||||
expect(series).toStrictEqual([
|
||||
'ES-Air - 0: Count',
|
||||
'ES-Air - 0: Average AvgTicketPrice',
|
||||
'ES-Air - 1: Count',
|
||||
'ES-Air - 1: Average AvgTicketPrice',
|
||||
'Kibana Airlines - 0: Count',
|
||||
'Kibana Airlines - 0: Average AvgTicketPrice',
|
||||
'Kibana Airlines - 1: Count',
|
||||
'Kibana Airlines - 1: Average AvgTicketPrice',
|
||||
'JetBeats - 0: Count',
|
||||
'JetBeats - 0: Average AvgTicketPrice',
|
||||
'JetBeats - 1: Count',
|
||||
'JetBeats - 1: Average AvgTicketPrice',
|
||||
'Logstash Airways - 0: Count',
|
||||
'Logstash Airways - 0: Average AvgTicketPrice',
|
||||
'Logstash Airways - 1: Count',
|
||||
'Logstash Airways - 1: Average AvgTicketPrice',
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -1,52 +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 { TickFormatter } from '@elastic/charts';
|
||||
import { DatatableRow } from '@kbn/expressions-plugin/public';
|
||||
import { Column, Aspect } from '../types';
|
||||
|
||||
interface SplitAccessors {
|
||||
accessor: Column['id'];
|
||||
formatter?: TickFormatter;
|
||||
}
|
||||
|
||||
export const getAllSeries = (
|
||||
rows: DatatableRow[],
|
||||
splitAccessors: SplitAccessors[] | undefined,
|
||||
yAspects: Aspect[]
|
||||
) => {
|
||||
const allSeries: string[] = [];
|
||||
if (!splitAccessors) return [];
|
||||
|
||||
rows.forEach((row) => {
|
||||
let seriesName = '';
|
||||
splitAccessors?.forEach(({ accessor, formatter }) => {
|
||||
if (!accessor) return;
|
||||
const name = formatter ? formatter(row[accessor]) : row[accessor];
|
||||
if (seriesName) {
|
||||
seriesName += ` - ${name}`;
|
||||
} else {
|
||||
seriesName = name;
|
||||
}
|
||||
});
|
||||
|
||||
// multiple y axis
|
||||
if (yAspects.length > 1) {
|
||||
yAspects.forEach((aspect) => {
|
||||
if (!allSeries.includes(`${seriesName}: ${aspect.title}`)) {
|
||||
allSeries.push(`${seriesName}: ${aspect.title}`);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (!allSeries.includes(seriesName)) {
|
||||
allSeries.push(seriesName);
|
||||
}
|
||||
}
|
||||
});
|
||||
return allSeries;
|
||||
};
|
|
@ -1,98 +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 React from 'react';
|
||||
import { LegendColorPickerProps, XYChartSeriesIdentifier } from '@elastic/charts';
|
||||
import { EuiPopover } from '@elastic/eui';
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { ComponentType, ReactWrapper } from 'enzyme';
|
||||
import { getColorPicker } from './get_color_picker';
|
||||
import { ColorPicker } from '@kbn/charts-plugin/public';
|
||||
import type { PersistedState } from '@kbn/visualizations-plugin/public';
|
||||
|
||||
jest.mock('@elastic/charts', () => {
|
||||
const original = jest.requireActual('@elastic/charts');
|
||||
|
||||
return {
|
||||
...original,
|
||||
getSpecId: jest.fn(() => {}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('getColorPicker', function () {
|
||||
const mockState = new Map();
|
||||
const uiState = {
|
||||
get: jest
|
||||
.fn()
|
||||
.mockImplementation((key, fallback) => (mockState.has(key) ? mockState.get(key) : fallback)),
|
||||
set: jest.fn().mockImplementation((key, value) => mockState.set(key, value)),
|
||||
emit: jest.fn(),
|
||||
setSilent: jest.fn(),
|
||||
} as unknown as PersistedState;
|
||||
|
||||
let wrapperProps: LegendColorPickerProps;
|
||||
const Component: ComponentType<LegendColorPickerProps> = getColorPicker(
|
||||
'left',
|
||||
jest.fn(),
|
||||
jest.fn().mockImplementation((seriesIdentifier) => seriesIdentifier.seriesKeys[0]),
|
||||
'default',
|
||||
uiState
|
||||
);
|
||||
let wrapper: ReactWrapper<LegendColorPickerProps>;
|
||||
|
||||
beforeAll(() => {
|
||||
wrapperProps = {
|
||||
color: 'rgb(109, 204, 177)',
|
||||
onClose: jest.fn(),
|
||||
onChange: jest.fn(),
|
||||
anchor: document.createElement('div'),
|
||||
seriesIdentifiers: [
|
||||
{
|
||||
yAccessor: 'col-2-1',
|
||||
splitAccessors: {},
|
||||
seriesKeys: ['Logstash Airways', 'col-2-1'],
|
||||
specId: 'histogram-col-2-1',
|
||||
key: 'groupId{__pseudo_stacked_group-ValueAxis-1__}spec{histogram-col-2-1}yAccessor{col-2-1}splitAccessors{col-1-3-Logstash Airways}',
|
||||
} as XYChartSeriesIdentifier,
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
it('renders the color picker', () => {
|
||||
wrapper = mountWithIntl(<Component {...wrapperProps} />);
|
||||
expect(wrapper.find(ColorPicker).length).toBe(1);
|
||||
});
|
||||
|
||||
it('renders the color picker with the colorIsOverwritten prop set to false if color is not overwritten for the specific series', () => {
|
||||
wrapper = mountWithIntl(<Component {...wrapperProps} />);
|
||||
expect(wrapper.find(ColorPicker).prop('colorIsOverwritten')).toBe(false);
|
||||
});
|
||||
|
||||
it('renders the color picker with the colorIsOverwritten prop set to true if color is overwritten for the specific series', () => {
|
||||
uiState.set('vis.colors', { 'Logstash Airways': '#6092c0' });
|
||||
wrapper = mountWithIntl(<Component {...wrapperProps} />);
|
||||
expect(wrapper.find(ColorPicker).prop('colorIsOverwritten')).toBe(true);
|
||||
});
|
||||
|
||||
it('renders the picker on the correct position', () => {
|
||||
wrapper = mountWithIntl(<Component {...wrapperProps} />);
|
||||
expect(wrapper.find(EuiPopover).prop('anchorPosition')).toEqual('rightCenter');
|
||||
});
|
||||
|
||||
it('renders the picker for kibana palette with useLegacyColors set to true', () => {
|
||||
const LegacyPaletteComponent: ComponentType<LegendColorPickerProps> = getColorPicker(
|
||||
'left',
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
'kibana_palette',
|
||||
uiState
|
||||
);
|
||||
wrapper = mountWithIntl(<LegacyPaletteComponent {...wrapperProps} />);
|
||||
expect(wrapper.find(ColorPicker).prop('useLegacyColors')).toBe(true);
|
||||
});
|
||||
});
|
|
@ -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 React, { useCallback } from 'react';
|
||||
|
||||
import { LegendColorPicker, Position, XYChartSeriesIdentifier, SeriesName } from '@elastic/charts';
|
||||
import { PopoverAnchorPosition, EuiWrappingPopover, EuiOutsideClickDetector } from '@elastic/eui';
|
||||
import type { PersistedState } from '@kbn/visualizations-plugin/public';
|
||||
import { ColorPicker } from '@kbn/charts-plugin/public';
|
||||
|
||||
function getAnchorPosition(legendPosition: Position): PopoverAnchorPosition {
|
||||
switch (legendPosition) {
|
||||
case Position.Bottom:
|
||||
return 'upCenter';
|
||||
case Position.Top:
|
||||
return 'downCenter';
|
||||
case Position.Left:
|
||||
return 'rightCenter';
|
||||
default:
|
||||
return 'leftCenter';
|
||||
}
|
||||
}
|
||||
|
||||
const KEY_CODE_ENTER = 13;
|
||||
|
||||
export const getColorPicker =
|
||||
(
|
||||
legendPosition: Position,
|
||||
setColor: (newColor: string | null, seriesKey: string | number) => void,
|
||||
getSeriesName: (series: XYChartSeriesIdentifier) => SeriesName,
|
||||
paletteName: string,
|
||||
uiState: PersistedState
|
||||
): LegendColorPicker =>
|
||||
({ anchor, color, onClose, onChange, seriesIdentifiers: [seriesIdentifier] }) => {
|
||||
const seriesName = getSeriesName(seriesIdentifier as XYChartSeriesIdentifier);
|
||||
const overwriteColors: Record<string, string> = uiState?.get('vis.colors', {});
|
||||
const colorIsOverwritten = Object.keys(overwriteColors).includes(seriesName as string);
|
||||
let keyDownEventOn = false;
|
||||
|
||||
const handleChange = (newColor: string | null) => {
|
||||
if (!seriesName) {
|
||||
return;
|
||||
}
|
||||
if (newColor) {
|
||||
onChange(newColor);
|
||||
}
|
||||
setColor(newColor, seriesName);
|
||||
// close the popover if no color is applied or the user has clicked a color
|
||||
if (!newColor || !keyDownEventOn) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const onKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
|
||||
if (e.keyCode === KEY_CODE_ENTER) {
|
||||
onClose?.();
|
||||
}
|
||||
keyDownEventOn = true;
|
||||
};
|
||||
|
||||
const handleOutsideClick = useCallback(() => {
|
||||
onClose?.();
|
||||
}, [onClose]);
|
||||
|
||||
return (
|
||||
<EuiOutsideClickDetector onOutsideClick={handleOutsideClick}>
|
||||
<EuiWrappingPopover
|
||||
isOpen
|
||||
ownFocus
|
||||
display="block"
|
||||
button={anchor}
|
||||
anchorPosition={getAnchorPosition(legendPosition)}
|
||||
closePopover={onClose}
|
||||
panelPaddingSize="s"
|
||||
>
|
||||
<ColorPicker
|
||||
color={paletteName === 'kibana_palette' ? color : color.toLowerCase()}
|
||||
onChange={handleChange}
|
||||
label={seriesName}
|
||||
useLegacyColors={paletteName === 'kibana_palette'}
|
||||
colorIsOverwritten={colorIsOverwritten}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
</EuiWrappingPopover>
|
||||
</EuiOutsideClickDetector>
|
||||
);
|
||||
};
|
|
@ -1,116 +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 React, { useState, useEffect, useMemo } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiContextMenuPanelDescriptor, EuiIcon, EuiPopover, EuiContextMenu } from '@elastic/eui';
|
||||
import {
|
||||
LegendAction,
|
||||
XYChartSeriesIdentifier,
|
||||
SeriesName,
|
||||
useLegendAction,
|
||||
} from '@elastic/charts';
|
||||
|
||||
import { ClickTriggerEvent } from '@kbn/charts-plugin/public';
|
||||
|
||||
export const getLegendActions = (
|
||||
canFilter: (data: ClickTriggerEvent | null) => Promise<boolean>,
|
||||
getFilterEventData: (series: XYChartSeriesIdentifier) => ClickTriggerEvent | null,
|
||||
onFilter: (data: ClickTriggerEvent, negate?: any) => void,
|
||||
getSeriesName: (series: XYChartSeriesIdentifier) => SeriesName
|
||||
): LegendAction => {
|
||||
return ({ series: [xySeries] }) => {
|
||||
const [popoverOpen, setPopoverOpen] = useState(false);
|
||||
const [isfilterable, setIsfilterable] = useState(false);
|
||||
const series = xySeries as XYChartSeriesIdentifier;
|
||||
const filterData = useMemo(() => getFilterEventData(series), [series]);
|
||||
const [ref, onClose] = useLegendAction<HTMLDivElement>();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => setIsfilterable(await canFilter(filterData)))();
|
||||
}, [filterData]);
|
||||
|
||||
if (!isfilterable || !filterData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const name = getSeriesName(series);
|
||||
const panels: EuiContextMenuPanelDescriptor[] = [
|
||||
{
|
||||
id: 'main',
|
||||
title: `${name}`,
|
||||
items: [
|
||||
{
|
||||
name: i18n.translate('visTypeXy.legend.filterForValueButtonAriaLabel', {
|
||||
defaultMessage: 'Filter for value',
|
||||
}),
|
||||
'data-test-subj': `legend-${name}-filterIn`,
|
||||
icon: <EuiIcon type="plusInCircle" size="m" />,
|
||||
onClick: () => {
|
||||
setPopoverOpen(false);
|
||||
onFilter(filterData);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: i18n.translate('visTypeXy.legend.filterOutValueButtonAriaLabel', {
|
||||
defaultMessage: 'Filter out value',
|
||||
}),
|
||||
'data-test-subj': `legend-${name}-filterOut`,
|
||||
icon: <EuiIcon type="minusInCircle" size="m" />,
|
||||
onClick: () => {
|
||||
setPopoverOpen(false);
|
||||
onFilter(filterData, true);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const Button = (
|
||||
<div
|
||||
tabIndex={0}
|
||||
ref={ref}
|
||||
role="button"
|
||||
aria-pressed="false"
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
marginLeft: 4,
|
||||
marginRight: 4,
|
||||
}}
|
||||
data-test-subj={`legend-${name}`}
|
||||
onKeyPress={() => setPopoverOpen(!popoverOpen)}
|
||||
onClick={() => setPopoverOpen(!popoverOpen)}
|
||||
>
|
||||
<EuiIcon size="s" type="boxesVertical" />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
button={Button}
|
||||
isOpen={popoverOpen}
|
||||
closePopover={() => {
|
||||
setPopoverOpen(false);
|
||||
onClose();
|
||||
}}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="upLeft"
|
||||
title={i18n.translate('visTypeXy.legend.filterOptionsLegend', {
|
||||
defaultMessage: '{legendDataLabel}, filter options',
|
||||
values: { legendDataLabel: name },
|
||||
})}
|
||||
>
|
||||
<EuiContextMenu initialPanelId="main" panels={panels} />
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
||||
};
|
|
@ -1,145 +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 { XYChartSeriesIdentifier } from '@elastic/charts';
|
||||
import { getSeriesNameFn } from './get_series_name_fn';
|
||||
|
||||
const aspects = {
|
||||
series: [
|
||||
{
|
||||
accessor: 'col-1-3',
|
||||
column: 1,
|
||||
title: 'FlightDelayType: Descending',
|
||||
format: {
|
||||
id: 'terms',
|
||||
params: {
|
||||
id: 'string',
|
||||
otherBucketLabel: 'Other',
|
||||
missingBucketLabel: 'Missing',
|
||||
},
|
||||
},
|
||||
aggType: 'terms',
|
||||
aggId: '3',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
x: {
|
||||
accessor: 'col-0-2',
|
||||
column: 0,
|
||||
title: 'timestamp per day',
|
||||
format: {
|
||||
id: 'date',
|
||||
params: {
|
||||
pattern: 'YYYY-MM-DD',
|
||||
},
|
||||
},
|
||||
aggType: 'date_histogram',
|
||||
aggId: '2',
|
||||
params: {
|
||||
date: true,
|
||||
intervalESUnit: 'd',
|
||||
intervalESValue: 1,
|
||||
interval: 86400000,
|
||||
format: 'YYYY-MM-DD',
|
||||
},
|
||||
},
|
||||
y: [
|
||||
{
|
||||
accessor: 'col-1-1',
|
||||
column: 1,
|
||||
title: 'Count',
|
||||
format: {
|
||||
id: 'number',
|
||||
},
|
||||
aggType: 'count',
|
||||
aggId: '1',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const series = {
|
||||
specId: 'histogram-col-1-1',
|
||||
seriesKeys: ['col-1-1'],
|
||||
yAccessor: 'col-1-1',
|
||||
splitAccessors: [],
|
||||
smVerticalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__',
|
||||
smHorizontalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__',
|
||||
groupId: '__pseudo_stacked_group-ValueAxis-1__',
|
||||
seriesType: 'bar',
|
||||
isStacked: true,
|
||||
} as unknown as XYChartSeriesIdentifier;
|
||||
|
||||
const splitAccessors = new Map();
|
||||
splitAccessors.set('col-1-3', 'Weather Delay');
|
||||
|
||||
const seriesSplitAccessors = {
|
||||
specId: 'histogram-col-2-1',
|
||||
seriesKeys: ['Weather Delay', 'col-2-1'],
|
||||
yAccessor: 'col-2-1',
|
||||
splitAccessors,
|
||||
smVerticalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__',
|
||||
smHorizontalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__',
|
||||
groupId: '__pseudo_stacked_group-ValueAxis-1__',
|
||||
seriesType: 'bar',
|
||||
isStacked: true,
|
||||
} as unknown as XYChartSeriesIdentifier;
|
||||
|
||||
describe('getSeriesNameFn', () => {
|
||||
it('returns the y aspects title if splitAccessors are empty array', () => {
|
||||
const getSeriesName = getSeriesNameFn(aspects, false);
|
||||
expect(getSeriesName(series)).toStrictEqual('Count');
|
||||
});
|
||||
|
||||
it('returns the y aspects title if splitAccessors are empty array but mupliple flag is set to true', () => {
|
||||
const getSeriesName = getSeriesNameFn(aspects, true);
|
||||
expect(getSeriesName(series)).toStrictEqual('Count');
|
||||
});
|
||||
|
||||
it('returns the correct string for multiple set to false and given split accessors', () => {
|
||||
const aspectsSplitSeries = {
|
||||
...aspects,
|
||||
y: [
|
||||
{
|
||||
accessor: 'col-2-1',
|
||||
column: 2,
|
||||
title: 'Count',
|
||||
format: {
|
||||
id: 'number',
|
||||
},
|
||||
aggType: 'count',
|
||||
aggId: '1',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
};
|
||||
const getSeriesName = getSeriesNameFn(aspectsSplitSeries, false);
|
||||
expect(getSeriesName(seriesSplitAccessors)).toStrictEqual('Weather Delay');
|
||||
});
|
||||
|
||||
it('returns the correct string for multiple set to true and given split accessors', () => {
|
||||
const aspectsSplitSeries = {
|
||||
...aspects,
|
||||
y: [
|
||||
{
|
||||
accessor: 'col-2-1',
|
||||
column: 2,
|
||||
title: 'Count',
|
||||
format: {
|
||||
id: 'number',
|
||||
},
|
||||
aggType: 'count',
|
||||
aggId: '1',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
};
|
||||
const getSeriesName = getSeriesNameFn(aspectsSplitSeries, true);
|
||||
expect(getSeriesName(seriesSplitAccessors)).toStrictEqual('Weather Delay: Count');
|
||||
});
|
||||
});
|
|
@ -1,48 +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 { memoize } from 'lodash';
|
||||
|
||||
import { XYChartSeriesIdentifier, SeriesName } from '@elastic/charts';
|
||||
|
||||
import { VisConfig } from '../types';
|
||||
|
||||
function getSplitValues(
|
||||
splitAccessors: XYChartSeriesIdentifier['splitAccessors'],
|
||||
seriesAspects?: VisConfig['aspects']['series']
|
||||
) {
|
||||
if (splitAccessors.size < 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const splitValues: Array<string | number> = [];
|
||||
splitAccessors.forEach((value, key) => {
|
||||
const split = (seriesAspects ?? []).find(({ accessor }) => accessor === key);
|
||||
splitValues.push(split?.formatter ? split?.formatter(value) : value);
|
||||
});
|
||||
return splitValues;
|
||||
}
|
||||
|
||||
export const getSeriesNameFn = (aspects: VisConfig['aspects'], multipleY = false) =>
|
||||
memoize(({ splitAccessors, yAccessor }: XYChartSeriesIdentifier): SeriesName => {
|
||||
const splitValues = getSplitValues(splitAccessors, aspects.series);
|
||||
const yAccessorTitle = aspects.y.find(({ accessor }) => accessor === yAccessor)?.title ?? null;
|
||||
|
||||
if (!yAccessorTitle) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (multipleY) {
|
||||
if (splitValues.length === 0) {
|
||||
return yAccessorTitle;
|
||||
}
|
||||
return `${splitValues.join(' - ')}: ${yAccessorTitle}`;
|
||||
}
|
||||
|
||||
return splitValues.length > 0 ? splitValues.join(' - ') : yAccessorTitle;
|
||||
});
|
|
@ -1,64 +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 { AggConfigs } from '@kbn/data-plugin/public';
|
||||
import type { SeriesParam } from '../types';
|
||||
import { getSeriesParams } from './get_series_params';
|
||||
import { sampleAreaVis } from '../sample_vis.test.mocks';
|
||||
|
||||
describe('getSeriesParams', () => {
|
||||
it('returns correct params', () => {
|
||||
const seriesParams = getSeriesParams(
|
||||
sampleAreaVis.data.aggs as unknown as AggConfigs,
|
||||
sampleAreaVis.params.seriesParams as unknown as SeriesParam[],
|
||||
'metric',
|
||||
'ValueAxis-1'
|
||||
);
|
||||
expect(seriesParams).toStrictEqual([
|
||||
{
|
||||
circlesRadius: 5,
|
||||
data: {
|
||||
id: '1',
|
||||
label: 'Total quantity',
|
||||
},
|
||||
drawLinesBetweenPoints: true,
|
||||
interpolate: 'linear',
|
||||
mode: 'stacked',
|
||||
show: 'true',
|
||||
showCircles: true,
|
||||
type: 'area',
|
||||
valueAxis: 'ValueAxis-1',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns default params if no params provided', () => {
|
||||
const seriesParams = getSeriesParams(
|
||||
sampleAreaVis.data.aggs as unknown as AggConfigs,
|
||||
[],
|
||||
'metric',
|
||||
'ValueAxis-1'
|
||||
);
|
||||
expect(seriesParams).toStrictEqual([
|
||||
{
|
||||
circlesRadius: 1,
|
||||
data: {
|
||||
id: '1',
|
||||
label: 'Total quantity',
|
||||
},
|
||||
drawLinesBetweenPoints: true,
|
||||
interpolate: 'linear',
|
||||
lineWidth: 2,
|
||||
mode: 'normal',
|
||||
show: true,
|
||||
showCircles: true,
|
||||
type: 'line',
|
||||
valueAxis: 'ValueAxis-1',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -1,25 +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 moment from 'moment';
|
||||
|
||||
import { getUISettings } from '../services';
|
||||
|
||||
/**
|
||||
* Get timeZone from uiSettings
|
||||
*/
|
||||
export function getTimeZone() {
|
||||
const uiSettings = getUISettings();
|
||||
if (uiSettings.isDefault('dateFormat:tz')) {
|
||||
const detectedTimeZone = moment.tz.guess();
|
||||
if (detectedTimeZone) return detectedTimeZone;
|
||||
else return moment().format('Z');
|
||||
} else {
|
||||
return uiSettings.get('dateFormat:tz', 'Browser');
|
||||
}
|
||||
}
|
|
@ -1,16 +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.
|
||||
*/
|
||||
|
||||
export { renderAllSeries } from './render_all_series';
|
||||
export { getTimeZone } from './get_time_zone';
|
||||
export { getLegendActions } from './get_legend_actions';
|
||||
export { getSeriesNameFn } from './get_series_name_fn';
|
||||
export { getXDomain, getAdjustedDomain } from './domain';
|
||||
export { getColorPicker } from './get_color_picker';
|
||||
export { getXAccessor } from './accessors';
|
||||
export { getAllSeries } from './get_all_series';
|
|
@ -1,529 +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 { AxisMode, VisConfig } from '../types';
|
||||
|
||||
export const getVisConfig = (): VisConfig => {
|
||||
return {
|
||||
markSizeRatio: 5.3999999999999995,
|
||||
fittingFunction: 'linear',
|
||||
detailedTooltip: true,
|
||||
isTimeChart: true,
|
||||
showCurrentTime: false,
|
||||
showValueLabel: false,
|
||||
enableHistogramMode: true,
|
||||
tooltip: {
|
||||
type: 'vertical',
|
||||
},
|
||||
aspects: {
|
||||
x: {
|
||||
accessor: 'col-0-2',
|
||||
column: 0,
|
||||
title: 'order_date per minute',
|
||||
format: {
|
||||
id: 'date',
|
||||
params: {
|
||||
pattern: 'HH:mm',
|
||||
},
|
||||
},
|
||||
aggType: 'date_histogram',
|
||||
aggId: '2',
|
||||
params: {
|
||||
date: true,
|
||||
intervalESUnit: 'm',
|
||||
intervalESValue: 1,
|
||||
interval: 60000,
|
||||
format: 'HH:mm',
|
||||
},
|
||||
},
|
||||
y: [
|
||||
{
|
||||
accessor: 'col-1-3',
|
||||
column: 1,
|
||||
title: 'Average products.base_price',
|
||||
format: {
|
||||
id: 'number',
|
||||
},
|
||||
aggType: 'avg',
|
||||
aggId: '3',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
xAxis: {
|
||||
id: 'CategoryAxis-1',
|
||||
position: 'bottom',
|
||||
show: true,
|
||||
style: {
|
||||
axisTitle: {
|
||||
visible: true,
|
||||
},
|
||||
tickLabel: {
|
||||
visible: true,
|
||||
rotation: 0,
|
||||
},
|
||||
},
|
||||
groupId: 'CategoryAxis-1',
|
||||
title: 'order_date per minute',
|
||||
ticks: {
|
||||
show: true,
|
||||
showOverlappingLabels: false,
|
||||
showDuplicates: false,
|
||||
},
|
||||
grid: {
|
||||
show: false,
|
||||
},
|
||||
scale: {
|
||||
type: 'time',
|
||||
},
|
||||
integersOnly: false,
|
||||
},
|
||||
yAxes: [
|
||||
{
|
||||
id: 'ValueAxis-1',
|
||||
position: 'left',
|
||||
show: true,
|
||||
style: {
|
||||
axisTitle: {
|
||||
visible: true,
|
||||
},
|
||||
tickLabel: {
|
||||
visible: true,
|
||||
rotation: 0,
|
||||
},
|
||||
},
|
||||
groupId: 'ValueAxis-1',
|
||||
title: 'Avg of products.base_price',
|
||||
ticks: {
|
||||
show: true,
|
||||
rotation: 0,
|
||||
showOverlappingLabels: true,
|
||||
showDuplicates: true,
|
||||
},
|
||||
grid: {
|
||||
show: false,
|
||||
},
|
||||
scale: {
|
||||
mode: AxisMode.Percentage,
|
||||
type: 'linear',
|
||||
},
|
||||
domain: {
|
||||
min: NaN,
|
||||
max: NaN,
|
||||
},
|
||||
integersOnly: false,
|
||||
},
|
||||
],
|
||||
legend: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
},
|
||||
rotation: 0,
|
||||
thresholdLine: {
|
||||
color: '#E7664C',
|
||||
show: false,
|
||||
value: 10,
|
||||
width: 1,
|
||||
groupId: 'ValueAxis-1',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getVisConfigMutipleYaxis = (): VisConfig => {
|
||||
return {
|
||||
markSizeRatio: 5.3999999999999995,
|
||||
fittingFunction: 'linear',
|
||||
detailedTooltip: true,
|
||||
isTimeChart: true,
|
||||
showCurrentTime: false,
|
||||
showValueLabel: false,
|
||||
enableHistogramMode: true,
|
||||
tooltip: {
|
||||
type: 'vertical',
|
||||
},
|
||||
aspects: {
|
||||
x: {
|
||||
accessor: 'col-0-2',
|
||||
column: 0,
|
||||
title: 'order_date per minute',
|
||||
format: {
|
||||
id: 'date',
|
||||
params: {
|
||||
pattern: 'HH:mm',
|
||||
},
|
||||
},
|
||||
aggType: 'date_histogram',
|
||||
aggId: '2',
|
||||
params: {
|
||||
date: true,
|
||||
intervalESUnit: 'm',
|
||||
intervalESValue: 1,
|
||||
interval: 60000,
|
||||
format: 'HH:mm',
|
||||
},
|
||||
},
|
||||
y: [
|
||||
{
|
||||
accessor: 'col-1-3',
|
||||
column: 1,
|
||||
title: 'Average products.base_price',
|
||||
format: {
|
||||
id: 'number',
|
||||
},
|
||||
aggType: 'avg',
|
||||
aggId: '3',
|
||||
params: {},
|
||||
},
|
||||
{
|
||||
accessor: 'col-1-2',
|
||||
column: 1,
|
||||
title: 'Average products.taxful_price',
|
||||
format: {
|
||||
id: 'number',
|
||||
},
|
||||
aggType: 'avg',
|
||||
aggId: '33',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
xAxis: {
|
||||
id: 'CategoryAxis-1',
|
||||
position: 'bottom',
|
||||
show: true,
|
||||
style: {
|
||||
axisTitle: {
|
||||
visible: true,
|
||||
},
|
||||
tickLabel: {
|
||||
visible: true,
|
||||
rotation: 0,
|
||||
},
|
||||
},
|
||||
groupId: 'CategoryAxis-1',
|
||||
title: 'order_date per minute',
|
||||
ticks: {
|
||||
show: true,
|
||||
showOverlappingLabels: false,
|
||||
showDuplicates: false,
|
||||
},
|
||||
grid: {
|
||||
show: false,
|
||||
},
|
||||
scale: {
|
||||
type: 'time',
|
||||
},
|
||||
integersOnly: false,
|
||||
},
|
||||
yAxes: [
|
||||
{
|
||||
id: 'ValueAxis-1',
|
||||
position: 'left',
|
||||
show: true,
|
||||
style: {
|
||||
axisTitle: {
|
||||
visible: true,
|
||||
},
|
||||
tickLabel: {
|
||||
visible: true,
|
||||
rotation: 0,
|
||||
},
|
||||
},
|
||||
groupId: 'ValueAxis-1',
|
||||
title: 'Avg of products.base_price',
|
||||
ticks: {
|
||||
show: true,
|
||||
rotation: 0,
|
||||
showOverlappingLabels: true,
|
||||
showDuplicates: true,
|
||||
},
|
||||
grid: {
|
||||
show: false,
|
||||
},
|
||||
scale: {
|
||||
mode: AxisMode.Normal,
|
||||
type: 'linear',
|
||||
},
|
||||
domain: {
|
||||
min: NaN,
|
||||
max: NaN,
|
||||
},
|
||||
integersOnly: false,
|
||||
},
|
||||
],
|
||||
legend: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
},
|
||||
rotation: 0,
|
||||
thresholdLine: {
|
||||
color: '#E7664C',
|
||||
show: false,
|
||||
value: 10,
|
||||
width: 1,
|
||||
groupId: 'ValueAxis-1',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getVisConfigPercentiles = (): VisConfig => {
|
||||
return {
|
||||
markSizeRatio: 5.3999999999999995,
|
||||
fittingFunction: 'linear',
|
||||
detailedTooltip: true,
|
||||
isTimeChart: true,
|
||||
showCurrentTime: false,
|
||||
showValueLabel: false,
|
||||
enableHistogramMode: true,
|
||||
tooltip: {
|
||||
type: 'vertical',
|
||||
},
|
||||
aspects: {
|
||||
x: {
|
||||
accessor: 'col-0-2',
|
||||
column: 0,
|
||||
title: 'order_date per minute',
|
||||
format: {
|
||||
id: 'date',
|
||||
params: {
|
||||
pattern: 'HH:mm',
|
||||
},
|
||||
},
|
||||
aggType: 'date_histogram',
|
||||
aggId: '2',
|
||||
params: {
|
||||
date: true,
|
||||
intervalESUnit: 'm',
|
||||
intervalESValue: 1,
|
||||
interval: 60000,
|
||||
format: 'HH:mm',
|
||||
},
|
||||
},
|
||||
y: [
|
||||
{
|
||||
accessor: 'col-1-3.1',
|
||||
column: 1,
|
||||
title: '1st percentile of products.base_price',
|
||||
format: {
|
||||
id: 'number',
|
||||
},
|
||||
aggType: 'percentiles',
|
||||
aggId: '3.1',
|
||||
params: {},
|
||||
},
|
||||
{
|
||||
accessor: 'col-2-3.5',
|
||||
column: 2,
|
||||
title: '5th percentile of products.base_price',
|
||||
format: {
|
||||
id: 'number',
|
||||
},
|
||||
aggType: 'percentiles',
|
||||
aggId: '3.5',
|
||||
params: {},
|
||||
},
|
||||
{
|
||||
accessor: 'col-3-3.25',
|
||||
column: 3,
|
||||
title: '25th percentile of products.base_price',
|
||||
format: {
|
||||
id: 'number',
|
||||
},
|
||||
aggType: 'percentiles',
|
||||
aggId: '3.25',
|
||||
params: {},
|
||||
},
|
||||
{
|
||||
accessor: 'col-4-3.50',
|
||||
column: 4,
|
||||
title: '50th percentile of products.base_price',
|
||||
format: {
|
||||
id: 'number',
|
||||
},
|
||||
aggType: 'percentiles',
|
||||
aggId: '3.50',
|
||||
params: {},
|
||||
},
|
||||
{
|
||||
accessor: 'col-5-3.75',
|
||||
column: 5,
|
||||
title: '75th percentile of products.base_price',
|
||||
format: {
|
||||
id: 'number',
|
||||
},
|
||||
aggType: 'percentiles',
|
||||
aggId: '3.75',
|
||||
params: {},
|
||||
},
|
||||
{
|
||||
accessor: 'col-6-3.95',
|
||||
column: 6,
|
||||
title: '95th percentile of products.base_price',
|
||||
format: {
|
||||
id: 'number',
|
||||
},
|
||||
aggType: 'percentiles',
|
||||
aggId: '3.95',
|
||||
params: {},
|
||||
},
|
||||
{
|
||||
accessor: 'col-7-3.99',
|
||||
column: 7,
|
||||
title: '99th percentile of products.base_price',
|
||||
format: {
|
||||
id: 'number',
|
||||
},
|
||||
aggType: 'percentiles',
|
||||
aggId: '3.99',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
xAxis: {
|
||||
id: 'CategoryAxis-1',
|
||||
position: 'bottom',
|
||||
show: true,
|
||||
style: {
|
||||
axisTitle: {
|
||||
visible: true,
|
||||
},
|
||||
tickLabel: {
|
||||
visible: true,
|
||||
rotation: 0,
|
||||
},
|
||||
},
|
||||
groupId: 'CategoryAxis-1',
|
||||
title: 'order_date per minute',
|
||||
ticks: {
|
||||
show: true,
|
||||
showOverlappingLabels: false,
|
||||
showDuplicates: false,
|
||||
},
|
||||
grid: {
|
||||
show: false,
|
||||
},
|
||||
scale: {
|
||||
type: 'time',
|
||||
},
|
||||
integersOnly: false,
|
||||
},
|
||||
yAxes: [
|
||||
{
|
||||
id: 'ValueAxis-1',
|
||||
position: 'left',
|
||||
show: true,
|
||||
style: {
|
||||
axisTitle: {
|
||||
visible: true,
|
||||
},
|
||||
tickLabel: {
|
||||
visible: true,
|
||||
rotation: 0,
|
||||
},
|
||||
},
|
||||
groupId: 'ValueAxis-1',
|
||||
title: 'Percentiles of products.base_price',
|
||||
ticks: {
|
||||
show: true,
|
||||
rotation: 0,
|
||||
showOverlappingLabels: true,
|
||||
showDuplicates: true,
|
||||
},
|
||||
grid: {
|
||||
show: false,
|
||||
},
|
||||
scale: {
|
||||
mode: AxisMode.Normal,
|
||||
type: 'linear',
|
||||
},
|
||||
domain: {
|
||||
min: NaN,
|
||||
max: NaN,
|
||||
},
|
||||
integersOnly: false,
|
||||
},
|
||||
],
|
||||
legend: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
},
|
||||
rotation: 0,
|
||||
thresholdLine: {
|
||||
color: '#E7664C',
|
||||
show: false,
|
||||
value: 10,
|
||||
width: 1,
|
||||
groupId: 'ValueAxis-1',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getPercentilesData = () => {
|
||||
return [
|
||||
{
|
||||
'col-0-2': 1610961900000,
|
||||
'col-1-3.1': 11.9921875,
|
||||
'col-2-3.5': 11.9921875,
|
||||
'col-3-3.25': 11.9921875,
|
||||
'col-4-3.50': 38.49609375,
|
||||
'col-5-3.75': 65,
|
||||
'col-6-3.95': 65,
|
||||
'col-7-3.99': 65,
|
||||
},
|
||||
{
|
||||
'col-0-2': 1610962980000,
|
||||
'col-1-3.1': 28.984375000000004,
|
||||
'col-2-3.5': 28.984375,
|
||||
'col-3-3.25': 28.984375,
|
||||
'col-4-3.50': 30.9921875,
|
||||
'col-5-3.75': 41.5,
|
||||
'col-6-3.95': 50,
|
||||
'col-7-3.99': 50,
|
||||
},
|
||||
{
|
||||
'col-0-2': 1610963280000,
|
||||
'col-1-3.1': 11.9921875,
|
||||
'col-2-3.5': 11.9921875,
|
||||
'col-3-3.25': 11.9921875,
|
||||
'col-4-3.50': 12.9921875,
|
||||
'col-5-3.75': 13.9921875,
|
||||
'col-6-3.95': 13.9921875,
|
||||
'col-7-3.99': 13.9921875,
|
||||
},
|
||||
{
|
||||
'col-0-2': 1610964180000,
|
||||
'col-1-3.1': 11.9921875,
|
||||
'col-2-3.5': 11.9921875,
|
||||
'col-3-3.25': 14.9921875,
|
||||
'col-4-3.50': 15.98828125,
|
||||
'col-5-3.75': 24.984375,
|
||||
'col-6-3.95': 85,
|
||||
'col-7-3.99': 85,
|
||||
},
|
||||
{
|
||||
'col-0-2': 1610964420000,
|
||||
'col-1-3.1': 11.9921875,
|
||||
'col-2-3.5': 11.9921875,
|
||||
'col-3-3.25': 11.9921875,
|
||||
'col-4-3.50': 23.99609375,
|
||||
'col-5-3.75': 42,
|
||||
'col-6-3.95': 42,
|
||||
'col-7-3.99': 42,
|
||||
},
|
||||
{
|
||||
'col-0-2': 1610964600000,
|
||||
'col-1-3.1': 10.9921875,
|
||||
'col-2-3.5': 10.992187500000002,
|
||||
'col-3-3.25': 10.9921875,
|
||||
'col-4-3.50': 12.4921875,
|
||||
'col-5-3.75': 13.9921875,
|
||||
'col-6-3.95': 13.9921875,
|
||||
'col-7-3.99': 13.9921875,
|
||||
},
|
||||
];
|
||||
};
|
|
@ -1,187 +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 React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { AreaSeries, BarSeries, CurveType } from '@elastic/charts';
|
||||
import { DatatableRow } from '@kbn/expressions-plugin/public';
|
||||
import { renderAllSeries } from './render_all_series';
|
||||
import {
|
||||
getVisConfig,
|
||||
getVisConfigPercentiles,
|
||||
getPercentilesData,
|
||||
getVisConfigMutipleYaxis,
|
||||
} from './render_all_series.test.mocks';
|
||||
import { SeriesParam, VisConfig } from '../types';
|
||||
|
||||
const defaultSeriesParams = [
|
||||
{
|
||||
data: {
|
||||
id: '3',
|
||||
label: 'Label',
|
||||
},
|
||||
drawLinesBetweenPoints: true,
|
||||
interpolate: 'linear',
|
||||
lineWidth: 2,
|
||||
mode: 'stacked',
|
||||
show: true,
|
||||
showCircles: true,
|
||||
circlesRadius: 3,
|
||||
type: 'area',
|
||||
valueAxis: 'ValueAxis-1',
|
||||
},
|
||||
] as SeriesParam[];
|
||||
|
||||
const defaultData = [
|
||||
{
|
||||
'col-0-2': 1610960220000,
|
||||
'col-1-3': 26.984375,
|
||||
},
|
||||
{
|
||||
'col-0-2': 1610961300000,
|
||||
'col-1-3': 30.99609375,
|
||||
},
|
||||
{
|
||||
'col-0-2': 1610961900000,
|
||||
'col-1-3': 38.49609375,
|
||||
},
|
||||
{
|
||||
'col-0-2': 1610962980000,
|
||||
'col-1-3': 35.2421875,
|
||||
},
|
||||
];
|
||||
|
||||
describe('renderAllSeries', function () {
|
||||
const getAllSeries = (visConfig: VisConfig, params: SeriesParam[], data: DatatableRow[]) => {
|
||||
return renderAllSeries(
|
||||
visConfig,
|
||||
params,
|
||||
data,
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
'Europe/Athens',
|
||||
'col-0-2',
|
||||
[]
|
||||
);
|
||||
};
|
||||
|
||||
it('renders an area Series and not a bar series if type is area', () => {
|
||||
const renderSeries = getAllSeries(getVisConfig(), defaultSeriesParams, defaultData);
|
||||
const wrapper = shallow(<div>{renderSeries}</div>);
|
||||
expect(wrapper.find(AreaSeries).length).toBe(1);
|
||||
expect(wrapper.find(BarSeries).length).toBe(0);
|
||||
});
|
||||
|
||||
it('renders a bar Series in case of histogram', () => {
|
||||
const barSeriesParams = [{ ...defaultSeriesParams[0], type: 'histogram' }];
|
||||
|
||||
const renderBarSeries = renderAllSeries(
|
||||
getVisConfig(),
|
||||
barSeriesParams as SeriesParam[],
|
||||
defaultData,
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
'Europe/Athens',
|
||||
'col-0-2',
|
||||
[]
|
||||
);
|
||||
const wrapper = shallow(<div>{renderBarSeries}</div>);
|
||||
expect(wrapper.find(AreaSeries).length).toBe(0);
|
||||
expect(wrapper.find(BarSeries).length).toBe(1);
|
||||
});
|
||||
|
||||
it('renders percentage data for percentage mode', () => {
|
||||
const barSeriesParams = [{ ...defaultSeriesParams[0], type: 'histogram', mode: 'percentage' }];
|
||||
const config = getVisConfig();
|
||||
|
||||
const renderBarSeries = renderAllSeries(
|
||||
config,
|
||||
barSeriesParams as SeriesParam[],
|
||||
defaultData,
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
'Europe/Athens',
|
||||
'col-0-2',
|
||||
[]
|
||||
);
|
||||
const wrapper = shallow(<div>{renderBarSeries}</div>);
|
||||
expect(wrapper.find(BarSeries).length).toBe(1);
|
||||
expect(wrapper.find(BarSeries).prop('stackMode')).toEqual('percentage');
|
||||
expect(wrapper.find(BarSeries).prop('data')).toEqual([
|
||||
{
|
||||
'col-0-2': 1610960220000,
|
||||
'col-1-3': 1,
|
||||
},
|
||||
{
|
||||
'col-0-2': 1610961300000,
|
||||
'col-1-3': 1,
|
||||
},
|
||||
{
|
||||
'col-0-2': 1610961900000,
|
||||
'col-1-3': 1,
|
||||
},
|
||||
{
|
||||
'col-0-2': 1610962980000,
|
||||
'col-1-3': 1,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('renders the correct yAccessors for not percentile aggs', () => {
|
||||
const renderSeries = getAllSeries(getVisConfig(), defaultSeriesParams, defaultData);
|
||||
const wrapper = shallow(<div>{renderSeries}</div>);
|
||||
expect(wrapper.find(AreaSeries).prop('yAccessors')).toEqual(['col-1-3']);
|
||||
});
|
||||
|
||||
it('renders the correct yAccessors for multiple yAxis', () => {
|
||||
const mutipleYAxisConfig = getVisConfigMutipleYaxis();
|
||||
const renderMutipleYAxisSeries = renderAllSeries(
|
||||
mutipleYAxisConfig,
|
||||
defaultSeriesParams as SeriesParam[],
|
||||
defaultData,
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
'Europe/Athens',
|
||||
'col-0-2',
|
||||
[]
|
||||
);
|
||||
const wrapper = shallow(<div>{renderMutipleYAxisSeries}</div>);
|
||||
expect(wrapper.find(AreaSeries).prop('yAccessors')).toEqual(['col-1-3']);
|
||||
});
|
||||
|
||||
it('renders the correct yAccessors for percentile aggs', () => {
|
||||
const percentilesConfig = getVisConfigPercentiles();
|
||||
const percentilesData = getPercentilesData();
|
||||
const renderPercentileSeries = renderAllSeries(
|
||||
percentilesConfig,
|
||||
defaultSeriesParams as SeriesParam[],
|
||||
percentilesData,
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
'Europe/Athens',
|
||||
'col-0-2',
|
||||
[]
|
||||
);
|
||||
const wrapper = shallow(<div>{renderPercentileSeries}</div>);
|
||||
expect(wrapper.find(AreaSeries).prop('yAccessors')).toEqual([
|
||||
'col-1-3.1',
|
||||
'col-2-3.5',
|
||||
'col-3-3.25',
|
||||
'col-4-3.50',
|
||||
'col-5-3.75',
|
||||
'col-6-3.95',
|
||||
'col-7-3.99',
|
||||
]);
|
||||
});
|
||||
|
||||
it('defaults the CurveType to linear', () => {
|
||||
const renderSeries = getAllSeries(getVisConfig(), defaultSeriesParams, defaultData);
|
||||
const wrapper = shallow(<div>{renderSeries}</div>);
|
||||
expect(wrapper.find(AreaSeries).prop('curve')).toEqual(CurveType.LINEAR);
|
||||
});
|
||||
});
|
|
@ -1,222 +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 React from 'react';
|
||||
import {
|
||||
AreaSeries,
|
||||
CurveType,
|
||||
BarSeries,
|
||||
XYChartSeriesIdentifier,
|
||||
SeriesColorAccessorFn,
|
||||
SeriesName,
|
||||
Accessor,
|
||||
AccessorFn,
|
||||
ColorVariant,
|
||||
LabelOverflowConstraint,
|
||||
} from '@elastic/charts';
|
||||
|
||||
import { DatatableRow } from '@kbn/expressions-plugin/public';
|
||||
|
||||
import { ChartType } from '../../common';
|
||||
import { SeriesParam, VisConfig, Aspect } from '../types';
|
||||
import { isValidSeriesForDimension } from './accessors';
|
||||
import { computePercentageData } from './compute_percentage_data';
|
||||
|
||||
/**
|
||||
* Matches vislib curve to elastic charts
|
||||
* @param type curve type
|
||||
*/
|
||||
const getCurveType = (type?: 'linear' | 'cardinal' | 'step-after'): CurveType => {
|
||||
switch (type) {
|
||||
case 'cardinal':
|
||||
return CurveType.CURVE_MONOTONE_X;
|
||||
case 'step-after':
|
||||
return CurveType.CURVE_STEP_AFTER;
|
||||
case 'linear':
|
||||
default:
|
||||
return CurveType.LINEAR;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders chart Line, Area or Bar series
|
||||
* @param config
|
||||
* @param seriesParams
|
||||
* @param data
|
||||
* @param getSeriesName
|
||||
* @param getSeriesColor
|
||||
*/
|
||||
export const renderAllSeries = (
|
||||
{
|
||||
aspects,
|
||||
yAxes,
|
||||
xAxis,
|
||||
showValueLabel,
|
||||
enableHistogramMode,
|
||||
fittingFunction,
|
||||
fillOpacity,
|
||||
}: VisConfig,
|
||||
seriesParams: SeriesParam[],
|
||||
data: DatatableRow[],
|
||||
getSeriesName: (series: XYChartSeriesIdentifier) => SeriesName,
|
||||
getSeriesColor: SeriesColorAccessorFn,
|
||||
timeZone: string,
|
||||
xAccessor: Accessor | AccessorFn,
|
||||
splitSeriesAccessors: Array<Accessor | AccessorFn>
|
||||
) => {
|
||||
let percentageModeComputedData: DatatableRow[] = [];
|
||||
yAxes.forEach((yAxis) => {
|
||||
const scale = yAxis.scale;
|
||||
// find the series that are positioned on this axis
|
||||
const series = seriesParams.filter((seriesParam) => seriesParam.valueAxis === yAxis.groupId);
|
||||
const yAspects: Aspect[] = [];
|
||||
series.forEach((seriesParam) => {
|
||||
const aggId = seriesParam.data.id;
|
||||
const accessorsInSeries = aspects.y.filter((aspect) => aspect.aggId === aggId);
|
||||
yAspects.push(...accessorsInSeries);
|
||||
});
|
||||
const yAccessors = yAspects.map((aspect) => {
|
||||
return aspect.accessor;
|
||||
}) as string[];
|
||||
if (scale.mode === 'percentage') {
|
||||
const splitChartAccessor = aspects.splitColumn?.accessor || aspects.splitRow?.accessor;
|
||||
percentageModeComputedData = computePercentageData(
|
||||
data,
|
||||
xAccessor,
|
||||
yAccessors,
|
||||
splitChartAccessor
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return seriesParams.map(
|
||||
({
|
||||
show,
|
||||
valueAxis: groupId,
|
||||
data: { id: paramId },
|
||||
lineWidth: strokeWidth,
|
||||
showCircles,
|
||||
circlesRadius,
|
||||
drawLinesBetweenPoints,
|
||||
mode,
|
||||
interpolate,
|
||||
type,
|
||||
}) => {
|
||||
const yAspects = aspects.y.filter((aspect) => isValidSeriesForDimension(paramId, aspect));
|
||||
if (!show || !yAspects.length) {
|
||||
return null;
|
||||
}
|
||||
const yAccessors = yAspects.map((aspect) => aspect.accessor) as string[];
|
||||
|
||||
const id = `${type}-${yAccessors[0]}`;
|
||||
const yAxisScale = yAxes.find(({ groupId: axisGroupId }) => axisGroupId === groupId)?.scale;
|
||||
|
||||
const isStacked = mode === 'stacked';
|
||||
|
||||
const stackMode = yAxisScale?.mode === 'normal' ? undefined : yAxisScale?.mode;
|
||||
// needed to seperate stacked and non-stacked bars into unique pseudo groups
|
||||
const pseudoGroupId = isStacked ? `__pseudo_stacked_group-${groupId}__` : groupId;
|
||||
// set domain of stacked groups to use actual groupId not pseudo groupdId
|
||||
const useDefaultGroupDomain = isStacked ? groupId : undefined;
|
||||
|
||||
switch (type) {
|
||||
case ChartType.Histogram:
|
||||
return (
|
||||
<BarSeries
|
||||
key={id}
|
||||
id={id}
|
||||
name={getSeriesName}
|
||||
color={getSeriesColor}
|
||||
tickFormat={yAspects[0].formatter}
|
||||
groupId={pseudoGroupId}
|
||||
useDefaultGroupDomain={useDefaultGroupDomain}
|
||||
xScaleType={xAxis.scale.type}
|
||||
yScaleType={yAxisScale?.type}
|
||||
xAccessor={xAccessor}
|
||||
yAccessors={yAccessors}
|
||||
splitSeriesAccessors={splitSeriesAccessors}
|
||||
data={
|
||||
!isStacked && yAxisScale?.mode === 'percentage' ? percentageModeComputedData : data
|
||||
}
|
||||
timeZone={timeZone}
|
||||
stackAccessors={isStacked ? ['__any_value__'] : undefined}
|
||||
enableHistogramMode={enableHistogramMode}
|
||||
stackMode={stackMode}
|
||||
minBarHeight={2}
|
||||
displayValueSettings={{
|
||||
showValueLabel,
|
||||
isValueContainedInElement: false,
|
||||
isAlternatingValueLabel: false,
|
||||
overflowConstraints: [
|
||||
LabelOverflowConstraint.ChartEdges,
|
||||
LabelOverflowConstraint.BarGeometry,
|
||||
],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
case ChartType.Area:
|
||||
case ChartType.Line:
|
||||
const markSizeAccessor = showCircles ? aspects.z?.accessor ?? undefined : undefined;
|
||||
|
||||
return (
|
||||
<AreaSeries
|
||||
key={id}
|
||||
id={id}
|
||||
fit={fittingFunction}
|
||||
color={getSeriesColor}
|
||||
tickFormat={yAspects[0].formatter}
|
||||
name={getSeriesName}
|
||||
curve={getCurveType(interpolate)}
|
||||
groupId={pseudoGroupId}
|
||||
useDefaultGroupDomain={useDefaultGroupDomain}
|
||||
xScaleType={xAxis.scale.type}
|
||||
yScaleType={yAxisScale?.type}
|
||||
xAccessor={xAccessor}
|
||||
yAccessors={yAccessors}
|
||||
markSizeAccessor={markSizeAccessor}
|
||||
markFormat={aspects.z?.formatter}
|
||||
splitSeriesAccessors={splitSeriesAccessors}
|
||||
data={
|
||||
!isStacked && yAxisScale?.mode === 'percentage' ? percentageModeComputedData : data
|
||||
}
|
||||
stackAccessors={isStacked ? ['__any_value__'] : undefined}
|
||||
displayValueSettings={{
|
||||
showValueLabel,
|
||||
overflowConstraints: [LabelOverflowConstraint.ChartEdges],
|
||||
}}
|
||||
timeZone={timeZone}
|
||||
stackMode={stackMode}
|
||||
areaSeriesStyle={{
|
||||
area: {
|
||||
...(type === ChartType.Line ? { opacity: 0 } : { opacity: fillOpacity }),
|
||||
},
|
||||
fit: {
|
||||
area: {
|
||||
...(type === ChartType.Line ? { opacity: 0 } : { opacity: fillOpacity }),
|
||||
},
|
||||
},
|
||||
line: {
|
||||
strokeWidth,
|
||||
visible: drawLinesBetweenPoints,
|
||||
},
|
||||
point: {
|
||||
visible: showCircles,
|
||||
fill: markSizeAccessor ? ColorVariant.Series : undefined,
|
||||
radius: circlesRadius,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
// Error: unsupported chart type
|
||||
return null;
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -1,413 +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 React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { PaletteRegistry } from '@kbn/coloring';
|
||||
import {
|
||||
Chart,
|
||||
ElementClickListener,
|
||||
XYChartElementEvent,
|
||||
Position,
|
||||
XYChartSeriesIdentifier,
|
||||
BrushEndListener,
|
||||
RenderChangeListener,
|
||||
ScaleType,
|
||||
AccessorFn,
|
||||
Accessor,
|
||||
XYBrushEvent,
|
||||
Placement,
|
||||
} from '@elastic/charts';
|
||||
|
||||
import { compact } from 'lodash';
|
||||
import {
|
||||
getFilterFromChartClickEventFn,
|
||||
getFilterFromSeriesFn,
|
||||
LegendToggle,
|
||||
getBrushFromChartBrushEventFn,
|
||||
ClickTriggerEvent,
|
||||
useActiveCursor,
|
||||
} from '@kbn/charts-plugin/public';
|
||||
import { Datatable, IInterpreterRenderHandlers } from '@kbn/expressions-plugin/public';
|
||||
import {
|
||||
DEFAULT_LEGEND_SIZE,
|
||||
LegendSizeToPixels,
|
||||
PersistedState,
|
||||
} from '@kbn/visualizations-plugin/public';
|
||||
import { VisParams } from './types';
|
||||
import {
|
||||
getAdjustedDomain,
|
||||
getXDomain,
|
||||
getTimeZone,
|
||||
renderAllSeries,
|
||||
getSeriesNameFn,
|
||||
getLegendActions,
|
||||
getColorPicker,
|
||||
getXAccessor,
|
||||
getAllSeries,
|
||||
} from './utils';
|
||||
import { XYAxis, XYEndzones, XYCurrentTime, XYSettings, XYThresholdLine } from './components';
|
||||
import { getConfig } from './config';
|
||||
import { getThemeService, getDataActions, getPalettesService, getActiveCursor } from './services';
|
||||
import { ChartType } from '../common';
|
||||
|
||||
import './_chart.scss';
|
||||
import {
|
||||
COMPLEX_SPLIT_ACCESSOR,
|
||||
getComplexAccessor,
|
||||
getSplitSeriesAccessorFnMap,
|
||||
} from './utils/accessors';
|
||||
import { ChartSplit } from './chart_split';
|
||||
|
||||
export interface VisComponentProps {
|
||||
visParams: VisParams;
|
||||
visData: Datatable;
|
||||
uiState: PersistedState;
|
||||
fireEvent: IInterpreterRenderHandlers['event'];
|
||||
renderComplete: IInterpreterRenderHandlers['done'];
|
||||
syncColors: boolean;
|
||||
syncTooltips: boolean;
|
||||
useLegacyTimeAxis: boolean;
|
||||
}
|
||||
|
||||
export type VisComponentType = typeof VisComponent;
|
||||
|
||||
const VisComponent = (props: VisComponentProps) => {
|
||||
const [showLegend, setShowLegend] = useState<boolean>(() => {
|
||||
// TODO: Check when this bwc can safely be removed
|
||||
const bwcLegendStateDefault =
|
||||
props.visParams.addLegend == null ? true : props.visParams.addLegend;
|
||||
return props.uiState?.get('vis.legendOpen', bwcLegendStateDefault) as boolean;
|
||||
});
|
||||
const [palettesRegistry, setPalettesRegistry] = useState<PaletteRegistry | null>(null);
|
||||
const chartRef = useRef<Chart>(null);
|
||||
|
||||
const handleCursorUpdate = useActiveCursor(getActiveCursor(), chartRef, {
|
||||
datatables: [props.visData],
|
||||
});
|
||||
|
||||
const onRenderChange = useCallback<RenderChangeListener>(
|
||||
(isRendered) => {
|
||||
if (isRendered) {
|
||||
props.renderComplete();
|
||||
}
|
||||
},
|
||||
[props]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPalettes = async () => {
|
||||
const palettes = await getPalettesService().getPalettes();
|
||||
setPalettesRegistry(palettes);
|
||||
};
|
||||
fetchPalettes();
|
||||
}, []);
|
||||
|
||||
const handleFilterClick = useCallback(
|
||||
(
|
||||
visData: Datatable,
|
||||
xAccessor: Accessor | AccessorFn,
|
||||
splitSeriesAccessors: Array<Accessor | AccessorFn>,
|
||||
splitChartAccessor?: Accessor | AccessorFn
|
||||
): ElementClickListener => {
|
||||
const splitSeriesAccessorFnMap = getSplitSeriesAccessorFnMap(splitSeriesAccessors);
|
||||
return (elements) => {
|
||||
if (xAccessor !== null) {
|
||||
const event = getFilterFromChartClickEventFn(
|
||||
visData,
|
||||
xAccessor,
|
||||
splitSeriesAccessorFnMap,
|
||||
splitChartAccessor
|
||||
)(elements as XYChartElementEvent[]);
|
||||
props.fireEvent(event);
|
||||
}
|
||||
};
|
||||
},
|
||||
[props]
|
||||
);
|
||||
|
||||
const handleBrush = useCallback(
|
||||
(
|
||||
visData: Datatable,
|
||||
xAccessor: Accessor | AccessorFn,
|
||||
isInterval: boolean
|
||||
): BrushEndListener | undefined => {
|
||||
if (xAccessor !== null && isInterval) {
|
||||
return (brushArea) => {
|
||||
const event = getBrushFromChartBrushEventFn(
|
||||
visData,
|
||||
xAccessor
|
||||
)(brushArea as XYBrushEvent);
|
||||
props.fireEvent(event);
|
||||
};
|
||||
}
|
||||
},
|
||||
[props]
|
||||
);
|
||||
|
||||
const getFilterEventData = useCallback(
|
||||
(
|
||||
visData: Datatable,
|
||||
xAccessor: Accessor | AccessorFn,
|
||||
splitSeriesAccessors: Array<Accessor | AccessorFn>
|
||||
) => {
|
||||
const splitSeriesAccessorFnMap = getSplitSeriesAccessorFnMap(splitSeriesAccessors);
|
||||
return (series: XYChartSeriesIdentifier): ClickTriggerEvent | null => {
|
||||
if (xAccessor !== null) {
|
||||
return getFilterFromSeriesFn(visData)(series, splitSeriesAccessorFnMap);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleFilterAction = useCallback(
|
||||
(event: ClickTriggerEvent, negate = false) => {
|
||||
props.fireEvent({
|
||||
...event,
|
||||
data: {
|
||||
...event.data,
|
||||
negate,
|
||||
},
|
||||
});
|
||||
},
|
||||
[props]
|
||||
);
|
||||
|
||||
const canFilter = async (event: ClickTriggerEvent | null): Promise<boolean> => {
|
||||
if (!event) {
|
||||
return false;
|
||||
}
|
||||
const filters = await getDataActions().createFiltersFromValueClickAction(event.data);
|
||||
return Boolean(filters.length);
|
||||
};
|
||||
|
||||
const toggleLegend = useCallback(() => {
|
||||
setShowLegend((value) => {
|
||||
const newValue = !value;
|
||||
if (props.uiState?.set) {
|
||||
props.uiState.set('vis.legendOpen', newValue);
|
||||
}
|
||||
return newValue;
|
||||
});
|
||||
}, [props.uiState]);
|
||||
|
||||
const setColor = useCallback(
|
||||
(newColor: string | null, seriesLabel: string | number) => {
|
||||
const colors = props.uiState?.get('vis.colors') || {};
|
||||
if (colors[seriesLabel] === newColor || !newColor) {
|
||||
delete colors[seriesLabel];
|
||||
} else {
|
||||
colors[seriesLabel] = newColor;
|
||||
}
|
||||
|
||||
if (props.uiState?.set) {
|
||||
props.uiState.setSilent('vis.colors', null);
|
||||
props.uiState.set('vis.colors', colors);
|
||||
props.uiState.emit('colorChanged');
|
||||
}
|
||||
},
|
||||
[props.uiState]
|
||||
);
|
||||
|
||||
const { visData, visParams, syncColors, syncTooltips } = props;
|
||||
const isDarkMode = getThemeService().useDarkMode();
|
||||
|
||||
const config = getConfig(visData, visParams, props.useLegacyTimeAxis, isDarkMode);
|
||||
const timeZone = getTimeZone();
|
||||
const xDomain =
|
||||
config.xAxis.scale.type === ScaleType.Ordinal ? undefined : getXDomain(config.aspects.x.params);
|
||||
const hasBars = visParams.seriesParams.some(
|
||||
({ type, data: { id: paramId } }) =>
|
||||
type === ChartType.Histogram &&
|
||||
config.aspects.y.find(({ aggId }) => aggId === paramId) !== undefined
|
||||
);
|
||||
const adjustedXDomain =
|
||||
config.xAxis.scale.type === ScaleType.Ordinal
|
||||
? undefined
|
||||
: getAdjustedDomain(visData.rows, config.aspects.x, timeZone, xDomain, hasBars);
|
||||
const legendPosition = useMemo(
|
||||
() => config.legend.position ?? Position.Right,
|
||||
[config.legend.position]
|
||||
);
|
||||
|
||||
const getSeriesName = getSeriesNameFn(config.aspects, config.aspects.y.length > 1);
|
||||
|
||||
const splitAccessors = config.aspects.series?.map(({ accessor, formatter }) => {
|
||||
return { accessor, formatter };
|
||||
});
|
||||
|
||||
const allSeries = useMemo(
|
||||
() => getAllSeries(visData.rows, splitAccessors, config.aspects.y),
|
||||
[config.aspects.y, splitAccessors, visData.rows]
|
||||
);
|
||||
|
||||
const getSeriesColor = useCallback(
|
||||
(series: XYChartSeriesIdentifier) => {
|
||||
const seriesName = getSeriesName(series) as string;
|
||||
if (!seriesName) {
|
||||
return null;
|
||||
}
|
||||
const overwriteColors: Record<string, string> = props.uiState?.get
|
||||
? props.uiState.get('vis.colors', {})
|
||||
: {};
|
||||
|
||||
if (Object.keys(overwriteColors).includes(seriesName)) {
|
||||
return overwriteColors[seriesName];
|
||||
}
|
||||
const outputColor = palettesRegistry?.get(visParams.palette.name).getCategoricalColor(
|
||||
[
|
||||
{
|
||||
name: seriesName,
|
||||
rankAtDepth: splitAccessors
|
||||
? allSeries.findIndex((name) => name === seriesName)
|
||||
: config.aspects.y.findIndex((aspect) => aspect.accessor === series.yAccessor),
|
||||
totalSeriesAtDepth: splitAccessors ? allSeries.length : config.aspects.y.length,
|
||||
},
|
||||
],
|
||||
{
|
||||
maxDepth: 1,
|
||||
totalSeries: splitAccessors ? allSeries.length : config.aspects.y.length,
|
||||
behindText: false,
|
||||
syncColors,
|
||||
}
|
||||
);
|
||||
return outputColor || null;
|
||||
},
|
||||
[
|
||||
allSeries,
|
||||
config.aspects.y,
|
||||
getSeriesName,
|
||||
props.uiState,
|
||||
splitAccessors,
|
||||
syncColors,
|
||||
visParams.palette.name,
|
||||
palettesRegistry,
|
||||
]
|
||||
);
|
||||
const xAccessor = getXAccessor(config.aspects.x);
|
||||
|
||||
const splitSeriesAccessors = useMemo(
|
||||
() =>
|
||||
config.aspects.series
|
||||
? compact(config.aspects.series.map(getComplexAccessor(COMPLEX_SPLIT_ACCESSOR)))
|
||||
: [],
|
||||
[config.aspects.series]
|
||||
);
|
||||
const splitChartColumnAccessor = config.aspects.splitColumn
|
||||
? getComplexAccessor(COMPLEX_SPLIT_ACCESSOR, true)(config.aspects.splitColumn)
|
||||
: undefined;
|
||||
const splitChartRowAccessor = config.aspects.splitRow
|
||||
? getComplexAccessor(COMPLEX_SPLIT_ACCESSOR, true)(config.aspects.splitRow)
|
||||
: undefined;
|
||||
|
||||
const renderSeries = useMemo(
|
||||
() =>
|
||||
renderAllSeries(
|
||||
config,
|
||||
visParams.seriesParams,
|
||||
visData.rows,
|
||||
getSeriesName,
|
||||
getSeriesColor,
|
||||
timeZone,
|
||||
xAccessor,
|
||||
splitSeriesAccessors
|
||||
),
|
||||
[
|
||||
config,
|
||||
getSeriesColor,
|
||||
getSeriesName,
|
||||
splitSeriesAccessors,
|
||||
timeZone,
|
||||
visData.rows,
|
||||
visParams.seriesParams,
|
||||
xAccessor,
|
||||
]
|
||||
);
|
||||
|
||||
const legendColorPicker = useMemo(
|
||||
() =>
|
||||
getColorPicker(
|
||||
legendPosition,
|
||||
setColor,
|
||||
getSeriesName,
|
||||
visParams.palette.name,
|
||||
props.uiState
|
||||
),
|
||||
[getSeriesName, legendPosition, props.uiState, setColor, visParams.palette.name]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="xyChart__container" data-test-subj="visTypeXyChart">
|
||||
<LegendToggle
|
||||
onClick={toggleLegend}
|
||||
showLegend={showLegend}
|
||||
legendPosition={legendPosition}
|
||||
/>
|
||||
<Chart size="100%" ref={chartRef}>
|
||||
<ChartSplit
|
||||
splitColumnAccessor={splitChartColumnAccessor}
|
||||
splitRowAccessor={splitChartRowAccessor}
|
||||
/>
|
||||
<XYSettings
|
||||
{...config}
|
||||
truncateLegend={visParams.truncateLegend}
|
||||
maxLegendLines={visParams.maxLegendLines}
|
||||
showLegend={showLegend}
|
||||
onPointerUpdate={handleCursorUpdate}
|
||||
externalPointerEvents={{
|
||||
tooltip: { visible: syncTooltips, placement: Placement.Right },
|
||||
}}
|
||||
legendPosition={legendPosition}
|
||||
legendSize={LegendSizeToPixels[visParams.legendSize ?? DEFAULT_LEGEND_SIZE]}
|
||||
xDomain={xDomain}
|
||||
adjustedXDomain={adjustedXDomain}
|
||||
legendColorPicker={legendColorPicker}
|
||||
onElementClick={handleFilterClick(
|
||||
visData,
|
||||
xAccessor,
|
||||
splitSeriesAccessors,
|
||||
splitChartColumnAccessor ?? splitChartRowAccessor
|
||||
)}
|
||||
ariaLabel={visParams.ariaLabel}
|
||||
onBrushEnd={handleBrush(visData, xAccessor, 'interval' in config.aspects.x.params)}
|
||||
onRenderChange={onRenderChange}
|
||||
legendAction={
|
||||
config.aspects.series && (config.aspects.series?.length ?? 0) > 0
|
||||
? getLegendActions(
|
||||
canFilter,
|
||||
getFilterEventData(visData, xAccessor, splitSeriesAccessors),
|
||||
handleFilterAction,
|
||||
getSeriesName
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<XYThresholdLine {...config.thresholdLine} />
|
||||
<XYCurrentTime enabled={config.showCurrentTime} isDarkMode={isDarkMode} domain={xDomain} />
|
||||
<XYEndzones
|
||||
isFullBin={!config.enableHistogramMode}
|
||||
enabled={config.isTimeChart}
|
||||
isDarkMode={isDarkMode}
|
||||
domain={xDomain}
|
||||
hideTooltips={!config.detailedTooltip}
|
||||
adjustedDomain={adjustedXDomain}
|
||||
/>
|
||||
<XYAxis {...config.xAxis} />
|
||||
{config.yAxes.map((axisProps) => (
|
||||
<XYAxis key={axisProps.id} {...axisProps} />
|
||||
))}
|
||||
{renderSeries}
|
||||
</Chart>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default memo(VisComponent);
|
|
@ -1,108 +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 React, { lazy } from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { KibanaExecutionContext } from '@kbn/core-execution-context-common';
|
||||
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import { VisualizationContainer } from '@kbn/visualizations-plugin/public';
|
||||
import type { PersistedState } from '@kbn/visualizations-plugin/public';
|
||||
import type { ExpressionRenderDefinition } from '@kbn/expressions-plugin/public';
|
||||
|
||||
import { LEGACY_TIME_AXIS } from '@kbn/charts-plugin/common';
|
||||
import { StartServicesGetter } from '@kbn/kibana-utils-plugin/public';
|
||||
import type { XyVisType } from '../common';
|
||||
import type { VisComponentType } from './vis_component';
|
||||
import { RenderValue, visName } from './expression_functions/xy_vis_fn';
|
||||
import { VisTypeXyPluginStartDependencies } from './plugin';
|
||||
|
||||
// @ts-ignore
|
||||
const VisComponent = lazy<VisComponentType>(() => import('./vis_component'));
|
||||
|
||||
function shouldShowNoResultsMessage(visData: any, visType: XyVisType): boolean {
|
||||
const rows: object[] | undefined = visData?.rows;
|
||||
const isZeroHits = visData?.hits === 0 || (rows && !rows.length);
|
||||
|
||||
return Boolean(isZeroHits);
|
||||
}
|
||||
|
||||
/** @internal **/
|
||||
const extractContainerType = (context?: KibanaExecutionContext): string | undefined => {
|
||||
if (context) {
|
||||
const recursiveGet = (item: KibanaExecutionContext): KibanaExecutionContext | undefined => {
|
||||
if (item.type) {
|
||||
return item;
|
||||
} else if (item.child) {
|
||||
return recursiveGet(item.child);
|
||||
}
|
||||
};
|
||||
return recursiveGet(context)?.type;
|
||||
}
|
||||
};
|
||||
|
||||
export const getXYVisRenderer: (deps: {
|
||||
getStartDeps: StartServicesGetter<VisTypeXyPluginStartDependencies>;
|
||||
}) => ExpressionRenderDefinition<RenderValue> = ({ getStartDeps }) => ({
|
||||
name: visName,
|
||||
displayName: 'XY visualization',
|
||||
reuseDomNode: true,
|
||||
render: async (domNode, { visData, visConfig, visType, syncColors, syncTooltips }, handlers) => {
|
||||
const { core, plugins } = getStartDeps();
|
||||
const showNoResult = shouldShowNoResultsMessage(visData, visType);
|
||||
|
||||
const renderComplete = () => {
|
||||
// Renaming according to business requirements
|
||||
const visTypeTelemetryMap: Record<string, string> = {
|
||||
histogram: 'vertical_bar',
|
||||
};
|
||||
const containerType = extractContainerType(handlers.getExecutionContext());
|
||||
const visualizationType = 'agg_based';
|
||||
|
||||
if (plugins.usageCollection && containerType) {
|
||||
const hasMixedXY = new Set(visConfig.seriesParams.map((item) => item.type));
|
||||
const counterEvents = [
|
||||
`render_${visualizationType}_${visTypeTelemetryMap[visType] ?? visType}`,
|
||||
hasMixedXY.size > 1 ? `render_${visualizationType}_mixed_xy` : undefined,
|
||||
].filter(Boolean) as string[];
|
||||
|
||||
plugins.usageCollection.reportUiCounter(containerType, METRIC_TYPE.COUNT, counterEvents);
|
||||
}
|
||||
|
||||
handlers.done();
|
||||
};
|
||||
|
||||
handlers.onDestroy(() => unmountComponentAtNode(domNode));
|
||||
|
||||
render(
|
||||
<KibanaThemeProvider theme$={core.theme.theme$}>
|
||||
<I18nProvider>
|
||||
<VisualizationContainer
|
||||
renderComplete={renderComplete}
|
||||
handlers={handlers}
|
||||
showNoResult={showNoResult}
|
||||
>
|
||||
<VisComponent
|
||||
visParams={visConfig}
|
||||
visData={visData}
|
||||
renderComplete={renderComplete}
|
||||
fireEvent={handlers.event}
|
||||
uiState={handlers.uiState as PersistedState}
|
||||
syncColors={syncColors}
|
||||
syncTooltips={syncTooltips}
|
||||
useLegacyTimeAxis={core.uiSettings.get(LEGACY_TIME_AXIS, false)}
|
||||
/>
|
||||
</VisualizationContainer>
|
||||
</I18nProvider>
|
||||
</KibanaThemeProvider>,
|
||||
domNode
|
||||
);
|
||||
},
|
||||
});
|
|
@ -15,10 +15,7 @@
|
|||
"references": [
|
||||
{ "path": "../../../core/tsconfig.json" },
|
||||
{ "path": "../../charts/tsconfig.json" },
|
||||
{ "path": "../../data/tsconfig.json" },
|
||||
{ "path": "../../expressions/tsconfig.json" },
|
||||
{ "path": "../../visualizations/tsconfig.json" },
|
||||
{ "path": "../../usage_collection/tsconfig.json" },
|
||||
{ "path": "../../kibana_utils/tsconfig.json" },
|
||||
{ "path": "../../vis_default_editor/tsconfig.json" },
|
||||
]
|
||||
|
|
|
@ -6074,8 +6074,6 @@
|
|||
"visTypeVislib.vislib.tooltip.valueLabel": "valeur",
|
||||
"visTypeXy.controls.pointSeries.seriesAccordionAriaLabel": "Basculer les options {agg}",
|
||||
"visTypeXy.controls.pointSeries.valueAxes.toggleOptionsAriaLabel": "Basculer les options {axisName}",
|
||||
"visTypeXy.legend.filterOptionsLegend": "{legendDataLabel}, options de filtre",
|
||||
"visTypeXy.aggResponse.allDocsTitle": "Tous les docs",
|
||||
"visTypeXy.area.areaDescription": "Mettez en avant les données entre un axe et une ligne.",
|
||||
"visTypeXy.area.areaTitle": "Aire",
|
||||
"visTypeXy.area.groupTitle": "Diviser la série",
|
||||
|
@ -6157,93 +6155,6 @@
|
|||
"visTypeXy.fittingFunctionsTitle.lookahead": "Suivante (remplit les blancs avec la valeur suivante)",
|
||||
"visTypeXy.fittingFunctionsTitle.none": "Masquer (ne remplit pas les blancs)",
|
||||
"visTypeXy.fittingFunctionsTitle.zero": "Zéro (remplit les blancs avec des zéros)",
|
||||
"visTypeXy.function.adimension.bucket": "Groupe",
|
||||
"visTypeXy.function.adimension.dotSize": "Taille du point",
|
||||
"visTypeXy.function.args.addLegend.help": "Afficher la légende du graphique",
|
||||
"visTypeXy.function.args.addTimeMarker.help": "Afficher le repère de temps",
|
||||
"visTypeXy.function.args.addTooltip.help": "Afficher l'infobulle au survol",
|
||||
"visTypeXy.function.args.args.chartType.help": "Type de graphique. Peut être linéaire, en aires ou histogramme",
|
||||
"visTypeXy.function.args.args.legendSize.help": "Spécifie la taille de la légende.",
|
||||
"visTypeXy.function.args.args.maxLegendLines.help": "Définit le nombre maximum de lignes par élément de légende",
|
||||
"visTypeXy.function.args.ariaLabel.help": "Spécifie l'attribut aria-label du graphique xy",
|
||||
"visTypeXy.function.args.categoryAxes.help": "Configuration de l'axe de catégorie",
|
||||
"visTypeXy.function.args.detailedTooltip.help": "Afficher l'infobulle détaillée",
|
||||
"visTypeXy.function.args.fillOpacity.help": "Définit l'opacité du remplissage du graphique en aires",
|
||||
"visTypeXy.function.args.fittingFunction.help": "Nom de la fonction d'adaptation",
|
||||
"visTypeXy.function.args.gridCategoryLines.help": "Afficher les lignes de catégories de la grille dans le graphique",
|
||||
"visTypeXy.function.args.gridValueAxis.help": "Nom de l'axe des valeurs pour lequel la grille est affichée",
|
||||
"visTypeXy.function.args.isVislibVis.help": "Indicateur des anciennes visualisations vislib. Utilisé pour la rétro-compatibilité, notamment pour les couleurs",
|
||||
"visTypeXy.function.args.labels.help": "Configuration des étiquettes du graphique",
|
||||
"visTypeXy.function.args.legendPosition.help": "Positionner la légende en haut, en bas, à gauche ou à droite du graphique",
|
||||
"visTypeXy.function.args.orderBucketsBySum.help": "Classer les groupes par somme",
|
||||
"visTypeXy.function.args.palette.help": "Définit le nom de la palette du graphique",
|
||||
"visTypeXy.function.args.radiusRatio.help": "Rapport de taille des points",
|
||||
"visTypeXy.function.args.seriesDimension.help": "Configuration de la dimension de la série",
|
||||
"visTypeXy.function.args.seriesParams.help": "Configuration des paramètres de la série",
|
||||
"visTypeXy.function.args.splitColumnDimension.help": "Configuration de la dimension Diviser par colonne",
|
||||
"visTypeXy.function.args.splitRowDimension.help": "Configuration de la dimension Diviser par ligne",
|
||||
"visTypeXy.function.args.thresholdLine.help": "Configuration de la ligne de seuil",
|
||||
"visTypeXy.function.args.times.help": "Configuration du repère de temps",
|
||||
"visTypeXy.function.args.truncateLegend.help": "Détermine si la légende sera tronquée ou non",
|
||||
"visTypeXy.function.args.valueAxes.help": "Configuration de l'axe des valeurs",
|
||||
"visTypeXy.function.args.widthDimension.help": "Configuration de la dimension en largeur",
|
||||
"visTypeXy.function.args.xDimension.help": "Configuration de la dimension de l'axe X",
|
||||
"visTypeXy.function.args.yDimension.help": "Configuration de la dimension de l'axe Y",
|
||||
"visTypeXy.function.args.zDimension.help": "Configuration de la dimension de l'axe Z",
|
||||
"visTypeXy.function.categoryAxis.help": "Génère l'objet axe de catégorie",
|
||||
"visTypeXy.function.categoryAxis.id.help": "ID de l'axe de catégorie",
|
||||
"visTypeXy.function.categoryAxis.labels.help": "Configuration de l'étiquette de l'axe",
|
||||
"visTypeXy.function.categoryAxis.position.help": "Position de l'axe de catégorie",
|
||||
"visTypeXy.function.categoryAxis.scale.help": "Configuration de l'échelle",
|
||||
"visTypeXy.function.categoryAxis.show.help": "Afficher l'axe de catégorie",
|
||||
"visTypeXy.function.categoryAxis.title.help": "Titre de l'axe de catégorie",
|
||||
"visTypeXy.function.categoryAxis.type.help": "Type de l'axe de catégorie. Peut être une catégorie ou une valeur",
|
||||
"visTypeXy.function.dimension.metric": "Indicateur",
|
||||
"visTypeXy.function.dimension.splitcolumn": "Division de colonne",
|
||||
"visTypeXy.function.dimension.splitrow": "Division de ligne",
|
||||
"visTypeXy.function.label.color.help": "Couleur de l'étiquette",
|
||||
"visTypeXy.function.label.filter.help": "Masque les étiquettes qui se chevauchent et les éléments en double sur l'axe",
|
||||
"visTypeXy.function.label.help": "Génère l'objet étiquette",
|
||||
"visTypeXy.function.label.overwriteColor.help": "Écraser la couleur",
|
||||
"visTypeXy.function.label.rotate.help": "Faire pivoter l'angle",
|
||||
"visTypeXy.function.label.show.help": "Afficher l'étiquette",
|
||||
"visTypeXy.function.label.truncate.help": "Nombre de symboles avant troncature",
|
||||
"visTypeXy.function.scale.boundsMargin.help": "Marge des limites",
|
||||
"visTypeXy.function.scale.defaultYExtents.help": "Indicateur qui permet de scaler sur les limites de données",
|
||||
"visTypeXy.function.scale.help": "Génère l'objet échelle",
|
||||
"visTypeXy.function.scale.max.help": "Valeur max",
|
||||
"visTypeXy.function.scale.min.help": "Valeur min",
|
||||
"visTypeXy.function.scale.mode.help": "Mode échelle. Peut être normal, pourcentage, ondulé ou silhouette",
|
||||
"visTypeXy.function.scale.setYExtents.help": "Indicateur qui permet de définir votre propre portée",
|
||||
"visTypeXy.function.scale.type.help": "Type d'échelle. Peut être linéaire, logarithmique ou racine carrée",
|
||||
"visTypeXy.function.seriesParam.circlesRadius.help": "Définit la taille des cercles (rayon)",
|
||||
"visTypeXy.function.seriesParam.drawLinesBetweenPoints.help": "Trace des lignes entre des points",
|
||||
"visTypeXy.function.seriesparam.help": "Génère un objet paramètres de la série",
|
||||
"visTypeXy.function.seriesParam.id.help": "ID des paramètres de la série",
|
||||
"visTypeXy.function.seriesParam.interpolate.help": "Mode d'interpolation. Peut être linéaire, cardinal ou palier suivant",
|
||||
"visTypeXy.function.seriesParam.label.help": "Nom des paramètres de la série",
|
||||
"visTypeXy.function.seriesParam.lineWidth.help": "Largeur de ligne",
|
||||
"visTypeXy.function.seriesParam.mode.help": "Mode graphique. Peut être empilé ou pourcentage",
|
||||
"visTypeXy.function.seriesParam.show.help": "Afficher les paramètres",
|
||||
"visTypeXy.function.seriesParam.showCircles.help": "Afficher les cercles",
|
||||
"visTypeXy.function.seriesParam.type.help": "Type de graphique. Peut être linéaire, en aires ou histogramme",
|
||||
"visTypeXy.function.seriesParam.valueAxis.help": "Nom de l'axe des valeurs",
|
||||
"visTypeXy.function.thresholdLine.color.help": "Couleur de la ligne de seuil",
|
||||
"visTypeXy.function.thresholdLine.help": "Génère un objet ligne de seuil",
|
||||
"visTypeXy.function.thresholdLine.show.help": "Afficher la ligne de seuil",
|
||||
"visTypeXy.function.thresholdLine.style.help": "Style de la ligne de seuil. Peut être pleine, en tirets ou en point-tiret",
|
||||
"visTypeXy.function.thresholdLine.value.help": "Valeur seuil",
|
||||
"visTypeXy.function.thresholdLine.width.help": "Largeur de la ligne de seuil",
|
||||
"visTypeXy.function.timeMarker.class.help": "Nom de classe Css",
|
||||
"visTypeXy.function.timeMarker.color.help": "Couleur du repère de temps",
|
||||
"visTypeXy.function.timemarker.help": "Génère un objet repère de temps",
|
||||
"visTypeXy.function.timeMarker.opacity.help": "Opacité du repère de temps",
|
||||
"visTypeXy.function.timeMarker.time.help": "Heure exacte",
|
||||
"visTypeXy.function.timeMarker.width.help": "Largeur du repère de temps",
|
||||
"visTypeXy.function.valueAxis.axisParams.help": "Paramètres de l'axe des valeurs",
|
||||
"visTypeXy.function.valueaxis.help": "Génère l'objet axe des valeurs",
|
||||
"visTypeXy.function.valueAxis.name.help": "Nom de l'axe des valeurs",
|
||||
"visTypeXy.functions.help": "Visualisation XY",
|
||||
"visTypeXy.histogram.groupTitle": "Diviser la série",
|
||||
"visTypeXy.histogram.histogramDescription": "Présente les données en barres verticales sur un axe.",
|
||||
"visTypeXy.histogram.histogramTitle": "Barre verticale",
|
||||
|
@ -6261,8 +6172,6 @@
|
|||
"visTypeXy.interpolationModes.smoothedText": "Lissé",
|
||||
"visTypeXy.interpolationModes.steppedText": "Par paliers",
|
||||
"visTypeXy.interpolationModes.straightText": "Droit",
|
||||
"visTypeXy.legend.filterForValueButtonAriaLabel": "Filtrer sur la valeur",
|
||||
"visTypeXy.legend.filterOutValueButtonAriaLabel": "Exclure la valeur",
|
||||
"visTypeXy.legendPositions.bottomText": "Bas",
|
||||
"visTypeXy.legendPositions.leftText": "Gauche",
|
||||
"visTypeXy.legendPositions.rightText": "Droite",
|
||||
|
|
|
@ -6070,8 +6070,6 @@
|
|||
"visTypeVislib.vislib.tooltip.valueLabel": "値",
|
||||
"visTypeXy.controls.pointSeries.seriesAccordionAriaLabel": "{agg} オプションを切り替える",
|
||||
"visTypeXy.controls.pointSeries.valueAxes.toggleOptionsAriaLabel": "{axisName} オプションを切り替える",
|
||||
"visTypeXy.legend.filterOptionsLegend": "{legendDataLabel}、フィルターオプション",
|
||||
"visTypeXy.aggResponse.allDocsTitle": "すべてのドキュメント",
|
||||
"visTypeXy.area.areaDescription": "軸と線の間のデータを強調します。",
|
||||
"visTypeXy.area.areaTitle": "エリア",
|
||||
"visTypeXy.area.groupTitle": "系列を分割",
|
||||
|
@ -6153,93 +6151,6 @@
|
|||
"visTypeXy.fittingFunctionsTitle.lookahead": "次(ギャップを次の値で埋める)",
|
||||
"visTypeXy.fittingFunctionsTitle.none": "非表示(ギャップを埋めない)",
|
||||
"visTypeXy.fittingFunctionsTitle.zero": "ゼロ(ギャップをゼロで埋める)",
|
||||
"visTypeXy.function.adimension.bucket": "バケット",
|
||||
"visTypeXy.function.adimension.dotSize": "点のサイズ",
|
||||
"visTypeXy.function.args.addLegend.help": "グラフ凡例を表示",
|
||||
"visTypeXy.function.args.addTimeMarker.help": "時刻マーカーを表示",
|
||||
"visTypeXy.function.args.addTooltip.help": "カーソルを置いたときにツールチップを表示",
|
||||
"visTypeXy.function.args.args.chartType.help": "グラフの種類。折れ線、エリア、ヒストグラムを選択できます",
|
||||
"visTypeXy.function.args.args.legendSize.help": "凡例サイズを指定します。",
|
||||
"visTypeXy.function.args.args.maxLegendLines.help": "凡例項目の最大行数を定義します",
|
||||
"visTypeXy.function.args.ariaLabel.help": "xyグラフのariaラベルを指定します",
|
||||
"visTypeXy.function.args.categoryAxes.help": "カテゴリ軸構成",
|
||||
"visTypeXy.function.args.detailedTooltip.help": "詳細ツールチップを表示",
|
||||
"visTypeXy.function.args.fillOpacity.help": "エリアグラフの塗りつぶしの透明度を定義します",
|
||||
"visTypeXy.function.args.fittingFunction.help": "適合関数の名前",
|
||||
"visTypeXy.function.args.gridCategoryLines.help": "グラフにグリッドカテゴリ線を表示",
|
||||
"visTypeXy.function.args.gridValueAxis.help": "グリッドを表示する値軸の名前",
|
||||
"visTypeXy.function.args.isVislibVis.help": "古いvislib可視化を示すフラグ。色を含む後方互換性のために使用されます",
|
||||
"visTypeXy.function.args.labels.help": "グラフラベル構成",
|
||||
"visTypeXy.function.args.legendPosition.help": "グラフの上、下、左、右に凡例を配置",
|
||||
"visTypeXy.function.args.orderBucketsBySum.help": "バケットを合計で並べ替え",
|
||||
"visTypeXy.function.args.palette.help": "グラフパレット名を定義します",
|
||||
"visTypeXy.function.args.radiusRatio.help": "点サイズ率",
|
||||
"visTypeXy.function.args.seriesDimension.help": "系列ディメンション構成",
|
||||
"visTypeXy.function.args.seriesParams.help": "系列パラメーター構成",
|
||||
"visTypeXy.function.args.splitColumnDimension.help": "列ディメンション構成で分割",
|
||||
"visTypeXy.function.args.splitRowDimension.help": "行ディメンション構成で分割",
|
||||
"visTypeXy.function.args.thresholdLine.help": "しきい値線構成",
|
||||
"visTypeXy.function.args.times.help": "時刻マーカー構成",
|
||||
"visTypeXy.function.args.truncateLegend.help": "凡例項目が切り捨てられるかどうかを定義します",
|
||||
"visTypeXy.function.args.valueAxes.help": "値軸構成",
|
||||
"visTypeXy.function.args.widthDimension.help": "幅ディメンション構成",
|
||||
"visTypeXy.function.args.xDimension.help": "X軸ディメンション構成",
|
||||
"visTypeXy.function.args.yDimension.help": "Y軸ディメンション構成",
|
||||
"visTypeXy.function.args.zDimension.help": "Z軸ディメンション構成",
|
||||
"visTypeXy.function.categoryAxis.help": "カテゴリ軸オブジェクトを生成します",
|
||||
"visTypeXy.function.categoryAxis.id.help": "カテゴリ軸のID",
|
||||
"visTypeXy.function.categoryAxis.labels.help": "軸ラベル構成",
|
||||
"visTypeXy.function.categoryAxis.position.help": "カテゴリ軸の位置",
|
||||
"visTypeXy.function.categoryAxis.scale.help": "スケール構成",
|
||||
"visTypeXy.function.categoryAxis.show.help": "カテゴリ軸を表示",
|
||||
"visTypeXy.function.categoryAxis.title.help": "カテゴリ軸のタイトル",
|
||||
"visTypeXy.function.categoryAxis.type.help": "カテゴリ軸の種類。カテゴリまたは値を選択できます",
|
||||
"visTypeXy.function.dimension.metric": "メトリック",
|
||||
"visTypeXy.function.dimension.splitcolumn": "列分割",
|
||||
"visTypeXy.function.dimension.splitrow": "行分割",
|
||||
"visTypeXy.function.label.color.help": "ラベルの色",
|
||||
"visTypeXy.function.label.filter.help": "軸の重なるラベルと重複を非表示にします",
|
||||
"visTypeXy.function.label.help": "ラベルオブジェクトを生成します",
|
||||
"visTypeXy.function.label.overwriteColor.help": "色を上書き",
|
||||
"visTypeXy.function.label.rotate.help": "角度を回転",
|
||||
"visTypeXy.function.label.show.help": "ラベルを表示",
|
||||
"visTypeXy.function.label.truncate.help": "切り捨てる前の記号の数",
|
||||
"visTypeXy.function.scale.boundsMargin.help": "境界のマージン",
|
||||
"visTypeXy.function.scale.defaultYExtents.help": "データ境界にスケールできるフラグ",
|
||||
"visTypeXy.function.scale.help": "スケールオブジェクトを生成します",
|
||||
"visTypeXy.function.scale.max.help": "最高値",
|
||||
"visTypeXy.function.scale.min.help": "最低値",
|
||||
"visTypeXy.function.scale.mode.help": "スケールモード。標準、割合、小刻み、シルエットを選択できます",
|
||||
"visTypeXy.function.scale.setYExtents.help": "独自の範囲を設定できるフラグ",
|
||||
"visTypeXy.function.scale.type.help": "スケールタイプ。線形、対数、平方根を選択できます",
|
||||
"visTypeXy.function.seriesParam.circlesRadius.help": "円のサイズ(半径)を定義します",
|
||||
"visTypeXy.function.seriesParam.drawLinesBetweenPoints.help": "点の間に線を描画",
|
||||
"visTypeXy.function.seriesparam.help": "系列パラメーターオブジェクトを生成します",
|
||||
"visTypeXy.function.seriesParam.id.help": "系列パラメーターのID",
|
||||
"visTypeXy.function.seriesParam.interpolate.help": "補間モード。線形、カーディナル、階段状を選択できます",
|
||||
"visTypeXy.function.seriesParam.label.help": "系列パラメーターの名前",
|
||||
"visTypeXy.function.seriesParam.lineWidth.help": "線の幅",
|
||||
"visTypeXy.function.seriesParam.mode.help": "グラフモード。積み上げまたは割合を選択できます",
|
||||
"visTypeXy.function.seriesParam.show.help": "パラメーターを表示",
|
||||
"visTypeXy.function.seriesParam.showCircles.help": "円を表示",
|
||||
"visTypeXy.function.seriesParam.type.help": "グラフの種類。折れ線、エリア、ヒストグラムを選択できます",
|
||||
"visTypeXy.function.seriesParam.valueAxis.help": "値軸の名前",
|
||||
"visTypeXy.function.thresholdLine.color.help": "しきい線の色",
|
||||
"visTypeXy.function.thresholdLine.help": "しきい値線オブジェクトを生成します",
|
||||
"visTypeXy.function.thresholdLine.show.help": "しきい線を表示",
|
||||
"visTypeXy.function.thresholdLine.style.help": "しきい線のスタイル。実線、点線、一点鎖線を選択できます",
|
||||
"visTypeXy.function.thresholdLine.value.help": "しきい値",
|
||||
"visTypeXy.function.thresholdLine.width.help": "しきい値線の幅",
|
||||
"visTypeXy.function.timeMarker.class.help": "CSSクラス名",
|
||||
"visTypeXy.function.timeMarker.color.help": "時刻マーカーの色",
|
||||
"visTypeXy.function.timemarker.help": "時刻マーカーオブジェクトを生成します",
|
||||
"visTypeXy.function.timeMarker.opacity.help": "時刻マーカーの透明度",
|
||||
"visTypeXy.function.timeMarker.time.help": "正確な時刻",
|
||||
"visTypeXy.function.timeMarker.width.help": "時刻マーカーの幅",
|
||||
"visTypeXy.function.valueAxis.axisParams.help": "値軸パラメーター",
|
||||
"visTypeXy.function.valueaxis.help": "値軸オブジェクトを生成します",
|
||||
"visTypeXy.function.valueAxis.name.help": "値軸の名前",
|
||||
"visTypeXy.functions.help": "XYビジュアライゼーション",
|
||||
"visTypeXy.histogram.groupTitle": "系列を分割",
|
||||
"visTypeXy.histogram.histogramDescription": "軸の縦棒にデータを表示します。",
|
||||
"visTypeXy.histogram.histogramTitle": "縦棒",
|
||||
|
@ -6257,8 +6168,6 @@
|
|||
"visTypeXy.interpolationModes.smoothedText": "スムーズ",
|
||||
"visTypeXy.interpolationModes.steppedText": "ステップ",
|
||||
"visTypeXy.interpolationModes.straightText": "直線",
|
||||
"visTypeXy.legend.filterForValueButtonAriaLabel": "値でフィルター",
|
||||
"visTypeXy.legend.filterOutValueButtonAriaLabel": "値を除外",
|
||||
"visTypeXy.legendPositions.bottomText": "一番下",
|
||||
"visTypeXy.legendPositions.leftText": "左",
|
||||
"visTypeXy.legendPositions.rightText": "右",
|
||||
|
|
|
@ -6077,8 +6077,6 @@
|
|||
"visTypeVislib.vislib.tooltip.valueLabel": "值",
|
||||
"visTypeXy.controls.pointSeries.seriesAccordionAriaLabel": "切换 {agg} 选项",
|
||||
"visTypeXy.controls.pointSeries.valueAxes.toggleOptionsAriaLabel": "切换 {axisName} 选项",
|
||||
"visTypeXy.legend.filterOptionsLegend": "{legendDataLabel}, 筛选选项",
|
||||
"visTypeXy.aggResponse.allDocsTitle": "所有文档",
|
||||
"visTypeXy.area.areaDescription": "突出轴与线之间的数据。",
|
||||
"visTypeXy.area.areaTitle": "面积图",
|
||||
"visTypeXy.area.groupTitle": "拆分序列",
|
||||
|
@ -6160,93 +6158,6 @@
|
|||
"visTypeXy.fittingFunctionsTitle.lookahead": "下一个(使用下一个值填充缺口)",
|
||||
"visTypeXy.fittingFunctionsTitle.none": "隐藏(不填充缺口)",
|
||||
"visTypeXy.fittingFunctionsTitle.zero": "零(使用零填充缺口)",
|
||||
"visTypeXy.function.adimension.bucket": "存储桶",
|
||||
"visTypeXy.function.adimension.dotSize": "圆点大小",
|
||||
"visTypeXy.function.args.addLegend.help": "显示图表图例",
|
||||
"visTypeXy.function.args.addTimeMarker.help": "显示时间标记",
|
||||
"visTypeXy.function.args.addTooltip.help": "在悬浮时显示工具提示",
|
||||
"visTypeXy.function.args.args.chartType.help": "图表的类型。可以是折线图、面积图或直方图",
|
||||
"visTypeXy.function.args.args.legendSize.help": "指定图例大小。",
|
||||
"visTypeXy.function.args.args.maxLegendLines.help": "定义每个图例项的最大行数",
|
||||
"visTypeXy.function.args.ariaLabel.help": "指定 xy 图表的 aria 标签",
|
||||
"visTypeXy.function.args.categoryAxes.help": "类别轴配置",
|
||||
"visTypeXy.function.args.detailedTooltip.help": "显示详细的工具提示",
|
||||
"visTypeXy.function.args.fillOpacity.help": "定义面积图填充透明度",
|
||||
"visTypeXy.function.args.fittingFunction.help": "拟合函数的名称",
|
||||
"visTypeXy.function.args.gridCategoryLines.help": "在图表中显示网格类别线条",
|
||||
"visTypeXy.function.args.gridValueAxis.help": "显示网格的值轴的名称",
|
||||
"visTypeXy.function.args.isVislibVis.help": "表示旧 vislib 可视化的标志。用于向后兼容,包括颜色",
|
||||
"visTypeXy.function.args.labels.help": "图表标签配置",
|
||||
"visTypeXy.function.args.legendPosition.help": "将图例定位于图表的顶部、底部、左侧、右侧",
|
||||
"visTypeXy.function.args.orderBucketsBySum.help": "按总计值排序存储桶",
|
||||
"visTypeXy.function.args.palette.help": "定义图表调色板名称",
|
||||
"visTypeXy.function.args.radiusRatio.help": "圆点大小比率",
|
||||
"visTypeXy.function.args.seriesDimension.help": "序列维度配置",
|
||||
"visTypeXy.function.args.seriesParams.help": "序列参数配置",
|
||||
"visTypeXy.function.args.splitColumnDimension.help": "按列维度配置拆分",
|
||||
"visTypeXy.function.args.splitRowDimension.help": "按行维度配置拆分",
|
||||
"visTypeXy.function.args.thresholdLine.help": "阈值线条配置",
|
||||
"visTypeXy.function.args.times.help": "时间标记配置",
|
||||
"visTypeXy.function.args.truncateLegend.help": "定义是否会截断图例",
|
||||
"visTypeXy.function.args.valueAxes.help": "值轴配置",
|
||||
"visTypeXy.function.args.widthDimension.help": "宽度维度配置",
|
||||
"visTypeXy.function.args.xDimension.help": "X 轴维度配置",
|
||||
"visTypeXy.function.args.yDimension.help": "Y 轴维度配置",
|
||||
"visTypeXy.function.args.zDimension.help": "Z 轴维度配置",
|
||||
"visTypeXy.function.categoryAxis.help": "生成类别轴对象",
|
||||
"visTypeXy.function.categoryAxis.id.help": "类别轴的 ID",
|
||||
"visTypeXy.function.categoryAxis.labels.help": "轴标签配置",
|
||||
"visTypeXy.function.categoryAxis.position.help": "类别轴的位置",
|
||||
"visTypeXy.function.categoryAxis.scale.help": "缩放配置",
|
||||
"visTypeXy.function.categoryAxis.show.help": "显示类别轴",
|
||||
"visTypeXy.function.categoryAxis.title.help": "类别轴的标题",
|
||||
"visTypeXy.function.categoryAxis.type.help": "类别轴的类型。可以是类别或值",
|
||||
"visTypeXy.function.dimension.metric": "指标",
|
||||
"visTypeXy.function.dimension.splitcolumn": "列拆分",
|
||||
"visTypeXy.function.dimension.splitrow": "行拆分",
|
||||
"visTypeXy.function.label.color.help": "标签的颜色",
|
||||
"visTypeXy.function.label.filter.help": "在轴上隐藏重叠标签和重复项",
|
||||
"visTypeXy.function.label.help": "生成标签对象",
|
||||
"visTypeXy.function.label.overwriteColor.help": "覆盖颜色",
|
||||
"visTypeXy.function.label.rotate.help": "旋转角度",
|
||||
"visTypeXy.function.label.show.help": "显示标签",
|
||||
"visTypeXy.function.label.truncate.help": "截断前的符号数",
|
||||
"visTypeXy.function.scale.boundsMargin.help": "边界的边距",
|
||||
"visTypeXy.function.scale.defaultYExtents.help": "允许缩放到数据边界的标志",
|
||||
"visTypeXy.function.scale.help": "生成缩放对象",
|
||||
"visTypeXy.function.scale.max.help": "最大值",
|
||||
"visTypeXy.function.scale.min.help": "最小值",
|
||||
"visTypeXy.function.scale.mode.help": "缩放模式。可以是正常、百分比、扭动或剪影",
|
||||
"visTypeXy.function.scale.setYExtents.help": "允许设置自己的范围的标志",
|
||||
"visTypeXy.function.scale.type.help": "缩放类型。可以是线性、对数或平方根",
|
||||
"visTypeXy.function.seriesParam.circlesRadius.help": "定义圆形大小(半径)",
|
||||
"visTypeXy.function.seriesParam.drawLinesBetweenPoints.help": "在点间绘制线",
|
||||
"visTypeXy.function.seriesparam.help": "生成序列参数对象",
|
||||
"visTypeXy.function.seriesParam.id.help": "序列参数的 ID",
|
||||
"visTypeXy.function.seriesParam.interpolate.help": "内插模式。可以是线性、基数或后步骤",
|
||||
"visTypeXy.function.seriesParam.label.help": "序列参数的名称",
|
||||
"visTypeXy.function.seriesParam.lineWidth.help": "线条的宽度",
|
||||
"visTypeXy.function.seriesParam.mode.help": "图表模式。可以是堆叠或百分比",
|
||||
"visTypeXy.function.seriesParam.show.help": "显示参数",
|
||||
"visTypeXy.function.seriesParam.showCircles.help": "显示圆形",
|
||||
"visTypeXy.function.seriesParam.type.help": "图表类型。可以是折线图、面积图或直方图",
|
||||
"visTypeXy.function.seriesParam.valueAxis.help": "值轴的名称",
|
||||
"visTypeXy.function.thresholdLine.color.help": "阈值线条的颜色",
|
||||
"visTypeXy.function.thresholdLine.help": "生成阈值线条对象",
|
||||
"visTypeXy.function.thresholdLine.show.help": "显示阈值线条",
|
||||
"visTypeXy.function.thresholdLine.style.help": "阈值线条的样式。可以实线、虚线或点虚线",
|
||||
"visTypeXy.function.thresholdLine.value.help": "阈值",
|
||||
"visTypeXy.function.thresholdLine.width.help": "阈值线条的宽度",
|
||||
"visTypeXy.function.timeMarker.class.help": "Css 类名称",
|
||||
"visTypeXy.function.timeMarker.color.help": "时间标记的颜色",
|
||||
"visTypeXy.function.timemarker.help": "生成时间标记对象",
|
||||
"visTypeXy.function.timeMarker.opacity.help": "时间标记的透明度",
|
||||
"visTypeXy.function.timeMarker.time.help": "确切时间",
|
||||
"visTypeXy.function.timeMarker.width.help": "时间标记的宽度",
|
||||
"visTypeXy.function.valueAxis.axisParams.help": "值轴参数",
|
||||
"visTypeXy.function.valueaxis.help": "生成值轴对象",
|
||||
"visTypeXy.function.valueAxis.name.help": "值轴的名称",
|
||||
"visTypeXy.functions.help": "XY 可视化",
|
||||
"visTypeXy.histogram.groupTitle": "拆分序列",
|
||||
"visTypeXy.histogram.histogramDescription": "在轴上以垂直条形图的形式呈现数据。",
|
||||
"visTypeXy.histogram.histogramTitle": "垂直条形图",
|
||||
|
@ -6264,8 +6175,6 @@
|
|||
"visTypeXy.interpolationModes.smoothedText": "平滑",
|
||||
"visTypeXy.interpolationModes.steppedText": "渐变",
|
||||
"visTypeXy.interpolationModes.straightText": "直线",
|
||||
"visTypeXy.legend.filterForValueButtonAriaLabel": "筛留值",
|
||||
"visTypeXy.legend.filterOutValueButtonAriaLabel": "筛除值",
|
||||
"visTypeXy.legendPositions.bottomText": "底部",
|
||||
"visTypeXy.legendPositions.leftText": "左",
|
||||
"visTypeXy.legendPositions.rightText": "右",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue