mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Timeline: Use theme getContrastText to calculate value color based on background color (#34072)
* Timeline: Use theme getContrastText to calculate value color based on background color * Changed back to using the old functions fill, and stroke
This commit is contained in:
@@ -73,7 +73,7 @@ export interface ThemeHoverStrengh {}
|
|||||||
/** @beta */
|
/** @beta */
|
||||||
export interface ThemeColors extends ThemeColorsBase<ThemeRichColor> {
|
export interface ThemeColors extends ThemeColorsBase<ThemeRichColor> {
|
||||||
/** Returns a text color for the background */
|
/** Returns a text color for the background */
|
||||||
getContrastText(background: string): string;
|
getContrastText(background: string, threshold?: number): string;
|
||||||
/* Brighten or darken a color by specified factor (0-1) */
|
/* Brighten or darken a color by specified factor (0-1) */
|
||||||
emphasize(color: string, amount?: number): string;
|
emphasize(color: string, amount?: number): string;
|
||||||
}
|
}
|
||||||
@@ -255,11 +255,9 @@ export function createColors(colors: ThemeColorsInput): ThemeColors {
|
|||||||
...other
|
...other
|
||||||
} = colors;
|
} = colors;
|
||||||
|
|
||||||
function getContrastText(background: string) {
|
function getContrastText(background: string, threshold: number = contrastThreshold) {
|
||||||
const contrastText =
|
const contrastText =
|
||||||
getContrastRatio(background, dark.text.maxContrast) >= contrastThreshold
|
getContrastRatio(background, dark.text.maxContrast) >= threshold ? dark.text.maxContrast : light.text.maxContrast;
|
||||||
? dark.text.maxContrast
|
|
||||||
: light.text.maxContrast;
|
|
||||||
// todo, need color framework
|
// todo, need color framework
|
||||||
return contrastText;
|
return contrastText;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { FIXED_UNIT } from '@grafana/ui/src/components/GraphNG/GraphNG';
|
|||||||
import { Quadtree, Rect, pointWithin } from 'app/plugins/panel/barchart/quadtree';
|
import { Quadtree, Rect, pointWithin } from 'app/plugins/panel/barchart/quadtree';
|
||||||
import { distribute, SPACE_BETWEEN } from 'app/plugins/panel/barchart/distribute';
|
import { distribute, SPACE_BETWEEN } from 'app/plugins/panel/barchart/distribute';
|
||||||
import { TimelineMode } from './types';
|
import { TimelineMode } from './types';
|
||||||
import { TimeRange } from '@grafana/data';
|
import { GrafanaTheme2, TimeRange } from '@grafana/data';
|
||||||
import { BarValueVisibility } from '@grafana/ui';
|
import { BarValueVisibility } from '@grafana/ui';
|
||||||
|
|
||||||
const { round, min, ceil } = Math;
|
const { round, min, ceil } = Math;
|
||||||
@@ -12,8 +12,6 @@ const pxRatio = devicePixelRatio;
|
|||||||
|
|
||||||
const laneDistr = SPACE_BETWEEN;
|
const laneDistr = SPACE_BETWEEN;
|
||||||
|
|
||||||
const font = Math.round(10 * pxRatio) + 'px Roboto';
|
|
||||||
|
|
||||||
type WalkCb = (idx: number, offPx: number, dimPx: number) => void;
|
type WalkCb = (idx: number, offPx: number, dimPx: number) => void;
|
||||||
|
|
||||||
function walk(rowHeight: number, yIdx: number | null, count: number, dim: number, draw: WalkCb) {
|
function walk(rowHeight: number, yIdx: number | null, count: number, dim: number, draw: WalkCb) {
|
||||||
@@ -33,12 +31,13 @@ export interface TimelineCoreOptions {
|
|||||||
numSeries: number;
|
numSeries: number;
|
||||||
rowHeight: number;
|
rowHeight: number;
|
||||||
colWidth?: number;
|
colWidth?: number;
|
||||||
|
theme: GrafanaTheme2;
|
||||||
showValue: BarValueVisibility;
|
showValue: BarValueVisibility;
|
||||||
isDiscrete: (seriesIdx: number) => boolean;
|
isDiscrete: (seriesIdx: number) => boolean;
|
||||||
|
colorLookup: (seriesIdx: number, value: any) => string;
|
||||||
label: (seriesIdx: number) => string;
|
label: (seriesIdx: number) => string;
|
||||||
fill: (seriesIdx: number, valueIdx: number, value: any) => CanvasRenderingContext2D['fillStyle'];
|
fill: (seriesIdx: number, value: any) => CanvasRenderingContext2D['fillStyle'];
|
||||||
stroke: (seriesIdx: number, valueIdx: number, value: any) => CanvasRenderingContext2D['strokeStyle'];
|
stroke: (seriesIdx: number, value: any) => CanvasRenderingContext2D['strokeStyle'];
|
||||||
getTimeRange: () => TimeRange;
|
getTimeRange: () => TimeRange;
|
||||||
formatValue?: (seriesIdx: number, value: any) => string;
|
formatValue?: (seriesIdx: number, value: any) => string;
|
||||||
onHover?: (seriesIdx: number, valueIdx: number) => void;
|
onHover?: (seriesIdx: number, valueIdx: number) => void;
|
||||||
@@ -56,11 +55,13 @@ export function getConfig(opts: TimelineCoreOptions) {
|
|||||||
rowHeight = 0,
|
rowHeight = 0,
|
||||||
colWidth = 0,
|
colWidth = 0,
|
||||||
showValue,
|
showValue,
|
||||||
|
theme,
|
||||||
label,
|
label,
|
||||||
fill,
|
fill,
|
||||||
stroke,
|
stroke,
|
||||||
formatValue,
|
formatValue,
|
||||||
getTimeRange,
|
getTimeRange,
|
||||||
|
colorLookup,
|
||||||
// onHover,
|
// onHover,
|
||||||
// onLeave,
|
// onLeave,
|
||||||
} = opts;
|
} = opts;
|
||||||
@@ -73,10 +74,11 @@ export function getConfig(opts: TimelineCoreOptions) {
|
|||||||
let mark = document.createElement('div');
|
let mark = document.createElement('div');
|
||||||
mark.classList.add('bar-mark');
|
mark.classList.add('bar-mark');
|
||||||
mark.style.position = 'absolute';
|
mark.style.position = 'absolute';
|
||||||
mark.style.background = 'rgba(255,255,255,0.4)';
|
mark.style.background = 'rgba(255,255,255,0.2)';
|
||||||
return mark;
|
return mark;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const font = `500 ${Math.round(12 * devicePixelRatio)}px ${theme.typography.fontFamily}`;
|
||||||
const hovered: Array<Rect | null> = Array(numSeries).fill(null);
|
const hovered: Array<Rect | null> = Array(numSeries).fill(null);
|
||||||
|
|
||||||
const size = [colWidth, Infinity];
|
const size = [colWidth, Infinity];
|
||||||
@@ -117,7 +119,7 @@ export function getConfig(opts: TimelineCoreOptions) {
|
|||||||
discrete: boolean
|
discrete: boolean
|
||||||
) {
|
) {
|
||||||
if (discrete) {
|
if (discrete) {
|
||||||
let fillStyle = fill(seriesIdx + 1, valueIdx, value);
|
let fillStyle = fill(seriesIdx + 1, value);
|
||||||
let fillPath = fillPaths.get(fillStyle);
|
let fillPath = fillPaths.get(fillStyle);
|
||||||
|
|
||||||
if (fillPath == null) {
|
if (fillPath == null) {
|
||||||
@@ -127,7 +129,7 @@ export function getConfig(opts: TimelineCoreOptions) {
|
|||||||
rect(fillPath, lft, top, wid, hgt);
|
rect(fillPath, lft, top, wid, hgt);
|
||||||
|
|
||||||
if (strokeWidth) {
|
if (strokeWidth) {
|
||||||
let strokeStyle = stroke(seriesIdx + 1, valueIdx, value);
|
let strokeStyle = stroke(seriesIdx + 1, value);
|
||||||
let strokePath = strokePaths.get(strokeStyle);
|
let strokePath = strokePaths.get(strokeStyle);
|
||||||
|
|
||||||
if (strokePath == null) {
|
if (strokePath == null) {
|
||||||
@@ -139,13 +141,13 @@ export function getConfig(opts: TimelineCoreOptions) {
|
|||||||
} else {
|
} else {
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
rect(ctx, lft, top, wid, hgt);
|
rect(ctx, lft, top, wid, hgt);
|
||||||
ctx.fillStyle = fill(seriesIdx, valueIdx, value);
|
ctx.fillStyle = fill(seriesIdx, value);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
|
||||||
if (strokeWidth) {
|
if (strokeWidth) {
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
rect(ctx, lft + strokeWidth / 2, top + strokeWidth / 2, wid - strokeWidth, hgt - strokeWidth);
|
rect(ctx, lft + strokeWidth / 2, top + strokeWidth / 2, wid - strokeWidth, hgt - strokeWidth);
|
||||||
ctx.strokeStyle = stroke(seriesIdx, valueIdx, value);
|
ctx.strokeStyle = stroke(seriesIdx, value);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,7 +197,7 @@ export function getConfig(opts: TimelineCoreOptions) {
|
|||||||
yOff,
|
yOff,
|
||||||
lft,
|
lft,
|
||||||
round(yOff + y0),
|
round(yOff + y0),
|
||||||
rgt - lft,
|
rgt - lft - 2,
|
||||||
round(hgt),
|
round(hgt),
|
||||||
strokeWidth,
|
strokeWidth,
|
||||||
iy,
|
iy,
|
||||||
@@ -257,7 +259,6 @@ export function getConfig(opts: TimelineCoreOptions) {
|
|||||||
u.ctx.clip();
|
u.ctx.clip();
|
||||||
|
|
||||||
u.ctx.font = font;
|
u.ctx.font = font;
|
||||||
u.ctx.fillStyle = 'black';
|
|
||||||
u.ctx.textAlign = mode === TimelineMode.Changes ? 'left' : 'center';
|
u.ctx.textAlign = mode === TimelineMode.Changes ? 'left' : 'center';
|
||||||
u.ctx.textBaseline = 'middle';
|
u.ctx.textBaseline = 'middle';
|
||||||
|
|
||||||
@@ -285,6 +286,15 @@ export function getConfig(opts: TimelineCoreOptions) {
|
|||||||
for (let ix = 0; ix < dataY.length; ix++) {
|
for (let ix = 0; ix < dataY.length; ix++) {
|
||||||
if (dataY[ix] != null) {
|
if (dataY[ix] != null) {
|
||||||
let x = valToPosX(dataX[ix], scaleX, xDim, xOff);
|
let x = valToPosX(dataX[ix], scaleX, xDim, xOff);
|
||||||
|
|
||||||
|
// For the left aligned values shift them 2 pixels of edge
|
||||||
|
if (mode === TimelineMode.Changes) {
|
||||||
|
x += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const valueColor = colorLookup(sidx, dataY[ix]);
|
||||||
|
|
||||||
|
u.ctx.fillStyle = theme.colors.getContrastText(valueColor, 3);
|
||||||
u.ctx.fillText(formatValue(sidx, dataY[ix]), x, y);
|
u.ctx.fillText(formatValue(sidx, dataY[ix]), x, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import {
|
|||||||
formattedValueToString,
|
formattedValueToString,
|
||||||
getFieldDisplayName,
|
getFieldDisplayName,
|
||||||
outerJoinDataFrames,
|
outerJoinDataFrames,
|
||||||
classicColors,
|
|
||||||
Field,
|
Field,
|
||||||
|
FALLBACK_COLOR,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import {
|
import {
|
||||||
UPlotConfigBuilder,
|
UPlotConfigBuilder,
|
||||||
@@ -72,15 +72,17 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
|
|||||||
return !(mode && field.display && mode.startsWith('continuous-'));
|
return !(mode && field.display && mode.startsWith('continuous-'));
|
||||||
};
|
};
|
||||||
|
|
||||||
const colorLookup = (seriesIdx: number, valueIdx: number, value: any) => {
|
const colorLookup = (seriesIdx: number, value: any) => {
|
||||||
const field = frame.fields[seriesIdx];
|
const field = frame.fields[seriesIdx];
|
||||||
|
|
||||||
if (field.display) {
|
if (field.display) {
|
||||||
const disp = field.display(value); // will apply color modes
|
const disp = field.display(value); // will apply color modes
|
||||||
if (disp.color) {
|
if (disp.color) {
|
||||||
return disp.color;
|
return disp.color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return classicColors[Math.floor(value % classicColors.length)];
|
|
||||||
|
return FALLBACK_COLOR;
|
||||||
};
|
};
|
||||||
|
|
||||||
const yAxisWidth =
|
const yAxisWidth =
|
||||||
@@ -99,9 +101,11 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
|
|||||||
rowHeight: rowHeight!,
|
rowHeight: rowHeight!,
|
||||||
colWidth: colWidth,
|
colWidth: colWidth,
|
||||||
showValue: showValue!,
|
showValue: showValue!,
|
||||||
|
theme,
|
||||||
label: (seriesIdx) => getFieldDisplayName(frame.fields[seriesIdx], frame),
|
label: (seriesIdx) => getFieldDisplayName(frame.fields[seriesIdx], frame),
|
||||||
fill: colorLookup,
|
fill: colorLookup,
|
||||||
stroke: colorLookup,
|
stroke: colorLookup,
|
||||||
|
colorLookup,
|
||||||
getTimeRange,
|
getTimeRange,
|
||||||
// hardcoded formatter for state values
|
// hardcoded formatter for state values
|
||||||
formatValue: (seriesIdx, value) => formattedValueToString(frame.fields[seriesIdx].display!(value)),
|
formatValue: (seriesIdx, value) => formattedValueToString(frame.fields[seriesIdx].display!(value)),
|
||||||
|
|||||||
Reference in New Issue
Block a user