v2.6.0

<bmx-process-explorer>

Process mining in the browser: give it an event log - one row for each thing done, saying which case (an order, a claim, a ticket) it was done to, what was done and when, exported from an ERP, CRM, ticketing or workflow system - and it shows how the work really flows:

13 properties · 3 events · 15 methods · 7 parts

Example

Four hundred purchase orders at a fictional supplier. Choose an activity or a path for its figures, switch the map to Duration to see where orders wait, or press Replay.

Show markup
<bmx-process-explorer id="ex-pe" reference='{"path":["Create requisition","Approve requisition","Create purchase order","Send purchase order","Receive goods","Receive invoice","Match invoice","Pay invoice"],"optional":["Approve requisition"]}' style="--bmx-process-explorer-height: 32rem"></bmx-process-explorer>
<p class="note" id="ex-pe-out" role="status" style="margin: 12px 0 0">Four hundred purchase orders at a fictional supplier. Choose an activity or a path for its figures, switch the map to Duration to see where orders wait, or press Replay.</p>

<script type="module">
  await customElements.whenDefined('bmx-process-explorer');
  const pe = document.getElementById('ex-pe');
  const out = document.getElementById('ex-pe-out');

  // A purchase-to-pay log for Fernbrook Supplies, a fictional company, made up here with a seeded
  // random generator so it reads the same every time: one row per thing done to a purchase order.
  let seed = 7;
  const rnd = () => (seed = (seed * 1664525 + 1013904223) >>> 0) / 4294967296;
  const pick = list => list[Math.floor(rnd() * list.length)];
  const H = 3600000;
  const people = { buyer: ['Ana Ruiz', 'Tom Hale', 'Priya Nair'], approver: ['M. Okafor', 'J. Lindqvist'], stores: ['Goods-in A', 'Goods-in B'], ap: ['Payables 1', 'Payables 2'] };
  const rows = [];
  for (let i = 0; i < 400; i += 1) {
    const order = `PO-${24001 + i}`;
    const region = rnd() < 0.55 ? 'North' : 'South';
    const amount = Math.round(200 + rnd() * rnd() * 18000);
    let t = Date.UTC(2026, 0, 5, 8) + Math.floor(rnd() * 85 * 24) * H;
    const step = (activity, who, waitHours) => {
      t += Math.round(waitHours * H);
      rows.push({ case: order, activity, timestamp: new Date(t).toISOString(), resource: who, region, amount: String(amount) });
    };
    step('Create requisition', pick(people.buyer), 0);
    // Small orders skip approval (the reference marks it optional); a few big ones are turned down.
    if (amount > 1000) {
      step('Approve requisition', pick(people.approver), 4 + rnd() * 30 * (amount > 8000 ? 2 : 1));
      if (rnd() < 0.06) {
        step('Reject requisition', pick(people.approver), 1);
        continue;
      }
    }
    step('Create purchase order', pick(people.buyer), 1 + rnd() * 6);
    if (rnd() < 0.14) {
      step('Change purchase order', pick(people.buyer), 2 + rnd() * 20);
      step('Approve requisition', pick(people.approver), 5 + rnd() * 30);
    }
    step('Send purchase order', pick(people.buyer), 0.5 + rnd() * 3);
    const invoiceFirst = rnd() < 0.12;
    if (invoiceFirst) step('Receive invoice', pick(people.ap), 24 + rnd() * 72);
    step('Receive goods', pick(people.stores), 48 + rnd() * 120);
    if (!invoiceFirst) step('Receive invoice', pick(people.ap), 2 + rnd() * 48);
    step('Match invoice', pick(people.ap), 2 + rnd() * 24);
    if (rnd() < 0.18) {
      step('Hold invoice', pick(people.ap), 1);
      step('Match invoice', pick(people.ap), 24 + rnd() * 96);
    }
    step('Pay invoice', pick(people.ap), 24 + rnd() * 200);
  }
  pe.log = rows;

  pe.addEventListener('bmxSelect', e => {
    const d = e.detail;
    out.textContent = d.kind === 'activity' ? `bmxSelect: activity "${d.activity}".` : d.kind === 'path' ? `bmxSelect: path ${d.path.from} → ${d.path.to}, ${d.path.count} times.` : `bmxSelect: ${d.kind}.`;
  });
  pe.addEventListener('bmxFilterChange', e => {
    out.textContent = `bmxFilterChange: ${e.detail.cases} cases left.`;
  });
