Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c4ade9514 | |||
| e60fb6b1d3 | |||
| 2992964f32 | |||
| 31a806281e | |||
| d0178cc95d | |||
| 8aa4f518d7 |
Generated
+1
@@ -31,6 +31,7 @@ export interface Options extends common.SingleStatBaseOptions {
|
||||
endpointMarker?: ('point' | 'glow' | 'none');
|
||||
minVizHeight: number;
|
||||
minVizWidth: number;
|
||||
neutral?: number;
|
||||
segmentCount: number;
|
||||
segmentSpacing: number;
|
||||
shape: ('circle' | 'gauge');
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { FALLBACK_COLOR, FieldDisplay } from '@grafana/data';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { colorManipulator, FALLBACK_COLOR, FieldDisplay } from '@grafana/data';
|
||||
|
||||
import { useTheme2 } from '../../themes/ThemeContext';
|
||||
|
||||
@@ -6,7 +8,6 @@ import { RadialArcPath } from './RadialArcPath';
|
||||
import { RadialShape, RadialGaugeDimensions, GradientStop } from './types';
|
||||
|
||||
export interface RadialBarProps {
|
||||
angle: number;
|
||||
angleRange: number;
|
||||
dimensions: RadialGaugeDimensions;
|
||||
fieldDisplay: FieldDisplay;
|
||||
@@ -15,11 +16,12 @@ export interface RadialBarProps {
|
||||
endpointMarker?: 'point' | 'glow';
|
||||
shape: RadialShape;
|
||||
startAngle: number;
|
||||
startValueAngle: number;
|
||||
endValueAngle: number;
|
||||
glowFilter?: string;
|
||||
endpointMarkerGlowFilter?: string;
|
||||
}
|
||||
export function RadialBar({
|
||||
angle,
|
||||
angleRange,
|
||||
dimensions,
|
||||
fieldDisplay,
|
||||
@@ -28,26 +30,45 @@ export function RadialBar({
|
||||
endpointMarker,
|
||||
shape,
|
||||
startAngle,
|
||||
startValueAngle,
|
||||
endValueAngle,
|
||||
glowFilter,
|
||||
endpointMarkerGlowFilter,
|
||||
}: RadialBarProps) {
|
||||
const theme = useTheme2();
|
||||
const colorProps = gradient ? { gradient } : { color: fieldDisplay.display.color ?? FALLBACK_COLOR };
|
||||
const trackColor = useMemo(
|
||||
() => colorManipulator.onBackground(theme.colors.action.hover, theme.colors.background.primary).toHexString(),
|
||||
[theme]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/** Track */}
|
||||
{/** Track before value */}
|
||||
{startValueAngle !== 0 && (
|
||||
<RadialArcPath
|
||||
arcLengthDeg={startValueAngle}
|
||||
fieldDisplay={fieldDisplay}
|
||||
color={trackColor}
|
||||
dimensions={dimensions}
|
||||
roundedBars={roundedBars}
|
||||
shape={shape}
|
||||
startAngle={startAngle}
|
||||
/>
|
||||
)}
|
||||
{/** Track after value */}
|
||||
<RadialArcPath
|
||||
arcLengthDeg={angleRange - angle}
|
||||
arcLengthDeg={angleRange - endValueAngle - startValueAngle}
|
||||
fieldDisplay={fieldDisplay}
|
||||
color={theme.colors.action.hover}
|
||||
color={trackColor}
|
||||
dimensions={dimensions}
|
||||
roundedBars={roundedBars}
|
||||
shape={shape}
|
||||
startAngle={startAngle + angle}
|
||||
startAngle={startAngle + startValueAngle + endValueAngle}
|
||||
/>
|
||||
{/** The colored bar */}
|
||||
<RadialArcPath
|
||||
arcLengthDeg={angle}
|
||||
arcLengthDeg={endValueAngle}
|
||||
barEndcaps={shape === 'circle' && roundedBars}
|
||||
dimensions={dimensions}
|
||||
endpointMarker={roundedBars ? endpointMarker : undefined}
|
||||
@@ -56,7 +77,7 @@ export function RadialBar({
|
||||
glowFilter={glowFilter}
|
||||
roundedBars={roundedBars}
|
||||
shape={shape}
|
||||
startAngle={startAngle}
|
||||
startAngle={startAngle + startValueAngle}
|
||||
{...colorProps}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
getFieldConfigMinMax,
|
||||
getFieldDisplayProcessor,
|
||||
getOptimalSegmentCount,
|
||||
getValuePercentageForValue,
|
||||
} from './utils';
|
||||
|
||||
export interface RadialBarSegmentedProps {
|
||||
@@ -18,6 +19,8 @@ export interface RadialBarSegmentedProps {
|
||||
dimensions: RadialGaugeDimensions;
|
||||
angleRange: number;
|
||||
startAngle: number;
|
||||
startValueAngle: number;
|
||||
endValueAngle: number;
|
||||
glowFilter?: string;
|
||||
segmentCount: number;
|
||||
segmentSpacing: number;
|
||||
@@ -36,22 +39,24 @@ export const RadialBarSegmented = memo(
|
||||
segmentCount,
|
||||
segmentSpacing,
|
||||
shape,
|
||||
startValueAngle,
|
||||
endValueAngle,
|
||||
}: RadialBarSegmentedProps) => {
|
||||
const theme = useTheme2();
|
||||
const segments: React.ReactNode[] = [];
|
||||
const segmentCountAdjusted = getOptimalSegmentCount(dimensions, segmentSpacing, segmentCount, angleRange);
|
||||
const [min, max] = getFieldConfigMinMax(fieldDisplay);
|
||||
const value = fieldDisplay.display.numeric;
|
||||
const angleBetweenSegments = getAngleBetweenSegments(segmentSpacing, segmentCount, angleRange);
|
||||
const segmentArcLengthDeg = angleRange / segmentCountAdjusted - angleBetweenSegments;
|
||||
const displayProcessor = getFieldDisplayProcessor(fieldDisplay);
|
||||
|
||||
for (let i = 0; i < segmentCountAdjusted; i++) {
|
||||
const angleValue = min + ((max - min) / segmentCountAdjusted) * i;
|
||||
const segmentAngle = startAngle + (angleRange / segmentCountAdjusted) * i + 0.01;
|
||||
const segmentColor =
|
||||
angleValue >= value ? theme.colors.border.medium : (displayProcessor(angleValue).color ?? FALLBACK_COLOR);
|
||||
const colorProps = angleValue < value && gradient ? { gradient } : { color: segmentColor };
|
||||
const value = min + ((max - min) / segmentCountAdjusted) * i;
|
||||
const segmentAngle = getValuePercentageForValue(fieldDisplay, value) * angleRange;
|
||||
const isTrack = segmentAngle < startValueAngle || segmentAngle >= startValueAngle + endValueAngle;
|
||||
const segmentStartAngle = startAngle + (angleRange / segmentCountAdjusted) * i + 0.01;
|
||||
const segmentColor = isTrack ? theme.colors.border.medium : (displayProcessor(value).color ?? FALLBACK_COLOR);
|
||||
const colorProps = !isTrack && gradient ? { gradient } : { color: segmentColor };
|
||||
|
||||
segments.push(
|
||||
<RadialArcPath
|
||||
@@ -61,7 +66,7 @@ export const RadialBarSegmented = memo(
|
||||
fieldDisplay={fieldDisplay}
|
||||
glowFilter={glowFilter}
|
||||
shape={shape}
|
||||
startAngle={segmentAngle}
|
||||
startAngle={segmentStartAngle}
|
||||
{...colorProps}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -50,6 +50,7 @@ const meta: Meta<StoryProps> = {
|
||||
thresholdsBar: false,
|
||||
colorScheme: FieldColorModeId.Thresholds,
|
||||
decimals: 0,
|
||||
neutral: undefined,
|
||||
},
|
||||
argTypes: {
|
||||
barWidthFactor: { control: { type: 'range', min: 0.1, max: 1, step: 0.01 } },
|
||||
@@ -75,6 +76,7 @@ const meta: Meta<StoryProps> = {
|
||||
],
|
||||
},
|
||||
decimals: { control: { type: 'range', min: 0, max: 7 } },
|
||||
neutral: { control: { type: 'number' } },
|
||||
},
|
||||
};
|
||||
|
||||
@@ -270,6 +272,23 @@ export const Examples: StoryFn<StoryProps> = (args) => {
|
||||
barWidthFactor={0.7}
|
||||
/>
|
||||
</Stack>
|
||||
<div>
|
||||
Neutral <em>(range -50 to 50, neutral = 0)</em>
|
||||
</div>
|
||||
<Stack direction={'row'} gap={3}>
|
||||
<RadialGaugeExample
|
||||
min={-50}
|
||||
max={50}
|
||||
value={-20}
|
||||
colorScheme={FieldColorModeId.Thresholds}
|
||||
gradient
|
||||
shape="gauge"
|
||||
glowCenter={true}
|
||||
roundedBars={false}
|
||||
barWidthFactor={0.7}
|
||||
neutral={0}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -330,6 +349,7 @@ interface ExampleProps {
|
||||
endpointMarker?: RadialGaugeProps['endpointMarker'];
|
||||
decimals?: number;
|
||||
showScaleLabels?: boolean;
|
||||
neutral?: number;
|
||||
}
|
||||
|
||||
export function RadialGaugeExample({
|
||||
@@ -357,6 +377,7 @@ export function RadialGaugeExample({
|
||||
endpointMarker = 'glow',
|
||||
decimals = 0,
|
||||
showScaleLabels,
|
||||
neutral,
|
||||
}: ExampleProps) {
|
||||
const theme = useTheme2();
|
||||
|
||||
@@ -442,6 +463,7 @@ export function RadialGaugeExample({
|
||||
thresholdsBar={thresholdsBar}
|
||||
showScaleLabels={showScaleLabels}
|
||||
endpointMarker={endpointMarker}
|
||||
neutral={neutral}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ describe('RadialGauge', () => {
|
||||
{ description: 'with endpoint marker point', props: { roundedBars: true, endpointMarker: 'point' } },
|
||||
{ description: 'with thresholds bar', props: { thresholdsBar: true } },
|
||||
{ description: 'with sparkline', props: { sparkline: true } },
|
||||
{ description: 'with neutral value', props: { neutral: 50 } },
|
||||
] satisfies Array<{ description: string; props?: ComponentProps<typeof RadialGaugeExample> }>)(
|
||||
'should render $description without throwing',
|
||||
({ props }) => {
|
||||
|
||||
@@ -67,6 +67,11 @@ export interface RadialGaugeProps {
|
||||
/** Specify which text should be visible */
|
||||
textMode?: RadialTextMode;
|
||||
showScaleLabels?: boolean;
|
||||
/**
|
||||
* If set, the gauge will use the neutral value instead of the min value as the starting point for a gauge.
|
||||
* this is most useful when you need to show positive and negative values on a gauge.
|
||||
*/
|
||||
neutral?: number;
|
||||
/** For data links */
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
timeRange?: TimeRange;
|
||||
@@ -91,6 +96,7 @@ export function RadialGauge(props: RadialGaugeProps) {
|
||||
roundedBars = true,
|
||||
thresholdsBar = false,
|
||||
showScaleLabels = false,
|
||||
neutral,
|
||||
endpointMarker,
|
||||
onClick,
|
||||
values,
|
||||
@@ -113,7 +119,13 @@ export function RadialGauge(props: RadialGaugeProps) {
|
||||
|
||||
for (let barIndex = 0; barIndex < values.length; barIndex++) {
|
||||
const displayValue = values[barIndex];
|
||||
const { angle, angleRange } = getValueAngleForValue(displayValue, startAngle, endAngle);
|
||||
const { startValueAngle, endValueAngle, angleRange } = getValueAngleForValue(
|
||||
displayValue,
|
||||
startAngle,
|
||||
endAngle,
|
||||
neutral
|
||||
);
|
||||
|
||||
const gradientStops = gradient ? buildGradientColors(theme, displayValue) : undefined;
|
||||
const color = displayValue.display.color ?? FALLBACK_COLOR;
|
||||
const dimensions = calculateDimensions(
|
||||
@@ -140,7 +152,7 @@ export function RadialGauge(props: RadialGaugeProps) {
|
||||
<SpotlightGradient
|
||||
key={spotlightGradientId}
|
||||
id={spotlightGradientId}
|
||||
angle={angle + startAngle}
|
||||
angle={endValueAngle + startAngle}
|
||||
dimensions={dimensions}
|
||||
roundedBars={roundedBars}
|
||||
theme={theme}
|
||||
@@ -156,6 +168,8 @@ export function RadialGauge(props: RadialGaugeProps) {
|
||||
fieldDisplay={displayValue}
|
||||
angleRange={angleRange}
|
||||
startAngle={startAngle}
|
||||
startValueAngle={startValueAngle}
|
||||
endValueAngle={endValueAngle}
|
||||
glowFilter={glowFilterRef}
|
||||
segmentCount={segmentCount}
|
||||
segmentSpacing={segmentSpacing}
|
||||
@@ -168,9 +182,10 @@ export function RadialGauge(props: RadialGaugeProps) {
|
||||
<RadialBar
|
||||
key={`radial-bar-${barIndex}-${gaugeId}`}
|
||||
dimensions={dimensions}
|
||||
angle={angle}
|
||||
angleRange={angleRange}
|
||||
startAngle={startAngle}
|
||||
startValueAngle={startValueAngle}
|
||||
endValueAngle={endValueAngle}
|
||||
roundedBars={roundedBars}
|
||||
glowFilter={glowFilterRef}
|
||||
endpointMarkerGlowFilter={spotlightGradientRef}
|
||||
|
||||
@@ -233,7 +233,8 @@ describe('RadialGauge utils', () => {
|
||||
const fieldDisplay = createFieldDisplay(50, 0, 100);
|
||||
const result = getValueAngleForValue(fieldDisplay, 0, 360);
|
||||
|
||||
expect(result.angle).toBe(180); // 50% of 360°
|
||||
expect(result.startValueAngle).toBe(0);
|
||||
expect(result.endValueAngle).toBe(180); // 50% of 360°
|
||||
expect(result.angleRange).toBe(360);
|
||||
});
|
||||
|
||||
@@ -241,7 +242,8 @@ describe('RadialGauge utils', () => {
|
||||
const fieldDisplay = createFieldDisplay(50, 0, 100);
|
||||
const result = getValueAngleForValue(fieldDisplay, 90, 270);
|
||||
|
||||
expect(result.angle).toBe(135); // 50% of 360° range
|
||||
expect(result.startValueAngle).toBe(0);
|
||||
expect(result.endValueAngle).toBe(135); // 50% of 360° range
|
||||
expect(result.angleRange).toBe(270);
|
||||
});
|
||||
|
||||
@@ -249,28 +251,28 @@ describe('RadialGauge utils', () => {
|
||||
const fieldDisplay = createFieldDisplay(150, 0, 100); // value exceeds max
|
||||
const result = getValueAngleForValue(fieldDisplay, 0, 360);
|
||||
|
||||
expect(result.angle).toBe(360); // clamped to angleRange
|
||||
expect(result.endValueAngle).toBe(360); // clamped to angleRange
|
||||
});
|
||||
|
||||
it('should handle minimum values', () => {
|
||||
const fieldDisplay = createFieldDisplay(0, 0, 100);
|
||||
const result = getValueAngleForValue(fieldDisplay, 0, 360);
|
||||
|
||||
expect(result.angle).toBe(0);
|
||||
expect(result.endValueAngle).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle maximum values', () => {
|
||||
const fieldDisplay = createFieldDisplay(100, 0, 100);
|
||||
const result = getValueAngleForValue(fieldDisplay, 0, 360);
|
||||
|
||||
expect(result.angle).toBe(360);
|
||||
expect(result.endValueAngle).toBe(360);
|
||||
});
|
||||
|
||||
it('should handle values lower than min', () => {
|
||||
const fieldDisplay = createFieldDisplay(-50, 0, 100);
|
||||
const result = getValueAngleForValue(fieldDisplay, 240, 120);
|
||||
|
||||
expect(result.angle).toBe(0);
|
||||
expect(result.endValueAngle).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle values higher than max', () => {
|
||||
@@ -278,7 +280,39 @@ describe('RadialGauge utils', () => {
|
||||
const result = getValueAngleForValue(fieldDisplay, 240, 120);
|
||||
|
||||
// Expect the angle to be clamped to the maximum range
|
||||
expect(result.angle).toBe(240);
|
||||
expect(result.endValueAngle).toBe(240);
|
||||
});
|
||||
|
||||
it('should handle neutral values', () => {
|
||||
const fieldDisplay = createFieldDisplay(75, 0, 100);
|
||||
const result = getValueAngleForValue(fieldDisplay, 0, 360, 50);
|
||||
|
||||
expect(result.startValueAngle).toBe(180); // Neutral at 50% of 360°
|
||||
expect(result.endValueAngle).toBe(90); // 75% - 50% = 25% of 360°
|
||||
});
|
||||
|
||||
it('should handle neutral values equal to value', () => {
|
||||
const fieldDisplay = createFieldDisplay(50, 0, 100);
|
||||
const result = getValueAngleForValue(fieldDisplay, 0, 360, 50);
|
||||
|
||||
expect(result.startValueAngle).toBe(180); // Neutral at 50% of 360°
|
||||
expect(result.endValueAngle).toBe(0); // No difference
|
||||
});
|
||||
|
||||
it('should handle neutral values greater than value', () => {
|
||||
const fieldDisplay = createFieldDisplay(25, 0, 100);
|
||||
const result = getValueAngleForValue(fieldDisplay, 0, 360, 150);
|
||||
|
||||
expect(result.startValueAngle).toBe(90);
|
||||
expect(result.endValueAngle).toBe(270); // remaining angle to 360
|
||||
});
|
||||
|
||||
it('should handle neutral values below range', () => {
|
||||
const fieldDisplay = createFieldDisplay(25, 0, 100);
|
||||
const result = getValueAngleForValue(fieldDisplay, 0, 360, -50);
|
||||
|
||||
expect(result.startValueAngle).toBe(0);
|
||||
expect(result.endValueAngle).toBe(90);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -28,19 +28,32 @@ export function getValueAngleForValue(
|
||||
fieldDisplay: FieldDisplay,
|
||||
startAngle: number,
|
||||
endAngle: number,
|
||||
value = fieldDisplay.display.numeric
|
||||
neutral?: number
|
||||
) {
|
||||
const angleRange = (360 % (startAngle === 0 ? 1 : startAngle)) + endAngle;
|
||||
const value = fieldDisplay.display.numeric;
|
||||
|
||||
let angle = getValuePercentageForValue(fieldDisplay, value) * angleRange;
|
||||
const valueAngle = getValuePercentageForValue(fieldDisplay, value) * angleRange;
|
||||
|
||||
if (angle > angleRange) {
|
||||
angle = angleRange;
|
||||
} else if (angle < 0) {
|
||||
angle = 0;
|
||||
let endValueAngle = valueAngle;
|
||||
|
||||
let startValueAngle = 0;
|
||||
if (typeof neutral === 'number') {
|
||||
const [min, max] = getFieldConfigMinMax(fieldDisplay);
|
||||
const clampedNeutral = Math.min(Math.max(min, neutral), max);
|
||||
const neutralAngle = getValuePercentageForValue(fieldDisplay, clampedNeutral) * angleRange;
|
||||
if (neutralAngle <= valueAngle) {
|
||||
startValueAngle = neutralAngle;
|
||||
endValueAngle = valueAngle - neutralAngle;
|
||||
} else {
|
||||
startValueAngle = valueAngle;
|
||||
endValueAngle = neutralAngle - valueAngle;
|
||||
}
|
||||
}
|
||||
|
||||
return { angleRange, angle };
|
||||
const clampedEndValueAngle = Math.min(Math.max(endValueAngle, 0), angleRange);
|
||||
|
||||
return { angleRange, startValueAngle, endValueAngle: clampedEndValueAngle };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,26 +32,27 @@ export function RadialBarPanel({
|
||||
|
||||
return (
|
||||
<RadialGauge
|
||||
values={[value]}
|
||||
width={width}
|
||||
height={height}
|
||||
alignmentFactors={valueProps.alignmentFactors}
|
||||
barWidthFactor={options.barWidthFactor}
|
||||
gradient={options.effects?.gradient}
|
||||
endpointMarker={options.endpointMarker !== 'none' ? options.endpointMarker : undefined}
|
||||
glowBar={options.effects?.barGlow}
|
||||
glowCenter={options.effects?.centerGlow}
|
||||
gradient={options.effects?.gradient}
|
||||
height={height}
|
||||
nameManualFontSize={options.text?.titleSize}
|
||||
neutral={options.neutral}
|
||||
onClick={menuProps.openMenu}
|
||||
roundedBars={options.barShape === 'rounded'}
|
||||
vizCount={valueProps.count}
|
||||
shape={options.shape}
|
||||
segmentCount={options.segmentCount}
|
||||
segmentSpacing={options.segmentSpacing}
|
||||
thresholdsBar={options.showThresholdMarkers}
|
||||
shape={options.shape}
|
||||
showScaleLabels={options.showThresholdLabels}
|
||||
alignmentFactors={valueProps.alignmentFactors}
|
||||
valueManualFontSize={options.text?.valueSize}
|
||||
nameManualFontSize={options.text?.titleSize}
|
||||
endpointMarker={options.endpointMarker !== 'none' ? options.endpointMarker : undefined}
|
||||
onClick={menuProps.openMenu}
|
||||
textMode={options.textMode}
|
||||
thresholdsBar={options.showThresholdMarkers}
|
||||
valueManualFontSize={options.text?.valueSize}
|
||||
values={[value]}
|
||||
vizCount={valueProps.count}
|
||||
width={width}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -161,6 +161,17 @@ export const plugin = new PanelPlugin<Options>(RadialBarPanel)
|
||||
defaultValue: defaultOptions.textMode,
|
||||
});
|
||||
|
||||
builder.addNumberInput({
|
||||
path: 'neutral',
|
||||
name: t('radialbar.config.neutral.title', 'Neutral value'),
|
||||
description: t('radialbar.config.neutral.description', 'Leave empty to use Min as neutral point'),
|
||||
category,
|
||||
settings: {
|
||||
placeholder: t('radialbar.config.neutral.placeholder', 'none'),
|
||||
step: 1,
|
||||
},
|
||||
});
|
||||
|
||||
builder.addBooleanSwitch({
|
||||
path: 'sparkline',
|
||||
name: t('radialbar.config.sparkline', 'Show sparkline'),
|
||||
|
||||
@@ -42,7 +42,8 @@ composableKinds: PanelCfg: {
|
||||
barWidthFactor: number | *0.5
|
||||
barShape: "flat" | "rounded" | *"flat"
|
||||
endpointMarker?: "point" | "glow" | "none" | *"point"
|
||||
textMode?: "auto" | "value_and_name" | "value" | "name" | "none" | *"auto"
|
||||
textMode?: "auto" | "value_and_name" | "value" | "name" | "none" | *"auto"
|
||||
neutral?: number
|
||||
effects: GaugePanelEffects | *{}
|
||||
sizing: common.BarGaugeSizing & (*"auto" | _)
|
||||
minVizWidth: uint32 | *75
|
||||
|
||||
@@ -29,6 +29,7 @@ export interface Options extends common.SingleStatBaseOptions {
|
||||
endpointMarker?: ('point' | 'glow' | 'none');
|
||||
minVizHeight: number;
|
||||
minVizWidth: number;
|
||||
neutral?: number;
|
||||
segmentCount: number;
|
||||
segmentSpacing: number;
|
||||
shape: ('circle' | 'gauge');
|
||||
|
||||
@@ -12572,6 +12572,11 @@
|
||||
"endpoint-marker-glow": "Glow",
|
||||
"endpoint-marker-none": "None",
|
||||
"endpoint-marker-point": "Point",
|
||||
"neutral": {
|
||||
"description": "Leave empty to use Min as neutral point",
|
||||
"placeholder": "none",
|
||||
"title": "Neutral value"
|
||||
},
|
||||
"segment-count": "Segments",
|
||||
"segment-spacing": "Segment spacing",
|
||||
"shape": "Style",
|
||||
|
||||
Reference in New Issue
Block a user