import { useState, useRef } from 'react' import { ComposedChart, Scatter, Line, XAxis, YAxis, CartesianGrid, Tooltip, ReferenceArea, ResponsiveContainer } from 'recharts' import { effectiveSentiment, sentimentColor, quadrantColor } from '../utils/sentiment' import { pearsonR, linearRegression, describeCorrelation } from '../utils/stats' import { downloadChartPng } from '../utils/chartExport' import CustomTooltip from './CustomTooltip' import ChartControls from './ChartControls' export default function ScoreScatter({ reviews }) { const [displayMode, setDisplayMode] = useState('both') const chartRef = useRef(null) if (!reviews || reviews.length === 0) return null // Filter reviews with ratings const rated = reviews.filter((r) => r.author_rating != null) if (rated.length < 2) { return (
Not enough rated reviews to show correlation.
) } // Build scatter data const scatterData = rated.map((r) => { const effective = effectiveSentiment(r.sentiment_label, r.sentiment_score) const { color, isDisagree } = quadrantColor(r.author_rating, effective) return { ...r, x: r.author_rating, y: effective, color, isDisagree, } }) // Pearson r const xs = scatterData.map((d) => d.x) const ys = scatterData.map((d) => d.y) const r = pearsonR(xs, ys) const { slope, intercept } = linearRegression(xs, ys) // Regression line endpoints const regressionData = [ { x: 0, regY: Math.max(0, Math.min(1, intercept)) }, { x: 10, regY: Math.max(0, Math.min(1, slope * 10 + intercept)) }, ] const handleDownload = () => { if (chartRef.current) downloadChartPng(chartRef.current, 'score-scatter.png') } return (

Stars vs. Sentiment

Scatter chart comparing {rated.length} reviews' star ratings against their sentiment scores.

{(() => { const d = describeCorrelation(r); return d.charAt(0).toUpperCase() + d.slice(1) })()} agreement {' '}(r = {r.toFixed(2)})

The closer r is to 1.0, the more a reviewer's star rating matches their words. Values near 0 suggest no relationship.

v.toFixed(1)} /> } /> {/* Quadrant backgrounds */} {/* Scatter points */} {(displayMode === 'points' || displayMode === 'both') && ( { const { cx, cy, payload } = props const radius = payload.isDisagree ? 7 : 5 return ( ) }} /> )} {/* Regression line */} {(displayMode === 'trend' || displayMode === 'both') && ( )}
{/* Annotation */}

Points far from the diagonal = your words and your stars don't match.

) }