diff --git a/src/data/MockDreams.ts b/src/data/MockDreams.ts index 9152a2c..fcdad42 100644 --- a/src/data/MockDreams.ts +++ b/src/data/MockDreams.ts @@ -38,12 +38,12 @@ export const mockDreams: Dream[] = [ input: { inputType: 'audio', transcript: 'Ich besuchte ein Konzert unter dem Ozean. Die Musiker waren Delfine, die Melodien anklickten, während Korallenpolypen im Takt zum Rhythmus mit Licht pulsierten.', - audio: '02.mp3', - + audio: 'in_02.mp3' } as AudioInput, ai: { interpretation: 'Das Unterwasserkonzert kann als Ausdruck von Kreativität und Harmonie gedeutet werden. Delfine symbolisieren Intelligenz und Spielfreude, Korallenlicht verweist auf emotionale Schwingungen und die Verbindung zum Inneren.', image: '02.png', + audio: '02.mp3', video: '02.mp4' } }), @@ -64,7 +64,8 @@ export const mockDreams: Dream[] = [ ai: { interpretation: 'Der Wüstenzug steht für eine Reise durch unbewusste Räume und persönliche Herausforderungen (Dünen). Der violette Himmel deutet auf Spiritualität hin, während die Laternen der Kamele Hoffnung und Wegweisung symbolisieren.', image: '03.png', - video: '03.mp4' + video: '03.mp4', + audio: '03.mp3' } }), @@ -78,11 +79,12 @@ export const mockDreams: Dream[] = [ input: { inputType: 'audio', transcript: 'Ich saß mit Freunden auf einer Wolke zu einer Teeparty. Jede Tasse war mit Sternenstaub gefüllt, und der Himmel um uns herum schimmerte in pastelligen Regenbogenfarben.', - audio: '04.mp3', + audio: 'in_04.mp3', } as AudioInput, ai: { interpretation: 'Diese Szene steht für Geborgenheit und Gemeinschaft auf einer höheren Ebene. Der Sternenstaub in den Tassen symbolisiert geteilte Träume, die pastelligen Regenbögen zeigen eine optimistische Grundstimmung und Leichtigkeit.', - image: '04.png' + image: '04.png', + audio: '04.mp3', } }), @@ -190,7 +192,10 @@ export const mockDreams: Dream[] = [ ] } as ChipInput, ai: { - interpretation: 'Das neuronale Netzwerk spiegelt dein Interesse an künstlicher Intelligenz und dem menschlichen Bewusstsein wider. Die Fähigkeit, Gedanken zu formen, deutet auf deinen Wunsch nach Kontrolle über deine mentalen Prozesse hin.' + interpretation: 'Das neuronale Netzwerk spiegelt dein Interesse an künstlicher Intelligenz und dem menschlichen Bewusstsein wider. Die Fähigkeit, Gedanken zu formen, deutet auf deinen Wunsch nach Kontrolle über deine mentalen Prozesse hin.', + audio: '10.mp3', + image: '10.png', + video: '10.mp4' } }), diff --git a/src/pages/DreamPage.tsx b/src/pages/DreamPage.tsx index 5fcdbbb..a5a80bb 100644 --- a/src/pages/DreamPage.tsx +++ b/src/pages/DreamPage.tsx @@ -9,14 +9,325 @@ import {formatDateNumeric, formatDateWithTime} from '../utils/DateUtils'; import Slider from 'react-slick'; import 'slick-carousel/slick/slick.css'; import 'slick-carousel/slick/slick-theme.css'; +import {useEffect, useRef} from 'react'; +import * as d3 from 'd3'; export default function DreamPage() { const {id} = useParams<{ id: string }>(); const navigate: NavigateFunction = useNavigate(); + const eegChartRef = useRef(null); + const vitalsChartRef = useRef(null); + const bewegungChartRef = useRef(null); const dream: Dream | undefined = mockDreams.find(d => d.id === Number(id)); const user: User | undefined = dream ? MockUsers.find(u => u.id === dream.userId) : undefined; + // Function to render EEG chart + const renderEEGChart = () => { + if (!dream || dream.input.inputType !== 'chip' || !eegChartRef.current) return; + + const chipInput = dream.input; + const eegData = [ + {name: 'Alpha', values: chipInput.eeg.alpha}, + {name: 'Beta', values: chipInput.eeg.beta}, + {name: 'Theta', values: chipInput.eeg.theta}, + {name: 'Delta', values: chipInput.eeg.delta} + ]; + + // Clear previous chart + d3.select(eegChartRef.current).selectAll('*').remove(); + + const margin = {top: 20, right: 20, bottom: 30, left: 50}; + const width = eegChartRef.current.clientWidth - margin.left - margin.right; + const height = eegChartRef.current.clientHeight - margin.top - margin.bottom; + + const svg = d3.select(eegChartRef.current) + .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})`); + + // X scale + const x = d3.scaleLinear() + .domain([0, chipInput.eeg.alpha.length - 1]) + .range([0, width]); + + // Y scale + const y = d3.scaleLinear() + .domain([0, d3.max(eegData.flatMap(d => d.values)) || 50]) + .range([height, 0]); + + // Line generator + const line = d3.line() + .x((_d, i) => x(i)) + .y(d => y(d)) + .curve(d3.curveMonotoneX); + + // Color scale + const color = d3.scaleOrdinal() + .domain(eegData.map(d => d.name)) + .range(['#8884d8', '#82ca9d', '#ffc658', '#ff8042']); + + // Add X axis + svg.append('g') + .attr('transform', `translate(0,${height})`) + .call(d3.axisBottom(x)) + .selectAll('text') + .style('fill', 'var(--text)'); + + // Add Y axis + svg.append('g') + .call(d3.axisLeft(y)) + .selectAll('text') + .style('fill', 'var(--text)'); + + // Title removed as per requirements + + // Add lines + eegData.forEach(d => { + svg.append('path') + .datum(d.values) + .attr('fill', 'none') + .attr('stroke', color(d.name)) + .attr('stroke-width', 1.5) + .attr('d', line); + }); + + // Add legend at the top right + const legendItemHeight = 20; // Height allocated for each legend item + + const legend = svg.append('g') + .attr('font-family', 'sans-serif') + .attr('font-size', 10) + .attr('text-anchor', 'end') + .selectAll('g') + .data(eegData) + .enter().append('g') + .attr('transform', (_d, i) => `translate(${width},${i * legendItemHeight + 10})`); + + legend.append('rect') + .attr('x', -15) + .attr('width', 15) + .attr('height', 15) + .attr('fill', d => color(d.name)); + + legend.append('text') + .attr('x', -20) + .attr('y', 7.5) + .attr('dy', '0.32em') + .style('fill', 'var(--text)') + .text(d => d.name); + }; + + // Function to render vitals chart + const renderVitalsChart = () => { + if (!dream || dream.input.inputType !== 'chip' || !vitalsChartRef.current) return; + + const chipInput = dream.input; + const vitalsData = [ + {name: 'Puls', values: chipInput.puls}, + {name: 'HRV', values: chipInput.hrv} + ]; + + // Clear previous chart + d3.select(vitalsChartRef.current).selectAll('*').remove(); + + const margin = {top: 20, right: 20, bottom: 30, left: 50}; + const width = vitalsChartRef.current.clientWidth - margin.left - margin.right; + const height = vitalsChartRef.current.clientHeight - margin.top - margin.bottom; + + const svg = d3.select(vitalsChartRef.current) + .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})`); + + // X scale + const x = d3.scaleLinear() + .domain([0, chipInput.puls.length - 1]) + .range([0, width]); + + // Y scale + const minValue = d3.min(vitalsData.flatMap(d => d.values)) || 0; + const maxValue = d3.max(vitalsData.flatMap(d => d.values)) || 100; + const y = d3.scaleLinear() + .domain([Math.max(0, minValue - 10), maxValue + 10]) + .range([height, 0]); + + // Line generator + const line = d3.line() + .x((_d, i) => x(i)) + .y(d => y(d)) + .curve(d3.curveMonotoneX); + + // Color scale + const color = d3.scaleOrdinal() + .domain(vitalsData.map(d => d.name)) + .range(['#ff6b6b', '#48dbfb']); + + // Add X axis + svg.append('g') + .attr('transform', `translate(0,${height})`) + .call(d3.axisBottom(x)) + .selectAll('text') + .style('fill', 'var(--text)'); + + // Add Y axis + svg.append('g') + .call(d3.axisLeft(y)) + .selectAll('text') + .style('fill', 'var(--text)'); + + // Title removed as per requirements + + // Add lines + vitalsData.forEach(d => { + svg.append('path') + .datum(d.values) + .attr('fill', 'none') + .attr('stroke', color(d.name)) + .attr('stroke-width', 1.5) + .attr('d', line); + }); + + // Add legend at the top right + const legendItemHeight = 20; // Height allocated for each legend item + + const legend = svg.append('g') + .attr('font-family', 'sans-serif') + .attr('font-size', 10) + .attr('text-anchor', 'end') + .selectAll('g') + .data(vitalsData) + .enter().append('g') + .attr('transform', (_d, i) => `translate(${width},${i * legendItemHeight + 10})`); + + legend.append('rect') + .attr('x', -15) + .attr('width', 15) + .attr('height', 15) + .attr('fill', d => color(d.name)); + + legend.append('text') + .attr('x', -20) + .attr('y', 7.5) + .attr('dy', '0.32em') + .style('fill', 'var(--text)') + .text(d => d.name); + }; + + // Function to render Bewegung chart + const renderBewegungChart = () => { + if (!dream || dream.input.inputType !== 'chip' || !bewegungChartRef.current) return; + + const chipInput = dream.input; + const bewegungData = [ + {name: 'Bewegung', values: chipInput.bewegung.map(v => v * 100)} // Scale movement for better visibility + ]; + + // Clear previous chart + d3.select(bewegungChartRef.current).selectAll('*').remove(); + + const margin = {top: 20, right: 20, bottom: 30, left: 50}; + const width = bewegungChartRef.current.clientWidth - margin.left - margin.right; + const height = bewegungChartRef.current.clientHeight - margin.top - margin.bottom; + + const svg = d3.select(bewegungChartRef.current) + .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})`); + + // X scale + const x = d3.scaleLinear() + .domain([0, chipInput.bewegung.length - 1]) + .range([0, width]); + + // Y scale + const y = d3.scaleLinear() + .domain([0, d3.max(bewegungData[0].values) || 100]) + .range([height, 0]); + + // Line generator + const line = d3.line() + .x((_d, i) => x(i)) + .y(d => y(d)) + .curve(d3.curveMonotoneX); + + // Color scale + const color = '#1dd1a1'; // Use the same color as before for consistency + + // Add X axis + svg.append('g') + .attr('transform', `translate(0,${height})`) + .call(d3.axisBottom(x)) + .selectAll('text') + .style('fill', 'var(--text)'); + + // Add Y axis + svg.append('g') + .call(d3.axisLeft(y)) + .selectAll('text') + .style('fill', 'var(--text)'); + + // Title removed as per requirements + + // Add line + svg.append('path') + .datum(bewegungData[0].values) + .attr('fill', 'none') + .attr('stroke', color) + .attr('stroke-width', 1.5) + .attr('d', line); + + // Add legend at the top right + const legendItemHeight = 20; // Height allocated for each legend item + + const legend = svg.append('g') + .attr('font-family', 'sans-serif') + .attr('font-size', 10) + .attr('text-anchor', 'end') + .selectAll('g') + .data(bewegungData) + .enter().append('g') + .attr('transform', (_d, i) => `translate(${width},${i * legendItemHeight + 10})`); + + legend.append('rect') + .attr('x', -15) + .attr('width', 15) + .attr('height', 15) + .attr('fill', () => color); + + legend.append('text') + .attr('x', -20) + .attr('y', 7.5) + .attr('dy', '0.32em') + .style('fill', 'var(--text)') + .text(d => d.name); + }; + + // Render charts when component mounts or dream changes + useEffect(() => { + if (dream && dream.input.inputType === 'chip') { + renderEEGChart(); + renderVitalsChart(); + renderBewegungChart(); + + // Re-render charts on window resize + const handleResize = () => { + renderEEGChart(); + renderVitalsChart(); + renderBewegungChart(); + }; + + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + } + }, [dream, renderEEGChart, renderVitalsChart, renderBewegungChart]); + if (!dream) { return (
- {(dream.input.inputType === 'image' || dream.input.inputType === 'audio') && ( -
- {dream.input.inputType === 'audio' && ()} - {dream.input.inputType === 'image' && ({dream.input.imgAlt})} -
)} -

- {(dream.input.inputType === 'text' && dream.input.input) - || (dream.input.inputType === 'audio' && dream.input.transcript) - || (dream.input.inputType === 'image' && dream.input.description) - || (dream.input.inputType === 'chip' && dream.input.text)} -

+ + {dream.input.inputType === 'image' && ( +
+
+ {dream.input.imgAlt} +
+
+

+ {dream.input.description} +

+
+
+ )} + + {dream.input.inputType === 'audio' && ( +
+
+ +
+
+

+ {dream.input.transcript} +

+
+
+ )} + + {dream.input.inputType === 'text' && ( +

+ {dream.input.input} +

+ )} + + {dream.input.inputType === 'chip' && ( +
+

+ {dream.input.text} +

+
+
+
+
+ )} {dream.ai?.interpretation && dream.ai.interpretation !== '' && (