@sjcrh/proteinpaint-shared 2.180.0 → 2.180.1
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/package.json +2 -2
- package/src/bulk.cnv.js +30 -30
- package/src/bulk.del.js +48 -48
- package/src/bulk.itd.js +48 -48
- package/src/bulk.js +31 -31
- package/src/bulk.snv.js +109 -72
- package/src/bulk.sv.js +78 -78
- package/src/bulk.svjson.js +33 -31
- package/src/bulk.trunc.js +53 -47
- package/src/clustering.js +27 -27
- package/src/common.js +665 -558
- package/src/compute.percentile.js +3 -1
- package/src/fetch-helpers.js +67 -42
- package/src/fileSize.js +4 -4
- package/src/filter.js +207 -179
- package/src/hash.js +8 -5
- package/src/helpers.js +17 -9
- package/src/index.js +24 -24
- package/src/mds3tk.js +14 -12
- package/src/roundValue.js +5 -4
- package/src/termdb.bins.js +151 -84
- package/src/termdb.initbinconfig.js +46 -18
- package/src/termdb.usecase.js +125 -116
- package/src/terms.js +281 -266
- package/src/tree.js +4 -4
- package/src/vcf.ann.js +9 -9
- package/src/vcf.csq.js +8 -8
- package/src/vcf.info.js +3 -3
- package/src/vcf.js +99 -74
- package/src/vcf.type.js +8 -2
package/src/roundValue.js
CHANGED
|
@@ -25,7 +25,8 @@ export function roundValue(value, digits) {
|
|
|
25
25
|
export function roundValueAuto(value, format = false, defaultDigits = 2) {
|
|
26
26
|
if (!value && value != 0) return value
|
|
27
27
|
const dp = decimalPlacesUntilFirstNonZero(value)
|
|
28
|
-
const digits =
|
|
28
|
+
const digits =
|
|
29
|
+
Math.abs(value) > 1 ? defaultDigits : dp > 0 ? dp + 1 : defaultDigits
|
|
29
30
|
if (format) return formatValue(value, digits)
|
|
30
31
|
return roundValue(value, digits)
|
|
31
32
|
}
|
|
@@ -35,7 +36,7 @@ export function decimalPlacesUntilFirstNonZero(number) {
|
|
|
35
36
|
const numberStr = number.toString()
|
|
36
37
|
|
|
37
38
|
// Find the position of the decimal point
|
|
38
|
-
const decimalIndex = numberStr.indexOf(
|
|
39
|
+
const decimalIndex = numberStr.indexOf(".")
|
|
39
40
|
|
|
40
41
|
// If decimal point is not found or number is an integer, return 0
|
|
41
42
|
if (decimalIndex === -1 || decimalIndex === numberStr.length - 1) {
|
|
@@ -46,9 +47,9 @@ export function decimalPlacesUntilFirstNonZero(number) {
|
|
|
46
47
|
let decimalPlaces = 0
|
|
47
48
|
for (let i = decimalIndex + 1; i < numberStr.length; i++) {
|
|
48
49
|
// Increment the count of decimal places until a non-zero digit is found
|
|
49
|
-
if (numberStr[i] ===
|
|
50
|
+
if (numberStr[i] === "0") {
|
|
50
51
|
decimalPlaces++
|
|
51
|
-
} else if (numberStr[i] >=
|
|
52
|
+
} else if (numberStr[i] >= "1" && numberStr[i] <= "9") {
|
|
52
53
|
break
|
|
53
54
|
}
|
|
54
55
|
}
|
package/src/termdb.bins.js
CHANGED
|
@@ -1,88 +1,92 @@
|
|
|
1
|
-
import { format } from
|
|
2
|
-
import { getColors } from
|
|
3
|
-
import { isNumeric, isStrictNumeric, convertUnits } from
|
|
1
|
+
import { format } from "d3-format"
|
|
2
|
+
import { getColors } from "./common.js"
|
|
3
|
+
import { isNumeric, isStrictNumeric, convertUnits } from "./helpers.js"
|
|
4
4
|
|
|
5
5
|
export default function validate_bins(binconfig) {
|
|
6
6
|
// Number.isFinite('1') returns false, which is desired
|
|
7
7
|
|
|
8
8
|
const bc = binconfig
|
|
9
|
-
if (!bc || typeof bc !==
|
|
9
|
+
if (!bc || typeof bc !== "object") throw "bin schema must be an object"
|
|
10
10
|
// assign default type
|
|
11
|
-
if (!(
|
|
11
|
+
if (!("type" in bc)) bc.type = "regular-bin"
|
|
12
12
|
|
|
13
|
-
if (bc.type ==
|
|
14
|
-
if (!Array.isArray(bc.lst)) throw
|
|
15
|
-
if (!bc.lst.length) throw
|
|
13
|
+
if (bc.type == "custom-bin") {
|
|
14
|
+
if (!Array.isArray(bc.lst)) throw "binconfig.lst must be an array"
|
|
15
|
+
if (!bc.lst.length) throw "binconfig.lst must have entries"
|
|
16
16
|
const first_bin = bc.lst[0]
|
|
17
17
|
const last_bin = bc.lst[bc.lst.length - 1]
|
|
18
18
|
|
|
19
19
|
for (const bin of bc.lst) {
|
|
20
|
-
if (!(
|
|
21
|
-
throw
|
|
20
|
+
if (!("startinclusive" in bin) && !("stopinclusive" in bin)) {
|
|
21
|
+
throw "custom bin.startinclusive and/or bin.stopinclusive must be defined"
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
if (bin == first_bin) {
|
|
25
|
-
if (
|
|
25
|
+
if ("startunbounded" in bin && !bin.startunbounded) {
|
|
26
26
|
throw `a custom first bin must not set bin.startunbounded to false`
|
|
27
27
|
}
|
|
28
28
|
bin.startunbounded = true
|
|
29
|
-
if (
|
|
30
|
-
throw
|
|
29
|
+
if ("start" in bin) {
|
|
30
|
+
throw "a custom first bin must not set a bin.start value"
|
|
31
31
|
}
|
|
32
|
-
if (
|
|
33
|
-
throw
|
|
32
|
+
if ("start_percentile" in bin) {
|
|
33
|
+
throw "the first bin must not set a bin.start_percentile value"
|
|
34
34
|
}
|
|
35
|
-
if (!(
|
|
35
|
+
if (!("stop" in bin)) {
|
|
36
36
|
throw `a custom first bin must define a bin.stop value`
|
|
37
37
|
}
|
|
38
38
|
if (!isStrictNumeric(bin.stop)) {
|
|
39
39
|
throw `a custom first bin.stop value should be numeric`
|
|
40
40
|
}
|
|
41
41
|
} else if (bin == last_bin) {
|
|
42
|
-
if (!(
|
|
42
|
+
if (!("start" in bin)) {
|
|
43
43
|
throw `a custom last bin must define a bin.start value`
|
|
44
44
|
}
|
|
45
45
|
if (!isStrictNumeric(bin.start)) {
|
|
46
46
|
throw `a custom last bin.start must be numeric`
|
|
47
47
|
}
|
|
48
|
-
if (
|
|
49
|
-
throw
|
|
48
|
+
if ("stopunbounded" in bin && !bin.stopunbounded) {
|
|
49
|
+
throw "a custom last bin must not set bin.stopunbounded to false"
|
|
50
50
|
}
|
|
51
51
|
bin.stopunbounded = true
|
|
52
|
-
if (
|
|
53
|
-
throw
|
|
52
|
+
if ("stop" in bin) {
|
|
53
|
+
throw "a custom last bin must not set a bin.stop value"
|
|
54
54
|
}
|
|
55
55
|
} else {
|
|
56
56
|
if (bin.startunbounded || bin.stopunbounded) {
|
|
57
|
-
throw
|
|
57
|
+
throw "bin.startunbounded and bin.stopunbounded must not be set for non-first/non-last bins"
|
|
58
58
|
}
|
|
59
|
-
if (!isStrictNumeric(bin.start))
|
|
60
|
-
|
|
59
|
+
if (!isStrictNumeric(bin.start))
|
|
60
|
+
throw "bin.start must be numeric for a non-first bin"
|
|
61
|
+
if (!isStrictNumeric(bin.stop))
|
|
62
|
+
throw "bin.stop must be numeric for a non-last bin"
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
|
-
} else if (bc.type ==
|
|
65
|
+
} else if (bc.type == "regular-bin") {
|
|
64
66
|
// required custom_bin parameter
|
|
65
|
-
if (!Number.isFinite(bc.bin_size)) throw
|
|
66
|
-
if (bc.bin_size <= 0) throw
|
|
67
|
+
if (!Number.isFinite(bc.bin_size)) throw "non-numeric bin_size"
|
|
68
|
+
if (bc.bin_size <= 0) throw "bin_size must be greater than 0"
|
|
67
69
|
|
|
68
70
|
if (!bc.startinclusive && !bc.stopinclusive) {
|
|
69
71
|
bc.startinclusive = 1
|
|
70
72
|
bc.stopinclusive = 0
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
if (!bc.first_bin) throw
|
|
74
|
-
if (typeof bc.first_bin !=
|
|
75
|
-
if (!Object.keys(bc.first_bin).length) throw
|
|
75
|
+
if (!bc.first_bin) throw "first_bin{} missing"
|
|
76
|
+
if (typeof bc.first_bin != "object") throw "first_bin{} is not an object"
|
|
77
|
+
if (!Object.keys(bc.first_bin).length) throw "first_bin is an empty object"
|
|
76
78
|
|
|
77
79
|
{
|
|
78
80
|
const b = bc.first_bin
|
|
79
81
|
b.startunbounded = true
|
|
80
82
|
// requires stop_percentile, or stop
|
|
81
83
|
if (b.stop_percentile) {
|
|
82
|
-
if (!Number.isInteger(b.stop_percentile))
|
|
83
|
-
|
|
84
|
+
if (!Number.isInteger(b.stop_percentile))
|
|
85
|
+
throw "first_bin.stop_percentile should be integer"
|
|
86
|
+
if (b.stop_percentile <= 0 || b.stop_percentile >= 100)
|
|
87
|
+
throw "first_bin.stop_percentile out of bound (0-100)"
|
|
84
88
|
} else if (!Number.isFinite(b.stop)) {
|
|
85
|
-
throw
|
|
89
|
+
throw "first_bin.stop not a number when stop_percentile is not set"
|
|
86
90
|
}
|
|
87
91
|
}
|
|
88
92
|
|
|
@@ -90,15 +94,17 @@ export default function validate_bins(binconfig) {
|
|
|
90
94
|
const b = bc.last_bin
|
|
91
95
|
// requires start_percentile or start
|
|
92
96
|
if (b.start_percentile) {
|
|
93
|
-
if (!Number.isInteger(b.start_percentile))
|
|
94
|
-
|
|
97
|
+
if (!Number.isInteger(b.start_percentile))
|
|
98
|
+
throw "last_bin.start_percentile should be integer"
|
|
99
|
+
if (b.start_percentile <= 0 || b.start_percentile >= 100)
|
|
100
|
+
throw "last_bin.start_percentile out of bound (0-100)"
|
|
95
101
|
} else if (!Number.isFinite(b.start)) {
|
|
96
|
-
throw
|
|
102
|
+
throw "last_bin.start not a number when start_percentile is not set"
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
b.stopunbounded = true
|
|
100
|
-
if (
|
|
101
|
-
throw
|
|
106
|
+
if ("stop" in b) {
|
|
107
|
+
throw "a regular last bin must not set a bin.stop value"
|
|
102
108
|
}
|
|
103
109
|
}
|
|
104
110
|
} else {
|
|
@@ -132,11 +138,13 @@ summaryfxn (percentiles)=> return {min, max, pX, pY, ...}
|
|
|
132
138
|
const k2c = getColors(bc.lst.length) //to color bins
|
|
133
139
|
for (const bin of bc.lst) bin.color = k2c(bin.label)
|
|
134
140
|
}
|
|
135
|
-
if (bc.type ==
|
|
136
|
-
if (typeof summaryfxn !=
|
|
141
|
+
if (bc.type == "custom-bin") return JSON.parse(JSON.stringify(bc.lst))
|
|
142
|
+
if (typeof summaryfxn != "function")
|
|
143
|
+
throw "summaryfxn required for modules/termdb.bins.js compute_bins()"
|
|
137
144
|
const percentiles = target_percentiles(bc)
|
|
138
145
|
const summary = summaryfxn(percentiles)
|
|
139
|
-
if (!summary || typeof summary !==
|
|
146
|
+
if (!summary || typeof summary !== "object")
|
|
147
|
+
throw "invalid returned value by summaryfxn"
|
|
140
148
|
bc.results = { summary }
|
|
141
149
|
if (!bc.binLabelFormatter) bc.binLabelFormatter = getNumDecimalsFormatter(bc)
|
|
142
150
|
|
|
@@ -147,7 +155,7 @@ summaryfxn (percentiles)=> return {min, max, pX, pY, ...}
|
|
|
147
155
|
const min = bc.first_bin.startunbounded
|
|
148
156
|
? minFloor
|
|
149
157
|
: bc.first_bin.start_percentile
|
|
150
|
-
? summary[
|
|
158
|
+
? summary["p" + bc.first_bin.start_percentile]
|
|
151
159
|
: bc.first_bin.start
|
|
152
160
|
let max = maxCeil, // in order to include the max value in the last bin
|
|
153
161
|
last_start,
|
|
@@ -157,26 +165,27 @@ summaryfxn (percentiles)=> return {min, max, pX, pY, ...}
|
|
|
157
165
|
max = bc.last_bin.stopunbounded
|
|
158
166
|
? maxCeil // in order to include the max value in the last bin
|
|
159
167
|
: bc.last_bin.stop_percentile
|
|
160
|
-
? summary[
|
|
168
|
+
? summary["p" + bc.last_bin.stop_percentile]
|
|
161
169
|
: isNumeric(bc.last_bin.stop) && bc.last_bin.stop <= summary.max // '0.0088' < 0.0088
|
|
162
170
|
? bc.last_bin.stop
|
|
163
171
|
: maxCeil // in order to include the max value in the last bin
|
|
164
172
|
last_start = isStrictNumeric(bc.last_bin.start_percentile)
|
|
165
|
-
? summary[
|
|
173
|
+
? summary["p" + bc.last_bin.start_percentile]
|
|
166
174
|
: isStrictNumeric(bc.last_bin.start)
|
|
167
175
|
? bc.last_bin.start
|
|
168
176
|
: undefined
|
|
169
177
|
last_stop = bc.last_bin.stopunbounded
|
|
170
178
|
? null
|
|
171
179
|
: bc.last_bin.stop_percentile
|
|
172
|
-
? summary[
|
|
180
|
+
? summary["p" + bc.last_bin.stop_percentile]
|
|
173
181
|
: isStrictNumeric(bc.last_bin.stop)
|
|
174
182
|
? bc.last_bin.stop
|
|
175
183
|
: null
|
|
176
184
|
} else if (bc.lst) {
|
|
177
185
|
const last_bin = bc.lst[bc.lst.length - 1]
|
|
178
186
|
last_start = last_bin.start
|
|
179
|
-
last_stop =
|
|
187
|
+
last_stop =
|
|
188
|
+
"stop" in last_bin && !last_bin.stopunbounded ? last_bin.stop : maxCeil
|
|
180
189
|
max = last_stop
|
|
181
190
|
} else {
|
|
182
191
|
last_start = maxCeil
|
|
@@ -194,18 +203,23 @@ summaryfxn (percentiles)=> return {min, max, pX, pY, ...}
|
|
|
194
203
|
startunbounded: bc.first_bin.startunbounded,
|
|
195
204
|
start: bc.first_bin.startunbounded ? undefined : min,
|
|
196
205
|
stop: isStrictNumeric(bc.first_bin.stop_percentile)
|
|
197
|
-
? +summary[
|
|
206
|
+
? +summary["p" + bc.first_bin.stop_percentile]
|
|
198
207
|
: isStrictNumeric(bc.first_bin.stop)
|
|
199
208
|
? +bc.first_bin.stop
|
|
200
209
|
: min + bc.bin_size,
|
|
201
210
|
startinclusive: bc.startinclusive,
|
|
202
|
-
stopinclusive: bc.stopinclusive
|
|
211
|
+
stopinclusive: bc.stopinclusive,
|
|
203
212
|
}
|
|
204
213
|
|
|
205
|
-
if (!isStrictNumeric(currBin.stop))
|
|
214
|
+
if (!isStrictNumeric(currBin.stop))
|
|
215
|
+
throw "the computed first_bin.stop is non-numeric" + currBin.stop
|
|
206
216
|
const maxNumBins = 100 // harcoded limit for now to not stress sqlite
|
|
207
217
|
|
|
208
|
-
while (
|
|
218
|
+
while (
|
|
219
|
+
(numericMax && currBin.stop <= max) ||
|
|
220
|
+
(currBin.startunbounded && !bins.length) ||
|
|
221
|
+
currBin.stopunbounded
|
|
222
|
+
) {
|
|
209
223
|
bins.push(currBin)
|
|
210
224
|
// force a computed last bin to have stopunbounded true
|
|
211
225
|
if (currBin.stop >= max) {
|
|
@@ -228,7 +242,7 @@ summaryfxn (percentiles)=> return {min, max, pX, pY, ...}
|
|
|
228
242
|
? last_stop
|
|
229
243
|
: numericLastStart && upper > last_start && previousStop != last_start
|
|
230
244
|
? last_start
|
|
231
|
-
: upper
|
|
245
|
+
: upper,
|
|
232
246
|
}
|
|
233
247
|
|
|
234
248
|
if (currBin.stop >= max) {
|
|
@@ -240,12 +254,17 @@ summaryfxn (percentiles)=> return {min, max, pX, pY, ...}
|
|
|
240
254
|
if (bc.last_bin && bc.last_bin.stopunbounded) currBin.stopunbounded = 1
|
|
241
255
|
}
|
|
242
256
|
if (currBin.start > currBin.stop) {
|
|
243
|
-
if (
|
|
257
|
+
if (
|
|
258
|
+
numericLastStart &&
|
|
259
|
+
currBin.stop == last_start &&
|
|
260
|
+
bc.last_bin &&
|
|
261
|
+
bc.last_bin.stopunbounded
|
|
262
|
+
)
|
|
244
263
|
currBin.stopunbounded = true
|
|
245
264
|
else break
|
|
246
265
|
}
|
|
247
266
|
if (bins.length + 1 >= maxNumBins) {
|
|
248
|
-
bc.error =
|
|
267
|
+
bc.error = "max_num_bins_reached"
|
|
249
268
|
break
|
|
250
269
|
}
|
|
251
270
|
}
|
|
@@ -260,65 +279,97 @@ summaryfxn (percentiles)=> return {min, max, pX, pY, ...}
|
|
|
260
279
|
|
|
261
280
|
function getNumDecimalsFormatter(bc) {
|
|
262
281
|
//return format('rounding' in bc ? bc.rounding : '')
|
|
263
|
-
return
|
|
282
|
+
return "rounding" in bc ? format(bc.rounding) : (d) => d // default to labeling using the start/stop value as-is
|
|
264
283
|
}
|
|
265
284
|
|
|
266
285
|
export function get_bin_label(bin, binconfig, valueConversion) {
|
|
267
286
|
/*
|
|
268
287
|
Generate a numeric bin label given a bin configuration and an optional term valueConversion object
|
|
269
288
|
*/
|
|
270
|
-
if (!bin) return
|
|
289
|
+
if (!bin) return "missing bin.label"
|
|
271
290
|
if (bin.label) return bin.label
|
|
272
291
|
|
|
273
292
|
const bc = binconfig
|
|
274
293
|
if (!bc.binLabelFormatter) bc.binLabelFormatter = getNumDecimalsFormatter(bc)
|
|
275
|
-
if (
|
|
294
|
+
if (
|
|
295
|
+
!bin.startunbounded &&
|
|
296
|
+
!bin.stopunbounded &&
|
|
297
|
+
!("startinclusive" in bin) &&
|
|
298
|
+
!("stopinclusive" in bin)
|
|
299
|
+
) {
|
|
276
300
|
if (bc.startinclusive) bin.startinclusive = true
|
|
277
301
|
else if (bc.stopinclusive) bin.stopinclusive = true
|
|
278
302
|
}
|
|
279
303
|
|
|
280
|
-
const start = bc.use_as ==
|
|
281
|
-
const stop = bc.use_as ==
|
|
304
|
+
const start = bc.use_as == "bins" || bin.start
|
|
305
|
+
const stop = bc.use_as == "bins" || bin.stop
|
|
282
306
|
|
|
283
307
|
let label_offset = 0
|
|
284
|
-
if (
|
|
285
|
-
bc.label_offset_ignored =
|
|
308
|
+
if ("label_offset" in bc) {
|
|
309
|
+
bc.label_offset_ignored = "bin_size" in bc && bc.bin_size < bc.label_offset
|
|
286
310
|
if (!bc.label_offset_ignored) label_offset = bc.label_offset
|
|
287
|
-
} else if (bc.bin_size === 1 && bc.termtype ==
|
|
311
|
+
} else if (bc.bin_size === 1 && bc.termtype == "integer") {
|
|
288
312
|
label_offset = 1
|
|
289
313
|
}
|
|
290
314
|
|
|
291
315
|
// one side-unbounded bins
|
|
292
316
|
// label will be ">v" or "<v"
|
|
293
317
|
if (bin.startunbounded) {
|
|
294
|
-
const oper = bin.stopinclusive ?
|
|
318
|
+
const oper = bin.stopinclusive ? "≤" : "<" // \u2264
|
|
295
319
|
const v1 = valueConversion
|
|
296
|
-
? convertUnits(
|
|
320
|
+
? convertUnits(
|
|
321
|
+
stop,
|
|
322
|
+
valueConversion.fromUnit,
|
|
323
|
+
valueConversion.toUnit,
|
|
324
|
+
valueConversion.scaleFactor,
|
|
325
|
+
true
|
|
326
|
+
)
|
|
297
327
|
: bc.binLabelFormatter(stop) //bin.startinclusive && label_offset ? stop - label_offset : stop)
|
|
298
328
|
return oper + v1
|
|
299
329
|
}
|
|
300
330
|
// a data value may coincide with the last bin's start
|
|
301
331
|
if (bin.stopunbounded || start === stop) {
|
|
302
|
-
const oper = bin.startinclusive /*|| label_offset*/ ?
|
|
332
|
+
const oper = bin.startinclusive /*|| label_offset*/ ? "≥" : ">" // \u2265
|
|
303
333
|
const v0 = valueConversion
|
|
304
|
-
? convertUnits(
|
|
334
|
+
? convertUnits(
|
|
335
|
+
start,
|
|
336
|
+
valueConversion.fromUnit,
|
|
337
|
+
valueConversion.toUnit,
|
|
338
|
+
valueConversion.scaleFactor,
|
|
339
|
+
true
|
|
340
|
+
)
|
|
305
341
|
: bc.binLabelFormatter(start) //bin.startinclusive || start == min ? start : start + label_offset)
|
|
306
342
|
return oper + v0
|
|
307
343
|
}
|
|
308
344
|
|
|
309
345
|
// two-sided bins
|
|
310
346
|
if (label_offset && bin.startinclusive && !bin.stopinclusive) {
|
|
311
|
-
if (
|
|
347
|
+
if (
|
|
348
|
+
Number.isInteger(bc.bin_size) &&
|
|
349
|
+
Math.abs(start - stop) === label_offset
|
|
350
|
+
) {
|
|
312
351
|
// make a simpler label when the range simply spans the bin_size
|
|
313
352
|
return (
|
|
314
|
-
|
|
353
|
+
"" +
|
|
315
354
|
(valueConversion
|
|
316
|
-
? convertUnits(
|
|
355
|
+
? convertUnits(
|
|
356
|
+
start,
|
|
357
|
+
valueConversion.fromUnit,
|
|
358
|
+
valueConversion.toUnit,
|
|
359
|
+
valueConversion.scaleFactor,
|
|
360
|
+
true
|
|
361
|
+
)
|
|
317
362
|
: bc.binLabelFormatter(start))
|
|
318
363
|
)
|
|
319
364
|
} else {
|
|
320
365
|
const v0 = valueConversion
|
|
321
|
-
? convertUnits(
|
|
366
|
+
? convertUnits(
|
|
367
|
+
start,
|
|
368
|
+
valueConversion.fromUnit,
|
|
369
|
+
valueConversion.toUnit,
|
|
370
|
+
valueConversion.scaleFactor,
|
|
371
|
+
true
|
|
372
|
+
)
|
|
322
373
|
: bc.binLabelFormatter(start)
|
|
323
374
|
const v1 = valueConversion
|
|
324
375
|
? convertUnits(
|
|
@@ -330,28 +381,40 @@ export function get_bin_label(bin, binconfig, valueConversion) {
|
|
|
330
381
|
)
|
|
331
382
|
: bc.binLabelFormatter(stop - label_offset)
|
|
332
383
|
// ensure that last two bin labels make sense (the last is stopunbounded)
|
|
333
|
-
return +v0 >= +v1 ? v0.toString() : v0 +
|
|
384
|
+
return +v0 >= +v1 ? v0.toString() : v0 + " to " + v1
|
|
334
385
|
}
|
|
335
386
|
} else {
|
|
336
387
|
// stop_inclusive || label_offset == 0
|
|
337
|
-
const oper0 = bin.startinclusive ?
|
|
338
|
-
const oper1 = bin.stopinclusive ?
|
|
388
|
+
const oper0 = bin.startinclusive ? "" : ">"
|
|
389
|
+
const oper1 = bin.stopinclusive ? "" : "<"
|
|
339
390
|
const v0 = valueConversion
|
|
340
|
-
? convertUnits(
|
|
391
|
+
? convertUnits(
|
|
392
|
+
start,
|
|
393
|
+
valueConversion.fromUnit,
|
|
394
|
+
valueConversion.toUnit,
|
|
395
|
+
valueConversion.scaleFactor,
|
|
396
|
+
true
|
|
397
|
+
)
|
|
341
398
|
: Number.isInteger(start)
|
|
342
399
|
? start
|
|
343
400
|
: bc.binLabelFormatter(start)
|
|
344
401
|
const v1 = valueConversion
|
|
345
|
-
? convertUnits(
|
|
402
|
+
? convertUnits(
|
|
403
|
+
stop,
|
|
404
|
+
valueConversion.fromUnit,
|
|
405
|
+
valueConversion.toUnit,
|
|
406
|
+
valueConversion.scaleFactor,
|
|
407
|
+
true
|
|
408
|
+
)
|
|
346
409
|
: Number.isInteger(stop)
|
|
347
410
|
? stop
|
|
348
411
|
: bc.binLabelFormatter(stop)
|
|
349
412
|
// after rounding the bin labels, the bin start may equal the last bin stop as derived from actual data
|
|
350
413
|
if (+v0 >= +v1) {
|
|
351
|
-
const oper = bin.startinclusive ?
|
|
414
|
+
const oper = bin.startinclusive ? "≥" : ">" // \u2265
|
|
352
415
|
return oper + v0
|
|
353
416
|
} else {
|
|
354
|
-
return oper0 + v0 +
|
|
417
|
+
return oper0 + v0 + " to " + oper1 + v1
|
|
355
418
|
}
|
|
356
419
|
}
|
|
357
420
|
}
|
|
@@ -362,17 +425,17 @@ export function get_bin_range_equation(bin, binconfig) {
|
|
|
362
425
|
let range_eq
|
|
363
426
|
// should always use computed (not user-customized) bin label to determine bin range text
|
|
364
427
|
const copy = structuredClone(bin)
|
|
365
|
-
copy.label =
|
|
428
|
+
copy.label = "" // mutate only the copy, and not the original bin argument
|
|
366
429
|
const bin_label = get_bin_label(copy, binconfig)
|
|
367
430
|
if (bin.startunbounded || bin.stopunbounded) {
|
|
368
431
|
// first or last bins, e.g. x ≤ 14 and x > 16
|
|
369
|
-
range_eq = x +
|
|
432
|
+
range_eq = x + " " + bin_label
|
|
370
433
|
} else if (bin.startinclusive) {
|
|
371
434
|
// bins with startinclusive, e.g. 14 ≤ x < 16
|
|
372
|
-
range_eq = bin_label.replace(
|
|
435
|
+
range_eq = bin_label.replace("to <", "≤ " + x + " <")
|
|
373
436
|
} else if (bin.stopinclusive) {
|
|
374
437
|
// bins with stopinclusive, e.g. 14 < x ≤ 16
|
|
375
|
-
range_eq = bin_label.replace(
|
|
438
|
+
range_eq = bin_label.replace(">", "").replace("to", "< " + x + " ≤")
|
|
376
439
|
}
|
|
377
440
|
return range_eq
|
|
378
441
|
}
|
|
@@ -380,10 +443,14 @@ export function get_bin_range_equation(bin, binconfig) {
|
|
|
380
443
|
export function target_percentiles(binconfig) {
|
|
381
444
|
const percentiles = []
|
|
382
445
|
const f = binconfig.first_bin
|
|
383
|
-
if (f && isStrictNumeric(f.start_percentile))
|
|
384
|
-
|
|
446
|
+
if (f && isStrictNumeric(f.start_percentile))
|
|
447
|
+
percentiles.push(f.start_percentile)
|
|
448
|
+
if (f && isStrictNumeric(f.stop_percentile))
|
|
449
|
+
percentiles.push(f.stop_percentile)
|
|
385
450
|
const l = binconfig.last_bin
|
|
386
|
-
if (l && isStrictNumeric(l.start_percentile))
|
|
387
|
-
|
|
451
|
+
if (l && isStrictNumeric(l.start_percentile))
|
|
452
|
+
percentiles.push(l.start_percentile)
|
|
453
|
+
if (l && isStrictNumeric(l.stop_percentile))
|
|
454
|
+
percentiles.push(l.stop_percentile)
|
|
388
455
|
return percentiles
|
|
389
456
|
}
|
|
@@ -7,8 +7,15 @@ Initialize a bin configuration for a numeric dataset
|
|
|
7
7
|
*/
|
|
8
8
|
export default function initBinConfig(data, opts = {}) {
|
|
9
9
|
if (!data.length)
|
|
10
|
-
return {
|
|
11
|
-
|
|
10
|
+
return {
|
|
11
|
+
mode: "discrete",
|
|
12
|
+
type: "regular-bin",
|
|
13
|
+
startinclusive: true,
|
|
14
|
+
bin_size: null,
|
|
15
|
+
first_bin: { stop: null },
|
|
16
|
+
}
|
|
17
|
+
if (data.find((d) => !Number.isFinite(d)))
|
|
18
|
+
throw new Error("non-numeric values found")
|
|
12
19
|
|
|
13
20
|
let binConfig
|
|
14
21
|
const s = new Set(data)
|
|
@@ -21,12 +28,28 @@ export default function initBinConfig(data, opts = {}) {
|
|
|
21
28
|
// all data values will fall into the second bin
|
|
22
29
|
const value = [...s][0]
|
|
23
30
|
binConfig = {
|
|
24
|
-
type:
|
|
31
|
+
type: "custom-bin",
|
|
25
32
|
lst: [
|
|
26
|
-
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
{
|
|
34
|
+
stop: value,
|
|
35
|
+
stopinclusive: false,
|
|
36
|
+
startunbounded: true,
|
|
37
|
+
label: "<" + value,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
start: value,
|
|
41
|
+
stop: value,
|
|
42
|
+
startinclusive: true,
|
|
43
|
+
stopinclusive: true,
|
|
44
|
+
label: "=" + value,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
start: value,
|
|
48
|
+
startinclusive: false,
|
|
49
|
+
stopunbounded: true,
|
|
50
|
+
label: ">" + value,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
30
53
|
}
|
|
31
54
|
} else {
|
|
32
55
|
// multiple unique values in data array
|
|
@@ -47,22 +70,23 @@ export default function initBinConfig(data, opts = {}) {
|
|
|
47
70
|
// first bin stop will equal either (minimum + bin size) or (5th percentile), whichever is larger.
|
|
48
71
|
const firstBinStop = Math.max(min + binSize, p5)
|
|
49
72
|
// round the bin values
|
|
50
|
-
let [binSize_rnd, firstBinStop_rnd, lastBinStart_rnd, rounding] =
|
|
73
|
+
let [binSize_rnd, firstBinStop_rnd, lastBinStart_rnd, rounding] =
|
|
74
|
+
roundBinVals(binSize, firstBinStop, max, min)
|
|
51
75
|
// generate the bin configuration
|
|
52
76
|
binConfig = {
|
|
53
|
-
type:
|
|
77
|
+
type: "regular-bin",
|
|
54
78
|
startinclusive: true,
|
|
55
79
|
bin_size: binSize_rnd,
|
|
56
|
-
first_bin: { stop: firstBinStop_rnd }
|
|
80
|
+
first_bin: { stop: firstBinStop_rnd },
|
|
57
81
|
}
|
|
58
82
|
if (lastBinStart_rnd) binConfig.last_bin = { start: lastBinStart_rnd }
|
|
59
83
|
if (rounding) binConfig.rounding = rounding
|
|
60
84
|
}
|
|
61
|
-
if (
|
|
62
|
-
if (opts.format ===
|
|
85
|
+
if ("format" in opts) {
|
|
86
|
+
if (opts.format === "string") {
|
|
63
87
|
return JSON.stringify(binConfig)
|
|
64
88
|
} else {
|
|
65
|
-
throw
|
|
89
|
+
throw "options are not in the correct format"
|
|
66
90
|
}
|
|
67
91
|
} else {
|
|
68
92
|
return binConfig
|
|
@@ -75,14 +99,17 @@ function roundBinVals(binSize, firstBinStop, max, min) {
|
|
|
75
99
|
if (binSize >= 0.1 && binSize <= 2) {
|
|
76
100
|
// Round to the nearest one for small bin sizes
|
|
77
101
|
binSize_rnd = Math.round(binSize / (1 * 10 ** log)) * (1 * 10 ** log)
|
|
78
|
-
firstBinStop_rnd =
|
|
102
|
+
firstBinStop_rnd =
|
|
103
|
+
Math.round(firstBinStop / (1 * 10 ** log)) * (1 * 10 ** log)
|
|
79
104
|
} else {
|
|
80
105
|
// Round to the nearest five for large bin sizes
|
|
81
106
|
binSize_rnd = Math.round(binSize / (5 * 10 ** log)) * (5 * 10 ** log)
|
|
82
|
-
firstBinStop_rnd =
|
|
107
|
+
firstBinStop_rnd =
|
|
108
|
+
Math.round(firstBinStop / (5 * 10 ** log)) * (5 * 10 ** log)
|
|
83
109
|
if (binSize_rnd === 0) binSize_rnd = 1 * 10 ** log
|
|
84
110
|
if (firstBinStop_rnd === 0) firstBinStop_rnd = 1 * 10 ** log
|
|
85
|
-
if (binSize_rnd === 5 * 10 ** log && firstBinStop_rnd === 1 * 10 ** log)
|
|
111
|
+
if (binSize_rnd === 5 * 10 ** log && firstBinStop_rnd === 1 * 10 ** log)
|
|
112
|
+
firstBinStop_rnd = 5 * 10 ** log
|
|
86
113
|
}
|
|
87
114
|
if (firstBinStop_rnd < min) firstBinStop_rnd = firstBinStop_rnd * 2
|
|
88
115
|
// if the number of bins is above 8 after rounding, then set the last bin start to restrict the number of bins to 8
|
|
@@ -94,8 +121,9 @@ function roundBinVals(binSize, firstBinStop, max, min) {
|
|
|
94
121
|
const digits = Math.abs(log)
|
|
95
122
|
binSize_rnd = Number(binSize_rnd.toFixed(digits))
|
|
96
123
|
firstBinStop_rnd = Number(firstBinStop_rnd.toFixed(digits))
|
|
97
|
-
if (lastBinStart_rnd)
|
|
98
|
-
|
|
124
|
+
if (lastBinStart_rnd)
|
|
125
|
+
lastBinStart_rnd = Number(lastBinStart_rnd.toFixed(digits))
|
|
126
|
+
rounding = "." + digits + "f"
|
|
99
127
|
}
|
|
100
128
|
if (Object.is(firstBinStop_rnd, -0)) firstBinStop_rnd = 0
|
|
101
129
|
return [binSize_rnd, firstBinStop_rnd, lastBinStart_rnd, rounding]
|