mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 13:52:16 +01:00
Improves the dashboard with additional metrics and visualizations to provide a more comprehensive overview of application usage and performance. Adds new charts, including: - Word cloud for category analysis - Geographic map for user distribution (simulated data) - Response time distribution chart Refactors existing components for improved clarity and reusability, including the introduction of a generic `MetricCard` component. Improves error handling and user feedback during data refresh and session loading. Adds recommended VSCode extensions for ESLint and Prettier.
119 lines
2.9 KiB
TypeScript
119 lines
2.9 KiB
TypeScript
"use client";
|
|
|
|
import { useRef, useEffect, useState } from "react";
|
|
import { select } from "d3-selection";
|
|
import cloud from "d3-cloud";
|
|
|
|
interface CloudWord {
|
|
text: string;
|
|
size: number;
|
|
x?: number;
|
|
y?: number;
|
|
rotate?: number;
|
|
}
|
|
|
|
interface WordCloudProps {
|
|
words: {
|
|
text: string;
|
|
value: number;
|
|
}[];
|
|
width?: number;
|
|
height?: number;
|
|
}
|
|
|
|
export default function WordCloud({
|
|
words,
|
|
width = 500,
|
|
height = 300,
|
|
}: WordCloudProps) {
|
|
const svgRef = useRef<SVGSVGElement | null>(null);
|
|
const [isClient, setIsClient] = useState(false);
|
|
|
|
useEffect(() => {
|
|
setIsClient(true);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!svgRef.current || !isClient || !words.length) return;
|
|
|
|
const svg = select(svgRef.current);
|
|
svg.selectAll("*").remove(); // Clear previous cloud
|
|
|
|
// Find the max value for proper scaling
|
|
const maxValue = Math.max(...words.map((w) => w.value || 1));
|
|
|
|
// Configure the layout
|
|
const layout = cloud()
|
|
.size([width, height])
|
|
.words(
|
|
words.map((d) => ({
|
|
text: d.text,
|
|
size: 10 + (d.value * 90) / maxValue, // Scale from 10 to 100 based on value
|
|
}))
|
|
)
|
|
.padding(5)
|
|
.rotate(() => (~~(Math.random() * 6) - 3) * 15) // Rotate between -45 and 45 degrees
|
|
.fontSize((d) => (d as any).size)
|
|
.on("end", draw);
|
|
|
|
layout.start();
|
|
|
|
function draw(words: CloudWord[]) {
|
|
svg
|
|
.append("g")
|
|
.attr("transform", `translate(${width / 2},${height / 2})`)
|
|
.selectAll("text")
|
|
.data(words)
|
|
.enter()
|
|
.append("text")
|
|
.style("font-size", (d: CloudWord) => `${d.size}px`)
|
|
.style("font-family", "Inter, Arial, sans-serif")
|
|
.style("fill", () => {
|
|
// Create a nice gradient of colors
|
|
const colors = [
|
|
"#4299E1", // blue-500
|
|
"#3182CE", // blue-600
|
|
"#2B6CB0", // blue-700
|
|
"#63B3ED", // blue-400
|
|
"#90CDF4", // blue-300
|
|
"#38B2AC", // teal-500
|
|
"#4FD1C5", // teal-400
|
|
];
|
|
return colors[Math.floor(Math.random() * colors.length)];
|
|
})
|
|
.style("cursor", "pointer")
|
|
.attr("text-anchor", "middle")
|
|
.attr(
|
|
"transform",
|
|
(d: CloudWord) =>
|
|
`translate(${d.x || 0},${d.y || 0}) rotate(${d.rotate || 0})`
|
|
)
|
|
.text((d: CloudWord) => d.text);
|
|
}
|
|
|
|
// Cleanup function
|
|
return () => {
|
|
svg.selectAll("*").remove();
|
|
};
|
|
}, [words, width, height, isClient]);
|
|
|
|
if (!isClient) {
|
|
return (
|
|
<div className="w-full h-full bg-white flex items-center justify-center">
|
|
<span className="text-gray-500">Loading word cloud...</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="flex justify-center w-full h-full">
|
|
<svg
|
|
ref={svgRef}
|
|
width={width}
|
|
height={height}
|
|
aria-label="Word cloud visualization of categories"
|
|
/>
|
|
</div>
|
|
);
|
|
}
|