wip: overhaul color in gauge
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
import { useId } from 'react';
|
||||
|
||||
import { colorManipulator } from '@grafana/data';
|
||||
|
||||
import { RadialColorDefs } from './RadialColorDefs';
|
||||
import { RadialShape } from './RadialGauge';
|
||||
import { GaugeDimensions, toRad } from './utils';
|
||||
|
||||
export interface RadialArcPathPropsBase {
|
||||
startAngle: number;
|
||||
dimensions: GaugeDimensions;
|
||||
colorDef: RadialColorDefs;
|
||||
colorDefs: RadialColorDefs;
|
||||
arcLengthDeg: number;
|
||||
shape: RadialShape;
|
||||
color?: string;
|
||||
gradient?: string;
|
||||
glowFilter?: string;
|
||||
@@ -116,9 +116,10 @@ export function RadialArcPath({
|
||||
startAngle: angle,
|
||||
dimensions,
|
||||
color,
|
||||
colorDef,
|
||||
gradient,
|
||||
colorDefs,
|
||||
shape,
|
||||
arcLengthDeg,
|
||||
glowFilter,
|
||||
roundedBars,
|
||||
showGuideDots,
|
||||
guideDotStartColor,
|
||||
@@ -129,7 +130,7 @@ export function RadialArcPath({
|
||||
|
||||
const startRadians = toRad(angle);
|
||||
const endRadians = toRad(angle + arcLengthDeg);
|
||||
const gradientList = colorDef?.getGradient();
|
||||
const [startColor, endColor] = colorDefs.getEndpointColors();
|
||||
|
||||
const path = drawRadialArcPath({ angle, arcLengthDeg, dimensions, roundedBars });
|
||||
let x1 = centerX + radius * Math.cos(startRadians);
|
||||
@@ -144,19 +145,34 @@ export function RadialArcPath({
|
||||
<clipPath id={id}>
|
||||
<path d={path} />
|
||||
</clipPath>
|
||||
<foreignObject x="0" y="0" width={centerX * 2} height={centerY * 2} clipPath={`url(#${id})`}>
|
||||
<div style={{ width: '100%', height: '100%', backgroundImage: gradient, backgroundColor: color }} />
|
||||
</foreignObject>
|
||||
<g filter={glowFilter}>
|
||||
<foreignObject
|
||||
x={centerX - radius - barWidth}
|
||||
y={centerY - radius - barWidth}
|
||||
width={(radius + barWidth) * 2}
|
||||
height={(radius + barWidth) * 2}
|
||||
clipPath={`url(#${id})`}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundImage: color ? undefined : colorDefs.getGradientDef(),
|
||||
backgroundColor: color,
|
||||
}}
|
||||
/>
|
||||
</foreignObject>
|
||||
</g>
|
||||
|
||||
{roundedBars && gradientList && (
|
||||
<>
|
||||
<circle cx={x1} cy={y1} r={dotRadius} fill={gradientList[0].color} />
|
||||
{/* this would actually need to be the color determined by the display */}
|
||||
<circle cx={x2} cy={y2} r={dotRadius} fill={gradientList[gradientList.length - 1].color} />
|
||||
</>
|
||||
)}
|
||||
{showGuideDots && (
|
||||
<>
|
||||
{shape === 'circle' && (
|
||||
<>
|
||||
<circle cx={x1} cy={y1} r={barWidth / 2} fill={startColor} />
|
||||
<circle cx={x2} cy={y2} r={barWidth / 2} fill={endColor} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{arcLengthDeg > 5 && <circle cx={x1} cy={y1} r={dotRadius} fill={guideDotStartColor} />}
|
||||
<circle cx={x2} cy={y2} r={dotRadius} fill={guideDotEndColor} />
|
||||
</>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useTheme2 } from '../../themes/ThemeContext';
|
||||
|
||||
import { RadialArcPath } from './RadialArcPath';
|
||||
import { RadialColorDefs } from './RadialColorDefs';
|
||||
import { RadialShape } from './RadialGauge';
|
||||
import { GaugeDimensions } from './utils';
|
||||
|
||||
export interface RadialBarProps {
|
||||
@@ -12,6 +13,7 @@ export interface RadialBarProps {
|
||||
startAngle: number;
|
||||
roundedBars?: boolean;
|
||||
glowFilter?: string;
|
||||
shape: RadialShape;
|
||||
}
|
||||
export function RadialBar({
|
||||
dimensions,
|
||||
@@ -21,6 +23,7 @@ export function RadialBar({
|
||||
startAngle,
|
||||
roundedBars,
|
||||
glowFilter,
|
||||
shape,
|
||||
}: RadialBarProps) {
|
||||
const theme = useTheme2();
|
||||
const [startDotColor, endDotColor] = colorDefs.getGuideDotColors();
|
||||
@@ -33,20 +36,24 @@ export function RadialBar({
|
||||
startAngle={startAngle + angle}
|
||||
dimensions={dimensions}
|
||||
arcLengthDeg={angleRange - angle}
|
||||
colorDefs={colorDefs}
|
||||
color={theme.colors.action.hover}
|
||||
roundedBars={roundedBars}
|
||||
shape={shape}
|
||||
/>
|
||||
{/** The colored bar */}
|
||||
<RadialArcPath
|
||||
dimensions={dimensions}
|
||||
startAngle={startAngle}
|
||||
arcLengthDeg={angle}
|
||||
colorDefs={colorDefs}
|
||||
gradient={colorDefs.getGradientDef()}
|
||||
roundedBars={roundedBars}
|
||||
glowFilter={glowFilter}
|
||||
showGuideDots={roundedBars}
|
||||
guideDotStartColor={startDotColor}
|
||||
guideDotEndColor={endDotColor}
|
||||
shape={shape}
|
||||
/>
|
||||
</g>
|
||||
<defs>{colorDefs.getDefs()}</defs>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useTheme2 } from '../../themes/ThemeContext';
|
||||
|
||||
import { RadialArcPath } from './RadialArcPath';
|
||||
import { RadialColorDefs } from './RadialColorDefs';
|
||||
import { RadialShape } from './RadialGauge';
|
||||
import { GaugeDimensions } from './utils';
|
||||
|
||||
export interface RadialBarSegmentedProps {
|
||||
@@ -15,6 +16,7 @@ export interface RadialBarSegmentedProps {
|
||||
glowFilter?: string;
|
||||
segmentCount: number;
|
||||
segmentSpacing: number;
|
||||
shape: RadialShape;
|
||||
}
|
||||
export function RadialBarSegmented({
|
||||
fieldDisplay,
|
||||
@@ -25,6 +27,7 @@ export function RadialBarSegmented({
|
||||
segmentCount,
|
||||
segmentSpacing,
|
||||
colorDefs,
|
||||
shape,
|
||||
}: RadialBarSegmentedProps) {
|
||||
const segments: React.ReactNode[] = [];
|
||||
const theme = useTheme2();
|
||||
@@ -38,9 +41,9 @@ export function RadialBarSegmented({
|
||||
|
||||
for (let i = 0; i < segmentCountAdjusted; i++) {
|
||||
const angleValue = min + ((max - min) / segmentCountAdjusted) * i;
|
||||
const angleColor = colorDefs.getSegmentColor(angleValue, i);
|
||||
// const angleColor = colorDefs.getSegmentColor(angleValue, i);
|
||||
const segmentAngle = startAngle + (angleRange / segmentCountAdjusted) * i + 0.01;
|
||||
const segmentColor = angleValue >= value ? theme.colors.action.hover : angleColor;
|
||||
const segmentColor = angleValue >= value ? theme.colors.action.hover : undefined;
|
||||
|
||||
segments.push(
|
||||
<RadialArcPath
|
||||
@@ -48,6 +51,8 @@ export function RadialBarSegmented({
|
||||
startAngle={segmentAngle}
|
||||
dimensions={dimensions}
|
||||
color={segmentColor}
|
||||
shape={shape}
|
||||
colorDefs={colorDefs}
|
||||
glowFilter={glowFilter}
|
||||
arcLengthDeg={segmentArcLengthDeg}
|
||||
/>
|
||||
|
||||
@@ -122,35 +122,24 @@ export class RadialColorDefs {
|
||||
}
|
||||
|
||||
getGradient(baseColor = this.getFieldBaseColor(), forSegment?: boolean): Array<{ color: string; percent: number }> {
|
||||
const { gradient, fieldDisplay, theme } = this.options;
|
||||
return buildGradientColors(gradient, baseColor, theme, fieldDisplay.field.color?.mode, forSegment);
|
||||
const { displayProcessor, gradient, fieldDisplay, theme } = this.options;
|
||||
return buildGradientColors(gradient, baseColor, theme, displayProcessor, fieldDisplay, forSegment);
|
||||
}
|
||||
|
||||
getGradientDef(): string {
|
||||
const gradientStops = this.getGradient();
|
||||
const colorStrings = gradientStops.map((stop) => `${stop.color} ${(stop.percent * 100).toFixed(2)}%`);
|
||||
return this.options.shape === 'circle'
|
||||
? `conic-gradient(from -10deg, ${colorStrings.join(', ')})`
|
||||
? `conic-gradient(from 0deg, ${colorStrings.join(', ')})`
|
||||
: 'linear-gradient(90deg, ' + colorStrings.join(', ') + ')';
|
||||
}
|
||||
|
||||
getGuideDotColors(): [string, string] {
|
||||
const { dimensions, fieldDisplay, shape } = this.options;
|
||||
getEndpointColors(): [string, string] {
|
||||
const { fieldDisplay } = this.options;
|
||||
|
||||
const gradient = this.getGradient();
|
||||
let valuePercent = fieldDisplay.display.percent ?? 0;
|
||||
|
||||
// the linear gradient used in circular gradients means that we want to use the
|
||||
// y position of the edge of the bar to determine the color. If we ever address
|
||||
// that shortcoming, we could delete this block.
|
||||
if (shape === 'circle') {
|
||||
const angleDeg = ((valuePercent - 0.25) % 1) * 360;
|
||||
const angleRad = (angleDeg * Math.PI) / 180;
|
||||
const yPos = dimensions.centerY + dimensions.radius * Math.sin(angleRad);
|
||||
valuePercent = yPos / (dimensions.centerY * 2);
|
||||
}
|
||||
|
||||
let startColor = gradient[0].color;
|
||||
const valuePercent = fieldDisplay.display.percent ?? 0;
|
||||
const startColor = gradient[0].color;
|
||||
let endColor = gradient[gradient.length - 1].color;
|
||||
|
||||
// if we have a percentageFilled, use it to get a the correct end color based on where the bar terminates
|
||||
@@ -161,7 +150,11 @@ export class RadialColorDefs {
|
||||
? endColorByPercentage.toHexString()
|
||||
: endColorByPercentage.toHex8String();
|
||||
}
|
||||
return [startColor, endColor];
|
||||
}
|
||||
|
||||
getGuideDotColors(): [string, string] {
|
||||
const [startColor, endColor] = this.getEndpointColors();
|
||||
return [getGuideDotColor(startColor), getGuideDotColor(endColor)];
|
||||
}
|
||||
|
||||
|
||||
@@ -155,6 +155,7 @@ export function RadialGauge(props: RadialGaugeProps) {
|
||||
segmentCount={segmentCount}
|
||||
segmentSpacing={segmentSpacing}
|
||||
colorDefs={colorDefs}
|
||||
shape={shape}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
@@ -168,6 +169,7 @@ export function RadialGauge(props: RadialGaugeProps) {
|
||||
startAngle={startAngle}
|
||||
roundedBars={roundedBars}
|
||||
glowFilter={`url(#${glowFilterId})`}
|
||||
shape={shape}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -228,6 +230,7 @@ export function RadialGauge(props: RadialGaugeProps) {
|
||||
roundedBars={roundedBars}
|
||||
glowFilter={`url(#${glowFilterId})`}
|
||||
colorDefs={colorDefs}
|
||||
shape={shape}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { FieldDisplay, Threshold } from '@grafana/data';
|
||||
|
||||
import { RadialArcPath } from './RadialArcPath';
|
||||
import { RadialColorDefs } from './RadialColorDefs';
|
||||
import { RadialShape } from './RadialGauge';
|
||||
import { GaugeDimensions } from './utils';
|
||||
|
||||
export interface Props {
|
||||
@@ -9,6 +10,7 @@ export interface Props {
|
||||
angleRange: number;
|
||||
startAngle: number;
|
||||
endAngle: number;
|
||||
shape: RadialShape;
|
||||
fieldDisplay: FieldDisplay;
|
||||
roundedBars?: boolean;
|
||||
glowFilter?: string;
|
||||
@@ -24,6 +26,7 @@ export function ThresholdsBar({
|
||||
glowFilter,
|
||||
colorDefs,
|
||||
thresholds,
|
||||
shape,
|
||||
}: Props) {
|
||||
const fieldConfig = fieldDisplay.field;
|
||||
const min = fieldConfig.min ?? 0;
|
||||
@@ -55,10 +58,11 @@ export function ThresholdsBar({
|
||||
key={i}
|
||||
startAngle={currentStart}
|
||||
arcLengthDeg={lengthDeg}
|
||||
colorDefs={colorDefs}
|
||||
shape={shape}
|
||||
dimensions={thresholdDimensions}
|
||||
roundedBars={roundedBars}
|
||||
glowFilter={glowFilter}
|
||||
color={colorDefs.getColor(threshold.color, i)}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import tinycolor from 'tinycolor2';
|
||||
|
||||
import { FieldColorModeId, getFieldColorMode, GrafanaTheme2 } from '@grafana/data';
|
||||
import { DisplayProcessor, FieldDisplay, getFieldColorMode, GrafanaTheme2 } from '@grafana/data';
|
||||
import { FieldColorModeId } from '@grafana/schema';
|
||||
|
||||
import { RadialGradientMode } from './RadialGauge';
|
||||
|
||||
@@ -8,7 +9,8 @@ export function buildGradientColors(
|
||||
gradientMode: RadialGradientMode,
|
||||
baseColor: string,
|
||||
theme: GrafanaTheme2,
|
||||
colorModeId?: FieldColorModeId | string,
|
||||
displayProcessor: DisplayProcessor,
|
||||
fieldDisplay: FieldDisplay,
|
||||
forSegment?: boolean
|
||||
): Array<{ color: string; percent: number }> {
|
||||
if (gradientMode === 'none') {
|
||||
@@ -18,15 +20,36 @@ export function buildGradientColors(
|
||||
];
|
||||
}
|
||||
|
||||
const colorMode = getFieldColorMode(colorModeId);
|
||||
const colorMode = getFieldColorMode(fieldDisplay.field.color?.mode);
|
||||
|
||||
if (colorMode.id === FieldColorModeId.Thresholds) {
|
||||
const thresholds = fieldDisplay.field.thresholds?.steps ?? [];
|
||||
const min = fieldDisplay.field.min ?? 0;
|
||||
const max = fieldDisplay.field.max ?? 100;
|
||||
|
||||
const result: Array<{ color: string; percent: number }> = [
|
||||
{ color: displayProcessor(min).color ?? baseColor, percent: 0 },
|
||||
];
|
||||
|
||||
for (const threshold of thresholds) {
|
||||
if (threshold.value > min && threshold.value < max) {
|
||||
const percent = (threshold.value - min) / (max - min);
|
||||
result.push({ color: threshold.color, percent });
|
||||
}
|
||||
}
|
||||
|
||||
result.push({ color: displayProcessor(max).color ?? baseColor, percent: 1 });
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO we need to return thresholded values here. those will have breakpoints
|
||||
// which map to exact percentages, and we should show that correctly.
|
||||
// Handle continuous color modes first
|
||||
if (colorMode.isContinuous && colorMode.getColors && !forSegment) {
|
||||
// Handle continuous color modes first
|
||||
const colors = colorMode.getColors(theme);
|
||||
return colors.map((color, idx) => ({ color, percent: idx / (colors.length - 1) }));
|
||||
} else if (colorMode.isByValue) {
|
||||
}
|
||||
|
||||
if (colorMode.isByValue) {
|
||||
// For value based colors we want to stay more true to the specific color
|
||||
// So a radial gradient that adds a bit of light and shade works best
|
||||
const darkerColor = tinycolor(baseColor).darken(5);
|
||||
|
||||
Reference in New Issue
Block a user