This site uses one functional cookie to keep feature rollouts consistent for you. Nothing is set until you choose. See the privacy notice.
Dev notes
This app drew its charts with recharts and unovis, both React-only, so nothing carried over to the Angular side of the design system. I rebuilt the core chart types as pure SVG computed from one dependency-free geometry core, so React and Angular render identical output and no charting runtime enters the published packages.
This app drew every chart with recharts and @unovis. Both are React-only. The design system I'd been building has an Angular package too, and it had no charts at all — the charting library was the coupling. Any chart I wanted in both frameworks was stuck behind a React-only runtime that drew it. On top of that, bundling a charting library into a published design system package drags a heavy dependency into every consumer, whether they render a chart or not.
The fix was to pull the math out of the rendering. I wrote a chartGeometry core with zero dependencies: it takes the data and the dimensions and returns plain numbers — point arrays for lines, rectangles for bars, arc paths for a donut. No React, no DOM, nothing framework-specific. It's just a function from data to coordinates.
Each framework then renders those numbers into SVG. React and Angular consume the same geometry and emit the same markup — the only difference is the templating syntax. Because the output is plain SVG computed by hand, no charting runtime enters the published packages at all.
I rebuilt the core chart types and shipped each one in both @paul-portfolio/react and @paul-portfolio/angular:
A new chart.css ships a token-driven --paul-chart-1..6 palette. A consumer overrides those custom properties and every chart re-skins with it — nothing is hard-coded.
The classic trap with "shared" logic that isn't truly shared is drift: someone edits one copy and the other silently rots. The geometry went RED-first — 20 tests — and I mirrored the exact same suite in both packages. If the React copy changes and the Angular one doesn't, a test goes red. The tests are the contract that keeps the two implementations honest without a build-time link between them.
SVG charts are usually an accessibility black hole. Every chart here is role="img" with a data summary as its accessible name, so a screen reader announces something useful instead of nothing. Colour is never the only signal, and axe tests confirm there are no violations. The React components were tested with Testing Library and axe before the implementation existed.
The charts were the headline, but while I was in the Angular package I closed a bit more of the React-to-Angular gap. I ported four existing React components whose CSS already shipped — PaulDivider, PaulSpinner, PaulIconButton, and PaulSwitch — and gave the Angular package its first vitest config and geometry tests. It had no test runner at all before this.
The order was strict: geometry RED-first (20 tests, mirrored in both packages), then the React components (Testing Library + axe), then the implementation. The full workspace lands green — react 184, angular 20, tokens 4. Version bumps and the CHANGELOG rode in the same PR, and I added Storybook stories for all three charts with a preview rendered from the real geometry.
I kept the scope honest rather than trying to draw everything at once. Deferred at the time, and disclosed in the PR: the specialty gallery charts (funnel, radar, scatter, cohort heatmap, pareto, gauge, word cloud, stacked and multi-series line), multi-series Sparkline, the remaining Angular ports (Textarea, Select, FilterBar, InfoTip, Ticker, TiltCard, GradientBackground, Spotlight), and Angular render tests — there was no TestBed infrastructure in that package, so the geometry was unit-tested and React was the tested reference for the rendered contract.
Every item on that list shipped in the follow-up work: the specialty gallery, multi-series Sparkline, the last eight Angular ports, and the TestBed infrastructure the render tests were waiting on. I am leaving the paragraph above as it was written rather than editing it into looking prescient, because the interesting part is what happened next.
The TestBed work paid for itself on its first probe, which failed. Mount a component, set an input, assert the render — the template rendered and setting the input did nothing. The Angular package was built with plain tsc, so what it published was raw decorators with no compiled component definitions, and every component in it uses a signal input, which needs the compiler. Any consumer binding an input got nothing back, silently, for as long as the package had existed. Nothing caught it because every test imported source files and nothing consumed the build.
That is the sharper version of the lesson this page already ends on. Deferring the Angular render tests was a reasonable call at the time, and it was also the reason a published package could be broken for months without anyone noticing. The cost of a deferral is not the feature you did not build; it is the class of bug you left yourself unable to see.
role="img" plus a data summary turns a silent graphic into something a screen reader can announce. It costs almost nothing and it's easy to assert with axe.