I created a dashboard using Google Charts and wanted to annotate data by using custom tooltips. But I only manage to display my custom tooltips for the latest created line (line of plot87 in JSFiddle), but lines created before are using the default tooltip (line of plot66 in JSFiddle).
It seems like I am somehow causing a fallback to the default tooltip, but I do not see how. After logging the data seems to be the same structure for both lines, so I do not know, where I force the fallback or cancel out my custom tooltip.
Here is my code, where I tried to reproduce my problem:
const jsonData = {
"title": "Dashboard",
"updated_on": "2025-02-21T14:08:28.741617",
"panels": [
{
"type": "timeseries",
"table": "air_temp",
"title": "Air Temperature 2 m above Surface",
"unit": "u00b0C",
"data": {
"plot66": [
{
"time": "2025-01-01T00:00:00",
"value": 7.468658447265625
},
{
"time": "2025-01-01T03:00:00",
"value": 7.459381103515625
},
{
"time": "2025-01-01T06:00:00",
"value": 7.389312744140625
},
{
"time": "2025-01-01T09:00:00",
"value": 9.727935791015625
},
{
"time": "2025-01-01T12:00:00",
"value": 10.906646728515625
},
{
"time": "2025-01-01T15:00:00",
"value": 11.067535400390625
},
{
"time": "2025-01-01T18:00:00",
"value": 8.872955322265625
},
{
"time": "2025-01-01T21:00:00",
"value": 8.459625244140625
},
{
"time": "2025-01-02T00:00:00",
"value": 8.005035400390625
},
{
"time": "2025-01-02T03:00:00",
"value": 7.952545166015625
},
{
"time": "2025-01-02T06:00:00",
"value": 7.978424072265625
},
{
"time": "2025-01-02T09:00:00",
"value": 10.216461181640625
},
{
"time": "2025-01-02T12:00:00",
"value": 10.678863525390625
},
{
"time": "2025-01-02T15:00:00",
"value": 10.543609619140625
},
{
"time": "2025-01-02T18:00:00",
"value": 9.369049072265625
},
{
"time": "2025-01-02T21:00:00",
"value": 9.340240478515625
},
{
"time": "2025-01-03T00:00:00",
"value": 8.868316650390625
},
{
"time": "2025-01-03T03:00:00",
"value": 8.211090087890625
},
{
"time": "2025-01-03T06:00:00",
"value": 7.642486572265625
},
{
"time": "2025-01-03T09:00:00",
"value": 9.831451416015625
},
{
"time": "2025-01-03T12:00:00",
"value": 10.954010009765625
},
{
"time": "2025-01-03T15:00:00",
"value": 11.588531494140625
},
{
"time": "2025-01-03T18:00:00",
"value": 10.276763916015625
},
{
"time": "2025-01-03T21:00:00",
"value": 10.511627197265625
},
{
"time": "2025-01-04T00:00:00",
"value": 10.682769775390625
},
{
"time": "2025-01-04T03:00:00",
"value": 10.562896728515625
},
{
"time": "2025-01-04T06:00:00",
"value": 10.225006103515625
},
{
"time": "2025-01-04T09:00:00",
"value": 10.914947509765625
},
{
"time": "2025-01-04T12:00:00",
"value": 11.296051025390625
},
{
"time": "2025-01-04T15:00:00",
"value": 10.217437744140625
},
{
"time": "2025-01-04T18:00:00",
"value": 8.537506103515625
},
{
"time": "2025-01-04T21:00:00",
"value": 7.460845947265625
},
{
"time": "2025-01-05T00:00:00",
"value": 6.343170166015625
},
{
"time": "2025-01-05T03:00:00",
"value": 5.995269775390625
},
{
"time": "2025-01-05T06:00:00",
"value": 6.114410400390625
},
{
"time": "2025-01-05T09:00:00",
"value": 6.575592041015625
},
{
"time": "2025-01-05T12:00:00",
"value": 7.698150634765625
},
{
"time": "2025-01-05T15:00:00",
"value": 7.824859619140625
},
{
"time": "2025-01-05T18:00:00",
"value": 7.553375244140625
},
{
"time": "2025-01-05T21:00:00",
"value": 7.653472900390625
}
],
"plot87": [
{
"time": "2025-01-01T00:00:00",
"value": 7.423492431640625
},
{
"time": "2025-01-01T03:00:00",
"value": 7.418609619140625
},
{
"time": "2025-01-01T06:00:00",
"value": 7.349761962890625
},
{
"time": "2025-01-01T09:00:00",
"value": 9.715728759765625
},
{
"time": "2025-01-01T12:00:00",
"value": 10.905670166015625
},
{
"time": "2025-01-01T15:00:00",
"value": 11.054107666015625
},
{
"time": "2025-01-01T18:00:00",
"value": 8.802398681640625
},
{
"time": "2025-01-01T21:00:00",
"value": 8.401763916015625
},
{
"time": "2025-01-02T00:00:00",
"value": 7.939361572265625
},
{
"time": "2025-01-02T03:00:00",
"value": 7.887359619140625
},
{
"time": "2025-01-02T06:00:00",
"value": 7.899566650390625
},
{
"time": "2025-01-02T09:00:00",
"value": 10.189361572265625
},
{
"time": "2025-01-02T12:00:00",
"value": 10.664215087890625
},
{
"time": "2025-01-02T15:00:00",
"value": 10.540191650390625
},
{
"time": "2025-01-02T18:00:00",
"value": 9.300933837890625
},
{
"time": "2025-01-02T21:00:00",
"value": 9.263092041015625
},
{
"time": "2025-01-03T00:00:00",
"value": 8.797027587890625
},
{
"time": "2025-01-03T03:00:00",
"value": 8.135162353515625
},
{
"time": "2025-01-03T06:00:00",
"value": 7.564117431640625
},
{
"time": "2025-01-03T09:00:00",
"value": 9.803619384765625
},
{
"time": "2025-01-03T12:00:00",
"value": 10.940582275390625
},
{
"time": "2025-01-03T15:00:00",
"value": 11.579498291015625
},
{
"time": "2025-01-03T18:00:00",
"value": 10.209869384765625
},
{
"time": "2025-01-03T21:00:00",
"value": 10.452056884765625
},
{
"time": "2025-01-04T00:00:00",
"value": 10.628082275390625
},
{
"time": "2025-01-04T03:00:00",
"value": 10.505523681640625
},
{
"time": "2025-01-04T06:00:00",
"value": 10.157867431640625
},
{
"time": "2025-01-04T09:00:00",
"value": 10.914459228515625
},
{
"time": "2025-01-04T12:00:00",
"value": 11.318023681640625
},
{
"time": "2025-01-04T15:00:00",
"value": 10.220611572265625
},
{
"time": "2025-01-04T18:00:00",
"value": 8.527984619140625
},
{
"time": "2025-01-04T21:00:00",
"value": 7.444732666015625
},
{
"time": "2025-01-05T00:00:00",
"value": 6.330230712890625
},
{
"time": "2025-01-05T03:00:00",
"value": 5.982574462890625
},
{
"time": "2025-01-05T06:00:00",
"value": 6.104888916015625
},
{
"time": "2025-01-05T09:00:00",
"value": 6.563629150390625
},
{
"time": "2025-01-05T12:00:00",
"value": 7.687896728515625
},
{
"time": "2025-01-05T15:00:00",
"value": 7.812896728515625
},
{
"time": "2025-01-05T18:00:00",
"value": 7.523345947265625
},
{
"time": "2025-01-05T21:00:00",
"value": 7.621490478515625
}
]
}
}
]
}
// Load Google Charts
google.charts.load('current', { packages: ['corechart', 'line'] });
google.charts.setOnLoadCallback(() => drawTimeSeriesChart(jsonData.panels));
// This part is not need for this fiddle, but in original script this is used for loading JSON data
/* async function fetchAndDrawCharts() {
try {
const response = await fetch('../db_data/google_dashboard/ketipis/dashboard.json');
const text = await response.text(); // Read as text first
// console.log("Raw JSON Response:", text); // Debug log
const data = JSON.parse(text); // Parse manually to catch errors
console.log("Parsed JSON:", data);
drawTimeSeriesChart(data.panels);
} catch (error) {
console.error("Error loading JSON:", error);
}
}
*/
function createChartContainer(id) {
// Select the container where you want to add the chart div
const container = document.querySelector(".dashboard-container");
if (!container) {
console.error("Dashboard container not found.");
return;
}
// Create a new div element
const chartDiv = document.createElement("div");
chartDiv.id = id; // Set ID
chartDiv.className = "chart"; // Set class
// Append the new div inside the container
container.appendChild(chartDiv);
}
function drawTimeSeriesChart(panels) {
if (!panels || panels.length === 0) {
console.error("No panels found in JSON data");
return;
}
panels.forEach(panel => {
let timeSeriesData = panel.data;
let chartData = new google.visualization.DataTable(); // Unique DataTable per chart
// Define columns (Time, Value per sensor, Tooltip)
chartData.addColumn({ type: 'date', label: 'Time' });
const plots = Object.keys(timeSeriesData);
plots.forEach(plot => chartData.addColumn({ type: 'number', label: plot }));
chartData.addColumn({ type: 'string', role: 'tooltip' });
const rows = [];
let originalData = [];
// Store the original data for reference
plots.forEach(plot => {
timeSeriesData[plot].forEach(entry => {
const dateObj = new Date(entry.time);
const date = entry.time.split("T")[0];
const time = entry.time.split("T")[1].slice(0,5);
const tooltipText = `${date} ${time} n${plot}: ${entry.value.toFixed(2)} ${panel.unit}`;
const row = [dateObj];
let rowValues = {};
plots.forEach(p => {
const value = (p === plot ? entry.value : null);
row.push(value);
rowValues[p] = value;
});
row.push(tooltipText); // Ensure tooltip is attached
rows.push(row);
originalData.push({ dateObj, rowValues, tooltipText });
});
});
chartData.addRows(rows);
// Track visibility of each series
let seriesVisibility = plots.reduce((obj, plot) => {
obj[plot] = true;
return obj;
}, {});
// Chart options
let options = {
title: panel.title,
curveType: 'function',
backgroundColor: '#1e1e1e',
titleTextStyle: { color: '#fff' },
legendTextStyle: { color: '#fff' },
hAxis: { textStyle: { color: '#fff' }, titleTextStyle: { color: '#fff' } },
vAxis: { textStyle: { color: '#fff' }, titleTextStyle: { color: '#fff' } },
legend: { position: 'top' },
tooltip: { isHtml: true },
explorer: { axis: 'horizontal', keepInBounds: true, maxZoomIn: 0.05 },
};
// Create separate chart container
createChartContainer(panel.table);
const chart = new google.visualization.LineChart(document.getElementById(panel.table));
// Function to toggle series visibility
function updateChart() {
let newData = new google.visualization.DataTable(); // Create fresh DataTable
newData.addColumn({ type: 'date', label: 'Time' });
plots.forEach(plot => newData.addColumn({ type: 'number', label: plot }));
newData.addColumn({ type: 'string', role: 'tooltip' });
let newRows = originalData.map(entry => {
let row = [entry.dateObj];
plots.forEach(plot => {
row.push(seriesVisibility[plot] ? entry.rowValues[plot] : null);
});
row.push(entry.tooltipText);
return row;
});
newData.addRows(newRows);
chart.draw(newData, options);
}
// Click event for legend toggling
google.visualization.events.addListener(chart, 'select', () => {
const selection = chart.getSelection();
if (selection.length > 0 && selection[0].column > 0) {
const seriesIndex = selection[0].column - 1;
const plotKey = plots[seriesIndex];
// Toggle visibility
seriesVisibility[plotKey] = !seriesVisibility[plotKey];
updateChart();
}
});
updateChart(); // Initial chart render
});
}
body {
background-color: #1e1e1e;
color: #fff;
font-family: Arial, sans-serif;
padding: 20px;
}
.dashboard-container {
display: flex;
flex-direction: column;
align-items: center;
}
.chart {
width: 900px;
height: 450px;
margin: 20px 0;
background-color: #2e2e2e;
padding: 10px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(255, 255, 255, 0.2);
}
<!DOCTYPE html>
<html>
<head>
<title>Dashboard</title>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
</head>
<body>
<h1>Sensor Dashboard</h1>
<div class="dashboard-container"></div>
</body>
</html>