# Adding a visualization The aveloxis web GUI uses [Chart.js](https://www.chartjs.org/) (loaded from CDN, no build step). Visualizations live inline in `internal/web/templates.go` as Go template strings with embedded `

{{.Repo.Owner}}/{{.Repo.Name}}

{{end}} ` ``` ### Step 3 — add the canvas + the fetch ```go // Add to the chart-row divs:
// Add to COLORS: const COLORS = { commits: '#1f77b4', prsOpened: '#2ca02c', prsMerged: '#9467bd', issues: '#d62728', contributors: '#ff7f0e', // new (matplotlib tab:orange) }; // Add a second fetch (parallel to the existing one is fine — both fire when the page loads): fetch('http://localhost:8383/api/v1/repos/{{.Repo.ID}}/contributor-timeseries?since=2024-01-01') .then(r => r.json()) .then(ts => { makeChart('chart-contributors', 'Active contributors / week', COLORS.contributors, ts.weekly_counts || []); }) .catch(err => console.error('contributor chart load failed', err)); ``` The `makeChart` factory expects an array of `{x: 'YYYY-WW', y: N}` objects — make sure your API response shape matches OR adapt the factory. ### Step 4 — handle the data-shape mismatch The existing `makeChart` factory probably expects a specific shape. Read its implementation: ```js function makeChart(canvasId, label, color, data) { new Chart(document.getElementById(canvasId), { type: 'line', data: { labels: data.map(d => d.week), // expects {week: '2025-W01', ...} datasets: [{ label: label, data: data.map(d => d.count), // expects {count: N} borderColor: color, // ... }], }, options: { /* ... */ }, }); } ``` If your API returns `active_contributors` instead of `count`, either: - **Adapt the response shape** in your REST endpoint to use `count`. Cleanest. - **Map at fetch time**: `.then(ts => makeChart(..., ts.weekly_counts.map(d => ({week: d.week, count: d.active_contributors}))))`. Smaller change but the mapping clutters the template. Preference: API returns the shape the factory expects. The factory is consumed by every chart; matching it keeps the templates simple. ## Patterns to follow ### Color palette The existing charts use matplotlib's "tab10" palette: | Series | Hex | matplotlib name | |---|---|---| | Commits | `#1f77b4` | tab:blue | | PRs opened | `#2ca02c` | tab:green | | PRs merged | `#9467bd` | tab:purple | | Issues | `#d62728` | tab:red | | Contributors (new) | `#ff7f0e` | tab:orange | Stay in the palette. Avoid colors that don't differ enough for colorblind users. ### Chart options Use the existing `makeChart` defaults unless you have a specific reason to deviate. Common deviations: - `type: 'bar'` instead of `'line'` for categorical comparisons. - `stacked: true` on the y-axis for cumulative views. - `scales.y.beginAtZero: true` for counts (don't truncate; it lies about magnitude). ### Comparison page differences If your chart should also appear on `/compare`, that template is separate. The comparison page wraps `makeChart` with a `renderComparisonChart` that handles multiple data series + the mode toggle (Raw / 100% / Z-Score). Search for `renderComparisonChart` in the template file. The contract: each repo's data is a separate dataset in the same chart. The mode toggle is a UI control above the charts that re-renders with normalized values. Implement the normalization in JS. ### Time zones API responses use ISO week numbers (`YYYY-WW`) — already timezone-agnostic. Don't return raw timestamps that the browser interprets in local time; the charts get misaligned across users in different zones. ### Empty data A repo with no data yet (newly added, collection in progress) should render an empty chart with a message, not a blank canvas. The existing pattern: ```js if (!data || data.length === 0) { document.getElementById(canvasId).parentElement.innerHTML = '
No data yet — collection may still be in progress
'; return; } ``` Add `.chart-empty` CSS for the styling. ## What NOT to do - **Don't add a frontend framework.** No React, no Vue, no Svelte, no Alpine. The current setup builds in milliseconds and has zero deploy complexity. Resist. - **Don't move chart code to a separate JS file.** The inline `