ControlChart

Centre line, control limits and the rule violations of a process (an SPC or Shewhart chart). Reach for it to see a process drift before it leaves its specification.

import { ControlChart, controlLimits, violations } from "@umriss-ui/charts";

Control limits are computed from a named reference window, never from what happens to be visible; specification limits are chosen and drawn with LimitLine (ADR-0008).

The rules - outlier, run, trend, two of three - are exported as plain functions, so the violations can stand beside the chart as text.

Examples

Chart a process against its control limits

In the plant: origin names the reference window the limits come from; the burner's overshoot breaks them while every tile stays in specification.

import { ControlChart, LimitLine, Tooltip, useChart } from "@umriss-ui/charts";

/* Data from the plant world, written out here so the example runs on its own. */

/** A small LCG - reproducible across runs and platforms. */
function random(seed: number): () => number {
  let s = seed >>> 0;
  return () => {
    s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
    return s / 4294967296;
  };
}

/** The early shift, 06:00 to 14:00. */
const SHIFT_MINUTES = 480;

/** The kiln's zone 3, in °C: the numbers every part reads it against, once.
    Between the two warnings lies the tolerance - a tile fired outside it is
    scrap; `returnTo` is the alarm's dead band. */
const KILN = { target: 1200, tolerance: [1185, 1215], alarm: 1230, returnTo: 1222 } as const;

/** The kiln fires one tile every quarter of a minute when nothing holds it up -
    the ideal cycle time of the OEE's performance. */
const IDEAL_CYCLE_MINUTES = 0.25;

/** One minute of the line. */
interface Reading {
  /** Minutes since the start of the shift. */
  minute: number;
  /** Zone 3 of the kiln, in °C. */
  kiln: number;
  /** The exit pyrometer, in °C - `null` while it sends nothing. */
  exit: number | null;
  /** Whether the belt runs. */
  running: boolean;
  /** Tiles out of the kiln in this minute. */
  fired: number;
  /** Of them, the ones fired inside the tolerance. */
  good: number;
}

/** A tile taken off the belt and measured - one every ten minutes while it runs. */
interface Sample {
  minute: number;
  /** The tile's length after firing, in mm (nominal 600). */
  length: number;
}

/** A batch of tiles through the three stations. */
interface Batch {
  id: string;
  name: string;
  /** How many tiles the batch is planned for. */
  tiles: number;
  /** Minutes since the start of the shift, per station. */
  press: readonly [number, number];
  dryer: readonly [number, number];
  kiln: readonly [number, number];
}

interface Plant {
  readings: readonly Reading[];
  samples: readonly Sample[];
  batches: readonly Batch[];
}

/* When the exit pyrometer goes silent, and for how long. Fixed rather than
   seeded, so that a running control room shows its reading turn stale, then
   lost, and come back. */
const SILENT_FROM = 288;

const SILENT_FOR = 50;

/** The shift, from a seed. */
function plant(seed: number): Plant {
  const r = random(seed);

  /* The one excursion: a burner overshoots somewhere between 02:30 and 03:30
     into the shift, for half an hour, peaking near 1242 °C. The operator stops
     the feed twenty-two minutes in, for eighteen minutes. The noise around it
     is too small to reach a warning on its own, so this is the only crossing -
     which is what the tests hold. */
  const surgeFrom = 150 + Math.floor(r() * 60);
  const surgeFor = 30;
  const stopFrom = surgeFrom + 22;
  const stopFor = 18;

  /* The plan: batches through the kiln one after another, each pressed an
     hour and dried half an hour before - the first ones during the night
     shift. Tiles come out of the kiln only while a batch is in it, so the
     OEE's count and the batches' counts are the same tiles. The plan is a
     plan: a stop of the belt does not move it. */
  const batches: Batch[] = [];
  for (let at = 0, i = 0; at < SHIFT_MINUTES; i++) {
    const length = 70 + Math.floor(r() * 25);
    batches.push({
      id: `b-${4121 + i}`,
      name: `B-${4121 + i}`,
      tiles: Math.round(length / IDEAL_CYCLE_MINUTES / 10) * 10,
      press: [at - 60, at - 60 + length],
      dryer: [at - 30, at - 30 + length],
      kiln: [at, at + length],
    });
    at += length + 5;
  }

  const readings: Reading[] = [];
  const samples: Sample[] = [];
  let drift = 0;
  for (let minute = 0; minute < SHIFT_MINUTES; minute++) {
    drift = 0.9 * drift + (r() - 0.5) * 3;
    const into = minute - surgeFrom;
    const surge = into >= 0 && into <= surgeFor ? 42 * Math.sin((Math.PI * into) / surgeFor) : 0;
    const kiln = Math.round((KILN.target + drift + surge) * 10) / 10;

    const running = !(minute >= stopFrom && minute < stopFrom + stopFor);
    const loaded = batches.some((batch) => batch.kiln[0] <= minute && minute < batch.kiln[1]);
    const jam = r() < 0.12;
    const fired = running && loaded ? (jam ? 3 : 4) : 0;
    const inTolerance = kiln >= KILN.tolerance[0] && kiln <= KILN.tolerance[1];
    const silent = minute >= SILENT_FROM && minute < SILENT_FROM + SILENT_FOR;
    const exit = silent ? null : Math.round((kiln - 380 + (r() - 0.5) * 4) * 10) / 10;

    readings.push({ minute, kiln, exit, running, fired, good: inTolerance ? fired : 0 });

    if (running && minute % 10 === 0) {
      /* Hotter shrinks more: the length follows the kiln, so the control
         chart sees the excursion the trend sees. */
      const length = 600 + (KILN.target - kiln) * 0.05 + (r() - 0.5) * 0.6;
      samples.push({ minute, length: Math.round(length * 100) / 100 });
    }
  }

  return { readings, samples, batches };
}

