From ff810c8544cfc81637367ba00cba1a652a336e4a Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Fri, 23 Oct 2020 18:58:59 +0300 Subject: [PATCH 01/44] First draft migrate timelion to elastic-charts --- .../public/components/_timelion_vis.scss | 5 + .../components/timelion_vis_component1.tsx | 177 ++++++++++++++++++ .../public/helpers/series_styles.ts | 58 ++++++ .../public/timelion_vis_renderer.tsx | 7 + 4 files changed, 247 insertions(+) create mode 100644 src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx create mode 100644 src/plugins/vis_type_timelion/public/helpers/series_styles.ts diff --git a/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss b/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss index 6729d400523cd8..a87e7781d7e53c 100644 --- a/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss +++ b/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss @@ -59,6 +59,11 @@ font-weight: $euiFontWeightBold; } +.timelionChart { + flex-direction: column; + flex: 1 1 100%; +} + .visEditor--timelion { .visEditorSidebar__timelionOptions { flex: 1 1 auto; diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx new file mode 100644 index 00000000000000..8f99d4a64bbbfa --- /dev/null +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx @@ -0,0 +1,177 @@ +/* + * 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, { useState, useEffect } from 'react'; +import { cloneDeep } from 'lodash'; +import { + AreaSeries, + BarSeries, + LineSeries, + Chart, + ScaleType, + Settings, + Position, + Axis, +} from '@elastic/charts'; + +import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { useKibana } from '../../../kibana_react/public'; + +import { buildOptions, colors } from '../helpers/panel_utils'; + +import { Series, Sheet } from '../helpers/timelion_request_handler'; +import { getBarStyles, getAreaStyles } from '../helpers/series_styles'; +import { TimelionVisDependencies } from '../plugin'; + +import './index.scss'; + +interface TimelionVisComponentProps { + fireEvent: IInterpreterRenderHandlers['event']; + interval: string; + seriesList: Sheet; + renderComplete: IInterpreterRenderHandlers['done']; +} + +function TimelionVisComponent1({ + interval, + seriesList, + renderComplete, + fireEvent, +}: TimelionVisComponentProps) { + const kibana = useKibana(); + const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); + const options = buildOptions( + interval, + kibana.services.timefilter, + kibana.services.uiSettings, + 400 + ); + + useEffect(() => { + const colorsSet: Array<[Series, string]> = []; + const newChart = seriesList.list.map((series: Series, seriesIndex: number) => { + const newSeries = { ...series }; + if (!newSeries.color) { + const colorIndex = seriesIndex % colors.length; + newSeries.color = colors[colorIndex]; + } + colorsSet.push([newSeries, newSeries.color]); + return newSeries; + }); + setChart(newChart); + }, [seriesList.list]); + + const getLegendPosition = function (chartGlobal: any) { + if (chartGlobal && chartGlobal.legend) { + switch (chartGlobal.legend.position) { + case 'ne': + return Position.Right; + case 'nw': + return Position.Left; + case 'se': + return Position.Right; + case 'sw': + return Position.Left; + } + } + return Position.Left; + }; + + return ( + + + + {chart[0]._global && chart[0]._global.yaxes ? ( + chart[0]._global.yaxes.map((data, index) => { + return ( + + ); + }) + ) : ( + + )} + {chart.map((data, index) => { + if (data.bars) { + return ( + + ); + } else if (data.lines) { + return ( + + ); + } else { + return ( + + ); + } + })} + + ); +} + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { TimelionVisComponent1 as default }; diff --git a/src/plugins/vis_type_timelion/public/helpers/series_styles.ts b/src/plugins/vis_type_timelion/public/helpers/series_styles.ts new file mode 100644 index 00000000000000..1681576954094c --- /dev/null +++ b/src/plugins/vis_type_timelion/public/helpers/series_styles.ts @@ -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 { CurveType } from '@elastic/charts'; + +const DEFAULT_COLOR = '#000'; + +export const getAreaStyles = ({ points = {}, lines = {}, color }) => ({ + areaSeriesStyle: { + line: { + stroke: color, + strokeWidth: Number(lines.lineWidth) || 0, + visible: Boolean(lines.lineWidth), + }, + area: { + fill: color, + opacity: lines.fill <= 0 || !lines.fill ? 0 : lines.fill, + visible: Boolean(lines.lineWidth), + }, + point: { + radius: points.radius || 0.5, + stroke: color || DEFAULT_COLOR, + strokeWidth: points.lineWidth || 5, + visible: points.lineWidth > 0 && Boolean(points.show), + }, + }, + curve: lines.steps ? CurveType.CURVE_STEP : CurveType.LINEAR, +}); + +export const getBarStyles = ({ show = true, lineWidth = 0, fill = 1 }, color) => ({ + barSeriesStyle: { + rectBorder: { + stroke: color || DEFAULT_COLOR, + strokeWidth: parseInt(lineWidth, 10), + visible: !!show, + }, + rect: { + fill: color || DEFAULT_COLOR, + opacity: fill, + }, + }, +}); diff --git a/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx b/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx index 04579407105e8a..48fc97325d1286 100644 --- a/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx +++ b/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx @@ -27,6 +27,7 @@ import { TimelionVisDependencies } from './plugin'; import { TimelionRenderValue } from './timelion_vis_fn'; // @ts-ignore const TimelionVisComponent = lazy(() => import('./components/timelion_vis_component')); +const TimelionVisComponent1 = lazy(() => import('./components/timelion_vis_component1')); export const getTimelionVisRenderer: ( deps: TimelionVisDependencies @@ -57,6 +58,12 @@ export const getTimelionVisRenderer: ( renderComplete={handlers.done} fireEvent={handlers.event} /> + , domNode From 6d7f1570477fc721aa7ccd8ca9ce99aaae6fc59d Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Fri, 23 Oct 2020 18:58:59 +0300 Subject: [PATCH 02/44] Some refactoring. Added brush event. --- .../public/components/_timelion_vis.scss | 5 + .../components/timelion_vis_component1.tsx | 209 ++++++++++++++++++ .../public/helpers/series_styles.ts | 79 +++++++ .../public/timelion_vis_renderer.tsx | 7 + 4 files changed, 300 insertions(+) create mode 100644 src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx create mode 100644 src/plugins/vis_type_timelion/public/helpers/series_styles.ts diff --git a/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss b/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss index 6729d400523cd8..a87e7781d7e53c 100644 --- a/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss +++ b/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss @@ -59,6 +59,11 @@ font-weight: $euiFontWeightBold; } +.timelionChart { + flex-direction: column; + flex: 1 1 100%; +} + .visEditor--timelion { .visEditorSidebar__timelionOptions { flex: 1 1 auto; diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx new file mode 100644 index 00000000000000..6246a394e4b8d0 --- /dev/null +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx @@ -0,0 +1,209 @@ +/* + * 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, { useState, useEffect, useCallback } from 'react'; +import { cloneDeep } from 'lodash'; +import { + AreaSeries, + BarSeries, + Chart, + ScaleType, + Settings, + Position, + Axis, + TooltipType, +} from '@elastic/charts'; + +import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { useKibana } from '../../../kibana_react/public'; + +import { buildOptions, colors } from '../helpers/panel_utils'; + +import { Series, Sheet } from '../helpers/timelion_request_handler'; +import { getBarStyles, getAreaStyles } from '../helpers/series_styles'; +import { TimelionVisDependencies } from '../plugin'; + +import './index.scss'; + +interface TimelionVisComponentProps { + fireEvent: IInterpreterRenderHandlers['event']; + interval: string; + seriesList: Sheet; + renderComplete: IInterpreterRenderHandlers['done']; +} + +function TimelionVisComponent1({ + interval, + seriesList, + renderComplete, + fireEvent, +}: TimelionVisComponentProps) { + const kibana = useKibana(); + const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); + const options = buildOptions( + interval, + kibana.services.timefilter, + kibana.services.uiSettings, + 400 + ); + + useEffect(() => { + const newChart = seriesList.list.map((series: Series, seriesIndex: number) => { + const newSeries = { ...series }; + if (!newSeries.color) { + const colorIndex = seriesIndex % colors.length; + newSeries.color = colors[colorIndex]; + } + return newSeries; + }); + setChart(newChart); + }, [seriesList.list]); + + const getLegendPosition = function (chartGlobal: any) { + if (chartGlobal && chartGlobal.legend) { + switch (chartGlobal.legend.position) { + case 'ne': + return Position.Right; + case 'nw': + return Position.Left; + case 'se': + return Position.Right; + case 'sw': + return Position.Left; + } + } + return Position.Left; + }; + + const brushEndListener = useCallback( + ({ x }) => { + fireEvent({ + name: 'applyFilter', + data: { + timeFieldName: '*', + filters: [ + { + range: { + '*': { + gte: x[0], + lte: x[1], + }, + }, + }, + ], + }, + }); + }, + [fireEvent] + ); + + const onRenderChange = function (data) { + if (!data[0]) { + renderComplete(); + } + }; + + return ( + + + + {chart[0]._global && chart[0]._global.yaxes ? ( + chart[0]._global.yaxes.map((data, index) => { + return ( + + ); + }) + ) : ( + + )} + {chart.map((data, index) => { + if (data.bars) { + return ( + + ); + } else { + return ( + + ); + } + })} + + ); +} + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { TimelionVisComponent1 as default }; diff --git a/src/plugins/vis_type_timelion/public/helpers/series_styles.ts b/src/plugins/vis_type_timelion/public/helpers/series_styles.ts new file mode 100644 index 00000000000000..e2a777c2b5cc4c --- /dev/null +++ b/src/plugins/vis_type_timelion/public/helpers/series_styles.ts @@ -0,0 +1,79 @@ +/* + * 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 { CurveType } from '@elastic/charts'; + +const DEFAULT_COLOR = '#000'; + +export const getAreaStyles = ({ + points = {}, + lines = {}, + color, +}: { + points: { + fill?: number; + fillColor?: string; + radius?: number; + show?: boolean; + lineWidth?: number; + }; + lines: { + lineWidth?: number; + fill?: number; + steps?: number; + show?: boolean; + }; + color?: string; +}) => ({ + areaSeriesStyle: { + line: { + stroke: color, + strokeWidth: Number(lines.lineWidth) || 3, + visible: lines.show === undefined ? true : lines.show, + }, + area: { + fill: color, + opacity: !lines.fill || lines.fill <= 0 ? 0 : lines.fill, + visible: lines.show === undefined ? true : lines.show, + }, + point: { + fill: points.fillColor, + opacity: !points.fill || points.fill < 0 ? 1 : points.fill, + radius: points.radius || 3, + stroke: color || DEFAULT_COLOR, + strokeWidth: points.lineWidth || 2, + visible: Boolean(points.show), + }, + }, + curve: lines.steps ? CurveType.CURVE_STEP : CurveType.LINEAR, +}); + +export const getBarStyles = ({ show = true, lineWidth = '0', fill = 1 }, color?: string) => ({ + barSeriesStyle: { + rectBorder: { + stroke: color || DEFAULT_COLOR, + strokeWidth: parseInt(lineWidth, 10), + visible: !!show, + }, + rect: { + fill: color || DEFAULT_COLOR, + opacity: fill, + }, + }, +}); diff --git a/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx b/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx index 04579407105e8a..48fc97325d1286 100644 --- a/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx +++ b/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx @@ -27,6 +27,7 @@ import { TimelionVisDependencies } from './plugin'; import { TimelionRenderValue } from './timelion_vis_fn'; // @ts-ignore const TimelionVisComponent = lazy(() => import('./components/timelion_vis_component')); +const TimelionVisComponent1 = lazy(() => import('./components/timelion_vis_component1')); export const getTimelionVisRenderer: ( deps: TimelionVisDependencies @@ -57,6 +58,12 @@ export const getTimelionVisRenderer: ( renderComplete={handlers.done} fireEvent={handlers.event} /> + , domNode From 999c2d77afd616aafb54f331325dacf75027a81e Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Tue, 27 Oct 2020 18:07:00 +0300 Subject: [PATCH 03/44] Added title. Some refactoring --- .../public/components/_timelion_vis.scss | 19 +- .../components/timelion_vis_component1.tsx | 170 +++++++++--------- 2 files changed, 99 insertions(+), 90 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss b/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss index a87e7781d7e53c..f271b2e568a80d 100644 --- a/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss +++ b/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss @@ -6,14 +6,6 @@ // Custom Jquery FLOT / schema selectors // Cannot change at the moment - - .chart-top-title { - @include euiFontSizeXS; - flex: 0; - text-align: center; - font-weight: $euiFontWeightBold; - } - .chart-canvas { min-width: 100%; flex: 1; @@ -60,8 +52,17 @@ } .timelionChart { + height: 100%; + width: 100%; + display: flex; flex-direction: column; - flex: 1 1 100%; +} + +.chart-top-title { + @include euiFontSizeXS; + flex: 0; + text-align: center; + font-weight: $euiFontWeightBold; } .visEditor--timelion { diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx index 6246a394e4b8d0..667febfe570eaa 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx @@ -17,8 +17,8 @@ * under the License. */ -import React, { useState, useEffect, useCallback } from 'react'; -import { cloneDeep } from 'lodash'; +import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import { compact, cloneDeep, last, map } from 'lodash'; import { AreaSeries, BarSeries, @@ -75,6 +75,7 @@ function TimelionVisComponent1({ setChart(newChart); }, [seriesList.list]); + // temp solution const getLegendPosition = function (chartGlobal: any) { if (chartGlobal && chartGlobal.legend) { switch (chartGlobal.legend.position) { @@ -113,94 +114,101 @@ function TimelionVisComponent1({ [fireEvent] ); - const onRenderChange = function (data) { - if (!data[0]) { + const onRenderChange = function (isRendered: boolean) { + if (!isRendered) { renderComplete(); } }; + const title: string = useMemo(() => last(compact(map(seriesList.list, '_title'))) || '', [ + seriesList.list, + ]); + return ( - - +
{title}
+ + - - {chart[0]._global && chart[0]._global.yaxes ? ( - chart[0]._global.yaxes.map((data, index) => { - return ( - - ); - }) - ) : ( - - )} - {chart.map((data, index) => { - if (data.bars) { - return ( - - ); - } else { - return ( - - ); - } - })} - + + {chart[0]._global && chart[0]._global.yaxes ? ( + chart[0]._global.yaxes.map((axis: any, index: number) => { + return ( + + ); + }) + ) : ( + + )} + {chart.map((data, index) => { + if (data.bars) { + return ( + + ); + } else { + return ( + + ); + } + })} +
+ ); } From f43a96534c3379f0be5ffc8865963a17b5e34aae Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Thu, 29 Oct 2020 20:00:20 +0300 Subject: [PATCH 04/44] Fixed some type problems. Added logic for yaxes function --- .../components/timelion_vis_component1.tsx | 36 +++++++++++++++++-- .../public/helpers/panel_utils.ts | 6 ++++ .../public/helpers/series_styles.ts | 4 +-- .../helpers/timelion_request_handler.ts | 5 ++- 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx index 667febfe570eaa..68dbeca1b7dd86 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx @@ -33,7 +33,8 @@ import { import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; import { useKibana } from '../../../kibana_react/public'; -import { buildOptions, colors } from '../helpers/panel_utils'; +import { buildOptions, colors, Axis as IAxis } from '../helpers/panel_utils'; +import { tickFormatters } from '../helpers/tick_formatters'; import { Series, Sheet } from '../helpers/timelion_request_handler'; import { getBarStyles, getAreaStyles } from '../helpers/series_styles'; @@ -70,8 +71,35 @@ function TimelionVisComponent1({ const colorIndex = seriesIndex % colors.length; newSeries.color = colors[colorIndex]; } + + if (newSeries._global && newSeries._global.yaxes) { + newSeries._global.yaxes.forEach((yaxis: IAxis) => { + if (yaxis && yaxis.units) { + const formatters = tickFormatters(); + yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; + } + + if (yaxis.max) { + yaxis.domain = { + max: yaxis.max, + }; + } + + if (yaxis.min) { + if (yaxis.domain) { + yaxis.domain.min = yaxis.min; + } else { + yaxis.domain = { + min: yaxis.min, + }; + } + } + }); + } + return newSeries; }); + setChart(newChart); }, [seriesList.list]); @@ -154,17 +182,19 @@ function TimelionVisComponent1({ tickFormat={options.xaxis.tickFormatter} /> {chart[0]._global && chart[0]._global.yaxes ? ( - chart[0]._global.yaxes.map((axis: any, index: number) => { + chart[0]._global.yaxes.map((axis: IAxis, index: number) => { return ( ); }) diff --git a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts index 860b4e9f2dbde1..77941ecdecede6 100644 --- a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -43,6 +43,12 @@ export interface Axis { tickFormatter: ((val: number) => string) | ((val: number, axis: Axis) => string); tickGenerator?(axis: Axis): number[]; units?: { type: string }; + domain?: { + min?: number; + max?: number; + }; + position: 'top' | 'bottom' | 'left' | 'right' | undefined; + axisLabel: string; } interface TimeRangeBounds { diff --git a/src/plugins/vis_type_timelion/public/helpers/series_styles.ts b/src/plugins/vis_type_timelion/public/helpers/series_styles.ts index e2a777c2b5cc4c..d223ce686a5108 100644 --- a/src/plugins/vis_type_timelion/public/helpers/series_styles.ts +++ b/src/plugins/vis_type_timelion/public/helpers/series_styles.ts @@ -26,14 +26,14 @@ export const getAreaStyles = ({ lines = {}, color, }: { - points: { + points?: { fill?: number; fillColor?: string; radius?: number; show?: boolean; lineWidth?: number; }; - lines: { + lines?: { lineWidth?: number; fill?: number; steps?: number; diff --git a/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts b/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts index 975d12a152d89d..e742d27330f5ba 100644 --- a/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts +++ b/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts @@ -33,7 +33,7 @@ interface Stats { } export interface Series { - _global?: boolean; + _global?: Record; _hide?: boolean; _id?: number; _title?: string; @@ -44,6 +44,9 @@ export interface Series { split: string; stack?: boolean; type: string; + bars?: Record; + lines?: Record; + points?: Record; } export interface Sheet { From af607922cced6047f9b48e508fe76cd8fc5d4168 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Fri, 30 Oct 2020 16:38:26 +0300 Subject: [PATCH 05/44] Fixed some types, added missing functionality for yaxes --- .../components/timelion_vis_component1.tsx | 72 ++++++++++--------- .../public/helpers/panel_utils.ts | 2 +- .../public/helpers/tick_formatters.ts | 8 +-- .../public/timelion_vis_renderer.tsx | 4 +- 4 files changed, 46 insertions(+), 40 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx index 68dbeca1b7dd86..729cfdaf2cee0f 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx @@ -28,6 +28,7 @@ import { Position, Axis, TooltipType, + YDomainRange, } from '@elastic/charts'; import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; @@ -57,12 +58,34 @@ function TimelionVisComponent1({ }: TimelionVisComponentProps) { const kibana = useKibana(); const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); - const options = buildOptions( - interval, - kibana.services.timefilter, - kibana.services.uiSettings, - 400 - ); + const options = buildOptions(interval, kibana.services.timefilter, kibana.services.uiSettings); + + const updateYAxes = function (yaxes: IAxis[]) { + yaxes.forEach((yaxis: IAxis) => { + if (yaxis.units) { + const formatters = tickFormatters(yaxis); + yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; + } else if (yaxis.tickDecimals) { + yaxis.tickFormatter = (val: number) => val.toFixed(yaxis.tickDecimals); + } + + if (yaxis.max) { + yaxis.domain = { + max: yaxis.max, + }; + } + + if (yaxis.min) { + if (yaxis.domain) { + yaxis.domain.min = yaxis.min; + } else { + yaxis.domain = { + min: yaxis.min, + }; + } + } + }); + }; useEffect(() => { const newChart = seriesList.list.map((series: Series, seriesIndex: number) => { @@ -73,28 +96,7 @@ function TimelionVisComponent1({ } if (newSeries._global && newSeries._global.yaxes) { - newSeries._global.yaxes.forEach((yaxis: IAxis) => { - if (yaxis && yaxis.units) { - const formatters = tickFormatters(); - yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; - } - - if (yaxis.max) { - yaxis.domain = { - max: yaxis.max, - }; - } - - if (yaxis.min) { - if (yaxis.domain) { - yaxis.domain.min = yaxis.min; - } else { - yaxis.domain = { - min: yaxis.min, - }; - } - } - }); + updateYAxes(newSeries._global.yaxes); } return newSeries; @@ -104,7 +106,8 @@ function TimelionVisComponent1({ }, [seriesList.list]); // temp solution - const getLegendPosition = function (chartGlobal: any) { + const getLegendPosition = useCallback(() => { + const chartGlobal = chart[0]._global; if (chartGlobal && chartGlobal.legend) { switch (chartGlobal.legend.position) { case 'ne': @@ -118,7 +121,7 @@ function TimelionVisComponent1({ } } return Position.Left; - }; + }, [chart]); const brushEndListener = useCallback( ({ x }) => { @@ -159,13 +162,16 @@ function TimelionVisComponent1({ ); }) diff --git a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts index 77941ecdecede6..fbcd0155f9f320 100644 --- a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -185,7 +185,7 @@ function buildOptions( return wrapperSpan.outerHTML; }, }, - } as jquery.flot.plotOptions & { yaxes?: Axis[] }; + }; return options; } diff --git a/src/plugins/vis_type_timelion/public/helpers/tick_formatters.ts b/src/plugins/vis_type_timelion/public/helpers/tick_formatters.ts index c80f9c3ed5f4b1..bed7761c6f7bcf 100644 --- a/src/plugins/vis_type_timelion/public/helpers/tick_formatters.ts +++ b/src/plugins/vis_type_timelion/public/helpers/tick_formatters.ts @@ -56,19 +56,19 @@ function unitFormatter(divisor: number, units: string[]) { }; } -export function tickFormatters() { +export function tickFormatters(axis: Axis) { return { bits: unitFormatter(1000, ['b', 'kb', 'mb', 'gb', 'tb', 'pb']), 'bits/s': unitFormatter(1000, ['b/s', 'kb/s', 'mb/s', 'gb/s', 'tb/s', 'pb/s']), bytes: unitFormatter(1024, ['B', 'KB', 'MB', 'GB', 'TB', 'PB']), 'bytes/s': unitFormatter(1024, ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s']), - currency(val: number, axis: Axis) { + currency(val: number) { return val.toLocaleString('en', { style: 'currency', currency: (axis && axis.options && axis.options.units.prefix) || 'USD', }); }, - percent(val: number, axis: Axis) { + percent(val: number) { let precision = get(axis, 'tickDecimals', 0) - get(axis, 'options.units.tickDecimalsShift', 0); // toFixed only accepts values between 0 and 20 @@ -80,7 +80,7 @@ export function tickFormatters() { return (val * 100).toFixed(precision) + '%'; }, - custom(val: number, axis: Axis) { + custom(val: number) { const formattedVal = baseTickFormatter(val, axis); const prefix = axis && axis.options && axis.options.units.prefix; const suffix = axis && axis.options && axis.options.units.suffix; diff --git a/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx b/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx index 48fc97325d1286..f6e9e90deb69a0 100644 --- a/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx +++ b/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx @@ -52,12 +52,12 @@ export const getTimelionVisRenderer: ( render( - + /> */} Date: Mon, 2 Nov 2020 18:39:12 +0300 Subject: [PATCH 06/44] Fixed some types, added missing functionality for stack property --- src/plugins/timelion/public/panels/timechart/schema.ts | 2 +- .../public/components/timelion_vis_component.tsx | 2 +- .../public/components/timelion_vis_component1.tsx | 6 ++++++ src/plugins/vis_type_timelion/public/helpers/panel_utils.ts | 3 ++- .../public/helpers/tick_formatters.test.ts | 3 ++- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/plugins/timelion/public/panels/timechart/schema.ts b/src/plugins/timelion/public/panels/timechart/schema.ts index d874f0d32c9d4a..f1426fa4778494 100644 --- a/src/plugins/timelion/public/panels/timechart/schema.ts +++ b/src/plugins/timelion/public/panels/timechart/schema.ts @@ -48,7 +48,6 @@ export function timechartFn(dependencies: TimelionVisualizationDependencies) { help: 'Draw a timeseries chart', render($scope: any, $elem: any) { const template = '
'; - const formatters = tickFormatters() as any; const getxAxisFormatter = xaxisFormatterProvider(uiSettings); const generateTicks = generateTicksProvider(); @@ -360,6 +359,7 @@ export function timechartFn(dependencies: TimelionVisualizationDependencies) { if (options.yaxes) { options.yaxes.forEach((yaxis: any) => { if (yaxis && yaxis.units) { + const formatters = tickFormatters(yaxis) as any; yaxis.tickFormatter = formatters[yaxis.units.type]; const byteModes = ['bytes', 'bytes/s']; if (byteModes.includes(yaxis.units.type)) { diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index 953ec5e819f448..7a1c05c032b268 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -206,7 +206,7 @@ function TimelionVisComponent({ if (options.yaxes) { options.yaxes.forEach((yaxis: Axis) => { if (yaxis && yaxis.units) { - const formatters = tickFormatters(); + const formatters = tickFormatters(yaxis); yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; const byteModes = ['bytes', 'bytes/s']; if (byteModes.includes(yaxis.units.type)) { diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx index 729cfdaf2cee0f..2f1b929f71eea4 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx @@ -225,6 +225,9 @@ function TimelionVisComponent1({ xAccessor={0} yAccessors={[1]} data={data.data} + sortIndex={index} + stackAccessors={data.stack ? [0] : undefined} + color={data.color} {...getBarStyles(data.bars, data.color)} /> ); @@ -238,6 +241,9 @@ function TimelionVisComponent1({ xAccessor={0} yAccessors={[1]} data={data.data} + sortIndex={index} + color={data.color} + stackAccessors={data.stack ? [0] : undefined} {...getAreaStyles(data)} /> ); diff --git a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts index fbcd0155f9f320..955c52bc1dc7bd 100644 --- a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -40,7 +40,7 @@ export interface Axis { tickLength: number; timezone: string; tickDecimals?: number; - tickFormatter: ((val: number) => string) | ((val: number, axis: Axis) => string); + tickFormatter: ((val: number) => string) | ((val: number, axis: any) => string); tickGenerator?(axis: Axis): number[]; units?: { type: string }; domain?: { @@ -185,6 +185,7 @@ function buildOptions( return wrapperSpan.outerHTML; }, }, + yaxes: [], }; return options; diff --git a/src/plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts b/src/plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts index cbcb869dc25780..70a44fe12a5591 100644 --- a/src/plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts +++ b/src/plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts @@ -18,12 +18,13 @@ */ import { tickFormatters } from './tick_formatters'; +import { Axis } from './panel_utils'; describe('Tick Formatters', function () { let formatters: any; beforeEach(function () { - formatters = tickFormatters(); + formatters = tickFormatters({} as Axis); }); describe('Bits mode', function () { From 5cd3a482c306b172d7aa51aa87dadc309026218d Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Tue, 3 Nov 2020 11:09:28 +0300 Subject: [PATCH 07/44] Fixed unit test --- .../public/helpers/tick_formatters.test.ts | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts b/src/plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts index 70a44fe12a5591..7d4e40a9afc3bf 100644 --- a/src/plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts +++ b/src/plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts @@ -143,7 +143,9 @@ describe('Tick Formatters', function () { units: {}, }, }; - expect(currencyFormatter(10.2, axis)).toEqual('$10.20'); + formatters = tickFormatters(axis as Axis); + currencyFormatter = formatters.currency; + expect(currencyFormatter(10.2)).toEqual('$10.20'); }); it('accepts currency in ISO 4217', function () { @@ -154,8 +156,9 @@ describe('Tick Formatters', function () { }, }, }; - - expect(currencyFormatter(10.2, axis)).toEqual('CN¥10.20'); + formatters = tickFormatters(axis as Axis); + currencyFormatter = formatters.currency; + expect(currencyFormatter(10.2)).toEqual('CN¥10.20'); }); }); @@ -175,7 +178,9 @@ describe('Tick Formatters', function () { units: {}, }, }; - expect(percentFormatter(0.1234, axis)).toEqual('12%'); + formatters = tickFormatters(axis as Axis); + percentFormatter = formatters.percent; + expect(percentFormatter(0.1234)).toEqual('12%'); }); it('formats with % with decimal precision', function () { @@ -188,8 +193,10 @@ describe('Tick Formatters', function () { tickDecimalsShift: tickDecimalShift, }, }, - }; - expect(percentFormatter(0.12345, axis)).toEqual('12.345%'); + } as unknown; + formatters = tickFormatters(axis as Axis); + percentFormatter = formatters.percent; + expect(percentFormatter(0.12345)).toEqual('12.345%'); }); }); @@ -213,8 +220,9 @@ describe('Tick Formatters', function () { }, tickDecimals: 1, }; - - expect(customFormatter(10.2, axis)).toEqual('prefix10.2suffix'); + formatters = tickFormatters(axis as Axis); + customFormatter = formatters.custom; + expect(customFormatter(10.2)).toEqual('prefix10.2suffix'); }); it('correctly renders small values', function () { @@ -227,8 +235,9 @@ describe('Tick Formatters', function () { }, tickDecimals: 3, }; - - expect(customFormatter(0.00499999999999999, axis)).toEqual('prefix0.005suffix'); + formatters = tickFormatters(axis as Axis); + customFormatter = formatters.custom; + expect(customFormatter(0.00499999999999999)).toEqual('prefix0.005suffix'); }); }); }); From 4b3a2948adf6f0a448958a24038ecec3704c6c8a Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Tue, 3 Nov 2020 15:02:01 +0300 Subject: [PATCH 08/44] Removed unneeded code --- .../public/components/_timelion_vis.scss | 47 -- .../components/timelion_vis_component.tsx | 478 ++++++------------ .../components/timelion_vis_component1.tsx | 259 ---------- .../public/helpers/panel_utils.ts | 88 +--- .../public/timelion_vis_renderer.tsx | 11 +- 5 files changed, 166 insertions(+), 717 deletions(-) delete mode 100644 src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx diff --git a/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss b/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss index f271b2e568a80d..29a6d5ff1e0545 100644 --- a/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss +++ b/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss @@ -1,50 +1,3 @@ -.timChart { - height: 100%; - width: 100%; - display: flex; - flex-direction: column; - - // Custom Jquery FLOT / schema selectors - // Cannot change at the moment - .chart-canvas { - min-width: 100%; - flex: 1; - overflow: hidden; - } - - .legendLabel { - white-space: nowrap; - text-overflow: ellipsis; - overflow-x: hidden; - line-height: normal; - } - - .legendColorBox { - vertical-align: middle; - } - - .ngLegendValue { - color: $euiTextColor; - cursor: pointer; - - &:focus, - &:hover { - text-decoration: underline; - } - } - - .ngLegendValueNumber { - margin-left: $euiSizeXS; - margin-right: $euiSizeXS; - font-weight: $euiFontWeightBold; - } - - .flot-tick-label { - font-size: $euiFontSizeXS; - color: $euiColorDarkShade; - } -} - .timChart__legendCaption { color: $euiTextColor; white-space: nowrap; diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index 7a1c05c032b268..2f1b929f71eea4 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -17,36 +17,32 @@ * under the License. */ -import React, { useState, useEffect, useMemo, useCallback } from 'react'; -import $ from 'jquery'; -import moment from 'moment-timezone'; -import { debounce, compact, get, each, cloneDeep, last, map } from 'lodash'; -import { useResizeObserver } from '@elastic/eui'; +import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import { compact, cloneDeep, last, map } from 'lodash'; +import { + AreaSeries, + BarSeries, + Chart, + ScaleType, + Settings, + Position, + Axis, + TooltipType, + YDomainRange, +} from '@elastic/charts'; import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; import { useKibana } from '../../../kibana_react/public'; -import { DEFAULT_TIME_FORMAT } from '../../common/lib'; -import { - buildSeriesData, - buildOptions, - SERIES_ID_ATTR, - colors, - Axis, -} from '../helpers/panel_utils'; +import { buildOptions, colors, Axis as IAxis } from '../helpers/panel_utils'; +import { tickFormatters } from '../helpers/tick_formatters'; import { Series, Sheet } from '../helpers/timelion_request_handler'; -import { tickFormatters } from '../helpers/tick_formatters'; -import { generateTicksProvider } from '../helpers/tick_generator'; +import { getBarStyles, getAreaStyles } from '../helpers/series_styles'; import { TimelionVisDependencies } from '../plugin'; import './index.scss'; -interface CrosshairPlot extends jquery.flot.plot { - setCrosshair: (pos: Position) => void; - clearCrosshair: () => void; -} - interface TimelionVisComponentProps { fireEvent: IInterpreterRenderHandlers['event']; interval: string; @@ -54,30 +50,7 @@ interface TimelionVisComponentProps { renderComplete: IInterpreterRenderHandlers['done']; } -interface Position { - x: number; - x1: number; - y: number; - y1: number; - pageX: number; - pageY: number; -} - -interface Range { - to: number; - from: number; -} - -interface Ranges { - xaxis: Range; - yaxis: Range; -} - -const DEBOUNCE_DELAY = 50; -// ensure legend is the same height with or without a caption so legend items do not move around -const emptyCaption = '
'; - -function TimelionVisComponent({ +function TimelionVisComponent1({ interval, seriesList, renderComplete, @@ -85,279 +58,73 @@ function TimelionVisComponent({ }: TimelionVisComponentProps) { const kibana = useKibana(); const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); - const [canvasElem, setCanvasElem] = useState(); - const [chartElem, setChartElem] = useState(null); - - const [originalColorMap, setOriginalColorMap] = useState(() => new Map()); - - const [highlightedSeries, setHighlightedSeries] = useState(null); - const [focusedSeries, setFocusedSeries] = useState(); - const [plot, setPlot] = useState(); - - // Used to toggle the series, and for displaying values on hover - const [legendValueNumbers, setLegendValueNumbers] = useState>(); - const [legendCaption, setLegendCaption] = useState>(); - - const canvasRef = useCallback((node: HTMLDivElement | null) => { - if (node !== null) { - setCanvasElem(node); - } - }, []); - - const elementRef = useCallback((node: HTMLDivElement | null) => { - if (node !== null) { - setChartElem(node); - } - }, []); - - useEffect( - () => () => { - if (chartElem) { - $(chartElem).off('plotselected').off('plothover').off('mouseleave'); - } - }, - [chartElem] - ); - - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - const highlightSeries = useCallback( - debounce(({ currentTarget }: JQuery.TriggeredEvent) => { - const id = Number(currentTarget.getAttribute(SERIES_ID_ATTR)); - if (highlightedSeries === id) { - return; + const options = buildOptions(interval, kibana.services.timefilter, kibana.services.uiSettings); + + const updateYAxes = function (yaxes: IAxis[]) { + yaxes.forEach((yaxis: IAxis) => { + if (yaxis.units) { + const formatters = tickFormatters(yaxis); + yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; + } else if (yaxis.tickDecimals) { + yaxis.tickFormatter = (val: number) => val.toFixed(yaxis.tickDecimals); } - setHighlightedSeries(id); - setChart((chartState) => - chartState.map((series: Series, seriesIndex: number) => { - series.color = - seriesIndex === id - ? originalColorMap.get(series) // color it like it was - : 'rgba(128,128,128,0.1)'; // mark as grey - - return series; - }) - ); - }, DEBOUNCE_DELAY), - [originalColorMap, highlightedSeries] - ); - - const focusSeries = useCallback( - (event: JQuery.TriggeredEvent) => { - const id = Number(event.currentTarget.getAttribute(SERIES_ID_ATTR)); - setFocusedSeries(id); - highlightSeries(event); - }, - [highlightSeries] - ); - - const toggleSeries = useCallback(({ currentTarget }: JQuery.TriggeredEvent) => { - const id = Number(currentTarget.getAttribute(SERIES_ID_ATTR)); - - setChart((chartState) => - chartState.map((series: Series, seriesIndex: number) => { - if (seriesIndex === id) { - series._hide = !series._hide; - } - return series; - }) - ); - }, []); - - const updateCaption = useCallback( - (plotData: any) => { - if (canvasElem && get(plotData, '[0]._global.legend.showTime', true)) { - const caption = $(''); - caption.html(emptyCaption); - setLegendCaption(caption); - - const canvasNode = $(canvasElem); - canvasNode.find('div.legend table').append(caption); - setLegendValueNumbers(canvasNode.find('.ngLegendValueNumber')); - - const legend = $(canvasElem).find('.ngLegendValue'); - if (legend) { - legend.click(toggleSeries); - legend.focus(focusSeries); - legend.mouseover(highlightSeries); - } - - // legend has been re-created. Apply focus on legend element when previously set - if (focusedSeries || focusedSeries === 0) { - canvasNode.find('div.legend table .legendLabel>span').get(focusedSeries).focus(); - } + if (yaxis.max) { + yaxis.domain = { + max: yaxis.max, + }; } - }, - [focusedSeries, canvasElem, toggleSeries, focusSeries, highlightSeries] - ); - - const updatePlot = useCallback( - (chartValue: Series[], grid?: boolean) => { - if (canvasElem && canvasElem.clientWidth > 0 && canvasElem.clientHeight > 0) { - const options = buildOptions( - interval, - kibana.services.timefilter, - kibana.services.uiSettings, - chartElem?.clientWidth, - grid - ); - const updatedSeries = buildSeriesData(chartValue, options); - if (options.yaxes) { - options.yaxes.forEach((yaxis: Axis) => { - if (yaxis && yaxis.units) { - const formatters = tickFormatters(yaxis); - yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; - const byteModes = ['bytes', 'bytes/s']; - if (byteModes.includes(yaxis.units.type)) { - yaxis.tickGenerator = generateTicksProvider(); - } - } - }); + if (yaxis.min) { + if (yaxis.domain) { + yaxis.domain.min = yaxis.min; + } else { + yaxis.domain = { + min: yaxis.min, + }; } - - const newPlot = $.plot($(canvasElem), updatedSeries, options); - setPlot(newPlot); - renderComplete(); - - updateCaption(newPlot.getData()); } - }, - [canvasElem, chartElem?.clientWidth, renderComplete, kibana.services, interval, updateCaption] - ); - - const dimensions = useResizeObserver(chartElem); - - useEffect(() => { - updatePlot(chart, seriesList.render && seriesList.render.grid); - }, [chart, updatePlot, seriesList.render, dimensions]); + }); + }; useEffect(() => { - const colorsSet: Array<[Series, string]> = []; const newChart = seriesList.list.map((series: Series, seriesIndex: number) => { const newSeries = { ...series }; if (!newSeries.color) { const colorIndex = seriesIndex % colors.length; newSeries.color = colors[colorIndex]; } - colorsSet.push([newSeries, newSeries.color]); - return newSeries; - }); - setChart(newChart); - setOriginalColorMap(new Map(colorsSet)); - }, [seriesList.list]); - - const unhighlightSeries = useCallback(() => { - if (highlightedSeries === null) { - return; - } - - setHighlightedSeries(null); - setFocusedSeries(null); - - setChart((chartState) => - chartState.map((series: Series) => { - series.color = originalColorMap.get(series); // reset the colors - return series; - }) - ); - }, [originalColorMap, highlightedSeries]); - - // Shamelessly borrowed from the flotCrosshairs example - const setLegendNumbers = useCallback( - (pos: Position) => { - unhighlightSeries(); - - const axes = plot!.getAxes(); - if (pos.x < axes.xaxis.min! || pos.x > axes.xaxis.max!) { - return; - } - const dataset = plot!.getData(); - if (legendCaption) { - legendCaption.text( - moment(pos.x).format(get(dataset, '[0]._global.legend.timeFormat', DEFAULT_TIME_FORMAT)) - ); - } - for (let i = 0; i < dataset.length; ++i) { - const series = dataset[i]; - const useNearestPoint = series.lines!.show && !series.lines!.steps; - const precision = get(series, '_meta.precision', 2); - - // We're setting this flag on top on the series object belonging to the flot library, so we're simply casting here. - if ((series as { _hide?: boolean })._hide) { - continue; - } - - const currentPoint = series.data.find((point: [number, number], index: number) => { - if (index + 1 === series.data.length) { - return true; - } - if (useNearestPoint) { - return pos.x - point[0] < series.data[index + 1][0] - pos.x; - } else { - return pos.x < series.data[index + 1][0]; - } - }); - - const y = currentPoint[1]; - - if (legendValueNumbers) { - if (y == null) { - legendValueNumbers.eq(i).empty(); - } else { - let label = y.toFixed(precision); - const formatter = ((series.yaxis as unknown) as Axis).tickFormatter; - if (formatter) { - label = formatter(Number(label), (series.yaxis as unknown) as Axis); - } - legendValueNumbers.eq(i).text(`(${label})`); - } - } + if (newSeries._global && newSeries._global.yaxes) { + updateYAxes(newSeries._global.yaxes); } - }, - [plot, legendValueNumbers, unhighlightSeries, legendCaption] - ); - - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - const debouncedSetLegendNumbers = useCallback( - debounce(setLegendNumbers, DEBOUNCE_DELAY, { - maxWait: DEBOUNCE_DELAY, - leading: true, - trailing: false, - }), - [setLegendNumbers] - ); - const clearLegendNumbers = useCallback(() => { - if (legendCaption) { - legendCaption.html(emptyCaption); - } - each(legendValueNumbers!, (num: Node) => { - $(num).empty(); + return newSeries; }); - }, [legendCaption, legendValueNumbers]); - const plotHoverHandler = useCallback( - (event: JQuery.TriggeredEvent, pos: Position) => { - if (!plot) { - return; + setChart(newChart); + }, [seriesList.list]); + + // temp solution + const getLegendPosition = useCallback(() => { + const chartGlobal = chart[0]._global; + if (chartGlobal && chartGlobal.legend) { + switch (chartGlobal.legend.position) { + case 'ne': + return Position.Right; + case 'nw': + return Position.Left; + case 'se': + return Position.Right; + case 'sw': + return Position.Left; } - (plot as CrosshairPlot).setCrosshair(pos); - debouncedSetLegendNumbers(pos); - }, - [plot, debouncedSetLegendNumbers] - ); - const mouseLeaveHandler = useCallback(() => { - if (!plot) { - return; } - (plot as CrosshairPlot).clearCrosshair(); - clearLegendNumbers(); - }, [plot, clearLegendNumbers]); + return Position.Left; + }, [chart]); - const plotSelectedHandler = useCallback( - (event: JQuery.TriggeredEvent, ranges: Ranges) => { + const brushEndListener = useCallback( + ({ x }) => { fireEvent({ name: 'applyFilter', data: { @@ -366,8 +133,8 @@ function TimelionVisComponent({ { range: { '*': { - gte: ranges.xaxis.from, - lte: ranges.xaxis.to, + gte: x[0], + lte: x[1], }, }, }, @@ -378,36 +145,115 @@ function TimelionVisComponent({ [fireEvent] ); - useEffect(() => { - if (chartElem) { - $(chartElem).off('plotselected').on('plotselected', plotSelectedHandler); + const onRenderChange = function (isRendered: boolean) { + if (!isRendered) { + renderComplete(); } - }, [chartElem, plotSelectedHandler]); - - useEffect(() => { - if (chartElem) { - $(chartElem).off('mouseleave').on('mouseleave', mouseLeaveHandler); - } - }, [chartElem, mouseLeaveHandler]); - - useEffect(() => { - if (chartElem) { - $(chartElem).off('plothover').on('plothover', plotHoverHandler); - } - }, [chartElem, plotHoverHandler]); + }; const title: string = useMemo(() => last(compact(map(seriesList.list, '_title'))) || '', [ seriesList.list, ]); return ( -
+
{title}
-
+ + + + {chart[0]._global && chart[0]._global.yaxes ? ( + chart[0]._global.yaxes.map((axis: IAxis, index: number) => { + return ( + + ); + }) + ) : ( + + )} + {chart.map((data, index) => { + if (data.bars) { + return ( + + ); + } else { + return ( + + ); + } + })} +
); } // default export required for React.Lazy // eslint-disable-next-line import/no-default-export -export { TimelionVisComponent as default }; +export { TimelionVisComponent1 as default }; diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx deleted file mode 100644 index 2f1b929f71eea4..00000000000000 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component1.tsx +++ /dev/null @@ -1,259 +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, { useState, useEffect, useCallback, useMemo } from 'react'; -import { compact, cloneDeep, last, map } from 'lodash'; -import { - AreaSeries, - BarSeries, - Chart, - ScaleType, - Settings, - Position, - Axis, - TooltipType, - YDomainRange, -} from '@elastic/charts'; - -import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; -import { useKibana } from '../../../kibana_react/public'; - -import { buildOptions, colors, Axis as IAxis } from '../helpers/panel_utils'; -import { tickFormatters } from '../helpers/tick_formatters'; - -import { Series, Sheet } from '../helpers/timelion_request_handler'; -import { getBarStyles, getAreaStyles } from '../helpers/series_styles'; -import { TimelionVisDependencies } from '../plugin'; - -import './index.scss'; - -interface TimelionVisComponentProps { - fireEvent: IInterpreterRenderHandlers['event']; - interval: string; - seriesList: Sheet; - renderComplete: IInterpreterRenderHandlers['done']; -} - -function TimelionVisComponent1({ - interval, - seriesList, - renderComplete, - fireEvent, -}: TimelionVisComponentProps) { - const kibana = useKibana(); - const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); - const options = buildOptions(interval, kibana.services.timefilter, kibana.services.uiSettings); - - const updateYAxes = function (yaxes: IAxis[]) { - yaxes.forEach((yaxis: IAxis) => { - if (yaxis.units) { - const formatters = tickFormatters(yaxis); - yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; - } else if (yaxis.tickDecimals) { - yaxis.tickFormatter = (val: number) => val.toFixed(yaxis.tickDecimals); - } - - if (yaxis.max) { - yaxis.domain = { - max: yaxis.max, - }; - } - - if (yaxis.min) { - if (yaxis.domain) { - yaxis.domain.min = yaxis.min; - } else { - yaxis.domain = { - min: yaxis.min, - }; - } - } - }); - }; - - useEffect(() => { - const newChart = seriesList.list.map((series: Series, seriesIndex: number) => { - const newSeries = { ...series }; - if (!newSeries.color) { - const colorIndex = seriesIndex % colors.length; - newSeries.color = colors[colorIndex]; - } - - if (newSeries._global && newSeries._global.yaxes) { - updateYAxes(newSeries._global.yaxes); - } - - return newSeries; - }); - - setChart(newChart); - }, [seriesList.list]); - - // temp solution - const getLegendPosition = useCallback(() => { - const chartGlobal = chart[0]._global; - if (chartGlobal && chartGlobal.legend) { - switch (chartGlobal.legend.position) { - case 'ne': - return Position.Right; - case 'nw': - return Position.Left; - case 'se': - return Position.Right; - case 'sw': - return Position.Left; - } - } - return Position.Left; - }, [chart]); - - const brushEndListener = useCallback( - ({ x }) => { - fireEvent({ - name: 'applyFilter', - data: { - timeFieldName: '*', - filters: [ - { - range: { - '*': { - gte: x[0], - lte: x[1], - }, - }, - }, - ], - }, - }); - }, - [fireEvent] - ); - - const onRenderChange = function (isRendered: boolean) { - if (!isRendered) { - renderComplete(); - } - }; - - const title: string = useMemo(() => last(compact(map(seriesList.list, '_title'))) || '', [ - seriesList.list, - ]); - - return ( -
-
{title}
- - - - {chart[0]._global && chart[0]._global.yaxes ? ( - chart[0]._global.yaxes.map((axis: IAxis, index: number) => { - return ( - - ); - }) - ) : ( - - )} - {chart.map((data, index) => { - if (data.bars) { - return ( - - ); - } else { - return ( - - ); - } - })} - -
- ); -} - -// default export required for React.Lazy -// eslint-disable-next-line import/no-default-export -export { TimelionVisComponent1 as default }; diff --git a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts index 955c52bc1dc7bd..8326d05f3f0749 100644 --- a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -17,7 +17,6 @@ * under the License. */ -import { cloneDeep, defaults, mergeWith, compact } from 'lodash'; import moment, { Moment } from 'moment-timezone'; import { TimefilterContract } from 'src/plugins/data/public'; @@ -25,7 +24,6 @@ import { IUiSettingsClient } from 'kibana/public'; import { calculateInterval } from '../../common/lib'; import { xaxisFormatterProvider } from './xaxis_formatter'; -import { Series } from './timelion_request_handler'; export interface Axis { delta?: number; @@ -69,58 +67,10 @@ const colors = [ '#D70060', ]; -const SERIES_ID_ATTR = 'data-series-id'; - -function buildSeriesData(chart: Series[], options: jquery.flot.plotOptions) { - const seriesData = chart.map((series: Series, seriesIndex: number) => { - const newSeries: Series = cloneDeep( - defaults(series, { - shadowSize: 0, - lines: { - lineWidth: 3, - }, - }) - ); - - newSeries._id = seriesIndex; - - if (series.color) { - const span = document.createElement('span'); - span.style.color = series.color; - newSeries.color = span.style.color; - } - - if (series._hide) { - newSeries.data = []; - newSeries.stack = false; - newSeries.label = `(hidden) ${series.label}`; - } - - if (series._global) { - mergeWith(options, series._global, (objVal, srcVal) => { - // This is kind of gross, it means that you can't replace a global value with a null - // best you can do is an empty string. Deal with it. - if (objVal == null) { - return srcVal; - } - if (srcVal == null) { - return objVal; - } - }); - } - - return newSeries; - }); - - return compact(seriesData); -} - function buildOptions( intervalValue: string, timefilter: TimefilterContract, - uiSettings: IUiSettingsClient, - clientWidth = 0, - showGrid?: boolean + uiSettings: IUiSettingsClient ) { // Get the X-axis tick format const time: TimeRangeBounds = timefilter.getBounds(); @@ -133,16 +83,11 @@ function buildOptions( ); const format = xaxisFormatterProvider(uiSettings)(interval); - const tickLetterWidth = 7; - const tickPadding = 45; - const options = { xaxis: { mode: 'time', tickLength: 5, timezone: 'browser', - // Calculate how many ticks can fit on the axis - ticks: Math.floor(clientWidth / (format.length * tickLetterWidth + tickPadding)), // Use moment to format ticks so we get timezone correction tickFormatter: (val: number) => moment(val).format(format), }, @@ -156,39 +101,10 @@ function buildOptions( lineWidth: 2, }, colors, - grid: { - show: showGrid, - borderWidth: 0, - borderColor: null, - margin: 10, - hoverable: true, - autoHighlight: false, - }, - legend: { - backgroundColor: 'rgb(255,255,255,0)', - position: 'nw', - labelBoxBorderColor: 'rgb(255,255,255,0)', - labelFormatter(label: string, series: { _id: number }) { - const wrapperSpan = document.createElement('span'); - const labelSpan = document.createElement('span'); - const numberSpan = document.createElement('span'); - - wrapperSpan.setAttribute('class', 'ngLegendValue'); - wrapperSpan.setAttribute(SERIES_ID_ATTR, `${series._id}`); - - labelSpan.appendChild(document.createTextNode(label)); - numberSpan.setAttribute('class', 'ngLegendValueNumber'); - - wrapperSpan.appendChild(labelSpan); - wrapperSpan.appendChild(numberSpan); - - return wrapperSpan.outerHTML; - }, - }, yaxes: [], }; return options; } -export { buildSeriesData, buildOptions, SERIES_ID_ATTR, colors }; +export { buildOptions, colors }; diff --git a/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx b/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx index fa7111b1650648..21fffc46d6d9b5 100644 --- a/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx +++ b/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx @@ -25,9 +25,8 @@ import { KibanaContextProvider } from '../../kibana_react/public'; import { VisualizationContainer } from '../../visualizations/public'; import { TimelionVisDependencies } from './plugin'; import { TimelionRenderValue } from './timelion_vis_fn'; -// @ts-ignore + const TimelionVisComponent = lazy(() => import('./components/timelion_vis_component')); -const TimelionVisComponent1 = lazy(() => import('./components/timelion_vis_component1')); export const getTimelionVisRenderer: ( deps: TimelionVisDependencies @@ -46,13 +45,7 @@ export const getTimelionVisRenderer: ( render( - {/* */} - Date: Wed, 4 Nov 2020 15:52:19 +0300 Subject: [PATCH 09/44] Some refactoring --- .../public/components/area_series/index.tsx | 69 ++++++++++++++++ .../public/components/bar_series/index.tsx | 58 ++++++++++++++ .../components/timelion_vis_component.tsx | 80 +++++++------------ .../public/helpers/panel_utils.ts | 22 ++--- .../public/helpers/series_styles.ts | 79 ------------------ 5 files changed, 169 insertions(+), 139 deletions(-) create mode 100644 src/plugins/vis_type_timelion/public/components/area_series/index.tsx create mode 100644 src/plugins/vis_type_timelion/public/components/bar_series/index.tsx delete mode 100644 src/plugins/vis_type_timelion/public/helpers/series_styles.ts diff --git a/src/plugins/vis_type_timelion/public/components/area_series/index.tsx b/src/plugins/vis_type_timelion/public/components/area_series/index.tsx new file mode 100644 index 00000000000000..cbe49705505949 --- /dev/null +++ b/src/plugins/vis_type_timelion/public/components/area_series/index.tsx @@ -0,0 +1,69 @@ +/* + * 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 { AreaSeries, ScaleType, CurveType } from '@elastic/charts'; + +interface AreaSeriesComponentProps { + data: any; + index: number; +} + +export function AreaSeriesComponent({ data, index }: AreaSeriesComponentProps) { + const lines = data.lines || {}; + const points = data.points || {}; + const styles = { + areaSeriesStyle: { + line: { + stroke: data.color, + strokeWidth: Number(lines.lineWidth) || 3, + visible: lines.show === undefined ? true : lines.show, + }, + area: { + fill: data.color, + opacity: !lines.fill || lines.fill <= 0 ? 0 : lines.fill, + visible: lines.show === undefined ? true : lines.show, + }, + point: { + fill: points.fillColor, + opacity: !points.fill || points.fill < 0 ? 1 : points.fill, + radius: points.radius || 3, + stroke: data.color || '#000', + strokeWidth: points.lineWidth || 2, + visible: Boolean(points.show), + }, + }, + curve: lines.steps ? CurveType.CURVE_STEP : CurveType.LINEAR, + }; + + return ( + + ); +} diff --git a/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx b/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx new file mode 100644 index 00000000000000..903a3f8677e8e0 --- /dev/null +++ b/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx @@ -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 from 'react'; +import { BarSeries, ScaleType } from '@elastic/charts'; + +interface BarSeriesComponentProps { + data: any; + index: number; +} + +export function BarSeriesComponent({ data, index }: BarSeriesComponentProps) { + const bars = data.bars || {}; + const styles = { + barSeriesStyle: { + rectBorder: { + stroke: data.color || '#000', + strokeWidth: parseInt(bars.lineWidth || '0', 10), + visible: bars.show === undefined ? true : !!bars.show, + }, + rect: { + fill: data.color || '#000', + opacity: !bars.fill || bars.fill < 0 ? 1 : bars.fill, + }, + }, + }; + + return ( + + ); +} diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index 2f1b929f71eea4..4aeeb4e17a709d 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -19,26 +19,18 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { compact, cloneDeep, last, map } from 'lodash'; -import { - AreaSeries, - BarSeries, - Chart, - ScaleType, - Settings, - Position, - Axis, - TooltipType, - YDomainRange, -} from '@elastic/charts'; +import { Chart, Settings, Position, Axis, TooltipType, YDomainRange } from '@elastic/charts'; import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; import { useKibana } from '../../../kibana_react/public'; -import { buildOptions, colors, Axis as IAxis } from '../helpers/panel_utils'; +import { AreaSeriesComponent } from './area_series'; +import { BarSeriesComponent } from './bar_series'; + +import { buildOptions, colors, Axis as IAxis, Options } from '../helpers/panel_utils'; import { tickFormatters } from '../helpers/tick_formatters'; import { Series, Sheet } from '../helpers/timelion_request_handler'; -import { getBarStyles, getAreaStyles } from '../helpers/series_styles'; import { TimelionVisDependencies } from '../plugin'; import './index.scss'; @@ -50,7 +42,7 @@ interface TimelionVisComponentProps { renderComplete: IInterpreterRenderHandlers['done']; } -function TimelionVisComponent1({ +function TimelionVisComponent({ interval, seriesList, renderComplete, @@ -58,7 +50,9 @@ function TimelionVisComponent1({ }: TimelionVisComponentProps) { const kibana = useKibana(); const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); - const options = buildOptions(interval, kibana.services.timefilter, kibana.services.uiSettings); + const [options, setOptions] = useState( + buildOptions(interval, kibana.services.timefilter, kibana.services.uiSettings) + ); const updateYAxes = function (yaxes: IAxis[]) { yaxes.forEach((yaxis: IAxis) => { @@ -105,6 +99,10 @@ function TimelionVisComponent1({ setChart(newChart); }, [seriesList.list]); + useEffect(() => { + setOptions(buildOptions(interval, kibana.services.timefilter, kibana.services.uiSettings)); + }, [interval, kibana.services.timefilter, kibana.services.uiSettings]); + // temp solution const getLegendPosition = useCallback(() => { const chartGlobal = chart[0]._global; @@ -125,6 +123,10 @@ function TimelionVisComponent1({ const brushEndListener = useCallback( ({ x }) => { + if (!x) { + return; + } + fireEvent({ name: 'applyFilter', data: { @@ -145,11 +147,14 @@ function TimelionVisComponent1({ [fireEvent] ); - const onRenderChange = function (isRendered: boolean) { - if (!isRendered) { - renderComplete(); - } - }; + const onRenderChange = useCallback( + (isRendered: boolean) => { + if (!isRendered) { + renderComplete(); + } + }, + [renderComplete] + ); const title: string = useMemo(() => last(compact(map(seriesList.list, '_title'))) || '', [ seriesList.list, @@ -215,38 +220,11 @@ function TimelionVisComponent1({ /> )} {chart.map((data, index) => { + const key = `${index}-${data.label}`; if (data.bars) { - return ( - - ); + return ; } else { - return ( - - ); + return ; } })} @@ -256,4 +234,4 @@ function TimelionVisComponent1({ // default export required for React.Lazy // eslint-disable-next-line import/no-default-export -export { TimelionVisComponent1 as default }; +export { TimelionVisComponent as default }; diff --git a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts index 8326d05f3f0749..cd7ca6d94973db 100644 --- a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -49,6 +49,18 @@ export interface Axis { axisLabel: string; } +export interface Options { + xaxis: { + tickFormatter: (val: number) => string; + }; + crosshair: { + color: string; + lineWidth: number; + }; + colors: string[]; + yaxes: Axis[]; +} + interface TimeRangeBounds { min: Moment | undefined; max: Moment | undefined; @@ -71,7 +83,7 @@ function buildOptions( intervalValue: string, timefilter: TimefilterContract, uiSettings: IUiSettingsClient -) { +): Options { // Get the X-axis tick format const time: TimeRangeBounds = timefilter.getBounds(); const interval = calculateInterval( @@ -85,18 +97,10 @@ function buildOptions( const options = { xaxis: { - mode: 'time', - tickLength: 5, - timezone: 'browser', // Use moment to format ticks so we get timezone correction tickFormatter: (val: number) => moment(val).format(format), }, - selection: { - mode: 'x', - color: '#ccc', - }, crosshair: { - mode: 'x', color: '#C66', lineWidth: 2, }, diff --git a/src/plugins/vis_type_timelion/public/helpers/series_styles.ts b/src/plugins/vis_type_timelion/public/helpers/series_styles.ts deleted file mode 100644 index d223ce686a5108..00000000000000 --- a/src/plugins/vis_type_timelion/public/helpers/series_styles.ts +++ /dev/null @@ -1,79 +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 { CurveType } from '@elastic/charts'; - -const DEFAULT_COLOR = '#000'; - -export const getAreaStyles = ({ - points = {}, - lines = {}, - color, -}: { - points?: { - fill?: number; - fillColor?: string; - radius?: number; - show?: boolean; - lineWidth?: number; - }; - lines?: { - lineWidth?: number; - fill?: number; - steps?: number; - show?: boolean; - }; - color?: string; -}) => ({ - areaSeriesStyle: { - line: { - stroke: color, - strokeWidth: Number(lines.lineWidth) || 3, - visible: lines.show === undefined ? true : lines.show, - }, - area: { - fill: color, - opacity: !lines.fill || lines.fill <= 0 ? 0 : lines.fill, - visible: lines.show === undefined ? true : lines.show, - }, - point: { - fill: points.fillColor, - opacity: !points.fill || points.fill < 0 ? 1 : points.fill, - radius: points.radius || 3, - stroke: color || DEFAULT_COLOR, - strokeWidth: points.lineWidth || 2, - visible: Boolean(points.show), - }, - }, - curve: lines.steps ? CurveType.CURVE_STEP : CurveType.LINEAR, -}); - -export const getBarStyles = ({ show = true, lineWidth = '0', fill = 1 }, color?: string) => ({ - barSeriesStyle: { - rectBorder: { - stroke: color || DEFAULT_COLOR, - strokeWidth: parseInt(lineWidth, 10), - visible: !!show, - }, - rect: { - fill: color || DEFAULT_COLOR, - opacity: fill, - }, - }, -}); From 901f8c7579126cc60889f82ac5acf0815cafca26 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Thu, 5 Nov 2020 17:47:17 +0300 Subject: [PATCH 10/44] Some refactoring --- .../public/components/area_series/index.tsx | 18 +++++++---------- .../public/components/bar_series/index.tsx | 16 ++++++--------- .../components/timelion_vis_component.tsx | 20 +++++++------------ 3 files changed, 20 insertions(+), 34 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/area_series/index.tsx b/src/plugins/vis_type_timelion/public/components/area_series/index.tsx index cbe49705505949..f256d04593a9bc 100644 --- a/src/plugins/vis_type_timelion/public/components/area_series/index.tsx +++ b/src/plugins/vis_type_timelion/public/components/area_series/index.tsx @@ -20,12 +20,7 @@ import React from 'react'; import { AreaSeries, ScaleType, CurveType } from '@elastic/charts'; -interface AreaSeriesComponentProps { - data: any; - index: number; -} - -export function AreaSeriesComponent({ data, index }: AreaSeriesComponentProps) { +export function AreaSeriesComponent({ data, index }: { data: any; index: number }) { const lines = data.lines || {}; const points = data.points || {}; const styles = { @@ -33,18 +28,18 @@ export function AreaSeriesComponent({ data, index }: AreaSeriesComponentProps) { line: { stroke: data.color, strokeWidth: Number(lines.lineWidth) || 3, - visible: lines.show === undefined ? true : lines.show, + visible: lines.show ?? true, }, area: { fill: data.color, - opacity: !lines.fill || lines.fill <= 0 ? 0 : lines.fill, - visible: lines.show === undefined ? true : lines.show, + opacity: !lines.fill || lines.fill < 0 ? 0 : lines.fill, + visible: lines.show ?? true, }, point: { fill: points.fillColor, opacity: !points.fill || points.fill < 0 ? 1 : points.fill, radius: points.radius || 3, - stroke: data.color || '#000', + stroke: data.color, strokeWidth: points.lineWidth || 2, visible: Boolean(points.show), }, @@ -54,7 +49,8 @@ export function AreaSeriesComponent({ data, index }: AreaSeriesComponentProps) { return ( val.toFixed(yaxis.tickDecimals); } + yaxis.domain = {}; + if (yaxis.max) { - yaxis.domain = { - max: yaxis.max, - }; + yaxis.domain.max = yaxis.max; } if (yaxis.min) { - if (yaxis.domain) { - yaxis.domain.min = yaxis.min; - } else { - yaxis.domain = { - min: yaxis.min, - }; - } + yaxis.domain.min = yaxis.min; } }); }; @@ -89,7 +83,7 @@ function TimelionVisComponent({ newSeries.color = colors[colorIndex]; } - if (newSeries._global && newSeries._global.yaxes) { + if (newSeries._global?.yaxes) { updateYAxes(newSeries._global.yaxes); } @@ -103,7 +97,7 @@ function TimelionVisComponent({ setOptions(buildOptions(interval, kibana.services.timefilter, kibana.services.uiSettings)); }, [interval, kibana.services.timefilter, kibana.services.uiSettings]); - // temp solution + // temp solution, will be changed after fix https://github.com/elastic/elastic-charts/issues/878 const getLegendPosition = useCallback(() => { const chartGlobal = chart[0]._global; if (chartGlobal && chartGlobal.legend) { @@ -192,7 +186,7 @@ function TimelionVisComponent({ showOverlappingTicks tickFormat={options.xaxis.tickFormatter} /> - {chart[0]._global && chart[0]._global.yaxes ? ( + {chart[0]._global?.yaxes ? ( chart[0]._global.yaxes.map((axis: IAxis, index: number) => { return ( Date: Fri, 6 Nov 2020 16:41:22 +0300 Subject: [PATCH 11/44] Fixed some remarks. --- src/plugins/timelion/public/index.scss | 7 +-- .../public/panels/timechart/_index.scss | 60 +++++++++++++++++++ .../public/components/_timelion_vis.scss | 6 -- .../public/components/area_series/index.tsx | 2 +- .../components/timelion_vis_component.tsx | 6 +- .../public/helpers/panel_utils.ts | 3 +- 6 files changed, 68 insertions(+), 16 deletions(-) create mode 100644 src/plugins/timelion/public/panels/timechart/_index.scss diff --git a/src/plugins/timelion/public/index.scss b/src/plugins/timelion/public/index.scss index f39e0c18a28706..dd0cf21e1441fc 100644 --- a/src/plugins/timelion/public/index.scss +++ b/src/plugins/timelion/public/index.scss @@ -10,9 +10,4 @@ @import './app'; @import './base'; @import './directives/index'; - -// these styles is needed to be loaded here explicitly if the timelion visualization was not opened in browser -// styles for timelion visualization are lazy loaded only while a vis is opened -// this will duplicate styles only if both Timelion app and timelion visualization are loaded -// could be left here as it is since the Timelion app is deprecated -@import '../../vis_type_timelion/public/components/index.scss'; +@import './panels/timechart/index'; diff --git a/src/plugins/timelion/public/panels/timechart/_index.scss b/src/plugins/timelion/public/panels/timechart/_index.scss new file mode 100644 index 00000000000000..059d9047f7b4f2 --- /dev/null +++ b/src/plugins/timelion/public/panels/timechart/_index.scss @@ -0,0 +1,60 @@ +.timChart { + height: 100%; + width: 100%; + display: flex; + flex-direction: column; + + // Custom Jquery FLOT / schema selectors + // Cannot change at the moment + + .chart-top-title { + @include euiFontSizeXS; + flex: 0; + text-align: center; + font-weight: $euiFontWeightBold; + } + + .chart-canvas { + min-width: 100%; + flex: 1; + overflow: hidden; + } + + .legendLabel { + white-space: nowrap; + text-overflow: ellipsis; + overflow-x: hidden; + line-height: normal; + } + + .legendColorBox { + vertical-align: middle; + } + + .ngLegendValue { + color: $euiTextColor; + cursor: pointer; + + &:focus, + &:hover { + text-decoration: underline; + } + } + + .ngLegendValueNumber { + margin-left: $euiSizeXS; + margin-right: $euiSizeXS; + font-weight: $euiFontWeightBold; + } + + .flot-tick-label { + font-size: $euiFontSizeXS; + color: $euiColorDarkShade; + } +} + +.timChart__legendCaption { + color: $euiTextColor; + white-space: nowrap; + font-weight: $euiFontWeightBold; +} \ No newline at end of file diff --git a/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss b/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss index 29a6d5ff1e0545..683868b8c896e9 100644 --- a/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss +++ b/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss @@ -1,9 +1,3 @@ -.timChart__legendCaption { - color: $euiTextColor; - white-space: nowrap; - font-weight: $euiFontWeightBold; -} - .timelionChart { height: 100%; width: 100%; diff --git a/src/plugins/vis_type_timelion/public/components/area_series/index.tsx b/src/plugins/vis_type_timelion/public/components/area_series/index.tsx index f256d04593a9bc..ce4d1a15444404 100644 --- a/src/plugins/vis_type_timelion/public/components/area_series/index.tsx +++ b/src/plugins/vis_type_timelion/public/components/area_series/index.tsx @@ -41,7 +41,7 @@ export function AreaSeriesComponent({ data, index }: { data: any; index: number radius: points.radius || 3, stroke: data.color, strokeWidth: points.lineWidth || 2, - visible: Boolean(points.show), + visible: points.show, }, }, curve: lines.steps ? CurveType.CURVE_STEP : CurveType.LINEAR, diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index 4268b00956cbea..00e1d1bd8e457b 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -35,6 +35,8 @@ import { TimelionVisDependencies } from '../plugin'; import './index.scss'; +const GRID_LINE_STROKE = 'rgba(125,125,125,0.3)'; + interface TimelionVisComponentProps { fireEvent: IInterpreterRenderHandlers['event']; interval: string; @@ -196,7 +198,7 @@ function TimelionVisComponent({ position={axis.position} tickFormat={axis.tickFormatter} gridLine={{ - stroke: 'rgba(125,125,125,0.3)', + stroke: GRID_LINE_STROKE, visible: true, }} domain={axis.domain as YDomainRange} @@ -208,7 +210,7 @@ function TimelionVisComponent({ id="left" position={Position.Left} gridLine={{ - stroke: 'rgba(125,125,125,0.3)', + stroke: GRID_LINE_STROKE, visible: true, }} /> diff --git a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts index cd7ca6d94973db..17a906ff7087e4 100644 --- a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -18,6 +18,7 @@ */ import moment, { Moment } from 'moment-timezone'; +import { Position } from '@elastic/charts'; import { TimefilterContract } from 'src/plugins/data/public'; import { IUiSettingsClient } from 'kibana/public'; @@ -45,7 +46,7 @@ export interface Axis { min?: number; max?: number; }; - position: 'top' | 'bottom' | 'left' | 'right' | undefined; + position?: Position; axisLabel: string; } From 4f8c59a5d1e2c0ae48cef382e7dd4525cf27227c Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Fri, 6 Nov 2020 16:49:22 +0300 Subject: [PATCH 12/44] Fixed some styles --- src/plugins/timelion/public/panels/timechart/_index.scss | 7 +++++++ .../vis_type_timelion/public/components/_timelion_vis.scss | 2 +- .../public/components/timelion_vis_component.tsx | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/plugins/timelion/public/panels/timechart/_index.scss b/src/plugins/timelion/public/panels/timechart/_index.scss index 059d9047f7b4f2..bfafd49ae08b52 100644 --- a/src/plugins/timelion/public/panels/timechart/_index.scss +++ b/src/plugins/timelion/public/panels/timechart/_index.scss @@ -57,4 +57,11 @@ color: $euiTextColor; white-space: nowrap; font-weight: $euiFontWeightBold; +} + +.chart-top-title { + @include euiFontSizeXS; + flex: 0; + text-align: center; + font-weight: $euiFontWeightBold; } \ No newline at end of file diff --git a/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss b/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss index 683868b8c896e9..5c7bc500949610 100644 --- a/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss +++ b/src/plugins/vis_type_timelion/public/components/_timelion_vis.scss @@ -5,7 +5,7 @@ flex-direction: column; } -.chart-top-title { +.timelionChart__topTitle { @include euiFontSizeXS; flex: 0; text-align: center; diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index 00e1d1bd8e457b..7d675637533cfb 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -158,7 +158,7 @@ function TimelionVisComponent({ return (
-
{title}
+
{title}
Date: Mon, 9 Nov 2020 16:43:48 +0300 Subject: [PATCH 13/44] Added themes. Removed unneeded styles in BarSeries --- src/plugins/vis_type_timelion/kibana.json | 2 +- .../public/components/bar_series/index.tsx | 5 ----- .../public/components/timelion_vis_component.tsx | 14 ++------------ src/plugins/vis_type_timelion/public/plugin.ts | 6 +++++- 4 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/plugins/vis_type_timelion/kibana.json b/src/plugins/vis_type_timelion/kibana.json index dda33c9fb6f2e2..fddc68275eb77b 100644 --- a/src/plugins/vis_type_timelion/kibana.json +++ b/src/plugins/vis_type_timelion/kibana.json @@ -4,6 +4,6 @@ "kibanaVersion": "kibana", "server": true, "ui": true, - "requiredPlugins": ["visualizations", "data", "expressions"], + "requiredPlugins": ["visualizations", "data", "expressions", "charts"], "requiredBundles": ["kibanaUtils", "kibanaReact", "visDefaultEditor"] } diff --git a/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx b/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx index a967b5a1ff2a26..f3e05d69dbb407 100644 --- a/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx +++ b/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx @@ -24,11 +24,6 @@ export function BarSeriesComponent({ data, index }: { data: any; index: number } const bars = data.bars || {}; const styles = { barSeriesStyle: { - rectBorder: { - stroke: data.color, - strokeWidth: parseInt(bars.lineWidth || '0', 10), - visible: bars.show ?? true, - }, rect: { fill: data.color, opacity: !bars.fill || bars.fill < 0 ? 1 : bars.fill, diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index 7d675637533cfb..a5b1f3ae12186b 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -165,18 +165,8 @@ function TimelionVisComponent({ showLegend legendPosition={getLegendPosition()} onRenderChange={onRenderChange} - theme={[ - { - crosshair: { - band: { - fill: options.crosshair.color, - }, - line: { - strokeWidth: options.crosshair.lineWidth, - }, - }, - }, - ]} + theme={kibana.services.chartTheme.useChartsTheme()} + baseTheme={kibana.services.chartTheme.useChartsBaseTheme()} tooltip={{ snap: true, type: TooltipType.VerticalCursor, diff --git a/src/plugins/vis_type_timelion/public/plugin.ts b/src/plugins/vis_type_timelion/public/plugin.ts index d74c127dce8817..eb53e997a246ff 100644 --- a/src/plugins/vis_type_timelion/public/plugin.ts +++ b/src/plugins/vis_type_timelion/public/plugin.ts @@ -33,6 +33,7 @@ import { } from 'src/plugins/data/public'; import { VisualizationsSetup } from '../../visualizations/public'; +import { ChartsPluginSetup } from '../../charts/public'; import { getTimelionVisualizationConfig } from './timelion_vis_fn'; import { getTimelionVisDefinition } from './timelion_vis_type'; @@ -47,6 +48,7 @@ export interface TimelionVisDependencies extends Partial { uiSettings: IUiSettingsClient; http: HttpSetup; timefilter: TimefilterContract; + chartTheme: ChartsPluginSetup['theme']; } /** @internal */ @@ -54,6 +56,7 @@ export interface TimelionVisSetupDependencies { expressions: ReturnType; visualizations: VisualizationsSetup; data: DataPublicPluginSetup; + charts: ChartsPluginSetup; } /** @internal */ @@ -84,12 +87,13 @@ export class TimelionVisPlugin public setup( core: CoreSetup, - { expressions, visualizations, data }: TimelionVisSetupDependencies + { expressions, visualizations, data, charts }: TimelionVisSetupDependencies ) { const dependencies: TimelionVisDependencies = { uiSettings: core.uiSettings, http: core.http, timefilter: data.query.timefilter.timefilter, + chartTheme: charts.theme, }; expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); From 6711daca8ab1f17646ecd85221721fa1ae062fd2 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Thu, 12 Nov 2020 14:42:18 +0300 Subject: [PATCH 14/44] removed unneeded code. --- src/plugins/vis_type_timelion/public/helpers/panel_utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts index 17a906ff7087e4..748476e82a0811 100644 --- a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -39,7 +39,7 @@ export interface Axis { tickLength: number; timezone: string; tickDecimals?: number; - tickFormatter: ((val: number) => string) | ((val: number, axis: any) => string); + tickFormatter: (val: number) => string; tickGenerator?(axis: Axis): number[]; units?: { type: string }; domain?: { From 6d740177fc3dc69883a1c85f7fd5524c1ca226cd Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Mon, 16 Nov 2020 10:41:51 +0300 Subject: [PATCH 15/44] Fixed some comments --- .../components/timelion_vis_component.tsx | 55 +++++++++---------- .../public/helpers/panel_utils.ts | 33 ++--------- 2 files changed, 31 insertions(+), 57 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index a5b1f3ae12186b..f4ec1a8a58e1a3 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -19,7 +19,15 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { compact, cloneDeep, last, map } from 'lodash'; -import { Chart, Settings, Position, Axis, TooltipType, YDomainRange } from '@elastic/charts'; +import { + Chart, + Settings, + Position, + Axis, + TooltipType, + YDomainRange, + BrushEndListener, +} from '@elastic/charts'; import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; import { useKibana } from '../../../kibana_react/public'; @@ -27,7 +35,7 @@ import { useKibana } from '../../../kibana_react/public'; import { AreaSeriesComponent } from './area_series'; import { BarSeriesComponent } from './bar_series'; -import { buildOptions, colors, Axis as IAxis, Options } from '../helpers/panel_utils'; +import { createTickFormat, colors, Axis as IAxis } from '../helpers/panel_utils'; import { tickFormatters } from '../helpers/tick_formatters'; import { Series, Sheet } from '../helpers/timelion_request_handler'; @@ -52,9 +60,6 @@ function TimelionVisComponent({ }: TimelionVisComponentProps) { const kibana = useKibana(); const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); - const [options, setOptions] = useState( - buildOptions(interval, kibana.services.timefilter, kibana.services.uiSettings) - ); const updateYAxes = function (yaxes: IAxis[]) { yaxes.forEach((yaxis: IAxis) => { @@ -95,29 +100,23 @@ function TimelionVisComponent({ setChart(newChart); }, [seriesList.list]); - useEffect(() => { - setOptions(buildOptions(interval, kibana.services.timefilter, kibana.services.uiSettings)); - }, [interval, kibana.services.timefilter, kibana.services.uiSettings]); - // temp solution, will be changed after fix https://github.com/elastic/elastic-charts/issues/878 const getLegendPosition = useCallback(() => { const chartGlobal = chart[0]._global; - if (chartGlobal && chartGlobal.legend) { - switch (chartGlobal.legend.position) { - case 'ne': - return Position.Right; - case 'nw': - return Position.Left; - case 'se': - return Position.Right; - case 'sw': - return Position.Left; - } + switch (chartGlobal?.legend.position) { + case 'ne': + return Position.Right; + case 'nw': + return Position.Left; + case 'se': + return Position.Right; + case 'sw': + return Position.Left; } return Position.Left; }, [chart]); - const brushEndListener = useCallback( + const brushEndListener = useCallback( ({ x }) => { if (!x) { return; @@ -145,7 +144,7 @@ function TimelionVisComponent({ const onRenderChange = useCallback( (isRendered: boolean) => { - if (!isRendered) { + if (isRendered) { renderComplete(); } }, @@ -156,6 +155,11 @@ function TimelionVisComponent({ seriesList.list, ]); + const tickFormat = useMemo( + () => createTickFormat(interval, kibana.services.timefilter, kibana.services.uiSettings), + [interval, kibana.services.timefilter, kibana.services.uiSettings] + ); + return (
{title}
@@ -172,12 +176,7 @@ function TimelionVisComponent({ type: TooltipType.VerticalCursor, }} /> - + {chart[0]._global?.yaxes ? ( chart[0]._global.yaxes.map((axis: IAxis, index: number) => { return ( diff --git a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts index 748476e82a0811..7ddff9bd6f4116 100644 --- a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -50,18 +50,6 @@ export interface Axis { axisLabel: string; } -export interface Options { - xaxis: { - tickFormatter: (val: number) => string; - }; - crosshair: { - color: string; - lineWidth: number; - }; - colors: string[]; - yaxes: Axis[]; -} - interface TimeRangeBounds { min: Moment | undefined; max: Moment | undefined; @@ -80,11 +68,11 @@ const colors = [ '#D70060', ]; -function buildOptions( +function createTickFormat( intervalValue: string, timefilter: TimefilterContract, uiSettings: IUiSettingsClient -): Options { +) { // Get the X-axis tick format const time: TimeRangeBounds = timefilter.getBounds(); const interval = calculateInterval( @@ -96,20 +84,7 @@ function buildOptions( ); const format = xaxisFormatterProvider(uiSettings)(interval); - const options = { - xaxis: { - // Use moment to format ticks so we get timezone correction - tickFormatter: (val: number) => moment(val).format(format), - }, - crosshair: { - color: '#C66', - lineWidth: 2, - }, - colors, - yaxes: [], - }; - - return options; + return (val: number) => moment(val).format(format); } -export { buildOptions, colors }; +export { createTickFormat, colors }; From de4125982a5c2f16536a7a2c282e9fdc4c255265 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Mon, 16 Nov 2020 17:08:08 +0300 Subject: [PATCH 16/44] Fixed vertical cursor across Timelion visualizations of a dashboad --- .../components/timelion_vis_component.tsx | 34 +++++++++++++++++-- .../public/helpers/panel_utils.ts | 4 +++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index f4ec1a8a58e1a3..001693336d34a3 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import React, { useState, useEffect, useCallback, useMemo, useRef, RefObject } from 'react'; import { compact, cloneDeep, last, map } from 'lodash'; import { Chart, @@ -27,6 +27,7 @@ import { TooltipType, YDomainRange, BrushEndListener, + PointerEvent, } from '@elastic/charts'; import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; @@ -35,7 +36,13 @@ import { useKibana } from '../../../kibana_react/public'; import { AreaSeriesComponent } from './area_series'; import { BarSeriesComponent } from './bar_series'; -import { createTickFormat, colors, Axis as IAxis } from '../helpers/panel_utils'; +import { + createTickFormat, + colors, + Axis as IAxis, + ACTIVE_CURSOR, + eventBus, +} from '../helpers/panel_utils'; import { tickFormatters } from '../helpers/tick_formatters'; import { Series, Sheet } from '../helpers/timelion_request_handler'; @@ -52,6 +59,10 @@ interface TimelionVisComponentProps { renderComplete: IInterpreterRenderHandlers['done']; } +const handleCursorUpdate = (cursor: PointerEvent) => { + eventBus.trigger(ACTIVE_CURSOR, cursor); +}; + function TimelionVisComponent({ interval, seriesList, @@ -60,6 +71,7 @@ function TimelionVisComponent({ }: TimelionVisComponentProps) { const kibana = useKibana(); const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); + const chartRef = useRef(); const updateYAxes = function (yaxes: IAxis[]) { yaxes.forEach((yaxis: IAxis) => { @@ -82,6 +94,20 @@ function TimelionVisComponent({ }); }; + useEffect(() => { + const updateCursor = (_: any, cursor: PointerEvent) => { + if (chartRef.current) { + chartRef.current.dispatchExternalPointerEvent(cursor); + } + }; + + eventBus.on(ACTIVE_CURSOR, updateCursor); + + return () => { + eventBus.off(ACTIVE_CURSOR, updateCursor); + }; + }, []); + useEffect(() => { const newChart = seriesList.list.map((series: Series, seriesIndex: number) => { const newSeries = { ...series }; @@ -163,18 +189,20 @@ function TimelionVisComponent({ return (
{title}
- + } renderer="canvas" size={{ width: '100%' }}> {chart[0]._global?.yaxes ? ( diff --git a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts index 7ddff9bd6f4116..3e48d14307784c 100644 --- a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -17,6 +17,7 @@ * under the License. */ +import $ from 'jquery'; import moment, { Moment } from 'moment-timezone'; import { Position } from '@elastic/charts'; @@ -55,6 +56,9 @@ interface TimeRangeBounds { max: Moment | undefined; } +export const ACTIVE_CURSOR = 'ACTIVE_CURSOR_TIMELION'; +export const eventBus = $({}); + const colors = [ '#01A4A4', '#C66', From 63a12db8b226cff6b61bd06c9dff7a9b0e447f2b Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Tue, 17 Nov 2020 14:10:15 +0300 Subject: [PATCH 17/44] Fix some problems with styles --- .../public/components/area_series/index.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/area_series/index.tsx b/src/plugins/vis_type_timelion/public/components/area_series/index.tsx index ce4d1a15444404..3df6d0a16f37c2 100644 --- a/src/plugins/vis_type_timelion/public/components/area_series/index.tsx +++ b/src/plugins/vis_type_timelion/public/components/area_series/index.tsx @@ -28,20 +28,20 @@ export function AreaSeriesComponent({ data, index }: { data: any; index: number line: { stroke: data.color, strokeWidth: Number(lines.lineWidth) || 3, - visible: lines.show ?? true, + visible: lines.show ?? !points.show, }, area: { fill: data.color, - opacity: !lines.fill || lines.fill < 0 ? 0 : lines.fill, - visible: lines.show ?? true, + opacity: lines.fill ?? 0, + visible: lines.show ?? !points.show, }, point: { fill: points.fillColor, - opacity: !points.fill || points.fill < 0 ? 1 : points.fill, + opacity: points.fill * 10 ?? 10, radius: points.radius || 3, stroke: data.color, strokeWidth: points.lineWidth || 2, - visible: points.show, + visible: points.show ?? true, }, }, curve: lines.steps ? CurveType.CURVE_STEP : CurveType.LINEAR, From ca9aeac458391074e78e0ff31b950b2d417f3e19 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Thu, 19 Nov 2020 19:37:25 +0300 Subject: [PATCH 18/44] Use RxJS instead of jQuery --- .../components/timelion_vis_component.tsx | 57 +++---------------- .../public/helpers/panel_utils.ts | 13 +++-- 2 files changed, 16 insertions(+), 54 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index d40ebedc57a5e2..5de94234e0b61c 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -42,6 +42,7 @@ import { Axis as IAxis, ACTIVE_CURSOR, eventBus, + TimelionEvent, } from '../helpers/panel_utils'; import { tickFormatters } from '../helpers/tick_formatters'; @@ -60,7 +61,7 @@ interface TimelionVisComponentProps { } const handleCursorUpdate = (cursor: PointerEvent) => { - eventBus.trigger(ACTIVE_CURSOR, cursor); + eventBus.next({ name: ACTIVE_CURSOR, data: cursor }); }; function TimelionVisComponent({ @@ -95,16 +96,16 @@ function TimelionVisComponent({ }; useEffect(() => { - const updateCursor = (_: any, cursor: PointerEvent) => { - if (chartRef.current) { - chartRef.current.dispatchExternalPointerEvent(cursor); + const updateCursor = ({ name, data }: TimelionEvent) => { + if (chartRef.current && name === ACTIVE_CURSOR && data) { + chartRef.current.dispatchExternalPointerEvent(data); } }; - eventBus.on(ACTIVE_CURSOR, updateCursor); + const subscription = eventBus.asObservable().subscribe(updateCursor); return () => { - eventBus.off(ACTIVE_CURSOR, updateCursor); + subscription.unsubscribe(); }; }, []); @@ -142,55 +143,11 @@ function TimelionVisComponent({ return Position.Left; }, [chart]); -<<<<<<< HEAD const brushEndListener = useCallback( ({ x }) => { if (!x) { return; } -======= - const plotHover = useCallback( - (pos: Position) => { - (plot as CrosshairPlot).setCrosshair(pos); - debouncedSetLegendNumbers(pos); - }, - [plot, debouncedSetLegendNumbers] - ); - - const plotHoverHandler = useCallback( - (event: JQuery.TriggeredEvent, pos: Position) => { - if (!plot) { - return; - } - plotHover(pos); - eventBus.trigger(ACTIVE_CURSOR, [event, pos]); - }, - [plot, plotHover] - ); - - useEffect(() => { - const updateCursor = (_: any, event: JQuery.TriggeredEvent, pos: Position) => { - if (!plot) { - return; - } - plotHover(pos); - }; - - eventBus.on(ACTIVE_CURSOR, updateCursor); - - return () => { - eventBus.off(ACTIVE_CURSOR, updateCursor); - }; - }, [plot, plotHover]); - - const mouseLeaveHandler = useCallback(() => { - if (!plot) { - return; - } - (plot as CrosshairPlot).clearCrosshair(); - clearLegendNumbers(); - }, [plot, clearLegendNumbers]); ->>>>>>> upstream/master fireEvent({ name: 'applyFilter', diff --git a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts index 3e48d14307784c..b398d6093336b0 100644 --- a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -17,7 +17,7 @@ * under the License. */ -import $ from 'jquery'; +import { Subject } from 'rxjs'; import moment, { Moment } from 'moment-timezone'; import { Position } from '@elastic/charts'; @@ -56,8 +56,13 @@ interface TimeRangeBounds { max: Moment | undefined; } -export const ACTIVE_CURSOR = 'ACTIVE_CURSOR_TIMELION'; -export const eventBus = $({}); +export interface TimelionEvent { + name: string; + data?: any; +} + +const ACTIVE_CURSOR = 'ACTIVE_CURSOR_TIMELION'; +const eventBus = new Subject(); const colors = [ '#01A4A4', @@ -91,4 +96,4 @@ function createTickFormat( return (val: number) => moment(val).format(format); } -export { createTickFormat, colors }; +export { createTickFormat, colors, ACTIVE_CURSOR, eventBus }; From 13033c5e56cb0c904db6df5ca06ff8f7e0476fe0 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Tue, 24 Nov 2020 13:49:56 +0300 Subject: [PATCH 19/44] Remove unneeded code --- .../components/timelion_vis_component.tsx | 19 ++++++------------- .../public/helpers/panel_utils.ts | 12 +++--------- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index 5de94234e0b61c..a8d7eba52831f0 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -36,14 +36,7 @@ import { useKibana } from '../../../kibana_react/public'; import { AreaSeriesComponent } from './area_series'; import { BarSeriesComponent } from './bar_series'; -import { - createTickFormat, - colors, - Axis as IAxis, - ACTIVE_CURSOR, - eventBus, - TimelionEvent, -} from '../helpers/panel_utils'; +import { createTickFormat, colors, Axis as IAxis, activeCursor$ } from '../helpers/panel_utils'; import { tickFormatters } from '../helpers/tick_formatters'; import { Series, Sheet } from '../helpers/timelion_request_handler'; @@ -61,7 +54,7 @@ interface TimelionVisComponentProps { } const handleCursorUpdate = (cursor: PointerEvent) => { - eventBus.next({ name: ACTIVE_CURSOR, data: cursor }); + activeCursor$.next(cursor); }; function TimelionVisComponent({ @@ -96,13 +89,13 @@ function TimelionVisComponent({ }; useEffect(() => { - const updateCursor = ({ name, data }: TimelionEvent) => { - if (chartRef.current && name === ACTIVE_CURSOR && data) { - chartRef.current.dispatchExternalPointerEvent(data); + const updateCursor = (cursor: PointerEvent) => { + if (chartRef.current) { + chartRef.current.dispatchExternalPointerEvent(cursor); } }; - const subscription = eventBus.asObservable().subscribe(updateCursor); + const subscription = activeCursor$.subscribe(updateCursor); return () => { subscription.unsubscribe(); diff --git a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts index b398d6093336b0..5534af76ea7d3b 100644 --- a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -19,7 +19,7 @@ import { Subject } from 'rxjs'; import moment, { Moment } from 'moment-timezone'; -import { Position } from '@elastic/charts'; +import { Position, PointerEvent } from '@elastic/charts'; import { TimefilterContract } from 'src/plugins/data/public'; import { IUiSettingsClient } from 'kibana/public'; @@ -56,13 +56,7 @@ interface TimeRangeBounds { max: Moment | undefined; } -export interface TimelionEvent { - name: string; - data?: any; -} - -const ACTIVE_CURSOR = 'ACTIVE_CURSOR_TIMELION'; -const eventBus = new Subject(); +const activeCursor$ = new Subject(); const colors = [ '#01A4A4', @@ -96,4 +90,4 @@ function createTickFormat( return (val: number) => moment(val).format(format); } -export { createTickFormat, colors, ACTIVE_CURSOR, eventBus }; +export { createTickFormat, colors, activeCursor$ }; From 18d9c374d030705cd1d9022fd33ee5059b611af8 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Wed, 25 Nov 2020 18:46:53 +0300 Subject: [PATCH 20/44] Fixed some problems --- .../public/components/area_series/index.tsx | 2 +- .../public/components/timelion_vis_component.tsx | 5 +++-- .../vis_type_timelion/public/helpers/panel_utils.ts | 2 +- .../vis_type_timelion/public/helpers/tick_formatters.ts | 9 ++++----- .../vis_type_timelion/server/series_functions/label.js | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/area_series/index.tsx b/src/plugins/vis_type_timelion/public/components/area_series/index.tsx index 3df6d0a16f37c2..58b093a778e032 100644 --- a/src/plugins/vis_type_timelion/public/components/area_series/index.tsx +++ b/src/plugins/vis_type_timelion/public/components/area_series/index.tsx @@ -41,7 +41,7 @@ export function AreaSeriesComponent({ data, index }: { data: any; index: number radius: points.radius || 3, stroke: data.color, strokeWidth: points.lineWidth || 2, - visible: points.show ?? true, + visible: points.show ?? false, }, }, curve: lines.steps ? CurveType.CURVE_STEP : CurveType.LINEAR, diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index a8d7eba52831f0..4fab270e8e8a3b 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -123,7 +123,7 @@ function TimelionVisComponent({ // temp solution, will be changed after fix https://github.com/elastic/elastic-charts/issues/878 const getLegendPosition = useCallback(() => { const chartGlobal = chart[0]._global; - switch (chartGlobal?.legend.position) { + switch (chartGlobal?.legend?.position) { case 'ne': return Position.Right; case 'nw': @@ -132,8 +132,9 @@ function TimelionVisComponent({ return Position.Right; case 'sw': return Position.Left; + default: + return Position.Left; } - return Position.Left; }, [chart]); const brushEndListener = useCallback( diff --git a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts index 5534af76ea7d3b..2d7ebefd6f1077 100644 --- a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -42,7 +42,7 @@ export interface Axis { tickDecimals?: number; tickFormatter: (val: number) => string; tickGenerator?(axis: Axis): number[]; - units?: { type: string }; + units?: { type: string; prefix: string; suffix: string }; domain?: { min?: number; max?: number; diff --git a/src/plugins/vis_type_timelion/public/helpers/tick_formatters.ts b/src/plugins/vis_type_timelion/public/helpers/tick_formatters.ts index bed7761c6f7bcf..a65d3614acce9c 100644 --- a/src/plugins/vis_type_timelion/public/helpers/tick_formatters.ts +++ b/src/plugins/vis_type_timelion/public/helpers/tick_formatters.ts @@ -65,12 +65,11 @@ export function tickFormatters(axis: Axis) { currency(val: number) { return val.toLocaleString('en', { style: 'currency', - currency: (axis && axis.options && axis.options.units.prefix) || 'USD', + currency: (axis && axis.units && axis.units.prefix) || 'USD', }); }, percent(val: number) { - let precision = - get(axis, 'tickDecimals', 0) - get(axis, 'options.units.tickDecimalsShift', 0); + let precision = get(axis, 'tickDecimals', 0) - get(axis, 'units.tickDecimalsShift', 0); // toFixed only accepts values between 0 and 20 if (precision < 0) { precision = 0; @@ -82,8 +81,8 @@ export function tickFormatters(axis: Axis) { }, custom(val: number) { const formattedVal = baseTickFormatter(val, axis); - const prefix = axis && axis.options && axis.options.units.prefix; - const suffix = axis && axis.options && axis.options.units.suffix; + const prefix = axis && axis.units && axis.units.prefix; + const suffix = axis && axis.units && axis.units.suffix; return prefix + formattedVal + suffix; }, }; diff --git a/src/plugins/vis_type_timelion/server/series_functions/label.js b/src/plugins/vis_type_timelion/server/series_functions/label.js index b5282967a62e0e..efd6e8450b81ad 100644 --- a/src/plugins/vis_type_timelion/server/series_functions/label.js +++ b/src/plugins/vis_type_timelion/server/series_functions/label.js @@ -55,7 +55,7 @@ export default new Chainable('label', { // that it doesn't prevent Kibana from starting up and we only have an issue using Timelion labels const RE2 = require('re2'); eachSeries.label = eachSeries.label.replace(new RE2(config.regex), config.label); - } else { + } else if (config.label) { eachSeries.label = config.label; } From 53b1c08139701c11b3d592f3ad42ac361d54b7d4 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Thu, 26 Nov 2020 10:48:55 +0300 Subject: [PATCH 21/44] Fixed unit test --- .../public/helpers/tick_formatters.test.ts | 36 +++++++------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts b/src/plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts index 7d4e40a9afc3bf..f5b1f33c11f94e 100644 --- a/src/plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts +++ b/src/plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts @@ -139,9 +139,7 @@ describe('Tick Formatters', function () { it('formats with $ by default', function () { const axis = { - options: { - units: {}, - }, + units: {}, }; formatters = tickFormatters(axis as Axis); currencyFormatter = formatters.currency; @@ -150,10 +148,8 @@ describe('Tick Formatters', function () { it('accepts currency in ISO 4217', function () { const axis = { - options: { - units: { - prefix: 'CNY', - }, + units: { + prefix: 'CNY', }, }; formatters = tickFormatters(axis as Axis); @@ -174,9 +170,7 @@ describe('Tick Formatters', function () { it('formats with %', function () { const axis = { - options: { - units: {}, - }, + units: {}, }; formatters = tickFormatters(axis as Axis); percentFormatter = formatters.percent; @@ -188,10 +182,8 @@ describe('Tick Formatters', function () { const tickDecimalShift = 2; const axis = { tickDecimals: tickDecimals + tickDecimalShift, - options: { - units: { - tickDecimalsShift: tickDecimalShift, - }, + units: { + tickDecimalsShift: tickDecimalShift, }, } as unknown; formatters = tickFormatters(axis as Axis); @@ -212,11 +204,9 @@ describe('Tick Formatters', function () { it('accepts prefix and suffix', function () { const axis = { - options: { - units: { - prefix: 'prefix', - suffix: 'suffix', - }, + units: { + prefix: 'prefix', + suffix: 'suffix', }, tickDecimals: 1, }; @@ -227,11 +217,9 @@ describe('Tick Formatters', function () { it('correctly renders small values', function () { const axis = { - options: { - units: { - prefix: 'prefix', - suffix: 'suffix', - }, + units: { + prefix: 'prefix', + suffix: 'suffix', }, tickDecimals: 3, }; From aac4b7789ad653cca64d06f23dc7860c0f3e22e0 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Mon, 1 Feb 2021 16:55:56 +0300 Subject: [PATCH 22/44] Fix CI --- .../public/components/area_series/index.tsx | 22 +++++-------------- .../public/components/bar_series/index.tsx | 21 +++++------------- 2 files changed, 11 insertions(+), 32 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/area_series/index.tsx b/src/plugins/vis_type_timelion/public/components/area_series/index.tsx index 58b093a778e032..a5b8b326d05e1a 100644 --- a/src/plugins/vis_type_timelion/public/components/area_series/index.tsx +++ b/src/plugins/vis_type_timelion/public/components/area_series/index.tsx @@ -1,20 +1,9 @@ /* - * 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. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. */ import React from 'react'; @@ -42,6 +31,7 @@ export function AreaSeriesComponent({ data, index }: { data: any; index: number stroke: data.color, strokeWidth: points.lineWidth || 2, visible: points.show ?? false, + shape: points.symbol, }, }, curve: lines.steps ? CurveType.CURVE_STEP : CurveType.LINEAR, diff --git a/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx b/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx index f3e05d69dbb407..af003ab072af24 100644 --- a/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx +++ b/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx @@ -1,20 +1,9 @@ /* - * 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. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. */ import React from 'react'; From 24a188a8faf601d61ab10ce199c61b9c52a0cbd5 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Thu, 4 Feb 2021 12:20:45 +0300 Subject: [PATCH 23/44] Fix eslint --- .../public/components/area_series/index.tsx | 6 +++--- .../public/components/bar_series/index.tsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/area_series/index.tsx b/src/plugins/vis_type_timelion/public/components/area_series/index.tsx index a5b8b326d05e1a..cd1030b5b7058e 100644 --- a/src/plugins/vis_type_timelion/public/components/area_series/index.tsx +++ b/src/plugins/vis_type_timelion/public/components/area_series/index.tsx @@ -1,9 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import React from 'react'; diff --git a/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx b/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx index af003ab072af24..f6fe290a55def8 100644 --- a/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx +++ b/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx @@ -1,9 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import React from 'react'; From d369fc4a9c3dcaac922829753f25557ac132c5b9 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Wed, 12 May 2021 18:37:50 +0300 Subject: [PATCH 24/44] Fix some gaps --- .../public/components/bar_series/index.tsx | 1 + .../components/timelion_vis_component.tsx | 26 ++++++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx b/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx index f6fe290a55def8..929a7e3983bca4 100644 --- a/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx +++ b/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx @@ -16,6 +16,7 @@ export function BarSeriesComponent({ data, index }: { data: any; index: number } rect: { fill: data.color, opacity: !bars.fill || bars.fill < 0 ? 1 : bars.fill, + widthPixel: bars.lineWidth, }, }, }; diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index f9953388bb1277..ae0e5d96fd520c 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -17,6 +17,8 @@ import { YDomainRange, BrushEndListener, PointerEvent, + LegendPositionConfig, + LayoutDirection, } from '@elastic/charts'; import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; @@ -109,21 +111,31 @@ function TimelionVisComponent({ setChart(newChart); }, [seriesList.list]); - // temp solution, will be changed after fix https://github.com/elastic/elastic-charts/issues/878 const getLegendPosition = useCallback(() => { const chartGlobal = chart[0]._global; + const legendPositionConf: LegendPositionConfig = { + floating: true, + vAlign: Position.Top, + hAlign: Position.Right, + direction: LayoutDirection.Vertical, + }; + switch (chartGlobal?.legend?.position) { case 'ne': - return Position.Right; + legendPositionConf.vAlign = Position.Top; + legendPositionConf.hAlign = Position.Right; case 'nw': - return Position.Left; + legendPositionConf.vAlign = Position.Top; + legendPositionConf.hAlign = Position.Left; case 'se': - return Position.Right; + legendPositionConf.vAlign = Position.Bottom; + legendPositionConf.hAlign = Position.Right; case 'sw': - return Position.Left; - default: - return Position.Left; + legendPositionConf.vAlign = Position.Bottom; + legendPositionConf.hAlign = Position.Left; } + + return legendPositionConf; }, [chart]); const brushEndListener = useCallback( From 15b6ec70b0fc22379cfb519506c35f23818ce31c Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Mon, 31 May 2021 18:45:37 +0300 Subject: [PATCH 25/44] Fix legend columns --- .../public/components/timelion_vis_component.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index ae0e5d96fd520c..d731aadeb7ed92 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -112,15 +112,25 @@ function TimelionVisComponent({ }, [seriesList.list]); const getLegendPosition = useCallback(() => { - const chartGlobal = chart[0]._global; + let chartLegendGlobal: Record = {}; + chart.forEach((series) => { + if (series._global?.legend) { + chartLegendGlobal = { + ...chartLegendGlobal, + ...series._global.legend, + }; + } + }); + const legendPositionConf: LegendPositionConfig = { floating: true, + floatingColumns: chartLegendGlobal?.noColumns ?? 1, vAlign: Position.Top, hAlign: Position.Right, direction: LayoutDirection.Vertical, }; - switch (chartGlobal?.legend?.position) { + switch (chartLegendGlobal?.position) { case 'ne': legendPositionConf.vAlign = Position.Top; legendPositionConf.hAlign = Position.Right; From 9944016b41d2648217ba5f792f3456cf403c7834 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Tue, 8 Jun 2021 21:21:24 +0300 Subject: [PATCH 26/44] Some fixes --- .../public/components/area_series/index.tsx | 8 +-- .../public/components/bar_series/index.tsx | 12 +++- .../components/timelion_vis_component.tsx | 69 +++++++++++-------- 3 files changed, 54 insertions(+), 35 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/area_series/index.tsx b/src/plugins/vis_type_timelion/public/components/area_series/index.tsx index cd1030b5b7058e..0bde77243c213b 100644 --- a/src/plugins/vis_type_timelion/public/components/area_series/index.tsx +++ b/src/plugins/vis_type_timelion/public/components/area_series/index.tsx @@ -16,7 +16,7 @@ export function AreaSeriesComponent({ data, index }: { data: any; index: number areaSeriesStyle: { line: { stroke: data.color, - strokeWidth: Number(lines.lineWidth) || 3, + strokeWidth: Number(lines.lineWidth) ?? 3, visible: lines.show ?? !points.show, }, area: { @@ -27,11 +27,11 @@ export function AreaSeriesComponent({ data, index }: { data: any; index: number point: { fill: points.fillColor, opacity: points.fill * 10 ?? 10, - radius: points.radius || 3, + radius: points.radius ?? 3, stroke: data.color, - strokeWidth: points.lineWidth || 2, + strokeWidth: points.lineWidth ?? 2, visible: points.show ?? false, - shape: points.symbol, + shape: points.symbol === 'cross' ? 'x' : points.symbol, }, }, curve: lines.steps ? CurveType.CURVE_STEP : CurveType.LINEAR, diff --git a/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx b/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx index 929a7e3983bca4..0212a4a62c9a1c 100644 --- a/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx +++ b/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx @@ -11,11 +11,21 @@ import { BarSeries, ScaleType } from '@elastic/charts'; export function BarSeriesComponent({ data, index }: { data: any; index: number }) { const bars = data.bars || {}; + let opacity = bars.fill; + + if (!bars.fill) { + opacity = 1; + } else if (bars.fill < 0) { + opacity = 0; + } else if (bars.fill > 1) { + opacity = 1; + } + const styles = { barSeriesStyle: { rect: { fill: data.color, - opacity: !bars.fill || bars.fill < 0 ? 1 : bars.fill, + opacity, widthPixel: bars.lineWidth, }, }, diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index d731aadeb7ed92..1149ce552bbb85 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -60,21 +60,25 @@ function TimelionVisComponent({ const updateYAxes = function (yaxes: IAxis[]) { yaxes.forEach((yaxis: IAxis) => { - if (yaxis.units) { - const formatters = tickFormatters(yaxis); - yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; - } else if (yaxis.tickDecimals) { - yaxis.tickFormatter = (val: number) => val.toFixed(yaxis.tickDecimals); - } + if (yaxis) { + if (yaxis.units) { + const formatters = tickFormatters(yaxis); + yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; + } else if (yaxis.tickDecimals) { + yaxis.tickFormatter = (val: number) => val.toFixed(yaxis.tickDecimals); + } + + yaxis.domain = { + fit: true, + }; - yaxis.domain = {}; + if (yaxis.max) { + yaxis.domain.max = yaxis.max; + } - if (yaxis.max) { - yaxis.domain.max = yaxis.max; - } - - if (yaxis.min) { - yaxis.domain.min = yaxis.min; + if (yaxis.min) { + yaxis.domain.min = yaxis.min; + } } }); }; @@ -126,7 +130,7 @@ function TimelionVisComponent({ floating: true, floatingColumns: chartLegendGlobal?.noColumns ?? 1, vAlign: Position.Top, - hAlign: Position.Right, + hAlign: Position.Left, direction: LayoutDirection.Vertical, }; @@ -134,15 +138,19 @@ function TimelionVisComponent({ case 'ne': legendPositionConf.vAlign = Position.Top; legendPositionConf.hAlign = Position.Right; + break; case 'nw': legendPositionConf.vAlign = Position.Top; legendPositionConf.hAlign = Position.Left; + break; case 'se': legendPositionConf.vAlign = Position.Bottom; legendPositionConf.hAlign = Position.Right; + break; case 'sw': legendPositionConf.vAlign = Position.Bottom; legendPositionConf.hAlign = Position.Left; + break; } return legendPositionConf; @@ -192,6 +200,18 @@ function TimelionVisComponent({ [interval, kibana.services.timefilter, kibana.services.uiSettings] ); + const yaxes = useMemo(() => { + const collectedYAxes = []; + chart.forEach((chartInst) => { + chartInst._global?.yaxes.forEach((yaxis) => { + if (yaxis) { + collectedYAxes.push(yaxis); + } + }); + }); + return collectedYAxes; + }, [chart]); + return (
{title}
@@ -211,8 +231,8 @@ function TimelionVisComponent({ externalPointerEvents={{ tooltip: { visible: false } }} /> - {chart[0]._global?.yaxes ? ( - chart[0]._global.yaxes.map((axis: IAxis, index: number) => { + {yaxes.length ? ( + yaxes.map((axis: IAxis, index: number) => { return ( ); }) ) : ( - + )} {chart.map((data, index) => { const key = `${index}-${data.label}`; if (data.bars) { return ; - } else { - return ; } + + return ; })}
From 010617c7b9b83db4447d3ad7bb72392f27001c04 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Wed, 9 Jun 2021 14:05:57 +0300 Subject: [PATCH 27/44] add 2 versions of Timeline app --- src/plugins/timelion/public/app.js | 4 +- .../timelion_expression_input_helpers.js | 4 +- src/plugins/timelion/public/index.scss | 7 +- .../public/panels/timechart/schema.ts | 18 +- .../vis_type_timelion/common/constants.ts | 18 + .../components/timelion_vis_component.tsx | 2 - src/plugins/vis_type_timelion/public/index.ts | 24 +- .../public/legacy/panel_utils.ts | 180 ++++++++ .../public/legacy/tick_formatters.test.ts | 222 +++++++++ .../public/legacy/tick_formatters.ts | 79 ++++ .../public/legacy/timelion_vis.scss} | 7 - .../public/legacy/timelion_vis_component.tsx | 428 ++++++++++++++++++ .../vis_type_timelion/public/plugin.ts | 6 +- .../public/timelion_vis_renderer.tsx | 8 +- .../vis_type_timelion/server/plugin.ts | 99 +--- .../vis_type_timelion/server/ui_settings.ts | 125 +++++ 16 files changed, 1098 insertions(+), 133 deletions(-) create mode 100644 src/plugins/vis_type_timelion/common/constants.ts create mode 100644 src/plugins/vis_type_timelion/public/legacy/panel_utils.ts create mode 100644 src/plugins/vis_type_timelion/public/legacy/tick_formatters.test.ts create mode 100644 src/plugins/vis_type_timelion/public/legacy/tick_formatters.ts rename src/plugins/{timelion/public/panels/timechart/_index.scss => vis_type_timelion/public/legacy/timelion_vis.scss} (89%) create mode 100644 src/plugins/vis_type_timelion/public/legacy/timelion_vis_component.tsx create mode 100644 src/plugins/vis_type_timelion/server/ui_settings.ts diff --git a/src/plugins/timelion/public/app.js b/src/plugins/timelion/public/app.js index 6db88ad65a1d1f..4a4b2be679dd35 100644 --- a/src/plugins/timelion/public/app.js +++ b/src/plugins/timelion/public/app.js @@ -21,7 +21,7 @@ import { registerListenEventListener, watchMultiDecorator, } from '../../kibana_legacy/public'; -import { getTimezone } from '../../vis_type_timelion/public'; +import { _LEGACY_ as visTypeTimelion } from '../../vis_type_timelion/public'; import { initCellsDirective } from './directives/cells/cells'; import { initFullscreenDirective } from './directives/fullscreen/fullscreen'; import { initFixedElementDirective } from './directives/fixed_element'; @@ -144,7 +144,7 @@ export function initTimelionApp(app, deps) { $scope.updatedSheets = []; const savedVisualizations = deps.plugins.visualizations.savedVisualizationsLoader; - const timezone = getTimezone(deps.core.uiSettings); + const timezone = visTypeTimelion.getTimezone(deps.core.uiSettings); const defaultExpression = '.es(*)'; diff --git a/src/plugins/timelion/public/directives/timelion_expression_input_helpers.js b/src/plugins/timelion/public/directives/timelion_expression_input_helpers.js index 2abfd2b1e7c7a9..0bc5897c49d6f4 100644 --- a/src/plugins/timelion/public/directives/timelion_expression_input_helpers.js +++ b/src/plugins/timelion/public/directives/timelion_expression_input_helpers.js @@ -7,7 +7,7 @@ */ import _ from 'lodash'; -import { parseTimelionExpressionAsync } from '../../../vis_type_timelion/public'; +import { _LEGACY_ as visTypeTimelion } from '../../../vis_type_timelion/public'; export const SUGGESTION_TYPE = { ARGUMENTS: 'arguments', @@ -180,7 +180,7 @@ async function extractSuggestionsFromParsedResult( export async function suggest(expression, functionList, cursorPosition, argValueSuggestions) { try { - const result = await parseTimelionExpressionAsync(expression); + const result = await visTypeTimelion.parseTimelionExpressionAsync(expression); return await extractSuggestionsFromParsedResult( result, cursorPosition, diff --git a/src/plugins/timelion/public/index.scss b/src/plugins/timelion/public/index.scss index dd0cf21e1441fc..7a4259b2a17c8a 100644 --- a/src/plugins/timelion/public/index.scss +++ b/src/plugins/timelion/public/index.scss @@ -10,4 +10,9 @@ @import './app'; @import './base'; @import './directives/index'; -@import './panels/timechart/index'; + +// these styles is needed to be loaded here explicitly if the timelion visualization was not opened in browser +// styles for timelion visualization are lazy loaded only while a vis is opened +// this will duplicate styles only if both Timelion app and timelion visualization are loaded +// could be left here as it is since the Timelion app is deprecated +@import '../../vis_type_timelion/public/legacy/timelion_vis.scss'; diff --git a/src/plugins/timelion/public/panels/timechart/schema.ts b/src/plugins/timelion/public/panels/timechart/schema.ts index 0381717fa45b04..dc26adc6ea5f56 100644 --- a/src/plugins/timelion/public/panels/timechart/schema.ts +++ b/src/plugins/timelion/public/panels/timechart/schema.ts @@ -11,13 +11,7 @@ import $ from 'jquery'; import moment from 'moment-timezone'; // @ts-ignore import observeResize from '../../lib/observe_resize'; -import { - calculateInterval, - DEFAULT_TIME_FORMAT, - tickFormatters, - xaxisFormatterProvider, - generateTicksProvider, -} from '../../../../vis_type_timelion/public'; +import { _LEGACY_ as visTypeTimelion } from '../../../../vis_type_timelion/public'; import { TimelionVisualizationDependencies } from '../../application'; const DEBOUNCE_DELAY = 50; @@ -37,8 +31,9 @@ export function timechartFn(dependencies: TimelionVisualizationDependencies) { help: 'Draw a timeseries chart', render($scope: any, $elem: any) { const template = '
'; - const getxAxisFormatter = xaxisFormatterProvider(uiSettings); - const generateTicks = generateTicksProvider(); + const formatters = visTypeTimelion.tickFormatters() as any; + const getxAxisFormatter = visTypeTimelion.xaxisFormatterProvider(uiSettings); + const generateTicks = visTypeTimelion.generateTicksProvider(); // TODO: I wonder if we should supply our own moment that sets this every time? // could just use angular's injection to provide a moment service? @@ -225,7 +220,7 @@ export function timechartFn(dependencies: TimelionVisualizationDependencies) { if (legendCaption) { legendCaption.text( moment(pos.x).format( - _.get(dataset, '[0]._global.legend.timeFormat', DEFAULT_TIME_FORMAT) + _.get(dataset, '[0]._global.legend.timeFormat', visTypeTimelion.DEFAULT_TIME_FORMAT) ) ); } @@ -288,7 +283,7 @@ export function timechartFn(dependencies: TimelionVisualizationDependencies) { // Get the X-axis tick format const time = timefilter.timefilter.getBounds() as any; - const interval = calculateInterval( + const interval = visTypeTimelion.calculateInterval( time.min.valueOf(), time.max.valueOf(), uiSettings.get('timelion:target_buckets') || 200, @@ -348,7 +343,6 @@ export function timechartFn(dependencies: TimelionVisualizationDependencies) { if (options.yaxes) { options.yaxes.forEach((yaxis: any) => { if (yaxis && yaxis.units) { - const formatters = tickFormatters(yaxis) as any; yaxis.tickFormatter = formatters[yaxis.units.type]; const byteModes = ['bytes', 'bytes/s']; if (byteModes.includes(yaxis.units.type)) { diff --git a/src/plugins/vis_type_timelion/common/constants.ts b/src/plugins/vis_type_timelion/common/constants.ts new file mode 100644 index 00000000000000..a97bdd855107ce --- /dev/null +++ b/src/plugins/vis_type_timelion/common/constants.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const UI_SETTINGS = { + LEGACY_CHARTS_LIBRARY: 'timelion:legacyChartsLibrary', + ES_TIMEFIELD: 'timelion:es.timefield', + DEFAULT_INDEX: 'timelion:es.default_index', + TARGET_BUCKETS: 'timelion:target_buckets', + MAX_BUCKETS: 'timelion:max_buckets', + MIN_INTERVAL: 'timelion:min_interval', + GRAPHITE_URL: 'timelion:graphite.url', + QUANDL_KEY: 'timelion:quandl.key', +}; diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index 1149ce552bbb85..8cacc80b0c162c 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -35,8 +35,6 @@ import { TimelionVisDependencies } from '../plugin'; import './timelion_vis.scss'; -const GRID_LINE_STROKE = 'rgba(125,125,125,0.3)'; - interface TimelionVisComponentProps { fireEvent: IInterpreterRenderHandlers['event']; interval: string; diff --git a/src/plugins/vis_type_timelion/public/index.ts b/src/plugins/vis_type_timelion/public/index.ts index fa257907a176dc..1ab572b497212a 100644 --- a/src/plugins/vis_type_timelion/public/index.ts +++ b/src/plugins/vis_type_timelion/public/index.ts @@ -9,16 +9,26 @@ import { PluginInitializerContext } from 'kibana/public'; import { TimelionVisPlugin as Plugin } from './plugin'; +import { tickFormatters } from './legacy/tick_formatters'; +import { getTimezone } from './helpers/get_timezone'; +import { xaxisFormatterProvider } from './helpers/xaxis_formatter'; +import { generateTicksProvider } from './helpers/tick_generator'; +import { DEFAULT_TIME_FORMAT, calculateInterval } from '../common/lib'; +import { parseTimelionExpressionAsync } from '../common/parser_async'; + export function plugin(initializerContext: PluginInitializerContext) { return new Plugin(initializerContext); } -export { getTimezone } from './helpers/get_timezone'; -export { tickFormatters } from './helpers/tick_formatters'; -export { xaxisFormatterProvider } from './helpers/xaxis_formatter'; -export { generateTicksProvider } from './helpers/tick_generator'; - -export { DEFAULT_TIME_FORMAT, calculateInterval } from '../common/lib'; -export { parseTimelionExpressionAsync } from '../common/parser_async'; +// This export should be removed on removing Timeline APP +export const _LEGACY_ = { + DEFAULT_TIME_FORMAT, + calculateInterval, + parseTimelionExpressionAsync, + tickFormatters, + getTimezone, + xaxisFormatterProvider, + generateTicksProvider, +}; export { VisTypeTimelionPluginStart, VisTypeTimelionPluginSetup } from './plugin'; diff --git a/src/plugins/vis_type_timelion/public/legacy/panel_utils.ts b/src/plugins/vis_type_timelion/public/legacy/panel_utils.ts new file mode 100644 index 00000000000000..0a00cc039ae752 --- /dev/null +++ b/src/plugins/vis_type_timelion/public/legacy/panel_utils.ts @@ -0,0 +1,180 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { cloneDeep, defaults, mergeWith, compact } from 'lodash'; +import $ from 'jquery'; +import moment, { Moment } from 'moment-timezone'; + +import { TimefilterContract } from 'src/plugins/data/public'; +import { IUiSettingsClient } from 'kibana/public'; + +import { calculateInterval } from '../../common/lib'; +import { xaxisFormatterProvider } from '../helpers/xaxis_formatter'; +import { Series } from '../helpers/timelion_request_handler'; + +export interface Axis { + delta?: number; + max?: number; + min?: number; + mode: string; + options?: { + units: { prefix: string; suffix: string }; + }; + tickSize?: number; + ticks: number; + tickLength: number; + timezone: string; + tickDecimals?: number; + tickFormatter: ((val: number) => string) | ((val: number, axis: Axis) => string); + tickGenerator?(axis: Axis): number[]; + units?: { type: string }; +} + +interface TimeRangeBounds { + min: Moment | undefined; + max: Moment | undefined; +} + +export const ACTIVE_CURSOR = 'ACTIVE_CURSOR_TIMELION'; +export const eventBus = $({}); + +const colors = [ + '#01A4A4', + '#C66', + '#D0D102', + '#616161', + '#00A1CB', + '#32742C', + '#F18D05', + '#113F8C', + '#61AE24', + '#D70060', +]; + +const SERIES_ID_ATTR = 'data-series-id'; + +function buildSeriesData(chart: Series[], options: jquery.flot.plotOptions) { + const seriesData = chart.map((series: Series, seriesIndex: number) => { + const newSeries: Series = cloneDeep( + defaults(series, { + shadowSize: 0, + lines: { + lineWidth: 3, + }, + }) + ); + + newSeries._id = seriesIndex; + + if (series.color) { + const span = document.createElement('span'); + span.style.color = series.color; + newSeries.color = span.style.color; + } + + if (series._hide) { + newSeries.data = []; + newSeries.stack = false; + newSeries.label = `(hidden) ${series.label}`; + } + + if (series._global) { + mergeWith(options, series._global, (objVal, srcVal) => { + // This is kind of gross, it means that you can't replace a global value with a null + // best you can do is an empty string. Deal with it. + if (objVal == null) { + return srcVal; + } + if (srcVal == null) { + return objVal; + } + }); + } + + return newSeries; + }); + + return compact(seriesData); +} + +function buildOptions( + intervalValue: string, + timefilter: TimefilterContract, + uiSettings: IUiSettingsClient, + clientWidth = 0, + showGrid?: boolean +) { + // Get the X-axis tick format + const time: TimeRangeBounds = timefilter.getBounds(); + const interval = calculateInterval( + (time.min && time.min.valueOf()) || 0, + (time.max && time.max.valueOf()) || 0, + uiSettings.get('timelion:target_buckets') || 200, + intervalValue, + uiSettings.get('timelion:min_interval') || '1ms' + ); + const format = xaxisFormatterProvider(uiSettings)(interval); + + const tickLetterWidth = 7; + const tickPadding = 45; + + const options = { + xaxis: { + mode: 'time', + tickLength: 5, + timezone: 'browser', + // Calculate how many ticks can fit on the axis + ticks: Math.floor(clientWidth / (format.length * tickLetterWidth + tickPadding)), + // Use moment to format ticks so we get timezone correction + tickFormatter: (val: number) => moment(val).format(format), + }, + selection: { + mode: 'x', + color: '#ccc', + }, + crosshair: { + mode: 'x', + color: '#C66', + lineWidth: 2, + }, + colors, + grid: { + show: showGrid, + borderWidth: 0, + borderColor: null, + margin: 10, + hoverable: true, + autoHighlight: false, + }, + legend: { + backgroundColor: 'rgb(255,255,255,0)', + position: 'nw', + labelBoxBorderColor: 'rgb(255,255,255,0)', + labelFormatter(label: string, series: { _id: number }) { + const wrapperSpan = document.createElement('span'); + const labelSpan = document.createElement('span'); + const numberSpan = document.createElement('span'); + + wrapperSpan.setAttribute('class', 'ngLegendValue'); + wrapperSpan.setAttribute(SERIES_ID_ATTR, `${series._id}`); + + labelSpan.appendChild(document.createTextNode(label)); + numberSpan.setAttribute('class', 'ngLegendValueNumber'); + + wrapperSpan.appendChild(labelSpan); + wrapperSpan.appendChild(numberSpan); + + return wrapperSpan.outerHTML; + }, + }, + } as jquery.flot.plotOptions & { yaxes?: Axis[] }; + + return options; +} + +export { buildSeriesData, buildOptions, SERIES_ID_ATTR, colors }; diff --git a/src/plugins/vis_type_timelion/public/legacy/tick_formatters.test.ts b/src/plugins/vis_type_timelion/public/legacy/tick_formatters.test.ts new file mode 100644 index 00000000000000..03b7c217069577 --- /dev/null +++ b/src/plugins/vis_type_timelion/public/legacy/tick_formatters.test.ts @@ -0,0 +1,222 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { tickFormatters } from './tick_formatters'; + +describe('Tick Formatters', function () { + let formatters: any; + + beforeEach(function () { + formatters = tickFormatters(); + }); + + describe('Bits mode', function () { + let bitFormatter: any; + beforeEach(function () { + bitFormatter = formatters.bits; + }); + + it('is a function', function () { + expect(bitFormatter).toEqual(expect.any(Function)); + }); + + it('formats with b/kb/mb/gb', function () { + expect(bitFormatter(7)).toEqual('7b'); + expect(bitFormatter(4 * 1000)).toEqual('4kb'); + expect(bitFormatter(4.1 * 1000 * 1000)).toEqual('4.1mb'); + expect(bitFormatter(3 * 1000 * 1000 * 1000)).toEqual('3gb'); + }); + + it('formats negative values with b/kb/mb/gb', () => { + expect(bitFormatter(-7)).toEqual('-7b'); + expect(bitFormatter(-4 * 1000)).toEqual('-4kb'); + expect(bitFormatter(-4.1 * 1000 * 1000)).toEqual('-4.1mb'); + expect(bitFormatter(-3 * 1000 * 1000 * 1000)).toEqual('-3gb'); + }); + }); + + describe('Bits/s mode', function () { + let bitsFormatter: any; + beforeEach(function () { + bitsFormatter = formatters['bits/s']; + }); + + it('is a function', function () { + expect(bitsFormatter).toEqual(expect.any(Function)); + }); + + it('formats with b/kb/mb/gb', function () { + expect(bitsFormatter(7)).toEqual('7b/s'); + expect(bitsFormatter(4 * 1000)).toEqual('4kb/s'); + expect(bitsFormatter(4.1 * 1000 * 1000)).toEqual('4.1mb/s'); + expect(bitsFormatter(3 * 1000 * 1000 * 1000)).toEqual('3gb/s'); + }); + + it('formats negative values with b/kb/mb/gb', function () { + expect(bitsFormatter(-7)).toEqual('-7b/s'); + expect(bitsFormatter(-4 * 1000)).toEqual('-4kb/s'); + expect(bitsFormatter(-4.1 * 1000 * 1000)).toEqual('-4.1mb/s'); + expect(bitsFormatter(-3 * 1000 * 1000 * 1000)).toEqual('-3gb/s'); + }); + }); + + describe('Bytes mode', function () { + let byteFormatter: any; + beforeEach(function () { + byteFormatter = formatters.bytes; + }); + + it('is a function', function () { + expect(byteFormatter).toEqual(expect.any(Function)); + }); + + it('formats with B/KB/MB/GB', function () { + expect(byteFormatter(10)).toEqual('10B'); + expect(byteFormatter(10 * 1024)).toEqual('10KB'); + expect(byteFormatter(10.2 * 1024 * 1024)).toEqual('10.2MB'); + expect(byteFormatter(3 * 1024 * 1024 * 1024)).toEqual('3GB'); + }); + + it('formats negative values with B/KB/MB/GB', function () { + expect(byteFormatter(-10)).toEqual('-10B'); + expect(byteFormatter(-10 * 1024)).toEqual('-10KB'); + expect(byteFormatter(-10.2 * 1024 * 1024)).toEqual('-10.2MB'); + expect(byteFormatter(-3 * 1024 * 1024 * 1024)).toEqual('-3GB'); + }); + }); + + describe('Bytes/s mode', function () { + let bytesFormatter: any; + beforeEach(function () { + bytesFormatter = formatters['bytes/s']; + }); + + it('is a function', function () { + expect(bytesFormatter).toEqual(expect.any(Function)); + }); + + it('formats with B/KB/MB/GB', function () { + expect(bytesFormatter(10)).toEqual('10B/s'); + expect(bytesFormatter(10 * 1024)).toEqual('10KB/s'); + expect(bytesFormatter(10.2 * 1024 * 1024)).toEqual('10.2MB/s'); + expect(bytesFormatter(3 * 1024 * 1024 * 1024)).toEqual('3GB/s'); + }); + + it('formats negative values with B/KB/MB/GB', function () { + expect(bytesFormatter(-10)).toEqual('-10B/s'); + expect(bytesFormatter(-10 * 1024)).toEqual('-10KB/s'); + expect(bytesFormatter(-10.2 * 1024 * 1024)).toEqual('-10.2MB/s'); + expect(bytesFormatter(-3 * 1024 * 1024 * 1024)).toEqual('-3GB/s'); + }); + }); + + describe('Currency mode', function () { + let currencyFormatter: any; + beforeEach(function () { + currencyFormatter = formatters.currency; + }); + + it('is a function', function () { + expect(currencyFormatter).toEqual(expect.any(Function)); + }); + + it('formats with $ by default', function () { + const axis = { + options: { + units: {}, + }, + }; + expect(currencyFormatter(10.2, axis)).toEqual('$10.20'); + }); + + it('accepts currency in ISO 4217', function () { + const axis = { + options: { + units: { + prefix: 'CNY', + }, + }, + }; + + expect(currencyFormatter(10.2, axis)).toEqual('CN¥10.20'); + }); + }); + + describe('Percent mode', function () { + let percentFormatter: any; + beforeEach(function () { + percentFormatter = formatters.percent; + }); + + it('is a function', function () { + expect(percentFormatter).toEqual(expect.any(Function)); + }); + + it('formats with %', function () { + const axis = { + options: { + units: {}, + }, + }; + expect(percentFormatter(0.1234, axis)).toEqual('12%'); + }); + + it('formats with % with decimal precision', function () { + const tickDecimals = 3; + const tickDecimalShift = 2; + const axis = { + tickDecimals: tickDecimals + tickDecimalShift, + options: { + units: { + tickDecimalsShift: tickDecimalShift, + }, + }, + }; + expect(percentFormatter(0.12345, axis)).toEqual('12.345%'); + }); + }); + + describe('Custom mode', function () { + let customFormatter: any; + beforeEach(function () { + customFormatter = formatters.custom; + }); + + it('is a function', function () { + expect(customFormatter).toEqual(expect.any(Function)); + }); + + it('accepts prefix and suffix', function () { + const axis = { + options: { + units: { + prefix: 'prefix', + suffix: 'suffix', + }, + }, + tickDecimals: 1, + }; + + expect(customFormatter(10.2, axis)).toEqual('prefix10.2suffix'); + }); + + it('correctly renders small values', function () { + const axis = { + options: { + units: { + prefix: 'prefix', + suffix: 'suffix', + }, + }, + tickDecimals: 3, + }; + + expect(customFormatter(0.00499999999999999, axis)).toEqual('prefix0.005suffix'); + }); + }); +}); diff --git a/src/plugins/vis_type_timelion/public/legacy/tick_formatters.ts b/src/plugins/vis_type_timelion/public/legacy/tick_formatters.ts new file mode 100644 index 00000000000000..43b84d4aaa42b6 --- /dev/null +++ b/src/plugins/vis_type_timelion/public/legacy/tick_formatters.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { get } from 'lodash'; + +import type { Axis } from './panel_utils'; + +function baseTickFormatter(value: number, axis: Axis) { + const factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; + const formatted = '' + Math.round(value * factor) / factor; + + // If tickDecimals was specified, ensure that we have exactly that + // much precision; otherwise default to the value's own precision. + + if (axis.tickDecimals != null) { + const decimal = formatted.indexOf('.'); + const precision = decimal === -1 ? 0 : formatted.length - decimal - 1; + if (precision < axis.tickDecimals) { + return ( + (precision ? formatted : formatted + '.') + + ('' + factor).substr(1, axis.tickDecimals - precision) + ); + } + } + + return formatted; +} + +function unitFormatter(divisor: number, units: string[]) { + return (val: number) => { + let index = 0; + const isNegative = val < 0; + val = Math.abs(val); + while (val >= divisor && index < units.length) { + val /= divisor; + index++; + } + const value = (Math.round(val * 100) / 100) * (isNegative ? -1 : 1); + return `${value}${units[index]}`; + }; +} + +export function tickFormatters() { + return { + bits: unitFormatter(1000, ['b', 'kb', 'mb', 'gb', 'tb', 'pb']), + 'bits/s': unitFormatter(1000, ['b/s', 'kb/s', 'mb/s', 'gb/s', 'tb/s', 'pb/s']), + bytes: unitFormatter(1024, ['B', 'KB', 'MB', 'GB', 'TB', 'PB']), + 'bytes/s': unitFormatter(1024, ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s']), + currency(val: number, axis: Axis) { + return val.toLocaleString('en', { + style: 'currency', + currency: (axis && axis.options && axis.options.units.prefix) || 'USD', + }); + }, + percent(val: number, axis: Axis) { + let precision = + get(axis, 'tickDecimals', 0) - get(axis, 'options.units.tickDecimalsShift', 0); + // toFixed only accepts values between 0 and 20 + if (precision < 0) { + precision = 0; + } else if (precision > 20) { + precision = 20; + } + + return (val * 100).toFixed(precision) + '%'; + }, + custom(val: number, axis: Axis) { + const formattedVal = baseTickFormatter(val, axis); + const prefix = axis && axis.options && axis.options.units.prefix; + const suffix = axis && axis.options && axis.options.units.suffix; + return prefix + formattedVal + suffix; + }, + }; +} diff --git a/src/plugins/timelion/public/panels/timechart/_index.scss b/src/plugins/vis_type_timelion/public/legacy/timelion_vis.scss similarity index 89% rename from src/plugins/timelion/public/panels/timechart/_index.scss rename to src/plugins/vis_type_timelion/public/legacy/timelion_vis.scss index bfafd49ae08b52..c4d591bc82cad9 100644 --- a/src/plugins/timelion/public/panels/timechart/_index.scss +++ b/src/plugins/vis_type_timelion/public/legacy/timelion_vis.scss @@ -58,10 +58,3 @@ white-space: nowrap; font-weight: $euiFontWeightBold; } - -.chart-top-title { - @include euiFontSizeXS; - flex: 0; - text-align: center; - font-weight: $euiFontWeightBold; -} \ No newline at end of file diff --git a/src/plugins/vis_type_timelion/public/legacy/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/legacy/timelion_vis_component.tsx new file mode 100644 index 00000000000000..eed52f36acedec --- /dev/null +++ b/src/plugins/vis_type_timelion/public/legacy/timelion_vis_component.tsx @@ -0,0 +1,428 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useState, useEffect, useMemo, useCallback } from 'react'; +import $ from 'jquery'; +import moment from 'moment-timezone'; +import { debounce, compact, get, each, cloneDeep, last, map } from 'lodash'; +import { useResizeObserver } from '@elastic/eui'; + +import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { useKibana } from '../../../kibana_react/public'; +import { DEFAULT_TIME_FORMAT } from '../../common/lib'; + +import { + buildSeriesData, + buildOptions, + SERIES_ID_ATTR, + colors, + Axis, + ACTIVE_CURSOR, + eventBus, +} from './panel_utils'; + +import { Series, Sheet } from '../helpers/timelion_request_handler'; +import { tickFormatters } from './tick_formatters'; +import { generateTicksProvider } from '../helpers/tick_generator'; +import { TimelionVisDependencies } from '../plugin'; + +import './timelion_vis.scss'; + +interface CrosshairPlot extends jquery.flot.plot { + setCrosshair: (pos: Position) => void; + clearCrosshair: () => void; +} + +interface TimelionVisComponentProps { + fireEvent: IInterpreterRenderHandlers['event']; + interval: string; + seriesList: Sheet; + renderComplete: IInterpreterRenderHandlers['done']; +} + +interface Position { + x: number; + x1: number; + y: number; + y1: number; + pageX: number; + pageY: number; +} + +interface Range { + to: number; + from: number; +} + +interface Ranges { + xaxis: Range; + yaxis: Range; +} + +const DEBOUNCE_DELAY = 50; +// ensure legend is the same height with or without a caption so legend items do not move around +const emptyCaption = '
'; + +function TimelionVisComponent({ + interval, + seriesList, + renderComplete, + fireEvent, +}: TimelionVisComponentProps) { + const kibana = useKibana(); + const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); + const [canvasElem, setCanvasElem] = useState(); + const [chartElem, setChartElem] = useState(null); + + const [originalColorMap, setOriginalColorMap] = useState(() => new Map()); + + const [highlightedSeries, setHighlightedSeries] = useState(null); + const [focusedSeries, setFocusedSeries] = useState(); + const [plot, setPlot] = useState(); + + // Used to toggle the series, and for displaying values on hover + const [legendValueNumbers, setLegendValueNumbers] = useState>(); + const [legendCaption, setLegendCaption] = useState>(); + + const canvasRef = useCallback((node: HTMLDivElement | null) => { + if (node !== null) { + setCanvasElem(node); + } + }, []); + + const elementRef = useCallback((node: HTMLDivElement | null) => { + if (node !== null) { + setChartElem(node); + } + }, []); + + useEffect( + () => () => { + if (chartElem) { + $(chartElem).off('plotselected').off('plothover').off('mouseleave'); + } + }, + [chartElem] + ); + + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + const highlightSeries = useCallback( + debounce(({ currentTarget }: JQuery.TriggeredEvent) => { + const id = Number(currentTarget.getAttribute(SERIES_ID_ATTR)); + if (highlightedSeries === id) { + return; + } + + setHighlightedSeries(id); + setChart((chartState) => + chartState.map((series: Series, seriesIndex: number) => { + series.color = + seriesIndex === id + ? originalColorMap.get(series) // color it like it was + : 'rgba(128,128,128,0.1)'; // mark as grey + + return series; + }) + ); + }, DEBOUNCE_DELAY), + [originalColorMap, highlightedSeries] + ); + + const focusSeries = useCallback( + (event: JQuery.TriggeredEvent) => { + const id = Number(event.currentTarget.getAttribute(SERIES_ID_ATTR)); + setFocusedSeries(id); + highlightSeries(event); + }, + [highlightSeries] + ); + + const toggleSeries = useCallback(({ currentTarget }: JQuery.TriggeredEvent) => { + const id = Number(currentTarget.getAttribute(SERIES_ID_ATTR)); + + setChart((chartState) => + chartState.map((series: Series, seriesIndex: number) => { + if (seriesIndex === id) { + series._hide = !series._hide; + } + return series; + }) + ); + }, []); + + const updateCaption = useCallback( + (plotData: any) => { + if (canvasElem && get(plotData, '[0]._global.legend.showTime', true)) { + const caption = $(''); + caption.html(emptyCaption); + setLegendCaption(caption); + + const canvasNode = $(canvasElem); + canvasNode.find('div.legend table').append(caption); + setLegendValueNumbers(canvasNode.find('.ngLegendValueNumber')); + + const legend = $(canvasElem).find('.ngLegendValue'); + if (legend) { + legend.click(toggleSeries); + legend.focus(focusSeries); + legend.mouseover(highlightSeries); + } + + // legend has been re-created. Apply focus on legend element when previously set + if (focusedSeries || focusedSeries === 0) { + canvasNode.find('div.legend table .legendLabel>span').get(focusedSeries).focus(); + } + } + }, + [focusedSeries, canvasElem, toggleSeries, focusSeries, highlightSeries] + ); + + const updatePlot = useCallback( + (chartValue: Series[], grid?: boolean) => { + if (canvasElem && canvasElem.clientWidth > 0 && canvasElem.clientHeight > 0) { + const options = buildOptions( + interval, + kibana.services.timefilter, + kibana.services.uiSettings, + chartElem?.clientWidth, + grid + ); + const updatedSeries = buildSeriesData(chartValue, options); + + if (options.yaxes) { + options.yaxes.forEach((yaxis: Axis) => { + if (yaxis && yaxis.units) { + const formatters = tickFormatters(); + yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; + const byteModes = ['bytes', 'bytes/s']; + if (byteModes.includes(yaxis.units.type)) { + yaxis.tickGenerator = generateTicksProvider(); + } + } + }); + } + + const newPlot = $.plot($(canvasElem), updatedSeries, options); + setPlot(newPlot); + renderComplete(); + + updateCaption(newPlot.getData()); + } + }, + [canvasElem, chartElem?.clientWidth, renderComplete, kibana.services, interval, updateCaption] + ); + + const dimensions = useResizeObserver(chartElem); + + useEffect(() => { + updatePlot(chart, seriesList.render && seriesList.render.grid); + }, [chart, updatePlot, seriesList.render, dimensions]); + + useEffect(() => { + const colorsSet: Array<[Series, string]> = []; + const newChart = seriesList.list.map((series: Series, seriesIndex: number) => { + const newSeries = { ...series }; + if (!newSeries.color) { + const colorIndex = seriesIndex % colors.length; + newSeries.color = colors[colorIndex]; + } + colorsSet.push([newSeries, newSeries.color]); + return newSeries; + }); + setChart(newChart); + setOriginalColorMap(new Map(colorsSet)); + }, [seriesList.list]); + + const unhighlightSeries = useCallback(() => { + if (highlightedSeries === null) { + return; + } + + setHighlightedSeries(null); + setFocusedSeries(null); + + setChart((chartState) => + chartState.map((series: Series) => { + series.color = originalColorMap.get(series); // reset the colors + return series; + }) + ); + }, [originalColorMap, highlightedSeries]); + + // Shamelessly borrowed from the flotCrosshairs example + const setLegendNumbers = useCallback( + (pos: Position) => { + unhighlightSeries(); + + const axes = plot!.getAxes(); + if (pos.x < axes.xaxis.min! || pos.x > axes.xaxis.max!) { + return; + } + + const dataset = plot!.getData(); + if (legendCaption) { + legendCaption.text( + moment(pos.x).format(get(dataset, '[0]._global.legend.timeFormat', DEFAULT_TIME_FORMAT)) + ); + } + for (let i = 0; i < dataset.length; ++i) { + const series = dataset[i]; + const useNearestPoint = series.lines!.show && !series.lines!.steps; + const precision = get(series, '_meta.precision', 2); + + // We're setting this flag on top on the series object belonging to the flot library, so we're simply casting here. + if ((series as { _hide?: boolean })._hide) { + continue; + } + + const currentPoint = series.data.find((point: [number, number], index: number) => { + if (index + 1 === series.data.length) { + return true; + } + if (useNearestPoint) { + return pos.x - point[0] < series.data[index + 1][0] - pos.x; + } else { + return pos.x < series.data[index + 1][0]; + } + }); + + const y = currentPoint[1]; + + if (legendValueNumbers) { + if (y == null) { + legendValueNumbers.eq(i).empty(); + } else { + let label = y.toFixed(precision); + const formatter = ((series.yaxis as unknown) as Axis).tickFormatter; + if (formatter) { + label = formatter(Number(label), (series.yaxis as unknown) as Axis); + } + legendValueNumbers.eq(i).text(`(${label})`); + } + } + } + }, + [plot, legendValueNumbers, unhighlightSeries, legendCaption] + ); + + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + const debouncedSetLegendNumbers = useCallback( + debounce(setLegendNumbers, DEBOUNCE_DELAY, { + maxWait: DEBOUNCE_DELAY, + leading: true, + trailing: false, + }), + [setLegendNumbers] + ); + + const clearLegendNumbers = useCallback(() => { + if (legendCaption) { + legendCaption.html(emptyCaption); + } + each(legendValueNumbers!, (num: Node) => { + $(num).empty(); + }); + }, [legendCaption, legendValueNumbers]); + + const plotHover = useCallback( + (pos: Position) => { + (plot as CrosshairPlot).setCrosshair(pos); + debouncedSetLegendNumbers(pos); + }, + [plot, debouncedSetLegendNumbers] + ); + + const plotHoverHandler = useCallback( + (event: JQuery.TriggeredEvent, pos: Position) => { + if (!plot) { + return; + } + plotHover(pos); + eventBus.trigger(ACTIVE_CURSOR, [event, pos]); + }, + [plot, plotHover] + ); + + useEffect(() => { + const updateCursor = (_: any, event: JQuery.TriggeredEvent, pos: Position) => { + if (!plot) { + return; + } + plotHover(pos); + }; + + eventBus.on(ACTIVE_CURSOR, updateCursor); + + return () => { + eventBus.off(ACTIVE_CURSOR, updateCursor); + }; + }, [plot, plotHover]); + + const mouseLeaveHandler = useCallback(() => { + if (!plot) { + return; + } + (plot as CrosshairPlot).clearCrosshair(); + clearLegendNumbers(); + }, [plot, clearLegendNumbers]); + + const plotSelectedHandler = useCallback( + (event: JQuery.TriggeredEvent, ranges: Ranges) => { + fireEvent({ + name: 'applyFilter', + data: { + timeFieldName: '*', + filters: [ + { + range: { + '*': { + gte: ranges.xaxis.from, + lte: ranges.xaxis.to, + }, + }, + }, + ], + }, + }); + }, + [fireEvent] + ); + + useEffect(() => { + if (chartElem) { + $(chartElem).off('plotselected').on('plotselected', plotSelectedHandler); + } + }, [chartElem, plotSelectedHandler]); + + useEffect(() => { + if (chartElem) { + $(chartElem).off('mouseleave').on('mouseleave', mouseLeaveHandler); + } + }, [chartElem, mouseLeaveHandler]); + + useEffect(() => { + if (chartElem) { + $(chartElem).off('plothover').on('plothover', plotHoverHandler); + } + }, [chartElem, plotHoverHandler]); + + const title: string = useMemo(() => last(compact(map(seriesList.list, '_title'))) || '', [ + seriesList.list, + ]); + + return ( +
+
{title}
+
+
+ ); +} + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { TimelionVisComponent as default }; diff --git a/src/plugins/vis_type_timelion/public/plugin.ts b/src/plugins/vis_type_timelion/public/plugin.ts index 7b27e72a818aa2..93712ae4507feb 100644 --- a/src/plugins/vis_type_timelion/public/plugin.ts +++ b/src/plugins/vis_type_timelion/public/plugin.ts @@ -75,12 +75,12 @@ export class TimelionVisPlugin constructor(public initializerContext: PluginInitializerContext) {} public setup( - core: CoreSetup, + { uiSettings, http }: CoreSetup, { expressions, visualizations, data, charts }: TimelionVisSetupDependencies ) { const dependencies: TimelionVisDependencies = { - uiSettings: core.uiSettings, - http: core.http, + http, + uiSettings, timefilter: data.query.timefilter.timefilter, chartTheme: charts.theme, }; diff --git a/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx b/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx index f8d7d16725643c..75b69a8a7d0fd4 100644 --- a/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx +++ b/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx @@ -14,8 +14,10 @@ import { KibanaContextProvider } from '../../kibana_react/public'; import { VisualizationContainer } from '../../visualizations/public'; import { TimelionVisDependencies } from './plugin'; import { TimelionRenderValue } from './timelion_vis_fn'; +import { UI_SETTINGS } from '../common/constants'; const TimelionVisComponent = lazy(() => import('./components/timelion_vis_component')); +const TimelionVisLegacyComponent = lazy(() => import('./legacy/timelion_vis_component')); export const getTimelionVisRenderer: ( deps: TimelionVisDependencies @@ -31,10 +33,14 @@ export const getTimelionVisRenderer: ( const [seriesList] = visData.sheet; const showNoResult = !seriesList || !seriesList.list.length; + const VisComponent = deps.uiSettings.get(UI_SETTINGS.LEGACY_CHARTS_LIBRARY, false) + ? TimelionVisLegacyComponent + : TimelionVisComponent; + render( - URL of your graphite host', - values: { experimentalLabel: `[${experimentalLabel}]` }, - }), - type: 'select', - options: config.graphiteUrls || [], - category: ['timelion'], - schema: schema.nullable(schema.string()), - }, - 'timelion:quandl.key': { - name: i18n.translate('timelion.uiSettings.quandlKeyLabel', { - defaultMessage: 'Quandl key', - }), - value: 'someKeyHere', - description: i18n.translate('timelion.uiSettings.quandlKeyDescription', { - defaultMessage: '{experimentalLabel} Your API key from www.quandl.com', - values: { experimentalLabel: `[${experimentalLabel}]` }, - }), - sensitive: true, - category: ['timelion'], - schema: schema.string(), - }, - }); + core.uiSettings.register(getUiSettings(config)); return deepFreeze({ uiEnabled: config.ui.enabled }); } diff --git a/src/plugins/vis_type_timelion/server/ui_settings.ts b/src/plugins/vis_type_timelion/server/ui_settings.ts new file mode 100644 index 00000000000000..37ac657866f865 --- /dev/null +++ b/src/plugins/vis_type_timelion/server/ui_settings.ts @@ -0,0 +1,125 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import { schema, TypeOf } from '@kbn/config-schema'; +import type { UiSettingsParams } from 'kibana/server'; + +import { UI_SETTINGS } from '../common/constants'; +import { configSchema } from '../config'; + +const experimentalLabel = i18n.translate('timelion.uiSettings.experimentalLabel', { + defaultMessage: 'experimental', +}); + +export function getUiSettings( + config: TypeOf +): Record> { + return { + [UI_SETTINGS.LEGACY_CHARTS_LIBRARY]: { + name: i18n.translate('timelion.uiSettings.legacyChartsLibraryLabel', { + defaultMessage: 'Legacy charts library', + }), + description: i18n.translate('timelion.uiSettings.legacyChartsLibraryDescription', { + defaultMessage: 'Enables legacy charts library for Timeline visualizations', + }), + value: false, + category: ['timelion'], + schema: schema.boolean(), + }, + + [UI_SETTINGS.ES_TIMEFIELD]: { + name: i18n.translate('timelion.uiSettings.timeFieldLabel', { + defaultMessage: 'Time field', + }), + value: '@timestamp', + description: i18n.translate('timelion.uiSettings.timeFieldDescription', { + defaultMessage: 'Default field containing a timestamp when using {esParam}', + values: { esParam: '.es()' }, + }), + category: ['timelion'], + schema: schema.string(), + }, + [UI_SETTINGS.DEFAULT_INDEX]: { + name: i18n.translate('timelion.uiSettings.defaultIndexLabel', { + defaultMessage: 'Default index', + }), + value: '_all', + description: i18n.translate('timelion.uiSettings.defaultIndexDescription', { + defaultMessage: 'Default elasticsearch index to search with {esParam}', + values: { esParam: '.es()' }, + }), + category: ['timelion'], + schema: schema.string(), + }, + [UI_SETTINGS.TARGET_BUCKETS]: { + name: i18n.translate('timelion.uiSettings.targetBucketsLabel', { + defaultMessage: 'Target buckets', + }), + value: 200, + description: i18n.translate('timelion.uiSettings.targetBucketsDescription', { + defaultMessage: 'The number of buckets to shoot for when using auto intervals', + }), + category: ['timelion'], + schema: schema.number(), + }, + [UI_SETTINGS.MAX_BUCKETS]: { + name: i18n.translate('timelion.uiSettings.maximumBucketsLabel', { + defaultMessage: 'Maximum buckets', + }), + value: 2000, + description: i18n.translate('timelion.uiSettings.maximumBucketsDescription', { + defaultMessage: 'The maximum number of buckets a single datasource can return', + }), + category: ['timelion'], + schema: schema.number(), + }, + [UI_SETTINGS.MIN_INTERVAL]: { + name: i18n.translate('timelion.uiSettings.minimumIntervalLabel', { + defaultMessage: 'Minimum interval', + }), + value: '1ms', + description: i18n.translate('timelion.uiSettings.minimumIntervalDescription', { + defaultMessage: 'The smallest interval that will be calculated when using "auto"', + description: '"auto" is a technical value in that context, that should not be translated.', + }), + category: ['timelion'], + schema: schema.string(), + }, + [UI_SETTINGS.GRAPHITE_URL]: { + name: i18n.translate('timelion.uiSettings.graphiteURLLabel', { + defaultMessage: 'Graphite URL', + description: + 'The URL should be in the form of https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite', + }), + value: config.graphiteUrls && config.graphiteUrls.length ? config.graphiteUrls[0] : null, + description: i18n.translate('timelion.uiSettings.graphiteURLDescription', { + defaultMessage: + '{experimentalLabel} The URL of your graphite host', + values: { experimentalLabel: `[${experimentalLabel}]` }, + }), + type: 'select', + options: config.graphiteUrls || [], + category: ['timelion'], + schema: schema.nullable(schema.string()), + }, + [UI_SETTINGS.QUANDL_KEY]: { + name: i18n.translate('timelion.uiSettings.quandlKeyLabel', { + defaultMessage: 'Quandl key', + }), + value: 'someKeyHere', + description: i18n.translate('timelion.uiSettings.quandlKeyDescription', { + defaultMessage: '{experimentalLabel} Your API key from www.quandl.com', + values: { experimentalLabel: `[${experimentalLabel}]` }, + }), + sensitive: true, + category: ['timelion'], + schema: schema.string(), + }, + }; +} From bc63434facacdcdb71273247edad30f3c470c4d5 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Wed, 9 Jun 2021 14:14:39 +0300 Subject: [PATCH 28/44] fix CI --- .../public/components/timelion_vis_component.tsx | 16 ++++++---------- .../public/helpers/panel_utils.ts | 1 + 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index 8cacc80b0c162c..0c88948b468e3b 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -68,15 +68,9 @@ function TimelionVisComponent({ yaxis.domain = { fit: true, + ...(yaxis.max ? { max: yaxis.max } : {}), + ...(yaxis.min ? { min: yaxis.min } : {}), }; - - if (yaxis.max) { - yaxis.domain.max = yaxis.max; - } - - if (yaxis.min) { - yaxis.domain.min = yaxis.min; - } } }); }; @@ -199,14 +193,16 @@ function TimelionVisComponent({ ); const yaxes = useMemo(() => { - const collectedYAxes = []; + const collectedYAxes: IAxis[] = []; + chart.forEach((chartInst) => { - chartInst._global?.yaxes.forEach((yaxis) => { + chartInst._global?.yaxes.forEach((yaxis: IAxis) => { if (yaxis) { collectedYAxes.push(yaxis); } }); }); + return collectedYAxes; }, [chart]); diff --git a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts index c6bc964efdf731..9e2388c385c083 100644 --- a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -33,6 +33,7 @@ export interface Axis { tickGenerator?(axis: Axis): number[]; units?: { type: string; prefix: string; suffix: string }; domain?: { + fit?: boolean; min?: number; max?: number; }; From 81add3101bbb14041e13ba67d98cce01b5748aec Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Wed, 9 Jun 2021 14:40:20 +0300 Subject: [PATCH 29/44] cleanup code --- .../components/timelion_vis_component.tsx | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index 0c88948b468e3b..02a2f4ca85af25 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -126,23 +126,14 @@ function TimelionVisComponent({ direction: LayoutDirection.Vertical, }; - switch (chartLegendGlobal?.position) { - case 'ne': - legendPositionConf.vAlign = Position.Top; - legendPositionConf.hAlign = Position.Right; - break; - case 'nw': - legendPositionConf.vAlign = Position.Top; - legendPositionConf.hAlign = Position.Left; - break; - case 'se': - legendPositionConf.vAlign = Position.Bottom; - legendPositionConf.hAlign = Position.Right; - break; - case 'sw': - legendPositionConf.vAlign = Position.Bottom; - legendPositionConf.hAlign = Position.Left; - break; + const validatePosition = (position: string) => /^(n|s)(e|w)$/s.test(position); + + // @todo test that + if (validatePosition(chartLegendGlobal?.position ?? '')) { + const [vAlign, hAlign] = chartLegendGlobal!.position.split(''); + + legendPositionConf.vAlign = vAlign === 'n' ? Position.Top : Position.Bottom; + legendPositionConf.hAlign = hAlign === 'e' ? Position.Right : Position.Left; } return legendPositionConf; From 0bc12e16dee35b529b115f498557cf0dcf9d7cb8 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Fri, 11 Jun 2021 10:47:12 +0300 Subject: [PATCH 30/44] fix CI --- .../server/collectors/management/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index bf28bb6cc01f5d..8926c21def0a93 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -42,6 +42,7 @@ export interface UsageStats { 'visualization:tileMap:maxPrecision': number; 'csv:separator': string; 'visualization:tileMap:WMSdefaults': string; + 'timelion:legacyChartsLibrary': boolean; 'timelion:target_buckets': number; 'timelion:max_buckets': number; 'timelion:es.timefield': string; From 6f33935982e49d70d305051618dba5cfe76f8db8 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Fri, 11 Jun 2021 13:00:04 +0300 Subject: [PATCH 31/44] fix legend position --- .../server/collectors/management/schema.ts | 4 + src/plugins/telemetry/schema/oss_plugins.json | 6 + .../components/timelion_vis_component.tsx | 152 +++++++++--------- .../public/helpers/panel_utils.ts | 4 +- .../public/helpers/tick_formatters.test.ts | 72 ++++----- .../public/helpers/tick_formatters.ts | 6 +- .../public/helpers/tick_generator.ts | 6 +- .../public/legacy/panel_utils.ts | 8 +- .../public/legacy/tick_formatters.ts | 10 +- .../public/legacy/timelion_vis_component.tsx | 8 +- .../vis_type_timelion/server/ui_settings.ts | 3 +- 11 files changed, 145 insertions(+), 134 deletions(-) diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index 5f70deccba93cf..6492cb79865730 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -80,6 +80,10 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'text', _meta: { description: 'Non-default value of setting.' }, }, + 'timelion:legacyChartsLibrary': { + type: 'boolean', + _meta: { description: 'Non-default value of setting.' }, + }, 'timelion:target_buckets': { type: 'long', _meta: { description: 'Non-default value of setting.' }, diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 1d37c25f52fd49..21de1e86123985 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -7768,6 +7768,12 @@ "description": "Non-default value of setting." } }, + "timelion:legacyChartsLibrary": { + "type": "boolean", + "_meta": { + "description": "Non-default value of setting." + } + }, "timelion:target_buckets": { "type": "long", "_meta": { diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index 02a2f4ca85af25..cabef4cf024859 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -21,17 +21,17 @@ import { LayoutDirection, } from '@elastic/charts'; -import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; import { useKibana } from '../../../kibana_react/public'; import { AreaSeriesComponent } from './area_series'; import { BarSeriesComponent } from './bar_series'; -import { createTickFormat, colors, Axis as IAxis, activeCursor$ } from '../helpers/panel_utils'; +import { createTickFormat, colors, IAxis, activeCursor$ } from '../helpers/panel_utils'; import { tickFormatters } from '../helpers/tick_formatters'; -import { Series, Sheet } from '../helpers/timelion_request_handler'; -import { TimelionVisDependencies } from '../plugin'; +import type { Series, Sheet } from '../helpers/timelion_request_handler'; +import type { IInterpreterRenderHandlers } from '../../../expressions'; +import type { TimelionVisDependencies } from '../plugin'; import './timelion_vis.scss'; @@ -46,6 +46,25 @@ const handleCursorUpdate = (cursor: PointerEvent) => { activeCursor$.next(cursor); }; +const updateYAxes = (yaxes: IAxis[]) => { + yaxes.forEach((yaxis: IAxis) => { + if (yaxis) { + if (yaxis.units) { + const formatters = tickFormatters(yaxis); + yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; + } else if (yaxis.tickDecimals) { + yaxis.tickFormatter = (val: number) => val.toFixed(yaxis.tickDecimals); + } + + yaxis.domain = { + fit: true, + ...(yaxis.max ? { max: yaxis.max } : {}), + ...(yaxis.min ? { min: yaxis.min } : {}), + }; + } + }); +}; + function TimelionVisComponent({ interval, seriesList, @@ -56,25 +75,6 @@ function TimelionVisComponent({ const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); const chartRef = useRef(); - const updateYAxes = function (yaxes: IAxis[]) { - yaxes.forEach((yaxis: IAxis) => { - if (yaxis) { - if (yaxis.units) { - const formatters = tickFormatters(yaxis); - yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; - } else if (yaxis.tickDecimals) { - yaxis.tickFormatter = (val: number) => val.toFixed(yaxis.tickDecimals); - } - - yaxis.domain = { - fit: true, - ...(yaxis.max ? { max: yaxis.max } : {}), - ...(yaxis.min ? { min: yaxis.min } : {}), - }; - } - }); - }; - useEffect(() => { const updateCursor = (cursor: PointerEvent) => { if (chartRef.current) { @@ -107,38 +107,6 @@ function TimelionVisComponent({ setChart(newChart); }, [seriesList.list]); - const getLegendPosition = useCallback(() => { - let chartLegendGlobal: Record = {}; - chart.forEach((series) => { - if (series._global?.legend) { - chartLegendGlobal = { - ...chartLegendGlobal, - ...series._global.legend, - }; - } - }); - - const legendPositionConf: LegendPositionConfig = { - floating: true, - floatingColumns: chartLegendGlobal?.noColumns ?? 1, - vAlign: Position.Top, - hAlign: Position.Left, - direction: LayoutDirection.Vertical, - }; - - const validatePosition = (position: string) => /^(n|s)(e|w)$/s.test(position); - - // @todo test that - if (validatePosition(chartLegendGlobal?.position ?? '')) { - const [vAlign, hAlign] = chartLegendGlobal!.position.split(''); - - legendPositionConf.vAlign = vAlign === 'n' ? Position.Top : Position.Bottom; - legendPositionConf.hAlign = hAlign === 'e' ? Position.Right : Position.Left; - } - - return legendPositionConf; - }, [chart]); - const brushEndListener = useCallback( ({ x }) => { if (!x) { @@ -187,7 +155,7 @@ function TimelionVisComponent({ const collectedYAxes: IAxis[] = []; chart.forEach((chartInst) => { - chartInst._global?.yaxes.forEach((yaxis: IAxis) => { + chartInst._global?.yaxes?.forEach((yaxis: IAxis) => { if (yaxis) { collectedYAxes.push(yaxis); } @@ -197,14 +165,50 @@ function TimelionVisComponent({ return collectedYAxes; }, [chart]); + const legend = useMemo(() => { + const validatePosition = (position: string) => /^(n|s)(e|w)$/s.test(position); + const legendPosition: LegendPositionConfig = { + floating: true, + floatingColumns: 1, + vAlign: Position.Top, + hAlign: Position.Left, + direction: LayoutDirection.Vertical, + }; + let showLegend = true; + + chart.forEach((series) => { + if (series._global?.legend) { + const { show = true, position, noColumns = legendPosition.floatingColumns } = + series._global?.legend ?? {}; + + if (validatePosition(position)) { + const [vAlign, hAlign] = position.split(''); + + legendPosition.vAlign = vAlign === 'n' ? Position.Top : Position.Bottom; + legendPosition.hAlign = hAlign === 'e' ? Position.Right : Position.Left; + } + + if (!show) { + showLegend = false; + } + + if (noColumns !== undefined) { + legendPosition.floatingColumns = noColumns; + } + } + }); + + return { legendPosition, showLegend }; + }, [chart]); + return (
{title}
} renderer="canvas" size={{ width: '100%' }}> + + {yaxes.length ? ( - yaxes.map((axis: IAxis, index: number) => { - return ( - - ); - }) + yaxes.map((axis: IAxis, index: number) => ( + + )) ) : ( )} + {chart.map((data, index) => { - const key = `${index}-${data.label}`; - if (data.bars) { - return ; - } + const SeriesComponent = data.bars ? BarSeriesComponent : AreaSeriesComponent; - return ; + return ; })}
diff --git a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts index 9e2388c385c083..efab0ec7b7206f 100644 --- a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -16,7 +16,7 @@ import { IUiSettingsClient } from 'kibana/public'; import { calculateInterval } from '../../common/lib'; import { xaxisFormatterProvider } from './xaxis_formatter'; -export interface Axis { +export interface IAxis { delta?: number; max?: number; min?: number; @@ -30,7 +30,7 @@ export interface Axis { timezone: string; tickDecimals?: number; tickFormatter: (val: number) => string; - tickGenerator?(axis: Axis): number[]; + tickGenerator?(axis: IAxis): number[]; units?: { type: string; prefix: string; suffix: string }; domain?: { fit?: boolean; diff --git a/src/plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts b/src/plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts index 89e922b3973270..9980644c0f9419 100644 --- a/src/plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts +++ b/src/plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts @@ -7,26 +7,26 @@ */ import { tickFormatters } from './tick_formatters'; -import { Axis } from './panel_utils'; +import type { IAxis } from './panel_utils'; -describe('Tick Formatters', function () { +describe('Tick Formatters', () => { let formatters: any; beforeEach(function () { - formatters = tickFormatters({} as Axis); + formatters = tickFormatters({} as IAxis); }); - describe('Bits mode', function () { + describe('Bits mode', () => { let bitFormatter: any; beforeEach(function () { bitFormatter = formatters.bits; }); - it('is a function', function () { + it('is a function', () => { expect(bitFormatter).toEqual(expect.any(Function)); }); - it('formats with b/kb/mb/gb', function () { + it('formats with b/kb/mb/gb', () => { expect(bitFormatter(7)).toEqual('7b'); expect(bitFormatter(4 * 1000)).toEqual('4kb'); expect(bitFormatter(4.1 * 1000 * 1000)).toEqual('4.1mb'); @@ -41,24 +41,24 @@ describe('Tick Formatters', function () { }); }); - describe('Bits/s mode', function () { + describe('Bits/s mode', () => { let bitsFormatter: any; beforeEach(function () { bitsFormatter = formatters['bits/s']; }); - it('is a function', function () { + it('is a function', () => { expect(bitsFormatter).toEqual(expect.any(Function)); }); - it('formats with b/kb/mb/gb', function () { + it('formats with b/kb/mb/gb', () => { expect(bitsFormatter(7)).toEqual('7b/s'); expect(bitsFormatter(4 * 1000)).toEqual('4kb/s'); expect(bitsFormatter(4.1 * 1000 * 1000)).toEqual('4.1mb/s'); expect(bitsFormatter(3 * 1000 * 1000 * 1000)).toEqual('3gb/s'); }); - it('formats negative values with b/kb/mb/gb', function () { + it('formats negative values with b/kb/mb/gb', () => { expect(bitsFormatter(-7)).toEqual('-7b/s'); expect(bitsFormatter(-4 * 1000)).toEqual('-4kb/s'); expect(bitsFormatter(-4.1 * 1000 * 1000)).toEqual('-4.1mb/s'); @@ -66,24 +66,24 @@ describe('Tick Formatters', function () { }); }); - describe('Bytes mode', function () { + describe('Bytes mode', () => { let byteFormatter: any; beforeEach(function () { byteFormatter = formatters.bytes; }); - it('is a function', function () { + it('is a function', () => { expect(byteFormatter).toEqual(expect.any(Function)); }); - it('formats with B/KB/MB/GB', function () { + it('formats with B/KB/MB/GB', () => { expect(byteFormatter(10)).toEqual('10B'); expect(byteFormatter(10 * 1024)).toEqual('10KB'); expect(byteFormatter(10.2 * 1024 * 1024)).toEqual('10.2MB'); expect(byteFormatter(3 * 1024 * 1024 * 1024)).toEqual('3GB'); }); - it('formats negative values with B/KB/MB/GB', function () { + it('formats negative values with B/KB/MB/GB', () => { expect(byteFormatter(-10)).toEqual('-10B'); expect(byteFormatter(-10 * 1024)).toEqual('-10KB'); expect(byteFormatter(-10.2 * 1024 * 1024)).toEqual('-10.2MB'); @@ -91,24 +91,24 @@ describe('Tick Formatters', function () { }); }); - describe('Bytes/s mode', function () { + describe('Bytes/s mode', () => { let bytesFormatter: any; beforeEach(function () { bytesFormatter = formatters['bytes/s']; }); - it('is a function', function () { + it('is a function', () => { expect(bytesFormatter).toEqual(expect.any(Function)); }); - it('formats with B/KB/MB/GB', function () { + it('formats with B/KB/MB/GB', () => { expect(bytesFormatter(10)).toEqual('10B/s'); expect(bytesFormatter(10 * 1024)).toEqual('10KB/s'); expect(bytesFormatter(10.2 * 1024 * 1024)).toEqual('10.2MB/s'); expect(bytesFormatter(3 * 1024 * 1024 * 1024)).toEqual('3GB/s'); }); - it('formats negative values with B/KB/MB/GB', function () { + it('formats negative values with B/KB/MB/GB', () => { expect(bytesFormatter(-10)).toEqual('-10B/s'); expect(bytesFormatter(-10 * 1024)).toEqual('-10KB/s'); expect(bytesFormatter(-10.2 * 1024 * 1024)).toEqual('-10.2MB/s'); @@ -116,57 +116,57 @@ describe('Tick Formatters', function () { }); }); - describe('Currency mode', function () { + describe('Currency mode', () => { let currencyFormatter: any; beforeEach(function () { currencyFormatter = formatters.currency; }); - it('is a function', function () { + it('is a function', () => { expect(currencyFormatter).toEqual(expect.any(Function)); }); - it('formats with $ by default', function () { + it('formats with $ by default', () => { const axis = { units: {}, }; - formatters = tickFormatters(axis as Axis); + formatters = tickFormatters(axis as IAxis); currencyFormatter = formatters.currency; expect(currencyFormatter(10.2)).toEqual('$10.20'); }); - it('accepts currency in ISO 4217', function () { + it('accepts currency in ISO 4217', () => { const axis = { units: { prefix: 'CNY', }, }; - formatters = tickFormatters(axis as Axis); + formatters = tickFormatters(axis as IAxis); currencyFormatter = formatters.currency; expect(currencyFormatter(10.2)).toEqual('CN¥10.20'); }); }); - describe('Percent mode', function () { + describe('Percent mode', () => { let percentFormatter: any; beforeEach(function () { percentFormatter = formatters.percent; }); - it('is a function', function () { + it('is a function', () => { expect(percentFormatter).toEqual(expect.any(Function)); }); - it('formats with %', function () { + it('formats with %', () => { const axis = { units: {}, }; - formatters = tickFormatters(axis as Axis); + formatters = tickFormatters(axis as IAxis); percentFormatter = formatters.percent; expect(percentFormatter(0.1234)).toEqual('12%'); }); - it('formats with % with decimal precision', function () { + it('formats with % with decimal precision', () => { const tickDecimals = 3; const tickDecimalShift = 2; const axis = { @@ -175,23 +175,23 @@ describe('Tick Formatters', function () { tickDecimalsShift: tickDecimalShift, }, } as unknown; - formatters = tickFormatters(axis as Axis); + formatters = tickFormatters(axis as IAxis); percentFormatter = formatters.percent; expect(percentFormatter(0.12345)).toEqual('12.345%'); }); }); - describe('Custom mode', function () { + describe('Custom mode', () => { let customFormatter: any; beforeEach(function () { customFormatter = formatters.custom; }); - it('is a function', function () { + it('is a function', () => { expect(customFormatter).toEqual(expect.any(Function)); }); - it('accepts prefix and suffix', function () { + it('accepts prefix and suffix', () => { const axis = { units: { prefix: 'prefix', @@ -199,12 +199,12 @@ describe('Tick Formatters', function () { }, tickDecimals: 1, }; - formatters = tickFormatters(axis as Axis); + formatters = tickFormatters(axis as IAxis); customFormatter = formatters.custom; expect(customFormatter(10.2)).toEqual('prefix10.2suffix'); }); - it('correctly renders small values', function () { + it('correctly renders small values', () => { const axis = { units: { prefix: 'prefix', @@ -212,7 +212,7 @@ describe('Tick Formatters', function () { }, tickDecimals: 3, }; - formatters = tickFormatters(axis as Axis); + formatters = tickFormatters(axis as IAxis); customFormatter = formatters.custom; expect(customFormatter(0.00499999999999999)).toEqual('prefix0.005suffix'); }); diff --git a/src/plugins/vis_type_timelion/public/helpers/tick_formatters.ts b/src/plugins/vis_type_timelion/public/helpers/tick_formatters.ts index ca50b3afe1e765..eb37e76e1f95d2 100644 --- a/src/plugins/vis_type_timelion/public/helpers/tick_formatters.ts +++ b/src/plugins/vis_type_timelion/public/helpers/tick_formatters.ts @@ -8,9 +8,9 @@ import { get } from 'lodash'; -import { Axis } from './panel_utils'; +import { IAxis } from './panel_utils'; -function baseTickFormatter(value: number, axis: Axis) { +function baseTickFormatter(value: number, axis: IAxis) { const factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; const formatted = '' + Math.round(value * factor) / factor; @@ -45,7 +45,7 @@ function unitFormatter(divisor: number, units: string[]) { }; } -export function tickFormatters(axis: Axis) { +export function tickFormatters(axis: IAxis) { return { bits: unitFormatter(1000, ['b', 'kb', 'mb', 'gb', 'tb', 'pb']), 'bits/s': unitFormatter(1000, ['b/s', 'kb/s', 'mb/s', 'gb/s', 'tb/s', 'pb/s']), diff --git a/src/plugins/vis_type_timelion/public/helpers/tick_generator.ts b/src/plugins/vis_type_timelion/public/helpers/tick_generator.ts index af559d5aaac2b1..6ffdda0bafdb61 100644 --- a/src/plugins/vis_type_timelion/public/helpers/tick_generator.ts +++ b/src/plugins/vis_type_timelion/public/helpers/tick_generator.ts @@ -6,14 +6,14 @@ * Side Public License, v 1. */ -import { Axis } from './panel_utils'; +import { IAxis } from './panel_utils'; export function generateTicksProvider() { function floorInBase(n: number, base: number) { return base * Math.floor(n / base); } - function generateTicks(axis: Axis) { + function generateTicks(axis: IAxis) { const returnTicks = []; let tickSize = 2; let delta = axis.delta || 0; @@ -46,5 +46,5 @@ export function generateTicksProvider() { return returnTicks; } - return (axis: Axis) => generateTicks(axis); + return (axis: IAxis) => generateTicks(axis); } diff --git a/src/plugins/vis_type_timelion/public/legacy/panel_utils.ts b/src/plugins/vis_type_timelion/public/legacy/panel_utils.ts index 0a00cc039ae752..b19f6761934828 100644 --- a/src/plugins/vis_type_timelion/public/legacy/panel_utils.ts +++ b/src/plugins/vis_type_timelion/public/legacy/panel_utils.ts @@ -17,7 +17,7 @@ import { calculateInterval } from '../../common/lib'; import { xaxisFormatterProvider } from '../helpers/xaxis_formatter'; import { Series } from '../helpers/timelion_request_handler'; -export interface Axis { +export interface LegacyAxis { delta?: number; max?: number; min?: number; @@ -30,8 +30,8 @@ export interface Axis { tickLength: number; timezone: string; tickDecimals?: number; - tickFormatter: ((val: number) => string) | ((val: number, axis: Axis) => string); - tickGenerator?(axis: Axis): number[]; + tickFormatter: ((val: number) => string) | ((val: number, axis: LegacyAxis) => string); + tickGenerator?(axis: LegacyAxis): number[]; units?: { type: string }; } @@ -172,7 +172,7 @@ function buildOptions( return wrapperSpan.outerHTML; }, }, - } as jquery.flot.plotOptions & { yaxes?: Axis[] }; + } as jquery.flot.plotOptions & { yaxes?: LegacyAxis[] }; return options; } diff --git a/src/plugins/vis_type_timelion/public/legacy/tick_formatters.ts b/src/plugins/vis_type_timelion/public/legacy/tick_formatters.ts index 43b84d4aaa42b6..950226968287ba 100644 --- a/src/plugins/vis_type_timelion/public/legacy/tick_formatters.ts +++ b/src/plugins/vis_type_timelion/public/legacy/tick_formatters.ts @@ -8,9 +8,9 @@ import { get } from 'lodash'; -import type { Axis } from './panel_utils'; +import type { LegacyAxis } from './panel_utils'; -function baseTickFormatter(value: number, axis: Axis) { +function baseTickFormatter(value: number, axis: LegacyAxis) { const factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; const formatted = '' + Math.round(value * factor) / factor; @@ -51,13 +51,13 @@ export function tickFormatters() { 'bits/s': unitFormatter(1000, ['b/s', 'kb/s', 'mb/s', 'gb/s', 'tb/s', 'pb/s']), bytes: unitFormatter(1024, ['B', 'KB', 'MB', 'GB', 'TB', 'PB']), 'bytes/s': unitFormatter(1024, ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s']), - currency(val: number, axis: Axis) { + currency(val: number, axis: LegacyAxis) { return val.toLocaleString('en', { style: 'currency', currency: (axis && axis.options && axis.options.units.prefix) || 'USD', }); }, - percent(val: number, axis: Axis) { + percent(val: number, axis: LegacyAxis) { let precision = get(axis, 'tickDecimals', 0) - get(axis, 'options.units.tickDecimalsShift', 0); // toFixed only accepts values between 0 and 20 @@ -69,7 +69,7 @@ export function tickFormatters() { return (val * 100).toFixed(precision) + '%'; }, - custom(val: number, axis: Axis) { + custom(val: number, axis: LegacyAxis) { const formattedVal = baseTickFormatter(val, axis); const prefix = axis && axis.options && axis.options.units.prefix; const suffix = axis && axis.options && axis.options.units.suffix; diff --git a/src/plugins/vis_type_timelion/public/legacy/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/legacy/timelion_vis_component.tsx index eed52f36acedec..a1e87e04f4decb 100644 --- a/src/plugins/vis_type_timelion/public/legacy/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/legacy/timelion_vis_component.tsx @@ -21,7 +21,7 @@ import { buildOptions, SERIES_ID_ATTR, colors, - Axis, + LegacyAxis, ACTIVE_CURSOR, eventBus, } from './panel_utils'; @@ -195,7 +195,7 @@ function TimelionVisComponent({ const updatedSeries = buildSeriesData(chartValue, options); if (options.yaxes) { - options.yaxes.forEach((yaxis: Axis) => { + options.yaxes.forEach((yaxis: LegacyAxis) => { if (yaxis && yaxis.units) { const formatters = tickFormatters(); yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; @@ -298,9 +298,9 @@ function TimelionVisComponent({ legendValueNumbers.eq(i).empty(); } else { let label = y.toFixed(precision); - const formatter = ((series.yaxis as unknown) as Axis).tickFormatter; + const formatter = ((series.yaxis as unknown) as LegacyAxis).tickFormatter; if (formatter) { - label = formatter(Number(label), (series.yaxis as unknown) as Axis); + label = formatter(Number(label), (series.yaxis as unknown) as LegacyAxis); } legendValueNumbers.eq(i).text(`(${label})`); } diff --git a/src/plugins/vis_type_timelion/server/ui_settings.ts b/src/plugins/vis_type_timelion/server/ui_settings.ts index 37ac657866f865..534ed728a9bc61 100644 --- a/src/plugins/vis_type_timelion/server/ui_settings.ts +++ b/src/plugins/vis_type_timelion/server/ui_settings.ts @@ -26,13 +26,12 @@ export function getUiSettings( defaultMessage: 'Legacy charts library', }), description: i18n.translate('timelion.uiSettings.legacyChartsLibraryDescription', { - defaultMessage: 'Enables legacy charts library for Timeline visualizations', + defaultMessage: 'Enables legacy charts library for Timelion visualizations', }), value: false, category: ['timelion'], schema: schema.boolean(), }, - [UI_SETTINGS.ES_TIMEFIELD]: { name: i18n.translate('timelion.uiSettings.timeFieldLabel', { defaultMessage: 'Time field', From 56c0760510c0a5a967c4be051345443371c816c4 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Fri, 11 Jun 2021 15:27:38 +0300 Subject: [PATCH 32/44] fix some cases --- .../vis_type_timelion/common/vis_data.ts | 32 +++++++++++++++++ .../index.tsx => series/area.tsx} | 34 ++++++++++++------- .../{bar_series/index.tsx => series/bar.tsx} | 25 +++++++++----- .../public/components/series/index.ts | 10 ++++++ .../components/timelion_vis_component.tsx | 6 ++-- .../helpers/timelion_request_handler.ts | 10 ++---- 6 files changed, 85 insertions(+), 32 deletions(-) create mode 100644 src/plugins/vis_type_timelion/common/vis_data.ts rename src/plugins/vis_type_timelion/public/components/{area_series/index.tsx => series/area.tsx} (64%) rename src/plugins/vis_type_timelion/public/components/{bar_series/index.tsx => series/bar.tsx} (68%) create mode 100644 src/plugins/vis_type_timelion/public/components/series/index.ts diff --git a/src/plugins/vis_type_timelion/common/vis_data.ts b/src/plugins/vis_type_timelion/common/vis_data.ts new file mode 100644 index 00000000000000..f0a78545bfd994 --- /dev/null +++ b/src/plugins/vis_type_timelion/common/vis_data.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export interface Series { + label: string; + lines?: { + show?: boolean; + lineWidth?: number; + fill?: number; + steps?: number; + }; + points?: { + show?: boolean; + symbol?: 'cross' | 'x' | 'circle' | 'square' | 'diamond' | 'plus' | 'triangle'; + fillColor?: string; + fill?: number; + radius?: number; + lineWidth?: number; + }; + bars: { + lineWidth?: number; + fill?: number; + }; + color?: string; + data: Array>; + stack: boolean; +} diff --git a/src/plugins/vis_type_timelion/public/components/area_series/index.tsx b/src/plugins/vis_type_timelion/public/components/series/area.tsx similarity index 64% rename from src/plugins/vis_type_timelion/public/components/area_series/index.tsx rename to src/plugins/vis_type_timelion/public/components/series/area.tsx index 0bde77243c213b..019afc4c59b3f7 100644 --- a/src/plugins/vis_type_timelion/public/components/area_series/index.tsx +++ b/src/plugins/vis_type_timelion/public/components/series/area.tsx @@ -8,27 +8,34 @@ import React from 'react'; import { AreaSeries, ScaleType, CurveType } from '@elastic/charts'; +import { Series } from '../../../common/vis_data'; -export function AreaSeriesComponent({ data, index }: { data: any; index: number }) { - const lines = data.lines || {}; - const points = data.points || {}; +interface AreaSeriesComponentProps { + index: number; + visData: Series; +} + +export function AreaSeriesComponent({ + index, + visData: { lines = {}, points = {}, color, label, data, stack }, +}: AreaSeriesComponentProps) { const styles = { areaSeriesStyle: { line: { - stroke: data.color, - strokeWidth: Number(lines.lineWidth) ?? 3, + stroke: color, + strokeWidth: lines.lineWidth !== undefined ? Number(lines.lineWidth) : 3, visible: lines.show ?? !points.show, }, area: { - fill: data.color, + fill: color, opacity: lines.fill ?? 0, visible: lines.show ?? !points.show, }, point: { fill: points.fillColor, - opacity: points.fill * 10 ?? 10, + opacity: points.lineWidth !== undefined ? (points.fill || 1) * 10 : 10, radius: points.radius ?? 3, - stroke: data.color, + stroke: color, strokeWidth: points.lineWidth ?? 2, visible: points.show ?? false, shape: points.symbol === 'cross' ? 'x' : points.symbol, @@ -39,16 +46,17 @@ export function AreaSeriesComponent({ data, index }: { data: any; index: number return ( ); diff --git a/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx b/src/plugins/vis_type_timelion/public/components/series/bar.tsx similarity index 68% rename from src/plugins/vis_type_timelion/public/components/bar_series/index.tsx rename to src/plugins/vis_type_timelion/public/components/series/bar.tsx index 0212a4a62c9a1c..1f20523c79ae3c 100644 --- a/src/plugins/vis_type_timelion/public/components/bar_series/index.tsx +++ b/src/plugins/vis_type_timelion/public/components/series/bar.tsx @@ -8,9 +8,17 @@ import React from 'react'; import { BarSeries, ScaleType } from '@elastic/charts'; +import { Series } from '../../../common/vis_data'; -export function BarSeriesComponent({ data, index }: { data: any; index: number }) { - const bars = data.bars || {}; +interface BarSeriesComponentProps { + index: number; + visData: Series; +} + +export function BarSeriesComponent({ + index, + visData: { color, data, label, stack, bars = {} }, +}: BarSeriesComponentProps) { let opacity = bars.fill; if (!bars.fill) { @@ -24,7 +32,7 @@ export function BarSeriesComponent({ data, index }: { data: any; index: number } const styles = { barSeriesStyle: { rect: { - fill: data.color, + fill: color, opacity, widthPixel: bars.lineWidth, }, @@ -33,16 +41,17 @@ export function BarSeriesComponent({ data, index }: { data: any; index: number } return ( ); diff --git a/src/plugins/vis_type_timelion/public/components/series/index.ts b/src/plugins/vis_type_timelion/public/components/series/index.ts new file mode 100644 index 00000000000000..3efe537d0b4672 --- /dev/null +++ b/src/plugins/vis_type_timelion/public/components/series/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { BarSeriesComponent } from './bar'; +export { AreaSeriesComponent } from './area'; diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index cabef4cf024859..a3b81523bb1fe7 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -23,8 +23,7 @@ import { import { useKibana } from '../../../kibana_react/public'; -import { AreaSeriesComponent } from './area_series'; -import { BarSeriesComponent } from './bar_series'; +import { AreaSeriesComponent, BarSeriesComponent } from './series'; import { createTickFormat, colors, IAxis, activeCursor$ } from '../helpers/panel_utils'; import { tickFormatters } from '../helpers/tick_formatters'; @@ -225,6 +224,7 @@ function TimelionVisComponent({ {yaxes.length ? ( yaxes.map((axis: IAxis, index: number) => ( { const SeriesComponent = data.bars ? BarSeriesComponent : AreaSeriesComponent; - return ; + return ; })}
diff --git a/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts b/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts index 4ca154e42f7718..44a4eb90e74d2e 100644 --- a/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts +++ b/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts @@ -12,6 +12,7 @@ import { TimelionVisDependencies } from '../plugin'; import { getTimezone } from './get_timezone'; import { TimelionVisParams } from '../timelion_vis_fn'; import { getDataSearch } from '../helpers/plugin_services'; +import { Series as VisSeries } from '../../common/vis_data'; interface Stats { cacheCount: number; @@ -21,21 +22,14 @@ interface Stats { sheetTime: number; } -export interface Series { +export interface Series extends VisSeries { _global?: Record; _hide?: boolean; _id?: number; _title?: string; - color?: string; - data: Array>; fit: string; - label: string; split: string; - stack?: boolean; type: string; - bars?: Record; - lines?: Record; - points?: Record; } export interface Sheet { From f52fd07dbac73790249e44aca704816864a993b4 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Mon, 14 Jun 2021 16:56:02 +0300 Subject: [PATCH 33/44] fix some cases --- .../vis_type_timelion/common/vis_data.ts | 1 + .../public/components/series/area.tsx | 88 ++++++------- .../public/components/series/bar.tsx | 71 +++++------ .../components/timelion_vis_component.tsx | 120 +++++++++--------- .../public/helpers/active_cursor.ts | 12 ++ .../public/helpers/chart_constants.ts | 20 +++ .../public/helpers/panel_utils.ts | 35 +---- .../public/legacy/panel_utils.ts | 16 +-- .../public/legacy/timelion_vis_component.tsx | 30 ++--- .../public/timelion_vis_renderer.tsx | 19 ++- 10 files changed, 206 insertions(+), 206 deletions(-) create mode 100644 src/plugins/vis_type_timelion/public/helpers/active_cursor.ts create mode 100644 src/plugins/vis_type_timelion/public/helpers/chart_constants.ts diff --git a/src/plugins/vis_type_timelion/common/vis_data.ts b/src/plugins/vis_type_timelion/common/vis_data.ts index f0a78545bfd994..6426fcfd736637 100644 --- a/src/plugins/vis_type_timelion/common/vis_data.ts +++ b/src/plugins/vis_type_timelion/common/vis_data.ts @@ -7,6 +7,7 @@ */ export interface Series { + yaxis?: number; label: string; lines?: { show?: boolean; diff --git a/src/plugins/vis_type_timelion/public/components/series/area.tsx b/src/plugins/vis_type_timelion/public/components/series/area.tsx index 019afc4c59b3f7..56ff58c81fa129 100644 --- a/src/plugins/vis_type_timelion/public/components/series/area.tsx +++ b/src/plugins/vis_type_timelion/public/components/series/area.tsx @@ -7,57 +7,53 @@ */ import React from 'react'; -import { AreaSeries, ScaleType, CurveType } from '@elastic/charts'; -import { Series } from '../../../common/vis_data'; +import { AreaSeries, ScaleType, CurveType, AreaSeriesStyle, PointShape } from '@elastic/charts'; +import type { Series } from '../../../common/vis_data'; interface AreaSeriesComponentProps { index: number; visData: Series; + groupId: string; } -export function AreaSeriesComponent({ - index, - visData: { lines = {}, points = {}, color, label, data, stack }, -}: AreaSeriesComponentProps) { - const styles = { - areaSeriesStyle: { - line: { - stroke: color, - strokeWidth: lines.lineWidth !== undefined ? Number(lines.lineWidth) : 3, - visible: lines.show ?? !points.show, - }, - area: { - fill: color, - opacity: lines.fill ?? 0, - visible: lines.show ?? !points.show, - }, - point: { - fill: points.fillColor, - opacity: points.lineWidth !== undefined ? (points.fill || 1) * 10 : 10, - radius: points.radius ?? 3, - stroke: color, - strokeWidth: points.lineWidth ?? 2, - visible: points.show ?? false, - shape: points.symbol === 'cross' ? 'x' : points.symbol, - }, +const getAreaSeriesStyle = ({ color, lines, points }: AreaSeriesComponentProps['visData']) => + ({ + line: { + opacity: 1, + stroke: color, + strokeWidth: lines?.lineWidth !== undefined ? Number(lines.lineWidth) : 3, + visible: lines?.show ?? points?.show ?? true, }, - curve: lines.steps ? CurveType.CURVE_STEP : CurveType.LINEAR, - }; + area: { + fill: color, + opacity: lines?.fill ?? 0, + visible: lines?.show ?? points?.show ?? true, + }, + point: { + fill: points?.fillColor ?? color, + opacity: points?.lineWidth !== undefined ? (points.fill || 1) * 10 : 10, + radius: points?.radius ?? 3, + stroke: color, + strokeWidth: points?.lineWidth ?? 2, + visible: points?.show ?? false, + shape: points?.symbol === 'cross' ? PointShape.X : points?.symbol, + }, + curve: lines?.steps ? CurveType.CURVE_STEP : CurveType.LINEAR, + } as AreaSeriesStyle); - return ( - - ); -} +export const AreaSeriesComponent = ({ index, groupId, visData }: AreaSeriesComponentProps) => ( + +); diff --git a/src/plugins/vis_type_timelion/public/components/series/bar.tsx b/src/plugins/vis_type_timelion/public/components/series/bar.tsx index 1f20523c79ae3c..a87073cd599fe1 100644 --- a/src/plugins/vis_type_timelion/public/components/series/bar.tsx +++ b/src/plugins/vis_type_timelion/public/components/series/bar.tsx @@ -7,52 +7,51 @@ */ import React from 'react'; -import { BarSeries, ScaleType } from '@elastic/charts'; -import { Series } from '../../../common/vis_data'; +import { BarSeries, ScaleType, BarSeriesStyle } from '@elastic/charts'; +import type { Series } from '../../../common/vis_data'; interface BarSeriesComponentProps { index: number; visData: Series; + groupId: string; } -export function BarSeriesComponent({ - index, - visData: { color, data, label, stack, bars = {} }, -}: BarSeriesComponentProps) { - let opacity = bars.fill; +const getBarSeriesStyle = ({ color, bars }: BarSeriesComponentProps['visData']) => { + let opacity = bars.fill ?? 1; - if (!bars.fill) { - opacity = 1; - } else if (bars.fill < 0) { + if (opacity < 0) { opacity = 0; - } else if (bars.fill > 1) { + } else if (opacity > 1) { opacity = 1; } - const styles = { - barSeriesStyle: { - rect: { - fill: color, - opacity, - widthPixel: bars.lineWidth, - }, + return { + rectBorder: { + stroke: color, + strokeWidth: Math.max(1, bars.lineWidth ? Math.ceil(bars.lineWidth / 2) : 1), + visible: true, + }, + rect: { + fill: color, + opacity, + widthRatio: 1, }, - }; + } as BarSeriesStyle; +}; - return ( - - ); -} +export const BarSeriesComponent = ({ index, groupId, visData }: BarSeriesComponentProps) => ( + +); diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index a3b81523bb1fe7..44bfcde2c76f90 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -15,7 +15,6 @@ import { Axis, TooltipType, YDomainRange, - BrushEndListener, PointerEvent, LegendPositionConfig, LayoutDirection, @@ -25,26 +24,27 @@ import { useKibana } from '../../../kibana_react/public'; import { AreaSeriesComponent, BarSeriesComponent } from './series'; -import { createTickFormat, colors, IAxis, activeCursor$ } from '../helpers/panel_utils'; +import { createTickFormat, IAxis, validateLegendPositionValue } from '../helpers/panel_utils'; +import { colors } from '../helpers/chart_constants'; import { tickFormatters } from '../helpers/tick_formatters'; +import { activeCursor$ } from '../helpers/active_cursor'; import type { Series, Sheet } from '../helpers/timelion_request_handler'; + import type { IInterpreterRenderHandlers } from '../../../expressions'; import type { TimelionVisDependencies } from '../plugin'; +import type { RangeFilterParams } from '../../../data/public'; import './timelion_vis.scss'; interface TimelionVisComponentProps { - fireEvent: IInterpreterRenderHandlers['event']; interval: string; seriesList: Sheet; + onBrushEvent: (rangeFilterParams: RangeFilterParams) => void; renderComplete: IInterpreterRenderHandlers['done']; } -const handleCursorUpdate = (cursor: PointerEvent) => { - activeCursor$.next(cursor); -}; - +// @todo: remove this method, we should not modify global object const updateYAxes = (yaxes: IAxis[]) => { yaxes.forEach((yaxis: IAxis) => { if (yaxis) { @@ -55,21 +55,28 @@ const updateYAxes = (yaxes: IAxis[]) => { yaxis.tickFormatter = (val: number) => val.toFixed(yaxis.tickDecimals); } + const max = yaxis.max ? yaxis.max : undefined; + const min = yaxis.min ? yaxis.min : undefined; + yaxis.domain = { - fit: true, - ...(yaxis.max ? { max: yaxis.max } : {}), - ...(yaxis.min ? { min: yaxis.min } : {}), + fit: min === undefined && max === undefined, + max, + min, }; } }); }; -function TimelionVisComponent({ +const MAIN_GROUP_ID = 1; + +const DefaultYAxis = () => ; + +const TimelionVisComponent = ({ interval, seriesList, renderComplete, - fireEvent, -}: TimelionVisComponentProps) { + onBrushEvent, +}: TimelionVisComponentProps) => { const kibana = useKibana(); const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); const chartRef = useRef(); @@ -106,30 +113,22 @@ function TimelionVisComponent({ setChart(newChart); }, [seriesList.list]); - const brushEndListener = useCallback( + const handleCursorUpdate = useCallback((cursor: PointerEvent) => { + activeCursor$.next(cursor); + }, []); + + const brushEndListener = useCallback( ({ x }) => { if (!x) { return; } - fireEvent({ - name: 'applyFilter', - data: { - timeFieldName: '*', - filters: [ - { - range: { - '*': { - gte: x[0], - lte: x[1], - }, - }, - }, - ], - }, + onBrushEvent({ + gte: x[0], + lte: x[1], }); }, - [fireEvent] + [onBrushEvent] ); const onRenderChange = useCallback( @@ -150,22 +149,7 @@ function TimelionVisComponent({ [interval, kibana.services.timefilter, kibana.services.uiSettings] ); - const yaxes = useMemo(() => { - const collectedYAxes: IAxis[] = []; - - chart.forEach((chartInst) => { - chartInst._global?.yaxes?.forEach((yaxis: IAxis) => { - if (yaxis) { - collectedYAxes.push(yaxis); - } - }); - }); - - return collectedYAxes; - }, [chart]); - const legend = useMemo(() => { - const validatePosition = (position: string) => /^(n|s)(e|w)$/s.test(position); const legendPosition: LegendPositionConfig = { floating: true, floatingColumns: 1, @@ -180,7 +164,7 @@ function TimelionVisComponent({ const { show = true, position, noColumns = legendPosition.floatingColumns } = series._global?.legend ?? {}; - if (validatePosition(position)) { + if (validateLegendPositionValue(position)) { const [vAlign, hAlign] = position.split(''); legendPosition.vAlign = vAlign === 'n' ? Position.Top : Position.Bottom; @@ -221,31 +205,45 @@ function TimelionVisComponent({ - {yaxes.length ? ( - yaxes.map((axis: IAxis, index: number) => ( - - )) + {chart.length ? ( + chart.map((data, index) => { + const { yaxis: y, _global } = data; + const yaxis = (_global?.yaxes ?? [])[y ? y - 1 : 0]; + + return yaxis ? ( + + ) : ( + + ); + }) ) : ( - + )} {chart.map((data, index) => { const SeriesComponent = data.bars ? BarSeriesComponent : AreaSeriesComponent; - return ; + return ( + + ); })}
); -} +}; // default export required for React.Lazy // eslint-disable-next-line import/no-default-export diff --git a/src/plugins/vis_type_timelion/public/helpers/active_cursor.ts b/src/plugins/vis_type_timelion/public/helpers/active_cursor.ts new file mode 100644 index 00000000000000..7f7f62fd6a9dab --- /dev/null +++ b/src/plugins/vis_type_timelion/public/helpers/active_cursor.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Subject } from 'rxjs'; +import { PointerEvent } from '@elastic/charts'; + +export const activeCursor$ = new Subject(); diff --git a/src/plugins/vis_type_timelion/public/helpers/chart_constants.ts b/src/plugins/vis_type_timelion/public/helpers/chart_constants.ts new file mode 100644 index 00000000000000..b530ec98bd8a17 --- /dev/null +++ b/src/plugins/vis_type_timelion/public/helpers/chart_constants.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const colors = [ + '#01A4A4', + '#C66', + '#D0D102', + '#616161', + '#00A1CB', + '#32742C', + '#F18D05', + '#113F8C', + '#61AE24', + '#D70060', +]; diff --git a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts index efab0ec7b7206f..4965c6b7dbd64a 100644 --- a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -6,9 +6,8 @@ * Side Public License, v 1. */ -import { Subject } from 'rxjs'; -import moment, { Moment } from 'moment-timezone'; -import { Position, PointerEvent } from '@elastic/charts'; +import moment from 'moment-timezone'; +import { Position } from '@elastic/charts'; import { TimefilterContract } from 'src/plugins/data/public'; import { IUiSettingsClient } from 'kibana/public'; @@ -41,33 +40,15 @@ export interface IAxis { axisLabel: string; } -interface TimeRangeBounds { - min: Moment | undefined; - max: Moment | undefined; -} - -const activeCursor$ = new Subject(); - -const colors = [ - '#01A4A4', - '#C66', - '#D0D102', - '#616161', - '#00A1CB', - '#32742C', - '#F18D05', - '#113F8C', - '#61AE24', - '#D70060', -]; +export const validateLegendPositionValue = (position: string) => /^(n|s)(e|w)$/s.test(position); -function createTickFormat( +export const createTickFormat = ( intervalValue: string, timefilter: TimefilterContract, uiSettings: IUiSettingsClient -) { +) => { // Get the X-axis tick format - const time: TimeRangeBounds = timefilter.getBounds(); + const time = timefilter.getBounds(); const interval = calculateInterval( (time.min && time.min.valueOf()) || 0, (time.max && time.max.valueOf()) || 0, @@ -78,6 +59,4 @@ function createTickFormat( const format = xaxisFormatterProvider(uiSettings)(interval); return (val: number) => moment(val).format(format); -} - -export { createTickFormat, colors, activeCursor$ }; +}; diff --git a/src/plugins/vis_type_timelion/public/legacy/panel_utils.ts b/src/plugins/vis_type_timelion/public/legacy/panel_utils.ts index b19f6761934828..5dd8431a5a2ab8 100644 --- a/src/plugins/vis_type_timelion/public/legacy/panel_utils.ts +++ b/src/plugins/vis_type_timelion/public/legacy/panel_utils.ts @@ -16,6 +16,7 @@ import { IUiSettingsClient } from 'kibana/public'; import { calculateInterval } from '../../common/lib'; import { xaxisFormatterProvider } from '../helpers/xaxis_formatter'; import { Series } from '../helpers/timelion_request_handler'; +import { colors } from '../helpers/chart_constants'; export interface LegacyAxis { delta?: number; @@ -43,19 +44,6 @@ interface TimeRangeBounds { export const ACTIVE_CURSOR = 'ACTIVE_CURSOR_TIMELION'; export const eventBus = $({}); -const colors = [ - '#01A4A4', - '#C66', - '#D0D102', - '#616161', - '#00A1CB', - '#32742C', - '#F18D05', - '#113F8C', - '#61AE24', - '#D70060', -]; - const SERIES_ID_ATTR = 'data-series-id'; function buildSeriesData(chart: Series[], options: jquery.flot.plotOptions) { @@ -177,4 +165,4 @@ function buildOptions( return options; } -export { buildSeriesData, buildOptions, SERIES_ID_ATTR, colors }; +export { buildSeriesData, buildOptions, SERIES_ID_ATTR }; diff --git a/src/plugins/vis_type_timelion/public/legacy/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/legacy/timelion_vis_component.tsx index a1e87e04f4decb..ddac86fa73beea 100644 --- a/src/plugins/vis_type_timelion/public/legacy/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/legacy/timelion_vis_component.tsx @@ -20,16 +20,18 @@ import { buildSeriesData, buildOptions, SERIES_ID_ATTR, - colors, LegacyAxis, ACTIVE_CURSOR, eventBus, } from './panel_utils'; import { Series, Sheet } from '../helpers/timelion_request_handler'; +import { colors } from '../helpers/chart_constants'; import { tickFormatters } from './tick_formatters'; import { generateTicksProvider } from '../helpers/tick_generator'; -import { TimelionVisDependencies } from '../plugin'; + +import type { TimelionVisDependencies } from '../plugin'; +import type { RangeFilterParams } from '../../../data/common'; import './timelion_vis.scss'; @@ -39,7 +41,7 @@ interface CrosshairPlot extends jquery.flot.plot { } interface TimelionVisComponentProps { - fireEvent: IInterpreterRenderHandlers['event']; + onBrushEvent: (rangeFilterParams: RangeFilterParams) => void; interval: string; seriesList: Sheet; renderComplete: IInterpreterRenderHandlers['done']; @@ -72,7 +74,7 @@ function TimelionVisComponent({ interval, seriesList, renderComplete, - fireEvent, + onBrushEvent, }: TimelionVisComponentProps) { const kibana = useKibana(); const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); @@ -373,24 +375,12 @@ function TimelionVisComponent({ const plotSelectedHandler = useCallback( (event: JQuery.TriggeredEvent, ranges: Ranges) => { - fireEvent({ - name: 'applyFilter', - data: { - timeFieldName: '*', - filters: [ - { - range: { - '*': { - gte: ranges.xaxis.from, - lte: ranges.xaxis.to, - }, - }, - }, - ], - }, + onBrushEvent({ + gte: ranges.xaxis.from, + lte: ranges.xaxis.to, }); }, - [fireEvent] + [onBrushEvent] ); useEffect(() => { diff --git a/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx b/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx index 75b69a8a7d0fd4..b14055a4d6b634 100644 --- a/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx +++ b/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx @@ -15,6 +15,7 @@ import { VisualizationContainer } from '../../visualizations/public'; import { TimelionVisDependencies } from './plugin'; import { TimelionRenderValue } from './timelion_vis_fn'; import { UI_SETTINGS } from '../common/constants'; +import { RangeFilterParams } from '../../data/public'; const TimelionVisComponent = lazy(() => import('./components/timelion_vis_component')); const TimelionVisLegacyComponent = lazy(() => import('./legacy/timelion_vis_component')); @@ -37,6 +38,22 @@ export const getTimelionVisRenderer: ( ? TimelionVisLegacyComponent : TimelionVisComponent; + const onBrushEvent = (rangeFilterParams: RangeFilterParams) => { + handlers.event({ + name: 'applyFilter', + data: { + timeFieldName: '*', + filters: [ + { + range: { + '*': rangeFilterParams, + }, + }, + ], + }, + }); + }; + render( @@ -44,7 +61,7 @@ export const getTimelionVisRenderer: ( interval={visParams.interval} seriesList={seriesList} renderComplete={handlers.done} - fireEvent={handlers.event} + onBrushEvent={onBrushEvent} /> , From cd2cb7ec6f1a715a67d8f7eac410c4aeedfdcc3f Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Mon, 14 Jun 2021 17:23:38 +0300 Subject: [PATCH 34/44] remove extra casting --- .../components/timelion_vis_component.tsx | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index 44bfcde2c76f90..cc8c796a1c86fe 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useState, useEffect, useCallback, useMemo, useRef, RefObject } from 'react'; +import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { compact, cloneDeep, last, map } from 'lodash'; import { Chart, @@ -14,7 +14,6 @@ import { Position, Axis, TooltipType, - YDomainRange, PointerEvent, LegendPositionConfig, LayoutDirection, @@ -79,16 +78,12 @@ const TimelionVisComponent = ({ }: TimelionVisComponentProps) => { const kibana = useKibana(); const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); - const chartRef = useRef(); + const chartRef = useRef(null); useEffect(() => { - const updateCursor = (cursor: PointerEvent) => { - if (chartRef.current) { - chartRef.current.dispatchExternalPointerEvent(cursor); - } - }; - - const subscription = activeCursor$.subscribe(updateCursor); + const subscription = activeCursor$.subscribe((cursor: PointerEvent) => { + chartRef.current?.dispatchExternalPointerEvent(cursor); + }); return () => { subscription.unsubscribe(); @@ -98,6 +93,7 @@ const TimelionVisComponent = ({ useEffect(() => { const newChart = seriesList.list.map((series: Series, seriesIndex: number) => { const newSeries = { ...series }; + if (!newSeries.color) { const colorIndex = seriesIndex % colors.length; newSeries.color = colors[colorIndex]; @@ -187,7 +183,7 @@ const TimelionVisComponent = ({ return (
{title}
- } renderer="canvas" size={{ width: '100%' }}> + ) : ( From e4c3b9649936730e94a63b759c2abc21224bd607 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Tue, 15 Jun 2021 12:34:41 +0300 Subject: [PATCH 35/44] cleanup code --- .../vis_type_timelion/common/vis_data.ts | 2 +- .../public/components/series/bar.tsx | 2 +- .../components/timelion_vis_component.tsx | 75 +++++-------------- .../public/helpers/panel_utils.ts | 48 +++++++++++- .../helpers/timelion_request_handler.ts | 2 +- 5 files changed, 67 insertions(+), 62 deletions(-) diff --git a/src/plugins/vis_type_timelion/common/vis_data.ts b/src/plugins/vis_type_timelion/common/vis_data.ts index 6426fcfd736637..e3041f43a8f193 100644 --- a/src/plugins/vis_type_timelion/common/vis_data.ts +++ b/src/plugins/vis_type_timelion/common/vis_data.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -export interface Series { +export interface VisSeries { yaxis?: number; label: string; lines?: { diff --git a/src/plugins/vis_type_timelion/public/components/series/bar.tsx b/src/plugins/vis_type_timelion/public/components/series/bar.tsx index a87073cd599fe1..f4cfecee324a40 100644 --- a/src/plugins/vis_type_timelion/public/components/series/bar.tsx +++ b/src/plugins/vis_type_timelion/public/components/series/bar.tsx @@ -34,7 +34,7 @@ const getBarSeriesStyle = ({ color, bars }: BarSeriesComponentProps['visData']) rect: { fill: color, opacity, - widthRatio: 1, + widthPixel: 1, }, } as BarSeriesStyle; }; diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index cc8c796a1c86fe..1fa3fab05e0ef2 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; -import { compact, cloneDeep, last, map } from 'lodash'; +import React, { useEffect, useCallback, useMemo, useRef } from 'react'; +import { compact, last, map } from 'lodash'; import { Chart, Settings, @@ -23,13 +23,16 @@ import { useKibana } from '../../../kibana_react/public'; import { AreaSeriesComponent, BarSeriesComponent } from './series'; -import { createTickFormat, IAxis, validateLegendPositionValue } from '../helpers/panel_utils'; +import { + extractYAxis, + createTickFormat, + validateLegendPositionValue, +} from '../helpers/panel_utils'; + import { colors } from '../helpers/chart_constants'; -import { tickFormatters } from '../helpers/tick_formatters'; import { activeCursor$ } from '../helpers/active_cursor'; -import type { Series, Sheet } from '../helpers/timelion_request_handler'; - +import type { Sheet } from '../helpers/timelion_request_handler'; import type { IInterpreterRenderHandlers } from '../../../expressions'; import type { TimelionVisDependencies } from '../plugin'; import type { RangeFilterParams } from '../../../data/public'; @@ -43,29 +46,6 @@ interface TimelionVisComponentProps { renderComplete: IInterpreterRenderHandlers['done']; } -// @todo: remove this method, we should not modify global object -const updateYAxes = (yaxes: IAxis[]) => { - yaxes.forEach((yaxis: IAxis) => { - if (yaxis) { - if (yaxis.units) { - const formatters = tickFormatters(yaxis); - yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; - } else if (yaxis.tickDecimals) { - yaxis.tickFormatter = (val: number) => val.toFixed(yaxis.tickDecimals); - } - - const max = yaxis.max ? yaxis.max : undefined; - const min = yaxis.min ? yaxis.min : undefined; - - yaxis.domain = { - fit: min === undefined && max === undefined, - max, - min, - }; - } - }); -}; - const MAIN_GROUP_ID = 1; const DefaultYAxis = () => ; @@ -77,8 +57,8 @@ const TimelionVisComponent = ({ onBrushEvent, }: TimelionVisComponentProps) => { const kibana = useKibana(); - const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); const chartRef = useRef(null); + const chart = seriesList.list; useEffect(() => { const subscription = activeCursor$.subscribe((cursor: PointerEvent) => { @@ -90,25 +70,6 @@ const TimelionVisComponent = ({ }; }, []); - useEffect(() => { - const newChart = seriesList.list.map((series: Series, seriesIndex: number) => { - const newSeries = { ...series }; - - if (!newSeries.color) { - const colorIndex = seriesIndex % colors.length; - newSeries.color = colors[colorIndex]; - } - - if (newSeries._global?.yaxes) { - updateYAxes(newSeries._global.yaxes); - } - - return newSeries; - }); - - setChart(newChart); - }, [seriesList.list]); - const handleCursorUpdate = useCallback((cursor: PointerEvent) => { activeCursor$.next(cursor); }, []); @@ -203,12 +164,11 @@ const TimelionVisComponent = ({ {chart.length ? ( chart.map((data, index) => { - const { yaxis: y, _global } = data; - const yaxis = (_global?.yaxes ?? [])[y ? y - 1 : 0]; + const yaxis = extractYAxis(data); return yaxis ? ( { + const visData = { ...data }; const SeriesComponent = data.bars ? BarSeriesComponent : AreaSeriesComponent; + if (!visData.color) { + visData.color = colors[index % colors.length]; + } + return ( ); })} diff --git a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts index 4965c6b7dbd64a..d1af9d6946207e 100644 --- a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -7,13 +7,15 @@ */ import moment from 'moment-timezone'; -import { Position } from '@elastic/charts'; - -import { TimefilterContract } from 'src/plugins/data/public'; -import { IUiSettingsClient } from 'kibana/public'; +import type { Position } from '@elastic/charts'; +import type { TimefilterContract } from 'src/plugins/data/public'; +import type { IUiSettingsClient } from 'kibana/public'; import { calculateInterval } from '../../common/lib'; import { xaxisFormatterProvider } from './xaxis_formatter'; +import { tickFormatters } from './tick_formatters'; + +import type { Series } from './timelion_request_handler'; export interface IAxis { delta?: number; @@ -60,3 +62,41 @@ export const createTickFormat = ( return (val: number) => moment(val).format(format); }; + +/** While we support 2 versions of the timeline, we need this adapter. **/ +const adaptYaxisParams = (yaxis: IAxis) => { + const y = { ...yaxis }; + + if (y.units) { + const formatters = tickFormatters(y); + y.tickFormatter = formatters[y.units.type as keyof typeof formatters]; + } else if (yaxis.tickDecimals) { + y.tickFormatter = (val: number) => val.toFixed(yaxis.tickDecimals); + } + + const max = yaxis.max ? yaxis.max : undefined; + const min = yaxis.min ? yaxis.min : undefined; + + y.domain = { + fit: min === undefined && max === undefined, + max, + min, + }; + return y; +}; + +export const extractYAxis = (series: Series) => { + let yaxis = (series._global?.yaxes ?? []).reduce( + (acc: IAxis, item: IAxis) => ({ + ...acc, + ...item, + }), + {} + ); + + if (yaxis) { + yaxis = adaptYaxisParams(yaxis); + } + + return yaxis; +}; diff --git a/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts b/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts index 44a4eb90e74d2e..fe5e9183976fa4 100644 --- a/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts +++ b/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts @@ -12,7 +12,7 @@ import { TimelionVisDependencies } from '../plugin'; import { getTimezone } from './get_timezone'; import { TimelionVisParams } from '../timelion_vis_fn'; import { getDataSearch } from '../helpers/plugin_services'; -import { Series as VisSeries } from '../../common/vis_data'; +import { VisSeries } from '../../common/vis_data'; interface Stats { cacheCount: number; From 451054c28aa8d8ab81387e80679ff6711720be9d Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Tue, 15 Jun 2021 15:16:25 +0300 Subject: [PATCH 36/44] fix issue with static --- .../components/timelion_vis_component.tsx | 72 +++++++++++++------ .../public/helpers/panel_utils.ts | 19 ++--- 2 files changed, 54 insertions(+), 37 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index 1fa3fab05e0ef2..70c0520768bccd 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -13,6 +13,7 @@ import { Settings, Position, Axis, + AxisSpec, TooltipType, PointerEvent, LegendPositionConfig, @@ -48,7 +49,24 @@ interface TimelionVisComponentProps { const MAIN_GROUP_ID = 1; -const DefaultYAxis = () => ; +const DefaultYAxis = () => ( + +); + +const withStaticPadding = (domain: AxisSpec['domain']): AxisSpec['domain'] => + ({ + ...domain, + padding: 50, + // @ts-expect-error + paddingUnit: 'pixel', + } as AxisSpec['domain']); const TimelionVisComponent = ({ interval, @@ -141,6 +159,36 @@ const TimelionVisComponent = ({ return { legendPosition, showLegend }; }, [chart]); + const yAxis = chart + .map((data, index) => { + const yaxis = extractYAxis(data); + + if (yaxis) { + const groupId = data.yaxis ? data.yaxis : MAIN_GROUP_ID; + + return ( + + ); + } + return null; + }) + .filter(Boolean); + return (
{title}
@@ -162,27 +210,7 @@ const TimelionVisComponent = ({ - {chart.length ? ( - chart.map((data, index) => { - const yaxis = extractYAxis(data); - - return yaxis ? ( - - ) : ( - - ); - }) - ) : ( - - )} + {yAxis.length ? yAxis : } {chart.map((data, index) => { const visData = { ...data }; diff --git a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts index d1af9d6946207e..fa0c43c8f7946d 100644 --- a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -7,7 +7,7 @@ */ import moment from 'moment-timezone'; -import type { Position } from '@elastic/charts'; +import { Position } from '@elastic/charts'; import type { TimefilterContract } from 'src/plugins/data/public'; import type { IUiSettingsClient } from 'kibana/public'; @@ -34,7 +34,6 @@ export interface IAxis { tickGenerator?(axis: IAxis): number[]; units?: { type: string; prefix: string; suffix: string }; domain?: { - fit?: boolean; min?: number; max?: number; }; @@ -74,19 +73,11 @@ const adaptYaxisParams = (yaxis: IAxis) => { y.tickFormatter = (val: number) => val.toFixed(yaxis.tickDecimals); } - const max = yaxis.max ? yaxis.max : undefined; - const min = yaxis.min ? yaxis.min : undefined; - - y.domain = { - fit: min === undefined && max === undefined, - max, - min, - }; return y; }; export const extractYAxis = (series: Series) => { - let yaxis = (series._global?.yaxes ?? []).reduce( + const yaxis = (series._global?.yaxes ?? []).reduce( (acc: IAxis, item: IAxis) => ({ ...acc, ...item, @@ -94,9 +85,7 @@ export const extractYAxis = (series: Series) => { {} ); - if (yaxis) { - yaxis = adaptYaxisParams(yaxis); + if (Object.keys(yaxis).length) { + return adaptYaxisParams(yaxis); } - - return yaxis; }; From 1898b8d94daaa2b8bf07656680aaf92cf3d98dda Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Tue, 15 Jun 2021 15:39:40 +0300 Subject: [PATCH 37/44] fix header formatter --- .../public/components/timelion_vis_component.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index 70c0520768bccd..45cd47887c2837 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -196,6 +196,7 @@ const TimelionVisComponent = ({ tickFormat(value), type: TooltipType.VerticalCursor, }} externalPointerEvents={{ tooltip: { visible: false } }} From a1edcf5ff737966cf588d9f64c945b41555194e2 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Tue, 15 Jun 2021 18:08:09 +0300 Subject: [PATCH 38/44] fix points --- .../public/components/series/area.tsx | 11 ++- .../public/components/series/bar.tsx | 5 +- .../components/timelion_vis_component.tsx | 81 +++++++++++-------- 3 files changed, 58 insertions(+), 39 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/series/area.tsx b/src/plugins/vis_type_timelion/public/components/series/area.tsx index 56ff58c81fa129..589a488d3acad4 100644 --- a/src/plugins/vis_type_timelion/public/components/series/area.tsx +++ b/src/plugins/vis_type_timelion/public/components/series/area.tsx @@ -8,21 +8,24 @@ import React from 'react'; import { AreaSeries, ScaleType, CurveType, AreaSeriesStyle, PointShape } from '@elastic/charts'; -import type { Series } from '../../../common/vis_data'; +import type { VisSeries } from '../../../common/vis_data'; interface AreaSeriesComponentProps { index: number; - visData: Series; + visData: VisSeries; groupId: string; } +const isShowLines = (lines: VisSeries['lines'], points: VisSeries['points']) => + lines?.show ? true : points?.show ? false : true; + const getAreaSeriesStyle = ({ color, lines, points }: AreaSeriesComponentProps['visData']) => ({ line: { - opacity: 1, + opacity: isShowLines(lines, points) ? 1 : 0, stroke: color, strokeWidth: lines?.lineWidth !== undefined ? Number(lines.lineWidth) : 3, - visible: lines?.show ?? points?.show ?? true, + visible: isShowLines(lines, points), }, area: { fill: color, diff --git a/src/plugins/vis_type_timelion/public/components/series/bar.tsx b/src/plugins/vis_type_timelion/public/components/series/bar.tsx index f4cfecee324a40..6a97c8fea96905 100644 --- a/src/plugins/vis_type_timelion/public/components/series/bar.tsx +++ b/src/plugins/vis_type_timelion/public/components/series/bar.tsx @@ -8,11 +8,11 @@ import React from 'react'; import { BarSeries, ScaleType, BarSeriesStyle } from '@elastic/charts'; -import type { Series } from '../../../common/vis_data'; +import type { VisSeries } from '../../../common/vis_data'; interface BarSeriesComponentProps { index: number; - visData: Series; + visData: VisSeries; groupId: string; } @@ -50,6 +50,7 @@ export const BarSeriesComponent = ({ index, groupId, visData }: BarSeriesCompone yAccessors={[1]} data={visData.data} sortIndex={index} + enableHistogramMode={false} color={visData.color} stackAccessors={visData.stack ? [0] : undefined} barSeriesStyle={getBarSeriesStyle(visData)} diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index 45cd47887c2837..e4a89500485e27 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -37,6 +37,7 @@ import type { Sheet } from '../helpers/timelion_request_handler'; import type { IInterpreterRenderHandlers } from '../../../expressions'; import type { TimelionVisDependencies } from '../plugin'; import type { RangeFilterParams } from '../../../data/public'; +import type { Series } from '../helpers/timelion_request_handler'; import './timelion_vis.scss'; @@ -60,6 +61,45 @@ const DefaultYAxis = () => ( /> ); +const renderYAxis = (series: Series[]) => { + let isShowYAxisGridLines = true; + + const yAxis = series + .map((data, index) => { + const yaxis = extractYAxis(data); + + if (yaxis) { + const groupId = data.yaxis ? data.yaxis : MAIN_GROUP_ID; + const gridLine = { + visible: isShowYAxisGridLines, + }; + + isShowYAxisGridLines = false; + + return ( + + ); + } + return null; + }) + .filter(Boolean); + + return yAxis.length ? yAxis : ; +}; + const withStaticPadding = (domain: AxisSpec['domain']): AxisSpec['domain'] => ({ ...domain, @@ -159,36 +199,6 @@ const TimelionVisComponent = ({ return { legendPosition, showLegend }; }, [chart]); - const yAxis = chart - .map((data, index) => { - const yaxis = extractYAxis(data); - - if (yaxis) { - const groupId = data.yaxis ? data.yaxis : MAIN_GROUP_ID; - - return ( - - ); - } - return null; - }) - .filter(Boolean); - return (
{title}
@@ -210,9 +220,15 @@ const TimelionVisComponent = ({ externalPointerEvents={{ tooltip: { visible: false } }} /> - + - {yAxis.length ? yAxis : } + {renderYAxis(chart)} {chart.map((data, index) => { const visData = { ...data }; @@ -221,7 +237,6 @@ const TimelionVisComponent = ({ if (!visData.color) { visData.color = colors[index % colors.length]; } - return ( Date: Wed, 16 Jun 2021 11:19:50 +0300 Subject: [PATCH 39/44] fix ts error --- .../public/components/timelion_vis_component.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index e4a89500485e27..4f3016245a1db8 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -101,12 +101,11 @@ const renderYAxis = (series: Series[]) => { }; const withStaticPadding = (domain: AxisSpec['domain']): AxisSpec['domain'] => - ({ + (({ ...domain, padding: 50, - // @ts-expect-error paddingUnit: 'pixel', - } as AxisSpec['domain']); + } as unknown) as AxisSpec['domain']); const TimelionVisComponent = ({ interval, From f08790297785dd0a5eaf8ff3d26a535aa971ff41 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Wed, 16 Jun 2021 15:10:45 +0300 Subject: [PATCH 40/44] Fix yaxis behavior --- .../components/timelion_vis_component.tsx | 96 ++++++++++++------- 1 file changed, 62 insertions(+), 34 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index 4f3016245a1db8..5acfc7ee67e68a 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -48,6 +48,16 @@ interface TimelionVisComponentProps { renderComplete: IInterpreterRenderHandlers['done']; } +interface AxisOptions { + groupId: number; + key: number; + id: string; + title?: string; + position?: Position; + tickFormat?: (val: number) => string; + domain?: AxisSpec['domain']; +} + const MAIN_GROUP_ID = 1; const DefaultYAxis = () => ( @@ -62,40 +72,58 @@ const DefaultYAxis = () => ( ); const renderYAxis = (series: Series[]) => { - let isShowYAxisGridLines = true; - - const yAxis = series - .map((data, index) => { - const yaxis = extractYAxis(data); - - if (yaxis) { - const groupId = data.yaxis ? data.yaxis : MAIN_GROUP_ID; - const gridLine = { - visible: isShowYAxisGridLines, - }; - - isShowYAxisGridLines = false; - - return ( - - ); - } - return null; - }) - .filter(Boolean); + const yAxisOptions = series.reduce((acc, data, index) => { + const yaxis = extractYAxis(data); + const groupId = data.yaxis ? data.yaxis : MAIN_GROUP_ID; + + let extraAxisOptions = {}; + + if (yaxis) { + extraAxisOptions = { + id: yaxis.position + yaxis.axisLabel, + title: yaxis.axisLabel, + position: yaxis.position, + tickFormat: yaxis.tickFormatter, + domain: withStaticPadding({ + fit: yaxis.min === undefined && yaxis.max === undefined, + min: yaxis.min, + max: yaxis.max, + }), + }; + } + + if (acc.every((axis) => axis.groupId !== groupId)) { + acc.push({ + groupId, + key: index, + domain: withStaticPadding({ + fit: false, + }), + id: 'left' + index, + ...extraAxisOptions, + }); + } else if (yaxis) { + const axisOptionIndex = acc.findIndex((axis) => axis.groupId === groupId); + acc[axisOptionIndex] = { ...acc[axisOptionIndex], ...extraAxisOptions }; + } + + return acc; + }, [] as AxisOptions[]); + + const yAxis = yAxisOptions.map((option, index) => ( + + )); return yAxis.length ? yAxis : ; }; From 9af9dcb257bb83aa1d64f96f51aff935a415df21 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Wed, 16 Jun 2021 17:41:56 +0300 Subject: [PATCH 41/44] Fix some case with yaxis --- .../components/timelion_vis_component.tsx | 68 ++----------------- .../public/helpers/panel_utils.ts | 50 ++++++++++++-- 2 files changed, 53 insertions(+), 65 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index 5acfc7ee67e68a..256e59ef511394 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -13,7 +13,6 @@ import { Settings, Position, Axis, - AxisSpec, TooltipType, PointerEvent, LegendPositionConfig, @@ -25,9 +24,11 @@ import { useKibana } from '../../../kibana_react/public'; import { AreaSeriesComponent, BarSeriesComponent } from './series'; import { - extractYAxis, + extractAllYAxis, + withStaticPadding, createTickFormat, validateLegendPositionValue, + MAIN_GROUP_ID, } from '../helpers/panel_utils'; import { colors } from '../helpers/chart_constants'; @@ -48,18 +49,6 @@ interface TimelionVisComponentProps { renderComplete: IInterpreterRenderHandlers['done']; } -interface AxisOptions { - groupId: number; - key: number; - id: string; - title?: string; - position?: Position; - tickFormat?: (val: number) => string; - domain?: AxisSpec['domain']; -} - -const MAIN_GROUP_ID = 1; - const DefaultYAxis = () => ( ( ); const renderYAxis = (series: Series[]) => { - const yAxisOptions = series.reduce((acc, data, index) => { - const yaxis = extractYAxis(data); - const groupId = data.yaxis ? data.yaxis : MAIN_GROUP_ID; - - let extraAxisOptions = {}; - - if (yaxis) { - extraAxisOptions = { - id: yaxis.position + yaxis.axisLabel, - title: yaxis.axisLabel, - position: yaxis.position, - tickFormat: yaxis.tickFormatter, - domain: withStaticPadding({ - fit: yaxis.min === undefined && yaxis.max === undefined, - min: yaxis.min, - max: yaxis.max, - }), - }; - } - - if (acc.every((axis) => axis.groupId !== groupId)) { - acc.push({ - groupId, - key: index, - domain: withStaticPadding({ - fit: false, - }), - id: 'left' + index, - ...extraAxisOptions, - }); - } else if (yaxis) { - const axisOptionIndex = acc.findIndex((axis) => axis.groupId === groupId); - acc[axisOptionIndex] = { ...acc[axisOptionIndex], ...extraAxisOptions }; - } - - return acc; - }, [] as AxisOptions[]); + const yAxisOptions = extractAllYAxis(series); const yAxis = yAxisOptions.map((option, index) => ( { return yAxis.length ? yAxis : ; }; -const withStaticPadding = (domain: AxisSpec['domain']): AxisSpec['domain'] => - (({ - ...domain, - padding: 50, - paddingUnit: 'pixel', - } as unknown) as AxisSpec['domain']); - const TimelionVisComponent = ({ interval, seriesList, diff --git a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts index fa0c43c8f7946d..1ee834b7d30ed5 100644 --- a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -7,7 +7,7 @@ */ import moment from 'moment-timezone'; -import { Position } from '@elastic/charts'; +import { Position, AxisSpec } from '@elastic/charts'; import type { TimefilterContract } from 'src/plugins/data/public'; import type { IUiSettingsClient } from 'kibana/public'; @@ -38,7 +38,7 @@ export interface IAxis { max?: number; }; position?: Position; - axisLabel: string; + axisLabel?: string; } export const validateLegendPositionValue = (position: string) => /^(n|s)(e|w)$/s.test(position); @@ -63,6 +63,15 @@ export const createTickFormat = ( }; /** While we support 2 versions of the timeline, we need this adapter. **/ +export const MAIN_GROUP_ID = 1; + +export const withStaticPadding = (domain: AxisSpec['domain']): AxisSpec['domain'] => + (({ + ...domain, + padding: 50, + paddingUnit: 'pixel', + } as unknown) as AxisSpec['domain']); + const adaptYaxisParams = (yaxis: IAxis) => { const y = { ...yaxis }; @@ -73,10 +82,19 @@ const adaptYaxisParams = (yaxis: IAxis) => { y.tickFormatter = (val: number) => val.toFixed(yaxis.tickDecimals); } - return y; + return { + title: y.axisLabel, + position: y.position, + tickFormat: y.tickFormatter, + domain: withStaticPadding({ + fit: y.min === undefined && y.max === undefined, + min: y.min, + max: y.max, + }), + }; }; -export const extractYAxis = (series: Series) => { +const extractYAxisForSeries = (series: Series) => { const yaxis = (series._global?.yaxes ?? []).reduce( (acc: IAxis, item: IAxis) => ({ ...acc, @@ -89,3 +107,27 @@ export const extractYAxis = (series: Series) => { return adaptYaxisParams(yaxis); } }; + +export const extractAllYAxis = (series: Series[]) => { + return series.reduce((acc, data, index) => { + const yaxis = extractYAxisForSeries(data); + const groupId = `${data.yaxis ? data.yaxis : MAIN_GROUP_ID}`; + + if (acc.every((axis) => axis.groupId !== groupId)) { + acc.push({ + groupId, + domain: withStaticPadding({ + fit: false, + }), + id: (yaxis?.position || Position.Left) + index, + position: Position.Left, + ...yaxis, + }); + } else if (yaxis) { + const axisOptionIndex = acc.findIndex((axis) => axis.groupId === groupId); + acc[axisOptionIndex] = { ...acc[axisOptionIndex], ...yaxis }; + } + + return acc; + }, [] as Array>); +}; From 0eb286fb3a579b654441d1925380ac3f7601144d Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Tue, 22 Jun 2021 14:25:57 +0300 Subject: [PATCH 42/44] Add deprecation message and update asciidoc --- docs/management/advanced-options.asciidoc | 3 +++ src/core/public/doc_links/doc_links_service.ts | 1 + src/plugins/vis_type_timelion/server/ui_settings.ts | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 853180ec816e97..e6581c0e334349 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -473,6 +473,9 @@ Shows the Timelion tutorial to users when they first open the Timelion app. Used for calculating automatic intervals in visualizations, this is the number of buckets to try to represent. +[[timelion-legacyChartsLibrary]]`timelion:legacyChartsLibrary`:: +Enables legacy charts library for Timelion visualizations. + [float] [[kibana-visualization-settings]] diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 95091a761639b6..668349418f1208 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -213,6 +213,7 @@ export class DocLinksService { indexManagement: `${ELASTICSEARCH_DOCS}index-mgmt.html`, kibanaSearchSettings: `${KIBANA_DOCS}advanced-options.html#kibana-search-settings`, visualizationSettings: `${KIBANA_DOCS}advanced-options.html#kibana-visualization-settings`, + timelionSettings: `${KIBANA_DOCS}advanced-options.html#kibana-timelion-settings`, }, ml: { guide: `${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/index.html`, diff --git a/src/plugins/vis_type_timelion/server/ui_settings.ts b/src/plugins/vis_type_timelion/server/ui_settings.ts index 534ed728a9bc61..7464088338f4ef 100644 --- a/src/plugins/vis_type_timelion/server/ui_settings.ts +++ b/src/plugins/vis_type_timelion/server/ui_settings.ts @@ -28,6 +28,12 @@ export function getUiSettings( description: i18n.translate('timelion.uiSettings.legacyChartsLibraryDescription', { defaultMessage: 'Enables legacy charts library for Timelion visualizations', }), + deprecation: { + message: i18n.translate('timelion.uiSettings.legacyChartsLibraryDeprication', { + defaultMessage: 'This setting is deprecated and will not be supported as of 8.0.', + }), + docLinksKey: 'timelionSettings', + }, value: false, category: ['timelion'], schema: schema.boolean(), From 572df6e84420367ca7123af0ed165e3b0d213046 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Mon, 28 Jun 2021 16:15:26 +0300 Subject: [PATCH 43/44] Fix title --- .../vis_type_timelion/public/components/timelion_vis.scss | 3 --- .../public/components/timelion_vis_component.tsx | 7 ++++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis.scss b/src/plugins/vis_type_timelion/public/components/timelion_vis.scss index 546863d43562ba..663563432d56b5 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis.scss +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis.scss @@ -6,8 +6,5 @@ } .timelionChart__topTitle { - @include euiFontSizeXS; - flex: 0; text-align: center; - font-weight: $euiFontWeightBold; } diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index 256e59ef511394..4690f4fe11e458 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -18,6 +18,7 @@ import { LegendPositionConfig, LayoutDirection, } from '@elastic/charts'; +import { EuiTitle } from '@elastic/eui'; import { useKibana } from '../../../kibana_react/public'; @@ -174,7 +175,11 @@ const TimelionVisComponent = ({ return (
-
{title}
+ {title && ( + +

{title}

+
+ )} Date: Thu, 29 Jul 2021 11:08:48 +0300 Subject: [PATCH 44/44] some text improvements --- src/plugins/vis_type_timelion/server/ui_settings.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/vis_type_timelion/server/ui_settings.ts b/src/plugins/vis_type_timelion/server/ui_settings.ts index 7464088338f4ef..1d8dc997a3f6aa 100644 --- a/src/plugins/vis_type_timelion/server/ui_settings.ts +++ b/src/plugins/vis_type_timelion/server/ui_settings.ts @@ -23,10 +23,10 @@ export function getUiSettings( return { [UI_SETTINGS.LEGACY_CHARTS_LIBRARY]: { name: i18n.translate('timelion.uiSettings.legacyChartsLibraryLabel', { - defaultMessage: 'Legacy charts library', + defaultMessage: 'Timelion legacy charts library', }), description: i18n.translate('timelion.uiSettings.legacyChartsLibraryDescription', { - defaultMessage: 'Enables legacy charts library for Timelion visualizations', + defaultMessage: 'Enables the legacy charts library for timelion charts in Visualize', }), deprecation: { message: i18n.translate('timelion.uiSettings.legacyChartsLibraryDeprication', {