GNU General Public License v3.0

技术解读

以下内容由 GitRead 中文社区 编辑团队基于项目 README 与官方资料整理, 结合中文开发者的常见使用场景补充技术选型建议。具体以项目官方文档为准。

把 GitHub 主页做成一张星图:galaxy-profile 技术拆解

项目背景与痛点

GitHub Profile README 几乎已经被模板化成流水线产物:stats 卡片、奖杯墙、语言占比条,每个人长得都差不多,唯一的区别是数字大小。问题的本质在于,绝大多数动态卡片只是把数据从 API 搬到 SVG 里,并不试图把数据变成属于某个人的标识。

galaxy-profile 的切入点是换一种表达方式:把仓库、star、主语言、活跃度这些元数据当成星体,渲染成一张星系图。仓库是恒星,star 数是星等,主语言决定光谱色,相似技术栈的仓库连成星座。这个比喻之所以成立,是因为你的仓库分布天然就是你独有的,不需要额外编造任何内容。

核心技术揭秘

这类项目的工程重心在于稳定、自动、低成本地画出同一张图,画图本身反而简单。整条链路本质上是一条静态数据管道。

graph TD A[GitHub Actions 定时触发] --> B[GraphQL 拉取用户与仓库数据] B --> C[数据清洗与归一化] C --> D{"内容指纹是否变化?"} D -->|"无变化"| E[跳过本次提交] D -->|"有变化"| F[星图布局引擎] F --> G[星座连边生成] G --> H[确定性伪随机渲染] H --> I[输出 SVG 卡片] I --> J[提交至 profile 仓库] J --> K[README img 引用] K --> L[Camo 代理缓存分发]

1. 确定性伪随机是整个项目的基础

星星的位置绝不能每次渲染都变。如果直接用 Math.random(),Actions 每跑一次你的星系就重排一次,用户刷新两回会看到两张完全不同的图,观感也就立刻崩了。合理做法是把 hash(login + repoName) 当作种子喂给一个轻量 PRNG(mulberry32、xorshift 之类),保证同一个仓库永远落在同一坐标。于是新增仓库只是“天上多一颗星”,原有星图稳定不动。

2. 布局靠数学,不靠手动调参

在固定画布上铺 N 个不重叠的点,本质上是采样问题。最省事的解法是黄金角螺旋(Phyllotaxis):第 i 个点放在半径约 sqrt(i)、角度约 i × 137.5° 的位置,自然均匀、天然无重叠,不需要迭代求解。再叠加对数缩放的半径,大仓库靠近星系中心,小仓库漂在外圈,层次感就有了。这比引入 d3-force 划算得多:力导向要跑几千次迭代,还带回随机性,正好与上面的确定性原则冲突。

3. 星座连线必须克制

连线是这类可视化最容易出问题的地方。仓库一多,全连接就是一张蜘蛛网。稳妥策略是:只在同一主语言的仓库之间连边,每条星取 k 近邻(k≈2~3),再对总边数设上限,按 star 权重裁剪。本质上是把“仓库相似度”当成稀疏图处理,用近邻图或最小生成树,而不是稠密图。

4. 输出 SVG 而非 PNG

这是对 GitHub 运行环境的正确判断。README 里的图片走 camo 代理,SVG 通常只有几十 KB,矢量在任何 DPR 下都清晰;还能内嵌 <style> 做星点闪烁、光晕呼吸的 CSS 动画,用 <img> 引用时依然生效。GitHub 会 sanitize 掉 script,但保留 CSS 动画。代价是渲染兼容性:必须避开 <filter> 高斯模糊这类重效果,否则部分客户端会直接糊成一片。

5. 缓存与新鲜度

camo 会缓存图片。正确解法并不是在 URL 上加时间戳,那样会污染 README;正确做法是让 SVG 文件本身的内容变化,从而更换 cache key。所以工作流里通常先做内容指纹比对,没变化就不提交,避免刷出一堆空 commit。

功能亮点与差异

  • 叙事差异化:同类项目拼“数据维度有多少”,它拼“呈现方式有多独特”。星图是带身份的装饰,而不是计数器。
  • 零服务依赖:不需要 Vercel、不需要自建 API,fork 之后打开 Actions 就能跑,比一堆 SaaS 化卡片服务干净得多。
  • 可复现渲染:同输入同输出,产物可以 diff、可以版本管理。
  • 主题可调:配色、星点密度、星座开关通常通过配置文件暴露,改样式不用动渲染逻辑。

