import { useState, useRef } from 'react' import { ComposedChart, Scatter, Line, XAxis, YAxis, CartesianGrid, Tooltip, ReferenceLine, ResponsiveContainer } from 'recharts' import { effectiveSentiment, sentimentColor, arcTitle } from '../utils/sentiment' import { rollingAverage } from '../utils/stats' import { downloadChartPng } from '../utils/chartExport' import CustomTooltip from './CustomTooltip' import ChartControls from './ChartControls' // Generate tick indices based on date cadence function getTickIndices(sorted, isMonthly) { if (sorted.length <= 1) return [0] const cadenceDays = isMonthly ? 30 : 7 const cadenceMs = cadenceDays * 24 * 60 * 60 * 1000 const first = new Date(sorted[0].created_at).getTime() const ticks = [0] let lastTickTime = first sorted.forEach((r, i) => { if (i === 0) return const t = new Date(r.created_at).getTime() if (t - lastTickTime >= cadenceMs) { ticks.push(i) lastTickTime = t } }) // Always include last const last = sorted.length - 1 if (ticks[ticks.length - 1] !== last) ticks.push(last) // Limit to max 10 ticks to avoid overcrowding if (ticks.length > 10) { const step = Math.ceil(ticks.length / 10) return ticks.filter((_, i) => i % step === 0 || i === ticks.length - 1) } return ticks } function formatDateTick(dateStr, isMonthly) { const d = new Date(dateStr) if (isMonthly) { return d.toLocaleDateString('en-US', { month: 'short', year: '2-digit' }) } return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) } export default function SentimentArc({ reviews }) { const [displayMode, setDisplayMode] = useState('both') const chartRef = useRef(null) if (!reviews || reviews.length === 0) return null // Sort chronologically const sorted = [...reviews].sort((a, b) => new Date(a.created_at) - new Date(b.created_at)) // Compute effective sentiment for each review const withEffective = sorted.map((r) => { const effective = effectiveSentiment(r.sentiment_label, r.sentiment_score) return { ...r, effective, color: sentimentColor(effective), value: effective } }) // Rolling average data — rollingAverage expects items with a `value` field const averaged = rollingAverage(withEffective, 7) // Determine tick density const firstDate = new Date(sorted[0].created_at) const lastDate = new Date(sorted[sorted.length - 1].created_at) const rangeMs = lastDate - firstDate const threeMonthsMs = 90 * 24 * 60 * 60 * 1000 const isMonthly = rangeMs >= threeMonthsMs // Dynamic title const trend = arcTitle(sorted) const titleMap = { improved: 'Sentiment improved over time', declined: 'Sentiment declined over time', 'remained stable': 'Sentiment remained stable over time', } const title = titleMap[trend] || 'Sentiment over time' // Scatter data uses index as x for consistent positioning across both datasets const scatterData = withEffective.map((r, i) => ({ ...r, x: i })) const trendData = averaged.map((r, i) => ({ x: i, avg: r.avg })) // X-axis ticks — date-cadence based (weekly or monthly) const tickIndices = getTickIndices(sorted, isMonthly) const handleDownload = () => { if (chartRef.current) downloadChartPng(chartRef.current, 'sentiment-arc.png') } return (
0 = Negative, 1 = Positive. Trend line shows 7-review rolling average.