Premium and licensing

Premium chart families and separate type modules, licence keys, domain binding, watermark and dev hosts.

View .md Agent setup

west-coast-charts/premium adds nine chart types for operational and analytical dashboards:

chart.type Use it for
sankey flows between stages (traffic, budgets, funnels with branches)
chord two-way relationships between groups (service-to-service calls, trade)
gantt schedules with dependencies, baselines and calendars
tree hierarchies and org charts with cards and semantic zoom
stock financial time series with indicators and drawing tools
map choropleths, markers and routes (world and US states maps included)
marimekko two-dimensional share (market × segment)
network node-link graphs
radialColumn radial dashboards that combine several rings with linked highlighting, see the radial column guide

Two more premium types come as a separate, lazily loaded type module, west-coast-charts/layers/quint (WestCoastCharts.use([premium, quint])):

chart.type Use it for
quintTimeline an operations day on one time axis: requests and error rate, cost heatmaps, error budgets, latency lines, region traffic, incidents and deploys, see the Quint guide
quintConvergence the same lanes with a labelled focus lens that magnifies the time around an incident (time only, never values)

Two further families are supplied as optional modules: west-coast-charts/layers/vitals adds vitalsClock (Vitals Clock), and west-coast-charts/layers/schedule adds schedule (Practice Schedule). The complete Premium offer therefore contains thirteen families: nine in the main bundle and four in the type modules.

The paid offer covers the separately supplied Premium distribution and services described in the business terms. It does not replace the MIT licence on MIT-marked code. See the licensing overview for the scope and links to the original notices and full terms.

Premium code is supplied in the separate complete package obtained through a trial or paid licence. The public MIT Core package does not export /premium, /layers or /maps. Install the complete package instead of the Core-only package before using the examples below. Without a licence key every chart renders and works fully, and on public host names it shows a small "unlicensed" line. Development hosts never show it.

Loading premium

With a bundler, install the plugin once at startup:

js
import WestCoastCharts from 'west-coast-charts'
import premium from 'west-coast-charts/premium'

WestCoastCharts.use(premium)

const chart = new WestCoastCharts(document.querySelector('#chart'), {
  chart: { type: 'sankey', height: 320 },
  series: [{
    data: [
      { from: 'Visits', to: 'Pricing page', value: 4200 },
      { from: 'Visits', to: 'Docs', value: 2600 },
      { from: 'Pricing page', to: 'Trial', value: 1100 },
      { from: 'Docs', to: 'Trial', value: 700 },
      { from: 'Trial', to: 'Paid', value: 420 },
    ],
  }],
})
await chart.render()

With script tags, load the premium bundle after the core. It installs itself into the global WestCoastCharts; its own global is WestCoastChartsPremium.

html
<script src="./dist/west-coast-charts.min.js"></script>
<script src="./dist/west-coast-charts-premium.min.js"></script>
<div id="chart"></div>
<script>
  WestCoastCharts.setLicenseKey('YOUR-LICENCE-KEY') // see below; omit while evaluating
  new WestCoastCharts(document.querySelector('#chart'), {
    chart: { type: 'chord', height: 360 },
    series: [{
      data: [
        { from: 'web', to: 'api', value: 120 },
        { from: 'api', to: 'db', value: 80 },
        { from: 'api', to: 'cache', value: 60 },
        { from: 'worker', to: 'db', value: 40 },
      ],
    }],
  }).render()
</script>

Install the plugin before the first premium chart renders. Without it, render() rejects with a message that tells you to import and use() the premium plugin.

