diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
index b9e26c042..baf73cb4a 100644
--- a/.github/workflows/pages.yml
+++ b/.github/workflows/pages.yml
@@ -178,7 +178,7 @@ jobs:
print(f"supporters: {len(supporters)}")
PY
- # Publish one themed growth chart for stars and release downloads.
+ # Publish separate light/dark charts for stars and release downloads.
- name: Generate project growth charts
env:
GITHUB_TOKEN: ${{ secrets.OCS_GITHUB_TOKEN }}
diff --git a/README.md b/README.md
index 8db51588f..b55069e7b 100644
--- a/README.md
+++ b/README.md
@@ -209,11 +209,23 @@ Bug reports, focused pull requests, translations, documentation improvements, an
## Project growth
+### Stars
+
-
+
+
+
+
+### Release downloads
+
+
+
+
+
+
diff --git a/scripts/generate-star-history.py b/scripts/generate-star-history.py
index 2840a5a02..31a51752f 100644
--- a/scripts/generate-star-history.py
+++ b/scripts/generate-star-history.py
@@ -162,39 +162,70 @@ def render_svg(
dates: list[datetime],
releases: list[tuple[str, datetime, int]],
theme: str,
+ chart: str,
) -> str:
colors = THEMES[theme]
now = datetime.now(timezone.utc)
- start = min(dates[0], releases[0][1]) - timedelta(days=2)
- end = max(now, dates[-1], releases[-1][1]) + timedelta(days=2)
+ stars = chart == "stars"
+ if stars:
+ points = [(moment, count) for count, moment in enumerate(dates, 1)]
+ title = f"{repository.split('/')[-1]} Stars"
+ subtitle = f"{len(dates)} GitHub stars · Updated {now:%d %b %Y}"
+ legend = "GitHub stars"
+ axis_label = "Stars"
+ line_color = colors["line"]
+ point_color = colors["point"]
+ last_label = f"{len(dates)} stars"
+ else:
+ points = [(published, downloads) for _, published, downloads in releases]
+ total_downloads = sum(downloads for _, _, downloads in releases)
+ title = f"{repository.split('/')[-1]} Release Downloads"
+ subtitle = (
+ f"{total_downloads:,} cleaned asset downloads across {len(releases)} releases "
+ f"· Latest excluded · Updated {now:%d %b %Y}"
+ )
+ legend = "Release downloads"
+ axis_label = "Downloads / release"
+ line_color = colors["download_line"]
+ point_color = colors["download_point"]
+ last_label = f"{releases[-1][0]} · {releases[-1][2]}"
+
+ start = points[0][0] - timedelta(days=2)
+ end = max(now, points[-1][0]) + timedelta(days=2)
duration = max((end - start).total_seconds(), 1)
plot_width = PLOT_RIGHT - PLOT_LEFT
plot_height = PLOT_BOTTOM - PLOT_TOP
- y_top, y_step = nice_axis(len(dates))
- download_top, download_step = nice_axis(max(downloads for _, _, downloads in releases))
+ y_top, y_step = nice_axis(max(value for _, value in points))
def x_position(moment: datetime) -> float:
ratio = (moment - start).total_seconds() / duration
return PLOT_LEFT + ratio * plot_width
- def y_position(stars: int) -> float:
- return PLOT_BOTTOM - (stars / y_top) * plot_height
+ def y_position(value: int) -> float:
+ return PLOT_BOTTOM - (value / y_top) * plot_height
- def download_y_position(downloads: int) -> float:
- return PLOT_BOTTOM - (downloads / download_top) * plot_height
-
- line_parts = [f"M {PLOT_LEFT:.1f} {PLOT_BOTTOM:.1f}"]
- for count, moment in enumerate(dates, 1):
- line_parts.append(f"H {x_position(moment):.1f} V {y_position(count):.1f}")
- line_path = " ".join(line_parts)
- area_path = f"{line_path} V {PLOT_BOTTOM:.1f} H {PLOT_LEFT:.1f} Z"
- download_path = " ".join(
- (
- "M" if index == 0 else "L"
+ if stars:
+ line_parts = [f"M {PLOT_LEFT:.1f} {PLOT_BOTTOM:.1f}"]
+ for moment, value in points:
+ line_parts.append(f"H {x_position(moment):.1f} V {y_position(value):.1f}")
+ line_path = " ".join(line_parts)
+ area = (
+ f''
+ )
+ point_marks = ""
+ else:
+ line_path = " ".join(
+ ("M" if index == 0 else "L")
+ + f" {x_position(moment):.1f} {y_position(value):.1f}"
+ for index, (moment, value) in enumerate(points)
+ )
+ area = ""
+ point_marks = "".join(
+ f''
+ for moment, value in points
)
- + f" {x_position(published):.1f} {download_y_position(downloads):.1f}"
- for index, (_, published, downloads) in enumerate(releases)
- )
grid = []
for value in range(0, y_top + 1, y_step):
@@ -208,18 +239,6 @@ def render_svg(
f'fill="{colors["muted"]}" font-size="12">{value}'
)
- download_axis = []
- for value in range(0, download_top + 1, download_step):
- y = download_y_position(value)
- download_axis.append(
- f''
- )
- download_axis.append(
- f'{value}'
- )
-
x_labels = []
for index in range(5):
moment = start + (end - start) * (index / 4)
@@ -237,30 +256,22 @@ def render_svg(
f'fill="{colors["muted"]}" font-size="12">{escape(label)}'
)
- title = f"{repository.split('/')[-1]} Growth"
- total_downloads = sum(downloads for _, _, downloads in releases)
- subtitle = (
- f"{len(dates)} GitHub stars · {total_downloads:,} cleaned asset downloads across "
- f"{len(releases)} releases · Latest excluded · Updated {now:%d %b %Y}"
- )
- last_x = x_position(dates[-1])
- last_y = y_position(len(dates))
- label_y = max(PLOT_TOP + 16, last_y - 14)
- last_release, last_published, last_downloads = releases[-1]
- last_release_x = x_position(last_published)
- last_release_y = download_y_position(last_downloads)
-
- download_points = "".join(
- f''
- for _, published, downloads in releases
- )
+ last_x = x_position(points[-1][0])
+ last_y = y_position(points[-1][1])
+ if stars:
+ label_x = PLOT_RIGHT
+ label_y = max(PLOT_TOP + 16, last_y - 14)
+ label_anchor = "end"
+ else:
+ label_x = last_x - 28
+ label_y = min(PLOT_BOTTOM - 8, last_y + 20)
+ label_anchor = "end"
return f'''
'''
@@ -306,10 +309,12 @@ def main() -> None:
releases = fetch_release_downloads(args.repository, token)
args.output_dir.mkdir(parents=True, exist_ok=True)
for theme in THEMES:
- output = args.output_dir / f"star-history-{theme}.svg"
- output.write_text(
- render_svg(args.repository, dates, releases, theme), encoding="utf-8"
- )
+ for chart in ("stars", "downloads"):
+ output = args.output_dir / f"{chart[:-1]}-history-{theme}.svg"
+ output.write_text(
+ render_svg(args.repository, dates, releases, theme, chart),
+ encoding="utf-8",
+ )
print(
f"growth history: {len(dates)} stars, "
f"{sum(downloads for _, _, downloads in releases)} downloads"