← Back to Projects

Derma:code Chart Library

An in-house shared chart library built to implement the product's distinctive visual language.

CompanyGaia Labs
RoleDesign & development
Team1
PeriodOct 2025 - May 2026
StatusGitHub Packages Used by DERMA:CODE V1 & V2

An in-house shared package that brings DERMA:CODE's charts into one library. It draws eight chart types—bar, line, hexagonal radar, bidirectional bar, pie, score, level and gauge—directly with the Canvas API, supported by design tokens and four themes. It is used by DERMA:CODE V1 and V2. I handled design, implementation, testing, release and documentation independently, and shipped 15 versions. I documented the testing process as a blog series.

Why I built it (blog, Korean) →

Problems and solutions

Problem

The gradient overlay on the hexagonal radar, the glass-textured markers and the sequenced animation were things I had already designed in Figma as the designer. The stock features of Recharts, Chart.js and D3 could not reproduce that look and behaviour as designed.

Judgement

Before committing to an implementation, I tried D3, the most flexible option, first. I implemented three charts, hexagonal radar, gauge and score, in both D3+SVG and Canvas and compared the output and the implementation cost.

Solution
Same three charts, DOM node countnodes
D3 + SVG65
Canvas1
Bundle size addedKB
D3 + SVG+42
Canvas0

What the user sees and the rendering speed were almost identical. The implementation cost was not: 65 DOM nodes versus 1, +42KB of bundle versus 0, and 49 to 82% less code in Canvas for the same effects.

With D3, every element's transition had to be controlled separately, and direct DOM manipulation did not sit well with React's rendering model.

Result

I judged Canvas the better fit for this project, left D3 out, and implemented all eight chart types directly with the Canvas API.

↗ Why I dropped D3 (blog, Korean)

Problem

Every new DERMA:CODE screen restyled its charts. The same metric looked different in colour and spacing from screen to screen, and a change meant touching several screens.

We decided to bundle the charts into a shared library, and the first decision was which rendering approach to standardise on.

Judgement
30 charts · 4× CPU throttlingfps · higher is better
Canvas76
SVG36
Recharts11
30 charts · DOM node countnodes
Canvas60
SVG1,320
Recharts1,830

Measured instead of argued. I built a benchmark app with the same chart implemented in Canvas, SVG and Recharts, and compared 1, 5 and 30 charts under normal conditions and with 4× CPU throttling.

With 30 charts under 4× CPU throttling: Canvas 76fps, SVG 36fps, Recharts 11fps. Click response even under normal conditions was Canvas 7.1ms versus SVG 14.66ms. DOM node counts were 60 / 1,320 / 1,830.

Solution

Looking only at the number of charts on today's screens, SVG would have been enough. But a shared library's standard has to consider whether animation and interaction can be controlled through one rendering approach, and what it costs as the chart count grows.

So Canvas became the common rendering approach, and every chart was implemented consistently inside one library.

Result

Along with the performance gains of Canvas, I documented its costs: accessibility handling, retina support, and having to implement all drawing by hand. The library's standard records the maintenance cost, not just the upside.

↗ Canvas vs SVG vs Recharts (blog, Korean)

Problem

Everything looked right in Storybook, but once installed in DERMA:CODE, the real consumer, click areas were off, labels overlapped and layouts broke.

Testing was hard too. jsdom has no Canvas implementation, so getContext('2d') returned null, and Canvas API behaviour such as isPointInPath() could not be verified as is.

Judgement

At first I built a fake context and asserted which APIs were called with which arguments. But logic that depends on Canvas's real geometry, like click detection, could only be mocked so far. Instead of a more elaborate mock, I decided to reduce the logic that depends on Canvas at all.

Solution

Separated the hit-testing logic from Canvas and extracted it into pure functions that take only coordinates and shape data and decide inside or outside, so it can be tested without a Canvas. The remaining rendering is verified with Vitest and a Canvas 2D mock.

Result

The click-area problem is now covered by tests, and hit testing can be unit-tested without a Canvas.

↗ Adding tests, the hard way (blog, Korean)

Problem

Writing the charts as code was not enough for the team to share them. It needed an install path, stable versioned releases, documentation to check usage, and a screen where designers could review the output.

Judgement

Broke the path to real team use into four parts: install, versioning, docs and review.

Solution

Split library, demo and benchmark in a pnpm monorepo, shipped CJS and ESM together with tsup and generated type declarations automatically. Pushing a tag publishes to GitHub Packages.

Defined colour, typography, spacing, opacity and line weight as design tokens, with four presets and per-instance overrides.

Registered every chart and state in Storybook so designers can check the output without touching code.

Result

Shipped 15 versions in total and built more than 50 stories in Storybook. Designers can review directly, and it is in use as the real shared library across internal projects.

Insights and limitations

No retina support

devicePixelRatio is not handled, so lines look blurry on high-density screens. It is the first thing I would fix in this library.

HexaChart is 783 lines

One file draws the gradient, axes, labels, data, overlay and markers. It needs a layered structure that composites offscreen canvases.

Screen readers cannot read it

Canvas content is not in the accessibility tree. ARIA attributes and a fallback table are needed and not yet done. This was a cost I wrote down when choosing Canvas.

Benchmark numbers are relative only

rAF-based measurement is subject to background-tab throttling and vsync timing error. They compare three approaches under the same conditions, not absolute values.

Tech Stack

Canvas APITypeScriptpnpm workspacetsup (CJS+ESM)VitestStorybookGitHub ActionsGitHub Packages