@sjcrh/proteinpaint-shared 2.187.0 → 2.188.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 +10 -2
- package/constants/AiHisto.ts +27 -0
- package/constants/README.md +11 -0
- package/devTs.ts +3 -0
- package/dist/constants/AiHisto.d.ts +23 -0
- package/dist/constants/AiHisto.js +31 -0
- package/dist/constants/AiHisto.js.map +7 -0
- package/dist/src/aiHisto.d.ts +5 -0
- package/dist/src/aiHisto.js +15 -0
- package/dist/src/aiHisto.js.map +7 -0
- package/dist/src/bulk.cnv.js +83 -0
- package/dist/src/bulk.cnv.js.map +7 -0
- package/dist/src/bulk.del.js +119 -0
- package/dist/src/bulk.del.js.map +7 -0
- package/dist/src/bulk.itd.js +119 -0
- package/dist/src/bulk.itd.js.map +7 -0
- package/dist/src/bulk.js +183 -0
- package/dist/src/bulk.js.map +7 -0
- package/dist/src/bulk.snv.js +175 -0
- package/dist/src/bulk.snv.js.map +7 -0
- package/dist/src/bulk.sv.js +266 -0
- package/dist/src/bulk.sv.js.map +7 -0
- package/dist/src/bulk.svjson.js +151 -0
- package/dist/src/bulk.svjson.js.map +7 -0
- package/dist/src/bulk.trunc.js +122 -0
- package/dist/src/bulk.trunc.js.map +7 -0
- package/dist/src/clustering.js +71 -0
- package/dist/src/clustering.js.map +7 -0
- package/dist/src/common.js +1302 -0
- package/dist/src/common.js.map +7 -0
- package/dist/src/compute.percentile.js +10 -0
- package/dist/src/compute.percentile.js.map +7 -0
- package/dist/src/doc.d.ts +7 -0
- package/dist/src/doc.js +10 -0
- package/dist/src/doc.js.map +7 -0
- package/dist/src/fetch-helpers.js +177 -0
- package/dist/src/fetch-helpers.js.map +7 -0
- package/dist/src/fileSize.js +10 -0
- package/dist/src/fileSize.js.map +7 -0
- package/dist/src/filter.d.ts +62 -0
- package/dist/src/filter.js +194 -0
- package/dist/src/filter.js.map +7 -0
- package/dist/src/hash.js +20 -0
- package/dist/src/hash.js.map +7 -0
- package/dist/src/helpers.js +66 -0
- package/dist/src/helpers.js.map +7 -0
- package/dist/src/index.d.ts +26 -0
- package/dist/src/index.js +27 -0
- package/dist/src/index.js.map +7 -0
- package/dist/src/joinUrl.d.ts +1 -0
- package/dist/src/joinUrl.js +17 -0
- package/dist/src/joinUrl.js.map +7 -0
- package/dist/src/mds3tk.js +64 -0
- package/dist/src/mds3tk.js.map +7 -0
- package/dist/src/roundValue.js +57 -0
- package/dist/src/roundValue.js.map +7 -0
- package/dist/src/termdb.bins.js +272 -0
- package/dist/src/termdb.bins.js.map +7 -0
- package/dist/src/termdb.initbinconfig.js +79 -0
- package/dist/src/termdb.initbinconfig.js.map +7 -0
- package/dist/src/termdb.usecase.js +239 -0
- package/dist/src/termdb.usecase.js.map +7 -0
- package/dist/src/terms.d.ts +83 -0
- package/dist/src/terms.js +327 -0
- package/dist/src/terms.js.map +7 -0
- package/dist/src/time.d.ts +9 -0
- package/dist/src/time.js +23 -0
- package/dist/src/time.js.map +7 -0
- package/dist/src/tree.js +82 -0
- package/dist/src/tree.js.map +7 -0
- package/dist/src/urljson.d.ts +8 -0
- package/dist/src/urljson.js +31 -0
- package/dist/src/urljson.js.map +7 -0
- package/dist/src/vcf.ann.js +56 -0
- package/dist/src/vcf.ann.js.map +7 -0
- package/dist/src/vcf.csq.js +82 -0
- package/dist/src/vcf.csq.js.map +7 -0
- package/dist/src/vcf.info.js +40 -0
- package/dist/src/vcf.info.js.map +7 -0
- package/dist/src/vcf.js +439 -0
- package/dist/src/vcf.js.map +7 -0
- package/dist/src/vcf.type.js +17 -0
- package/dist/src/vcf.type.js.map +7 -0
- package/package.json +20 -11
- package/src/bulk.cnv.js +0 -86
- package/src/bulk.del.js +0 -124
- package/src/bulk.itd.js +0 -123
- package/src/bulk.js +0 -197
- package/src/bulk.snv.js +0 -271
- package/src/bulk.sv.js +0 -276
- package/src/bulk.svjson.js +0 -164
- package/src/bulk.trunc.js +0 -132
- package/src/clustering.js +0 -66
- package/src/common.js +0 -1608
- package/src/compute.percentile.js +0 -11
- package/src/doc.js +0 -6
- package/src/fetch-helpers.js +0 -323
- package/src/fileSize.js +0 -6
- package/src/filter.js +0 -221
- package/src/hash.js +0 -21
- package/src/helpers.js +0 -88
- package/src/index.js +0 -26
- package/src/joinUrl.js +0 -14
- package/src/mds3tk.js +0 -100
- package/src/roundValue.js +0 -94
- package/src/termdb.bins.js +0 -456
- package/src/termdb.initbinconfig.js +0 -130
- package/src/termdb.usecase.js +0 -344
- package/src/terms.js +0 -341
- package/src/time.js +0 -22
- package/src/tree.js +0 -138
- package/src/urljson.js +0 -41
- package/src/vcf.ann.js +0 -62
- package/src/vcf.csq.js +0 -153
- package/src/vcf.info.js +0 -50
- package/src/vcf.js +0 -654
- package/src/vcf.type.js +0 -24
package/src/index.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
// please list in alphanumeric order
|
|
2
|
-
export * from "./bulk.js"
|
|
3
|
-
export * from "./clustering.js"
|
|
4
|
-
export * from "./common.js"
|
|
5
|
-
export * from "./compute.percentile.js"
|
|
6
|
-
export * from "./fetch-helpers.js"
|
|
7
|
-
export * from "./fileSize.js"
|
|
8
|
-
export * from "./filter.js"
|
|
9
|
-
export * from "./hash.js"
|
|
10
|
-
export * from "./helpers.js"
|
|
11
|
-
export * from "./joinUrl.js"
|
|
12
|
-
export * from "./mds3tk.js"
|
|
13
|
-
export * from "./roundValue.js"
|
|
14
|
-
export * from "./termdb.bins.js"
|
|
15
|
-
export * from "./termdb.initbinconfig.js"
|
|
16
|
-
export * from "./termdb.usecase.js"
|
|
17
|
-
export * from "./terms.js"
|
|
18
|
-
export * from "./time.js"
|
|
19
|
-
export * from "./tree.js"
|
|
20
|
-
// do `npm run esm` to generate .js file from .ts file
|
|
21
|
-
export * from "./urljson.js"
|
|
22
|
-
export * from "./vcf.ann.js"
|
|
23
|
-
export * from "./vcf.csq.js"
|
|
24
|
-
export * from "./vcf.info.js"
|
|
25
|
-
export * from "./vcf.js"
|
|
26
|
-
export * from "./vcf.type.js"
|
package/src/joinUrl.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
function joinUrl(p1, ...p2) {
|
|
2
|
-
if (typeof p1 != "string") throw `first argument must be string type`
|
|
3
|
-
if (!p1) throw "blank string not allowed"
|
|
4
|
-
if (p1.indexOf("?") != -1) throw "search string not allowed"
|
|
5
|
-
let url = p1
|
|
6
|
-
for (const p of p2) {
|
|
7
|
-
if (typeof p != "string") throw `all arguments must be string type`
|
|
8
|
-
if (!p) throw "blank string not allowed"
|
|
9
|
-
if (url.slice(-1) != "/") url += "/"
|
|
10
|
-
url += p.startsWith("/") ? p.substring(1) : p
|
|
11
|
-
}
|
|
12
|
-
return url
|
|
13
|
-
}
|
|
14
|
-
export { joinUrl }
|
package/src/mds3tk.js
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { dtcnv, dtsnvindel, dtsv, dtfusionrna } from "./common.js"
|
|
2
|
-
|
|
3
|
-
// this script should contain mds3 track-related stuff shared between client and backend
|
|
4
|
-
|
|
5
|
-
/*
|
|
6
|
-
the separator is used to join essential bits of a variant obj into a string as the "ssm_id", aims to uniquely identify a variant irrespective of sample
|
|
7
|
-
this is to mimic the GDC "ssm_id" which is a random id, with below benefits:
|
|
8
|
-
- consistent way to pass request body for both gdc and non-gdc
|
|
9
|
-
- uniform identification of ssm/cnv/sv in non-gdc backend code
|
|
10
|
-
- uniform identification of all variants in client
|
|
11
|
-
|
|
12
|
-
ssm: chr + pos + ref + alt
|
|
13
|
-
cnv: chr + start + stop + class
|
|
14
|
-
svfusion: dt + chr + pos + strand + pairlstidx + mname
|
|
15
|
-
|
|
16
|
-
the separator must avoid conflicting with characters from gene names, and can be changed based on needs
|
|
17
|
-
*/
|
|
18
|
-
export const ssmIdFieldsSeparator = "__"
|
|
19
|
-
|
|
20
|
-
/*
|
|
21
|
-
input: array of mixture of ssm, svfusion and cnv
|
|
22
|
-
|
|
23
|
-
output: sorted array. each element: [ class/dt, count of m ]
|
|
24
|
-
*/
|
|
25
|
-
export function summarize_mclass(mlst) {
|
|
26
|
-
const m2c = new Map() // k: mclass, v: {}
|
|
27
|
-
const cnvs = []
|
|
28
|
-
for (const m of mlst) {
|
|
29
|
-
if (m.dt == dtcnv) {
|
|
30
|
-
cnvs.push(m)
|
|
31
|
-
continue // process cnv later
|
|
32
|
-
}
|
|
33
|
-
// snvindel has m.class=str, svfusion has only dt=int
|
|
34
|
-
const key = m.class || m.dt
|
|
35
|
-
m2c.set(key, 1 + (m2c.get(key) || 0))
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (cnvs.length) {
|
|
39
|
-
if (Number.isFinite(cnvs[0].value)) {
|
|
40
|
-
// first cnv uses numeric value (assumes all the same). record by dt
|
|
41
|
-
m2c.set(dtcnv, cnvs.length)
|
|
42
|
-
} else {
|
|
43
|
-
// cnv not numeric and uses class; record by each class
|
|
44
|
-
for (const c of cnvs) {
|
|
45
|
-
if (!c.class) continue // should not happen
|
|
46
|
-
m2c.set(c.class, 1 + (m2c.get(c.class) || 0))
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return [...m2c].sort((i, j) => j[1] - i[1])
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/*
|
|
54
|
-
ssmid is not specific for ssm, it covers all alterations
|
|
55
|
-
gdc ssm are identified by a specific uuid, thus the design
|
|
56
|
-
*/
|
|
57
|
-
export function guessSsmid(ssmid) {
|
|
58
|
-
const l = ssmid.split(ssmIdFieldsSeparator)
|
|
59
|
-
if (l.length == 4) {
|
|
60
|
-
const [chr, tmp, ref, alt] = l
|
|
61
|
-
const pos = Number(tmp)
|
|
62
|
-
if (Number.isNaN(pos)) throw "ssmid snvindel pos not integer"
|
|
63
|
-
return { dt: dtsnvindel, l: [chr, pos, ref, alt] }
|
|
64
|
-
}
|
|
65
|
-
if (l.length == 5) {
|
|
66
|
-
// cnv. if type=cat, _value is blank string
|
|
67
|
-
const [chr, _start, _stop, _class, _value] = l
|
|
68
|
-
const start = Number(_start),
|
|
69
|
-
stop = Number(_stop),
|
|
70
|
-
value = _value == "" ? null : Number(_value)
|
|
71
|
-
if (Number.isNaN(start) || Number.isNaN(stop))
|
|
72
|
-
throw "ssmid cnv start/stop not integer"
|
|
73
|
-
return { dt: dtcnv, l: [chr, start, stop, _class, value] }
|
|
74
|
-
}
|
|
75
|
-
if (l.length == 6) {
|
|
76
|
-
if (l[3] == "+" || l[3] == "-") {
|
|
77
|
-
// sv/fusion
|
|
78
|
-
const [_dt, chr, _pos, strand, _pi, _mname] = l
|
|
79
|
-
|
|
80
|
-
// mname is encoded in case it contains comma (and is same as ssmIdFieldsSeparator)
|
|
81
|
-
const mname = decodeURIComponent(_mname)
|
|
82
|
-
const dt = Number(_dt)
|
|
83
|
-
if (dt != dtsv && dt != dtfusionrna) throw "ssmid dt not sv/fusion"
|
|
84
|
-
const pos = Number(_pos)
|
|
85
|
-
if (Number.isNaN(pos)) throw "ssmid svfusion position not integer"
|
|
86
|
-
const pairlstIdx = Number(_pi)
|
|
87
|
-
if (Number.isNaN(pairlstIdx)) throw "ssmid pairlstIdx not integer"
|
|
88
|
-
return { dt, l: [dt, chr, pos, strand, pairlstIdx, mname] }
|
|
89
|
-
}
|
|
90
|
-
// cnv with sample
|
|
91
|
-
const [chr, _start, _stop, _class, _value, sample] = l
|
|
92
|
-
const start = Number(_start),
|
|
93
|
-
stop = Number(_stop),
|
|
94
|
-
value = _value == "" ? null : Number(_value) // if cnv not using value, must avoid `Number('')=0`
|
|
95
|
-
if (Number.isNaN(start) || Number.isNaN(stop))
|
|
96
|
-
throw "ssmid cnv start/stop not integer"
|
|
97
|
-
return { dt: dtcnv, l: [chr, start, stop, _class, value, sample] }
|
|
98
|
-
}
|
|
99
|
-
throw "unknown ssmid"
|
|
100
|
-
}
|
package/src/roundValue.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
round a value to specified digits
|
|
3
|
-
- if value is integer, value is returned
|
|
4
|
-
- if value is a fractional float, round to precision
|
|
5
|
-
- if value is not a fractional float, round to decimal point
|
|
6
|
-
- TODO: handle scientific notation
|
|
7
|
-
value: given value
|
|
8
|
-
digits: number of digits to round to
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export function roundValue(value, digits) {
|
|
12
|
-
const v = Number(value)
|
|
13
|
-
if (Number.isInteger(v)) return v
|
|
14
|
-
if (Math.abs(v) < 1) return Number(v.toPrecision(digits))
|
|
15
|
-
return Number(v.toFixed(digits))
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** Rounds numbers to the appropriate decimal point
|
|
19
|
-
* if format is true, returns either a number or string in
|
|
20
|
-
* scientific notation.
|
|
21
|
-
*
|
|
22
|
-
* TODO: Review digit logic.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
export function roundValueAuto(value, format = false, defaultDigits = 2) {
|
|
26
|
-
if (!value && value != 0) return value
|
|
27
|
-
const dp = decimalPlacesUntilFirstNonZero(value)
|
|
28
|
-
const digits =
|
|
29
|
-
Math.abs(value) > 1 ? defaultDigits : dp > 0 ? dp + 1 : defaultDigits
|
|
30
|
-
if (format) return formatValue(value, digits)
|
|
31
|
-
return roundValue(value, digits)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function decimalPlacesUntilFirstNonZero(number) {
|
|
35
|
-
// Convert number to string
|
|
36
|
-
const numberStr = number.toString()
|
|
37
|
-
|
|
38
|
-
// Find the position of the decimal point
|
|
39
|
-
const decimalIndex = numberStr.indexOf(".")
|
|
40
|
-
|
|
41
|
-
// If decimal point is not found or number is an integer, return 0
|
|
42
|
-
if (decimalIndex === -1 || decimalIndex === numberStr.length - 1) {
|
|
43
|
-
return 0
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Iterate through characters after the decimal point
|
|
47
|
-
let decimalPlaces = 0
|
|
48
|
-
for (let i = decimalIndex + 1; i < numberStr.length; i++) {
|
|
49
|
-
// Increment the count of decimal places until a non-zero digit is found
|
|
50
|
-
if (numberStr[i] === "0") {
|
|
51
|
-
decimalPlaces++
|
|
52
|
-
} else if (numberStr[i] >= "1" && numberStr[i] <= "9") {
|
|
53
|
-
break
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return decimalPlaces
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/*
|
|
61
|
-
simple logic to return a number close to original while rounding up decimals.
|
|
62
|
-
supplements roundValueAuto which rounds 12345 to 1.2e4 which is only suitable for human quick glance but not subsequent computing
|
|
63
|
-
|
|
64
|
-
TODO:
|
|
65
|
-
10000 and 10001 to 1e4
|
|
66
|
-
0.00001 to 1e-5
|
|
67
|
-
1.00001 to 1
|
|
68
|
-
*/
|
|
69
|
-
export function roundValue2(value) {
|
|
70
|
-
if (!Number.isFinite(value)) return value // not a number
|
|
71
|
-
if (Number.isInteger(value)) return value // is integer, do not convert
|
|
72
|
-
const abs = Math.abs(value)
|
|
73
|
-
if (abs > 100) return Math.round(value) // 12345.1234 to 12345 (compared to 1.2e4 from roundValueAuto)
|
|
74
|
-
if (abs > 10) return Number(value.toFixed(1)) // 99.1234 to 99.1
|
|
75
|
-
if (abs > 1) return Number(value.toFixed(2)) // 9.1234 to 9.12
|
|
76
|
-
if (abs > 0.1) return Number(value.toFixed(3)) // 0.12345 to 0.123
|
|
77
|
-
if (abs > 0.01) return Number(value.toFixed(4)) // 0.012345 to 0.0123
|
|
78
|
-
return value // as is
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** Use to return displayed values in scientific notation
|
|
82
|
-
* Do not use for values intended for calculation later.
|
|
83
|
-
*/
|
|
84
|
-
export function formatValue(value, digits) {
|
|
85
|
-
const v = Number(value)
|
|
86
|
-
if (Number.isInteger(v)) return v
|
|
87
|
-
const abs = Math.abs(v)
|
|
88
|
-
if (abs < 1 || abs > 9999) {
|
|
89
|
-
//Number() reverts positive values less than 10^21 to a whole number
|
|
90
|
-
//To return the value in scientific notation, use toPrecision without Number()
|
|
91
|
-
return abs > 9999 ? v.toPrecision(digits) : Number(v.toPrecision(digits))
|
|
92
|
-
}
|
|
93
|
-
return Number(v.toFixed(digits))
|
|
94
|
-
}
|
package/src/termdb.bins.js
DELETED
|
@@ -1,456 +0,0 @@
|
|
|
1
|
-
import { format } from "d3-format"
|
|
2
|
-
import { getColors } from "./common.js"
|
|
3
|
-
import { isNumeric, isStrictNumeric, convertUnits } from "./helpers.js"
|
|
4
|
-
|
|
5
|
-
export default function validate_bins(binconfig) {
|
|
6
|
-
// Number.isFinite('1') returns false, which is desired
|
|
7
|
-
|
|
8
|
-
const bc = binconfig
|
|
9
|
-
if (!bc || typeof bc !== "object") throw "bin schema must be an object"
|
|
10
|
-
// assign default type
|
|
11
|
-
if (!("type" in bc)) bc.type = "regular-bin"
|
|
12
|
-
|
|
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
|
-
const first_bin = bc.lst[0]
|
|
17
|
-
const last_bin = bc.lst[bc.lst.length - 1]
|
|
18
|
-
|
|
19
|
-
for (const bin of bc.lst) {
|
|
20
|
-
if (!("startinclusive" in bin) && !("stopinclusive" in bin)) {
|
|
21
|
-
throw "custom bin.startinclusive and/or bin.stopinclusive must be defined"
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (bin == first_bin) {
|
|
25
|
-
if ("startunbounded" in bin && !bin.startunbounded) {
|
|
26
|
-
throw `a custom first bin must not set bin.startunbounded to false`
|
|
27
|
-
}
|
|
28
|
-
bin.startunbounded = true
|
|
29
|
-
if ("start" in bin) {
|
|
30
|
-
throw "a custom first bin must not set a bin.start value"
|
|
31
|
-
}
|
|
32
|
-
if ("start_percentile" in bin) {
|
|
33
|
-
throw "the first bin must not set a bin.start_percentile value"
|
|
34
|
-
}
|
|
35
|
-
if (!("stop" in bin)) {
|
|
36
|
-
throw `a custom first bin must define a bin.stop value`
|
|
37
|
-
}
|
|
38
|
-
if (!isStrictNumeric(bin.stop)) {
|
|
39
|
-
throw `a custom first bin.stop value should be numeric`
|
|
40
|
-
}
|
|
41
|
-
} else if (bin == last_bin) {
|
|
42
|
-
if (!("start" in bin)) {
|
|
43
|
-
throw `a custom last bin must define a bin.start value`
|
|
44
|
-
}
|
|
45
|
-
if (!isStrictNumeric(bin.start)) {
|
|
46
|
-
throw `a custom last bin.start must be numeric`
|
|
47
|
-
}
|
|
48
|
-
if ("stopunbounded" in bin && !bin.stopunbounded) {
|
|
49
|
-
throw "a custom last bin must not set bin.stopunbounded to false"
|
|
50
|
-
}
|
|
51
|
-
bin.stopunbounded = true
|
|
52
|
-
if ("stop" in bin) {
|
|
53
|
-
throw "a custom last bin must not set a bin.stop value"
|
|
54
|
-
}
|
|
55
|
-
} else {
|
|
56
|
-
if (bin.startunbounded || bin.stopunbounded) {
|
|
57
|
-
throw "bin.startunbounded and bin.stopunbounded must not be set for non-first/non-last bins"
|
|
58
|
-
}
|
|
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"
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
} else if (bc.type == "regular-bin") {
|
|
66
|
-
// required custom_bin parameter
|
|
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"
|
|
69
|
-
|
|
70
|
-
if (!bc.startinclusive && !bc.stopinclusive) {
|
|
71
|
-
bc.startinclusive = 1
|
|
72
|
-
bc.stopinclusive = 0
|
|
73
|
-
}
|
|
74
|
-
|
|
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"
|
|
78
|
-
|
|
79
|
-
{
|
|
80
|
-
const b = bc.first_bin
|
|
81
|
-
b.startunbounded = true
|
|
82
|
-
// requires stop_percentile, or stop
|
|
83
|
-
if (b.stop_percentile) {
|
|
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)"
|
|
88
|
-
} else if (!Number.isFinite(b.stop)) {
|
|
89
|
-
throw "first_bin.stop not a number when stop_percentile is not set"
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (bc.last_bin) {
|
|
94
|
-
const b = bc.last_bin
|
|
95
|
-
// requires start_percentile or start
|
|
96
|
-
if (b.start_percentile) {
|
|
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)"
|
|
101
|
-
} else if (!Number.isFinite(b.start)) {
|
|
102
|
-
throw "last_bin.start not a number when start_percentile is not set"
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
b.stopunbounded = true
|
|
106
|
-
if ("stop" in b) {
|
|
107
|
-
throw "a regular last bin must not set a bin.stop value"
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
} else {
|
|
111
|
-
throw `invalid binconfig.type="${bc.type}"`
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export function compute_bins(binconfig, summaryfxn, valueConversion) {
|
|
116
|
-
/*
|
|
117
|
-
Bins generator
|
|
118
|
-
|
|
119
|
-
binconfig
|
|
120
|
-
configuration of bins per the Numerical Binning Scheme
|
|
121
|
-
|
|
122
|
-
summaryfxn (percentiles)=> return {min, max, pX, pY, ...}
|
|
123
|
-
- required function
|
|
124
|
-
|
|
125
|
-
- must accept an array of desired percentile values
|
|
126
|
-
and returns an object of computed properties
|
|
127
|
-
{
|
|
128
|
-
min: minimum value
|
|
129
|
-
max: maximum value
|
|
130
|
-
pX: percentile at X value, so p10 will be 10th percentile value
|
|
131
|
-
pY: .. corresponding to the desired percentile values
|
|
132
|
-
}
|
|
133
|
-
*/
|
|
134
|
-
const bc = binconfig
|
|
135
|
-
|
|
136
|
-
validate_bins(bc)
|
|
137
|
-
if (bc.lst) {
|
|
138
|
-
const k2c = getColors(bc.lst.length) //to color bins
|
|
139
|
-
for (const bin of bc.lst) bin.color = k2c(bin.label)
|
|
140
|
-
}
|
|
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()"
|
|
144
|
-
const percentiles = target_percentiles(bc)
|
|
145
|
-
const summary = summaryfxn(percentiles)
|
|
146
|
-
if (!summary || typeof summary !== "object")
|
|
147
|
-
throw "invalid returned value by summaryfxn"
|
|
148
|
-
bc.results = { summary }
|
|
149
|
-
if (!bc.binLabelFormatter) bc.binLabelFormatter = getNumDecimalsFormatter(bc)
|
|
150
|
-
|
|
151
|
-
// round the min and max values for use as bin start and stop
|
|
152
|
-
// in the first and last bins, respectively
|
|
153
|
-
const minFloor = Math.floor(summary.min * 100) / 100
|
|
154
|
-
const maxCeil = Math.ceil(summary.max * 100) / 100
|
|
155
|
-
const min = bc.first_bin.startunbounded
|
|
156
|
-
? minFloor
|
|
157
|
-
: bc.first_bin.start_percentile
|
|
158
|
-
? summary["p" + bc.first_bin.start_percentile]
|
|
159
|
-
: bc.first_bin.start
|
|
160
|
-
let max = maxCeil, // in order to include the max value in the last bin
|
|
161
|
-
last_start,
|
|
162
|
-
last_stop
|
|
163
|
-
|
|
164
|
-
if (bc.last_bin) {
|
|
165
|
-
max = bc.last_bin.stopunbounded
|
|
166
|
-
? maxCeil // in order to include the max value in the last bin
|
|
167
|
-
: bc.last_bin.stop_percentile
|
|
168
|
-
? summary["p" + bc.last_bin.stop_percentile]
|
|
169
|
-
: isNumeric(bc.last_bin.stop) && bc.last_bin.stop <= summary.max // '0.0088' < 0.0088
|
|
170
|
-
? bc.last_bin.stop
|
|
171
|
-
: maxCeil // in order to include the max value in the last bin
|
|
172
|
-
last_start = isStrictNumeric(bc.last_bin.start_percentile)
|
|
173
|
-
? summary["p" + bc.last_bin.start_percentile]
|
|
174
|
-
: isStrictNumeric(bc.last_bin.start)
|
|
175
|
-
? bc.last_bin.start
|
|
176
|
-
: undefined
|
|
177
|
-
last_stop = bc.last_bin.stopunbounded
|
|
178
|
-
? null
|
|
179
|
-
: bc.last_bin.stop_percentile
|
|
180
|
-
? summary["p" + bc.last_bin.stop_percentile]
|
|
181
|
-
: isStrictNumeric(bc.last_bin.stop)
|
|
182
|
-
? bc.last_bin.stop
|
|
183
|
-
: null
|
|
184
|
-
} else if (bc.lst) {
|
|
185
|
-
const last_bin = bc.lst[bc.lst.length - 1]
|
|
186
|
-
last_start = last_bin.start
|
|
187
|
-
last_stop =
|
|
188
|
-
"stop" in last_bin && !last_bin.stopunbounded ? last_bin.stop : maxCeil
|
|
189
|
-
max = last_stop
|
|
190
|
-
} else {
|
|
191
|
-
last_start = maxCeil
|
|
192
|
-
last_stop = maxCeil
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const numericMax = isStrictNumeric(max)
|
|
196
|
-
const numericLastStart = isStrictNumeric(last_start)
|
|
197
|
-
const numericLastStop = isStrictNumeric(last_stop)
|
|
198
|
-
|
|
199
|
-
if (!numericMax && !numericLastStart) return [] //throw 'unable to compute the last bin start or stop'
|
|
200
|
-
|
|
201
|
-
const bins = []
|
|
202
|
-
let currBin = {
|
|
203
|
-
startunbounded: bc.first_bin.startunbounded,
|
|
204
|
-
start: bc.first_bin.startunbounded ? undefined : min,
|
|
205
|
-
stop: isStrictNumeric(bc.first_bin.stop_percentile)
|
|
206
|
-
? +summary["p" + bc.first_bin.stop_percentile]
|
|
207
|
-
: isStrictNumeric(bc.first_bin.stop)
|
|
208
|
-
? +bc.first_bin.stop
|
|
209
|
-
: min + bc.bin_size,
|
|
210
|
-
startinclusive: bc.startinclusive,
|
|
211
|
-
stopinclusive: bc.stopinclusive,
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (!isStrictNumeric(currBin.stop))
|
|
215
|
-
throw "the computed first_bin.stop is non-numeric" + currBin.stop
|
|
216
|
-
const maxNumBins = 100 // harcoded limit for now to not stress sqlite
|
|
217
|
-
|
|
218
|
-
while (
|
|
219
|
-
(numericMax && currBin.stop <= max) ||
|
|
220
|
-
(currBin.startunbounded && !bins.length) ||
|
|
221
|
-
currBin.stopunbounded
|
|
222
|
-
) {
|
|
223
|
-
bins.push(currBin)
|
|
224
|
-
// force a computed last bin to have stopunbounded true
|
|
225
|
-
if (currBin.stop >= max) {
|
|
226
|
-
currBin.stopunbounded = true
|
|
227
|
-
if (bins.length > 1) {
|
|
228
|
-
delete currBin.stop
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
currBin.label = get_bin_label(currBin, bc, valueConversion)
|
|
232
|
-
if (currBin.stopunbounded) break
|
|
233
|
-
|
|
234
|
-
const upper = currBin.stop + bc.bin_size
|
|
235
|
-
const previousStop = currBin.stop
|
|
236
|
-
currBin = {
|
|
237
|
-
startinclusive: bc.startinclusive,
|
|
238
|
-
stopinclusive: bc.stopinclusive,
|
|
239
|
-
start: previousStop,
|
|
240
|
-
stop:
|
|
241
|
-
numericLastStop && (previousStop == last_start || upper > last_stop)
|
|
242
|
-
? last_stop
|
|
243
|
-
: numericLastStart && upper > last_start && previousStop != last_start
|
|
244
|
-
? last_start
|
|
245
|
-
: upper,
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (currBin.stop >= max) {
|
|
249
|
-
currBin.stop = max
|
|
250
|
-
if (bc.last_bin && bc.last_bin.stopunbounded) currBin.stopunbounded = 1
|
|
251
|
-
if (bc.last_bin && bc.last_bin.stopinclusive) currBin.stopinclusive = 1
|
|
252
|
-
}
|
|
253
|
-
if (numericLastStart && currBin.start == last_start) {
|
|
254
|
-
if (bc.last_bin && bc.last_bin.stopunbounded) currBin.stopunbounded = 1
|
|
255
|
-
}
|
|
256
|
-
if (currBin.start > currBin.stop) {
|
|
257
|
-
if (
|
|
258
|
-
numericLastStart &&
|
|
259
|
-
currBin.stop == last_start &&
|
|
260
|
-
bc.last_bin &&
|
|
261
|
-
bc.last_bin.stopunbounded
|
|
262
|
-
)
|
|
263
|
-
currBin.stopunbounded = true
|
|
264
|
-
else break
|
|
265
|
-
}
|
|
266
|
-
if (bins.length + 1 >= maxNumBins) {
|
|
267
|
-
bc.error = "max_num_bins_reached"
|
|
268
|
-
break
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
delete bc.binLabelFormatter
|
|
272
|
-
if (bins.length > 1) {
|
|
273
|
-
delete bins[bins.length - 1].stop
|
|
274
|
-
}
|
|
275
|
-
const k2c = getColors(bins.length) //to color bins
|
|
276
|
-
for (const bin of bins) bin.color = k2c(bin.label)
|
|
277
|
-
return bins
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function getNumDecimalsFormatter(bc) {
|
|
281
|
-
//return format('rounding' in bc ? bc.rounding : '')
|
|
282
|
-
return "rounding" in bc ? format(bc.rounding) : (d) => d // default to labeling using the start/stop value as-is
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
export function get_bin_label(bin, binconfig, valueConversion) {
|
|
286
|
-
/*
|
|
287
|
-
Generate a numeric bin label given a bin configuration and an optional term valueConversion object
|
|
288
|
-
*/
|
|
289
|
-
if (!bin) return "missing bin.label"
|
|
290
|
-
if (bin.label) return bin.label
|
|
291
|
-
|
|
292
|
-
const bc = binconfig
|
|
293
|
-
if (!bc.binLabelFormatter) bc.binLabelFormatter = getNumDecimalsFormatter(bc)
|
|
294
|
-
if (
|
|
295
|
-
!bin.startunbounded &&
|
|
296
|
-
!bin.stopunbounded &&
|
|
297
|
-
!("startinclusive" in bin) &&
|
|
298
|
-
!("stopinclusive" in bin)
|
|
299
|
-
) {
|
|
300
|
-
if (bc.startinclusive) bin.startinclusive = true
|
|
301
|
-
else if (bc.stopinclusive) bin.stopinclusive = true
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const start = bc.use_as == "bins" || bin.start
|
|
305
|
-
const stop = bc.use_as == "bins" || bin.stop
|
|
306
|
-
|
|
307
|
-
let label_offset = 0
|
|
308
|
-
if ("label_offset" in bc) {
|
|
309
|
-
bc.label_offset_ignored = "bin_size" in bc && bc.bin_size < bc.label_offset
|
|
310
|
-
if (!bc.label_offset_ignored) label_offset = bc.label_offset
|
|
311
|
-
} else if (bc.bin_size === 1 && bc.termtype == "integer") {
|
|
312
|
-
label_offset = 1
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// one side-unbounded bins
|
|
316
|
-
// label will be ">v" or "<v"
|
|
317
|
-
if (bin.startunbounded) {
|
|
318
|
-
const oper = bin.stopinclusive ? "≤" : "<" // \u2264
|
|
319
|
-
const v1 = valueConversion
|
|
320
|
-
? convertUnits(
|
|
321
|
-
stop,
|
|
322
|
-
valueConversion.fromUnit,
|
|
323
|
-
valueConversion.toUnit,
|
|
324
|
-
valueConversion.scaleFactor,
|
|
325
|
-
true
|
|
326
|
-
)
|
|
327
|
-
: bc.binLabelFormatter(stop) //bin.startinclusive && label_offset ? stop - label_offset : stop)
|
|
328
|
-
return oper + v1
|
|
329
|
-
}
|
|
330
|
-
// a data value may coincide with the last bin's start
|
|
331
|
-
if (bin.stopunbounded || start === stop) {
|
|
332
|
-
const oper = bin.startinclusive /*|| label_offset*/ ? "≥" : ">" // \u2265
|
|
333
|
-
const v0 = valueConversion
|
|
334
|
-
? convertUnits(
|
|
335
|
-
start,
|
|
336
|
-
valueConversion.fromUnit,
|
|
337
|
-
valueConversion.toUnit,
|
|
338
|
-
valueConversion.scaleFactor,
|
|
339
|
-
true
|
|
340
|
-
)
|
|
341
|
-
: bc.binLabelFormatter(start) //bin.startinclusive || start == min ? start : start + label_offset)
|
|
342
|
-
return oper + v0
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// two-sided bins
|
|
346
|
-
if (label_offset && bin.startinclusive && !bin.stopinclusive) {
|
|
347
|
-
if (
|
|
348
|
-
Number.isInteger(bc.bin_size) &&
|
|
349
|
-
Math.abs(start - stop) === label_offset
|
|
350
|
-
) {
|
|
351
|
-
// make a simpler label when the range simply spans the bin_size
|
|
352
|
-
return (
|
|
353
|
-
"" +
|
|
354
|
-
(valueConversion
|
|
355
|
-
? convertUnits(
|
|
356
|
-
start,
|
|
357
|
-
valueConversion.fromUnit,
|
|
358
|
-
valueConversion.toUnit,
|
|
359
|
-
valueConversion.scaleFactor,
|
|
360
|
-
true
|
|
361
|
-
)
|
|
362
|
-
: bc.binLabelFormatter(start))
|
|
363
|
-
)
|
|
364
|
-
} else {
|
|
365
|
-
const v0 = valueConversion
|
|
366
|
-
? convertUnits(
|
|
367
|
-
start,
|
|
368
|
-
valueConversion.fromUnit,
|
|
369
|
-
valueConversion.toUnit,
|
|
370
|
-
valueConversion.scaleFactor,
|
|
371
|
-
true
|
|
372
|
-
)
|
|
373
|
-
: bc.binLabelFormatter(start)
|
|
374
|
-
const v1 = valueConversion
|
|
375
|
-
? convertUnits(
|
|
376
|
-
stop - label_offset,
|
|
377
|
-
valueConversion.fromUnit,
|
|
378
|
-
valueConversion.toUnit,
|
|
379
|
-
valueConversion.scaleFactor,
|
|
380
|
-
true
|
|
381
|
-
)
|
|
382
|
-
: bc.binLabelFormatter(stop - label_offset)
|
|
383
|
-
// ensure that last two bin labels make sense (the last is stopunbounded)
|
|
384
|
-
return +v0 >= +v1 ? v0.toString() : v0 + " to " + v1
|
|
385
|
-
}
|
|
386
|
-
} else {
|
|
387
|
-
// stop_inclusive || label_offset == 0
|
|
388
|
-
const oper0 = bin.startinclusive ? "" : ">"
|
|
389
|
-
const oper1 = bin.stopinclusive ? "" : "<"
|
|
390
|
-
const v0 = valueConversion
|
|
391
|
-
? convertUnits(
|
|
392
|
-
start,
|
|
393
|
-
valueConversion.fromUnit,
|
|
394
|
-
valueConversion.toUnit,
|
|
395
|
-
valueConversion.scaleFactor,
|
|
396
|
-
true
|
|
397
|
-
)
|
|
398
|
-
: Number.isInteger(start)
|
|
399
|
-
? start
|
|
400
|
-
: bc.binLabelFormatter(start)
|
|
401
|
-
const v1 = valueConversion
|
|
402
|
-
? convertUnits(
|
|
403
|
-
stop,
|
|
404
|
-
valueConversion.fromUnit,
|
|
405
|
-
valueConversion.toUnit,
|
|
406
|
-
valueConversion.scaleFactor,
|
|
407
|
-
true
|
|
408
|
-
)
|
|
409
|
-
: Number.isInteger(stop)
|
|
410
|
-
? stop
|
|
411
|
-
: bc.binLabelFormatter(stop)
|
|
412
|
-
// after rounding the bin labels, the bin start may equal the last bin stop as derived from actual data
|
|
413
|
-
if (+v0 >= +v1) {
|
|
414
|
-
const oper = bin.startinclusive ? "≥" : ">" // \u2265
|
|
415
|
-
return oper + v0
|
|
416
|
-
} else {
|
|
417
|
-
return oper0 + v0 + " to " + oper1 + v1
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// get bin range equation from bin label and bin properties
|
|
423
|
-
export function get_bin_range_equation(bin, binconfig) {
|
|
424
|
-
const x = '<span style="font-family:Times;font-style:italic;">x</span>'
|
|
425
|
-
let range_eq
|
|
426
|
-
// should always use computed (not user-customized) bin label to determine bin range text
|
|
427
|
-
const copy = structuredClone(bin)
|
|
428
|
-
copy.label = "" // mutate only the copy, and not the original bin argument
|
|
429
|
-
const bin_label = get_bin_label(copy, binconfig)
|
|
430
|
-
if (bin.startunbounded || bin.stopunbounded) {
|
|
431
|
-
// first or last bins, e.g. x ≤ 14 and x > 16
|
|
432
|
-
range_eq = x + " " + bin_label
|
|
433
|
-
} else if (bin.startinclusive) {
|
|
434
|
-
// bins with startinclusive, e.g. 14 ≤ x < 16
|
|
435
|
-
range_eq = bin_label.replace("to <", "≤ " + x + " <")
|
|
436
|
-
} else if (bin.stopinclusive) {
|
|
437
|
-
// bins with stopinclusive, e.g. 14 < x ≤ 16
|
|
438
|
-
range_eq = bin_label.replace(">", "").replace("to", "< " + x + " ≤")
|
|
439
|
-
}
|
|
440
|
-
return range_eq
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
export function target_percentiles(binconfig) {
|
|
444
|
-
const percentiles = []
|
|
445
|
-
const f = binconfig.first_bin
|
|
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)
|
|
450
|
-
const l = binconfig.last_bin
|
|
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)
|
|
455
|
-
return percentiles
|
|
456
|
-
}
|