added d3
and @types/d3
dependencies for enhanced data visualization capabilities in UI development
Signed-off-by: Matthias Puchstein <matthias@puchstein.bayern>
This commit is contained in:
@@ -4,6 +4,240 @@ import HeroSection from '../../components/dreamarchive/HeroSection';
|
||||
import SectionHeader from '../../components/dreamarchive/SectionHeader';
|
||||
import DreamyCard from '../../components/dreamarchive/DreamyCard';
|
||||
import IconWithBackground from '../../components/dreamarchive/IconWithBackground';
|
||||
import {useEffect, useRef} from 'react';
|
||||
import * as d3 from 'd3';
|
||||
|
||||
// Dream types data with percentages and colors
|
||||
const dreamTypesData = [
|
||||
{type: "Angstträume", percentage: 34, color: "#ef4444", darkColor: "#dc2626", note: "Global durchschnittlich"},
|
||||
{type: "Soziale Träume", percentage: 28, color: "#3b82f6", darkColor: "#2563eb", note: "Kulturelle Variationen"},
|
||||
{type: "Sexuelle Träume", percentage: 12, color: "#ec4899", darkColor: "#db2777", note: "Altersabhängig"},
|
||||
{type: "Flugträume", percentage: 18, color: "#a855f7", darkColor: "#9333ea", note: "Persönlichkeitsabhängig"},
|
||||
{type: "Verfolgungsträume", percentage: 8, color: "#f97316", darkColor: "#ea580c", note: "Stresskorreliert"}
|
||||
];
|
||||
|
||||
// D3.js Pie Chart Component
|
||||
function D3PieChart() {
|
||||
const svgRef = useRef<SVGSVGElement>(null);
|
||||
const tooltipRef = useRef<HTMLDivElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
|
||||
useEffect(() => {
|
||||
if (!svgRef.current || !tooltipRef.current || !containerRef.current) return;
|
||||
|
||||
// Clear any existing SVG content
|
||||
d3.select(svgRef.current).selectAll("*").remove();
|
||||
|
||||
// Set up dimensions
|
||||
const width = svgRef.current.clientWidth;
|
||||
const height = svgRef.current.clientHeight;
|
||||
const margin = 10;
|
||||
const radius = Math.min(width, height) / 2 - margin;
|
||||
|
||||
// Create SVG
|
||||
const svg = d3.select(svgRef.current)
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.append("g")
|
||||
.attr("transform", `translate(${width / 2}, ${height / 2})`);
|
||||
|
||||
// Create pie generator
|
||||
const pie = d3.pie<typeof dreamTypesData[0]>()
|
||||
.value(d => d.percentage)
|
||||
.sort(null);
|
||||
|
||||
// Create arc generator
|
||||
const arc = d3.arc<d3.PieArcDatum<typeof dreamTypesData[0]>>()
|
||||
.innerRadius(0)
|
||||
.outerRadius(radius);
|
||||
|
||||
// Get tooltip element
|
||||
const tooltip = d3.select(tooltipRef.current);
|
||||
|
||||
// Function to show tooltip
|
||||
const showTooltip = (event: MouseEvent | TouchEvent, d: d3.PieArcDatum<typeof dreamTypesData[0]>) => {
|
||||
const data = d.data;
|
||||
|
||||
// Set tooltip content
|
||||
tooltip.html(`
|
||||
<div class="font-bold">${data.type}</div>
|
||||
<div>${data.percentage}%</div>
|
||||
<div class="text-xs opacity-75">${data.note}</div>
|
||||
`);
|
||||
|
||||
// Make tooltip visible
|
||||
tooltip
|
||||
.style("opacity", 1)
|
||||
.style("visibility", "visible");
|
||||
|
||||
// Position tooltip
|
||||
const containerRect = containerRef.current!.getBoundingClientRect();
|
||||
let x, y;
|
||||
|
||||
if (event instanceof MouseEvent) {
|
||||
x = event.clientX - containerRect.left;
|
||||
y = event.clientY - containerRect.top;
|
||||
} else {
|
||||
// TouchEvent
|
||||
const touch = (event as TouchEvent).touches[0];
|
||||
x = touch.clientX - containerRect.left;
|
||||
y = touch.clientY - containerRect.top;
|
||||
}
|
||||
|
||||
// Adjust position to avoid going off the container
|
||||
const tooltipRect = tooltipRef.current!.getBoundingClientRect();
|
||||
if (x + tooltipRect.width > containerRect.width) {
|
||||
x = x - tooltipRect.width;
|
||||
}
|
||||
if (y + tooltipRect.height > containerRect.height) {
|
||||
y = y - tooltipRect.height;
|
||||
}
|
||||
|
||||
tooltip
|
||||
.style("left", `${x + 10}px`)
|
||||
.style("top", `${y + 10}px`);
|
||||
};
|
||||
|
||||
// Function to hide tooltip
|
||||
const hideTooltip = () => {
|
||||
tooltip
|
||||
.style("opacity", 0)
|
||||
.style("visibility", "hidden");
|
||||
};
|
||||
|
||||
// Create pie chart with event handlers
|
||||
svg.selectAll("path")
|
||||
.data(pie(dreamTypesData))
|
||||
.enter()
|
||||
.append("path")
|
||||
.attr("d", arc)
|
||||
.attr("fill", d => isDarkMode ? d.data.darkColor : d.data.color)
|
||||
.attr("stroke", "white")
|
||||
.style("stroke-width", "1px")
|
||||
.style("cursor", "pointer")
|
||||
// Desktop hover events
|
||||
.on("mouseover", function (event, d) {
|
||||
// Calculate centroid for this arc to ensure zoom is centered properly
|
||||
const centroid = arc.centroid(d);
|
||||
const x = centroid[0];
|
||||
const y = centroid[1];
|
||||
|
||||
d3.select(this).transition().duration(200)
|
||||
.attr("opacity", 0.8)
|
||||
.attr("stroke-width", "2px")
|
||||
// Apply zoom transformation centered on the arc's centroid
|
||||
.attr("transform", `translate(${x},${y}) scale(1.05) translate(${-x},${-y})`);
|
||||
showTooltip(event, d);
|
||||
})
|
||||
.on("mouseout", function () {
|
||||
d3.select(this).transition().duration(200)
|
||||
.attr("opacity", 1)
|
||||
.attr("stroke-width", "1px")
|
||||
.attr("transform", null); // Completely remove the transform to reset zoom
|
||||
hideTooltip();
|
||||
})
|
||||
// Mobile/touch events
|
||||
.on("touchstart", function (event, d) {
|
||||
// Stop propagation to prevent document touchstart from firing
|
||||
event.stopPropagation();
|
||||
// Prevent default to avoid any browser handling
|
||||
event.preventDefault();
|
||||
|
||||
// Store a reference to the element without using 'this'
|
||||
const element = event.currentTarget;
|
||||
|
||||
// Calculate centroid for this arc to ensure zoom is centered properly
|
||||
const centroid = arc.centroid(d);
|
||||
const x = centroid[0];
|
||||
const y = centroid[1];
|
||||
|
||||
// Visual feedback
|
||||
d3.select(element).transition().duration(200)
|
||||
.attr("opacity", 0.8)
|
||||
.attr("stroke-width", "2px")
|
||||
// Apply zoom transformation centered on the arc's centroid
|
||||
.attr("transform", `translate(${x},${y}) scale(1.05) translate(${-x},${-y})`);
|
||||
|
||||
// Show tooltip
|
||||
showTooltip(event, d);
|
||||
|
||||
// Add a temporary touchend handler to reset the visual state
|
||||
// but keep the tooltip visible
|
||||
document.addEventListener("touchend", function () {
|
||||
d3.select(element).transition().duration(200)
|
||||
.attr("opacity", 1)
|
||||
.attr("stroke-width", "1px")
|
||||
.attr("transform", null); // Completely remove the transform to reset zoom
|
||||
}, {once: true});
|
||||
})
|
||||
.on("click", function (event, d) {
|
||||
event.stopPropagation();
|
||||
showTooltip(event, d);
|
||||
});
|
||||
|
||||
// Add center circle
|
||||
svg.append("circle")
|
||||
.attr("cx", 0)
|
||||
.attr("cy", 0)
|
||||
.attr("r", radius * 0.4)
|
||||
.attr("fill", isDarkMode ? "#1f2937" : "#ffffff")
|
||||
.attr("stroke", isDarkMode ? "#374151" : "#e5e7eb")
|
||||
.style("stroke-width", "1px");
|
||||
|
||||
// Add center text
|
||||
svg.append("text")
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("dy", "0.35em")
|
||||
.attr("class", "text-sm font-bold")
|
||||
.attr("fill", isDarkMode ? "#e5e7eb" : "#1f2937")
|
||||
.text("100%");
|
||||
|
||||
// Create a function to handle document touchstart that doesn't immediately hide the tooltip
|
||||
const handleDocumentTouchStart = (event: TouchEvent) => {
|
||||
// Only hide the tooltip if the touch is outside the pie chart segments
|
||||
const paths = svg.selectAll("path").nodes();
|
||||
const touchTarget = document.elementFromPoint(
|
||||
event.touches[0].clientX,
|
||||
event.touches[0].clientY
|
||||
);
|
||||
|
||||
// Don't hide if touching a pie segment
|
||||
if (paths.some(path => path === touchTarget)) {
|
||||
return;
|
||||
}
|
||||
|
||||
hideTooltip();
|
||||
};
|
||||
|
||||
// Add click event to document to hide tooltip when clicking outside
|
||||
document.addEventListener("click", hideTooltip);
|
||||
document.addEventListener("touchstart", handleDocumentTouchStart);
|
||||
|
||||
// Cleanup event listeners on unmount
|
||||
return () => {
|
||||
document.removeEventListener("click", hideTooltip);
|
||||
document.removeEventListener("touchstart", handleDocumentTouchStart);
|
||||
};
|
||||
}, [isDarkMode]);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="relative w-full aspect-square max-w-xs mx-auto">
|
||||
<svg ref={svgRef} className="w-full h-full"></svg>
|
||||
<div
|
||||
ref={tooltipRef}
|
||||
className="absolute pointer-events-none bg-white dark:bg-gray-800 p-2 rounded shadow-lg text-sm z-10 transition-opacity duration-200"
|
||||
style={{
|
||||
opacity: 0,
|
||||
visibility: 'hidden',
|
||||
maxWidth: '150px',
|
||||
border: '1px solid',
|
||||
borderColor: isDarkMode ? 'rgba(75, 85, 99, 0.5)' : 'rgba(229, 231, 235, 0.5)'
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Technology() {
|
||||
return (<div className="p-4 pt-24 pb-20 max-w-6xl mx-auto relative overflow-hidden">
|
||||
@@ -116,42 +350,8 @@ export default function Technology() {
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
||||
<div>
|
||||
{/* Pie Chart Visualization (Mock) */}
|
||||
<div className="relative w-full aspect-square max-w-xs mx-auto">
|
||||
<div className="absolute inset-0 rounded-full bg-gray-200 dark:bg-gray-700"></div>
|
||||
|
||||
{/* Angstträume: 34% */}
|
||||
<div className="absolute inset-0 rounded-full bg-red-500 dark:bg-red-600" style={{
|
||||
clipPath: 'polygon(50% 50%, 50% 0%, 100% 0%, 100% 50%, 75% 75%)'
|
||||
}}></div>
|
||||
|
||||
{/* Soziale Träume: 28% */}
|
||||
<div className="absolute inset-0 rounded-full bg-blue-500 dark:bg-blue-600" style={{
|
||||
clipPath: 'polygon(50% 50%, 75% 75%, 50% 100%, 0% 100%, 0% 50%)'
|
||||
}}></div>
|
||||
|
||||
{/* Sexuelle Träume: 12% */}
|
||||
<div className="absolute inset-0 rounded-full bg-pink-500 dark:bg-pink-600" style={{
|
||||
clipPath: 'polygon(50% 50%, 0% 50%, 0% 0%, 25% 0%)'
|
||||
}}></div>
|
||||
|
||||
{/* Flugträume: 18% */}
|
||||
<div className="absolute inset-0 rounded-full bg-purple-500 dark:bg-purple-600" style={{
|
||||
clipPath: 'polygon(50% 50%, 25% 0%, 50% 0%)'
|
||||
}}></div>
|
||||
|
||||
{/* Verfolgungsträume: 8% */}
|
||||
<div className="absolute inset-0 rounded-full bg-orange-500 dark:bg-orange-600" style={{
|
||||
clipPath: 'polygon(50% 50%, 50% 100%, 25% 100%)'
|
||||
}}></div>
|
||||
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div
|
||||
className="w-16 h-16 rounded-full bg-white dark:bg-gray-900 flex items-center justify-center text-sm font-bold">
|
||||
100%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* D3.js Pie Chart */}
|
||||
<D3PieChart/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
Reference in New Issue
Block a user