Skip to content

packages/core/src/features/viz/lib/bar-fill.ts

Read as Markdown

This is the source snapshot used to build these API details. View this revision on GitHub.

Back to reference

1 import type { BarSeriesStyle } from './bar-series.js';
2 import type { FillStyle } from "@pibbl/core";
3 
4 /**
5  * Domain thresholds divide the bar fill into sharp, ordered color intervals.
6  *
7  * @see {@link BarSeriesStyle}
8  */
9 export interface BarThresholdFill {
10   /** The literal "threshold" identifying this variant. See {@link BarThresholdFill}. */
11   readonly type: "threshold";
12   /** Ordered numeric boundaries separating fill-color intervals. See {@link BarThresholdFill}. */
13   readonly thresholds: readonly number[];
14   /** Colors assigned to successive threshold intervals. See {@link BarThresholdFill}. */
15   readonly colors: readonly string[];
16 }
17 
18 export function barFill(
19   ctx: Pick<CanvasRenderingContext2D, "createLinearGradient">,
20   fill: FillStyle | BarThresholdFill,
21   map: (value: number) => number,
22   orientation: "vertical" | "horizontal",
23 ): (start: number, end: number) => FillStyle {
24   if (
25     typeof fill === "string" ||
26     fill instanceof CanvasGradient ||
27     fill instanceof CanvasPattern
28   )
29     return () => fill;
30   if (fill && !("type" in fill)) return () => fill;
31   if (!fill || fill.type !== "threshold")
32     throw new TypeError("BarSeries fill must be a paint or a threshold fill.");
33   const { thresholds, colors } = fill;
34   if (
35     !Array.isArray(thresholds) ||
36     !Array.isArray(colors) ||
37     colors.length !== thresholds.length + 1
38   )
39     throw new TypeError(
40       "BarSeries threshold fill requires one more color than thresholds.",
41     );
42   for (let i = 0; i < thresholds.length; i++) {
43     if (
44       typeof thresholds[i] !== "number" ||
45       !Number.isFinite(thresholds[i]) ||
46       (i > 0 && thresholds[i] <= thresholds[i - 1])
47     )
48       throw new RangeError(
49         "BarSeries fill thresholds must be finite and strictly increasing.",
50       );
51   }
52   // Validate even colors whose intervals are not present in the current data.
53   const validation = ctx.createLinearGradient(0, 0, 0, 1);
54   for (const color of colors) {
55     if (typeof color !== "string")
56       throw new TypeError(
57         "BarSeries threshold colors must be CSS color strings.",
58       );
59     validation.addColorStop(0, color);
60   }
61   return (start, end) => {
62     const low = Math.min(start, end),
63       high = Math.max(start, end);
64     let index = 0;
65     while (index < thresholds.length && thresholds[index] <= low) index++;
66     const valueStart = map(low),
67       valueEnd = map(high);
68     if (valueStart === valueEnd) return colors[index];
69     // Rectangle placement translates its local paint coordinates.
70     const origin = Math.min(valueStart, valueEnd);
71     const gradient = orientation === "vertical"
72       ? ctx.createLinearGradient(0, valueStart - origin, 0, valueEnd - origin)
73       : ctx.createLinearGradient(valueStart - origin, 0, valueEnd - origin, 0);
74     gradient.addColorStop(0, colors[index]);
75     for (; index < thresholds.length && thresholds[index] < high; index++) {
76       const offset = Math.max(
77         0,
78         Math.min(
79           1,
80           (map(thresholds[index]) - valueStart) / (valueEnd - valueStart),
81         ),
82       );
83       gradient.addColorStop(offset, colors[index]);
84       gradient.addColorStop(offset, colors[index + 1]);
85     }
86     gradient.addColorStop(1, colors[index]);
87     return gradient;
88   };
89 }
90 

Documentation built with @pibbl/core 0.0.2, revision 272a94a. ALPHA — NOT FOR PRODUCTION USE.