127 lines
4.0 KiB
HTML
127 lines
4.0 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Live Heatmap with Continuous Scrolling</title>
|
|
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
<style>
|
|
svg {
|
|
display: block;
|
|
margin: auto;
|
|
}
|
|
.y-axis {
|
|
display: none;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h2>Live Heatmap with Continuous Scrolling</h2>
|
|
<div id="chart"></div>
|
|
|
|
<script>
|
|
// Dimensions
|
|
const margin = { top: 20, right: 20, bottom: 20, left: 50 };
|
|
const width = 800 - margin.left - margin.right;
|
|
const height = 500 - margin.top - margin.bottom;
|
|
|
|
// Create SVG
|
|
const svg = d3.select("#chart")
|
|
.append("svg")
|
|
.attr("width", width + margin.left + margin.right)
|
|
.attr("height", height + margin.top + margin.bottom)
|
|
.append("g")
|
|
.attr("transform", `translate(${margin.left},${margin.top})`);
|
|
|
|
// Time scales
|
|
const y = d3.scaleTime()
|
|
.domain([new Date(Date.now() - 60000), new Date()]) // Past 1 min to now
|
|
.range([height, 0]);
|
|
|
|
const x = d3.scaleBand()
|
|
.domain(d3.range(10)) // Example: 10 categories
|
|
.range([0, width]);
|
|
|
|
// Heatmap color scale
|
|
const colorScale = d3.scaleSequential(d3.interpolateYlOrRd)
|
|
.domain([0, 100]); // Adjust for event intensity
|
|
|
|
// Axis
|
|
const yAxis = svg.append("g")
|
|
.attr("class", "y-axis")
|
|
.call(d3.axisLeft(y).ticks(6));
|
|
|
|
const grid = svg.append("g");
|
|
|
|
// Initialize heatmap
|
|
function updateHeatmap(data) {
|
|
const cells = grid.selectAll("rect").data(data, d => d.id);
|
|
|
|
// Enter new data
|
|
cells.enter()
|
|
.append("rect")
|
|
.attr("x", d => x(d.category))
|
|
.attr("width", x.bandwidth())
|
|
.attr("y", d => y(new Date(d.timestamp)))
|
|
.attr("height", 5) // Fixed height for heatmap blocks
|
|
.attr("fill", d => colorScale(d.value))
|
|
.merge(cells) // Merge updates
|
|
.attr("y", d => y(new Date(d.timestamp)))
|
|
.attr("visibility", d =>
|
|
y(new Date(d.timestamp)) >= 0 && y(new Date(d.timestamp)) <= height
|
|
? "visible"
|
|
: "hidden"
|
|
);
|
|
|
|
// Remove old data that moves off the screen
|
|
cells.exit().remove();
|
|
}
|
|
|
|
// Real-time simulation
|
|
let allData = [];
|
|
const scrollingSpeed = 10; // Pixels per second
|
|
const pixelsPerMillisecond = scrollingSpeed / 1000;
|
|
|
|
function generateData() {
|
|
const newData = Array.from({ length: 10 }, (_, i) => ({
|
|
id: `${Date.now()}-${i}`,
|
|
category: i,
|
|
timestamp: Date.now(),
|
|
value: Math.random() * 100,
|
|
}));
|
|
|
|
// Append new data and remove older ones beyond the last 60 seconds
|
|
allData = [...allData, ...newData].filter(d =>
|
|
new Date(d.timestamp) >= new Date(Date.now() - 60000)
|
|
);
|
|
}
|
|
|
|
// Continuous scroll
|
|
let lastTimestamp = Date.now();
|
|
|
|
d3.timer(elapsed => {
|
|
const now = Date.now();
|
|
const delta = now - lastTimestamp;
|
|
|
|
// Generate data periodically (every 1 second)
|
|
if (delta >= 1000) {
|
|
generateData();
|
|
lastTimestamp = now;
|
|
}
|
|
|
|
// Update the domain of the y-axis
|
|
const currentTime = new Date();
|
|
const pastTime = new Date(currentTime.getTime() - 60000);
|
|
y.domain([pastTime, currentTime]);
|
|
|
|
// Update positions of all heatmap cells
|
|
updateHeatmap(allData);
|
|
|
|
// Smoothly shift axis
|
|
yAxis.call(d3.axisLeft(y).ticks(6));
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|