/* A tile measured off the belt every ten minutes of the early shift. */
const SAMPLES = plant(7).samples;

/* The first fifteen samples, 06:00 to 08:20: the kiln demonstrably ran in
   control. Never the samples that happen to be visible (ADR-0008). */
const REFERENCE_WINDOW = { kind: "referenceWindow", from: 0, to: 15 } as const;

export default function ControlChartExample() {
  const { Chart, XAxis, YAxis } = useChart(SAMPLES);
  return (
    <Chart height={300} ariaLabel="Control chart of the tile length after firing">
      <XAxis value="minute" label="Minute of the shift" />
      <YAxis label="mm" />
      {/* Specification limits are chosen, control limits are computed. */}
      <LimitLine value={602.5} severity="alarm" label="USL" />
      <LimitLine value={597.5} severity="alarm" label="LSL" />
      <ControlChart
        value="length"
        data={SAMPLES}
        origin={REFERENCE_WINDOW}
        name="Tile length"
        labelUpper="UCL"
        labelLower="LCL"
        violationName="Tile length - rule violation"
      />
      <Tooltip mode="x" />
    </Chart>
  );
}

List the rule violations

In the plant: onViolations hands over the verdict the chart marks, so the same finds can stand beside it as text.

import { useState } from "react";
import { ControlChart, Tooltip, useChart } from "@umriss-ui/charts";
import type { RuleName, Violation } from "@umriss-ui/charts";

/* Data from the plant world, written out here so the example runs on its own. */

/** A small LCG - reproducible across runs and platforms. */
function random(seed: number): () => number {
  let s = seed >>> 0;
  return () => {
    s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
    return s / 4294967296;
  };
}

/** The early shift, 06:00 to 14:00. */
const SHIFT_MINUTES = 480;

/** The kiln's zone 3, in °C: the numbers every part reads it against, once.
    Between the two warnings lies the tolerance - a tile fired outside it is
    scrap; `returnTo` is the alarm's dead band. */
const KILN = { target: 1200, tolerance: [1185, 1215], alarm: 1230, returnTo: 1222 } as const;

/** The kiln fires one tile every quarter of a minute when nothing holds it up -
    the ideal cycle time of the OEE's performance. */
const IDEAL_CYCLE_MINUTES = 0.25;

/** One minute of the line. */
interface Reading {
  /** Minutes since the start of the shift. */
  minute: number;
  /** Zone 3 of the kiln, in °C. */
  kiln: number;
  /** The exit pyrometer, in °C - `null` while it sends nothing. */
  exit: number | null;
  /** Whether the belt runs. */
  running: boolean;
  /** Tiles out of the kiln in this minute. */
  fired: number;
  /** Of them, the ones fired inside the tolerance. */
  good: number;
}

/** A tile taken off the belt and measured - one every ten minutes while it runs. */
interface Sample {
  minute: number;
  /** The tile's length after firing, in mm (nominal 600). */
  length: number;
}

