From 921c5962dc6a9e6456afe8bc70b9294ae4cfd8b7 Mon Sep 17 00:00:00 2001 From: Lukas Russell Date: Thu, 24 Apr 2025 13:48:31 +0100 Subject: [PATCH] fixed 0 error --- src/components/charts/PerceivedSpidergram.tsx | 227 +++++++++--------- 1 file changed, 109 insertions(+), 118 deletions(-) diff --git a/src/components/charts/PerceivedSpidergram.tsx b/src/components/charts/PerceivedSpidergram.tsx index 6244a02..68e9842 100644 --- a/src/components/charts/PerceivedSpidergram.tsx +++ b/src/components/charts/PerceivedSpidergram.tsx @@ -3,27 +3,26 @@ import { useState, useEffect } from 'react' import { + Radar, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, - Tooltip, ResponsiveContainer, Customized } from 'recharts' import { Card, CardContent, Typography, Box, Button } from '@mui/material' - import { toast } from 'react-toastify' const domains = [ 'Neuromusculoskeletal', 'Pain', - 'Fatigue', - 'Gastrointestinal', - 'Cardiac Dysautonomia', 'Urogenital', 'Anxiety', - 'Depression' + 'Depression', + 'Fatigue', + 'Gastrointestinal', + 'Cardiac Dysautonomia' ] interface PerceivedSpidergramProps { @@ -32,10 +31,10 @@ interface PerceivedSpidergramProps { onBack: () => void onSubmit: () => void } + const MAX_VALUE = 100 const GRID_LEVELS = 10 -const TICK_VALUES = Array.from({ length: GRID_LEVELS }, (_, i) => (i + 1) * 10) - +const TICK_VALUES = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, MAX_VALUE] const initialData = domains.map((label, i) => ({ subject: label, value: 0, id: 1000 + i })) export default function PerceivedSpidergram({ values, onUpdate, onBack, onSubmit }: PerceivedSpidergramProps) { @@ -43,178 +42,170 @@ export default function PerceivedSpidergram({ values, onUpdate, onBack, onSubmit const [hoveredPoint, setHoveredPoint] = useState<{ axisIndex: number; value: number } | null>(null) useEffect(() => { - const mappedData = initialData.map(item => ({ + const mapped = initialData.map(item => ({ ...item, - value: values[item.subject]?.score || 0 + value: values[item.subject]?.score ?? null })) - setData(mappedData) + setData(mapped) }, [values]) interface InteractionEvent extends React.MouseEvent { currentTarget: HTMLDivElement } - interface RingLevel { radius: number value: number } - const handleSubmitWithValidation = () => { - const allSpiderGramValues = data.map(d => d.value) - const allSet = allSpiderGramValues.every(value => value > 0) - const noneSet = allSpiderGramValues.every(value => value === 0) - - if (allSet || noneSet) { - onSubmit() - } else { - toast.error('Please set all values or none') - } - } + const handleInteraction = (e: InteractionEvent, isClick: boolean = false) => { + const { left, top, width, height } = e.currentTarget.getBoundingClientRect() + const mouseX = e.clientX - left + const mouseY = e.clientY - top + const cx = width / 2 + const cy = height / 2 - const handleInteraction = (e: InteractionEvent, isClick: boolean = false): void => { - const rect = e.currentTarget.getBoundingClientRect() - const mouseX = e.clientX - rect.left - const mouseY = e.clientY - rect.top - const cx = rect.width / 2 - const cy = rect.height / 2 const dx = mouseX - cx - const dy = mouseY - cy - const distance = Math.sqrt(dx * dx + dy * dy) - - const angleDeg = (Math.atan2(-dy, dx) * 180) / Math.PI - const angle = (angleDeg + 360) % 360 + const dy = cy - mouseY // ← flip here - const sectionAngle = 360 / domains.length - let closestAxisIndex = 0 - let minAxisDiff = Infinity + const RAD = Math.PI / 180 + const sector = 360 / domains.length + let bestDot = -Infinity + let overlayIndex = 0 for (let i = 0; i < domains.length; i++) { - const axisAngle = sectionAngle * i - const diff = Math.abs(axisAngle - angle) - const wrappedDiff = Math.min(diff, 360 - diff) + const angleDeg = (360 - i * sector) % 360 + const rad = angleDeg * RAD + + const ux = Math.cos(rad) + const uy = Math.sin(rad) + const dot = dx * ux + dy * uy - if (wrappedDiff < minAxisDiff) { - minAxisDiff = wrappedDiff - closestAxisIndex = i + if (dot > bestDot) { + bestDot = dot + overlayIndex = i } } - const ANGLE_THRESHOLD = 10 - - if (minAxisDiff > ANGLE_THRESHOLD) return setHoveredPoint(null) - - const radius = Math.min(rect.width, rect.height) * 0.45 + const chartIndex = (domains.length - overlayIndex) % domains.length - const ringLevels: RingLevel[] = Array.from({ length: GRID_LEVELS }, (_, i) => { - const r = ((i + 1) / GRID_LEVELS) * radius - const value = (i + 1) * 10 + const dist = Math.hypot(dx, dy) + const maxR = Math.min(width, height) * 0.45 - return { radius: r, value } - }) + const rings: RingLevel[] = [ + { radius: 0, value: 0 }, + ...Array.from({ length: GRID_LEVELS }, (_, i) => ({ + radius: ((i + 1) / GRID_LEVELS) * maxR, + value: (i + 1) * 10 + })) + ] - let closestRing = ringLevels[0] - let minRingDiff = Infinity + let closest = rings[0] + let md = Math.abs(dist - closest.radius) - for (const ring of ringLevels) { - const diff = Math.abs(distance - ring.radius) + for (const r of rings) { + const d = Math.abs(dist - r.radius) - if (diff < minRingDiff) { - minRingDiff = diff - closestRing = ring + if (d < md) { + md = d + closest = r } } if (isClick) { - const updated = data.map((item, i) => (i === closestAxisIndex ? { ...item, value: closestRing.value } : item)) + //console.log(`setting ${domains[overlayIndex]} to ${closest.value}`) useful to check tracking is correct - setData(updated) - - const newValues = Object.fromEntries(updated.map(d => [d.subject, { score: d.value }])) + const updated = data.map((d, i) => (i === overlayIndex ? { ...d, value: closest.value } : d)) - onUpdate(newValues) + setData(updated) + onUpdate(Object.fromEntries(updated.map(d => [d.subject, { score: d.value }]))) } else { - setHoveredPoint({ axisIndex: closestAxisIndex, value: closestRing.value }) + setHoveredPoint({ axisIndex: chartIndex, value: closest.value }) } } - const CustomOverlay = ({ width, height }: { width: number; height: number }) => { - const RADIAN = Math.PI / 180 - const cx = width / 2 - const cy = height / 2 - const radius = Math.min(width, height) * 0.45 - - const ringLevels = Array.from({ length: GRID_LEVELS }, (_, i) => { - const r = ((i + 1) / GRID_LEVELS) * radius - const value = (i + 1) * 10 - - return { radius: r, value } - }) + const CustomOverlay = (props: any) => { + const { viewBox, width: propW, height: propH } = props + const viewX = viewBox?.x ?? 0 + const viewY = viewBox?.y ?? 0 + const width = viewBox?.width ?? propW ?? 0 + const height = viewBox?.height ?? propH ?? 0 - const points = data.map((entry, index) => { - const angle = (360 / data.length) * index - const r = ringLevels.find(r => r.value === entry.value)?.radius || 0 - const x = cx + r * Math.cos(-angle * RADIAN) - const y = cy + r * Math.sin(-angle * RADIAN) - - return [x, y] - }) + const RAD = Math.PI / 180 + const cx = viewX + width / 2 + const cy = viewY + height / 2 + const radius = Math.min(width, height) * 0.45 - const filled = data.every(d => d.value > 0) + const ringLevels: RingLevel[] = Array.from({ length: GRID_LEVELS }, (_, i) => ({ + radius: ((i + 1) / GRID_LEVELS) * radius, + value: (i + 1) * 10 + })) const previewDot = hoveredPoint ? (() => { const angle = (360 / domains.length) * hoveredPoint.axisIndex - const r = ringLevels.find(r => r.value === hoveredPoint.value)?.radius || 0 - const x = cx + r * Math.cos(-angle * RADIAN) - const y = cy + r * Math.sin(-angle * RADIAN) - - return + const rLevel = ringLevels.find(r => r.value === hoveredPoint.value) + const r = rLevel ? rLevel.radius : 0 + + return ( + + ) })() : null - const path = filled && points.map(([x, y], i) => `${i === 0 ? 'M' : 'L'} ${x},${y}`).join(' ') + ' Z' + return <>{previewDot} + } + + const handleSubmit = () => { + const vals = data.map(d => d.value) + + console.log('vals', data) - return ( - <> - {filled && path && } - {points.map(([x, y], i) => (data[i].value > 0 ? : null))} - {previewDot} - - ) + if (vals.every(v => v > 0) || vals.every(v => v !== null)) { + onSubmit() + } else { + toast.error('Please set all values or none') + } } return ( <> - - {'Perceived Spidergram'} + + Perceived Spidergram - + Mark on the graph how much these symptoms have impacted your daily life during the past ONE month. (Optional) - + handleInteraction(e, true)} onMouseMove={e => handleInteraction(e)} > - + - - (typeof val === 'number' && val > 0 ? val : 'Click axis to set')} /> - + + + @@ -222,7 +213,7 @@ export default function PerceivedSpidergram({ values, onUpdate, onBack, onSubmit -