feat(chart): add c2-overlap-chart (Venn diagram) and fix declared series after hydration
Merged
CO
1mo ago+3607+3607+0123456789012345678901234567890123456789-72-72-01234567890123456789
Summary
Adds c2-overlap-chart to @c2n/chart: a Venn diagram of two or three sets. Each set is a circle, and each shared area is labelled with the number of members in exactly that combination (or its share, or both). A whole set (one circle) can be highlighted or selected, including by clicking anywhere inside it with selection="set". The other circles can be restyled while one is highlighted, and custom tooltips receive the hovered region or set.
For every chart:
- A click highlights: clicking a series in the plot, or its legend entry, brings it forward and dims the others.
legend-action="toggle"restores hide/show for the legend. - The
legendandtooltipslots are verified on every chart type.
It also fixes three existing bugs:
- After server rendering, every chart ignored its
<c2-chart-series>children and drew series guessed from its data instead. point-clicknever fired on the uPlot charts.- On the uPlot charts the cursor always resolved to the first series, because the left-axis series carried an explicit
scale: undefined. That overwrote uPlot's default scale, sovalToPosreturnedNaN.
c2-overlap-chart
<c2-overlap-chart selectable selection="set" labels="count-percent" data='[{"sets":["web"],"size":18420},{"sets":["web","mobile"],"size":6880}, …]'>
<c2-chart-series field="web" label="Web app"></c2-chart-series>
<c2-chart-series field="mobile" label="Mobile app"></c2-chart-series>
<c2-chart-series field="api" label="Public API"></c2-chart-series>
</c2-overlap-chart>
- Data: one row per combination, giving the number of members in all of its sets. Missing combinations count as 0, and the chart works out the exclusive regions itself.
sets-fieldandsize-fieldrename the row fields. Theregionsgetter returns the computed regions, andsetDetail(key)returns one set's totals. - Engine: the ECharts
customseries, registered as a newcustomfeature in the loader.@upsetjs/venn.js2.0.0 is a new exact-pinned dependency for the proportional layout; the functions we use come to about 6 KB gzip, with no D3. It is kept external in the Vite build. - Region shapes: each exclusive region is one path whose outlines are wound by inclusion–exclusion, so the canvas fill and ECharts' hit-testing are both exact under the non-zero rule. A browser test checks this against
isPointInPathin both layouts. - Options:
layoutisproportional(the default) oruniform.labelsiscount,percent,count-percentornone. Withcount-percentthe share goes on a smaller second line under the count, so the label stays narrow enough to sit inside most regions.formatsets the number format.
- Set names and legend:
- By default the sets are named in the chart's legend, all in one place that
legendpositions (bottom by default), instead of names scattered around the circles. set-labels="around"writes each set's name and total beside its circle instead; pair it withlegend="none". Set names are pushed apart or moved to the free side of their circle.- Custom legends work as on every chart: the
legendslot,renderLegendItemor a linkedc2-chart-legend. - A legend click highlights the set. With
legend-action="toggle"it switches the set off and lays the diagram out again without it; the last visible set stays on.
- By default the sets are named in the chart's legend, all in one place that
- Region labels: text is measured on a canvas. A region too small for its label gets it outside with a leader line, placed clear of the other labels.
- Regions: hovering a region shows its name, count, share and combination total, and fires
region-hover; clicking firesregion-click. - Whole sets:
- Hovering a set's legend entry (or its name, with
set-labels="around") highlights the whole circle and dims the others. - The highlighted circle comes forward:
- its outline is drawn above the others with a glow in the set's colour;
- its fill goes to
--c2-chart__set__highlight--fill-opacity(0.4); - each part it shares with another circle is tinted in that other circle's colour at
--c2-chart__set-overlap__highlight--fill-opacity(0.35, a lens path per pair). Withwebhighlighted, the web + mobile zone mixes in mobile's colour, so the mixed zones show which set they are shared with.
- The tooltip describes the set: members, share of the whole, and members in no other set.
- It fires
set-hoverandset-click. A click highlights the set, andset-clickis cancelable. highlighted(shared with every chart) sets the highlight from the application.- Clicks that land while the chart redraws under the pointer are read from the plot's DOM
click.
- Hovering a set's legend entry (or its name, with
- Dimmed circles:
- The other circles keep their own colour at
--c2-chart__set__dimmed--opacity(0.6) of their fill and outline. Their fill is drawn only outside the highlighted circle (differencePath), so the areas they share with it read as part of the highlight. --c2-chart__set__dimmed--colorrepaints them in one colour, resolved through a hidden probe sovar()andcolor-mix()values work.--c2-chart__set__dimmed--fill-stylefills themsolid,hatch,dotsornone.
- The other circles keep their own colour at
- Selection:
- With
selectable, clicking a region setsselectedand clicking a set's name setsselected-set. The two replace each other.selection-changecarries{ selected, selectedSet }. selection="set"makes a click anywhere inside a circle select that whole circle. In an overlap the smallest circle comes first, then the next on further clicks, then none.
- With
- Custom tooltip: it works through the
tooltipslot,renderTooltipor a linkedc2-chart-tooltip.renderTooltipandtooltip-changereceive anOverlapTooltipContext, which carries the hoveredregionorset. - Keyboard: the plot is one tab stop. The arrow keys move through the sets, then the regions from largest to smallest. Enter selects (following
selection) and Escape clears. A focused set gets a dashed ring around its name, or around its circle when names are off. - Accessibility: a hidden table of regions, and a live region that announces the focused set or region.
- Limits: with more than three sets the chart shows its error state.
- CSS variables:
- Sets:
--c2-chart__set--fill-opacity,--c2-chart__set--stroke-width,--c2-chart__set__highlight--fill-opacityand--c2-chart__set-overlap__highlight--fill-opacity. - Dimmed sets:
--c2-chart__set__dimmed--opacity,--c2-chart__set__dimmed--colorand--c2-chart__set__dimmed--fill-style. - Regions:
--c2-chart__region__hover--opacityand--c2-chart__region__selected--opacity. - Labels:
--c2-chart__region-label--font-size,--c2-chart__set-label--font-sizeand--c2-chart__set-label--font-weight. - Focus:
--c2-chart__plot__focus--outline.
- Sets:
Highlight, legend and tooltip, every chart
- Click to highlight (the default): clicking a series in the plot (a line, a bar, a scatter point, a pie slice, a stacked area band) or its legend entry highlights it, fading the others to
--c2-chart__series__dimmed--opacity(0.25). A second click clears it.highlightedholds the key: the seriesfield, a pie's slice label, or an overlap chart's set key. The application can set it.highlight(key)and the non-bubblingseries-highlightevent report it.point-clickfires first and is cancelable:preventDefault()keeps the highlight as it is.- Single-series charts (gauge, candlestick) have nothing to set apart.
- The highlighted series comes forward instead of only staying put while the rest fades:
- uPlot: a line 1px heavier, and a deeper fill on area charts.
- ECharts: drawn above the others (
z: 3) with a line 1px heavier. - Pie: a drop shadow on the slice.
- Area charts hit-test the band under the pointer (
hitMode = 'band'): the band is the closest stacked line above the cursor. So every band can be hovered and clicked, not just the top one. The other uPlot charts keep the nearest-point search, which now compares CSS pixels. legend-action:highlightis the default.togglemakes a legend click hide or show the series instead, as it did before this PR. This is a behaviour change for existing charts; see the note below.- Slots:
slot="legend"replaces the generated entries in place, andslot="tooltip"becomes the body of the chart's own tooltip bubble. A test checks both on line, area, bar, scatter, radar, pie, gauge, candlestick and overlap charts. - uPlot click fix: the uPlot adapter never reported clicks, so
point-clicknever fired on line, area, bar or sparkline charts. It now reads clicks from the plot overlay and ignores the release of a drag-to-zoom. - Engines: uPlot dims through the series
alpha, ECharts multiplies the opacity of each series' style blocks (dimSeries), and the pie fades the other slices per datum. - Legend entries:
ChartLegendItemgainskey,pressedanddimmed, andtoggleruns the chart's legend action, so a linkedc2-chart-legendfollows the mode. The faded legend style moved from[aria-pressed='false']to a dimmed class.
Shared chart changes
- Protected hooks:
- on
ChartBase:handleEngineHover,handleEngineClick,tooltipContextAt,pointAt,defaultTooltip,validationErrorandhighlightKeyAt; - on
UplotChartBase:hitModeandhighlightStyle.
- on
- Declared series after hydration: the chart now also reads its definitions from the light DOM on the first update, because after hydration
slotchangefires before Lit adds its listener. - Legend entries are rendered into the legend slot after each update, not as part of the hydrated template.
- Legend highlight hook:
ChartLegendItemgains an optionalhighlight(active)callback for hover and focus. - Lazy engine updates:
EchartsChartBase#lazyEngineUpdates(defaulttrue) is turned off by the overlap chart. A stress run of 40 rapid click-and-move cycles on pie, scatter and radar raised no errors.
Docs site
mdx-codeblock-remark.mjs: nested plain tags (c2-chart-series,c2-tab,c2-tree-item) now get capitalised attribute names, sofieldandlabelreach the HTML.- Behaviour change: docs charts now draw the series they declare. For example, one line-chart example on the site declares 3 series but used to draw 5.
- The chart overview's "Slots" and "Hide or highlight from the legend" sections describe highlight as the default and
toggleas the alternative, and how a highlighted series comes forward. The event demo now also showsseries-highlight. - A new Overlap chart docs page with:
- a "Click a circle" example (
labels="count-percent",OverlapSelectionDemo.astro), where the other circles keep their own colours; - a gallery of 7 cards, using both the grouped legend and
set-labels="around", with a grey hatched "Highlighted set" card as the opt-in alternative; - a landing-page preview, presets and slot-audit entries.
- a "Click a circle" example (
- Regenerated the manifest, React/Vue types, umbrella package and skill catalog.
Behaviour change for existing charts
Legend clicks now highlight by default instead of hiding the series. An app that relies on the old behaviour sets legend-action="toggle". series-toggle still fires whenever a series is hidden or shown.
Testing
packages/components/chart/testplus the umbrella suite: 105 tests pass in Chromium. They cover:- for each of 9 chart types: slot replacement, legend highlight, click-to-highlight (and clearing), and
preventDefault(); - the new default and
toggle; - a real uPlot click and a drag;
- area band hits on each of three bands, with fixed and auto-ranged y axes;
- 21 overlap-chart tests, including that each shared zone of a highlighted circle is tinted in the other set's colour, that dimmed fills stay outside the highlighted circle,
count-percentlabels, and the grouped legend by default versusset-labels="around".
- for each of 9 chart types: slot replacement, legend highlight, click-to-highlight (and clearing), and
apps/ui/test: all 14 tests pass, including the hydration suite.ui:build,build:tools,mcp:smoke,docs:check,check:dogfood,lint,format:checkandtest:type-checkpass on Node 24.- On the built docs site (DPR 2, light and dark):
- real clicks on each band of the area chart highlight solar, wind and hydro respectively;
- with each set of the "Click a circle" example selected, the shared zones show the other set's colour.
Known cosmetic issues:
- On the narrow "Two products" gallery card, one leader label still sits on a circle outline.
- With
set-labels="around", a set name at the top edge of the plot gets its tooltip below the name, covering it.
🤖 Generated with Claude Code