feat: with no fade continuous

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
Kasper Juul Hermansen 2024-11-21 21:08:35 +01:00
parent fa3cc87b9b
commit 23885cd9e4
Signed by: kjuulh
GPG Key ID: D85D7535F18F35FA

View File

@ -3,109 +3,108 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Live Timeline Heatmap</title> <title>Live Heatmap with Smooth Scrolling</title>
<script src="https://d3js.org/d3.v7.min.js"></script> <script src="https://d3js.org/d3.v7.min.js"></script>
<style> <style>
.heatmap rect { svg {
//stroke: #aaa; display: block;
margin: auto;
} }
</style> </style>
</head> </head>
<body> <body>
<h2>Live Timeline Heatmap</h2> <h2>Live Heatmap with Smooth Scrolling</h2>
<div id="chart"></div> <div id="chart"></div>
<script> <script>
// Configuration // Dimensions
const width = 500; const margin = { top: 20, right: 20, bottom: 20, left: 50 };
const height = 500; const width = 800 - margin.left - margin.right;
const numCols = 10; // Number of columns const height = 500 - margin.top - margin.bottom;
const numRows = 20; // Number of rows
const cellWidth = width / numCols; // Cell width
const cellHeight = height / numRows; // Cell height
const fadeOutRate = 0.8; // Intensity decay
const maxIntensity = 10; // Maximum intensity value
// Create SVG // Create SVG
const svg = d3.select("#chart") const svg = d3.select("#chart")
.append("svg") .append("svg")
.attr("width", width) .attr("width", width + margin.left + margin.right)
.attr("height", height) .attr("height", height + margin.top + margin.bottom)
.append("g") .append("g")
.attr("class", "heatmap"); .attr("transform", `translate(${margin.left},${margin.top})`);
// Initialize grid // Time scales
let grid = Array.from({ length: numRows }, () => const y = d3.scaleTime()
Array.from({ length: numCols }, () => 0) .domain([new Date(Date.now() - 60000), new Date()]) // Past 1 min to now
); .range([height, 0]);
// Color scale const x = d3.scaleBand()
const colorScale = d3.scaleSequential(d3.interpolateTurbo).domain([0, maxIntensity]); .domain(d3.range(10)) // Example: 10 categories
.range([0, width]);
// Create grid rectangles // Heatmap color scale
svg.selectAll("rect") const colorScale = d3.scaleSequential(d3.interpolateYlOrRd)
.data(grid.flat()) .domain([0, 100]); // Adjust for event intensity
.enter()
.append("rect")
.attr("x", (_, i) => (i % numCols) * cellWidth)
.attr("y", (_, i) => Math.floor(i / numCols) * cellHeight)
.attr("width", cellWidth)
.attr("height", cellHeight)
.attr("fill", colorScale(0));
// Function to update the heatmap // Axis
function updateHeatmap() { const yAxis = svg.append("g")
// Decay old values .attr("class", "y-axis")
for (let row of grid) { .call(d3.axisLeft(y).ticks(20));
for (let col = 0; col < row.length; col++) {
row[col] *= fadeOutRate;
}
}
// Update heatmap colors const grid = svg.append("g");
svg.selectAll("rect")
.data(grid.flat()) // 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
.transition() .transition()
.duration(200) .duration(500)
.attr("fill", d => colorScale(d)); .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"
); // Hide items out of bounds
// Scroll rows upward and add a new empty row at the bottom // Remove old data that moves off the screen
function scrollHeatmap() { cells.exit().remove();
grid.shift(); // Remove the top row
grid.push(Array.from({ length: numCols }, () => 0)); // Add an empty row at the bottom
// Update grid positions // Shift all cells upward to simulate scrolling
svg.selectAll("rect") grid.selectAll("rect")
.data(grid.flat())
.transition() .transition()
.duration(200) .duration(500)
.attr("y", (_, i) => Math.floor(i / numCols) * cellHeight); .attr("y", d => y(new Date(d.timestamp)));
} }
// Function to add events to the bottom row // Simulate real-time updates
function addEvent(col, intensity) { let allData = [];
if (col >= 0 && col < numCols) {
grid[grid.length - 1][col] = Math.min(
grid[grid.length - 1][col] + intensity,
maxIntensity
);
}
}
// Simulate incoming events
setInterval(() => { setInterval(() => {
const randomCol = Math.floor(Math.random() * numCols); // Generate random data
const randomIntensity = Math.random() * 8 + 2; // Random intensity (2-10) const newData = Array.from({ length: 10 }, (_, i) => ({
addEvent(randomCol, randomIntensity); id: `${Date.now()}-${i}`,
}, 300); // Events every 300ms category: i,
timestamp: Date.now(),
value: Math.random() * 100,
}));
// Animation loop // Add new data to the global array and keep only recent data
d3.interval(() => { allData = [...allData, ...newData].filter(d =>
scrollHeatmap(); new Date(d.timestamp) >= new Date(Date.now() - 60000)
updateHeatmap(); );
}, 500); // Updates every 500ms
// Update time scale domain
y.domain([new Date(Date.now() - 60000), new Date()]);
yAxis.transition().duration(500).call(d3.axisLeft(y).ticks(20));
// Update heatmap with filtered data
updateHeatmap(allData);
}, 1000);
</script> </script>
</body> </body>
</html> </html>