/** A batch of tiles through the three stations. */
interface Batch {
  id: string;
  name: string;
  /** How many tiles the batch is planned for. */
  tiles: number;
  /** Minutes since the start of the shift, per station. */
  press: readonly [number, number];
  dryer: readonly [number, number];
  kiln: readonly [number, number];
}

interface Plant {
  readings: readonly Reading[];
  samples: readonly Sample[];
  batches: readonly Batch[];
}

/* When the exit pyrometer goes silent, and for how long. Fixed rather than
   seeded, so that a running control room shows its reading turn stale, then
   lost, and come back. */
const SILENT_FROM = 288;

const SILENT_FOR = 50;

/** The shift, from a seed. */
function plant(seed: number): Plant {
  const r = random(seed);

  /* The one excursion: a burner overshoots somewhere between 02:30 and 03:30
     into the shift, for half an hour, peaking near 1242 °C. The operator stops
     the feed twenty-two minutes in, for eighteen minutes. The noise around it
     is too small to reach a warning on its own, so this is the only crossing -
     which is what the tests hold. */
  const surgeFrom = 150 + Math.floor(r() * 60);
  const surgeFor = 30;
  const stopFrom = surgeFrom + 22;
  const stopFor = 18;

  /* The plan: batches through the kiln one after another, each pressed an
     hour and dried half an hour before - the first ones during the night
     shift. Tiles come out of the kiln only while a batch is in it, so the
     OEE's count and the batches' counts are the same tiles. The plan is a
     plan: a stop of the belt does not move it. */
  const batches: Batch[] = [];
  for (let at = 0, i = 0; at < SHIFT_MINUTES; i++) {
    const length = 70 + Math.floor(r() * 25);
    batches.push({
      id: `b-${4121 + i}`,
      name: `B-${4121 + i}`,
      tiles: Math.round(length / IDEAL_CYCLE_MINUTES / 10) * 10,
      press: [at - 60, at - 60 + length],
      dryer: [at - 30, at - 30 + length],
      kiln: [at, at + length],
    });
    at += length + 5;
  }

  const readings: Reading[] = [];
  const samples: Sample[] = [];
  let drift = 0;
  for (let minute = 0; minute < SHIFT_MINUTES; minute++) {
    drift = 0.9 * drift + (r() - 0.5) * 3;
    const into = minute - surgeFrom;
    const surge = into >= 0 && into <= surgeFor ? 42 * Math.sin((Math.PI * into) / surgeFor) : 0;
    const kiln = Math.round((KILN.target + drift + surge) * 10) / 10;

    const running = !(minute >= stopFrom && minute < stopFrom + stopFor);
    const loaded = batches.some((batch) => batch.kiln[0] <= minute && minute < batch.kiln[1]);
    const jam = r() < 0.12;
    const fired = running && loaded ? (jam ? 3 : 4) : 0;
    const inTolerance = kiln >= KILN.tolerance[0] && kiln <= KILN.tolerance[1];
    const silent = minute >= SILENT_FROM && minute < SILENT_FROM + SILENT_FOR;
    const exit = silent ? null : Math.round((kiln - 380 + (r() - 0.5) * 4) * 10) / 10;

    readings.push({ minute, kiln, exit, running, fired, good: inTolerance ? fired : 0 });

    if (running && minute % 10 === 0) {
      /* Hotter shrinks more: the length follows the kiln, so the control
         chart sees the excursion the trend sees. */
      const length = 600 + (KILN.target - kiln) * 0.05 + (r() - 0.5) * 0.6;
      samples.push({ minute, length: Math.round(length * 100) / 100 });
    }
  }

  return { readings, samples, batches };
}

const SAMPLES = plant(7).samples;
const REFERENCE_WINDOW = { kind: "referenceWindow", from: 0, to: 15 } as const;

const RULES: Record<RuleName, string> = {
  outlier: "Beyond a control limit",
  run: "A run on one side of the centre",
  trend: "A steady climb or fall",
  twoOfThree: "Two of three near a limit",
};

const clock = (minute: number) => {
  const total = 6 * 60 + minute;
  return `${String(Math.floor(total / 60)).padStart(2, "0")}:${String(total % 60).padStart(2, "0")}`;
};

