BarChart: Highlight bars option for easier interaction (#60530)

* Add bar highlighter plugin to BarChart

* refactor feature

* Horizontal bars can also be hovered

* Tooltip mode UI reflects settings when stacked and bar highlight toggled

* rename variables

* override tooltip option + zIndex on hover box

* change option desc and simplify condition
This commit is contained in:
Victor Marin 2023-01-11 10:30:01 +02:00 committed by GitHub
parent f9daf61e96
commit 2cf628e37a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 40 additions and 2 deletions

View File

@ -20,6 +20,7 @@ import {
measureText, measureText,
PlotLegend, PlotLegend,
Portal, Portal,
StackingMode,
TooltipDisplayMode, TooltipDisplayMode,
UPlotConfigBuilder, UPlotConfigBuilder,
UPLOT_AXIS_FONT_SIZE, UPLOT_AXIS_FONT_SIZE,
@ -168,6 +169,8 @@ export const BarChartPanel: React.FunctionComponent<Props> = ({
const disp = getFieldDisplayName(field, alignedFrame); const disp = getFieldDisplayName(field, alignedFrame);
seriesIdx = info.aligned.fields.findIndex((f) => disp === getFieldDisplayName(f, info.aligned)); seriesIdx = info.aligned.fields.findIndex((f) => disp === getFieldDisplayName(f, info.aligned));
} }
const tooltipMode =
options.fullHighlight && options.stacking !== StackingMode.None ? TooltipDisplayMode.Multi : options.tooltip.mode;
return ( return (
<> <>
@ -195,7 +198,7 @@ export const BarChartPanel: React.FunctionComponent<Props> = ({
rowIndex={datapointIdx} rowIndex={datapointIdx}
columnIndex={seriesIdx} columnIndex={seriesIdx}
sortOrder={options.tooltip.sort} sortOrder={options.tooltip.sort}
mode={options.tooltip.mode} mode={tooltipMode}
/> />
</> </>
); );
@ -280,6 +283,7 @@ export const BarChartPanel: React.FunctionComponent<Props> = ({
text, text,
xTickLabelRotation, xTickLabelRotation,
xTickLabelSpacing, xTickLabelSpacing,
fullHighlight,
} = options; } = options;
return preparePlotConfigBuilder({ return preparePlotConfigBuilder({
@ -305,6 +309,7 @@ export const BarChartPanel: React.FunctionComponent<Props> = ({
getColor, getColor,
fillOpacity, fillOpacity,
allFrames: info.viz, allFrames: info.viz,
fullHighlight,
}); });
}; };

View File

@ -60,6 +60,7 @@ export interface BarsOptions {
xSpacing?: number; xSpacing?: number;
xTimeAuto?: boolean; xTimeAuto?: boolean;
negY?: boolean[]; negY?: boolean[];
fullHighlight?: boolean;
} }
/** /**
@ -315,6 +316,17 @@ export function getConfig(opts: BarsOptions, theme: GrafanaTheme2) {
} }
let barRect = { x: lft, y: top, w: wid, h: hgt, sidx: seriesIdx, didx: dataIdx }; let barRect = { x: lft, y: top, w: wid, h: hgt, sidx: seriesIdx, didx: dataIdx };
if (opts.fullHighlight) {
if (opts.xOri === ScaleOrientation.Horizontal) {
barRect.y = 0;
barRect.h = u.bbox.height;
} else {
barRect.x = 0;
barRect.w = u.bbox.width;
}
}
qt.add(barRect); qt.add(barRect);
if (showValue !== VisibilityMode.Never) { if (showValue !== VisibilityMode.Never) {
@ -429,6 +441,9 @@ export function getConfig(opts: BarsOptions, theme: GrafanaTheme2) {
u.root.querySelectorAll('.u-cursor-pt').forEach((el) => { u.root.querySelectorAll('.u-cursor-pt').forEach((el) => {
if (el instanceof HTMLElement) { if (el instanceof HTMLElement) {
el.style.borderRadius = '0'; el.style.borderRadius = '0';
if (opts.fullHighlight) {
el.style.zIndex = '-1';
}
} }
}); });
}; };

View File

@ -53,6 +53,8 @@ Panel: thema.#Lineage & {
barWidth: float64 & >= 0 & <= 1 | *0.97 barWidth: float64 & >= 0 & <= 1 | *0.97
// Controls the width of groups. 1 = max with, 0 = min width. // Controls the width of groups. 1 = max with, 0 = min width.
groupWidth: float64 & >= 0 & <= 1 | *0.7 groupWidth: float64 & >= 0 & <= 1 | *0.7
// Enables mode which highlights the entire bar area and shows tooltip when cursor hovers over highlighted area
fullHighlight: bool | *false
} @cuetsy(kind="interface") } @cuetsy(kind="interface")
PanelFieldConfig: { PanelFieldConfig: {
ui.AxisConfig ui.AxisConfig

View File

@ -25,6 +25,10 @@ export interface PanelOptions extends ui.OptionsWithLegend, ui.OptionsWithToolti
* TODO docs * TODO docs
*/ */
colorByField?: string; colorByField?: string;
/**
* Enables mode which highlights the entire bar area and shows tooltip when cursor hovers over highlighted area
*/
fullHighlight: boolean;
/** /**
* Controls the width of groups. 1 = max with, 0 = min width. * Controls the width of groups. 1 = max with, 0 = min width.
*/ */
@ -63,6 +67,7 @@ export interface PanelOptions extends ui.OptionsWithLegend, ui.OptionsWithToolti
export const defaultPanelOptions: Partial<PanelOptions> = { export const defaultPanelOptions: Partial<PanelOptions> = {
barRadius: 0, barRadius: 0,
barWidth: 0.97, barWidth: 0.97,
fullHighlight: false,
groupWidth: 0.7, groupWidth: 0.7,
orientation: ui.VizOrientation.Auto, orientation: ui.VizOrientation.Auto,
showValue: ui.VisibilityMode.Auto, showValue: ui.VisibilityMode.Auto,

View File

@ -220,6 +220,11 @@ export const plugin = new PanelPlugin<PanelOptions, PanelFieldConfig>(BarChartPa
max: 0.5, max: 0.5,
step: 0.05, step: 0.05,
}, },
})
.addBooleanSwitch({
path: 'fullHighlight',
name: 'Highlight full area on hover',
defaultValue: defaultPanelOptions.fullHighlight,
}); });
builder.addFieldNamePicker({ builder.addFieldNamePicker({
@ -228,7 +233,10 @@ export const plugin = new PanelPlugin<PanelOptions, PanelFieldConfig>(BarChartPa
description: 'Use the color value for a sibling field to color each bar value.', description: 'Use the color value for a sibling field to color each bar value.',
}); });
if (!context.options?.fullHighlight || context.options?.stacking === StackingMode.None) {
commonOptionsBuilder.addTooltipOptions(builder); commonOptionsBuilder.addTooltipOptions(builder);
}
commonOptionsBuilder.addLegendOptions(builder); commonOptionsBuilder.addLegendOptions(builder);
commonOptionsBuilder.addTextSizeOptions(builder, false); commonOptionsBuilder.addTextSizeOptions(builder, false);
}) })

