diff --git a/unique_bubble_graph.yaml b/unique_bubble_graph.yaml
index 09a4458..3e494b3 100644
--- a/unique_bubble_graph.yaml
+++ b/unique_bubble_graph.yaml
@@ -1,6 +1,6 @@
unique_bubble_graph:
name: Unique Bubble Multi History Background Graph
- version: 2.8.6
+ version: 2.8.7
creator: Torsten
supported:
- button
@@ -120,6 +120,26 @@ unique_bubble_graph:
pointer-events: none;
}
+ .bubble-history-point-markers {
+ position: absolute !important;
+ inset: 0 !important;
+ z-index: 8 !important;
+ display: none;
+ pointer-events: none;
+ }
+
+ .bubble-history-point-marker {
+ position: absolute !important;
+ width: 9px;
+ height: 9px;
+ border-radius: 50%;
+ transform: translate(-50%, -50%);
+ box-shadow:
+ 0 0 0 2px rgba(20, 20, 20, 0.55),
+ 0 0 8px rgba(255, 255, 255, 0.65);
+ pointer-events: none;
+ }
+
.bubble-button-card-container,
.bubble-button-card,
.bubble-name-container,
@@ -175,6 +195,9 @@ unique_bubble_graph:
const extendToNow = cfg.extend_to_now !== false;
const extendThresholdMinutes = Number(cfg.extend_threshold_minutes ?? 2);
+ const tooltipIndicator = ["points", "line", "both", "none"].includes(cfg.tooltip_indicator)
+ ? cfg.tooltip_indicator
+ : "points";
const sameScale = cfg.same_scale === true;
const numberOrNull = (value) => {
@@ -360,6 +383,7 @@ unique_bubble_graph:
let bg = host.querySelector(".bubble-history-background");
let tooltip = host.querySelector(".bubble-history-tooltip");
let marker = host.querySelector(".bubble-history-marker");
+ let pointLayer = host.querySelector(".bubble-history-point-markers");
let loader = host.querySelector(".bubble-history-loader");
if (!bg) {
@@ -381,6 +405,12 @@ unique_bubble_graph:
host.appendChild(marker);
}
+ if (!pointLayer) {
+ pointLayer = document.createElement("div");
+ pointLayer.className = "bubble-history-point-markers";
+ host.appendChild(pointLayer);
+ }
+
if (!loader) {
loader = document.createElement("div");
loader.className = "bubble-history-loader";
@@ -537,7 +567,10 @@ unique_bubble_graph:
host.__bubbleHistoryTooltip = tooltip;
host.__bubbleHistoryMarker = marker;
+ host.__bubbleHistoryPointLayer = pointLayer;
host.__bubbleHistoryTooltipTop = tooltipTop;
+ host.__bubbleHistoryTooltipIndicator = tooltipIndicator;
+ host.__bubbleHistoryPaddingY = paddingY;
if (host.__bubbleHistoryTooltipAttached) return;
@@ -551,8 +584,9 @@ unique_bubble_graph:
const tooltipElement = host.__bubbleHistoryTooltip;
const markerElement = host.__bubbleHistoryMarker;
+ const pointLayerElement = host.__bubbleHistoryPointLayer;
- if (!tooltipElement || !markerElement) return;
+ if (!tooltipElement || !markerElement || !pointLayerElement) return;
const rect = host.getBoundingClientRect();
const xRatio = Math.min(1, Math.max(0, (event.clientX - rect.left) / rect.width));
@@ -568,11 +602,15 @@ unique_bubble_graph:
: Math.max(...usableSeries.map((series) => series.points[series.points.length - 1].t));
const targetTime = globalMinTime + xRatio * (globalMaxTime - globalMinTime);
+ const timeRange = globalMaxTime - globalMinTime || 1;
+ const indicatorMode = host.__bubbleHistoryTooltipIndicator || "points";
- const rows = usableSeries.map((series) => {
- const nearest = findNearestPoint(series.points, targetTime);
- if (!nearest) return "";
+ const nearestBySeries = usableSeries.map((series) => ({
+ series,
+ nearest: findNearestPoint(series.points, targetTime)
+ })).filter((entry) => entry.nearest);
+ const rows = nearestBySeries.map(({ series, nearest }) => {
return `
@@ -581,31 +619,68 @@ unique_bubble_graph:
`;
}).join("");
- const firstNearest = findNearestPoint(usableSeries[0].points, targetTime);
+ const firstNearest = nearestBySeries[0]?.nearest;
const tooltipTime = firstNearest ? firstNearest.t : targetTime;
- const x = ((targetTime - globalMinTime) / (globalMaxTime - globalMinTime || 1)) * rect.width;
+ const x = ((targetTime - globalMinTime) / timeRange) * rect.width;
const safeX = Math.min(rect.width - 60, Math.max(60, x));
+ const pointMarkers = nearestBySeries.map(({ series, nearest }) => {
+ const minValue = Number.isFinite(series.__minValue) ? series.__minValue : 0;
+ const maxValue = Number.isFinite(series.__maxValue) ? series.__maxValue : 1;
+ const valueRange = maxValue - minValue || 1;
+ const pointX = ((nearest.t - globalMinTime) / timeRange) * rect.width;
+ const pointYView =
+ 100 -
+ host.__bubbleHistoryPaddingY -
+ ((nearest.v - minValue) / valueRange) *
+ (100 - host.__bubbleHistoryPaddingY * 2);
+ const pointY = (pointYView / 100) * rect.height;
+
+ return `
+
+ `;
+ }).join("");
+
tooltipElement.innerHTML = `
${rows}
${formatTime(tooltipTime)}
`;
tooltipElement.style.display = "block";
- markerElement.style.display = "block";
-
tooltipElement.style.left = `${safeX}px`;
tooltipElement.style.top = `${host.__bubbleHistoryTooltipTop}px`;
- markerElement.style.left = `${x}px`;
+
+ if (indicatorMode === "line" || indicatorMode === "both") {
+ markerElement.style.display = "block";
+ markerElement.style.left = `${x}px`;
+ } else {
+ markerElement.style.display = "none";
+ }
+
+ if (indicatorMode === "points" || indicatorMode === "both") {
+ pointLayerElement.innerHTML = pointMarkers;
+ pointLayerElement.style.display = "block";
+ } else {
+ pointLayerElement.innerHTML = "";
+ pointLayerElement.style.display = "none";
+ }
};
host.onmouseleave = () => {
const tooltipElement = host.__bubbleHistoryTooltip;
const markerElement = host.__bubbleHistoryMarker;
+ const pointLayerElement = host.__bubbleHistoryPointLayer;
if (tooltipElement) tooltipElement.style.display = "none";
if (markerElement) markerElement.style.display = "none";
+ if (pointLayerElement) {
+ pointLayerElement.style.display = "none";
+ pointLayerElement.innerHTML = "";
+ }
};
};
@@ -645,25 +720,39 @@ unique_bubble_graph:
const globalMaxTime = Math.max(...usableSeries.map((series) => series.points[series.points.length - 1].t));
const sameScaleRange = sameScale ? getSameScaleRange(usableSeries) : null;
- const buildPath = (series) => {
- let minValue;
- let maxValue;
-
+ const getValueRange = (series) => {
if (sameScaleRange) {
- minValue = sameScaleRange.minValue;
- maxValue = sameScaleRange.maxValue;
- } else {
- const rawMinValue = Math.min(...series.points.map((point) => point.v));
- const rawMaxValue = Math.max(...series.points.map((point) => point.v));
-
- const rawRange = rawMaxValue - rawMinValue;
- const safeRange = rawRange || Math.max(Math.abs(rawMaxValue), 1);
- const valuePadding = safeRange * (valuePaddingPercent / 100);
-
- minValue = rawMinValue - valuePadding;
- maxValue = rawMaxValue + valuePadding;
+ return {
+ minValue: sameScaleRange.minValue,
+ maxValue: sameScaleRange.maxValue
+ };
}
+ const rawMinValue = Math.min(...series.points.map((point) => point.v));
+ const rawMaxValue = Math.max(...series.points.map((point) => point.v));
+ const rawRange = rawMaxValue - rawMinValue;
+ const safeRange = rawRange || Math.max(Math.abs(rawMaxValue), 1);
+ const valuePadding = safeRange * (valuePaddingPercent / 100);
+
+ return {
+ minValue: rawMinValue - valuePadding,
+ maxValue: rawMaxValue + valuePadding
+ };
+ };
+
+ const renderSeries = usableSeries.map((series) => {
+ const range = getValueRange(series);
+
+ return {
+ ...series,
+ __minValue: range.minValue,
+ __maxValue: range.maxValue
+ };
+ });
+
+ const buildPath = (series) => {
+ const minValue = series.__minValue;
+ const maxValue = series.__maxValue;
const timeRange = globalMaxTime - globalMinTime || 1;
const valueRange = maxValue - minValue || 1;
@@ -685,7 +774,7 @@ unique_bubble_graph:
.join(" ");
};
- const paths = usableSeries.map((series, index) => {
+ const paths = renderSeries.map((series, index) => {
const linePath = buildPath(series);
const areaPath = `${linePath} L ${width} ${height} L 0 ${height} Z`;
@@ -714,7 +803,7 @@ unique_bubble_graph:
`;
- host.__bubbleHistorySeries = usableSeries;
+ host.__bubbleHistorySeries = renderSeries;
host.__bubbleHistoryMinTime = globalMinTime;
host.__bubbleHistoryMaxTime = globalMaxTime;
attachTooltipOnce();
@@ -1197,6 +1286,19 @@ unique_bubble_graph:
label: Tooltip anzeigen
selector:
boolean: null
+ - name: tooltip_indicator
+ label: Tooltip Markierung
+ selector:
+ select:
+ options:
+ - label: Punkte auf dem Graphen
+ value: points
+ - label: Vertikale Linie
+ value: line
+ - label: Punkte und Linie
+ value: both
+ - label: Keine Markierung
+ value: none
- name: tooltip_top
label: Tooltip Position oben
selector: