feat: with no fade continuous
Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
parent
fa3cc87b9b
commit
23885cd9e4
151
index.html
151
index.html
@ -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>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user