harshapitla/github-actions-analytics
0
1import React, { useMemo } from "react";2import {3 ScatterChart,4 Scatter,5 XAxis,6 YAxis,7 ZAxis,8 CartesianGrid,9 Tooltip,10 ResponsiveContainer,11 Label,12} from "recharts";13import ChartCard from "../components/charts/ChartCard";14 15const toNumber = (value) => {16 const num = Number(value);17 return Number.isFinite(num) ? num : 0;18};19 20const toTimestamp = (value) => {21 if (!value) return 0;22 const date = new Date(value);23 return Number.isNaN(date.getTime()) ? 0 : date.getTime();24};25 26function TooltipContent({ payload }) {27 if (!payload || !payload.length) return null;28 const p = payload[0].payload;29 return (30 <div31 style={{32 background: "rgba(255,255,255,0.95)",33 color: "#0f172a",34 padding: "8px 12px",35 borderRadius: 6,36 border: "1px solid #e2e8f0",37 boxShadow: "0 4px 6px -1px rgb(0 0 0 / 0.1)",38 }}39 >40 <div>41 <strong>{p.name}</strong>42 </div>43 <div className="small">Publisher: {p.publisher}</div>44 <div className="small">Downloads: {Math.round(p.downloads).toLocaleString()}</div>45 <div className="small">Rating: {p.rating?.toFixed?.(1) ?? p.rating}</div>46 <div className="small">47 Last update: {p.lastUpdated ? new Date(p.lastUpdated).toLocaleDateString() : "Unknown"}48 </div>49 </div>50 );51}52 53export default function EvolutionScatter({ rows = [] }) {54 const data = useMemo(() => {55 if (!Array.isArray(rows)) return [];56 return rows57 .map((record) => {58 const downloads = Math.max(1, toNumber(record.downloads_count));59 const rating = toNumber(record.rating);60 const lastUpdated = record.last_updated || record.last_update || record.published_at;61 return {62 x: downloads,63 y: rating,64 z: toTimestamp(lastUpdated),65 name: record.name,66 publisher: record.publisher,67 downloads,68 rating,69 lastUpdated,70 };71 })72 .filter((point) => point.y > 0);73 }, [rows]);74 75 if (!data.length) {76 return (77 <ChartCard title="Popularity vs. Quality" subtitle="Downloads vs. rating (log scale).">78 <p className="small">Not enough rating information to compare extensions.</p>79 </ChartCard>80 );81 }82 83 return (84 <ChartCard title="Popularity vs. Quality" subtitle="Downloads vs. rating, bubble size = update recency.">85 <div style={{ height: 360 }}>86 <ResponsiveContainer>87 <ScatterChart margin={{ top: 20, right: 30, left: 20, bottom: 40 }}>88 <CartesianGrid strokeDasharray="3 3" strokeOpacity={0.3} />89 <XAxis90 type="number"91 dataKey="x"92 name="downloads"93 scale="log"94 domain={["dataMin", "dataMax"]}95 tickFormatter={(value) => (value >= 1_000_000 ? `${value / 1_000_000}M` : value >= 1_000 ? `${value / 1_000}k` : value)}96 >97 <Label value="Downloads (Log Scale)" offset={-25} position="insideBottom" />98 </XAxis>99 <YAxis type="number" dataKey="y" name="rating" domain={[1, 5]} ticks={[1, 2, 3, 4, 5]}>100 <Label value="Rating" angle={-90} position="insideLeft" style={{ textAnchor: "middle" }} />101 </YAxis>102 <ZAxis type="number" dataKey="z" range={[10, 200]} />103 <Tooltip content={<TooltipContent />} />104 <Scatter name="extensions" data={data} fill="#7c3aed" fillOpacity={0.6} />105 </ScatterChart>106 </ResponsiveContainer>107 </div>108 </ChartCard>109 );110}111 