mcpbeat

Reactor Charts

microsoft/reactor-charts

Reactor charts and data visualization — choosing a chart type (line, bar, area, donut, `TreeChart`, `ForceGraph`), the chart DSL, `LabelView` / `XTickLabelView` / `YTickLabelView` for icon-plus-text and rich labels, and the visualization-best-practices rules. Use when adding charts or designing visualizations.

4k tokens
context cost
the whole folder, loaded on every use
1
files
instructions only
0
copies elsewhere
how many repositories repackaged it
604
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/microsoft/microsoft-ui-reactor --skill reactor-charts

The instruction itself

13 sections, as written by the author

Reactor Charts

The chart surface lives in Reactor.D3.Charts (full guide:

docs/guide/charting.md). This skill is the AI-agent quick reference —

chart-choice rules, the new *View extension points, and the a11y/accuracy

rails you should keep on by default.

Choose the right chart first

Most "my chart looks bad" problems are wrong-chart problems, not styling

problems. Cleveland & McGill's perceptual ranking (1984) — accuracy of

graphical judgment, best to worst:

> position on a common scale > position on non-aligned scales > length /

> direction / angle > area > volume > shading / color saturation

That ordering is why bars and dots win and why pies lose. Translate it into

rules of thumb:

| Chart | Use when | Avoid when |

|---|---|---|

| Bar | Discrete categories, time buckets (Q1..Q4), ranked comparison. Default when in doubt. | (Use a different chart if the data is continuous, e.g. dense time-series — line is clearer.) |

| Line | Continuous data, many x-values, trends/slopes, autocorrelated series (prices, sensor readings). | Few categorical observations — use bar. |

| Area | Cumulative magnitude or parts-of-whole over time. | Series cross frequently — overlap occludes; switch to line. |

| Pie | Parts-of-a-whole and ≤5 slices and large differences and proportions land near recognizable fractions (½, ¼, ⅓). | Anything else. Ranking, change-over-time, negative values, precise comparison — use a sorted bar chart. |

| Donut (PieChart with InnerRadius > 0) | Same conditions as pie. The inner hole gives you a place for a center label (total, current selection) — useful when the chart sits inside a dashboard. | Same as pie. The hole doesn't fix pie's ranking weakness. |

| Tree (TreeChart) | Hierarchical data: org charts, file/folder structures, taxonomies, decision trees. Visualizes parent→child links. | Wide hierarchies (>~30 nodes per level) — the tree gets unreadable; consider a treemap (use the D3.Layout.Treemap primitive — no top-level factory yet). |

| Force graph (ForceGraph) | Relationship networks: dependency graphs, social graphs, knowledge graphs. Shows which nodes cluster. | Hierarchical data — use TreeChart (an explicit layout always reads better when one exists). Static print/snapshot output — force layouts converge differently each run. |

When the user asks for a pie chart, push back unless all four pie conditions

hold. A sorted bar chart usually wins. (Few/Tufte say "almost never pie";

Cairo/Wilke allow the narrow case above. Default to "no" and concede when

the conditions are met.)

Charts not yet exposed as top-level factories: Sankey, Treemap, Cluster

dendrograms, partition/sunburst, stratified hierarchies. The math primitives

exist in src/Reactor.Advanced/Charting/D3/Layout/ (Sankey.cs, Treemap.cs,

Cluster.cs, Stratify.cs, TreeLayout.cs) and can be composed with

D3Canvas + the d3 shape generators. If a user asks for one of these and

isn't comfortable assembling D3 primitives directly, suggest filing a

factory request rather than hand-rolling — the resulting chart will lack

the shared accessibility surface.

The DSL — quick reference

LineChart (data, x, y)              // continuous line
BarChart  (data, x, y)              // vertical bars (always anchored at zero — see pitfalls)
AreaChart (data, x, y)              // filled area
PieChart  (data, value)             // slices summing to 100%
PieChart  (data, value)             // donut variant: chain .InnerRadius(40+)
    .InnerRadius(60)
TreeChart (root, children)          // hierarchical (org chart, file tree, taxonomy)
ForceGraph(nodes, links)            // network graph (dependencies, relationships)

Common fluent knobs (chain-call any subset):

