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.
)
}