<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Space-Themed Heatmap</title>
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <style>
        html, body {
            background-color: #0B0C10;
            color: #E5E5E5;
            font-family: Arial, sans-serif;
        }

        svg {
            display: block;
            margin: auto;
        }

        #title {
            padding: 1rem 2rem;
        }

        .divider {
            height: 1px;
            width: 80%;

            background-color: #E5E5E5;
        }

        .legend {
            font-size: 12px;
            display: flex;
            justify-content: center;
            flex-wrap: wrap;
        }

        .legend-item {
            display: flex;
            align-items: center;
            margin: 5px;
        }

        .legend-color {
            width: 15px;
            height: 15px;
            margin-right: 5px;
        }
    </style>
</head>
<body>
    <h2 id="title">Live Activity</h2>
    
    <div id="chart"></div>
    <div id="legend" class="legend"></div>

    <script>
        const horizontal = true;
        const categoryAmount = 10;

        const parentWidth = document.querySelector('#chart').parentElement.offsetWidth;

        // Dimensions
        const margin = { top: 20, right: 20, bottom: 20, left: 50 };
        //const width = 1200 - margin.left - margin.right;
        const width = parentWidth - margin.left - margin.right;
        const blockLength = width / (60 * 2); // 60 seconds * 2 poll rate pr second
        const height = 75 * categoryAmount / 2 - margin.top - margin.bottom;

        // Categories with labels
        const categories = [
            { id: 0, label: "User Onboarded", color: "#D90429" }, // NASA Red
            { id: 1, label: "Payment Accepted", color: "#0E79B2" }, // Space Blue
            { id: 2, label: "Payment Failed", color: "#F46036" }, // Fuel Orange
            { id: 3, label: "Product Added to Cart", color: "#F2C94C" }, // Solar Gold
            { id: 4, label: "Checkout Started", color: "#9CA3AF" }, // Neutral Gray
            { id: 5, label: "Order Placed", color: "#8B4513" },
            { id: 6, label: "Order Cancelled", color: "#FF69B4" },
            { id: 7, label: "Refund Processed", color: "#A9A9A9" },
            { id: 8, label: "Subscription Renewed", color: "#BDB76B" },
            { id: 9, label: "Feedback Submitted", color: "#00FFFF" },
        ].filter((c) => c.id < categoryAmount);

        // 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
        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})`);

        const defs = svg.append("defs");
        defs.append("filter")
            .attr("id", "blend-multiply")
            .append("feBlend")
            .attr("mode", "multiply")
            .attr("in", "SourceGraphic")
            .attr("in2", "BackgroundImage");

        // Time scales
        var x, y;
        if (horizontal) {
            x = d3.scaleTime()
                .domain([new Date(Date.now() - 60000), new Date()]) // Past 1 min to now
                .range([width, 0]);

            y = d3.scaleBand()
                .domain(categories.map(c => c.id)) // Category IDs
                .range([0, height]);
        } else {
            y = d3.scaleTime()
                .domain([new Date(Date.now() - 60000), new Date()]) // Past 1 min to now
                .range([height, 0]);

            x = d3.scaleBand()
                .domain(categories.map(c => c.id)) // Category IDs
                .range([0, width]);
        }

        

        // Heatmap group
        const grid = svg.append("g");

        // Initialize heatmap
        function updateHeatmap(data) {
            const cells = grid.selectAll("rect").data(data, d => d.id);

            if (horizontal) {
                cells.enter()
                    .append("rect")
                    .attr("y", d => y(d.category))
                    //.attr("width", 12)
                    .attr("width", blockLength)
                    .attr("x", d => x(new Date(d.timestamp)))
                    .attr("height", 25) // Minimized spacing between blocks
                    .attr("fill", d => categories[d.category].color)
                    .attr("opacity", d => d.intensity)
                    //.attr("filter", "url(#blend-multiply)") // Apply blend mode
                    .merge(cells) // Merge updates
                    .attr("x", d => x(new Date(d.timestamp)))
                    .attr("opacity", d => d.intensity)
                    .attr("visibility", d =>
                        x(new Date(d.timestamp)) >= 0 && x(new Date(d.timestamp)) <= width
                            ? "visible"
                            : "hidden"
                    );
            } else {
                // 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", 4) // Minimized spacing between blocks
                    .attr("fill", d => categories[d.category].color)
                    .attr("opacity", d => d.intensity)
                    .merge(cells) // Merge updates
                    .attr("y", d => y(new Date(d.timestamp)))
                    .attr("opacity", d => d.intensity)
                    .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 = 20; // Pixels per second

        function generateData() {
            // Simulate sporadic events with intensity
            const newData = categories.map(c => {
                if (Math.random() < 0.7) return null; // 70% chance no data for this category
                const newIntensity = Math.random(); // Random intensity
                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
            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 500ms for more activity)
            if (delta >= 500) {
                generateData();
                lastTimestamp = now;
            }

            // Update the domain of the y-axis
            const currentTime = new Date();
            const pastTime = new Date(currentTime.getTime() - 60000);
            if (horizontal) {
                x.domain([pastTime, currentTime]);
            } else {
                y.domain([pastTime, currentTime]);
            }

            // Update positions of all heatmap cells
            updateHeatmap(allData);
        });
    </script>
</body>
</html>