应用场景与落地建议

适合个人 Profile 头部视觉、技术博主主页装饰、组织级 org profile 展示。

落地时注意几点:

  • Token 最小化:只需 read:user 级别的 PAT,存成 repository secret,不要给全权限。
  • 调度频率:日更足够。每小时跑一次既浪费 Actions 额度,也会让仓库 commit 记录变得难看。
  • 产物隔离:生成物推到独立的 output 分支或 assets 目录,主分支保持干净。
  • 无障碍:SVG 内加 <title> / <desc>,<img> 带 alt,别只顾好看。
  • 定位清醒:它是装饰件,不是数据面板,别指望用它分析技术栈。

综合评价

优点很明确:比喻选得准,技术选型克制(纯静态 + 确定性随机),工程链路完整(触发、拉取、渲染、提交、分发闭环),对 GitHub 的平台限制(camo 缓存、sanitize、禁 JS)摸得很清楚。

短板同样明显:信息密度低,一张星图传达不了一个人的技术深度,本质上仍是“好看优先”;SVG 动画在移动端和不同客户端表现不一致;可配置项一旦放开就容易变成参数堆砌,对默认值的审美要求反而更高。这类项目生命周期普遍偏短,GitHub 一改 README 渲染规则或 camo 行为就得跟着修。

一句话总结:它不解决工程问题,它解决的是“凭什么让人在你的主页上多停留三秒”。对这个目标来说,星图是个聪明的答案。

关于

GitHub 原始描述(英文)

Your GitHub profile, reimagined as a galaxy. Auto-generated SVG cards with stars, constellations, and cosmic stats.

描述取自 GitHub 官方 API,保持仓库作者书写的原文。

简要分析

热度分
922
价值分
356
活跃状态
活跃

语言占比

Makefile
Python

Release

贡献者

README

galaxy-profile

License Python

A GitHub profile README generator. It reads your public repositories and your contribution calendar and draws them as four SVG images: a galaxy in which every star is one of your repositories, your year of contributions as a light curve, your languages as a spectrum, and your featured projects as a short catalogue of stars.

Nothing in the galaxy is there for decoration except the dust. Two profiles give two different galaxies.

Preview

A galaxy drawn from the sample profile: one star per repository, one arm per focus area

Contributions over the last year, week by week

Languages of the public repositories and the declared stack

Featured projects

These are generated from sample data (python -m generator.main --demo).

How to read the galaxy

What you see What it is
A star One public repository. Forks and the profile repository itself are left out. Up to 48 are drawn: featured projects first, then by stargazers.
How bright a star is Its stargazers, on a logarithmic scale. Brighter means a wider glow and longer rays, not a bigger disc.
A star in the theme’s accent colour Pushed to in the last month. The brightest of these, and the named ones, have a glow that breathes.
A star in the plain colour Pushed to in the last year.
A hollow ring Dormant: no push for more than a year.
An arm A focus area from your config that has at least one repository. An area with none is not drawn. If no repository matches any area, two unnamed arms are drawn and every star floats between them.
The length of an arm How many repositories it has.
Where a star sits on its arm The order of creation: the oldest near the core, the newest at the tip.
A star between the arms A repository whose code matches none of your focus areas.
A name next to a star Your featured projects and your brightest repository: at most four names.
Dust and the faint stars in the background Nothing. They give the galaxy its body.

A repository joins the focus area whose items account for the largest share of its code, if that share is 15% or more. You can also place it yourself (see the configuration reference).

The other three images:

  • Contributions. Each dot is one week. The curve is a five-week moving average, and the star marks the busiest week. Beside the title, up to three counters.
  • Languages. One band, split by bytes of code across your public repositories that are not forks (the profile repository itself is left out). Below it, the stack you declare in your focus areas: each area’s items on up to two lines, cut with an ellipsis when there are more.
  • Featured projects. Up to three, brightest first, lettered α, β, γ the way stars are named within a constellation. Each is drawn with the same star it has in the galaxy.

Themes, phones and motion

Each image is written in four files:

File For
galaxy-header.svg GitHub’s dark theme, wide screens. This is the default.
galaxy-header-light.svg GitHub’s light theme.
galaxy-header-mobile.svg Dark theme, narrow screens: a layout of its own, 390 units wide, with type you can read on a phone.
galaxy-header-mobile-light.svg Light theme, narrow screens.

README.profile.md wraps each image in a <picture> element so the visitor’s browser picks the right file. If you would rather keep things simple, use the <img> line alone and everyone gets the default.

There are two palettes, and you choose one for each theme:

  • deep-sky: a night sky, with stars in their real colours (blue for the active ones, warm for the dormant).
  • cyanotype: two inks, Prussian blue and paper white, with one warm colour for what is alive.

The images move. The galaxy gathers out of a scattered field of stars, then its dust streams along the arms while the spiral itself stays still; the contribution curve draws itself and a point of light runs along it; a sweep of light opens the language band. All of it is CSS animation inside the SVG, and:

  • visitors whose system asks for reduced motion get still images, complete;
  • motion: false in the config writes still images for everyone;
  • an image that is off screen has its clock paused by the browser, so the entrance plays when you scroll to it (checked in Chrome).

A tool that captures an image at the very first instant of its animation (some screenshot and link-preview services) catches the entrance before the text has faded in. If you need captures, generate with motion: false.

Text is drawn as outlines of Spectral, so it looks the same in every browser and needs no font on the visitor’s side. Text the font does not cover (CJK, emoji and others) falls back to the system’s serif.

Quick Start

  1. Fork this repo (or use “Use this template”) and rename it to your GitHub username.
  2. Create your config, by hand or with the wizard:
    cp config.example.yml config.yml        # then edit it
    # or
    pip install -r requirements.txt
    python -m generator.main init
    
  3. Replace README.md with the contents of README.profile.md and customize the social links.
  4. Commit config.yml and push. The GitHub Action generates the images and commits them.

This repo ships with --demo mode enabled in the GitHub Action so it works out of the box with sample data. After creating your config.yml, edit .github/workflows/generate-profile.yml and change the generate step from

run: python -m generator.main --demo

to

run: python -m generator.main

so it reads your real profile.

The first run may need a manual trigger: Actions > “Generate Profile SVGs” > “Run workflow”.

Configuration Reference

All configuration lives in config.yml. See config.example.yml for a commented template.

Key Description
username Your GitHub username (required).
profile name (required), tagline and philosophy are drawn in the header. company, location and bio are not drawn; they are kept for your own reference.
social Email, LinkedIn handle, website URL. Not drawn either: write the links in your README.
galaxy_arms Your focus areas. Each has a name, a list of items (languages and tools) and, optionally, repos: repository names to place on that arm whatever their languages say.
projects Featured repositories. Each has repo (owner/name; another owner’s repository is fine), an optional description (defaults to the repository’s own) and an optional arm (index of a focus area, from 0) to place it yourself.
theme.dark, theme.light deep-sky or cyanotype, chosen separately for GitHub’s dark and light themes. Default: deep-sky for both.
theme.<colour> Optional hex colours that adjust the dark palette: void (background), text_bright (text), text_dim (secondary text), text_faint (hairlines), synapse_cyan (active stars), axon_amber (dormant stars); these last two and dendrite_violet also recolour the dust, in the order the palette lists its dust colours. A colour equal to its version 1 default is ignored.
motion true (default) or false for still images.
stats.metrics Which counters go beside the contribution chart, in order: stars, prs, issues, repos. Up to three are shown. commits is accepted and is the chart’s own title.
languages Languages to exclude and how many to show (max_display, up to 20).

Migrating from version 1

An existing config.yml works without changes, and the four file names you already reference are still written. What you will notice:

  • The images have new sizes. The header is 850×430 (it was 850×280), contributions 850×254 (850×120 without a calendar), languages 850×226 or taller, featured projects 850×214 (850×110 with none). An <img> with width="850" and no height adapts by itself.
  • Twelve new files appear in assets/generated/ next to the four you had. To use them, replace the image lines in your README with the <picture> blocks from README.profile.md. Run the generator once before you do, so the files exist.
  • The galaxy is drawn from your repositories now. Which arm a repository sits on follows its languages; use galaxy_arms[].repos or projects[].arm to decide it yourself. A focus area with no repository is not an arm, though it still shows in the languages image.
  • galaxy_arms[].color no longer has an effect, and the legend is gone: arms are told apart by their names, written along them.
  • The nine theme colours are now adjustments to the dark palette. A colour left at its old default is ignored, so a config copied from the old example gets the new palette as designed. nebula and star_dust painted card backgrounds and borders, which no longer exist.
  • The first counter of the old stats card, commits, is now the title of the contribution chart: contributions in the last year, from your contribution calendar.
  • The contribution calendar needs a token. In GitHub Actions the workflow’s own GITHUB_TOKEN is enough. Without one, that image shrinks to the counters alone.
  • Language shares are computed over your 100 most-starred public repositories (the 40 most-starred when there is no token), and the profile repository itself no longer counts.
  • If GitHub cannot be reached or answers with an error, the run now fails and leaves the previous images in place. Version 1 wrote images full of zeros.