</script>
  • the process map, discovered from the log: every activity and every hand-over, drawn thicker the more cases take it, or coloured by how long the wait is; sliders keep only the activities and paths that matter;
    • an animated replay of the cases moving through the map over time;
    • variants (the different ways cases went), ranked by how common;
    • every case, with its timeline;
    • conformance against the way cases are meant to go: skipped, extra, repeated and out-of-order steps, case by case;
    • statistics: durations, cases started each week, the longest waits;
    • filters by date, duration, activity, variant and any attribute;
    • exports: the map as SVG or PNG, cases and variants as CSV.
<bmx-process-explorer id="pe" reference="Order → Approve → Ship → Invoice → Pay"></bmx-process-explorer>
<script>
  pe.log = await (await fetch('orders.csv')).text();   // CSV, XES, JSON or an array of records
</script>

Everything runs in the page: the log never leaves it. The same engine is in dist/node/bmx-process-explorer.mjs for a Worker or a server.

The map is keyboard-first: every activity is a button (arrow keys move to the nearest activity in that direction, Enter shows its figures, + and - zoom, 0 fits), and every figure the map draws is also in a table in the Statistics view.

Properties

PropertyAttributeTypeDefaultDescription
activityCoverage activity-coverage number 100 The share of all events the activities kept cover, 1-100 (the busiest first). Mutable.
allowOpen allow-open boolean true Offer the "Open a log" button.
columns columns BmxProcessColumns | string — Which columns hold the case, activity, time, end and resource (found from the header when not given). Object or JSON.
direction direction BmxProcessDirection 'down' Which way the map flows. Mutable.
fileName file-name string 'process' The name downloads are given (without the extension).
filters filters BmxProcessFilters | string {} The filters applied (object or JSON). Mutable.
label label string — The element's accessible name. Default: "Process explorer".
log log readonly Record<string, unknown>[] | string '' The event log: an array of records, or CSV, XES or JSON text.
metric metric BmxProcessMetric 'frequency' What the map shows: how often (frequency) or how long (duration). Mutable.
pathCoverage path-coverage number 100 The share of the path traffic drawn, 1-100 (the busiest first). Mutable.
reference reference BmxProcessReference | readonly string[] | string — The way cases are meant to go, to check them against: A → B → C, an array, or { path, optional } (object or JSON).
strings strings Partial<BmxProcessExplorerStrings> | string — Words to show instead of the English ones, as an object or JSON.
view view BmxProcessView 'map' The view shown. Mutable.

Events

EventDetailDescription
bmxFilterChange BmxProcessFilterDetail Fired when the filters change.
bmxLoad BmxProcessLoadDetail Fired when a log has been read.
bmxSelect BmxProcessSelectDetail Fired when an activity, path, variant or case is chosen.

Methods

MethodSignatureDescription
clearFilters clearFilters() => Promise<void> Removes every filter.
download download(format?: BmxProcessExportFormat, fileName?: string) => Promise<void> Saves an export as a file.
exportCsv exportCsv(kind?: "cases" | "variants") => Promise<string> The cases (with conformance, when there is a reference) or the variants, as CSV.
exportSvg exportSvg() => Promise<string> The map as a stand-alone SVG document.
fit fit() => Promise<void> Fits the map to the view.
getCases getCases() => Promise<BmxProcessCase[]> The cases the filters leave.
getConformance getConformance() => Promise<BmxConformanceSummary | undefined> The conformance of the cases the filters leave (needs reference).
getMap getMap() => Promise<BmxProcessMap> The map (activities and paths) as drawn.
getSummary getSummary() => Promise<BmxProcessSummary> The figures for the cases the filters leave.
getVariants getVariants() => Promise<BmxProcessVariant[]> The variants of the cases the filters leave, most frequent first.
pause pause() => Promise<void> Pauses the replay.
play play() => Promise<void> Starts the replay (from the start when it has finished).
seek seek(time: number) => Promise<void> Moves the replay to a moment (milliseconds).
selectActivity selectActivity(name: string) => Promise<void> Chooses an activity on the map and shows its figures.
setFilters setFilters(filters: BmxProcessFilters) => Promise<void> Replaces the filters.

CSS shadow parts

PartDescription
details The details panel beside the map.
edge A path on the map.
node An activity on the map; also node-selected.
stats The figures across the top.
table A table (variants, cases, statistics).
tabs The row of views.
viewport The area the map is drawn in.

CSS custom properties

PropertyDescription
--bmx-process-explorer-background The map's background.
--bmx-process-explorer-details-width The details panel's width.
--bmx-process-explorer-height The map's height.
--bmx-process-explorer-path The colour of paths.
--bmx-process-explorer-radius Corner radius of the element.
--bmx-process-explorer-token The colour of replay tokens.