View File

@ -108,6 +108,7 @@ describe('BarChart utils', () => {
text: { text: {
valueSize: 10, valueSize: 10,
}, },
fullHighlight: false,
rawValue: (seriesIdx: number, valueIdx: number) => frame.fields[seriesIdx].values.get(valueIdx), rawValue: (seriesIdx: number, valueIdx: number) => frame.fields[seriesIdx].values.get(valueIdx),
}; };

View File

@ -79,6 +79,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<BarChartOptionsEX> = ({
xTickLabelSpacing = 0, xTickLabelSpacing = 0,
legend, legend,
timeZone, timeZone,
fullHighlight,
}) => { }) => {
const builder = new UPlotConfigBuilder(); const builder = new UPlotConfigBuilder();
@ -118,6 +119,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<BarChartOptionsEX> = ({
xSpacing: xTickLabelSpacing, xSpacing: xTickLabelSpacing,
xTimeAuto: frame.fields[0]?.type === FieldType.time && !frame.fields[0].config.unit?.startsWith('time:'), xTimeAuto: frame.fields[0]?.type === FieldType.time && !frame.fields[0].config.unit?.startsWith('time:'),
negY: frame.fields.map((f) => f.config.custom?.transform === GraphTransform.NegativeY), negY: frame.fields.map((f) => f.config.custom?.transform === GraphTransform.NegativeY),
fullHighlight,
}; };
const config = getConfig(opts, theme); const config = getConfig(opts, theme);