Weiter zum Inhalt
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Hell
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dunkel
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Visualisierung
  4. [gelöst] Balkendiagramm für Momentanwerte?

NEWS

  • wichtiges UPDATE für controller 7.2.2 im stable
    HomoranH
    Homoran
    10
    1
    1.8k

  • Neues YouTube-Video: Visualisierung im Devices-Adapter
    BluefoxB
    Bluefox
    16
    1
    3.6k

  • Neuer ioBroker-Blog online: Monatsrückblick März/April 2026
    BluefoxB
    Bluefox
    8
    1
    3.3k

[gelöst] Balkendiagramm für Momentanwerte?

Geplant Angeheftet Gesperrt Verschoben Visualisierung
6 Beiträge 3 Kommentatoren 464 Aufrufe 2 Beobachtet
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • hardwarefehlerH Online
    hardwarefehlerH Online
    hardwarefehler
    schrieb am zuletzt editiert von hardwarefehler
    #1

    Hi, ich würde gern kleine Balkendiagramme für die momentane Wirkleistung der jeweils drei Phasen an verschiedenen Netzknoten anzeigen.

    In vis-2:
    Wiget Basic / Bar is nicht geeignet, da es keine negativen Werte (Produktion/Einspeisung) kann.
    Widget Vis 2 - Energie-Widgets / Verbrauchsvergleich macht im Wesentlichen, was ich suche, hat aber zwei Probleme:

    • auch wenn ich in den Ebenen die Einheit "W" angebe, wird rechts unten im Diagramm kWh angezeigt (das W funktioniert nur im Mouseover für die Einzelwerte),
    • die Skalierung passt sich dynamisch an. Das ist einerseits sehr unruhig, da sich die Werte sekündlich ändern, andererseits will ich die absoluten Werte vergleichen, also wo grade der Großverbraucher anspringt.

    Habt ihr einen anderen Vorschlag oder eine Idee, die obigen Probleme zu beheben?
    Wirkleistung 2026-06-22 17-57-17.png

    fuzzy1955F 1 Antwort Letzte Antwort
    0
    • hardwarefehlerH hardwarefehler

      Hi, ich würde gern kleine Balkendiagramme für die momentane Wirkleistung der jeweils drei Phasen an verschiedenen Netzknoten anzeigen.

      In vis-2:
      Wiget Basic / Bar is nicht geeignet, da es keine negativen Werte (Produktion/Einspeisung) kann.
      Widget Vis 2 - Energie-Widgets / Verbrauchsvergleich macht im Wesentlichen, was ich suche, hat aber zwei Probleme:

      • auch wenn ich in den Ebenen die Einheit "W" angebe, wird rechts unten im Diagramm kWh angezeigt (das W funktioniert nur im Mouseover für die Einzelwerte),
      • die Skalierung passt sich dynamisch an. Das ist einerseits sehr unruhig, da sich die Werte sekündlich ändern, andererseits will ich die absoluten Werte vergleichen, also wo grade der Großverbraucher anspringt.

      Habt ihr einen anderen Vorschlag oder eine Idee, die obigen Probleme zu beheben?
      Wirkleistung 2026-06-22 17-57-17.png

      fuzzy1955F Offline
      fuzzy1955F Offline
      fuzzy1955
      schrieb am zuletzt editiert von fuzzy1955
      #2

      @hardwarefehler sagte:

      Habt ihr einen anderen Vorschlag

      eCharts?
      b495355c-1538-4989-8471-41bcba5e1e3c-image.jpeg

      Smart-Home-Automation unter IOB auf RASPI5, 16GB RAM, 50GB SSD, MariaSQL, VIS-2.
      Anfang 2025 war ich Newbie in Sachen RaspBerry, Linux und IOB.
      Jetzt läuft alles konstant gut und ich gebe gern meine Erfahrungen und mein Wissen an die Forums-User/Innen weiter.
      Großes Danke an alle tatkräftigen Helfer im Forum!

      1 Antwort Letzte Antwort
      0
      • hardwarefehlerH Online
        hardwarefehlerH Online
        hardwarefehler
        schrieb am zuletzt editiert von hardwarefehler
        #3

        @fuzzy1955 - danke für den Gedankenanstoß. allerdings ist eCharts auf Zeitreihen spezialisiert und ich will nicht alle Momentanwerte historisieren.
        Bin daher jetzt bei Flexcharts gelandet und schon gut vorangekommen.

        Edit: so sieht es jetzt aus:
        flexcharts 2026-06-25.png

        Edit2:
        Der Flexchart-Adapter nutzt dabei ein Objekt unter 0_userdata, dass mittels eines Javascripts dynamisch mit einer JSON-Struktur befüllt wird.
        Das Diagramm ist dann unter der URL
        http://localhost:8082/flexcharts/echarts.html?source=state&id=0_userdata.0.flexcharts.energy.chart&sse abrufbar, welche ich auch per iFrame-Widget in die vis2 einbinden kann.

        U 1 Antwort Letzte Antwort
        0
        • hardwarefehlerH hardwarefehler

          @fuzzy1955 - danke für den Gedankenanstoß. allerdings ist eCharts auf Zeitreihen spezialisiert und ich will nicht alle Momentanwerte historisieren.
          Bin daher jetzt bei Flexcharts gelandet und schon gut vorangekommen.

          Edit: so sieht es jetzt aus:
          flexcharts 2026-06-25.png

          Edit2:
          Der Flexchart-Adapter nutzt dabei ein Objekt unter 0_userdata, dass mittels eines Javascripts dynamisch mit einer JSON-Struktur befüllt wird.
          Das Diagramm ist dann unter der URL
          http://localhost:8082/flexcharts/echarts.html?source=state&id=0_userdata.0.flexcharts.energy.chart&sse abrufbar, welche ich auch per iFrame-Widget in die vis2 einbinden kann.

          U Offline
          U Offline
          UlliJ
          schrieb am zuletzt editiert von
          #4

          @hardwarefehler

          so etwas vielleicht?

          1c01b97c-d394-47d4-b039-bdaa56452a6c-image.jpeg

          ist ebenfalls Apache echart für aktuelle Werte von DP's, aber ohne Adapter. Über Json konfigurierbar und Anzeige im HTML widget

          Proxmox auf iNuc, lxc für IoB, InfluxDB2, Grafana, u.a. *** Homematic & Homematic IP, Shellies, Zigbee etc

          hardwarefehlerH 1 Antwort Letzte Antwort
          0
          • U UlliJ

            @hardwarefehler

            so etwas vielleicht?

            1c01b97c-d394-47d4-b039-bdaa56452a6c-image.jpeg

            ist ebenfalls Apache echart für aktuelle Werte von DP's, aber ohne Adapter. Über Json konfigurierbar und Anzeige im HTML widget

            hardwarefehlerH Online
            hardwarefehlerH Online
            hardwarefehler
            schrieb zuletzt editiert von
            #5

            @UlliJ sagte: ist ebenfalls Apache echart für aktuelle Werte von DP's, aber ohne Adapter. Über Json konfigurierbar und Anzeige im HTML widget

            Und wo lagert deine JSON-Konfig und wie aktualisierst du sie?

            Bei meiner Lösung (habe ich oben mal noch etwas ergänzt) erzeuge ich die Struktur alle 5 s serverseitig neu.
            Noch hübscher wäre natürlich, wenn sie nur erzeugt wird, wenn auch jemand raufschaut.

            U 1 Antwort Letzte Antwort
            0
            • hardwarefehlerH hardwarefehler

              @UlliJ sagte: ist ebenfalls Apache echart für aktuelle Werte von DP's, aber ohne Adapter. Über Json konfigurierbar und Anzeige im HTML widget

              Und wo lagert deine JSON-Konfig und wie aktualisierst du sie?

              Bei meiner Lösung (habe ich oben mal noch etwas ergänzt) erzeuge ich die Struktur alle 5 s serverseitig neu.
              Noch hübscher wäre natürlich, wenn sie nur erzeugt wird, wenn auch jemand raufschaut.

              U Offline
              U Offline
              UlliJ
              schrieb zuletzt editiert von UlliJ
              #6

              @hardwarefehler
              die Konfig liegt in einem Json DP - Ort beliebig unter userdata
              die Optionen sind im Spoiler erklärt, ein lauffähiges Beispiel ist unten angehängt
              es gibt eine vordefinierte Farbpalette. Die kann genutzt werden oder es werden Fallbacks verwendet (wenn nicht vorhanden) oder in der Config etwas anderes steht. Wenn er verwendet werden soll müssen die DP Pfade im Skript und im HTML passen.

              {
               "_readme": "StatusChart Cheat-Sheet — alle verfügbaren Optionen. Diesen Eintrag als Vorlage kopieren, _kommentar-Felder vor dem Speichern entfernen (JSON erlaubt keine echten Kommentare). id muss eindeutig sein.",
               "statusCharts": [
                 {
                   "_kommentar": "═══ BAR CHART — alle Optionen ═══",
                   "id": 901,
                   "title": "Beispiel Bar",
                   "showTitle": true,
                   "showLegend": false,
                   "_showLegend_info": "nur relevant für Pie, bei Bar wirkungslos",
                   "showAxes": true,
                   "_showAxes_info": "true=Achsen/Gridlines sichtbar, false=ausgeblendet (Skala bleibt intern aktiv)",
                   "showValues": true,
                   "_showValues_info": "Wert-Labels an den Balken ein/aus",
                   "chartType": "bar",
                   "orientation": "horizontal",
                   "_orientation_info": "horizontal | vertical",
                   "refreshInterval": 10000,
                   "_refreshInterval_info": "ms zwischen Aktualisierungen, default 10000",
                   "unit": "W",
                   "_unit_info": "Einheit für Achsenbeschriftung und Werte-Labels",
                   "thresholds": [
                     {
                       "above": -1500,
                       "color": "rgba(229,57,53,0.9)"
                     },
                     {
                       "above": 0,
                       "color": "rgba(255,152,0,0.9)"
                     },
                     {
                       "above": 1000,
                       "color": "rgba(76,175,80,0.9)"
                     }
                   ],
                   "_thresholds_info": "Chart-weite Farbskala. Gilt für alle items mit gradient:true ODER ohne eigene color/Threshold-Treffer. Höchster zutreffender above-Wert gewinnt (fester Modus ohne gradient). Bezieht sich auf ABSOLUTE Werte, nicht Prozent.",
                   "items": [
                     {
                       "dp": "0_userdata.0.Beispiel.Wert1",
                       "name": "Phase 1",
                       "color": null,
                       "_color_info": "feste Farbe, überschreibt Palette und Threshold-Modus. null = automatisch",
                       "gradient": true,
                       "_gradient_info": "true = stufenloser Farbverlauf entlang der Balkenlänge, basierend auf chart-weiten thresholds. false/fehlt = feste Farbe (Threshold-Modus oder color oder Palette)",
                       "thresholds": null,
                       "_item_thresholds_info": "optional, überschreibt nur für DIESES item die Chart-weiten thresholds (Rückwärtskompatibilität, normalerweise nicht nötig)"
                     },
                     {
                       "dp": "0_userdata.0.Beispiel.Wert2",
                       "name": "Phase 2",
                       "color": "rgba(100,181,246,0.9)"
                     }
                   ]
                 },
                 {
                   "_kommentar": "═══ PIE CHART — alle Optionen ═══",
                   "id": 902,
                   "title": "Beispiel Pie",
                   "showTitle": true,
                   "showLegend": true,
                   "_showLegend_info": "Legende unten anzeigen (nur Pie)",
                   "showAxes": true,
                   "_showAxes_info": "wirkungslos bei Pie",
                   "showValues": true,
                   "_showValues_info": "Name+Wert-Label an den Segmenten ein/aus",
                   "chartType": "pie",
                   "refreshInterval": 10000,
                   "unit": "W",
                   "pieRadius": null,
                   "_pieRadius_info": "Eckenrundung der Segmente in px. null = Default aus jsonChartStyle.pie.borderRadius (sonst 8)",
                   "pieBorderWidth": null,
                   "_pieBorderWidth_info": "Lückenbreite zwischen Segmenten in px. null = Default aus jsonChartStyle.pie.borderWidth (sonst 3)",
                   "centerText": {
                     "show": true,
                     "label": "Total",
                     "_label_info": "Label über dem Wert, leer/weglassen = nur Wert ohne Label",
                     "dp": null,
                     "_dp_info": "eigener DP für den Mitte-Wert. null/weglassen = automatisch Summe der Beträge aller items",
                     "unit": "W",
                     "_unit_info": "Einheit im Mitte-Text, weglassen = chart-unit wird genutzt"
                   },
                   "_centerText_info": "komplettes Objekt weglassen oder show:false = kein Mitte-Text (klassischer Donut)",
                   "items": [
                     {
                       "dp": "0_userdata.0.Beispiel.Wert1",
                       "name": "Anteil A",
                       "color": null,
                       "_color_info": "null = automatisch aus jsonChartStyle.colors Palette nach Reihenfolge im Array"
                     },
                     {
                       "dp": "0_userdata.0.Beispiel.Wert2",
                       "name": "Anteil B",
                       "color": "rgba(255,183,77,0.9)"
                     }
                   ],
                   "_pie_negative_info": "Negative Werte werden als Betrag dargestellt (Segmentgröße), Label/Tooltip zeigen weiterhin den Original-Wert mit Vorzeichen"
                 }
               ],
               "_aufbau_dp": "Dieser komplette Inhalt gehört in: 0_userdata.0.0000_Visualisierung.Echarts.statusCharts.jsonStatusChartDef",
               "_aufbau_output": "Output je Chart landet automatisch in: 0_userdata.0.0000_Visualisierung.Echarts.statusCharts.jsonStatusChartOutput{id}",
               "_aufbau_widget": "Im vis-2 iframe einbinden: StatusChartWidget.html?id={id}  (style=\"border:none;background:transparent\")"
              }
              

              Farb DP:

              {
                "colors": {
                  "light": [
                    "rgba(33,150,243,0.85)",
                    "rgba(76,175,80,0.85)",
                    "rgba(255,152,0,0.85)",
                    "rgba(233,30,99,0.85)",
                    "rgba(156,39,176,0.85)",
                    "rgba(77,208,225,0.85)"
                  ],
                  "dark": [
                    "rgba(100,181,246,0.9)",
                    "rgba(129,199,132,0.9)",
                    "rgba(255,183,77,0.9)",
                    "rgba(240,98,146,0.9)",
                    "rgba(206,147,216,0.9)",
                    "rgba(77,208,225,0.9)"
                  ]
                },
                "bar": {
                  "radius": 6,
                  "categoryGap": "20%",
                  "barGap": "8%"
                },
                "legend": {
                  "show": true,
                  "position": "top",
                  "fontSize": 13
                },
                "fontSize": {
                  "axis": 12,
                  "title": 15,
                  "tooltip": 13
                },
                "animation": {
                  "enabled": true,
                  "duration": 800,
                  "easing": "cubicOut"
                },
                "compare": {
                  "style": "transparent",
                  "opacity": 0.35,
                  "borderWidth": 1.5,
                  "borderType": "dashed"
                },
                "pie": {
                  "borderRadius": 8,
                  "borderWidth": 3
                }
              }
              

              Das Skript liest die Config und schreibt die Output DP's zyklisch entsprechend der Definition in der Config.

              • oben im Skriptheader ggf. die DP's anpassen
              • die Output DP's müssen manuell angelegt werden, z.B. 0_userdata.0.0000_Visualisierung.Echarts.Statuscharts.jsonStatusChartOutput1.

              // ============================================================
              // @desc        StatusChart V1 — Momentanwerte als Bar/Pie für Vis-2
              // @version     1.0
              // @desc-long   Liest aktuelle Werte mehrerer DPs zyklisch aus und
              // @desc-long   stellt sie als horizontalen/vertikalen Balken-Chart
              // @desc-long   oder Pie-Chart dar. Eigenständig, unabhängig von
              // @desc-long   UniChart/MiniChart/Sparkline.
              // @vis         Energie | Batterie | System
              //
              // @ipo-input   jsonStatusChartDef | beliebige State-DPs (Momentanwerte)
              // @ipo-proc    loadRegistry | calcStatusChart | resolveColor (Threshold) | writeOutput
              // @ipo-output  jsonStatusChartOutput{id}
              //
              // @schema id              | number                | Eindeutige Chart-ID
              // @schema chartType       | bar/pie               | Darstellungstyp
              // @schema orientation     | horizontal/vertical    | nur bei chartType=bar
              // @schema refreshInterval | number (ms)            | Pollingintervall
              // @schema unit            | string                 | Einheit für Anzeige
              // @schema items[].dp          | string             | State-DP mit Momentanwert
              // @schema items[].name        | string             | Anzeigename
              // @schema items[].color       | string|null        | Default-Farbe, sonst aus jsonChartStyle
              // @schema thresholds          | array              | Chart-weite Farbskala: [{ above: number, color: string }, ...]
              //                                                     gilt für alle Items mit gradient:true oder ohne eigene Farbe;
              //                                                     höchster zutreffender "above"-Wert gewinnt (fester Modus)
              // @schema items[].thresholds  | array (optional)   | Überschreibt die Chart-weite Skala nur für dieses Item
              //                                                     (Rückwärtskompatibilität, normalerweise nicht nötig)
              // @schema items[].gradient    | true/false (default false) | nur Bar: Chart-weite thresholds als stufenlosen
              //                                                     Farbverlauf statt fester Farbe darstellen.
              //                                                     Stop-Positionen = above-Wert / Achsen-Max.
              // @schema showTitle       | true/false (default false) | Titel anzeigen
              // @schema showLegend      | true/false (default false) | Legende anzeigen (nur Pie)
              // @schema showAxes        | true/false (default true)  | Achsen anzeigen (nur Bar)
              // @schema showValues      | true/false (default true)  | Wert-Labels an Balken/Segmenten anzeigen
              // @schema pieRadius       | number (default: jsonChartStyle.pie.borderRadius || 8) | Eckenrundung der Pie-Segmente
              // @schema pieBorderWidth  | number (default: jsonChartStyle.pie.borderWidth  || 3) | Lückenbreite zwischen Pie-Segmenten
              // @schema centerText.show  | true/false           | Mitte-Text im Donut anzeigen (nur Pie)
              // @schema centerText.label | string                | Label über dem Wert (z.B. "Total")
              // @schema centerText.dp    | string (optional)     | Eigener DP für den Mitte-Wert; ohne dp = Summe der items[]-Beträge
              // @schema centerText.unit  | string (optional)     | Einheit im Mitte-Text, sonst chart-unit
              // ============================================================
              
              var DP_DEF       = '0_userdata.0.0000_Visualisierung.Echarts.Statuscharts.jsonChartDef';
              var DP_PFX       = '0_userdata.0.0000_Visualisierung.Echarts.Statuscharts.jsonStatusChartOutput';
              var DP_STYLE     = '0_userdata.0.0000_Visualisierung.Echarts.jsonChartStyle';
              var LOG_ENABLED  = true;
              
              function sLog(msg, level) {
                 var lvl = level || 'info';
                 if (!LOG_ENABLED && lvl !== 'error' && lvl !== 'warn') return;
                 log('[StatusChart] ' + msg, lvl);
              }
              
              var DEFAULT_INTERVAL = 10000;
              var _intervals = {}; // chartId -> intervalHandle, für Reload bei Registry-Änderung
              
              // ── Style-Palette laden ───────────────────────────────────────
              function loadPalette() {
                 try {
                     var raw   = getState(DP_STYLE).val;
                     var style = typeof raw === 'object' ? raw : JSON.parse(raw);
                     return style.colors ? (style.colors.dark || style.colors.light) : null;
                 } catch(e) {
                     return null;
                 }
              }
              
              // ── Farbe für einen Wert auflösen (Threshold > Item-Color > Palette) ──
              // @func resolveColor | Höchster zutreffender Threshold gewinnt, sonst item.color, sonst Palette
              function resolveColor(item, value, idx, palette) {
                 if (item.thresholds && Array.isArray(item.thresholds) && value !== null) {
                     var matching = item.thresholds
                         .filter(function(t) { return value >= t.above; })
                         .sort(function(a, b) { return b.above - a.above; });
                     if (matching.length > 0) return matching[0].color;
                 }
                 if (item.color) return item.color;
                 if (palette) return palette[idx % palette.length];
                 return null;
              }
              
              // ── Einen StatusChart berechnen ───────────────────────────────
              // @func calcStatusChart | Liest alle item-DPs aus, löst Farben auf, schreibt Output
              function calcStatusChart(def) {
                 var palette = loadPalette();
                 var items   = def.items || [];
              
                 var data = items.map(function(item, idx) {
                     var state = getState(item.dp);
                     var value = (state && state.val !== null && state.val !== undefined)
                         ? Math.round(Number(state.val) * 100) / 100
                         : null;
              
                     // gradient:true (nur Bar-Charts) → kein eigener Farbwert, das Widget
                     // baut den linearGradient aus den Chart-weiten def.thresholds
                     // (gelten für alle gradient-Items gleich, da sie sich auf die
                     // gemeinsame Werte-Achse beziehen).
                     // Ohne gradient-Flag: bisheriges Verhalten, feste Farbe nach
                     // höchstem zutreffendem Threshold (nutzt ebenfalls def.thresholds,
                     // falls item keine eigenen mehr hat — Rückwärtskompatibilität via
                     // item.thresholds bleibt zusätzlich möglich).
                     var entry = { name: item.name || item.dp, value: value };
                     if (item.gradient === true) {
                         entry.gradient = true;
                     } else {
                         var thresholdsForItem = item.thresholds || def.thresholds || null;
                         entry.color = resolveColor({ thresholds: thresholdsForItem, color: item.color }, value, idx, palette);
                     }
                     return entry;
                 });
              
                 // ── centerText: eigener DP ODER Summe der items[] (Beträge) ──
                 var centerText = null;
                 if (def.centerText && def.centerText.show) {
                     var ctValue;
                     if (def.centerText.dp) {
                         // Eigener DP für den Mitte-Wert
                         var ctState = getState(def.centerText.dp);
                         ctValue = (ctState && ctState.val !== null && ctState.val !== undefined)
                             ? Math.round(Number(ctState.val) * 100) / 100
                             : null;
                     } else {
                         // Automatisch: Summe der Beträge aller items
                         ctValue = data.reduce(function(sum, d) {
                             return sum + (d.value !== null ? Math.abs(d.value) : 0);
                         }, 0);
                         ctValue = Math.round(ctValue * 100) / 100;
                     }
                     centerText = {
                         label: def.centerText.label || '',
                         value: ctValue,
                         unit:  def.centerText.unit !== undefined ? def.centerText.unit : (def.unit || '')
                     };
                 }
              
                 var output = {
                     id:          def.id,
                     title:       def.title       || '',
                     showTitle:   def.showTitle   || false,
                     showLegend:  def.showLegend  || false,
                     showAxes:    def.showAxes    !== false,  // default true
                     showValues:  def.showValues  !== false,  // default true
                     chartType:   def.chartType   || 'bar',
                     orientation: def.orientation || 'horizontal',
                     unit:        def.unit        || '',
                     // Chart-weite Farbskala für gradient:true Items (siehe oben)
                     thresholds:  def.thresholds || null,
                     // Pie-Style Override: nur durchreichen wenn explizit in Def gesetzt,
                     // sonst zieht das Widget den Default aus jsonChartStyle.pie
                     pieRadius:      def.pieRadius      !== undefined ? def.pieRadius      : null,
                     pieBorderWidth: def.pieBorderWidth !== undefined ? def.pieBorderWidth : null,
                     centerText:     centerText,
                     data:        data
                 };
              
                 setState(DP_PFX + def.id, JSON.stringify(output), true);
                 //sLog('StatusChart ' + def.id + ' (' + (def.title || '-') + ') geschrieben | ' + data.length + ' Items');
              }
              
              // ── Registry laden ────────────────────────────────────────────
              // @func loadRegistry | Liest und parst Chart-Definitionen aus jsonStatusChartDef
              function loadRegistry() {
                 try {
                     var raw    = getState(DP_DEF).val;
                     var parsed = typeof raw === 'object' ? raw : JSON.parse(raw);
                     return parsed.statusCharts || [];
                 } catch(e) {
                     sLog('Registry Fehler: ' + e, 'error');
                     return [];
                 }
              }
              
              // ── Alle Charts laden + Intervalle setzen ─────────────────────
              // @func loadAndSchedule | Lädt Registry, startet Polling je Chart, räumt alte Intervalle auf
              function loadAndSchedule() {
                 // Alte Intervalle stoppen (bei Registry-Reload)
                 Object.keys(_intervals).forEach(function(id) {
                     clearInterval(_intervals[id]);
                 });
                 _intervals = {};
              
                 var charts = loadRegistry();
                 sLog(charts.length + ' StatusChart Definitionen geladen');
              
                 charts.forEach(function(def) {
                     calcStatusChart(def);
                     var interval = def.refreshInterval || DEFAULT_INTERVAL;
                     _intervals[def.id] = setInterval(function() { calcStatusChart(def); }, interval);
                     //sLog('StatusChart ' + def.id + ': Intervall ' + Math.round(interval / 1000) + ' s');
                 });
              }
              
              // Bei Änderung der Registry neu laden
              on({ id: DP_DEF, change: 'any' }, function() {
                 sLog('Registry geändert – neu laden');
                 loadAndSchedule();
              });
              
              // Start
              setTimeout(function() { loadAndSchedule(); }, 2000);
              

              Das rendern wird über das HTML widget gemacht

              • muss irgendwo in den Bereich des Web adapters hochgeladen werden
              • z.B. http://192.168.178.9:8082/vis-2.0/charts/eChartsStatus/StatusChartWidget.html
              • die echarts Bibliothek muss in den übergeordneten Ordner (echarts.min.js). Sonst Pfad im HTML anpassen
              • im HTML den Pfad für den output Datenpunkt ggf. anpassen
              • der Adapter Socket io muss installiert sein und laufen

              <!DOCTYPE html>
              <html lang="de">
              <head>
              <meta charset="UTF-8"/>
              <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
              <title>StatusChart</title>
              <style>
               *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
               html, body {
                 background: transparent;
                 width: 100%; height: 100vh;
                 overflow: hidden;
                 font-family: Roboto, Arial, sans-serif;
               }
               #title {
                 font-size: 12px;
                 color: #888;
                 padding: 4px 8px 0;
                 white-space: nowrap;
                 overflow: hidden;
                 text-overflow: ellipsis;
               }
               #chart {
                 width: 100%;
               }
              </style>
              <script src="/lib/js/socket.io.js"></script>
              <script src="../echarts.min.js"></script>
              </head>
              <body>
              <div id="title" style="display:none"></div>
              <div id="chart"></div>
              
              <script>
              var chartId  = new URLSearchParams(window.location.search).get('id') || '1';
              var DP_OUT   = '0_userdata.0.0000_Visualisierung.Echarts.Statuscharts.jsonStatusChartOutput' + chartId;
              var DP_STYLE = '0_userdata.0.0000_Visualisierung.Echarts.jsonChartStyle';
              var gStyle   = null;
              
              var socket  = io.connect(window.location.origin);
              var titleEl = document.getElementById('title');
              var chartEl = document.getElementById('chart');
              var myChart = null;
              
              // ── Style laden ──────────────────────────────────────────────
              function loadStyle() {
                 try {
                     var raw = getState(DP_STYLE).val;
                     gStyle  = typeof raw === 'object' ? raw : JSON.parse(raw);
                 } catch(e) {
                     gStyle = null;
                 }
              }
              
              function isDark() {
                 return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
              }
              
              function getColor(item, idx) {
                 if (item.color) return item.color;
                 if (gStyle && gStyle.colors) {
                     var palette = isDark() ? gStyle.colors.dark : gStyle.colors.light;
                     if (palette && palette[idx % palette.length]) return palette[idx % palette.length];
                 }
                 return null;
              }
              
              function initChart() {
                 myChart = echarts.init(chartEl, 'dark');
              }
              
              function renderChart(data) {
                 if (!myChart) initChart();
              
                 var showTitle = data.showTitle && data.title;
                 titleEl.style.display = showTitle ? 'block' : 'none';
                 if (showTitle) titleEl.textContent = data.title;
              
                 var titleH = showTitle ? titleEl.offsetHeight : 0;
                 chartEl.style.height = (window.innerHeight - titleH) + 'px';
                 myChart.resize();
              
                 if (data.chartType === 'pie') {
                     renderPie(data);
                 } else {
                     renderBar(data);
                 }
              }
              
              // ── Pie Chart ────────────────────────────────────────────────
              function renderPie(data) {
                 var unit       = data.unit || '';
                 var showLegend = data.showLegend === true;
                 var showValues = data.showValues !== false;
                 var items      = (data.data || []).filter(function(d) { return d.value !== null; });
              
                 // Pie-Style: Default aus jsonChartStyle.pie, pro Chart überschreibbar
                 var styleDefRadius      = (gStyle && gStyle.pie && gStyle.pie.borderRadius !== undefined) ? gStyle.pie.borderRadius : 8;
                 var styleDefBorderWidth = (gStyle && gStyle.pie && gStyle.pie.borderWidth  !== undefined) ? gStyle.pie.borderWidth  : 3;
                 var pieBorderRadius = (data.pieRadius      !== undefined && data.pieRadius      !== null) ? data.pieRadius      : styleDefRadius;
                 var pieBorderWidth  = (data.pieBorderWidth !== undefined && data.pieBorderWidth !== null) ? data.pieBorderWidth : styleDefBorderWidth;
              
                 var pieData = items.map(function(d, idx) {
                     // Betrag für die Segmentgröße: Pie zeigt Anteile, negative Vorzeichen
                     // (z.B. Einspeisung) ergeben keinen darstellbaren Kreissegment-Anteil.
                     // Original-Wert wird zusätzlich mitgeführt für Label/Tooltip.
                     var absValue = Math.abs(d.value);
                     var item = { name: d.name, value: absValue, original: d.value };
                     var color = getColor(d, idx);
                     if (color) item.itemStyle = { color: color };
                     return item;
                 });
              
                 var centerY = showLegend ? '45%' : '50%';
              
                 var option = {
                     backgroundColor: 'transparent',
                     tooltip: {
                         trigger: 'item',
                         formatter: function(p) {
                             return p.name + ': ' + p.data.original + ' ' + unit + ' (' + p.percent + '%)';
                         }
                     },
                     legend: {
                         show: showLegend,
                         bottom: 0,
                         textStyle: { color: '#999', fontSize: 11 }
                     },
                     series: [{
                         type: 'pie',
                         radius: ['35%', '70%'],
                         center: ['50%', centerY],
                         data: pieData,
                         // Segmente getrennt + abgerundet darstellen
                         // borderColor = Seiten-Hintergrund, erzeugt die optische Lücke
                         itemStyle: {
                             borderRadius: pieBorderRadius,
                             borderColor: isDark() ? '#1a1a1a' : '#f5f5f5',
                             borderWidth: pieBorderWidth
                         },
                         label: {
                             show: showValues,
                             color: '#aaa',
                             fontSize: 11,
                             formatter: function(p) { return p.name + '\n' + p.data.original + ' ' + unit; }
                         },
                         emphasis: {
                             itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0,0,0,0.5)' }
                         }
                     }]
                 };
              
                 // ── centerText: Label + Wert mittig im Donut ──────────────
                 // ECharts graphic.top/left akzeptieren px-Werte (Zahl) für absolute
                 // Positionierung relativ zur Chart-Box. Mittelpunkt wird daher in
                 // Pixeln berechnet statt über CSS-artiges 'calc()' (nicht von ECharts
                 // unterstützt) oder ein nicht-existierendes extra.offsetY.
                 if (data.centerText) {
                     var ct = data.centerText;
                     var ctUnit = ct.unit ? ' ' + ct.unit : '';
              
                     var chartW   = chartEl.clientWidth  || 200;
                     var chartH   = chartEl.clientHeight || 200;
                     var centerYPct = parseFloat(centerY) / 100; // '50%' -> 0.5
                     var centerYpx  = chartH * centerYPct;
              
                     var graphics = [{
                         type: 'text',
                         left: 'center',
                         top: ct.label ? (centerYpx - 12) : centerYpx,
                         style: {
                             text: ct.value + ctUnit,
                             fill: '#eee',
                             fontSize: 18,
                             fontWeight: 600,
                             textAlign: 'center',
                             textVerticalAlign: 'middle'
                         }
                     }];
              
                     if (ct.label) {
                         graphics.push({
                             type: 'text',
                             left: 'center',
                             top: centerYpx + 10,
                             style: {
                                 text: ct.label,
                                 fill: '#888',
                                 fontSize: 12,
                                 textAlign: 'center',
                                 textVerticalAlign: 'middle'
                             }
                         });
                     }
                     option.graphic = graphics;
                 }
              
                 myChart.setOption(option, true);
              }
              
              // ── Bar Chart (horizontal/vertikal) ──────────────────────────
              function renderBar(data) {
                 var unit        = data.unit || '';
                 var horizontal  = (data.orientation || 'horizontal') === 'horizontal';
                 var showAxes    = data.showAxes   !== false;
                 var showValues  = data.showValues !== false;
                 var items       = data.data || [];
                 var names       = items.map(function(d) { return d.name; });
                 var values      = items.map(function(d) { return d.value; });
              
                 var barRadius    = gStyle && gStyle.bar ? gStyle.bar.radius      || 4   : 4;
                 var categoryGap  = gStyle && gStyle.bar ? gStyle.bar.categoryGap || '30%' : '30%';
                 var animEnabled  = gStyle && gStyle.animation ? gStyle.animation.enabled  !== false : true;
                 var animDuration = gStyle && gStyle.animation ? gStyle.animation.duration || 600    : 600;
                 var axisFontSize = gStyle && gStyle.fontSize  ? gStyle.fontSize.axis      || 11    : 11;
              
                 // Achsen-Spannweite ermitteln (für Gradient-Stop-Positionen relativ zur Skala,
                 // nicht relativ zum einzelnen Balken — sonst sähe jeder Balken trotz
                 // unterschiedlicher Länge den gleichen Verlauf-Ausschnitt)
                 var allVals  = values.filter(function(v) { return v !== null; });
                 var axisMax  = allVals.length ? Math.max.apply(null, allVals.map(Math.abs)) : 1;
                 if (axisMax === 0) axisMax = 1;
              
                 // Chart-weite Farbskala für gradient:true Items
                 var chartThresholds = data.thresholds || null;
              
                 // @func buildGradientColor | Baut linearGradient aus thresholds, Stops relativ zum Achsen-Max
                 function buildGradientColor(negative) {
                     var stops = chartThresholds
                         .map(function(t) { return { pos: Math.min(Math.max(t.above / axisMax, 0), 1), color: t.color }; })
                         .sort(function(a, b) { return a.pos - b.pos; });
              
                     // Doppelte/zu nahe Positionen vermeiden (ECharts mag das nicht)
                     var colorStops = stops.map(function(s) { return { offset: s.pos, color: s.color }; });
                     if (colorStops.length === 1) {
                         colorStops = [{ offset: 0, color: colorStops[0].color }, { offset: 1, color: colorStops[0].color }];
                     }
              
                     // Verlaufsrichtung: horizontal = x-Achse, vertikal = y-Achse
                     // Bei negativen Werten ist die "0"-Seite am Ende des sichtbaren Balkens,
                     // daher Richtung umkehren damit die Farbskala weiterhin von der Achsen-Basis aus stimmt.
                     if (horizontal) {
                         return negative
                             ? { type: 'linear', x: 1, y: 0, x2: 0, y2: 0, colorStops: colorStops }
                             : { type: 'linear', x: 0, y: 0, x2: 1, y2: 0, colorStops: colorStops };
                     }
                     return negative
                         ? { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: colorStops }
                         : { type: 'linear', x: 0, y: 1, x2: 0, y2: 0, colorStops: colorStops };
                 }
              
                 var seriesData = items.map(function(d, idx) {
                     var negative = (d.value || 0) < 0;
                     // Rundung auf die Seite legen, zu der der Balken tatsächlich wächst:
                     // horizontal positiv → wächst nach rechts → rechte Ecken rund
                     // horizontal negativ → wächst nach links  → linke Ecken rund
                     // vertikal   positiv → wächst nach oben   → obere Ecken rund
                     // vertikal   negativ → wächst nach unten  → untere Ecken rund
                     var radius;
                     if (horizontal) {
                         radius = negative ? [barRadius, 0, 0, barRadius] : [0, barRadius, barRadius, 0];
                     } else {
                         radius = negative ? [0, 0, barRadius, barRadius] : [barRadius, barRadius, 0, 0];
                     }
              
                     // Gradient bevorzugt, wenn item.gradient=true UND Chart-weite thresholds vorhanden —
                     // sonst normale Farbauflösung über die bestehende getColor()-Logik
                     var color;
                     if (d.gradient === true && chartThresholds && chartThresholds.length > 0) {
                         color = buildGradientColor(negative);
                     } else {
                         color = getColor(d, idx);
                     }
              
                     return {
                         value: d.value,
                         itemStyle: color ? {
                             color: color,
                             borderRadius: radius
                         } : { borderRadius: radius }
                     };
                 });
              
                 // Achse bleibt aktiv (für korrekte Skalierung/Layout) –
                 // nur Labels/Linien/Gridlines werden ein-/ausgeblendet
                 var categoryAxis = {
                     type: 'category',
                     data: names,
                     axisLabel: { color: '#999', fontSize: axisFontSize, show: showAxes },
                     axisLine:  { show: showAxes, lineStyle: { color: 'rgba(255,255,255,0.08)' } },
                     axisTick:  { show: false }
                 };
                 var valueAxis = {
                     type: 'value',
                     axisLabel: {
                         color: '#666',
                         fontSize: axisFontSize - 1,
                         show: showAxes,
                         formatter: function(v) { return v + (unit ? ' ' + unit : ''); }
                     },
                     axisLine:  { show: false },
                     axisTick:  { show: false },
                     splitLine: { show: showAxes, lineStyle: { color: 'rgba(255,255,255,0.06)' } }
                 };
              
                 myChart.setOption({
                     backgroundColor: 'transparent',
                     grid: {
                         top: 12, bottom: horizontal ? 12 : 30,
                         left: 8,
                         right: horizontal ? (showValues ? 56 : 16) : 8,
                         containLabel: true
                     },
                     tooltip: {
                         trigger: 'axis',
                         axisPointer: { type: 'shadow' },
                         backgroundColor: 'rgba(20,20,20,0.9)',
                         borderColor: 'rgba(255,255,255,0.1)',
                         textStyle: { color: '#ddd', fontSize: 11 },
                         formatter: function(p) {
                             var item = p[0];
                             return item.name + ': ' + item.value + ' ' + unit;
                         }
                     },
                     xAxis: horizontal ? valueAxis : categoryAxis,
                     yAxis: horizontal ? categoryAxis : valueAxis,
                     series: [{
                         type: 'bar',
                         data: seriesData,
                         barCategoryGap: categoryGap,
                         animation: animEnabled,
                         animationDuration: animDuration,
                         label: {
                             show: showValues,
                             position: horizontal ? 'right' : 'top',
                             color: '#ccc',
                             fontSize: axisFontSize - 1,
                             formatter: function(p) { return p.value + ' ' + unit; }
                         }
                     }]
                 }, true);
              }
              
              // ── Resize ───────────────────────────────────────────────────
              window.addEventListener('resize', function() {
                 if (myChart) myChart.resize();
              });
              
              // ── Socket ───────────────────────────────────────────────────
              function parseAndRender(val) {
                 try {
                     loadStyle();
                     var data = typeof val === 'object' ? val : JSON.parse(val);
                     renderChart(data);
                 } catch(e) { console.error('StatusChart parse error:', e); }
              }
              
              socket.on('connect', function() {
                 socket.emit('subscribe', DP_OUT);
                 socket.emit('getState', DP_OUT, function(err, state) {
                     if (state && state.val) parseAndRender(state.val);
                 });
              });
              socket.on('stateChange', function(id, state) {
                 if (id === DP_OUT && state && state.val) parseAndRender(state.val);
              });
              
              initChart();
              </script>
              </body>
              </html>
              
              

              ein Beispiel:

              Definition

              {
                "statusCharts": [
                  {
                    "id": 1,
                    "title": "Phasenleistung",
                    "showTitle": false,
                    "showLegend": false,
                    "showAxes": false,
                    "showValues": true,
                    "chartType": "bar",
                    "orientation": "horizontal",
                    "refreshInterval": 2000,
                    "unit": "W",
                    "items": [
                      {
                        "dp": "0_userdata.0.4000_EnergieErzeugung.Batterie.State.Consumption_W",
                        "name": "Haus",
                        "gradient": true,
                        "thresholds": [
                          {
                            "above": 0,
                            "color": "rgba(229,57,53,0.9)"
                          },
                          {
                            "above": 20,
                            "color": "rgba(255,152,0,0.9)"
                          },
                          {
                            "above": 80,
                            "color": "rgba(76,175,80,0.9)"
                          }
                        ]
                      },
                      {
                        "dp": "0_userdata.0.4000_EnergieErzeugung.Batterie.State.Production_W",
                        "name": "PV",
                        "gradient": true,
                        "thresholds": [
                          {
                            "above": 0,
                            "color": "rgba(229,57,53,0.9)"
                          },
                          {
                            "above": 20,
                            "color": "rgba(255,152,0,0.9)"
                          },
                          {
                            "above": 80,
                            "color": "rgba(76,175,80,0.9)"
                          }
                        ]
                      },
                      {
                        "dp": "alias.0.4000_Energieerzeugung.Batterie.PacNetz",
                        "name": "L3",
                        "gradient": true,
                        "thresholds": [
                          {
                            "above": 0,
                            "color": "rgba(229,57,53,0.9)"
                          },
                          {
                            "above": 20,
                            "color": "rgba(255,152,0,0.9)"
                          },
                          {
                            "above": 80,
                            "color": "rgba(76,175,80,0.9)"
                          }
                        ]
                      }
                    ]
                  }
              ]
              }
              

              eingebunden in vis-2 über eine HTML Vorlage. Die Id hängt den Renderer an den entsprechenden Output DP

              <iframe src="http://192.168.178.9:8082/vis-2.0/charts/eChartsStatus/StatusChartWidget.html?id=1" 
                      style="border:none; background:transparent; width:100%; height:100%;"
                      frameborder="0">
              </iframe>
              

              sieht dann so aus
              bfbe08ca-4656-40c9-9b3e-eccf1faec229-image.jpeg

              • Achsen/Beschriftungen/Label sind konfigurierbar
              • die //@... im Skript kannst Du alle löschen. Die nutze ich für anderes, hatte nur keine Lust die raus zu nehmen.

              edit: die Charts sind nur für einen dark mode getestet

              Proxmox auf iNuc, lxc für IoB, InfluxDB2, Grafana, u.a. *** Homematic & Homematic IP, Shellies, Zigbee etc

              1 Antwort Letzte Antwort
              0

              Hey! Du scheinst an dieser Unterhaltung interessiert zu sein, hast aber noch kein Konto.

              Hast du es satt, bei jedem Besuch durch die gleichen Beiträge zu scrollen? Wenn du dich für ein Konto anmeldest, kommst du immer genau dorthin zurück, wo du zuvor warst, und kannst dich über neue Antworten benachrichtigen lassen (entweder per E-Mail oder Push-Benachrichtigung). Du kannst auch Lesezeichen speichern und Beiträge positiv bewerten, um anderen Community-Mitgliedern deine Wertschätzung zu zeigen.

              Mit deinem Input könnte dieser Beitrag noch besser werden 💗

              Registrieren Anmelden
              Antworten
              • In einem neuen Thema antworten
              Anmelden zum Antworten
              • Älteste zuerst
              • Neuste zuerst
              • Meiste Stimmen


              Support us

              ioBroker
              Community Adapters
              Donate

              508

              Online

              33.0k

              Benutzer

              83.3k

              Themen

              1.3m

              Beiträge
              Community
              Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
              ioBroker Community 2014-2026
              logo
              • Anmelden

              • Du hast noch kein Konto? Registrieren

              • Anmelden oder registrieren, um zu suchen
              • Erster Beitrag
                Letzter Beitrag
              0
              • Home
              • Aktuell
              • Tags
              • Ungelesen 0
              • Kategorien
              • Unreplied
              • Beliebt
              • GitHub
              • Docu
              • Hilfe