export default function ListTheViolations() {
  const [found, setFound] = useState<readonly Violation[]>([]);
  const { Chart, XAxis, YAxis } = useChart(SAMPLES);
  return (
    <div className="side-by-side">
      <div style={{ flex: "1 1 320px" }}>
        <Chart height={240} ariaLabel="Control chart of the tile length, its violations listed beside it">
          <XAxis value="minute" label="Minute of the shift" />
          <YAxis label="mm" />
          <ControlChart value="length" data={SAMPLES} origin={REFERENCE_WINDOW} name="Tile length" onViolations={setFound} />
          <Tooltip mode="x" />
        </Chart>
      </div>
      <ul className="side-note">
        {found.length === 0 ? (
          <li>No rule violated.</li>
        ) : (
          found.map((v) => (
            <li key={v.rule}>
              <strong>{RULES[v.rule]}</strong>: {v.indices.map((i) => clock(SAMPLES[i]!.minute)).join(", ")}
            </li>
          ))
        )}
      </ul>
    </div>
  );
}

Choose the rules

In the plant: rules switches rules off and sets their run lengths - here rule 4 is off and a run counts from eight - and without zoneLines the sigma zones stay unmarked.

import { useState } from "react";
import { ControlChart, Tooltip, useChart } from "@umriss-ui/charts";
import type { RuleName, Violation } from "@umriss-ui/charts";

/* Data from the plant world, written out here so the example runs on its own. */

/** A small LCG - reproducible across runs and platforms. */
function random(seed: number): () => number {
  let s = seed >>> 0;
  return () => {
    s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
    return s / 4294967296;
  };
}

/** The early shift, 06:00 to 14:00. */
const SHIFT_MINUTES = 480;

/** The kiln's zone 3, in °C: the numbers every part reads it against, once.
    Between the two warnings lies the tolerance - a tile fired outside it is
    scrap; `returnTo` is the alarm's dead band. */
const KILN = { target: 1200, tolerance: [1185, 1215], alarm: 1230, returnTo: 1222 } as const;

/** The kiln fires one tile every quarter of a minute when nothing holds it up -
    the ideal cycle time of the OEE's performance. */
const IDEAL_CYCLE_MINUTES = 0.25;

/** One minute of the line. */
interface Reading {
  /** Minutes since the start of the shift. */
  minute: number;
  /** Zone 3 of the kiln, in °C. */
  kiln: number;
  /** The exit pyrometer, in °C - `null` while it sends nothing. */
  exit: number | null;
  /** Whether the belt runs. */
  running: boolean;
  /** Tiles out of the kiln in this minute. */
  fired: number;
  /** Of them, the ones fired inside the tolerance. */
  good: number;
}

/** A tile taken off the belt and measured - one every ten minutes while it runs. */
interface Sample {
  minute: number;
  /** The tile's length after firing, in mm (nominal 600). */
  length: number;
}

/** A batch of tiles through the three stations. */
interface Batch {
  id: string;
  name: string;
  /** How many tiles the batch is planned for. */
  tiles: number;
  /** Minutes since the start of the shift, per station. */
  press: readonly [number, number];
  dryer: readonly [number, number];
  kiln: readonly [number, number];
}

interface Plant {
  readings: readonly Reading[];
  samples: readonly Sample[];
  batches: readonly Batch[];
}

/* When the exit pyrometer goes silent, and for how long. Fixed rather than
   seeded, so that a running control room shows its reading turn stale, then
   lost, and come back. */
const SILENT_FROM = 288;

const SILENT_FOR = 50;

