Skip to content

feat: health score report chart type redesign IN-1304 - #2204

Merged
gaspergrom merged 4 commits into
mainfrom
fix/IN-1304-chart-conversions
Sep 15, 2026
Merged

gaspergrom merged 4 commits into
mainfrom
fix/IN-1304-chart-conversions

Conversation

@gaspergrom

@gaspergrom gaspergrom commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Converts five report widgets to new chart types per design feedback, and adds a reusable donut chart config builder (app/components/uikit/chart/configs/pie.chart.ts) alongside the existing bar/heatmap/gauge builders.

  • Health score bands (band-distribution.vue): horizontal bars → vertical grouped bars. Scope tabs (All/Linux Foundation/Other) still work unchanged.
  • How projects are maintained (lifecycle-distribution.vue): horizontal bars → donut chart, colored with the lifecycle-stage hexes already used on the collection page (collection-lifecycle-badge.vue), rendered in the fixed stage order from PR fix: health score report copy, titles, and lifecycle ordering IN-1304 #2203. unavailable (not in that badge's map) falls back to a neutral gray since the donut needs to render all 7 stages.
  • Where the strongest repositories pull ahead (signal-scores.vue): three separate grouped bar charts (one per category) → a single heatmap by percentile. Rows are all 11 signals grouped by category (fixed order), columns are "Top 20% of repositories" and "Typical repository, the median", color intensity encodes the percentage on one shared blue scale. This also resolves the original inconsistent coloring (only the Top-20% series carried category color, median was always gray) — color now consistently encodes value, not category.
  • Repositories we can score, by category (category-coverage.vue): horizontal bars → donut chart, reusing the same category color scheme as the signal-scores/signal-availability widgets. Label shows percentage (donuts read most naturally as proportions); raw count stays in the tooltip.
  • Security coverage on GitHub-hosted repositories (github-security-funnel.vue): this widget turned out to be a table (<lfx-table>), not a horizontal bar chart as the design feedback assumed — verified directly against the file before converting. Converted to a vertical grouped bar chart (stage on the x-axis, Linux Foundation vs other projects as two series), which satisfies the same intent (more chart variance, easier to scan) as the other conversions.

Known/accepted limitation

"Signal availability inside and outside the Linux Foundation" (signal-availability-lf.vue) is not part of this PR — no chart-type change there per the design feedback. It still shows a binary Linux-Foundation-vs-Other comparison rather than a full distribution, because the underlying Tinybird pipe only returns one aggregate percentage per scope. A true distribution chart would need a backend/pipe change, out of scope here. See PR #2204 for that widget's color update.

Test plan

  • pnpm tsc-check — passes
  • pnpm lint:fix — 0 errors (pre-existing unrelated warnings only)
  • pnpm test — all assertions pass
  • Manually verified in the running app: every widget renders its new chart type, tooltips work (hover-tested band chart, donut charts, heatmap, and the new grouped bar chart), scope tabs re-render both the vertical bars and the donut correctly, no console errors
  • Screenshots in attachments/IN-1304/

Deploy order

N/A — frontend-only change, no backend/pipe dependency for the chart-type conversions themselves.

Migration checklist

N/A — no database changes.

Notes

Stacked on #2203 (fix/IN-1304-copy-and-structure) because the lifecycle donut's slice order depends on that PR's lifecycle stage sort-order fix — the donut renders rows in whatever order the server mapper returns, so it needs the fixed-order fix to render correctly. Targets that branch, not release/IN-1304-health-score-report-feedback, directly — merge order matters.

Renames the report to "Open Source Health Score" (page title, meta
description, and view heading), updates the two section headers with
eyebrow tags per design feedback ("How healthy are open source
projects?" / health score distribution, "Which signals are available?"
/ health score availability), and fixes the lifecycle widget's stage
order to follow the fixed active/stable/declining/inert/abandoned/
archived/unavailable sequence instead of sorting by project count,
which caused stages to jump around as counts changed.

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Converts five report widgets to the chart types from the design
feedback, adding a new donut chart config builder alongside the
existing bar/heatmap/gauge builders:

- Health score bands: horizontal bars -> vertical grouped bars
- How projects are maintained: horizontal bars -> donut, colored with
  the same lifecycle-stage hexes already used on the collection page
  (collection-lifecycle-badge.vue), rendered in the fixed stage order
- Where the strongest repositories pull ahead: three grouped bar
  charts -> a single heatmap by percentile (Top 20% vs median per
  signal), replacing the previous per-category/gray bar coloring with
  one consistent value-based color scale
- Repositories we can score, by category: horizontal bars -> donut,
  percentage-primary label with raw count in the tooltip
- Security coverage on GitHub-hosted repositories: this widget turned
  out to be a table, not a horizontal bar chart as the design feedback
  assumed. Converted it to a vertical grouped bar chart (stage on the
  x-axis, Linux Foundation vs other projects as two series), which
  satisfies the same "more chart variance" intent as the other
  conversions.

Also includes the axis-label and grid adjustments needed to keep the
new heatmap's row labels and the new grouped-bar chart's x-axis labels
legible, since both are new chart configs rather than edits to
existing ones.

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Redesigns five Health Score report visualizations and adds a reusable donut-chart configuration.

Changes:

  • Converts distribution widgets to grouped bars and donut charts.
  • Replaces signal-score charts with a consolidated heatmap.
  • Converts the security table to a grouped bar chart.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