Local Development

git clone https://github.com/YOUR_USERNAME/galaxy-profile.git
cd galaxy-profile
make install        # creates .venv and installs the development requirements
make test
make demo           # writes the sixteen sample images to assets/generated/

Without make:

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements-dev.txt
python -m generator.main --demo
pytest

Generating from a real profile:

cp config.example.yml config.yml                           # and edit it
GITHUB_TOKEN=ghp_your_token_here python -m generator.main

To create a token: github.com/settings/tokens > Generate new token (classic) > select the read:user scope. Without a token the generator uses the public REST API: no contribution calendar, languages read for your 40 most-starred repositories only, and at most 60 requests an hour. If that limit is reached the run stops and says so.

To look at the result, open any file in assets/generated/ in a browser.

The tests need no network and no token. They render every image in every variant from the sample data and check, among other things, that nothing is invisible once the animation has played, that every text stays inside its image, that the same input gives the same bytes, and that each file stays under its size budget.

How the GitHub Action Works

The workflow (.github/workflows/generate-profile.yml) runs:

  • every 12 hours;
  • on push, when config.yml or generator/** change;
  • manually, from the Actions tab.

It generates the images and commits them with the message chore: update profile SVGs [skip ci]. The output only changes when your data does, so most runs commit nothing.

Troubleshooting

  • “Could not read the profile from GitHub”. The token is invalid or expired, the username is wrong, GitHub answered with an error, or the rate limit was reached without a token. The message says which. The previous images are kept.
  • The contribution image shows only numbers. The run had no token, so there was no calendar to draw.
  • A repository is on the wrong arm, or on none. List it under repos in the focus area where it belongs. A name that matches no star is reported in the log.
  • A repository is missing from the galaxy. Forks are left out unless you feature them in projects, and only the 48 brightest are drawn.
  • Text looks different from the rest. It has characters Spectral does not cover and was drawn in the system’s serif.

Contributing

  1. Bug reports: open an issue with steps to reproduce and what you expected.
  2. Feature suggestions: open an issue describing the feature and why it is useful.
  3. Pull requests: fork, branch from main, run make test, look at the images in a browser, and open a PR with a clear description.

Each image is one module in generator/plates/: a pure function of its data, a theme and two flags. tests/contract/ holds the rules every image must keep in every variant; a new image gets them by being listed in generator/build.py. When a change to the output is intended, UPDATE_GOLDEN=1 pytest tests/contract -k digest records the new reference and make demo refreshes the previews.

Architecture

generator/
├── main.py         # Command line: config in, sixteen SVG files out
├── cli_init.py     # The `init` wizard
├── config.py       # Config validation and defaults
├── data.py         # GitHub GraphQL and REST, and the sample data: one snapshot of a profile
├── model.py        # What each image shows: arms and stars, weekly series, language shares, featured projects
├── build.py        # Every image in every variant
├── themes.py       # The two palettes, dark and light
├── typeset.py      # Text as glyph outlines: measure, set on a line, set along a curve
├── svg.py          # Shared pieces: particles, the repository star, the travelling light, the frame
├── motion.py       # The motion switch and the animation catalogue
├── fonts/          # Glyph outlines and metrics, built by tools/build_font_atlas.py
└── plates/
    ├── galaxy.py         # The galaxy header
    ├── contributions.py  # The contribution light curve
    ├── languages.py      # The language band and the declared stack
    └── featured.py       # Featured projects

Credits

Type is Spectral by Production Type, with Greek letters from Noto Serif, both under the SIL Open Font License (see assets/fonts/).

评论

暂无评论