VizLegend: Represent line style in series legend and tooltip (#87558)

* wip

* aand finished

* remove console log

* use css for styling

* user boerder for solid as well

* misc fixes

* attempt css based approach

---------

Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
Domas
2024-05-10 21:27:09 +03:00
committed by GitHub
parent f880abc292
commit 708bcda76a
11 changed files with 76 additions and 28 deletions

View File

@@ -1,19 +1,21 @@
import { css, cx } from '@emotion/css';
import React, { CSSProperties } from 'react';
import { fieldColorModeRegistry } from '@grafana/data';
import { GrafanaTheme2, fieldColorModeRegistry } from '@grafana/data';
import { LineStyle } from '@grafana/schema';
import { useTheme2, useStyles2 } from '../../themes';
export interface Props extends React.HTMLAttributes<HTMLDivElement> {
color?: string;
gradient?: string;
lineStyle?: LineStyle;
}
export const SeriesIcon = React.memo(
React.forwardRef<HTMLDivElement, Props>(({ color, className, gradient, ...restProps }, ref) => {
React.forwardRef<HTMLDivElement, Props>(({ color, className, gradient, lineStyle, ...restProps }, ref) => {
const theme = useTheme2();
const styles2 = useStyles2(getStyles);
const styles = useStyles2(getStyles);
let cssColor: string;
@@ -29,28 +31,48 @@ export const SeriesIcon = React.memo(
cssColor = color!;
}
const styles: CSSProperties = {
background: cssColor,
width: '14px',
height: '4px',
borderRadius: theme.shape.radius.pill,
display: 'inline-block',
marginRight: '8px',
};
let customStyle: CSSProperties;
if (lineStyle?.fill === 'dot' && !gradient) {
// make a circle bg image and repeat it
customStyle = {
backgroundImage: `radial-gradient(circle at 2px 2px, ${color} 2px, transparent 0)`,
backgroundSize: '4px 4px',
backgroundRepeat: 'space',
};
} else if (lineStyle?.fill === 'dash' && !gradient) {
// make a rectangle bg image and repeat it
customStyle = {
backgroundImage: `linear-gradient(to right, ${color} 100%, transparent 0%)`,
backgroundSize: '6px 4px',
backgroundRepeat: 'space',
};
} else {
customStyle = {
background: cssColor,
borderRadius: theme.shape.radius.pill,
};
}
return (
<div
data-testid="series-icon"
ref={ref}
className={cx(className, styles2.forcedColors)}
style={styles}
className={cx(className, styles.forcedColors, styles.container)}
style={customStyle}
{...restProps}
/>
);
})
);
const getStyles = () => ({
const getStyles = (theme: GrafanaTheme2) => ({
container: css({
marginRight: '8px',
display: 'inline-block',
width: '14px',
height: '4px',
}),
forcedColors: css({
'@media (forced-colors: active)': {
forcedColorAdjust: 'none',

View File

@@ -75,6 +75,7 @@ export const VizLegendListItem = <T = unknown,>({
color={item.color}
gradient={item.gradient}
readonly={readonly}
lineStyle={item.lineStyle}
/>
<button
disabled={readonly}

View File

@@ -1,5 +1,7 @@
import React, { useCallback } from 'react';
import { LineStyle } from '@grafana/schema';
import { SeriesColorPicker } from '../ColorPicker/ColorPicker';
import { usePanelContext } from '../PanelChrome';
@@ -10,12 +12,13 @@ interface Props {
color?: string;
gradient?: string;
readonly?: boolean;
lineStyle?: LineStyle;
}
/**
* @internal
*/
export const VizLegendSeriesIcon = React.memo(({ seriesName, color, gradient, readonly }: Props) => {
export const VizLegendSeriesIcon = React.memo(({ seriesName, color, gradient, readonly, lineStyle }: Props) => {
const { onSeriesColorChange } = usePanelContext();
const onChange = useCallback(
(color: string) => {
@@ -34,12 +37,13 @@ export const VizLegendSeriesIcon = React.memo(({ seriesName, color, gradient, re
ref={ref}
onClick={showColorPicker}
onMouseLeave={hideColorPicker}
lineStyle={lineStyle}
/>
)}
</SeriesColorPicker>
);
}
return <SeriesIcon color={color} gradient={gradient} />;
return <SeriesIcon color={color} gradient={gradient} lineStyle={lineStyle} />;
});
VizLegendSeriesIcon.displayName = 'VizLegendSeriesIcon';

View File

@@ -69,7 +69,12 @@ export const LegendTableItem = ({
<tr className={cx(styles.row, className)}>
<td>
<span className={styles.itemWrapper}>
<VizLegendSeriesIcon color={item.color} seriesName={item.fieldName ?? item.label} readonly={readonly} />
<VizLegendSeriesIcon
color={item.color}
seriesName={item.fieldName ?? item.label}
readonly={readonly}
lineStyle={item.lineStyle}
/>
<button
disabled={readonly}
type="button"

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { DataFrameFieldIndex, DisplayValue } from '@grafana/data';
import { LegendDisplayMode, LegendPlacement } from '@grafana/schema';
import { LegendDisplayMode, LegendPlacement, LineStyle } from '@grafana/schema';
export enum SeriesVisibilityChangeBehavior {
Isolate,
@@ -49,4 +49,5 @@ export interface VizLegendItem<T = any> {
fieldIndex?: DataFrameFieldIndex;
fieldName?: string;
data?: T;
lineStyle?: LineStyle;
}

View File

@@ -2,8 +2,10 @@ import { css, cx } from '@emotion/css';
import React from 'react';
import { FALLBACK_COLOR, GrafanaTheme2 } from '@grafana/data';
import { LineStyle } from '@grafana/schema';
import { useStyles2 } from '../../themes';
import { SeriesIcon } from '../VizLegend/SeriesIcon';
import { ColorIndicator, DEFAULT_COLOR_INDICATOR } from './types';
import { getColorIndicatorClass } from './utils';
@@ -17,6 +19,7 @@ interface Props {
color?: string;
colorIndicator?: ColorIndicator;
position?: ColorIndicatorPosition;
lineStyle?: LineStyle;
}
export type ColorIndicatorStyles = ReturnType<typeof getStyles>;
@@ -25,9 +28,20 @@ export const VizTooltipColorIndicator = ({
color = FALLBACK_COLOR,
colorIndicator = DEFAULT_COLOR_INDICATOR,
position = ColorIndicatorPosition.Leading,
lineStyle,
}: Props) => {
const styles = useStyles2(getStyles);
if (colorIndicator === ColorIndicator.series) {
return (
<SeriesIcon
color={color}
lineStyle={lineStyle}
className={position === ColorIndicatorPosition.Leading ? styles.leading : styles.trailing}
/>
);
}
return (
<span
style={{ backgroundColor: color }}
@@ -47,12 +61,6 @@ const getStyles = (theme: GrafanaTheme2) => ({
trailing: css({
marginLeft: theme.spacing(0.5),
}),
series: css({
width: '14px',
height: '4px',
borderRadius: theme.shape.radius.pill,
minWidth: '14px',
}),
value: css({
width: '12px',
height: '12px',

View File

@@ -34,7 +34,7 @@ export const VizTooltipContent = ({
return (
<div className={styles.wrapper} style={scrollableStyle}>
{items.map(({ label, value, color, colorIndicator, colorPlacement, isActive }, i) => (
{items.map(({ label, value, color, colorIndicator, colorPlacement, isActive, lineStyle }, i) => (
<VizTooltipRow
key={i}
label={label}
@@ -45,6 +45,7 @@ export const VizTooltipContent = ({
isActive={isActive}
justify={'space-between'}
isPinned={isPinned}
lineStyle={lineStyle}
/>
))}
{children}

View File

@@ -36,6 +36,7 @@ export const VizTooltipRow = ({
isActive = false,
marginRight = '0px',
isPinned,
lineStyle,
}: VizTooltipRowProps) => {
const styles = useStyles2(getStyles, justify, marginRight);
@@ -118,7 +119,7 @@ export const VizTooltipRow = ({
{(color || label) && (
<div className={styles.valueWrapper}>
{color && colorPlacement === ColorPlacement.first && (
<VizTooltipColorIndicator color={color} colorIndicator={colorIndicator} />
<VizTooltipColorIndicator color={color} colorIndicator={colorIndicator} lineStyle={lineStyle} />
)}
{!isPinned ? (
<div className={cx(styles.label, isActive && styles.activeSeries)}>{label}</div>
@@ -154,6 +155,7 @@ export const VizTooltipRow = ({
color={color}
colorIndicator={colorIndicator}
position={ColorIndicatorPosition.Leading}
lineStyle={lineStyle}
/>
)}
@@ -186,6 +188,7 @@ export const VizTooltipRow = ({
color={color}
colorIndicator={colorIndicator}
position={ColorIndicatorPosition.Trailing}
lineStyle={lineStyle}
/>
)}
</div>

View File

@@ -1,3 +1,5 @@
import { LineStyle } from '@grafana/schema';
export enum ColorIndicator {
series = 'series',
value = 'value',
@@ -24,6 +26,7 @@ export interface VizTooltipItem {
colorIndicator?: ColorIndicator;
colorPlacement?: ColorPlacement;
isActive?: boolean;
lineStyle?: LineStyle;
// internal/tmp for sorting
numeric?: number;

View File

@@ -49,8 +49,6 @@ export const getColorIndicatorClass = (colorIndicator: string, styles: ColorIndi
switch (colorIndicator) {
case ColorIndicator.value:
return styles.value;
case ColorIndicator.series:
return styles.series;
case ColorIndicator.hexagon:
return styles.hexagon;
case ColorIndicator.pie_1_4:
@@ -149,6 +147,7 @@ export const getContentItems = (
colorPlacement,
isActive: mode === TooltipDisplayMode.Multi && seriesIdx === i,
numeric,
lineStyle: field.config.custom?.lineStyle,
});
}

View File

@@ -71,6 +71,7 @@ export const PlotLegend = React.memo(
yAxis: axisPlacement === AxisPlacement.Left || axisPlacement === AxisPlacement.Bottom ? 1 : 2,
getDisplayValues: () => getDisplayValuesForCalcs(calcs, field, theme),
getItemKey: () => `${label}-${fieldIndex.frameIndex}-${fieldIndex.fieldIndex}`,
lineStyle: seriesConfig.lineStyle,
};
})
.filter((i): i is VizLegendItem => i !== undefined);