Fixed battery padding

This commit is contained in:
root
2025-10-11 19:08:18 +02:00
parent ff4b00401f
commit a215920949
+44 -38
View File
@@ -1,6 +1,5 @@
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// ChargeVizPanel.tsx // ChargeVizPanel.tsx
// Responsive Grafana battery visual with trimmed SVG padding.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
import React from 'react'; import React from 'react';
@@ -19,11 +18,13 @@ interface ChargeVizOptions {
interface Props extends PanelProps<ChargeVizOptions> {} interface Props extends PanelProps<ChargeVizOptions> {}
// Utility: safely convert any value to a finite number.
const toFinite = (v: any): number | undefined => { const toFinite = (v: any): number | undefined => {
const n = Number(v); const n = Number(v);
return Number.isFinite(n) ? n : undefined; return Number.isFinite(n) ? n : undefined;
}; };
// Compute least-squares slope (% per minute) for stable trend estimation.
function slopePercentPerMinute(times: number[], values: number[]): number { function slopePercentPerMinute(times: number[], values: number[]): number {
const pts: Array<[number, number]> = []; const pts: Array<[number, number]> = [];
for (let i = 0; i < times.length; i++) { for (let i = 0; i < times.length; i++) {
@@ -60,6 +61,9 @@ export const ChargeVizPanel: React.FC<Props> = ({ data, width, height, options }
const useRegression = options.useRegressionForRate ?? false; const useRegression = options.useRegressionForRate ?? false;
const hideText = options.hideText ?? false; const hideText = options.hideText ?? false;
// -----------------------------------------------------------------------
// Data extraction and basic stats
// -----------------------------------------------------------------------
let currentValue = 0; let currentValue = 0;
let rate = 0; let rate = 0;
let low = 0; let low = 0;
@@ -101,11 +105,17 @@ export const ChargeVizPanel: React.FC<Props> = ({ data, width, height, options }
} }
} }
// Clamp the percentage range.
const fillLevel = Math.max(0, Math.min(100, currentValue)); const fillLevel = Math.max(0, Math.min(100, currentValue));
// Select colour by thresholds.
let fillColour = highColour; let fillColour = highColour;
if (fillLevel < 30) fillColour = lowColour; if (fillLevel < 30) fillColour = lowColour;
else if (fillLevel < 70) fillColour = midColour; else if (fillLevel < 70) fillColour = midColour;
// -----------------------------------------------------------------------
// Gradient setup for charge/discharge effect
// -----------------------------------------------------------------------
const gradientId = 'batteryGradient'; const gradientId = 'batteryGradient';
let gradientDef: React.ReactNode = null; let gradientDef: React.ReactNode = null;
if (gradientFill) { if (gradientFill) {
@@ -129,57 +139,51 @@ export const ChargeVizPanel: React.FC<Props> = ({ data, width, height, options }
); );
} }
// Dynamic scaling (Option B) // The SVG’s internal fill height (for the battery graphic)
const aspectRatio = 0.5;
const panelRatio = height / width;
let batteryWidth: number;
let batteryHeight: number;
if (panelRatio < aspectRatio) {
batteryHeight = height * 0.9;
batteryWidth = batteryHeight / aspectRatio;
} else {
batteryWidth = width * (hideText ? 0.95 : 0.6);
batteryHeight = batteryWidth * aspectRatio;
}
const fillHeightPx = (fillLevel / 100) * 220; const fillHeightPx = (fillLevel / 100) * 220;
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// Render // Render layout
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
return ( return (
<div <div
style={{ style={{
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: hideText ? 'center' : 'flex-start', justifyContent: 'flex-start',
height: '100%', height: '100%',
width: '100%', width: '100%',
fontFamily: 'Inter, sans-serif', fontFamily: 'Inter, sans-serif',
color: theme.colors.text.primary, color: theme.colors.text.primary,
padding: 2, padding: 0,
boxSizing: 'border-box', boxSizing: 'border-box',
gap: hideText ? '0px' : '6px',
}} }}
> >
{/* Battery SVG with reduced horizontal padding */} {/* Independent battery element */}
<div
style={{
flex: '0 0 auto',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
}}
>
<svg <svg
style={{ style={{
height: `${batteryHeight}px`, height: '100%', // fill available panel height
width: `${batteryWidth}px`, width: 'auto', // preserve natural aspect ratio – crucial fix
flex: '0 0 auto',
}} }}
viewBox="0 0 112 240" // narrower overall width viewBox="0 0 110 240"
preserveAspectRatio="xMidYMid meet" preserveAspectRatio="xMidYMid meet"
> >
{gradientDef} {gradientDef}
{/* Outline - starts closer to edges */} {/* Battery outline */}
<rect <rect
x="2" x="2"
y="10" y="10"
width="108" // narrower outline width width="106"
height="220" height="220"
rx="6" rx="6"
ry="6" ry="6"
@@ -190,7 +194,7 @@ export const ChargeVizPanel: React.FC<Props> = ({ data, width, height, options }
{/* Terminal */} {/* Terminal */}
<rect <rect
x="46" x="45"
y="0" y="0"
width="20" width="20"
height="8" height="8"
@@ -199,18 +203,18 @@ export const ChargeVizPanel: React.FC<Props> = ({ data, width, height, options }
fill={theme.colors.text.secondary} fill={theme.colors.text.secondary}
/> />
{/* Fill - adjusted for new inner width */} {/* Fill area */}
<rect <rect
x="4" x="5"
y={230 - fillHeightPx} y={230 - fillHeightPx}
width="104" width="100"
height={fillHeightPx} height={fillHeightPx}
fill={gradientFill ? `url(#${gradientId})` : fillColour} fill={gradientFill ? `url(#${gradientId})` : fillColour}
/> />
{/* Text inside battery */} {/* Percentage text inside the battery */}
<text <text
x="56" x="55"
y="120" y="120"
textAnchor="middle" textAnchor="middle"
dominantBaseline="middle" dominantBaseline="middle"
@@ -221,23 +225,25 @@ export const ChargeVizPanel: React.FC<Props> = ({ data, width, height, options }
{fillLevel.toFixed(0)}% {fillLevel.toFixed(0)}%
</text> </text>
</svg> </svg>
</div>
{/* Optional stats text */} {/* Optional side text – vertically centred next to battery */}
{!hideText && ( {!hideText && (
<div <div
style={{ style={{
flex: '1 1 auto',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
justifyContent: 'center', justifyContent: 'center',
flex: '1 1 auto', alignItems: 'flex-start',
minWidth: 120, marginLeft: '10px',
overflow: 'hidden',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
lineHeight: '1.4em', overflow: 'hidden',
lineHeight: '1.5em',
}} }}
> >
<div style={{ fontSize: '1.05em', fontWeight: 600 }}> <div style={{ fontSize: '1.1em', fontWeight: 600 }}>
Rate: {rate.toFixed(dp)} %/min Rate: {rate.toFixed(dp)} %/min
</div> </div>
<div>Low: {low.toFixed(dp)}%</div> <div>Low: {low.toFixed(dp)}%</div>