Files
ghstats/internal/card/card.go
T
tiennm99 d0d3862780 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.
2026-04-18 21:20:52 +07:00

50 lines
1.3 KiB
Go

// Package card renders Profile data into SVG cards on disk.
package card
import (
"fmt"
"os"
"path/filepath"
"github.com/tiennm99/ghstats/internal/github"
"github.com/tiennm99/ghstats/internal/theme"
)
// Card renders one SVG for a Profile under the given theme.
type Card interface {
// Filename is the on-disk basename (e.g. "0-profile-details.svg").
Filename() string
// SVG returns the rendered SVG bytes.
SVG(p *github.Profile, t theme.Theme) ([]byte, error)
}
// allCards is the ordered list rendered by RenderAll.
// Keep filename prefixes numeric so the output directory lists in a predictable order.
var allCards = []Card{
profileCard{},
reposPerLanguageCard{},
mostCommitLanguageCard{},
statsCard{},
productiveCard{},
contributionsCard{},
}
// RenderAll writes every card into outDir/<themeID>/.
func RenderAll(p *github.Profile, t theme.Theme, outDir string) error {
dir := filepath.Join(outDir, t.ID)
if err := os.MkdirAll(dir, 0o755); err != nil {
return fmt.Errorf("mkdir %s: %w", dir, err)
}
for _, c := range allCards {
data, err := c.SVG(p, t)
if err != nil {
return fmt.Errorf("render %s: %w", c.Filename(), err)
}
path := filepath.Join(dir, c.Filename())
if err := os.WriteFile(path, data, 0o644); err != nil {
return fmt.Errorf("write %s: %w", path, err)
}
}
return nil
}