.Title("Monthly Revenue")
.SeriesName("Revenue")
.Units(xUnits: "months", yUnits: "USD")
.AxisLabel(ChartAxisType.X, "Month")
.AxisLabel(ChartAxisType.Y, "Revenue (USD)")
.Width(600).Height(250)
.Stroke("#0078D4").StrokeWidth(2.5)
.Fill("#50C878")                                  // bars / area / pie slice baseline
.ShowGrid(true).ShowAxes(true)
.DataLabel((point, idx) => $"{point.Revenue:C}")  // string label per point
.Palette(ChartPalette.Categorical)                // Tier 1 curated palette (a11y-scanned)
.ChartBackground("#1E1E1E")                        // declare active bg → scopes A11Y_CHART_011

AxisLabel text and Title are reserved for *labeling the chart*, not

labeling individual data points or ticks — for those, see the next two

sections.

Make D3 do the work

Charts compose D3-style scales and generators internally. When you reach

under the DSL hood (or hand-roll Canvas drawings), follow these:

  • Scales map domain → range — never compute pixel positions by hand. A

LinearScale from [min, max] to [plotLeft, plotRight] is the boundary

between "data" and "pixels". Mixing them is the #1 source of "off by 5px"

bugs.

  • Pick the right scale type for the data. Linear for quantitative,

log for exponential, time for dates, ordinal/band for categories. Linear-

on-exponential hides patterns; log-on-linear inflates them.

  • Always .nice() quantitative domains. Axis bounds should round to

human-friendly numbers (0/100/200, not 13.7/187.4). Pair with .ticks(n)

as a *suggestion*, not a hard count — D3 picks readable intervals.

  • Reserve margins. Long y-axis labels and multi-line tick *View

elements need gutter. The Bostock margin convention

({top, right, bottom, left}) is the safe default; if you customize tick

labels (next section), measure the longest one and grow the margin.

  • Don't reinvent the axis. The built-in axis generator handles tick

selection, formatting, alignment, label rotation. Custom tick code is

where charts go to die.

When string labels aren't enough — *View

Three cases the string-label APIs (DataLabel, AxisLabel, LabelAccessor)

can't handle:

  • The label needs an icon next to or inside the text.
  • The label needs multi-line text, wrapping, or different colors /

weights per fragment.

  • The label needs to render a mini sub-tree (badge, sparkline, mini

button, anything Reactor can build).

For these, reach for the *View extensions. They take a render delegate and

substitute its returned Element for the built-in TextBlock at the same

anchor position:

// Pie slice: replace text label with arbitrary element rendered at the slice centroid
PieChart(data, d => d.Value)
    .LabelView((d, layout) => HStack(
        FontIcon(IconForCategory(d.Category)).FontSize(12),
        TextBlock($"{layout.Fraction:P0}").FontWeight(FontWeights.SemiBold)));

// Axis tick: replace numeric tick label with arbitrary element
LineChart(data, x, y)
    .XTickLabelView(t => VStack(
        TextBlock(MonthName((int)t)).FontSize(11),
        TextBlock("month").FontSize(8).Foreground(Theme.SecondaryText)))
    .YTickLabelView(t => TextBlock($"${t:N0}").FontFamily("Cascadia Mono"));

PieChartElement<T>.LabelView(Func<T, PieSliceLayout, Element>)

The render delegate receives:

  • T data — the slice's source data item.
  • PieSliceLayout layout — a readonly record struct with everything you

