<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Dynamische SVG-Progress-Bar]]></title><description><![CDATA[<p dir="auto">Dieses Skript bietet eine elegante Lösung, um ohne aufwendige Widget-Konfigurationen voll anpassbare Fortschrittsbalken (horizontal oder vertikal) direkt per JavaScript zu generieren. Ideal für Dashboards, bei denen Füllstände, Batteriestände oder Sensorwerte grafisch ansprechend visualisiert werden sollen.</p>
<p dir="auto"><img src="/assets/uploads/files/1782934257849-c39bcee3-eb86-4bf4-9cb5-cb2138ebdfe8-image.jpeg" alt="c39bcee3-eb86-4bf4-9cb5-cb2138ebdfe8-image.jpeg" class=" img-fluid img-markdown" /></p>
<p dir="auto"><strong>Hinweis: Dieses Skript wurde mit Unterstützung von KI entwickelt, um eine effiziente und saubere Code-Struktur für die dynamische SVG-Generierung sicherzustellen.</strong></p>
<p dir="auto"><strong>Warum dieses Skript?</strong><br />
In der Visualisierung (VIS) stößt man bei vielen gleichartigen Elementen oft an Grenzen. Anstatt für jedes Widget manuell Einstellungen vorzunehmen, generiert dieses Skript den SVG-Code "on-the-fly".</p>
<p dir="auto"><strong>Einheitliches Design:</strong><br />
Alle Balken sind konsistent.</p>
<p dir="auto"><strong>Flexibel:</strong><br />
Einfache Anpassung von Farben, Größen und Stilen über Parameter.</p>
<p dir="auto"><strong>Was ist nötig?</strong><br />
Du benötigst die Instanz javascript.</p>
<p dir="auto"><strong>Datenpunkt:</strong><br />
Erstelle einen Datenpunkt (Typ String) in <strong>0_userdata.0</strong>, in dem das generierte SVG gespeichert wird.</p>
<p dir="auto"><strong>Widget:</strong><br />
Erstelle in deiner VIS ein <strong>String (unescaped) Widget</strong> und binde den Datenpunkt über die Objekt ID ein: <strong>{0_userdata.0.dein.datenpunkt}</strong>.</p>
<p dir="auto"><strong>Die Parameter-Erklärung</strong></p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th style="text-align:left">Parameter</th>
<th style="text-align:left">Typ</th>
<th style="text-align:left">Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">orientation</td>
<td style="text-align:left">String</td>
<td style="text-align:left">"horizontal" oder "vertical"</td>
</tr>
<tr>
<td style="text-align:left">value</td>
<td style="text-align:left">Number</td>
<td style="text-align:left">Aktueller Wert in Prozent (0 - 100)</td>
</tr>
<tr>
<td style="text-align:left">progressColor</td>
<td style="text-align:left">String</td>
<td style="text-align:left">Farbe des Balkens (Hex, RGB oder RGBA)</td>
</tr>
<tr>
<td style="text-align:left">backgroundColor</td>
<td style="text-align:left">String</td>
<td style="text-align:left">Hintergrundfarbe (Hex, RGB, RGBA, "transparent")</td>
</tr>
<tr>
<td style="text-align:left">border</td>
<td style="text-align:left">Number</td>
<td style="text-align:left">Rahmenstärke in Pixeln</td>
</tr>
<tr>
<td style="text-align:left">borderColor</td>
<td style="text-align:left">String</td>
<td style="text-align:left">Rahmenfarbe (Hex, RGB oder RGBA)</td>
</tr>
<tr>
<td style="text-align:left">borderRadius</td>
<td style="text-align:left">Number</td>
<td style="text-align:left">Eckenabrundung in Pixeln</td>
</tr>
<tr>
<td style="text-align:left">labelFont</td>
<td style="text-align:left">String</td>
<td style="text-align:left">Schriftart (z.B. "Arial")</td>
</tr>
<tr>
<td style="text-align:left">labelSize</td>
<td style="text-align:left">String</td>
<td style="text-align:left">Schriftgröße (z.B. "20px") "0px" wenn das Label ausgeblendet werden soll</td>
</tr>
<tr>
<td style="text-align:left">labelColor</td>
<td style="text-align:left">String</td>
<td style="text-align:left">Textfarbe (Hex, RGB oder RGBA)</td>
</tr>
<tr>
<td style="text-align:left">labelshadow</td>
<td style="text-align:left">Boolean</td>
<td style="text-align:left">true für dezenten Text-Schatten</td>
</tr>
<tr>
<td style="text-align:left">labelsuffix</td>
<td style="text-align:left">String</td>
<td style="text-align:left">Anhang hinter dem Wert (z.B. "%")</td>
</tr>
<tr>
<td style="text-align:left">gradient</td>
<td style="text-align:left">Boolean</td>
<td style="text-align:left">true für automatischen Farbverlauf</td>
</tr>
<tr>
<td style="text-align:left">effect</td>
<td style="text-align:left">String</td>
<td style="text-align:left">"glanz" für Glas-Effekt oder "none"</td>
</tr>
<tr>
<td style="text-align:left">maxValue</td>
<td style="text-align:left">Number</td>
<td style="text-align:left">Standardwert = 100, kann aber auch jeder andere Wert sein</td>
</tr>
<tr>
<td style="text-align:left">width</td>
<td style="text-align:left">Number</td>
<td style="text-align:left">Breite, für Automatik <strong>null</strong> belassen, ansonsten gewünschten Wert - z.B. 200</td>
</tr>
<tr>
<td style="text-align:left">height</td>
<td style="text-align:left">Number</td>
<td style="text-align:left">Höhe, für Automatik <strong>null</strong> belassen, ansonsten gewünschten Wert - z.B. 40</td>
</tr>
</tbody>
</table>
<p dir="auto"><strong>Farbangaben &amp; Transparenz</strong><br />
Die Funktion ist für Standard-Farben ausgelegt. Du kannst für alle Farb-Parameter (progressColor, backgroundColor, borderColor, labelColor) die folgenden Formate verwenden:</p>
<p dir="auto"><strong>Hex-Werte:</strong> (z. B. "#2a2a2a"). Wichtig: Für den Parameter gradient = true solltest du bevorzugt Hex-Werte verwenden, da die interne Logik für Farbverläufe darauf optimiert ist.</p>
<p dir="auto"><strong>RGB / RGBA:</strong> Du kannst Farben als rgb(r, g, b) oder mit Transparenz als rgba(r, g, b, alpha) definieren.</p>
<p dir="auto"><strong>Beispiel:</strong> "rgba(0, 128, 0, 0.5)" für ein halbtransparentes Grün.</p>
<p dir="auto"><strong>"transparent":</strong> Verwende das Schlüsselwort "transparent", um den Hintergrund komplett auszublenden.</p>
<h2>Hier das Script:</h2>
<pre><code>// === Dynamische SVG-Progress-Bar ===
 
//Erst-Version 1.0.0 - 01.07.2026
//Version 1.0.2 - 02.07.2026
//Ersteller Ro75.
 
//Voraussetzungen (Version 1.0.2 getestet mit)
//NodeJS: 22.22.x
//Javascript-Adapter: 9.0.18
//Admin-Adapter: 7.8.23
//js-controler: 7.2.2

function buildProgressBarSvg(orientation, value, progressColor, backgroundColor, border, borderColor, borderRadius, labelFont, labelSize, labelColor, labelshadow, labelsuffix, gradient, effect, maxValue = 100, Width = null, Height = null) {
    const pct = (maxValue === 0) ? 0 : (value / maxValue) * 100;

    let svgW, svgH;
    if (orientation === "horizontal") {
        svgW = 80;
        svgH = 15;
    } else {
        svgW = 15;
        svgH = 80;
    }

    if (Width !== null) svgW = Width;
    if (Height !== null) svgH = Height;

    function adjustColor(color, lightnessChange) {
        let r, g, b, a = null;

        if (color.startsWith("#")) {
            let hex = color.slice(1);
            if (hex.length === 3) hex = hex.split("").map(c =&gt; c + c).join("");
            const num = parseInt(hex, 16);
            r = (num &gt;&gt; 16) &amp; 255;
            g = (num &gt;&gt; 8) &amp; 255;
            b = num &amp; 255;
        } else if (color.startsWith("rgb")) {
            const parts = color.match(/[\d.]+/g).map(Number);
            [r, g, b] = parts;
            if (parts.length === 4) a = parts[3];
        }
        else return color;

        r /= 255; g /= 255; b /= 255;

        const max = Math.max(r, g, b);
        const min = Math.min(r, g, b);
        let h, s;
        let l = (max + min) / 2;

        if (max === min) {
            h = s = 0;
        } else {
            const d = max - min;
            s = l &gt; 0.5 ? d / (2 - max - min) : d / (max + min);

            switch (max) {
                case r: h = (g - b) / d + (g &lt; b ? 6 : 0); break;
                case g: h = (b - r) / d + 2; break;
                case b: h = (r - g) / d + 4; break;
            }
            h /= 6;
        }

        l += lightnessChange / 100;
        l = Math.max(0, Math.min(1, l));

        function hue2rgb(p, q, t) {
            if (t &lt; 0) t += 1;
            if (t &gt; 1) t -= 1;
            if (t &lt; 1/6) return p + (q - p) * 6 * t;
            if (t &lt; 1/2) return q;
            if (t &lt; 2/3) return p + (q - p) * (2/3 - t) * 6;
            return p;
        }

        let rr, gg, bb;

        if (s === 0) {
            rr = gg = bb = l;
        } else {
            const q = l &lt; 0.5 ? l * (1 + s) : l + s - l * s;
            const p = 2 * l - q;

            rr = hue2rgb(p, q, h + 1/3);
            gg = hue2rgb(p, q, h);
            bb = hue2rgb(p, q, h - 1/3);
        }

        rr = Math.round(rr * 255);
        gg = Math.round(gg * 255);
        bb = Math.round(bb * 255);

        if (a !== null) return `rgba(${rr},${gg},${bb},${a})`;
        return "#" + ((1 &lt;&lt; 24) | (rr &lt;&lt; 16) | (gg &lt;&lt; 8) | bb).toString(16).slice(1);
    }

    let finalProgressColor = progressColor;
    let gradientDef = "";

    if (gradient === true) {
        const gradId = "grad_" + Math.random().toString(36).slice(2);

        const c1 = adjustColor(progressColor, -25);
        const c2 = adjustColor(progressColor, -15);
        const c3 = adjustColor(progressColor, -5);
        const c4 = adjustColor(progressColor, 5);
        const c5 = adjustColor(progressColor, 15);
        const c6 = adjustColor(progressColor, 25);

        if (orientation === "horizontal") {
            gradientDef = `
                &lt;linearGradient id="${gradId}" x1="0" y1="0" x2="1" y2="0"&gt;
                    &lt;stop offset="0%"   stop-color="${c1}"/&gt;
                    &lt;stop offset="20%"  stop-color="${c2}"/&gt;
                    &lt;stop offset="40%"  stop-color="${c3}"/&gt;
                    &lt;stop offset="60%"  stop-color="${c4}"/&gt;
                    &lt;stop offset="80%"  stop-color="${c5}"/&gt;
                    &lt;stop offset="100%" stop-color="${c6}"/&gt;
                &lt;/linearGradient&gt;
            `;
        } else {
            gradientDef = `
                &lt;linearGradient id="${gradId}" x1="0" y1="1" x2="0" y2="0"&gt;
                    &lt;stop offset="0%"   stop-color="${c1}"/&gt;
                    &lt;stop offset="20%"  stop-color="${c2}"/&gt;
                    &lt;stop offset="40%"  stop-color="${c3}"/&gt;
                    &lt;stop offset="60%"  stop-color="${c4}"/&gt;
                    &lt;stop offset="80%"  stop-color="${c5}"/&gt;
                    &lt;stop offset="100%" stop-color="${c6}"/&gt;
                &lt;/linearGradient&gt;
            `;
        }
        finalProgressColor = `url(#${gradId})`;
    }

    const innerX = border;
    const innerY = border;
    const innerW = svgW - border * 2;
    const innerH = svgH - border * 2;

    const outerRadius = borderRadius;
    const innerRadius = Math.max(0, outerRadius - border);
    const effectiveRadius = innerRadius - 0.3;

    const clipId = "clip_" + Math.random().toString(36).slice(2);
    const glossClipId = "clip_gloss_" + Math.random().toString(36).slice(2);

    let clipDef = "";
    let glossClipDef = "";

    if (pct &gt; 0) {
        if (orientation === "horizontal") {
            const fillW = innerW * (pct / 100);

            clipDef = `&lt;clipPath id="${clipId}"&gt;&lt;rect x="${innerX}" y="${innerY}" width="${fillW}" height="${innerH}" /&gt;&lt;/clipPath&gt;`;

            if (effect === "glanz") {
                const glossH = innerH * 0.45;
                glossClipDef = `&lt;clipPath id="${glossClipId}"&gt;&lt;rect x="${innerX}" y="${innerY}" width="${fillW}" height="${glossH}" /&gt;&lt;/clipPath&gt;`;
            }
        } else {
            const fillH = innerH * (pct / 100);
            const yFill = innerY + innerH - fillH;

            clipDef = `&lt;clipPath id="${clipId}"&gt;&lt;rect x="${innerX}" y="${yFill}" width="${innerW}" height="${fillH}" /&gt;&lt;/clipPath&gt;`;

            if (effect === "glanz") {
                const glossW = innerW * 0.45;
                glossClipDef = `&lt;clipPath id="${glossClipId}"&gt;&lt;rect x="${innerX}" y="${yFill}" width="${glossW}" height="${fillH}" /&gt;&lt;/clipPath&gt;`;
            }
        }
    }

    const labelText = Math.round(pct * 100) / 100 + " " + labelsuffix;
    const shadow = labelshadow ? "text-shadow:1px 1px 2px black;" : "";

    return `
        &lt;svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 ${svgW} ${svgH}" preserveAspectRatio="none"&gt;
            &lt;defs&gt;
                ${gradientDef}
                ${clipDef}
                ${glossClipDef}
            &lt;/defs&gt;

            &lt;rect x="0" y="0" width="${svgW}" height="${svgH}" fill="${borderColor}" rx="${outerRadius}" ry="${outerRadius}" /&gt;
            &lt;rect x="${innerX}" y="${innerY}" width="${innerW}" height="${innerH}" fill="${backgroundColor}" rx="${innerRadius}" ry="${innerRadius}" /&gt;

            ${pct &gt; 0 ? `&lt;rect x="${innerX}" y="${innerY}" width="${innerW}" height="${innerH}" fill="${finalProgressColor}" rx="${effectiveRadius}" ry="${effectiveRadius}" clip-path="url(#${clipId})" /&gt;` : ""}
            ${effect === "glanz" &amp;&amp; pct &gt; 0 ? `&lt;rect x="${innerX}" y="${innerY}" width="${innerW}" height="${innerH}" fill="rgba(255,255,255,0.35)" rx="${effectiveRadius}" ry="${effectiveRadius}" clip-path="url(#${glossClipId})" /&gt;` : ""}

            &lt;foreignObject x="0" y="0" width="${svgW}" height="${svgH}"&gt;
                &lt;div xmlns="http://www.w3.org/1999/xhtml"
                    style="
                        position:absolute;
                        top:50%;
                        left:50%;
                        transform:translate(-50%,-50%) ${orientation === 'vertical' ? 'rotate(-90deg)' : ''};
                        font-family:${labelFont};
                        font-size:${labelSize};
                        font-weight:bold;
                        color:${labelColor};
                        ${shadow}
                        white-space:nowrap;
                        pointer-events:none;
                    "&gt;
                    ${labelText}
                &lt;/div&gt;
            &lt;/foreignObject&gt;
        &lt;/svg&gt;
    `;
}
</code></pre>
<p dir="auto"><strong>Anwendung mit on()-Trigger (Automatische Aktualisierung):</strong><br />
Anstatt den Wert manuell zu setzen, solltest du das Skript durch einen Trigger auslösen lassen. So aktualisiert sich deine ProgressBar automatisch, sobald sich der Wert deines Sensors ändert:</p>
<pre><code>// Beispiel: Überwache einen Datenpunkt (z.B. einen Füllstand)
on({ id: 'alias.0.Geräte.Level', change: 'any' }, function (obj) {
    
    // Die ProgressBar wird bei jeder Änderung des Datenpunktes neu berechnet
    let barSVG = buildProgressBarSvg(
        "horizontal", 
        obj.state.val,              // Der aktuelle Wert vom Sensor
        "#004400",                  // progressColor
        "#333333",                  // backgroundColor
        0.5,                        // border
        "white",                    // borderColor
        5,                          // borderRadius
        "Arial",                    // labelFont
        "6px",                      // labelSize
        "#ffffff",                  // labelColor
        true,                       // labelshadow
        "%",                        // labelsuffix
        true,                       // gradient
        "none",                     // effect
        100                         // maxValue
        200                         // width
        40                          // height
    );

    // Das Ergebnis in den Ziel-Datenpunkt schreiben
    setState('0_userdata.0.meine_bar', barSVG);
});
</code></pre>
<p dir="auto"><strong>Hinweis zu anderen Visualisierungen:</strong><br />
Dieses Skript generiert sauberen SVG-Code. Dadurch sollte es nicht nur in der klassischen vis, sondern auch in anderen Visualisierungen innerhalb von ioBroker problemlos funktionieren.</p>
<p dir="auto"><strong>Solltest du das Skript in einem anderen Adapter einsetzen und Probleme bei der Darstellung haben, empfehle ich, kurz im jeweiligen Unterforum des Adapters nachzufragen.</strong></p>
<p dir="auto">Viel Erfolg beim Einbinden in dein Dashboard. Wer mag, kann sich auch die <a href="https://forum.iobroker.net/topic/84899/dynamische-html-progress-bar">HTML-Version</a> ansehen.</p>
<p dir="auto">Ro75.</p>
<h2>EDIT Version 1.0.1</h2>
<ul>
<li>es kann nun explizit die <strong>Höhe (height)</strong> und die <strong>Breite (width)</strong> mit übergeben werden</li>
</ul>
<h2>EDIT Version 1.0.2</h2>
<ul>
<li>unter Umstanden wurde der Wert vom <strong>Labe</strong>l falsch dargestellt (<strong>Nachkommastellen / Rundung</strong>)</li>
</ul>
]]></description><link>https://forum.iobroker.net/topic/84905/dynamische-svg-progress-bar</link><generator>RSS for Node</generator><lastBuildDate>Thu, 09 Jul 2026 00:42:18 GMT</lastBuildDate><atom:link href="https://forum.iobroker.net/topic/84905.rss" rel="self" type="application/rss+xml"/><pubDate>Wed, 01 Jul 2026 19:35:51 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Dynamische SVG-Progress-Bar on Thu, 02 Jul 2026 12:34:25 GMT]]></title><description><![CDATA[<p dir="auto">Mal ein praktisches Anwendungsbeispiel bei mir:<br />
<img src="/assets/uploads/files/1782994846681-8338f67a-91b1-4e1b-8d1b-c2a3cae4c6e1-image.jpeg" alt="8338f67a-91b1-4e1b-8d1b-c2a3cae4c6e1-image.jpeg" class=" img-fluid img-markdown" /></p>
<p dir="auto">Meine Wetterview. In der Mitte haben wir die Prognosedaten für die nächsten 48 Stunden für Bewölkung, Luftfeuchtigkeit, Regenwahrscheinlichkeit und Regenmenge. Insgesamt sind das 192 SVG-Progressbar. Aber nicht als Dateien oder Datenpunkte. Das ganze ist <strong>ein Widget</strong> in dem diese 192 SVG (Base64 codiert) dargestellt werden. Alles ohne Verzögerung und <strong>ohne nennenswerter Systembelastung</strong>.<br />
<img src="/assets/uploads/files/1782995534289-182a1344-5db2-4047-b03b-760bf068c456-image.jpeg" alt="182a1344-5db2-4047-b03b-760bf068c456-image.jpeg" class=" img-fluid img-markdown" /><br />
Alles was hier zu sehen ist kommt aus meinem Script "<strong>EnviroSense</strong>". Dieses Script liefert mir mit nur 3 Werten folgende Daten:</p>
<ul>
<li>Wetter (Openweathermap API) - wie der Adapter - 5 Tage, 48 Stunden</li>
<li>Luftqualitätsdaten (Openweathermap API)</li>
<li>DWD Daten - wie der Adapter</li>
<li>Strahlungsdaten - wie der ODL - Adapter</li>
<li>Pollenflugdaten - wie der Adapter, aber mit mehr Infos</li>
<li>Biowetterdaten</li>
<li>jede Menge Trigger die auf Wetterereignisse reagieren</li>
<li>zusätzliche Sensorwerte durch externe Sensoren</li>
<li>jede Menge an jsons zur Visualisierung mit Grafana</li>
</ul>
<p dir="auto">Falls an dem Script interesse besteht einfach melden und vielleicht einen Daumen hoch.</p>
<p dir="auto">Ro75.</p>
]]></description><link>https://forum.iobroker.net/post/1346799</link><guid isPermaLink="true">https://forum.iobroker.net/post/1346799</guid><dc:creator><![CDATA[Ro75]]></dc:creator><pubDate>Thu, 02 Jul 2026 12:34:25 GMT</pubDate></item><item><title><![CDATA[Reply to Dynamische SVG-Progress-Bar on Thu, 02 Jul 2026 11:33:50 GMT]]></title><description><![CDATA[<h2>EDIT Version 1.0.2</h2>
<ul>
<li>unter Umstanden wurde der Wert vom <strong>Label</strong> falsch dargestellt (<strong>Nachkommastellen / Rundung</strong>)</li>
</ul>
<p dir="auto">Script wurde in Post #1 aktualisiert.</p>
<p dir="auto">Ro75.</p>
]]></description><link>https://forum.iobroker.net/post/1346795</link><guid isPermaLink="true">https://forum.iobroker.net/post/1346795</guid><dc:creator><![CDATA[Ro75]]></dc:creator><pubDate>Thu, 02 Jul 2026 11:33:50 GMT</pubDate></item><item><title><![CDATA[Reply to Dynamische SVG-Progress-Bar on Thu, 02 Jul 2026 08:02:51 GMT]]></title><description><![CDATA[<h2>EDIT Version 1.0.1</h2>
<ul>
<li>es kann nun explizit die <strong>Höhe (height)</strong> und die <strong>Breite (width)</strong> mit übergeben werden</li>
</ul>
<p dir="auto">Script wurde in Post #1 aktualisiert.</p>
<p dir="auto">Ro75.</p>
]]></description><link>https://forum.iobroker.net/post/1346781</link><guid isPermaLink="true">https://forum.iobroker.net/post/1346781</guid><dc:creator><![CDATA[Ro75]]></dc:creator><pubDate>Thu, 02 Jul 2026 08:02:51 GMT</pubDate></item><item><title><![CDATA[Reply to Dynamische SVG-Progress-Bar on Wed, 01 Jul 2026 20:53:09 GMT]]></title><description><![CDATA[<p dir="auto">Ja, das mag sein. Ich zeige Möglichkeiten damit man nicht immer 20 verschiedene Adapter installieren muss, wenn es auch mit "Basics" geht. Das ist mein Ansatz und den Teile ich.</p>
<p dir="auto">Ro75.</p>
]]></description><link>https://forum.iobroker.net/post/1346774</link><guid isPermaLink="true">https://forum.iobroker.net/post/1346774</guid><dc:creator><![CDATA[Ro75]]></dc:creator><pubDate>Wed, 01 Jul 2026 20:53:09 GMT</pubDate></item><item><title><![CDATA[Reply to Dynamische SVG-Progress-Bar on Wed, 01 Jul 2026 20:08:57 GMT]]></title><description><![CDATA[<p dir="auto">Das geht aber doch auch ganz ohne Script mit unserem inventwo Slider für VIS2.</p>
]]></description><link>https://forum.iobroker.net/post/1346770</link><guid isPermaLink="true">https://forum.iobroker.net/post/1346770</guid><dc:creator><![CDATA[skvarel]]></dc:creator><pubDate>Wed, 01 Jul 2026 20:08:57 GMT</pubDate></item></channel></rss>