Skip to content

packages/core/src/features/viz/lib/pie-slices.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 { PieLayoutProps, PieLayoutSlice } from './pie-types.js';
2 import type { LineAccessor } from "./types.js";
3 import type { PlotKeyAccessor } from "./plot-types.js";
4 import { pointAccessor, pointData } from "./point-data.js";
5 
6 /**
7  * Value and stable-key accessors used to derive pie-slice geometry.
8  *
9  * @see {@link LineAccessor}
10  * @see {@link PlotKeyAccessor}
11  * @see {@link pieSlices}
12  * @see {@link PieLayoutProps}
13  */
14 export interface PieOptions<D> {
15   /** Value associated with this sample, input, or result. See {@link LineAccessor}. */
16   readonly value: LineAccessor<D>;
17   /** Property or callback supplying stable identity for each datum. See {@link PlotKeyAccessor}. */
18   readonly keyBy?: PlotKeyAccessor<D>;
19 }
20 
21 /**
22  * A datum's key, value, fraction, and angular interval in a pie layout.
23  *
24  * @see {@link pieSlices}
25  * @see {@link PieLayoutProps}
26  * @see {@link PieLayoutSlice}
27  */
28 export interface PieSliceGeometry<D> {
29   /** The original data item selected by this result. See {@link PieSliceGeometry}. */
30   readonly datum: D;
31   /** Index of the selected datum in its source data. See {@link PieSliceGeometry}. */
32   readonly index: number;
33   /** Stable identity used by the containing protocol. See {@link PieSliceGeometry}. */
34   readonly key: string;
35   /** Value associated with this sample, input, or result. See {@link PieSliceGeometry}. */
36   readonly value: number;
37   /** Normalized share or position represented by this value. See {@link PieSliceGeometry}. */
38   readonly fraction: number;
39   /** Angle at which the arc or slice begins. See {@link PieSliceGeometry}. */
40   readonly startAngle: number;
41   /** Angle at which the arc or slice ends. See {@link PieSliceGeometry}. */
42   readonly endAngle: number;
43 }
44 
45 /**
46  * Pure source-ordered full-circle layout. Angles use Canvas radians, clockwise from the top.
47  *
48  * @param data - Records whose values determine slice proportions.
49  * @param options - Value accessor and optional stable key accessor. See {@link PieOptions}.
50  * @returns Ordered slice geometry associated with the input records. See {@link PieSliceGeometry}.
51  *
52  * @see {@link PieOptions}
53  * @see {@link PieSliceGeometry}
54  */
55 export function pieSlices<D>(
56   data: readonly D[],
57   { value: input, keyBy }: PieOptions<D>,
58 ): readonly PieSliceGeometry<D>[] {
59   pointData(data, "pieSlices");
60   const accessor = pointAccessor(input);
61   const keys = new Set<string>();
62   let total = 0;
63   const rows = data.map((datum, index) => {
64     const raw = accessor(datum, index, data);
65     const value = raw == null ? 0 : raw;
66     if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
67       throw new RangeError(
68         `pieSlices: value at index ${index} must be finite and nonnegative, null, or undefined.`,
69       );
70     total += value;
71     const rawKey =
72       keyBy === undefined
73         ? index
74         : typeof keyBy === "function"
75           ? keyBy(datum, index, data)
76           : datum[keyBy];
77     if (
78       typeof rawKey !== "string" &&
79       (typeof rawKey !== "number" || !Number.isFinite(rawKey))
80     )
81       throw new TypeError(
82         `pieSlices: keyBy at index ${index} must return a string or finite number.`,
83       );
84     const key = String(rawKey);
85     if (keys.has(key)) throw new Error(`pieSlices: duplicate key "${key}".`);
86     keys.add(key);
87     return { datum, index, key, value };
88   });
89   if (!Number.isFinite(total))
90     throw new RangeError(
91       "pieSlices: value total must be finite; rescale the values.",
92     );
93   const start = -Math.PI / 2;
94   let sum = 0;
95   let angle = start;
96   return Object.freeze(
97     rows.map((row) => {
98       const startAngle = angle;
99       sum += row.value;
100       if (total > 0 && row.value > 0)
101         angle = start + (sum / total) * Math.PI * 2;
102       return Object.freeze({
103         ...row,
104         fraction: total === 0 ? 0 : row.value / total,
105         startAngle,
106         endAngle: angle,
107       });
108     }),
109   );
110 }
111 

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