Create vis_type_xy plugin to replace histogram, area and line charts (#78154)

This commit is contained in:
Nick Partridge 2020-12-18 08:38:07 -06:00 committed by GitHub
parent cea865f5a9
commit ddea10e718
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
282 changed files with 8359 additions and 2765 deletions

View file

@ -272,7 +272,7 @@ heatmap charts.
|{kib-repo}blob/{branch}/src/plugins/vis_type_xy/README.md[visTypeXy]
|Contains the new xy-axis chart using the elastic-charts library, which will eventually
replace the vislib xy-axis (bar, area, line) charts.
replace the vislib xy-axis charts including bar, area, and line.
|{kib-repo}blob/{branch}/src/plugins/visualizations/README.md[visualizations]

View file

@ -453,6 +453,9 @@ of buckets to try to represent.
==== Visualization
[horizontal]
[[visualization-visualize-chartslibrary]]`visualization:visualize:chartsLibrary`::
Enables the new charts library for area, line, and bar charts in visualization panels. Does *not* support the split chart aggregation.
[[visualization-colormapping]]`visualization:colorMapping`::
**This setting is deprecated and will not be supported as of 8.0.**
Maps values to specific colors in *Visualize* charts and *TSVB*. This setting does not apply to *Lens*.

View file

@ -6,7 +6,7 @@ pageLoadAssetSize:
beatsManagement: 188135
bfetch: 41874
canvas: 1066647
charts: 159211
charts: 195358
cloud: 21076
console: 46091
core: 692106
@ -98,7 +98,7 @@ pageLoadAssetSize:
visTypeTimeseries: 155203
visTypeVega: 153573
visTypeVislib: 242838
visTypeXy: 20255
visTypeXy: 113478
visualizations: 295025
visualize: 57431
watcher: 43598

View file

@ -62,14 +62,17 @@ describe('Vislib Color Service', () => {
it('should throw an error if input is not an array', () => {
expect(() => {
// @ts-expect-error
colors.createColorLookupFunction(200);
}).toThrowError();
expect(() => {
// @ts-expect-error
colors.createColorLookupFunction('help');
}).toThrowError();
expect(() => {
// @ts-expect-error
colors.createColorLookupFunction(true);
}).toThrowError();
@ -78,10 +81,12 @@ describe('Vislib Color Service', () => {
}).toThrowError();
expect(() => {
// @ts-expect-error
colors.createColorLookupFunction(nullValue);
}).toThrowError();
expect(() => {
// @ts-expect-error
colors.createColorLookupFunction(emptyObject);
}).toThrowError();
});
@ -89,14 +94,17 @@ describe('Vislib Color Service', () => {
describe('when array is not composed of numbers, strings, or undefined values', () => {
it('should throw an error', () => {
expect(() => {
// @ts-expect-error
colors.createColorLookupFunction(arrayOfObjects);
}).toThrowError();
expect(() => {
// @ts-expect-error
colors.createColorLookupFunction(arrayOfBooleans);
}).toThrowError();
expect(() => {
// @ts-expect-error
colors.createColorLookupFunction(arrayOfNullValues);
}).toThrowError();
});
@ -113,6 +121,7 @@ describe('Vislib Color Service', () => {
}).not.toThrowError();
expect(() => {
// @ts-expect-error
colors.createColorLookupFunction(arrayOfUndefinedValues);
}).not.toThrowError();
});

View file

@ -48,7 +48,7 @@ export class LegacyColorsService {
}
createColorLookupFunction(
arrayOfStringsOrNumbers?: any,
arrayOfStringsOrNumbers?: Array<string | number>,
colorMapping: Partial<Record<string, string>> = {}
) {
if (!Array.isArray(arrayOfStringsOrNumbers)) {
@ -67,7 +67,7 @@ export class LegacyColorsService {
this.mappedColors.mapKeys(arrayOfStringsOrNumbers);
return (value: string) => {
return (value: string | number) => {
return colorMapping[value] || this.mappedColors.get(value);
};
}

View file

@ -18,9 +18,11 @@
*/
import React from 'react';
import { i18n } from '@kbn/i18n';
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
import { VisOptionsProps } from '../../../../vis_default_editor/public';
import { SwitchOption } from './switch';
import { SelectOption } from './select';

View file

@ -18,17 +18,22 @@
*/
import { $Values } from '@kbn/utility-types';
import { i18n } from '@kbn/i18n';
export const ColorModes = Object.freeze({
BACKGROUND: 'Background' as 'Background',
LABELS: 'Labels' as 'Labels',
NONE: 'None' as 'None',
export const ColorMode = Object.freeze({
Background: 'Background' as 'Background',
Labels: 'Labels' as 'Labels',
None: 'None' as 'None',
});
export type ColorModes = $Values<typeof ColorModes>;
export type ColorMode = $Values<typeof ColorMode>;
export const Rotates = Object.freeze({
HORIZONTAL: 0,
VERTICAL: 90,
ANGLED: 75,
export const LabelRotation = Object.freeze({
Horizontal: 0,
Vertical: 90,
Angled: 75,
});
export type LabelRotation = $Values<typeof LabelRotation>;
export const defaultCountLabel = i18n.translate('charts.countText', {
defaultMessage: 'Count',
});
export type Rotates = $Values<typeof Rotates>;

View file

@ -0,0 +1,18 @@
$visColorPickerWidth: $euiSizeL * 8; // 8 columns
.visColorPicker__value {
width: $visColorPickerWidth;
}
.visColorPicker__valueDot {
cursor: pointer;
&:hover {
transform: scale(1.4);
}
&-isSelected {
border: $euiSizeXS solid;
border-radius: 100%;
}
}

View file

@ -0,0 +1,138 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import classNames from 'classnames';
import React, { BaseSyntheticEvent } from 'react';
import { EuiButtonEmpty, EuiFlexItem, EuiIcon } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import './color_picker.scss';
export const legendColors: string[] = [
'#3F6833',
'#967302',
'#2F575E',
'#99440A',
'#58140C',
'#052B51',
'#511749',
'#3F2B5B',
'#508642',
'#CCA300',
'#447EBC',
'#C15C17',
'#890F02',
'#0A437C',
'#6D1F62',
'#584477',
'#629E51',
'#E5AC0E',
'#64B0C8',
'#E0752D',
'#BF1B00',
'#0A50A1',
'#962D82',
'#614D93',
'#7EB26D',
'#EAB839',
'#6ED0E0',
'#EF843C',
'#E24D42',
'#1F78C1',
'#BA43A9',
'#705DA0',
'#9AC48A',
'#F2C96D',
'#65C5DB',
'#F9934E',
'#EA6460',
'#5195CE',
'#D683CE',
'#806EB7',
'#B7DBAB',
'#F4D598',
'#70DBED',
'#F9BA8F',
'#F29191',
'#82B5D8',
'#E5A8E2',
'#AEA2E0',
'#E0F9D7',
'#FCEACA',
'#CFFAFF',
'#F9E2D2',
'#FCE2DE',
'#BADFF4',
'#F9D9F9',
'#DEDAF7',
];
interface ColorPickerProps {
id?: string;
label: string | number | null;
onChange: (color: string | null, event: BaseSyntheticEvent) => void;
color: string;
}
export const ColorPicker = ({ onChange, color: selectedColor, id, label }: ColorPickerProps) => (
<div className="visColorPicker">
<span id={`${id}ColorPickerDesc`} className="euiScreenReaderOnly">
<FormattedMessage
id="charts.colorPicker.setColor.screenReaderDescription"
defaultMessage="Set color for value {legendDataLabel}"
values={{ legendDataLabel: label }}
/>
</span>
<div className="visColorPicker__value" role="listbox">
{legendColors.map((color) => (
<EuiIcon
role="option"
tabIndex={0}
type="dot"
size="l"
color={selectedColor}
key={color}
aria-label={color}
aria-describedby={`${id}ColorPickerDesc`}
aria-selected={color === selectedColor}
onClick={(e) => onChange(color, e)}
onKeyPress={(e) => onChange(color, e)}
className={classNames('visColorPicker__valueDot', {
// eslint-disable-next-line @typescript-eslint/naming-convention
'visColorPicker__valueDot-isSelected': color === selectedColor,
})}
style={{ color }}
data-test-subj={`visColorPickerColor-${color}`}
/>
))}
</div>
{legendColors.some((c) => c === selectedColor) && (
<EuiFlexItem grow={false}>
<EuiButtonEmpty
size="s"
onClick={(e: any) => onChange(null, e)}
onKeyPress={(e: any) => onChange(null, e)}
>
<FormattedMessage id="charts.colorPicker.clearColor" defaultMessage="Clear color" />
</EuiButtonEmpty>
</EuiFlexItem>
)}
</div>
);

View file

@ -0,0 +1,64 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import moment, { Moment } from 'moment';
import React, { FC } from 'react';
import { LineAnnotation, AnnotationDomainTypes, LineAnnotationStyle } from '@elastic/charts';
import lightEuiTheme from '@elastic/eui/dist/eui_theme_light.json';
import darkEuiTheme from '@elastic/eui/dist/eui_theme_dark.json';
interface CurrentTimeProps {
isDarkMode: boolean;
domainEnd?: number | Moment;
}
/**
* Render current time line annotation on @elastic/charts `Chart`
*/
export const CurrentTime: FC<CurrentTimeProps> = ({ isDarkMode, domainEnd }) => {
const lineAnnotationStyle: Partial<LineAnnotationStyle> = {
line: {
strokeWidth: 2,
stroke: isDarkMode ? darkEuiTheme.euiColorDanger : lightEuiTheme.euiColorDanger,
opacity: 0.7,
},
};
// Domain end of 'now' will be milliseconds behind current time, so we extend time by 1 minute and check if
// the annotation is within this range; if so, the line annotation uses the domainEnd as its value
const now = moment();
const isAnnotationAtEdge = domainEnd
? moment(domainEnd).add(1, 'm').isAfter(now) && now.isAfter(domainEnd)
: false;
const lineAnnotationData = [
{
dataValue: isAnnotationAtEdge ? domainEnd : now.valueOf(),
},
];
return (
<LineAnnotation
id="__current-time__"
hideTooltips
domainType={AnnotationDomainTypes.XDomain}
dataValues={lineAnnotationData}
style={lineAnnotationStyle}
/>
);
};

View file

@ -0,0 +1,197 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { FC } from 'react';
import moment, { unitOfTime } from 'moment';
import {
TooltipValue,
RectAnnotation,
RectAnnotationDatum,
RectAnnotationStyle,
} from '@elastic/charts';
import { i18n } from '@kbn/i18n';
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer } from '@elastic/eui';
import lightEuiTheme from '@elastic/eui/dist/eui_theme_light.json';
import darkEuiTheme from '@elastic/eui/dist/eui_theme_dark.json';
interface EndzonesProps {
isDarkMode: boolean;
domainStart: number;
domainEnd: number;
interval: number;
domainMin: number;
domainMax: number;
hideTooltips?: boolean;
/**
* used to toggle full bin endzones for multiple non-stacked bars
*/
isFullBin?: boolean;
}
export const Endzones: FC<EndzonesProps> = ({
isDarkMode,
domainStart,
domainEnd,
interval,
domainMin,
domainMax,
hideTooltips = true,
isFullBin = false,
}) => {
const rectAnnotationStyle: Partial<RectAnnotationStyle> = {
stroke: isDarkMode ? darkEuiTheme.euiColorLightShade : lightEuiTheme.euiColorDarkShade,
strokeWidth: 0,
opacity: isDarkMode ? 0.6 : 0.2,
fill: isDarkMode ? darkEuiTheme.euiColorLightShade : lightEuiTheme.euiColorDarkShade,
};
const rectAnnotations: RectAnnotationDatum[] = [];
if (domainStart > domainMin) {
rectAnnotations.push({
coordinates: {
x1: isFullBin ? domainMin : domainStart,
},
});
}
if (domainEnd - interval < domainMax) {
rectAnnotations.push({
coordinates: {
x0: isFullBin ? domainMax : domainEnd,
},
});
}
return (
<RectAnnotation
id="__endzones__"
hideTooltips={hideTooltips}
customTooltipDetails={Prompt}
zIndex={2}
dataValues={rectAnnotations}
style={rectAnnotationStyle}
/>
);
};
const findIntervalFromDuration = (
dateValue: number,
esValue: number,
esUnit: unitOfTime.Base,
timeZone: string
) => {
const date = moment.tz(dateValue, timeZone);
const startOfDate = moment.tz(date, timeZone).startOf(esUnit);
const endOfDate = moment.tz(date, timeZone).startOf(esUnit).add(esValue, esUnit);
return endOfDate.valueOf() - startOfDate.valueOf();
};
const getIntervalInMs = (
value: number,
esValue: number,
esUnit: unitOfTime.Base,
timeZone: string
): number => {
switch (esUnit) {
case 's':
return 1000 * esValue;
case 'ms':
return 1 * esValue;
default:
return findIntervalFromDuration(value, esValue, esUnit, timeZone);
}
};
/**
* Returns the adjusted interval based on the data
*
* @param xValues sorted and unquie x values
* @param esValue
* @param esUnit
* @param timeZone
*/
export const getAdjustedInterval = (
xValues: number[],
esValue: number,
esUnit: unitOfTime.Base,
timeZone: string
): number => {
const newInterval = xValues.reduce((minInterval, currentXvalue, index) => {
let currentDiff = minInterval;
if (index > 0) {
currentDiff = Math.abs(xValues[index - 1] - currentXvalue);
}
const singleUnitInterval = getIntervalInMs(currentXvalue, esValue, esUnit, timeZone);
return Math.min(minInterval, singleUnitInterval, currentDiff);
}, Number.MAX_SAFE_INTEGER);
return newInterval > 0 ? newInterval : moment.duration(esValue, esUnit).asMilliseconds();
};
const partialDataText = i18n.translate('charts.partialData.bucketTooltipText', {
defaultMessage:
'The selected time range does not include this entire bucket. It might contain partial data.',
});
const Prompt = () => (
<EuiFlexGroup
alignItems="center"
className="dscHistogram__header--partial"
responsive={false}
gutterSize="xs"
>
<EuiFlexItem grow={false}>
<EuiIcon type="iInCircle" />
</EuiFlexItem>
<EuiFlexItem>{partialDataText}</EuiFlexItem>
</EuiFlexGroup>
);
export const renderEndzoneTooltip = (
xInterval?: number,
domainStart?: number,
domainEnd?: number,
formatter?: (v: any) => string,
renderValue = true
) => (headerData: TooltipValue): JSX.Element | string => {
const headerDataValue = headerData.value;
const formattedValue = formatter ? formatter(headerDataValue) : headerDataValue;
if (
(domainStart !== undefined && domainStart > headerDataValue) ||
(domainEnd !== undefined && xInterval !== undefined && domainEnd - xInterval < headerDataValue)
) {
return (
<>
<Prompt />
{renderValue && (
<>
<EuiSpacer size="xs" />
<p>{formattedValue}</p>
</>
)}
</>
);
}
return renderValue ? formattedValue : null;
};

View file

@ -18,7 +18,7 @@
*/
export { BasicOptions } from './basic_options';
export { ColorModes, Rotates } from './collections';
export { ColorMode, LabelRotation, defaultCountLabel } from './collections';
export { ColorRanges, SetColorRangeValue } from './color_ranges';
export { ColorSchemaOptions, SetColorSchemaOptionsValue } from './color_schema';
export { ColorSchemaParams, Labels, Style } from './types';
@ -28,3 +28,7 @@ export { RequiredNumberInputOption } from './required_number_input';
export { SelectOption } from './select';
export { SwitchOption } from './switch';
export { TextInputOption } from './text_input';
export { LegendToggle } from './legend_toggle';
export { ColorPicker } from './color_picker';
export { CurrentTime } from './current_time';
export * from './endzones';

View file

@ -0,0 +1,20 @@
.echLegend__toggle {
position: absolute;
bottom: 0;
left: 0;
z-index: 1;
margin: $euiSizeXS;
&--isOpen {
background-color: $euiColorLightestShade;
}
&--position-left,
&--position-bottom {
left: auto;
bottom: auto;
right: 0;
top: 0;
}
}

View file

@ -0,0 +1,62 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { memo, useMemo } from 'react';
import classNames from 'classnames';
import { i18n } from '@kbn/i18n';
import { htmlIdGenerator, EuiButtonIcon } from '@elastic/eui';
import { Position } from '@elastic/charts';
import './legend_toggle.scss';
interface LegendToggleProps {
onClick: () => void;
showLegend: boolean;
legendPosition: Position;
}
const LegendToggleComponent = ({ onClick, showLegend, legendPosition }: LegendToggleProps) => {
const legendId = useMemo(() => htmlIdGenerator()('legend'), []);
return (
<EuiButtonIcon
type="button"
iconType="list"
color="subdued"
onClick={onClick}
className={classNames('echLegend__toggle', `echLegend__toggle--position-${legendPosition}`, {
// eslint-disable-next-line @typescript-eslint/naming-convention
'echLegend__toggle--isOpen': showLegend,
})}
aria-label={i18n.translate('charts.legend.toggleLegendButtonAriaLabel', {
defaultMessage: 'Toggle legend',
})}
aria-expanded={showLegend}
aria-controls={legendId}
isSelected={showLegend}
data-test-subj="vislibToggleLegend"
title={i18n.translate('charts.legend.toggleLegendButtonTitle', {
defaultMessage: 'Toggle legend',
})}
/>
);
};
export const LegendToggle = memo(LegendToggleComponent);

View file

@ -18,7 +18,7 @@
*/
import { ColorSchemas } from '../color_maps';
import { Rotates } from './collections';
import { LabelRotation } from './collections';
export interface ColorSchemaParams {
colorSchema: ColorSchemas;
@ -29,8 +29,8 @@ export interface Labels {
color?: string;
filter?: boolean;
overwriteColor?: boolean;
rotate?: Rotates;
show: boolean;
rotate?: LabelRotation;
show?: boolean;
truncate?: number | null;
}

View file

@ -20,3 +20,4 @@
export * from './color_maps';
export * from './colors';
export * from './components';
export * from './utils';

View file

@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export * from './transform_click_event';

View file

@ -0,0 +1,238 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
XYChartSeriesIdentifier,
GeometryValue,
XYBrushArea,
Accessor,
AccessorFn,
Datum,
} from '@elastic/charts';
import { RangeSelectContext, ValueClickContext } from '../../../../embeddable/public';
import { Datatable } from '../../../../expressions/public';
export interface ClickTriggerEvent {
name: 'filterBucket';
data: ValueClickContext['data'];
}
export interface BrushTriggerEvent {
name: 'brush';
data: RangeSelectContext['data'];
}
type AllSeriesAccessors = Array<[accessor: Accessor | AccessorFn, value: string | number]>;
/**
* returns accessor value from string or function accessor
* @param datum
* @param accessor
*/
function getAccessorValue(datum: Datum, accessor: Accessor | AccessorFn) {
if (typeof accessor === 'function') {
return accessor(datum);
}
return 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,
]);
/**
* Reduces matching column indexes
*
* @param xAccessor
* @param yAccessor
* @param splitAccessors
*/
const columnReducer = (
xAccessor: Accessor | AccessorFn | null,
yAccessor: Accessor | AccessorFn | null,
splitAccessors: AllSeriesAccessors
) => (
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)) ||
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
) => (row: Datatable['rows'][number]): boolean =>
(geometry === null ||
(xAccessor !== null &&
getAccessorValue(row, xAccessor) === geometry.x &&
yAccessor !== null &&
getAccessorValue(row, yAccessor) === geometry.y)) &&
[...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>,
negate: boolean = false
) => (points: Array<[GeometryValue, XYChartSeriesIdentifier]>): ClickTriggerEvent => {
const data: ValueClickContext['data']['data'] = [];
points.forEach((point) => {
const [geometry, { yAccessor, splitAccessors }] = point;
const allSplitAccessors = getAllSplitAccessors(splitAccessors, splitSeriesAccessorFnMap);
const columns = table.columns.reduce<Array<[index: number, id: string]>>(
columnReducer(xAccessor, yAccessor, allSplitAccessors),
[]
);
const row = table.rows.findIndex(
rowFindPredicate(geometry, xAccessor, yAccessor, allSplitAccessors)
);
const newData = columns.map(([column, id]) => ({
table,
column,
row,
value: table.rows?.[row]?.[id] ?? null,
}));
data.push(...newData);
});
return {
name: 'filterBucket',
data: {
negate,
data,
},
};
};
/**
* Helper function to get filter action event from series
*/
export const getFilterFromSeriesFn = (table: Datatable) => (
{ splitAccessors }: XYChartSeriesIdentifier,
splitSeriesAccessorFnMap?: Map<string | number, AccessorFn>,
negate = false
): ClickTriggerEvent => {
const allSplitAccessors = getAllSplitAccessors(splitAccessors, splitSeriesAccessorFnMap);
const columns = table.columns.reduce<Array<[index: number, id: string]>>(
columnReducer(null, null, allSplitAccessors),
[]
);
const row = table.rows.findIndex(rowFindPredicate(null, null, null, allSplitAccessors));
const data: ValueClickContext['data']['data'] = columns.map(([column, id]) => ({
table,
column,
row,
value: table.rows?.[row]?.[id] ?? null,
}));
return {
name: 'filterBucket',
data: {
negate,
data,
},
};
};
/**
* Helper function to transform `@elastic/charts` brush event into brush action event
*/
export const getBrushFromChartBrushEventFn = (
table: Datatable,
xAccessor: Accessor | AccessorFn
) => ({ x: selectedRange }: XYBrushArea): 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

@ -17,25 +17,17 @@
* under the License.
*/
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer } from '@elastic/eui';
import moment from 'moment-timezone';
import { unitOfTime } from 'moment';
import moment, { unitOfTime } from 'moment-timezone';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import lightEuiTheme from '@elastic/eui/dist/eui_theme_light.json';
import darkEuiTheme from '@elastic/eui/dist/eui_theme_dark.json';
import {
AnnotationDomainTypes,
Axis,
Chart,
HistogramBarSeries,
LineAnnotation,
Position,
ScaleType,
Settings,
RectAnnotation,
TooltipValue,
TooltipType,
ElementClickListener,
XYChartElementEvent,
@ -43,12 +35,17 @@ import {
Theme,
} from '@elastic/charts';
import { i18n } from '@kbn/i18n';
import { IUiSettingsClient } from 'kibana/public';
import { EuiChartThemeType } from '@elastic/eui/dist/eui_charts_theme';
import { Subscription, combineLatest } from 'rxjs';
import { getServices } from '../../../kibana_services';
import { Chart as IChart } from '../helpers/point_series';
import {
CurrentTime,
Endzones,
getAdjustedInterval,
renderEndzoneTooltip,
} from '../../../../../charts/public';
export interface DiscoverHistogramProps {
chartData: IChart;
@ -60,34 +57,6 @@ interface DiscoverHistogramState {
chartsBaseTheme: Theme;
}
function findIntervalFromDuration(
dateValue: number,
esValue: number,
esUnit: unitOfTime.Base,
timeZone: string
) {
const date = moment.tz(dateValue, timeZone);
const startOfDate = moment.tz(date, timeZone).startOf(esUnit);
const endOfDate = moment.tz(date, timeZone).startOf(esUnit).add(esValue, esUnit);
return endOfDate.valueOf() - startOfDate.valueOf();
}
function getIntervalInMs(
value: number,
esValue: number,
esUnit: unitOfTime.Base,
timeZone: string
): number {
switch (esUnit) {
case 's':
return 1000 * esValue;
case 'ms':
return 1 * esValue;
default:
return findIntervalFromDuration(value, esValue, esUnit, timeZone);
}
}
function getTimezone(uiSettings: IUiSettingsClient) {
if (uiSettings.isDefault('dateFormat:tz')) {
const detectedTimezone = moment.tz.guess();
@ -98,27 +67,6 @@ function getTimezone(uiSettings: IUiSettingsClient) {
}
}
export function findMinInterval(
xValues: number[],
esValue: number,
esUnit: string,
timeZone: string
): number {
return xValues.reduce((minInterval, currentXvalue, index) => {
let currentDiff = minInterval;
if (index > 0) {
currentDiff = Math.abs(xValues[index - 1] - currentXvalue);
}
const singleUnitInterval = getIntervalInMs(
currentXvalue,
esValue,
esUnit as unitOfTime.Base,
timeZone
);
return Math.min(minInterval, singleUnitInterval, currentDiff);
}, Number.MAX_SAFE_INTEGER);
}
export class DiscoverHistogram extends Component<DiscoverHistogramProps, DiscoverHistogramState> {
public static propTypes = {
chartData: PropTypes.object,
@ -132,10 +80,10 @@ export class DiscoverHistogram extends Component<DiscoverHistogramProps, Discove
};
componentDidMount() {
this.subscription = combineLatest(
this.subscription = combineLatest([
getServices().theme.chartsTheme$,
getServices().theme.chartsBaseTheme$
).subscribe(([chartsTheme, chartsBaseTheme]) =>
getServices().theme.chartsBaseTheme$,
]).subscribe(([chartsTheme, chartsBaseTheme]) =>
this.setState({ chartsTheme, chartsBaseTheme })
);
}
@ -171,40 +119,6 @@ export class DiscoverHistogram extends Component<DiscoverHistogramProps, Discove
return moment(val).format(xAxisFormat);
};
public renderBarTooltip = (xInterval: number, domainStart: number, domainEnd: number) => (
headerData: TooltipValue
): JSX.Element | string => {
const headerDataValue = headerData.value;
const formattedValue = this.formatXValue(headerDataValue);
const partialDataText = i18n.translate('discover.histogram.partialData.bucketTooltipText', {
defaultMessage:
'The selected time range does not include this entire bucket, it may contain partial data.',
});
if (headerDataValue < domainStart || headerDataValue + xInterval > domainEnd) {
return (
<React.Fragment>
<EuiFlexGroup
alignItems="center"
className="dscHistogram__header--partial"
responsive={false}
gutterSize="xs"
>
<EuiFlexItem grow={false}>
<EuiIcon type="iInCircle" />
</EuiFlexItem>
<EuiFlexItem>{partialDataText}</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="xs" />
<p>{formattedValue}</p>
</React.Fragment>
);
}
return formattedValue;
};
public render() {
const uiSettings = getServices().uiSettings;
const timeZone = getTimezone(uiSettings);
@ -216,8 +130,9 @@ export class DiscoverHistogram extends Component<DiscoverHistogramProps, Discove
}
const data = chartData.values;
const isDarkMode = uiSettings.get('theme:darkMode');
/**
/*
* Deprecation: [interval] on [date_histogram] is deprecated, use [fixed_interval] or [calendar_interval].
* see https://github.com/elastic/kibana/issues/27410
* TODO: Once the Discover query has been update, we should change the below to use the new field
@ -232,61 +147,21 @@ export class DiscoverHistogram extends Component<DiscoverHistogramProps, Discove
const domainStart = domain.min.valueOf();
const domainEnd = domain.max.valueOf();
const domainMin = data[0]?.x > domainStart ? domainStart : data[0]?.x;
const domainMax = domainEnd - xInterval > lastXValue ? domainEnd - xInterval : lastXValue;
const domainMin = Math.min(data[0]?.x, domainStart);
const domainMax = Math.max(domainEnd - xInterval, lastXValue);
const xDomain = {
min: domainMin,
max: domainMax,
minInterval: findMinInterval(xValues, intervalESValue, intervalESUnit, timeZone),
minInterval: getAdjustedInterval(
xValues,
intervalESValue,
intervalESUnit as unitOfTime.Base,
timeZone
),
};
// Domain end of 'now' will be milliseconds behind current time, so we extend time by 1 minute and check if
// the annotation is within this range; if so, the line annotation uses the domainEnd as its value
const now = moment();
const isAnnotationAtEdge = moment(domainEnd).add(60000).isAfter(now) && now.isAfter(domainEnd);
const lineAnnotationValue = isAnnotationAtEdge ? domainEnd : now;
const lineAnnotationData = [
{
dataValue: lineAnnotationValue,
},
];
const isDarkMode = uiSettings.get('theme:darkMode');
const lineAnnotationStyle = {
line: {
strokeWidth: 2,
stroke: isDarkMode ? darkEuiTheme.euiColorDanger : lightEuiTheme.euiColorDanger,
opacity: 0.7,
},
};
const rectAnnotations = [];
if (domainStart !== domainMin) {
rectAnnotations.push({
coordinates: {
x1: domainStart,
},
});
}
if (domainEnd !== domainMax) {
rectAnnotations.push({
coordinates: {
x0: domainEnd,
},
});
}
const rectAnnotationStyle = {
stroke: isDarkMode ? darkEuiTheme.euiColorLightShade : lightEuiTheme.euiColorDarkShade,
strokeWidth: 0,
opacity: isDarkMode ? 0.6 : 0.2,
fill: isDarkMode ? darkEuiTheme.euiColorLightShade : lightEuiTheme.euiColorDarkShade,
};
const tooltipProps = {
headerFormatter: this.renderBarTooltip(xInterval, domainStart, domainEnd),
headerFormatter: renderEndzoneTooltip(xInterval, domainStart, domainEnd, this.formatXValue),
type: TooltipType.VerticalCursor,
};
@ -313,19 +188,14 @@ export class DiscoverHistogram extends Component<DiscoverHistogramProps, Discove
tickFormat={this.formatXValue}
ticks={10}
/>
<LineAnnotation
id="line-annotation"
domainType={AnnotationDomainTypes.XDomain}
dataValues={lineAnnotationData}
hideTooltips={true}
style={lineAnnotationStyle}
/>
<RectAnnotation
dataValues={rectAnnotations}
id="rect-annotation"
zIndex={2}
style={rectAnnotationStyle}
hideTooltips={true}
<CurrentTime isDarkMode={isDarkMode} domainEnd={domainEnd} />
<Endzones
isDarkMode={isDarkMode}
domainStart={domainStart}
domainEnd={domainEnd}
interval={xDomain.minInterval}
domainMin={xDomain.min}
domainMax={xDomain.max}
/>
<HistogramBarSeries
id="discover-histogram"

View file

@ -34,7 +34,7 @@ export const getSavedObjects = (): SavedObject[] => [
defaultMessage: '[eCommerce] Sales by Category',
}),
visState:
'{"title":"[eCommerce] Sales by Category","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Sum of total_quantity"}}],"seriesParams":[{"show":"true","type":"area","mode":"stacked","data":{"label":"Sum of total_quantity","id":"1"},"drawLinesBetweenPoints":true,"showCircles":true,"interpolate":"linear","valueAxis":"ValueAxis-1"}],"addTooltip":true,"addLegend":true,"legendPosition":"top","times":[],"addTimeMarker":false},"aggs":[{"id":"1","enabled":true,"type":"sum","schema":"metric","params":{"field":"total_quantity"}},{"id":"2","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"order_date","interval":"auto","time_zone":"America/New_York","drop_partials":false,"customInterval":"2h","min_doc_count":1,"extended_bounds":{}}},{"id":"3","enabled":true,"type":"terms","schema":"group","params":{"field":"category.keyword","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing"}}]}',
'{"title":"[eCommerce] Sales by Category","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Sum of total_quantity"}}],"seriesParams":[{"show":"true","type":"area","mode":"stacked","data":{"label":"Sum of total_quantity","id":"1"},"drawLinesBetweenPoints":true,"showCircles":true,"interpolate":"linear","valueAxis":"ValueAxis-1"}],"addTooltip":true,"addLegend":true,"legendPosition":"top","times":[],"addTimeMarker":false,"detailedTooltip":true},"aggs":[{"id":"1","enabled":true,"type":"sum","schema":"metric","params":{"field":"total_quantity"}},{"id":"2","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"order_date","interval":"auto","drop_partials":false,"min_doc_count":1,"extended_bounds":{}}},{"id":"3","enabled":true,"type":"terms","schema":"group","params":{"field":"category.keyword","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing"}}]}',
uiStateJSON: '{}',
description: '',
version: 1,

View file

@ -56,7 +56,7 @@ export const getSavedObjects = (): SavedObject[] => [
defaultMessage: '[Flights] Flight Count and Average Ticket Price',
}),
visState:
'{"title":"[Flights] Flight Count and Average Ticket Price","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Average Ticket Price"}},{"id":"ValueAxis-2","name":"RightAxis-1","type":"value","position":"right","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Flight Count"}}],"seriesParams":[{"show":true,"mode":"stacked","type":"area","drawLinesBetweenPoints":true,"showCircles":false,"interpolate":"linear","lineWidth":2,"data":{"id":"5","label":"Flight Count"},"valueAxis":"ValueAxis-2"},{"show":true,"mode":"stacked","type":"line","drawLinesBetweenPoints":false,"showCircles":true,"interpolate":"linear","data":{"id":"4","label":"Average Ticket Price"},"valueAxis":"ValueAxis-1","lineWidth":2}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"radiusRatio":13},"aggs":[{"id":"3","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"timestamp","interval":"auto","customInterval":"2h","min_doc_count":1,"extended_bounds":{}}},{"id":"5","enabled":true,"type":"count","schema":"metric","params":{"customLabel":"Flight Count"}},{"id":"4","enabled":true,"type":"avg","schema":"metric","params":{"field":"AvgTicketPrice","customLabel":"Average Ticket Price"}},{"id":"2","enabled":true,"type":"avg","schema":"radius","params":{"field":"AvgTicketPrice"}}]}',
'{"title":"[Flights] Flight Count and Average Ticket Price","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Average Ticket Price"}},{"id":"ValueAxis-2","name":"RightAxis-1","type":"value","position":"right","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Flight Count"}}],"seriesParams":[{"show":true,"mode":"stacked","type":"area","drawLinesBetweenPoints":true,"showCircles":false,"interpolate":"linear","lineWidth":2,"data":{"id":"5","label":"Flight Count"},"valueAxis":"ValueAxis-2"},{"show":true,"mode":"stacked","type":"line","drawLinesBetweenPoints":false,"showCircles":true,"interpolate":"linear","data":{"id":"4","label":"Average Ticket Price"},"valueAxis":"ValueAxis-1","lineWidth":2}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"radiusRatio":13,"detailedTooltip":true},"aggs":[{"id":"3","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"timestamp","interval":"auto","min_doc_count":1,"extended_bounds":{}}},{"id":"5","enabled":true,"type":"count","schema":"metric","params":{"customLabel":"Flight Count"}},{"id":"4","enabled":true,"type":"avg","schema":"metric","params":{"field":"AvgTicketPrice","customLabel":"Average Ticket Price"}},{"id":"2","enabled":true,"type":"avg","schema":"radius","params":{"field":"AvgTicketPrice"}}]}',
uiStateJSON:
'{"vis":{"legendOpen":true,"colors":{"Average Ticket Price":"#629E51","Flight Count":"#AEA2E0"}}}',
description: '',
@ -133,7 +133,7 @@ export const getSavedObjects = (): SavedObject[] => [
defaultMessage: '[Flights] Delay Type',
}),
visState:
'{"title":"[Flights] Delay Type","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"drawLinesBetweenPoints":true,"showCircles":true,"interpolate":"cardinal","valueAxis":"ValueAxis-1"}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{}},{"id":"2","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"timestamp","interval":"auto","customInterval":"2h","min_doc_count":1,"extended_bounds":{}}},{"id":"3","enabled":true,"type":"terms","schema":"group","params":{"field":"FlightDelayType","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing"}}]}',
'{"title":"[Flights] Delay Type","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"drawLinesBetweenPoints":true,"showCircles":true,"interpolate":"cardinal","valueAxis":"ValueAxis-1"}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"detailedTooltip":true},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{}},{"id":"2","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"timestamp","interval":"auto","min_doc_count":1,"extended_bounds":{}}},{"id":"3","enabled":true,"type":"terms","schema":"group","params":{"field":"FlightDelayType","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing"}}]}',
uiStateJSON: '{}',
description: '',
version: 1,
@ -176,7 +176,7 @@ export const getSavedObjects = (): SavedObject[] => [
defaultMessage: '[Flights] Delay Buckets',
}),
visState:
'{"title":"[Flights] Delay Buckets","type":"histogram","params":{"type":"histogram","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"valueAxis":"ValueAxis-1","drawLinesBetweenPoints":true,"showCircles":true}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{}},{"id":"2","enabled":true,"type":"histogram","schema":"segment","params":{"field":"FlightDelayMin","interval":30,"extended_bounds":{},"customLabel":"Flight Delay Minutes"}}]}',
'{"title":"[Flights] Delay Buckets","type":"histogram","params":{"type":"histogram","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"valueAxis":"ValueAxis-1","drawLinesBetweenPoints":true,"showCircles":true}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"detailedTooltip":true},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{}},{"id":"2","enabled":true,"type":"histogram","schema":"segment","params":{"field":"FlightDelayMin","interval":30,"extended_bounds":{},"customLabel":"Flight Delay Minutes"}}]}',
uiStateJSON: '{"vis":{"legendOpen":false}}',
description: '',
version: 1,
@ -198,7 +198,7 @@ export const getSavedObjects = (): SavedObject[] => [
defaultMessage: '[Flights] Flight Delays',
}),
visState:
'{"title":"[Flights] Flight Delays","type":"histogram","params":{"type":"histogram","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"left","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"BottomAxis-1","type":"value","position":"bottom","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"valueAxis":"ValueAxis-1","drawLinesBetweenPoints":true,"showCircles":true}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{"customLabel":""}},{"id":"2","enabled":true,"type":"terms","schema":"segment","params":{"field":"FlightDelay","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing","customLabel":"Flight Delays"}}]}',
'{"title":"[Flights] Flight Delays","type":"histogram","params":{"type":"histogram","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"left","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"BottomAxis-1","type":"value","position":"bottom","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"valueAxis":"ValueAxis-1","drawLinesBetweenPoints":true,"showCircles":true}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"detailedTooltip":true},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{"customLabel":""}},{"id":"2","enabled":true,"type":"terms","schema":"segment","params":{"field":"FlightDelay","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing","customLabel":"Flight Delays"}}]}',
uiStateJSON: '{}',
description: '',
version: 1,
@ -220,7 +220,7 @@ export const getSavedObjects = (): SavedObject[] => [
defaultMessage: '[Flights] Flight Cancellations',
}),
visState:
'{"title":"[Flights] Flight Cancellations","type":"histogram","params":{"type":"histogram","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"left","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"BottomAxis-1","type":"value","position":"bottom","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"valueAxis":"ValueAxis-1","drawLinesBetweenPoints":true,"showCircles":true}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{"customLabel":""}},{"id":"2","enabled":true,"type":"terms","schema":"segment","params":{"field":"Cancelled","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing","customLabel":"Flight Cancellations"}}]}',
'{"title":"[Flights] Flight Cancellations","type":"histogram","params":{"type":"histogram","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"left","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"BottomAxis-1","type":"value","position":"bottom","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"valueAxis":"ValueAxis-1","drawLinesBetweenPoints":true,"showCircles":true}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"detailedTooltip":true},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{"customLabel":""}},{"id":"2","enabled":true,"type":"terms","schema":"segment","params":{"field":"Cancelled","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing","customLabel":"Flight Cancellations"}}]}',
uiStateJSON: '{}',
description: '',
version: 1,

View file

@ -33,7 +33,7 @@ export const getSavedObjects = (): SavedObject[] => [
defaultMessage: '[Logs] Unique Visitors vs. Average Bytes',
}),
visState:
'{"title":"[Logs] Unique Visitors vs. Average Bytes","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Avg. Bytes"}},{"id":"ValueAxis-2","name":"RightAxis-1","type":"value","position":"right","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Unique Visitors"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Avg. Bytes","id":"1"},"drawLinesBetweenPoints":true,"showCircles":true,"interpolate":"linear","valueAxis":"ValueAxis-1"},{"show":true,"mode":"stacked","type":"line","drawLinesBetweenPoints":false,"showCircles":true,"interpolate":"linear","data":{"id":"2","label":"Unique Visitors"},"valueAxis":"ValueAxis-2"}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"radiusRatio":17},"aggs":[{"id":"1","enabled":true,"type":"avg","schema":"metric","params":{"field":"bytes","customLabel":"Avg. Bytes"}},{"id":"2","enabled":true,"type":"cardinality","schema":"metric","params":{"field":"clientip","customLabel":"Unique Visitors"}},{"id":"3","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"timestamp","interval":"auto","time_zone":"America/Los_Angeles","customInterval":"2h","min_doc_count":1,"extended_bounds":{}}},{"id":"4","enabled":true,"type":"count","schema":"radius","params":{}}]}',
'{"title":"[Logs] Unique Visitors vs. Average Bytes","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Avg. Bytes"}},{"id":"ValueAxis-2","name":"RightAxis-1","type":"value","position":"right","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Unique Visitors"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Avg. Bytes","id":"1"},"drawLinesBetweenPoints":true,"showCircles":true,"interpolate":"linear","valueAxis":"ValueAxis-1"},{"show":true,"mode":"stacked","type":"line","drawLinesBetweenPoints":false,"showCircles":true,"interpolate":"linear","data":{"id":"2","label":"Unique Visitors"},"valueAxis":"ValueAxis-2"}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"radiusRatio":17,"detailedTooltip":true},"aggs":[{"id":"1","enabled":true,"type":"avg","schema":"metric","params":{"field":"bytes","customLabel":"Avg. Bytes"}},{"id":"2","enabled":true,"type":"cardinality","schema":"metric","params":{"field":"clientip","customLabel":"Unique Visitors"}},{"id":"3","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"timestamp","interval":"auto","min_doc_count":1,"extended_bounds":{}}},{"id":"4","enabled":true,"type":"count","schema":"radius","params":{}}]}',
uiStateJSON: '{"vis":{"colors":{"Avg. Bytes":"#70DBED","Unique Visitors":"#0A437C"}}}',
description: '',
version: 1,

View file

@ -76,10 +76,17 @@ function DefaultEditorAggAdd({
</EuiButtonEmpty>
);
const isSchemaDisabled = (schema: Schema): boolean => {
const isMaxedCount = (schema: Schema): boolean => {
const count = group.filter((agg) => agg.schema === schema.name).length;
return count >= schema.max;
};
const isSchemaDisabled = (schema: Schema, maxedCount: boolean): boolean => {
return schema.disabled ?? maxedCount;
};
const maxTooltipText = i18n.translate('visDefaultEditor.aggAdd.maxBuckets', {
defaultMessage: 'Max {groupNameLabel} count reached',
values: { groupNameLabel },
});
return (
<EuiFlexGroup justifyContent="center" responsive={false}>
@ -109,16 +116,21 @@ function DefaultEditorAggAdd({
)}
</EuiPopoverTitle>
<EuiContextMenuPanel
items={schemas.map((schema) => (
<EuiContextMenuItem
key={`${schema.name}_${schema.title}`}
data-test-subj={`visEditorAdd_${groupName}_${schema.title}`}
disabled={isPopoverOpen && isSchemaDisabled(schema)}
onClick={() => onSelectSchema(schema)}
>
{schema.title}
</EuiContextMenuItem>
))}
items={schemas.map((schema) => {
const maxedCount = isMaxedCount(schema);
return (
<EuiContextMenuItem
key={`${schema.name}_${schema.title}`}
data-test-subj={`visEditorAdd_${groupName}_${schema.title}`}
disabled={isPopoverOpen && isSchemaDisabled(schema, maxedCount)}
onClick={() => onSelectSchema(schema)}
toolTipContent={schema.tooltip ?? (maxedCount ? maxTooltipText : undefined)}
>
{schema.title}
</EuiContextMenuItem>
);
})}
/>
</EuiPopover>
</EuiFlexItem>

View file

@ -20,9 +20,10 @@
import { useCallback, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { Vis } from 'src/plugins/visualizations/public';
import { DefaultEditorDataTab, DefaultEditorDataTabProps } from './data_tab';
import { Vis } from '../../../../visualizations/public';
import { VisOptionsProps } from '../../vis_options_props';
import { DefaultEditorDataTab, DefaultEditorDataTabProps } from './data_tab';
export interface OptionTab {
editor: React.ComponentType<VisOptionsProps | DefaultEditorDataTabProps>;

View file

@ -25,6 +25,7 @@ import { EuiErrorBoundary, EuiLoadingChart } from '@elastic/eui';
import { EditorRenderProps, IEditorController } from 'src/plugins/visualize/public';
import { Vis, VisualizeEmbeddableContract } from 'src/plugins/visualizations/public';
// @ts-ignore
const DefaultEditor = lazy(() => import('./default_editor'));
class DefaultEditorController implements IEditorController {

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { ReactNode } from 'react';
import _, { defaults } from 'lodash';
import { Optional } from '@kbn/utility-types';
@ -42,6 +43,8 @@ export interface Schema {
hideCustomLabel?: boolean;
mustBeFirst?: boolean;
aggSettings?: any;
disabled?: boolean;
tooltip?: ReactNode;
}
export class Schemas implements ISchemas {

View file

@ -42,12 +42,14 @@ describe('markdown vis toExpressionAst function', () => {
it('without params', () => {
vis.params = {};
// @ts-expect-error
const actual = toExpressionAst(vis);
expect(actual).toMatchSnapshot();
});
it('with params', () => {
vis.params = { markdown: '### my markdown', fontSize: 15, openLinksInNewTab: true };
// @ts-expect-error
const actual = toExpressionAst(vis);
expect(actual).toMatchSnapshot();
});

View file

@ -17,11 +17,11 @@
* under the License.
*/
import { Vis } from '../../visualizations/public';
import { VisToExpressionAst } from '../../visualizations/public';
import { buildExpression, buildExpressionFunction } from '../../expressions/public';
import { MarkdownVisExpressionFunctionDefinition } from './markdown_fn';
export const toExpressionAst = (vis: Vis) => {
export const toExpressionAst: VisToExpressionAst = (vis) => {
const { markdown, fontSize, openLinksInNewTab } = vis.params;
const markdownVis = buildExpressionFunction<MarkdownVisExpressionFunctionDefinition>(

View file

@ -31,7 +31,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
import {
ColorModes,
ColorMode,
ColorRanges,
ColorSchemaOptions,
SwitchOption,
@ -86,7 +86,7 @@ function MetricVisOptions({
);
const setColorMode: EuiButtonGroupProps['onChange'] = useCallback(
(id: string) => setMetricValue('metricColorMode', id as ColorModes),
(id: string) => setMetricValue('metricColorMode', id as ColorMode),
[setMetricValue]
);
@ -158,7 +158,7 @@ function MetricVisOptions({
colorSchemas={vis.type.editorConfig.collections.colorSchemas}
disabled={
stateParams.metric.colorsRange.length === 1 ||
stateParams.metric.metricColorMode === ColorModes.NONE
stateParams.metric.metricColorMode === ColorMode.None
}
invertColors={stateParams.metric.invertColors}
setValue={setMetricValue as SetColorSchemaOptionsValue}

View file

@ -27,14 +27,14 @@ import {
Style,
} from '../../expressions/public';
import { visType, DimensionsVisParam, VisParams } from './types';
import { ColorSchemas, vislibColorMaps, ColorModes } from '../../charts/public';
import { ColorSchemas, vislibColorMaps, ColorMode } from '../../charts/public';
export type Input = Datatable;
interface Arguments {
percentageMode: boolean;
colorSchema: ColorSchemas;
colorMode: ColorModes;
colorMode: ColorMode;
useRanges: boolean;
invertColors: boolean;
showLabels: boolean;
@ -86,7 +86,7 @@ export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({
colorMode: {
types: ['string'],
default: '"None"',
options: [ColorModes.NONE, ColorModes.LABELS, ColorModes.BACKGROUND],
options: [ColorMode.None, ColorMode.Labels, ColorMode.Background],
help: i18n.translate('visTypeMetric.function.colorMode.help', {
defaultMessage: 'Which part of metric to color',
}),
@ -197,8 +197,8 @@ export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({
invertColors: args.invertColors,
style: {
bgFill: args.bgFill,
bgColor: args.colorMode === ColorModes.BACKGROUND,
labelColor: args.colorMode === ColorModes.LABELS,
bgColor: args.colorMode === ColorMode.Background,
labelColor: args.colorMode === ColorMode.Labels,
subText: args.subText,
fontSize,
},

View file

@ -20,7 +20,7 @@
import { i18n } from '@kbn/i18n';
import { BaseVisTypeOptions } from 'src/plugins/visualizations/public';
import { MetricVisOptions } from './components/metric_vis_options';
import { ColorSchemas, colorSchemas, ColorModes } from '../../charts/public';
import { ColorSchemas, colorSchemas, ColorMode } from '../../charts/public';
import { AggGroupNames } from '../../data/public';
import { Schemas } from '../../vis_default_editor/public';
import { toExpressionAst } from './to_ast';
@ -42,7 +42,7 @@ export const createMetricVisTypeDefinition = (): BaseVisTypeOptions => ({
percentageMode: false,
useRanges: false,
colorSchema: ColorSchemas.GreenToRed,
metricColorMode: ColorModes.NONE,
metricColorMode: ColorMode.None,
colorsRange: [{ from: 0, to: 10000 }],
labels: {
show: true,
@ -62,19 +62,19 @@ export const createMetricVisTypeDefinition = (): BaseVisTypeOptions => ({
collections: {
metricColorMode: [
{
id: ColorModes.NONE,
id: ColorMode.None,
label: i18n.translate('visTypeMetric.colorModes.noneOptionLabel', {
defaultMessage: 'None',
}),
},
{
id: ColorModes.LABELS,
id: ColorMode.Labels,
label: i18n.translate('visTypeMetric.colorModes.labelsOptionLabel', {
defaultMessage: 'Labels',
}),
},
{
id: ColorModes.BACKGROUND,
id: ColorMode.Background,
label: i18n.translate('visTypeMetric.colorModes.backgroundOptionLabel', {
defaultMessage: 'Background',
}),

View file

@ -19,7 +19,7 @@
import { Range } from '../../expressions/public';
import { SchemaConfig } from '../../visualizations/public';
import { ColorModes, Labels, Style, ColorSchemas } from '../../charts/public';
import { ColorMode, Labels, Style, ColorSchemas } from '../../charts/public';
export const visType = 'metric';
@ -32,7 +32,7 @@ export interface MetricVisParam {
percentageMode: boolean;
useRanges: boolean;
colorSchema: ColorSchemas;
metricColorMode: ColorModes;
metricColorMode: ColorMode;
colorsRange: Range[];
labels: Labels;
invertColors: boolean;

View file

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/area_decorator.js <AreaSeriesDecorator /> should render and match a snapshot 1`] = `
<AreaSeries
<Connect(SpecInstance)
areaSeriesStyle={
Object {
"area": Object {

View file

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/bar_decorator.js <BarSeriesDecorator /> should render and match a snapshot 1`] = `
<BarSeries
<Connect(SpecInstance)
barSeriesStyle={
Object {
"rect": Object {

View file

@ -4,6 +4,5 @@
"server": true,
"ui": true,
"requiredPlugins": ["charts", "data", "expressions", "visualizations", "kibanaLegacy"],
"optionalPlugins": ["visTypeXy"],
"requiredBundles": ["kibanaUtils", "visDefaultEditor"]
"requiredBundles": ["kibanaUtils", "visDefaultEditor", "visTypeXy"]
}

View file

@ -17,172 +17,14 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
// @ts-ignore
import { palettes } from '@elastic/eui/lib/services';
// @ts-ignore
import { euiPaletteColorBlind } from '@elastic/eui/lib/services';
import { xyVisTypes } from '../../vis_type_xy/public';
import { BaseVisTypeOptions } from '../../visualizations/public';
import { AggGroupNames } from '../../data/public';
import { Schemas } from '../../vis_default_editor/public';
import {
Positions,
ChartTypes,
ChartModes,
InterpolationModes,
AxisTypes,
ScaleTypes,
AxisModes,
ThresholdLineStyles,
getConfigCollections,
} from './utils/collections';
import { getAreaOptionTabs, countLabel } from './utils/common_config';
import { Rotates } from '../../charts/public';
import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
import { toExpressionAst } from './to_ast';
import { BasicVislibParams } from './types';
export const areaVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
name: 'area',
title: i18n.translate('visTypeVislib.area.areaTitle', { defaultMessage: 'Area' }),
icon: 'visArea',
description: i18n.translate('visTypeVislib.area.areaDescription', {
defaultMessage: 'Emphasize the data between an axis and a line.',
}),
getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush],
...(xyVisTypes.area() as BaseVisTypeOptions<BasicVislibParams>),
toExpressionAst,
visConfig: {
defaults: {
type: 'area',
grid: {
categoryLines: false,
},
categoryAxes: [
{
id: 'CategoryAxis-1',
type: AxisTypes.CATEGORY,
position: Positions.BOTTOM,
show: true,
style: {},
scale: {
type: ScaleTypes.LINEAR,
},
labels: {
show: true,
filter: true,
truncate: 100,
},
title: {},
},
],
valueAxes: [
{
id: 'ValueAxis-1',
name: 'LeftAxis-1',
type: AxisTypes.VALUE,
position: Positions.LEFT,
show: true,
style: {},
scale: {
type: ScaleTypes.LINEAR,
mode: AxisModes.NORMAL,
},
labels: {
show: true,
rotate: Rotates.HORIZONTAL,
filter: false,
truncate: 100,
},
title: {
text: countLabel,
},
},
],
seriesParams: [
{
show: true,
type: ChartTypes.AREA,
mode: ChartModes.STACKED,
data: {
label: countLabel,
id: '1',
},
drawLinesBetweenPoints: true,
lineWidth: 2,
showCircles: true,
interpolate: InterpolationModes.LINEAR,
valueAxis: 'ValueAxis-1',
},
],
addTooltip: true,
addLegend: true,
legendPosition: Positions.RIGHT,
times: [],
addTimeMarker: false,
thresholdLine: {
show: false,
value: 10,
width: 1,
style: ThresholdLineStyles.FULL,
color: euiPaletteColorBlind()[9],
},
labels: {},
},
},
editorConfig: {
collections: getConfigCollections(),
optionTabs: getAreaOptionTabs(),
schemas: new Schemas([
{
group: AggGroupNames.Metrics,
name: 'metric',
title: i18n.translate('visTypeVislib.area.metricsTitle', {
defaultMessage: 'Y-axis',
}),
aggFilter: ['!geo_centroid', '!geo_bounds'],
min: 1,
defaults: [{ schema: 'metric', type: 'count' }],
},
{
group: AggGroupNames.Metrics,
name: 'radius',
title: i18n.translate('visTypeVislib.area.radiusTitle', {
defaultMessage: 'Dot size',
}),
min: 0,
max: 1,
aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'],
},
{
group: AggGroupNames.Buckets,
name: 'segment',
title: i18n.translate('visTypeVislib.area.segmentTitle', {
defaultMessage: 'X-axis',
}),
min: 0,
max: 1,
aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'],
},
{
group: AggGroupNames.Buckets,
name: 'group',
title: i18n.translate('visTypeVislib.area.groupTitle', {
defaultMessage: 'Split series',
}),
min: 0,
max: 3,
aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'],
},
{
group: AggGroupNames.Buckets,
name: 'split',
title: i18n.translate('visTypeVislib.area.splitTitle', {
defaultMessage: 'Split chart',
}),
min: 0,
max: 1,
aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'],
},
]),
},
visualization: undefined,
};

View file

@ -1,248 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { mount, shallow } from 'enzyme';
import { IAggConfig, IAggType } from 'src/plugins/data/public';
import MetricsAxisOptions from './index';
import { BasicVislibParams, SeriesParam, ValueAxis } from '../../../types';
import { ValidationVisOptionsProps } from '../../common';
import { Positions } from '../../../utils/collections';
import { ValueAxesPanel } from './value_axes_panel';
import { CategoryAxisPanel } from './category_axis_panel';
import { ChartTypes } from '../../../utils/collections';
import { defaultValueAxisId, valueAxis, seriesParam, categoryAxis } from './mocks';
jest.mock('./series_panel', () => ({
SeriesPanel: () => 'SeriesPanel',
}));
jest.mock('./category_axis_panel', () => ({
CategoryAxisPanel: () => 'CategoryAxisPanel',
}));
jest.mock('./value_axes_panel', () => ({
ValueAxesPanel: () => 'ValueAxesPanel',
}));
const SERIES_PARAMS = 'seriesParams';
const VALUE_AXES = 'valueAxes';
const aggCount: IAggConfig = {
id: '1',
type: { name: 'count' },
makeLabel: () => 'Count',
} as IAggConfig;
const aggAverage: IAggConfig = {
id: '2',
type: { name: 'average' } as IAggType,
makeLabel: () => 'Average',
} as IAggConfig;
const createAggs = (aggs: any[]) => ({
aggs,
bySchemaName: () => aggs,
});
describe('MetricsAxisOptions component', () => {
let setValue: jest.Mock;
let defaultProps: ValidationVisOptionsProps<BasicVislibParams>;
let axis: ValueAxis;
let axisRight: ValueAxis;
let chart: SeriesParam;
beforeEach(() => {
setValue = jest.fn();
axis = {
...valueAxis,
name: 'LeftAxis-1',
position: Positions.LEFT,
};
axisRight = {
...valueAxis,
id: 'ValueAxis-2',
name: 'RightAxis-1',
position: Positions.RIGHT,
};
chart = {
...seriesParam,
type: ChartTypes.AREA,
};
defaultProps = {
aggs: createAggs([aggCount]),
isTabSelected: true,
vis: {
type: {
type: ChartTypes.AREA,
schemas: { metrics: [{ name: 'metric' }] },
},
setState: jest.fn(),
serialize: jest.fn(),
},
stateParams: {
valueAxes: [axis],
seriesParams: [chart],
categoryAxes: [categoryAxis],
grid: { valueAxis: defaultValueAxisId },
},
setValue,
} as any;
});
it('should init with the default set of props', () => {
const comp = shallow(<MetricsAxisOptions {...defaultProps} />);
expect(comp).toMatchSnapshot();
});
describe('useEffect', () => {
it('should update series when new agg is added', () => {
const comp = mount(<MetricsAxisOptions {...defaultProps} />);
comp.setProps({
aggs: createAggs([aggCount, aggAverage]),
});
const updatedSeries = [chart, { ...chart, data: { id: '2', label: aggAverage.makeLabel() } }];
expect(setValue).toHaveBeenLastCalledWith(SERIES_PARAMS, updatedSeries);
});
it('should update series when new agg label is changed', () => {
const comp = mount(<MetricsAxisOptions {...defaultProps} />);
const agg = { id: aggCount.id, makeLabel: () => 'New label' };
comp.setProps({
aggs: createAggs([agg]),
});
const updatedSeries = [{ ...chart, data: { id: agg.id, label: agg.makeLabel() } }];
expect(setValue).toHaveBeenCalledWith(SERIES_PARAMS, updatedSeries);
});
});
describe('updateAxisTitle', () => {
it('should not update the value axis title if custom title was set', () => {
defaultProps.stateParams.valueAxes[0].title.text = 'Custom title';
const comp = mount(<MetricsAxisOptions {...defaultProps} />);
const newAgg = {
...aggCount,
makeLabel: () => 'Custom label',
};
comp.setProps({
aggs: createAggs([newAgg]),
});
const updatedValues = [{ ...axis, title: { text: newAgg.makeLabel() } }];
expect(setValue).not.toHaveBeenCalledWith(VALUE_AXES, updatedValues);
});
it('should set the custom title to match the value axis label when only one agg exists for that axis', () => {
const comp = mount(<MetricsAxisOptions {...defaultProps} />);
const agg = {
id: aggCount.id,
params: { customLabel: 'Custom label' },
makeLabel: () => 'Custom label',
};
comp.setProps({
aggs: createAggs([agg]),
});
const updatedSeriesParams = [{ ...chart, data: { ...chart.data, label: agg.makeLabel() } }];
const updatedValues = [{ ...axis, title: { text: agg.makeLabel() } }];
expect(setValue).toHaveBeenCalledTimes(5);
expect(setValue).toHaveBeenNthCalledWith(3, SERIES_PARAMS, updatedSeriesParams);
expect(setValue).toHaveBeenNthCalledWith(5, SERIES_PARAMS, updatedSeriesParams);
expect(setValue).toHaveBeenNthCalledWith(4, VALUE_AXES, updatedValues);
});
it('should not set the custom title to match the value axis label when more than one agg exists for that axis', () => {
const comp = mount(<MetricsAxisOptions {...defaultProps} />);
const agg = { id: aggCount.id, makeLabel: () => 'Custom label' };
comp.setProps({
aggs: createAggs([agg, aggAverage]),
stateParams: {
...defaultProps.stateParams,
seriesParams: [chart, chart],
},
});
expect(setValue).not.toHaveBeenCalledWith(VALUE_AXES);
});
it('should not overwrite the custom title with the value axis label if the custom title has been changed', () => {
defaultProps.stateParams.valueAxes[0].title.text = 'Custom title';
const comp = mount(<MetricsAxisOptions {...defaultProps} />);
const agg = {
id: aggCount.id,
params: { customLabel: 'Custom label' },
makeLabel: () => 'Custom label',
};
comp.setProps({
aggs: createAggs([agg]),
});
expect(setValue).not.toHaveBeenCalledWith(VALUE_AXES);
});
});
it('should add value axis', () => {
const comp = shallow(<MetricsAxisOptions {...defaultProps} />);
comp.find(ValueAxesPanel).prop('addValueAxis')();
expect(setValue).toHaveBeenCalledWith(VALUE_AXES, [axis, axisRight]);
});
describe('removeValueAxis', () => {
beforeEach(() => {
defaultProps.stateParams.valueAxes = [axis, axisRight];
});
it('should remove value axis', () => {
const comp = shallow(<MetricsAxisOptions {...defaultProps} />);
comp.find(ValueAxesPanel).prop('removeValueAxis')(axis);
expect(setValue).toHaveBeenCalledWith(VALUE_AXES, [axisRight]);
});
it('should update seriesParams "valueAxis" prop', () => {
const updatedSeriesParam = { ...chart, valueAxis: 'ValueAxis-2' };
const comp = shallow(<MetricsAxisOptions {...defaultProps} />);
comp.find(ValueAxesPanel).prop('removeValueAxis')(axis);
expect(setValue).toHaveBeenCalledWith(SERIES_PARAMS, [updatedSeriesParam]);
});
it('should reset grid "valueAxis" prop', () => {
const updatedGrid = { valueAxis: undefined };
defaultProps.stateParams.seriesParams[0].valueAxis = 'ValueAxis-2';
const comp = shallow(<MetricsAxisOptions {...defaultProps} />);
comp.find(ValueAxesPanel).prop('removeValueAxis')(axis);
expect(setValue).toHaveBeenCalledWith('grid', updatedGrid);
});
});
it('should update axis value when when category position chnaged', () => {
const comp = shallow(<MetricsAxisOptions {...defaultProps} />);
comp.find(CategoryAxisPanel).prop('onPositionChanged')(Positions.LEFT);
const updatedValues = [{ ...axis, name: 'BottomAxis-1', position: Positions.BOTTOM }];
expect(setValue).toHaveBeenCalledWith(VALUE_AXES, updatedValues);
});
});

View file

@ -0,0 +1,73 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { colorSchemas } from '../../../charts/public';
import { getPositions, getScaleTypes } from '../../../vis_type_xy/public';
import { Alignment, GaugeType } from '../types';
export const getGaugeTypes = () => [
{
text: i18n.translate('visTypeVislib.gauge.gaugeTypes.arcText', {
defaultMessage: 'Arc',
}),
value: GaugeType.Arc,
},
{
text: i18n.translate('visTypeVislib.gauge.gaugeTypes.circleText', {
defaultMessage: 'Circle',
}),
value: GaugeType.Circle,
},
];
export const getAlignments = () => [
{
text: i18n.translate('visTypeVislib.gauge.alignmentAutomaticTitle', {
defaultMessage: 'Automatic',
}),
value: Alignment.Automatic,
},
{
text: i18n.translate('visTypeVislib.gauge.alignmentHorizontalTitle', {
defaultMessage: 'Horizontal',
}),
value: Alignment.Horizontal,
},
{
text: i18n.translate('visTypeVislib.gauge.alignmentVerticalTitle', {
defaultMessage: 'Vertical',
}),
value: Alignment.Vertical,
},
];
export const getGaugeCollections = () => ({
gaugeTypes: getGaugeTypes(),
alignments: getAlignments(),
colorSchemas,
});
export const getHeatmapCollections = () => ({
legendPositions: getPositions(),
scales: getScaleTypes(),
colorSchemas,
});

View file

@ -23,7 +23,8 @@ import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
import { VisOptionsProps } from '../../../../../vis_default_editor/public';
import { ValueAxis } from '../../../../../vis_type_xy/public';
import {
BasicOptions,
ColorRanges,
@ -34,8 +35,8 @@ import {
SetColorSchemaOptionsValue,
SetColorRangeValue,
} from '../../../../../charts/public';
import { HeatmapVisParams } from '../../../heatmap';
import { ValueAxis } from '../../../types';
import { LabelsPanel } from './labels_panel';
function HeatmapOptions(props: VisOptionsProps<HeatmapVisParams>) {

View file

@ -23,10 +23,11 @@ import { EuiColorPicker, EuiFormRow, EuiPanel, EuiSpacer, EuiTitle } from '@elas
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
import { ValueAxis } from '../../../types';
import { HeatmapVisParams } from '../../../heatmap';
import { VisOptionsProps } from '../../../../../vis_default_editor/public';
import { SwitchOption } from '../../../../../charts/public';
import { ValueAxis } from '../../../../../vis_type_xy/public';
import { HeatmapVisParams } from '../../../heatmap';
const VERTICAL_ROTATION = 270;

View file

@ -19,18 +19,15 @@
import React, { lazy } from 'react';
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
import { ValidationVisOptionsProps } from '../common';
import { VisOptionsProps } from '../../../../vis_default_editor/public';
import { GaugeVisParams } from '../../gauge';
import { PieVisParams } from '../../pie';
import { BasicVislibParams } from '../../types';
import { HeatmapVisParams } from '../../heatmap';
const GaugeOptionsLazy = lazy(() => import('./gauge'));
const PieOptionsLazy = lazy(() => import('./pie'));
const PointSeriesOptionsLazy = lazy(() => import('./point_series'));
const HeatmapOptionsLazy = lazy(() => import('./heatmap'));
const MetricsAxisOptionsLazy = lazy(() => import('./metrics_axes'));
export const GaugeOptions = (props: VisOptionsProps<GaugeVisParams>) => (
<GaugeOptionsLazy {...props} />
@ -38,14 +35,6 @@ export const GaugeOptions = (props: VisOptionsProps<GaugeVisParams>) => (
export const PieOptions = (props: VisOptionsProps<PieVisParams>) => <PieOptionsLazy {...props} />;
export const PointSeriesOptions = (props: ValidationVisOptionsProps<BasicVislibParams>) => (
<PointSeriesOptionsLazy {...props} />
);
export const HeatmapOptions = (props: VisOptionsProps<HeatmapVisParams>) => (
<HeatmapOptionsLazy {...props} />
);
export const MetricsAxisOptions = (props: ValidationVisOptionsProps<BasicVislibParams>) => (
<MetricsAxisOptionsLazy {...props} />
);

View file

@ -22,9 +22,10 @@ import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
import { TruncateLabelsOption } from '../common';
import { VisOptionsProps } from '../../../../vis_default_editor/public';
import { BasicOptions, SwitchOption } from '../../../../charts/public';
import { TruncateLabelsOption } from '../../../../vis_type_xy/public';
import { PieVisParams } from '../../pie';
function PieOptions(props: VisOptionsProps<PieVisParams>) {

View file

@ -0,0 +1,21 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export * from './collections';
export * from './components';

View file

@ -19,24 +19,25 @@
import { i18n } from '@kbn/i18n';
import { ColorMode, ColorSchemas, ColorSchemaParams, Labels, Style } from '../../charts/public';
import { RangeValues, Schemas } from '../../vis_default_editor/public';
import { AggGroupNames } from '../../data/public';
import { GaugeOptions } from './components/options';
import { getGaugeCollections, Alignments, GaugeTypes } from './utils/collections';
import { ColorModes, ColorSchemas, ColorSchemaParams, Labels, Style } from '../../charts/public';
import { toExpressionAst } from './to_ast';
import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../visualizations/public';
import { BasicVislibParams } from './types';
import { Alignment, GaugeType, BasicVislibParams, VislibChartType } from './types';
import { getGaugeCollections } from './editor';
import { toExpressionAst } from './to_ast';
import { GaugeOptions } from './editor/components';
export interface Gauge extends ColorSchemaParams {
backStyle: 'Full';
gaugeStyle: 'Full';
orientation: 'vertical';
type: 'meter';
alignment: Alignments;
alignment: Alignment;
colorsRange: RangeValues[];
extendRange: boolean;
gaugeType: GaugeTypes;
gaugeType: GaugeType;
labels: Labels;
percentageMode: boolean;
outline?: boolean;
@ -67,20 +68,20 @@ export const gaugeVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
toExpressionAst,
visConfig: {
defaults: {
type: 'gauge',
type: VislibChartType.Gauge,
addTooltip: true,
addLegend: true,
isDisplayWarning: false,
gauge: {
alignment: Alignments.AUTOMATIC,
alignment: Alignment.Automatic,
extendRange: true,
percentageMode: false,
gaugeType: GaugeTypes.ARC,
gaugeType: GaugeType.Arc,
gaugeStyle: 'Full',
backStyle: 'Full',
orientation: 'vertical',
colorSchema: ColorSchemas.GreenToRed,
gaugeColorMode: ColorModes.LABELS,
gaugeColorMode: ColorMode.Labels,
colorsRange: [
{ from: 0, to: 50 },
{ from: 50, to: 75 },

View file

@ -19,14 +19,14 @@
import { i18n } from '@kbn/i18n';
import { GaugeOptions } from './components/options';
import { getGaugeCollections, GaugeTypes } from './utils/collections';
import { ColorModes, ColorSchemas } from '../../charts/public';
import { AggGroupNames } from '../../data/public';
import { Schemas } from '../../vis_default_editor/public';
import { toExpressionAst } from './to_ast';
import { ColorMode, ColorSchemas } from '../../charts/public';
import { BaseVisTypeOptions } from '../../visualizations/public';
import { BasicVislibParams } from './types';
import { getGaugeCollections, GaugeOptions } from './editor';
import { toExpressionAst } from './to_ast';
import { GaugeType, BasicVislibParams } from './types';
export const goalVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
name: 'goal',
@ -46,13 +46,13 @@ export const goalVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
verticalSplit: false,
autoExtend: false,
percentageMode: true,
gaugeType: GaugeTypes.ARC,
gaugeType: GaugeType.Arc,
gaugeStyle: 'Full',
backStyle: 'Full',
orientation: 'vertical',
useRanges: false,
colorSchema: ColorSchemas.GreenToRed,
gaugeColorMode: ColorModes.NONE,
gaugeColorMode: ColorMode.None,
colorsRange: [{ from: 0, to: 10000 }],
invertColors: false,
labels: {

View file

@ -18,15 +18,17 @@
*/
import { i18n } from '@kbn/i18n';
import { Position } from '@elastic/charts';
import { RangeValues, Schemas } from '../../vis_default_editor/public';
import { AggGroupNames } from '../../data/public';
import { AxisTypes, getHeatmapCollections, Positions, ScaleTypes } from './utils/collections';
import { HeatmapOptions } from './components/options';
import { TimeMarker } from './vislib/visualizations/time_marker';
import { BasicVislibParams, CommonVislibParams, ValueAxis } from './types';
import { ColorSchemas, ColorSchemaParams } from '../../charts/public';
import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
import { VIS_EVENT_TO_TRIGGER, BaseVisTypeOptions } from '../../visualizations/public';
import { ValueAxis, ScaleType, AxisType } from '../../vis_type_xy/public';
import { HeatmapOptions, getHeatmapCollections } from './editor';
import { TimeMarker } from './vislib/visualizations/time_marker';
import { CommonVislibParams, BasicVislibParams, VislibChartType } from './types';
import { toExpressionAst } from './to_ast';
export interface HeatmapVisParams extends CommonVislibParams, ColorSchemaParams {
@ -48,15 +50,15 @@ export const heatmapVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
description: i18n.translate('visTypeVislib.heatmap.heatmapDescription', {
defaultMessage: 'Shade data in cells in a matrix.',
}),
getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter],
toExpressionAst,
getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter],
visConfig: {
defaults: {
type: 'heatmap',
type: VislibChartType.Heatmap,
addTooltip: true,
addLegend: true,
enableHover: false,
legendPosition: Positions.RIGHT,
legendPosition: Position.Right,
times: [],
colorsNumber: 4,
colorSchema: ColorSchemas.Greens,
@ -68,9 +70,9 @@ export const heatmapVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
{
show: false,
id: 'ValueAxis-1',
type: AxisTypes.VALUE,
type: AxisType.Value,
scale: {
type: ScaleTypes.LINEAR,
type: ScaleType.Linear,
defaultYExtents: false,
},
labels: {

View file

@ -17,174 +17,14 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
// @ts-ignore
import { palettes } from '@elastic/eui/lib/services';
// @ts-ignore
import { euiPaletteColorBlind } from '@elastic/eui/lib/services';
import { xyVisTypes } from '../../vis_type_xy/public';
import { BaseVisTypeOptions } from '../../visualizations/public';
import { AggGroupNames } from '../../data/public';
import { Schemas } from '../../vis_default_editor/public';
import {
Positions,
ChartTypes,
ChartModes,
AxisTypes,
ScaleTypes,
AxisModes,
ThresholdLineStyles,
getConfigCollections,
} from './utils/collections';
import { getAreaOptionTabs, countLabel } from './utils/common_config';
import { Rotates } from '../../charts/public';
import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
import { BasicVislibParams } from './types';
import { toExpressionAst } from './to_ast';
import { BasicVislibParams } from './types';
export const histogramVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
name: 'histogram',
title: i18n.translate('visTypeVislib.histogram.histogramTitle', {
defaultMessage: 'Vertical bar',
}),
icon: 'visBarVertical',
description: i18n.translate('visTypeVislib.histogram.histogramDescription', {
defaultMessage: 'Present data in vertical bars on an axis.',
}),
getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush],
...(xyVisTypes.histogram() as BaseVisTypeOptions<BasicVislibParams>),
toExpressionAst,
visConfig: {
defaults: {
type: 'histogram',
grid: {
categoryLines: false,
},
categoryAxes: [
{
id: 'CategoryAxis-1',
type: AxisTypes.CATEGORY,
position: Positions.BOTTOM,
show: true,
style: {},
scale: {
type: ScaleTypes.LINEAR,
},
labels: {
show: true,
filter: true,
truncate: 100,
},
title: {},
},
],
valueAxes: [
{
id: 'ValueAxis-1',
name: 'LeftAxis-1',
type: AxisTypes.VALUE,
position: Positions.LEFT,
show: true,
style: {},
scale: {
type: ScaleTypes.LINEAR,
mode: AxisModes.NORMAL,
},
labels: {
show: true,
rotate: Rotates.HORIZONTAL,
filter: false,
truncate: 100,
},
title: {
text: countLabel,
},
},
],
seriesParams: [
{
show: true,
type: ChartTypes.HISTOGRAM,
mode: ChartModes.STACKED,
data: {
label: countLabel,
id: '1',
},
valueAxis: 'ValueAxis-1',
drawLinesBetweenPoints: true,
lineWidth: 2,
showCircles: true,
},
],
addTooltip: true,
addLegend: true,
legendPosition: Positions.RIGHT,
times: [],
addTimeMarker: false,
labels: {
show: false,
},
thresholdLine: {
show: false,
value: 10,
width: 1,
style: ThresholdLineStyles.FULL,
color: euiPaletteColorBlind()[9],
},
},
},
editorConfig: {
collections: getConfigCollections(),
optionTabs: getAreaOptionTabs(),
schemas: new Schemas([
{
group: AggGroupNames.Metrics,
name: 'metric',
title: i18n.translate('visTypeVislib.histogram.metricTitle', {
defaultMessage: 'Y-axis',
}),
min: 1,
aggFilter: ['!geo_centroid', '!geo_bounds'],
defaults: [{ schema: 'metric', type: 'count' }],
},
{
group: AggGroupNames.Metrics,
name: 'radius',
title: i18n.translate('visTypeVislib.histogram.radiusTitle', {
defaultMessage: 'Dot size',
}),
min: 0,
max: 1,
aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'],
},
{
group: AggGroupNames.Buckets,
name: 'segment',
title: i18n.translate('visTypeVislib.histogram.segmentTitle', {
defaultMessage: 'X-axis',
}),
min: 0,
max: 1,
aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'],
},
{
group: AggGroupNames.Buckets,
name: 'group',
title: i18n.translate('visTypeVislib.histogram.groupTitle', {
defaultMessage: 'Split series',
}),
min: 0,
max: 3,
aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'],
},
{
group: AggGroupNames.Buckets,
name: 'split',
title: i18n.translate('visTypeVislib.histogram.splitTitle', {
defaultMessage: 'Split chart',
}),
min: 0,
max: 1,
aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'],
},
]),
},
visualization: undefined,
};

View file

@ -17,171 +17,14 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
// @ts-ignore
import { palettes, euiPaletteColorBlind } from '@elastic/eui/lib/services';
import { xyVisTypes } from '../../vis_type_xy/public';
import { BaseVisTypeOptions } from '../../visualizations/public';
import { AggGroupNames } from '../../data/public';
import { Schemas } from '../../vis_default_editor/public';
import {
Positions,
ChartTypes,
ChartModes,
AxisTypes,
ScaleTypes,
AxisModes,
ThresholdLineStyles,
getConfigCollections,
} from './utils/collections';
import { getAreaOptionTabs, countLabel } from './utils/common_config';
import { Rotates } from '../../charts/public';
import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
import { BasicVislibParams } from './types';
import { toExpressionAst } from './to_ast';
import { BasicVislibParams } from './types';
export const horizontalBarVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
name: 'horizontal_bar',
title: i18n.translate('visTypeVislib.horizontalBar.horizontalBarTitle', {
defaultMessage: 'Horizontal bar',
}),
icon: 'visBarHorizontal',
description: i18n.translate('visTypeVislib.horizontalBar.horizontalBarDescription', {
defaultMessage: 'Present data in horizontal bars on an axis.',
}),
getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush],
...(xyVisTypes.horizontalBar() as BaseVisTypeOptions<BasicVislibParams>),
toExpressionAst,
visConfig: {
defaults: {
type: 'histogram',
grid: {
categoryLines: false,
},
categoryAxes: [
{
id: 'CategoryAxis-1',
type: AxisTypes.CATEGORY,
position: Positions.LEFT,
show: true,
style: {},
scale: {
type: ScaleTypes.LINEAR,
},
labels: {
show: true,
rotate: Rotates.HORIZONTAL,
filter: false,
truncate: 200,
},
title: {},
},
],
valueAxes: [
{
id: 'ValueAxis-1',
name: 'LeftAxis-1',
type: AxisTypes.VALUE,
position: Positions.BOTTOM,
show: true,
style: {},
scale: {
type: ScaleTypes.LINEAR,
mode: AxisModes.NORMAL,
},
labels: {
show: true,
rotate: Rotates.ANGLED,
filter: true,
truncate: 100,
},
title: {
text: countLabel,
},
},
],
seriesParams: [
{
show: true,
type: ChartTypes.HISTOGRAM,
mode: ChartModes.NORMAL,
data: {
label: countLabel,
id: '1',
},
valueAxis: 'ValueAxis-1',
drawLinesBetweenPoints: true,
lineWidth: 2,
showCircles: true,
},
],
addTooltip: true,
addLegend: true,
legendPosition: Positions.RIGHT,
times: [],
addTimeMarker: false,
labels: {},
thresholdLine: {
show: false,
value: 10,
width: 1,
style: ThresholdLineStyles.FULL,
color: euiPaletteColorBlind()[9],
},
},
},
editorConfig: {
collections: getConfigCollections(),
optionTabs: getAreaOptionTabs(),
schemas: new Schemas([
{
group: AggGroupNames.Metrics,
name: 'metric',
title: i18n.translate('visTypeVislib.horizontalBar.metricTitle', {
defaultMessage: 'Y-axis',
}),
min: 1,
aggFilter: ['!geo_centroid', '!geo_bounds'],
defaults: [{ schema: 'metric', type: 'count' }],
},
{
group: AggGroupNames.Metrics,
name: 'radius',
title: i18n.translate('visTypeVislib.horizontalBar.radiusTitle', {
defaultMessage: 'Dot size',
}),
min: 0,
max: 1,
aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'],
},
{
group: AggGroupNames.Buckets,
name: 'segment',
title: i18n.translate('visTypeVislib.horizontalBar.segmentTitle', {
defaultMessage: 'X-axis',
}),
min: 0,
max: 1,
aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'],
},
{
group: AggGroupNames.Buckets,
name: 'group',
title: i18n.translate('visTypeVislib.horizontalBar.groupTitle', {
defaultMessage: 'Split series',
}),
min: 0,
max: 3,
aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'],
},
{
group: AggGroupNames.Buckets,
name: 'split',
title: i18n.translate('visTypeVislib.horizontalBar.splitTitle', {
defaultMessage: 'Split chart',
}),
min: 0,
max: 1,
aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'],
},
]),
},
visualization: undefined,
};

View file

@ -17,164 +17,14 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
// @ts-ignore
import { palettes, euiPaletteColorBlind } from '@elastic/eui/lib/services';
import { xyVisTypes } from '../../vis_type_xy/public';
import { BaseVisTypeOptions } from '../../visualizations/public';
import { AggGroupNames } from '../../data/public';
import { Schemas } from '../../vis_default_editor/public';
import {
Positions,
ChartTypes,
ChartModes,
AxisTypes,
ScaleTypes,
AxisModes,
ThresholdLineStyles,
InterpolationModes,
getConfigCollections,
} from './utils/collections';
import { getAreaOptionTabs, countLabel } from './utils/common_config';
import { Rotates } from '../../charts/public';
import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
import { toExpressionAst } from './to_ast';
import { BasicVislibParams } from './types';
export const lineVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
name: 'line',
title: i18n.translate('visTypeVislib.line.lineTitle', { defaultMessage: 'Line' }),
icon: 'visLine',
description: i18n.translate('visTypeVislib.line.lineDescription', {
defaultMessage: 'Display data as a series of points.',
}),
getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush],
...(xyVisTypes.line() as BaseVisTypeOptions<BasicVislibParams>),
toExpressionAst,
visConfig: {
defaults: {
type: 'line',
grid: {
categoryLines: false,
},
categoryAxes: [
{
id: 'CategoryAxis-1',
type: AxisTypes.CATEGORY,
position: Positions.BOTTOM,
show: true,
style: {},
scale: {
type: ScaleTypes.LINEAR,
},
labels: {
show: true,
filter: true,
truncate: 100,
},
title: {},
},
],
valueAxes: [
{
id: 'ValueAxis-1',
name: 'LeftAxis-1',
type: AxisTypes.VALUE,
position: Positions.LEFT,
show: true,
style: {},
scale: {
type: ScaleTypes.LINEAR,
mode: AxisModes.NORMAL,
},
labels: {
show: true,
rotate: Rotates.HORIZONTAL,
filter: false,
truncate: 100,
},
title: {
text: countLabel,
},
},
],
seriesParams: [
{
show: true,
type: ChartTypes.LINE,
mode: ChartModes.NORMAL,
data: {
label: countLabel,
id: '1',
},
valueAxis: 'ValueAxis-1',
drawLinesBetweenPoints: true,
lineWidth: 2,
interpolate: InterpolationModes.LINEAR,
showCircles: true,
},
],
addTooltip: true,
addLegend: true,
legendPosition: Positions.RIGHT,
times: [],
addTimeMarker: false,
labels: {},
thresholdLine: {
show: false,
value: 10,
width: 1,
style: ThresholdLineStyles.FULL,
color: euiPaletteColorBlind()[9],
},
},
},
editorConfig: {
collections: getConfigCollections(),
optionTabs: getAreaOptionTabs(),
schemas: new Schemas([
{
group: AggGroupNames.Metrics,
name: 'metric',
title: i18n.translate('visTypeVislib.line.metricTitle', { defaultMessage: 'Y-axis' }),
min: 1,
aggFilter: ['!geo_centroid', '!geo_bounds'],
defaults: [{ schema: 'metric', type: 'count' }],
},
{
group: AggGroupNames.Metrics,
name: 'radius',
title: i18n.translate('visTypeVislib.line.radiusTitle', { defaultMessage: 'Dot size' }),
min: 0,
max: 1,
aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits'],
},
{
group: AggGroupNames.Buckets,
name: 'segment',
title: i18n.translate('visTypeVislib.line.segmentTitle', { defaultMessage: 'X-axis' }),
min: 0,
max: 1,
aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'],
},
{
group: AggGroupNames.Buckets,
name: 'group',
title: i18n.translate('visTypeVislib.line.groupTitle', {
defaultMessage: 'Split series',
}),
min: 0,
max: 3,
aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'],
},
{
group: AggGroupNames.Buckets,
name: 'split',
title: i18n.translate('visTypeVislib.line.splitTitle', {
defaultMessage: 'Split chart',
}),
min: 0,
max: 1,
aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'],
},
]),
},
visualization: undefined,
};

View file

@ -18,13 +18,15 @@
*/
import { i18n } from '@kbn/i18n';
import { Position } from '@elastic/charts';
import { AggGroupNames } from '../../data/public';
import { Schemas } from '../../vis_default_editor/public';
import { PieOptions } from './components/options';
import { getPositions, Positions } from './utils/collections';
import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../visualizations/public';
import { getPositions } from '../../vis_type_xy/public';
import { CommonVislibParams } from './types';
import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
import { PieOptions } from './editor';
import { toExpressionAst } from './to_ast_pie';
export interface PieVisParams extends CommonVislibParams {
@ -52,7 +54,7 @@ export const pieVisTypeDefinition: BaseVisTypeOptions<PieVisParams> = {
type: 'pie',
addTooltip: true,
addLegend: true,
legendPosition: Positions.RIGHT,
legendPosition: Position.Right,
isDonut: true,
labels: {
show: false,

View file

@ -24,6 +24,7 @@ import { ExpressionFunctionDefinition, Datatable, Render } from '../../expressio
// @ts-ignore
import { vislibSlicesResponseHandler } from './vislib/response_handler';
import { PieVisParams } from './pie';
import { VislibChartType } from './types';
import { vislibVisName } from './vis_type_vislib_vis_fn';
export const vislibPieName = 'vislib_pie_vis';
@ -32,9 +33,9 @@ interface Arguments {
visConfig: string;
}
interface RenderValue {
export interface PieRenderValue {
visType: Extract<VislibChartType, 'pie'>;
visData: unknown;
visType: string;
visConfig: PieVisParams;
}
@ -42,7 +43,7 @@ export type VisTypeVislibPieExpressionFunctionDefinition = ExpressionFunctionDef
typeof vislibPieName,
Datatable,
Arguments,
Render<RenderValue>
Render<PieRenderValue>
>;
export const createPieVisFn = (): VisTypeVislibPieExpressionFunctionDefinition => ({
@ -73,7 +74,7 @@ export const createPieVisFn = (): VisTypeVislibPieExpressionFunctionDefinition =
value: {
visData,
visConfig,
visType: 'pie',
visType: VislibChartType.Pie,
},
};
},

View file

@ -19,25 +19,28 @@
import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'kibana/public';
import { VisTypeXyPluginSetup } from 'src/plugins/vis_type_xy/public';
import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public';
import { BaseVisTypeOptions, VisualizationsSetup } from '../../visualizations/public';
import { createVisTypeVislibVisFn } from './vis_type_vislib_vis_fn';
import { createPieVisFn } from './pie_fn';
import { visLibVisTypeDefinitions, pieVisTypeDefinition } from './vis_type_vislib_vis_types';
import { VisualizationsSetup } from '../../visualizations/public';
import { ChartsPluginSetup } from '../../charts/public';
import { DataPublicPluginStart } from '../../data/public';
import { KibanaLegacyStart } from '../../kibana_legacy/public';
import { CHARTS_LIBRARY } from '../../vis_type_xy/public';
import { createVisTypeVislibVisFn } from './vis_type_vislib_vis_fn';
import { createPieVisFn } from './pie_fn';
import {
convertedTypeDefinitions,
pieVisTypeDefinition,
visLibVisTypeDefinitions,
} from './vis_type_vislib_vis_types';
import { setFormatService, setDataActions } from './services';
import { getVislibVisRenderer } from './vis_renderer';
import { BasicVislibParams } from './types';
/** @internal */
export interface VisTypeVislibPluginSetupDependencies {
expressions: ReturnType<ExpressionsPublicPlugin['setup']>;
visualizations: VisualizationsSetup;
charts: ChartsPluginSetup;
visTypeXy?: VisTypeXyPluginSetup;
}
/** @internal */
@ -56,23 +59,21 @@ export class VisTypeVislibPlugin
public async setup(
core: VisTypeVislibCoreSetup,
{ expressions, visualizations, charts, visTypeXy }: VisTypeVislibPluginSetupDependencies
{ expressions, visualizations, charts }: VisTypeVislibPluginSetupDependencies
) {
// if visTypeXy plugin is disabled it's config will be undefined
if (!visTypeXy) {
const convertedTypes: Array<BaseVisTypeOptions<BasicVislibParams>> = [];
const convertedFns: any[] = [];
// Register legacy vislib types that have been converted
convertedFns.forEach(expressions.registerFunction);
convertedTypes.forEach(visualizations.createBaseVisualization);
if (core.uiSettings.get(CHARTS_LIBRARY)) {
// Register only non-replaced vis types
convertedTypeDefinitions.forEach(visualizations.createBaseVisualization);
visualizations.createBaseVisualization(pieVisTypeDefinition);
expressions.registerRenderer(getVislibVisRenderer(core, charts));
[createVisTypeVislibVisFn(), createPieVisFn()].forEach(expressions.registerFunction);
} else {
// Register all vis types
visLibVisTypeDefinitions.forEach(visualizations.createBaseVisualization);
visualizations.createBaseVisualization(pieVisTypeDefinition);
expressions.registerRenderer(getVislibVisRenderer(core, charts));
[createVisTypeVislibVisFn(), createPieVisFn()].forEach(expressions.registerFunction);
}
// Register non-converted types
visLibVisTypeDefinitions.forEach(visualizations.createBaseVisualization);
visualizations.createBaseVisualization(pieVisTypeDefinition);
expressions.registerRenderer(getVislibVisRenderer(core, charts));
[createVisTypeVislibVisFn(), createPieVisFn()].forEach(expressions.registerFunction);
}
public start(core: CoreStart, { data }: VisTypeVislibPluginStartDependencies) {

View file

@ -21,14 +21,11 @@ import moment from 'moment';
import { VisToExpressionAst, getVisSchemas } from '../../visualizations/public';
import { buildExpression, buildExpressionFunction } from '../../expressions/public';
import type { Dimensions, DateHistogramParams, HistogramParams } from '../../vis_type_xy/public';
import { BUCKET_TYPES } from '../../data/public';
import { vislibVisName, VisTypeVislibExpressionFunctionDefinition } from './vis_type_vislib_vis_fn';
import { BasicVislibParams } from './types';
import {
DateHistogramParams,
Dimensions,
HistogramParams,
} from './vislib/helpers/point_series/point_series';
import { BasicVislibParams, VislibChartType } from './types';
import { getEsaggsFn } from './to_ast_esaggs';
export const toExpressionAst: VisToExpressionAst<BasicVislibParams> = async (vis, params) => {
@ -47,7 +44,7 @@ export const toExpressionAst: VisToExpressionAst<BasicVislibParams> = async (vis
if (dimensions.x) {
const xAgg = responseAggs[dimensions.x.accessor] as any;
if (xAgg.type.name === 'date_histogram') {
if (xAgg.type.name === BUCKET_TYPES.DATE_HISTOGRAM) {
(dimensions.x.params as DateHistogramParams).date = true;
const { esUnit, esValue } = xAgg.buckets.getInterval();
(dimensions.x.params as DateHistogramParams).intervalESUnit = esUnit;
@ -57,7 +54,7 @@ export const toExpressionAst: VisToExpressionAst<BasicVislibParams> = async (vis
.asMilliseconds();
(dimensions.x.params as DateHistogramParams).format = xAgg.buckets.getScaledDateFormat();
(dimensions.x.params as DateHistogramParams).bounds = xAgg.buckets.getBounds();
} else if (xAgg.type.name === 'histogram') {
} else if (xAgg.type.name === BUCKET_TYPES.HISTOGRAM) {
const intervalParam = xAgg.type.paramByName('interval');
const output = { params: {} as any };
await intervalParam.modifyAggConfigOnSearchRequestStart(xAgg, vis.data.searchSource, {
@ -88,15 +85,15 @@ export const toExpressionAst: VisToExpressionAst<BasicVislibParams> = async (vis
visConfig.dimensions = dimensions;
const visTypeXy = buildExpressionFunction<VisTypeVislibExpressionFunctionDefinition>(
const visTypeVislib = buildExpressionFunction<VisTypeVislibExpressionFunctionDefinition>(
vislibVisName,
{
type: vis.type.name,
type: vis.type.name as Exclude<VislibChartType, 'pie'>,
visConfig: JSON.stringify(visConfig),
}
);
const ast = buildExpression([getEsaggsFn(vis), visTypeXy]);
const ast = buildExpression([getEsaggsFn(vis), visTypeVislib]);
return ast.toAst();
};

View file

@ -29,6 +29,8 @@ import { BasicVislibParams } from './types';
/**
* Get esaggs expressions function
* TODO: replace this with vis.data.aggs!.toExpressionAst();
* https://github.com/elastic/kibana/issues/61768
* @param vis
*/
export function getEsaggsFn(vis: Vis<PieVisParams> | Vis<BasicVislibParams>) {

View file

@ -17,87 +17,71 @@
* under the License.
*/
import { TimeMarker } from './vislib/visualizations/time_marker';
import { $Values } from '@kbn/utility-types';
import { Position } from '@elastic/charts';
import { Labels } from '../../charts/public';
import {
Positions,
ChartModes,
ChartTypes,
AxisModes,
AxisTypes,
InterpolationModes,
ScaleTypes,
ThresholdLineStyles,
} from './utils/collections';
import { Labels, Style } from '../../charts/public';
import { Dimensions } from './vislib/helpers/point_series/point_series';
CategoryAxis,
Dimensions,
Grid,
SeriesParam,
ThresholdLine,
ValueAxis,
} from '../../vis_type_xy/public';
import { TimeMarker } from './vislib/visualizations/time_marker';
/**
* Gauge title alignment
*/
export const Alignment = Object.freeze({
Automatic: 'automatic' as const,
Horizontal: 'horizontal' as const,
Vertical: 'vertical' as const,
});
export type Alignment = $Values<typeof Alignment>;
export const GaugeType = Object.freeze({
Arc: 'Arc' as const,
Circle: 'Circle' as const,
});
export type GaugeType = $Values<typeof GaugeType>;
export const VislibChartType = Object.freeze({
Histogram: 'histogram' as const,
HorizontalBar: 'horizontal_bar' as const,
Line: 'line' as const,
Pie: 'pie' as const,
Area: 'area' as const,
PointSeries: 'point_series' as const,
Heatmap: 'heatmap' as const,
Gauge: 'gauge' as const,
Goal: 'goal' as const,
Metric: 'metric' as const,
});
export type VislibChartType = $Values<typeof VislibChartType>;
export interface CommonVislibParams {
addTooltip: boolean;
addLegend: boolean;
legendPosition: Positions;
legendPosition: Position;
dimensions: Dimensions;
}
export interface Scale {
boundsMargin?: number | '';
defaultYExtents?: boolean;
max?: number | null;
min?: number | null;
mode?: AxisModes;
setYExtents?: boolean;
type: ScaleTypes;
}
interface ThresholdLine {
show: boolean;
value: number | null;
width: number | null;
style: ThresholdLineStyles;
color: string;
}
export interface Axis {
id: string;
labels: Labels;
position: Positions;
scale: Scale;
show: boolean;
style: Style;
title: { text: string };
type: AxisTypes;
}
export interface ValueAxis extends Axis {
name: string;
}
export interface SeriesParam {
data: { label: string; id: string };
drawLinesBetweenPoints: boolean;
interpolate: InterpolationModes;
lineWidth?: number;
mode: ChartModes;
show: boolean;
showCircles: boolean;
type: ChartTypes;
valueAxis: string;
}
export interface BasicVislibParams extends CommonVislibParams {
type: VislibChartType;
addLegend: boolean;
addTimeMarker: boolean;
categoryAxes: Axis[];
categoryAxes: CategoryAxis[];
orderBucketsBySum?: boolean;
labels: Labels;
thresholdLine: ThresholdLine;
valueAxes: ValueAxis[];
grid: Grid;
gauge?: {
percentageMode: boolean;
};
grid: {
categoryLines: boolean;
valueAxis?: string;
};
seriesParams: SeriesParam[];
times: TimeMarker[];
type: string;
radiusRatio: number;
}

View file

@ -1,338 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { $Values } from '@kbn/utility-types';
import { colorSchemas, Rotates } from '../../../charts/public';
export const Positions = Object.freeze({
RIGHT: 'right' as 'right',
LEFT: 'left' as 'left',
TOP: 'top' as 'top',
BOTTOM: 'bottom' as 'bottom',
});
export type Positions = $Values<typeof Positions>;
const getPositions = () => [
{
text: i18n.translate('visTypeVislib.legendPositions.topText', {
defaultMessage: 'Top',
}),
value: Positions.TOP,
},
{
text: i18n.translate('visTypeVislib.legendPositions.leftText', {
defaultMessage: 'Left',
}),
value: Positions.LEFT,
},
{
text: i18n.translate('visTypeVislib.legendPositions.rightText', {
defaultMessage: 'Right',
}),
value: Positions.RIGHT,
},
{
text: i18n.translate('visTypeVislib.legendPositions.bottomText', {
defaultMessage: 'Bottom',
}),
value: Positions.BOTTOM,
},
];
export const ChartTypes = Object.freeze({
LINE: 'line' as 'line',
AREA: 'area' as 'area',
HISTOGRAM: 'histogram' as 'histogram',
});
export type ChartTypes = $Values<typeof ChartTypes>;
const getChartTypes = () => [
{
text: i18n.translate('visTypeVislib.chartTypes.lineText', {
defaultMessage: 'Line',
}),
value: ChartTypes.LINE,
},
{
text: i18n.translate('visTypeVislib.chartTypes.areaText', {
defaultMessage: 'Area',
}),
value: ChartTypes.AREA,
},
{
text: i18n.translate('visTypeVislib.chartTypes.barText', {
defaultMessage: 'Bar',
}),
value: ChartTypes.HISTOGRAM,
},
];
export const ChartModes = Object.freeze({
NORMAL: 'normal' as 'normal',
STACKED: 'stacked' as 'stacked',
});
export type ChartModes = $Values<typeof ChartModes>;
const getChartModes = () => [
{
text: i18n.translate('visTypeVislib.chartModes.normalText', {
defaultMessage: 'Normal',
}),
value: ChartModes.NORMAL,
},
{
text: i18n.translate('visTypeVislib.chartModes.stackedText', {
defaultMessage: 'Stacked',
}),
value: ChartModes.STACKED,
},
];
export const InterpolationModes = Object.freeze({
LINEAR: 'linear' as 'linear',
CARDINAL: 'cardinal' as 'cardinal',
STEP_AFTER: 'step-after' as 'step-after',
});
export type InterpolationModes = $Values<typeof InterpolationModes>;
const getInterpolationModes = () => [
{
text: i18n.translate('visTypeVislib.interpolationModes.straightText', {
defaultMessage: 'Straight',
}),
value: InterpolationModes.LINEAR,
},
{
text: i18n.translate('visTypeVislib.interpolationModes.smoothedText', {
defaultMessage: 'Smoothed',
}),
value: InterpolationModes.CARDINAL,
},
{
text: i18n.translate('visTypeVislib.interpolationModes.steppedText', {
defaultMessage: 'Stepped',
}),
value: InterpolationModes.STEP_AFTER,
},
];
export const AxisTypes = Object.freeze({
CATEGORY: 'category' as 'category',
VALUE: 'value' as 'value',
});
export type AxisTypes = $Values<typeof AxisTypes>;
export const ScaleTypes = Object.freeze({
LINEAR: 'linear' as 'linear',
LOG: 'log' as 'log',
SQUARE_ROOT: 'square root' as 'square root',
});
export type ScaleTypes = $Values<typeof ScaleTypes>;
const getScaleTypes = () => [
{
text: i18n.translate('visTypeVislib.scaleTypes.linearText', {
defaultMessage: 'Linear',
}),
value: ScaleTypes.LINEAR,
},
{
text: i18n.translate('visTypeVislib.scaleTypes.logText', {
defaultMessage: 'Log',
}),
value: ScaleTypes.LOG,
},
{
text: i18n.translate('visTypeVislib.scaleTypes.squareRootText', {
defaultMessage: 'Square root',
}),
value: ScaleTypes.SQUARE_ROOT,
},
];
export const AxisModes = Object.freeze({
NORMAL: 'normal' as 'normal',
PERCENTAGE: 'percentage' as 'percentage',
WIGGLE: 'wiggle' as 'wiggle',
SILHOUETTE: 'silhouette' as 'silhouette',
});
export type AxisModes = $Values<typeof AxisModes>;
const getAxisModes = () => [
{
text: i18n.translate('visTypeVislib.axisModes.normalText', {
defaultMessage: 'Normal',
}),
value: AxisModes.NORMAL,
},
{
text: i18n.translate('visTypeVislib.axisModes.percentageText', {
defaultMessage: 'Percentage',
}),
value: AxisModes.PERCENTAGE,
},
{
text: i18n.translate('visTypeVislib.axisModes.wiggleText', {
defaultMessage: 'Wiggle',
}),
value: AxisModes.WIGGLE,
},
{
text: i18n.translate('visTypeVislib.axisModes.silhouetteText', {
defaultMessage: 'Silhouette',
}),
value: AxisModes.SILHOUETTE,
},
];
export const ThresholdLineStyles = Object.freeze({
FULL: 'full' as 'full',
DASHED: 'dashed' as 'dashed',
DOT_DASHED: 'dot-dashed' as 'dot-dashed',
});
export type ThresholdLineStyles = $Values<typeof ThresholdLineStyles>;
const getThresholdLineStyles = () => [
{
value: ThresholdLineStyles.FULL,
text: i18n.translate('visTypeVislib.thresholdLine.style.fullText', {
defaultMessage: 'Full',
}),
},
{
value: ThresholdLineStyles.DASHED,
text: i18n.translate('visTypeVislib.thresholdLine.style.dashedText', {
defaultMessage: 'Dashed',
}),
},
{
value: ThresholdLineStyles.DOT_DASHED,
text: i18n.translate('visTypeVislib.thresholdLine.style.dotdashedText', {
defaultMessage: 'Dot-dashed',
}),
},
];
const getRotateOptions = () => [
{
text: i18n.translate('visTypeVislib.categoryAxis.rotate.horizontalText', {
defaultMessage: 'Horizontal',
}),
value: Rotates.HORIZONTAL,
},
{
text: i18n.translate('visTypeVislib.categoryAxis.rotate.verticalText', {
defaultMessage: 'Vertical',
}),
value: Rotates.VERTICAL,
},
{
text: i18n.translate('visTypeVislib.categoryAxis.rotate.angledText', {
defaultMessage: 'Angled',
}),
value: Rotates.ANGLED,
},
];
export const GaugeTypes = Object.freeze({
ARC: 'Arc' as 'Arc',
CIRCLE: 'Circle' as 'Circle',
});
export type GaugeTypes = $Values<typeof GaugeTypes>;
const getGaugeTypes = () => [
{
text: i18n.translate('visTypeVislib.gauge.gaugeTypes.arcText', {
defaultMessage: 'Arc',
}),
value: GaugeTypes.ARC,
},
{
text: i18n.translate('visTypeVislib.gauge.gaugeTypes.circleText', {
defaultMessage: 'Circle',
}),
value: GaugeTypes.CIRCLE,
},
];
export const Alignments = Object.freeze({
AUTOMATIC: 'automatic' as 'automatic',
HORIZONTAL: 'horizontal' as 'horizontal',
VERTICAL: 'vertical' as 'vertical',
});
export type Alignments = $Values<typeof Alignments>;
const getAlignments = () => [
{
text: i18n.translate('visTypeVislib.gauge.alignmentAutomaticTitle', {
defaultMessage: 'Automatic',
}),
value: Alignments.AUTOMATIC,
},
{
text: i18n.translate('visTypeVislib.gauge.alignmentHorizontalTitle', {
defaultMessage: 'Horizontal',
}),
value: Alignments.HORIZONTAL,
},
{
text: i18n.translate('visTypeVislib.gauge.alignmentVerticalTitle', {
defaultMessage: 'Vertical',
}),
value: Alignments.VERTICAL,
},
];
const getConfigCollections = () => ({
legendPositions: getPositions(),
positions: getPositions(),
chartTypes: getChartTypes(),
axisModes: getAxisModes(),
scaleTypes: getScaleTypes(),
chartModes: getChartModes(),
interpolationModes: getInterpolationModes(),
thresholdLineStyles: getThresholdLineStyles(),
});
const getGaugeCollections = () => ({
gaugeTypes: getGaugeTypes(),
alignments: getAlignments(),
colorSchemas,
});
const getHeatmapCollections = () => ({
legendPositions: getPositions(),
scales: getScaleTypes(),
colorSchemas,
});
export {
getConfigCollections,
getGaugeCollections,
getHeatmapCollections,
getPositions,
getRotateOptions,
getScaleTypes,
getInterpolationModes,
getChartTypes,
getChartModes,
getAxisModes,
};

View file

@ -26,9 +26,13 @@ import { ChartsPluginSetup } from '../../charts/public';
import { VisTypeVislibCoreSetup } from './plugin';
import { VislibRenderValue, vislibVisName } from './vis_type_vislib_vis_fn';
import { VislibChartType } from './types';
import { PieRenderValue } from './pie_fn';
function shouldShowNoResultsMessage(visData: any, visType: string): boolean {
if (['goal', 'gauge'].includes(visType)) {
const VislibWrapper = lazy(() => import('./vis_wrapper'));
function shouldShowNoResultsMessage(visData: any, visType: VislibChartType): boolean {
if (['goal', 'gauge'].includes(visType as string)) {
return false;
}
@ -38,13 +42,12 @@ function shouldShowNoResultsMessage(visData: any, visType: string): boolean {
return Boolean(isZeroHits);
}
const VislibWrapper = lazy(() => import('./vis_wrapper'));
export const getVislibVisRenderer: (
core: VisTypeVislibCoreSetup,
charts: ChartsPluginSetup
) => ExpressionRenderDefinition<VislibRenderValue> = (core, charts) => ({
) => ExpressionRenderDefinition<VislibRenderValue | PieRenderValue> = (core, charts) => ({
name: vislibVisName,
displayName: 'Vislib visualization',
reuseDomNode: true,
render: async (domNode, config, handlers) => {
const showNoResult = shouldShowNoResultsMessage(config.visData, config.visType);

View file

@ -23,18 +23,18 @@ import { ExpressionFunctionDefinition, Datatable, Render } from '../../expressio
// @ts-ignore
import { vislibSeriesResponseHandler } from './vislib/response_handler';
import { BasicVislibParams } from './types';
import { BasicVislibParams, VislibChartType } from './types';
export const vislibVisName = 'vislib_vis';
interface Arguments {
type: string;
type: Exclude<VislibChartType, 'pie'>;
visConfig: string;
}
export interface VislibRenderValue {
visData: any;
visType: string;
visType: Exclude<VislibChartType, 'pie'>;
visData: unknown;
visConfig: BasicVislibParams;
}
@ -65,7 +65,7 @@ export const createVisTypeVislibVisFn = (): VisTypeVislibExpressionFunctionDefin
},
},
fn(context, args, handlers) {
const visType = args.type;
const visType = args.type as Exclude<VislibChartType, 'pie'>;
const visConfig = JSON.parse(args.visConfig) as BasicVislibParams;
const visData = vislibSeriesResponseHandler(context, visConfig.dimensions);

View file

@ -36,3 +36,9 @@ export const visLibVisTypeDefinitions = [
gaugeVisTypeDefinition,
goalVisTypeDefinition,
];
export const convertedTypeDefinitions = [
heatmapVisTypeDefinition,
gaugeVisTypeDefinition,
goalVisTypeDefinition,
];

View file

@ -27,10 +27,11 @@ import { ChartsPluginSetup } from '../../charts/public';
import { VislibRenderValue } from './vis_type_vislib_vis_fn';
import { createVislibVisController, VislibVisController } from './vis_controller';
import { VisTypeVislibCoreSetup } from './plugin';
import { PieRenderValue } from './pie_fn';
import './index.scss';
type VislibWrapperProps = VislibRenderValue & {
type VislibWrapperProps = (VislibRenderValue | PieRenderValue) & {
core: VisTypeVislibCoreSetup;
charts: ChartsPluginSetup;
handlers: IInterpreterRenderHandlers;

View file

@ -3,8 +3,6 @@
// NOTE: Some of the styles attempt to align with the TSVB legend
$visLegendWidth: 150px;
$visColorPickerWidth: $euiSizeM * 10;
$visLegendLineHeight: $euiSize;
.visLegend__toggle {
border-radius: $euiBorderRadius;
@ -81,20 +79,3 @@ $visLegendLineHeight: $euiSize;
visibility: hidden;
}
}
.visLegend__valueColorPicker {
width: ($euiSizeL * 8); // 8 columns
}
.visLegend__valueColorPickerDot {
cursor: pointer;
&:hover {
transform: scale(1.4);
}
&-isSelected {
border: $euiSizeXS solid;
border-radius: 100%;
}
}

View file

@ -243,7 +243,7 @@ describe('VisLegend Component', () => {
const first = getLegendItems(wrapper).first();
first.simulate('click');
expect(wrapper.exists('.visLegend__valueDetails')).toBe(true);
expect(wrapper.exists('.visColorPicker')).toBe(true);
});
});
@ -256,8 +256,8 @@ describe('VisLegend Component', () => {
const first = getLegendItems(wrapper).first();
first.simulate('click');
const popover = wrapper.find('.visLegend__valueDetails').first();
const firstColor = popover.find('.visLegend__valueColorPickerDot').first();
const popover = wrapper.find('.visColorPicker').first();
const firstColor = popover.find('.visColorPicker__valueDot').first();
firstColor.simulate('click');
const colors = mockState.get('vis.colors');

View file

@ -78,18 +78,20 @@ export class VisLegend extends PureComponent<VisLegendProps, VisLegendState> {
});
};
setColor = (label: string, color: string) => (event: BaseSyntheticEvent) => {
setColor = (label: string | number, color: string | null, event: BaseSyntheticEvent) => {
if ((event as KeyboardEvent).key && (event as KeyboardEvent).key !== keys.ENTER) {
return;
}
const colors = this.props.uiState?.get('vis.colors') || {};
if (colors[label] === color) delete colors[label];
else colors[label] = color;
this.props.uiState?.setSilent('vis.colors', null);
this.props.uiState?.set('vis.colors', colors);
this.props.uiState?.emit('colorChanged');
this.refresh();
this.setState({ selectedLabel: null }, () => {
const colors = this.props.uiState?.get('vis.colors') || {};
if (colors[label] === color || !color) delete colors[label];
else colors[label] = color;
this.props.uiState?.setSilent('vis.colors', null);
this.props.uiState?.set('vis.colors', colors);
this.props.uiState?.emit('colorChanged');
this.refresh();
});
};
filter = ({ values: data }: LegendItem, negate: boolean) => {

View file

@ -18,10 +18,8 @@
*/
import React, { memo, useState, BaseSyntheticEvent, KeyboardEvent } from 'react';
import classNames from 'classnames';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiPopover,
keys,
@ -33,7 +31,8 @@ import {
EuiButtonGroupOptionProps,
} from '@elastic/eui';
import { legendColors, LegendItem } from './models';
import { LegendItem } from './models';
import { ColorPicker } from '../../../../../charts/public';
interface Props {
item: LegendItem;
@ -45,7 +44,7 @@ interface Props {
onSelect: (label: string | null) => (event?: BaseSyntheticEvent) => void;
onHighlight: (event: BaseSyntheticEvent) => void;
onUnhighlight: (event: BaseSyntheticEvent) => void;
setColor: (label: string, color: string) => (event: BaseSyntheticEvent) => void;
setColor: (label: string, color: string | null, event: BaseSyntheticEvent) => void;
getColor: (label: string) => string;
}
@ -159,40 +158,14 @@ const VisLegendItemComponent = ({
closePopover={onSelect(null)}
panelPaddingSize="s"
>
<div className="visLegend__valueDetails">
{canFilter && renderFilterBar()}
{canFilter && renderFilterBar()}
<div className="visLegend__valueColorPicker" role="listbox">
<span id={`${legendId}ColorPickerDesc`} className="euiScreenReaderOnly">
<FormattedMessage
id="visTypeVislib.vislib.legend.setColorScreenReaderDescription"
defaultMessage="Set color for value {legendDataLabel}"
values={{ legendDataLabel: item.label }}
/>
</span>
{legendColors.map((color) => (
<EuiIcon
role="option"
tabIndex={0}
type="dot"
size="l"
color={getColor(item.label)}
key={color}
aria-label={color}
aria-describedby={`${legendId}ColorPickerDesc`}
aria-selected={color === getColor(item.label)}
onClick={setColor(item.label, color)}
onKeyPress={setColor(item.label, color)}
className={classNames('visLegend__valueColorPickerDot', {
// eslint-disable-next-line @typescript-eslint/naming-convention
'visLegend__valueColorPickerDot-isSelected': color === getColor(item.label),
})}
style={{ color }}
data-test-subj={`legendSelectColor-${color}`}
/>
))}
</div>
</div>
<ColorPicker
id={legendId}
label={item.label}
color={getColor(item.label)}
onChange={(c, e) => setColor(item.label, c, e)}
/>
</EuiPopover>
);

View file

@ -17,9 +17,10 @@
* under the License.
*/
import type { Dimension } from '../../../../../vis_type_xy/public';
import { addToSiri, Serie } from './_add_to_siri';
import { Point } from './_get_point';
import { Dimension } from './point_series';
describe('addToSiri', function () {
it('creates a new series the first time it sees an id', function () {

View file

@ -17,8 +17,10 @@
* under the License.
*/
import { getAggId } from '../../../../../vis_type_xy/public';
import type { Dimension } from '../../../../../vis_type_xy/public';
import { Point } from './_get_point';
import { Dimension } from './point_series';
export interface Serie {
id: string;
@ -48,7 +50,7 @@ export function addToSiri(
}
series.set(id, {
id: id.split('-').pop() as string,
id: getAggId(id),
rawId: id,
label: yLabel == null ? id : yLabel,
count: 0,

View file

@ -17,8 +17,10 @@
* under the License.
*/
import type { Dimension, Dimensions } from '../../../../../vis_type_xy/public';
import { getAspects } from './_get_aspects';
import { Dimension, Dimensions, Aspect } from './point_series';
import { Aspect } from './point_series';
import { Table, Row } from '../../types';
describe('getAspects', function () {

View file

@ -17,8 +17,10 @@
* under the License.
*/
import type { Dimensions } from '../../../../../vis_type_xy/public';
import { makeFakeXAspect } from './_fake_x_aspect';
import { Dimensions, Aspects } from './point_series';
import { Aspects } from './point_series';
import { Table } from '../../types';
/**

View file

@ -18,6 +18,7 @@
*/
import { IFieldFormatsRegistry } from '../../../../../data/common';
import { getPoint } from './_get_point';
import { setFormatService } from '../../../services';
import { Aspect } from './point_series';
@ -94,7 +95,12 @@ describe('getPoint', function () {
it('should call deserialize', function () {
const seriesAspect = [
{ accessor: '1', format: { id: 'number', params: { pattern: '$' } } } as Aspect,
{
title: 'series',
accessor: '1',
format: { id: 'number', params: { pattern: '$' } },
params: {},
} as Aspect,
];
getPoint(table, xAspect, seriesAspect, row, 0, yAspect);

View file

@ -18,16 +18,12 @@
*/
import moment from 'moment';
import type { DateHistogramParams, HistogramParams } from '../../../../../vis_type_xy/public';
import { initXAxis } from './_init_x_axis';
import { makeFakeXAspect } from './_fake_x_aspect';
import {
Aspects,
Chart,
DateHistogramOrdered,
DateHistogramParams,
HistogramOrdered,
HistogramParams,
} from './point_series';
import { Aspects, Chart, DateHistogramOrdered, HistogramOrdered } from './point_series';
import { Table, Column } from '../../types';
describe('initXAxis', function () {
@ -110,7 +106,7 @@ describe('initXAxis', function () {
it('reads the date interval param from the x agg', function () {
const dateHistogramParams = chart.aspects.x[0].params as DateHistogramParams;
dateHistogramParams.interval = 'P1D';
dateHistogramParams.interval = moment.duration(1, 'd').asMilliseconds();
dateHistogramParams.intervalESValue = 1;
dateHistogramParams.intervalESUnit = 'd';
dateHistogramParams.date = true;

View file

@ -19,8 +19,11 @@
import moment from 'moment';
import _ from 'lodash';
import type { DateHistogramParams } from '../../../../../vis_type_xy/public/types';
import { orderedDateAxis } from './_ordered_date_axis';
import { DateHistogramParams, OrderedChart } from './point_series';
import { OrderedChart } from './point_series';
describe('orderedDateAxis', function () {
const baseArgs = {

View file

@ -18,7 +18,10 @@
*/
import _ from 'lodash';
import { buildPointSeriesData, Dimensions } from './point_series';
import type { Dimensions } from '../../../../../vis_type_xy/public';
import { buildPointSeriesData } from './point_series';
import { Table, Column } from '../../types';
import { setFormatService } from '../../../services';
import { Serie } from './_add_to_siri';

View file

@ -18,6 +18,14 @@
*/
import { Duration } from 'moment';
import type {
Dimension,
Dimensions,
DateHistogramParams,
HistogramParams,
} from '../../../../../vis_type_xy/public';
import { getSeries } from './_get_series';
import { getAspects } from './_get_aspects';
import { initYAxis } from './_init_y_axis';
@ -26,41 +34,6 @@ import { orderedDateAxis } from './_ordered_date_axis';
import { Serie } from './_add_to_siri';
import { Column, Table } from '../../types';
export interface DateHistogramParams {
date: boolean;
interval: number | string;
intervalESValue: number;
intervalESUnit: string;
format: string;
bounds?: {
min: string | number;
max: string | number;
};
}
export interface HistogramParams {
interval: number;
}
export interface FakeParams {
defaultValue: string;
}
export interface Dimension {
accessor: number;
format: {
id?: string;
params?: { pattern?: string; [key: string]: any };
};
params: DateHistogramParams | HistogramParams | FakeParams | {};
}
export interface Dimensions {
x: Dimension | null;
y: Dimension[];
z?: Dimension[];
series?: Dimension | Dimension[];
width?: Dimension[];
splitRow?: Dimension[];
splitColumn?: Dimension[];
}
export interface Aspect {
accessor: Column['id'];
column?: Dimension['accessor'];

View file

@ -21,15 +21,16 @@ import d3 from 'd3';
import _ from 'lodash';
import MarkdownIt from 'markdown-it';
import { dispatchRenderComplete } from '../../../../kibana_utils/public';
import { visTypes as chartTypes } from '../visualizations/vis_types';
import { NoResults } from '../errors';
import { Layout } from './layout/layout';
import { ChartTitle } from './chart_title';
import { Alerts } from './alerts';
import { Axis } from './axis/axis';
import { ChartGrid as Grid } from './chart_grid';
import { visTypes as chartTypes } from '../visualizations/vis_types';
import { Binder } from './binder';
import { dispatchRenderComplete } from '../../../../kibana_utils/public';
const markdownIt = new MarkdownIt({
html: false,

View file

@ -31,7 +31,7 @@
},
"params": {
"date": true,
"interval": "P1D",
"interval": 86400000,
"format": "YYYY-MM-DD",
"bounds": {
"min": "2019-05-10T04:00:00.000Z",
@ -128,7 +128,7 @@
},
"xAxisLabel": "timestamp per day",
"ordered": {
"interval": "P1D",
"interval": 86400000,
"date": true,
"min": 1557460800000,
"max": 1557656337342

View file

@ -31,7 +31,7 @@
},
"params": {
"date": true,
"interval": "P1D",
"interval": 86400000,
"format": "YYYY-MM-DD",
"bounds": {
"min": "2019-05-10T04:00:00.000Z",
@ -128,7 +128,7 @@
},
"xAxisLabel": "timestamp per day",
"ordered": {
"interval": "P1D",
"interval": 86400000,
"date": true,
"min": 1557460800000,
"max": 1557656337342
@ -460,4 +460,4 @@
"50th percentile of AvgTicketPrice"
]
}
}
}

View file

@ -32,7 +32,7 @@
},
"params": {
"date": true,
"interval": "P1D",
"interval": 86400000,
"format": "YYYY-MM-DD",
"bounds": {
"min": "2019-05-10T04:00:00.000Z",
@ -453,4 +453,4 @@
}
],
"enableHover": true
}
}

View file

@ -32,7 +32,7 @@
},
"params": {
"date": true,
"interval": "P1D",
"interval": 86400000,
"format": "YYYY-MM-DD",
"bounds": {
"min": "2019-05-10T04:00:00.000Z",
@ -455,4 +455,4 @@
}
],
"enableHover": true
}
}

View file

@ -22,8 +22,8 @@ import { Chart } from './_chart';
import { gaugeTypes } from './gauges/gauge_types';
export class GaugeChart extends Chart {
constructor(handler, chartEl, chartData, deps) {
super(handler, chartEl, chartData, deps);
constructor(handler, chartEl, chartData, uiSettings) {
super(handler, chartEl, chartData, uiSettings);
this.gaugeConfig = handler.visConfig.get('gauge', {});
this.gauge = new gaugeTypes[this.gaugeConfig.type](this);
}

View file

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

View file

@ -24,6 +24,8 @@ import { UiSettingsParams } from 'kibana/server';
import { DIMMING_OPACITY_SETTING, HEATMAP_MAX_BUCKETS_SETTING } from '../common';
export const uiSettings: Record<string, UiSettingsParams> = {
// TODO: move this to vis_type_xy when vislib is removed
// https://github.com/elastic/kibana/issues/56143
[DIMMING_OPACITY_SETTING]: {
name: i18n.translate('visTypeVislib.advancedSettings.visualization.dimmingOpacityTitle', {
defaultMessage: 'Dimming opacity',

View file

@ -1,2 +1,2 @@
Contains the new xy-axis chart using the elastic-charts library, which will eventually
replace the vislib xy-axis (bar, area, line) charts.
replace the vislib xy-axis charts including bar, area, and line.

View file

@ -0,0 +1,37 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { $Values } from '@kbn/utility-types';
/**
* Type of charts able to render
*/
export const ChartType = Object.freeze({
Line: 'line' as const,
Area: 'area' as const,
Histogram: 'histogram' as const,
});
export type ChartType = $Values<typeof ChartType>;
/**
* Type of xy visualizations
*/
export type XyVisType = ChartType | 'horizontal_bar';
export const CHARTS_LIBRARY = 'visualization:visualize:chartsLibrary';

View file

@ -0,0 +1,24 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../..',
roots: ['<rootDir>/src/plugins/vis_type_xy'],
};

View file

@ -3,5 +3,6 @@
"version": "kibana",
"server": true,
"ui": true,
"requiredPlugins": ["charts", "expressions", "visualizations"]
"requiredPlugins": ["charts", "data", "expressions", "visualizations"],
"requiredBundles": ["kibanaUtils", "visDefaultEditor"]
}

View file

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

View file

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

View file

@ -0,0 +1,34 @@
.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

@ -0,0 +1,142 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { isNil } from 'lodash';
import {
CustomTooltip,
TooltipValue,
TooltipValueFormatter,
XYChartSeriesIdentifier,
} from '@elastic/charts';
import { BUCKET_TYPES } from '../../../data/public';
import { Aspects } from '../types';
import './_detailed_tooltip.scss';
import { fillEmptyValue } from '../utils/get_series_name_fn';
import { COMPLEX_SPLIT_ACCESSOR } from '../utils/accessors';
interface TooltipData {
label: string;
value: string;
}
const getTooltipData = (
aspects: Aspects,
header: TooltipValue | null,
value: TooltipValue
): TooltipData[] => {
const data: TooltipData[] = [];
if (header) {
const xFormatter =
aspects.x.aggType === BUCKET_TYPES.DATE_RANGE || aspects.x.aggType === BUCKET_TYPES.RANGE
? 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}`,
});
}
});
return data;
};
const renderData = ({ label, value: rawValue }: TooltipData, index: number) => {
const value = fillEmptyValue(rawValue);
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

@ -0,0 +1,25 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
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';
export { SplitChartWarning } from './split_chart_warning';

View file

@ -0,0 +1,55 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { FC } from 'react';
import { EuiLink, EuiCallOut } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { getDocLinks } from '../services';
export const SplitChartWarning: FC = () => {
const advancedSettingsLink = getDocLinks().links.management.visualizationSettings;
return (
<EuiCallOut
title={i18n.translate('visTypeXy.splitChartWarning.title', {
defaultMessage: 'Warning',
})}
color="warning"
iconType="help"
>
<FormattedMessage
id="visTypeXy.splitChartWarning.content"
defaultMessage="The new charts library does not support split chart aggregation. Please disable the {link} advanced setting to use split chart aggregation."
values={{
link: (
<EuiLink href={advancedSettingsLink} target="_blank" external>
<FormattedMessage
id="visTypeXy.splitChartWarning.link"
defaultMessage="Charts library"
/>
</EuiLink>
),
}}
/>
</EuiCallOut>
);
};

View file

@ -0,0 +1,55 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { 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,
}) => (
<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}
/>
);

View file

@ -0,0 +1,37 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { FC } from 'react';
import { DomainRange } from '@elastic/charts';
import { CurrentTime } from '../../../charts/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

@ -0,0 +1,68 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { FC } from 'react';
import { DomainRange } from '@elastic/charts';
import { Endzones } from '../../../charts/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

@ -0,0 +1,182 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { FC } from 'react';
import {
Direction,
Settings,
DomainRange,
Position,
PartialTheme,
ElementClickListener,
BrushEndListener,
RenderChangeListener,
LegendAction,
LegendColorPicker,
TooltipProps,
TickFormatter,
} from '@elastic/charts';
import { renderEndzoneTooltip } from '../../../charts/public';
import { getThemeService, getUISettings } from '../services';
import { VisConfig } from '../types';
import { fillEmptyValue } from '../utils/get_series_name_fn';
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'
> & {
xDomain?: DomainRange;
adjustedXDomain?: DomainRange;
showLegend: boolean;
onElementClick: ElementClickListener;
onBrushEnd?: BrushEndListener;
onRenderChange: RenderChangeListener;
legendAction?: LegendAction;
legendColorPicker: LegendColorPicker;
legendPosition: Position;
};
export const XYSettings: FC<XYSettingsProps> = ({
markSizeRatio,
rotation,
enableHistogramMode,
tooltip,
isTimeChart,
xAxis,
orderBucketsBySum,
xDomain,
adjustedXDomain,
showLegend,
onElementClick,
onBrushEnd,
onRenderChange,
legendAction,
legendColorPicker,
legendPosition,
}) => {
const themeService = getThemeService();
const theme = themeService.useChartsTheme();
const baseTheme = themeService.useChartsBaseTheme();
const dimmingOpacity = getUISettings().get<number | undefined>('visualization:dimmingOpacity');
const fontSize =
typeof theme.barSeriesStyle?.displayValue?.fontSize === 'number'
? { min: theme.barSeriesStyle?.displayValue?.fontSize }
: theme.barSeriesStyle?.displayValue?.fontSize ?? { min: 8 };
const themeOverrides: PartialTheme = {
markSizeRatio,
sharedStyle: {
unhighlighted: {
opacity: dimmingOpacity,
},
},
barSeriesStyle: {
displayValue: {
fontSize,
alignment: {
horizontal: 'center',
vertical: 'middle',
},
},
},
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) => fillEmptyValue(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 tooltipProps: TooltipProps = tooltip.detailedTooltip
? {
...tooltip,
customTooltip: tooltip.detailedTooltip(headerFormatter),
headerFormatter: undefined,
}
: { ...tooltip, headerFormatter };
return (
<Settings
debugState={window._echDebugStateFlag ?? false}
xDomain={adjustedXDomain}
rotation={rotation}
theme={[themeOverrides, theme]}
baseTheme={baseTheme}
showLegend={showLegend}
legendPosition={legendPosition}
allowBrushingLastHistogramBucket={isTimeChart}
roundHistogramBrushValues={enableHistogramMode && !isTimeChart}
legendColorPicker={legendColorPicker}
onElementClick={onElementClick}
onBrushEnd={onBrushEnd}
onRenderChange={onRenderChange}
legendAction={legendAction}
tooltip={tooltipProps}
orderOrdinalBinsBy={
orderBucketsBySum
? {
direction: Direction.Descending,
}
: undefined
}
/>
);
};

View file

@ -0,0 +1,58 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { FC } from 'react';
import { AnnotationDomainTypes, 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={AnnotationDomainTypes.YDomain}
dataValues={[{ dataValue }]}
style={{
line: {
stroke: color,
strokeWidth: width ?? 2,
opacity: 1,
dash,
},
}}
/>
);
};

Some files were not shown because too many files have changed in this diff Show more