Fixed battery padding

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