[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:
Uladzislau Lasitsa 2022-08-18 10:59:40 +03:00 committed by GitHub
parent 5b993bed5c
commit bb705c8ba5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
73 changed files with 28 additions and 6417 deletions

View file

@ -70,7 +70,7 @@ pageLoadAssetSize:
visTypeTimeseries: 55203
visTypeVega: 153573
visTypeVislib: 242838
visTypeXy: 113478
visTypeXy: 30000
visualizations: 90000
watcher: 43598
runtimeFields: 41752

View file

@ -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,

View file

@ -8,5 +8,4 @@
export * from './colors';
export * from './components';
export * from './utils';
export * from '../../common/static/styles';

View file

@ -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';

View file

@ -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',
};
};
}

View file

@ -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"

View file

@ -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;

View file

@ -14,8 +14,3 @@ export enum ChartType {
Area = 'area',
Histogram = 'histogram',
}
/**
* Type of xy visualizations
*/
export type XyVisType = ChartType | 'horizontal_bar';

View file

@ -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",

View file

@ -1,7 +0,0 @@
.xyChart__container {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}

View file

@ -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}
/>
</>
);
};

View file

@ -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%);
}

View file

@ -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,
};

View file

@ -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',
},
]);
});
});

View file

@ -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>
);
};
};

View file

@ -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';

View file

@ -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}
/>
);

View file

@ -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} />;
};

View file

@ -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;
};

View file

@ -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
}
/>
);
};

View file

@ -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,
},
}}
/>
);
};

View file

@ -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() ?? '';

View file

@ -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,
});

View file

@ -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');
});
});

View file

@ -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 };
}

View file

@ -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);
});
});

View file

@ -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';
});
};

View file

@ -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,
};
}

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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,
};
}

View file

@ -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';

View file

@ -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,
};
},
});

View file

@ -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';

View file

@ -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,
};
},
});

View file

@ -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,
};
},
});

View file

@ -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,
};
},
});

View file

@ -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,
};
},
});

View file

@ -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,
};
},
});

View file

@ -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,
};
},
});

View file

@ -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,
},
};
},
});

View file

@ -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';

View file

@ -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 {};
}
}

View file

@ -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');

View file

@ -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;
}

View file

@ -40,9 +40,3 @@ export enum ThresholdLineStyle {
Dashed = 'dashed',
DotDashed = 'dot-dashed',
}
export enum ColorMode {
Background = 'Background',
Labels = 'Labels',
None = 'None',
}

View file

@ -7,6 +7,4 @@
*/
export * from './constants';
export * from './config';
export * from './param';
export type { VisTypeNames, XyVisTypeDefinition } from './vis_type';

View file

@ -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;
}

View file

@ -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>;

View file

@ -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();
});
});

View file

@ -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;

View file

@ -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,
},
]);
});
});

View file

@ -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 })];
})
);
};

View file

@ -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,
};
};

View file

@ -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',
]);
});
});

View file

@ -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;
};

View file

@ -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);
});
});

View file

@ -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>
);
};

View file

@ -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>
);
};
};

View file

@ -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');
});
});

View file

@ -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;
});

View file

@ -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',
},
]);
});
});

View file

@ -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');
}
}

View file

@ -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';

View file

@ -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,
},
];
};

View file

@ -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);
});
});

View file

@ -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;
}
}
);
};

View file

@ -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);

View file

@ -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
);
},
});

View file

@ -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" },
]

View file

@ -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",

View file

@ -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": "右",

View file

@ -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": "右",