refactoring defs into utils
This commit is contained in:
@@ -319,7 +319,7 @@ export { type MonacoLanguageRegistryItem, monacoLanguageRegistry } from './monac
|
|||||||
export { createTheme } from './themes/createTheme';
|
export { createTheme } from './themes/createTheme';
|
||||||
export { getThemeById, getBuiltInThemes, type ThemeRegistryItem } from './themes/registry';
|
export { getThemeById, getBuiltInThemes, type ThemeRegistryItem } from './themes/registry';
|
||||||
export type { NewThemeOptions } from './themes/createTheme';
|
export type { NewThemeOptions } from './themes/createTheme';
|
||||||
export type { ThemeRichColor, GrafanaTheme2 } from './themes/types';
|
export type { ThemeRichColor, GrafanaTheme2, GradientStop } from './themes/types';
|
||||||
export type { ThemeColors } from './themes/createColors';
|
export type { ThemeColors } from './themes/createColors';
|
||||||
export type { ThemeBreakpoints, ThemeBreakpointsKey } from './themes/breakpoints';
|
export type { ThemeBreakpoints, ThemeBreakpointsKey } from './themes/breakpoints';
|
||||||
export type { ThemeShadows } from './themes/createShadows';
|
export type { ThemeShadows } from './themes/createShadows';
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
|
|
||||||
|
import { GradientStop } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a number whose value is limited to the given range.
|
* Returns a number whose value is limited to the given range.
|
||||||
* @param value The value to be clamped
|
* @param value The value to be clamped
|
||||||
@@ -393,16 +395,14 @@ export const onBackground = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @alpha
|
||||||
* Given color stops (each with a color and percentage 0..1) returns the color at a given percentage.
|
* Given color stops (each with a color and percentage 0..1) returns the color at a given percentage.
|
||||||
* Uses tinycolor.mix for interpolation.
|
* Uses tinycolor.mix for interpolation.
|
||||||
* @params stops - array of color stops (percentages 0..1)
|
* @params stops - array of color stops (percentages 0..1)
|
||||||
* @params percent - percentage 0..1
|
* @params percent - percentage 0..1
|
||||||
* @returns color at the given percentage
|
* @returns color at the given percentage
|
||||||
*/
|
*/
|
||||||
export function colorAtGradientPercent(
|
export function colorAtGradientPercent(stops: GradientStop[], percent: number): tinycolor.Instance {
|
||||||
stops: Array<{ color: string; percent: number }>,
|
|
||||||
percent: number
|
|
||||||
): tinycolor.Instance {
|
|
||||||
if (!stops || stops.length < 2) {
|
if (!stops || stops.length < 2) {
|
||||||
throw new Error('colorAtGradientPercent requires at least two color stops');
|
throw new Error('colorAtGradientPercent requires at least two color stops');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,3 +59,9 @@ export interface ThemeRichColor {
|
|||||||
export type DeepPartial<T> = {
|
export type DeepPartial<T> = {
|
||||||
[P in keyof T]?: DeepPartial<T[P]>;
|
[P in keyof T]?: DeepPartial<T[P]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @alpha */
|
||||||
|
export interface GradientStop {
|
||||||
|
color: string;
|
||||||
|
percent: number;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
import { useId, useEffect } from 'react';
|
import { useId, useMemo } from 'react';
|
||||||
|
|
||||||
import { RadialColorDefs } from './RadialColorDefs';
|
import { DisplayProcessor, FieldDisplay } from '@grafana/data';
|
||||||
import { RadialShape } from './RadialGauge';
|
|
||||||
import { GaugeDimensions, toRad } from './utils';
|
import { useTheme2 } from '../../themes/ThemeContext';
|
||||||
|
|
||||||
|
import { RadialGradientMode, RadialShape } from './RadialGauge';
|
||||||
|
import { buildGradientColors, getEndpointColors, getGradientCss, getGuideDotColors } from './colors';
|
||||||
|
import { drawRadialArcPath, GaugeDimensions, toRad } from './utils';
|
||||||
|
|
||||||
export interface RadialArcPathPropsBase {
|
export interface RadialArcPathPropsBase {
|
||||||
startAngle: number;
|
|
||||||
dimensions: GaugeDimensions;
|
|
||||||
colorDefs: RadialColorDefs;
|
|
||||||
arcLengthDeg: number;
|
arcLengthDeg: number;
|
||||||
shape: RadialShape;
|
|
||||||
color?: string;
|
color?: string;
|
||||||
|
dimensions: GaugeDimensions;
|
||||||
|
displayProcessor: DisplayProcessor;
|
||||||
|
fieldDisplay: FieldDisplay;
|
||||||
glowFilter?: string;
|
glowFilter?: string;
|
||||||
|
gradientMode: RadialGradientMode;
|
||||||
roundedBars?: boolean;
|
roundedBars?: boolean;
|
||||||
|
shape: RadialShape;
|
||||||
showGuideDots?: boolean;
|
showGuideDots?: boolean;
|
||||||
guideDotStartColor?: string;
|
startAngle: number;
|
||||||
guideDotEndColor?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RadialArcPathPropsWithGuideDot extends RadialArcPathPropsBase {
|
interface RadialArcPathPropsWithGuideDot extends RadialArcPathPropsBase {
|
||||||
@@ -28,110 +32,60 @@ type RadialArcPathProps = RadialArcPathPropsBase | RadialArcPathPropsWithGuideDo
|
|||||||
|
|
||||||
const MAX_DOT_RADIUS = 8;
|
const MAX_DOT_RADIUS = 8;
|
||||||
|
|
||||||
function drawRadialArcPath({
|
|
||||||
angle,
|
|
||||||
arcLengthDeg,
|
|
||||||
dimensions,
|
|
||||||
roundedBars,
|
|
||||||
}: {
|
|
||||||
angle: number;
|
|
||||||
dimensions: GaugeDimensions;
|
|
||||||
arcLengthDeg: number;
|
|
||||||
roundedBars?: boolean;
|
|
||||||
}): string {
|
|
||||||
const { radius, centerX, centerY, barWidth } = dimensions;
|
|
||||||
|
|
||||||
if (arcLengthDeg === 360) {
|
|
||||||
// For some reason a 100% full arc cannot be rendered
|
|
||||||
arcLengthDeg = 359.99;
|
|
||||||
}
|
|
||||||
|
|
||||||
const startRadians = toRad(angle);
|
|
||||||
const endRadians = toRad(angle + arcLengthDeg);
|
|
||||||
|
|
||||||
const largeArc = arcLengthDeg > 180 ? 1 : 0;
|
|
||||||
|
|
||||||
const outerR = radius + barWidth / 2;
|
|
||||||
const innerR = Math.max(0, radius - barWidth / 2);
|
|
||||||
|
|
||||||
const ox1 = centerX + outerR * Math.cos(startRadians);
|
|
||||||
const oy1 = centerY + outerR * Math.sin(startRadians);
|
|
||||||
const ox2 = centerX + outerR * Math.cos(endRadians);
|
|
||||||
const oy2 = centerY + outerR * Math.sin(endRadians);
|
|
||||||
|
|
||||||
const ix1 = centerX + innerR * Math.cos(startRadians);
|
|
||||||
const iy1 = centerY + innerR * Math.sin(startRadians);
|
|
||||||
const ix2 = centerX + innerR * Math.cos(endRadians);
|
|
||||||
const iy2 = centerY + innerR * Math.sin(endRadians);
|
|
||||||
|
|
||||||
const capR = barWidth / 2;
|
|
||||||
|
|
||||||
const pathParts = [
|
|
||||||
// start at outer start
|
|
||||||
'M',
|
|
||||||
ox1,
|
|
||||||
oy1,
|
|
||||||
// outer arc from start to end (clockwise)
|
|
||||||
'A',
|
|
||||||
outerR,
|
|
||||||
outerR,
|
|
||||||
0,
|
|
||||||
largeArc,
|
|
||||||
1,
|
|
||||||
ox2,
|
|
||||||
oy2,
|
|
||||||
];
|
|
||||||
|
|
||||||
if (roundedBars) {
|
|
||||||
// rounded end cap: small arc connecting outer end to inner end
|
|
||||||
pathParts.push('A', capR, capR, 0, 0, 1, ix2, iy2);
|
|
||||||
} else {
|
|
||||||
// straight line to inner end
|
|
||||||
pathParts.push('L', ix2, iy2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (innerR <= 0) {
|
|
||||||
// if inner radius collapsed to center, line to center and close
|
|
||||||
pathParts.push('L', centerX, centerY, 'Z');
|
|
||||||
} else {
|
|
||||||
// inner arc from end back to start (counter-clockwise)
|
|
||||||
pathParts.push('A', innerR, innerR, 0, largeArc, 0, ix1, iy1);
|
|
||||||
|
|
||||||
if (roundedBars) {
|
|
||||||
// rounded start cap: small arc connecting inner start back to outer start
|
|
||||||
pathParts.push('A', capR, capR, 0, 0, 1, ox1, oy1);
|
|
||||||
} else {
|
|
||||||
// straight line back to outer start
|
|
||||||
pathParts.push('L', ox1, oy1);
|
|
||||||
}
|
|
||||||
|
|
||||||
pathParts.push('Z');
|
|
||||||
}
|
|
||||||
|
|
||||||
return pathParts.join(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function RadialArcPath({
|
export function RadialArcPath({
|
||||||
startAngle: angle,
|
|
||||||
dimensions,
|
|
||||||
color,
|
|
||||||
colorDefs,
|
|
||||||
shape,
|
|
||||||
arcLengthDeg,
|
arcLengthDeg,
|
||||||
|
color,
|
||||||
|
dimensions,
|
||||||
|
displayProcessor,
|
||||||
|
fieldDisplay,
|
||||||
glowFilter,
|
glowFilter,
|
||||||
|
gradientMode,
|
||||||
roundedBars,
|
roundedBars,
|
||||||
|
shape,
|
||||||
showGuideDots,
|
showGuideDots,
|
||||||
guideDotStartColor,
|
startAngle: angle,
|
||||||
guideDotEndColor,
|
|
||||||
}: RadialArcPathProps) {
|
}: RadialArcPathProps) {
|
||||||
|
const theme = useTheme2();
|
||||||
const id = useId();
|
const id = useId();
|
||||||
|
|
||||||
const { radius, centerX, centerY, barWidth } = dimensions;
|
const { radius, centerX, centerY, barWidth } = dimensions;
|
||||||
|
|
||||||
|
const gradientStops = useMemo(() => {
|
||||||
|
if (gradientMode === 'none') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return buildGradientColors(gradientMode, theme, displayProcessor, fieldDisplay, fieldDisplay.display.color);
|
||||||
|
}, [gradientMode, fieldDisplay, theme, displayProcessor]);
|
||||||
|
|
||||||
|
const { guideDotColors, endpointColors } = useMemo(() => {
|
||||||
|
if (!showGuideDots || shape !== 'circle' || gradientStops.length === 0) {
|
||||||
|
return {
|
||||||
|
guideDotStartColor: undefined,
|
||||||
|
guideDotEndColor: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
guideDotColors: getGuideDotColors(gradientStops, fieldDisplay.display.percent ?? 0),
|
||||||
|
endpointColors: getEndpointColors(gradientStops, fieldDisplay.display.percent ?? 0),
|
||||||
|
};
|
||||||
|
}, [showGuideDots, shape, fieldDisplay, gradientStops]);
|
||||||
|
|
||||||
|
const bgDivStyle = useMemo(() => {
|
||||||
|
const baseStyles = { width: '100%', height: '100%' };
|
||||||
|
if (color) {
|
||||||
|
return { backgroundColor: color, ...baseStyles };
|
||||||
|
}
|
||||||
|
const gradientCss = getGradientCss(gradientStops, shape);
|
||||||
|
return { backgroundImage: gradientCss, ...baseStyles };
|
||||||
|
}, [color, gradientStops, shape]);
|
||||||
|
|
||||||
const startRadians = toRad(angle);
|
const startRadians = toRad(angle);
|
||||||
const endRadians = toRad(angle + arcLengthDeg);
|
const endRadians = toRad(angle + arcLengthDeg);
|
||||||
const [startColor, endColor] = colorDefs.getEndpointColors();
|
|
||||||
|
|
||||||
const path = drawRadialArcPath({ angle, arcLengthDeg, dimensions, roundedBars });
|
const path = useMemo(
|
||||||
|
() => drawRadialArcPath(angle, arcLengthDeg, dimensions, roundedBars),
|
||||||
|
[angle, arcLengthDeg, dimensions, roundedBars]
|
||||||
|
);
|
||||||
let x1 = centerX + radius * Math.cos(startRadians);
|
let x1 = centerX + radius * Math.cos(startRadians);
|
||||||
let y1 = centerY + radius * Math.sin(startRadians);
|
let y1 = centerY + radius * Math.sin(startRadians);
|
||||||
let x2 = centerX + radius * Math.cos(endRadians);
|
let x2 = centerX + radius * Math.cos(endRadians);
|
||||||
@@ -152,28 +106,16 @@ export function RadialArcPath({
|
|||||||
height={(radius + barWidth) * 2}
|
height={(radius + barWidth) * 2}
|
||||||
clipPath={`url(#${id})`}
|
clipPath={`url(#${id})`}
|
||||||
>
|
>
|
||||||
<div
|
<div style={bgDivStyle} />
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
backgroundImage: color ? undefined : colorDefs.getGradientDef(),
|
|
||||||
backgroundColor: color,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</foreignObject>
|
</foreignObject>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
{showGuideDots && (
|
{showGuideDots && (
|
||||||
<>
|
<>
|
||||||
{shape === 'circle' && (
|
{endpointColors && <circle cx={x1} cy={y1} r={barWidth / 2} fill={endpointColors[0]} />}
|
||||||
<>
|
{endpointColors && <circle cx={x2} cy={y2} r={barWidth / 2} fill={endpointColors[1]} />}
|
||||||
<circle cx={x1} cy={y1} r={barWidth / 2} fill={startColor} />
|
{guideDotColors && arcLengthDeg > 5 && <circle cx={x1} cy={y1} r={dotRadius} fill={guideDotColors[0]} />}
|
||||||
<circle cx={x2} cy={y2} r={barWidth / 2} fill={endColor} />
|
{guideDotColors && <circle cx={x2} cy={y2} r={dotRadius} fill={guideDotColors[1]} />}
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{arcLengthDeg > 5 && <circle cx={x1} cy={y1} r={dotRadius} fill={guideDotStartColor} />}
|
|
||||||
<circle cx={x2} cy={y2} r={dotRadius} fill={guideDotEndColor} />
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,60 +1,64 @@
|
|||||||
|
import { DisplayProcessor, FieldDisplay } from '@grafana/data';
|
||||||
|
|
||||||
import { useTheme2 } from '../../themes/ThemeContext';
|
import { useTheme2 } from '../../themes/ThemeContext';
|
||||||
|
|
||||||
import { RadialArcPath } from './RadialArcPath';
|
import { RadialArcPath } from './RadialArcPath';
|
||||||
import { RadialColorDefs } from './RadialColorDefs';
|
import { RadialGradientMode, RadialShape } from './RadialGauge';
|
||||||
import { RadialShape } from './RadialGauge';
|
|
||||||
import { GaugeDimensions } from './utils';
|
import { GaugeDimensions } from './utils';
|
||||||
|
|
||||||
export interface RadialBarProps {
|
export interface RadialBarProps {
|
||||||
dimensions: GaugeDimensions;
|
|
||||||
colorDefs: RadialColorDefs;
|
|
||||||
angleRange: number;
|
|
||||||
angle: number;
|
angle: number;
|
||||||
startAngle: number;
|
angleRange: number;
|
||||||
roundedBars?: boolean;
|
dimensions: GaugeDimensions;
|
||||||
|
displayProcessor: DisplayProcessor;
|
||||||
|
fieldDisplay: FieldDisplay;
|
||||||
glowFilter?: string;
|
glowFilter?: string;
|
||||||
|
gradientMode: RadialGradientMode;
|
||||||
|
roundedBars?: boolean;
|
||||||
shape: RadialShape;
|
shape: RadialShape;
|
||||||
|
startAngle: number;
|
||||||
}
|
}
|
||||||
export function RadialBar({
|
export function RadialBar({
|
||||||
dimensions,
|
|
||||||
colorDefs,
|
|
||||||
angleRange,
|
|
||||||
angle,
|
angle,
|
||||||
startAngle,
|
angleRange,
|
||||||
roundedBars,
|
dimensions,
|
||||||
|
displayProcessor,
|
||||||
|
fieldDisplay,
|
||||||
glowFilter,
|
glowFilter,
|
||||||
|
gradientMode,
|
||||||
|
roundedBars,
|
||||||
shape,
|
shape,
|
||||||
|
startAngle,
|
||||||
}: RadialBarProps) {
|
}: RadialBarProps) {
|
||||||
const theme = useTheme2();
|
const theme = useTheme2();
|
||||||
const [startDotColor, endDotColor] = colorDefs.getGuideDotColors();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<g>
|
{/** Track */}
|
||||||
{/** Track */}
|
<RadialArcPath
|
||||||
<RadialArcPath
|
arcLengthDeg={angleRange - angle}
|
||||||
startAngle={startAngle + angle}
|
color={theme.colors.action.hover}
|
||||||
dimensions={dimensions}
|
dimensions={dimensions}
|
||||||
arcLengthDeg={angleRange - angle}
|
displayProcessor={displayProcessor}
|
||||||
colorDefs={colorDefs}
|
fieldDisplay={fieldDisplay}
|
||||||
color={theme.colors.action.hover}
|
gradientMode="none"
|
||||||
roundedBars={roundedBars}
|
roundedBars={roundedBars}
|
||||||
shape={shape}
|
shape={shape}
|
||||||
/>
|
startAngle={startAngle + angle}
|
||||||
{/** The colored bar */}
|
/>
|
||||||
<RadialArcPath
|
{/** The colored bar */}
|
||||||
dimensions={dimensions}
|
<RadialArcPath
|
||||||
startAngle={startAngle}
|
arcLengthDeg={angle}
|
||||||
arcLengthDeg={angle}
|
color={gradientMode === 'none' ? fieldDisplay.display.color : undefined}
|
||||||
colorDefs={colorDefs}
|
dimensions={dimensions}
|
||||||
roundedBars={roundedBars}
|
displayProcessor={displayProcessor}
|
||||||
glowFilter={glowFilter}
|
fieldDisplay={fieldDisplay}
|
||||||
showGuideDots={roundedBars}
|
glowFilter={glowFilter}
|
||||||
guideDotStartColor={startDotColor}
|
gradientMode={gradientMode}
|
||||||
guideDotEndColor={endDotColor}
|
roundedBars={roundedBars}
|
||||||
shape={shape}
|
shape={shape}
|
||||||
/>
|
showGuideDots={roundedBars}
|
||||||
</g>
|
startAngle={startAngle}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import { FieldDisplay } from '@grafana/data';
|
import { DisplayProcessor, FALLBACK_COLOR, FieldDisplay } from '@grafana/data';
|
||||||
|
|
||||||
import { useTheme2 } from '../../themes/ThemeContext';
|
import { useTheme2 } from '../../themes/ThemeContext';
|
||||||
|
|
||||||
import { RadialArcPath } from './RadialArcPath';
|
import { RadialArcPath } from './RadialArcPath';
|
||||||
import { RadialColorDefs } from './RadialColorDefs';
|
|
||||||
import { RadialGradientMode, RadialShape } from './RadialGauge';
|
import { RadialGradientMode, RadialShape } from './RadialGauge';
|
||||||
import { GaugeDimensions } from './utils';
|
import { GaugeDimensions } from './utils';
|
||||||
|
|
||||||
export interface RadialBarSegmentedProps {
|
export interface RadialBarSegmentedProps {
|
||||||
fieldDisplay: FieldDisplay;
|
fieldDisplay: FieldDisplay;
|
||||||
|
displayProcessor: DisplayProcessor;
|
||||||
dimensions: GaugeDimensions;
|
dimensions: GaugeDimensions;
|
||||||
colorDefs: RadialColorDefs;
|
|
||||||
angleRange: number;
|
angleRange: number;
|
||||||
startAngle: number;
|
startAngle: number;
|
||||||
glowFilter?: string;
|
glowFilter?: string;
|
||||||
@@ -21,13 +20,13 @@ export interface RadialBarSegmentedProps {
|
|||||||
}
|
}
|
||||||
export function RadialBarSegmented({
|
export function RadialBarSegmented({
|
||||||
fieldDisplay,
|
fieldDisplay,
|
||||||
|
displayProcessor,
|
||||||
dimensions,
|
dimensions,
|
||||||
startAngle,
|
startAngle,
|
||||||
angleRange,
|
angleRange,
|
||||||
glowFilter,
|
glowFilter,
|
||||||
segmentCount,
|
segmentCount,
|
||||||
segmentSpacing,
|
segmentSpacing,
|
||||||
colorDefs,
|
|
||||||
shape,
|
shape,
|
||||||
gradientMode,
|
gradientMode,
|
||||||
}: RadialBarSegmentedProps) {
|
}: RadialBarSegmentedProps) {
|
||||||
@@ -48,7 +47,7 @@ export function RadialBarSegmented({
|
|||||||
if (angleValue >= value) {
|
if (angleValue >= value) {
|
||||||
segmentColor = theme.colors.action.hover;
|
segmentColor = theme.colors.action.hover;
|
||||||
} else if (gradientMode === 'none') {
|
} else if (gradientMode === 'none') {
|
||||||
segmentColor = colorDefs.getSegmentColor(angleValue);
|
segmentColor = displayProcessor(angleValue).color ?? FALLBACK_COLOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
segments.push(
|
segments.push(
|
||||||
@@ -58,9 +57,11 @@ export function RadialBarSegmented({
|
|||||||
dimensions={dimensions}
|
dimensions={dimensions}
|
||||||
color={segmentColor}
|
color={segmentColor}
|
||||||
shape={shape}
|
shape={shape}
|
||||||
colorDefs={colorDefs}
|
|
||||||
glowFilter={glowFilter}
|
glowFilter={glowFilter}
|
||||||
arcLengthDeg={segmentArcLengthDeg}
|
arcLengthDeg={segmentArcLengthDeg}
|
||||||
|
gradientMode={gradientMode}
|
||||||
|
fieldDisplay={fieldDisplay}
|
||||||
|
displayProcessor={displayProcessor}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -89,44 +90,3 @@ function getOptimalSegmentCount(
|
|||||||
|
|
||||||
return Math.min(maxSegments, segmentCount);
|
return Math.min(maxSegments, segmentCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// export function RadialSegmentLine({
|
|
||||||
// gaugeId,
|
|
||||||
// center,
|
|
||||||
// angle,
|
|
||||||
// size,
|
|
||||||
// color,
|
|
||||||
// barWidth,
|
|
||||||
// roundedBars,
|
|
||||||
// glow,
|
|
||||||
// margin,
|
|
||||||
// segmentWidth,
|
|
||||||
// }: RadialSegmentProps) {
|
|
||||||
// const arcSize = size - barWidth;
|
|
||||||
// const radius = arcSize / 2 - margin;
|
|
||||||
|
|
||||||
// const angleRad = (Math.PI * (angle - 90)) / 180;
|
|
||||||
// const lineLength = radius - barWidth;
|
|
||||||
|
|
||||||
// const x1 = center + radius * Math.cos(angleRad);
|
|
||||||
// const y1 = center + radius * Math.sin(angleRad);
|
|
||||||
// const x2 = center + lineLength * Math.cos(angleRad);
|
|
||||||
// const y2 = center + lineLength * Math.sin(angleRad);
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// <line
|
|
||||||
// x1={x1}
|
|
||||||
// y1={y1}
|
|
||||||
// x2={x2}
|
|
||||||
// y2={y2}
|
|
||||||
// fill="none"
|
|
||||||
// fillOpacity="0.85"
|
|
||||||
// stroke={color}
|
|
||||||
// strokeOpacity="1"
|
|
||||||
// strokeLinecap={roundedBars ? 'round' : 'butt'}
|
|
||||||
// strokeWidth={segmentWidth}
|
|
||||||
// strokeDasharray="0"
|
|
||||||
// filter={glow ? `url(#glow-${gaugeId})` : undefined}
|
|
||||||
// />
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
import { colorManipulator, DisplayProcessor, FALLBACK_COLOR, FieldDisplay, GrafanaTheme2 } from '@grafana/data';
|
|
||||||
|
|
||||||
import { RadialGradientMode, RadialShape } from './RadialGauge';
|
|
||||||
import { buildGradientColors } from './colors';
|
|
||||||
import { GaugeDimensions } from './utils';
|
|
||||||
|
|
||||||
export interface RadialColorDefsOptions {
|
|
||||||
gradient: RadialGradientMode;
|
|
||||||
fieldDisplay: FieldDisplay;
|
|
||||||
theme: GrafanaTheme2;
|
|
||||||
dimensions: GaugeDimensions;
|
|
||||||
shape: RadialShape;
|
|
||||||
gaugeId: string;
|
|
||||||
displayProcessor: DisplayProcessor;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CONTRAST_THRESHOLD_MAX = 4.5;
|
|
||||||
const getGuideDotColor = (color: string): string => {
|
|
||||||
const darkColor = '#111217'; // gray05
|
|
||||||
const lightColor = '#fbfbfb'; // gray90
|
|
||||||
return colorManipulator.getContrastRatio(darkColor, color) >= CONTRAST_THRESHOLD_MAX ? darkColor : lightColor;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class RadialColorDefs {
|
|
||||||
constructor(private options: RadialColorDefsOptions) {}
|
|
||||||
|
|
||||||
getSegmentColor(forValue: number): string {
|
|
||||||
const { displayProcessor } = this.options;
|
|
||||||
return displayProcessor(forValue).color ?? FALLBACK_COLOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
getFieldBaseColor(): string {
|
|
||||||
return this.options.fieldDisplay.display.color ?? FALLBACK_COLOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
getGradient(baseColor = this.getFieldBaseColor(), forSegment?: boolean): Array<{ color: string; percent: number }> {
|
|
||||||
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 0deg, ${colorStrings.join(', ')})`
|
|
||||||
: `linear-gradient(90deg, ${colorStrings.join(', ')})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
getEndpointColors(): [string, string] {
|
|
||||||
const { fieldDisplay } = this.options;
|
|
||||||
|
|
||||||
const gradient = this.getGradient();
|
|
||||||
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
|
|
||||||
if (gradient.length >= 2) {
|
|
||||||
const endColorByPercentage = colorManipulator.colorAtGradientPercent(gradient, valuePercent);
|
|
||||||
endColor =
|
|
||||||
endColorByPercentage.getAlpha() === 1
|
|
||||||
? endColorByPercentage.toHexString()
|
|
||||||
: endColorByPercentage.toHex8String();
|
|
||||||
}
|
|
||||||
return [startColor, endColor];
|
|
||||||
}
|
|
||||||
|
|
||||||
getGuideDotColors(): [string, string] {
|
|
||||||
const [startColor, endColor] = this.getEndpointColors();
|
|
||||||
return [getGuideDotColor(startColor), getGuideDotColor(endColor)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,6 @@ import { getFormattedThresholds } from '../Gauge/utils';
|
|||||||
|
|
||||||
import { RadialBar } from './RadialBar';
|
import { RadialBar } from './RadialBar';
|
||||||
import { RadialBarSegmented } from './RadialBarSegmented';
|
import { RadialBarSegmented } from './RadialBarSegmented';
|
||||||
import { RadialColorDefs } from './RadialColorDefs';
|
|
||||||
import { RadialScaleLabels } from './RadialScaleLabels';
|
import { RadialScaleLabels } from './RadialScaleLabels';
|
||||||
import { RadialSparkline } from './RadialSparkline';
|
import { RadialSparkline } from './RadialSparkline';
|
||||||
import { RadialText } from './RadialText';
|
import { RadialText } from './RadialText';
|
||||||
@@ -133,15 +132,6 @@ export function RadialGauge(props: RadialGaugeProps) {
|
|||||||
|
|
||||||
const displayProcessor = getFieldDisplayProcessor(displayValue);
|
const displayProcessor = getFieldDisplayProcessor(displayValue);
|
||||||
const glowFilterId = `glow-${gaugeId}`;
|
const glowFilterId = `glow-${gaugeId}`;
|
||||||
const colorDefs = new RadialColorDefs({
|
|
||||||
gradient,
|
|
||||||
fieldDisplay: displayValue,
|
|
||||||
theme,
|
|
||||||
dimensions,
|
|
||||||
shape,
|
|
||||||
gaugeId,
|
|
||||||
displayProcessor,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (segmentCount > 1) {
|
if (segmentCount > 1) {
|
||||||
graphics.push(
|
graphics.push(
|
||||||
@@ -154,9 +144,9 @@ export function RadialGauge(props: RadialGaugeProps) {
|
|||||||
glowFilter={`url(#${glowFilterId})`}
|
glowFilter={`url(#${glowFilterId})`}
|
||||||
segmentCount={segmentCount}
|
segmentCount={segmentCount}
|
||||||
segmentSpacing={segmentSpacing}
|
segmentSpacing={segmentSpacing}
|
||||||
colorDefs={colorDefs}
|
|
||||||
shape={shape}
|
shape={shape}
|
||||||
gradientMode={gradient}
|
gradientMode={gradient}
|
||||||
|
displayProcessor={displayProcessor}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -164,13 +154,15 @@ export function RadialGauge(props: RadialGaugeProps) {
|
|||||||
<RadialBar
|
<RadialBar
|
||||||
key={`radial-bar-${barIndex}-${gaugeId}`}
|
key={`radial-bar-${barIndex}-${gaugeId}`}
|
||||||
dimensions={dimensions}
|
dimensions={dimensions}
|
||||||
colorDefs={colorDefs}
|
|
||||||
angle={angle}
|
angle={angle}
|
||||||
angleRange={angleRange}
|
angleRange={angleRange}
|
||||||
startAngle={startAngle}
|
startAngle={startAngle}
|
||||||
roundedBars={roundedBars}
|
roundedBars={roundedBars}
|
||||||
glowFilter={`url(#${glowFilterId})`}
|
glowFilter={`url(#${glowFilterId})`}
|
||||||
shape={shape}
|
shape={shape}
|
||||||
|
gradientMode={gradient}
|
||||||
|
displayProcessor={displayProcessor}
|
||||||
|
fieldDisplay={displayValue}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -230,9 +222,9 @@ export function RadialGauge(props: RadialGaugeProps) {
|
|||||||
angleRange={angleRange}
|
angleRange={angleRange}
|
||||||
roundedBars={roundedBars}
|
roundedBars={roundedBars}
|
||||||
glowFilter={`url(#${glowFilterId})`}
|
glowFilter={`url(#${glowFilterId})`}
|
||||||
colorDefs={colorDefs}
|
|
||||||
shape={shape}
|
shape={shape}
|
||||||
gradientMode={gradient}
|
gradientMode={gradient}
|
||||||
|
displayProcessor={displayProcessor}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { FieldDisplay, Threshold } from '@grafana/data';
|
import { DisplayProcessor, FieldDisplay, Threshold } from '@grafana/data';
|
||||||
|
|
||||||
import { useTheme2 } from '../../themes/ThemeContext';
|
|
||||||
|
|
||||||
import { RadialArcPath } from './RadialArcPath';
|
import { RadialArcPath } from './RadialArcPath';
|
||||||
import { RadialColorDefs } from './RadialColorDefs';
|
|
||||||
import { RadialGradientMode, RadialShape } from './RadialGauge';
|
import { RadialGradientMode, RadialShape } from './RadialGauge';
|
||||||
import { GaugeDimensions } from './utils';
|
import { GaugeDimensions } from './utils';
|
||||||
|
|
||||||
@@ -16,9 +13,9 @@ export interface Props {
|
|||||||
fieldDisplay: FieldDisplay;
|
fieldDisplay: FieldDisplay;
|
||||||
roundedBars?: boolean;
|
roundedBars?: boolean;
|
||||||
glowFilter?: string;
|
glowFilter?: string;
|
||||||
colorDefs: RadialColorDefs;
|
|
||||||
thresholds: Threshold[];
|
thresholds: Threshold[];
|
||||||
gradientMode: RadialGradientMode;
|
gradientMode: RadialGradientMode;
|
||||||
|
displayProcessor: DisplayProcessor;
|
||||||
}
|
}
|
||||||
export function ThresholdsBar({
|
export function ThresholdsBar({
|
||||||
dimensions,
|
dimensions,
|
||||||
@@ -27,12 +24,11 @@ export function ThresholdsBar({
|
|||||||
angleRange,
|
angleRange,
|
||||||
roundedBars,
|
roundedBars,
|
||||||
glowFilter,
|
glowFilter,
|
||||||
colorDefs,
|
|
||||||
thresholds,
|
thresholds,
|
||||||
shape,
|
shape,
|
||||||
gradientMode,
|
gradientMode,
|
||||||
|
displayProcessor,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const theme = useTheme2();
|
|
||||||
const fieldConfig = fieldDisplay.field;
|
const fieldConfig = fieldDisplay.field;
|
||||||
const min = fieldConfig.min ?? 0;
|
const min = fieldConfig.min ?? 0;
|
||||||
const max = fieldConfig.max ?? 100;
|
const max = fieldConfig.max ?? 100;
|
||||||
@@ -63,12 +59,14 @@ export function ThresholdsBar({
|
|||||||
key={i}
|
key={i}
|
||||||
startAngle={currentStart}
|
startAngle={currentStart}
|
||||||
arcLengthDeg={lengthDeg}
|
arcLengthDeg={lengthDeg}
|
||||||
colorDefs={colorDefs}
|
|
||||||
color={gradientMode === 'none' ? threshold.color : undefined}
|
color={gradientMode === 'none' ? threshold.color : undefined}
|
||||||
shape={shape}
|
shape={shape}
|
||||||
dimensions={thresholdDimensions}
|
dimensions={thresholdDimensions}
|
||||||
roundedBars={roundedBars}
|
roundedBars={roundedBars}
|
||||||
glowFilter={glowFilter}
|
glowFilter={glowFilter}
|
||||||
|
gradientMode={gradientMode}
|
||||||
|
displayProcessor={displayProcessor}
|
||||||
|
fieldDisplay={fieldDisplay}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,26 @@
|
|||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
|
|
||||||
import { DisplayProcessor, FieldDisplay, getFieldColorMode, GrafanaTheme2 } from '@grafana/data';
|
import {
|
||||||
|
colorManipulator,
|
||||||
|
DisplayProcessor,
|
||||||
|
FALLBACK_COLOR,
|
||||||
|
FieldDisplay,
|
||||||
|
getFieldColorMode,
|
||||||
|
GradientStop,
|
||||||
|
GrafanaTheme2,
|
||||||
|
} from '@grafana/data';
|
||||||
import { FieldColorModeId } from '@grafana/schema';
|
import { FieldColorModeId } from '@grafana/schema';
|
||||||
|
|
||||||
import { RadialGradientMode } from './RadialGauge';
|
import { RadialGradientMode, RadialShape } from './RadialGauge';
|
||||||
|
|
||||||
export function buildGradientColors(
|
export function buildGradientColors(
|
||||||
gradientMode: RadialGradientMode,
|
gradientMode: RadialGradientMode,
|
||||||
baseColor: string,
|
|
||||||
theme: GrafanaTheme2,
|
theme: GrafanaTheme2,
|
||||||
displayProcessor: DisplayProcessor,
|
displayProcessor: DisplayProcessor,
|
||||||
fieldDisplay: FieldDisplay,
|
fieldDisplay: FieldDisplay,
|
||||||
|
baseColor = FALLBACK_COLOR,
|
||||||
forSegment?: boolean
|
forSegment?: boolean
|
||||||
): Array<{ color: string; percent: number }> {
|
): GradientStop[] {
|
||||||
if (gradientMode === 'none') {
|
if (gradientMode === 'none') {
|
||||||
return [
|
return [
|
||||||
{ color: baseColor, percent: 0 },
|
{ color: baseColor, percent: 0 },
|
||||||
@@ -80,3 +88,35 @@ export function buildGradientColors(
|
|||||||
{ color: darkerColor.toString(), percent: 1 },
|
{ color: darkerColor.toString(), percent: 1 },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getEndpointColors(gradientStops: GradientStop[], percent = 0): [string, string] {
|
||||||
|
const startColor = gradientStops[0].color;
|
||||||
|
let endColor = gradientStops[gradientStops.length - 1].color;
|
||||||
|
|
||||||
|
// if we have a percentageFilled, use it to get a the correct end color based on where the bar terminates
|
||||||
|
if (gradientStops.length >= 2) {
|
||||||
|
const endColorByPercentage = colorManipulator.colorAtGradientPercent(gradientStops, percent);
|
||||||
|
endColor =
|
||||||
|
endColorByPercentage.getAlpha() === 1 ? endColorByPercentage.toHexString() : endColorByPercentage.toHex8String();
|
||||||
|
}
|
||||||
|
return [startColor, endColor];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getGradientCss(gradientStops: GradientStop[], shape: RadialShape): string {
|
||||||
|
const colorStrings = gradientStops.map((stop) => `${stop.color} ${(stop.percent * 100).toFixed(2)}%`);
|
||||||
|
return shape === 'circle'
|
||||||
|
? `conic-gradient(from 0deg, ${colorStrings.join(', ')})`
|
||||||
|
: `linear-gradient(90deg, ${colorStrings.join(', ')})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CONTRAST_THRESHOLD_MAX = 4.5;
|
||||||
|
const getGuideDotColor = (color: string): string => {
|
||||||
|
const darkColor = '#111217'; // gray05
|
||||||
|
const lightColor = '#fbfbfb'; // gray90
|
||||||
|
return colorManipulator.getContrastRatio(darkColor, color) >= CONTRAST_THRESHOLD_MAX ? darkColor : lightColor;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getGuideDotColors(gradientStops: GradientStop[], percent = 0): [string, string] {
|
||||||
|
const [startColor, endColor] = getEndpointColors(gradientStops, percent);
|
||||||
|
return [getGuideDotColor(startColor), getGuideDotColor(endColor)];
|
||||||
|
}
|
||||||
|
|||||||
@@ -155,3 +155,81 @@ export function toCartesian(centerX: number, centerY: number, radius: number, an
|
|||||||
y: centerY + radius * Math.sin(radian),
|
y: centerY + radius * Math.sin(radian),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function drawRadialArcPath(
|
||||||
|
angle: number,
|
||||||
|
arcLengthDeg: number,
|
||||||
|
dimensions: GaugeDimensions,
|
||||||
|
roundedBars?: boolean
|
||||||
|
): string {
|
||||||
|
const { radius, centerX, centerY, barWidth } = dimensions;
|
||||||
|
|
||||||
|
if (arcLengthDeg === 360) {
|
||||||
|
// For some reason a 100% full arc cannot be rendered
|
||||||
|
arcLengthDeg = 359.99;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startRadians = toRad(angle);
|
||||||
|
const endRadians = toRad(angle + arcLengthDeg);
|
||||||
|
|
||||||
|
const largeArc = arcLengthDeg > 180 ? 1 : 0;
|
||||||
|
|
||||||
|
const outerR = radius + barWidth / 2;
|
||||||
|
const innerR = Math.max(0, radius - barWidth / 2);
|
||||||
|
|
||||||
|
const ox1 = centerX + outerR * Math.cos(startRadians);
|
||||||
|
const oy1 = centerY + outerR * Math.sin(startRadians);
|
||||||
|
const ox2 = centerX + outerR * Math.cos(endRadians);
|
||||||
|
const oy2 = centerY + outerR * Math.sin(endRadians);
|
||||||
|
|
||||||
|
const ix1 = centerX + innerR * Math.cos(startRadians);
|
||||||
|
const iy1 = centerY + innerR * Math.sin(startRadians);
|
||||||
|
const ix2 = centerX + innerR * Math.cos(endRadians);
|
||||||
|
const iy2 = centerY + innerR * Math.sin(endRadians);
|
||||||
|
|
||||||
|
const capR = barWidth / 2;
|
||||||
|
|
||||||
|
const pathParts = [
|
||||||
|
// start at outer start
|
||||||
|
'M',
|
||||||
|
ox1,
|
||||||
|
oy1,
|
||||||
|
// outer arc from start to end (clockwise)
|
||||||
|
'A',
|
||||||
|
outerR,
|
||||||
|
outerR,
|
||||||
|
0,
|
||||||
|
largeArc,
|
||||||
|
1,
|
||||||
|
ox2,
|
||||||
|
oy2,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (roundedBars) {
|
||||||
|
// rounded end cap: small arc connecting outer end to inner end
|
||||||
|
pathParts.push('A', capR, capR, 0, 0, 1, ix2, iy2);
|
||||||
|
} else {
|
||||||
|
// straight line to inner end
|
||||||
|
pathParts.push('L', ix2, iy2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (innerR <= 0) {
|
||||||
|
// if inner radius collapsed to center, line to center and close
|
||||||
|
pathParts.push('L', centerX, centerY, 'Z');
|
||||||
|
} else {
|
||||||
|
// inner arc from end back to start (counter-clockwise)
|
||||||
|
pathParts.push('A', innerR, innerR, 0, largeArc, 0, ix1, iy1);
|
||||||
|
|
||||||
|
if (roundedBars) {
|
||||||
|
// rounded start cap: small arc connecting inner start back to outer start
|
||||||
|
pathParts.push('A', capR, capR, 0, 0, 1, ox1, oy1);
|
||||||
|
} else {
|
||||||
|
// straight line back to outer start
|
||||||
|
pathParts.push('L', ox1, oy1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pathParts.push('Z');
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathParts.join(' ');
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user