Skip to content

Commit

Permalink
feat(Yagr plugin): add legendColorKey property to series options (#440
Browse files Browse the repository at this point in the history
)

* chore: add issue story

* feat(Yagr plugin): add legendColorKey property to series options

---------

Co-authored-by: Evgeny Alaev <[email protected]>
  • Loading branch information
artemmufazalov and korvin89 authored Mar 14, 2024
1 parent 8853135 commit ae6a00f
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 63 deletions.
19 changes: 18 additions & 1 deletion src/plugins/yagr/__stories__/Yagr.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {ChartKit} from '../../../components/ChartKit';
import {settings} from '../../../libs';
import type {ChartKitRef} from '../../../types';

import {getNewConfig, line10} from './mocks/line10';
import {getNewConfig, line10, line10WithGrafanaStyle} from './mocks/line10';

import '@gravity-ui/yagr/dist/index.css';

Expand Down Expand Up @@ -132,6 +132,23 @@ const CustomTooltipImpl: Story<any> = () => {
);
};

const AreaTemplate: Story<any> = () => {
const [shown, setShown] = React.useState(false);
const chartkitRef = React.useRef<ChartKitRef>();

if (!shown) {
settings.set({plugins: [YagrPlugin]});
return <Button onClick={() => setShown(true)}>Show chart</Button>;
}

return (
<div style={{height: 300, width: '100%'}}>
<ChartKit ref={chartkitRef} id="1" type="yagr" data={line10WithGrafanaStyle} />
</div>
);
};

export const Line = LineTemplate.bind({});
export const Updates = UpdatesTemplate.bind({});
export const CustomTooltip = CustomTooltipImpl.bind({});
export const Area = AreaTemplate.bind({});
106 changes: 105 additions & 1 deletion src/plugins/yagr/__stories__/mocks/line10.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {YagrWidgetData} from '../../types';
import type {AreaSeriesOptions, YagrSeriesData, YagrWidgetData} from '../../types';

export const line10: YagrWidgetData = {
data: {
Expand Down Expand Up @@ -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<AreaSeriesOptions>[] = [
{
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: {},
},
};
143 changes: 85 additions & 58 deletions src/plugins/yagr/renderer/tooltip/renderTooltip.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {dateTime} from '@gravity-ui/date-utils';

import type {TooltipRenderOptions, TooltipRow, ValueFormatter} from '../../types';
import type {TooltipRenderOptions, TooltipRow, ValueFormatter, YagrWidgetData} from '../../types';

import {formatTooltip} from './tooltip';
import type {TooltipData, TooltipLine} from './types';
Expand All @@ -13,71 +13,98 @@ const calcOption = <T>(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<number>(opts.maxLines);
const valueFormatter = calcOption<ValueFormatter>(opts.value);
// eslint-disable-next-line no-nested-ternary
const hiddenRowsNumber = state.pinned
? undefined
: lines > maxLines
? Math.abs(maxLines - lines)
: undefined;

const maxLines = calcOption<number>(opts.maxLines);
const valueFormatter = calcOption<ValueFormatter>(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<boolean>(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<boolean>(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,
});
};
2 changes: 1 addition & 1 deletion src/plugins/yagr/renderer/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,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
Expand Down
15 changes: 13 additions & 2 deletions src/plugins/yagr/types.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -15,9 +15,20 @@ export type YagrWidgetProps = ChartKitProps<'yagr'> & {
id: string;
};

export type YagrSeriesData<T = Omit<SeriesOptions, 'type'>> = RawSerieData<T> & {
/**
* 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.
Expand Down

0 comments on commit ae6a00f

Please sign in to comment.