initial commit
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
export default function Gauge({ value, min = 0, max = 100, label, unit }) {
|
||||
const safeMin = Number.isFinite(Number(min)) ? Number(min) : 0;
|
||||
const safeMax = Number.isFinite(Number(max)) ? Number(max) : 100;
|
||||
const safeValue = Number.isFinite(Number(value)) ? Number(value) : safeMin;
|
||||
const ratio = Math.max(0, Math.min(1, (safeValue - safeMin) / (safeMax - safeMin || 1)));
|
||||
const angle = -110 + ratio * 220;
|
||||
const radians = (angle * Math.PI) / 180;
|
||||
const cx = 110;
|
||||
const cy = 110;
|
||||
const x = cx + Math.cos(radians) * 72;
|
||||
const y = cy + Math.sin(radians) * 72;
|
||||
|
||||
return (
|
||||
<div className="gauge-card">
|
||||
<svg viewBox="0 0 220 150" className="gauge-svg">
|
||||
<path d="M25 110 A85 85 0 0 1 195 110" className="gauge-track" />
|
||||
<path
|
||||
d={`M110 110 L${x.toFixed(1)} ${y.toFixed(1)}`}
|
||||
className="gauge-needle"
|
||||
/>
|
||||
<circle cx="110" cy="110" r="7" className="gauge-center" />
|
||||
</svg>
|
||||
<div className="gauge-value">
|
||||
<span>{Number.isFinite(safeValue) ? safeValue.toFixed(2) : "--"}</span>
|
||||
<small>{unit}</small>
|
||||
</div>
|
||||
<div className="gauge-label">{label}</div>
|
||||
<div className="gauge-range">
|
||||
<span>{safeMin}</span>
|
||||
<span>{safeMax}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
CartesianGrid,
|
||||
Legend,
|
||||
Line,
|
||||
LineChart,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
|
||||
const COLORS = ["#2563eb", "#dc2626", "#1d4ed8", "#b91c1c", "#3b82f6", "#ef4444"];
|
||||
|
||||
export default function TrendChart({ result, title = "Trendansicht" }) {
|
||||
const hostRef = useRef(null);
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const onChange = () => {
|
||||
setIsFullscreen(document.fullscreenElement === hostRef.current);
|
||||
};
|
||||
|
||||
document.addEventListener("fullscreenchange", onChange);
|
||||
return () => document.removeEventListener("fullscreenchange", onChange);
|
||||
}, []);
|
||||
|
||||
if (!result?.rows?.length) {
|
||||
return (
|
||||
<div className="panel chart-panel empty-state">
|
||||
<h3>{title}</h3>
|
||||
<p>Keine Daten im gewaehlten Zeitraum.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const rows = result.rows.map((row) => ({
|
||||
...row,
|
||||
timestampLabel: new Date(row.timestamp).toLocaleString("de-DE"),
|
||||
}));
|
||||
|
||||
const toggleFullscreen = async () => {
|
||||
if (!hostRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (document.fullscreenElement === hostRef.current) {
|
||||
await document.exitFullscreen();
|
||||
return;
|
||||
}
|
||||
|
||||
await hostRef.current.requestFullscreen();
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={hostRef} className="panel chart-panel">
|
||||
<div className="panel-header">
|
||||
<h3>{title}</h3>
|
||||
<button className="ghost-button" type="button" onClick={toggleFullscreen}>
|
||||
{isFullscreen ? "Zurueck" : "Vollbild"}
|
||||
</button>
|
||||
</div>
|
||||
<div className="chart-wrapper">
|
||||
<ResponsiveContainer width="100%" height={isFullscreen ? 520 : 320}>
|
||||
<LineChart data={rows}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="var(--grid)" />
|
||||
<XAxis dataKey="timestampLabel" minTickGap={30} stroke="var(--muted)" />
|
||||
<YAxis stroke="var(--muted)" />
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
background: "var(--surface-strong)",
|
||||
border: "1px solid var(--border-strong)",
|
||||
borderRadius: 10,
|
||||
fontSize: 12,
|
||||
}}
|
||||
/>
|
||||
<Legend />
|
||||
{result.series.map((series, index) => (
|
||||
<Line
|
||||
key={series.key}
|
||||
type="monotone"
|
||||
dataKey={series.key}
|
||||
dot={false}
|
||||
strokeWidth={1.75}
|
||||
stroke={COLORS[index % COLORS.length]}
|
||||
name={series.displayName + " / " + series.alias + (series.unit ? " (" + series.unit + ")" : "")}
|
||||
/>
|
||||
))}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user