@sjcrh/proteinpaint-shared 2.187.0 → 2.188.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.
Files changed (117) hide show
  1. package/README.md +10 -2
  2. package/constants/AiHisto.ts +27 -0
  3. package/constants/README.md +11 -0
  4. package/devTs.ts +3 -0
  5. package/dist/constants/AiHisto.d.ts +23 -0
  6. package/dist/constants/AiHisto.js +31 -0
  7. package/dist/constants/AiHisto.js.map +7 -0
  8. package/dist/src/aiHisto.d.ts +5 -0
  9. package/dist/src/aiHisto.js +15 -0
  10. package/dist/src/aiHisto.js.map +7 -0
  11. package/dist/src/bulk.cnv.js +83 -0
  12. package/dist/src/bulk.cnv.js.map +7 -0
  13. package/dist/src/bulk.del.js +119 -0
  14. package/dist/src/bulk.del.js.map +7 -0
  15. package/dist/src/bulk.itd.js +119 -0
  16. package/dist/src/bulk.itd.js.map +7 -0
  17. package/dist/src/bulk.js +183 -0
  18. package/dist/src/bulk.js.map +7 -0
  19. package/dist/src/bulk.snv.js +175 -0
  20. package/dist/src/bulk.snv.js.map +7 -0
  21. package/dist/src/bulk.sv.js +266 -0
  22. package/dist/src/bulk.sv.js.map +7 -0
  23. package/dist/src/bulk.svjson.js +151 -0
  24. package/dist/src/bulk.svjson.js.map +7 -0
  25. package/dist/src/bulk.trunc.js +122 -0
  26. package/dist/src/bulk.trunc.js.map +7 -0
  27. package/dist/src/clustering.js +71 -0
  28. package/dist/src/clustering.js.map +7 -0
  29. package/dist/src/common.js +1302 -0
  30. package/dist/src/common.js.map +7 -0
  31. package/dist/src/compute.percentile.js +10 -0
  32. package/dist/src/compute.percentile.js.map +7 -0
  33. package/dist/src/doc.d.ts +7 -0
  34. package/dist/src/doc.js +10 -0
  35. package/dist/src/doc.js.map +7 -0
  36. package/dist/src/fetch-helpers.js +177 -0
  37. package/dist/src/fetch-helpers.js.map +7 -0
  38. package/dist/src/fileSize.js +10 -0
  39. package/dist/src/fileSize.js.map +7 -0
  40. package/dist/src/filter.d.ts +62 -0
  41. package/dist/src/filter.js +194 -0
  42. package/dist/src/filter.js.map +7 -0
  43. package/dist/src/hash.js +20 -0
  44. package/dist/src/hash.js.map +7 -0
  45. package/dist/src/helpers.js +66 -0
  46. package/dist/src/helpers.js.map +7 -0
  47. package/dist/src/index.d.ts +26 -0
  48. package/dist/src/index.js +27 -0
  49. package/dist/src/index.js.map +7 -0
  50. package/dist/src/joinUrl.d.ts +1 -0
  51. package/dist/src/joinUrl.js +17 -0
  52. package/dist/src/joinUrl.js.map +7 -0
  53. package/dist/src/mds3tk.js +64 -0
  54. package/dist/src/mds3tk.js.map +7 -0
  55. package/dist/src/roundValue.js +57 -0
  56. package/dist/src/roundValue.js.map +7 -0
  57. package/dist/src/termdb.bins.js +272 -0
  58. package/dist/src/termdb.bins.js.map +7 -0
  59. package/dist/src/termdb.initbinconfig.js +79 -0
  60. package/dist/src/termdb.initbinconfig.js.map +7 -0
  61. package/dist/src/termdb.usecase.js +239 -0
  62. package/dist/src/termdb.usecase.js.map +7 -0
  63. package/dist/src/terms.d.ts +83 -0
  64. package/dist/src/terms.js +327 -0
  65. package/dist/src/terms.js.map +7 -0
  66. package/dist/src/time.d.ts +9 -0
  67. package/dist/src/time.js +23 -0
  68. package/dist/src/time.js.map +7 -0
  69. package/dist/src/tree.js +82 -0
  70. package/dist/src/tree.js.map +7 -0
  71. package/dist/src/urljson.d.ts +8 -0
  72. package/dist/src/urljson.js +31 -0
  73. package/dist/src/urljson.js.map +7 -0
  74. package/dist/src/vcf.ann.js +56 -0
  75. package/dist/src/vcf.ann.js.map +7 -0
  76. package/dist/src/vcf.csq.js +82 -0
  77. package/dist/src/vcf.csq.js.map +7 -0
  78. package/dist/src/vcf.info.js +40 -0
  79. package/dist/src/vcf.info.js.map +7 -0
  80. package/dist/src/vcf.js +439 -0
  81. package/dist/src/vcf.js.map +7 -0
  82. package/dist/src/vcf.type.js +17 -0
  83. package/dist/src/vcf.type.js.map +7 -0
  84. package/package.json +20 -11
  85. package/src/bulk.cnv.js +0 -86
  86. package/src/bulk.del.js +0 -124
  87. package/src/bulk.itd.js +0 -123
  88. package/src/bulk.js +0 -197
  89. package/src/bulk.snv.js +0 -271
  90. package/src/bulk.sv.js +0 -276
  91. package/src/bulk.svjson.js +0 -164
  92. package/src/bulk.trunc.js +0 -132
  93. package/src/clustering.js +0 -66
  94. package/src/common.js +0 -1608
  95. package/src/compute.percentile.js +0 -11
  96. package/src/doc.js +0 -6
  97. package/src/fetch-helpers.js +0 -323
  98. package/src/fileSize.js +0 -6
  99. package/src/filter.js +0 -221
  100. package/src/hash.js +0 -21
  101. package/src/helpers.js +0 -88
  102. package/src/index.js +0 -26
  103. package/src/joinUrl.js +0 -14
  104. package/src/mds3tk.js +0 -100
  105. package/src/roundValue.js +0 -94
  106. package/src/termdb.bins.js +0 -456
  107. package/src/termdb.initbinconfig.js +0 -130
  108. package/src/termdb.usecase.js +0 -344
  109. package/src/terms.js +0 -341
  110. package/src/time.js +0 -22
  111. package/src/tree.js +0 -138
  112. package/src/urljson.js +0 -41
  113. package/src/vcf.ann.js +0 -62
  114. package/src/vcf.csq.js +0 -153
  115. package/src/vcf.info.js +0 -50
  116. package/src/vcf.js +0 -654
  117. 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
- }
@@ -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
- }