Skip to content

Commit

Permalink
Bug: limits and markers not working (#86)
Browse files Browse the repository at this point in the history
* placeholder for presets

* placeholder for presets

* change default start marker

* add marker migrations

* fixes markers

* remove todo

* fix comment

* fix line length

* fix min angle

* update config
  • Loading branch information
briangann authored Sep 25, 2023
1 parent d3f63d9 commit d0071e3
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 61 deletions.
8 changes: 4 additions & 4 deletions .config/webpack/webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,14 +197,14 @@ const config = async (env): Promise<Configuration> => {
modules: [path.resolve(process.cwd(), 'src'), 'node_modules'],
unsafeCache: true,
},
};
}

if (isWSL()) {
if(isWSL()) {
baseConfig.watchOptions = {
poll: 3000,
ignored: /node_modules/,
};
}
}}


return baseConfig;

Expand Down
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

All changes noted here.

## v2.0.0 - 2023-09-21
## v2.0.0 - 2023-09-23

- Rewritten from Angular to React
- NEW: Needle Width can now be set
- NEW: Needle Width can now be specified
- NEW: Thresholds now use the standard Grafana threshold mechanics
- NEW: Needle can optionally exceed the tick mark (min and max) to show values
that are outside of limits
- NEW: Preset gauge types can be selected and then modified
- NEW: Needle Center can use all marker types, with arrow-inverse added to options

## v0.0.9 - 2021-04-21

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ See the [CONTRIBUTING.md](CONTRIBUTING.md) doc for more information.

## Acknowledgements

This panel is based on the "SingleStat" panel by Grafana, along with large portions of these excellent D3 examples:
This panel is based on the "SingleStat" panel by Grafana, along with large
portions of these excellent D3 examples:

* <https://oliverbinns.com/articles/D3js-gauge/>
* <http://bl.ocks.org/tomerd/1499279>
Expand Down
18 changes: 13 additions & 5 deletions src/components/Gauge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useStyles2, useTheme2 } from '@grafana/ui';
import { css } from '@emotion/css';
import { getActiveThreshold, GrafanaTheme2, Threshold, sortThresholds } from '@grafana/data';

import { ExpandedThresholdBand, GaugeOptions, MarkerEndShapes, MarkerStartShapes } from './types';
import { ExpandedThresholdBand, GaugeOptions, Markers } from './types';
import { scaleLinear, line, interpolateString, select } from 'd3';
import { easeQuadIn } from 'd3-ease';