/** The shift, from a seed. */
function plant(seed: number): Plant {
  const r = random(seed);

  /* The one excursion: a burner overshoots somewhere between 02:30 and 03:30
     into the shift, for half an hour, peaking near 1242 °C. The operator stops
     the feed twenty-two minutes in, for eighteen minutes. The noise around it
     is too small to reach a warning on its own, so this is the only crossing -
     which is what the tests hold. */
  const surgeFrom = 150 + Math.floor(r() * 60);
  const surgeFor = 30;
  const stopFrom = surgeFrom + 22;
  const stopFor = 18;

  /* The plan: batches through the kiln one after another, each pressed an
     hour and dried half an hour before - the first ones during the night
     shift. Tiles come out of the kiln only while a batch is in it, so the
     OEE's count and the batches' counts are the same tiles. The plan is a
     plan: a stop of the belt does not move it. */
  const batches: Batch[] = [];
  for (let at = 0, i = 0; at < SHIFT_MINUTES; i++) {
    const length = 70 + Math.floor(r() * 25);
    batches.push({
      id: `b-${4121 + i}`,
      name: `B-${4121 + i}`,
      tiles: Math.round(length / IDEAL_CYCLE_MINUTES / 10) * 10,
      press: [at - 60, at - 60 + length],
      dryer: [at - 30, at - 30 + length],
      kiln: [at, at + length],
    });
    at += length + 5;
  }

  const readings: Reading[] = [];
  const samples: Sample[] = [];
  let drift = 0;
  for (let minute = 0; minute < SHIFT_MINUTES; minute++) {
    drift = 0.9 * drift + (r() - 0.5) * 3;
    const into = minute - surgeFrom;
    const surge = into >= 0 && into <= surgeFor ? 42 * Math.sin((Math.PI * into) / surgeFor) : 0;
    const kiln = Math.round((KILN.target + drift + surge) * 10) / 10;

    const running = !(minute >= stopFrom && minute < stopFrom + stopFor);
    const loaded = batches.some((batch) => batch.kiln[0] <= minute && minute < batch.kiln[1]);
    const jam = r() < 0.12;
    const fired = running && loaded ? (jam ? 3 : 4) : 0;
    const inTolerance = kiln >= KILN.tolerance[0] && kiln <= KILN.tolerance[1];
    const silent = minute >= SILENT_FROM && minute < SILENT_FROM + SILENT_FOR;
    const exit = silent ? null : Math.round((kiln - 380 + (r() - 0.5) * 4) * 10) / 10;

    readings.push({ minute, kiln, exit, running, fired, good: inTolerance ? fired : 0 });

    if (running && minute % 10 === 0) {
      /* Hotter shrinks more: the length follows the kiln, so the control
         chart sees the excursion the trend sees. */
      const length = 600 + (KILN.target - kiln) * 0.05 + (r() - 0.5) * 0.6;
      samples.push({ minute, length: Math.round(length * 100) / 100 });
    }
  }

  return { readings, samples, batches };
}

const SAMPLES = plant(7).samples;
const REFERENCE_WINDOW = { kind: "referenceWindow", from: 0, to: 15 } as const;

/* Western Electric's run of eight in place of Nelson's nine; "two of three
   near a limit" is not watched on this line, so its zones need no lines. */
const CHOSEN = { twoOfThree: false, runLength: 8 } as const;

const RULES: Record<RuleName, string> = {
  outlier: "Beyond a control limit",
  run: "Eight on one side of the centre",
  trend: "A steady climb or fall",
  twoOfThree: "Two of three near a limit",
};

const clock = (minute: number) => {
  const total = 6 * 60 + minute;
  return `${String(Math.floor(total / 60)).padStart(2, "0")}:${String(total % 60).padStart(2, "0")}`;
};

export default function ChooseTheRules() {
  const [found, setFound] = useState<readonly Violation[]>([]);
  const { Chart, XAxis, YAxis } = useChart(SAMPLES);
  return (
    <div className="side-by-side">
      <div style={{ flex: "1 1 320px" }}>
        <Chart height={240} ariaLabel="Control chart of the tile length under three of the four rules">
          <XAxis value="minute" label="Minute of the shift" />
          <YAxis label="mm" />
          <ControlChart
            value="length"
            data={SAMPLES}
            origin={REFERENCE_WINDOW}
            name="Tile length"
            rules={CHOSEN}
            zoneLines={false}
            onViolations={setFound}
          />
          <Tooltip mode="x" />
        </Chart>
      </div>
      <ul className="side-note">
        {found.length === 0 ? (
          <li>No rule violated.</li>
        ) : (
          found.map((v) => (
            <li key={v.rule}>
              <strong>{RULES[v.rule]}</strong>: {v.indices.map((i) => clock(SAMPLES[i]!.minute)).join(", ")}
            </li>
          ))
        )}
      </ul>
    </div>
  );
}

When to use something else

Keyboard

The keys of Chart apply here.

API

ControlChartProps<T>

NameTypeDefaultDescription
valuerequiredValue<T>—The values of the chart - a number field of its data's rows or a function of them.

Shown in: Chart a process against its control limits, List the rule violations, Choose the rules and 1 more

datarequiredreadonly T[]—Required: the limits arise out of these values, not out of what happens to be visible.

