apex-commons
The shared core behind the ApexCharts product family: licensing, watermarking, theming, internationalisation, spring motion and coordinated filtering.
It exists because these things are not chart-specific. A licence key, a theme token, a text direction and a filter mean the same thing to a chart, a tree, a sankey diagram and a grid, and each of those having its own copy meant four implementations drifting apart. Anything the family needs to agree on lives here, so it is agreed on once.
Most users never install this directly. It arrives as a dependency of the product you are using, and the parts you touch are reached through that product's own API. Install it yourself when you are building against several of them at once, or using the crossfilter engine with no chart present.
Installation
npm install apex-commons
Licensing
LicenseManager is the enforcement path for the whole family, so a key set once here is honoured by every Apex product on the page.
import {LicenseManager} from 'apex-commons';
LicenseManager.setLicense('your-license-key');
Without a valid key a product keeps working and carries a trial watermark. That is deterrence and honest-customer compliance rather than DRM: no feature is ever degraded.
The products this serves are free under the Community terms for organisations under the revenue threshold, and need a Commercial licence otherwise. See https://apexcharts.com/pricing.
| Member | Description |
|---|---|
LicenseManager |
Static class holding the licence for every Apex product on the page. |
setLicense |
Set the key. Call before creating any instance. |
getKey |
The key currently in force, or null. |
isLicenseValid |
Whether the key in force is valid right now. |
isKeyValid |
Whether a given key would be valid, without setting it. |
getLicenseStatus |
The full LicenseValidationResult for the key in force. |
validateKey |
Validate a given key and return its result, without setting it. |
onChange |
Subscribe to licence changes. Returns an unsubscribe function. |
LicenseData |
The decoded payload: plan, issue and expiry dates, optional domains. |
LicenseValidationResult |
The outcome of a check: whether it is valid, and why not when it is not. |
LicenseChangeListener |
The callback shape onChange takes. |
Plan tiers
Whether a licence covers a feature, in one place, so every Apex product answers it the same way.
planAtLeast has one hard boundary and one deliberate softness. Without a valid licence it always returns false. With a valid licence whose plan string is not recognised it returns true and warns once, because the holder has a signed, unexpired key: refusing them a feature over a string we failed to map is worse than granting one. Gating here is deterrence and honest-customer compliance, not DRM.
import {planAtLeast} from 'apex-commons';
if (planAtLeast('premium')) {
// show the tool
}
| Member | Description |
|---|---|
planAtLeast |
Whether the licence in force covers a feature needing at least the given plan. |
currentPlan |
The plan the licence grants, or null. For display; use planAtLeast to gate. |
normalisePlan |
Resolve a raw plan string to a known plan, ignoring case and space. null if unknown. |
PLAN_ORDER |
Every plan string a key may carry, in commercial ladder order. Not the gating order. |
PLAN_ALIASES |
Plan strings that may appear in issued keys but are not current plan names. |
ApexPlan |
The plan names: community, pro, premium, embedded, oem. |
There are three gating levels, not five. community < pro < premium, and embedded and oem rank equal to premium rather than above it. Those two sell redistribution rights and, in the words of the licence they are sold under, not usage rights: what an OEM customer's runtime may do is decided by the per-developer licence bought alongside, which is what the key carries. So premium is the ceiling, and nothing should gate above it. A feature that did would be reachable by no purchasable licence at all.
Both strings stay in the vocabulary rather than being removed, because planAtLeast fails open on a plan it does not recognise: drop them and a key carrying one would be granted everything, which is the opposite of the intent.
community never appears in a key, because that tier is issued none, so planAtLeast('community') means "holds any licence at all".
oem was called enterprise until 2026-09-14, and PLAN_ALIASES maps the old string so a key carrying it still resolves to a real rank rather than fail-opening.
Watermark
The trial mark an unlicensed product adds to its container.
| Member | Description |
|---|---|
Watermark |
Static class that adds, finds and removes the trial mark. |
WatermarkOptions |
Options accepted by add and remove. |
ATTR |
The attribute the mark carries, for finding it. |
add |
Add the mark to a container. Returns the element, or null. |
remove |
Remove the mark from a container. |
exists |
Whether a container currently carries a mark. |
node |
The mark element inside a container, or null. |
applyStyles |
Apply the mark's styles to an element. |
untrack |
Stop tracking a container, without removing its mark. |
Internationalisation
| Member | Description |
|---|---|
TextDirection |
'ltr', 'rtl' or 'auto'. |
resolveMessages |
Merge a caller's partial message overrides onto a product's defaults. |
applyDirection |
Set text direction on an element, resolving 'auto' against the document. |
isRTL |
Whether a direction resolves to right-to-left, given an optional element. |
Theming
A page declares the --apx-* custom properties once on :root and every Apex product on it follows. Anything set explicitly on a product wins, so a product already themed through its own options is untouched.
| Member | Description |
|---|---|
ApxTokens |
The resolved token set: accent, foreground, grid, surface and the series palette. |
APX_TOKEN_VARS |
The custom property name for each token role. |
APX_SERIES_VAR_PREFIX |
Prefix for the ordered series palette, --apx-series-N. |
readTokens |
Read the tokens currently in force on an element. |
resolveTokenLayer |
Read the tokens for an element, with a registered theme underneath. |
pickToken |
Resolve one value through the precedence order: option, then CSS variable, then token, then fallback. |
TokenPrecedenceInput |
The candidates pickToken chooses between. |
ApxTheme |
A named theme: the token roles it contributes. |
registerTheme |
Register or override a named theme on the shared registry. |
getTheme |
Look up a registered theme by name. |
registeredThemes |
The names currently registered. |
unregisterTheme |
Remove a registered theme. |
OwnedToken |
Tracks a custom property a product set itself, so it can put back what it found. |
OSThemeWatcher |
Watches the operating system's light and dark preference. |
OSThemeState |
The current OS preference. |
OSThemeWatcherOptions |
Options for the watcher. |
Motion
A headless spring driver, shared so that motion feels the same across the family. It computes values and holds no opinion about what draws them.
| Member | Description |
|---|---|
Spring |
One spring: its current value, velocity, target and rest thresholds. |
makeSpring |
Create a spring at a value, with a stiffness and damping. |
stepSpring |
Advance a spring by a timestep. Returns whether it is still moving. |
retarget |
Point a spring at a new target, keeping its velocity. |
snapSpring |
Move a spring to a value immediately, clearing velocity. |
SpringRest |
The velocity and displacement below which a spring counts as arrived. |
UNIT_REST |
Rest thresholds for springs running over a 0 to 1 range, rather than pixels. |
SpringPreset |
'crisp', 'gentle' or 'snappy'. |
SPRING_PRESETS |
The stiffness and damping pair behind each preset. |
resolveSpring |
The pair for a preset, falling back to the default. |
CAMERA_SPRING |
The pair used for pan and zoom, which wants less overshoot than content. |
WAVE_STAGGER_MS |
Delay between neighbours in a staggered entrance. |
SpringScene |
Drives many named springs on one animation loop, with a single rest callback. |
SpringSceneOptions |
Options for a scene, including its clock. |
SpringValues |
The current value of every spring in a scene, by name. |
SceneObjectOptions |
Options for one object added to a scene. |
SceneClock |
The scene's time source. Substitutable, so tests need no timers. |
diffNodes |
Split previous and next id sets into entering, surviving and leaving. |
NodeDiff |
The result of diffNodes. |
ringDistances |
Distance of each node from a set of roots, for staggering a wave outwards. |
ReducedMotionWatcher |
Watches the user's reduced-motion preference. |
ReducedMotionWatcherOptions |
Options for the watcher. |
Crossfilter
Coordinated filtering across any number of views. One shared record set, and per view a dimension (row => key), a reduction and a filter.
A view's aggregation is computed over the records passing every other view's filter, never its own, so each view keeps showing what else is reachable instead of collapsing to its own selection. A view's own filter only marks which of its buckets are selected.
Because a dimension is an arbitrary function, a map region, a tree subtree, a sankey link, a grid column and a chart category are all the same thing to it, and a set of views can coordinate with no chart present at all.
import {Crossfilter} from 'apex-commons';
const cf = Crossfilter.getOrCreate({id: 'audit'});
cf.setRecords(rows);
cf.registerDimension('byRegion', {dimension: (r) => r.region});
cf.on('change', () => render(cf.aggregateAll()));
cf.filter('byRegion', ['EU']);
| Member | Description |
|---|---|
Crossfilter |
The coordinator. |
getOrCreate |
Static. The coordinator for an id, creating it if needed. |
get |
Static. The coordinator for an id, or null. |
setRecords |
Replace the shared record set. |
registerDimension |
Add or replace a view's dimension and reduction. |
hasDimension |
Whether a view is registered. |
removeDimension |
Remove a view. |
filter |
Set a view's filter. |
toggleKey |
Add or remove one key from a view's filter. |
filterOf |
A view's current filter, or null. |
clear |
Clear one view's filter. |
reset |
Clear every filter. |
aggregateFor |
The aggregation for one view. |
aggregateAll |
The aggregation for every registered view. |
filteredRecords |
Records passing every filter, optionally excluding one view's own. |
filteredRows |
Records passing every filter. |
state |
A snapshot of records, filters and aggregations. |
on |
Subscribe to an event. Returns an unsubscribe function. |
off |
Remove a handler. |
dataTable |
Render a paged table of the filtered rows into an element. |
Crossfilter types
| Type | Description |
|---|---|
CrossfilterOptions |
Options for getOrCreate, including the shared id. |
CrossfilterRow |
One record. Shape is entirely the caller's business. |
CrossfilterState |
The snapshot state() returns. |
CrossfilterEvent |
The events a coordinator emits. |
CrossfilterListener |
The callback shape on takes. |
DimensionSpec |
A view's dimension, reduction, ordering and binning. |
DimensionAccessor |
Maps a row to this view's key. |
DimensionType |
Whether a dimension is categorical or numeric. |
DimensionOrder |
How a view's buckets are ordered. |
BinSpec |
How a numeric dimension is bucketed. |
ReduceSpec |
How rows in a bucket become a scalar. |
Reducer |
A custom reduction function. |
FilterInput |
What filter accepts: a set of keys, or a numeric range. |
Aggregate |
Union of the aggregation shapes. Narrow it before reading. |
CategoryAggregate |
A flat list of keys and values. |
RangeAggregate |
A numeric range aggregation. |
MatrixAggregate |
A two-dimensional aggregation, which has no flat keys list. |
DataTableOptions |
Options for dataTable. |
DataTableColumn |
One column in a data table. |
DataTableHandle |
The handle dataTable returns. |
Indicators
Moving-window indicators as pure functions, lifted out of apexstock so the chart family and the stock product share one implementation. Each takes Array<number | null> and returns the same length, with null wherever a value is not defined.
A window containing a gap yields null rather than an average of however many values were present, which would quietly compute a mean of four and show it beside means of ten.
sma is slice-invariant: a window averages to the same value whatever precedes it, so sma(values.slice(i - period + 1, i + 1), period) at its last position equals sma(values, period)[i] exactly. That is what lets a streaming consumer extend a series by recomputing only the tail window. It costs a running total, so sma is O(n * period).
| Member | Description |
|---|---|
sma |
Trailing simple moving average, aligned to the last position in each window. Slice-invariant, as above. |
ema |
Exponential moving average, seeded from the first complete simple window rather than the first value. A gap ends the run and the next complete window starts a new one. |
bollinger |
A simple moving average with a moving standard deviation either side, using the population denominator, which is the convention for this band. |
Bands |
What bollinger returns: middle, upper and lower, each the length of the input. |
import {bollinger, ema, sma} from '@apex/commons';
sma([1, 2, 3, 4, 5, 6], 3); // [null, null, 2, 3, 4, 5]
ema([1, 2, 3, 4, 5], 3); // [null, null, 2, 3, 4]
bollinger([2, 4, 4], 3, 2).upper;
Histogram binning
Observations in, bin edges and counts out. Pure arithmetic, so it is testable on its own and inert under SSR.
Gaps mean the opposite of what they mean above. A moving average over a window containing a gap is undefined, because the window is a claim about a stretch of the series. A histogram is a tally of the observations that exist, so a gap is simply not one; histogram skips them and reports how many it skipped.
| Member | Description |
|---|---|
histogram |
Bin a series and count it in one call, tolerating gaps. The convenience most callers want. |
computeBinning |
Choose bin edges for a set of finite observations. Precedence: an explicit binWidth, then an explicit bin count, then a named rule. |
binCounts |
Count observations into a set of edges. |
binIndexOf |
The bin holding one value, or -1 outside the edges. Bins are half-open except the last, which includes its upper edge. |
quantileSorted |
Quantile of an ascending-sorted array, by the "type 7" definition that R, NumPy and a spreadsheet's QUARTILE all share. |
Histogram |
What histogram returns: a Binning plus counts, midpoints, total and missing. |
Binning |
edges, binWidth, the rule that actually chose the width, and whether the count was capped. |
BinOptions |
bins (a rule name or a fixed count), binWidth, and a range to frame the bins on so several histograms share a scale. |
BinRule |
The named rules: auto, fd, rice, scott, sqrt, sturges. auto is the narrower of fd and sturges, falling back to sturges when the IQR is zero. |
import {histogram} from '@apex/commons';
const h = histogram([1, 2, 2, 3, null, 9]);
h.counts; // observations per bin
h.missing; // 1
Browser support
Every module is SSR-safe: nothing touches document at import time, and the pieces that need the DOM take the element they act on. Crossfilter is DOM-free apart from its optional dataTable helper.