@sjcrh/proteinpaint-shared 2.113.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 +132 -146
- package/src/filter.js +11 -0
- package/src/termdb.usecase.js +6 -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,156 +1154,141 @@ export const CNVClasses = Object.values(mclass)
|
|
|
1153
1154
|
.filter(m => m.dt == dtcnv)
|
|
1154
1155
|
.map(m => m.key)
|
|
1155
1156
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
'
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1157
|
+
// dt terms used for filtering variants for geneVariant term
|
|
1158
|
+
const _dtTerms = [
|
|
1159
|
+
{
|
|
1160
|
+
id: 'snvindel',
|
|
1161
|
+
query: 'snvindel',
|
|
1162
|
+
name: dt2label[dtsnvindel],
|
|
1163
|
+
parent_id: null,
|
|
1164
|
+
isleaf: true,
|
|
1165
|
+
type: 'dtsnvindel',
|
|
1166
|
+
dt: dtsnvindel,
|
|
1167
|
+
values: Object.fromEntries(
|
|
1168
|
+
mutationClasses.filter(key => key != 'Blank').map(key => [key, { label: mclass[key].label }])
|
|
1169
|
+
)
|
|
1170
|
+
},
|
|
1171
|
+
{
|
|
1172
|
+
id: 'cnv',
|
|
1173
|
+
query: 'cnv',
|
|
1174
|
+
name: dt2label[dtcnv],
|
|
1175
|
+
parent_id: null,
|
|
1176
|
+
isleaf: true,
|
|
1177
|
+
type: 'dtcnv',
|
|
1178
|
+
dt: dtcnv,
|
|
1179
|
+
values: Object.fromEntries([...CNVClasses, 'WT'].map(key => [key, { label: mclass[key].label }]))
|
|
1180
|
+
},
|
|
1181
|
+
{
|
|
1182
|
+
id: 'fusion',
|
|
1183
|
+
query: 'svfusion',
|
|
1184
|
+
name: dt2label[dtfusionrna],
|
|
1185
|
+
parent_id: null,
|
|
1186
|
+
isleaf: true,
|
|
1187
|
+
type: 'dtfusion',
|
|
1188
|
+
dt: dtfusionrna,
|
|
1189
|
+
values: Object.fromEntries([mclassfusionrna, 'WT'].map(key => [key, { label: mclass[key].label }]))
|
|
1190
|
+
},
|
|
1191
|
+
{
|
|
1192
|
+
id: 'sv',
|
|
1193
|
+
query: 'svfusion',
|
|
1194
|
+
name: dt2label[dtsv],
|
|
1195
|
+
parent_id: null,
|
|
1196
|
+
isleaf: true,
|
|
1197
|
+
type: 'dtsv',
|
|
1198
|
+
dt: dtsv,
|
|
1199
|
+
values: Object.fromEntries([mclasssv, 'WT'].map(key => [key, { label: mclass[key].label }]))
|
|
1200
|
+
}
|
|
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
|
+
}
|
|
1164
1287
|
|
|
1165
1288
|
export const geneVariantTermGroupsetting = {
|
|
1166
1289
|
disabled: false, // as const, // TODO: may need to add is when converting common.js to .ts
|
|
1167
1290
|
type: 'custom',
|
|
1168
|
-
lst:
|
|
1169
|
-
{
|
|
1170
|
-
// SNV/indel groupsetting
|
|
1171
|
-
name: 'Mutated vs. wildtype',
|
|
1172
|
-
groups: [
|
|
1173
|
-
{
|
|
1174
|
-
type: 'values', // as const, // TODO: may need to add is when converting common.js to .ts
|
|
1175
|
-
name: 'Mutated',
|
|
1176
|
-
values: mutationClasses
|
|
1177
|
-
.filter(key => key != 'WT' && key != 'Blank')
|
|
1178
|
-
.map(key => {
|
|
1179
|
-
return { key, label: mclass[key].label }
|
|
1180
|
-
})
|
|
1181
|
-
},
|
|
1182
|
-
{
|
|
1183
|
-
type: 'values',
|
|
1184
|
-
name: 'Wildtype',
|
|
1185
|
-
values: [{ key: 'WT', label: 'Wildtype' }]
|
|
1186
|
-
},
|
|
1187
|
-
{
|
|
1188
|
-
type: 'values',
|
|
1189
|
-
name: 'Not tested',
|
|
1190
|
-
values: [{ key: 'Blank', label: 'Not tested' }],
|
|
1191
|
-
uncomputable: true
|
|
1192
|
-
}
|
|
1193
|
-
]
|
|
1194
|
-
},
|
|
1195
|
-
// SNV/indel groupsetting
|
|
1196
|
-
{
|
|
1197
|
-
name: 'Protein-changing vs. rest',
|
|
1198
|
-
groups: [
|
|
1199
|
-
{
|
|
1200
|
-
type: 'values',
|
|
1201
|
-
name: 'Protein-changing',
|
|
1202
|
-
values: proteinChangingMutations.map(key => {
|
|
1203
|
-
return { key, label: mclass[key].label }
|
|
1204
|
-
})
|
|
1205
|
-
},
|
|
1206
|
-
{
|
|
1207
|
-
type: 'values',
|
|
1208
|
-
name: 'Rest',
|
|
1209
|
-
values: Object.keys(mclass)
|
|
1210
|
-
.filter(key => !proteinChangingMutations.includes(key) && key != 'Blank')
|
|
1211
|
-
.map(key => {
|
|
1212
|
-
return { key, label: mclass[key].label }
|
|
1213
|
-
})
|
|
1214
|
-
},
|
|
1215
|
-
{
|
|
1216
|
-
type: 'values',
|
|
1217
|
-
name: 'Not tested',
|
|
1218
|
-
values: [{ key: 'Blank', label: 'Not tested' }],
|
|
1219
|
-
uncomputable: true
|
|
1220
|
-
}
|
|
1221
|
-
]
|
|
1222
|
-
},
|
|
1223
|
-
// SNV/indel groupsetting
|
|
1224
|
-
{
|
|
1225
|
-
name: 'Truncating vs. rest',
|
|
1226
|
-
groups: [
|
|
1227
|
-
{
|
|
1228
|
-
type: 'values',
|
|
1229
|
-
name: 'Truncating',
|
|
1230
|
-
values: truncatingMutations.map(key => {
|
|
1231
|
-
return { key, label: mclass[key].label }
|
|
1232
|
-
})
|
|
1233
|
-
},
|
|
1234
|
-
{
|
|
1235
|
-
type: 'values',
|
|
1236
|
-
name: 'Rest',
|
|
1237
|
-
values: Object.keys(mclass)
|
|
1238
|
-
.filter(key => !truncatingMutations.includes(key) && key != 'Blank')
|
|
1239
|
-
.map(key => {
|
|
1240
|
-
return { key, label: mclass[key].label }
|
|
1241
|
-
})
|
|
1242
|
-
},
|
|
1243
|
-
{
|
|
1244
|
-
type: 'values',
|
|
1245
|
-
name: 'Not tested',
|
|
1246
|
-
values: [{ key: 'Blank', label: 'Not tested' }],
|
|
1247
|
-
uncomputable: true
|
|
1248
|
-
}
|
|
1249
|
-
]
|
|
1250
|
-
},
|
|
1251
|
-
// CNV groupsetting
|
|
1252
|
-
{
|
|
1253
|
-
name: 'Gain vs. Loss vs. LOH vs. Wildtype',
|
|
1254
|
-
groups: [
|
|
1255
|
-
{
|
|
1256
|
-
type: 'values',
|
|
1257
|
-
name: 'Copy number gain',
|
|
1258
|
-
values: [{ key: 'CNV_amp', label: mclass['CNV_amp'].label }]
|
|
1259
|
-
},
|
|
1260
|
-
{
|
|
1261
|
-
type: 'values',
|
|
1262
|
-
name: 'Copy number loss',
|
|
1263
|
-
values: [{ key: 'CNV_loss', label: mclass['CNV_loss'].label }]
|
|
1264
|
-
},
|
|
1265
|
-
{
|
|
1266
|
-
type: 'values',
|
|
1267
|
-
name: 'LOH',
|
|
1268
|
-
values: [{ key: 'CNV_loh', label: mclass['CNV_loh'].label }]
|
|
1269
|
-
},
|
|
1270
|
-
{
|
|
1271
|
-
type: 'values',
|
|
1272
|
-
name: 'Wildtype',
|
|
1273
|
-
values: [{ key: 'WT', label: 'Wildtype' }]
|
|
1274
|
-
},
|
|
1275
|
-
{
|
|
1276
|
-
type: 'values',
|
|
1277
|
-
name: 'Not tested',
|
|
1278
|
-
values: [{ key: 'Blank', label: 'Not tested' }],
|
|
1279
|
-
uncomputable: true
|
|
1280
|
-
}
|
|
1281
|
-
]
|
|
1282
|
-
},
|
|
1283
|
-
// SV fusion groupsetting
|
|
1284
|
-
{
|
|
1285
|
-
name: 'Fusion vs. Wildtype',
|
|
1286
|
-
groups: [
|
|
1287
|
-
{
|
|
1288
|
-
type: 'values',
|
|
1289
|
-
name: 'Fusion transcript',
|
|
1290
|
-
values: [{ key: 'Fuserna', label: mclass['Fuserna'].label }]
|
|
1291
|
-
},
|
|
1292
|
-
{
|
|
1293
|
-
type: 'values',
|
|
1294
|
-
name: 'Wildtype',
|
|
1295
|
-
values: [{ key: 'WT', label: 'Wildtype' }]
|
|
1296
|
-
},
|
|
1297
|
-
{
|
|
1298
|
-
type: 'values',
|
|
1299
|
-
name: 'Not tested',
|
|
1300
|
-
values: [{ key: 'Blank', label: 'Not tested' }],
|
|
1301
|
-
uncomputable: true
|
|
1302
|
-
}
|
|
1303
|
-
]
|
|
1304
|
-
}
|
|
1305
|
-
]
|
|
1291
|
+
lst: groupsetLst
|
|
1306
1292
|
}
|
|
1307
1293
|
|
|
1308
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
|
@@ -11,11 +11,17 @@ export const graphableTypes = new Set([
|
|
|
11
11
|
'geneVariant',
|
|
12
12
|
'samplelst',
|
|
13
13
|
'geneExpression',
|
|
14
|
+
'dtcnv',
|
|
15
|
+
'dtsnvindel',
|
|
16
|
+
'dtfusion',
|
|
17
|
+
'dtsv',
|
|
18
|
+
'date',
|
|
14
19
|
TermTypes.METABOLITE_INTENSITY,
|
|
15
20
|
TermTypes.SINGLECELL_GENE_EXPRESSION,
|
|
16
21
|
TermTypes.SINGLECELL_CELLTYPE,
|
|
17
22
|
TermTypes.SNP
|
|
18
23
|
])
|
|
24
|
+
|
|
19
25
|
/*
|
|
20
26
|
isUsableTerm() will
|
|
21
27
|
|
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
|
+
};
|