mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Legend: Render legend threshold colors (#92838)
* feat(barchart): render legend threshold and value mapping colors Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
@@ -16,6 +16,8 @@ import { mapMouseEventToMode } from './utils';
|
||||
*/
|
||||
export function VizLegend<T>({
|
||||
items,
|
||||
thresholdItems,
|
||||
mappingItems,
|
||||
displayMode,
|
||||
sortBy: sortKey,
|
||||
seriesVisibilityChangeBehavior = SeriesVisibilityChangeBehavior.Isolate,
|
||||
@@ -83,6 +85,24 @@ export function VizLegend<T>({
|
||||
[onToggleSeriesVisibility, onLabelClick, seriesVisibilityChangeBehavior]
|
||||
);
|
||||
|
||||
const makeVizLegendList = useCallback(
|
||||
(items: VizLegendItem[]) => {
|
||||
return (
|
||||
<VizLegendList<T>
|
||||
className={className}
|
||||
placement={placement}
|
||||
onLabelMouseOver={onMouseOver}
|
||||
onLabelMouseOut={onMouseOut}
|
||||
onLabelClick={onLegendLabelClick}
|
||||
itemRenderer={itemRenderer}
|
||||
readonly={readonly}
|
||||
items={items}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[className, placement, onMouseOver, onMouseOut, onLegendLabelClick, itemRenderer, readonly]
|
||||
);
|
||||
|
||||
switch (displayMode) {
|
||||
case LegendDisplayMode.Table:
|
||||
return (
|
||||
@@ -102,17 +122,19 @@ export function VizLegend<T>({
|
||||
/>
|
||||
);
|
||||
case LegendDisplayMode.List:
|
||||
const isThresholdsEnabled = thresholdItems && thresholdItems.length > 1;
|
||||
const isValueMappingEnabled = mappingItems && mappingItems.length > 0;
|
||||
return (
|
||||
<VizLegendList<T>
|
||||
className={className}
|
||||
items={items}
|
||||
placement={placement}
|
||||
onLabelMouseOver={onMouseOver}
|
||||
onLabelMouseOut={onMouseOut}
|
||||
onLabelClick={onLegendLabelClick}
|
||||
itemRenderer={itemRenderer}
|
||||
readonly={readonly}
|
||||
/>
|
||||
<>
|
||||
{/* render items when single series and there is no thresholds and no value mappings
|
||||
* render items when multi series and there is no thresholds
|
||||
*/}
|
||||
{!isThresholdsEnabled && (!isValueMappingEnabled || items.length > 1) && makeVizLegendList(items)}
|
||||
{/* render threshold colors if From thresholds scheme selected */}
|
||||
{isThresholdsEnabled && makeVizLegendList(thresholdItems)}
|
||||
{/* render value mapping colors */}
|
||||
{isValueMappingEnabled && makeVizLegendList(mappingItems)}
|
||||
</>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
|
||||
@@ -12,6 +12,8 @@ export interface VizLegendBaseProps<T> {
|
||||
placement: LegendPlacement;
|
||||
className?: string;
|
||||
items: Array<VizLegendItem<T>>;
|
||||
thresholdItems?: Array<VizLegendItem<T>>;
|
||||
mappingItems?: Array<VizLegendItem<T>>;
|
||||
seriesVisibilityChangeBehavior?: SeriesVisibilityChangeBehavior;
|
||||
onLabelClick?: (item: VizLegendItem<T>, event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
itemRenderer?: (item: VizLegendItem<T>, index: number) => JSX.Element;
|
||||
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
TimeRange,
|
||||
cacheFieldDisplayNames,
|
||||
outerJoinDataFrames,
|
||||
ValueMapping,
|
||||
ThresholdsConfig,
|
||||
} from '@grafana/data';
|
||||
import { maybeSortFrame, NULL_RETAIN } from '@grafana/data/src/transformations/transformers/joinDataFrames';
|
||||
import { applyNullInsertThreshold } from '@grafana/data/src/transformations/transformers/nulls/nullInsertThreshold';
|
||||
@@ -453,9 +455,13 @@ export function makeFramePerSeries(frames: DataFrame[]) {
|
||||
return outFrames;
|
||||
}
|
||||
|
||||
export function getThresholdItems(fieldConfig: FieldConfig, theme: GrafanaTheme2): VizLegendItem[] {
|
||||
export function getThresholdItems(
|
||||
fieldConfig: FieldConfig,
|
||||
theme: GrafanaTheme2,
|
||||
thresholdItems?: ThresholdsConfig
|
||||
): VizLegendItem[] {
|
||||
const items: VizLegendItem[] = [];
|
||||
const thresholds = fieldConfig.thresholds;
|
||||
const thresholds = thresholdItems ? thresholdItems : fieldConfig.thresholds;
|
||||
if (!thresholds || !thresholds.steps.length) {
|
||||
return items;
|
||||
}
|
||||
@@ -491,6 +497,66 @@ export function getThresholdItems(fieldConfig: FieldConfig, theme: GrafanaTheme2
|
||||
return items;
|
||||
}
|
||||
|
||||
export function getValueMappingItems(mappings: ValueMapping[], theme: GrafanaTheme2): VizLegendItem[] {
|
||||
const items: VizLegendItem[] = [];
|
||||
if (!mappings) {
|
||||
return items;
|
||||
}
|
||||
|
||||
for (let mapping of mappings) {
|
||||
const { options, type } = mapping;
|
||||
|
||||
if (type === MappingType.ValueToText) {
|
||||
for (let [label, value] of Object.entries(options)) {
|
||||
const color = value.color;
|
||||
items.push({
|
||||
label: label,
|
||||
color: theme.visualization.getColorByName(color ?? FALLBACK_COLOR),
|
||||
yAxis: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (type === MappingType.RangeToText) {
|
||||
const { from, result, to } = options;
|
||||
const { text, color } = result;
|
||||
const label = text ? `[${from} - ${to}] ${text}` : `[${from} - ${to}]`;
|
||||
|
||||
items.push({
|
||||
label: label,
|
||||
color: theme.visualization.getColorByName(color ?? FALLBACK_COLOR),
|
||||
yAxis: 1,
|
||||
});
|
||||
}
|
||||
|
||||
if (type === MappingType.RegexToText) {
|
||||
const { pattern, result } = options;
|
||||
const { text, color } = result;
|
||||
const label = `${text || pattern}`;
|
||||
|
||||
items.push({
|
||||
label: label,
|
||||
color: theme.visualization.getColorByName(color ?? FALLBACK_COLOR),
|
||||
yAxis: 1,
|
||||
});
|
||||
}
|
||||
|
||||
if (type === MappingType.SpecialValue) {
|
||||
const { match, result } = options;
|
||||
const { text, color } = result;
|
||||
const label = `${text || match}`;
|
||||
|
||||
items.push({
|
||||
label: label,
|
||||
color: theme.visualization.getColorByName(color ?? FALLBACK_COLOR),
|
||||
yAxis: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
export function prepareTimelineLegendItems(
|
||||
frames: DataFrame[] | undefined,
|
||||
options: VizLegendOptions,
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import { includes } from 'lodash';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { DataFrame, Field, getFieldSeriesColor } from '@grafana/data';
|
||||
import {
|
||||
DataFrame,
|
||||
Field,
|
||||
FieldColorModeId,
|
||||
getFieldSeriesColor,
|
||||
ThresholdsConfig,
|
||||
ThresholdsMode,
|
||||
ValueMapping,
|
||||
} from '@grafana/data';
|
||||
import { VizLegendOptions, AxisPlacement } from '@grafana/schema';
|
||||
import { UPlotConfigBuilder, VizLayout, VizLayoutLegendProps, VizLegend, VizLegendItem, useTheme2 } from '@grafana/ui';
|
||||
import { getDisplayValuesForCalcs } from '@grafana/ui/src/components/uPlot/utils';
|
||||
import { getFieldLegendItem } from 'app/core/components/TimelineChart/utils';
|
||||
|
||||
import { getThresholdItems, getValueMappingItems } from 'app/core/components/TimelineChart/utils';
|
||||
interface BarChartLegend2Props extends VizLegendOptions, Omit<VizLayoutLegendProps, 'children'> {
|
||||
data: DataFrame[];
|
||||
colorField?: Field | null;
|
||||
@@ -32,17 +40,51 @@ export const BarChartLegend = memo(
|
||||
({ data, placement, calcs, displayMode, colorField, ...vizLayoutLegendProps }: BarChartLegend2Props) => {
|
||||
const theme = useTheme2();
|
||||
|
||||
if (colorField != null) {
|
||||
const items = getFieldLegendItem([colorField], theme);
|
||||
const fieldConfig = data[0].fields[0].config;
|
||||
const colorMode = fieldConfig.color?.mode;
|
||||
|
||||
if (items?.length) {
|
||||
return (
|
||||
<VizLayout.Legend placement={placement}>
|
||||
<VizLegend placement={placement} items={items} displayMode={displayMode} />
|
||||
</VizLayout.Legend>
|
||||
);
|
||||
const thresholdItems: VizLegendItem[] = [];
|
||||
if (colorMode === FieldColorModeId.Thresholds) {
|
||||
const thresholdsAbsolute: ThresholdsConfig = { mode: ThresholdsMode.Absolute, steps: [] };
|
||||
const thresholdsPercent: ThresholdsConfig = { mode: ThresholdsMode.Percentage, steps: [] };
|
||||
|
||||
for (let i = 1; i < data[0].fields.length; i++) {
|
||||
const field = data[0].fields[i];
|
||||
// there is no reason to add threshold with only one (Base) step
|
||||
if (field.config.thresholds && field.config.thresholds.steps.length > 1) {
|
||||
if (field.config.thresholds.mode === ThresholdsMode.Absolute) {
|
||||
for (const step of field.config.thresholds.steps) {
|
||||
if (!includes(thresholdsAbsolute.steps, step)) {
|
||||
thresholdsAbsolute.steps.push(step);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const step of field.config.thresholds.steps) {
|
||||
if (!includes(thresholdsPercent.steps, step)) {
|
||||
thresholdsPercent.steps.push(step);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const thresholdAbsoluteItems: VizLegendItem[] = getThresholdItems(fieldConfig, theme, thresholdsAbsolute);
|
||||
const thresholdPercentItems: VizLegendItem[] = getThresholdItems(fieldConfig, theme, thresholdsPercent);
|
||||
thresholdItems.push(...thresholdAbsoluteItems, ...thresholdPercentItems);
|
||||
}
|
||||
|
||||
const valueMappings: ValueMapping[] = [];
|
||||
for (let i = 1; i < data[0].fields.length; i++) {
|
||||
const mappings = data[0].fields[i].config.mappings;
|
||||
if (mappings) {
|
||||
for (const mapping of mappings) {
|
||||
if (!includes(valueMappings, mapping)) {
|
||||
valueMappings.push(mapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const valueMappingItems: VizLegendItem[] = getValueMappingItems(valueMappings, theme);
|
||||
|
||||
const legendItems = data[0].fields
|
||||
.slice(1)
|
||||
@@ -80,6 +122,8 @@ export const BarChartLegend = memo(
|
||||
<VizLegend
|
||||
placement={placement}
|
||||
items={legendItems}
|
||||
thresholdItems={thresholdItems}
|
||||
mappingItems={valueMappingItems}
|
||||
displayMode={displayMode}
|
||||
sortBy={vizLayoutLegendProps.sortBy}
|
||||
sortDesc={vizLayoutLegendProps.sortDesc}
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
AxisColorMode,
|
||||
AxisPlacement,
|
||||
FieldColorModeId,
|
||||
GraphGradientMode,
|
||||
GraphThresholdsStyleMode,
|
||||
GraphTransform,
|
||||
ScaleDistribution,
|
||||
@@ -230,9 +229,7 @@ export const prepConfig = ({ series, totalSeries, color, orientation, options, t
|
||||
getColor = (seriesIdx: number, valueIdx: number) => disp(color!.values[valueIdx]).color!;
|
||||
} else {
|
||||
const hasPerBarColor = frame.fields.some((f) => {
|
||||
const fromThresholds =
|
||||
f.config.custom?.gradientMode === GraphGradientMode.Scheme &&
|
||||
f.config.color?.mode === FieldColorModeId.Thresholds;
|
||||
const fromThresholds = f.config.color?.mode === FieldColorModeId.Thresholds;
|
||||
|
||||
return (
|
||||
fromThresholds ||
|
||||
|
||||
Reference in New Issue
Block a user