Skip to content

Commit 3665b8c

Browse files
fetch and animate new pca data
1 parent 1549957 commit 3665b8c

6 files changed

Lines changed: 138 additions & 30 deletions

File tree

client-participation-alpha/src/api/pca.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export interface PCAData {
6464
repness?: {
6565
[groupId: string]: RepnessItem[];
6666
};
67+
mathTick?: number;
6768
}
6869

6970
export async function fetchPCAData(conversationId: string): Promise<PCAData> {

client-participation-alpha/src/components/PCAVisualization.tsx

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ function CheckCircleIcon({ fill, size = 22 }: { fill: string; size?: number }) {
5656
style={{ display: 'block' }}
5757
>
5858
<path
59-
d="M1299 813l-422 422q-19 19-45 19t-45-19l-294-294q-19-19-19-45t19-45l102-102q19-19 45-19t45 19l147 147 275-275q19-19 45-19t45 19l102 102q19 19 19 45t-19 45zm141 83q0-148-73-273t-198-198-273-73-273 73-198 198-73 273 73 273-73 198-198 73-273zm224 0q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"
59+
d="M1299 813l-422 422q-19 19-45 19t-45-19l-294-294q-19-19-19-45t19-45l102-102q19-19 45-19t45 19l147 147 275-275q19-19 45-19t45 19l102 102q19 19 19 45t-19 45zm141 83q0-148-73-273t-198-198-273-73-273 73-198 198-73 273 73 273 198 198 273 73 273-73 198-198 73-273zm224 0q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"
6060
fill={fill}
6161
/>
6262
</svg>
@@ -417,18 +417,27 @@ export default function PCAVisualization({ data, comments, conversationId }: PCA
417417
const groupKey = `group-${groupId}`;
418418

419419
if (hull) {
420+
const pathString = `M${hull.map((point: [number, number]) => point.join(',')).join('L')}Z`;
420421
return (
421422
<motion.path
422423
key={`${groupKey}-hull`}
423-
d={`M${hull.map((point: [number, number]) => point.join(',')).join('L')}Z`}
424+
d={pathString}
424425
fill={color}
425426
fillOpacity={isSelected ? 0.35 : 0.2}
426427
stroke={color}
427428
strokeWidth={isSelected ? 3 : 2}
428429
strokeOpacity={isSelected ? 1 : 1}
429-
initial={{ pathLength: 0, opacity: 0 }}
430-
animate={{ pathLength: 1, opacity: 1 }}
431-
transition={{ duration: 1, delay: i * 0.2 }}
430+
initial={false}
431+
animate={{
432+
d: pathString,
433+
fill: color,
434+
fillOpacity: isSelected ? 0.35 : 0.2,
435+
strokeWidth: isSelected ? 3 : 2,
436+
}}
437+
transition={{
438+
duration: 0.8,
439+
ease: "easeInOut"
440+
}}
432441
/>
433442
);
434443
}
@@ -444,9 +453,16 @@ export default function PCAVisualization({ data, comments, conversationId }: PCA
444453
stroke={color}
445454
strokeWidth={isSelected ? 3 : 2}
446455
strokeLinecap="round"
447-
initial={{ opacity: 0 }}
448-
animate={{ opacity: 1 }}
449-
transition={{ duration: 1, delay: i * 0.2 }}
456+
initial={false}
457+
animate={{
458+
x1: points[0][0],
459+
y1: points[0][1],
460+
x2: points[1][0],
461+
y2: points[1][1],
462+
stroke: color,
463+
strokeWidth: isSelected ? 3 : 2,
464+
}}
465+
transition={{ duration: 0.8, ease: "easeInOut" }}
450466
/>
451467
);
452468
}
@@ -479,32 +495,59 @@ export default function PCAVisualization({ data, comments, conversationId }: PCA
479495
<feColorMatrix type="saturate" values="0" />
480496
</filter>
481497
</defs>
482-
<circle
498+
<motion.circle
483499
cx={userPosition.x}
484500
cy={userPosition.y}
485501
r={13}
486502
fill="none"
487503
stroke="#03a9f4"
488504
strokeWidth={4}
505+
initial={false}
506+
animate={{
507+
cx: userPosition.x,
508+
cy: userPosition.y
509+
}}
510+
transition={{
511+
duration: 0.8,
512+
ease: "easeInOut"
513+
}}
489514
/>
490-
<circle
515+
<motion.circle
491516
cx={userPosition.x}
492517
cy={userPosition.y}
493518
r={11}
494519
fill="url(#user-profile-pattern)"
495520
filter="url(#grayscale-filter)"
521+
initial={false}
522+
animate={{
523+
cx: userPosition.x,
524+
cy: userPosition.y
525+
}}
526+
transition={{
527+
duration: 0.8,
528+
ease: "easeInOut"
529+
}}
496530
/>
497-
<text
531+
<motion.text
498532
x={userPosition.x}
499533
y={userPosition.y - 18}
500534
textAnchor="middle"
501535
fontSize={12}
502536
fontWeight="bold"
503537
fill="#000"
504538
style={{ textShadow: '0 1px 2px rgba(255,255,255,0.8)' }}
539+
initial={false}
540+
animate={{
541+
x: userPosition.x,
542+
y: userPosition.y - 18
543+
}}
544+
transition={{
545+
duration: 0.8,
546+
ease: "easeInOut"
547+
}}
505548
>
506549
You
507-
</text>
550+
</motion.text>
508551
</Group>
509552
)}
510553

client-participation-alpha/src/components/Survey.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ const submitVoteAndGetNextCommentAPI = async (vote, conversation_id, high_priori
2020
vote: vote.vote,
2121
});
2222

