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 + - Open CAD Studio stars and release downloads + Open CAD Studio star history + + + +### Release downloads + + + + + + Open CAD Studio release download history 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''' {escape(title)} {escape(subtitle)} - + @@ -269,24 +280,16 @@ def render_svg( {escape(title)} {escape(subtitle)} - - GitHub stars - - - Release downloads - Stars - Downloads / release + + {legend} + {axis_label} {''.join(grid)} - {''.join(download_axis)} {''.join(x_labels)} - - - - {download_points} - - {len(dates)} stars - - {escape(last_release)} · {last_downloads} + {area} + + {point_marks} + + {escape(last_label)} ''' @@ -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"