Shown in: Chart a process against its control limits, List the rule violations, Choose the rules and 1 more

originrequiredControlLimitOrigin—Given, or from a named reference window - never tacitly from everything on the screen (ADR-0008).

Shown in: Chart a process against its control limits, List the rule violations, Choose the rules and 1 more

xAxisIdstring"x"Binding to an x axis.
yAxisIdstring"y"Binding to a y axis.
namestring"Series n", after its place in the chartThe name in legend and tooltip. Without one a warning stands in DEV - an unnamed series is a colour nobody can look up.

Shown in: Chart a process against its control limits, List the rule violations, Choose the rules and 1 more

format(value: number) => stringthe y axis' tickFormat, else the built-in number formatThe value as the tooltip writes it - on the line and on the violations.
colorstringthe palette's --uc-series-NAny CSS colour value.
tone| "ok"
| "warning"
| "alarm"
—A role instead of a colour value for the line; the theme resolves it. The violations are always "alarm".
rulesPartial<RuleOptions>—Switchable one by one; run lengths are parameters.

Shown in: Choose the rules

zoneLinesbooleantrueThe faint zone lines at one and two sigma. Rule 4 is about the two-sigma zone; without them a reader cannot check it.

Shown in: Choose the rules

labelUpperstringno labelLabel of the upper control limit in the axis band. The package brings no text along, not even "UCL" and "LCL".

Shown in: Chart a process against its control limits, Hold the tile length in control (Scenarios)

labelLowerstringno labelThe same for the lower control limit. Named separately because a chart may well want one of the two labelled and not the other.

Shown in: Chart a process against its control limits, Hold the tile length in control (Scenarios)

violationNamestring"Series n", after its place in the chartName of the scatter of violations in the legend and the tooltip. Without one a legend warns once in DEV. Before this, a German suffix stood on the name of the chart.

Shown in: Chart a process against its control limits, Hold the tile length in control (Scenarios)

Events

NameTypeDefaultDescription
onViolations(found: readonly Violation[]) => void—The violations as data - they can be listed beside the chart as well as marked on it.

Shown in: List the rule violations, Choose the rules

Types on this page

Value<T>

A value read from the row (ADR-0048): the name of a number field, compared by its name, or a function, compared by its source text (ValueFunction).

type Value<T> = NumberField<T> | ValueFunction<T>;

ControlLimitOrigin

Where the limits come from. There are exactly these two possibilities.

type ControlLimitOrigin =
  /** From a baseline the caller has fixed. Taken as it is - even where the
      series would yield something else out of itself. */
  | { readonly kind: "given"; readonly center: number; readonly sigma: number }
  /** From exactly this index range of the series, half-open [from, to). */
  | { readonly kind: "referenceWindow"; readonly from: number; readonly to: number };

RuleOptions

Which rules run, and with which run lengths.

NameTypeDefaultDescription
outlierrequiredboolean—
runrequiredboolean—
trendrequiredboolean—
twoOfThreerequiredboolean—
runLengthrequirednumber—Points on one side of the centre line from which rule 2 fires.
trendLengthrequirednumber—Points in succession from which rule 3 fires.

Violation

A rule and the indices it violates.

NameTypeDefaultDescription
rulerequiredRuleName
| "outlier"
| "run"
| "trend"
| "twoOfThree"
—

Shown in: List the rule violations, Choose the rules, Hold the tile length in control (Scenarios)

indicesrequiredreadonly number[]—

Shown in: List the rule violations, Choose the rules, Hold the tile length in control (Scenarios)

NumberField<T>

The fields of a row whose value is a number, or absent.

type NumberField<T> = NoInfer<
  {
    [K in keyof T & string]-?: T[K] extends number | null | undefined ? K : never;
  }[keyof T & string]
>;

ValueFunction<T>

A value computed from the row - the function form of Value. Compared by its source text, not its identity - an inline function is new on every render. Known limit: one that reads a changed closure variable under the same text is not run again; pass a new data reference then. A native or bound function is compared by identity.

type ValueFunction<T> = (d: T, index: number) => number | null | undefined;

RuleName

Name of a rule, the way it is switched on and off in violations.

type RuleName = "outlier" | "run" | "trend" | "twoOfThree";

Every page of @umriss-ui/charts

Getting started

Chart

Series

Limits and alarms

Around the chart

API index