Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 8 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
50 changes: 36 additions & 14 deletions packages/grafana-ui/src/components/VizLegend/SeriesIcon.tsx
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export const VizLegendListItem = <T = unknown,>({
color={item.color}
gradient={item.gradient}
readonly={readonly}
lineStyle={item.lineStyle}
/>
<button
disabled={readonly}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, { useCallback } from 'react';

import { LineStyle } from '@grafana/schema';

import { SeriesColorPicker } from '../ColorPicker/ColorPicker';
import { usePanelContext } from '../PanelChrome';

Expand All @@ -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) => {
Expand All @@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 2 additions & 1 deletion packages/grafana-ui/src/components/VizLegend/types.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -49,4 +49,5 @@ export interface VizLegendItem<T = any> {
fieldIndex?: DataFrameFieldIndex;
fieldName?: string;
data?: T;
lineStyle?: LineStyle;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -17,6 +19,7 @@ interface Props {
color?: string;
colorIndicator?: ColorIndicator;
position?: ColorIndicatorPosition;
lineStyle?: LineStyle;
}

export type ColorIndicatorStyles = ReturnType<typeof getStyles>;
Expand All @@ -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 }}
Expand All @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -45,6 +45,7 @@ export const VizTooltipContent = ({
isActive={isActive}
justify={'space-between'}
isPinned={isPinned}
lineStyle={lineStyle}
/>
))}
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const VizTooltipRow = ({
isActive = false,
marginRight = '0px',
isPinned,
lineStyle,
}: VizTooltipRowProps) => {
const styles = useStyles2(getStyles, justify, marginRight);

Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -154,6 +155,7 @@ export const VizTooltipRow = ({
color={color}
colorIndicator={colorIndicator}
position={ColorIndicatorPosition.Leading}
lineStyle={lineStyle}
/>
)}

Expand Down Expand Up @@ -186,6 +188,7 @@ export const VizTooltipRow = ({
color={color}
colorIndicator={colorIndicator}
position={ColorIndicatorPosition.Trailing}
lineStyle={lineStyle}
/>
)}
</div>
Expand Down
3 changes: 3 additions & 0 deletions packages/grafana-ui/src/components/VizTooltip/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { LineStyle } from '@grafana/schema';

export enum ColorIndicator {
series = 'series',
value = 'value',
Expand All @@ -24,6 +26,7 @@ export interface VizTooltipItem {
colorIndicator?: ColorIndicator;
colorPlacement?: ColorPlacement;
isActive?: boolean;
lineStyle?: LineStyle;

// internal/tmp for sorting
numeric?: number;
Expand Down
3 changes: 1 addition & 2 deletions packages/grafana-ui/src/components/VizTooltip/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -149,6 +147,7 @@ export const getContentItems = (
colorPlacement,
isActive: mode === TooltipDisplayMode.Multi && seriesIdx === i,
numeric,
lineStyle: field.config.custom?.lineStyle,
});
}

Expand Down
1 change: 1 addition & 0 deletions packages/grafana-ui/src/components/uPlot/PlotLegend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down