import React, { useState, useMemo, useRef } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { ArrowLeftIcon, LayoutIcon, DatabaseIcon, PlusIcon, TrashIcon, SlidersIcon, TrendUpIcon, FileSvgIcon, } from '@phosphor-icons/react'; import { Link } from 'react-router-dom'; import domtoimage from 'dom-to-image-more'; import Seo from '../../components/Seo'; import CustomDropdown from '../../components/CustomDropdown'; import CustomSlider from '../../components/CustomSlider'; import CustomColorPicker from '../../components/CustomColorPicker'; import CustomToggle from '../../components/CustomToggle'; import BreadcrumbTitle from '../../components/BreadcrumbTitle'; import { useToast } from '../../hooks/useToast'; const CHART_TYPES = [ { label: 'Box Plot (Statistical)', value: 'box' }, { label: 'Frequency Dots', value: 'dot' }, { label: 'Brutalist Bar', value: 'bar' }, { label: 'Metric Signal (Line)', value: 'line' }, { label: 'Pie Chart', value: 'pie' }, ]; const INITIAL_DATA = [ { label: 'Horror', value: 6.2, min: 4.5, q1: 5.8, median: 6.2, q3: 6.8, max: 8.5, color: '#f87171', }, { label: 'Sci-Fi', value: 7.1, min: 5.2, q1: 6.5, median: 7.1, q3: 7.8, max: 9.2, color: '#3b82f6', }, { label: 'Drama', value: 7.8, min: 6.0, q1: 7.2, median: 7.8, q3: 8.4, max: 9.8, color: '#10b981', }, ]; // --- Visualizers --- const BauhausGrid = () => (
); const BoxPlotVis = ({ data, params }) => { const allValues = data.flatMap((d) => [d.min, d.max]); const gMin = Math.min(...allValues) * 0.9; const gMax = Math.max(...allValues) * 1.1; const gRange = gMax - gMin; const mapY = (val) => 360 - ((val - gMin) / (gRange || 1)) * 320; const boxWidth = params.boxWidth || 40; return ( ); }; const DotPlotVis = ({ data, params }) => { const points = useMemo(() => { return data.flatMap((item, idx) => { const count = Math.floor(item.value * params.density); const spacing = 400 / (params.density * 10); const centerX = 400 + (idx - (data.length - 1) / 2) * (600 / (data.length || 1)); return Array.from({ length: count }, (_, i) => ({ id: `${idx}-${i}`, x: centerX + (params.jitter ? (Math.random() - 0.5) * 20 : 0), y: 320 - i * spacing, color: item.color, })); }); }, [data, params.density, params.jitter]); return ( ); }; const BarChartVis = ({ data }) => { const max = Math.max(...data.map((d) => d.value)); return ( ); }; const LineGraphVis = ({ data, params }) => { const max = Math.max(...data.map((d) => d.value)); const points = data.map((d, i) => ({ x: 400 + (i - (data.length - 1) / 2) * (600 / (data.length - 1 || 1)), y: 320 - (d.value / (max || 1)) * 200, color: d.color, })); const pathD = points.length > 0 ? `M ${points.map((p) => `${p.x},${p.y}`).join(' L ')}` : ''; const areaD = points.length > 0 ? `${pathD} L ${points[points.length - 1].x},320 L ${points[0].x},320 Z` : ''; return ( ); }; const PieChartVis = ({ data, params }) => { const total = data.reduce((acc, curr) => acc + curr.value, 0); let currentAngle = 0; return ( ); }; // --- Main Component --- const DataPrismPage = () => { const [data, setData] = useState(INITIAL_DATA); const [chartType, setChartType] = useState('box'); const [isPanelOpen, setIsPanelOpen] = useState(true); const visContainerRef = useRef(null); const { addToast } = useToast(); const [visualParams, setVisualParams] = useState({ boxWidth: 40, iqrMultiplier: 1.2, showOutliers: true, density: 8, jitter: false, showArea: true, showLabels: false, showValues: false, metricLineColor: '#ffffff', metricAreaColor: '#ffffff', }); const updateNodeField = (index, field, value) => { const newData = [...data]; newData[index][field] = field === 'label' || field === 'color' ? value : parseFloat(value) || 0; setData(newData); }; const handleAddNode = () => { const colors = [ '#f87171', '#3b82f6', '#10b981', '#fb923c', '#ffffff', '#a78bfa', '#ec4899', ]; setData([ ...data, { label: 'New Node', value: 5.0, min: 2, q1: 4, median: 5, q3: 7, max: 9, color: colors[data.length % colors.length], }, ]); }; const handleRemoveNode = (index) => { if (data.length <= 1) return; setData(data.filter((_, i) => i !== index)); }; const handleExportSVG = () => { if (!visContainerRef.current) return; domtoimage .toSvg(visContainerRef.current, { bgcolor: '#050505', width: 1600, height: 800, style: { display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '40px', }, }) .then((dataUrl) => { const link = document.createElement('a'); link.download = `fezcodex-prism-${chartType}.svg`; link.href = dataUrl; link.click(); addToast({ title: 'EXPORT_SVG', message: 'Vector artifact saved.', type: 'success', }); }) .catch((error) => { console.error('SVG Export Failed:', error); addToast({ title: 'EXPORT_ERROR', message: 'SVG generation failed.', type: 'error', }); }); }; return (