1- import { Bar , BarChart , CartesianGrid , Cell , Pie , PieChart , XAxis , YAxis } from "recharts" ;
2- import {
3- ChartContainer ,
4- ChartLegend ,
5- ChartLegendContent ,
6- ChartTooltip ,
7- ChartTooltipContent ,
8- } from "@/components/ui/chart" ;
1+ import { Bar , BarChart , CartesianGrid , Cell , Pie , PieChart , Tooltip , XAxis , YAxis } from "recharts" ;
2+ import { ChartContainer , ChartLegend , ChartLegendContent } from "@/components/ui/chart" ;
93import type { AggregatedAnalyticsData , Distribution , VersionDistribution } from "./types" ;
10- import { chartConfig , getColor } from "./types" ;
4+ import { CHART_COLORS , chartConfig , getColor , truncateLabel } from "./types" ;
5+
6+ function CustomYAxisTick ( {
7+ x,
8+ y,
9+ payload,
10+ maxChars = 10 ,
11+ } : {
12+ x : number ;
13+ y : number ;
14+ payload : { value : string } ;
15+ maxChars ?: number ;
16+ } ) {
17+ const label = truncateLabel ( String ( payload . value ) , maxChars ) ;
18+ return (
19+ < g transform = { `translate(${ x } ,${ y } )` } >
20+ < text x = { - 4 } y = { 0 } dy = { 4 } textAnchor = "end" fill = "hsl(var(--muted-foreground))" fontSize = { 11 } >
21+ { label }
22+ </ text >
23+ </ g >
24+ ) ;
25+ }
26+
27+ function CustomXAxisTick ( {
28+ x,
29+ y,
30+ payload,
31+ maxChars = 7 ,
32+ } : {
33+ x : number ;
34+ y : number ;
35+ payload : { value : string } ;
36+ maxChars ?: number ;
37+ } ) {
38+ const label = truncateLabel ( String ( payload . value ) , maxChars ) ;
39+ return (
40+ < g transform = { `translate(${ x } ,${ y } )` } >
41+ < text
42+ x = { 0 }
43+ y = { 0 }
44+ dy = { 12 }
45+ textAnchor = "middle"
46+ fill = "hsl(var(--muted-foreground))"
47+ fontSize = { 10 }
48+ >
49+ { label }
50+ </ text >
51+ </ g >
52+ ) ;
53+ }
1154
1255function ChartCard ( {
1356 title,
@@ -32,22 +75,39 @@ function ChartCard({
3275 ) ;
3376}
3477
78+ function CustomTooltip ( {
79+ active,
80+ payload,
81+ } : {
82+ active ?: boolean ;
83+ payload ?: Array < { value : number ; payload : { name : string } } > ;
84+ } ) {
85+ if ( ! active || ! payload ?. length ) return null ;
86+ const item = payload [ 0 ] ;
87+ return (
88+ < div className = "rounded border border-border/50 bg-background px-3 py-2 text-xs shadow-lg" >
89+ < p className = "font-medium" > { item . payload . name } </ p >
90+ < p className = "text-muted-foreground" > { item . value . toLocaleString ( ) } projects</ p >
91+ </ div >
92+ ) ;
93+ }
94+
3595function HorizontalBarChart ( { data, height = 280 } : { data : Distribution ; height ?: number } ) {
3696 return (
3797 < ChartContainer config = { chartConfig } className = "w-full" style = { { height } } >
38- < BarChart data = { data } layout = "vertical" margin = { { left : 0 } } >
39- < CartesianGrid horizontal = { false } className = "stroke-border" />
40- < XAxis type = "number" tickLine = { false } axisLine = { false } className = "text-xs" />
98+ < BarChart data = { data } layout = "vertical" margin = { { left : 4 , right : 12 , top : 4 , bottom : 4 } } >
99+ < CartesianGrid horizontal = { false } className = "stroke-border/40 " />
100+ < XAxis type = "number" tickLine = { false } axisLine = { false } tick = { { fontSize : 10 } } />
41101 < YAxis
42102 dataKey = "name"
43103 type = "category"
44104 tickLine = { false }
45105 axisLine = { false }
46- width = { 80 }
47- className = "text-xs"
106+ width = { 75 }
107+ tick = { ( props ) => < CustomYAxisTick { ... props } maxChars = { 10 } /> }
48108 />
49- < ChartTooltip content = { < ChartTooltipContent /> } />
50- < Bar dataKey = "value" radius = { 4 } >
109+ < Tooltip content = { < CustomTooltip /> } cursor = { { fill : "hsl(var(--muted))" , opacity : 0.3 } } />
110+ < Bar dataKey = "value" radius = { 3 } >
51111 { data . map ( ( entry , i ) => (
52112 < Cell key = { entry . name } fill = { getColor ( i ) } />
53113 ) ) }
@@ -60,36 +120,78 @@ function HorizontalBarChart({ data, height = 280 }: { data: Distribution; height
60120function VersionBarChart ( { data, height = 280 } : { data : VersionDistribution ; height ?: number } ) {
61121 return (
62122 < ChartContainer config = { chartConfig } className = "w-full" style = { { height } } >
63- < BarChart data = { data } >
64- < CartesianGrid vertical = { false } className = "stroke-border" />
65- < XAxis dataKey = "version" tickLine = { false } axisLine = { false } className = "text-xs" />
66- < YAxis tickLine = { false } axisLine = { false } className = "text-xs" />
67- < ChartTooltip content = { < ChartTooltipContent /> } />
68- < Bar dataKey = "count" radius = { 4 } fill = "hsl(var(--chart-1))" />
123+ < BarChart data = { data } margin = { { left : - 10 , right : 8 , top : 8 , bottom : 4 } } >
124+ < CartesianGrid vertical = { false } className = "stroke-border/40" />
125+ < XAxis
126+ dataKey = "version"
127+ tickLine = { false }
128+ axisLine = { false }
129+ tick = { ( props ) => < CustomXAxisTick { ...props } maxChars = { 7 } /> }
130+ interval = { 0 }
131+ />
132+ < YAxis tickLine = { false } axisLine = { false } tick = { { fontSize : 10 } } width = { 35 } />
133+ < Tooltip
134+ content = { ( { active, payload } ) => {
135+ if ( ! active || ! payload ?. length ) return null ;
136+ const item = payload [ 0 ] . payload as { version : string ; count : number } ;
137+ return (
138+ < div className = "rounded border border-border/50 bg-background px-3 py-2 text-xs shadow-lg" >
139+ < p className = "font-medium" > { item . version } </ p >
140+ < p className = "text-muted-foreground" > { item . count . toLocaleString ( ) } projects</ p >
141+ </ div >
142+ ) ;
143+ } }
144+ cursor = { { fill : "hsl(var(--muted))" , opacity : 0.3 } }
145+ />
146+ < Bar dataKey = "count" radius = { 3 } >
147+ { data . map ( ( _ , i ) => (
148+ < Cell key = { i } fill = { CHART_COLORS [ 4 ] } />
149+ ) ) }
150+ </ Bar >
69151 </ BarChart >
70152 </ ChartContainer >
71153 ) ;
72154}
73155
74156function PieChartComponent ( { data } : { data : Distribution } ) {
157+ const total = data . reduce ( ( sum , d ) => sum + d . value , 0 ) ;
158+
75159 return (
76160 < ChartContainer config = { chartConfig } className = "h-[280px] w-full" >
77161 < PieChart >
78- < ChartTooltip content = { < ChartTooltipContent nameKey = "name" /> } />
162+ < Tooltip
163+ content = { ( { active, payload } ) => {
164+ if ( ! active || ! payload ?. length ) return null ;
165+ const item = payload [ 0 ] . payload as { name : string ; value : number } ;
166+ const percent = ( ( item . value / total ) * 100 ) . toFixed ( 1 ) ;
167+ return (
168+ < div className = "rounded border border-border/50 bg-background px-3 py-2 text-xs shadow-lg" >
169+ < p className = "font-medium" > { item . name } </ p >
170+ < p className = "text-muted-foreground" >
171+ { item . value . toLocaleString ( ) } ({ percent } %)
172+ </ p >
173+ </ div >
174+ ) ;
175+ } }
176+ />
79177 < Pie
80178 data = { data }
81179 cx = "50%"
82- cy = "50%"
83- outerRadius = { 80 }
180+ cy = "45%"
181+ outerRadius = { 65 }
182+ innerRadius = { 35 }
84183 dataKey = "value"
85- label = { ( { name, percent } ) => `${ name } ${ ( percent * 100 ) . toFixed ( 0 ) } %` }
86- labelLine = { false }
184+ paddingAngle = { 2 }
87185 >
88186 { data . map ( ( entry , i ) => (
89187 < Cell key = { entry . name } fill = { getColor ( i ) } />
90188 ) ) }
91189 </ Pie >
92- < ChartLegend content = { < ChartLegendContent /> } />
190+ < ChartLegend
191+ content = { < ChartLegendContent nameKey = "name" /> }
192+ formatter = { ( value ) => truncateLabel ( String ( value ) , 8 ) }
193+ wrapperStyle = { { fontSize : 11 } }
194+ />
93195 </ PieChart >
94196 </ ChartContainer >
95197 ) ;
0 commit comments