Solar: neue Energiefluss-Uebersicht, anpassbar je Haus
Das feste realtime.svg ist ersetzt durch einen Katalog (js/solar/energieflussKatalog.js: jeder Kreis einmal, samt Werten aus MQTT) und einen Renderer (js/solar/energiefluss.js): Ringe mit Segmenten, Fluesse als Boegen mit laufenden Punkten, Fuellstandswelle fuer Batterie und Auto, animierte Symbole, Plaketten, Hochkant-Ansicht auf schmalen Schirmen. Hierarchie und Lage wie bisher: Quellen -> Wechselrichter -> Verbrauch -> OG/EG/UG -> Wallboxen, Heizstab. Einstellungen -> Uebersicht: Kreise ein/aus (Kinder ruecken eine Ebene hoeher), Namen, Lage per Ziehen in der Vorschau, PV-Flaechen. Gespeichert werden nur Abweichungen in homeMesh.energiefluss. Der PV-Lageplan hinter der Sonne liegt unveraendert als assets/img/pvplan.svg und ist ueber den Umschalter Uebersicht/Anlage erreichbar. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -183,13 +183,13 @@ genau die Topics, die auf den Kacheln stehen (`tileTopics()` in `rooms.php`).
|
|||||||
|
|
||||||
| Seite (`?action=`) | Vorlage | Seitenskripte | holt per fetch | hört auf MQTT |
|
| Seite (`?action=`) | Vorlage | Seitenskripte | holt per fetch | hört auf MQTT |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| `solar` (Vorgabe) | `solar.php` | `jahresstatistik.js`, `solarMQTT.js` | `getProdData`, `getConsData`, `getForecastData`, `getSunrise`, `getStats`, `carEG`, `carOG`, `heater`, `watering` | `solarManager/#`, `weatherStation/#`, `wattpilot/#`, `go-eCharger/#`, `Gartenwasser/#` |
|
| `solar` (Vorgabe) | `solar.php` | `jahresstatistik.js`, `energieflussKatalog.js`, `energiefluss.js`, `solarMQTT.js` | `getProdData`, `getConsData`, `getForecastData`, `getSunrise`, `getStats`, `carEG`, `carOG`, `heater`, `watering` | `solarManager/#`, `weatherStation/#`, `wattpilot/#`, `go-eCharger/#`, `Gartenwasser/#` |
|
||||||
| `home` | `home.php` | `autoActionFuncs.js`, `zeitleiste.js`, `homeMQTT.js` | `room.php`, `AutoAction.php` | die Topics der Kacheln, `Raumtemp/#` |
|
| `home` | `home.php` | `autoActionFuncs.js`, `zeitleiste.js`, `homeMQTT.js` | `room.php`, `AutoAction.php` | die Topics der Kacheln, `Raumtemp/#` |
|
||||||
| `heat` | `heat.php` | `heatMQTT.js` | `getHeaterData`, `getWaterData`, `getSunrise` | `solarManager/#`, `weatherStation/#`, Wallbox-Topics |
|
| `heat` | `heat.php` | `heatMQTT.js` | `getHeaterData`, `getWaterData`, `getSunrise` | `solarManager/#`, `weatherStation/#`, Wallbox-Topics |
|
||||||
| `history` | `history.php` | `jahresstatistik.js`, `historyMQTT.js` | `energyHistory` (6 ×), `getStats` | — |
|
| `history` | `history.php` | `jahresstatistik.js`, `historyMQTT.js` | `energyHistory` (6 ×), `getStats` | — |
|
||||||
| `skoda` | `skoda.php` | `skodaMQTT.js` | `skoda.php?was=…`, `skodaCmd.php`, `tile.php` | `solarManager/#` |
|
| `skoda` | `skoda.php` | `skodaMQTT.js` | `skoda.php?was=…`, `skodaCmd.php`, `tile.php` | `solarManager/#` |
|
||||||
| `weather` | `weather.php` | `weatherMQTT.js` | nichts — das Meteogramm ist ein fremdes Dokument im `<iframe>` (`js/meteogram.js`) | `weatherStation/#` |
|
| `weather` | `weather.php` | `weatherMQTT.js` | nichts — das Meteogramm ist ein fremdes Dokument im `<iframe>` (`js/meteogram.js`) | `weatherStation/#` |
|
||||||
| `settings` | `settings.php` | `settings.js`, `grundriss.js` | `settings.php?action=…` | — |
|
| `settings` | `settings.php` | `settings.js`, `grundriss.js`, `energieflussKatalog.js`, `energiefluss.js`, `energieflussEinstellungen.js` | `settings.php?action=…` | — |
|
||||||
| `logs` | `logs.php` | `logs.js` | `logs.php?action=…` | — |
|
| `logs` | `logs.php` | `logs.js` | `logs.php?action=…` | — |
|
||||||
| `einfuehrung` | `einfuehrung.php` | `einfuehrung.js` | nichts — Folien und Bilder stehen in der Seite | — |
|
| `einfuehrung` | `einfuehrung.php` | `einfuehrung.js` | nichts — Folien und Bilder stehen in der Seite | — |
|
||||||
|
|
||||||
@@ -247,6 +247,8 @@ flowchart TB
|
|||||||
SETA --> COSTS & KACH & AUTOM
|
SETA --> COSTS & KACH & AUTOM
|
||||||
STATS --> COSTS
|
STATS --> COSTS
|
||||||
SETA --> GRM["grundriss.php"]
|
SETA --> GRM["grundriss.php"]
|
||||||
|
SETA --> ENF["energiefluss.php"]
|
||||||
|
ENF --> HOMEMESH
|
||||||
KACH --> ROOMS
|
KACH --> ROOMS
|
||||||
GRM --> ROOMS
|
GRM --> ROOMS
|
||||||
LOGA --> LOGD
|
LOGA --> LOGD
|
||||||
@@ -300,12 +302,31 @@ Kilowatt melden — die Wallboxen), runden (`stellen`), Einheit anhängen. Klick
|
|||||||
öffnet das Raum-Modal über `ajax/room.php?room=<Raumnummer>`; Heizsymbol,
|
öffnet das Raum-Modal über `ajax/room.php?room=<Raumnummer>`; Heizsymbol,
|
||||||
Regler und Ist-Werte lesen aus dem Thermostat-Zweig des Raums (`mqttZweig()`).
|
Regler und Ist-Werte lesen aus dem Thermostat-Zweig des Raums (`mqttZweig()`).
|
||||||
|
|
||||||
**`solarMQTT.js`** — die große Anlagenübersicht. `solarSVG` zeichnet den
|
**`solarMQTT.js`** — die Solar-Seite. Startet die Energiefluss-Übersicht
|
||||||
Energiefluss (Breite der Pfeile aus der Leistung), darunter hängen die drei
|
(`energieflussStarten()`), füllt den PV-Lageplan hinter der Sonne
|
||||||
Diagramme Prognose, Verbrauch und Erzeugung über `getData()`. Änderungen am SVG laufen über kleine Setter
|
(`assets/img/pvplan.svg`, Ids `det_pvNP`, `invList`) und die Bewässerungszonen,
|
||||||
(`setText`, `setAttr`, `setFlowWidth`) und werden pro Bildrahmen gesammelt
|
darunter hängen die Diagramme Prognose, Verbrauch und Erzeugung über
|
||||||
(`solarSvgUpdatePending`), damit ein Schwall MQTT-Nachrichten nicht hundertmal
|
`getData()`. Aktualisiert wird höchstens einmal je Sekunde
|
||||||
Layout auslöst.
|
(`solarSvgUpdatePending`); die Setter (`setText`, `setAttr`) schreiben nur,
|
||||||
|
was sich geändert hat.
|
||||||
|
|
||||||
|
**Energiefluss-Übersicht** — drei Dateien mit klarer Arbeitsteilung:
|
||||||
|
|
||||||
|
| Datei | enthält |
|
||||||
|
|---|---|
|
||||||
|
| `energieflussKatalog.js` | **jeden Kreis genau einmal**: Name, Symbol, Farbe, Elternkreis, Lage, Ringskala, Klickziel und `lesen(m, opt)`, das aus den MQTT-Werten Leistung, Zeile darunter, Ringsegmente, Fluss, Plaketten und Symbolzustand macht. Ein neuer Kreis ist ein neuer Eintrag hier — sonst nichts. |
|
||||||
|
| `energiefluss.js` | den Renderer (`new Energiefluss(behälter, opt)`): Symbole (24er-Raster, animierbare Teile), Kreise mit Ring und Füllstandswelle, Flüsse als Bögen mit laufenden Punkten, Plaketten, Hochkant-Ansicht (x/y vertauscht unter 720 px Breite), Bearbeitungsmodus mit Ziehen. Kennt keinen Kreis beim Namen. |
|
||||||
|
| `energieflussEinstellungen.js` | Reiter „Übersicht“ in den Einstellungen: Vorschau mit Beispielwerten, Kreise an/aus, Name, Lage per Ziehen, PV-Flächen (welche Einträge von `P_PVn` zu welcher Fläche gehören). |
|
||||||
|
|
||||||
|
Was ein Haus vom Katalog abweicht, steht in `homeMesh.energiefluss`
|
||||||
|
(`homeMesh_energiefluss.sql`, gelesen/geschrieben in `restricted/energiefluss.php`) —
|
||||||
|
nur echte Abweichungen, eine leere Tabelle ist der Katalog. Ist ein Kreis aus,
|
||||||
|
hängen seine Kinder am nächsten eingeschalteten darüber, und sein Anteil im
|
||||||
|
Ring des Elternkreises fällt in das letzte Segment. Die Animationen stehen
|
||||||
|
still, solange die Karte nicht zu sehen ist, und entfallen bei
|
||||||
|
`prefers-reduced-motion`. Stile: Abschnitt „Energiefluss-Übersicht“ in
|
||||||
|
`css/solar.css`. Bis September 2026 stand hier `assets/img/realtime.svg` mit
|
||||||
|
festen Kreisen.
|
||||||
|
|
||||||
**`skodaMQTT.js`** — Fahrzeugseite. Holt alles über `ajax/skoda.php?was=…`
|
**`skodaMQTT.js`** — Fahrzeugseite. Holt alles über `ajax/skoda.php?was=…`
|
||||||
(live, ladungen, kurve, gesundheit, fahrten, strecke), zeichnet das Fahrzeug
|
(live, ladungen, kurve, gesundheit, fahrten, strecke), zeichnet das Fahrzeug
|
||||||
@@ -490,6 +511,7 @@ Haus hätte man den Quelltext umschreiben müssen.
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `floors` | `code` (fest: URL, SVG-Ids, `automations.floor`), `label`, Reihenfolge, Grundrissbild, Standard-Etage |
|
| `floors` | `code` (fest: URL, SVG-Ids, `automations.floor`), `label`, Reihenfolge, Grundrissbild, Standard-Etage |
|
||||||
| `rooms` | feste Nummer `id`, Etage, Name, `kuerzel` (SVG-Ids), Kachel `x`/`y` (leer = keine Kachel), `thermostat` (MQTT-Zweig), `werte` (JSON, leer = Vorgabe) |
|
| `rooms` | feste Nummer `id`, Etage, Name, `kuerzel` (SVG-Ids), Kachel `x`/`y` (leer = keine Kachel), `thermostat` (MQTT-Zweig), `werte` (JSON, leer = Vorgabe) |
|
||||||
|
| `energiefluss` | Abweichungen der Solar-Übersicht vom Katalog: `aktiv`, `name`, `x`/`y`, `optionen` (JSON) — eigene Datei `homeMesh_energiefluss.sql`, keine Zeile = Katalog |
|
||||||
|
|
||||||
Auf die feste Raumnummer zeigen `actors.room_id` und das Raum-Modal
|
Auf die feste Raumnummer zeigen `actors.room_id` und das Raum-Modal
|
||||||
(`room.php?room=12`). Ein umbenannter Raum behält damit Geräte, Regler und
|
(`room.php?room=12`). Ein umbenannter Raum behält damit Geräte, Regler und
|
||||||
@@ -816,7 +838,9 @@ ausgeliefert.
|
|||||||
|---|---|
|
|---|---|
|
||||||
| Ein Raum oder eine Etage kommt dazu, heißt anders, bekommt ein neues Grundrissbild | Einstellungen → Grundriss (keine Codeänderung) — Kachel, Menü und MQTT-Abo ergeben sich daraus |
|
| Ein Raum oder eine Etage kommt dazu, heißt anders, bekommt ein neues Grundrissbild | Einstellungen → Grundriss (keine Codeänderung) — Kachel, Menü und MQTT-Abo ergeben sich daraus |
|
||||||
| Andere Werte auf einer Kachel | Einstellungen → Home-Kacheln (keine Codeänderung). Vorgabe ohne eigene Werte: `kachelVorgabe()` in `rooms.php` |
|
| Andere Werte auf einer Kachel | Einstellungen → Home-Kacheln (keine Codeänderung). Vorgabe ohne eigene Werte: `kachelVorgabe()` in `rooms.php` |
|
||||||
| Ein anderes Haus einrichten | `homeMesh_DB-layout.sql`, `homeMesh_automations.sql`, `homeMesh_grundriss.sql` in dieser Reihenfolge; dann Einstellungen → Grundriss |
|
| Ein anderes Haus einrichten | `homeMesh_DB-layout.sql`, `homeMesh_automations.sql`, `homeMesh_grundriss.sql`, `homeMesh_energiefluss.sql` in dieser Reihenfolge; dann Einstellungen → Grundriss und → Übersicht |
|
||||||
|
| Kreis der Energiefluss-Übersicht aus, umbenennen, verschieben; PV-Flächen | Einstellungen → Übersicht (keine Codeänderung) |
|
||||||
|
| Neuer Kreis in der Energiefluss-Übersicht (Gerät, Etage, Quelle) | ein Eintrag in `js/solar/energieflussKatalog.js`; neues Symbol dazu in `SYMBOLE` (`energiefluss.js`) |
|
||||||
| Neues Symbol auf einer Kachel | Name eines Bootstrap-Icons ins Feld tippen. Vorschlagsliste: `kachelSymbole()` in `kacheln.php` |
|
| Neues Symbol auf einer Kachel | Name eines Bootstrap-Icons ins Feld tippen. Vorschlagsliste: `kachelSymbole()` in `kacheln.php` |
|
||||||
| Neue Kennzahl in der Jahresstatistik | `KENNZAHLEN` **und** `kennzahlenAus()` in `ajax/getStats.php` — sonst nichts |
|
| Neue Kennzahl in der Jahresstatistik | `KENNZAHLEN` **und** `kennzahlenAus()` in `ajax/getStats.php` — sonst nichts |
|
||||||
| Neues Diagramm auf einer Seite | Karte in der Vorlage, `<canvas>`, dazu ein Endpunkt nach dem Muster von `getProdData.php`, geladen mit `getData()` |
|
| Neues Diagramm auf einer Seite | Karte in der Vorlage, `<canvas>`, dazu ein Endpunkt nach dem Muster von `getProdData.php`, geladen mit `getData()` |
|
||||||
|
|||||||
@@ -24,6 +24,9 @@
|
|||||||
* POST ?action=raum-loeschen {"nr": 12}
|
* POST ?action=raum-loeschen {"nr": 12}
|
||||||
* Die Grundriss-Aktionen antworten mit dem ganzen neuen Stand.
|
* Die Grundriss-Aktionen antworten mit dem ganzen neuen Stand.
|
||||||
*
|
*
|
||||||
|
* GET ?action=energiefluss Abweichungen der Übersicht vom Katalog
|
||||||
|
* POST ?action=energiefluss-save {"knoten": [{id, aktiv, name, x, y, optionen}, ...]}
|
||||||
|
*
|
||||||
* Ein reiner Verteiler: geprueft und geschrieben wird in
|
* Ein reiner Verteiler: geprueft und geschrieben wird in
|
||||||
* restricted/costs.php, kacheln.php bzw. grundriss.php, damit Maske und
|
* restricted/costs.php, kacheln.php bzw. grundriss.php, damit Maske und
|
||||||
* Endpunkt dieselbe Wahrheit ueber einen Zeitraum oder eine Kachel haben.
|
* Endpunkt dieselbe Wahrheit ueber einen Zeitraum oder eine Kachel haben.
|
||||||
@@ -35,6 +38,7 @@ require_once("../restricted/kacheln.php");
|
|||||||
require_once("../restricted/automations.php");
|
require_once("../restricted/automations.php");
|
||||||
require_once("../restricted/skodaKeys.php");
|
require_once("../restricted/skodaKeys.php");
|
||||||
require_once("../restricted/grundriss.php");
|
require_once("../restricted/grundriss.php");
|
||||||
|
require_once("../restricted/energiefluss.php");
|
||||||
|
|
||||||
if (!checkLogin()) {
|
if (!checkLogin()) {
|
||||||
http_response_code(403);
|
http_response_code(403);
|
||||||
@@ -140,6 +144,12 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
|||||||
grundriss(true);
|
grundriss(true);
|
||||||
jsonAntwort(["stand" => grundrissStand()]);
|
jsonAntwort(["stand" => grundrissStand()]);
|
||||||
|
|
||||||
|
case "energiefluss-save":
|
||||||
|
// Zurück kommt der gespeicherte Stand - ohne die Einträge,
|
||||||
|
// die dem Katalog entsprechen.
|
||||||
|
energieflussSpeichern($body["knoten"] ?? null);
|
||||||
|
jsonAntwort(["einstellungen" => (object)energieflussLaden()]);
|
||||||
|
|
||||||
case "skoda-keys-save":
|
case "skoda-keys-save":
|
||||||
/*
|
/*
|
||||||
* Neue Schluessel eintragen. Zurueck kommt der neue Stand -
|
* Neue Schluessel eintragen. Zurueck kommt der neue Stand -
|
||||||
@@ -188,6 +198,14 @@ if ($action === "grundriss") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($action === "energiefluss") {
|
||||||
|
try {
|
||||||
|
jsonAntwort(["einstellungen" => (object)energieflussLaden()]);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
jsonAntwort(["error" => "Übersicht nicht abrufbar: " . $e->getMessage()], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($action === "skoda-keys") {
|
if ($action === "skoda-keys") {
|
||||||
try {
|
try {
|
||||||
jsonAntwort(skodaStand());
|
jsonAntwort(skodaStand());
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
<!-- Lageplan der PV-Platten, bis September 2026 Teil von realtime.svg (Gruppe
|
||||||
|
pvDetail, geoeffnet ueber die Sonne). Unveraendert herausgeloest, damit die
|
||||||
|
neue Energiefluss-Uebersicht ihn als eigene Ansicht zeigen kann. Die Werte
|
||||||
|
schreibt solarMQTT.js ueber die Ids det_pvNP und invList. -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="40 0 1260 740" width="100%" class="enf-pvplan">
|
||||||
|
<style>
|
||||||
|
.enf-pvplan .infoTextClass { font-size: 18px; font-family: sans-serif; text-anchor: middle; fill: rgb(200, 210, 210); }
|
||||||
|
.enf-pvplan .tooltipTextClass { font-size: 18px; font-family: sans-serif; text-anchor: start; fill: rgb(200, 210, 210); }
|
||||||
|
</style>
|
||||||
|
<defs>
|
||||||
|
<filter id="shadowMiddle" x="-0.4" y="-0.4" width="180%" height="180%">
|
||||||
|
<feMorphology in="SourceAlpha" result="offOut" operator="dilate" radius="3" />
|
||||||
|
<feGaussianBlur result="blurOut" in="offOut" stdDeviation="1" />
|
||||||
|
<feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
|
||||||
|
</filter>
|
||||||
|
<filter id="shadowBig" x="-0.45" y="-0.45" width="190%" height="190%">
|
||||||
|
<feMorphology in="SourceAlpha" result="offOut" operator="dilate" radius="3" />
|
||||||
|
<feGaussianBlur result="offOut" in="offOut" stdDeviation="2" />
|
||||||
|
<feBlend in="SourceGraphic" in2="offOut" mode="normal" />
|
||||||
|
</filter>
|
||||||
|
<g id="pvPlate" fill="none" stroke-width="4" stroke="#f7c000">
|
||||||
|
<rect width="70" height="40" x="0" y="0" rx="5" ry="5" />
|
||||||
|
</g>
|
||||||
|
<g id="northIcon" fill="#808090">
|
||||||
|
<path
|
||||||
|
d="M42.066 0v20h3.752V6.957L53.881 20h4.053V0h-3.752v13.355L45.996 0zm5.57 26.688l-25.77 69.949c-.823 2.238 1.658 4.248 3.677 2.978L50 84.195l24.455 15.42c2.02 1.273 4.504-.738 3.68-2.978l-25.79-70c-.472-1.096-1.283-1.632-2.384-1.635c-1.1-.003-2.017.856-2.324 1.686zm-.136 14.83V79.86L29.1 91.463z"></path>
|
||||||
|
</g>
|
||||||
|
</defs>
|
||||||
|
<g id="pvDetail">
|
||||||
|
<use href="#northIcon" transform="translate(1200,10),rotate(10)" />
|
||||||
|
<!--<polygon points="370 0, 1300 0, 1300 700, 0 700, 0 250, 370 250" fill="rgb(24, 27, 31)" />-->
|
||||||
|
<polygon points="560,80 720,80 720,300 1160,300 1160,370 560,370" stroke-linejoin="round"
|
||||||
|
fill="#f7c002" stroke-width="30" stroke="#f7c002" opacity="0.1" />
|
||||||
|
<use href="#pvPlate" transform="translate(600,80), rotate(90)" />
|
||||||
|
<use href="#pvPlate" transform="translate(600,150), rotate(90)" />
|
||||||
|
<use href="#pvPlate" transform="translate(600,220), rotate(90)" />
|
||||||
|
<use href="#pvPlate" transform="translate(600,290), rotate(90)" />
|
||||||
|
|
||||||
|
<use href="#pvPlate" transform="translate(640,80), rotate(90)" />
|
||||||
|
<use href="#pvPlate" transform="translate(640,150), rotate(90)" />
|
||||||
|
<use href="#pvPlate" transform="translate(640,220), rotate(90)" />
|
||||||
|
<use href="#pvPlate" transform="translate(640,290), rotate(90)" />
|
||||||
|
|
||||||
|
<use href="#pvPlate" transform="translate(680,80), rotate(90)" />
|
||||||
|
<use href="#pvPlate" transform="translate(680,150), rotate(90)" />
|
||||||
|
<use href="#pvPlate" transform="translate(680,220), rotate(90)" />
|
||||||
|
<use href="#pvPlate" transform="translate(680,290), rotate(90)" />
|
||||||
|
<use href="#pvPlate" transform="translate(720,80), rotate(90)" />
|
||||||
|
<use href="#pvPlate" transform="translate(720,150), rotate(90)" />
|
||||||
|
<use href="#pvPlate" transform="translate(720,220), rotate(90)" />
|
||||||
|
|
||||||
|
<use href="#pvPlate" transform="translate(790,290)" />
|
||||||
|
<use href="#pvPlate" transform="translate(720,330)" />
|
||||||
|
<use href="#pvPlate" transform="translate(790,330)" />
|
||||||
|
|
||||||
|
<use href="#pvPlate" transform="translate(870,290)" />
|
||||||
|
<use href="#pvPlate" transform="translate(870,330)" />
|
||||||
|
<use href="#pvPlate" transform="translate(940,330)" />
|
||||||
|
|
||||||
|
<use href="#pvPlate" transform="translate(1160,300), rotate(90)" />
|
||||||
|
<use href="#pvPlate" transform="translate(1120,300), rotate(90)" />
|
||||||
|
<use href="#pvPlate" transform="translate(1080,300), rotate(90)" />
|
||||||
|
|
||||||
|
|
||||||
|
<text id="det_pv0P" filter="url(#shadowBig)" class="infoTextClass" dx="55" dy="30"
|
||||||
|
transform="translate(560, 150), scale(1.5,1.5)">-.-- kW</text>
|
||||||
|
<polygon points="980,60 1160,60 1160,265 980,265" stroke-linejoin="round" fill="#F76D00"
|
||||||
|
stroke-width="30" stroke="#F76D00" opacity="0.1" />
|
||||||
|
<use href="#pvPlate" transform="translate(1160,200), rotate(90), scale(1,1.125)" />
|
||||||
|
<use href="#pvPlate" transform="translate(1115,200), rotate(90), scale(1,1.125)" />
|
||||||
|
<use href="#pvPlate" transform="translate(1070,200), rotate(90), scale(1,1.125)" />
|
||||||
|
<use href="#pvPlate" transform="translate(1160,130), rotate(90), scale(1,1.125)" />
|
||||||
|
<use href="#pvPlate" transform="translate(1115,130), rotate(90), scale(1,1.125)" />
|
||||||
|
<use href="#pvPlate" transform="translate(1070,130), rotate(90), scale(1,1.125)" />
|
||||||
|
<use href="#pvPlate" transform="translate(1025,130), rotate(90), scale(1,1.125)" />
|
||||||
|
<use href="#pvPlate" transform="translate(1160,60), rotate(90), scale(1,1.125)" />
|
||||||
|
<use href="#pvPlate" transform="translate(1115,60), rotate(90), scale(1,1.125)" />
|
||||||
|
<use href="#pvPlate" transform="translate(1070,60), rotate(90), scale(1,1.125)" />
|
||||||
|
<text id="det_pv1P" filter="url(#shadowBig)" class="infoTextClass" dx="55" dy="30"
|
||||||
|
transform="translate(990, 140), scale(1.5,1.5)">-.-- kW</text>
|
||||||
|
|
||||||
|
<use href="#pvPlate" x="200" y="680" />
|
||||||
|
<text id="det_pv33P" filter="url(#shadowMiddle)" class="infoTextClass" x="200" y="680" dx="36"
|
||||||
|
dy="26">#19</text>
|
||||||
|
<use href="#pvPlate" x="380" y="550" />
|
||||||
|
<text id="det_pv32P" filter="url(#shadowMiddle)" class="infoTextClass" x="380" y="550" dx="36"
|
||||||
|
dy="26">#19</text>
|
||||||
|
<use href="#pvPlate" x="380" y="590" />
|
||||||
|
<text id="det_pv31P" filter="url(#shadowMiddle)" class="infoTextClass" x="380" y="590" dx="36"
|
||||||
|
dy="26">#18</text>
|
||||||
|
<use href="#pvPlate" x="380" y="630" />
|
||||||
|
<text id="det_pv30P" filter="url(#shadowMiddle)" class="infoTextClass" x="380" y="630" dx="36"
|
||||||
|
dy="26">#17</text>
|
||||||
|
<use href="#pvPlate" x="310" y="510" />
|
||||||
|
<text id="det_pv29P" filter="url(#shadowMiddle)" class="infoTextClass" x="310" y="510" dx="36"
|
||||||
|
dy="26">#16</text>
|
||||||
|
<use href="#pvPlate" x="310" y="550" />
|
||||||
|
<text id="det_pv28P" filter="url(#shadowMiddle)" class="infoTextClass" x="310" y="550" dx="36"
|
||||||
|
dy="26">#15</text>
|
||||||
|
<use href="#pvPlate" x="310" y="590" />
|
||||||
|
<text id="det_pv27P" filter="url(#shadowMiddle)" class="infoTextClass" x="310" y="590" dx="36"
|
||||||
|
dy="26">#14</text>
|
||||||
|
<use href="#pvPlate" x="310" y="630" />
|
||||||
|
<text id="det_pv26P" filter="url(#shadowMiddle)" class="infoTextClass" x="310" y="630" dx="36"
|
||||||
|
dy="26">#13</text>
|
||||||
|
<use href="#pvPlate" x="240" y="510" />
|
||||||
|
<text id="det_pv25P" filter="url(#shadowMiddle)" class="infoTextClass" x="240" y="510" dx="36"
|
||||||
|
dy="26">#12</text>
|
||||||
|
<use href="#pvPlate" x="240" y="550" />
|
||||||
|
<text id="det_pv24P" filter="url(#shadowMiddle)" class="infoTextClass" x="240" y="550" dx="36"
|
||||||
|
dy="26">#11</text>
|
||||||
|
<use href="#pvPlate" x="240" y="590" />
|
||||||
|
<text id="det_pv23P" filter="url(#shadowMiddle)" class="infoTextClass" x="240" y="590" dx="36"
|
||||||
|
dy="26">#10</text>
|
||||||
|
<use href="#pvPlate" x="240" y="630" />
|
||||||
|
<text id="det_pv22P" filter="url(#shadowMiddle)" class="infoTextClass" x="240" y="630" dx="36"
|
||||||
|
dy="26">#9</text>
|
||||||
|
<use href="#pvPlate" x="170" y="510" />
|
||||||
|
<text id="det_pv20P" filter="url(#shadowMiddle)" class="infoTextClass" x="170" y="510" dx="36"
|
||||||
|
dy="26">#8</text>
|
||||||
|
<use href="#pvPlate" x="170" y="550" />
|
||||||
|
<text id="det_pv21P" filter="url(#shadowMiddle)" class="infoTextClass" x="170" y="550" dx="36"
|
||||||
|
dy="26">#7</text>
|
||||||
|
<use href="#pvPlate" x="170" y="590" />
|
||||||
|
<text id="det_pv19P" filter="url(#shadowMiddle)" class="infoTextClass" x="170" y="590" dx="36"
|
||||||
|
dy="26">#6</text>
|
||||||
|
<use href="#pvPlate" x="170" y="630" />
|
||||||
|
<text id="det_pv18P" filter="url(#shadowMiddle)" class="infoTextClass" x="170" y="630" dx="36"
|
||||||
|
dy="26">#5</text>
|
||||||
|
<use href="#pvPlate" x="100" y="510" />
|
||||||
|
<text id="det_pv14P" filter="url(#shadowMiddle)" class="infoTextClass" x="100" y="510" dx="36"
|
||||||
|
dy="26">#1</text>
|
||||||
|
<use href="#pvPlate" x="100" y="550" />
|
||||||
|
<text id="det_pv15P" filter="url(#shadowMiddle)" class="infoTextClass" x="100" y="550" dx="36"
|
||||||
|
dy="26">#2</text>
|
||||||
|
<use href="#pvPlate" x="100" y="590" />
|
||||||
|
<text id="det_pv16P" filter="url(#shadowMiddle)" class="infoTextClass" x="100" y="590" dx="36"
|
||||||
|
dy="26">#3</text>
|
||||||
|
<use href="#pvPlate" x="100" y="630" />
|
||||||
|
<text id="det_pv17P" filter="url(#shadowMiddle)" class="infoTextClass" x="100" y="630" dx="36"
|
||||||
|
dy="26">#4</text>
|
||||||
|
|
||||||
|
<use href="#pvPlate" transform="translate(600,500)" />
|
||||||
|
<text id="det_pv13P" filter="url(#shadowMiddle)" class="infoTextClass" x="600" y="500" dx="36"
|
||||||
|
dy="26">- W</text>
|
||||||
|
<use href="#pvPlate" transform="translate(500,500)" />
|
||||||
|
<text id="det_pv12P" filter="url(#shadowMiddle)" class="infoTextClass" x="500" y="500" dx="36"
|
||||||
|
dy="26">- W</text>
|
||||||
|
|
||||||
|
<use href="#pvPlate" transform="translate(460,120), rotate(-90)" />
|
||||||
|
<text id="det_pv5P" filter="url(#shadowMiddle)" class="infoTextClass"
|
||||||
|
transform="translate(460,120), rotate(-90)" dx="36" dy="26">- W</text>
|
||||||
|
<use href="#pvPlate" transform="translate(460,190), rotate(-90)" />
|
||||||
|
<text id="det_pv4P" filter="url(#shadowMiddle)" class="infoTextClass"
|
||||||
|
transform="translate(460,190), rotate(-90)" dx="36" dy="26">- W</text>
|
||||||
|
|
||||||
|
<use href="#pvPlate" transform="translate(460,270), rotate(-90)" />
|
||||||
|
<text id="det_pv9P" filter="url(#shadowMiddle)" class="infoTextClass"
|
||||||
|
transform="translate(460,270), rotate(-90)" dx="36" dy="26">- W</text>
|
||||||
|
<use href="#pvPlate" transform="translate(460,340), rotate(-90)" />
|
||||||
|
<text id="det_pv8P" filter="url(#shadowMiddle)" class="infoTextClass"
|
||||||
|
transform="translate(460,340), rotate(-90)" dx="36" dy="26">- W</text>
|
||||||
|
|
||||||
|
<use href="#pvPlate" transform="translate(460,420), rotate(-90)" />
|
||||||
|
<text id="det_pv11P" filter="url(#shadowMiddle)" class="infoTextClass"
|
||||||
|
transform="translate(460,420), rotate(-90)" dx="36" dy="26">- W</text>
|
||||||
|
<use href="#pvPlate" transform="translate(460,490), rotate(-90)" />
|
||||||
|
<text id="det_pv10P" filter="url(#shadowMiddle)" class="infoTextClass"
|
||||||
|
transform="translate(460,490), rotate(-90)" dx="36" dy="26">- W</text>
|
||||||
|
|
||||||
|
<use href="#pvPlate" transform="translate(400,120), rotate(-90)" />
|
||||||
|
<text id="det_pv7P" filter="url(#shadowMiddle)" class="infoTextClass"
|
||||||
|
transform="translate(400,120), rotate(-90)" dx="36" dy="26">- W</text>
|
||||||
|
<use href="#pvPlate" transform="translate(400,190), rotate(-90)" />
|
||||||
|
<text id="det_pv6P" filter="url(#shadowMiddle)" class="infoTextClass"
|
||||||
|
transform="translate(400,190), rotate(-90)" dx="36" dy="26">- W</text>
|
||||||
|
|
||||||
|
<use href="#pvPlate" transform="translate(400,270), rotate(-90)" />
|
||||||
|
<text id="det_pv3P" filter="url(#shadowMiddle)" class="infoTextClass"
|
||||||
|
transform="translate(400,270), rotate(-90)" dx="36" dy="26">- W</text>
|
||||||
|
|
||||||
|
<use href="#pvPlate" transform="translate(400,340), rotate(-90)" />
|
||||||
|
<text id="det_pv2P" filter="url(#shadowMiddle)" class="infoTextClass"
|
||||||
|
transform="translate(400,340), rotate(-90)" dx="36" dy="26">- W</text>
|
||||||
|
|
||||||
|
|
||||||
|
<text id="invList" filter="url(#shadowMiddle)" class="tooltipTextClass"
|
||||||
|
transform="translate(80,255)"></text>
|
||||||
|
<rect width="305" height="210" x="70" y="250" rx="5" ry="5" fill="none" stroke-width="4px"
|
||||||
|
stroke="#f7c000" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 11 KiB |
@@ -1,893 +0,0 @@
|
|||||||
<style>
|
|
||||||
/* Fluss-Animation als transform statt stroke-dashoffset.
|
|
||||||
stroke-dashoffset ist nicht kompositierbar - jede Stufe kostete einen
|
|
||||||
Neuzeichenvorgang auf dem Hauptthread. Ein transform kann der Compositor
|
|
||||||
dagegen selbst verschieben.
|
|
||||||
Die Punkte laufen eine Periode (111 Einheiten) weit, danach steht der
|
|
||||||
naechste an derselben Stelle - der Zyklus schliesst nahtlos.
|
|
||||||
--dx/--dy stehen pro Linie im style-Attribut, --w setzt setFlowWidth().
|
|
||||||
steps(40) = 20 Bewegungen pro Sekunde, wie in der urspruenglichen
|
|
||||||
Fassung. Das deckelt die Neuzeichnungen fuer den Fall, dass der Browser
|
|
||||||
das transform doch nicht kompositiert; auf dem Compositor kostet die
|
|
||||||
Stufung nichts. Fuer volle Fluessigkeit: linear statt steps(40). */
|
|
||||||
.stream, .stream-rev {
|
|
||||||
animation: flow 2s steps(40) infinite;
|
|
||||||
}
|
|
||||||
.stream-rev {
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
@keyframes flow {
|
|
||||||
to { transform: translate(var(--dx), var(--dy)); }
|
|
||||||
}
|
|
||||||
.stream circle, .stream-rev circle {
|
|
||||||
r: calc(var(--w, 0px) / 2);
|
|
||||||
fill: gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
.powerTextClass {
|
|
||||||
font-size: 27px;
|
|
||||||
font-family: sans-serif;
|
|
||||||
text-anchor: middle;
|
|
||||||
fill: rgb(200, 210, 210);
|
|
||||||
letter-spacing: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.infoTextClass {
|
|
||||||
font-size: 18px;
|
|
||||||
font-family: sans-serif;
|
|
||||||
text-anchor: middle;
|
|
||||||
fill: rgb(200, 210, 210);
|
|
||||||
}
|
|
||||||
.tooltipTextClass {
|
|
||||||
font-size: 18px;
|
|
||||||
font-family: sans-serif;
|
|
||||||
text-anchor: left;
|
|
||||||
fill: rgb(200, 210, 210);
|
|
||||||
}
|
|
||||||
.clickable {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="-100 -100 1600 950" width="100%" height="100%" id="realtimeSvgAni">
|
|
||||||
<defs>
|
|
||||||
<filter
|
|
||||||
id="glow" filterUnits="userSpaceOnUse"
|
|
||||||
x="-20%" y="-20%" width="120%" height="120%">
|
|
||||||
<feGaussianBlur in="SourceGraphic" stdDeviation="7" result="blur5" />
|
|
||||||
<feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur10" />
|
|
||||||
<feGaussianBlur in="SourceGraphic" stdDeviation="14" result="blur20" />
|
|
||||||
<feGaussianBlur in="SourceGraphic" stdDeviation="17" result="blur30" />
|
|
||||||
<feGaussianBlur in="SourceGraphic" stdDeviation="20" result="blur50" />
|
|
||||||
<feMerge result="blur-merged">
|
|
||||||
<feMergeNode in="blur5" />
|
|
||||||
<feMergeNode in="blur10" />
|
|
||||||
<feMergeNode in="blur10" />
|
|
||||||
<feMergeNode in="blur20" />
|
|
||||||
</feMerge>
|
|
||||||
<feMerge>
|
|
||||||
<feMergeNode in="blur-merged" />
|
|
||||||
<feMergeNode in="SourceGraphic" />
|
|
||||||
</feMerge>
|
|
||||||
</filter>
|
|
||||||
<clipPath id="clip_consOGani"><rect x="-30" y="-30" width="404.4" height="60" transform="translate(770.0,380.0) rotate(-64.1790)"/></clipPath>
|
|
||||||
<clipPath id="clip_consAni"><rect x="-30" y="-30" width="400.0" height="60" transform="translate(430.0,380.0) rotate(0.0000)"/></clipPath>
|
|
||||||
<clipPath id="clip_consEGani"><rect x="-30" y="-30" width="386.5" height="60" transform="translate(770.0,380.0) rotate(62.6501)"/></clipPath>
|
|
||||||
<clipPath id="clip_consUGani"><rect x="-30" y="-30" width="396.2" height="60" transform="translate(770.0,380.0) rotate(120.3791)"/></clipPath>
|
|
||||||
<clipPath id="clip_consHeatAni"><rect x="-30" y="-30" width="410.0" height="60" transform="translate(770.0,380.0) rotate(0.0000)"/></clipPath>
|
|
||||||
<clipPath id="clip_consEVani"><rect x="-30" y="-30" width="360.0" height="60" transform="translate(920.0,670.0) rotate(0.0000)"/></clipPath>
|
|
||||||
<clipPath id="clip_consEVOGani"><rect x="-30" y="-30" width="360.0" height="60" transform="translate(920.0,70.0) rotate(0.0000)"/></clipPath>
|
|
||||||
<clipPath id="clip_consGridani"><rect x="-30" y="-30" width="390.0" height="60" transform="translate(100.0,380.0) rotate(0.0000)"/></clipPath>
|
|
||||||
<clipPath id="clip_pvani"><rect x="-30" y="-30" width="413.6" height="60" transform="translate(180.0,130.0) rotate(45.0000)"/></clipPath>
|
|
||||||
<clipPath id="clip_battani"><rect x="-30" y="-30" width="413.6" height="60" transform="translate(180.0,630.0) rotate(-45.0000)"/></clipPath>
|
|
||||||
</defs>
|
|
||||||
<g opacity="0">
|
|
||||||
<animate begin="lastInitAni.end" attributetype="CSS" attributeName="opacity" from="0.0" to="1.0"
|
|
||||||
dur="0.5s" repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1"
|
|
||||||
fill="freeze" />
|
|
||||||
<animate begin="pvInfo.click" attributetype="CSS" attributeName="opacity" from="1.0" to="0.0"
|
|
||||||
dur="1s"
|
|
||||||
repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1" fill="freeze" />
|
|
||||||
<animate begin="closeDetails.click" attributetype="CSS" attributeName="opacity" from="0.0"
|
|
||||||
to="1.0"
|
|
||||||
dur="1s" repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1"
|
|
||||||
fill="freeze" />
|
|
||||||
<!-- CONS pos: 330, 280
|
|
||||||
+340, 0 (center +100 in x und y)
|
|
||||||
Cons OG pos: 330,280
|
|
||||||
+500 -300 (center +90 in x und y)
|
|
||||||
-->
|
|
||||||
<line x1="770" y1="380" x2="920" y2="70" style="stroke:#777;stroke-width:2;stroke-dasharray:1,6;" />
|
|
||||||
<g clip-path="url(#clip_consOGani)"><g id="consOGani" class="stream" style="--dx:48.347px;--dy:-99.918px"><circle cx="721.7" cy="479.9"/><circle cx="770.0" cy="380.0"/><circle cx="818.3" cy="280.1"/><circle cx="866.7" cy="180.2"/><circle cx="915.0" cy="80.2"/><circle cx="963.4" cy="-19.7"/></g></g>
|
|
||||||
|
|
||||||
<line x1="430" y1="380" x2="770" y2="380" style="stroke:#777;stroke-width:2;stroke-dasharray:1,6;" />
|
|
||||||
<g clip-path="url(#clip_consAni)"><g id="consAni" class="stream" style="--dx:111.000px;--dy:0.000px"><circle cx="319.0" cy="380.0"/><circle cx="430.0" cy="380.0"/><circle cx="541.0" cy="380.0"/><circle cx="652.0" cy="380.0"/><circle cx="763.0" cy="380.0"/><circle cx="874.0" cy="380.0"/></g></g>
|
|
||||||
|
|
||||||
<line x1="770" y1="380" x2="920" y2="670" style="stroke:#777;stroke-width:2;stroke-dasharray:1,6;" />
|
|
||||||
<g clip-path="url(#clip_consEGani)"><g id="consEGani" class="stream" style="--dx:50.996px;--dy:98.592px"><circle cx="719.0" cy="281.4"/><circle cx="770.0" cy="380.0"/><circle cx="821.0" cy="478.6"/><circle cx="872.0" cy="577.2"/><circle cx="923.0" cy="675.8"/></g></g>
|
|
||||||
|
|
||||||
<line x1="770" y1="380" x2="600" y2="670" style="stroke:#777;stroke-width:2;stroke-dasharray:1,6;" />
|
|
||||||
<g clip-path="url(#clip_consUGani)"><g id="consUGani" class="stream" style="--dx:-56.135px;--dy:95.759px"><circle cx="826.1" cy="284.2"/><circle cx="770.0" cy="380.0"/><circle cx="713.9" cy="475.8"/><circle cx="657.7" cy="571.5"/><circle cx="601.6" cy="667.3"/><circle cx="545.5" cy="763.0"/></g></g>
|
|
||||||
|
|
||||||
<line x1="770" y1="380" x2="1120" y2="380" style="stroke:#777;stroke-width:2;stroke-dasharray:1,6;" />
|
|
||||||
<g clip-path="url(#clip_consHeatAni)"><g id="consHeatAni" class="stream" style="--dx:111.000px;--dy:0.000px"><circle cx="659.0" cy="380.0"/><circle cx="770.0" cy="380.0"/><circle cx="881.0" cy="380.0"/><circle cx="992.0" cy="380.0"/><circle cx="1103.0" cy="380.0"/><circle cx="1214.0" cy="380.0"/></g></g>
|
|
||||||
|
|
||||||
<line x1="920" y1="670" x2="1220" y2="670" style="stroke:#777;stroke-width:2;stroke-dasharray:1,6;" />
|
|
||||||
<g clip-path="url(#clip_consEVani)"><g id="consEVani" class="stream" style="--dx:111.000px;--dy:0.000px"><circle cx="809.0" cy="670.0"/><circle cx="920.0" cy="670.0"/><circle cx="1031.0" cy="670.0"/><circle cx="1142.0" cy="670.0"/><circle cx="1253.0" cy="670.0"/></g></g>
|
|
||||||
|
|
||||||
<line x1="920" y1="70" x2="1220" y2="70" style="stroke:#777;stroke-width:2;stroke-dasharray:1,6;" />
|
|
||||||
<g clip-path="url(#clip_consEVOGani)"><g id="consEVOGani" class="stream" style="--dx:111.000px;--dy:0.000px"><circle cx="809.0" cy="70.0"/><circle cx="920.0" cy="70.0"/><circle cx="1031.0" cy="70.0"/><circle cx="1142.0" cy="70.0"/><circle cx="1253.0" cy="70.0"/></g></g>
|
|
||||||
|
|
||||||
<line x1="100" y1="380" x2="430" y2="380" style="stroke:#777;stroke-width:2;stroke-dasharray:1,6;" />
|
|
||||||
<g clip-path="url(#clip_consGridani)"><g id="consGridani" class="stream" style="--dx:111.000px;--dy:0.000px"><circle cx="-11.0" cy="380.0"/><circle cx="100.0" cy="380.0"/><circle cx="211.0" cy="380.0"/><circle cx="322.0" cy="380.0"/><circle cx="433.0" cy="380.0"/></g></g>
|
|
||||||
|
|
||||||
<line x1="180" y1="130" x2="430" y2="380" style="stroke:#777;stroke-width:2;stroke-dasharray:1,6;" />
|
|
||||||
<g clip-path="url(#clip_pvani)"><g id="pvani" class="stream" style="--dx:78.489px;--dy:78.489px"><circle cx="101.5" cy="51.5"/><circle cx="180.0" cy="130.0"/><circle cx="258.5" cy="208.5"/><circle cx="337.0" cy="287.0"/><circle cx="415.5" cy="365.5"/><circle cx="494.0" cy="444.0"/></g></g>
|
|
||||||
|
|
||||||
<line x1="180" y1="630" x2="430" y2="380" style="stroke:#777;stroke-width:2;stroke-dasharray:1,6;" />
|
|
||||||
<g clip-path="url(#clip_battani)"><g id="battani" class="stream" style="--dx:78.489px;--dy:-78.489px"><circle cx="101.5" cy="708.5"/><circle cx="180.0" cy="630.0"/><circle cx="258.5" cy="551.5"/><circle cx="337.0" cy="473.0"/><circle cx="415.5" cy="394.5"/><circle cx="494.0" cy="316.0"/></g></g>
|
|
||||||
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg"
|
|
||||||
style="position:absolute;top:0;"
|
|
||||||
viewBox="-100 -100 1600 950" width="100%" height="100%" id="realtimeSvg">
|
|
||||||
<defs>
|
|
||||||
<filter id="shadow" x="-0.3" y="-0.3" width="160%" height="160%">
|
|
||||||
<feMorphology in="SourceAlpha" result="offOut" operator="dilate" radius="0.7" />
|
|
||||||
<feGaussianBlur result="blurOut" in="offOut" stdDeviation="0.5" />
|
|
||||||
<feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
|
|
||||||
</filter>
|
|
||||||
<filter id="shadowMiddle" x="-0.4" y="-0.4" width="180%" height="180%">
|
|
||||||
<feMorphology in="SourceAlpha" result="offOut" operator="dilate" radius="3" />
|
|
||||||
<feGaussianBlur result="blurOut" in="offOut" stdDeviation="1" />
|
|
||||||
<feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
|
|
||||||
</filter>
|
|
||||||
<filter id="shadowBig" x="-0.45" y="-0.45" width="190%" height="190%">
|
|
||||||
<feMorphology in="SourceAlpha" result="offOut" operator="dilate" radius="3" />
|
|
||||||
<feGaussianBlur result="offOut" in="offOut" stdDeviation="2" />
|
|
||||||
<feBlend in="SourceGraphic" in2="offOut" mode="normal" />
|
|
||||||
</filter>
|
|
||||||
|
|
||||||
<g id="circGrid">
|
|
||||||
<line x2='27.7' y2='76.5' x1='-18.9' y1='61.4' style='stroke-width:4px' />
|
|
||||||
<line x2='16.5' y2='115.9' x1='-8.1' y1='120.6' style='stroke:#222;stroke-width:2;' />
|
|
||||||
<line x2='34.5' y2='154.2' x1='15.2' y1='170.1' style='stroke:#222;stroke-width:2;' />
|
|
||||||
<line x2='68.7' y2='179' x1='59.5' y1='202.3' style='stroke:#222;stroke-width:2;' />
|
|
||||||
<line x2='110.7' y2='184.3' x1='113.8' y1='209.1' style='stroke:#222;stroke-width:2;' />
|
|
||||||
<line x2='150' y2='168.8' x1='164.7' y1='189' style='stroke:#222;stroke-width:2;' />
|
|
||||||
<line x2='176.9' y2='136.2' x1='199.5' y1='146.8' style='stroke:#222;stroke-width:2;' />
|
|
||||||
<line x2='184.8' y2='94.7' x1='209.8' y1='93.1' style='stroke:#222;stroke-width:2;' />
|
|
||||||
<line x2='171.8' y2='54.5' x1='192.9' y1='41.1' style='stroke:#222;stroke-width:2;' />
|
|
||||||
<line x2='140.9' y2='25.5' x1='153' y1='3.6' style='stroke:#222;stroke-width:2;' />
|
|
||||||
<line x2='100' y2='24' x1='100' y1='-24' style='stroke-width:4px' />
|
|
||||||
</g>
|
|
||||||
<g id="circGrid6kW">
|
|
||||||
<line x2='27.7' y2='76.5' x1='-18.9' y1='61.4' style='stroke-width:4px' />
|
|
||||||
<line x2='26.4' y2='142.5' x1='4.7' y1='155' style='stroke:#222;stroke-width:2;' />
|
|
||||||
<line x2='82.3' y2='183.1' x1='77.1' y1='207.6' style='stroke:#555;stroke-width:4px;' />
|
|
||||||
<line x2='149.9' y2='168.8' x1='164.6' y1='189.0' style='stroke:#222;stroke-width:2;' />
|
|
||||||
<line x2='184.5' y2='108.9' x1='209.4' y1='111.5' style='stroke:#555;stroke-width:4px;' />
|
|
||||||
<line x2='163.2' y2='43.1' x1='181.7' y1='26.4' style='stroke:#222;stroke-width:2;' />
|
|
||||||
<line x2='100' y2='24' x1='100' y1='-24' style='stroke-width:4px' />
|
|
||||||
</g>
|
|
||||||
<linearGradient id="gradient1" x1="0" x2="0" y1="0" y2="1">
|
|
||||||
<stop id="tmpH" offset="0%" stop-color="#2389BA" stop-opacity="0.7" />
|
|
||||||
<stop id="tmpM" offset="50%" stop-color="#2389BA" stop-opacity="0.7" />
|
|
||||||
<stop id="tmpL" offset="100%" stop-color="#2389BA" stop-opacity="0.7" />
|
|
||||||
</linearGradient>
|
|
||||||
<filter id="fillpartialEV" primitiveUnits="objectBoundingBox" x="-5%" y="-5%" width="110%"
|
|
||||||
height="110%">
|
|
||||||
<feFlood flood-color="#1f5a66" />
|
|
||||||
<feComposite id="evChargeState" y="100%" operator="in" in2="SourceGraphic" />
|
|
||||||
<feComposite operator="over" in2="SourceGraphic" />
|
|
||||||
</filter>
|
|
||||||
<filter id="fillpartialHeat" primitiveUnits="objectBoundingBox" x="-5%" y="-5%" width="110%"
|
|
||||||
height="110%">
|
|
||||||
<feFlood flood-color="#5E2B21" />
|
|
||||||
<feComposite id="heatChargeState" y="100%" operator="in" in2="SourceGraphic" />
|
|
||||||
<feComposite operator="over" in2="SourceGraphic" />
|
|
||||||
</filter>
|
|
||||||
<filter id="fillpartialBat" primitiveUnits="objectBoundingBox" x="-5%" y="-5%" width="110%"
|
|
||||||
height="110%">
|
|
||||||
<feFlood flood-color="#25481C" />
|
|
||||||
<feComposite id="batChargeState" y="100%" operator="in" in2="SourceGraphic" />
|
|
||||||
<feComposite operator="over" in2="SourceGraphic" />
|
|
||||||
</filter>
|
|
||||||
<filter id="fillpartialWater" primitiveUnits="objectBoundingBox" x="-5%" y="-5%" width="110%"
|
|
||||||
height="110%">
|
|
||||||
<feFlood flood-color="#182C43" />
|
|
||||||
<feComposite id="waterState" y="50%" operator="in" in2="SourceGraphic" />
|
|
||||||
<feComposite operator="over" in2="SourceGraphic" />
|
|
||||||
</filter>
|
|
||||||
|
|
||||||
<g id="heater" transform="rotate(-90),scale(0.07,0.07)">
|
|
||||||
<path
|
|
||||||
d="M485.924,134.508c14.381,0,26.076-11.669,26.076-26.017v-17.39c0-14.348-11.695-26.017-26.076-26.017H468.61v-8.737
|
|
||||||
c0-23.89-19.466-43.331-43.39-43.331s-43.39,19.441-43.39,43.331v8.737h-26.034v-8.737c0-23.89-19.466-43.331-43.39-43.331
|
|
||||||
c-23.924,0-43.39,19.441-43.39,43.331v8.737h-26.034v-8.737c0-23.89-19.466-43.331-43.39-43.331
|
|
||||||
c-23.924,0-43.39,19.441-43.39,43.331v8.737h-26.034v-8.737c0-23.89-19.466-43.331-43.39-43.331S43.39,32.458,43.39,56.348v8.737
|
|
||||||
H17.356V39.051H0v34.712v52.068v34.712h17.356v-26.034H43.39v269.017H8.678c-4.797,0-8.678,3.881-8.678,8.678v34.712
|
|
||||||
c0,4.797,3.881,8.678,8.678,8.678c4.797,0,8.678-3.881,8.678-8.678v-26.034H43.39v34.78c0,23.89,19.466,43.322,43.39,43.322
|
|
||||||
s43.39-19.432,43.39-43.322v-0.068h26.034v0.068c0,23.89,19.466,43.322,43.39,43.322c23.924,0,43.39-19.432,43.39-43.322v-0.068
|
|
||||||
h26.034v0.068c0,23.89,19.466,43.322,43.39,43.322c23.924,0,43.39-19.432,43.39-43.322v-0.068h26.034v0.068
|
|
||||||
c0,23.89,19.466,43.322,43.39,43.322s43.39-19.432,43.39-43.322v-0.068h17.314c14.381,0,26.076-11.669,26.076-26.017v-17.39
|
|
||||||
c0-14.347-11.695-26.017-26.076-26.017H468.61V134.508H485.924z M468.61,82.441h17.314c4.805,0,8.72,3.881,8.72,8.661v17.39
|
|
||||||
c0,4.78-3.915,8.661-8.72,8.661H468.61V82.441z M43.39,117.153H17.356V82.441H43.39V117.153z M112.814,73.763v52.068v269.017
|
|
||||||
v52.068v8.746c0,14.313-11.678,25.966-26.034,25.966s-26.034-11.653-26.034-25.966V125.831V73.763V56.348
|
|
||||||
c0-14.322,11.678-25.975,26.034-25.975s26.034,11.653,26.034,25.975V73.763z M156.203,438.237h-26.034v-34.712h26.034V438.237z
|
|
||||||
M156.203,386.169h-26.034V134.508h26.034V386.169z M156.203,117.153h-26.034V82.441h26.034V117.153z M225.627,73.763v52.068
|
|
||||||
v269.017v52.068v8.746c0,14.313-11.678,25.966-26.034,25.966s-26.034-11.653-26.034-25.966v-8.746v-52.068V125.831V73.763V56.348
|
|
||||||
c0-14.322,11.678-25.975,26.034-25.975s26.034,11.653,26.034,25.975V73.763z M269.017,438.237h-26.034v-34.712h26.034V438.237z
|
|
||||||
M269.017,386.169h-26.034V134.508h26.034V386.169z M269.017,117.153h-26.034V82.441h26.034V117.153z M338.441,73.763v52.068
|
|
||||||
v269.017v52.068v8.746c0,14.313-11.678,25.966-26.034,25.966s-26.034-11.653-26.034-25.966v-8.746v-52.068V125.831V73.763V56.348
|
|
||||||
c0-14.322,11.678-25.975,26.034-25.975s26.034,11.653,26.034,25.975V73.763z M381.831,438.237h-26.034v-34.712h26.034V438.237z
|
|
||||||
M381.831,386.169h-26.034V134.508h26.034V386.169z M381.831,117.153h-26.034V82.441h26.034V117.153z M451.254,73.763v52.068
|
|
||||||
v269.017v52.068v8.746c0,14.313-11.678,25.966-26.034,25.966s-26.034-11.653-26.034-25.966v-8.746v-52.068V125.831V73.763V56.348
|
|
||||||
c0-14.322,11.678-25.975,26.034-25.975s26.034,11.653,26.034,25.975V73.763z M485.924,403.525c4.805,0,8.72,3.881,8.72,8.661
|
|
||||||
v17.39c0,4.78-3.915,8.661-8.72,8.661H468.61v-34.712H485.924z" />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<g id="gas">
|
|
||||||
<path fill-opacity="1" stroke-width="0.2" stroke-linejoin="round"
|
|
||||||
d="M 42.75,19L 42.75,31.4977C 44.5834,31.378 48.9184,32.7881 48.2917,49.0833C 49.0833,53.8333 54.625,53.8333 55.8125,47.5C 57,42.75 53.8333,42.75 52.25,28.5C 52.25,26.9167 52.25,22.1667 45.9167,19L 45.9167,17.4167C 52.25,17.4167 57,25.3333 57,26.9167C 57,28.5 55.4167,28.5 55.4167,28.5C 55.4167,28.5 55.8125,38 57.3958,39.5833C 58.9792,41.1667 58.5833,45.9167 58.5833,49.0833C 58.5833,52.25 53.8333,55.4167 52.25,55.4167C 50.6667,55.4167 45.9167,55.4167 45.9167,50.6667C 45.9167,46.3403 45.2599,36.7598 42.75,35.0854L 42.75,55.4167C 43.6244,55.4167 44.3333,56.1255 44.3333,57L 44.3333,60.1667L 19,60.1667L 19,57C 19,56.1255 19.7089,55.4167 20.5833,55.4167L 20.5833,19C 20.5833,17.2511 22.0011,15.8333 23.75,15.8333L 39.5833,15.8333C 41.3322,15.8333 42.75,17.2511 42.75,19 Z M 25.7291,33.2501L 37.6041,33.25C 38.4786,33.25 39.5833,32.1453 39.5833,31.2709L 39.5833,20.9792C 39.5833,20.1048 38.4786,19 37.6041,19L 25.7292,19C 24.8547,19 23.75,20.1048 23.75,20.9792L 23.75,31.2709C 23.75,32.1453 24.8547,33.2501 25.7291,33.2501 Z " />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<g id="water">
|
|
||||||
<path
|
|
||||||
d="M283.897,92.846c-36.582-49.345-73.688-89.267-74.061-89.664C207.944,1.153,205.296,0,202.523,0
|
|
||||||
c-2.774,0-5.423,1.152-7.314,3.182c-0.371,0.397-37.478,40.319-74.06,89.664c-49.971,67.403-75.308,119.726-75.308,155.513
|
|
||||||
c0,86.396,70.287,156.688,156.682,156.688c86.396,0,156.683-70.29,156.683-156.688C359.206,212.572,333.868,160.25,283.897,92.846z
|
|
||||||
M218.171,354.342c-8.213,1.941-16.68,2.926-25.162,2.926c-60.294,0-109.347-49.055-109.347-109.35
|
|
||||||
c0-8.312,2.559-23.373,14.75-47.914c1.225-2.467,4.046-3.691,6.687-2.908c2.639,0.785,4.33,3.357,4.007,6.091
|
|
||||||
c-0.28,2.361-0.421,4.584-0.421,6.607c0,64.629,45.966,120.77,109.298,133.484c2.607,0.525,4.5,2.795,4.545,5.455
|
|
||||||
C222.575,351.396,220.761,353.729,218.171,354.342z" />
|
|
||||||
</g>
|
|
||||||
<g id="raisedBedIcon" filter="url(#shadow)">
|
|
||||||
<path d="M2 8h20v12H2V8Z" fill="none" stroke="currentColor" stroke-width="1.8" />
|
|
||||||
<path d="M4 12h16M5 16h14" fill="none" stroke="currentColor" stroke-width="1.4" />
|
|
||||||
<path d="M7 8V4m0 2L5 4m2 2 2-2M13 8V3m0 3-2-2m2 2 2-2M18 8V4m0 2-2-2m2 2 2-2" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
|
|
||||||
</g>
|
|
||||||
<g id="flowerPotIcon" filter="url(#shadow)">
|
|
||||||
<path d="M3 8h18l-2 4H5L3 8Z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" />
|
|
||||||
<path d="M6 12h12l-2 8H8l-2-8Z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" />
|
|
||||||
<path d="M12 8V3m0 3L9 4m3 2 3-2" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
|
|
||||||
</g>
|
|
||||||
<g id="gardenPlantIcon" filter="url(#shadow)">
|
|
||||||
<path d="M12 19V10m0 4-4-3m4 1 4-4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
|
|
||||||
<path d="M12 3C9 3 7 5 7 8c-3 0-5 2-5 5s2 5 5 5h10c3 0 5-2 5-5s-2-5-5-5c0-3-2-5-5-5Z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" />
|
|
||||||
<path d="M5 22h14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<g id="voltage">
|
|
||||||
<path stroke="#212529" stroke-width="0.2px" d="M11.46,18V13.58H9.86L12.54,8v3.89h1.6Z" />
|
|
||||||
</g>
|
|
||||||
<path id="powerTextPath" d="M 100, 100 m -87, 0 a 87,87 0 1,1 174,0 a 87,87 0 1,1 -174,0"></path>
|
|
||||||
<path id="infoTextPath" d="M 100, 100 m -105, 0 a 105,105 0 1,0 210,0 a 105,105 0 1,0 -210,0"></path>
|
|
||||||
<g id="pvPlate" fill="none" stroke-width="4" stroke="#f7c000">
|
|
||||||
<rect width="70" height="40" x="0" y="0" rx="5" ry="5" />
|
|
||||||
</g>
|
|
||||||
<g id="ElementBG">
|
|
||||||
<path fill="#212529" stroke-width="1.5"
|
|
||||||
d="M 99.80277789465349 -12.999827891201505 A 113 113 0 1 0 100 -13" />
|
|
||||||
<path fill="none" stroke-width="4"
|
|
||||||
d="M 99.86386438745993 22.00011880076356 A 78 78 0 1 0 100 22" />
|
|
||||||
</g>
|
|
||||||
<g id="plugIcon" filter="url(#shadow)" transform="translate(30,27),scale(6,6)" fill="#71afcd">
|
|
||||||
<path d="M15.21,6.73V2.63A.72.72,0,0,0,14.4,2h0a.71.71,0,0,0-.75.66V6.73Z" />
|
|
||||||
<path d="M10.39,6.73V2.62A.7.7,0,0,0,9.6,2h0a.73.73,0,0,0-.78.66V6.73Z" />
|
|
||||||
<path
|
|
||||||
d="M6.71,7.23C6.37,7.22,6,8,6,8.8V10c0,2.48.57,4.62,1.63,5.28S9,16,10.35,16.77V22h3.3V16.77c1.38-.8,1.66-.85,2.72-1.51S18,12.46,18,10V8.8c0-.78-.37-1.58-.71-1.57Z" />
|
|
||||||
</g>
|
|
||||||
<g id="ogIcon" filter="url(#shadow)" transform="translate(52,45),scale(4,4)">
|
|
||||||
<polygon fill="#70CCBB"
|
|
||||||
points="12.01 1.5 2 10.32 3.11 10.32 3.11 21.5 5.12 21.5 5.12 10.32 12 4.13 18.89 10.32 18.89 21.5 20.89 21.5 20.89 10.32 22 10.32 12.01 1.5" />
|
|
||||||
<path fill="#70CCBB"
|
|
||||||
d="M8.55,12.65v3.49a2.12,2.12,0,0,0,2.13,2.09h.67s0,3.23,0,3.27h1.49s0-3.24,0-3.28h.77a1.82,1.82,0,0,0,1.8-1.85l0-3.73" />
|
|
||||||
<path fill="#70CCBB" d="M10.76,11.41v-1.3c0-.45-.17-.67-.52-.68s-.54.22-.54.69v1.67h1.06Z" />
|
|
||||||
<path fill="#70CCBB"
|
|
||||||
d="M14.3,11.41c0-.44,0-.88,0-1.3s-.17-.67-.51-.68-.55.22-.55.69v1.67H14.3Z" />
|
|
||||||
<path fill="#212529"
|
|
||||||
d="M9.86,13.86h4.28a0,0,0,0,1,0,0v2a1,1,0,0,1-1,1H10.86a1,1,0,0,1-1-1v-2A0,0,0,0,1,9.86,13.86Z" />
|
|
||||||
</g>
|
|
||||||
<g id="heatIcon" filter="url(#shadow)" transform="translate(52,70),scale(4,4)" fill="#C2614E">
|
|
||||||
<path clip-path="M-22 2.24h42V22h-42z"
|
|
||||||
d="M16.543 8.028c-.023 1.503-.523 3.538-2.867 4.327.734-1.746.846-3.417.326-4.979-.695-2.097-3.014-3.735-4.557-4.627-.527-.306-1.203.074-1.193.683.02 1.112-.318 2.737-1.959 4.378C4.107 9.994 3 12.251 3 14.517 3 17.362 5 21 9 21c-4.041-4.041-1-7.483-1-7.483C8.846 19.431 12.988 21 15 21c1.711 0 5-1.25 5-6.448 0-3.133-1.332-5.511-2.385-6.899-.347-.458-1.064-.198-1.072.375" />
|
|
||||||
</g>
|
|
||||||
<g id="egIcon" filter="url(#shadow)" transform="translate(52,50),scale(4,4)">
|
|
||||||
<rect stroke="#70CC9C" stroke-width="2" fill="none" x="3" y="3" width="18" height="18" rx="2" />
|
|
||||||
<path fill="#70CC9C"
|
|
||||||
d="M8.55,9.2v3.47a2.11,2.11,0,0,0,2.13,2.08h.67s0,3.22,0,3.26h1.49s0-3.22,0-3.26h.77a1.81,1.81,0,0,0,1.8-1.84l0-3.71" />
|
|
||||||
<path fill="#70CC9C" d="M10.76,8V6.67c0-.45-.17-.66-.52-.67s-.54.22-.54.68V8.34h1.06Z" />
|
|
||||||
<path fill="#70CC9C" d="M14.3,8c0-.44,0-.87,0-1.3S14.12,6,13.78,6s-.55.22-.55.68V8.34H14.3Z" />
|
|
||||||
<path fill="#212529"
|
|
||||||
d="M9.86,10.41h4.28a0,0,0,0,1,0,0v2a1,1,0,0,1-1,1H10.86a1,1,0,0,1-1-1v-2A0,0,0,0,1,9.86,10.41Z" />
|
|
||||||
</g>
|
|
||||||
<g id="ugIcon" filter="url(#shadow)" transform="translate(52,50),scale(4,4)">
|
|
||||||
<path
|
|
||||||
style="fill:none;stroke:#83a55b;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"
|
|
||||||
d="M 2.977125,13.982235 V 2.973455 h 17.952227 l 0.03597,16.261337"
|
|
||||||
id="path12" />
|
|
||||||
<path fill="#83A55B"
|
|
||||||
d="M8.55,9.2v3.47a2.11,2.11,0,0,0,2.13,2.08h.67s0,3.22,0,3.26h1.49s0-3.22,0-3.26h.77a1.81,1.81,0,0,0,1.8-1.84l0-3.71" />
|
|
||||||
<path fill="#83A55B" d="M10.76,8V6.67c0-.45-.17-.66-.52-.67s-.54.22-.54.68V8.34h1.06Z" />
|
|
||||||
<path fill="#83A55B" d="M14.3,8c0-.44,0-.87,0-1.3S14.12,6,13.78,6s-.55.22-.55.68V8.34H14.3Z" />
|
|
||||||
<path fill="#212529"
|
|
||||||
d="M9.86,10.41h4.28a0,0,0,0,1,0,0v2a1,1,0,0,1-1,1H10.86a1,1,0,0,1-1-1v-2A0,0,0,0,1,9.86,10.41Z" />
|
|
||||||
<path
|
|
||||||
style="fill:none;stroke:#83a55b;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;"
|
|
||||||
d="M 23.96101,19.98372 C 11.24143,20.28899 11.139675,15.0994 -3.106255,14.081835" />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<g id="carIcon" filter="url(#shadow)" transform="translate(45,45),scale(4.8,4.8)"
|
|
||||||
fill="#212529" stroke-linecap="round">
|
|
||||||
<path d="M16.22,15.37l-9.3-.16L3,15,.88,14.47.75,10.39A2.78,2.78,0,0,1,1.31,9.2L2.55,7.63" />
|
|
||||||
<path
|
|
||||||
d="M.78,7.46,7,7l.87,0L11.37,7a5.57,5.57,0,0,1,1.06.23,9.43,9.43,0,0,1,2.16,1L17.23,9.9l4.22.7a1.73,1.73,0,0,1,1.21.93l.26.49a2.84,2.84,0,0,1,.32,1.6l0,.23A1.52,1.52,0,0,1,22.53,15h0a1.53,1.53,0,0,1-.56.21l-.89.15H15.59" />
|
|
||||||
<path fill="#888888" stroke="none"
|
|
||||||
d="M16.06,11c-1.85.11-3.7.14-5.55.16a13.36,13.36,0,0,1-2.82,0,2.62,2.62,0,0,1-.87-.4,1.28,1.28,0,0,1-.53-1.05,1.4,1.4,0,0,1,.09-.51,2.67,2.67,0,0,1,.22-.35,2.79,2.79,0,0,1,.5-.58s.09,0,.11,0a.06.06,0,0,1,0,.05h0c0,.25,0,.49,0,.71s0,.23,0,.31a.75.75,0,0,0,.08.18c.07.09.17.14.17.08a1.81,1.81,0,0,0,.43.05,14.41,14.41,0,0,1,2.68.33c1.82.26,3.64.53,5.46.88a.08.08,0,0,1,.06.09A.07.07,0,0,1,16.06,11Z" />
|
|
||||||
<circle cx="4.94" cy="15.08" r="1.98" />
|
|
||||||
<circle cx="19.19" cy="15.08" r="1.98" />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<g id="plugCordIcon" transform="scale(0.08,0.08),rotate(340),translate(-350,1200)">
|
|
||||||
<path
|
|
||||||
d="M489.8,280.92c0-13.9-11.3-25.2-25.2-25.2h-74.1c-62.1,0-112.4,50.2-112.7,112.2c-0.1,32.3-23,61-55,65
|
|
||||||
c-38.4,4.8-71.2-25.2-71.2-62.6v-56.4c57.6-11.7,101-62.6,101-123.7v-64c0-9.1-7.3-16.4-16.4-16.4h-31.3v-79.1
|
|
||||||
c0-12.4-9-23.4-21.3-24.6c-14.1-1.4-26,9.7-26,23.6v80.1H95.2v-79.1c0-12.4-9-23.4-21.4-24.6c-14.1-1.3-26,9.7-26,23.6v80.1H16.4
|
|
||||||
c-9.1,0-16.4,7.3-16.4,16.4v64c0,61.1,43.4,112,101,123.7v53.5c0,61.5,47.7,114,109.2,116.3c64.6,2.4,118-49.4,118-113.5v-0.9
|
|
||||||
c0-34.9,28.3-63.1,63.1-63.1h73.2C478.5,306.12,489.8,294.82,489.8,280.92L489.8,280.92z" />
|
|
||||||
</g>
|
|
||||||
<g id="lockIcon" transform="translate(80,130),scale(0.11,0.11)" style="">
|
|
||||||
<path
|
|
||||||
d="M65,330h200c8.284,0,15-6.716,15-15V145c0-8.284-6.716-15-15-15h-15V85c0-46.869-38.131-85-85-85
|
|
||||||
S80,38.131,80,85v45H65c-8.284,0-15,6.716-15,15v170C50,323.284,56.716,330,65,330z M207.481,219.356l-42.5,42.5
|
|
||||||
c-2.929,2.929-6.768,4.394-10.606,4.394s-7.678-1.465-10.606-4.394l-21.25-21.25c-5.858-5.858-5.858-15.354,0-21.213
|
|
||||||
c5.857-5.858,15.355-5.858,21.213,0l10.644,10.643l31.894-31.893c5.857-5.858,15.355-5.858,21.213,0
|
|
||||||
C213.34,204.002,213.34,213.498,207.481,219.356z M110,85c0-30.327,24.673-55,55-55s55,24.673,55,55v45H110V85z"
|
|
||||||
style="stroke-width:15px;stroke:rgb(30,30,40);fill:#778;" />
|
|
||||||
</g>
|
|
||||||
<g filter="url(#shadow)" id="clock" style="fill:#cc0;"
|
|
||||||
transform="translate(80,32),scale(0.07,0.07)">
|
|
||||||
<path
|
|
||||||
d="m256,51.8c-112.6,0-204.2,91.6-204.2,204.2s91.6,204.2 204.2,204.2 204.2-91.6 204.2-204.2-91.6-204.2-204.2-204.2v-1.42109e-14zm0,449.2c-135.1,0-245-109.9-245-245s109.9-245 245-245 245,109.9 245,245-109.9,245-245,245z" />
|
|
||||||
<path
|
|
||||||
d="m327.9,276.4h-71.9c-11.3,0-20.4-9.1-20.4-20.4v-132.2c0-11.3 9.1-20.4 20.4-20.4 11.3,0 20.4,9.1 20.4,20.4v111.8h51.5c11.3,0 20.4,9.1 20.4,20.4s-9.1,20.4-20.4,20.4z" />
|
|
||||||
</g>
|
|
||||||
<g id="sunIcon" filter="url(#shadow)" transform="translate(30,27),scale(6,6)"
|
|
||||||
style="stroke:#f7c000;stroke-width:2;stroke-linecap:round;">
|
|
||||||
<path id="Pfad_7565" data-name="Pfad 7565" fill="#f7c002" stroke-width="0"
|
|
||||||
d="M12,8a4,4,0,1,0,4,4A4,4,0,0,0,12,8Z" />
|
|
||||||
<line x1="12" y1="3" x2="12" y2="5" />
|
|
||||||
<line x1="12" y1="19" x2="12" y2="21" />
|
|
||||||
<line x1="19" y1="12" x2="21" y2="12" />
|
|
||||||
<line x1="3" y1="12" x2="5" y2="12" />
|
|
||||||
<line x1="5.7" y1="18.5" x2="7" y2="17" />
|
|
||||||
<line x1="5.7" y1="5.7" x2="7" y2="7" />
|
|
||||||
<line x1="18.5" y1="18.5" x2="16.7" y2="17" />
|
|
||||||
<line x1="18.5" y1="5.7" x2="16.7" y2="7" />
|
|
||||||
</g>
|
|
||||||
<g id="battIcon" filter="url(#shadow)" transform="translate(30,25),scale(6,6)">
|
|
||||||
<g>
|
|
||||||
<rect fill="#6cbe58" x="9.25" y="3" width="5.5" height="4" rx="1" ry="1" />
|
|
||||||
<path fill="#6cbe58"
|
|
||||||
d="M16.28,5H7.72A1.21,1.21,0,0,0,6.5,6.2V19.73A1.28,1.28,0,0,0,7.72,21h8.56a1.28,1.28,0,0,0,1.22-1.27V6.2A1.21,1.21,0,0,0,16.28,5Z" />
|
|
||||||
<rect id="discharged" fill="#212529" rx="1" ry="1" x="8" y="6.5" width="8"
|
|
||||||
height="13" />
|
|
||||||
<use id="charge" fill="#cccc00" href="#voltage" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g id="gridIcon" filter="url(#shadow)" transform="translate(30,27),scale(6,6)">
|
|
||||||
<path fill="#808090"
|
|
||||||
d="M13.38,8.54V6.63H18.3V8.06h.95V5.7l-6.35-1L12,2l-1,2.8-6.25.9V8.07H5.6V6.63h4.84V8.54l-7.59,1.9v2.84H3.8V11.76H9.87L6.93,22H9.11l.57-2.46h4.59L14.8,22h2.27L14.13,11.76H20.2v1.52h.95V10.44Zm-3.32,8.81L12,11.11l1.91,6.24Z" />
|
|
||||||
</g>
|
|
||||||
<g id="inverterIcon" filter="url(#shadowBig)" fill="#b2b2b2" stroke-width="2px" stroke="#666"
|
|
||||||
transform="translate(43,43),scale(1.6,1.6)">
|
|
||||||
<rect stroke-width="2px" x="5.65" y="3" width="58.7" height="64" rx="6.8" />
|
|
||||||
<circle cx="35.03" cy="29.61" r="11" />
|
|
||||||
<circle cx="15" cy="40" r="4" />
|
|
||||||
<path
|
|
||||||
d="M40.19,52H36.73a2,2,0,0,0-3.46,0H29.81a1.08,1.08,0,1,0,0,2.15h3.46a2,2,0,0,0,3.46,0h3.46a1.08,1.08,0,1,0,0-2.15Z" />
|
|
||||||
<path fill="#e2001a"
|
|
||||||
d="M34.89,61.87c-3.24,0-5.87-.73-5.87-1.62s2.63-1.64,5.87-1.64,5.87.74,5.87,1.64S38.13,61.87,34.89,61.87Z" />
|
|
||||||
<line x1="5.07" y1="53.08" x2="28.73" y2="53.08" />
|
|
||||||
<line x1="41.27" y1="53.08" x2="64.93" y2="53.08" />
|
|
||||||
</g>
|
|
||||||
<g id="northIcon" fill="#808090">
|
|
||||||
<path
|
|
||||||
d="M42.066 0v20h3.752V6.957L53.881 20h4.053V0h-3.752v13.355L45.996 0zm5.57 26.688l-25.77 69.949c-.823 2.238 1.658 4.248 3.677 2.978L50 84.195l24.455 15.42c2.02 1.273 4.504-.738 3.68-2.978l-25.79-70c-.472-1.096-1.283-1.632-2.384-1.635c-1.1-.003-2.017.856-2.324 1.686zm-.136 14.83V79.86L29.1 91.463z"></path>
|
|
||||||
</g>
|
|
||||||
<g filter="url(#shadow)" id="ecoIcon" transform="translate(60,12)">
|
|
||||||
<path style="stroke:#6cbe58;fill:rgb(30,30,40);" stroke-width="3" stroke-linejoin="round"
|
|
||||||
d="M 57,22.1667C 49.0833,28.5 52.25,31.6667 50.6667,36.4167C 49.0833,41.1667 50.6667,42.75 44.3333,47.5C 36.2227,53.583 29.2917,49.875 29.2917,49.875C 27.7083,51.4583 21.7708,54.2292 21.7708,54.2292L 20.0143,50.7285C 26.3476,49.1452 27.7083,47.1042 27.7083,47.1042C 26.125,43.9375 23.75,38 28.5,30.0833C 33.25,22.1667 57,22.1667 57,22.1667 Z M 30.0833,44.3333L 31.6667,45.9167C 31.6667,45.9167 34.8333,44.3333 36.4167,39.5833C 38,34.8333 45.9167,26.9167 45.9167,26.9167C 42.75,28.5 36.4167,34.8333 34.8333,39.5833C 33.25,44.3333 30.0833,44.3333 30.0833,44.3333 Z " />
|
|
||||||
</g>
|
|
||||||
<g filter="url(#shadow)" id="alertIcon" fill="#aa0" transform="translate(34,28),scale(2.2,2.2)">
|
|
||||||
<path
|
|
||||||
d="M214.413,746a4.455,4.455,0,0,1-3.84-2.166,4.249,4.249,0,0,1,0-4.334l25.572-43.331a4.483,4.483,0,0,1,7.679,0L269.4,739.5a4.249,4.249,0,0,1,0,4.334,4.452,4.452,0,0,1-3.84,2.166H214.413ZM240,706a4,4,0,0,0-4,4v16a4,4,0,0,0,8,0V710A4,4,0,0,0,240,706Zm0,36a4,4,0,1,0-4-4A4,4,0,0,0,240,742Z"
|
|
||||||
id="attention" transform="translate(-209.969 -694)" />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
</defs>
|
|
||||||
|
|
||||||
<g id="pvInfo" class="clickable" transform="translate(330,280)">
|
|
||||||
<animateMotion begin="0.5s" dur="2s" repeatCount="1" calcMode="spline" keyTimes="0;1"
|
|
||||||
keySplines=".1,0,.27,1" path="M0 0 L-250 -250" fill="freeze">
|
|
||||||
</animateMotion>
|
|
||||||
<animate id="openDetails" begin="click" attributetype="CSS" attributeName="opacity" from="1"
|
|
||||||
to="1"
|
|
||||||
dur="0.1s" repeatCount="1" calcMode="spline"
|
|
||||||
fill="freeze">
|
|
||||||
</animate>
|
|
||||||
<!--<g filter="url(#glow)">-->
|
|
||||||
<use href="#ElementBG" stroke="#574400" />
|
|
||||||
<use href="#sunIcon" />
|
|
||||||
<path id="pvArc" fill="none" stroke="#f7c000" stroke-width="30" />
|
|
||||||
<path id="pv2Arc" fill="none" stroke="#f78d00" stroke-width="30" />
|
|
||||||
<path id="pv3Arc" fill="none" stroke="#f72300" stroke-width="30" />
|
|
||||||
<use href="#circGrid" style="stroke:#574400;" />
|
|
||||||
<!--</g>-->
|
|
||||||
<text filter="url(#shadowMiddle)" class="powerTextClass">
|
|
||||||
<textPath id="pvText" href="#powerTextPath" startOffset="15%">--.-- kW</textPath>
|
|
||||||
<textPath id="pv3txt" href="#infoTextPath" startOffset="12%">--.-- kW</textPath>
|
|
||||||
<textPath id="pv2txt" href="#infoTextPath" startOffset="35%">--.-- kW</textPath>
|
|
||||||
<textPath id="pv1txt" href="#infoTextPath" startOffset="55%">--W</textPath>
|
|
||||||
</text>
|
|
||||||
</g>
|
|
||||||
<circle id="closeDetails" fill="transparent" transform="translate(250,124)">
|
|
||||||
<animate begin="click" attributetype="CSS" attributeName="r" from="120" to="0" dur="0.1s"
|
|
||||||
repeatCount="1"
|
|
||||||
fill="freeze" />
|
|
||||||
<animate begin="pvInfo.click" attributeName="r" from="0" to="120" dur="0.1s" repeatCount="1"
|
|
||||||
fill="freeze" />
|
|
||||||
</circle>
|
|
||||||
<!--NON-PV-->
|
|
||||||
<g id="nonPVGroup">
|
|
||||||
<animate begin="pvInfo.click" attributetype="CSS" attributeName="opacity" from="1.0" to="0.0"
|
|
||||||
dur="1s"
|
|
||||||
repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1" fill="freeze" />
|
|
||||||
<animate begin="closeDetails.click" attributetype="CSS" attributeName="opacity" from="0.0"
|
|
||||||
to="1.0"
|
|
||||||
dur="1s" repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1"
|
|
||||||
fill="freeze" />
|
|
||||||
<g transform="translate(330,280), scale(0.90,0.90)" id="heatInfo" class="clickable" onclick="openModal('heater')">
|
|
||||||
<animateMotion begin="0.5s" dur=".7s" repeatCount="1" calcMode="spline" keyTimes="0;1"
|
|
||||||
keySplines=".1,0,.27,1" path="M0 0 L340 0" fill="freeze"></animateMotion>
|
|
||||||
<animateMotion begin="1.2s" dur=".7s" repeatCount="1" calcMode="spline" keyTimes="0;1"
|
|
||||||
keySplines=".6,0,.27,1" path="M340 0 L700 0" fill="freeze"></animateMotion>
|
|
||||||
<!--<g
|
|
||||||
filter="url(#glow)">-->
|
|
||||||
<use href="#ElementBG" stroke="#5E2B21" />
|
|
||||||
<path fill="url(#gradient1)" stroke="#5E2B21" stroke-width="4"
|
|
||||||
d="M 99.86386438745993 22.00011880076356 A 78 78 0 1 0 100 22" />
|
|
||||||
<use href="#heatIcon" />
|
|
||||||
<path id="heatArc" fill="none" stroke="#C2614E" stroke-width="30" />
|
|
||||||
<use href="#circGrid6kW" style="stroke:#5E2B21;" />
|
|
||||||
<!--</g>-->
|
|
||||||
<text filter="url(#shadowMiddle)" class="powerTextClass">
|
|
||||||
<textPath id="heatText" href="#powerTextPath" startOffset="15%">--.-- kW</textPath>
|
|
||||||
<textPath id="heatUp" href="#infoTextPath" startOffset="9%">--°C</textPath>
|
|
||||||
<textPath id="heatDown" href="#infoTextPath" startOffset="36%">--°C</textPath>
|
|
||||||
</text>
|
|
||||||
<use id="heatEco" href="#ecoIcon" style="display:none;" />
|
|
||||||
<use id="heatDefault" href="#alertIcon" style="display:none;" />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<g transform="translate(330,280), scale(0.90,0.90)" class="clickable" id="CarInfoOG" onclick="openModal('CarOG')">
|
|
||||||
<animateMotion begin="0.5s" dur=".7s" repeatCount="1" calcMode="spline" keyTimes="0;1"
|
|
||||||
keySplines=".1,0,.27,1" path="M0 0 L340 0" fill="freeze" />
|
|
||||||
<animateMotion id="lastInitAni" begin="1.2s" dur=".7s" repeatCount="1" calcMode="spline"
|
|
||||||
keyTimes="0;1" keySplines=".6,0,.27,1" path="M340 0 L800 -300" fill="freeze" />
|
|
||||||
<!--<g
|
|
||||||
filter="url(#glow)">-->
|
|
||||||
<use href="#ElementBG" stroke="#182C43" />
|
|
||||||
<!--<path
|
|
||||||
filter="url(#fillpartialEV)" fill="#212529" stroke="#642445" stroke-width="4" d="M 99.863863
|
|
||||||
22.00012 A 78 78 0 1 0 100 22" />-->
|
|
||||||
<use href="#carIcon" stroke="#4b71a6" />
|
|
||||||
|
|
||||||
|
|
||||||
<use id="evChargeOG" href="#voltage" transform="translate(167,45),rotate(90),scale(5,5)"
|
|
||||||
style="fill:#cccc00; display:none;" />
|
|
||||||
<path id="evArcOG" fill="none" stroke="#4b71a6" stroke-width="30" />
|
|
||||||
<use href="#circGrid" style="stroke:#182C43;" />
|
|
||||||
<!--</g>-->
|
|
||||||
<use id="evPlugOG" href="#plugCordIcon" style="fill:#4b71a6;display:none;" />
|
|
||||||
<!--<use
|
|
||||||
href="#voltage" style="fill:#888;" transform="scale(3, 3),translate(7,32), rotate(50)" />-->
|
|
||||||
<!--<use
|
|
||||||
href="#gas" style="fill:#888;" transform="scale(0.55, 0.55),translate(210,319), rotate(337)"
|
|
||||||
/>-->
|
|
||||||
<text filter="url(#shadowMiddle)" class="powerTextClass">
|
|
||||||
<textPath id="evTextOG" href="#powerTextPath" startOffset="15%">--.-- kW</textPath>
|
|
||||||
<!--<textPath
|
|
||||||
id="evFuelOG" href="#infoTextPath" startOffset="40%">-%</textPath>-->
|
|
||||||
<!--<textPath
|
|
||||||
id="evSOCOG" href="#infoTextPath" startOffset="15%">-%</textPath>-->
|
|
||||||
</text>
|
|
||||||
<!--<use
|
|
||||||
id="evLockOG" href="#lockIcon" style="display:none;" />-->
|
|
||||||
<use id="evEcoOG" href="#ecoIcon" style="display:none;" />
|
|
||||||
<use id="evDefaultOG" href="#plugCordIcon" transform="translate(105,-65),rotate(20)"
|
|
||||||
style="fill:#a53;display:none;" />
|
|
||||||
<use id="evNextTripOG" href="#clock" style="display:none;" />
|
|
||||||
<text dy="160" dx="100" id="evRemText" class="powerTextClass" />
|
|
||||||
</g>
|
|
||||||
<g transform="translate(330,280), scale(0.90,0.90)" class="clickable" id="CarInfo" onclick="openModal('CarEG')">
|
|
||||||
|
|
||||||
<animateMotion begin="0.5s" dur=".7s" repeatCount="1" calcMode="spline" keyTimes="0;1"
|
|
||||||
keySplines=".1,0,.27,1" path="M0 0 L340 0" fill="freeze" />
|
|
||||||
<animateMotion id="lastInitAni" begin="1.2s" dur=".7s" repeatCount="1" calcMode="spline"
|
|
||||||
keyTimes="0;1" keySplines=".6,0,.27,1" path="M340 0 L800 300" fill="freeze" />
|
|
||||||
<!--<g
|
|
||||||
filter="url(#glow)">-->
|
|
||||||
<use href="#ElementBG" stroke="#1f5a66" />
|
|
||||||
<path filter="url(#fillpartialEV)" fill="#212529" stroke="#1f5a66" stroke-width="4"
|
|
||||||
d="M 99.863863 22.00012 A 78 78 0 1 0 100 22" />
|
|
||||||
<use href="#carIcon" stroke="#60afbf" />
|
|
||||||
|
|
||||||
|
|
||||||
<use id="evCharge" href="#voltage" transform="translate(167,45),rotate(90),scale(5,5)"
|
|
||||||
style="fill:#cccc00; display:none;" />
|
|
||||||
<path id="evArc" fill="none" stroke="#60afbf" stroke-width="30" />
|
|
||||||
<use href="#circGrid" style="stroke:#1f5a66;" />
|
|
||||||
<use id="evPlug" href="#plugCordIcon" style="fill:#60afbf;display:none;" />
|
|
||||||
<!--</g>-->
|
|
||||||
<use href="#voltage" style="fill:#888;" transform="scale(3, 3),translate(7,32), rotate(50)" />
|
|
||||||
<use href="#gas" style="fill:#888;"
|
|
||||||
transform="scale(0.55, 0.55),translate(210,319), rotate(337)" />
|
|
||||||
<text filter="url(#shadowMiddle)" class="powerTextClass">
|
|
||||||
<textPath id="evText" href="#powerTextPath" startOffset="15%">--.-- kW</textPath>
|
|
||||||
<textPath id="evFuel" href="#infoTextPath" startOffset="40%">--%</textPath>
|
|
||||||
<textPath id="evSOC" href="#infoTextPath" startOffset="15%">--%</textPath>
|
|
||||||
</text>
|
|
||||||
<use id="evLock" href="#lockIcon" style="display:none;" />
|
|
||||||
<use id="evEco" href="#ecoIcon" style="display:none;" />
|
|
||||||
<use id="evDefault" href="#plugCordIcon" transform="translate(105,-65),rotate(20)"
|
|
||||||
style="fill:#a53;display:none;" />
|
|
||||||
<use id="evNextTrip" href="#clock" style="display:none;" />
|
|
||||||
<text dy="160" dx="100" id="evRemText" class="powerTextClass" />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<g transform="translate(330,280), scale(0.90,0.90)" id="UGInfo">
|
|
||||||
<animateMotion begin="0.5s" dur=".7s" repeatCount="1" calcMode="spline" keyTimes="0;1"
|
|
||||||
keySplines=".1,0,.27,1" path="M0 0 L340 0" fill="freeze" />
|
|
||||||
<animateMotion begin="1.2s" dur=".7s" repeatCount="1" calcMode="spline" keyTimes="0;1"
|
|
||||||
keySplines=".6,0,.27,1" path="M340 0 L180 300" fill="freeze" />
|
|
||||||
<!--<g
|
|
||||||
filter="url(#glow)">-->
|
|
||||||
<use href="#ElementBG" stroke="#314619" />
|
|
||||||
<use href="#ugIcon" />
|
|
||||||
<path id="ugArc" fill="none" stroke="#83A55B" stroke-width="30" />
|
|
||||||
<use href="#circGrid" style="stroke:#314619;" />
|
|
||||||
<!--</g>-->
|
|
||||||
<text filter="url(#shadowMiddle)" class="powerTextClass">
|
|
||||||
<textPath id="ugText" href="#powerTextPath" startOffset="15%">--.-- kW</textPath>
|
|
||||||
</text>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<g transform="translate(330,280), scale(0.90,0.90)" id="EGInfo">
|
|
||||||
<animateMotion begin="0.5s" dur=".7s" repeatCount="1" calcMode="spline" keyTimes="0;1"
|
|
||||||
keySplines=".1,0,.27,1" path="M0 0 L340 0" fill="freeze" />
|
|
||||||
<animateMotion begin="1.2s" dur=".7s" repeatCount="1" calcMode="spline" keyTimes="0;1"
|
|
||||||
keySplines=".6,0,.27,1" path="M340 0 L500 300" fill="freeze" />
|
|
||||||
<!--<g
|
|
||||||
filter="url(#glow)">-->
|
|
||||||
<use href="#ElementBG" stroke="#18423B" />
|
|
||||||
<use href="#egIcon" />
|
|
||||||
<path id="egArc" fill="none" stroke="#70CC9C" stroke-width="30" />
|
|
||||||
<use href="#circGrid" style="stroke:#18423B;" />
|
|
||||||
<!--</g>-->
|
|
||||||
<text filter="url(#shadowMiddle)" class="powerTextClass">
|
|
||||||
<textPath id="egText" href="#powerTextPath" startOffset="15%">--.-- kW</textPath>
|
|
||||||
</text>
|
|
||||||
<use id="heatEG" transform="translate(120,170)"
|
|
||||||
style='display:none;mix-blend-mode: difference;fill:#70CCBB;' href="#heater" />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<g transform="translate(330,280), scale(0.90,0.90)" id="waterInfo" class="clickable" onclick="openModal('watering')">
|
|
||||||
<animateMotion begin="0.5s" dur=".7s" repeatCount="1" calcMode="spline" keyTimes="0;1"
|
|
||||||
keySplines=".1,0,.27,1" path="M0 0 L340 0" fill="freeze" />
|
|
||||||
<animateMotion begin="1.2s" dur=".7s" repeatCount="1" calcMode="spline" keyTimes="0;1"
|
|
||||||
keySplines=".6,0,.27,1" path="M340 0 L120 -300" fill="freeze" />
|
|
||||||
<use href="#ElementBG" stroke="#182C43" />
|
|
||||||
<path filter="url(#fillpartialWater)" fill="#212529" stroke="#182C43" stroke-width="4"
|
|
||||||
d="M 99.863863 22.00012 A 78 78 0 1 0 100 22" />
|
|
||||||
<use href="#water" transform="translate(60,55), scale(0.20,0.20)" fill="#3A6292" />
|
|
||||||
<!-- Wasserstand und Temperatur werden von der Bewässerungssteuerung nicht unterstützt.
|
|
||||||
<text id="waterText" filter="url(#shadowMiddle)" class="powerTextClass" text-anchor="middle" x="100" y="55">-.- cm</text>
|
|
||||||
<text id="waterTemp" filter="url(#shadowMiddle)" class="powerTextClass" text-anchor="middle" x="100" y="88">-.- °C</text>
|
|
||||||
-->
|
|
||||||
<use id="wateringFrontIcon" href="#gardenPlantIcon" transform="translate(152,162),rotate(310),scale(1.3)" fill="#6c757d" color="#6c757d">
|
|
||||||
<title>Vorn: aus</title>
|
|
||||||
</use>
|
|
||||||
<use id="wateringTroughIcon" href="#flowerPotIcon" transform="translate(18,100),rotate(75),scale(1.3)" fill="#6c757d" color="#6c757d">
|
|
||||||
<title>Tröge: aus</title>
|
|
||||||
</use>
|
|
||||||
<use id="wateringBedsIcon" href="#raisedBedIcon" transform="translate(66,174),rotate(15),scale(1.3)" fill="#6c757d" color="#6c757d">
|
|
||||||
<title>Beete: aus</title>
|
|
||||||
</use>
|
|
||||||
<text filter="url(#shadowMiddle)" class="powerTextClass">
|
|
||||||
<textPath id="wateringFrontStatus" href="#infoTextPath" startOffset="46%">Aus </textPath>
|
|
||||||
<textPath id="wateringTroughStatus" href="#infoTextPath" startOffset="10%">Aus </textPath>
|
|
||||||
<textPath id="wateringBedsStatus" href="#infoTextPath" startOffset="28%">Aus </textPath>
|
|
||||||
</text>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<g transform="translate(330,280), scale(0.90,0.90)" id="OGInfo">
|
|
||||||
<animateMotion begin="0.5s" dur=".7s" repeatCount="1" calcMode="spline" keyTimes="0;1"
|
|
||||||
keySplines=".1,0,.27,1" path="M0 0 L340 0" fill="freeze" />
|
|
||||||
<animateMotion begin="1.2s" dur=".7s" repeatCount="1" calcMode="spline" keyTimes="0;1"
|
|
||||||
keySplines=".6,0,.27,1" path="M340 0 L500 -300" fill="freeze" />
|
|
||||||
<use href="#ElementBG" stroke="#18423B" />
|
|
||||||
<use href="#ogIcon" />
|
|
||||||
<path id="ogArc" fill="none" stroke="#70CCBB" stroke-width="30" />
|
|
||||||
<use href="#circGrid" style="stroke:#18423B;" />
|
|
||||||
<text filter="url(#shadowMiddle)" class="powerTextClass">
|
|
||||||
<textPath id="ogText" href="#powerTextPath" startOffset="15%">--.-- kW</textPath>
|
|
||||||
</text>
|
|
||||||
<use id="heatOG" transform="translate(120,165)"
|
|
||||||
style='display:none;mix-blend-mode: difference;fill:#70CCBB;' href="#heater" />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<g transform="translate(330,280)">
|
|
||||||
<animateMotion begin="0.5s" dur=".7s" repeatCount="1" calcMode="spline" keyTimes="0;1"
|
|
||||||
keySplines=".1,0,.27,1" path="M0 0 L340 0" fill="freeze" />
|
|
||||||
<!--<g
|
|
||||||
filter="url(#glow)">-->
|
|
||||||
<use href="#ElementBG" stroke="#1D4154" />
|
|
||||||
<use href="#plugIcon" />
|
|
||||||
<path id="consumerArc" fill="none" stroke="#71afcd" stroke-width="30" />
|
|
||||||
<use href="#circGrid" style="stroke:#1D4154;" />
|
|
||||||
<!--</g>-->
|
|
||||||
<text filter="url(#shadowMiddle)" class="powerTextClass">
|
|
||||||
<textPath id="consumerText" href="#powerTextPath" startOffset="15%">--.-- kW</textPath>
|
|
||||||
<textPath id="consumerTextAllg" href="#infoTextPath" startOffset="25%">--.-- kW</textPath>
|
|
||||||
<!--<textPath id="consumerTextEG" href="#infoTextPath" startOffset="35%">.- kW</textPath>
|
|
||||||
<textPath id="consumerTextUG" href="#infoTextPath" startOffset="55%">-.- kW</textPath> -->
|
|
||||||
</text>
|
|
||||||
</g>
|
|
||||||
<g transform="translate(330,280)">
|
|
||||||
<animateMotion begin="0.5s" dur="1.5s" repeatCount="1" calcMode="spline" keyTimes="0;1"
|
|
||||||
keySplines=".1,0,.27,1" path="M0 0 L-250 250" fill="freeze" />
|
|
||||||
<!--<g
|
|
||||||
filter="url(#glow)">-->
|
|
||||||
<use href="#ElementBG" stroke="#25481C" />
|
|
||||||
<path id="batArc" fill="none" stroke="#6cbe58" stroke-width="30" />
|
|
||||||
<path filter="url(#fillpartialBat)" fill="#212529" stroke="#25481C" stroke-width="4"
|
|
||||||
d="M 99.863863 22.00012 A 78 78 0 1 0 100 22" />
|
|
||||||
<use href="#battIcon" />
|
|
||||||
<use href="#circGrid" style="stroke:#25481C;" />
|
|
||||||
<use id="batLim" href="#clock" style="" transform="translate(4,40)" />
|
|
||||||
<!--</g>-->
|
|
||||||
<text id="batLimText" filter="url(#shadowMiddle)" class="infoTextClass"
|
|
||||||
transform="translate(103,132)"></text>
|
|
||||||
<text filter="url(#shadowMiddle)" class="powerTextClass">
|
|
||||||
<textPath id="batText" href="#powerTextPath" startOffset="15%">--.-- kW</textPath>
|
|
||||||
<textPath id="batSOC" href="#infoTextPath" startOffset="26%">--.- %</textPath>
|
|
||||||
</text>
|
|
||||||
</g>
|
|
||||||
<g transform="translate(330,280)">
|
|
||||||
<animateMotion begin="0.5s" dur="1s" repeatCount="1" calcMode="spline" keyTimes="0;1"
|
|
||||||
keySplines=".1,0,.27,1" path="M0 0 L-330 0" fill="freeze" />
|
|
||||||
<!--<g
|
|
||||||
filter="url(#glow)">-->
|
|
||||||
<use href="#ElementBG" stroke="#2D2D34" />
|
|
||||||
<path id="gridArc" fill="none" stroke="#808090" stroke-width="30" />
|
|
||||||
<use href="#circGrid" style="stroke:#2D2D34;" />
|
|
||||||
<!--</g>-->
|
|
||||||
<text filter="url(#shadowMiddle)" class="powerTextClass">
|
|
||||||
<textPath id="gridText" href="#powerTextPath" startOffset="15%">--.-- kW</textPath>
|
|
||||||
<textPath id="il1txt" href="#infoTextPath" startOffset="10%">--.- A</textPath>
|
|
||||||
<textPath id="il2txt" href="#infoTextPath" startOffset="25%">--.- A</textPath>
|
|
||||||
<textPath id="il3txt" href="#infoTextPath" startOffset="40%">--.- A</textPath>
|
|
||||||
</text>
|
|
||||||
<use href="#gridIcon" />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<g transform="translate(330,280), scale(1.1,1.1)" class="tooltip-trigger"
|
|
||||||
data-tooltip-text="The amazing pink box">
|
|
||||||
<!--<g
|
|
||||||
filter="url(#glow)">-->
|
|
||||||
<use href="#ElementBG" stroke="#2D2D34" />
|
|
||||||
<!--</g>-->
|
|
||||||
<text filter="url(#shadowMiddle)" class="powerTextClass">
|
|
||||||
<textPath id="genText" href="#powerTextPath" startOffset="22%">Autarkie: </textPath>
|
|
||||||
<textPath id="genInfoText" href="#infoTextPath" startOffset="25%"></textPath>
|
|
||||||
|
|
||||||
</text>
|
|
||||||
<use href="#inverterIcon" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<g id="pvDetail" opacity="0" transform="scale(0.1,0.1)">
|
|
||||||
<animate begin="openDetails.begin" attributetype="CSS" attributeName="opacity" from="0" to="1.0"
|
|
||||||
dur="0.7s" repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1"
|
|
||||||
fill="freeze" />
|
|
||||||
<animateTransform attributeName="transform"
|
|
||||||
type="scale"
|
|
||||||
from="0.1,0.1"
|
|
||||||
to="1,1"
|
|
||||||
begin="openDetails.begin"
|
|
||||||
dur="0.3s" repeatCount="1" fill="freeze" />
|
|
||||||
<animate begin="closeDetails.click" attributetype="CSS" attributeName="opacity" from="1.0"
|
|
||||||
to="0"
|
|
||||||
dur="0.4s" repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1"
|
|
||||||
fill="freeze" />
|
|
||||||
<animateTransform attributeName="transform"
|
|
||||||
type="scale"
|
|
||||||
from="1,1"
|
|
||||||
to="0.1,0.1"
|
|
||||||
begin="closeDetails.click"
|
|
||||||
dur="0.7s" repeatCount="1" fill="freeze" />
|
|
||||||
<use href="#northIcon" transform="translate(1200,10),rotate(10)" />
|
|
||||||
<!--<polygon points="370 0, 1300 0, 1300 700, 0 700, 0 250, 370 250" fill="rgb(24, 27, 31)" />-->
|
|
||||||
<polygon points="560,80 720,80 720,300 1160,300 1160,370 560,370" stroke-linejoin="round"
|
|
||||||
fill="#f7c002" stroke-width="30" stroke="#f7c002" opacity="0.1" />
|
|
||||||
<use href="#pvPlate" transform="translate(600,80), rotate(90)" />
|
|
||||||
<use href="#pvPlate" transform="translate(600,150), rotate(90)" />
|
|
||||||
<use href="#pvPlate" transform="translate(600,220), rotate(90)" />
|
|
||||||
<use href="#pvPlate" transform="translate(600,290), rotate(90)" />
|
|
||||||
|
|
||||||
<use href="#pvPlate" transform="translate(640,80), rotate(90)" />
|
|
||||||
<use href="#pvPlate" transform="translate(640,150), rotate(90)" />
|
|
||||||
<use href="#pvPlate" transform="translate(640,220), rotate(90)" />
|
|
||||||
<use href="#pvPlate" transform="translate(640,290), rotate(90)" />
|
|
||||||
|
|
||||||
<use href="#pvPlate" transform="translate(680,80), rotate(90)" />
|
|
||||||
<use href="#pvPlate" transform="translate(680,150), rotate(90)" />
|
|
||||||
<use href="#pvPlate" transform="translate(680,220), rotate(90)" />
|
|
||||||
<use href="#pvPlate" transform="translate(680,290), rotate(90)" />
|
|
||||||
<use href="#pvPlate" transform="translate(720,80), rotate(90)" />
|
|
||||||
<use href="#pvPlate" transform="translate(720,150), rotate(90)" />
|
|
||||||
<use href="#pvPlate" transform="translate(720,220), rotate(90)" />
|
|
||||||
|
|
||||||
<use href="#pvPlate" transform="translate(790,290)" />
|
|
||||||
<use href="#pvPlate" transform="translate(720,330)" />
|
|
||||||
<use href="#pvPlate" transform="translate(790,330)" />
|
|
||||||
|
|
||||||
<use href="#pvPlate" transform="translate(870,290)" />
|
|
||||||
<use href="#pvPlate" transform="translate(870,330)" />
|
|
||||||
<use href="#pvPlate" transform="translate(940,330)" />
|
|
||||||
|
|
||||||
<use href="#pvPlate" transform="translate(1160,300), rotate(90)" />
|
|
||||||
<use href="#pvPlate" transform="translate(1120,300), rotate(90)" />
|
|
||||||
<use href="#pvPlate" transform="translate(1080,300), rotate(90)" />
|
|
||||||
|
|
||||||
|
|
||||||
<text id="det_pv0P" filter="url(#shadowBig)" class="infoTextClass" dx="55" dy="30"
|
|
||||||
transform="translate(560, 150), scale(1.5,1.5)">-.-- kW</text>
|
|
||||||
<polygon points="980,60 1160,60 1160,265 980,265" stroke-linejoin="round" fill="#F76D00"
|
|
||||||
stroke-width="30" stroke="#F76D00" opacity="0.1" />
|
|
||||||
<use href="#pvPlate" transform="translate(1160,200), rotate(90), scale(1,1.125)" />
|
|
||||||
<use href="#pvPlate" transform="translate(1115,200), rotate(90), scale(1,1.125)" />
|
|
||||||
<use href="#pvPlate" transform="translate(1070,200), rotate(90), scale(1,1.125)" />
|
|
||||||
<use href="#pvPlate" transform="translate(1160,130), rotate(90), scale(1,1.125)" />
|
|
||||||
<use href="#pvPlate" transform="translate(1115,130), rotate(90), scale(1,1.125)" />
|
|
||||||
<use href="#pvPlate" transform="translate(1070,130), rotate(90), scale(1,1.125)" />
|
|
||||||
<use href="#pvPlate" transform="translate(1025,130), rotate(90), scale(1,1.125)" />
|
|
||||||
<use href="#pvPlate" transform="translate(1160,60), rotate(90), scale(1,1.125)" />
|
|
||||||
<use href="#pvPlate" transform="translate(1115,60), rotate(90), scale(1,1.125)" />
|
|
||||||
<use href="#pvPlate" transform="translate(1070,60), rotate(90), scale(1,1.125)" />
|
|
||||||
<text id="det_pv1P" filter="url(#shadowBig)" class="infoTextClass" dx="55" dy="30"
|
|
||||||
transform="translate(990, 140), scale(1.5,1.5)">-.-- kW</text>
|
|
||||||
|
|
||||||
<use href="#pvPlate" x="200" y="680" />
|
|
||||||
<text id="det_pv33P" filter="url(#shadowMiddle)" class="infoTextClass" x="200" y="680" dx="36"
|
|
||||||
dy="26">#19</text>
|
|
||||||
<use href="#pvPlate" x="380" y="550" />
|
|
||||||
<text id="det_pv32P" filter="url(#shadowMiddle)" class="infoTextClass" x="380" y="550" dx="36"
|
|
||||||
dy="26">#19</text>
|
|
||||||
<use href="#pvPlate" x="380" y="590" />
|
|
||||||
<text id="det_pv31P" filter="url(#shadowMiddle)" class="infoTextClass" x="380" y="590" dx="36"
|
|
||||||
dy="26">#18</text>
|
|
||||||
<use href="#pvPlate" x="380" y="630" />
|
|
||||||
<text id="det_pv30P" filter="url(#shadowMiddle)" class="infoTextClass" x="380" y="630" dx="36"
|
|
||||||
dy="26">#17</text>
|
|
||||||
<use href="#pvPlate" x="310" y="510" />
|
|
||||||
<text id="det_pv29P" filter="url(#shadowMiddle)" class="infoTextClass" x="310" y="510" dx="36"
|
|
||||||
dy="26">#16</text>
|
|
||||||
<use href="#pvPlate" x="310" y="550" />
|
|
||||||
<text id="det_pv28P" filter="url(#shadowMiddle)" class="infoTextClass" x="310" y="550" dx="36"
|
|
||||||
dy="26">#15</text>
|
|
||||||
<use href="#pvPlate" x="310" y="590" />
|
|
||||||
<text id="det_pv27P" filter="url(#shadowMiddle)" class="infoTextClass" x="310" y="590" dx="36"
|
|
||||||
dy="26">#14</text>
|
|
||||||
<use href="#pvPlate" x="310" y="630" />
|
|
||||||
<text id="det_pv26P" filter="url(#shadowMiddle)" class="infoTextClass" x="310" y="630" dx="36"
|
|
||||||
dy="26">#13</text>
|
|
||||||
<use href="#pvPlate" x="240" y="510" />
|
|
||||||
<text id="det_pv25P" filter="url(#shadowMiddle)" class="infoTextClass" x="240" y="510" dx="36"
|
|
||||||
dy="26">#12</text>
|
|
||||||
<use href="#pvPlate" x="240" y="550" />
|
|
||||||
<text id="det_pv24P" filter="url(#shadowMiddle)" class="infoTextClass" x="240" y="550" dx="36"
|
|
||||||
dy="26">#11</text>
|
|
||||||
<use href="#pvPlate" x="240" y="590" />
|
|
||||||
<text id="det_pv23P" filter="url(#shadowMiddle)" class="infoTextClass" x="240" y="590" dx="36"
|
|
||||||
dy="26">#10</text>
|
|
||||||
<use href="#pvPlate" x="240" y="630" />
|
|
||||||
<text id="det_pv22P" filter="url(#shadowMiddle)" class="infoTextClass" x="240" y="630" dx="36"
|
|
||||||
dy="26">#9</text>
|
|
||||||
<use href="#pvPlate" x="170" y="510" />
|
|
||||||
<text id="det_pv20P" filter="url(#shadowMiddle)" class="infoTextClass" x="170" y="510" dx="36"
|
|
||||||
dy="26">#8</text>
|
|
||||||
<use href="#pvPlate" x="170" y="550" />
|
|
||||||
<text id="det_pv21P" filter="url(#shadowMiddle)" class="infoTextClass" x="170" y="550" dx="36"
|
|
||||||
dy="26">#7</text>
|
|
||||||
<use href="#pvPlate" x="170" y="590" />
|
|
||||||
<text id="det_pv19P" filter="url(#shadowMiddle)" class="infoTextClass" x="170" y="590" dx="36"
|
|
||||||
dy="26">#6</text>
|
|
||||||
<use href="#pvPlate" x="170" y="630" />
|
|
||||||
<text id="det_pv18P" filter="url(#shadowMiddle)" class="infoTextClass" x="170" y="630" dx="36"
|
|
||||||
dy="26">#5</text>
|
|
||||||
<use href="#pvPlate" x="100" y="510" />
|
|
||||||
<text id="det_pv14P" filter="url(#shadowMiddle)" class="infoTextClass" x="100" y="510" dx="36"
|
|
||||||
dy="26">#1</text>
|
|
||||||
<use href="#pvPlate" x="100" y="550" />
|
|
||||||
<text id="det_pv15P" filter="url(#shadowMiddle)" class="infoTextClass" x="100" y="550" dx="36"
|
|
||||||
dy="26">#2</text>
|
|
||||||
<use href="#pvPlate" x="100" y="590" />
|
|
||||||
<text id="det_pv16P" filter="url(#shadowMiddle)" class="infoTextClass" x="100" y="590" dx="36"
|
|
||||||
dy="26">#3</text>
|
|
||||||
<use href="#pvPlate" x="100" y="630" />
|
|
||||||
<text id="det_pv17P" filter="url(#shadowMiddle)" class="infoTextClass" x="100" y="630" dx="36"
|
|
||||||
dy="26">#4</text>
|
|
||||||
|
|
||||||
<use href="#pvPlate" transform="translate(600,500)" />
|
|
||||||
<text id="det_pv13P" filter="url(#shadowMiddle)" class="infoTextClass" x="600" y="500" dx="36"
|
|
||||||
dy="26">- W</text>
|
|
||||||
<use href="#pvPlate" transform="translate(500,500)" />
|
|
||||||
<text id="det_pv12P" filter="url(#shadowMiddle)" class="infoTextClass" x="500" y="500" dx="36"
|
|
||||||
dy="26">- W</text>
|
|
||||||
|
|
||||||
<use href="#pvPlate" transform="translate(460,120), rotate(-90)" />
|
|
||||||
<text id="det_pv5P" filter="url(#shadowMiddle)" class="infoTextClass"
|
|
||||||
transform="translate(460,120), rotate(-90)" dx="36" dy="26">- W</text>
|
|
||||||
<use href="#pvPlate" transform="translate(460,190), rotate(-90)" />
|
|
||||||
<text id="det_pv4P" filter="url(#shadowMiddle)" class="infoTextClass"
|
|
||||||
transform="translate(460,190), rotate(-90)" dx="36" dy="26">- W</text>
|
|
||||||
|
|
||||||
<use href="#pvPlate" transform="translate(460,270), rotate(-90)" />
|
|
||||||
<text id="det_pv9P" filter="url(#shadowMiddle)" class="infoTextClass"
|
|
||||||
transform="translate(460,270), rotate(-90)" dx="36" dy="26">- W</text>
|
|
||||||
<use href="#pvPlate" transform="translate(460,340), rotate(-90)" />
|
|
||||||
<text id="det_pv8P" filter="url(#shadowMiddle)" class="infoTextClass"
|
|
||||||
transform="translate(460,340), rotate(-90)" dx="36" dy="26">- W</text>
|
|
||||||
|
|
||||||
<use href="#pvPlate" transform="translate(460,420), rotate(-90)" />
|
|
||||||
<text id="det_pv11P" filter="url(#shadowMiddle)" class="infoTextClass"
|
|
||||||
transform="translate(460,420), rotate(-90)" dx="36" dy="26">- W</text>
|
|
||||||
<use href="#pvPlate" transform="translate(460,490), rotate(-90)" />
|
|
||||||
<text id="det_pv10P" filter="url(#shadowMiddle)" class="infoTextClass"
|
|
||||||
transform="translate(460,490), rotate(-90)" dx="36" dy="26">- W</text>
|
|
||||||
|
|
||||||
<use href="#pvPlate" transform="translate(400,120), rotate(-90)" />
|
|
||||||
<text id="det_pv7P" filter="url(#shadowMiddle)" class="infoTextClass"
|
|
||||||
transform="translate(400,120), rotate(-90)" dx="36" dy="26">- W</text>
|
|
||||||
<use href="#pvPlate" transform="translate(400,190), rotate(-90)" />
|
|
||||||
<text id="det_pv6P" filter="url(#shadowMiddle)" class="infoTextClass"
|
|
||||||
transform="translate(400,190), rotate(-90)" dx="36" dy="26">- W</text>
|
|
||||||
|
|
||||||
<use href="#pvPlate" transform="translate(400,270), rotate(-90)" />
|
|
||||||
<text id="det_pv3P" filter="url(#shadowMiddle)" class="infoTextClass"
|
|
||||||
transform="translate(400,270), rotate(-90)" dx="36" dy="26">- W</text>
|
|
||||||
|
|
||||||
<use href="#pvPlate" transform="translate(400,340), rotate(-90)" />
|
|
||||||
<text id="det_pv2P" filter="url(#shadowMiddle)" class="infoTextClass"
|
|
||||||
transform="translate(400,340), rotate(-90)" dx="36" dy="26">- W</text>
|
|
||||||
|
|
||||||
|
|
||||||
<text id="invList" filter="url(#shadowMiddle)" class="tooltipTextClass"
|
|
||||||
transform="translate(80,255)"></text>
|
|
||||||
<rect width="305" height="210" x="70" y="250" rx="5" ry="5" fill="none" stroke-width="4px"
|
|
||||||
stroke="#f7c000" />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<g id="tooltip" visibility="hidden">
|
|
||||||
<rect x="2" y="2" width="200" height="60" fill="black" opacity="0.4" rx="2" ry="2" />
|
|
||||||
<rect width="200" height="60" fill="white" rx="2" ry="2" />
|
|
||||||
<text x="4" y="6">Tooltip</text>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
+170
@@ -3431,3 +3431,173 @@ input[type='range']::-webkit-slider-runnable-track {
|
|||||||
border-color: #00788F;
|
border-color: #00788F;
|
||||||
color: var(--ton-schrift, #e8ede9);
|
color: var(--ton-schrift, #e8ede9);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
Energiefluss-Übersicht (js/solar/energiefluss.js, Katalog in
|
||||||
|
js/solar/energieflussKatalog.js).
|
||||||
|
|
||||||
|
Farben je Kreis kommen als --farbe aus dem Katalog, Breite und Tempo der
|
||||||
|
Flüsse als --breite/--dauer aus dem Renderer. Alles Übrige steht hier.
|
||||||
|
Transformationen liegen nie auf Elementen mit transform-Attribut (Kreis-
|
||||||
|
gruppe, Plakette) - CSS würde deren Lage überschreiben.
|
||||||
|
========================================================================== */
|
||||||
|
.enf-behaelter { position: relative; }
|
||||||
|
svg.enf {
|
||||||
|
display: block; width: 100%; height: auto; max-height: 80vh;
|
||||||
|
font-family: "Poppins", system-ui, sans-serif;
|
||||||
|
user-select: none; -webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
svg.enf.enf-hoch { max-height: none; }
|
||||||
|
|
||||||
|
/* --- Kreis ------------------------------------------------------------- */
|
||||||
|
.enf .enf-hof { fill: #1c2024; stroke: rgba(255, 255, 255, .06); stroke-width: 1.5; transition: stroke .3s, stroke-width .3s; }
|
||||||
|
.enf .enf-spur { fill: none; stroke: #2b3139; stroke-width: 14; }
|
||||||
|
.enf .enf-bogen {
|
||||||
|
fill: none; stroke-width: 14; stroke-dasharray: 0 100;
|
||||||
|
transition: stroke-dasharray 1s cubic-bezier(.2, .7, .2, 1), stroke-dashoffset 1s cubic-bezier(.2, .7, .2, 1), stroke .4s;
|
||||||
|
}
|
||||||
|
.enf .enf-scheibe { fill: #20252b; }
|
||||||
|
.enf .enf-wert { fill: var(--ton-schrift, #e8ede9); font-size: 32px; font-weight: 600; text-anchor: middle; font-variant-numeric: tabular-nums; }
|
||||||
|
.enf .enf-neben { fill: var(--ton-schrift-2, #9aa6ad); font-size: 14.5px; text-anchor: middle; font-variant-numeric: tabular-nums; }
|
||||||
|
.enf .enf-name {
|
||||||
|
fill: var(--ton-schrift-2, #9aa6ad); font-size: 16px; font-weight: 600; text-anchor: middle; letter-spacing: .08em; text-transform: uppercase;
|
||||||
|
/* Kontur in Kartenfarbe: bleibt lesbar, wo ein Fluss unter dem Namen durchlaeuft. */
|
||||||
|
paint-order: stroke; stroke: var(--ton-flaeche, #23282c); stroke-width: 7px; stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enf .enf-koerper { transform-box: view-box; transform-origin: 0 0; transition: transform .3s cubic-bezier(.2, .7, .2, 1), opacity .6s; }
|
||||||
|
.enf .enf-ruhe .enf-koerper { opacity: .6; }
|
||||||
|
.enf .enf-ruhe .enf-name { opacity: .7; }
|
||||||
|
.enf .enf-aus { opacity: .3; }
|
||||||
|
.enf .enf-aus .enf-spur { stroke-dasharray: 8 8; }
|
||||||
|
|
||||||
|
.enf .enf-klickbar { cursor: pointer; outline: none; }
|
||||||
|
.enf .enf-klickbar:hover .enf-koerper { transform: scale(1.045); }
|
||||||
|
.enf .enf-klickbar:hover .enf-hof,
|
||||||
|
.enf .enf-klickbar:focus-visible .enf-hof { stroke: var(--farbe); stroke-width: 3; }
|
||||||
|
.enf .enf-tipp circle { fill: #262c33; stroke: #3a424b; stroke-width: 1.5; }
|
||||||
|
.enf .enf-tipp path { fill: none; stroke: var(--farbe); stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; transition: transform .25s; }
|
||||||
|
.enf .enf-klickbar:hover .enf-tipp path { transform: translateX(2px); }
|
||||||
|
|
||||||
|
/* --- Füllstand als Welle ------------------------------------------------ */
|
||||||
|
.enf .enf-fuellung {
|
||||||
|
--r: 67;
|
||||||
|
transform: translateY(calc(var(--r) * (1 - 2 * var(--stand)) * 1px));
|
||||||
|
transition: transform 1.4s cubic-bezier(.2, .7, .2, 1);
|
||||||
|
}
|
||||||
|
.enf .enf-welle { fill: var(--farbe); fill-opacity: .14; animation: enf-wogen 8s linear infinite; }
|
||||||
|
@keyframes enf-wogen { to { transform: translateX(calc(var(--r) * 2px)); } }
|
||||||
|
|
||||||
|
/* --- Symbole ------------------------------------------------------------ */
|
||||||
|
.enf .enf-symbol { color: var(--farbe); fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
|
||||||
|
.enf .enf-s-fein { stroke-width: 1.1; opacity: .75; }
|
||||||
|
.enf .enf-s-kern { fill: currentColor; fill-opacity: .2; transition: fill-opacity .6s; }
|
||||||
|
.enf .enf-an .enf-s-kern { fill-opacity: .55; }
|
||||||
|
.enf .enf-s-strahlen { transform-box: view-box; transform-origin: 0 0; opacity: .55; transition: opacity .6s; }
|
||||||
|
.enf .enf-an .enf-s-strahlen { opacity: 1; animation: enf-drehen 30s linear infinite; }
|
||||||
|
@keyframes enf-drehen { to { transform: rotate(360deg); } }
|
||||||
|
|
||||||
|
.enf .enf-s-stand {
|
||||||
|
fill: currentColor; stroke: none; opacity: .45;
|
||||||
|
transform-box: fill-box; transform-origin: 50% 100%;
|
||||||
|
transform: scaleY(var(--stand, 0)); transition: transform 1.2s cubic-bezier(.2, .7, .2, 1);
|
||||||
|
}
|
||||||
|
.enf .enf-s-blitz { fill: currentColor; stroke: none; opacity: 0; transition: opacity .4s; }
|
||||||
|
.enf .enf-sym-akku .enf-s-blitz { fill: #fff; }
|
||||||
|
.enf .enf-s-blitz.enf-s-an,
|
||||||
|
.enf .enf-laden .enf-s-blitz,
|
||||||
|
.enf .enf-an.enf-knoten .enf-sym-wallbox .enf-s-blitz { opacity: 1; }
|
||||||
|
.enf .enf-laden .enf-s-blitz,
|
||||||
|
.enf .enf-an .enf-sym-wallbox .enf-s-blitz { animation: enf-puls 1.8s ease-in-out infinite; }
|
||||||
|
@keyframes enf-puls { 50% { opacity: .35; } }
|
||||||
|
|
||||||
|
.enf .enf-an .enf-s-welle { animation: enf-puls 1.4s ease-in-out infinite; }
|
||||||
|
.enf .enf-s-ebene { fill: currentColor; stroke: none; opacity: .38; }
|
||||||
|
.enf .enf-an .enf-sym-wallbox .enf-s-kabel { stroke-dasharray: 2.4 2.4; animation: enf-kabel .9s linear infinite; }
|
||||||
|
@keyframes enf-kabel { to { stroke-dashoffset: -4.8; } }
|
||||||
|
.enf .enf-s-temps rect { stroke: none; fill: #2b3139; opacity: .9; transition: fill 1.5s; }
|
||||||
|
.enf .enf-s-spirale { stroke-width: 1.3; transition: stroke .4s; }
|
||||||
|
.enf .enf-an .enf-s-spirale { stroke: #ffb04a; animation: enf-gluehen 1.1s ease-in-out infinite alternate; }
|
||||||
|
@keyframes enf-gluehen { to { stroke: #ff5a2a; } }
|
||||||
|
|
||||||
|
/* --- Plaketten ---------------------------------------------------------- */
|
||||||
|
.enf .enf-plakette { animation: enf-einblenden .5s both; animation-delay: calc(var(--i) * 80ms); }
|
||||||
|
.enf .enf-plakette circle,
|
||||||
|
.enf .enf-plakette rect { fill: #1c2024; stroke: var(--pfarbe); stroke-width: 2; }
|
||||||
|
.enf .enf-plakette-glyph { font-family: bootstrap-icons; font-size: 16px; fill: var(--pfarbe); text-anchor: middle; dominant-baseline: central; }
|
||||||
|
.enf .enf-plakette-text { font-size: 14px; font-weight: 600; fill: var(--ton-schrift, #e8ede9); dominant-baseline: central; }
|
||||||
|
|
||||||
|
/* --- Bewässerung --------------------------------------------------------- */
|
||||||
|
.enf .enf-zone-glyph { font-family: bootstrap-icons; font-size: 17px; fill: currentColor; color: #6c757d; text-anchor: middle; dominant-baseline: central; transition: color .4s; }
|
||||||
|
.enf .enf-zone-name { font-size: 15px; fill: var(--ton-schrift-2, #9aa6ad); dominant-baseline: central; }
|
||||||
|
.enf .enf-zone-status { font-size: 15px; font-weight: 600; fill: var(--ton-schrift, #e8ede9); text-anchor: end; dominant-baseline: central; font-variant-numeric: tabular-nums; }
|
||||||
|
|
||||||
|
/* --- Flüsse -------------------------------------------------------------- */
|
||||||
|
.enf .enf-bett {
|
||||||
|
fill: none; stroke: #252b31; stroke-linecap: round;
|
||||||
|
stroke-width: calc(var(--breite, 0) * 1px + 5px);
|
||||||
|
stroke-dasharray: 1 1; stroke-dashoffset: 1;
|
||||||
|
transition: stroke-width .8s;
|
||||||
|
animation: enf-zeichnen .9s cubic-bezier(.5, 0, .2, 1) forwards; animation-delay: var(--verz);
|
||||||
|
}
|
||||||
|
@keyframes enf-zeichnen { to { stroke-dashoffset: 0; } }
|
||||||
|
.enf .enf-strom,
|
||||||
|
.enf .enf-strom-hof {
|
||||||
|
fill: none; stroke: var(--farbe); stroke-linecap: round;
|
||||||
|
stroke-dasharray: 0 20;
|
||||||
|
/* Stufen statt stetig: stroke-dashoffset laeuft nicht auf dem Compositor,
|
||||||
|
jede Stufe ist ein Neuzeichnen. 20 je Abschnitt sehen fluessig aus. */
|
||||||
|
animation: enf-fliessen var(--dauer, 1.5s) steps(20) infinite;
|
||||||
|
transition: stroke-width .8s, opacity .6s;
|
||||||
|
}
|
||||||
|
.enf .enf-strom { stroke-width: calc(var(--breite, 0) * 1px); }
|
||||||
|
.enf .enf-strom-hof { stroke-width: calc(var(--breite, 0) * 1.9px); opacity: .14; }
|
||||||
|
.enf .enf-rueck .enf-strom,
|
||||||
|
.enf .enf-rueck .enf-strom-hof { animation-direction: reverse; }
|
||||||
|
.enf .enf-still .enf-strom,
|
||||||
|
.enf .enf-still .enf-strom-hof { opacity: 0; animation: none; }
|
||||||
|
@keyframes enf-fliessen { to { stroke-dashoffset: -20; } }
|
||||||
|
|
||||||
|
/* --- Auftritt ------------------------------------------------------------ */
|
||||||
|
.enf .enf-koerper { animation: enf-erscheinen .7s cubic-bezier(.2, .9, .3, 1.25) backwards; animation-delay: var(--verz); }
|
||||||
|
.enf .enf-name { animation: enf-einblenden .7s backwards; animation-delay: calc(var(--verz) + 150ms); }
|
||||||
|
@keyframes enf-erscheinen { from { opacity: 0; transform: scale(.55); } }
|
||||||
|
@keyframes enf-einblenden { from { opacity: 0; } }
|
||||||
|
|
||||||
|
.enf.enf-pause,
|
||||||
|
.enf.enf-pause * { animation-play-state: paused !important; }
|
||||||
|
|
||||||
|
/* --- Bearbeiten (Einstellungen) ------------------------------------------ */
|
||||||
|
.enf.enf-bearbeiten .enf-koerper,
|
||||||
|
.enf.enf-bearbeiten .enf-name,
|
||||||
|
.enf.enf-bearbeiten .enf-bett { animation: none; stroke-dashoffset: 0; }
|
||||||
|
.enf.enf-bearbeiten .enf-ziehbar { cursor: grab; touch-action: none; }
|
||||||
|
.enf.enf-bearbeiten .enf-zieht { cursor: grabbing; }
|
||||||
|
.enf.enf-bearbeiten .enf-zieht .enf-hof { stroke: var(--farbe); stroke-width: 3; }
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.enf, .enf * { animation: none !important; transition: none !important; }
|
||||||
|
.enf .enf-bett { stroke-dashoffset: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Einstellungen: Reiter "Übersicht" ------------------------------------ */
|
||||||
|
.enf-vorschau { background: var(--ton-grund, #191d20); border-radius: var(--ton-radius, .875rem); padding: .5rem; position: sticky; top: 4.5rem; }
|
||||||
|
.enf-e-zeile {
|
||||||
|
display: grid; grid-template-columns: auto minmax(0, 1fr) auto 1.2rem; gap: .35rem .7rem; align-items: center;
|
||||||
|
padding: .45rem 0; border-bottom: 1px solid var(--ton-linie, rgba(138, 148, 158, .2));
|
||||||
|
}
|
||||||
|
.enf-e-zeile.enf-e-aus .enf-e-name { opacity: .5; }
|
||||||
|
.enf-e-name { display: flex; align-items: center; gap: .55rem; margin: 0; }
|
||||||
|
.enf-e-punkt { flex: 0 0 auto; width: .7rem; height: .7rem; border-radius: 50%; background: var(--farbe); box-shadow: 0 0 0 3px color-mix(in srgb, var(--farbe) 22%, transparent); }
|
||||||
|
.enf-e-eltern { white-space: nowrap; }
|
||||||
|
.enf-e-flaechen { grid-column: 2 / -1; padding: .2rem 0 .3rem; }
|
||||||
|
.enf-e-flaeche { display: grid; grid-template-columns: 2.2rem minmax(0, 1fr) 4.2rem 4.2rem 1.2rem; gap: .35rem; align-items: center; margin-bottom: .3rem; }
|
||||||
|
.enf-e-flaeche .form-control.form-control-color { width: 2.2rem; height: 1.9rem; padding: .15rem; }
|
||||||
|
|
||||||
|
/* --- Karte: Umschalter Übersicht / Anlage -------------------------------- */
|
||||||
|
.enf-umschalter { display: inline-flex; gap: 2px; padding: 3px; border-radius: var(--ton-radius-klein, .5rem); background: var(--ton-grund, #191d20); }
|
||||||
|
.enf-umschalter .btn.btn-sm { border: 0; color: var(--ton-schrift-2, #9aa6ad); font-weight: 600; padding: .2rem .8rem; }
|
||||||
|
.enf-umschalter .btn.btn-sm.active { background: var(--ton-flaeche-2, #2b3237); color: var(--ton-schrift, #e8ede9); }
|
||||||
|
.enf-ansicht[hidden] { display: none !important; }
|
||||||
|
.enf-ansicht.enf-rein { animation: enf-ansicht-rein .35s cubic-bezier(.2, .7, .2, 1); }
|
||||||
|
@keyframes enf-ansicht-rein { from { opacity: 0; transform: scale(.98); } }
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Energiefluss-Uebersicht (Seite Solar, Karte "Realtime") - Datenbank `homeMesh`
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Welche Kreise es ueberhaupt gibt, woher ihre Werte kommen, wie sie heissen
|
||||||
|
-- und wo sie stehen, beschreibt der Katalog in js/solar/energieflussKatalog.js.
|
||||||
|
-- Hier steht nur, was ein Haus davon abweichend einstellt - unter
|
||||||
|
-- Einstellungen -> Uebersicht:
|
||||||
|
--
|
||||||
|
-- aktiv 0 blendet den Kreis samt seinen Fluessen aus. Haengen andere
|
||||||
|
-- daran (Wallbox OG am Obergeschoss), rutschen sie eine Ebene
|
||||||
|
-- hoeher (an den Verbrauch).
|
||||||
|
-- name eigene Beschriftung statt der aus dem Katalog.
|
||||||
|
-- x, y eigene Lage in der Queransicht. Die Hochkant-Ansicht auf
|
||||||
|
-- schmalen Schirmen spiegelt sie selbst.
|
||||||
|
-- optionen JSON fuer Einstellungen, die nur ein Kreis kennt - etwa die
|
||||||
|
-- Aufteilung der PV-Leistung auf Flaechen.
|
||||||
|
--
|
||||||
|
-- Fehlt eine Zeile, gilt der Katalog. Ein neuer Kreis im Katalog erscheint
|
||||||
|
-- damit ohne Zutun, und eine leere Tabelle ist die Uebersicht dieses Hauses,
|
||||||
|
-- wie sie ausgeliefert wird.
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
SET NAMES utf8mb4;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `energiefluss` (
|
||||||
|
`id` varchar(20) NOT NULL COMMENT 'Kennung aus dem Katalog, z.B. og, wallboxEG',
|
||||||
|
`aktiv` tinyint(1) NOT NULL DEFAULT 1,
|
||||||
|
`name` varchar(40) DEFAULT NULL COMMENT 'NULL = Name aus dem Katalog',
|
||||||
|
`x` smallint(6) DEFAULT NULL COMMENT 'Mitte in der Queransicht; NULL = Katalog',
|
||||||
|
`y` smallint(6) DEFAULT NULL,
|
||||||
|
`optionen` text DEFAULT NULL COMMENT 'JSON, je Kreis verschieden; NULL = Katalog',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
@@ -100,7 +100,8 @@ if (isset($_GET["addUser"])) {
|
|||||||
*/
|
*/
|
||||||
$pages = [
|
$pages = [
|
||||||
"solar" => ["template" => "solar.php", "mqtt" => true, "charts" => true, "meteogram" => true, "leaflet" => false,
|
"solar" => ["template" => "solar.php", "mqtt" => true, "charts" => true, "meteogram" => true, "leaflet" => false,
|
||||||
"scripts" => ["js/solar/jahresstatistik.js", "js/solar/solarMQTT.js"]],
|
"scripts" => ["js/solar/jahresstatistik.js", "js/solar/energieflussKatalog.js",
|
||||||
|
"js/solar/energiefluss.js", "js/solar/solarMQTT.js"]],
|
||||||
"home" => ["template" => "home.php", "mqtt" => true, "charts" => false, "meteogram" => false, "leaflet" => false,
|
"home" => ["template" => "home.php", "mqtt" => true, "charts" => false, "meteogram" => false, "leaflet" => false,
|
||||||
"scripts" => ["js/solar/autoActionFuncs.js", "js/solar/zeitleiste.js", "js/solar/homeMQTT.js"]],
|
"scripts" => ["js/solar/autoActionFuncs.js", "js/solar/zeitleiste.js", "js/solar/homeMQTT.js"]],
|
||||||
"heat" => ["template" => "heat.php", "mqtt" => true, "charts" => true, "meteogram" => false, "leaflet" => false,
|
"heat" => ["template" => "heat.php", "mqtt" => true, "charts" => true, "meteogram" => false, "leaflet" => false,
|
||||||
@@ -112,7 +113,9 @@ if (isset($_GET["addUser"])) {
|
|||||||
"weather" => ["template" => "weather.php", "mqtt" => true, "charts" => false, "meteogram" => true, "leaflet" => false,
|
"weather" => ["template" => "weather.php", "mqtt" => true, "charts" => false, "meteogram" => true, "leaflet" => false,
|
||||||
"scripts" => ["js/solar/weatherMQTT.js"]],
|
"scripts" => ["js/solar/weatherMQTT.js"]],
|
||||||
"settings" => ["template" => "settings.php", "mqtt" => false, "charts" => false, "meteogram" => false, "leaflet" => false,
|
"settings" => ["template" => "settings.php", "mqtt" => false, "charts" => false, "meteogram" => false, "leaflet" => false,
|
||||||
"scripts" => ["js/solar/settings.js", "js/solar/grundriss.js"]],
|
"scripts" => ["js/solar/settings.js", "js/solar/grundriss.js",
|
||||||
|
"js/solar/energieflussKatalog.js", "js/solar/energiefluss.js",
|
||||||
|
"js/solar/energieflussEinstellungen.js"]],
|
||||||
"logs" => ["template" => "logs.php", "mqtt" => false, "charts" => false, "meteogram" => false, "leaflet" => false,
|
"logs" => ["template" => "logs.php", "mqtt" => false, "charts" => false, "meteogram" => false, "leaflet" => false,
|
||||||
"scripts" => ["js/solar/logs.js"]],
|
"scripts" => ["js/solar/logs.js"]],
|
||||||
"einfuehrung" => ["template" => "einfuehrung.php", "mqtt" => false, "charts" => false, "meteogram" => false, "leaflet" => false,
|
"einfuehrung" => ["template" => "einfuehrung.php", "mqtt" => false, "charts" => false, "meteogram" => false, "leaflet" => false,
|
||||||
|
|||||||
@@ -0,0 +1,556 @@
|
|||||||
|
/* ==========================================================================
|
||||||
|
Energiefluss-Übersicht: zeichnet den Katalog (energieflussKatalog.js) als
|
||||||
|
animiertes SVG und hält es mit den MQTT-Werten aktuell.
|
||||||
|
|
||||||
|
const bild = new Energiefluss(behaelter, {
|
||||||
|
einstellungen, // Abweichungen aus homeMesh.energiefluss
|
||||||
|
bearbeiten: false, // true = Einstellungsvorschau, Kreise ziehbar
|
||||||
|
onKlick(knoten), // Kreis mit klick-Ziel angetippt
|
||||||
|
onVerschoben(id, x, y), // nur beim Bearbeiten
|
||||||
|
onGezeichnet(), // nach jedem (Neu-)Zeichnen
|
||||||
|
});
|
||||||
|
bild.aktualisieren(mqttData);
|
||||||
|
|
||||||
|
Der Renderer kennt keinen Kreis beim Namen. Alles, was einen Kreis
|
||||||
|
ausmacht, steht im Katalog; hier steht nur, wie Kreise, Flüsse, Symbole
|
||||||
|
und Plaketten grundsätzlich aussehen.
|
||||||
|
|
||||||
|
Zwei Ansichten: quer (Lage aus dem Katalog) und hochkant für schmale
|
||||||
|
Schirme. Hochkant sind x und y vertauscht - aus "Quellen links, Verbraucher
|
||||||
|
rechts" wird "Quellen oben, Verbraucher unten". So braucht es keine zweite
|
||||||
|
Lage je Kreis, und eine eigene Lage aus den Einstellungen gilt in beiden.
|
||||||
|
|
||||||
|
Zur Leistung: die Flüsse laufen über stroke-dashoffset in groben Stufen
|
||||||
|
(steps), und alle Animationen stehen still, solange die Karte nicht zu
|
||||||
|
sehen ist. Werte werden nur geschrieben, wenn sie sich geändert haben.
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const NS = "http://www.w3.org/2000/svg";
|
||||||
|
const RADIUS = 78;
|
||||||
|
const RADIUS_GROSS = 92;
|
||||||
|
const RAND = 60; // Luft um die äußersten Kreise
|
||||||
|
const HOCHKANT_UNTER = 720; // px Breite, ab der hochkant gezeichnet wird
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------
|
||||||
|
Symbole: 24er Raster, Mitte 0/0, Linien in currentColor. Teile mit
|
||||||
|
Klasse enf-s-* werden über CSS animiert oder über den Zustand gesetzt.
|
||||||
|
------------------------------------------------------------------------ */
|
||||||
|
const SYMBOLE = {
|
||||||
|
sonne: () => `
|
||||||
|
<g class="enf-s-strahlen">
|
||||||
|
${[0, 45, 90, 135, 180, 225, 270, 315].map(w =>
|
||||||
|
`<path d="M0-8.2V-11" transform="rotate(${w})"/>`).join("")}
|
||||||
|
</g>
|
||||||
|
<circle r="4.6" class="enf-s-kern"/>`,
|
||||||
|
|
||||||
|
netz: () => `
|
||||||
|
<path d="M-5 11.5L-1.6-10.5h3.2L5 11.5"/>
|
||||||
|
<path d="M-9.5-5.5h19M-7-1h14"/>
|
||||||
|
<path d="M-8.5-5.5v2.4M8.5-5.5v2.4M-6-1v2.4M6-1v2.4"/>
|
||||||
|
<path d="M-3.3 3.5l6.8 8M3.3 3.5l-6.8 8" class="enf-s-fein"/>`,
|
||||||
|
|
||||||
|
akku: () => `
|
||||||
|
<rect x="-6.5" y="-8.5" width="13" height="20" rx="2.4"/>
|
||||||
|
<path d="M-2.6-11h5.2"/>
|
||||||
|
<rect x="-4" y="-6" width="8" height="15" rx="1" class="enf-s-stand"/>
|
||||||
|
<path d="M1.2-4.2l-4 5.6h3l-1.4 4.6 4.2-6h-3z" class="enf-s-blitz"/>`,
|
||||||
|
|
||||||
|
wechselrichter: () => `
|
||||||
|
<rect x="-10.5" y="-10.5" width="21" height="21" rx="3.5"/>
|
||||||
|
<path d="M-10.5 10.5l21-21" class="enf-s-fein"/>
|
||||||
|
<path d="M-7.5-5.2h5M-7.5-2.4h5"/>
|
||||||
|
<path d="M1.8 4.2c1-1.7 2.1-1.7 3.1 0s2.1 1.7 3.1 0" class="enf-s-welle"/>`,
|
||||||
|
|
||||||
|
verbrauch: () => `
|
||||||
|
<path d="M-11-1.5L0-10.5l11 9"/>
|
||||||
|
<path d="M-8-4v14.5h16V-4"/>
|
||||||
|
<path d="M1.3-3.8l-4.4 6.2h3.3l-1.6 5 4.6-6.6H-.1z" class="enf-s-blitz enf-s-an"/>`,
|
||||||
|
|
||||||
|
etageOben: () => etage(-4.5, 1.5),
|
||||||
|
etageMitte: () => etage(1.5, 7.5),
|
||||||
|
etageUnten: () => etage(7.5, 11.5),
|
||||||
|
|
||||||
|
wallbox: () => `
|
||||||
|
<rect x="-10.5" y="-10.5" width="11" height="21" rx="2.2"/>
|
||||||
|
<rect x="-8" y="-7.5" width="6" height="4" rx=".8" class="enf-s-fein"/>
|
||||||
|
<path d="M-6.4 1.2l-2 3.5h2.2l-1 3.2 2.9-4.2h-2.2z" class="enf-s-blitz"/>
|
||||||
|
<path d="M.5 2h2.8a2.3 2.3 0 0 1 2.3 2.3v3.9a1.9 1.9 0 0 0 3.8 0V-.5" class="enf-s-kabel"/>
|
||||||
|
<path d="M7.2-4.6h3.6v2.3a1.8 1.8 0 0 1-3.6 0z"/>
|
||||||
|
<path d="M8.2-4.6v-2M9.8-4.6v-2"/>`,
|
||||||
|
|
||||||
|
speicher: () => `
|
||||||
|
<defs><clipPath id="{id}-tank"><rect x="-6.8" y="-10.8" width="13.6" height="19.6" rx="5.2"/></clipPath></defs>
|
||||||
|
<g clip-path="url(#{id}-tank)" class="enf-s-temps">
|
||||||
|
<rect x="-8" y="-12" width="16" height="7.6" class="enf-s-t0"/>
|
||||||
|
<rect x="-8" y="-4.4" width="16" height="6.6" class="enf-s-t1"/>
|
||||||
|
<rect x="-8" y="2.2" width="16" height="7.6" class="enf-s-t2"/>
|
||||||
|
</g>
|
||||||
|
<rect x="-7" y="-11" width="14" height="20" rx="5.4"/>
|
||||||
|
<path d="M-3.4 5.6l1.1-1.8 1.2 1.8 1.1-1.8 1.2 1.8 1.1-1.8" class="enf-s-spirale"/>
|
||||||
|
<path d="M-4 9v2.5M4 9v2.5"/>`,
|
||||||
|
|
||||||
|
tropfen: () => `
|
||||||
|
<path d="M0-11c4.6 5.5 7 9 7 12.6a7 7 0 0 1-14 0C-7-2-4.6-5.5 0-11z"/>
|
||||||
|
<path d="M-3.6 3.4c1.2 1 2.4 1 3.6 0s2.4-1 3.6 0" class="enf-s-welle"/>`,
|
||||||
|
};
|
||||||
|
|
||||||
|
function etage(von, bis) {
|
||||||
|
return `
|
||||||
|
<rect x="-8" y="${von}" width="16" height="${bis - von}" class="enf-s-ebene"/>
|
||||||
|
<path d="M-11.5-3L0-11l11.5 8"/>
|
||||||
|
<path d="M-8-5v12.5h16V-5"/>
|
||||||
|
<path d="M-8 1.5h16" class="enf-s-fein"/>
|
||||||
|
<path d="M-12 7.5h24"/>
|
||||||
|
<path d="M-8 7.5v4h16v-4" class="enf-s-fein enf-s-keller"/>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plaketten am Kreis: Zeichen aus Bootstrap Icons (liegt ohnehin im Dashboard). */
|
||||||
|
const ZEICHEN = {
|
||||||
|
eco: { glyph: "奈", farbe: "#6cbe58" }, // bi-leaf
|
||||||
|
uhr: { glyph: "", farbe: "#e8a33a" }, // bi-clock-fill
|
||||||
|
manuell: { glyph: "", farbe: "#c9a227" }, // bi-sliders
|
||||||
|
stecker: { glyph: "", farbe: "#8fb8de" }, // bi-plug-fill
|
||||||
|
schloss: { glyph: "", farbe: "#b9c0c8" }, // bi-lock-fill
|
||||||
|
heizung: { glyph: "", farbe: "#e5624f" }, // bi-fire
|
||||||
|
tank: { glyph: "", farbe: "#b9c0c8" }, // bi-fuel-pump-fill
|
||||||
|
warnung: { glyph: "", farbe: "#e5624f" }, // bi-exclamation-triangle-fill
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------
|
||||||
|
Hilfen
|
||||||
|
------------------------------------------------------------------------ */
|
||||||
|
function el(name, attr, eltern) {
|
||||||
|
const e = document.createElementNS(NS, name);
|
||||||
|
if (attr) for (const k in attr) e.setAttribute(k, attr[k]);
|
||||||
|
if (eltern) eltern.appendChild(e);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
function leistungText(w) {
|
||||||
|
const a = Math.abs(w);
|
||||||
|
if (a < 1000) return Math.round(a) + " W";
|
||||||
|
return (a / 1000).toFixed(a < 10000 ? 2 : 1).replace(".", ",") + " kW";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Liest mqttData: Werte kommen als Buffer, Zahl und Text wie gehabt. */
|
||||||
|
function leser(daten) {
|
||||||
|
const cache = {};
|
||||||
|
return {
|
||||||
|
text(topic) {
|
||||||
|
const v = daten[topic];
|
||||||
|
return v === undefined ? undefined : String(v);
|
||||||
|
},
|
||||||
|
zahl(topic) {
|
||||||
|
const v = Number(String(daten[topic]));
|
||||||
|
return isFinite(v) ? v : 0;
|
||||||
|
},
|
||||||
|
json(topic) {
|
||||||
|
if (topic in cache) return cache[topic];
|
||||||
|
try { cache[topic] = JSON.parse(String(daten[topic])); } catch (e) { cache[topic] = null; }
|
||||||
|
return cache[topic];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Farbe zwischen kalt und heiß, wie früher assignColor() fürs Pufferbild. */
|
||||||
|
function tempFarbe(t) {
|
||||||
|
const f = Math.max(0, Math.min(1, (t - 20) / 60));
|
||||||
|
const kalt = [35, 137, 186], heiss = [186, 59, 35];
|
||||||
|
return "rgb(" + kalt.map((k, i) => Math.round(k + (heiss[i] - k) * f)).join(",") + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
let laufendeNummer = 0;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------
|
||||||
|
Die Übersicht
|
||||||
|
------------------------------------------------------------------------ */
|
||||||
|
class Energiefluss {
|
||||||
|
constructor(behaelter, opt = {}) {
|
||||||
|
this.behaelter = behaelter;
|
||||||
|
this.opt = opt;
|
||||||
|
this.bearbeiten = !!opt.bearbeiten;
|
||||||
|
this.nummer = ++laufendeNummer;
|
||||||
|
this.letzte = null;
|
||||||
|
this.setzeEinstellungen(opt.einstellungen || {});
|
||||||
|
|
||||||
|
// Hochkant oder quer - neu zeichnen nur, wenn die Ansicht wechselt.
|
||||||
|
this.beobachter = new ResizeObserver(() => {
|
||||||
|
const hoch = this.hochkantGewuenscht();
|
||||||
|
if (hoch !== this.hochkant) this.zeichnen();
|
||||||
|
});
|
||||||
|
this.beobachter.observe(behaelter);
|
||||||
|
|
||||||
|
// Animationen anhalten, solange niemand hinsieht.
|
||||||
|
this.sichtbar = true;
|
||||||
|
this.sicht = new IntersectionObserver(eintraege => {
|
||||||
|
this.sichtbar = eintraege[0].isIntersecting;
|
||||||
|
this.pausieren();
|
||||||
|
});
|
||||||
|
this.sicht.observe(behaelter);
|
||||||
|
document.addEventListener("visibilitychange", () => this.pausieren());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Katalog und Abweichungen zu einer Liste zusammenführen. */
|
||||||
|
setzeEinstellungen(einstellungen) {
|
||||||
|
this.einstellungen = einstellungen;
|
||||||
|
this.knoten = ENERGIEFLUSS_KATALOG.knoten.map(k => {
|
||||||
|
const e = einstellungen[k.id] || {};
|
||||||
|
return Object.assign({}, k, {
|
||||||
|
aktiv: k.pflicht || e.aktiv !== false,
|
||||||
|
anzeigeName: e.name || k.name,
|
||||||
|
lage: [e.x ?? k.x, e.y ?? k.y],
|
||||||
|
optionenWirksam: Object.assign({}, k.optionen || {}, e.optionen || {}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.nachId = Object.fromEntries(this.knoten.map(k => [k.id, k]));
|
||||||
|
// Wirksamer Elternkreis: der nächste eingeschaltete in der Kette.
|
||||||
|
for (const k of this.knoten) {
|
||||||
|
let e = k.eltern ? this.nachId[k.eltern] : null;
|
||||||
|
while (e && !e.aktiv) e = e.eltern ? this.nachId[e.eltern] : null;
|
||||||
|
k.elternWirksam = e;
|
||||||
|
let tiefe = 0;
|
||||||
|
for (let p = e; p; p = p.elternWirksam) tiefe++;
|
||||||
|
k.tiefe = tiefe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hochkantGewuenscht() {
|
||||||
|
return !this.bearbeiten && this.behaelter.clientWidth > 0 && this.behaelter.clientWidth < HOCHKANT_UNTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Lage eines Kreises in der aktuellen Ansicht. */
|
||||||
|
punkt(k) {
|
||||||
|
return this.hochkant ? [k.lage[1], k.lage[0]] : [k.lage[0], k.lage[1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
radius(k) {
|
||||||
|
return k.gross ? RADIUS_GROSS : RADIUS;
|
||||||
|
}
|
||||||
|
|
||||||
|
sichtbareKnoten() {
|
||||||
|
return this.knoten.filter(k => k.aktiv || this.bearbeiten);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------- Zeichnen */
|
||||||
|
zeichnen() {
|
||||||
|
this.hochkant = this.hochkantGewuenscht();
|
||||||
|
this.behaelter.innerHTML = "";
|
||||||
|
const svg = this.svg = el("svg", {
|
||||||
|
class: "enf" + (this.bearbeiten ? " enf-bearbeiten" : "") + (this.hochkant ? " enf-hoch" : ""),
|
||||||
|
role: "img", "aria-label": "Energiefluss",
|
||||||
|
}, this.behaelter);
|
||||||
|
this.ebeneFluss = el("g", { class: "enf-fluesse" }, svg);
|
||||||
|
this.ebeneKnoten = el("g", { class: "enf-knotenebene" }, svg);
|
||||||
|
|
||||||
|
this.elemente = {};
|
||||||
|
for (const k of this.sichtbareKnoten()) this.knotenZeichnen(k);
|
||||||
|
for (const k of this.sichtbareKnoten()) if (k.aktiv && k.elternWirksam) this.flussZeichnen(k);
|
||||||
|
this.rahmenSetzen();
|
||||||
|
this.pausieren();
|
||||||
|
|
||||||
|
if (this.letzte) {
|
||||||
|
const d = this.letzte;
|
||||||
|
this.letzte = null;
|
||||||
|
this.aktualisieren(d);
|
||||||
|
}
|
||||||
|
if (this.opt.onGezeichnet) this.opt.onGezeichnet();
|
||||||
|
}
|
||||||
|
|
||||||
|
rahmenSetzen() {
|
||||||
|
const ks = this.sichtbareKnoten();
|
||||||
|
if (!ks.length) return;
|
||||||
|
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
|
||||||
|
for (const k of ks) {
|
||||||
|
const [x, y] = this.punkt(k), r = this.radius(k);
|
||||||
|
x0 = Math.min(x0, x - r - 20); x1 = Math.max(x1, x + r + 20);
|
||||||
|
y0 = Math.min(y0, y - r - 20); y1 = Math.max(y1, y + r + 56); // Name unter dem Kreis
|
||||||
|
}
|
||||||
|
this.svg.setAttribute("viewBox",
|
||||||
|
[x0 - RAND / 2, y0 - RAND / 2, x1 - x0 + RAND, y1 - y0 + RAND].map(Math.round).join(" "));
|
||||||
|
}
|
||||||
|
|
||||||
|
knotenZeichnen(k) {
|
||||||
|
const [x, y] = this.punkt(k);
|
||||||
|
const r = this.radius(k);
|
||||||
|
const id = "enf" + this.nummer + "-" + k.id;
|
||||||
|
const g = el("g", {
|
||||||
|
class: "enf-knoten" + (k.aktiv ? "" : " enf-aus") + ((k.klick && !this.bearbeiten) ? " enf-klickbar" : ""),
|
||||||
|
transform: `translate(${x} ${y})`,
|
||||||
|
style: `--farbe:${k.farbe};--verz:${k.tiefe * 110}ms`,
|
||||||
|
"data-knoten": k.id,
|
||||||
|
}, this.ebeneKnoten);
|
||||||
|
|
||||||
|
const koerper = el("g", { class: "enf-koerper" }, g);
|
||||||
|
el("circle", { r: r + 17, class: "enf-hof" }, koerper);
|
||||||
|
el("circle", { r, class: "enf-spur" }, koerper);
|
||||||
|
const ring = el("g", { class: "enf-ring", transform: "rotate(-90)" }, koerper);
|
||||||
|
el("circle", { r: r - 11, class: "enf-scheibe" }, koerper);
|
||||||
|
|
||||||
|
// Füllstand (Batterie, Auto): Welle, die mit dem Stand steigt.
|
||||||
|
let fuellung = null;
|
||||||
|
if (k.symbol === "akku" || k.symbol === "wallbox") {
|
||||||
|
el("clipPath", { id: id + "-innen" }, el("defs", null, koerper))
|
||||||
|
.appendChild(el("circle", { r: r - 11 }));
|
||||||
|
const clip = el("g", { "clip-path": `url(#${id}-innen)` }, koerper);
|
||||||
|
fuellung = el("g", { class: "enf-fuellung", style: `--stand:0;--r:${r - 11}` }, clip);
|
||||||
|
const b = r * 4;
|
||||||
|
el("path", {
|
||||||
|
class: "enf-welle",
|
||||||
|
d: `M${-b} 0 q${r / 2} -9 ${r} 0 t${r} 0 t${r} 0 t${r} 0 t${r} 0 t${r} 0 t${r} 0 t${r} 0 V${r * 2} H${-b} Z`,
|
||||||
|
}, fuellung);
|
||||||
|
}
|
||||||
|
|
||||||
|
const symbol = el("g", {
|
||||||
|
class: "enf-symbol enf-sym-" + k.symbol,
|
||||||
|
transform: `translate(0 ${-r * 0.43}) scale(${k.gross ? 1.95 : 1.75})`,
|
||||||
|
}, koerper);
|
||||||
|
symbol.innerHTML = SYMBOLE[k.symbol]().replace(/\{id\}/g, id);
|
||||||
|
|
||||||
|
const wert = el("text", { class: "enf-wert", y: r * 0.2 + 6 }, koerper);
|
||||||
|
const neben = el("text", { class: "enf-neben", y: r * 0.2 + 33 }, koerper);
|
||||||
|
const name = el("text", { class: "enf-name", y: r + 46 }, g);
|
||||||
|
name.textContent = k.anzeigeName;
|
||||||
|
|
||||||
|
const zeichen = el("g", { class: "enf-zeichen" }, koerper);
|
||||||
|
|
||||||
|
if (k.klick && !this.bearbeiten) {
|
||||||
|
const t = el("g", { class: "enf-tipp", transform: `translate(${r * 0.72} ${-r * 0.72})` }, koerper);
|
||||||
|
el("circle", { r: 15 }, t);
|
||||||
|
el("path", { d: "M-3.5-6l6 6-6 6" }, t);
|
||||||
|
g.setAttribute("tabindex", "0");
|
||||||
|
g.setAttribute("role", "button");
|
||||||
|
g.setAttribute("aria-label", k.anzeigeName + " – Details");
|
||||||
|
const los = () => this.opt.onKlick && this.opt.onKlick(k);
|
||||||
|
g.addEventListener("click", los);
|
||||||
|
g.addEventListener("keydown", e => {
|
||||||
|
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); los(); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bewässerung: Zonen als Zeilen im Kreis. Die Ids gehören solarMQTT.js
|
||||||
|
// und werden nur außerhalb der Einstellungsvorschau vergeben.
|
||||||
|
if (k.zonen) {
|
||||||
|
wert.remove(); neben.remove();
|
||||||
|
symbol.setAttribute("transform", `translate(0 ${-r * 0.5}) scale(1.4)`);
|
||||||
|
k.zonen.forEach((z, i) => {
|
||||||
|
const zy = -r * 0.1 + i * 25;
|
||||||
|
const glyph = el("text", { class: "enf-zone-glyph", x: -r * 0.52, y: zy }, koerper);
|
||||||
|
glyph.textContent = z.zeichen;
|
||||||
|
el("title", null, glyph).textContent = z.name;
|
||||||
|
const nm = el("text", { class: "enf-zone-name", x: -r * 0.36, y: zy }, koerper);
|
||||||
|
nm.textContent = z.name;
|
||||||
|
const st = el("text", { class: "enf-zone-status", x: r * 0.6, y: zy }, koerper);
|
||||||
|
st.textContent = "Aus";
|
||||||
|
if (!this.bearbeiten) { glyph.id = z.symbol; st.id = z.status; }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.bearbeiten) this.ziehbarMachen(g, k);
|
||||||
|
|
||||||
|
this.elemente[k.id] = { g, ring, wert, neben, zeichen, symbol, fuellung, fluss: null, segmente: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
flussPfad(k) {
|
||||||
|
const e = k.elternWirksam;
|
||||||
|
const a = this.punkt(e), b = this.punkt(k);
|
||||||
|
// Knick: quer vom Elternkreis senkrecht los, dann waagerecht in den Kreis
|
||||||
|
// (Ecke x vom Eltern, y vom Kind); hochkant genau umgekehrt.
|
||||||
|
let c;
|
||||||
|
if (Math.abs(a[0] - b[0]) < 30 || Math.abs(a[1] - b[1]) < 30) {
|
||||||
|
c = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
|
||||||
|
} else {
|
||||||
|
c = this.hochkant ? [b[0], a[1]] : [a[0], b[1]];
|
||||||
|
}
|
||||||
|
const rand = (p, q, r) => {
|
||||||
|
const dx = q[0] - p[0], dy = q[1] - p[1], l = Math.hypot(dx, dy) || 1;
|
||||||
|
return [p[0] + dx / l * (r + 20), p[1] + dy / l * (r + 20)];
|
||||||
|
};
|
||||||
|
const s = rand(a, c, this.radius(e)), z = rand(b, c, this.radius(k));
|
||||||
|
return `M${s[0].toFixed(1)} ${s[1].toFixed(1)} Q${c[0]} ${c[1]} ${z[0].toFixed(1)} ${z[1].toFixed(1)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
flussZeichnen(k) {
|
||||||
|
const d = this.flussPfad(k);
|
||||||
|
const g = el("g", { class: "enf-fluss enf-still", style: `--farbe:${k.farbe};--verz:${k.tiefe * 110 + 150}ms` }, this.ebeneFluss);
|
||||||
|
const bett = el("path", { d, class: "enf-bett", pathLength: "1" }, g);
|
||||||
|
const hof = el("path", { d, class: "enf-strom-hof" }, g);
|
||||||
|
const strom = el("path", { d, class: "enf-strom" }, g);
|
||||||
|
this.elemente[k.id].fluss = { g, bett, hof, strom };
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------ Aktualisieren */
|
||||||
|
aktualisieren(daten) {
|
||||||
|
this.letzte = daten;
|
||||||
|
if (!this.svg) return;
|
||||||
|
const m = leser(daten);
|
||||||
|
for (const k of this.sichtbareKnoten()) {
|
||||||
|
const e = this.elemente[k.id];
|
||||||
|
if (!e) continue;
|
||||||
|
let z;
|
||||||
|
try {
|
||||||
|
z = k.lesen(m, k.optionenWirksam) || {};
|
||||||
|
} catch (fehler) {
|
||||||
|
console.warn("Energiefluss: " + k.id, fehler);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.knotenSetzen(k, e, z);
|
||||||
|
if (e.fluss) this.flussSetzen(e.fluss, z.fluss || 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
knotenSetzen(k, e, z) {
|
||||||
|
// Werte
|
||||||
|
const wert = z.wert !== undefined ? z.wert : leistungText(z.leistung || 0);
|
||||||
|
if (e.wert.isConnected && e.wert.textContent !== wert) e.wert.textContent = wert;
|
||||||
|
const neben = z.neben || "";
|
||||||
|
if (e.neben.isConnected && e.neben.textContent !== neben) e.neben.textContent = neben;
|
||||||
|
e.g.classList.toggle("enf-ruhe", !!z.ruhe && k.aktiv);
|
||||||
|
|
||||||
|
// Ring: Segmente in Hundertsteln des Umfangs
|
||||||
|
let segmente = z.segmente || (z.leistung !== undefined ? [{ w: Math.abs(z.leistung), farbe: k.farbe }] : []);
|
||||||
|
segmente = this.segmenteZusammenfassen(segmente);
|
||||||
|
const skala = k.skala || 15000;
|
||||||
|
while (e.segmente.length < segmente.length) {
|
||||||
|
e.segmente.push(el("circle", { r: this.radius(k), class: "enf-bogen", pathLength: "100" }, e.ring));
|
||||||
|
}
|
||||||
|
let start = 0;
|
||||||
|
e.segmente.forEach((kreis, i) => {
|
||||||
|
const s = segmente[i];
|
||||||
|
let laenge = s ? Math.max(0, Math.min(100 - start, (s.w / skala) * 100)) : 0;
|
||||||
|
if (laenge < 0.4) laenge = 0;
|
||||||
|
const luecke = laenge > 0 && start > 0 ? 0.6 : 0;
|
||||||
|
const a = (laenge - luecke).toFixed(2), off = (-(start + luecke)).toFixed(2);
|
||||||
|
const schluessel = a + "|" + off + "|" + (s ? s.farbe : "");
|
||||||
|
if (kreis._stand !== schluessel) {
|
||||||
|
kreis._stand = schluessel;
|
||||||
|
kreis.style.strokeDasharray = `${Math.max(0, a)} 100`;
|
||||||
|
kreis.style.strokeDashoffset = off;
|
||||||
|
if (s) kreis.style.stroke = s.farbe;
|
||||||
|
}
|
||||||
|
start += laenge;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Zustand des Symbols
|
||||||
|
const zu = z.zustand || {};
|
||||||
|
e.g.classList.toggle("enf-an", !!zu.an);
|
||||||
|
e.g.classList.toggle("enf-laden", !!zu.laden);
|
||||||
|
if (zu.stand !== undefined && isFinite(zu.stand)) {
|
||||||
|
const s = Math.max(0, Math.min(1, zu.stand)).toFixed(3);
|
||||||
|
if (e.g.style.getPropertyValue("--stand") !== s) {
|
||||||
|
e.g.style.setProperty("--stand", s);
|
||||||
|
if (e.fuellung) e.fuellung.style.setProperty("--stand", s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (zu.temps) {
|
||||||
|
zu.temps.forEach((t, i) => {
|
||||||
|
const r = e.symbol.querySelector(".enf-s-t" + i);
|
||||||
|
const f = tempFarbe(t);
|
||||||
|
if (r && r._f !== f) { r._f = f; r.style.fill = f; }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plaketten
|
||||||
|
const zeichen = z.zeichen || [];
|
||||||
|
const sig = zeichen.map(x => x.art + ":" + (x.text || "") + ":" + (x.titel || "")).join("|");
|
||||||
|
if (e.zeichen._sig !== sig) {
|
||||||
|
e.zeichen._sig = sig;
|
||||||
|
this.zeichenSetzen(k, e.zeichen, zeichen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Anteile ausgeschalteter Kreise fallen ins letzte Segment. */
|
||||||
|
segmenteZusammenfassen(segmente) {
|
||||||
|
const aus = segmente.filter(s => s.knoten && this.nachId[s.knoten] && !this.nachId[s.knoten].aktiv);
|
||||||
|
if (!aus.length) return segmente;
|
||||||
|
const rest = segmente.filter(s => !aus.includes(s)).map(s => Object.assign({}, s));
|
||||||
|
if (rest.length) rest[rest.length - 1].w += aus.reduce((a, s) => a + Math.max(0, s.w), 0);
|
||||||
|
return rest;
|
||||||
|
}
|
||||||
|
|
||||||
|
zeichenSetzen(k, gruppe, zeichen) {
|
||||||
|
gruppe.innerHTML = "";
|
||||||
|
const r = this.radius(k);
|
||||||
|
zeichen.forEach((z, i) => {
|
||||||
|
const art = ZEICHEN[z.art];
|
||||||
|
if (!art) return;
|
||||||
|
// Außen am Ring rechts unten, eine Plakette je 36 Grad - so bleiben
|
||||||
|
// Wert und Zeile darunter frei.
|
||||||
|
const winkel = (88 + i * 36) * Math.PI / 180;
|
||||||
|
const px = Math.sin(winkel) * (r + 12), py = -Math.cos(winkel) * (r + 12);
|
||||||
|
const p = el("g", { class: "enf-plakette", transform: `translate(${px.toFixed(1)} ${py.toFixed(1)})`, style: `--pfarbe:${art.farbe};--i:${i}` }, gruppe);
|
||||||
|
if (z.titel) el("title", null, p).textContent = z.titel;
|
||||||
|
if (z.text) {
|
||||||
|
const breite = 26 + z.text.length * 10;
|
||||||
|
el("rect", { x: -16, y: -16, width: breite, height: 32, rx: 16 }, p);
|
||||||
|
el("text", { class: "enf-plakette-text", x: 12, y: 1 }, p).textContent = z.text;
|
||||||
|
} else {
|
||||||
|
el("circle", { r: 16 }, p);
|
||||||
|
}
|
||||||
|
el("text", { class: "enf-plakette-glyph", x: 0, y: 1 }, p).textContent = art.glyph;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
flussSetzen(f, w) {
|
||||||
|
const a = Math.abs(w);
|
||||||
|
const still = a < 15;
|
||||||
|
const breite = still ? 0 : 3.5 + 6.5 * (1 - Math.exp(-a / 3000));
|
||||||
|
// Schneller bei mehr Leistung: 2,6 s je Abschnitt bei wenig, 0,7 s bei viel.
|
||||||
|
const dauer = still ? 0 : 0.7 + 1.9 * Math.exp(-a / 3000);
|
||||||
|
const sig = still ? "still" : breite.toFixed(1) + "|" + dauer.toFixed(2) + "|" + (w < 0);
|
||||||
|
if (f._sig === sig) return;
|
||||||
|
f._sig = sig;
|
||||||
|
f.g.classList.toggle("enf-still", still);
|
||||||
|
f.g.classList.toggle("enf-rueck", w < 0);
|
||||||
|
f.g.style.setProperty("--breite", breite.toFixed(1));
|
||||||
|
f.g.style.setProperty("--dauer", dauer.toFixed(2) + "s");
|
||||||
|
}
|
||||||
|
|
||||||
|
pausieren() {
|
||||||
|
if (!this.svg) return;
|
||||||
|
this.svg.classList.toggle("enf-pause", !this.sichtbar || document.hidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------- Bearbeiten */
|
||||||
|
ziehbarMachen(g, k) {
|
||||||
|
g.classList.add("enf-ziehbar");
|
||||||
|
g.addEventListener("pointerdown", ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
g.setPointerCapture(ev.pointerId);
|
||||||
|
const pt = this.svg.createSVGPoint();
|
||||||
|
const svgPunkt = e2 => {
|
||||||
|
pt.x = e2.clientX; pt.y = e2.clientY;
|
||||||
|
return pt.matrixTransform(this.svg.getScreenCTM().inverse());
|
||||||
|
};
|
||||||
|
const start = svgPunkt(ev);
|
||||||
|
const [x0, y0] = k.lage;
|
||||||
|
g.classList.add("enf-zieht");
|
||||||
|
const bewegen = e2 => {
|
||||||
|
const p = svgPunkt(e2);
|
||||||
|
k.lage = [Math.round((x0 + p.x - start.x) / 10) * 10, Math.round((y0 + p.y - start.y) / 10) * 10];
|
||||||
|
g.setAttribute("transform", `translate(${k.lage[0]} ${k.lage[1]})`);
|
||||||
|
for (const kind of this.knoten) {
|
||||||
|
const f = this.elemente[kind.id] && this.elemente[kind.id].fluss;
|
||||||
|
if (f && (kind === k || kind.elternWirksam === k)) {
|
||||||
|
const d = this.flussPfad(kind);
|
||||||
|
f.bett.setAttribute("d", d); f.hof.setAttribute("d", d); f.strom.setAttribute("d", d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const ende = () => {
|
||||||
|
g.removeEventListener("pointermove", bewegen);
|
||||||
|
g.classList.remove("enf-zieht");
|
||||||
|
this.rahmenSetzen();
|
||||||
|
if (this.opt.onVerschoben) this.opt.onVerschoben(k.id, k.lage[0], k.lage[1]);
|
||||||
|
};
|
||||||
|
g.addEventListener("pointermove", bewegen);
|
||||||
|
g.addEventListener("pointerup", ende, { once: true });
|
||||||
|
g.addEventListener("pointercancel", ende, { once: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Energiefluss = Energiefluss;
|
||||||
|
})();
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
/* ==========================================================================
|
||||||
|
Reiter "Übersicht": die Energiefluss-Übersicht an dieses Haus anpassen.
|
||||||
|
|
||||||
|
Links die Übersicht selbst im Bearbeitungsmodus (energiefluss.js) mit
|
||||||
|
Beispielwerten, rechts je Kreis Schalter und Name, beim PV-Kreis die
|
||||||
|
Flächen. Verschoben wird in der Vorschau. Gespeichert wird der ganze Stand
|
||||||
|
auf einmal (restricted/energiefluss.php hält davon nur die Abweichungen).
|
||||||
|
|
||||||
|
Nutzt textSchuetzen() aus settings.js.
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// Beispielwerte: ein sonniger Nachmittag, Auto lädt im EG, Heizstab läuft.
|
||||||
|
const BEISPIEL = {
|
||||||
|
"solarManager/P_PV": "8400", "solarManager/P_PVn": JSON.stringify([2600, 1300, 130, 130, 140, 140, 130, 130, 135, 135, 130, 130, 120, 120,
|
||||||
|
155, 150, 152, 158, 150, 149, 150, 154, 151, 150, 153, 155, 150, 92, 150, 152, 151, 150, 149, 0]),
|
||||||
|
"solarManager/P_Grid": "-900", "solarManager/i_l1evu": "-1.3", "solarManager/i_l2evu": "-1.4", "solarManager/i_l3evu": "-1.2",
|
||||||
|
"solarManager/P_Akku": "-2100", "solarManager/SOC": "64", "solarManager/crgMaxPct": "100",
|
||||||
|
"solarManager/autarky": "100", "solarManager/eff": "96",
|
||||||
|
"solarManager/P_Load": "-5400", "solarManager/og": "-350", "solarManager/eg": "3720", "solarManager/ug": "270",
|
||||||
|
"solarManager/pHeat": "1000", "solarManager/t_buffT": "61", "solarManager/t_buffM": "48", "solarManager/t_buffB": "35",
|
||||||
|
"solarManager/heatMode": "eco", "solarManager/heatOG": "1", "solarManager/heatEG": "0",
|
||||||
|
"solarManager/evPower": "3.2", "solarManager/evSOC": "78", "solarManager/evFuel": "0", "solarManager/evMode": "Eco",
|
||||||
|
"solarManager/evPlug": "1", "solarManager/evLock": "1", "solarManager/carRemChrg": "95",
|
||||||
|
"solarManager/evPowerOG": "0", "solarManager/evPlugOG": "0", "solarManager/evModeOG": "Eco",
|
||||||
|
};
|
||||||
|
|
||||||
|
let bild = null;
|
||||||
|
let gespeichert = {}; // Stand der Datenbank
|
||||||
|
let stand = {}; // Stand der Maske
|
||||||
|
let geladen = false;
|
||||||
|
|
||||||
|
const $ = id => document.getElementById(id);
|
||||||
|
const katalog = () => ENERGIEFLUSS_KATALOG.knoten;
|
||||||
|
const kopie = o => JSON.parse(JSON.stringify(o));
|
||||||
|
|
||||||
|
function hinweis(text, klasse) {
|
||||||
|
const ziel = $("energieflussHinweis");
|
||||||
|
if (!ziel) return;
|
||||||
|
ziel.className = "small " + (klasse || "text-body-secondary");
|
||||||
|
ziel.textContent = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function geaendert() {
|
||||||
|
const sauber = s => JSON.stringify(Object.keys(s).sort().map(k => [k, s[k]]));
|
||||||
|
const anders = sauber(normalisiert(stand)) !== sauber(normalisiert(gespeichert));
|
||||||
|
hinweis(anders ? "Nicht gespeichert." : "", anders ? "text-warning" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Nur echte Abweichungen, in fester Form - zum Vergleichen und Schicken. */
|
||||||
|
function normalisiert(s) {
|
||||||
|
const aus = {};
|
||||||
|
for (const k of katalog()) {
|
||||||
|
const e = s[k.id] || {};
|
||||||
|
const eintrag = {
|
||||||
|
aktiv: k.pflicht ? true : e.aktiv !== false,
|
||||||
|
name: (e.name || "").trim() || null,
|
||||||
|
x: e.x ?? null, y: e.y ?? null,
|
||||||
|
optionen: e.optionen || null,
|
||||||
|
};
|
||||||
|
if (eintrag.x === k.x && eintrag.y === k.y) { eintrag.x = null; eintrag.y = null; }
|
||||||
|
if (!eintrag.aktiv || eintrag.name || eintrag.x !== null || eintrag.optionen) aus[k.id] = eintrag;
|
||||||
|
}
|
||||||
|
return aus;
|
||||||
|
}
|
||||||
|
|
||||||
|
function vorschauZeichnen() {
|
||||||
|
if (!bild) {
|
||||||
|
bild = new Energiefluss($("energieflussVorschau"), {
|
||||||
|
bearbeiten: true,
|
||||||
|
einstellungen: stand,
|
||||||
|
onVerschoben(id, x, y) {
|
||||||
|
stand[id] = Object.assign({}, stand[id], { x, y });
|
||||||
|
listeZeichnen();
|
||||||
|
geaendert();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
bild.setzeEinstellungen(normalisiert(stand));
|
||||||
|
bild.zeichnen();
|
||||||
|
bild.aktualisieren(BEISPIEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ Liste */
|
||||||
|
function listeZeichnen() {
|
||||||
|
const ziel = $("energieflussListe");
|
||||||
|
const wirksam = bild ? bild.nachId : {};
|
||||||
|
ziel.innerHTML = katalog().map(k => {
|
||||||
|
const e = stand[k.id] || {};
|
||||||
|
const aktiv = k.pflicht || e.aktiv !== false;
|
||||||
|
const w = wirksam[k.id];
|
||||||
|
const eltern = w && w.elternWirksam;
|
||||||
|
const elternName = eltern ? (stand[eltern.id] && stand[eltern.id].name) || eltern.titel || eltern.name : null;
|
||||||
|
const verschoben = e.x != null && (e.x !== k.x || e.y !== k.y);
|
||||||
|
const flaechen = k.id === "pv" && aktiv ? flaechenZeichnen(k, e) : "";
|
||||||
|
return `
|
||||||
|
<div class="enf-e-zeile${aktiv ? "" : " enf-e-aus"}" data-id="${k.id}">
|
||||||
|
<div class="form-check form-switch m-0" title="${k.pflicht ? "Gehört immer dazu" : "Ein- oder ausschalten"}">
|
||||||
|
<input class="form-check-input" type="checkbox" id="enf-aktiv-${k.id}" data-feld="aktiv"
|
||||||
|
${aktiv ? "checked" : ""} ${k.pflicht ? "disabled" : ""}>
|
||||||
|
</div>
|
||||||
|
<label class="enf-e-name" for="enf-name-${k.id}">
|
||||||
|
<span class="enf-e-punkt" style="--farbe:${k.farbe}"></span>
|
||||||
|
<input class="form-control form-control-sm" id="enf-name-${k.id}" data-feld="name" maxlength="40"
|
||||||
|
placeholder="${textSchuetzen(k.name)}" title="${textSchuetzen(k.titel || k.name)}" value="${textSchuetzen(e.name || "")}" ${aktiv ? "" : "disabled"}>
|
||||||
|
</label>
|
||||||
|
<span class="enf-e-eltern small text-body-secondary">${elternName ? "an " + textSchuetzen(elternName) : k.eltern ? "" : "frei"}</span>
|
||||||
|
<button type="button" class="btn btn-sm btn-link text-body-secondary p-0" data-aktion="lage"
|
||||||
|
title="Lage aus dem Katalog" ${verschoben ? "" : "hidden"}><i class="bi bi-arrow-counterclockwise"></i></button>
|
||||||
|
${flaechen}
|
||||||
|
</div>`;
|
||||||
|
}).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function flaechenZeichnen(k, e) {
|
||||||
|
const liste = (e.optionen && e.optionen.flaechen) || k.optionen.flaechen;
|
||||||
|
const eigen = !!(e.optionen && e.optionen.flaechen);
|
||||||
|
return `
|
||||||
|
<div class="enf-e-flaechen">
|
||||||
|
<div class="small text-body-secondary mb-1">
|
||||||
|
Flächen im Sonnenring – welche Einträge von <code>P_PVn</code> dazugehören
|
||||||
|
${eigen ? `<button type="button" class="btn btn-sm btn-link p-0 ms-2" data-aktion="flaechen-zurueck">Katalog</button>` : ""}
|
||||||
|
</div>
|
||||||
|
${liste.map((f, i) => `
|
||||||
|
<div class="enf-e-flaeche" data-index="${i}">
|
||||||
|
<input type="color" class="form-control form-control-sm form-control-color" data-flaeche="farbe" value="${f.farbe}" title="Farbe">
|
||||||
|
<input class="form-control form-control-sm" data-flaeche="name" value="${textSchuetzen(f.name)}" placeholder="Name" maxlength="20">
|
||||||
|
<input class="form-control form-control-sm" type="number" min="0" max="99" data-flaeche="von" value="${f.von}" title="erster Eintrag">
|
||||||
|
<input class="form-control form-control-sm" type="number" min="0" max="99" data-flaeche="bis" value="${f.bis}" title="letzter Eintrag">
|
||||||
|
<button type="button" class="btn btn-sm btn-link text-body-secondary p-0" data-aktion="flaeche-weg" title="Fläche entfernen"><i class="bi bi-x-lg"></i></button>
|
||||||
|
</div>`).join("")}
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary mt-1" data-aktion="flaeche-neu"><i class="bi bi-plus-lg me-1"></i>Fläche</button>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function flaechenLesen(zeile) {
|
||||||
|
return [...zeile.querySelectorAll(".enf-e-flaeche")].map(r => ({
|
||||||
|
name: r.querySelector('[data-flaeche="name"]').value.trim(),
|
||||||
|
von: Math.max(0, parseInt(r.querySelector('[data-flaeche="von"]').value, 10) || 0),
|
||||||
|
bis: Math.max(0, parseInt(r.querySelector('[data-flaeche="bis"]').value, 10) || 0),
|
||||||
|
farbe: r.querySelector('[data-flaeche="farbe"]').value,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function flaechenSetzen(id, flaechen) {
|
||||||
|
const e = stand[id] = Object.assign({}, stand[id]);
|
||||||
|
e.optionen = Object.assign({}, e.optionen, { flaechen });
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------- Ereignisse */
|
||||||
|
function binden() {
|
||||||
|
const liste = $("energieflussListe");
|
||||||
|
|
||||||
|
liste.addEventListener("change", ev => {
|
||||||
|
const zeile = ev.target.closest(".enf-e-zeile");
|
||||||
|
if (!zeile) return;
|
||||||
|
const id = zeile.dataset.id;
|
||||||
|
if (ev.target.dataset.feld === "aktiv") {
|
||||||
|
stand[id] = Object.assign({}, stand[id], { aktiv: ev.target.checked });
|
||||||
|
vorschauZeichnen();
|
||||||
|
listeZeichnen();
|
||||||
|
} else if (ev.target.dataset.flaeche) {
|
||||||
|
flaechenSetzen(id, flaechenLesen(zeile));
|
||||||
|
vorschauZeichnen();
|
||||||
|
}
|
||||||
|
geaendert();
|
||||||
|
});
|
||||||
|
|
||||||
|
liste.addEventListener("input", ev => {
|
||||||
|
const zeile = ev.target.closest(".enf-e-zeile");
|
||||||
|
if (!zeile || ev.target.dataset.feld !== "name") return;
|
||||||
|
stand[zeile.dataset.id] = Object.assign({}, stand[zeile.dataset.id], { name: ev.target.value });
|
||||||
|
clearTimeout(binden._uhr);
|
||||||
|
binden._uhr = setTimeout(vorschauZeichnen, 300);
|
||||||
|
geaendert();
|
||||||
|
});
|
||||||
|
|
||||||
|
liste.addEventListener("click", ev => {
|
||||||
|
const knopf = ev.target.closest("[data-aktion]");
|
||||||
|
if (!knopf) return;
|
||||||
|
const zeile = knopf.closest(".enf-e-zeile");
|
||||||
|
const id = zeile.dataset.id;
|
||||||
|
switch (knopf.dataset.aktion) {
|
||||||
|
case "lage":
|
||||||
|
stand[id] = Object.assign({}, stand[id], { x: null, y: null });
|
||||||
|
break;
|
||||||
|
case "flaeche-neu": {
|
||||||
|
const f = flaechenLesen(zeile);
|
||||||
|
const bis = f.length ? f[f.length - 1].bis + 1 : 0;
|
||||||
|
f.push({ name: "Fläche " + (f.length + 1), von: bis, bis, farbe: "#f7c000" });
|
||||||
|
flaechenSetzen(id, f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "flaeche-weg": {
|
||||||
|
const f = flaechenLesen(zeile);
|
||||||
|
f.splice(Number(knopf.closest(".enf-e-flaeche").dataset.index), 1);
|
||||||
|
flaechenSetzen(id, f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "flaechen-zurueck":
|
||||||
|
if (stand[id] && stand[id].optionen) {
|
||||||
|
const o = Object.assign({}, stand[id].optionen);
|
||||||
|
delete o.flaechen;
|
||||||
|
stand[id].optionen = Object.keys(o).length ? o : null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vorschauZeichnen();
|
||||||
|
listeZeichnen();
|
||||||
|
geaendert();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("energieflussSpeichern").addEventListener("click", speichern);
|
||||||
|
$("energieflussNeuLaden").addEventListener("click", () => {
|
||||||
|
stand = kopie(gespeichert);
|
||||||
|
vorschauZeichnen(); listeZeichnen(); geaendert();
|
||||||
|
});
|
||||||
|
$("energieflussZuruecksetzen").addEventListener("click", () => {
|
||||||
|
stand = {};
|
||||||
|
vorschauZeichnen(); listeZeichnen(); geaendert();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function speichern() {
|
||||||
|
const knopf = $("energieflussSpeichern");
|
||||||
|
knopf.disabled = true;
|
||||||
|
try {
|
||||||
|
const n = normalisiert(stand);
|
||||||
|
const knoten = Object.keys(n).map(id => Object.assign({ id }, n[id]));
|
||||||
|
const antwort = await fetch("ajax/settings.php?action=energiefluss-save", {
|
||||||
|
method: "POST", headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ knoten }),
|
||||||
|
});
|
||||||
|
const d = await antwort.json();
|
||||||
|
if (!antwort.ok || d.error) throw new Error(d.error || "HTTP " + antwort.status);
|
||||||
|
gespeichert = d.einstellungen || {};
|
||||||
|
stand = kopie(gespeichert);
|
||||||
|
vorschauZeichnen(); listeZeichnen();
|
||||||
|
hinweis("Gespeichert – die Solar-Seite zeigt es beim nächsten Laden.", "text-success");
|
||||||
|
} catch (e) {
|
||||||
|
hinweis(e.message, "text-danger");
|
||||||
|
} finally {
|
||||||
|
knopf.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function laden() {
|
||||||
|
if (geladen) return;
|
||||||
|
geladen = true;
|
||||||
|
try {
|
||||||
|
const antwort = await fetch("ajax/settings.php?action=energiefluss", { cache: "no-store" });
|
||||||
|
const d = await antwort.json();
|
||||||
|
if (!antwort.ok || d.error) throw new Error(d.error || "HTTP " + antwort.status);
|
||||||
|
gespeichert = d.einstellungen || {};
|
||||||
|
stand = kopie(gespeichert);
|
||||||
|
binden();
|
||||||
|
vorschauZeichnen();
|
||||||
|
listeZeichnen();
|
||||||
|
} catch (e) {
|
||||||
|
geladen = false;
|
||||||
|
$("energieflussListe").innerHTML = '<div class="alert alert-danger mb-0">Übersicht nicht abrufbar: '
|
||||||
|
+ textSchuetzen(e.message) + "</div>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
if (!$("energieflussVorschau") || typeof Energiefluss === "undefined") return;
|
||||||
|
// Erst zeichnen, wenn der Reiter offen ist - verborgen hat die Vorschau keine Größe.
|
||||||
|
const knopf = $("einstellung-energiefluss-tab");
|
||||||
|
if (knopf) knopf.addEventListener("shown.bs.tab", laden);
|
||||||
|
if ($("einstellung-energiefluss").classList.contains("active")) laden();
|
||||||
|
// Ein Anker in der Adresse öffnet den Reiter erst nach DOMContentLoaded.
|
||||||
|
setTimeout(() => { if ($("einstellung-energiefluss").classList.contains("active")) laden(); }, 0);
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
/* ==========================================================================
|
||||||
|
Katalog der Energiefluss-Übersicht (Solar-Seite, Karte "Realtime").
|
||||||
|
|
||||||
|
Jeder Kreis steht hier genau einmal: wie er heißt, wie er aussieht, woran
|
||||||
|
er hängt, wo er steht und woher seine Werte kommen. Gezeichnet wird er von
|
||||||
|
js/solar/energiefluss.js, ohne dass der Renderer einen Kreis beim Namen
|
||||||
|
kennt. Ein neuer Kreis ist also ein neuer Eintrag hier - sonst nichts.
|
||||||
|
|
||||||
|
Was ein Haus davon abweichend einstellt (Kreis aus, eigener Name, eigene
|
||||||
|
Lage, Optionen), steht in homeMesh.energiefluss und wird unter
|
||||||
|
Einstellungen -> Übersicht gepflegt.
|
||||||
|
|
||||||
|
Felder eines Eintrags:
|
||||||
|
id feste Kennung, steht in der Datenbank
|
||||||
|
name Beschriftung unter dem Kreis
|
||||||
|
titel Bezeichnung in den Einstellungen, falls name dort missverständlich ist
|
||||||
|
symbol Schlüssel in ENERGIEFLUSS_SYMBOLE (energiefluss.js)
|
||||||
|
farbe Hauptfarbe: Ring, Symbol, Fluss
|
||||||
|
eltern Kennung des Kreises, mit dem er über einen Fluss verbunden
|
||||||
|
ist; null = frei stehend. Ist der Elternkreis aus, hängt der
|
||||||
|
Kreis am nächsten eingeschalteten darüber.
|
||||||
|
pflicht lässt sich nicht ausschalten (die Mitte des Bildes)
|
||||||
|
gross größerer Kreis
|
||||||
|
x, y Mitte in der Queransicht (Zeichenfläche etwa 1400 x 850)
|
||||||
|
skala Leistung in W, bei der der Ring voll ist
|
||||||
|
klick {ansicht: "anlage"} oder {modal: "heater"} - Ziel beim Antippen
|
||||||
|
optionen Vorgabe für die Optionen, die in den Einstellungen änderbar sind
|
||||||
|
zonen nur Bewässerung: die Anzeigen, die solarMQTT.js beschreibt
|
||||||
|
lesen(m, opt) liefert den Zustand aus den MQTT-Werten:
|
||||||
|
leistung W, bestimmt den Hauptwert und den Ring
|
||||||
|
wert Text statt der Leistung (optional)
|
||||||
|
neben kleine Zeile unter dem Wert
|
||||||
|
segmente [{w, farbe, knoten?}] statt eines einzigen Rings; knoten =
|
||||||
|
der Anteil gehört zu diesem Kreis und fällt, ist der aus, in
|
||||||
|
das letzte Segment
|
||||||
|
fluss W vom Elternkreis zu diesem hin; negativ = in Gegenrichtung
|
||||||
|
ruhe Kreis blass zeichnen (nichts los)
|
||||||
|
zeichen kleine Plaketten: [{art, text?, titel?}] - art siehe
|
||||||
|
ENERGIEFLUSS_ZEICHEN in energiefluss.js
|
||||||
|
zustand was das Symbol braucht: {an, stand, laden, temps}
|
||||||
|
|
||||||
|
Vorzeichen wie im Manager: P_Grid > 0 Bezug, P_Akku > 0 Entladen,
|
||||||
|
P_Load und og negativ.
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
const ENERGIEFLUSS_KATALOG = (function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const kw = w => (Math.abs(w) / 1000).toFixed(1).replace(".", ",");
|
||||||
|
|
||||||
|
const wallboxZeichen = (modus, stecker, gesperrt) => {
|
||||||
|
const z = [];
|
||||||
|
if (modus === "Eco") z.push({ art: "eco", titel: "Eco-Modus" });
|
||||||
|
else if (modus === "Next Trip") z.push({ art: "uhr", titel: "Nächste Fahrt" });
|
||||||
|
else if (modus !== undefined) z.push({ art: "manuell", titel: "Standard-Modus" });
|
||||||
|
if (stecker) z.push({ art: "stecker", titel: "Fahrzeug angesteckt" });
|
||||||
|
if (gesperrt) z.push({ art: "schloss", titel: "Fahrzeug verriegelt" });
|
||||||
|
return z;
|
||||||
|
};
|
||||||
|
|
||||||
|
const knoten = [
|
||||||
|
// ---------------------------------------------------------------- Quellen
|
||||||
|
{
|
||||||
|
id: "pv", name: "Sonne", symbol: "sonne", farbe: "#f7c000",
|
||||||
|
eltern: "wr", x: 180, y: 130, skala: 15000,
|
||||||
|
klick: { ansicht: "anlage" },
|
||||||
|
// Welche Einträge von solarManager/P_PVn zu welcher Fläche gehören.
|
||||||
|
// Reihenfolge wie in solarManager.py.
|
||||||
|
optionen: {
|
||||||
|
flaechen: [
|
||||||
|
{ name: "Dach", von: 0, bis: 1, farbe: "#f7c000" },
|
||||||
|
{ name: "Veranda", von: 2, bis: 13, farbe: "#f78d00" },
|
||||||
|
{ name: "Carport", von: 14, bis: 33, farbe: "#f75a1e" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
lesen(m, opt) {
|
||||||
|
const pvn = m.json("solarManager/P_PVn") || [];
|
||||||
|
const pv = m.zahl("solarManager/P_PV");
|
||||||
|
const flaechen = (opt.flaechen || []).map(f => {
|
||||||
|
let w = 0;
|
||||||
|
for (let i = f.von; i <= f.bis && i < pvn.length; i++) w += Number(pvn[i]) || 0;
|
||||||
|
return { w, farbe: f.farbe, name: f.name };
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
leistung: pv,
|
||||||
|
segmente: flaechen.length ? flaechen : null,
|
||||||
|
neben: flaechen.map(f => kw(f.w)).join(" · "),
|
||||||
|
fluss: -pv,
|
||||||
|
ruhe: pv < 20,
|
||||||
|
zustand: { an: pv >= 20, stand: Math.min(1, pv / 10000) },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "netz", name: "Netz", symbol: "netz", farbe: "#9a9aab",
|
||||||
|
eltern: "wr", x: 100, y: 380, skala: 15000,
|
||||||
|
lesen(m) {
|
||||||
|
const netz = m.zahl("solarManager/P_Grid");
|
||||||
|
const a = n => (Math.round(Math.abs(m.zahl("solarManager/i_" + n + "evu")) * 10) / 10).toString().replace(".", ",");
|
||||||
|
return {
|
||||||
|
leistung: Math.abs(netz),
|
||||||
|
neben: a("l1") + "·" + a("l2") + "·" + a("l3") + " A",
|
||||||
|
fluss: -netz,
|
||||||
|
ruhe: Math.abs(netz) < 30,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "akku", name: "Batterie", symbol: "akku", farbe: "#6cbe58",
|
||||||
|
eltern: "wr", x: 180, y: 630, skala: 15000,
|
||||||
|
lesen(m) {
|
||||||
|
const p = m.zahl("solarManager/P_Akku");
|
||||||
|
const soc = m.zahl("solarManager/SOC");
|
||||||
|
const grenze = m.zahl("solarManager/crgMaxPct");
|
||||||
|
const laden = p < -20;
|
||||||
|
const zeichen = [];
|
||||||
|
if (laden && grenze < 100) {
|
||||||
|
zeichen.push({ art: "uhr", text: Math.round(grenze) + " %", titel: "Ladeleistung begrenzt" });
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
leistung: Math.abs(p),
|
||||||
|
neben: Math.round(soc) + " %" + (laden ? " · lädt" : p > 20 ? " · entlädt" : ""),
|
||||||
|
fluss: -p,
|
||||||
|
ruhe: Math.abs(p) < 20,
|
||||||
|
zeichen,
|
||||||
|
zustand: { stand: soc / 100, laden, an: Math.abs(p) >= 20 },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ Mitte
|
||||||
|
{
|
||||||
|
id: "wr", name: "Autarkie", titel: "Wechselrichter (Autarkie)", symbol: "wechselrichter", farbe: "#b9c0c8",
|
||||||
|
eltern: null, pflicht: true, gross: true, x: 430, y: 380, skala: 100,
|
||||||
|
lesen(m) {
|
||||||
|
const aut = m.zahl("solarManager/autarky");
|
||||||
|
return {
|
||||||
|
leistung: aut,
|
||||||
|
wert: Math.round(aut) + " %",
|
||||||
|
neben: "Wirkungsgrad " + Math.round(m.zahl("solarManager/eff")) + " %",
|
||||||
|
segmente: [{ w: aut, farbe: "#6cbe58" }],
|
||||||
|
zustand: { an: m.zahl("solarManager/P_PV") >= 20 },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "verbrauch", name: "Verbrauch", symbol: "verbrauch", farbe: "#71afcd",
|
||||||
|
eltern: "wr", pflicht: true, gross: true, x: 770, y: 380, skala: 15000,
|
||||||
|
lesen(m) {
|
||||||
|
const last = -m.zahl("solarManager/P_Load");
|
||||||
|
const og = -m.zahl("solarManager/og");
|
||||||
|
const eg = m.zahl("solarManager/eg");
|
||||||
|
const ug = m.zahl("solarManager/ug");
|
||||||
|
const heiz = m.zahl("solarManager/pHeat");
|
||||||
|
const gemein = Math.max(0, last - ug - eg - og);
|
||||||
|
return {
|
||||||
|
leistung: last,
|
||||||
|
neben: "Gemein " + kw(gemein) + " kW",
|
||||||
|
segmente: [
|
||||||
|
{ w: og, farbe: "#70ccbb", knoten: "og" },
|
||||||
|
{ w: eg, farbe: "#70cc9c", knoten: "eg" },
|
||||||
|
{ w: ug, farbe: "#83a55b", knoten: "ug" },
|
||||||
|
{ w: heiz, farbe: "#c2614e", knoten: "heizstab" },
|
||||||
|
{ w: gemein, farbe: "#71afcd" },
|
||||||
|
],
|
||||||
|
fluss: last,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// --------------------------------------------------------------- Etagen
|
||||||
|
{
|
||||||
|
id: "og", name: "Obergeschoss", symbol: "etageOben", farbe: "#70ccbb",
|
||||||
|
eltern: "verbrauch", x: 920, y: 70, skala: 15000,
|
||||||
|
lesen(m) {
|
||||||
|
const w = -m.zahl("solarManager/og");
|
||||||
|
return {
|
||||||
|
leistung: w, fluss: w, ruhe: w < 20,
|
||||||
|
zeichen: m.zahl("solarManager/heatOG") ? [{ art: "heizung", titel: "Heizung an" }] : [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "eg", name: "Erdgeschoss", symbol: "etageMitte", farbe: "#70cc9c",
|
||||||
|
eltern: "verbrauch", x: 920, y: 670, skala: 15000,
|
||||||
|
lesen(m) {
|
||||||
|
const w = m.zahl("solarManager/eg");
|
||||||
|
const wb = m.zahl("solarManager/evPower") * 1000;
|
||||||
|
return {
|
||||||
|
leistung: w, fluss: w, ruhe: w < 20,
|
||||||
|
segmente: [
|
||||||
|
{ w: Math.max(0, w - wb), farbe: "#70cc9c" },
|
||||||
|
{ w: Math.min(w, wb), farbe: "#60afbf", knoten: "wallboxEG" },
|
||||||
|
],
|
||||||
|
zeichen: m.zahl("solarManager/heatEG") ? [{ art: "heizung", titel: "Heizung an" }] : [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "ug", name: "Untergeschoss", symbol: "etageUnten", farbe: "#83a55b",
|
||||||
|
eltern: "verbrauch", x: 600, y: 670, skala: 15000,
|
||||||
|
lesen(m) {
|
||||||
|
const w = m.zahl("solarManager/ug");
|
||||||
|
return { leistung: w, fluss: w, ruhe: w < 20 };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// ----------------------------------------------------------- Verbraucher
|
||||||
|
{
|
||||||
|
id: "heizstab", name: "Heizstab", symbol: "speicher", farbe: "#c2614e",
|
||||||
|
eltern: "verbrauch", x: 1120, y: 380, skala: 6000,
|
||||||
|
klick: { modal: "heater" },
|
||||||
|
lesen(m) {
|
||||||
|
const w = m.zahl("solarManager/pHeat");
|
||||||
|
const oben = m.zahl("solarManager/t_buffT");
|
||||||
|
const mitte = m.zahl("solarManager/t_buffM");
|
||||||
|
const unten = m.zahl("solarManager/t_buffB");
|
||||||
|
const eco = m.text("solarManager/heatMode") === "eco";
|
||||||
|
const grad = t => (Math.round(t * 10) / 10).toString().replace(".", ",");
|
||||||
|
return {
|
||||||
|
leistung: w, fluss: w, ruhe: w < 20,
|
||||||
|
neben: grad(oben) + " · " + grad(mitte) + " °C",
|
||||||
|
zeichen: [eco ? { art: "eco", titel: "Automatik (Überschuss)" } : { art: "manuell", titel: "Feste Leistung" }],
|
||||||
|
zustand: { an: w >= 20, temps: [oben, mitte, unten] },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "wallboxOG", name: "Wallbox OG", symbol: "wallbox", farbe: "#4b71a6",
|
||||||
|
eltern: "og", x: 1220, y: 70, skala: 11000,
|
||||||
|
klick: { modal: "CarOG" },
|
||||||
|
lesen(m) {
|
||||||
|
const w = m.zahl("solarManager/evPowerOG") * 1000;
|
||||||
|
const stecker = m.zahl("solarManager/evPlugOG") > 0;
|
||||||
|
return {
|
||||||
|
leistung: w, fluss: w, ruhe: w < 50 && !stecker,
|
||||||
|
neben: w >= 50 ? "lädt" : stecker ? "angesteckt" : "kein Auto",
|
||||||
|
zeichen: wallboxZeichen(m.text("solarManager/evModeOG"), stecker, false),
|
||||||
|
zustand: { an: w >= 50 },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "wallboxEG", name: "Wallbox EG", symbol: "wallbox", farbe: "#60afbf",
|
||||||
|
eltern: "eg", x: 1220, y: 670, skala: 11000,
|
||||||
|
klick: { modal: "CarEG" },
|
||||||
|
lesen(m) {
|
||||||
|
const w = m.zahl("solarManager/evPower") * 1000;
|
||||||
|
const soc = m.zahl("solarManager/evSOC");
|
||||||
|
const tank = m.zahl("solarManager/evFuel");
|
||||||
|
// solarManager/evPlug kommt als 1/0; aeltere Manager schickten den
|
||||||
|
// Text der Wallbox ("no car", "charging", ...).
|
||||||
|
const roh = m.text("solarManager/evPlug");
|
||||||
|
const stecker = isNaN(Number(roh)) ? (roh !== undefined && roh !== "no car") : Number(roh) > 0;
|
||||||
|
let neben = Math.round(soc) + " %";
|
||||||
|
const rest = m.zahl("solarManager/carRemChrg");
|
||||||
|
if (w >= 50 && rest > -1 && m.text("solarManager/carRemChrg") !== undefined) {
|
||||||
|
const fertig = new Date(Date.now() + rest * 60000);
|
||||||
|
neben = "fertig " + String(fertig.getHours()).padStart(2, "0") + ":" + String(fertig.getMinutes()).padStart(2, "0");
|
||||||
|
}
|
||||||
|
const zeichen = wallboxZeichen(m.text("solarManager/evMode"), stecker, m.zahl("solarManager/evLock") > 0);
|
||||||
|
if (tank > 0) zeichen.push({ art: "tank", text: Math.round(tank) + " %", titel: "Tank" });
|
||||||
|
return {
|
||||||
|
leistung: w, fluss: w, ruhe: w < 50 && !stecker,
|
||||||
|
neben, zeichen,
|
||||||
|
zustand: { an: w >= 50, stand: soc / 100 },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// ------------------------------------------------------------- frei
|
||||||
|
{
|
||||||
|
id: "bewaesserung", name: "Bewässerung", symbol: "tropfen", farbe: "#4b8fd6",
|
||||||
|
eltern: null, x: 550, y: 70,
|
||||||
|
klick: { modal: "watering" },
|
||||||
|
// Ids wie in wateringZones (solarMQTT.js) - dort werden Farbe, Text und
|
||||||
|
// Tooltip gesetzt, hier nur die Stellen dafür angelegt.
|
||||||
|
zonen: [
|
||||||
|
{ name: "Tröge", zeichen: "", status: "wateringTroughStatus", symbol: "wateringTroughIcon" },
|
||||||
|
{ name: "Vorn", zeichen: "", status: "wateringFrontStatus", symbol: "wateringFrontIcon" },
|
||||||
|
{ name: "Beete", zeichen: "", status: "wateringBedsStatus", symbol: "wateringBedsIcon" },
|
||||||
|
],
|
||||||
|
lesen() {
|
||||||
|
return { wert: "" };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return { knoten };
|
||||||
|
})();
|
||||||
+64
-235
@@ -142,255 +142,84 @@ function setFlowWidth(id, width) {
|
|||||||
flow.style.willChange = sichtbar ? "transform" : "auto";
|
flow.style.willChange = sichtbar ? "transform" : "auto";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Die Energiefluss-Uebersicht (js/solar/energiefluss.js) und der PV-Lageplan.
|
||||||
|
// Bis September 2026 stand hier updateValuesMQTT fuer das feste realtime.svg -
|
||||||
|
// jeder Kreis mit eigenen Zeilen. Was ein Kreis zeigt, steht jetzt im Katalog
|
||||||
|
// (energieflussKatalog.js); hier bleibt nur der Lageplan hinter der Sonne.
|
||||||
|
let energiefluss = null;
|
||||||
|
|
||||||
const solarSVG = {
|
const solarSVG = {
|
||||||
updateCnt: 99,
|
updateCnt: 99,
|
||||||
/**
|
/**
|
||||||
* Beide Zwischenspeicher verwerfen - noetig, falls das SVG neu gezeichnet
|
* Beide Zwischenspeicher verwerfen - noetig, wenn die Uebersicht neu
|
||||||
* wird. Nur die Knoten zu vergessen reicht nicht: letzterWert wuerde dann
|
* gezeichnet wird (Wechsel quer/hochkant). Nur die Knoten zu vergessen
|
||||||
* jeden Schreibzugriff als "unveraendert" ueberspringen und das frische
|
* reicht nicht: letzterWert wuerde dann jeden Schreibzugriff als
|
||||||
* SVG behielte seine Platzhalter.
|
* "unveraendert" ueberspringen und die frischen Elemente behielten ihre
|
||||||
|
* Platzhalter.
|
||||||
*/
|
*/
|
||||||
fillElementArray: function () {
|
fillElementArray: function () {
|
||||||
for (const id in svgElements) delete svgElements[id];
|
for (const id in svgElements) delete svgElements[id];
|
||||||
letzterWert.clear();
|
letzterWert.clear();
|
||||||
},
|
},
|
||||||
updateValuesMQTT: function (mqttData) {
|
updateValuesMQTT: function (mqttData) {
|
||||||
|
if (energiefluss) energiefluss.aktualisieren(mqttData);
|
||||||
|
|
||||||
const angleFactor = 15000 / 288;
|
// Lageplan (assets/img/pvplan.svg): Leistung je Eingang und die Liste
|
||||||
const sixkWangleFactor = 6000 / 288;
|
// der Wechselrichter.
|
||||||
var cons = Number(mqttData["solarManager/P_Load"]);
|
let pvn = [];
|
||||||
var pvn = JSON.parse(mqttData["solarManager/P_PVn"]);
|
try { pvn = JSON.parse(mqttData["solarManager/P_PVn"]); } catch (e) { pvn = []; }
|
||||||
var pv = Number(mqttData["solarManager/P_PV"]);
|
for (let i = 0; i < pvn.length; i++) {
|
||||||
var pv2 = Number(pvn[1]);
|
if (svgElement("det_pv" + i + "P")) setText("det_pv" + i + "P", powerToString(pvn[i]));
|
||||||
var pv1 = Number(pvn[0]);
|
|
||||||
var pv3 = pv - (pv1 + pv2);
|
|
||||||
var pbatt = Number(mqttData["solarManager/P_Akku"]);
|
|
||||||
var limbatt = Number(mqttData["solarManager/crgMaxPct"]);
|
|
||||||
var grid = Number(mqttData["solarManager/P_Grid"]);
|
|
||||||
var soc = Number(mqttData["solarManager/SOC"]);
|
|
||||||
var og = Number(mqttData["solarManager/og"]);
|
|
||||||
var eg = Number(mqttData["solarManager/eg"]);
|
|
||||||
var ug = Number(mqttData["solarManager/ug"]);
|
|
||||||
var evsoc = Number(mqttData["solarManager/evSOC"]);
|
|
||||||
var evlock = Number(mqttData["solarManager/evLock"]);
|
|
||||||
var evMode = mqttData["solarManager/evMode"];
|
|
||||||
var evPower = Number(mqttData["solarManager/evPower"]);
|
|
||||||
var plugev = Number(mqttData["solarManager/evPlug"]);
|
|
||||||
var fuelev = Number(mqttData["solarManager/evFuel"]);
|
|
||||||
var heatOG = Number(mqttData["solarManager/heatOG"]);
|
|
||||||
var heatEG = Number(mqttData["solarManager/heatEG"]);
|
|
||||||
var puffO = Number(mqttData["solarManager/t_buffT"]);
|
|
||||||
var puffM = Number(mqttData["solarManager/t_buffM"]);
|
|
||||||
var puffU = Number(mqttData["solarManager/t_buffB"]);
|
|
||||||
var heatMode = mqttData["solarManager/heatMode"];
|
|
||||||
var carRemChrg = Number(mqttData["solarManager/carRemChrg"]);
|
|
||||||
var aut = Number(mqttData["solarManager/autarky"]);
|
|
||||||
var Pheat = Number(mqttData["solarManager/pHeat"]);
|
|
||||||
var waterHeight = Number(mqttData["solarManager/waterHeight"]);
|
|
||||||
var waterTemp = Number(mqttData["solarManager/waterTemp"]);
|
|
||||||
var p_wr = Number(mqttData["solarManager/P_WR"]);
|
|
||||||
var eff = Number(mqttData["solarManager/eff"]);
|
|
||||||
var i_l1evu = Number(mqttData["solarManager/i_l1evu"]);
|
|
||||||
var i_l2evu = Number(mqttData["solarManager/i_l2evu"]);
|
|
||||||
var i_l3evu = Number(mqttData["solarManager/i_l3evu"]);
|
|
||||||
var evPowerOG = Number(mqttData["solarManager/evPowerOG"]);
|
|
||||||
var evPlugOG = Number(mqttData["solarManager/evPlugOG"]);
|
|
||||||
var evModeOG = mqttData["solarManager/evModeOG"];
|
|
||||||
|
|
||||||
|
|
||||||
var common = -cons - ug - eg + og ;
|
|
||||||
if(common < 0){
|
|
||||||
common = 0;
|
|
||||||
}
|
}
|
||||||
|
let liste = "";
|
||||||
|
for (let i = 0; mqttData["solarManager/inverters" + i + "/name"] !== undefined; i++) {
|
||||||
|
const basis = "solarManager/inverters" + i;
|
||||||
|
const gestoert = mqttData[basis + "/error"] > 0;
|
||||||
|
liste += '<tspan x="0" dy="1.2em">' + mqttData[basis + "/name"] + ":</tspan>"
|
||||||
|
+ '<tspan x="160">' + (gestoert ? "--" : Math.round(mqttData[basis + "/temp"])) + " °C</tspan>"
|
||||||
|
+ '<tspan x="215">' + (gestoert ? "-- W" : powerToString(mqttData[basis + "/p_AC"])) + "</tspan>";
|
||||||
|
}
|
||||||
|
if (svgElement("invList")) setHTML("invList", liste);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
setAttr('consumerArc', "d", describeArc(100, 100, 95, 0, Math.round(-cons / angleFactor)));
|
/** Energiefluss aufbauen und den Umschalter Uebersicht/Anlage verdrahten. */
|
||||||
|
function energieflussStarten() {
|
||||||
|
const behaelter = document.getElementById("energiefluss");
|
||||||
|
if (!behaelter || typeof Energiefluss === "undefined") return;
|
||||||
|
|
||||||
setAttr("ogArc", "d", describeArc(100, 100, 95, 0, Math.round(-og / angleFactor)));
|
const zeigeAnsicht = function (name) {
|
||||||
setText("consumerText", powerToString(-cons));
|
document.querySelectorAll("[data-enf-ansicht]").forEach(function (knopf) {
|
||||||
|
const an = knopf.dataset.enfAnsicht === name;
|
||||||
setText("consumerTextAllg", "Gemein: "+powerToString(common));
|
knopf.classList.toggle("active", an);
|
||||||
|
knopf.setAttribute("aria-selected", an ? "true" : "false");
|
||||||
if (evPower > 0) {
|
});
|
||||||
setDisplay("evCharge", "");
|
document.querySelectorAll("[data-enf-ansicht-flaeche]").forEach(function (flaeche) {
|
||||||
if (carRemChrg > -1) {
|
const an = flaeche.dataset.enfAnsichtFlaeche === name;
|
||||||
var today = new Date();
|
flaeche.hidden = !an;
|
||||||
today.setMinutes(today.getMinutes() + carRemChrg);
|
if (an) {
|
||||||
var h = today.getHours();
|
flaeche.classList.remove("enf-rein");
|
||||||
var m = today.getMinutes();
|
void flaeche.offsetWidth;
|
||||||
if (h < 10)
|
flaeche.classList.add("enf-rein");
|
||||||
h = "0" + h;
|
|
||||||
if (m < 10)
|
|
||||||
m = "0" + m;
|
|
||||||
setText("evRemText", h + ":" + m);
|
|
||||||
}
|
}
|
||||||
} else {
|
});
|
||||||
setText("evRemText", "");
|
};
|
||||||
}
|
document.querySelectorAll("[data-enf-ansicht]").forEach(function (knopf) {
|
||||||
setText("ogText", powerToString(-og));
|
knopf.addEventListener("click", function () { zeigeAnsicht(knopf.dataset.enfAnsicht); });
|
||||||
setText("egText", powerToString(eg));
|
});
|
||||||
setText("ugText", powerToString(ug));
|
|
||||||
setText("pvText", powerToString(pv));
|
|
||||||
setText("pv1txt", powerToString(pv1));
|
|
||||||
setText("pv2txt", powerToString(pv2));
|
|
||||||
setText("pv3txt", powerToString(pv3));
|
|
||||||
setText("il1txt", Math.round(i_l1evu * 10) / 10 + " A");
|
|
||||||
setText("il2txt", Math.round(i_l2evu * 10) / 10 + " A");
|
|
||||||
setText("il3txt", Math.round(i_l3evu * 10) / 10 + " A");
|
|
||||||
setText("batText", powerToString(pbatt));
|
|
||||||
setText("heatText", powerToString(Pheat));
|
|
||||||
setText("evText", powerToString(evPower * 1000));
|
|
||||||
setText("evTextOG", powerToString(evPowerOG * 1000));
|
|
||||||
setText("genText", "Autarkie: " + Math.round(aut) + " %");
|
|
||||||
setText("genInfoText", "Wirkungsgrad: " + Math.round(eff) + " %");// powerToString(p_wr);
|
|
||||||
setText("gridText", powerToString(grid));
|
|
||||||
|
|
||||||
for (i = 0; i < pvn.length; i++) {
|
|
||||||
setText("det_pv" + i + "P", powerToString(pvn[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
tttext = ""
|
|
||||||
var i = 0;
|
|
||||||
while (mqttData["solarManager/inverters" + i]) {
|
|
||||||
if (mqttData["solarManager/inverters" + i+"/error"] > 0) {
|
|
||||||
tttext = tttext + '<tspan x="0" dy="1.2em">' + mqttData["solarManager/inverters" + i+"/name"] + ":</tspan>"
|
|
||||||
tttext = tttext + '<tspan x="160">' + "--" + " °C</tspan>"
|
|
||||||
tttext = tttext + '<tspan x="215">' + "-- W" + "</tspan>"
|
|
||||||
} else {
|
|
||||||
tttext = tttext + '<tspan x="0" dy="1.2em">' + mqttData["solarManager/inverters" + i+"/name"] + ":</tspan>"
|
|
||||||
tttext = tttext + '<tspan x="160">' + Math.round(mqttData["solarManager/inverters" + i+"/temp"]) + " °C</tspan>"
|
|
||||||
tttext = tttext + '<tspan x="215">' + powerToString(mqttData["solarManager/inverters" + i+"/p_AC"]) + "</tspan>"
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
setHTML("invList", tttext);
|
|
||||||
setAttr("egArc", "d", describeArc(100, 100, 95, 0, Math.round(eg / angleFactor)));
|
|
||||||
setAttr("ugArc", "d", describeArc(100, 100, 95, 0, Math.round(ug / angleFactor)));
|
|
||||||
setAttr("pvArc", "d", describeArc(100, 100, 95, 0, Math.round(pv1 / angleFactor)));
|
|
||||||
setAttr("pv2Arc", "d", describeArc(100, 100, 95, Math.round(pv1 / angleFactor), Math.round(pv1 / angleFactor) + Math.round(pv2 / angleFactor)));
|
|
||||||
setAttr("pv3Arc", "d", describeArc(100, 100, 95, Math.round(pv1 / angleFactor) + Math.round(pv2 / angleFactor), Math.round(pv1 / angleFactor) + Math.round(pv2 / angleFactor) + Math.round(pv3 / angleFactor)));
|
|
||||||
|
|
||||||
if (pbatt < 0) {
|
|
||||||
if (limbatt < 100) {
|
|
||||||
setDisplay("charge", "none");
|
|
||||||
setDisplay("batLim", "");
|
|
||||||
setText("batLimText", Math.round(limbatt) + "%");
|
|
||||||
} else {
|
|
||||||
setDisplay("charge", "");
|
|
||||||
setDisplay("batLim", "none");
|
|
||||||
}
|
|
||||||
setAttr("batArc", "d", describeArc(100, 100, 95, 0, Math.round(-pbatt / angleFactor)));
|
|
||||||
setAttr("battani", "class", "stream-rev");
|
|
||||||
setFlowWidth("battani", 55 * (1 - Math.exp(pbatt / 2000)));
|
|
||||||
} else {
|
|
||||||
setDisplay("charge", "none");
|
|
||||||
setDisplay("batLim", "none");
|
|
||||||
setAttr("batArc", "d", describeArc(100, 100, 95, 0, Math.round(pbatt / angleFactor)));
|
|
||||||
setAttr("battani", "class", "stream");
|
|
||||||
setFlowWidth("battani", 55 * (1 - Math.exp(-pbatt / 2000)));
|
|
||||||
}
|
|
||||||
setAttr("batChargeState", "y", (100 - soc) + "%");
|
|
||||||
//setAttr("heatChargeState", "y", 100 - ((((puffO + puffM) / 2) - 30) * 1.66) + "%");
|
|
||||||
setAttr('tmpH', "stop-color", assignColor("#2389BA", "#BA3B23", 20, 80, puffO));
|
|
||||||
setAttr('tmpM', "stop-color", assignColor("#2389BA", "#BA3B23", 20, 80, puffM));
|
|
||||||
setAttr('tmpL', "stop-color", assignColor("#2389BA", "#BA3B23", 20, 80, puffU));
|
|
||||||
setText("batSOC", soc + " %");
|
|
||||||
setAttr("evArc", "d", describeArc(100, 100, 95, 0, Math.round(evPower * 2000 / angleFactor)));
|
|
||||||
setAttr("evChargeState", "y", (100 - evsoc) + "%");
|
|
||||||
setText("evSOC", evsoc + " %");
|
|
||||||
setText("evFuel", fuelev + " %");
|
|
||||||
|
|
||||||
setAttr("heatArc", "d", describeArc(100, 100, 95, 0, Math.round(Pheat / sixkWangleFactor)));
|
|
||||||
setText("heatUp", Math.round(puffO * 10) / 10 + " °C");
|
|
||||||
setText("heatDown", Math.round(puffM * 10) / 10 + " °C");
|
|
||||||
|
|
||||||
if (evlock == false) {
|
|
||||||
setDisplay("evLock", "none");
|
|
||||||
} else {
|
|
||||||
setDisplay("evLock", "");
|
|
||||||
}
|
|
||||||
if (heatMode == "eco") {
|
|
||||||
setDisplay("heatEco", "");
|
|
||||||
setDisplay("heatDefault", "none");
|
|
||||||
} else {
|
|
||||||
setDisplay("heatEco", "none");
|
|
||||||
setDisplay("heatDefault", "");
|
|
||||||
}
|
|
||||||
if (evMode == "Eco") {
|
|
||||||
setDisplay("evEco", "");
|
|
||||||
setDisplay("evNextTrip", "none");
|
|
||||||
setDisplay("evDefault", "none");
|
|
||||||
} else if (evMode == "Next Trip") {
|
|
||||||
setDisplay("evEco", "none");
|
|
||||||
setDisplay("evNextTrip", "");
|
|
||||||
setDisplay("evDefault", "none");
|
|
||||||
} else {
|
|
||||||
setDisplay("evEco", "none");
|
|
||||||
setDisplay("evNextTrip", "none");
|
|
||||||
setDisplay("evDefault", "");
|
|
||||||
}
|
|
||||||
if (heatOG) {
|
|
||||||
setDisplay("heatOG", "");
|
|
||||||
} else {
|
|
||||||
setDisplay("heatOG", "none");
|
|
||||||
}
|
|
||||||
if (heatEG) {
|
|
||||||
setDisplay("heatEG", "");
|
|
||||||
} else {
|
|
||||||
setDisplay("heatEG", "none");
|
|
||||||
}
|
|
||||||
// solarManager/evPlug kommt als 1 oder 0, wie evPlugOG. Frueher kam
|
|
||||||
// hier der Text der Wallbox an ("no car", "charging", ...); Number()
|
|
||||||
// machte daraus NaN, und NaN != "no car" ist immer wahr - das
|
|
||||||
// Steckersymbol stand dauerhaft da. Den Text weiter zu verstehen
|
|
||||||
// kostet eine Zeile und haelt die Anzeige richtig, egal ob der
|
|
||||||
// Manager schon neu gestartet ist oder nicht.
|
|
||||||
const plugText = mqttData["solarManager/evPlug"];
|
|
||||||
if (isNaN(plugev) ? (plugText !== undefined && plugText !== "no car") : plugev > 0) {
|
|
||||||
setDisplay("evPlug", "");
|
|
||||||
} else {
|
|
||||||
setDisplay("evPlug", "none");
|
|
||||||
}
|
|
||||||
if (evModeOG == "Eco") {
|
|
||||||
setDisplay("evEcoOG", "");
|
|
||||||
setDisplay("evNextTripOG", "none");
|
|
||||||
setDisplay("evDefaultOG", "none");
|
|
||||||
} else if (evModeOG == "Next Trip") {
|
|
||||||
setDisplay("evEcoOG", "none");
|
|
||||||
setDisplay("evNextTripOG", "");
|
|
||||||
setDisplay("evDefaultOG", "none");
|
|
||||||
} else {
|
|
||||||
setDisplay("evEcoOG", "none");
|
|
||||||
setDisplay("evNextTripOG", "none");
|
|
||||||
setDisplay("evDefaultOG", "");
|
|
||||||
}
|
|
||||||
if (evPlugOG > 0) {
|
|
||||||
setDisplay("evPlugOG", "");
|
|
||||||
} else {
|
|
||||||
setDisplay("evPlugOG", "none");
|
|
||||||
}
|
|
||||||
if (grid < 0) {
|
|
||||||
setAttr("gridArc", "d", describeArc(100, 100, 95, 0, -grid / angleFactor));
|
|
||||||
setAttr("consGridani", "class", "stream-rev");
|
|
||||||
setFlowWidth("consGridani", 55 * (1 - Math.exp(grid / 2000)));
|
|
||||||
} else {
|
|
||||||
setAttr("gridArc", "d", describeArc(100, 100, 95, 0, grid / angleFactor));
|
|
||||||
setAttr("consGridani", "class", "stream");
|
|
||||||
setFlowWidth("consGridani", 55 * (1 - Math.exp(-grid / 2000)));
|
|
||||||
}
|
|
||||||
|
|
||||||
setFlowWidth("consEVani", 55 * (1 - Math.exp(-evPower)));
|
|
||||||
setFlowWidth("consEVOGani", 55 * (1 - Math.exp(-evPowerOG)));
|
|
||||||
setFlowWidth("consOGani", 55 * (1 - Math.exp(og / 2000)));
|
|
||||||
|
|
||||||
setFlowWidth("consUGani", 55 * (1 - Math.exp( -ug / 2000)));
|
|
||||||
|
|
||||||
setFlowWidth("consHeatAni", 55 * (1 - Math.exp(-Pheat / 2000)));
|
|
||||||
setFlowWidth("consAni", 55 * (1 - Math.exp(cons / 2000)));
|
|
||||||
setFlowWidth("consEGani", 55 * (1 - Math.exp(-eg / 2000)));
|
|
||||||
setFlowWidth("pvani", 55 * (1 - Math.exp(-pv / 2000)));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
energiefluss = new Energiefluss(behaelter, {
|
||||||
|
einstellungen: typeof energieflussEinstellungen !== "undefined" ? energieflussEinstellungen : {},
|
||||||
|
onKlick: function (knoten) {
|
||||||
|
if (knoten.klick.ansicht) zeigeAnsicht(knoten.klick.ansicht);
|
||||||
|
else if (knoten.klick.modal) openModal(knoten.klick.modal);
|
||||||
|
},
|
||||||
|
onGezeichnet: function () {
|
||||||
|
solarSVG.fillElementArray();
|
||||||
|
if (typeof updateWateringStatus === "function") updateWateringStatus();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
energiefluss.zeichnen();
|
||||||
}
|
}
|
||||||
|
|
||||||
var chartSettings = {
|
var chartSettings = {
|
||||||
@@ -604,7 +433,7 @@ var chartSettings = {
|
|||||||
// hier ist sie Beiwerk, dort der Zweck der Seite.
|
// hier ist sie Beiwerk, dort der Zweck der Seite.
|
||||||
baueJahresstatistik(document.getElementById("Stats-Year"),
|
baueJahresstatistik(document.getElementById("Stats-Year"),
|
||||||
{ spalten: 1, werkzeuge: false, fussnoten: false });
|
{ spalten: 1, werkzeuge: false, fussnoten: false });
|
||||||
solarSVG.fillElementArray();
|
energieflussStarten();
|
||||||
mountMeteogram();
|
mountMeteogram();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Einstellungen der Energiefluss-Übersicht (Karte "Realtime" auf der
|
||||||
|
* Solar-Seite), Tabelle homeMesh.energiefluss - siehe homeMesh_energiefluss.sql.
|
||||||
|
*
|
||||||
|
* Welche Kreise es gibt und wie sie gezeichnet werden, weiß allein der
|
||||||
|
* Katalog in js/solar/energieflussKatalog.js. Hier wird nur gespeichert, was
|
||||||
|
* ein Haus davon abweichend einstellt. PHP kennt deshalb die Kennungen nicht
|
||||||
|
* und prüft nur ihre Form - ein neuer Kreis im Katalog braucht hier keine
|
||||||
|
* Zeile.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once(__DIR__ . "/meshdb.php");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alle Abweichungen, je Kennung.
|
||||||
|
*
|
||||||
|
* Fehlt die Tabelle (Installation ohne homeMesh_energiefluss.sql), kommt ein
|
||||||
|
* leeres Ergebnis zurück: die Übersicht zeichnet dann den Katalog, statt die
|
||||||
|
* Solar-Seite mit einem Fehler zu verlieren.
|
||||||
|
*
|
||||||
|
* @return array<string, array{aktiv: bool, name: ?string, x: ?int, y: ?int, optionen: ?array}>
|
||||||
|
*/
|
||||||
|
function energieflussLaden()
|
||||||
|
{
|
||||||
|
$ergebnis = [];
|
||||||
|
try {
|
||||||
|
$res = meshDb()->query("SELECT id, aktiv, name, x, y, optionen FROM energiefluss");
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
return $ergebnis;
|
||||||
|
}
|
||||||
|
while ($res && $z = $res->fetch_assoc()) {
|
||||||
|
$optionen = $z["optionen"] !== null ? json_decode($z["optionen"], true) : null;
|
||||||
|
$ergebnis[$z["id"]] = [
|
||||||
|
"aktiv" => (bool)$z["aktiv"],
|
||||||
|
"name" => $z["name"],
|
||||||
|
"x" => $z["x"] !== null ? (int)$z["x"] : null,
|
||||||
|
"y" => $z["y"] !== null ? (int)$z["y"] : null,
|
||||||
|
"optionen" => is_array($optionen) ? $optionen : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $ergebnis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Den ganzen Stand ersetzen.
|
||||||
|
*
|
||||||
|
* Die Maske schickt immer alle Kreise, auch unveränderte - so gibt es keinen
|
||||||
|
* Zwischenstand, in dem die Hälfte gespeichert ist. Einträge, die dem Katalog
|
||||||
|
* entsprechen (aktiv, ohne Name, Lage und Optionen), werden gar nicht erst
|
||||||
|
* geschrieben: die Tabelle hält nur echte Abweichungen.
|
||||||
|
*
|
||||||
|
* @param array $knoten [{id, aktiv, name, x, y, optionen}, ...]
|
||||||
|
* @return int Anzahl gespeicherter Abweichungen
|
||||||
|
*/
|
||||||
|
function energieflussSpeichern($knoten)
|
||||||
|
{
|
||||||
|
if (!is_array($knoten)) {
|
||||||
|
throw new InvalidArgumentException("Keine Kreise übergeben.");
|
||||||
|
}
|
||||||
|
$zeilen = [];
|
||||||
|
foreach ($knoten as $k) {
|
||||||
|
$id = (string)($k["id"] ?? "");
|
||||||
|
if (!preg_match('/^[A-Za-z][A-Za-z0-9_]{0,19}$/', $id)) {
|
||||||
|
throw new InvalidArgumentException("Ungültige Kennung: " . $id);
|
||||||
|
}
|
||||||
|
$name = trim((string)($k["name"] ?? ""));
|
||||||
|
if (mb_strlen($name) > 40) {
|
||||||
|
throw new InvalidArgumentException("Der Name „" . $name . "“ ist länger als 40 Zeichen.");
|
||||||
|
}
|
||||||
|
$x = isset($k["x"]) && $k["x"] !== null ? (int)round($k["x"]) : null;
|
||||||
|
$y = isset($k["y"]) && $k["y"] !== null ? (int)round($k["y"]) : null;
|
||||||
|
foreach ([$x, $y] as $w) {
|
||||||
|
if ($w !== null && ($w < -2000 || $w > 4000)) {
|
||||||
|
throw new InvalidArgumentException("Die Lage von „" . $id . "“ liegt außerhalb der Zeichenfläche.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$optionen = $k["optionen"] ?? null;
|
||||||
|
if ($optionen !== null && !is_array($optionen)) {
|
||||||
|
throw new InvalidArgumentException("Optionen von „" . $id . "“ sind kein Objekt.");
|
||||||
|
}
|
||||||
|
$aktiv = !isset($k["aktiv"]) || (bool)$k["aktiv"];
|
||||||
|
if ($aktiv && $name === "" && $x === null && $y === null && $optionen === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$zeilen[] = [$id, $aktiv ? 1 : 0, $name === "" ? null : $name, $x, $y,
|
||||||
|
$optionen === null ? null : json_encode($optionen, JSON_UNESCAPED_UNICODE)];
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = meshDb();
|
||||||
|
$db->begin_transaction();
|
||||||
|
try {
|
||||||
|
$db->query("DELETE FROM energiefluss");
|
||||||
|
$st = $db->prepare("INSERT INTO energiefluss (id, aktiv, name, x, y, optionen) VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
|
foreach ($zeilen as $z) {
|
||||||
|
$st->bind_param("sisiis", $z[0], $z[1], $z[2], $z[3], $z[4], $z[5]);
|
||||||
|
$st->execute();
|
||||||
|
}
|
||||||
|
$db->commit();
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$db->rollback();
|
||||||
|
throw new RuntimeException("Die Übersicht wurde nicht gespeichert: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
return count($zeilen);
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@ require_once(__DIR__ . "/skodaKeys.php");
|
|||||||
$reiter = [
|
$reiter = [
|
||||||
["id" => "preise", "titel" => "Preise", "symbol" => "bi-currency-euro"],
|
["id" => "preise", "titel" => "Preise", "symbol" => "bi-currency-euro"],
|
||||||
["id" => "grundriss", "titel" => "Grundriss", "symbol" => "bi-house"],
|
["id" => "grundriss", "titel" => "Grundriss", "symbol" => "bi-house"],
|
||||||
|
["id" => "energiefluss", "titel" => "Übersicht", "symbol" => "bi-diagram-3"],
|
||||||
["id" => "kacheln", "titel" => "Home-Kacheln", "symbol" => "bi-grid-1x2"],
|
["id" => "kacheln", "titel" => "Home-Kacheln", "symbol" => "bi-grid-1x2"],
|
||||||
["id" => "geraete", "titel" => "Geräte", "symbol" => "bi-house-gear"],
|
["id" => "geraete", "titel" => "Geräte", "symbol" => "bi-house-gear"],
|
||||||
["id" => "fahrzeug", "titel" => "Fahrzeug", "symbol" => "bi-ev-front"],
|
["id" => "fahrzeug", "titel" => "Fahrzeug", "symbol" => "bi-ev-front"],
|
||||||
@@ -162,6 +163,48 @@ try {
|
|||||||
</div>
|
</div>
|
||||||
<?= reiterFlaecheZu() ?><!-- /Grundriss -->
|
<?= reiterFlaecheZu() ?><!-- /Grundriss -->
|
||||||
|
|
||||||
|
<!-- ================================================== Übersicht ==== -->
|
||||||
|
<!-- Die Energiefluss-Übersicht der Solar-Seite. Welche Kreise es gibt,
|
||||||
|
steht im Katalog (js/solar/energieflussKatalog.js); hier wird nur
|
||||||
|
eingestellt, was dieses Haus davon abweicht - gespeichert in
|
||||||
|
homeMesh.energiefluss. Vorschau und Liste zeichnet
|
||||||
|
js/solar/energieflussEinstellungen.js. -->
|
||||||
|
<?= reiterFlaecheAuf("energiefluss", $reiterOpt) ?>
|
||||||
|
<div class="card mb-4" id="energieflussKarte">
|
||||||
|
<div class="card-header pb-2 pt-2">
|
||||||
|
<h3 class="card-title"><i class="bi bi-diagram-3 me-2"></i>Energiefluss-Übersicht</h3>
|
||||||
|
<div class="card-tools">
|
||||||
|
<button type="button" class="btn btn-tool" id="energieflussNeuLaden" title="Änderungen verwerfen">
|
||||||
|
<i class="bi bi-arrow-counterclockwise"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="text-secondary small mb-3">
|
||||||
|
Kreise, die es in diesem Haus nicht gibt, werden ausgeschaltet – hängen andere
|
||||||
|
daran, rücken sie eine Ebene nach oben. In der Vorschau lassen sich die Kreise
|
||||||
|
mit der Maus verschieben. Auf schmalen Bildschirmen dreht die Übersicht sich
|
||||||
|
von selbst hochkant. Die Werte in der Vorschau sind Beispiele.
|
||||||
|
</p>
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col-12 col-xl-7">
|
||||||
|
<div class="enf-vorschau" id="energieflussVorschau"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-xl-5">
|
||||||
|
<div id="energieflussListe">
|
||||||
|
<p class="text-secondary small mb-0">Wird geladen …</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer d-flex flex-wrap align-items-center gap-3">
|
||||||
|
<button type="button" class="btn btn-primary" id="energieflussSpeichern">Speichern</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" id="energieflussZuruecksetzen">Alles auf Katalog zurücksetzen</button>
|
||||||
|
<span class="small" id="energieflussHinweis"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?= reiterFlaecheZu() ?><!-- /Übersicht -->
|
||||||
|
|
||||||
<!-- ================================================ Home-Kacheln ==== -->
|
<!-- ================================================ Home-Kacheln ==== -->
|
||||||
<!-- Der Inhalt kommt aus js/solar/settings.js: eine Karte je Etage, darin
|
<!-- Der Inhalt kommt aus js/solar/settings.js: eine Karte je Etage, darin
|
||||||
je Raum eine Zeile mit ihren Werten. Gezeichnet wird aus dem Modell,
|
je Raum eine Zeile mit ihren Werten. Gezeichnet wird aus dem Modell,
|
||||||
|
|||||||
+24
-9
@@ -1,3 +1,4 @@
|
|||||||
|
<?php require_once(__DIR__ . "/energiefluss.php"); ?>
|
||||||
<style>
|
<style>
|
||||||
.datepicker-native {
|
.datepicker-native {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -47,22 +48,36 @@
|
|||||||
<!--begin::Row-->
|
<!--begin::Row-->
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- Start col -->
|
<!-- Start col -->
|
||||||
|
<!-- Energiefluss. Gezeichnet von js/solar/energiefluss.js aus dem Katalog
|
||||||
|
(energieflussKatalog.js) und den Einstellungen dieses Hauses
|
||||||
|
(Einstellungen -> Übersicht). Bis September 2026 stand hier
|
||||||
|
assets/img/realtime.svg mit festen Kreisen. -->
|
||||||
<div class="col-xxl-12 col-xl-12 d-flex align-items-stretch">
|
<div class="col-xxl-12 col-xl-12 d-flex align-items-stretch">
|
||||||
<div class="card p-0 mb-4" style="width:100%">
|
<div class="card p-0 mb-4" style="width:100%" id="energieflussKarte">
|
||||||
<div class="card-header pb-0 pt-1">
|
<div class="card-header pb-1 pt-1 d-flex align-items-center">
|
||||||
<h3 class="card-title">Realtime</h3>
|
<h3 class="card-title me-auto">Realtime</h3>
|
||||||
|
<div class="enf-umschalter me-2" role="tablist" aria-label="Ansicht">
|
||||||
|
<button type="button" class="btn btn-sm active" role="tab" aria-selected="true" data-enf-ansicht="fluss">Übersicht</button>
|
||||||
|
<button type="button" class="btn btn-sm" role="tab" aria-selected="false" data-enf-ansicht="anlage">Anlage</button>
|
||||||
|
</div>
|
||||||
<div class="card-tools">
|
<div class="card-tools">
|
||||||
<button type="button" class="btn btn-tool" data-lte-toggle="card-remove">
|
<a class="btn btn-tool" href="index.php?action=settings#energiefluss" title="Übersicht einstellen">
|
||||||
<i class="bi bi-x-lg"></i>
|
<i class="bi bi-sliders"></i>
|
||||||
</button>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-0 d-flex flex-column" id="realtimeSVGcontainer" style="position:relative;max-height:800px;">
|
<div class="card-body p-2 p-md-3">
|
||||||
<?php readfile(__DIR__ . "/../assets/img/realtime.svg"); ?>
|
<div class="enf-ansicht enf-behaelter" data-enf-ansicht-flaeche="fluss" id="energiefluss"></div>
|
||||||
|
<div class="enf-ansicht" data-enf-ansicht-flaeche="anlage" hidden>
|
||||||
|
<?php readfile(__DIR__ . "/../assets/img/pvplan.svg"); ?>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- /.card -->
|
|
||||||
</div>
|
</div>
|
||||||
|
<script>
|
||||||
|
// Abweichungen dieses Hauses vom Katalog, siehe restricted/energiefluss.php.
|
||||||
|
const energieflussEinstellungen = <?= json_encode((object)energieflussLaden(), JSON_UNESCAPED_UNICODE) ?>;
|
||||||
|
</script>
|
||||||
|
|
||||||
<div class="col-xxl-12 col-xl-12 col-lg-12 col-md-12 d-flex align-items-stretch">
|
<div class="col-xxl-12 col-xl-12 col-lg-12 col-md-12 d-flex align-items-stretch">
|
||||||
<div class="card p-0 mb-4" style="width:100%">
|
<div class="card p-0 mb-4" style="width:100%">
|
||||||
|
|||||||
Reference in New Issue
Block a user