mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
@@ -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',
|
||||
|
||||
@@ -75,6 +75,7 @@ export const VizLegendListItem = <T = unknown,>({
|
||||
color={item.color}
|
||||
gradient={item.gradient}
|
||||
readonly={readonly}
|
||||
lineStyle={item.lineStyle}
|
||||
/>
|
||||
<button
|
||||
disabled={readonly}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user