packages/core/src/features/viz/lib/bar-fill.ts
This is the source snapshot used to build these API details. View this revision on GitHub.
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 version
Section titled “Documentation version”Documentation built with @pibbl/core 0.0.2, revision 272a94a. ALPHA — NOT FOR PRODUCTION USE.