The map type needs map shapes. world and us-states ship in west-coast-charts/maps/* (dist/maps/*.js for script tags) and register themselves with WestCoastCharts.registerMap.

Licence keys

A licence key is a single string you receive from West Coast Software when you buy a licence. It is checked offline in the browser: there is no network request, no telemetry and no licence server.

Register it once, before you create premium charts:

js
import WestCoastCharts from 'west-coast-charts'
import premium from 'west-coast-charts/premium'

WestCoastCharts.use(premium)

const licence = await WestCoastCharts.setLicenseKey('YOUR-LICENCE-KEY')
if (!licence) {
  // invalid, expired, for another major version or another domain: charts still work, with the watermark
  console.warn('WestCoast Charts licence not accepted')
}

setLicenseKey(key) returns a promise. It resolves to the key's contents when the key is valid for this page, and to null otherwise. It never throws and never blocks rendering. The contents look like this:

Field Meaning
l licensee (your company)
t tier: 'premium' or 'oem'
e expiry in milliseconds since 1970; 0 = perpetual
v [first, last] major versions covered, inclusive
i issue date (milliseconds since 1970), optional
d host patterns the key is bound to, optional (see Domain binding)

The same function is available as premium.setLicenseKey(key). To use a different key for one chart, for example in a multi-tenant page, set chart.licenseKey; it wins over the global key for that chart:

js
import WestCoastCharts from 'west-coast-charts'
import premium from 'west-coast-charts/premium'

WestCoastCharts.use(premium)

const chart = new WestCoastCharts(document.querySelector('#chart'), {
  chart: { type: 'marimekko', height: 300, licenseKey: 'TENANT-SPECIFIC-KEY' },
  series: [
    { name: 'Enterprise', data: [{ x: 'EMEA', y: 40 }, { x: 'Americas', y: 55 }, { x: 'APAC', y: 25 }] },
    { name: 'SMB', data: [{ x: 'EMEA', y: 30 }, { x: 'Americas', y: 20 }, { x: 'APAC', y: 35 }] },
  ],
})
await chart.render()
// 'TENANT-SPECIFIC-KEY' is a placeholder, so this example shows the watermark

How a key is checked:

  • The signature is verified with ECDSA P-256 / SHA-256 in plain JavaScript. It takes roughly 15 ms once per key and page, and the result is cached.
  • It needs no crypto.subtle, so it works the same on HTTPS, plain HTTP, intranet host names and bare IP addresses.
  • A key covers the major versions in v. The version that counts is the major number of the installed package: WestCoast Charts 1.0.0 and every later 1.x release are major 1, so a key with v: [1, 1] covers all of them.
  • The runtime must also trust the key's signer. Version 1.1.1-rc.4 introduces a new active signer and retains the previous public key for existing licences. Keys issued by the new signer need 1.1.1-rc.4 or a later runtime that trusts it; older runtimes do not accept those new signatures merely because the key's major-version range includes 1. Existing licences retain their original expiry, major-version and host restrictions.
  • An expired key is treated like a missing key: the watermark returns, nothing stops working.

Issuing evaluation, review and gift keys

The companion tools/license/issue-access.cjs supports --purpose evaluation and --purpose review, delegating signing to the unchanged approved issuer. These future keys expire 30 days after issuance by default; an explicit --expires must be after issuance. They cannot use --perpetual or an expiry of zero. Add --domains "review.example.com" when the evaluation host is known; host binding is optional when reviewers need several unknown environments.

Intentional gifts use explicit --purpose grant; the additional --gift flag is rejected without that purpose. A grant may deliberately be perpetual. The ten previously issued perpetual gift keys retain their original major-version, host and expiry rights. They are not reclassified as trials or modified by this issuance policy. Existing paid issuance remains backward compatible: omitted --purpose means paid, with its previous expiry behavior. Purpose controls issuance only and is not a new runtime permission or signed payload field.

sh
node tools/license/issue-access.cjs --licensee "Review organisation" --purpose review --domains "review.example.com"
node tools/license/issue-access.cjs --licensee "Intentional gift" --purpose grant --gift --perpetual --versions 1

Verification is offline. There is no online revocation service or global revocation switch. Changing future issuance defaults does not revoke an issued signature. An older installed bundle continues accepting a signature it trusts while that key satisfies the bundle's existing expiry, version and host checks. A future runtime could change its own trust policy, but that would not update older copies or withdraw independently granted licence rights.

Domain binding

A key may carry host patterns in d. The key is then accepted only when the page's host name matches one pattern completely (case-insensitive, port ignored). * stands for any run of characters, including dots.

Pattern Matches Does not match
acme.com acme.com www.acme.com
*.acme.com app.acme.com, eu.app.acme.com acme.com
10.20.* 10.20.0.5, 10.20.31.7 10.2.0.5
ops-* ops-grafana, ops-01 devops-01

A key without d is valid on every host. To cover both the apex domain and its subdomains, the key needs both patterns: acme.com and *.acme.com.

The unlicensed watermark

When a premium chart renders on a host that is not exempt (below) and no valid key applies, the chart gets one quiet line in its bottom-right corner:

WestCoast Charts Premium · unlicensed

  • It is SVG text (<text class="wcc-unlicensed">) in 10 px, the theme's muted colour at reduced opacity, pointer-events: none and aria-hidden="true", so it never blocks interaction or reaches screen readers.
  • It is part of the chart SVG, so it also appears in PNG and SVG exports.
  • The console shows one warning per page: WestCoast Charts premium: no valid licence key for this site, see WestCoastCharts.setLicenseKey().
  • Nothing else changes: no feature is disabled, no data is hidden, nothing is sent anywhere.
  • Core and west-coast-charts/types charts never get a watermark.
js
import WestCoastCharts from 'west-coast-charts'
import premium from 'west-coast-charts/premium'

WestCoastCharts.use(premium)

// a public host without a key: the chart works and carries the watermark
await new WestCoastCharts(document.querySelector('#chart'), {
  chart: { type: 'network', height: 300 },
  series: [{
    data: [
      ['gateway', 'auth'],
      ['gateway', 'orders'],
      ['orders', 'payments'],
    ],
  }],
}).render()

Set the key before the first premium chart renders. A watermark that is already drawn stays until the chart redraws.

Development and test hosts

These pages never show the watermark, with or without a key:

  • localhost, 127.0.0.1 and [::1]
  • host names ending in .local or .localhost (for example myapp.localhost, printer.local)
  • file:// pages
  • environments without location or without a host name (server-side code, workers, jsdom without a URL)
js
import WestCoastCharts from 'west-coast-charts'
import premium from 'west-coast-charts/premium'

WestCoastCharts.use(premium)

// on a dev server: no key needed, no watermark
await new WestCoastCharts(document.querySelector('#chart'), {
  chart: { type: 'sankey', height: 260 },
  series: [{ data: [{ from: 'A', to: 'B', value: 3 }, { from: 'A', to: 'C', value: 2 }] }],
}).render()

Staging and preview hosts (staging.acme.com, *.vercel.app, an intranet IP) are not exempt; include them in the key's patterns.

setLicenseKey() itself has no exemption: on localhost it still tells you whether the key would be accepted, so you can test a key before you deploy it.

js
import WestCoastCharts from 'west-coast-charts'
import premium from 'west-coast-charts/premium'

WestCoastCharts.use(premium)

const result = await WestCoastCharts.setLicenseKey('not-a-real-key')
console.log(result) // null: the dev host hides the watermark, but the key itself is rejected
if (result !== null) throw new Error('expected null')

Browser requirements (ES2020)

The premium bundle is built for ES2020 engines because the offline signature check uses BigInt:

Browser Minimum
Chrome, Edge 80
Firefox 80
Safari (macOS, iOS) 14

Older engines (Internet Explorer, legacy Edge, Safari 13) cannot load the premium bundle; the core and the chart types still run there (ES2018, see Getting started). The ES2020 build also keeps the bundle smaller: it saves 2,232 B gzip, more than the 1,708 B gzip the licence check adds (figures from docs/architecture/size-ledger.md (library repository)).

FAQ

Does the licence check contact a server? No. The key carries its own signature, which is checked against a public key built into the bundle.

Which releases does my key cover? Signature acceptance requires both a trusted signer and the major-version range in the key. The commercial offer covers major version 1 and available updates during the paid term. Later major versions are not included unless separately agreed. New RC4-signer keys require RC4 or a later runtime trusting that signer; older RC3 runtimes do not accept them.

What happens when a key expires? An expired technical key brings back the watermark and console warning; charts keep working. A paid annual entitlement and a key's technical expiry are different: a key may remain valid to support continued use of an existing licensed deployment after the paid term ends. This does not extend update delivery or support.

What does the watermark mean? It indicates that the runtime has not accepted a key for this chart and host. It does not determine or limit independent MIT rights. Contact service@pekagi.io for help with a purchased key or an integration issue.

What licence applies to each part? The MIT-marked source keeps its MIT licence. Premium distribution, updates and support have separate business terms. See the licensing overview, package LICENSE, and NOTICE.

Network layouts and timing diagnostics

The default plotOptions.network.layout.type: 'force' computes positions. If your application already has a layout, choose 'preset' and provide finite numeric x and y on every item in series[].nodes:

js
import WestCoastCharts from 'west-coast-charts'
import premium from 'west-coast-charts/premium'

WestCoastCharts.use(premium)
const chart = new WestCoastCharts(document.querySelector('#chart'), {
  chart: { type: 'network', height: 400 },
  series: [{
    nodes: [{ id: 'api', x: -60, y: 0 }, { id: 'db', x: 60, y: 0 }],
    edges: [{ source: 'api', target: 'db', weight: 3 }],
  }],
  plotOptions: { network: { layout: { type: 'preset' } } },
})
await chart.render()
const timings = premium.api(chart).performanceStats()

Preset coordinates are world coordinates, fitted to the chart viewport without running a force simulation or moving overlapping nodes apart. Nodes referenced by edges also need explicit positioned definitions. Missing or invalid positions produce a one-time warning naming the nodes and fall back to the force layout. Dragging changes the current view; a subsequent data/options update reads the supplied positions again. Preset mode is independent of SVG/Canvas rendering.

Canvas changes drawing, not the force-layout calculation. Similar total SVG and Canvas render times can occur when layout dominates. Compare the same data, options, browser and hardware before drawing a performance conclusion. performanceStats() returns a diagnostic copy containing modelMs, layoutMs, drawMs, labelsMs, renderMs, frames, settled, the actual layout, and renderer. It returns null for a chart without network data.

The instrumented phases accumulate for the current renderer instance. modelMs covers model construction, layoutMs layout setup and simulation/decluttering, drawMs drawing setup and frames, and labelsMs label planning. Updating label anchors during a frame belongs to drawing. Phase counters are scoped diagnostic regions rather than an exhaustive partition of all application work; their sum is not a whole-page load time. renderMs measures the initial renderer call. With animated force layouts the first render is labelled while geometry can continue moving; listen for layoutSettled if you need the settled arrangement. Timings depend on the environment and are excluded from describe() and the semantic data contract.

WestCoast Charts 1.1.5 · generated from the library guide at commit 95f093ba. Code examples in the guide are executed by the library's docs check.