unique_bubble_graph.yaml aktualisiert

This commit is contained in:
2026-06-10 11:22:37 +00:00
parent a195e18983
commit f352d20589

View File

@@ -1,10 +1,10 @@
unique_bubble_graph: unique_bubble_graph:
name: Unique Bubble History Background Graph name: Unique Bubble Multi History Background Graph
version: 1.3.1 version: 2.6.0
creator: Torsten creator: Torsten
supported: supported:
- button - button
description: Shows a Home Assistant sensor history graph as Bubble Card background. description: Shows up to three Home Assistant sensor history graphs as Bubble Card background with data-point cache, persistent cache, centered loading spinner and extend-to-now.
code: | code: |
ha-card { ha-card {
position: relative !important; position: relative !important;
@@ -43,22 +43,40 @@ unique_bubble_graph:
} }
.bubble-history-background path.area { .bubble-history-background path.area {
fill: var(--bubble-history-fill-color, var(--primary-color)) !important; stroke: none !important;
opacity: var(--bubble-history-fill-opacity, 0.10) !important;
} }
.bubble-history-background path.line { .bubble-history-background path.line {
fill: none !important; fill: none !important;
stroke: var(--bubble-history-line-color, var(--primary-color)) !important;
stroke-width: var(--bubble-history-line-width, 2.5) !important;
stroke-linecap: round !important; stroke-linecap: round !important;
stroke-linejoin: round !important; stroke-linejoin: round !important;
opacity: var(--bubble-history-line-opacity, 0.75) !important;
} }
.bubble-history-background path.loading-line { .bubble-history-loader {
opacity: 0.35 !important; position: absolute !important;
stroke-dasharray: 6 6 !important; left: 50%;
top: 50%;
z-index: 9 !important;
width: 18px;
height: 18px;
margin-left: -9px;
margin-top: -9px;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.25);
border-top-color: var(--primary-color);
animation: bubble-history-spin 0.8s linear infinite;
display: none;
pointer-events: none;
opacity: 0.9;
}
@keyframes bubble-history-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
} }
.bubble-history-tooltip { .bubble-history-tooltip {
@@ -70,13 +88,27 @@ unique_bubble_graph:
background: rgba(20, 20, 20, 0.82); background: rgba(20, 20, 20, 0.82);
color: white; color: white;
font-size: 11px; font-size: 11px;
line-height: 1.25; line-height: 1.35;
white-space: nowrap; white-space: nowrap;
pointer-events: none; pointer-events: none;
transform: translate(-50%, 0); transform: translate(-50%, 0);
backdrop-filter: blur(8px); backdrop-filter: blur(8px);
} }
.bubble-history-tooltip-row {
display: flex;
gap: 6px;
align-items: center;
}
.bubble-history-tooltip-dot {
width: 7px;
height: 7px;
border-radius: 50%;
display: inline-block;
flex: 0 0 auto;
}
.bubble-history-marker { .bubble-history-marker {
position: absolute !important; position: absolute !important;
z-index: 8 !important; z-index: 8 !important;
@@ -107,57 +139,119 @@ unique_bubble_graph:
this.config.history_background_graph || this.config.history_background_graph ||
{}; {};
const entity = cfg.entity || this.config.entity || "sensor.sma_tripower_x_pv_power";
const entityLabel =
cfg.label ||
hass.states?.[entity]?.attributes?.friendly_name ||
entity ||
"Sensor";
const unit = hass.states?.[entity]?.attributes?.unit_of_measurement || "";
const hoursToShow = Number(cfg.hours_to_show ?? 24); const hoursToShow = Number(cfg.hours_to_show ?? 24);
const lineWidth = Number(cfg.line_width ?? 2.5); const refreshMinutes = Number(cfg.refresh_minutes ?? 10);
const refreshMinutes = Number(cfg.refresh_minutes ?? 0); const maxPoints = Number(cfg.max_points ?? 120);
const maxPoints = Number(cfg.max_points ?? 160);
const lineOpacity = Number(cfg.line_opacity ?? 0.75);
const fillOpacity = Number(cfg.fill_opacity ?? 0.10);
const borderRadius = Number(cfg.border_radius ?? 28); const borderRadius = Number(cfg.border_radius ?? 28);
const tooltipTop = Number(cfg.tooltip_top ?? 52); const tooltipTop = Number(cfg.tooltip_top ?? 52);
const paddingY = Number(cfg.padding_y ?? 12); const paddingY = Number(cfg.padding_y ?? 12);
const valuePaddingPercent = Number(cfg.value_padding_percent ?? 8);
const showLoading = cfg.show_loading !== false;
const persistentCache = cfg.persistent_cache !== false;
const maxPersistentCacheAgeHours = Number(cfg.max_cache_age_hours ?? 24);
const lineColor = cfg.line_color || "var(--primary-color)"; const extendToNow = cfg.extend_to_now !== false;
const fillColor = cfg.fill_color || lineColor; const extendThresholdMinutes = Number(cfg.extend_threshold_minutes ?? 2);
const defaultLineWidth = Number(cfg.line_width ?? 2.5);
const defaultLineOpacity = Number(cfg.line_opacity ?? 0.75);
const defaultFillOpacity = Number(cfg.fill_opacity ?? 0.08);
const defaultColors = [
cfg.graph_1_color || cfg.line_color || "var(--primary-color)",
cfg.graph_2_color || "#2196f3",
cfg.graph_3_color || "#4caf50"
];
const buildGraphs = () => {
if (Array.isArray(cfg.graphs) && cfg.graphs.length) {
return cfg.graphs
.slice(0, 3)
.map((graph, index) => ({
entity: graph.entity,
label:
graph.label ||
hass.states?.[graph.entity]?.attributes?.friendly_name ||
graph.entity ||
`Graph ${index + 1}`,
color: graph.color || graph.line_color || defaultColors[index],
lineWidth: Number(graph.line_width ?? defaultLineWidth),
lineOpacity: Number(graph.line_opacity ?? defaultLineOpacity),
fillOpacity: Number(graph.fill_opacity ?? defaultFillOpacity),
unit: hass.states?.[graph.entity]?.attributes?.unit_of_measurement || ""
}))
.filter((graph) => graph.entity);
}
const graph1Entity = cfg.graph_1_entity || cfg.entity || this.config.entity || "sensor.sma_tripower_x_pv_power";
const graph2Entity = cfg.graph_2_entity;
const graph3Entity = cfg.graph_3_entity;
return [
{
entity: graph1Entity,
label:
cfg.graph_1_label ||
cfg.label ||
hass.states?.[graph1Entity]?.attributes?.friendly_name ||
graph1Entity ||
"Graph 1",
color: cfg.graph_1_color || cfg.line_color || defaultColors[0],
lineWidth: Number(cfg.graph_1_line_width ?? defaultLineWidth),
lineOpacity: Number(cfg.graph_1_line_opacity ?? defaultLineOpacity),
fillOpacity: Number(cfg.graph_1_fill_opacity ?? defaultFillOpacity),
unit: hass.states?.[graph1Entity]?.attributes?.unit_of_measurement || ""
},
{
entity: graph2Entity,
label:
cfg.graph_2_label ||
hass.states?.[graph2Entity]?.attributes?.friendly_name ||
graph2Entity ||
"Graph 2",
color: cfg.graph_2_color || defaultColors[1],
lineWidth: Number(cfg.graph_2_line_width ?? defaultLineWidth),
lineOpacity: Number(cfg.graph_2_line_opacity ?? defaultLineOpacity),
fillOpacity: Number(cfg.graph_2_fill_opacity ?? defaultFillOpacity),
unit: hass.states?.[graph2Entity]?.attributes?.unit_of_measurement || ""
},
{
entity: graph3Entity,
label:
cfg.graph_3_label ||
hass.states?.[graph3Entity]?.attributes?.friendly_name ||
graph3Entity ||
"Graph 3",
color: cfg.graph_3_color || defaultColors[2],
lineWidth: Number(cfg.graph_3_line_width ?? defaultLineWidth),
lineOpacity: Number(cfg.graph_3_line_opacity ?? defaultLineOpacity),
fillOpacity: Number(cfg.graph_3_fill_opacity ?? defaultFillOpacity),
unit: hass.states?.[graph3Entity]?.attributes?.unit_of_measurement || ""
}
].filter((graph) => graph.entity);
};
const graphs = buildGraphs();
const host = this.shadowRoot?.querySelector("ha-card") const host = this.shadowRoot?.querySelector("ha-card")
|| this.querySelector?.("ha-card") || this.querySelector?.("ha-card")
|| card?.querySelector?.("ha-card") || card?.querySelector?.("ha-card")
|| card; || card;
if (!host || !entity) return ""; if (!host || !graphs.length) return "";
host.style.setProperty("--bubble-history-line-width", `${lineWidth}`);
host.style.setProperty("--bubble-history-line-opacity", `${lineOpacity}`);
host.style.setProperty("--bubble-history-fill-opacity", `${fillOpacity}`);
host.style.setProperty("--bubble-history-border-radius", `${borderRadius}px`); host.style.setProperty("--bubble-history-border-radius", `${borderRadius}px`);
host.style.setProperty("--bubble-history-line-color", lineColor);
host.style.setProperty("--bubble-history-fill-color", fillColor);
let bg = host.querySelector(".bubble-history-background"); let bg = host.querySelector(".bubble-history-background");
let tooltip = host.querySelector(".bubble-history-tooltip"); let tooltip = host.querySelector(".bubble-history-tooltip");
let marker = host.querySelector(".bubble-history-marker"); let marker = host.querySelector(".bubble-history-marker");
let loader = host.querySelector(".bubble-history-loader");
if (!bg) { if (!bg) {
bg = document.createElement("div"); bg = document.createElement("div");
bg.className = "bubble-history-background"; bg.className = "bubble-history-background";
host.prepend(bg); host.prepend(bg);
bg.innerHTML = `<svg viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true"></svg>`;
bg.innerHTML = `
<svg viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true">
<path class="line loading-line" d="M 0 70 L 20 55 L 40 65 L 60 45 L 80 58 L 100 38"></path>
</svg>
`;
} }
if (!tooltip) { if (!tooltip) {
@@ -172,9 +266,30 @@ unique_bubble_graph:
host.appendChild(marker); host.appendChild(marker);
} }
const formatValue = (value) => { if (!loader) {
if (unit === "W" && Math.abs(value) >= 1000) { loader = document.createElement("div");
return `${(value / 1000).toFixed(2)} kW`; loader.className = "bubble-history-loader";
host.appendChild(loader);
}
const showLoader = () => {
if (showLoading) loader.style.display = "block";
};
const hideLoader = () => {
loader.style.display = "none";
};
const formatValue = (value, unit) => {
if (unit === "W") {
if (Math.abs(value) >= 1000) {
return `${(value / 1000).toFixed(2)} kW`;
}
return `${Math.round(value)} W`;
}
if (unit === "%") {
return `${Math.round(value)} %`;
} }
if (Number.isInteger(value)) { if (Number.isInteger(value)) {
@@ -193,36 +308,86 @@ unique_bubble_graph:
}); });
}; };
const attachTooltip = (points) => { const findNearestPoint = (points, targetTime) => {
if (!points?.length) return null;
let nearest = points[0];
let nearestDiff = Math.abs(points[0].t - targetTime);
for (const point of points) {
const diff = Math.abs(point.t - targetTime);
if (diff < nearestDiff) {
nearest = point;
nearestDiff = diff;
}
}
return nearest;
};
const extendSeriesToNow = (seriesList) => {
if (!extendToNow) return seriesList;
const now = Date.now();
const thresholdMs = extendThresholdMinutes * 60 * 1000;
return seriesList.map((series) => {
const points = [...(series.points || [])];
const lastPoint = points[points.length - 1];
const currentValue = Number(hass.states?.[series.entity]?.state);
if (
Number.isFinite(currentValue) &&
(!lastPoint || now - lastPoint.t > thresholdMs)
) {
points.push({
t: now,
v: currentValue
});
}
return {
...series,
points
};
});
};
const attachTooltip = (seriesList) => {
if (cfg.tooltip === false) return; if (cfg.tooltip === false) return;
host.onmousemove = (event) => { host.onmousemove = (event) => {
if (!points?.length) return; const usableSeries = seriesList.filter((series) => series.points?.length);
if (!usableSeries.length) return;
const rect = host.getBoundingClientRect(); const rect = host.getBoundingClientRect();
const xRatio = Math.min(1, Math.max(0, (event.clientX - rect.left) / rect.width)); const xRatio = Math.min(1, Math.max(0, (event.clientX - rect.left) / rect.width));
const minTime = points[0].t; const globalMinTime = Math.min(...usableSeries.map((series) => series.points[0].t));
const maxTime = points[points.length - 1].t; const globalMaxTime = Math.max(...usableSeries.map((series) => series.points[series.points.length - 1].t));
const targetTime = minTime + xRatio * (maxTime - minTime); const targetTime = globalMinTime + xRatio * (globalMaxTime - globalMinTime);
let nearest = points[0]; const rows = usableSeries.map((series) => {
let nearestDiff = Math.abs(points[0].t - targetTime); const nearest = findNearestPoint(series.points, targetTime);
if (!nearest) return "";
for (const point of points) { return `
const diff = Math.abs(point.t - targetTime); <div class="bubble-history-tooltip-row">
if (diff < nearestDiff) { <span class="bubble-history-tooltip-dot" style="background:${series.color};"></span>
nearest = point; <span><strong>${series.label}:</strong> ${formatValue(nearest.v, series.unit)}</span>
nearestDiff = diff; </div>
} `;
} }).join("");
const x = ((nearest.t - minTime) / (maxTime - minTime || 1)) * rect.width; const firstNearest = findNearestPoint(usableSeries[0].points, targetTime);
const safeX = Math.min(rect.width - 55, Math.max(55, x)); const tooltipTime = firstNearest ? firstNearest.t : targetTime;
const x = ((targetTime - globalMinTime) / (globalMaxTime - globalMinTime || 1)) * rect.width;
const safeX = Math.min(rect.width - 60, Math.max(60, x));
tooltip.innerHTML = ` tooltip.innerHTML = `
<strong>${entityLabel}: ${formatValue(nearest.v)}</strong><br> ${rows}
${formatTime(nearest.t)} <div style="opacity:0.75;margin-top:3px;">${formatTime(tooltipTime)}</div>
`; `;
tooltip.style.display = "block"; tooltip.style.display = "block";
@@ -239,66 +404,34 @@ unique_bubble_graph:
}; };
}; };
if (!hass?.states?.[entity]) { const renderGraph = (seriesList) => {
console.warn(`Bubble history graph: entity not found: ${entity}`); const usableSeries = extendSeriesToNow(seriesList)
return ""; .filter((series) => series.points?.length >= 2);
}
if (!hass?.fetchWithAuth) { if (!usableSeries.length) return false;
console.warn("Bubble history graph: hass.fetchWithAuth not available");
return "";
}
if (bg.dataset.loading === "true") return ""; const width = 100;
bg.dataset.loading = "true"; const height = 100;
const end = new Date(); const globalMinTime = Math.min(...usableSeries.map((series) => series.points[0].t));
const start = new Date(end.getTime() - hoursToShow * 60 * 60 * 1000); const globalMaxTime = Math.max(...usableSeries.map((series) => series.points[series.points.length - 1].t));
const url = const buildPath = (series) => {
`/api/history/period/${encodeURIComponent(start.toISOString())}` + const rawMinValue = Math.min(...series.points.map((point) => point.v));
`?filter_entity_id=${encodeURIComponent(entity)}` + const rawMaxValue = Math.max(...series.points.map((point) => point.v));
`&end_time=${encodeURIComponent(end.toISOString())}` +
`&minimal_response`;
hass.fetchWithAuth(url) const rawRange = rawMaxValue - rawMinValue;
.then((response) => response.json()) const safeRange = rawRange || Math.max(Math.abs(rawMaxValue), 1);
.then((history) => { const valuePadding = safeRange * (valuePaddingPercent / 100);
bg.dataset.loading = "false";
const states = Array.isArray(history) ? history.flat() : []; const minValue = rawMinValue - valuePadding;
const maxValue = rawMaxValue + valuePadding;
let points = states const timeRange = globalMaxTime - globalMinTime || 1;
.map((state) => ({
t: new Date(state.lu || state.lc || state.last_updated || state.last_changed).getTime(),
v: Number(state.s ?? state.state)
}))
.filter((point) => Number.isFinite(point.t) && Number.isFinite(point.v))
.sort((a, b) => a.t - b.t);
if (points.length < 2) {
console.warn(`Bubble history graph: not enough points: ${points.length}`);
return;
}
if (points.length > maxPoints) {
const step = Math.ceil(points.length / maxPoints);
points = points.filter((_, index) => index % step === 0);
}
const width = 100;
const height = 100;
const minTime = Math.min(...points.map((point) => point.t));
const maxTime = Math.max(...points.map((point) => point.t));
const minValue = Math.min(...points.map((point) => point.v));
const maxValue = Math.max(...points.map((point) => point.v));
const timeRange = maxTime - minTime || 1;
const valueRange = maxValue - minValue || 1; const valueRange = maxValue - minValue || 1;
const xy = points.map((point) => { const xy = series.points.map((point) => {
const x = ((point.t - minTime) / timeRange) * width; const x = ((point.t - globalMinTime) / timeRange) * width;
const y = const y =
height - height -
paddingY - paddingY -
@@ -308,51 +441,362 @@ unique_bubble_graph:
return [x, y]; return [x, y];
}); });
const linePath = xy return xy
.map(([x, y], index) => .map(([x, y], index) =>
`${index === 0 ? "M" : "L"} ${x.toFixed(2)} ${y.toFixed(2)}` `${index === 0 ? "M" : "L"} ${x.toFixed(2)} ${y.toFixed(2)}`
) )
.join(" "); .join(" ");
};
const paths = usableSeries.map((series, index) => {
const linePath = buildPath(series);
const areaPath = `${linePath} L ${width} ${height} L 0 ${height} Z`; const areaPath = `${linePath} L ${width} ${height} L 0 ${height} Z`;
const svg = ` const area = series.fillOpacity > 0
<svg viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true"> ? `<path class="area area-${index + 1}" d="${areaPath}" style="fill:${series.color};opacity:${series.fillOpacity};"></path>`
<path class="area" d="${areaPath}"></path> : "";
<path class="line" d="${linePath}"></path>
</svg> const line = `
<path
class="line line-${index + 1}"
d="${linePath}"
style="
stroke:${series.color};
stroke-width:${series.lineWidth};
opacity:${series.lineOpacity};
"
></path>
`; `;
bg.innerHTML = svg; return `${area}${line}`;
attachTooltip(points); }).join("");
bg.innerHTML = `
<svg viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true">
${paths}
</svg>
`;
attachTooltip(usableSeries);
return true;
};
if (!hass?.fetchWithAuth) {
console.warn("Bubble history graph: hass.fetchWithAuth not available");
hideLoader();
return "";
}
const validGraphs = graphs.filter((graph) => {
if (!hass?.states?.[graph.entity]) {
console.warn(`Bubble history graph: entity not found: ${graph.entity}`);
return false;
}
return true;
});
if (!validGraphs.length) {
hideLoader();
return "";
}
const dataCacheParts = validGraphs.map((graph) => graph.entity).join("|");
const cacheKey = [
"unique-bubble-graph-data-v260",
dataCacheParts,
hoursToShow,
maxPoints
].join("::");
const storageKey = `unique-bubble-graph-data-cache:${cacheKey}`;
window.__uniqueBubbleGraphDataCache = window.__uniqueBubbleGraphDataCache || {};
const readPersistentCache = () => {
if (!persistentCache) return null;
try {
const raw = localStorage.getItem(storageKey);
if (!raw) return null;
const parsed = JSON.parse(raw);
if (!Array.isArray(parsed?.seriesList) || !parsed?.timestamp) {
return null;
}
const maxAgeMs = maxPersistentCacheAgeHours * 60 * 60 * 1000;
if (Date.now() - parsed.timestamp > maxAgeMs) {
localStorage.removeItem(storageKey);
return null;
}
return parsed;
} catch (error) {
console.warn("Bubble history graph persistent data cache read error:", error);
return null;
}
};
const writePersistentCache = (data) => {
if (!persistentCache) return;
try {
localStorage.setItem(storageKey, JSON.stringify({
timestamp: data.timestamp,
seriesList: data.seriesList
}));
} catch (error) {
console.warn("Bubble history graph persistent data cache write error:", error);
}
};
let cache =
window.__uniqueBubbleGraphDataCache[cacheKey] ||
readPersistentCache();
if (cache?.seriesList) {
renderGraph(cache.seriesList);
window.__uniqueBubbleGraphDataCache[cacheKey] = {
...cache,
loading: false
};
}
if (cache?.timestamp && Date.now() - cache.timestamp < refreshMinutes * 60 * 1000) {
hideLoader();
return "";
}
if (window.__uniqueBubbleGraphDataCache[cacheKey]?.loading) {
if (!cache?.seriesList) showLoader();
return "";
}
showLoader();
window.__uniqueBubbleGraphDataCache[cacheKey] = {
...(window.__uniqueBubbleGraphDataCache[cacheKey] || {}),
...(cache || {}),
loading: true
};
const end = new Date();
const start = new Date(end.getTime() - hoursToShow * 60 * 60 * 1000);
const fetchHistory = (graph) => {
const url =
`/api/history/period/${encodeURIComponent(start.toISOString())}` +
`?filter_entity_id=${encodeURIComponent(graph.entity)}` +
`&end_time=${encodeURIComponent(end.toISOString())}` +
`&minimal_response`;
return hass.fetchWithAuth(url)
.then((response) => response.json())
.then((history) => {
const states = Array.isArray(history) ? history.flat() : [];
let points = states
.map((state) => ({
t: new Date(state.lu || state.lc || state.last_updated || state.last_changed).getTime(),
v: Number(state.s ?? state.state)
}))
.filter((point) => Number.isFinite(point.t) && Number.isFinite(point.v))
.sort((a, b) => a.t - b.t);
if (points.length > maxPoints) {
const originalLastPoint = points[points.length - 1];
const step = Math.ceil(points.length / maxPoints);
points = points.filter((_, index) => index % step === 0);
if (
originalLastPoint &&
points[points.length - 1]?.t !== originalLastPoint.t
) {
points.push(originalLastPoint);
}
}
return {
...graph,
points
};
});
};
Promise.all(validGraphs.map(fetchHistory))
.then((seriesList) => {
hideLoader();
const usableSeries = seriesList.filter((series) => series.points.length >= 2);
if (!usableSeries.length) {
console.warn("Bubble history graph: not enough points for all graphs");
window.__uniqueBubbleGraphDataCache[cacheKey] = {
...(window.__uniqueBubbleGraphDataCache[cacheKey] || {}),
loading: false,
timestamp: Date.now()
};
return;
}
const newCache = {
timestamp: Date.now(),
loading: false,
seriesList: usableSeries
};
window.__uniqueBubbleGraphDataCache[cacheKey] = newCache;
writePersistentCache(newCache);
renderGraph(usableSeries);
}) })
.catch((error) => { .catch((error) => {
bg.dataset.loading = "false"; hideLoader();
window.__uniqueBubbleGraphDataCache[cacheKey] = {
...(window.__uniqueBubbleGraphDataCache[cacheKey] || {}),
loading: false,
timestamp: Date.now()
};
console.warn("Bubble history graph error:", error); console.warn("Bubble history graph error:", error);
}); });
return ""; return "";
})()} })()}
editor: editor:
- name: entity
label: Graph Sensor
selector:
entity:
domain: sensor
- name: label
label: Tooltip Label
selector:
text: null
- name: hours_to_show - name: hours_to_show
label: Stunden anzeigen label: Allgemein · Stunden anzeigen
selector: selector:
number: number:
min: 1 min: 1
max: 168 max: 168
step: 1 step: 1
mode: box mode: box
- name: refresh_minutes
label: Allgemein · Aktualisierung Minuten
selector:
number:
min: 1
max: 120
step: 1
mode: box
- name: persistent_cache
label: Allgemein · Cache nach F5 behalten
selector:
boolean: null
- name: max_cache_age_hours
label: Allgemein · Max. Cache-Alter Stunden
selector:
number:
min: 1
max: 168
step: 1
mode: box
- name: show_loading
label: Allgemein · Lade-Kringel anzeigen
selector:
boolean: null
- name: graph_1_entity
label: ① Graph · Sensor
selector:
entity:
domain: sensor
- name: graph_1_label
label: ① Graph · Label
selector:
text: null
- name: graph_1_color
label: ① Graph · Farbe
selector:
text: null
- name: graph_1_line_width
label: ① Graph · Linien Dicke
selector:
number:
min: 0.5
max: 10
step: 0.1
mode: box
- name: graph_1_fill_opacity
label: ① Graph · Fläche
selector:
number:
min: 0
max: 1
step: 0.05
mode: slider
- name: graph_2_entity
label: ② Graph · Sensor
selector:
entity:
domain: sensor
- name: graph_2_label
label: ② Graph · Label
selector:
text: null
- name: graph_2_color
label: ② Graph · Farbe
selector:
text: null
- name: graph_2_line_width
label: ② Graph · Linien Dicke
selector:
number:
min: 0.5
max: 10
step: 0.1
mode: box
- name: graph_2_fill_opacity
label: ② Graph · Fläche
selector:
number:
min: 0
max: 1
step: 0.05
mode: slider
- name: graph_3_entity
label: ③ Graph · Sensor
selector:
entity:
domain: sensor
- name: graph_3_label
label: ③ Graph · Label
selector:
text: null
- name: graph_3_color
label: ③ Graph · Farbe
selector:
text: null
- name: graph_3_line_width
label: ③ Graph · Linien Dicke
selector:
number:
min: 0.5
max: 10
step: 0.1
mode: box
- name: graph_3_fill_opacity
label: ③ Graph · Fläche
selector:
number:
min: 0
max: 1
step: 0.05
mode: slider
- name: line_width - name: line_width
label: Linien Dicke label: Anzeige · Standard Linien Dicke
selector: selector:
number: number:
min: 0.5 min: 0.5
@@ -360,7 +804,7 @@ unique_bubble_graph:
step: 0.1 step: 0.1
mode: box mode: box
- name: line_opacity - name: line_opacity
label: Linien Sichtbarkeit label: Anzeige · Linien Sichtbarkeit
selector: selector:
number: number:
min: 0 min: 0
@@ -368,35 +812,23 @@ unique_bubble_graph:
step: 0.05 step: 0.05
mode: slider mode: slider
- name: fill_opacity - name: fill_opacity
label: Flächen Sichtbarkeit label: Anzeige · Standard Fläche
selector: selector:
number: number:
min: 0 min: 0
max: 1 max: 1
step: 0.05 step: 0.05
mode: slider mode: slider
- name: max_points - name: value_padding_percent
label: Max. Datenpunkte label: Anzeige · Abstand oben/unten Prozent
selector:
number:
min: 20
max: 500
step: 10
mode: box
- name: tooltip
label: Tooltip anzeigen
selector:
boolean: null
- name: tooltip_top
label: Tooltip Position oben
selector: selector:
number: number:
min: 0 min: 0
max: 200 max: 30
step: 1 step: 1
mode: box mode: box
- name: padding_y - name: padding_y
label: Graph Abstand oben/unten label: Anzeige · Graph Abstand oben/unten
selector: selector:
number: number:
min: 0 min: 0
@@ -404,10 +836,43 @@ unique_bubble_graph:
step: 1 step: 1
mode: box mode: box
- name: border_radius - name: border_radius
label: Eckenradius label: Anzeige · Eckenradius
selector: selector:
number: number:
min: 0 min: 0
max: 60 max: 60
step: 1 step: 1
mode: box mode: box
- name: extend_to_now
label: Erweitert · Linie bis jetzt verlängern
selector:
boolean: null
- name: extend_threshold_minutes
label: Erweitert · Verlängern nach Minuten
selector:
number:
min: 0
max: 60
step: 1
mode: box
- name: max_points
label: Erweitert · Max. Datenpunkte
selector:
number:
min: 20
max: 500
step: 10
mode: box
- name: tooltip
label: Erweitert · Tooltip anzeigen
selector:
boolean: null
- name: tooltip_top
label: Erweitert · Tooltip Position oben
selector:
number:
min: 0
max: 200
step: 1
mode: box