feat(card): add contributions area chart card

New 5-contributions.svg renders the last year's contribution calendar
as a monthly smooth-filled area chart. Pure Go SVG; no extra API calls
— one additional contributionCalendar.weeks block in the existing
profile GraphQL query carries the data.

- Y-axis mirrored on both sides with nice ticks.
- X-axis labels in YY/MM format, every other month to avoid overlap.
- Smooth curve via Catmull-Rom interpolation converted to cubic Bezier
  (d3.curveCatmullRom default tension 0.5).
- Missing months between first and last are inserted as zero-count so
  the chart stays time-continuous.
This commit is contained in:
2026-04-18 21:20:52 +07:00
parent 442da96f99
commit d0d3862780
5 changed files with 227 additions and 1 deletions
+11
View File
@@ -38,11 +38,22 @@ type Profile struct {
// Commit counts grouped by hour-of-day (0-23) in the configured timezone.
Productive [24]int
// DailyContributions is the raw per-day contribution calendar covering
// the most recent year. The area chart aggregates it into monthly
// buckets; kept granular here so any downstream card can re-bin freely.
DailyContributions []DailyContribution
// TopRepos are owned repos sorted by stargazer count desc. Populated by
// FetchProfile and consumed by FetchProductive.
TopRepos []RepoInfo
}
// DailyContribution is a single day in the contributions calendar.
type DailyContribution struct {
Date time.Time
Count int
}
// LangStat is one row in a language breakdown card. Value is repo count or
// commit count depending on which slice holds it.
type LangStat struct {
+20
View File
@@ -36,6 +36,12 @@ type profileGQL struct {
RestrictedContributionsCount int `json:"restrictedContributionsCount"`
ContributionCalendar struct {
TotalContributions int `json:"totalContributions"`
Weeks []struct {
ContributionDays []struct {
ContributionCount int `json:"contributionCount"`
Date string `json:"date"`
} `json:"contributionDays"`
} `json:"weeks"`
} `json:"contributionCalendar"`
} `json:"contributionsCollection"`
@@ -101,6 +107,20 @@ func (c *Client) FetchProfile(login string) (*Profile, error) {
p.TotalCommits = cc.TotalCommitContributions
p.TotalReviews = cc.TotalPullRequestReviewContributions
p.TotalContributions = cc.ContributionCalendar.TotalContributions + cc.RestrictedContributionsCount
// Flatten week → day into a linear daily series sorted by date.
for _, w := range cc.ContributionCalendar.Weeks {
for _, d := range w.ContributionDays {
t, err := time.Parse("2006-01-02", d.Date)
if err != nil {
continue
}
p.DailyContributions = append(p.DailyContributions, DailyContribution{
Date: t,
Count: d.ContributionCount,
})
}
}
}
for _, r := range u.Repositories.Nodes {
+9 -1
View File
@@ -30,7 +30,15 @@ query($login: String!, $after: String) {
totalPullRequestReviewContributions
totalRepositoryContributions
restrictedContributionsCount
contributionCalendar { totalContributions }
contributionCalendar {
totalContributions
weeks {
contributionDays {
contributionCount
date
}
}
}
}
repositories(
first: 100