Skip to main content

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

repos
array
required
Array of repository objects. Only the first 12 repositories are analyzed for language statistics.
repos: Array<{
  name: string,
  // ... other repo fields
}>
getLanguages
function
required
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
}
username
string
required
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

Custom Tooltip

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

  1. Fetch language data for top 12 repos
  2. Aggregate byte counts by language
  3. Sort by total bytes (descending)
  4. Calculate percentages
  5. Take top 10 languages
  6. 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.

Build docs developers (and LLMs) love