@sjcrh/proteinpaint-shared 2.114.0 → 2.115.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/package.json +4 -2
- package/src/common.js +93 -148
- package/src/filter.js +11 -0
- package/src/termdb.usecase.js +1 -0
- package/src/terms.js +39 -2
- package/src/time.js +26 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-shared",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.115.0",
|
|
4
4
|
"description": "ProteinPaint code that is shared between server and client-side workspaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "esbuild src/*.ts --platform=node --outdir=src/ --format=esm && prettier --no-semi --use-tabs --write src/urljson.js src/joinUrl.js src/doc.js",
|
|
13
13
|
"prepack": "npm run build",
|
|
14
|
-
"
|
|
14
|
+
"pretest": "mkdir -p test && node emitImports > test/internals-test.ts",
|
|
15
|
+
"test": "tsx test/internals-test.ts",
|
|
16
|
+
"test-x": "ls src/test/*.spec* | xargs -I % bash -c '{ tsx %; sleep 0.001; }'"
|
|
15
17
|
},
|
|
16
18
|
"author": "",
|
|
17
19
|
"license": "ISC",
|
package/src/common.js
CHANGED
|
@@ -11,6 +11,7 @@ exported functions
|
|
|
11
11
|
import { rgb } from 'd3-color'
|
|
12
12
|
import * as d3scale from 'd3-scale'
|
|
13
13
|
import * as d3 from 'd3'
|
|
14
|
+
import { getWrappedTvslst } from './filter.js'
|
|
14
15
|
|
|
15
16
|
// moved from `#shared/terms` to here, so that this can be passed as
|
|
16
17
|
// part of 'common' argument to exported dataset js function, at server runtime
|
|
@@ -1153,10 +1154,11 @@ export const CNVClasses = Object.values(mclass)
|
|
|
1153
1154
|
.filter(m => m.dt == dtcnv)
|
|
1154
1155
|
.map(m => m.key)
|
|
1155
1156
|
|
|
1156
|
-
//
|
|
1157
|
-
|
|
1157
|
+
// dt terms used for filtering variants for geneVariant term
|
|
1158
|
+
const _dtTerms = [
|
|
1158
1159
|
{
|
|
1159
1160
|
id: 'snvindel',
|
|
1161
|
+
query: 'snvindel',
|
|
1160
1162
|
name: dt2label[dtsnvindel],
|
|
1161
1163
|
parent_id: null,
|
|
1162
1164
|
isleaf: true,
|
|
@@ -1168,6 +1170,7 @@ export const dtTerms = [
|
|
|
1168
1170
|
},
|
|
1169
1171
|
{
|
|
1170
1172
|
id: 'cnv',
|
|
1173
|
+
query: 'cnv',
|
|
1171
1174
|
name: dt2label[dtcnv],
|
|
1172
1175
|
parent_id: null,
|
|
1173
1176
|
isleaf: true,
|
|
@@ -1177,6 +1180,7 @@ export const dtTerms = [
|
|
|
1177
1180
|
},
|
|
1178
1181
|
{
|
|
1179
1182
|
id: 'fusion',
|
|
1183
|
+
query: 'svfusion',
|
|
1180
1184
|
name: dt2label[dtfusionrna],
|
|
1181
1185
|
parent_id: null,
|
|
1182
1186
|
isleaf: true,
|
|
@@ -1186,6 +1190,7 @@ export const dtTerms = [
|
|
|
1186
1190
|
},
|
|
1187
1191
|
{
|
|
1188
1192
|
id: 'sv',
|
|
1193
|
+
query: 'svfusion',
|
|
1189
1194
|
name: dt2label[dtsv],
|
|
1190
1195
|
parent_id: null,
|
|
1191
1196
|
isleaf: true,
|
|
@@ -1194,156 +1199,96 @@ export const dtTerms = [
|
|
|
1194
1199
|
values: Object.fromEntries([mclasssv, 'WT'].map(key => [key, { label: mclass[key].label }]))
|
|
1195
1200
|
}
|
|
1196
1201
|
]
|
|
1202
|
+
// add origin annotations to dt terms
|
|
1203
|
+
export const dtTerms = []
|
|
1204
|
+
for (const _dtTerm of _dtTerms) {
|
|
1205
|
+
const dtTerm = structuredClone(_dtTerm)
|
|
1206
|
+
dtTerm.name_noOrigin = dtTerm.name // for labeling groups in groupsetting
|
|
1207
|
+
dtTerms.push(dtTerm) // no origin
|
|
1208
|
+
for (const origin of ['somatic', 'germline']) {
|
|
1209
|
+
// add origins
|
|
1210
|
+
const addOrigin = {
|
|
1211
|
+
id: `${dtTerm.id}_${origin}`,
|
|
1212
|
+
name: `${dtTerm.name} (${origin})`,
|
|
1213
|
+
origin
|
|
1214
|
+
}
|
|
1215
|
+
dtTerms.push(Object.assign({}, dtTerm, addOrigin))
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
const dtFilter = {
|
|
1220
|
+
opts: { joinWith: ['and', 'or'] },
|
|
1221
|
+
terms: dtTerms
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
const groupsetLst = []
|
|
1225
|
+
for (const term of dtTerms) {
|
|
1226
|
+
// wildtype filter
|
|
1227
|
+
const WTfilter = structuredClone(dtFilter)
|
|
1228
|
+
WTfilter.group = 2
|
|
1229
|
+
const WT = 'WT'
|
|
1230
|
+
const WTvalue = { key: WT, label: term.values[WT].label, value: WT }
|
|
1231
|
+
const WTtvs = { type: 'tvs', tvs: { term, values: [WTvalue] } }
|
|
1232
|
+
WTfilter.active = getWrappedTvslst([WTtvs])
|
|
1233
|
+
let WTname = 'Wildtype'
|
|
1234
|
+
// mutated filter
|
|
1235
|
+
const MUTfilter = structuredClone(dtFilter)
|
|
1236
|
+
MUTfilter.group = 1
|
|
1237
|
+
const classes = Object.keys(term.values)
|
|
1238
|
+
if (classes.length < 2) throw 'should have at least 2 classes'
|
|
1239
|
+
let MUTtvs, MUTname
|
|
1240
|
+
if (classes.length == 2) {
|
|
1241
|
+
// only 2 classes
|
|
1242
|
+
// mutant filter will filter for the mutant class
|
|
1243
|
+
const MUT = classes.find(c => c != WT)
|
|
1244
|
+
const MUTvalue = { key: MUT, label: term.values[MUT].label, value: MUT }
|
|
1245
|
+
MUTtvs = { type: 'tvs', tvs: { term, values: [MUTvalue] } }
|
|
1246
|
+
MUTname = term.values[MUT].label
|
|
1247
|
+
} else {
|
|
1248
|
+
// more than 2 classes
|
|
1249
|
+
// mutant filter will filter for all non-wildtype classes
|
|
1250
|
+
MUTtvs = { type: 'tvs', tvs: { term, values: [WTvalue], isnot: true } }
|
|
1251
|
+
MUTname = term.name_noOrigin
|
|
1252
|
+
}
|
|
1253
|
+
MUTfilter.active = getWrappedTvslst([MUTtvs])
|
|
1254
|
+
// excluded filter
|
|
1255
|
+
const EXCLUDEfilter = structuredClone(dtFilter)
|
|
1256
|
+
EXCLUDEfilter.group = 0
|
|
1257
|
+
EXCLUDEfilter.active = getWrappedTvslst()
|
|
1258
|
+
// assign filters to groups
|
|
1259
|
+
let GRPSETname = `${MUTname} vs. Wildtype`
|
|
1260
|
+
if (term.origin) GRPSETname += ` (${term.origin})`
|
|
1261
|
+
const WTgroup = {
|
|
1262
|
+
name: WTname,
|
|
1263
|
+
type: 'filter',
|
|
1264
|
+
uncomputable: false,
|
|
1265
|
+
filter: WTfilter
|
|
1266
|
+
}
|
|
1267
|
+
const MUTgroup = {
|
|
1268
|
+
name: MUTname,
|
|
1269
|
+
type: 'filter',
|
|
1270
|
+
uncomputable: false,
|
|
1271
|
+
filter: MUTfilter
|
|
1272
|
+
}
|
|
1273
|
+
const EXCLUDEgroup = {
|
|
1274
|
+
name: 'Excluded categories',
|
|
1275
|
+
type: 'filter',
|
|
1276
|
+
uncomputable: true,
|
|
1277
|
+
filter: EXCLUDEfilter
|
|
1278
|
+
}
|
|
1279
|
+
// assign groups to groupset
|
|
1280
|
+
const groupset = {
|
|
1281
|
+
name: GRPSETname,
|
|
1282
|
+
groups: [EXCLUDEgroup, MUTgroup, WTgroup],
|
|
1283
|
+
id: term.id
|
|
1284
|
+
}
|
|
1285
|
+
groupsetLst.push(groupset)
|
|
1286
|
+
}
|
|
1197
1287
|
|
|
1198
|
-
/*
|
|
1199
|
-
Term groupsetting used for geneVariant term
|
|
1200
|
-
NOTE: for each groupsetting, groups[] is ordered by priority
|
|
1201
|
-
for example: in the 'Protein-changing vs. rest' groupsetting, the
|
|
1202
|
-
'Protein-changing' group is listed first in groups[] so that samples
|
|
1203
|
-
that have both missense and silent mutations are classified in the
|
|
1204
|
-
'Protein-changing' group
|
|
1205
|
-
*/
|
|
1206
1288
|
export const geneVariantTermGroupsetting = {
|
|
1207
1289
|
disabled: false, // as const, // TODO: may need to add is when converting common.js to .ts
|
|
1208
1290
|
type: 'custom',
|
|
1209
|
-
lst:
|
|
1210
|
-
{
|
|
1211
|
-
// SNV/indel groupsetting
|
|
1212
|
-
name: 'Mutated vs. wildtype',
|
|
1213
|
-
groups: [
|
|
1214
|
-
{
|
|
1215
|
-
type: 'values', // as const, // TODO: may need to add is when converting common.js to .ts
|
|
1216
|
-
name: 'Mutated',
|
|
1217
|
-
values: mutationClasses
|
|
1218
|
-
.filter(key => key != 'WT' && key != 'Blank')
|
|
1219
|
-
.map(key => {
|
|
1220
|
-
return { key, label: mclass[key].label }
|
|
1221
|
-
})
|
|
1222
|
-
},
|
|
1223
|
-
{
|
|
1224
|
-
type: 'values',
|
|
1225
|
-
name: 'Wildtype',
|
|
1226
|
-
values: [{ key: 'WT', label: 'Wildtype' }]
|
|
1227
|
-
},
|
|
1228
|
-
{
|
|
1229
|
-
type: 'values',
|
|
1230
|
-
name: 'Not tested',
|
|
1231
|
-
values: [{ key: 'Blank', label: 'Not tested' }],
|
|
1232
|
-
uncomputable: true
|
|
1233
|
-
}
|
|
1234
|
-
]
|
|
1235
|
-
},
|
|
1236
|
-
// SNV/indel groupsetting
|
|
1237
|
-
{
|
|
1238
|
-
name: 'Protein-changing vs. rest',
|
|
1239
|
-
groups: [
|
|
1240
|
-
{
|
|
1241
|
-
type: 'values',
|
|
1242
|
-
name: 'Protein-changing',
|
|
1243
|
-
values: proteinChangingMutations.map(key => {
|
|
1244
|
-
return { key, label: mclass[key].label }
|
|
1245
|
-
})
|
|
1246
|
-
},
|
|
1247
|
-
{
|
|
1248
|
-
type: 'values',
|
|
1249
|
-
name: 'Rest',
|
|
1250
|
-
values: Object.keys(mclass)
|
|
1251
|
-
.filter(key => !proteinChangingMutations.includes(key) && key != 'Blank')
|
|
1252
|
-
.map(key => {
|
|
1253
|
-
return { key, label: mclass[key].label }
|
|
1254
|
-
})
|
|
1255
|
-
},
|
|
1256
|
-
{
|
|
1257
|
-
type: 'values',
|
|
1258
|
-
name: 'Not tested',
|
|
1259
|
-
values: [{ key: 'Blank', label: 'Not tested' }],
|
|
1260
|
-
uncomputable: true
|
|
1261
|
-
}
|
|
1262
|
-
]
|
|
1263
|
-
},
|
|
1264
|
-
// SNV/indel groupsetting
|
|
1265
|
-
{
|
|
1266
|
-
name: 'Truncating vs. rest',
|
|
1267
|
-
groups: [
|
|
1268
|
-
{
|
|
1269
|
-
type: 'values',
|
|
1270
|
-
name: 'Truncating',
|
|
1271
|
-
values: truncatingMutations.map(key => {
|
|
1272
|
-
return { key, label: mclass[key].label }
|
|
1273
|
-
})
|
|
1274
|
-
},
|
|
1275
|
-
{
|
|
1276
|
-
type: 'values',
|
|
1277
|
-
name: 'Rest',
|
|
1278
|
-
values: Object.keys(mclass)
|
|
1279
|
-
.filter(key => !truncatingMutations.includes(key) && key != 'Blank')
|
|
1280
|
-
.map(key => {
|
|
1281
|
-
return { key, label: mclass[key].label }
|
|
1282
|
-
})
|
|
1283
|
-
},
|
|
1284
|
-
{
|
|
1285
|
-
type: 'values',
|
|
1286
|
-
name: 'Not tested',
|
|
1287
|
-
values: [{ key: 'Blank', label: 'Not tested' }],
|
|
1288
|
-
uncomputable: true
|
|
1289
|
-
}
|
|
1290
|
-
]
|
|
1291
|
-
},
|
|
1292
|
-
// CNV groupsetting
|
|
1293
|
-
{
|
|
1294
|
-
name: 'Gain vs. Loss vs. LOH vs. Wildtype',
|
|
1295
|
-
groups: [
|
|
1296
|
-
{
|
|
1297
|
-
type: 'values',
|
|
1298
|
-
name: 'Copy number gain',
|
|
1299
|
-
values: [{ key: 'CNV_amp', label: mclass['CNV_amp'].label }]
|
|
1300
|
-
},
|
|
1301
|
-
{
|
|
1302
|
-
type: 'values',
|
|
1303
|
-
name: 'Copy number loss',
|
|
1304
|
-
values: [{ key: 'CNV_loss', label: mclass['CNV_loss'].label }]
|
|
1305
|
-
},
|
|
1306
|
-
{
|
|
1307
|
-
type: 'values',
|
|
1308
|
-
name: 'LOH',
|
|
1309
|
-
values: [{ key: 'CNV_loh', label: mclass['CNV_loh'].label }]
|
|
1310
|
-
},
|
|
1311
|
-
{
|
|
1312
|
-
type: 'values',
|
|
1313
|
-
name: 'Wildtype',
|
|
1314
|
-
values: [{ key: 'WT', label: 'Wildtype' }]
|
|
1315
|
-
},
|
|
1316
|
-
{
|
|
1317
|
-
type: 'values',
|
|
1318
|
-
name: 'Not tested',
|
|
1319
|
-
values: [{ key: 'Blank', label: 'Not tested' }],
|
|
1320
|
-
uncomputable: true
|
|
1321
|
-
}
|
|
1322
|
-
]
|
|
1323
|
-
},
|
|
1324
|
-
// SV fusion groupsetting
|
|
1325
|
-
{
|
|
1326
|
-
name: 'Fusion vs. Wildtype',
|
|
1327
|
-
groups: [
|
|
1328
|
-
{
|
|
1329
|
-
type: 'values',
|
|
1330
|
-
name: 'Fusion transcript',
|
|
1331
|
-
values: [{ key: 'Fuserna', label: mclass['Fuserna'].label }]
|
|
1332
|
-
},
|
|
1333
|
-
{
|
|
1334
|
-
type: 'values',
|
|
1335
|
-
name: 'Wildtype',
|
|
1336
|
-
values: [{ key: 'WT', label: 'Wildtype' }]
|
|
1337
|
-
},
|
|
1338
|
-
{
|
|
1339
|
-
type: 'values',
|
|
1340
|
-
name: 'Not tested',
|
|
1341
|
-
values: [{ key: 'Blank', label: 'Not tested' }],
|
|
1342
|
-
uncomputable: true
|
|
1343
|
-
}
|
|
1344
|
-
]
|
|
1345
|
-
}
|
|
1346
|
-
]
|
|
1291
|
+
lst: groupsetLst
|
|
1347
1292
|
}
|
|
1348
1293
|
|
|
1349
1294
|
export const colorScaleMap = {
|
package/src/filter.js
CHANGED
|
@@ -242,3 +242,14 @@ export function filterJoin(lst) {
|
|
|
242
242
|
}
|
|
243
243
|
return f
|
|
244
244
|
}
|
|
245
|
+
|
|
246
|
+
export function getWrappedTvslst(lst = [], join = '', $id = null) {
|
|
247
|
+
const filter = {
|
|
248
|
+
type: 'tvslst',
|
|
249
|
+
in: true,
|
|
250
|
+
join,
|
|
251
|
+
lst
|
|
252
|
+
}
|
|
253
|
+
if ($id !== null && filter.$id !== undefined) filter.$id = $id
|
|
254
|
+
return filter
|
|
255
|
+
}
|
package/src/termdb.usecase.js
CHANGED
package/src/terms.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { dtgeneexpression, dtmetaboliteintensity, TermTypeGroups } from './common.js'
|
|
2
|
+
import { roundValueAuto } from './roundValue.js'
|
|
2
3
|
|
|
3
4
|
// moved TermTypeGroups to `server/src/common.js`, so now has to re-export
|
|
4
5
|
export { TermTypeGroups } from './common.js'
|
|
@@ -37,7 +38,8 @@ export const TermTypes = {
|
|
|
37
38
|
METABOLITE_INTENSITY: 'metaboliteIntensity',
|
|
38
39
|
SINGLECELL_GENE_EXPRESSION: 'singleCellGeneExpression',
|
|
39
40
|
SINGLECELL_CELLTYPE: 'singleCellCellType',
|
|
40
|
-
MULTIVALUE: 'multivalue'
|
|
41
|
+
MULTIVALUE: 'multivalue',
|
|
42
|
+
DATE: 'date'
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
export const NUMERIC_DICTIONARY_TERM = 'numericDictTerm'
|
|
@@ -80,7 +82,8 @@ export const numericTypes = new Set([
|
|
|
80
82
|
TermTypes.FLOAT,
|
|
81
83
|
TermTypes.GENE_EXPRESSION,
|
|
82
84
|
TermTypes.METABOLITE_INTENSITY,
|
|
83
|
-
TermTypes.SINGLECELL_GENE_EXPRESSION
|
|
85
|
+
TermTypes.SINGLECELL_GENE_EXPRESSION,
|
|
86
|
+
TermTypes.DATE
|
|
84
87
|
])
|
|
85
88
|
|
|
86
89
|
const categoricalTypes = new Set([TermTypes.CATEGORICAL, TermTypes.SNP])
|
|
@@ -206,3 +209,37 @@ const typeMap = {
|
|
|
206
209
|
export function termType2label(type) {
|
|
207
210
|
return typeMap[type] || 'Unknown term type'
|
|
208
211
|
}
|
|
212
|
+
|
|
213
|
+
/*
|
|
214
|
+
Value is a decimal year.
|
|
215
|
+
A decimal year is a way of expressing a date or time period as a year with a decimal part, where the decimal portion
|
|
216
|
+
represents the fraction of the year that has elapsed.
|
|
217
|
+
Example:
|
|
218
|
+
2025.0 represents the beginning of the year 2025.
|
|
219
|
+
2025.5 represents the middle of the year 2025.
|
|
220
|
+
*/
|
|
221
|
+
export function getDateStrFromNumber(value) {
|
|
222
|
+
const year = Math.floor(value)
|
|
223
|
+
const time = (value - year) * 365 * 24 * 60 * 60 * 1000 // convert to milliseconds
|
|
224
|
+
const january1st = new Date(year, 0, 0)
|
|
225
|
+
const date = new Date(january1st.getTime() + time)
|
|
226
|
+
|
|
227
|
+
//Omit day to deidentify the patients
|
|
228
|
+
return date.toLocaleDateString('en-US', {
|
|
229
|
+
year: 'numeric',
|
|
230
|
+
month: 'long'
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
//The value returned is a decimal year
|
|
235
|
+
//A decimal year is a way of expressing a date or time period as a year with a decimal part, where the decimal portion
|
|
236
|
+
//represents the fraction of the year that has elapsed.
|
|
237
|
+
export function getNumberFromDateStr(str) {
|
|
238
|
+
const date = new Date(str)
|
|
239
|
+
const year = date.getFullYear()
|
|
240
|
+
const january1st = new Date(year, 0, 0)
|
|
241
|
+
const diffTime = date - january1st
|
|
242
|
+
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24))
|
|
243
|
+
const decimal = roundValueAuto(diffDays / 365)
|
|
244
|
+
return year + decimal
|
|
245
|
+
}
|
package/src/time.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
function formatElapsedTime(ms) {
|
|
2
|
+
if (typeof ms !== "number") {
|
|
3
|
+
return "Invalid time: not a number";
|
|
4
|
+
}
|
|
5
|
+
if (isNaN(ms)) {
|
|
6
|
+
return "Invalid time: NaN";
|
|
7
|
+
}
|
|
8
|
+
if (!isFinite(ms)) {
|
|
9
|
+
return ms > 0 ? "Infinite time" : "-Infinite time";
|
|
10
|
+
}
|
|
11
|
+
const absMs = Math.abs(ms);
|
|
12
|
+
const sign = ms < 0 ? "-" : "";
|
|
13
|
+
if (absMs < 1e3) {
|
|
14
|
+
return `${sign}${absMs}ms`;
|
|
15
|
+
} else if (absMs < 6e4) {
|
|
16
|
+
const seconds = (absMs / 1e3).toFixed(2);
|
|
17
|
+
return `${sign}${seconds}s`;
|
|
18
|
+
} else {
|
|
19
|
+
const minutes = Math.floor(absMs / 6e4);
|
|
20
|
+
const seconds = (absMs % 6e4 / 1e3).toFixed(2);
|
|
21
|
+
return `${sign}${minutes}m ${seconds}s`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
formatElapsedTime
|
|
26
|
+
};
|