diff --git a/src/plugins/yagr/__stories__/Yagr.stories.tsx b/src/plugins/yagr/__stories__/Yagr.stories.tsx index 27cc6fab..d314661c 100644 --- a/src/plugins/yagr/__stories__/Yagr.stories.tsx +++ b/src/plugins/yagr/__stories__/Yagr.stories.tsx @@ -4,8 +4,9 @@ import {Button} from '@gravity-ui/uikit'; import {settings} from '../../../libs'; import {ChartKit} from '../../../components/ChartKit'; import type {ChartKitRef} from '../../../types'; + import {CustomTooltipProps, TooltipHandlerData, YagrPlugin} from '../'; -import {getNewConfig, line10} from './mocks/line10'; +import {getNewConfig, line10, line10WithGrafanaStyle} from './mocks/line10'; import '@gravity-ui/yagr/dist/index.css'; import placement from '@gravity-ui/yagr/dist/YagrCore/plugins/tooltip/placement'; @@ -129,6 +130,23 @@ const CustomTooltipImpl: Story = () => { ); }; +const AreaTemplate: Story = () => { + const [shown, setShown] = React.useState(false); + const chartkitRef = React.useRef(); + + if (!shown) { + settings.set({plugins: [YagrPlugin]}); + return ; + } + + return ( +
+ +
+ ); +}; + export const Line = LineTemplate.bind({}); export const Updates = UpdatesTemplate.bind({}); export const CustomTooltip = CustomTooltipImpl.bind({}); +export const Area = AreaTemplate.bind({}); diff --git a/src/plugins/yagr/__stories__/mocks/line10.ts b/src/plugins/yagr/__stories__/mocks/line10.ts index eff50066..eba7506f 100644 --- a/src/plugins/yagr/__stories__/mocks/line10.ts +++ b/src/plugins/yagr/__stories__/mocks/line10.ts @@ -1,4 +1,4 @@ -import type {YagrWidgetData} from '../../types'; +import type {AreaSeriesOptions, YagrSeriesData, YagrWidgetData} from '../../types'; export const line10: YagrWidgetData = { data: { @@ -89,3 +89,107 @@ export const getNewConfig = () => { }, }; }; + +// Grafana classic colors palette +const colors = [ + '#7EB26D', // 0: pale green + '#EAB839', // 1: mustard + '#6ED0E0', // 2: light blue + '#EF843C', // 3: orange + '#E24D42', // 4: red + '#1F78C1', // 5: ocean + '#BA43A9', // 6: purple + '#705DA0', // 7: violet + '#508642', // 8: dark green + '#CCA300', // 9: dark sand +]; + +function colorHexToRGBA(htmlColor: string, opacity: number) { + const COLOR_REGEX = /^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/; + const arrRGB = htmlColor.match(COLOR_REGEX); + if (arrRGB === null) { + throw new Error( + 'Invalid color passed, the color should be in the html format. Example: #ff0033', + ); + } + const red = parseInt(arrRGB[1], 16); + const green = parseInt(arrRGB[2], 16); + const blue = parseInt(arrRGB[3], 16); + return `rgba(${[red, green, blue, opacity].join(',')})`; +} + +const graphs: YagrSeriesData[] = [ + { + id: '0', + name: 'Serie 1', + type: 'area', + color: colorHexToRGBA(colors[0], 0.1), + lineColor: colors[0], + legendColorKey: 'lineColor', + data: [45, 52, 89, 72, 39, 49, 82, 59, 36, 5], + }, + { + id: '1', + name: 'Serie 2', + type: 'area', + color: colorHexToRGBA(colors[1], 0.1), + lineColor: colors[1], + legendColorKey: 'lineColor', + data: [37, 6, 51, 10, 65, 35, 72, 0, 94, 54], + }, + { + id: '2', + name: 'Serie 3', + type: 'area', + color: colorHexToRGBA(colors[2], 0.1), + lineColor: colors[2], + legendColorKey: 'lineColor', + data: [26, 54, 15, 40, 43, 18, 65, 46, 51, 33], + }, +]; + +export const line10WithGrafanaStyle: YagrWidgetData = { + data: { + timeline: [ + 1636838612441, 1636925012441, 1637011412441, 1637097812441, 1637184212441, + 1637270612441, 1637357012441, 1637443412441, 1637529812441, 1637616212441, + ], + graphs, + }, + libraryConfig: { + chart: { + series: { + type: 'area', + lineWidth: 1.5, + }, + select: { + zoom: false, + }, + }, + title: { + text: 'line: random 10 pts', + }, + axes: { + x: {}, + }, + scales: { + x: {}, + y: { + type: 'linear', + range: 'nice', + }, + }, + cursor: { + x: { + visible: true, + style: 'solid 2px rgba(230, 2, 7, 0.3)', + }, + }, + tooltip: { + show: true, + tracking: 'sticky', + }, + legend: {}, + processing: {}, + }, +}; diff --git a/src/plugins/yagr/renderer/tooltip/renderTooltip.ts b/src/plugins/yagr/renderer/tooltip/renderTooltip.ts index a44f226b..ea957183 100644 --- a/src/plugins/yagr/renderer/tooltip/renderTooltip.ts +++ b/src/plugins/yagr/renderer/tooltip/renderTooltip.ts @@ -1,7 +1,9 @@ import {dateTime} from '@gravity-ui/date-utils'; -import type {TooltipRow, TooltipRenderOptions, ValueFormatter} from '../../types'; -import type {TooltipData, TooltipLine} from './types'; + +import type {TooltipRenderOptions, TooltipRow, ValueFormatter, YagrWidgetData} from '../../types'; + import {formatTooltip} from './tooltip'; +import type {TooltipData, TooltipLine} from './types'; const calcOption = (d: T | {[key in string]: T} | undefined) => { return typeof d === 'object' && d !== null @@ -11,71 +13,98 @@ const calcOption = (d: T | {[key in string]: T} | undefined) => { : d; }; +const getSeriesColorProperty = (args: { + data: TooltipRenderOptions; + userData: YagrWidgetData['data']; + row: TooltipRow; + rowIndex: number; +}) => { + const {data, userData, row, rowIndex} = args; + const userSeries = userData.graphs[rowIndex]; + const lineColor = data.yagr.getSeriesById(row.id)?.lineColor; + let seriesColor = row.color; + + switch (userSeries?.legendColorKey) { + case 'lineColor': { + if (lineColor) { + seriesColor = lineColor; + } + break; + } + case 'color': + default: { + seriesColor = row.color; + } + } + + return seriesColor; +}; + /* * Default tooltip renderer. * Adapter between native Yagr tooltip config and ChartKit * tooltip renderer. */ -export const getRenderTooltip = (timeZone?: string) => (data: TooltipRenderOptions) => { - const cfg = data.yagr.config; - const timeMultiplier = cfg.chart.timeMultiplier || 1; - const opts = data.options; - const {x, state} = data; +export const getRenderTooltip = + (userData: YagrWidgetData['data']) => (data: TooltipRenderOptions) => { + const {timeZone} = userData; + const cfg = data.yagr.config; + const timeMultiplier = cfg.chart.timeMultiplier || 1; + const opts = data.options; + const {x, state} = data; - let sumTotal = 0; - const rows = Object.values(data.scales).reduce((acc, scale) => { - sumTotal += scale.sum || 0; - return acc.concat(scale.rows); - }, [] as TooltipRow[]); + let sumTotal = 0; + const rows = Object.values(data.scales).reduce((acc, scale) => { + sumTotal += scale.sum || 0; + return acc.concat(scale.rows); + }, [] as TooltipRow[]); + const lines = rows.length; + const sum = calcOption(opts.sum); - const lines = rows.length; - const sum = calcOption(opts.sum); + const maxLines = calcOption(opts.maxLines); + const valueFormatter = calcOption(opts.value); + // eslint-disable-next-line no-nested-ternary + const hiddenRowsNumber = state.pinned + ? undefined + : lines > maxLines + ? Math.abs(maxLines - lines) + : undefined; - const maxLines = calcOption(opts.maxLines); - const valueFormatter = calcOption(opts.value); - // eslint-disable-next-line no-nested-ternary - const hiddenRowsNumber = state.pinned - ? undefined - : lines > maxLines - ? Math.abs(maxLines - lines) - : undefined; + const hiddenRowsSum = hiddenRowsNumber + ? valueFormatter( + rows + .slice(-hiddenRowsNumber) + .reduce((acc, {originalValue}) => acc + (originalValue || 0), 0), + ) + : undefined; + const tooltipFormatOptions: TooltipData = { + activeRowAlwaysFirstInTooltip: rows.length > 1, + tooltipHeader: dateTime({input: x / timeMultiplier, timeZone}).format( + 'DD MMMM YYYY HH:mm:ss', + ), + shared: true, + lines: rows.map( + (row, i) => + ({ + ...row, + seriesName: row.name || 'Serie ' + (i + 1), + seriesColor: getSeriesColorProperty({data, userData, row, rowIndex: i}), + selectedSeries: row.active, + seriesIdx: row.seriesIdx, + percentValue: + typeof row.transformed === 'number' ? row.transformed.toFixed(1) : '', + } as TooltipLine), + ), + withPercent: calcOption(opts.percent), + hiddenRowsNumber: hiddenRowsNumber as number, + hiddenRowsSum, + }; - const hiddenRowsSum = hiddenRowsNumber - ? valueFormatter( - rows - .slice(-hiddenRowsNumber) - .reduce((acc, {originalValue}) => acc + (originalValue || 0), 0), - ) - : undefined; + if (sum) { + tooltipFormatOptions.sum = valueFormatter(sumTotal); + } - const tooltipFormatOptions: TooltipData = { - activeRowAlwaysFirstInTooltip: rows.length > 1, - tooltipHeader: dateTime({input: x / timeMultiplier, timeZone}).format( - 'DD MMMM YYYY HH:mm:ss', - ), - shared: true, - lines: rows.map( - (row, i) => - ({ - ...row, - seriesName: row.name || 'Serie ' + (i + 1), - seriesColor: row.color, - selectedSeries: row.active, - seriesIdx: row.seriesIdx, - percentValue: - typeof row.transformed === 'number' ? row.transformed.toFixed(1) : '', - } as TooltipLine), - ), - withPercent: calcOption(opts.percent), - hiddenRowsNumber: hiddenRowsNumber as number, - hiddenRowsSum, + return formatTooltip(tooltipFormatOptions, { + lastVisibleRowIndex: state.pinned ? rows.length - 1 : maxLines - 1, + }); }; - - if (sum) { - tooltipFormatOptions.sum = valueFormatter(sumTotal); - } - - return formatTooltip(tooltipFormatOptions, { - lastVisibleRowIndex: state.pinned ? rows.length - 1 : maxLines - 1, - }); -}; diff --git a/src/plugins/yagr/renderer/utils.ts b/src/plugins/yagr/renderer/utils.ts index f3163a57..462faf4c 100644 --- a/src/plugins/yagr/renderer/utils.ts +++ b/src/plugins/yagr/renderer/utils.ts @@ -172,7 +172,7 @@ export const shapeYagrConfig = (args: ShapeYagrConfigArgs): MinimalValidConfig = if (config.tooltip?.show) { config.tooltip = config.tooltip || {}; - config.tooltip.render = config.tooltip?.render || getRenderTooltip(timeZone); + config.tooltip.render = config.tooltip?.render || getRenderTooltip(data); if (!config.tooltip.className) { // "className" property prevent default yagr styles adding diff --git a/src/plugins/yagr/types.ts b/src/plugins/yagr/types.ts index f90340a9..49b88bc3 100644 --- a/src/plugins/yagr/types.ts +++ b/src/plugins/yagr/types.ts @@ -1,4 +1,4 @@ -import type {MinimalValidConfig, RawSerieData, YagrConfig} from '@gravity-ui/yagr'; +import type {MinimalValidConfig, RawSerieData, SeriesOptions, YagrConfig} from '@gravity-ui/yagr'; import type Yagr from '@gravity-ui/yagr'; import {ChartKitProps} from 'src/types'; @@ -14,9 +14,20 @@ export type YagrWidgetProps = ChartKitProps<'yagr'> & { id: string; }; +export type YagrSeriesData> = RawSerieData & { + /** + * Determines what data value should be used to get a color for tooltip series. Does not work in case of using custom tooltip rendered via `tooltip` property. + * - `lineColor` indicates that lineColor property should be used + * - `color` indicates that color property should be used + * + * @default 'color' + */ + legendColorKey?: 'color' | 'lineColor'; +}; + export type YagrWidgetData = { data: { - graphs: RawSerieData[]; + graphs: YagrSeriesData[]; timeline: number[]; /** * Allow to setup timezone for X axis and tooltip's header.