@parliamentarch/core 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # Parliamentarch-TS Core
2
+
3
+ Tools to generate arch-styled parliamentary diagrams.
4
+
5
+ ![Example diagram](../../sample.svg)
6
+
7
+ This package handles two things: majorly (in the `geometry` submodule), the geometry of how the seats are arranged in space, and as an aside (in the `utils` submodule), some util functions shared by the other modules taking over from there.
8
+
9
+ Those won't be enough to generate SVG files or nodes by themselves. In fact, there is nothing specific to SVG in this package, and a wholly different display system could be used to generate a diagram from what this package provides.
10
+
11
+ ## Base math and layout
12
+
13
+ The idea is to display a certain number of seats so that they collectively form a hemicycle, which is a half-annulus where the inner radius is also a half of the outer radius. The shape fits in a 2:1 rectangle in a landscape orientation, with the hole of the annulus at the bottom.
14
+
15
+ The seats are placed in rows, such that:
16
+
17
+ - The rows are semi-circles, concentric to the annulus.
18
+ - The difference between the radii of two consecutive rows is a constant called the "row thickness" (radii is the plural of radius).
19
+ - The seats are circles (or disks) of equal diameter. That radius divided by the row thickness is called the "seat radius factor".
20
+ - The center of a seat is on the semi-circle the seat's row.
21
+ - Within each row, the distance between the centers of two consecutive seats is a constant, at least equal to the row thickness.
22
+ - The innermost row's semi-circle is the inner arc of the annulus.
23
+ - The radius of the outermost row's semi-circle is equal to the radius of the outer arc minus half of the row thickness, such that no seat may overlap the outer arc.
24
+ - The vertical distance between the center of the bottom-most seats of each row, which are the first and last seat of each row, is equal to half of the row thickness, such that no seat may overlap the bottom of the rectangle.
25
+ - However, when a row contains only one seat, the previous rule does not apply, and the seat is placed at the horizontal center of the diagram.
26
+
27
+ As a result of these constraints, there is a maximum number of seats that can be placed in a diagram with a given number of rows. For numbers of seats below this maximum, there exists a number of strategies to distribute the seats among the rows.
28
+
29
+ It is also possible to change the span angle of the diagram (which is 180° in the example above). If a smaller angle is specified, the initial annulus must be cut along two radii of the larger circle. For seat placements, what applied to the bottom of the rectangle now applies to the two radii.
30
+
31
+ ## Tweakable parameters
32
+
33
+ As hinted above, some parameters can be set to customize the layout of the diagram:
34
+
35
+ - The span angle of the hemicycle can be sety to a value lower than 180° (higher values are not supported). However, values so low as to prevent some rows from containing even one seat are not supported, will yield incorrect results, and may throw errors in future versions.
36
+ - The number of rows can be set higher than the minimum required to hold the provided number of seats. This will result in smaller seats (more precisely, a smaller row thickness).
37
+ - The seat radius factor can be set between 0 and 1, with the seats touching their neighbors when the factor is 1.
38
+ - As long as the number of seats is not the maximum number of seats for the given number of rows, different strategies can be chosen to distribute the seats.
39
+
40
+ ## Geometry module contents
41
+
42
+ These are found in the `@parliamentarch/core/geometry` module.
43
+
44
+ `getNRowsFromNSeats(nSeats: number, spanAngle?: number): number`
45
+
46
+ Returns the minimum number of rows required to hold the given number of seats in a diagram with the given span angle.
47
+
48
+ `getRowsFromNRows(nRows: number, spanAngle?: number): number[]`
49
+
50
+ Returns a list of each row's maximum seat capacity, starting from inner to outer, from a given number of rows and span angle. The list is increasing and its length is equal to the number of rows.
51
+
52
+ `getRowThickness(nRows: number): number`
53
+
54
+ Returns the row thickness, i.e the difference between the radii of two consecutive rows, for a given number of rows.
55
+
56
+ `FillingStrategy`
57
+
58
+ A string enum of the implemented strategies to fill the seats among the rows:
59
+
60
+ - `DEFAULT`: The seats are distributed proportionally to the maximal number of seats each row can hold. The result is that the lateral distance between the seats is close among all rows.
61
+ - `EMPTY_INNER`: This selects as few outermost rows as necessary to hold the given seats, then distributes the seats proportionally among them. Depending on the number of seats and rows, this either leaves empty inner rows, or is equivalent to the `DEFAULT` strategy. This is equivalent to the legacy "dense rows" option, in that in non-empty rows, the distance between consecutive seats is the smallest possible, and is close among all rows.
62
+ - `OUTER_PRIORITY`: This fills the rows to their maximal capacity, starting with the outermost rows going in. The result is that given a number of rows, adding one seat makes a change in only one row.
63
+
64
+ `getSeatCenters(nSeats: number, options?): Map<[number, number], number>`
65
+
66
+ This is the main function of the submodule. The options are as follows:
67
+
68
+ - `minNRows?: number`: Sets a minimum number of rows.
69
+ - `fillingStrategy?: FillingStrategy`: The strategy to use, defaults to `DEFAULT`.
70
+ - `spanAngle?: number`: The span angle of the diagram in degrees, defaults to 180.
71
+
72
+ The function returns a map representing the ensemble of seats. The keys are `[x, y]` pairs, the cartesian coordinates of the center of the seat. The coordinates start from the bottom-left corner of the rectangle, with the x axis pointing to the right and the y axis pointing up. The outer radius of the annulus, equal to the height and to half of the width of the rectangle, is 1, so `x` goes from 0 to 2 and `y` goes from 0 to 1.
73
+
74
+ The values are the angle, in radians, calculated from the right-outermost point of the annulus arc, through the center of the annulus, to the center of the seat. Sorting the keys by decreasing value returns the seats arranged from left to right. The order of the entries in the Map is meaningless.
75
+
76
+ ## Utils module contents
77
+
78
+ These are found in the `@parliamentarch/core/utils` module.
79
+
80
+ `dispatchSeats<SeatDisplay, SeatLocation>(attribution, seats): Map<SeatDisplay, SeatLocation[]>`
81
+
82
+ This generic function merges an attribution of seat informations with a list of seat locations, to a more useful format.
83
+
84
+ - `attribution: ReadonlyMap<SeatDisplay, number> | readonly WithNumber<SeatDisplay>[]` (WithNumber meaning that it takes an optional `nSeats` number parameter, defaulting to 1): SeatDisplay usually represents a team owning seats, and this parameter represents how many seats each team has.
85
+ - `seats: Iterable<SeatLocation>`: the location of seats. These must be in the same number as the sum of seats for all teams, otherwise an error will be thrown.
86
+
87
+ `regroupSeatCenters<SeatDisplay, SeatLocation=(readonly [number, number])>(seatCenters): Iterable<readonly [SeatDisplay, readonly SeatLocation[]]>`
88
+
89
+ This generic function turns one representation of how each seat is displayed, into a more versatile one.
90
+
91
+ - `seatCenters: Iterable<readonly [SeatLocation, SeatDisplay]>`: a simple, naive mapping of each seat location to how it should be displayed
92
+
93
+ `precomputeFromAttribution<SeatDisplay>(attribution, options?): PrecomputeReturn<SeatDisplay>`
94
+
95
+ This function pre-calculates some information from an attribution of seats, making it almost enough to be displayed. The return value is an object containing grouped seat centers as returned by the previous function, under the key `groupedSeatCenters`, and the radius of the seats in the same unit as the coordinates, under the key `seatActualRadius`.
96
+
97
+ - `attribution: ReadonlyMap<SeatDisplay, number> | readonly WithNumber<SeatDisplay>[]`
98
+ - `options.seatRadiusFactor`: the factor between 0 and 1 described earlier. At 1, neighboring seats will touch one another.
99
+ - `options`: the rest of the options are the same as taken by the `getSeatCenters` function.
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Returns the thickness of a row in the same unit as the coordinates.
3
+ * It is equal to the diameter of a single seat.
4
+ *
5
+ * If you divide the half-disk of the hemicycle into one half-disk of half the radius and one half-annulus outside of it,
6
+ * the innermost row lies on the border between the two, and the outermost row lies entirely inside the half-annulus.
7
+ * So, looking at the line cutting the circle and the annulus in half (which is the bottom border of the diagram),
8
+ * all rows minus one half of the innermost are on the left, same on the right,
9
+ * and the radius of the void at the center is equal to that value again.
10
+ * So, total = 4 * (nRows-.5) = 4 * nRows - 2.
11
+ */
12
+ export declare function getRowThickness(nRows: number): number;
13
+ /**
14
+ * This indicates the maximal number of seats for each row for a given number of rows.
15
+ * @param spanAngle if provided, it is the angle in degrees that the hemicycle, as an annulus arc, covers.
16
+ * @returns an array of number of seats per row, from inner to outer.
17
+ * The length of the array is nRows.
18
+ */
19
+ export declare function getRowsFromNRows(nRows: number, spanAngle?: number): number[];
20
+ /**
21
+ * Returns the minimal number of rows necessary to contain nSeats seats.
22
+ */
23
+ export declare function getNRowsFromNSeats(nSeats: number, spanAngle?: number): number;
24
+ export declare enum FillingStrategy {
25
+ /**
26
+ * The seats are distributed among all the rows,
27
+ * proportionally to the maximal number of seats each row can contain.
28
+ */
29
+ DEFAULT = "default",
30
+ /**
31
+ * Selects as few outermost rows as necessary, then distributes the seats among them,
32
+ * proportionally to the maximal number of seats each row can contain.
33
+ */
34
+ EMPTY_INNER = "empty_inner",
35
+ /**
36
+ * Fills up the rows as much as possible, starting from the outermost ones.
37
+ */
38
+ OUTER_PRIORITY = "outer_priority"
39
+ }
40
+ export interface GetSeatCentersOptions {
41
+ minNRows: number;
42
+ fillingStrategy: FillingStrategy;
43
+ spanAngle: number;
44
+ }
45
+ /**
46
+ * Computes the coordinates of the centers of the seats, with (as a bonus) the angle of each seat in the hemicycle.
47
+ * The canvas is assumed to be 2 in width and 1 in height, with the x axis pointing right and the y axis pointing up.
48
+ * The angle is calculated from the [1, 0] center of the hemicycle, in radians and in trigonometric positive direction,
49
+ * tending to 0 for the rightmost seats, 90° or PI/2 for the center, and 180° or PI for the leftmost seats.
50
+ * @param minNRows The minimal number of rows required to contain `nSeats` seats will be computed automatically.
51
+ * If `minNRows` is greater, then that will be the number of rows, otherwise the parameter is ignored.
52
+ * Passing a higher number of rows will make the diagram sparser,
53
+ * for which non-default filling strategies are more adapted.
54
+ * @param fillingStrategy determines how the seats are distributed among the rows, see the `FillingStrategy` enum.
55
+ * @param spanAngle is the angle from the side of the rightmost seats,
56
+ * through the center, to the side of the leftmost seats.
57
+ * It takes a value in degrees and defaults to 180 to make a true hemicycle.
58
+ * Values above 180 are not supported.
59
+ * @returns a map whose keys are the seat centers as [x, y] coordinates, with the angle of the seat as values.
60
+ */
61
+ export declare function getSeatCenters(nSeats: number, { minNRows, fillingStrategy, spanAngle, }?: Partial<GetSeatCentersOptions>): Map<[number, number], number>;
@@ -0,0 +1,162 @@
1
+ // default angle, in degrees, coming from the rightmost seats through the center to the leftmost seats
2
+ const DEFAULT_SPAN_ANGLE = 180;
3
+ /**
4
+ * Returns the thickness of a row in the same unit as the coordinates.
5
+ * It is equal to the diameter of a single seat.
6
+ *
7
+ * If you divide the half-disk of the hemicycle into one half-disk of half the radius and one half-annulus outside of it,
8
+ * the innermost row lies on the border between the two, and the outermost row lies entirely inside the half-annulus.
9
+ * So, looking at the line cutting the circle and the annulus in half (which is the bottom border of the diagram),
10
+ * all rows minus one half of the innermost are on the left, same on the right,
11
+ * and the radius of the void at the center is equal to that value again.
12
+ * So, total = 4 * (nRows-.5) = 4 * nRows - 2.
13
+ */
14
+ export function getRowThickness(nRows) {
15
+ return 1 / (4 * nRows - 2);
16
+ }
17
+ /**
18
+ * This indicates the maximal number of seats for each row for a given number of rows.
19
+ * @param spanAngle if provided, it is the angle in degrees that the hemicycle, as an annulus arc, covers.
20
+ * @returns an array of number of seats per row, from inner to outer.
21
+ * The length of the array is nRows.
22
+ */
23
+ export function getRowsFromNRows(nRows, spanAngle = DEFAULT_SPAN_ANGLE) {
24
+ const rad = getRowThickness(nRows);
25
+ const radianSpanAngle = Math.PI * spanAngle / 180;
26
+ return Array.from({ length: nRows }, (_, r) => {
27
+ const rowArcRadius = .5 + 2 * r * rad;
28
+ return Math.floor(radianSpanAngle * rowArcRadius / (2 * rad));
29
+ });
30
+ }
31
+ /**
32
+ * Returns the minimal number of rows necessary to contain nSeats seats.
33
+ */
34
+ export function getNRowsFromNSeats(nSeats, spanAngle = DEFAULT_SPAN_ANGLE) {
35
+ let nRows = 1;
36
+ while (getRowsFromNRows(nRows, spanAngle).reduce((a, b) => a + b, 0) < nSeats) {
37
+ nRows++;
38
+ }
39
+ return nRows;
40
+ }
41
+ export var FillingStrategy;
42
+ (function (FillingStrategy) {
43
+ /**
44
+ * The seats are distributed among all the rows,
45
+ * proportionally to the maximal number of seats each row can contain.
46
+ */
47
+ FillingStrategy["DEFAULT"] = "default";
48
+ /**
49
+ * Selects as few outermost rows as necessary, then distributes the seats among them,
50
+ * proportionally to the maximal number of seats each row can contain.
51
+ */
52
+ FillingStrategy["EMPTY_INNER"] = "empty_inner";
53
+ /**
54
+ * Fills up the rows as much as possible, starting from the outermost ones.
55
+ */
56
+ FillingStrategy["OUTER_PRIORITY"] = "outer_priority";
57
+ })(FillingStrategy || (FillingStrategy = {}));
58
+ /**
59
+ * Computes the coordinates of the centers of the seats, with (as a bonus) the angle of each seat in the hemicycle.
60
+ * The canvas is assumed to be 2 in width and 1 in height, with the x axis pointing right and the y axis pointing up.
61
+ * The angle is calculated from the [1, 0] center of the hemicycle, in radians and in trigonometric positive direction,
62
+ * tending to 0 for the rightmost seats, 90° or PI/2 for the center, and 180° or PI for the leftmost seats.
63
+ * @param minNRows The minimal number of rows required to contain `nSeats` seats will be computed automatically.
64
+ * If `minNRows` is greater, then that will be the number of rows, otherwise the parameter is ignored.
65
+ * Passing a higher number of rows will make the diagram sparser,
66
+ * for which non-default filling strategies are more adapted.
67
+ * @param fillingStrategy determines how the seats are distributed among the rows, see the `FillingStrategy` enum.
68
+ * @param spanAngle is the angle from the side of the rightmost seats,
69
+ * through the center, to the side of the leftmost seats.
70
+ * It takes a value in degrees and defaults to 180 to make a true hemicycle.
71
+ * Values above 180 are not supported.
72
+ * @returns a map whose keys are the seat centers as [x, y] coordinates, with the angle of the seat as values.
73
+ */
74
+ export function getSeatCenters(nSeats, { minNRows = 0, fillingStrategy = FillingStrategy.DEFAULT, spanAngle = DEFAULT_SPAN_ANGLE, } = {}) {
75
+ const nRows = Math.max(minNRows, getNRowsFromNSeats(nSeats, spanAngle));
76
+ const rowThicc = getRowThickness(nRows);
77
+ const spanAngleMargin = (1 - spanAngle / 180) * Math.PI / 2;
78
+ const maxedRows = getRowsFromNRows(nRows, spanAngle);
79
+ let startingRow, fillingRatio, seatsOnStartingRow;
80
+ switch (fillingStrategy) {
81
+ case FillingStrategy.DEFAULT:
82
+ startingRow = 0;
83
+ fillingRatio = nSeats / maxedRows.reduce((a, b) => a + b, 0);
84
+ break;
85
+ case FillingStrategy.EMPTY_INNER:
86
+ {
87
+ const rows = maxedRows.slice();
88
+ while (rows.slice(1).reduce((a, b) => a + b, 0) >= nSeats) {
89
+ rows.shift();
90
+ }
91
+ // here, rows represents the rows which are enough to contain nSeats,
92
+ // and their number of seats.
93
+ // this row will be the first one to contain seats
94
+ // the innermost ones are empty
95
+ startingRow = nRows - rows.length;
96
+ fillingRatio = nSeats / rows.reduce((a, b) => a + b, 0);
97
+ }
98
+ break;
99
+ case FillingStrategy.OUTER_PRIORITY:
100
+ {
101
+ const rows = maxedRows.slice();
102
+ while (rows.reduce((a, b) => a + b, 0) > nSeats) {
103
+ rows.shift();
104
+ }
105
+ // here, rows represents the rows which will be fully filled,
106
+ // and their number of seats.
107
+ // this row will be only one to be partially filled
108
+ // the innermore ones are empty, the outermost ones are fully filled
109
+ startingRow = nRows - rows.length - 1;
110
+ seatsOnStartingRow = nSeats - rows.reduce((a, b) => a + b, 0);
111
+ }
112
+ break;
113
+ default:
114
+ throw new Error(`Invalid filling strategy: ${fillingStrategy}`);
115
+ }
116
+ const positions = new Map();
117
+ for (let r = startingRow; r < nRows; r++) {
118
+ let nSeatsThisRow;
119
+ if (r === nRows - 1) { // if it's the last, outermost row
120
+ // fit all the remaining seats
121
+ nSeatsThisRow = nSeats - positions.size;
122
+ }
123
+ else if (fillingStrategy === FillingStrategy.OUTER_PRIORITY) {
124
+ if (r === startingRow) {
125
+ nSeatsThisRow = seatsOnStartingRow;
126
+ }
127
+ else {
128
+ nSeatsThisRow = maxedRows[r];
129
+ }
130
+ }
131
+ else {
132
+ // fullness of the diagram times the maximal number of seats in this row
133
+ nSeatsThisRow = Math.round(fillingRatio * maxedRows[r]);
134
+ // actually more precise rounding : avoid rounding errors to accumulate too much
135
+ // nSeatsThisRow = Math.round((nSeats-positions.size) * maxedRows[r] / maxedRows.reduce((a, b) => a + b, 0));
136
+ }
137
+ // row radius : the radius of the circle crossing the center of each seat in the row
138
+ const rowArcRadius = .5 + 2 * r * rowThicc;
139
+ if (nSeatsThisRow === 1) {
140
+ positions.set([1, rowArcRadius], Math.PI / 2);
141
+ }
142
+ else {
143
+ // the angle necessary in this row to put the first (and last) seats fully on the canvas
144
+ const angleMargin = Math.asin(rowThicc / rowArcRadius)
145
+ // add the margin to make up the side angle
146
+ + spanAngleMargin;
147
+ // alternatively, allow the centers of the seats by the side to reach the angle's limits
148
+ // const angleMargin = Math.max(angleMargin, spanAngleMargin);
149
+ // the angle separating two seats on that row
150
+ const angleStep = (Math.PI - 2 * angleMargin) / (nSeatsThisRow - 1);
151
+ // a fraction of the remaining space, keeping in mind that the same elevation
152
+ // on start and end limits that remaining space to less than 2PI
153
+ for (let s = 0; s < nSeatsThisRow; s++) {
154
+ const angle = angleMargin + s * angleStep;
155
+ // an oriented angle, so it goes trig positive (counterclockwise)
156
+ positions.set([1 + rowArcRadius * Math.cos(angle), rowArcRadius * Math.sin(angle)], angle);
157
+ }
158
+ }
159
+ }
160
+ return positions;
161
+ }
162
+ //# sourceMappingURL=geometry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geometry.js","sourceRoot":"","sources":["../src/geometry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import { GetSeatCentersOptions } from "./geometry.js";
2
+ type WithNumber<T> = T & {
3
+ readonly nSeats?: number | undefined;
4
+ };
5
+ /**
6
+ * Typically SeatLocation is a tuple of x/y coordinates, and SeatDisplay gives infos on what seats should look like.
7
+ * Typically the groups are ordered from the left to the right, and the seats are ordered from left to right.
8
+ * If too few or too many seats are provided, an error is thrown.
9
+ * @param attribution a mapping of groups associating the groups in a given order to the number of seats each group holds
10
+ * @param seats an iterable of seats in a given order, its length should be the sum of the values in attribution
11
+ * @returns a mapping of each group to the seats it holds
12
+ */
13
+ export declare function dispatchSeats<SeatDisplay, SeatLocation>(attribution: ReadonlyMap<SeatDisplay, number> | readonly WithNumber<SeatDisplay>[], seats: Iterable<SeatLocation>): Map<SeatDisplay, SeatLocation[]>;
14
+ type SeatCenter = readonly [number, number];
15
+ type MappedSeatCenters<SeatDisplay, SeatLocation> = Iterable<readonly [SeatLocation, SeatDisplay]>;
16
+ type GroupedSeatCenters<SeatDisplay, SeatLocation> = Iterable<readonly [SeatDisplay, readonly SeatLocation[]]>;
17
+ /**
18
+ * Util function to convert seat centers representation
19
+ * @param seatCenters seating data oriented by seat center
20
+ * @returns grouped seat centers, organized by display, as taken by the component's input
21
+ */
22
+ export declare function regroupSeatCenters<SeatDisplay, SeatLocation = SeatCenter>(seatCenters: MappedSeatCenters<SeatDisplay, SeatLocation>): GroupedSeatCenters<SeatDisplay, SeatLocation>;
23
+ export interface PrecomputeOptions extends GetSeatCentersOptions {
24
+ seatRadiusFactor: number;
25
+ }
26
+ export interface PrecomputeReturn<SeatDisplay> {
27
+ groupedSeatCenters: Map<SeatDisplay, SeatCenter[]>;
28
+ seatActualRadius: number;
29
+ }
30
+ /**
31
+ * Pre-computes some values that are useful in the extensions that generate actual diagrams.
32
+ * The SeatDisplay type will depend on the extension.
33
+ * @param options.seatRadiusFactor the ratio (between 0 and 1) of the seat radius over the row thickness. Defaults to .8.
34
+ * @param options the rest of the options are those passed through to the options parameter of the getSeatCenters function.
35
+ * @returns an object with two properties:
36
+ * the groupedSeatCenters key, a mapping of SeatDisplay objects to the list of the corresponding seats' coordinates,
37
+ * and the seatActualRadius key, the actual radius of the seats (in the same unit as the coordinates)
38
+ */
39
+ export declare function precomputeFromAttribution<SeatDisplay>(attribution: ReadonlyMap<SeatDisplay, number> | readonly WithNumber<SeatDisplay>[], options?: Partial<Readonly<PrecomputeOptions>>): PrecomputeReturn<SeatDisplay>;
40
+ export {};
package/dist/utils.js ADDED
@@ -0,0 +1,70 @@
1
+ import { getNRowsFromNSeats, getRowThickness, getSeatCenters } from "./geometry.js";
2
+ const isReadonlyMap = v => v instanceof Map;
3
+ /**
4
+ * Typically SeatLocation is a tuple of x/y coordinates, and SeatDisplay gives infos on what seats should look like.
5
+ * Typically the groups are ordered from the left to the right, and the seats are ordered from left to right.
6
+ * If too few or too many seats are provided, an error is thrown.
7
+ * @param attribution a mapping of groups associating the groups in a given order to the number of seats each group holds
8
+ * @param seats an iterable of seats in a given order, its length should be the sum of the values in attribution
9
+ * @returns a mapping of each group to the seats it holds
10
+ */
11
+ export function dispatchSeats(attribution, seats) {
12
+ const seatIterator = seats[Symbol.iterator]();
13
+ const entries = isReadonlyMap(attribution) ?
14
+ Array.from(attribution) :
15
+ attribution.map(seatData => { var _a; return [seatData, (_a = seatData.nSeats) !== null && _a !== void 0 ? _a : 1]; });
16
+ try {
17
+ return new Map(entries.map(([group, nSeats]) => [group, Array.from({ length: nSeats }, () => {
18
+ const seatIteration = seatIterator.next();
19
+ if (seatIteration.done) {
20
+ throw new Error("Not enough seats");
21
+ }
22
+ return seatIteration.value;
23
+ })]));
24
+ }
25
+ finally {
26
+ if (!seatIterator.next().done) {
27
+ throw new Error("Too many seats");
28
+ }
29
+ }
30
+ }
31
+ /**
32
+ * Util function to convert seat centers representation
33
+ * @param seatCenters seating data oriented by seat center
34
+ * @returns grouped seat centers, organized by display, as taken by the component's input
35
+ */
36
+ export function regroupSeatCenters(seatCenters) {
37
+ const seatCentersByGroup = new Map();
38
+ for (const [seat, group] of seatCenters) {
39
+ if (!seatCentersByGroup.has(group)) {
40
+ seatCentersByGroup.set(group, []);
41
+ }
42
+ seatCentersByGroup.get(group).push(seat);
43
+ }
44
+ return seatCentersByGroup;
45
+ }
46
+ /**
47
+ * Pre-computes some values that are useful in the extensions that generate actual diagrams.
48
+ * The SeatDisplay type will depend on the extension.
49
+ * @param options.seatRadiusFactor the ratio (between 0 and 1) of the seat radius over the row thickness. Defaults to .8.
50
+ * @param options the rest of the options are those passed through to the options parameter of the getSeatCenters function.
51
+ * @returns an object with two properties:
52
+ * the groupedSeatCenters key, a mapping of SeatDisplay objects to the list of the corresponding seats' coordinates,
53
+ * and the seatActualRadius key, the actual radius of the seats (in the same unit as the coordinates)
54
+ */
55
+ export function precomputeFromAttribution(attribution, options = {}) {
56
+ var _a;
57
+ if (!isReadonlyMap(attribution)) {
58
+ attribution = new Map(attribution.map(seatData => { var _a; return [seatData, (_a = seatData.nSeats) !== null && _a !== void 0 ? _a : 1]; }));
59
+ }
60
+ const seatRadiusFactor = (_a = options.seatRadiusFactor) !== null && _a !== void 0 ? _a : .8;
61
+ const nSeats = [...attribution.values()].reduce((a, b) => a + b, 0);
62
+ const results = getSeatCenters(nSeats, options);
63
+ const groupedSeatCenters = dispatchSeats(attribution, [...results.keys()].sort((a, b) => results.get(b) - results.get(a)));
64
+ const seatActualRadius = seatRadiusFactor * getRowThickness(getNRowsFromNSeats(nSeats, options.spanAngle));
65
+ return {
66
+ groupedSeatCenters,
67
+ seatActualRadius,
68
+ };
69
+ }
70
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@parliamentarch/core",
3
+ "version": "4.0.0",
4
+ "description": "Tools to generate arch-styled parliamentary diagrams",
5
+ "type": "module",
6
+
7
+ "main": "./dist/geometry.js",
8
+ "types": "./dist/geometry.d.ts",
9
+ "exports": {
10
+ "./geometry": {
11
+ "import": {
12
+ "types": "./dist/geometry.d.ts",
13
+ "default": "./dist/geometry.js"
14
+ }
15
+ },
16
+ "./utils": {
17
+ "import": {
18
+ "types": "./dist/utils.d.ts",
19
+ "default": "./dist/utils.js"
20
+ }
21
+ }
22
+ },
23
+
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "scripts": {
28
+ "clean": "node ../../scripts/clean.ts -d ./dist",
29
+ "compile": "tsc",
30
+ "build": "npm run clean & npm run compile",
31
+ "pub": "npm run build & npm publish --access public",
32
+ "test": "echo \"Error: no test specified\" && exit 1"
33
+ },
34
+
35
+ "devDependencies": {
36
+ "@tsconfig/strictest": "^2.0.8",
37
+ "typescript": "^6.0.2"
38
+ },
39
+
40
+ "author": "Gouvernathor",
41
+ "license": "BSD-3-Clause",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://codeberg.org/Gouvernathor/ParliamentArch-TS.git",
45
+ "directory": "packages/core"
46
+ },
47
+ "homepage": "https://codeberg.org/Gouvernathor/ParliamentArch-TS/src/packages/core#readme",
48
+ "bugs": {
49
+ "url": "https://github.com/Gouvernathor/ParliamentArch-TS/issues"
50
+ },
51
+ "funding": "https://ko-fi.com/gouvernathor",
52
+ "keywords": [
53
+ "parliament",
54
+ "diagram",
55
+ "arch",
56
+ "hemicycle",
57
+ "svg"
58
+ ]
59
+ }