/
stacked-intervals.ts
82 lines (76 loc) · 2.23 KB
/
stacked-intervals.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import type { Fn, Fn2 } from "@thi.ng/api";
import {
comp,
filter,
iterator,
map,
mapcatIndexed,
push,
some,
transduce,
} from "@thi.ng/transducers";
import type { Domain, PlotFn } from "../api";
import { valueMapper } from "./utils";
export interface StackedIntervalOpts<T> {
attribs?: any;
interval: Fn<T, number[]>;
overlap: number;
sort?: Fn2<[number[], T], [number[], T], number>;
shape: Fn2<[number[], number[], T, number], Fn<number[], number[]>, any>;
}
type Row<T> = [number[], T][];
const overlap = ([a, b]: number[], [c, d]: number[], pad = 0) =>
a <= d + pad && b + pad >= c;
const rowStacking = <T>(data: [number[], T][], pad = 0) =>
data.reduce((acc, item) => {
const rx = item[0];
for (let i = 0; true; i++) {
const row = acc[i];
if (!row || !some((y) => overlap(rx, y[0], pad), row)) {
row ? row.push(item) : (acc[i] = [item]);
return acc;
}
}
}, <Row<T>[]>[]);
const processRow = <T>(mapper: Fn<number[], number[]>, [d1, d2]: Domain) => (
i: number,
row: Row<T>
) =>
map(
([[a, b], item]) =>
<[number[], number[], T, number]>[
mapper([Math.max(d1, a), i]),
mapper([Math.min(d2, b), i]),
item,
i,
],
row
);
export const stackedIntervals = <T>(
data: T[],
opts: StackedIntervalOpts<T>
): PlotFn => (spec) => {
const mapper = valueMapper(spec.xaxis, spec.yaxis, spec.project);
const domain = spec.xaxis.domain;
return [
"g",
opts.attribs,
...iterator(
comp(
mapcatIndexed(processRow<T>(mapper, domain)),
map((x) => opts.shape(x, mapper))
),
rowStacking(
transduce(
comp(
map((x) => <[number[], T]>[opts.interval(x), x]),
filter(([x]) => overlap(domain, x, opts.overlap))
),
push<[number[], T]>(),
data
).sort(opts.sort || ((a, b) => a[0][0] - b[0][0])),
opts.overlap
)
),
];
};