Skip to content

Commit

Permalink
Feat: Dynamic Lights (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
jackw authored Oct 11, 2024
1 parent 54e3be1 commit 68acdae
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 33 deletions.
90 changes: 72 additions & 18 deletions provisioning/dashboards/panels.json
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,68 @@
"title": "Traffic Light with value and trend",
"type": "heywesty-trafficlight-panel"
},
{
"datasource": {
"type": "testdata",
"uid": "P814D78962B0F8AC2"
},
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "percentage",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "#EAB839",
"value": 33
},
{
"color": "green",
"value": 66
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 12,
"w": 3,
"x": 10,
"y": 0
},
"id": 22,
"options": {
"horizontal": false,
"minLightWidth": 75,
"reverseColors": false,
"seriesCountSize": "sm",
"showLegend": true,
"showSeriesCount": false,
"showTrend": false,
"showValue": false,
"singleRow": false,
"sortLights": "none",
"style": "dynamic",
"text": "Default value of text input option"
},
"targets": [
{
"datasource": {
"type": "testdata"
},
"refId": "A",
"scenarioId": "random_walk",
"seriesCount": 1
}
],
"title": "Traffic Light (dynamic)",
"type": "heywesty-trafficlight-panel"
},
{
"datasource": {
"type": "testdata",
Expand Down Expand Up @@ -243,8 +305,8 @@
},
"gridPos": {
"h": 12,
"w": 14,
"x": 10,
"w": 11,
"x": 13,
"y": 0
},
"id": 7,
Expand Down Expand Up @@ -353,8 +415,7 @@
"mode": "percentage",
"steps": [
{
"color": "red",
"value": null
"color": "red"
},
{
"color": "#EAB839",
Expand Down Expand Up @@ -416,8 +477,7 @@
"mode": "percentage",
"steps": [
{
"color": "red",
"value": null
"color": "red"
},
{
"color": "#EAB839",
Expand Down Expand Up @@ -479,8 +539,7 @@
"mode": "percentage",
"steps": [
{
"color": "red",
"value": null
"color": "red"
},
{
"color": "#EAB839",
Expand Down Expand Up @@ -542,8 +601,7 @@
"mode": "percentage",
"steps": [
{
"color": "red",
"value": null
"color": "red"
},
{
"color": "#EAB839",
Expand Down Expand Up @@ -620,8 +678,7 @@
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
"color": "red"
},
{
"color": "#EAB839",
Expand Down Expand Up @@ -683,8 +740,7 @@
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
"color": "green"
},
{
"color": "red",
Expand Down Expand Up @@ -731,8 +787,7 @@
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
"color": "green"
},
{
"color": "red",
Expand Down Expand Up @@ -790,8 +845,7 @@
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
"color": "green"
},
{
"color": "red",
Expand Down
3 changes: 2 additions & 1 deletion src/components/TrafficLightDefault.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from 'react';
import { TEST_IDS } from '../constants';
import { Colors } from 'types';

type TrafficLightProps = {
colors: any;
colors: Colors[];
bgColor?: string;
emptyColor?: string;
onClick?: React.MouseEventHandler<SVGSVGElement>;
Expand Down
104 changes: 104 additions & 0 deletions src/components/TrafficLightDynamic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React from 'react';
import { Colors } from 'types';
import { clamp } from 'utils/utils';

type TrafficLightProps = {
colors: Colors[];
bgColor?: string;
emptyColor?: string;
onClick?: React.MouseEventHandler<SVGSVGElement>;
horizontal: boolean;
};

export function TrafficLightDynamic({
colors = [],
bgColor = 'grey',
emptyColor = 'black',
onClick,
horizontal = false,
}: TrafficLightProps) {
const totalAvailableHeight = 406;
const minGap = 4;
const maxGap = 24;
const numberOfLights = colors.length;
const x = 52;
const lightWidth = 168;
const { height, yPosition } = calculateLightPositions(totalAvailableHeight, minGap, maxGap, numberOfLights);

return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox={horizontal ? '0 0 512 272' : '0 0 272 512'}
style={{ height: '100%', width: '100%' }}
onClick={onClick}
>
<g transform={horizontal ? 'rotate(-90 0 0)' : undefined} style={{ transformOrigin: '25% center' }}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M46 13C34.9543 13 26 21.9543 26 33V479.095C26 490.141 34.9543 499.095 46 499.095H226C237.046 499.095 246 490.141 246 479.095V33C246 21.9543 237.046 13 226 13H46Z"
fill={bgColor}
/>

{colors.map((light, index) => (
<g key={index}>
<rect
x={x}
y={yPosition[index]}
width={lightWidth}
height={height}
rx="8"
fill={light.active ? light.color : emptyColor}
style={
light.active
? {
filter: `drop-shadow(0 0 16px ${light.color})`,
}
: {}
}
/>
{light.active && (
<ellipse
cx={x + lightWidth - 16}
cy={yPosition[index] + 16}
rx={8}
ry={8}
fill="white"
fillOpacity="0.35"
/>
)}
</g>
))}
</g>
</svg>
);
}

function calculateLightPositions(
totalHeight: number,
minGap: number,
maxGap: number,
numberOfLights: number,
startOffset = 57
) {
const numGaps = numberOfLights - 1;
const initialGapSize = maxGap / (numberOfLights * 0.5);
const gapSize = clamp(initialGapSize, minGap, maxGap);
const totalGapHeight = gapSize * numGaps;
const remainingHeightForLights = totalHeight - totalGapHeight;
const lightHeight = remainingHeightForLights / numberOfLights;

let currentYPosition = startOffset;
const yPositions: number[] = [];

for (let i = 0; i < numberOfLights; i++) {
yPositions.push(currentYPosition);
currentYPosition += lightHeight + gapSize;
}

return {
height: lightHeight,
yPosition: yPositions,
};
}
6 changes: 4 additions & 2 deletions src/components/TrafficLightPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { GrafanaTheme2, PanelProps } from '@grafana/data';
import React from 'react';
import { DataLinksContextMenu, useTheme2 } from '@grafana/ui';
import { LightsDataResultStatus, LightsDataValues, TrafficLightOptions } from '../types';
import { LightsDataResultStatus, LightsDataValues, TrafficLightOptions, TrafficLightStyles } from '../types';
import { useLightsData } from 'hooks/useLightsData';
import { calculateRowsAndColumns } from 'utils/utils';
import { TEST_IDS } from '../constants';
import { ThresholdsAssistant } from './ThresholdsAssistant';
import { TrafficLightDefault } from './TrafficLightDefault';
import { TrafficLightRounded } from './TrafficLightRounded';
import { TrafficLightSideLights } from './TrafficLightSideLights';
import { TrafficLightDynamic } from './TrafficLightDynamic';

interface TrafficLightPanelProps extends PanelProps<TrafficLightOptions> {}

const TrafficLightsComponentMap = {
default: TrafficLightDefault,
rounded: TrafficLightRounded,
sidelights: TrafficLightSideLights,
dynamic: TrafficLightDynamic,
};

export function TrafficLightPanel({
Expand Down Expand Up @@ -58,7 +60,7 @@ export function TrafficLightPanel({
);
}

if (status === LightsDataResultStatus.incorrectThresholds) {
if (status === LightsDataResultStatus.incorrectThresholds && style !== TrafficLightStyles.Dynamic) {
return (
<div style={styles.centeredContent}>
<ThresholdsAssistant thresholds={invalidThresholds} />
Expand Down
3 changes: 2 additions & 1 deletion src/components/TrafficLightRounded.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from 'react';
import { TEST_IDS } from '../constants';
import { Colors } from 'types';

type TrafficLightProps = {
colors: any;
colors: Colors[];
bgColor?: string;
emptyColor?: string;
onClick?: React.MouseEventHandler<SVGSVGElement>;
Expand Down
3 changes: 2 additions & 1 deletion src/components/TrafficLightSideLights.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from 'react';
import { TEST_IDS } from '../constants';
import { Colors } from 'types';

type TrafficLightProps = {
colors: any;
colors: Colors[];
bgColor?: string;
emptyColor?: string;
onClick?: React.MouseEventHandler<SVGSVGElement>;
Expand Down
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const EMPTY_VALUE: LightsDataValues = {
title: '',
value: '',
numericValue: NaN,
colors: [],
trend: { color: 'transparent', value: 0 },
hasLinks: false,
};
Expand Down
1 change: 1 addition & 0 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export const plugin = new PanelPlugin<TrafficLightOptions>(TrafficLightPanel)
{ value: TrafficLightStyles.Default, label: 'Default' },
{ value: TrafficLightStyles.Rounded, label: 'Rounded' },
{ value: TrafficLightStyles.SideLights, label: 'Side lights' },
{ value: TrafficLightStyles.Dynamic, label: 'Dynamic' },
],
},
})
Expand Down
11 changes: 7 additions & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export enum TrafficLightStyles {
Default = 'default',
Rounded = 'rounded',
SideLights = 'sidelights',
Dynamic = 'dynamic',
}

export enum LightsDataResultStatus {
Expand All @@ -31,16 +32,18 @@ export enum LightsDataResultStatus {
success = 'success',
}

export type Colors = {
color: string;
active: boolean;
};

export type LightsDataValues = {
title?: string;
value: string;
numericValue: number;
prefix?: string;
suffix?: string;
colors?: Array<{
color: string;
active: boolean;
}>;
colors: Colors[];
trend: {
color: string;
value: number;
Expand Down
Loading

0 comments on commit 68acdae

Please sign in to comment.