23+
// Dispatch event to notify visualization to update
24+
window.dispatchEvent(new CustomEvent('polis-vote-submitted', {
25+
detail: { conversation_id }
26+
}));
27+
2328
return resp;
2429
} catch (error) {
2530
// The net module already handles JWT extraction and storage

client-participation-alpha/src/components/SurveyForm.jsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ const submitPerspectiveAPI = async (text, conversation_id) => {
1414
vote: -1,
1515
});
1616

17+
// Dispatch event to notify visualization to update
18+
window.dispatchEvent(new CustomEvent('polis-comment-submitted', {
19+
detail: { conversation_id }
20+
}));
21+
1722
// The net module automatically handles JWT extraction and storage
1823
return resp;
1924
} catch (error) {
@@ -23,7 +28,6 @@ const submitPerspectiveAPI = async (text, conversation_id) => {
2328
}
2429
};
2530

26-
2731
export default function SurveyForm({ s, conversation_id, requiresInviteCode = false }) {
2832
const [text, setText] = useState('');
2933
const [feedback, setFeedback] = useState('');

client-participation-alpha/src/components/VisualizationContainer.tsx

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import { useState, useEffect } from 'react';
1+
import { useState, useEffect, useRef, useCallback } from 'react';
22
import PCAVisualization from './PCAVisualization';
33
import { fetchPCAData, type PCAData } from '../api/pca';
44
import { fetchComments, type Comment } from '../api/comments';
55

6+
const REFRESH_DELAY_MS = 500;
7+
68
interface VisualizationContainerProps {
79
conversation_id: string;
810
}
@@ -12,35 +14,81 @@ export default function VisualizationContainer({ conversation_id }: Visualizatio
1214
const [comments, setComments] = useState<Comment[] | null>(null);
1315
const [loading, setLoading] = useState(true);
1416
const [error, setError] = useState<string | null>(null);
17+
const currentMathTick = useRef<number | undefined>(undefined);
18+
const refetchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
1519

16-
useEffect(() => {
20+
const loadData = useCallback(async (showLoadingState = true) => {
1721
if (!conversation_id) {
1822
setLoading(false);
1923
setError('No conversation ID provided');
2024
return;
2125
}
2226

23-
async function loadData() {
24-
try {
27+
try {
28+
if (showLoadingState) {
2529
setLoading(true);
26-
setError(null);
27-
// Fetch both PCA data and comments in parallel
28-
const [pcaDataResult, commentsResult] = await Promise.all([
29-
fetchPCAData(conversation_id),
30-
fetchComments(conversation_id),
31-
]);
32-
setPcaData(pcaDataResult);
33-
setComments(commentsResult);
34-
} catch (err) {
35-
setError(err instanceof Error ? err.message : 'Failed to fetch data');
36-
console.error('Error fetching data:', err);
37-
} finally {
30+
}
31+
setError(null);
32+
33+
// Fetch both PCA data and comments in parallel
34+
const [pcaDataResult, commentsResult] = await Promise.all([
35+
fetchPCAData(conversation_id),
36+
fetchComments(conversation_id),
37+
]);
38+
39+
// Check if mathTick has changed (skip update if unchanged)
40+
if (pcaDataResult.mathTick !== undefined &&
41+
pcaDataResult.mathTick === currentMathTick.current) {
42+
// Math hasn't been recalculated yet, data is the same
43+
return;
44+
}
45+
46+
currentMathTick.current = pcaDataResult.mathTick;
47+
setPcaData(pcaDataResult);
48+
setComments(commentsResult);
49+
} catch (err) {
50+
setError(err instanceof Error ? err.message : 'Failed to fetch data');
51+
console.error('Error fetching data:', err);
52+
} finally {
53+
if (showLoadingState) {
3854
setLoading(false);
3955
}
4056
}
57+
}, [conversation_id]);
4158

59+
// Initial load
60+
useEffect(() => {
4261
loadData();
43-
}, [conversation_id]);
62+
}, [loadData]);
63+
64+
// Listen for vote/comment submissions and refetch after delay
65+
useEffect(() => {
66+
const handleDataChange = (e: Event) => {
67+
const detail = (e as CustomEvent).detail;
68+
if (detail && detail.conversation_id === conversation_id) {
69+
// Clear any pending refetch
70+
if (refetchTimeoutRef.current) {
71+
clearTimeout(refetchTimeoutRef.current);
72+
}
73+
74+
// Schedule refetch after 1 second delay
75+
refetchTimeoutRef.current = setTimeout(() => {
76+
loadData(false); // Don't show loading state for updates
77+
}, REFRESH_DELAY_MS);
78+
}
79+
};
80+
81+
window.addEventListener('polis-vote-submitted', handleDataChange);
82+
window.addEventListener('polis-comment-submitted', handleDataChange);
83+
84+
return () => {
85+
window.removeEventListener('polis-vote-submitted', handleDataChange);
86+
window.removeEventListener('polis-comment-submitted', handleDataChange);
87+
if (refetchTimeoutRef.current) {
88+
clearTimeout(refetchTimeoutRef.current);
89+
}
90+
};
91+
}, [conversation_id, loadData]);
4492

4593
if (loading) {
4694
return (

client-participation-alpha/src/lib/hull.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,8 @@ class PointGrid {
460460
}
461461
}
462462
}
463+
464+
return points
463465
}
464466

465467
/**
@@ -626,6 +628,11 @@ function _bBoxAround(edge: [Point, Point]): Bbox {
626628
}
627629

628630
function _midPoint(edge: [Point, Point], innerPoints: Point[], convex: Point[]): Point | null {
631+
// Safety check: ensure innerPoints is defined and is an array
632+
if (!innerPoints || !Array.isArray(innerPoints) || innerPoints.length === 0) {
633+
return null
634+
}
635+
629636
let point: Point | null = null
630637
let angle1Cos = MAX_CONCAVE_ANGLE_COS
631638
let angle2Cos = MAX_CONCAVE_ANGLE_COS

0 commit comments

Comments
 (0)