Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/check-dist.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,18 @@ jobs:
uses: actions/setup-node@v6
with:
node-version-file: .node-version
cache: npm

- name: Setup mise
id: setup-mise
uses: jdx/mise-action@v2

- name: Remove dist/ Directory
id: remove-dist
run: npx rimraf ./dist

- name: Install Dependencies
id: install
run: npm ci
run: bun ci

- name: Build dist/ Directory
id: build
Expand Down
5 changes: 2 additions & 3 deletions .github/workflows/generate-svg.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
uses: jdx/mise-action@v2

- name: Install Dependencies
run: bun install --frozen-lockfile
run: bun ci

- name: Bundle
run: npm run bundle
Expand All @@ -34,9 +34,8 @@ jobs:
uses: ./
with:
username: Orta
text: Orta Therox
text: Github gravity
output-path: deploy/gravity.svg
sample-rate: '8'

- name: Deploy to output branch
uses: peaceiris/actions-gh-pages@v4
Expand Down
62 changes: 62 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Deploy Web Player

on:
workflow_dispatch:
push:
branches:
- main
paths:
- 'src/**'
- 'public/**'
- 'package.json'
- 'bun.lock'
- '.github/workflows/pages.yml'

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
name: Build
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup mise
uses: jdx/mise-action@v2

- name: Install Dependencies
run: bun ci

- name: Build Web Player
run: bun run build:web

- name: Setup Pages
uses: actions/configure-pages@v6

- name: Upload Artifact
uses: actions/upload-pages-artifact@v5
with:
path: dist-web

deploy:
name: Deploy
runs-on: ubuntu-latest
needs: build

environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}

steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ Thumbs.db
# Ignore built ts files
__tests__/runner/*

# Static build of the web player
dist-web

# IDE files
.idea
*.code-workspace
72 changes: 66 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# GitHub Gravity

![CI](https://github.com/actions/typescript-action/actions/workflows/ci.yml/badge.svg)
![CI](https://github.com/flycran/github-gravity/actions/workflows/ci.yml/badge.svg)
![Coverage](./badges/coverage.svg)

Make your GitHub contribution graph fall under gravity, colliding with custom text to produce a beautiful animated SVG.
Expand All @@ -11,6 +11,13 @@ Make your GitHub contribution graph fall under gravity, colliding with custom te

![Gravity Animation](https://raw.githubusercontent.com/flycran/github-gravity/output/gravity.svg)

## Live Preview

Try it in the browser: tweak the options, upload a font file or paste a font URL, then play
the animation back.

<https://flycran.github.io/github-gravity/>

## Inspiration

Inspired by [Orta Therox](https://github.com/Orta). Thanks Orta for your outstanding contributions to the open-source community!
Expand All @@ -21,8 +28,8 @@ Inspired by [Orta Therox](https://github.com/Orta). Thanks Orta for your outstan
- name: Generate Gravity SVG
uses: flycran/github-gravity@v1
with:
username: Orta
text: Orta Therox
username: flycran
text: GitHub Gravity
output-path: gravity.svg
```

Expand All @@ -34,11 +41,61 @@ Inspired by [Orta Therox](https://github.com/Orta). Thanks Orta for your outstan
| `text` | ❌ | `username` value | Text to display as collision obstacles |
| `output-path` | ❌ | `gravity.svg` | Path to write the generated SVG file |
| `font-size` | ❌ | `80` | Font size of the collision text |
| `sample-rate` | ❌ | `4` | Trajectory sample rate (higher = smaller SVG, less smooth animation) |
| `compression-tolerance` | ❌ | `0.5` | Trajectory compression tolerance in SVG pixels. At every original moment, the animated position stays within this distance of the real one. The default fits most cases; `0` is lossless in both position and timing (see below) |
| `compression-algorithm` | ❌ | `Greedy` | Trajectory compression algorithm: `Greedy` (fast), `DP` (smallest SVG), `RDP` (fastest). See below for the trade-offs |
| `shape` | ❌ | `circle` | Shape of contribution blocks: `circle` or `square` |
| `text-top` | ❌ | `50` | Distance from the top of the world to the text |
| `background-color` | ❌ | `transparent` | Background color of the SVG (CSS color value) |
| `text-color` | ❌ | `black` | Color of the collision text (CSS color value) |
| `width` | ❌ | `300` | Width of the physics world. ⚠️ Affects layout and SVG size |
| `height` | ❌ | `150` | Height of the physics world. ⚠️ Affects layout and SVG size |
| `size` | ❌ | `4` | Side length of each contribution block. ⚠️ Affects physics |
| `interval` | ❌ | `1.5` | Spacing between contribution blocks. ⚠️ Affects physics |
| `font-path` | ❌ | bundled font | Custom font file (`.ttf`/`.otf`), relative to the repository root, or an `http(s)` URL. ⚠️ Some fonts may fail to load |

### Reducing file size

The trajectory is compressed by default: `compression-tolerance` (default `0.5`) merges
trajectory points into keyframes so that, at every original moment, the interpolated
position stays within the given distance (in SVG pixels) of the real one. Each keyframe
keeps its original timing via `keyTimes`, so speed changes — bounces, landings, stops —
play back at their real pace.

The default is invisible to the eye and already shrinks the output by orders of magnitude,
so most setups can keep it. Set `0` for a lossless trajectory — only uniform straight
segments and pauses get compressed — or a higher value (e.g. `1.5`) to compress further at
the cost of visibly cutting corners.

### Choosing a compression algorithm

All three algorithms make the same fidelity guarantee; they differ only in how many
keyframes they keep and how long they take. Measured on 360 real trajectories of ~1,000
points each at the default tolerance `0.5`:

| Algorithm | Avg. keyframes | Compression | Runtime | Notes |
|---|---|---|---|---|
| `Greedy` (default) | 32.1 | 30.7x | ~0.2s | Single pass, no backtracking. Within 1–3% of the optimum. |
| `DP` | 31.3 | 31.5x | ~2s | Globally fewest keyframes = smallest SVG. Runtime grows superlinearly with trajectory length. |
| `RDP` | 37.1 | 26.6x | ~40ms | Fastest, keeps the most keyframes. |

In most cases, just keep the default.

### Advanced customization

`width`, `height`, `size` and `interval` change the physics world itself, not just the output
size. The contribution blocks are laid out from `size` and `interval`, the text is centered
with `width`, and the drop height follows `height` — change them together and re-check the
result rather than tweaking one in isolation.

`font-path` swaps the collision font. It accepts a local `.ttf`/`.otf` path or an `http(s)` URL.
The bundled Liberation Serif Bold is used by default because its contours are clean and
reliably triangulated. Custom fonts are risky:

- Contours that are not closed or that self-intersect are silently tolerated by most
renderers, but the triangulation used for the physics collider rejects them.
- Some fonts cannot be parsed by opentype.js at all (common ones such as DejaVu fail on their
GSUB tables).
- Different font metrics usually require re-tuning `font-size` and `text-top`.

### Outputs

Expand Down Expand Up @@ -66,8 +123,8 @@ jobs:
- uses: actions/checkout@v6
- uses: flycran/github-gravity@v1
with:
username: Orta
text: Orta Therox
username: flycran
text: GitHub Gravity
output-path: deploy/gravity.svg
- uses: peaceiris/actions-gh-pages@v4
with:
Expand Down Expand Up @@ -100,6 +157,9 @@ After modifying code under `src/`, re-bundle:
npm run bundle
```

The Action bundle does not include the browser player; that one is built separately with
`bun run build:web`, which writes static assets to `dist-web/`.

### Local Testing

```bash
Expand Down
61 changes: 55 additions & 6 deletions README_zh.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# GitHub Gravity

![CI](https://github.com/actions/typescript-action/actions/workflows/ci.yml/badge.svg)
![CI](https://github.com/flycran/github-gravity/actions/workflows/ci.yml/badge.svg)
![Coverage](./badges/coverage.svg)

让你的 GitHub 贡献图在重力作用下掉落,与自定义文字碰撞,生成精美的 SVG 动画。
Expand All @@ -11,6 +11,12 @@

![Gravity Animation](https://raw.githubusercontent.com/flycran/github-gravity/output/gravity.svg)

## 在线预览

在浏览器里直接试用:调整参数、上传字体文件或填写字体 URL,然后播放查看效果。

<https://flycran.github.io/github-gravity/>

## 灵感来源

本项目灵感来自 [Orta Therox](https://github.com/Orta) 的创意。感谢 Orta 为开源社区做出的杰出贡献!
Expand All @@ -21,8 +27,8 @@
- name: Generate Gravity SVG
uses: flycran/github-gravity@v1
with:
username: Orta
text: Orta Therox
username: flycran
text: GitHub Gravity
output-path: gravity.svg
```

Expand All @@ -34,11 +40,52 @@
| `text` | ❌ | `username` 的值 | 作为碰撞障碍物的文字 |
| `output-path` | ❌ | `gravity.svg` | SVG 输出路径 |
| `font-size` | ❌ | `80` | 碰撞文字大小 |
| `sample-rate` | ❌ | `4` | 轨迹采样率(越大文件越小,动画越不流畅) |
| `compression-tolerance` | ❌ | `0.5` | 轨迹压缩容差(SVG 像素)。播放时每个原始时刻的动画位置与真实位置的偏差不超过该值。默认值适合多数场景;`0` 为完全无损(位置与节奏都精确)(见下文) |
| `compression-algorithm` | ❌ | `Greedy` | 轨迹压缩算法:`Greedy`(快)、`DP`(体积最小)、`RDP`(最快),差异见下文 |
| `shape` | ❌ | `circle` | 贡献方块的形状(`circle` 或 `square`) |
| `text-top` | ❌ | `50` | 文字距离世界顶部的距离,控制文字垂直位置 |
| `background-color` | ❌ | `transparent` | SVG 背景颜色(CSS 颜色值) |
| `text-color` | ❌ | `black` | 碰撞文字颜色(CSS 颜色值) |
| `width` | ❌ | `300` | 物理世界宽度。⚠️ 会影响布局与 SVG 尺寸 |
| `height` | ❌ | `150` | 物理世界高度。⚠️ 会影响布局与 SVG 尺寸 |
| `size` | ❌ | `4` | 贡献方块边长。⚠️ 会影响物理表现 |
| `interval` | ❌ | `1.5` | 贡献方块间距。⚠️ 会影响物理表现 |
| `font-path` | ❌ | 内置字体 | 自定义字体文件(`.ttf`/`.otf`),相对仓库根目录,也可以直接给 `http(s)` 链接。⚠️ 部分字体可能无法正常加载 |

### 减小文件体积

轨迹默认开启压缩:`compression-tolerance`(默认 `0.5`)会把轨迹点合并为关键帧,保证播放时
每个原始时刻的插值位置与真实位置的偏差(SVG 像素)不超过阈值,并通过 `keyTimes` 保留每个
关键帧的原始时间——碰撞、落地、停顿等速度变化都会保留为关键帧,按真实节奏播放。

默认值肉眼不可见,体积已经能小几个数量级,多数场景保留默认即可。设为 `0` 得到无损轨迹——
只有匀速直线段和静止段会被压缩;调大(如 `1.5`)压缩更狠,但轨迹会明显走样。

### 选择压缩算法

三种算法保证相同的保真度,差异只在保留的关键帧数量和耗时。以下为 360 条真实轨迹
(每条约 1000 点)在默认容差 `0.5` 下的实测数据:

| 算法 | 平均关键帧 | 压缩率 | 耗时 | 特点 |
|---|---|---|---|---|
| `Greedy`(默认) | 32.1 | 30.7x | ~0.2s | 单遍贪心不回溯,结果距最优仅 1~3% |
| `DP` | 31.3 | 31.5x | ~2s | 全局最少关键帧 = 体积最小;耗时随轨迹长度超线性增长 |
| `RDP` | 37.1 | 26.6x | ~40ms | 最快,保留的关键帧最多 |

通常保持默认值即可。

### 高级自定义

`width`、`height`、`size`、`interval` 改动的是物理世界本身,而不只是输出尺寸。贡献方块按
`size` 和 `interval` 排布,文字以 `width` 居中,下落高度由 `height` 决定——请成套调整并核对
效果,不要只改其中一个。

`font-path` 用于替换碰撞字体,可以给本地 `.ttf`/`.otf` 路径,也可以直接给 `http(s)` 链接。
默认使用内置的 Liberation Serif Bold,因为它轮廓规整、能稳定三角化。自定义字体有风险:

- 未闭合或自相交的轮廓,大多数渲染器会静默忽略,但物理碰撞体使用的三角化算法会直接报错。
- 部分字体根本无法被 opentype.js 解析(例如很常见的 DejaVu 系列,会因 GSUB 表解析失败)。
- 字体度量不同,通常需要重新调整 `font-size` 和 `text-top`。

### 输出

Expand Down Expand Up @@ -66,8 +113,8 @@ jobs:
- uses: actions/checkout@v6
- uses: flycran/github-gravity@v1
with:
username: Orta
text: Orta Therox
username: flycran
text: GitHub Gravity
output-path: deploy/gravity.svg
- uses: peaceiris/actions-gh-pages@v4
with:
Expand Down Expand Up @@ -100,6 +147,8 @@ npm run test
npm run bundle
```

Action 的打包产物不包含网页版,网页版用 `bun run build:web` 单独构建,静态资源输出到 `dist-web/`。

### 本地测试

```bash
Expand Down
Loading
Loading