feat: nearly there just need some better colors and make the graph more continuous

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
Kasper Juul Hermansen 2024-11-21 21:25:29 +01:00
parent d5d1a30a4c
commit e424fa2d03
Signed by: kjuulh
GPG Key ID: D85D7535F18F35FA

View File

@ -3,21 +3,35 @@
<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 Heatmap with Continuous Scrolling</title> <title>Live Heatmap with Intensity</title>
<script src="https://d3js.org/d3.v7.min.js"></script> <script src="https://d3js.org/d3.v7.min.js"></script>
<style> <style>
svg { svg {
display: block; display: block;
margin: auto; margin: auto;
} }
.y-axis {
display: none; .legend {
font-size: 12px;
}
.legend-item {
display: flex;
align-items: center;
margin-bottom: 5px;
}
.legend-color {
width: 15px;
height: 15px;
margin-right: 5px;
} }
</style> </style>
</head> </head>
<body> <body>
<h2>Live Heatmap with Continuous Scrolling</h2> <h2>Live Heatmap with Intensity</h2>
<div id="chart"></div> <div id="chart"></div>
<div id="legend" class="legend"></div>
<script> <script>
// Dimensions // Dimensions
@ -25,6 +39,33 @@
const width = 800 - margin.left - margin.right; const width = 800 - margin.left - margin.right;
const height = 500 - margin.top - margin.bottom; const height = 500 - margin.top - margin.bottom;
// Categories with labels
const categories = [
{ id: 0, label: "User Onboarded", color: "#1f77b4" },
{ id: 1, label: "Payment Accepted", color: "#ff7f0e" },
{ id: 2, label: "Payment Failed", color: "#d62728" },
{ id: 3, label: "Product Added to Cart", color: "#9467bd" },
{ id: 4, label: "Checkout Started", color: "#2ca02c" },
{ id: 5, label: "Order Placed", color: "#8c564b" },
{ id: 6, label: "Order Cancelled", color: "#e377c2" },
{ id: 7, label: "Refund Processed", color: "#7f7f7f" },
{ id: 8, label: "Subscription Renewed", color: "#bcbd22" },
{ id: 9, label: "Feedback Submitted", color: "#17becf" },
];
// Store last intensity for each category
const lastIntensity = new Map(categories.map(c => [c.id, Math.random()]));
// Create Legend
const legend = d3.select("#legend");
categories.forEach(category => {
const item = legend.append("div").attr("class", "legend-item");
item.append("div")
.attr("class", "legend-color")
.style("background-color", category.color);
item.append("span").text(category.label);
});
// Create SVG // Create SVG
const svg = d3.select("#chart") const svg = d3.select("#chart")
.append("svg") .append("svg")
@ -39,18 +80,10 @@
.range([height, 0]); .range([height, 0]);
const x = d3.scaleBand() const x = d3.scaleBand()
.domain(d3.range(10)) // Example: 10 categories .domain(categories.map(c => c.id)) // Category IDs
.range([0, width]); .range([0, width]);
// Heatmap color scale // Heatmap group
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"); const grid = svg.append("g");
// Initialize heatmap // Initialize heatmap
@ -64,9 +97,11 @@
.attr("width", x.bandwidth()) .attr("width", x.bandwidth())
.attr("y", d => y(new Date(d.timestamp))) .attr("y", d => y(new Date(d.timestamp)))
.attr("height", 5) // Fixed height for heatmap blocks .attr("height", 5) // Fixed height for heatmap blocks
.attr("fill", d => colorScale(d.value)) .attr("fill", d => categories[d.category].color)
.attr("opacity", d => d.intensity)
.merge(cells) // Merge updates .merge(cells) // Merge updates
.attr("y", d => y(new Date(d.timestamp))) .attr("y", d => y(new Date(d.timestamp)))
.attr("opacity", d => d.intensity)
.attr("visibility", d => .attr("visibility", d =>
y(new Date(d.timestamp)) >= 0 && y(new Date(d.timestamp)) <= height y(new Date(d.timestamp)) >= 0 && y(new Date(d.timestamp)) <= height
? "visible" ? "visible"
@ -80,15 +115,21 @@
// Real-time simulation // Real-time simulation
let allData = []; let allData = [];
const scrollingSpeed = 10; // Pixels per second const scrollingSpeed = 10; // Pixels per second
const pixelsPerMillisecond = scrollingSpeed / 1000;
function generateData() { function generateData() {
const newData = Array.from({ length: 10 }, (_, i) => ({ // Simulate sporadic events with intensity
id: `${Date.now()}-${i}`, const newData = categories.map(c => {
category: i, if (Math.random() < 0.5) return null; // 50% chance no data for this category
timestamp: Date.now(), const newIntensity = Math.random(); // Random intensity
value: Math.random() * 100, const smoothIntensity = d3.interpolate(lastIntensity.get(c.id), newIntensity)(0.5); // Smooth transition
})); lastIntensity.set(c.id, smoothIntensity); // Update last intensity
return {
id: `${Date.now()}-${c.id}`,
category: c.id,
timestamp: Date.now(),
intensity: smoothIntensity,
};
}).filter(Boolean); // Remove null values
// Append new data and remove older ones beyond the last 60 seconds // Append new data and remove older ones beyond the last 60 seconds
allData = [...allData, ...newData].filter(d => allData = [...allData, ...newData].filter(d =>
@ -116,9 +157,6 @@
// Update positions of all heatmap cells // Update positions of all heatmap cells
updateHeatmap(allData); updateHeatmap(allData);
// Smoothly shift axis
yAxis.call(d3.axisLeft(y).ticks(6));
}); });
</script> </script>
</body> </body>