need to render a label that knows its slice geometry:

  • Index, Value, Fraction (0..1 of total)
  • CentroidX, CentroidY (absolute canvas coords — already applied via

CenterAt, you don't need to apply them yourself)

  • StartAngle, EndAngle (radians, clockwise from 12 o'clock — d3 semantics)
  • InnerRadius, OuterRadius
  • Color — the resolved D3Color from the chart's palette, so your label

can echo the slice color without a separate lookup.

The returned element is auto-anchored at the centroid via CenterAt. You do

not need a known size at construction time; the reconciler recomputes

position after layout from ActualWidth/ActualHeight.

ChartElement<T>.XTickLabelView(Func<double, Element>) and YTickLabelView

Same pattern for axis ticks. The delegate receives the tick's domain value

(double); X labels are anchored horizontally centered on the tick mark, Y

labels right-anchored to the axis edge and vertically centered.

Direct labeling vs legend vs tooltip

A label-placement decision tree (Tufte, Cleveland — direct labeling raises

the data-ink ratio and avoids legend ping-pong):

  • Direct labels first. Label series at the end of each line, label pie

slices on the chart, label bars above the bar. LabelView /

*TickLabelView exist precisely so you can do this without re-templating

the whole chart. Eyes don't ping-pong from line to legend; print works;

screen readers work.

  • Use a legend only when direct labeling would collide — many series,

tightly-packed lines, repeating palette across small multiples. Treat it

as a fallback, not a default.

  • Use a tooltip for precise values on dense data, *in addition to* —

never *instead of* — direct labels or a legend. Tooltips fail print,

screen readers, mobile-tap accuracy, and keyboard navigation.

  • Pie slices: large slices get inside-labels; small slices (<~5%) need

outside leader lines or get rolled into "Other". Don't label tiny slices

inline — they overlap.

  • Backfire cases: dense scatterplots, tightly-packed bars, many

overlapping line endpoints. Label collisions there are worse than a

legend; switch to legend.

Anchor primitive (used by *View internally)

The *View methods are built on Canvas's anchor extensions

(CanvasExtensions.cs). You'll rarely call them directly, but if you need to

position arbitrary content on a Reactor Canvas without knowing its size at

build time (overlay markers, custom callouts), use these:

.Canvas(left, top, anchorX, anchorY)   // 0..1 fractions of rendered size
.CenterAt(x, y)                        // sugar for anchor (0.5, 0.5)

The reconciler subscribes once to Loaded + SizeChanged per anchored element

and recomputes Canvas.Left/Top as target − anchor × ActualWidth/Height.

Zero-anchor (0, 0) is the legacy fast path with no subscription overhead.

Accessibility — beyond the framework defaults

Charts implement IChartAccessibilityData, which exposes axis ranges, units,

point values, and (for pie) slice descriptors via UIA. Don't disable that.

Beyond that, your responsibilities:

  • Color is never the sole channel (WCAG 1.4.1). Pair color with shape,

pattern, dash style, or — best — a direct text label. Useful for series

identification when the user can't distinguish two of your colors.

  • Color-blind-safe palettes. Use ChartPalette.Categorical (Reactor's

curated set; Okabe-Ito-style). Avoid red/green pairings. Avoid rainbow

for ordinal data — use a sequential ramp (Viridis-style) instead.

  • Contrast. WCAG 1.4.11 — 3:1 for non-text essential graphics, 4.5:1

for text labels. The default chart palette meets this against the

framework's surface tokens; if you override with .Stroke("#…") /

.Fill("#…"), check contrast against Theme.SurfaceBackground.

  • Custom colors & the scanner. Three tiers, in order of safety:

.Palette(ChartPalette.Categorical) (Tier 1, curated) and .SeriesColors(…)

(Tier 3, your own colors) are both contrast-checked by the a11y scanner

(A11Y_CHART_011). .RawColors(…) (Tier 4) is an escape hatch that bypasses

the check and itself trips the A11Y_CHART_012 info. A pie chart's low-level

.SetColors(…) colors are the slices it actually draws, so they are

scanner-visible too — validated as a Tier-3 custom palette. When a pie sets

both .SetColors(…) and .Palette(…), the rendered .SetColors(…) colors are

the ones scanned.

  • Declare the background you render on. A11Y_CHART_011 is theme-agnostic

by default, so it can only flag a custom color against *either* fixed

light/dark background as an info. Call .ChartBackground("#1E1E1E") (also

takes a D3Color or Windows.UI.Color) to scope the check to the single

background the chart actually renders on — the finding becomes an actionable

warning with a real, contrast-hardened fix, and palettes that are fine on

their actual background stop being flagged.

  • Screen-reader fallback. UIA structured data is good but not enough

for screen-reader-only users — a hidden, expandable data table next to

the chart is the pattern Tenon and the W3C WAI accessibility guidance

recommend. Build it from the same data the chart consumed.

  • Keyboard nav. Data points should be focusable in reading order, and

focus should announce category + value. The IChartAccessibilityData

surface drives this; if you turn the chart Interactive(false) you lose

it — keep it on unless there's a reason.

  • *View defaults. Custom labels are non-interactive by default: the

chart force-applies AccessibilityView=Raw to the **entire realized

subtree** (not just the outer wrapper) and clears IsTabStop on every inner

control, so no inner peer surfaces to UIA and nothing inside the label joins

the tab order. Prefer keeping the string LabelAccessor (PieChart) or

DataLabel (line/bar/area) set — those feed UIA. When the visual lives

entirely in the *View, pass name: (a Func<T,string> for pie /

Func<double,string> for ticks) so the accessible name matches the visible

label instead of falling back to Slice {n}. Set interactive: true only

when the label has focusable children that must stay reachable — then you

own its accessibility. Signatures:

LabelView(render, name: null, interactive: false),

XTickLabelView(render, name: null, interactive: false),

YTickLabelView(render, name: null, interactive: false).

Common pitfalls — refuse to ship these

The most-cited visualization mistakes (Tufte, Few, Cairo, Wilke). If a user

asks for one, push back with the alternative.

  • Truncated bar baselines. Bars MUST start at zero. A 3% gap with a

truncated baseline visually looks like 300%. Cairo's *How Charts Lie*

spends a chapter on this. Line charts may truncate because shape, not

height, conveys meaning — bars never.

  • Dual y-axes. Fabricates correlation between unrelated series. Use two

small multiples or normalize both series to an index (e.g. `(value /

baseline) × 100`) so they share a single axis.

  • 3D / exploded pies, drop shadows, gradients on bars. Tufte's

"chartjunk." 3D pies are actively misleading — angle distortion changes

the visual proportion of slices.

  • Too many series. >5–7 lines, >7 pie slices. Group the tail into

"Other" or split into small multiples.

  • Rainbow / unordered categorical palettes on ordinal data. When the

variable has order (low/medium/high, age buckets, sentiment), use a

sequential ramp. A categorical palette implies "different kind", not

"different magnitude".

  • Pie chart for ranking. Pies hide rank — angle judgments are

imprecise. A sorted bar chart shows it directly.

When to reach for *View (and when not to)

Reach for it when:

  • You need an icon-plus-text axis tick or slice label.
  • You need to render the slice percent in the slice itself instead of a

side legend (direct labeling, see above).

  • You're embedding a chart in a dashboard whose typography contract demands

fonts/colors the built-in ChartAxis style doesn't match.

Skip it when a string works:

  • Plain numeric formatting → DataLabel((d, i) => d.Value.ToString("C")).
  • Custom number-to-string for ticks → built-in tick formatting (Fmt(t)

handles short numbers cleanly).

  • Just changing color/font of a built-in label — that's not exposed yet;

if you need it, file an issue rather than dropping to *View for a

one-property override.

Reading list

  • docs/guide/charting.md — full user-facing chart guide.
  • src/Reactor.Advanced/Charting/Charts.csChartElement<T> / PieChartElement<T>

fluent API. *View methods sit near the bottom of each class.

  • src/Reactor.Advanced/Charting/Charts.Tree.csTreeChart and ForceGraph

factories + their elements.

  • src/Reactor.Advanced/Charting/D3Charts.cs — d3 primitives (D3Pie, D3Axes,

D3Grid, D3Canvas). D3Axes is where the optional xTickLabel /

yTickLabel delegates plug in.

  • src/Reactor.Advanced/Charting/D3/Layout/ — composable layout algorithms

(Sankey.cs, Treemap.cs, Cluster.cs, Stratify.cs, TreeLayout.cs).

No top-level factory wrapping these yet; they're the building blocks for

one when the time comes.

  • src/Reactor/Elements/CanvasExtensions.csCenterAt and the anchor

overload of Canvas.

External (read these once if charting is new to you):

  • Cleveland & McGill (1984), *Graphical Perception* — the perceptual ranking.
  • Edward Tufte, *The Visual Display of Quantitative Information* — data-ink ratio, chartjunk.
  • Stephen Few, *Show Me the Numbers* — practitioner playbook for business charts.
  • Claus Wilke, *Fundamentals of Data Visualization* — free online; the modern reference.
  • Alberto Cairo, *How Charts Lie* — the pitfalls chapter is required reading.
  • d3js.org docs on d3-scale, d3-axis, d3-shape — even when working through Reactor's wrappers.

How to use it

Copy the folder

Take microsoft/reactor-charts from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.