Skip to content

πŸ’Ž Version 6.10.0

Latest

Choose a tag to compare

@junedchhipa junedchhipa released this 18 Aug 01:30
· 1 commit to main since this release

A feature release built around one question: what if the arrangement of a chart's marks were something you could hand it?

The unit chart already drew one dot per thing counted, and 6.9.0 opened a seam for supplying the positions yourself. This release ships the thing that seam was for. apexcharts/unit-shapes is a companion kit of 39 shapes a count can take: a heart, a house, a globe, a checkmark, a heartbeat trace, or the figure 1,024 drawn in 1,024 dots. Each one is a function of the marks and the plot rectangle rather than a picture, so the same shape serves 40 dots in a sparkline and 3,000 in a poster.

Alongside it: outer name labels for those shapes, so a crowd of dots reads without a legend, and a fix worth reading even if you never draw a heart, because it swallowed any update that changed only a callback.

✨ New

apexcharts/unit-shapes, 39 shapes a count can take

A new entry point, tree-shaken per shape. Importing one costs about 4 KB gzipped; importing the catalog is not something you need to do.

import ApexCharts from 'apexcharts'
import { heart } from 'apexcharts/unit-shapes'

new ApexCharts(el, {
  chart: { type: 'unit' },
  series: [57600, 16800, 4200, 3400],
  labels: ['Repeat donors', 'First-time', 'Workplace drives', 'Emergency call-ups'],
  plotOptions: {
    unit: { layout: 'custom', positions: heart, unitValue: 100 },
  },
}).render()

A shape is a plain callable, so positions: heart needs no registration step, and plotOptions.unit.positions already accepted a function: nothing in the chart had to learn about shapes.

The dots are packed, not stamped onto a template. Rows are cut across the outline, each row split into the spans that fall inside it, and the gap between dots is then bisected until the spans hold exactly the number of marks the data asks for. Density follows the shape's own area, which is why one outline covers three orders of magnitude of dot count. A thin limb, fin or tip keeps its single dot rather than dropping out, because that is what stops a shape dissolving as the count falls.

Three kinds, because things in the world are not all areas:

  • Silhouettes (29) fill an outline: heart, house, tree, leaf, flame, droplet, fish, sun, human, group, star, crown, trophy, moneybag, funnel, shield, gear, robot, bulb, flask, car, plane, rocket, battery, pin, mountain, cross, bolt, arrow.
  • Strokes (7) pack a thickened centreline, for a thing with no interior: check, wifi, pulse, xmark, percent, question, spiral. A dotted checkmark still reads as a checkmark, which is how a stroke degrades where a thin silhouette feature would simply vanish.
  • Generated (3) compute their positions from maths and have no outline at all: globe (latitude rings with a tilt), target (concentric bands), pyramid (tiers).

Every shape carries its metadata: which category it belongs to, how it was made, and minUnits, the count below which it stops being recognisable. Ask for fewer and the chart says so in a console warning naming the shape, rather than rendering mush.

Composition, rather than a bigger catalog

outlined(heart)                  // trace the outline instead of filling it
heart.with({ order: 'cols' })    // where each series band lands inside the shape
glyphs('1,024')                  // the number, drawn in that many dots
preview(heart, { series })       // -> an SVG string, no chart and no DOM

outlined() gives all 29 silhouettes a hollow twin for no new artwork, since a stroked closed path is a ring. The fill order is what decides where each category sits: rows bands a shape top to bottom, cols left to right (which is why battery fills like a charge meter), centerOut puts the first series at its heart. preview() renders a shape to standalone SVG, so docs galleries, README images and launch graphics can be generated at build time or on a server from the catalog alone.

Shapes are also registrable by name (ApexCharts.registerUnitLayout), and from a script tag dist/unit-shapes.js exposes the kit as ApexUnitShapes with every shape pre-registered.

On provenance: every outline in the kit was drawn in this repository. No third-party path is admitted, permissively licensed or not, because an outline ships verbatim inside the bundle: a copied path would make its licence notice travel into every consumer's build forever. Brand marks are excluded outright. A test enforces it.

Outer name labels for unit shapes

A shape packed with four categories used to need a legend, which asks the reader to match a swatch to a band. Names can now sit in the margin with a leader line to their own dots, the way a pie names its slices.

plotOptions: {
  unit: { clusterLabels: { external: { show: true } } },
}

The gutter is reserved on both sides before the dot size is chosen, so the shape is sized for the room it will actually get instead of being scaled down afterwards, and it stays centred. Each label anchors on a real dot of its own band, sides are assigned from how the bands are actually arranged, and crowded labels are spaced apart in one pass before they reach the DOM. The implementation is the one pie and donut already use, extracted rather than rewritten.

Samples

Twenty new unit demos, one per shape family, including a gallery that switches between all 39 shapes with the same dots flowing from one arrangement into the next. Palettes use separated hues rather than tints of one colour: at dot size a lightness ramp cannot be read back, and the palest end disappears against the card.

πŸ› Fixes

An update that changes only a function no longer vanishes

update() skips a redundant render by comparing the incoming options with the previous ones, and that comparison went through JSON.stringify, which drops function values. Two configs differing only in a callback therefore serialised identically and the update was thrown away.

Any function-valued option was affected: a new dataLabels.formatter, a new custom tooltip, a new plotOptions.unit.positions. The first such update always worked, since there was nothing to compare against yet, which is what made it look like a rendering problem rather than an update problem.

Functions are now compared by identity: passing the same function twice still skips, so the optimisation keeps paying, while a different one gets the render it asked for. A caller who builds a fresh closure on every update now gets a render every time, which is the safe direction to err in, since the closure may capture new state.

Millisecond resolution survives a Date on a datetime axis

A data point whose x is a Date object had its milliseconds truncated, so points inside the same second collapsed onto each other. The type definitions also refused a Date there, despite it being the natural thing to pass. Thanks to @aron-intframe (#5277).

πŸ”§ Internal

  • the unit and unit-shapes sub-entry artifacts are built and published, so apexcharts/unit and apexcharts/unit-shapes resolve for bundlers and script tags alike
  • shape geometry is covered by its own suite: containment against the outline it claims, spacing, the exact-count guarantee at every dot count, provenance, and a cross-check that the catalog, the exports and the type definitions can never disagree
  • two authoring tools ship with the repo rather than the package: a contact sheet that renders every shape at a given count, and a winding checker that catches a subpath which would punch a hole where it meant to fill

Full Changelog: v6.9.0...v6.10.0