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:
Torkel Ödegaard
2021-05-13 21:41:40 +02:00
committed by GitHub
parent 36c4083264
commit 917a9ace57
3 changed files with 33 additions and 21 deletions

View File

@@ -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;
} }

View File

@@ -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);
} }
} }

View File

@@ -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)),