Expand Down Expand Up @@ -52,6 +52,12 @@ export const Gauge: React.FC<GaugeOptions> = (options) => {
const originY = options.gaugeRadius;
let needleElement: JSX.Element | null = null;

/*
useEffect(() => {
console.log(`presetIndex set to ${options.presetIndex}`);
options.innerColor = GaugePresetOptions[options.presetIndex].faceColor;
}, [options]);
*/

useEffect(() => {

Expand Down Expand Up @@ -209,15 +215,17 @@ export const Gauge: React.FC<GaugeOptions> = (options) => {

const createNeedle = () => {
const pathNeedle = needleCalc(options.zeroNeedleAngle, originX, originY, needlePathStart, needlePathLength);
const markerEndShape = Markers.find(e => e.name === options.markerEndShape) || Markers[0];
const markerStartShape = Markers.find(e => e.name === options.markerStartShape) || Markers[1];
return (
<g id='needle'>
{pathNeedle.length > 0 && (
<>
<path
ref={needleRef}
d={pathNeedle}
markerEnd={options.markerEndEnabled ? 'url(#marker_' + MarkerEndShapes[0].name + ')' : undefined}
markerStart={options.markerStartEnabled ? 'url(#marker_' + MarkerStartShapes[0].name + ')' : undefined}
markerEnd={options.markerEndEnabled ? 'url(#marker_' + markerEndShape.name + ')' : undefined}
markerStart={options.markerStartEnabled ? 'url(#marker_' + markerStartShape.name + ')' : undefined}
markerHeight={6}
markerWidth={6}
strokeLinecap='round'
Expand Down Expand Up @@ -493,10 +501,10 @@ export const Gauge: React.FC<GaugeOptions> = (options) => {
needleAngleOld = 0;
}
if (needleAngleNew + options.zeroNeedleAngle > options.maxTickAngle) {
needleAngleNew = getNeedleAngleMaximum(options.allowNeedleCrossLimits, needleAngleNew, options.zeroTickAngle, options.maxTickAngle, options.needleCrossLimitDegrees);
needleAngleNew = getNeedleAngleMaximum(options.allowNeedleCrossLimits, needleAngleNew, options.zeroTickAngle, options.zeroNeedleAngle, options.maxTickAngle, options.needleCrossLimitDegrees);
}
if (needleAngleNew + options.zeroNeedleAngle < options.zeroTickAngle) {
needleAngleNew = getNeedleAngleMinimum(options.allowNeedleCrossLimits, needleAngleNew, options.zeroTickAngle, options.needleCrossLimitDegrees);
needleAngleNew = getNeedleAngleMinimum(options.allowNeedleCrossLimits, needleAngleNew, options.zeroTickAngle, options.zeroNeedleAngle, options.needleCrossLimitDegrees);
}
const needleCentre = originX + ',' + originY;
return interpolateString(
Expand Down
30 changes: 15 additions & 15 deletions src/components/needle_utils.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,49 @@ describe('Needle Utils', () => {
const crossLimitDegree = 5;
describe('Check Min Needle Angle with cross limits disabled', () => {
it('minimum angle should be at min', () => {
const atZero = getNeedleAngleMinimum(false, 0, 90, 5);
const atZero = getNeedleAngleMinimum(false, 0, 90, 90, 5);
expect(atZero).toEqual(0);
const aboveZero = getNeedleAngleMinimum(false, 30, 90, 5);
const aboveZero = getNeedleAngleMinimum(false, 30, 90, 90, 5);
expect(aboveZero).toEqual(30);
const belowZero = getNeedleAngleMinimum(false, -10, 90, 5);
const belowZero = getNeedleAngleMinimum(false, -10, 90, 90, 5);
expect(belowZero).toEqual(90);
// this will return a value beyond the max (270), and will be caught by code logic
const aboveMax = getNeedleAngleMinimum(false, 300, 90, 5);
const aboveMax = getNeedleAngleMinimum(false, 300, 90, 90, 5);
expect(aboveMax).toEqual(300);
});
});
describe('Check Min Needle Angle with cross limits enabled', () => {
it('minimum angle should be bound by min with limit cross of 5 degrees', () => {
const belowZero = getNeedleAngleMinimum(true, -10, 90, crossLimitDegree);
const belowZero = getNeedleAngleMinimum(true, -10, 90, 90, crossLimitDegree);
expect(belowZero).toEqual(-5);
const atZero = getNeedleAngleMinimum(true, 0, 90, crossLimitDegree);
const atZero = getNeedleAngleMinimum(true, 0, 90, 90, crossLimitDegree);
expect(atZero).toEqual(0);
const aboveZero = getNeedleAngleMinimum(true, 30, 90, crossLimitDegree);
const aboveZero = getNeedleAngleMinimum(true, 30, 90, 90, crossLimitDegree);
expect(aboveZero).toEqual(30);
const aboveMax = getNeedleAngleMinimum(true, 300, 90, crossLimitDegree);
const aboveMax = getNeedleAngleMinimum(true, 300, 90, 90, crossLimitDegree);
expect(aboveMax).toEqual(300);
});
});

describe('Check Max Needle Angle with cross limits disabled', () => {
it('max angle should be bound by maxTickAngle', () => {
const atMax = getNeedleAngleMaximum(false, 270, 90, 270, crossLimitDegree);
const atMax = getNeedleAngleMaximum(false, 270, 90, 90, 270, crossLimitDegree);
expect(atMax).toEqual(180);
const belowMax = getNeedleAngleMaximum(false, 30, 90, 270, crossLimitDegree);
const belowMax = getNeedleAngleMaximum(false, 30, 90, 90, 270, crossLimitDegree);
expect(belowMax).toEqual(-60);
const aboveMax = getNeedleAngleMaximum(false, 300, 90, 270, crossLimitDegree);
const aboveMax = getNeedleAngleMaximum(false, 300, 90, 90, 270, crossLimitDegree);
expect(aboveMax).toEqual(180);
});
});
describe('Check Max Needle Angle with cross limits enabled', () => {
it('max angle should be bound by max with limit cross of 5 degrees', () => {
const atMax = getNeedleAngleMaximum(true, 270, 90, 270, crossLimitDegree);
const atMax = getNeedleAngleMaximum(true, 270, 90, 90, 270, crossLimitDegree);
expect(atMax).toEqual(185);
const belowMax = getNeedleAngleMaximum(true, 30, 90, 270, crossLimitDegree);
const belowMax = getNeedleAngleMaximum(true, 30, 90, 90, 270, crossLimitDegree);
expect(belowMax).toEqual(-60);
const aboveMax = getNeedleAngleMaximum(true, 275, 90, 270, crossLimitDegree);
const aboveMax = getNeedleAngleMaximum(true, 275, 90, 90, 270, crossLimitDegree);
expect(aboveMax).toEqual(185);
const aboveMax2 = getNeedleAngleMaximum(true, 320, 90, 270, crossLimitDegree);
const aboveMax2 = getNeedleAngleMaximum(true, 320, 90, 90, 270, crossLimitDegree);
expect(aboveMax2).toEqual(185);
});
});
Expand Down
29 changes: 13 additions & 16 deletions src/components/needle_utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,29 @@ import React from 'react';
* @param allowNeedleCrossLimits boolean
* @param needleAngle angle to test
* @param zeroTickAngle angle where the tick label starts
* @param zeroNeedleAngle angle where the needle starts
* @param crossLimitDegree how far to cross limits
* @returns angle to be used (relative to the zeroNeedleAnge)
* @returns angle to be used (absolute angle, no longer relative)
*/
export const getNeedleAngleMinimum =
(allowNeedleCrossLimits: boolean, needleAngle: number, zeroTickAngle: number, crossLimitDegree: number) => {
// the angle is relative to the zeroNeedleAngle
// check if the needleAngle is below the zeroNeedleAngle
if (needleAngle + zeroTickAngle < zeroTickAngle) {
(allowNeedleCrossLimits: boolean, needleAngle: number, zeroTickAngle: number, zeroNeedleAngle: number, crossLimitDegree: number) => {
// check if the needleAngle is below the zeroTickAngle
if (needleAngle + zeroNeedleAngle < zeroTickAngle) {
// check if burying the needle is enabled
if (allowNeedleCrossLimits) {
// make sure it is not below zero when accounting for the zeroNeedleAngle
if (zeroTickAngle >= crossLimitDegree) {
// allow it to be set to zeroTickAngle minus 5 degrees, without going below zero
if (needleAngle < zeroTickAngle) {
return (-crossLimitDegree);
} else {
return (zeroTickAngle);
return (zeroNeedleAngle);
}
} else {
return (zeroTickAngle);
return (zeroNeedleAngle);
}
}
return needleAngle;
};

export const getNeedleAngleMaximum = (allowNeedleCrossLimits: boolean, needleAngle: number, zeroTickAngle: number, maxTickAngle: number, crossLimitDegree: number) => {
export const getNeedleAngleMaximum = (allowNeedleCrossLimits: boolean, needleAngle: number, zeroTickAngle: number, zeroNeedleAngle: number, maxTickAngle: number, crossLimitDegree: number) => {
// angle passed in is relative to zeroTickAngle
if (needleAngle + zeroTickAngle > maxTickAngle) {
if (allowNeedleCrossLimits) {
Expand All @@ -50,16 +48,15 @@ export const getNeedleAngleMaximum = (allowNeedleCrossLimits: boolean, needleAng
// make sure it is not above 360 minus cross limit
if (maxTickAngle < (360 - crossLimitDegree)) {
// allow it to be set to maxTickAngle plus 5 degrees, without going below zero
return(maxTickAngle + crossLimitDegree - zeroTickAngle);
return (maxTickAngle + crossLimitDegree - zeroNeedleAngle);
} else {
// console.log(`needle cannot be buried beyond maxNeedleAngle ${testMaxAngle}`);
return(maxTickAngle - zeroTickAngle);
return (maxTickAngle - zeroNeedleAngle);
}
}
} else {
return (maxTickAngle - zeroTickAngle);
return (maxTickAngle - zeroNeedleAngle);
}
}
// remove the zeroTickAngle to get the relative value again
return (needleAngle - zeroTickAngle);
return (needleAngle - zeroNeedleAngle);
};
47 changes: 35 additions & 12 deletions src/components/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export interface GaugeOptions {
tickLabelFontSize: number;
tickFont: string;

// preset index
// presetIndex: number;
// Needle Options
animateNeedleValueTransition: boolean;
animateNeedleValueTransitionSpeed: number;
Expand Down Expand Up @@ -84,13 +86,7 @@ export interface GaugeOptions {
// tslint:disable-next-line
export interface GaugeModel {}

export const MarkerStartShapes = [
{ id: 0, name: 'circle' },
{ id: 1, name: 'square' },
{ id: 2, name: 'stub' },
];

export const MarkerEndShapes = [{ id: 0, name: 'arrow' }];

export enum FontFamilies {
ARIAL = 'Arial',
Expand Down Expand Up @@ -178,12 +174,6 @@ export const OperatorOptions: SelectableValue[] = [
{ value: 'step', label: 'Step' },
];

export const MarkerOptions: SelectableValue[] = [
{ value: 0, label: 'arrow' },
{ value: 1, label: 'circle' },
{ value: 2, label: 'square' },
{ value: 3, label: 'stub' },
];

export interface MarkerType {
id: number;
Expand All @@ -196,6 +186,27 @@ export const Markers: MarkerType[] = [
{ id: 1, name: 'circle', path: 'M 0, 0 m -5, 0 a 5,5 0 1,0 10,0 a 5,5 0 1,0 -10,0', viewBox: '-6 -6 12 12' },
{ id: 2, name: 'square', path: 'M 0,0 m -5,-5 L 5,-5 L 5,5 L -5,5 Z', viewBox: '-5 -5 10 10' },
{ id: 3, name: 'stub', path: 'M 0,0 m -1,-5 L 1,-5 L 1,5 L -1,5 Z', viewBox: '-1 -5 2 10' },
{ id: 4, name: 'arrow-inverse', path: 'M 0,0 m 5,5 L -5,0 L 5,-5 Z', viewBox: '-5 -5 10 10' },
];

/*
export const MarkerStartShapes = [
{ id: 0, name: 'circle' },
{ id: 1, name: 'square' },
{ id: 2, name: 'stub' },
];
export const MarkerEndShapes = [
{ id: 0, name: 'arrow' }
];
*/

export const MarkerOptions: SelectableValue[] = [
{ value: 'arrow', label: 'arrow' },
{ value: 'circle', label: 'circle' },
{ value: 'square', label: 'square' },
{ value: 'stub', label: 'stub' },
{ value: 'arrow-inverse', label: 'arrow-inverse' },
];

export interface ExpandedThresholdBand {
Expand All @@ -204,3 +215,15 @@ export interface ExpandedThresholdBand {
max: number;
color: string;
}

export interface GaugePresetType {
id: number;
name: string;
faceColor: string;
}

export const GaugePresetOptions: GaugePresetType[] = [
{ id: 0, name: 'Default', faceColor: '#FFFFFF' },
{ id: 1, name: 'Red', faceColor: '#FF0000' },
{ id: 2, name: 'Compass', faceColor: '#00F0FF' },
];
49 changes: 48 additions & 1 deletion src/migrations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from './migrations';

describe('D3Gauge -> D3GaugeV2 migrations', () => {
it('only migrates old d3gauge', () => {
it('migrates empty d3gauge', () => {
const panel: PanelModel = {
id: 0,
type: 'panel',
Expand All @@ -21,6 +21,53 @@ describe('D3Gauge -> D3GaugeV2 migrations', () => {
expect(options).toEqual({});
});

it('migrates start and end markers from angular d3gauge', () => {
const panel: PanelModel = {
id: 0,
type: 'panel',
options: {
markerStartEnabled: true,
markerStartShape: 'circle',
markerEndEnabled: true,
markerEndShape: 'arrow',
},
fieldConfig: {
defaults: {},
overrides: [],
},
};
const options = PanelMigrationHandler(panel);
expect(options).toEqual({
markerStartEnabled: true,
markerStartShape: 'circle',
markerEndEnabled: true,
markerEndShape: 'arrow',
});
});
it('migrates start and end disabled markers from angular d3gauge', () => {
const panel: PanelModel = {
id: 0,
type: 'panel',
options: {
markerStartEnabled: false,
markerStartShape: 'circle',
markerEndEnabled: true,
markerEndShape: 'arrow',
},
fieldConfig: {
defaults: {},
overrides: [],
},
};
const options = PanelMigrationHandler(panel);
expect(options).toEqual({
markerStartEnabled: false,
markerStartShape: 'circle',
markerEndEnabled: true,
markerEndShape: 'arrow',
});
});

it('checks if roboto is available to runtime', () => {
const versions = new Map<string, boolean>([
['8.4.11', true],
Expand Down
10 changes: 9 additions & 1 deletion src/migrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { FieldConfigSource, PanelModel, ThresholdsConfig, ThresholdsMode, ValueM
import { config } from '@grafana/runtime';
import { satisfies, coerce } from 'semver';

import { FontFamilies, GaugeOptions } from './components/types';
import { FontFamilies, GaugeOptions, GaugePresetOptions, Markers } from './components/types';
import { TickMapItemType } from 'components/TickMaps/types';

interface AngularTickMap {
Expand Down Expand Up @@ -147,8 +147,16 @@ export const PanelMigrationHandler = (panel: PanelModel<GaugeOptions>): Partial<
// @ts-ignore
delete panel.gaugeDivId;
// @ts-ignore
options.markerEndEnabled = panel.markerEndEnabled;
// @ts-ignore
options.markerEndShape = Markers.find(e => e.name === panel.markerEndShape) || Markers[0];
// @ts-ignore
delete panel.markerEndShapes;
// @ts-ignore
options.markerStartEnabled = panel.markerStartEnabled;
// @ts-ignore
options.markerStartShape = Markers.find(e => e.name === panel.markerStartShape) || Markers[1];
// @ts-ignore
delete panel.markerStartShapes;
// @ts-ignore
delete panel.operatorNameOptions;
Expand Down
Loading

0 comments on commit d0071e3

Please sign in to comment.