band-distribution.vue Uses vertical grouped bars.
lifecycle-distribution.vue Uses a lifecycle donut chart.
signal-scores.vue Consolidates signals into a heatmap.
category-coverage.vue Uses a category coverage donut.
github-security-funnel.vue Replaces the table with grouped bars.
pie.chart.ts Adds the shared donut configuration.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +106 to +109
const donutData: DonutChartData[] = rows.map((row) => ({
name: CATEGORY_LABELS[row.categoryKey] ?? row.categoryKey,
value: row.scored,
color: CATEGORY_COLORS[row.categoryKey] ?? lfxColors.brand[500],
Comment on lines +46 to +50
<client-only>
<lfx-chart
:config="chartConfig"
:animation="true"
/>
Comment on lines +137 to 141
left: 320,
right: '5%',
top: 4,
bottom: 4,
top: 24,
bottom: 56,
containLabel: false,
inRange: {
color: [lfxColors.neutral[200], lfxColors.brand[200], lfxColors.brand[500], lfxColors.brand[700]],
},
text: ['Distinguishes strongly', 'Barely distinguishes'],
Comment on lines +41 to +43
* Get donut chart config. Renders a single-series donut (pie with an inner radius) with each
* slice colored per `DonutChartData.color`. Tooltip and label show name, value and percent, per
* ECharts' default pie behavior.
The comment claimed this widget's category colors matched signal-scores.vue
and signal-availability-lf.vue, but both were changed to a heatmap
value-scale and an LF/non-LF blue-purple convention respectively. Update
the comment to reflect that this widget's category coloring now stands on
its own.

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copilot AI review requested due to automatic review settings September 15, 2026 12:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (4)

frontend/app/components/modules/report/health-score-coverage/components/category-coverage.vue:114

  • These three counts overlap and each uses reposTracked as its denominator, so they cannot be slices of one pie. ECharts instead computes {d}/percent against the sum of all three counts: with the existing fixture, Maintainer Health is 20,665 / 31,593 = 65.4%, but this donut reports 20,665 / 52,857 = 39.1%, producing the contradictory tooltip 39.1% (20,665 of 31,593). Please represent each category independently against reposTracked (for example, separate rings or bars) rather than as mutually exclusive slices.
  const donutData: DonutChartData[] = rows.map((row) => ({
    name: CATEGORY_LABELS[row.categoryKey] ?? row.categoryKey,
    value: row.scored,
    color: CATEGORY_COLORS[row.categoryKey] ?? lfxColors.brand[500],
  }));

  return getDonutChartConfig(donutData, {

frontend/app/components/modules/report/health-score-coverage/components/signal-scores.vue:137

  • The plot reserves a fixed 320 px left margin, but this full-width card has no minimum width or horizontal overflow. On common 320–390 px mobile viewports, the card's inner width leaves the two-column heatmap only a few pixels (or a negative grid width after the 5% right margin), so the cells and column labels cannot render legibly. Please add a scrollable minimum-width chart wrapper or responsive grid/axis-label settings.
      left: 320,

frontend/app/components/modules/report/health-score-coverage/components/github-security-funnel.vue:50

  • Replacing the table with LfxChart removes the only programmatic access to each stage's counts and percentages. frontend/app/components/uikit/chart/chart.vue:7-15 renders only the ECharts view, and this config provides no semantic table/ARIA data, so keyboard and screen-reader users can no longer retrieve the values exposed by the former table. Keep an accessible data table or equivalent semantic summary alongside the visual chart.
        <client-only>
          <lfx-chart
            :config="chartConfig"
            :animation="true"
          />

frontend/app/components/modules/report/health-score-coverage/components/signal-scores.vue:184

  • Color intensity represents each cell's absolute score percentage, not its discriminating power; only the difference between the two cells in a row indicates discrimination. Labeling the high end Distinguishes strongly therefore misstates what the scale encodes (for example, two equally high cells barely distinguish). Label the endpoints as higher/lower score percentages instead.
      text: ['Distinguishes strongly', 'Barely distinguishes'],

@gaspergrom
gaspergrom requested a review from epipav September 15, 2026 13:08
@gaspergrom
gaspergrom changed the base branch from fix/IN-1304-copy-and-structure to main September 15, 2026 13:08
Copilot AI review requested due to automatic review settings September 15, 2026 15:31
@gaspergrom
gaspergrom merged commit 7fddc75 into main Sep 15, 2026
11 checks passed
@gaspergrom
gaspergrom deleted the fix/IN-1304-chart-conversions branch September 15, 2026 15:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (4)

frontend/app/components/modules/report/health-score-coverage/components/category-coverage.vue:114

  • These category counts overlap: a repository can be scored in multiple categories, so they are each percentages of reposTracked, not parts of one whole. ECharts computes donut percentages against the sum of row.scored values (for the existing test fixture this produces 39.1%/37.7%/23.2% instead of 65.4%/63.0%/38.9%), making both slice sizes and the displayed “of repos tracked” percentages incorrect. Keep independent bars/progress indicators or use another visualization that preserves scored / reposTracked.
  const donutData: DonutChartData[] = rows.map((row) => ({
    name: CATEGORY_LABELS[row.categoryKey] ?? row.categoryKey,
    value: row.scored,
    color: CATEGORY_COLORS[row.categoryKey] ?? lfxColors.brand[500],
  }));

  return getDonutChartConfig(donutData, {

frontend/app/components/modules/report/health-score-coverage/components/github-security-funnel.vue:50

  • This removes the widget’s semantic table and replaces it with a canvas chart. LfxChart renders only VChart and does not provide a data table or equivalent summary, so screen-reader users lose access to every stage, count, and percentage. Retain an accessible table (it may be visually hidden) or provide an equivalent structured summary alongside the chart.
        <client-only>
          <lfx-chart
            :config="chartConfig"
            :animation="true"
          />

frontend/app/components/modules/report/health-score-coverage/components/signal-scores.vue:137

  • A fixed 320 px left grid leaves no usable plot area on supported narrow layouts: this full-width card can be about 288–343 px wide on a phone, before the 5% right margin is applied. The heatmap cells therefore collapse or disappear. Make the label/grid width responsive, abbreviate labels on small screens, or place the chart in a horizontally scrollable minimum-width container.
      left: 320,

frontend/app/components/modules/report/health-score-coverage/components/signal-scores.vue:184

  • The visual map encodes the absolute score percentage, not how strongly a signal distinguishes repositories. A high median and nearly identical p80 value is colored toward “Distinguishes strongly” even though their gap is small, contradicting the explanatory copy. Label this scale in terms of score magnitude; distinction strength is communicated only by comparing the two cells.
      text: ['Distinguishes strongly', 'Barely distinguishes'],

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants