Overview
The LanguageChart component analyzes the top 12 repositories and aggregates language usage data to display a pie chart and horizontal bar chart showing the distribution of programming languages used.
Location in UI: Left sidebar, below the UserCard component
Source: ~/workspace/source/src/components/LanguageChart.jsx
Props
Array of repository objects. Only the first 12 repositories are analyzed for language statistics.repos: Array<{
name: string,
// ... other repo fields
}>
Function to fetch language statistics for a specific repository.getLanguages: (username: string, repoName: string) => Promise<Record<string, number>>
Returns object mapping language names to byte counts:{
"JavaScript": 45231,
"TypeScript": 12843,
"CSS": 3421
}
GitHub username of the repository owner. Used to construct API requests for language data.
Usage Example
From App.jsx:103-107:
<LanguageChart
repos={repos}
getLanguages={getLanguages}
username={user.login}
/>
Key Features
Language Data Aggregation
Fetches and aggregates language data from top 12 repositories:
const [data, setData] = useState([])
const [loading, setLoading] = useState(false)
const cache = useRef({})
useEffect(() => {
if (!repos.length) return
let cancelled = false
setLoading(true)
const top = repos.slice(0, 12)
const fetches = top.map(r => {
const key = `${username}/${r.name}`
if (cache.current[key]) return Promise.resolve(cache.current[key])
return getLanguages(username, r.name).then(d => {
cache.current[key] = d
return d
}).catch(() => ({}))
})
Promise.all(fetches).then(results => {
if (cancelled) return
const totals = {}
results.forEach(langs => {
Object.entries(langs).forEach(([lang, bytes]) => {
totals[lang] = (totals[lang] || 0) + bytes
})
})
const sorted = Object.entries(totals).sort((a, b) => b[1] - a[1])
const total = sorted.reduce((s, [, v]) => s + v, 0)
const top10 = sorted.slice(0, 10).map(([name, val], i) => ({
name,
value: val,
pct: Math.round((val / total) * 100),
color: COLORS[i % COLORS.length],
}))
setData(top10)
setLoading(false)
})
return () => { cancelled = true }
}, [repos, username])
Key behaviors:
- Analyzes only top 12 repositories
- Caches language data to avoid redundant API calls
- Aggregates byte counts across all repositories
- Shows top 10 languages by usage
- Calculates percentage of total
Caching Strategy
Uses ref-based caching to prevent re-fetching:
const cache = useRef({})
const key = `${username}/${r.name}`
if (cache.current[key]) return Promise.resolve(cache.current[key])
Pie Chart Visualization
Renders interactive donut chart using Recharts:
<ResponsiveContainer width="100%" height={200}>
<PieChart>
<Pie data={data} cx="50%" cy="50%" innerRadius={55} outerRadius={85}
paddingAngle={2} dataKey="value">
{data.map((entry, i) => (
<Cell key={entry.name} fill={entry.color} stroke="none" />
))}
</Pie>
<Tooltip content={<CustomTooltip />} />
</PieChart>
</ResponsiveContainer>
Chart configuration:
- Inner radius: 55px
- Outer radius: 85px
- Padding angle: 2 degrees between segments
- No stroke on segments
- Custom tooltip component
Displays language name and percentage on hover:
const CustomTooltip = ({ active, payload }) => {
if (active && payload?.length) {
const d = payload[0]
return (
<div className={styles.tooltip}>
<span className={styles.dot} style={{ background: d.payload.color }} />
<span>{d.name}</span>
<strong>{d.payload.pct}%</strong>
</div>
)
}
return null
}
Language Legend
Horizontal bar chart with percentages:
<ul className={styles.legend}>
{data.map(d => (
<li key={d.name} className={styles.legendItem}>
<div className={styles.legendLeft}>
<span className={styles.legendDot} style={{ background: d.color }} />
<span className={styles.legendName}>{d.name}</span>
</div>
<div className={styles.barWrap}>
<div className={styles.bar} style={{ width: `${d.pct}%`, background: d.color }} />
</div>
<span className={styles.legendPct}>{d.pct}%</span>
</li>
))}
</ul>
Color Palette
Predefined color scheme for languages:
const COLORS = [
'#2563eb', '#10b981', '#f59e0b', '#8b5cf6', '#ef4444',
'#06b6d4', '#f97316', '#ec4899', '#84cc16', '#6366f1',
]
Colors are assigned sequentially with wrapping:
color: COLORS[i % COLORS.length]
Loading State
Shows skeleton loaders while fetching data:
if (loading) return (
<div className={styles.card}>
<h3 className={styles.title}>Lenguajes más usados</h3>
<div className={styles.loadingGrid}>
{[1,2,3,4,5].map(i => (
<div key={i} className={`skeleton ${styles.skRow}`} />
))}
</div>
</div>
)
Conditional Rendering
Hides component if no language data is available:
if (!data.length) return null
Implementation Details
State Management
const [data, setData] = useState([]) // Top 10 languages with percentages
const [loading, setLoading] = useState(false)
const cache = useRef({}) // Cache for language data
Cleanup
Prevents state updates after unmount:
let cancelled = false
// ... async operations
Promise.all(fetches).then(results => {
if (cancelled) return
// ... update state
})
return () => { cancelled = true }
Error Handling
Silently handles failed language fetches:
return getLanguages(username, r.name)
.then(d => { /* ... */ })
.catch(() => ({})) // Return empty object on error
Data Processing
- Fetch language data for top 12 repos
- Aggregate byte counts by language
- Sort by total bytes (descending)
- Calculate percentages
- Take top 10 languages
- Assign colors
Animation
Applies delayed fade-up animation:
<div className={`${styles.card} fade-up fade-up-delay-2`}>
Dependencies
Uses Recharts library for visualization:
PieChart
Pie
Cell
Tooltip
ResponsiveContainer
Styling
Imports modular CSS from LanguageChart.module.css for component-scoped styles.