gramene-search 2.0.4 → 2.0.6
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/.claude/settings.local.json +4 -1
- package/.parcel-cache/83e7562660f7cc15-BundleGraph +0 -0
- package/.parcel-cache/d3a1b9507cb44047-AssetGraph +0 -0
- package/.parcel-cache/data.mdb +0 -0
- package/.parcel-cache/dc1da35000e13623-RequestGraph +0 -0
- package/.parcel-cache/lock.mdb +0 -0
- package/.parcel-cache/snapshot-dc1da35000e13623.txt +2 -2
- package/dist/index.css +174 -57
- package/dist/index.css.map +1 -1
- package/dist/index.js +710 -252
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/bundles/api.js +33 -26
- package/src/bundles/exporter.js +4 -1
- package/src/bundles/swaggerFields.js +18 -6
- package/src/bundles/views.js +12 -0
- package/src/components/exporter/expressionResolver.js +2 -2
- package/src/components/exprViz/ExprTable.js +257 -101
- package/src/components/exprViz/ExprVizView.js +151 -7
- package/src/components/exprViz/FieldsModal.js +45 -18
- package/src/components/exprViz/ParallelCoordsPlot.js +99 -6
- package/src/components/exprViz/styles.css +211 -56
- package/src/fieldCatalog.overlay.json +1 -1
package/dist/index.js
CHANGED
|
@@ -351,8 +351,16 @@ const $9d9aeaf9299e61a1$var$expressionStudies = (0, $gXNCa$reduxbundler.createAs
|
|
|
351
351
|
return fetch(`${store.selectGrameneAPI()}/experiments?rows=-1`).then((res)=>res.json()).then((res)=>(0, ($parcel$interopDefault($gXNCa$lodash))).groupBy(res, 'taxon_id'));
|
|
352
352
|
}
|
|
353
353
|
});
|
|
354
|
-
|
|
355
|
-
|
|
354
|
+
// Only fetch when a view that consumes expression studies is enabled.
|
|
355
|
+
const $9d9aeaf9299e61a1$var$EXPRESSION_VIEWS = [
|
|
356
|
+
'exprViz',
|
|
357
|
+
'expression',
|
|
358
|
+
'export'
|
|
359
|
+
];
|
|
360
|
+
$9d9aeaf9299e61a1$var$expressionStudies.reactExpressionStudies = (0, $gXNCa$reduxbundler.createSelector)('selectExpressionStudiesShouldUpdate', 'selectGrameneViewsOn', (shouldUpdate, viewsOn)=>{
|
|
361
|
+
if (!shouldUpdate) return;
|
|
362
|
+
if (!viewsOn || !$9d9aeaf9299e61a1$var$EXPRESSION_VIEWS.some((id)=>viewsOn.has(id))) return;
|
|
363
|
+
return {
|
|
356
364
|
actionCreator: 'doFetchExpressionStudies'
|
|
357
365
|
};
|
|
358
366
|
});
|
|
@@ -372,8 +380,10 @@ const $9d9aeaf9299e61a1$var$expressionSamples = (0, $gXNCa$reduxbundler.createAs
|
|
|
372
380
|
});
|
|
373
381
|
}
|
|
374
382
|
});
|
|
375
|
-
$9d9aeaf9299e61a1$var$expressionSamples.reactExpressionSamples = (0, $gXNCa$reduxbundler.createSelector)('selectExpressionSamplesShouldUpdate', (shouldUpdate)=>{
|
|
376
|
-
if (shouldUpdate) return
|
|
383
|
+
$9d9aeaf9299e61a1$var$expressionSamples.reactExpressionSamples = (0, $gXNCa$reduxbundler.createSelector)('selectExpressionSamplesShouldUpdate', 'selectGrameneViewsOn', (shouldUpdate, viewsOn)=>{
|
|
384
|
+
if (!shouldUpdate) return;
|
|
385
|
+
if (!viewsOn || !$9d9aeaf9299e61a1$var$EXPRESSION_VIEWS.some((id)=>viewsOn.has(id))) return;
|
|
386
|
+
return {
|
|
377
387
|
actionCreator: 'doFetchExpressionSamples'
|
|
378
388
|
};
|
|
379
389
|
});
|
|
@@ -385,8 +395,12 @@ const $9d9aeaf9299e61a1$var$curatedGenes = (0, $gXNCa$reduxbundler.createAsyncRe
|
|
|
385
395
|
return fetch(`https://devdata.gramene.org/curation/curations?rows=0&minFlagged=0&since=12-12-2029`).then((res)=>res.json()).then((curation)=>(0, ($parcel$interopDefault($gXNCa$lodash))).keyBy(curation.genes, 'gene_id'));
|
|
386
396
|
}
|
|
387
397
|
});
|
|
388
|
-
|
|
389
|
-
|
|
398
|
+
// Curated annotations are consumed by GeneList rows and Homology details,
|
|
399
|
+
// both inside the gene-list view.
|
|
400
|
+
$9d9aeaf9299e61a1$var$curatedGenes.reactCuratedGenes = (0, $gXNCa$reduxbundler.createSelector)('selectCuratedGenesShouldUpdate', 'selectGrameneViewsOn', (shouldUpdate, viewsOn)=>{
|
|
401
|
+
if (!shouldUpdate) return;
|
|
402
|
+
if (!viewsOn || !viewsOn.has('list')) return;
|
|
403
|
+
return {
|
|
390
404
|
actionCreator: 'doFetchCuratedGenes'
|
|
391
405
|
};
|
|
392
406
|
});
|
|
@@ -403,8 +417,11 @@ const $9d9aeaf9299e61a1$var$grameneGermplasm = (0, $gXNCa$reduxbundler.createAsy
|
|
|
403
417
|
});
|
|
404
418
|
}
|
|
405
419
|
});
|
|
406
|
-
|
|
407
|
-
|
|
420
|
+
// Germplasm metadata is consumed by the VEP detail panel inside the gene list.
|
|
421
|
+
$9d9aeaf9299e61a1$var$grameneGermplasm.reactGrameneGermplasm = (0, $gXNCa$reduxbundler.createSelector)('selectGrameneGermplasmShouldUpdate', 'selectGrameneViewsOn', (shouldUpdate, viewsOn)=>{
|
|
422
|
+
if (!shouldUpdate) return;
|
|
423
|
+
if (!viewsOn || !viewsOn.has('list')) return;
|
|
424
|
+
return {
|
|
408
425
|
actionCreator: 'doFetchGrameneGermplasm'
|
|
409
426
|
};
|
|
410
427
|
});
|
|
@@ -522,13 +539,13 @@ const $9d9aeaf9299e61a1$var$grameneGeneAttribs = (0, $gXNCa$reduxbundler.createA
|
|
|
522
539
|
return fetch(`${store.selectGrameneAPI()}/geneAttributes`).then((res)=>$9d9aeaf9299e61a1$var$geneAttribs);
|
|
523
540
|
}
|
|
524
541
|
});
|
|
525
|
-
$9d9aeaf9299e61a1$var$grameneGeneAttribs.reactGrameneGeneAttribs = (0, $gXNCa$reduxbundler.createSelector)('selectGrameneGeneAttribsShouldUpdate', 'selectGrameneFiltersStatus', '
|
|
526
|
-
if (shouldUpdate
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
}
|
|
542
|
+
$9d9aeaf9299e61a1$var$grameneGeneAttribs.reactGrameneGeneAttribs = (0, $gXNCa$reduxbundler.createSelector)('selectGrameneGeneAttribsShouldUpdate', 'selectGrameneFiltersStatus', 'selectGrameneViewsOn', (shouldUpdate, status, viewsOn)=>{
|
|
543
|
+
if (!shouldUpdate) return;
|
|
544
|
+
if (status !== 'finished' && status !== 'ready') return;
|
|
545
|
+
if (!viewsOn || !viewsOn.has('attribs')) return;
|
|
546
|
+
return {
|
|
547
|
+
actionCreator: 'doFetchGrameneGeneAttribs'
|
|
548
|
+
};
|
|
532
549
|
});
|
|
533
550
|
const $9d9aeaf9299e61a1$var$grameneSearch = (0, $gXNCa$reduxbundler.createAsyncResourceBundle)({
|
|
534
551
|
name: 'grameneSearch',
|
|
@@ -538,8 +555,8 @@ const $9d9aeaf9299e61a1$var$grameneSearch = (0, $gXNCa$reduxbundler.createAsyncR
|
|
|
538
555
|
const offset = store.selectGrameneSearchOffset();
|
|
539
556
|
const rows = store.selectGrameneSearchRows();
|
|
540
557
|
const g = store.selectGrameneGenomes();
|
|
541
|
-
const m = store.selectGrameneMaps();
|
|
542
|
-
const taxa = Object.keys(g.active).filter((tid)
|
|
558
|
+
const m = store.selectGrameneMaps() || {};
|
|
559
|
+
const taxa = Object.keys(g && g.active || {}).filter((tid)=>m[tid] && !m[tid].hidden);
|
|
543
560
|
let fq = '';
|
|
544
561
|
if (taxa.length) {
|
|
545
562
|
console.log('search add a fq for ', taxa);
|
|
@@ -1568,6 +1585,15 @@ const $24971af0a229e0e3$var$grameneViews = {
|
|
|
1568
1585
|
...raw,
|
|
1569
1586
|
options: options
|
|
1570
1587
|
};
|
|
1588
|
+
}),
|
|
1589
|
+
// Set of view ids whose `show` is currently 'on'. Used by data bundles to
|
|
1590
|
+
// gate their auto-fetch reactors so we don't pay the cost of loading data
|
|
1591
|
+
// for views the user has turned off.
|
|
1592
|
+
selectGrameneViewsOn: (0, $gXNCa$reduxbundler.createSelector)('selectGrameneViews', (views)=>{
|
|
1593
|
+
const ids = new Set();
|
|
1594
|
+
const opts = views && views.options || [];
|
|
1595
|
+
for (const v of opts)if (v && v.show === 'on') ids.add(v.id);
|
|
1596
|
+
return ids;
|
|
1571
1597
|
})
|
|
1572
1598
|
};
|
|
1573
1599
|
var $24971af0a229e0e3$export$2e2bcd8739ae039 = $24971af0a229e0e3$var$grameneViews;
|
|
@@ -2050,7 +2076,7 @@ var $0d54502f6cafe273$export$2e2bcd8739ae039 = $0d54502f6cafe273$var$grameneGeno
|
|
|
2050
2076
|
|
|
2051
2077
|
|
|
2052
2078
|
var $65709bd8598fce20$exports = {};
|
|
2053
|
-
$65709bd8598fce20$exports = JSON.parse('{"groups":{"core":{"label":"Core identifiers","order":0},"location":{"label":"Genomic location","order":1},"structure":{"label":"Gene structure","order":2},"homology":{"label":"Homology","order":3},"expression":{"label":"Gene expression","order":4},"differential":{"label":"Differential expression","order":5},"hierarchical":{"label":"Hierarchical annotations","order":6},"pathways":{"label":"Pathways","order":7},"GO":{"label":"Gene Ontology","order":7},"PO":{"label":"Plant Ontology","order":8},"lof":{"label":"Loss of function alleles","order":9},"MAKER":{"label":"MAKER transcript metrics","order":10},"xrefs":{"label":"External references","order":11},"other":{"label":"Other","order":99}},"patterns":[{"id":"homology","match":"^homology__(.+)$","group":"homology","multiValued":true,"labelTemplate":"Homology: $1"},{"id":"pathways","match":"^pathways__(.+)$","group":"pathways","multiValued":true,"labelTemplate":"Pathway: $1"},{"id":"maker","match":"^MAKER__(.+)__attr_([a-z])$","group":"MAKER","labelTemplate":"MAKER: $1"},{"id":"xrefs","match":"^(.+)__xrefs$","group":"xrefs","multiValued":true,"labelTemplate":"$1 (xrefs)"},{"id":"expr","match":"^(E[-_][A-Za-z0-9_-]+?)_g(\\\\d+)__expr$","group":"expression","expression":true,"labelTemplate":"$1 \xb7 g$2"},{"id":"diffexpr","match":"^(E[-_][A-Za-z0-9_-]+?)_g(\\\\d+)_g(\\\\d+)_(pval|
|
|
2079
|
+
$65709bd8598fce20$exports = JSON.parse('{"groups":{"core":{"label":"Core identifiers","order":0},"location":{"label":"Genomic location","order":1},"structure":{"label":"Gene structure","order":2},"homology":{"label":"Homology","order":3},"expression":{"label":"Gene expression","order":4},"differential":{"label":"Differential expression","order":5},"hierarchical":{"label":"Hierarchical annotations","order":6},"pathways":{"label":"Pathways","order":7},"GO":{"label":"Gene Ontology","order":7},"PO":{"label":"Plant Ontology","order":8},"lof":{"label":"Loss of function alleles","order":9},"MAKER":{"label":"MAKER transcript metrics","order":10},"xrefs":{"label":"External references","order":11},"other":{"label":"Other","order":99}},"patterns":[{"id":"homology","match":"^homology__(.+)$","group":"homology","multiValued":true,"labelTemplate":"Homology: $1"},{"id":"pathways","match":"^pathways__(.+)$","group":"pathways","multiValued":true,"labelTemplate":"Pathway: $1"},{"id":"maker","match":"^MAKER__(.+)__attr_([a-z])$","group":"MAKER","labelTemplate":"MAKER: $1"},{"id":"xrefs","match":"^(.+)__xrefs$","group":"xrefs","multiValued":true,"labelTemplate":"$1 (xrefs)"},{"id":"expr","match":"^(E[-_][A-Za-z0-9_-]+?)_g(\\\\d+)__expr$","group":"expression","expression":true,"labelTemplate":"$1 \xb7 g$2"},{"id":"diffexpr","match":"^(E[-_][A-Za-z0-9_-]+?)_g(\\\\d+)_g(\\\\d+)_(pval|l2fc)_attr_([a-z])$","group":"differential","diffExpression":true,"labelTemplate":"$1 \xb7 g$2 vs g$3 \xb7 $4"},{"id":"bins","match":"^(fixed|uniform)_([0-9a-zA-Z]+)__bin$","group":"bins","is_hidden":true,"labelTemplate":"$1 bin ($2)"},{"id":"neighbors","match":".*neighbors_[0-9]+$","group":"neighbors","is_hidden":true,"labelTemplate":"neighbors"},{"id":"vep_merged","match":"^VEP__merged__(NAT|EMS)__attr_ss$","group":"lof","multiValued":true,"labelTemplate":"Merged $1 accessions"},{"id":"vep_detail","match":"^VEP__(.+?)__(homo|het)__(.+?)__(\\\\d+)__attr_ss$","group":"lof","multiValued":true,"labelTemplate":"$1 ($2) $3/$4"},{"id":"generic_attr","match":"^(.+)__attr_([a-z])$","group":"other","labelTemplate":"$1"}],"fields":{"id":{"group":"core","label":"Gene ID","order":1},"name":{"group":"core","label":"Name","order":2},"description":{"group":"core","label":"Description","order":4},"summary":{"group":"core","label":"Summary","order":5},"synonyms":{"group":"core","label":"Synonyms","multiValued":true,"order":3},"biotype":{"group":"core","label":"Biotype","order":6},"taxon_id":{"group":"core","label":"Taxon ID","order":8},"system_name":{"group":"core","label":"System name","order":7},"db_type":{"group":"core","label":"DB type","order":9},"closest_rep_id":{"group":"homology","label":"Closest representative ID","order":1},"closest_rep_name":{"group":"homology","label":"Closest representative name","order":2},"closest_rep_identity":{"group":"homology","label":"Closest representative identity","order":3},"closest_rep_taxon_id":{"group":"homology","label":"Closest representative taxon","order":4},"closest_rep_description":{"group":"homology","label":"Closest representative description","order":5},"model_rep_id":{"group":"homology","label":"Model representative ID","order":6},"model_rep_name":{"group":"homology","label":"Model representative name","order":7},"model_rep_identity":{"group":"homology","label":"Model representative identity","order":8},"model_rep_taxon_id":{"group":"homology","label":"Model representative taxon","order":9},"model_rep_description":{"group":"homology","label":"Model representative description","order":10},"gene_tree":{"group":"homology","label":"Gene tree ID","order":11},"pan_tree":{"group":"homology","label":"Pan-gene tree ID","order":14},"capabilities":{"group":"other","label":"Capabilities","multiValued":true},"map":{"group":"location","label":"Map","order":1},"region":{"group":"location","label":"Region","order":2},"start":{"group":"location","label":"Start","order":3},"end":{"group":"location","label":"End","order":4},"strand":{"group":"location","label":"Strand","order":5},"GO__ancestors":{"group":"hierarchical","label":"GO terms","multiValued":true,"order":1},"PO__ancestors":{"group":"hierarchical","label":"PO terms","multiValued":true,"order":2},"TO__ancestors":{"group":"hierarchical","label":"TO terms","multiValued":true,"order":3},"QTL_TO__ancestors":{"group":"hierarchical","label":"QTL traits (TO)","multiValued":true,"order":7},"domains__ancestors":{"group":"hierarchical","label":"Domains","multiValued":true,"order":4},"taxonomy__ancestors":{"group":"hierarchical","label":"Taxonomy","multiValued":true,"order":6},"supertree_attr_s":{"group":"homology","label":"Supertree","order":13},"gene_tree_root_taxon_id":{"group":"homology","label":"Gene tree root taxon","order":12},"protein__length":{"group":"structure","label":"Protein length"},"transcript__count":{"group":"structure","label":"Transcript count"},"transcript__exons":{"group":"structure","label":"Exon count"},"transcript__length":{"group":"structure","label":"Transcript length"},"expressed_in_gxa_attr_ss":{"group":"expression","label":"Expressed in GXA","multiValued":true},"MAKER__AED__attr_f":{"group":"MAKER","label":"AED","description":"Annotation Edit Distance"},"MAKER__QI1__attr_i":{"group":"MAKER","label":"QI1: Length of the 5\' UTR"},"MAKER__QI2__attr_f":{"group":"MAKER","label":"QI2: Fraction of splice sites confirmed by EST"},"MAKER__QI3__attr_f":{"group":"MAKER","label":"QI3: Fraction of exons overlapping an EST"},"MAKER__QI4__attr_f":{"group":"MAKER","label":"QI4: Fraction of exons overlapping EST or protein"},"MAKER__QI5__attr_f":{"group":"MAKER","label":"QI5: Fraction of splice sites confirmed by SNAP"},"MAKER__QI6__attr_f":{"group":"MAKER","label":"QI6: Fraction of exons overlapping a SNAP"},"MAKER__QI7__attr_i":{"group":"MAKER","label":"QI7: Number of exons in the mRNA"},"MAKER__QI8__attr_i":{"group":"MAKER","label":"QI8: Length of the 3\' UTR"},"MAKER__QI9__attr_i":{"group":"MAKER","label":"QI9: Length of the protein sequence"},"homology__all_orthologs":{"group":"homology","label":"All orthologs","multiValued":true,"order":15},"homology__ortholog_one2one":{"group":"homology","label":"1:1 orthologs","multiValued":true,"order":16},"homology__ortholog_one2many":{"group":"homology","label":"1:many orthologs","multiValued":true,"order":17},"homology__ortholog_many2many":{"group":"homology","label":"Many:many orthologs","multiValued":true,"order":18},"homology__syntenic_ortholog_one2one":{"group":"homology","label":"Syntenic 1:1 orthologs","multiValued":true,"order":19},"homology__within_species_paralog":{"group":"homology","label":"Within-species paralogs","multiValued":true,"order":20},"pathways__ancestors":{"group":"hierarchical","label":"Pathways","multiValued":true,"order":5},"canonical_transcript__attr_s":{"group":"structure","label":"Canonical transcript ID"}},"hidden":["_version_","_terms","score","gene_idx","gene_idx_multi","species_idx","compara_idx","compara_idx_multi","_id","annotations","bins","gene_structure","homology","location","xrefs","domain_roots","familyRoot__ancestors","taxonomy__ancestors","capabilities","saved_search"]}');
|
|
2054
2080
|
|
|
2055
2081
|
|
|
2056
2082
|
const $49d5cbca2ec74b2f$export$428c2f647a2a7545 = {
|
|
@@ -2287,6 +2313,19 @@ const $0f839422d0d8c772$var$grameneFieldCatalog = (0, $gXNCa$reduxbundler.create
|
|
|
2287
2313
|
return fetch(url).then((r)=>r.text()).then((text)=>$0f839422d0d8c772$var$parseCsvHeader(text)).catch(()=>null);
|
|
2288
2314
|
}
|
|
2289
2315
|
});
|
|
2316
|
+
// Auto-fetch the field catalog only when a view that needs it (exprViz fields
|
|
2317
|
+
// modal, exporter field tree) is enabled. Other views never read it.
|
|
2318
|
+
const $0f839422d0d8c772$var$FIELD_CATALOG_VIEWS = [
|
|
2319
|
+
'exprViz',
|
|
2320
|
+
'export'
|
|
2321
|
+
];
|
|
2322
|
+
$0f839422d0d8c772$var$grameneFieldCatalog.reactGrameneFieldCatalog = (0, $gXNCa$reduxbundler.createSelector)('selectGrameneFieldCatalogShouldUpdate', 'selectGrameneViewsOn', (shouldUpdate, viewsOn)=>{
|
|
2323
|
+
if (!shouldUpdate) return;
|
|
2324
|
+
if (!viewsOn || !$0f839422d0d8c772$var$FIELD_CATALOG_VIEWS.some((id)=>viewsOn.has(id))) return;
|
|
2325
|
+
return {
|
|
2326
|
+
actionCreator: 'doFetchGrameneFieldCatalog'
|
|
2327
|
+
};
|
|
2328
|
+
});
|
|
2290
2329
|
function $0f839422d0d8c772$var$assayFactorLabel(assay) {
|
|
2291
2330
|
if (!assay) return '';
|
|
2292
2331
|
const factors = Array.isArray(assay.factor) ? assay.factor : [];
|
|
@@ -2340,14 +2379,13 @@ function $0f839422d0d8c772$var$buildSpeciesNameIndex(grameneMaps) {
|
|
|
2340
2379
|
function $0f839422d0d8c772$var$fieldExperimentId(name) {
|
|
2341
2380
|
let m = name.match(/^(\w+?)_g\d+__expr$/);
|
|
2342
2381
|
if (m) return m[1].replace(/_/g, '-');
|
|
2343
|
-
m = name.match(/^(\w+?)_g\d+_g\d+_(pval|
|
|
2382
|
+
m = name.match(/^(\w+?)_g\d+_g\d+_(pval|l2fc)_attr_[a-z]$/);
|
|
2344
2383
|
if (m) return m[1].replace(/_/g, '-');
|
|
2345
2384
|
return null;
|
|
2346
2385
|
}
|
|
2347
2386
|
const $0f839422d0d8c772$var$STAT_RANK = {
|
|
2348
2387
|
l2fc: 0,
|
|
2349
|
-
|
|
2350
|
-
pval: 2
|
|
2388
|
+
pval: 1
|
|
2351
2389
|
};
|
|
2352
2390
|
function $0f839422d0d8c772$var$collapseDiffExprInSubgroups(subgroups, fieldsOut, collator) {
|
|
2353
2391
|
for (const taxGroup of subgroups)for (const expGroup of taxGroup.subgroups || []){
|
|
@@ -2384,7 +2422,7 @@ function $0f839422d0d8c772$var$collapseDiffExprInSubgroups(subgroups, fieldsOut,
|
|
|
2384
2422
|
});
|
|
2385
2423
|
const rep = names[0];
|
|
2386
2424
|
const repEntry = fieldsOut[rep];
|
|
2387
|
-
const label = (repEntry.label || rep).replace(/\s+\((?:pval|
|
|
2425
|
+
const label = (repEntry.label || rep).replace(/\s+\((?:pval|l2fc)\)/, '');
|
|
2388
2426
|
fieldsOut[rep] = {
|
|
2389
2427
|
...repEntry,
|
|
2390
2428
|
label: label,
|
|
@@ -2687,15 +2725,12 @@ $0f839422d0d8c772$var$grameneFieldCatalog.selectFieldCatalog = (0, $gXNCa$reduxb
|
|
|
2687
2725
|
const present = new Set(fieldNames);
|
|
2688
2726
|
// Build a synthetic doc from the discovered field names; values carry the
|
|
2689
2727
|
// right JS type so inferType picks the correct multiValued/type (arrays
|
|
2690
|
-
// for multi-valued fields, scalars otherwise). Derive pval
|
|
2691
|
-
//
|
|
2728
|
+
// for multi-valued fields, scalars otherwise). Derive the pval companion
|
|
2729
|
+
// from every l2fc field.
|
|
2692
2730
|
const doc = {};
|
|
2693
2731
|
for (const name of present){
|
|
2694
2732
|
doc[name] = $0f839422d0d8c772$var$synthesizedValue(name);
|
|
2695
|
-
if (/_l2fc_attr_f$/.test(name))
|
|
2696
|
-
doc[name.replace('_l2fc_', '_pval_')] = 0;
|
|
2697
|
-
doc[name.replace('_l2fc_', '_logfc_')] = 0;
|
|
2698
|
-
}
|
|
2733
|
+
if (/_l2fc_attr_f$/.test(name)) doc[name.replace('_l2fc_', '_pval_')] = 0;
|
|
2699
2734
|
}
|
|
2700
2735
|
const catalog = $0f839422d0d8c772$var$buildCatalog([
|
|
2701
2736
|
doc
|
|
@@ -2710,7 +2745,7 @@ var $0f839422d0d8c772$export$2e2bcd8739ae039 = $0f839422d0d8c772$var$grameneFiel
|
|
|
2710
2745
|
|
|
2711
2746
|
|
|
2712
2747
|
const $6d28e8e62a4d602f$var$EXPR_FIELD_RE = /^(E[-_][A-Za-z0-9_-]+?)_g(\d+)__expr$/;
|
|
2713
|
-
const $6d28e8e62a4d602f$var$DIFFEXPR_FIELD_RE = /^(E[-_][A-Za-z0-9_-]+?)_g(\d+)_g(\d+)_(pval|
|
|
2748
|
+
const $6d28e8e62a4d602f$var$DIFFEXPR_FIELD_RE = /^(E[-_][A-Za-z0-9_-]+?)_g(\d+)_g(\d+)_(pval|l2fc)_attr_([a-z])$/;
|
|
2714
2749
|
const $6d28e8e62a4d602f$export$adbe84e3322ddf23 = [
|
|
2715
2750
|
'experiment',
|
|
2716
2751
|
'experiment_name',
|
|
@@ -2835,7 +2870,7 @@ function $6d28e8e62a4d602f$export$ba4cd509d0763838(doc, diffExpressionFields, ex
|
|
|
2835
2870
|
byContrast.set(key, entry);
|
|
2836
2871
|
}
|
|
2837
2872
|
if (parsed.stat === 'pval') entry.pval = val;
|
|
2838
|
-
else entry.l2fc = val;
|
|
2873
|
+
else entry.l2fc = val;
|
|
2839
2874
|
}
|
|
2840
2875
|
const maxPval = cutoffs && cutoffs.diffMaxPval;
|
|
2841
2876
|
const maxPvalActive = maxPval !== null && maxPval !== undefined && maxPval !== '' && Number.isFinite(+maxPval);
|
|
@@ -3662,10 +3697,12 @@ const $1508f5a42be6e7b5$var$exporter = {
|
|
|
3662
3697
|
});
|
|
3663
3698
|
}, $1508f5a42be6e7b5$var$PREVIEW_DEBOUNCE_MS);
|
|
3664
3699
|
},
|
|
3665
|
-
reactExporterPreview: (0, $gXNCa$reduxbundler.createSelector)('selectExporter', 'selectGrameneFiltersStatus', 'selectGrameneFiltersQueryString', 'selectGrameneGenomes', 'selectGrameneMaps', (exp, filtersStatus, q, g, m)=>{
|
|
3700
|
+
reactExporterPreview: (0, $gXNCa$reduxbundler.createSelector)('selectExporter', 'selectGrameneFiltersStatus', 'selectGrameneFiltersQueryString', 'selectGrameneGenomes', 'selectGrameneMaps', 'selectGrameneViewsOn', (exp, filtersStatus, q, g, m, viewsOn)=>{
|
|
3666
3701
|
if (!exp || !exp.preview) return;
|
|
3667
3702
|
if (filtersStatus === 'init') return;
|
|
3668
3703
|
if (exp.preview.status === 'loading') return;
|
|
3704
|
+
// Don't run the preview query unless the user has the exporter view open.
|
|
3705
|
+
if (!viewsOn || !viewsOn.has('export')) return;
|
|
3669
3706
|
const maps = m || {};
|
|
3670
3707
|
const taxa = Object.keys(g && g.active || {}).filter((tid)=>maps[tid] && !maps[tid].hidden);
|
|
3671
3708
|
const sig = `${q}|${taxa.sort().join(',')}`;
|
|
@@ -11002,6 +11039,21 @@ const $4d0c2e01f58b53b1$var$FieldsModalCmp = (props)=>{
|
|
|
11002
11039
|
records,
|
|
11003
11040
|
selections
|
|
11004
11041
|
]);
|
|
11042
|
+
// Distinct values per property type across the unfiltered candidate set.
|
|
11043
|
+
// A type with a single value across every record can't discriminate (e.g.
|
|
11044
|
+
// "Organism" when every field belongs to the same species), so we hide it
|
|
11045
|
+
// from the property tree.
|
|
11046
|
+
const totalValueCountByKey = (0, $gXNCa$react.useMemo)(()=>{
|
|
11047
|
+
const m = {};
|
|
11048
|
+
for (const r of records)for (const [k, v] of Object.entries(r.props)){
|
|
11049
|
+
if (v == null) continue;
|
|
11050
|
+
if (!m[k]) m[k] = new Set();
|
|
11051
|
+
m[k].add(v);
|
|
11052
|
+
}
|
|
11053
|
+
return m;
|
|
11054
|
+
}, [
|
|
11055
|
+
records
|
|
11056
|
+
]);
|
|
11005
11057
|
// Reset state when modal (re)opens for a taxon.
|
|
11006
11058
|
(0, $gXNCa$react.useEffect)(()=>{
|
|
11007
11059
|
if (open && taxon) {
|
|
@@ -11169,6 +11221,13 @@ const $4d0c2e01f58b53b1$var$FieldsModalCmp = (props)=>{
|
|
|
11169
11221
|
}),
|
|
11170
11222
|
propTree.map((grp)=>{
|
|
11171
11223
|
const typeRows = grp.types.map((t)=>{
|
|
11224
|
+
// Hide property types that can't discriminate among the
|
|
11225
|
+
// candidates: zero values, or a single value shared by every
|
|
11226
|
+
// record. The selection-aware count (valueSetByKey) is what
|
|
11227
|
+
// we display; the unfiltered count gates visibility so a type
|
|
11228
|
+
// doesn't pop in/out as the user clicks values.
|
|
11229
|
+
const totalDistinct = totalValueCountByKey[t.key] && totalValueCountByKey[t.key].size || 0;
|
|
11230
|
+
if (totalDistinct < 2) return null;
|
|
11172
11231
|
const numValues = valueSetByKey[t.key] && valueSetByKey[t.key].size || 0;
|
|
11173
11232
|
if (numValues === 0) return null;
|
|
11174
11233
|
const typeLabelMatches = isSearching && t.label.toLowerCase().includes(searchLc);
|
|
@@ -11239,37 +11298,32 @@ const $4d0c2e01f58b53b1$var$FieldsModalCmp = (props)=>{
|
|
|
11239
11298
|
}),
|
|
11240
11299
|
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
11241
11300
|
className: "exprviz-fields-preview",
|
|
11242
|
-
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.
|
|
11301
|
+
children: orderedSelectedKeys.length === 0 ? /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
11302
|
+
className: "exprviz-fields-placeholder",
|
|
11303
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("em", {
|
|
11304
|
+
children: "Select a property type to preview the property values for each matching field."
|
|
11305
|
+
})
|
|
11306
|
+
}) : /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("table", {
|
|
11243
11307
|
className: "exprviz-fields-table",
|
|
11244
11308
|
children: [
|
|
11245
11309
|
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("thead", {
|
|
11246
|
-
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.
|
|
11247
|
-
children:
|
|
11248
|
-
|
|
11249
|
-
|
|
11250
|
-
}),
|
|
11251
|
-
orderedSelectedKeys.map((k)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("th", {
|
|
11252
|
-
children: $4d0c2e01f58b53b1$var$labelForKey(k, propTree)
|
|
11253
|
-
}, k))
|
|
11254
|
-
]
|
|
11310
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("tr", {
|
|
11311
|
+
children: orderedSelectedKeys.map((k)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("th", {
|
|
11312
|
+
children: $4d0c2e01f58b53b1$var$labelForKey(k, propTree)
|
|
11313
|
+
}, k))
|
|
11255
11314
|
})
|
|
11256
11315
|
}),
|
|
11257
11316
|
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("tbody", {
|
|
11258
11317
|
children: [
|
|
11259
|
-
matchingFields.map((f)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.
|
|
11260
|
-
|
|
11261
|
-
|
|
11262
|
-
|
|
11263
|
-
|
|
11264
|
-
}),
|
|
11265
|
-
orderedSelectedKeys.map((k)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("td", {
|
|
11266
|
-
children: f.props[k] || ''
|
|
11267
|
-
}, k))
|
|
11268
|
-
]
|
|
11318
|
+
matchingFields.map((f)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("tr", {
|
|
11319
|
+
title: f.fieldName,
|
|
11320
|
+
children: orderedSelectedKeys.map((k)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("td", {
|
|
11321
|
+
children: f.props[k] || ''
|
|
11322
|
+
}, k))
|
|
11269
11323
|
}, f.fieldName)),
|
|
11270
11324
|
matchingFields.length === 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("tr", {
|
|
11271
11325
|
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("td", {
|
|
11272
|
-
colSpan:
|
|
11326
|
+
colSpan: orderedSelectedKeys.length,
|
|
11273
11327
|
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("em", {
|
|
11274
11328
|
children: "No matching fields"
|
|
11275
11329
|
})
|
|
@@ -11316,7 +11370,6 @@ var $4d0c2e01f58b53b1$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.conn
|
|
|
11316
11370
|
|
|
11317
11371
|
|
|
11318
11372
|
|
|
11319
|
-
|
|
11320
11373
|
const $4ab64e76c1caef59$var$baseColDefs = [
|
|
11321
11374
|
{
|
|
11322
11375
|
field: 'id',
|
|
@@ -11328,16 +11381,27 @@ const $4ab64e76c1caef59$var$baseColDefs = [
|
|
|
11328
11381
|
{
|
|
11329
11382
|
field: 'name',
|
|
11330
11383
|
headerName: 'Name',
|
|
11384
|
+
pinned: 'left',
|
|
11331
11385
|
width: 140,
|
|
11332
11386
|
suppressMovable: true
|
|
11333
11387
|
}
|
|
11334
11388
|
];
|
|
11389
|
+
// Hoisted so the reference is stable across renders. ag-grid otherwise sees a
|
|
11390
|
+
// "new" defaultColDef on every parent re-render (e.g. when hovering a row
|
|
11391
|
+
// triggers setHoveredId in the parent) and re-applies column state, which
|
|
11392
|
+
// snaps any user-resized columns back to their original widths.
|
|
11393
|
+
const $4ab64e76c1caef59$var$DEFAULT_COL_DEF = {
|
|
11394
|
+
resizable: true,
|
|
11395
|
+
sortable: true,
|
|
11396
|
+
filter: false,
|
|
11397
|
+
suppressMenu: true
|
|
11398
|
+
};
|
|
11335
11399
|
function $4ab64e76c1caef59$var$arraysEqual(a, b) {
|
|
11336
11400
|
if (a.length !== b.length) return false;
|
|
11337
11401
|
for(let i = 0; i < a.length; i++)if (a[i] !== b[i]) return false;
|
|
11338
11402
|
return true;
|
|
11339
11403
|
}
|
|
11340
|
-
function $4ab64e76c1caef59$
|
|
11404
|
+
function $4ab64e76c1caef59$export$7b242440eb2c300d(fields, studies, expressionSamples) {
|
|
11341
11405
|
const info = {};
|
|
11342
11406
|
if (!fields || !studies || !expressionSamples) return info;
|
|
11343
11407
|
const wanted = new Set(fields);
|
|
@@ -11372,189 +11436,287 @@ function $4ab64e76c1caef59$var$buildFieldInfo(fields, studies, expressionSamples
|
|
|
11372
11436
|
}
|
|
11373
11437
|
return info;
|
|
11374
11438
|
}
|
|
11375
|
-
//
|
|
11376
|
-
//
|
|
11377
|
-
|
|
11378
|
-
|
|
11379
|
-
|
|
11380
|
-
const
|
|
11381
|
-
(0, $gXNCa$
|
|
11382
|
-
|
|
11383
|
-
|
|
11384
|
-
|
|
11385
|
-
|
|
11386
|
-
|
|
11387
|
-
|
|
11388
|
-
|
|
11389
|
-
const
|
|
11390
|
-
if (enableSorting && progressSort) progressSort(event.shiftKey);
|
|
11391
|
-
};
|
|
11392
|
-
const onMenuClick = (event)=>{
|
|
11393
|
-
event.stopPropagation();
|
|
11394
|
-
if (showColumnMenu && menuRef.current) showColumnMenu(menuRef.current);
|
|
11395
|
-
};
|
|
11396
|
-
const factorEntries = info ? Object.entries(info.factors || {}) : [];
|
|
11397
|
-
const charEntries = info ? Object.entries(info.characteristics || {}) : [];
|
|
11398
|
-
const popover = info ? /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Popover), {
|
|
11399
|
-
id: `exprviz-header-${info.studyId}-${info.group}`,
|
|
11400
|
-
className: "exprviz-header-popover",
|
|
11401
|
-
children: [
|
|
11402
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Popover).Header, {
|
|
11403
|
-
as: "h6",
|
|
11404
|
-
children: info.studyDescription
|
|
11405
|
-
}),
|
|
11406
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Popover).Body, {
|
|
11407
|
-
children: [
|
|
11408
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
11409
|
-
children: [
|
|
11410
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("strong", {
|
|
11411
|
-
children: "Study:"
|
|
11412
|
-
}),
|
|
11413
|
-
" ",
|
|
11414
|
-
info.studyId
|
|
11415
|
-
]
|
|
11416
|
-
}),
|
|
11417
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
11418
|
-
children: [
|
|
11419
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("strong", {
|
|
11420
|
-
children: "Group:"
|
|
11421
|
-
}),
|
|
11422
|
-
" ",
|
|
11423
|
-
info.group,
|
|
11424
|
-
" (",
|
|
11425
|
-
info.replicates,
|
|
11426
|
-
" ",
|
|
11427
|
-
info.replicates === 1 ? 'rep' : 'reps',
|
|
11428
|
-
")"
|
|
11429
|
-
]
|
|
11430
|
-
}),
|
|
11431
|
-
factorEntries.length > 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
11432
|
-
className: "exprviz-header-section",
|
|
11433
|
-
children: [
|
|
11434
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("strong", {
|
|
11435
|
-
children: "Factors"
|
|
11436
|
-
}),
|
|
11437
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("ul", {
|
|
11438
|
-
children: factorEntries.map(([t, v])=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("li", {
|
|
11439
|
-
children: [
|
|
11440
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("em", {
|
|
11441
|
-
children: [
|
|
11442
|
-
t,
|
|
11443
|
-
":"
|
|
11444
|
-
]
|
|
11445
|
-
}),
|
|
11446
|
-
" ",
|
|
11447
|
-
v
|
|
11448
|
-
]
|
|
11449
|
-
}, t))
|
|
11450
|
-
})
|
|
11451
|
-
]
|
|
11452
|
-
}),
|
|
11453
|
-
charEntries.length > 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
11454
|
-
className: "exprviz-header-section",
|
|
11455
|
-
children: [
|
|
11456
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("strong", {
|
|
11457
|
-
children: "Characteristics"
|
|
11458
|
-
}),
|
|
11459
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("ul", {
|
|
11460
|
-
children: charEntries.map(([t, v])=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("li", {
|
|
11461
|
-
children: [
|
|
11462
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("em", {
|
|
11463
|
-
children: [
|
|
11464
|
-
t,
|
|
11465
|
-
":"
|
|
11466
|
-
]
|
|
11467
|
-
}),
|
|
11468
|
-
" ",
|
|
11469
|
-
v
|
|
11470
|
-
]
|
|
11471
|
-
}, t))
|
|
11472
|
-
})
|
|
11473
|
-
]
|
|
11474
|
-
})
|
|
11475
|
-
]
|
|
11476
|
-
})
|
|
11477
|
-
]
|
|
11478
|
-
}) : null;
|
|
11439
|
+
// Plain right-aligned label cell for the metadata header rows above the
|
|
11440
|
+
// Gene ID / Name columns. Rendered as a React component (instead of
|
|
11441
|
+
// relying on ag-grid's default group label) so the alignment is controlled
|
|
11442
|
+
// by our own DOM and isn't fighting the quartz theme's CSS.
|
|
11443
|
+
const $4ab64e76c1caef59$var$LabelHeaderGroup = (props)=>{
|
|
11444
|
+
const { displayName: displayName } = props;
|
|
11445
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
11446
|
+
className: "exprviz-label-header",
|
|
11447
|
+
children: displayName
|
|
11448
|
+
});
|
|
11449
|
+
};
|
|
11450
|
+
// Same idea, but for the cells that toggle a section between expanded and
|
|
11451
|
+
// collapsed. The caret + label sit at the right edge.
|
|
11452
|
+
const $4ab64e76c1caef59$var$ToggleHeaderGroup = (props)=>{
|
|
11453
|
+
const { displayName: displayName, onToggle: onToggle, expanded: expanded, suffix: suffix } = props;
|
|
11479
11454
|
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
11480
|
-
className: "exprviz-header",
|
|
11455
|
+
className: "exprviz-toggle-header",
|
|
11456
|
+
onClick: onToggle,
|
|
11457
|
+
role: "button",
|
|
11458
|
+
tabIndex: 0,
|
|
11459
|
+
onKeyDown: (e)=>{
|
|
11460
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
11461
|
+
e.preventDefault();
|
|
11462
|
+
onToggle && onToggle();
|
|
11463
|
+
}
|
|
11464
|
+
},
|
|
11465
|
+
title: expanded ? 'Click to collapse' : 'Click to expand',
|
|
11481
11466
|
children: [
|
|
11482
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.
|
|
11483
|
-
className: "exprviz-
|
|
11484
|
-
|
|
11485
|
-
style: {
|
|
11486
|
-
cursor: enableSorting ? 'pointer' : 'default'
|
|
11487
|
-
},
|
|
11488
|
-
children: [
|
|
11489
|
-
displayName,
|
|
11490
|
-
sort === 'asc' && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
11491
|
-
className: "exprviz-header-sort",
|
|
11492
|
-
children: " \u25B2"
|
|
11493
|
-
}),
|
|
11494
|
-
sort === 'desc' && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
11495
|
-
className: "exprviz-header-sort",
|
|
11496
|
-
children: " \u25BC"
|
|
11497
|
-
})
|
|
11498
|
-
]
|
|
11499
|
-
}),
|
|
11500
|
-
info && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.OverlayTrigger), {
|
|
11501
|
-
trigger: [
|
|
11502
|
-
'hover',
|
|
11503
|
-
'focus'
|
|
11504
|
-
],
|
|
11505
|
-
placement: "bottom",
|
|
11506
|
-
overlay: popover,
|
|
11507
|
-
delay: {
|
|
11508
|
-
show: 200,
|
|
11509
|
-
hide: 100
|
|
11510
|
-
},
|
|
11511
|
-
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
11512
|
-
className: "exprviz-header-info",
|
|
11513
|
-
onClick: (e)=>e.stopPropagation(),
|
|
11514
|
-
onMouseDown: (e)=>e.stopPropagation(),
|
|
11515
|
-
"aria-label": "More info",
|
|
11516
|
-
children: "\u24D8"
|
|
11517
|
-
})
|
|
11467
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
11468
|
+
className: "exprviz-toggle-caret",
|
|
11469
|
+
children: expanded ? "\u25BC" : "\u25B6"
|
|
11518
11470
|
}),
|
|
11519
|
-
|
|
11520
|
-
|
|
11521
|
-
|
|
11522
|
-
onClick: onMenuClick
|
|
11523
|
-
})
|
|
11471
|
+
' ',
|
|
11472
|
+
displayName,
|
|
11473
|
+
suffix ? ` ${suffix}` : ''
|
|
11524
11474
|
]
|
|
11525
11475
|
});
|
|
11526
11476
|
};
|
|
11477
|
+
function $4ab64e76c1caef59$var$exprValueFormatter(p) {
|
|
11478
|
+
const v = p.value;
|
|
11479
|
+
if (v == null) return '';
|
|
11480
|
+
if (Array.isArray(v)) return v.join(', ');
|
|
11481
|
+
if (typeof v === 'object') return JSON.stringify(v);
|
|
11482
|
+
return String(v);
|
|
11483
|
+
}
|
|
11484
|
+
// Discover every distinct factor and characteristic type across the loaded
|
|
11485
|
+
// fields. Each becomes one header row (with adjacent equal-value cells
|
|
11486
|
+
// merged). Order is alphabetical for stability across reloads.
|
|
11487
|
+
function $4ab64e76c1caef59$var$collectMetadataTypes(fields, fieldInfo) {
|
|
11488
|
+
const factorTypes = new Set();
|
|
11489
|
+
const charTypes = new Set();
|
|
11490
|
+
for (const f of fields || []){
|
|
11491
|
+
const info = fieldInfo[f];
|
|
11492
|
+
if (!info) continue;
|
|
11493
|
+
Object.keys(info.factors || {}).forEach((t)=>factorTypes.add(t));
|
|
11494
|
+
Object.keys(info.characteristics || {}).forEach((t)=>charTypes.add(t));
|
|
11495
|
+
}
|
|
11496
|
+
return {
|
|
11497
|
+
factorTypes: Array.from(factorTypes).sort(),
|
|
11498
|
+
charTypes: Array.from(charTypes).sort()
|
|
11499
|
+
};
|
|
11500
|
+
}
|
|
11501
|
+
// Wrap a leaf column in N nested column groups so it participates at every
|
|
11502
|
+
// header row, with a custom label per level. Lets the Gene ID / Name columns
|
|
11503
|
+
// carry the row labels ("Study"/"Factor"/"Characteristic" and the type name).
|
|
11504
|
+
function $4ab64e76c1caef59$var$wrapLeafWithLabels(leaf, labels) {
|
|
11505
|
+
let wrapped = leaf;
|
|
11506
|
+
for(let i = labels.length - 1; i >= 0; i--){
|
|
11507
|
+
const label = labels[i];
|
|
11508
|
+
const cls = label.headerClass ? `${label.headerClass} exprviz-hg-labels` : 'exprviz-hg-labels';
|
|
11509
|
+
wrapped = {
|
|
11510
|
+
headerName: label.headerName,
|
|
11511
|
+
headerClass: cls,
|
|
11512
|
+
// Always render through one of our components so the alignment and
|
|
11513
|
+
// spacing are controlled by our own DOM, not ag-grid's defaults.
|
|
11514
|
+
headerGroupComponent: label.headerGroupComponent || $4ab64e76c1caef59$var$LabelHeaderGroup,
|
|
11515
|
+
headerGroupComponentParams: label.headerGroupComponentParams,
|
|
11516
|
+
children: [
|
|
11517
|
+
wrapped
|
|
11518
|
+
]
|
|
11519
|
+
};
|
|
11520
|
+
}
|
|
11521
|
+
return wrapped;
|
|
11522
|
+
}
|
|
11523
|
+
// Build the column tree:
|
|
11524
|
+
// row 1 — Study | Title | <study description>…
|
|
11525
|
+
// row 2..(2+F-1) — Factor | <factor type> | <value or blank>…
|
|
11526
|
+
// row 2+F..end-1 — Characteristic | <char type> | <value or blank>…
|
|
11527
|
+
// leaf row — Gene ID | Name | g3, g4, …
|
|
11528
|
+
// Adjacent expression cells sharing the same value at a given level merge
|
|
11529
|
+
// (because they're children of one group definition). Blank values for a
|
|
11530
|
+
// type that's defined only in other studies render as empty cells, and
|
|
11531
|
+
// adjacent blanks under the same parent merge automatically.
|
|
11532
|
+
function $4ab64e76c1caef59$var$buildColumnDefs(fields, fieldInfo, expanded, toggles) {
|
|
11533
|
+
if (!fields || fields.length === 0) return $4ab64e76c1caef59$var$baseColDefs;
|
|
11534
|
+
const { factorTypes: factorTypes, charTypes: charTypes } = $4ab64e76c1caef59$var$collectMetadataTypes(fields, fieldInfo);
|
|
11535
|
+
const levels = [
|
|
11536
|
+
{
|
|
11537
|
+
kind: 'study',
|
|
11538
|
+
getValue: (info)=>info && info.studyDescription || ''
|
|
11539
|
+
}
|
|
11540
|
+
];
|
|
11541
|
+
if (expanded.factors) for (const t of factorTypes)levels.push({
|
|
11542
|
+
kind: 'factor',
|
|
11543
|
+
type: t,
|
|
11544
|
+
getValue: (info)=>info && info.factors && info.factors[t] || '',
|
|
11545
|
+
headerClass: 'exprviz-hg-factors'
|
|
11546
|
+
});
|
|
11547
|
+
else if (factorTypes.length > 0) // One placeholder level standing in for the collapsed factor rows.
|
|
11548
|
+
levels.push({
|
|
11549
|
+
kind: 'factors-collapsed',
|
|
11550
|
+
getValue: ()=>'',
|
|
11551
|
+
headerClass: 'exprviz-hg-factors exprviz-hg-collapsed'
|
|
11552
|
+
});
|
|
11553
|
+
if (expanded.chars) for (const t of charTypes)levels.push({
|
|
11554
|
+
kind: 'char',
|
|
11555
|
+
type: t,
|
|
11556
|
+
getValue: (info)=>info && info.characteristics && info.characteristics[t] || '',
|
|
11557
|
+
headerClass: 'exprviz-hg-chars'
|
|
11558
|
+
});
|
|
11559
|
+
else if (charTypes.length > 0) levels.push({
|
|
11560
|
+
kind: 'chars-collapsed',
|
|
11561
|
+
getValue: ()=>'',
|
|
11562
|
+
headerClass: 'exprviz-hg-chars exprviz-hg-collapsed'
|
|
11563
|
+
});
|
|
11564
|
+
// Walk fields, opening a new group at level i (and resetting all levels
|
|
11565
|
+
// below) the first time the value at level i differs from the previous
|
|
11566
|
+
// field's. Same value → same parent group → ag-grid merges the cells.
|
|
11567
|
+
const exprTopGroups = [];
|
|
11568
|
+
const currentGroups = new Array(levels.length).fill(null);
|
|
11569
|
+
const currentKeys = new Array(levels.length).fill(undefined);
|
|
11570
|
+
for (const f of fields){
|
|
11571
|
+
const info = fieldInfo[f] || {};
|
|
11572
|
+
let firstChange = levels.length;
|
|
11573
|
+
for(let i = 0; i < levels.length; i++){
|
|
11574
|
+
const key = levels[i].getValue(info);
|
|
11575
|
+
if (currentGroups[i] === null || currentKeys[i] !== key) {
|
|
11576
|
+
firstChange = i;
|
|
11577
|
+
break;
|
|
11578
|
+
}
|
|
11579
|
+
}
|
|
11580
|
+
for(let i = firstChange; i < levels.length; i++){
|
|
11581
|
+
const key = levels[i].getValue(info);
|
|
11582
|
+
const group = {
|
|
11583
|
+
headerName: key,
|
|
11584
|
+
headerClass: levels[i].headerClass,
|
|
11585
|
+
children: []
|
|
11586
|
+
};
|
|
11587
|
+
currentGroups[i] = group;
|
|
11588
|
+
currentKeys[i] = key;
|
|
11589
|
+
if (i === 0) exprTopGroups.push(group);
|
|
11590
|
+
else currentGroups[i - 1].children.push(group);
|
|
11591
|
+
}
|
|
11592
|
+
const leaf = {
|
|
11593
|
+
field: f,
|
|
11594
|
+
headerName: info.group || f.replace(/__expr$/, ''),
|
|
11595
|
+
width: 160,
|
|
11596
|
+
suppressMovable: false,
|
|
11597
|
+
valueFormatter: $4ab64e76c1caef59$var$exprValueFormatter
|
|
11598
|
+
};
|
|
11599
|
+
currentGroups[levels.length - 1].children.push(leaf);
|
|
11600
|
+
}
|
|
11601
|
+
// Labels for the two pinned columns. The leftmost column carries the row
|
|
11602
|
+
// category ("Study" / "Factor" / "Characteristic"); the next column shows
|
|
11603
|
+
// the specific row name (the literal "Title" for the study row, then each
|
|
11604
|
+
// factor/characteristic type name). The first row of each
|
|
11605
|
+
// factor/characteristic section gets a clickable caret that toggles the
|
|
11606
|
+
// section between expanded (one row per type) and collapsed (one
|
|
11607
|
+
// placeholder row).
|
|
11608
|
+
const studyLabels = [
|
|
11609
|
+
{
|
|
11610
|
+
headerName: 'Study'
|
|
11611
|
+
}
|
|
11612
|
+
];
|
|
11613
|
+
const titleLabels = [
|
|
11614
|
+
{
|
|
11615
|
+
headerName: 'Title'
|
|
11616
|
+
}
|
|
11617
|
+
];
|
|
11618
|
+
if (expanded.factors) factorTypes.forEach((t, i)=>{
|
|
11619
|
+
studyLabels.push({
|
|
11620
|
+
headerName: 'Factor',
|
|
11621
|
+
headerClass: 'exprviz-hg-factors',
|
|
11622
|
+
...i === 0 ? {
|
|
11623
|
+
headerGroupComponent: $4ab64e76c1caef59$var$ToggleHeaderGroup,
|
|
11624
|
+
headerGroupComponentParams: {
|
|
11625
|
+
onToggle: toggles.toggleFactors,
|
|
11626
|
+
expanded: true
|
|
11627
|
+
}
|
|
11628
|
+
} : {}
|
|
11629
|
+
});
|
|
11630
|
+
titleLabels.push({
|
|
11631
|
+
headerName: t,
|
|
11632
|
+
headerClass: 'exprviz-hg-factors'
|
|
11633
|
+
});
|
|
11634
|
+
});
|
|
11635
|
+
else if (factorTypes.length > 0) {
|
|
11636
|
+
studyLabels.push({
|
|
11637
|
+
headerName: 'Factors',
|
|
11638
|
+
headerClass: 'exprviz-hg-factors exprviz-hg-collapsed',
|
|
11639
|
+
headerGroupComponent: $4ab64e76c1caef59$var$ToggleHeaderGroup,
|
|
11640
|
+
headerGroupComponentParams: {
|
|
11641
|
+
onToggle: toggles.toggleFactors,
|
|
11642
|
+
expanded: false,
|
|
11643
|
+
suffix: `(${factorTypes.length})`
|
|
11644
|
+
}
|
|
11645
|
+
});
|
|
11646
|
+
titleLabels.push({
|
|
11647
|
+
headerName: '',
|
|
11648
|
+
headerClass: 'exprviz-hg-factors exprviz-hg-collapsed'
|
|
11649
|
+
});
|
|
11650
|
+
}
|
|
11651
|
+
if (expanded.chars) charTypes.forEach((t, i)=>{
|
|
11652
|
+
studyLabels.push({
|
|
11653
|
+
headerName: 'Characteristic',
|
|
11654
|
+
headerClass: 'exprviz-hg-chars',
|
|
11655
|
+
...i === 0 ? {
|
|
11656
|
+
headerGroupComponent: $4ab64e76c1caef59$var$ToggleHeaderGroup,
|
|
11657
|
+
headerGroupComponentParams: {
|
|
11658
|
+
onToggle: toggles.toggleChars,
|
|
11659
|
+
expanded: true
|
|
11660
|
+
}
|
|
11661
|
+
} : {}
|
|
11662
|
+
});
|
|
11663
|
+
titleLabels.push({
|
|
11664
|
+
headerName: t,
|
|
11665
|
+
headerClass: 'exprviz-hg-chars'
|
|
11666
|
+
});
|
|
11667
|
+
});
|
|
11668
|
+
else if (charTypes.length > 0) {
|
|
11669
|
+
studyLabels.push({
|
|
11670
|
+
headerName: 'Characteristics',
|
|
11671
|
+
headerClass: 'exprviz-hg-chars exprviz-hg-collapsed',
|
|
11672
|
+
headerGroupComponent: $4ab64e76c1caef59$var$ToggleHeaderGroup,
|
|
11673
|
+
headerGroupComponentParams: {
|
|
11674
|
+
onToggle: toggles.toggleChars,
|
|
11675
|
+
expanded: false,
|
|
11676
|
+
suffix: `(${charTypes.length})`
|
|
11677
|
+
}
|
|
11678
|
+
});
|
|
11679
|
+
titleLabels.push({
|
|
11680
|
+
headerName: '',
|
|
11681
|
+
headerClass: 'exprviz-hg-chars exprviz-hg-collapsed'
|
|
11682
|
+
});
|
|
11683
|
+
}
|
|
11684
|
+
const idCol = $4ab64e76c1caef59$var$wrapLeafWithLabels($4ab64e76c1caef59$var$baseColDefs[0], studyLabels);
|
|
11685
|
+
const nameCol = $4ab64e76c1caef59$var$wrapLeafWithLabels($4ab64e76c1caef59$var$baseColDefs[1], titleLabels);
|
|
11686
|
+
return [
|
|
11687
|
+
idCol,
|
|
11688
|
+
nameCol,
|
|
11689
|
+
...exprTopGroups
|
|
11690
|
+
];
|
|
11691
|
+
}
|
|
11527
11692
|
const $4ab64e76c1caef59$var$ExprTable = ({ rows: rows, fields: fields, onReorder: onReorder, studies: studies, expressionSamples: expressionSamples, onHoverRow: onHoverRow })=>{
|
|
11528
|
-
const fieldInfo = (0, $gXNCa$react.useMemo)(()=>$4ab64e76c1caef59$
|
|
11693
|
+
const fieldInfo = (0, $gXNCa$react.useMemo)(()=>$4ab64e76c1caef59$export$7b242440eb2c300d(fields, studies, expressionSamples), [
|
|
11529
11694
|
fields,
|
|
11530
11695
|
studies,
|
|
11531
11696
|
expressionSamples
|
|
11532
11697
|
]);
|
|
11533
|
-
|
|
11534
|
-
|
|
11535
|
-
|
|
11536
|
-
|
|
11537
|
-
|
|
11538
|
-
|
|
11539
|
-
|
|
11540
|
-
|
|
11541
|
-
|
|
11542
|
-
|
|
11543
|
-
|
|
11544
|
-
|
|
11545
|
-
|
|
11546
|
-
|
|
11547
|
-
|
|
11548
|
-
|
|
11549
|
-
|
|
11550
|
-
}));
|
|
11551
|
-
return [
|
|
11552
|
-
...$4ab64e76c1caef59$var$baseColDefs,
|
|
11553
|
-
...expressionCols
|
|
11554
|
-
];
|
|
11555
|
-
}, [
|
|
11698
|
+
// Default: factor rows expanded, characteristic rows collapsed (per spec).
|
|
11699
|
+
const [expanded, setExpanded] = (0, $gXNCa$react.useState)({
|
|
11700
|
+
factors: true,
|
|
11701
|
+
chars: false
|
|
11702
|
+
});
|
|
11703
|
+
const toggleFactors = (0, $gXNCa$react.useCallback)(()=>setExpanded((e)=>({
|
|
11704
|
+
...e,
|
|
11705
|
+
factors: !e.factors
|
|
11706
|
+
})), []);
|
|
11707
|
+
const toggleChars = (0, $gXNCa$react.useCallback)(()=>setExpanded((e)=>({
|
|
11708
|
+
...e,
|
|
11709
|
+
chars: !e.chars
|
|
11710
|
+
})), []);
|
|
11711
|
+
const columnDefs = (0, $gXNCa$react.useMemo)(()=>$4ab64e76c1caef59$var$buildColumnDefs(fields, fieldInfo, expanded, {
|
|
11712
|
+
toggleFactors: toggleFactors,
|
|
11713
|
+
toggleChars: toggleChars
|
|
11714
|
+
}), [
|
|
11556
11715
|
fields,
|
|
11557
|
-
fieldInfo
|
|
11716
|
+
fieldInfo,
|
|
11717
|
+
expanded,
|
|
11718
|
+
toggleFactors,
|
|
11719
|
+
toggleChars
|
|
11558
11720
|
]);
|
|
11559
11721
|
const onColumnMoved = (e)=>{
|
|
11560
11722
|
if (!onReorder || !e.finished) return;
|
|
@@ -11574,14 +11736,12 @@ const $4ab64e76c1caef59$var$ExprTable = ({ rows: rows, fields: fields, onReorder
|
|
|
11574
11736
|
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$aggridreact.AgGridReact), {
|
|
11575
11737
|
rowData: rows,
|
|
11576
11738
|
columnDefs: columnDefs,
|
|
11577
|
-
defaultColDef:
|
|
11578
|
-
resizable: true,
|
|
11579
|
-
sortable: true,
|
|
11580
|
-
filter: true
|
|
11581
|
-
},
|
|
11739
|
+
defaultColDef: $4ab64e76c1caef59$var$DEFAULT_COL_DEF,
|
|
11582
11740
|
animateRows: false,
|
|
11583
11741
|
suppressFieldDotNotation: true,
|
|
11584
11742
|
suppressDragLeaveHidesColumns: true,
|
|
11743
|
+
suppressColumnVirtualisation: true,
|
|
11744
|
+
groupHeaderHeight: 24,
|
|
11585
11745
|
onColumnMoved: onColumnMoved,
|
|
11586
11746
|
onCellMouseOver: (e)=>onHoverRow && onHoverRow(e.data && e.data.id),
|
|
11587
11747
|
onCellMouseOut: ()=>onHoverRow && onHoverRow(null)
|
|
@@ -11647,12 +11807,41 @@ function $caf32827df861c4e$var$logTickFormat(v) {
|
|
|
11647
11807
|
if (a >= 0.01 && a < 10000) return $gXNCa$d3.format('~g')(v);
|
|
11648
11808
|
return $gXNCa$d3.format('.0e')(v);
|
|
11649
11809
|
}
|
|
11650
|
-
const $caf32827df861c4e$var$ParallelCoordsPlot = ({ rows: rows, fields: fields, scale: scale = 'linear', onBrushChange: onBrushChange, onReorder: onReorder, clearVersion: clearVersion = 0, hoveredId: hoveredId = null })=>{
|
|
11810
|
+
const $caf32827df861c4e$var$ParallelCoordsPlot = ({ rows: rows, fields: fields, scale: scale = 'linear', onBrushChange: onBrushChange, onReorder: onReorder, clearVersion: clearVersion = 0, hoveredId: hoveredId = null, axisLabels: axisLabels = null })=>{
|
|
11651
11811
|
const svgRef = (0, $gXNCa$react.useRef)(null);
|
|
11652
11812
|
const containerRef = (0, $gXNCa$react.useRef)(null);
|
|
11653
11813
|
// selections in data domain: { [field]: [lo, hi] }
|
|
11654
11814
|
const selectionsRef = (0, $gXNCa$react.useRef)({});
|
|
11655
11815
|
const lastClearRef = (0, $gXNCa$react.useRef)(0);
|
|
11816
|
+
// Track container size so the d3 render reruns when the user drags the
|
|
11817
|
+
// pane resizer (or when the window is resized). The values themselves
|
|
11818
|
+
// aren't read inside the effect — the effect always reads clientWidth/
|
|
11819
|
+
// clientHeight — but listing them in the deps array is what triggers it.
|
|
11820
|
+
const [size, setSize] = (0, $gXNCa$react.useState)({
|
|
11821
|
+
w: 0,
|
|
11822
|
+
h: 0
|
|
11823
|
+
});
|
|
11824
|
+
// Custom HTML tooltip for axis labels — gives us bold labels and structured
|
|
11825
|
+
// sections, which the native SVG <title> can't do.
|
|
11826
|
+
const [tooltip, setTooltip] = (0, $gXNCa$react.useState)(null);
|
|
11827
|
+
(0, $gXNCa$react.useEffect)(()=>{
|
|
11828
|
+
const el = containerRef.current;
|
|
11829
|
+
if (!el || typeof ResizeObserver === 'undefined') return;
|
|
11830
|
+
const ro = new ResizeObserver((entries)=>{
|
|
11831
|
+
for (const entry of entries){
|
|
11832
|
+
const { width: width, height: height } = entry.contentRect;
|
|
11833
|
+
setSize((prev)=>{
|
|
11834
|
+
if (Math.abs(prev.w - width) < 1 && Math.abs(prev.h - height) < 1) return prev;
|
|
11835
|
+
return {
|
|
11836
|
+
w: width,
|
|
11837
|
+
h: height
|
|
11838
|
+
};
|
|
11839
|
+
});
|
|
11840
|
+
}
|
|
11841
|
+
});
|
|
11842
|
+
ro.observe(el);
|
|
11843
|
+
return ()=>ro.disconnect();
|
|
11844
|
+
}, []);
|
|
11656
11845
|
(0, $gXNCa$react.useEffect)(()=>{
|
|
11657
11846
|
if (clearVersion !== lastClearRef.current) {
|
|
11658
11847
|
selectionsRef.current = {};
|
|
@@ -11749,9 +11938,32 @@ const $caf32827df861c4e$var$ParallelCoordsPlot = ({ rows: rows, fields: fields,
|
|
|
11749
11938
|
if (scale === 'log') axisGen.tickValues($caf32827df861c4e$var$logTickValues(yByField[f].domain())).tickFormat($caf32827df861c4e$var$logTickFormat);
|
|
11750
11939
|
else axisGen.ticks(5);
|
|
11751
11940
|
ax.call(axisGen);
|
|
11752
|
-
|
|
11941
|
+
// Compact axis label. Hovering the label or its drag-handle rect shows
|
|
11942
|
+
// a custom HTML tooltip (rendered outside the SVG by React) that can
|
|
11943
|
+
// include bold labels and section headings.
|
|
11944
|
+
const labelInfo = axisLabels && axisLabels[f] || {
|
|
11945
|
+
short: f.replace(/__expr$/, ''),
|
|
11946
|
+
structured: {
|
|
11947
|
+
studyTitle: f,
|
|
11948
|
+
group: '',
|
|
11949
|
+
factors: [],
|
|
11950
|
+
characteristics: []
|
|
11951
|
+
}
|
|
11952
|
+
};
|
|
11953
|
+
const showTip = (event)=>setTooltip({
|
|
11954
|
+
x: event.clientX,
|
|
11955
|
+
y: event.clientY,
|
|
11956
|
+
info: labelInfo.structured
|
|
11957
|
+
});
|
|
11958
|
+
const moveTip = (event)=>setTooltip((t)=>t ? {
|
|
11959
|
+
...t,
|
|
11960
|
+
x: event.clientX,
|
|
11961
|
+
y: event.clientY
|
|
11962
|
+
} : null);
|
|
11963
|
+
const hideTip = ()=>setTooltip(null);
|
|
11964
|
+
ax.append('text').attr('class', 'exprviz-pc-axis-label').attr('x', 4).attr('y', -4).attr('text-anchor', 'start').attr('transform', `rotate(${$caf32827df861c4e$var$LABEL_ROTATION}, 0, -4)`).attr('fill', '#333').style('font-size', '10px').style('cursor', 'grab').text(labelInfo.short).on('mouseenter', showTip).on('mousemove', moveTip).on('mouseleave', hideTip);
|
|
11753
11965
|
// hit area for grabbing — sits along the rotated label
|
|
11754
|
-
ax.append('rect').attr('class', 'exprviz-pc-axis-handle').attr('x', 0).attr('y', -11).attr('width', 140).attr('height', 14).attr('transform', `rotate(${$caf32827df861c4e$var$LABEL_ROTATION}, 0, -4)`).attr('fill', 'transparent').style('cursor', 'grab');
|
|
11966
|
+
ax.append('rect').attr('class', 'exprviz-pc-axis-handle').attr('x', 0).attr('y', -11).attr('width', 140).attr('height', 14).attr('transform', `rotate(${$caf32827df861c4e$var$LABEL_ROTATION}, 0, -4)`).attr('fill', 'transparent').style('cursor', 'grab').on('mouseenter', showTip).on('mousemove', moveTip).on('mouseleave', hideTip);
|
|
11755
11967
|
const brush = $gXNCa$d3.brushY().extent([
|
|
11756
11968
|
[
|
|
11757
11969
|
-$caf32827df861c4e$var$BRUSH_WIDTH / 2,
|
|
@@ -11844,7 +12056,10 @@ const $caf32827df861c4e$var$ParallelCoordsPlot = ({ rows: rows, fields: fields,
|
|
|
11844
12056
|
scale,
|
|
11845
12057
|
onBrushChange,
|
|
11846
12058
|
onReorder,
|
|
11847
|
-
clearVersion
|
|
12059
|
+
clearVersion,
|
|
12060
|
+
axisLabels,
|
|
12061
|
+
size.w,
|
|
12062
|
+
size.h
|
|
11848
12063
|
]);
|
|
11849
12064
|
// Highlight the polyline matching the hovered row id without rebuilding the
|
|
11850
12065
|
// SVG. Raises the highlighted path so it draws above its neighbors.
|
|
@@ -11870,15 +12085,114 @@ const $caf32827df861c4e$var$ParallelCoordsPlot = ({ rows: rows, fields: fields,
|
|
|
11870
12085
|
children: "Select fields to plot."
|
|
11871
12086
|
})
|
|
11872
12087
|
});
|
|
11873
|
-
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.
|
|
12088
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
11874
12089
|
ref: containerRef,
|
|
11875
12090
|
className: "exprviz-pc-container",
|
|
11876
|
-
children:
|
|
11877
|
-
|
|
11878
|
-
|
|
11879
|
-
|
|
11880
|
-
|
|
11881
|
-
|
|
12091
|
+
children: [
|
|
12092
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("svg", {
|
|
12093
|
+
ref: svgRef,
|
|
12094
|
+
width: "100%",
|
|
12095
|
+
height: "100%",
|
|
12096
|
+
preserveAspectRatio: "none"
|
|
12097
|
+
}),
|
|
12098
|
+
tooltip && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)($caf32827df861c4e$var$AxisTooltip, {
|
|
12099
|
+
x: tooltip.x,
|
|
12100
|
+
y: tooltip.y,
|
|
12101
|
+
info: tooltip.info
|
|
12102
|
+
})
|
|
12103
|
+
]
|
|
12104
|
+
});
|
|
12105
|
+
};
|
|
12106
|
+
// Position-fixed so it can escape the plot pane's clipping. Offset slightly
|
|
12107
|
+
// from the cursor and clamped to the viewport so it never spills off-screen.
|
|
12108
|
+
const $caf32827df861c4e$var$AxisTooltip = ({ x: x, y: y, info: info })=>{
|
|
12109
|
+
const ref = (0, $gXNCa$react.useRef)(null);
|
|
12110
|
+
const [pos, setPos] = (0, $gXNCa$react.useState)({
|
|
12111
|
+
left: x + 12,
|
|
12112
|
+
top: y + 12
|
|
12113
|
+
});
|
|
12114
|
+
(0, $gXNCa$react.useEffect)(()=>{
|
|
12115
|
+
const el = ref.current;
|
|
12116
|
+
if (!el) return;
|
|
12117
|
+
const w = el.offsetWidth;
|
|
12118
|
+
const h = el.offsetHeight;
|
|
12119
|
+
const vw = window.innerWidth;
|
|
12120
|
+
const vh = window.innerHeight;
|
|
12121
|
+
let left = x + 12;
|
|
12122
|
+
let top = y + 12;
|
|
12123
|
+
if (left + w > vw - 4) left = Math.max(4, x - 12 - w);
|
|
12124
|
+
if (top + h > vh - 4) top = Math.max(4, y - 12 - h);
|
|
12125
|
+
setPos({
|
|
12126
|
+
left: left,
|
|
12127
|
+
top: top
|
|
12128
|
+
});
|
|
12129
|
+
}, [
|
|
12130
|
+
x,
|
|
12131
|
+
y,
|
|
12132
|
+
info
|
|
12133
|
+
]);
|
|
12134
|
+
const { studyTitle: studyTitle, group: group, factors: factors, characteristics: characteristics } = info;
|
|
12135
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
12136
|
+
ref: ref,
|
|
12137
|
+
className: "exprviz-pc-tooltip",
|
|
12138
|
+
style: pos,
|
|
12139
|
+
children: [
|
|
12140
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
12141
|
+
children: [
|
|
12142
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
12143
|
+
className: "exprviz-pc-tip-key",
|
|
12144
|
+
children: "Study:"
|
|
12145
|
+
}),
|
|
12146
|
+
" ",
|
|
12147
|
+
studyTitle,
|
|
12148
|
+
group ? ` (${group})` : ''
|
|
12149
|
+
]
|
|
12150
|
+
}),
|
|
12151
|
+
factors.length > 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactjsxruntime.Fragment), {
|
|
12152
|
+
children: [
|
|
12153
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
12154
|
+
className: "exprviz-pc-tip-section",
|
|
12155
|
+
children: "Factors"
|
|
12156
|
+
}),
|
|
12157
|
+
factors.map((p, i)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
12158
|
+
className: "exprviz-pc-tip-row",
|
|
12159
|
+
children: [
|
|
12160
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("span", {
|
|
12161
|
+
className: "exprviz-pc-tip-key",
|
|
12162
|
+
children: [
|
|
12163
|
+
p.name,
|
|
12164
|
+
":"
|
|
12165
|
+
]
|
|
12166
|
+
}),
|
|
12167
|
+
" ",
|
|
12168
|
+
p.value
|
|
12169
|
+
]
|
|
12170
|
+
}, `f-${i}`))
|
|
12171
|
+
]
|
|
12172
|
+
}),
|
|
12173
|
+
characteristics.length > 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactjsxruntime.Fragment), {
|
|
12174
|
+
children: [
|
|
12175
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
12176
|
+
className: "exprviz-pc-tip-section",
|
|
12177
|
+
children: "Characteristics"
|
|
12178
|
+
}),
|
|
12179
|
+
characteristics.map((p, i)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
12180
|
+
className: "exprviz-pc-tip-row",
|
|
12181
|
+
children: [
|
|
12182
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("span", {
|
|
12183
|
+
className: "exprviz-pc-tip-key",
|
|
12184
|
+
children: [
|
|
12185
|
+
p.name,
|
|
12186
|
+
":"
|
|
12187
|
+
]
|
|
12188
|
+
}),
|
|
12189
|
+
" ",
|
|
12190
|
+
p.value
|
|
12191
|
+
]
|
|
12192
|
+
}, `c-${i}`))
|
|
12193
|
+
]
|
|
12194
|
+
})
|
|
12195
|
+
]
|
|
11882
12196
|
});
|
|
11883
12197
|
};
|
|
11884
12198
|
var $caf32827df861c4e$export$2e2bcd8739ae039 = $caf32827df861c4e$var$ParallelCoordsPlot;
|
|
@@ -11975,6 +12289,75 @@ const $1fd2507769d5bd00$var$ExprVizViewCmp = (props)=>{
|
|
|
11975
12289
|
]
|
|
11976
12290
|
});
|
|
11977
12291
|
};
|
|
12292
|
+
// Compact axis labels for the parallel-coords plot. The raw Solr field name
|
|
12293
|
+
// (e.g. "E_CURD_148_g5__expr") is uninformative; we prefer the assay's
|
|
12294
|
+
// factor labels, falling back to "organism part" then to the group id.
|
|
12295
|
+
// `full` is exposed via an SVG <title> so the user can hover to see study,
|
|
12296
|
+
// group, and every factor/characteristic.
|
|
12297
|
+
const $1fd2507769d5bd00$var$AXIS_LABEL_MAX = 22;
|
|
12298
|
+
function $1fd2507769d5bd00$var$truncateLabel(s, n) {
|
|
12299
|
+
if (!s) return '';
|
|
12300
|
+
return s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
|
|
12301
|
+
}
|
|
12302
|
+
function $1fd2507769d5bd00$var$compactAssayLabel(assay, group) {
|
|
12303
|
+
if (!assay) return group || '';
|
|
12304
|
+
const factorVals = (assay.factor || []).map((f)=>f && f.label).filter(Boolean);
|
|
12305
|
+
if (factorVals.length) return factorVals.join('; ');
|
|
12306
|
+
const chars = assay.characteristic || [];
|
|
12307
|
+
const organ = chars.find((c)=>c && c.type === 'organism part');
|
|
12308
|
+
if (organ && organ.label) return organ.label;
|
|
12309
|
+
const firstChar = chars.find((c)=>c && c.label);
|
|
12310
|
+
if (firstChar) return firstChar.label;
|
|
12311
|
+
return group || '';
|
|
12312
|
+
}
|
|
12313
|
+
function $1fd2507769d5bd00$var$assayPairs(list) {
|
|
12314
|
+
return (list || []).filter((x)=>x && x.label).map((x)=>({
|
|
12315
|
+
name: x.type || '',
|
|
12316
|
+
value: x.label
|
|
12317
|
+
}));
|
|
12318
|
+
}
|
|
12319
|
+
function $1fd2507769d5bd00$var$buildAxisLabels(fields, studies, expressionSamples) {
|
|
12320
|
+
const labels = {};
|
|
12321
|
+
if (!fields) return labels;
|
|
12322
|
+
const studyById = {};
|
|
12323
|
+
(studies || []).forEach((s)=>{
|
|
12324
|
+
if (s && s._id) studyById[s._id] = s;
|
|
12325
|
+
});
|
|
12326
|
+
const findAssay = (studyId, group)=>{
|
|
12327
|
+
const arr = expressionSamples && expressionSamples[studyId];
|
|
12328
|
+
return arr ? arr.find((a)=>a.group === group) : null;
|
|
12329
|
+
};
|
|
12330
|
+
for (const f of fields){
|
|
12331
|
+
const m = f.match(/^(.+?)_g(\d+)__expr$/);
|
|
12332
|
+
if (!m) {
|
|
12333
|
+
labels[f] = {
|
|
12334
|
+
short: $1fd2507769d5bd00$var$truncateLabel(f.replace(/__expr$/, ''), $1fd2507769d5bd00$var$AXIS_LABEL_MAX),
|
|
12335
|
+
structured: {
|
|
12336
|
+
studyTitle: f,
|
|
12337
|
+
group: '',
|
|
12338
|
+
factors: [],
|
|
12339
|
+
characteristics: []
|
|
12340
|
+
}
|
|
12341
|
+
};
|
|
12342
|
+
continue;
|
|
12343
|
+
}
|
|
12344
|
+
const expId = m[1].replace(/_/g, '-');
|
|
12345
|
+
const group = 'g' + m[2];
|
|
12346
|
+
const assay = findAssay(expId, group);
|
|
12347
|
+
const study = studyById[expId];
|
|
12348
|
+
const studyName = study && study.description || expId;
|
|
12349
|
+
labels[f] = {
|
|
12350
|
+
short: $1fd2507769d5bd00$var$truncateLabel($1fd2507769d5bd00$var$compactAssayLabel(assay, group), $1fd2507769d5bd00$var$AXIS_LABEL_MAX),
|
|
12351
|
+
structured: {
|
|
12352
|
+
studyTitle: studyName,
|
|
12353
|
+
group: group,
|
|
12354
|
+
factors: $1fd2507769d5bd00$var$assayPairs(assay && assay.factor),
|
|
12355
|
+
characteristics: $1fd2507769d5bd00$var$assayPairs(assay && assay.characteristic)
|
|
12356
|
+
}
|
|
12357
|
+
};
|
|
12358
|
+
}
|
|
12359
|
+
return labels;
|
|
12360
|
+
}
|
|
11978
12361
|
function $1fd2507769d5bd00$var$rowMatchesSelections(row, selections) {
|
|
11979
12362
|
for (const f of Object.keys(selections)){
|
|
11980
12363
|
const v = row[f];
|
|
@@ -11998,20 +12381,50 @@ function $1fd2507769d5bd00$var$tsvCell(v) {
|
|
|
11998
12381
|
const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
|
11999
12382
|
return s.replace(/[\t\r\n]+/g, ' ');
|
|
12000
12383
|
}
|
|
12001
|
-
|
|
12384
|
+
// Mirror the on-screen table header in the TSV: one row per metadata level
|
|
12385
|
+
// the table is showing (Study, then one row per distinct factor type, then
|
|
12386
|
+
// one row per distinct characteristic type), followed by the leaf header
|
|
12387
|
+
// (Gene ID / Name / per-sample group). The first two columns are repurposed
|
|
12388
|
+
// to carry the row category and the row's specific name, matching the
|
|
12389
|
+
// pinned-column labels in ExprTable.
|
|
12390
|
+
function $1fd2507769d5bd00$var$downloadTsv(filename, rows, fields, studies, expressionSamples) {
|
|
12002
12391
|
const cols = [
|
|
12003
12392
|
'id',
|
|
12004
12393
|
'name',
|
|
12005
12394
|
...fields
|
|
12006
12395
|
];
|
|
12007
|
-
const
|
|
12008
|
-
|
|
12009
|
-
|
|
12010
|
-
|
|
12011
|
-
|
|
12012
|
-
|
|
12013
|
-
|
|
12014
|
-
|
|
12396
|
+
const fieldInfo = (0, $4ab64e76c1caef59$export$7b242440eb2c300d)(fields, studies, expressionSamples);
|
|
12397
|
+
const factorTypes = new Set();
|
|
12398
|
+
const charTypes = new Set();
|
|
12399
|
+
for (const f of fields){
|
|
12400
|
+
const info = fieldInfo[f];
|
|
12401
|
+
if (!info) continue;
|
|
12402
|
+
Object.keys(info.factors || {}).forEach((t)=>factorTypes.add(t));
|
|
12403
|
+
Object.keys(info.characteristics || {}).forEach((t)=>charTypes.add(t));
|
|
12404
|
+
}
|
|
12405
|
+
const factorTypeList = Array.from(factorTypes).sort();
|
|
12406
|
+
const charTypeList = Array.from(charTypes).sort();
|
|
12407
|
+
const metaRow = (cat, label, getValue)=>{
|
|
12408
|
+
const cells = [
|
|
12409
|
+
cat,
|
|
12410
|
+
label
|
|
12411
|
+
];
|
|
12412
|
+
for (const f of fields)cells.push($1fd2507769d5bd00$var$tsvCell(getValue(fieldInfo[f] || {})));
|
|
12413
|
+
return cells.join('\t');
|
|
12414
|
+
};
|
|
12415
|
+
const lines = [];
|
|
12416
|
+
lines.push(metaRow('Study', 'Title', (info)=>info.studyDescription || ''));
|
|
12417
|
+
for (const t of factorTypeList)lines.push(metaRow('Factor', t, (info)=>info.factors && info.factors[t] || ''));
|
|
12418
|
+
for (const t of charTypeList)lines.push(metaRow('Characteristic', t, (info)=>info.characteristics && info.characteristics[t] || ''));
|
|
12419
|
+
// Leaf header — column ids for the data rows.
|
|
12420
|
+
lines.push([
|
|
12421
|
+
'Gene ID',
|
|
12422
|
+
'Name',
|
|
12423
|
+
...fields.map((f)=>{
|
|
12424
|
+
const info = fieldInfo[f];
|
|
12425
|
+
return info && info.group || f.replace(/__expr$/, '');
|
|
12426
|
+
})
|
|
12427
|
+
].join('\t'));
|
|
12015
12428
|
for (const r of rows)lines.push(cols.map((c)=>$1fd2507769d5bd00$var$tsvCell(r[c])).join('\t'));
|
|
12016
12429
|
const blob = new Blob([
|
|
12017
12430
|
lines.join('\n') + '\n'
|
|
@@ -12038,6 +12451,34 @@ const $1fd2507769d5bd00$var$TaxonPanel = ({ taxon: taxon, studies: studies, expr
|
|
|
12038
12451
|
const [selections, setSelections] = (0, $gXNCa$react.useState)({});
|
|
12039
12452
|
const [clearVersion, setClearVersion] = (0, $gXNCa$react.useState)(0);
|
|
12040
12453
|
const [hoveredId, setHoveredId] = (0, $gXNCa$react.useState)(null);
|
|
12454
|
+
const [plotHeight, setPlotHeight] = (0, $gXNCa$react.useState)(320);
|
|
12455
|
+
const resizeStateRef = (0, $gXNCa$react.useRef)(null);
|
|
12456
|
+
// Drag the horizontal separator between the plot and the table to retune
|
|
12457
|
+
// their relative sizes. Bounded so neither pane disappears entirely.
|
|
12458
|
+
const startResize = (e)=>{
|
|
12459
|
+
e.preventDefault();
|
|
12460
|
+
resizeStateRef.current = {
|
|
12461
|
+
startY: e.clientY,
|
|
12462
|
+
startHeight: plotHeight
|
|
12463
|
+
};
|
|
12464
|
+
const onMove = (ev)=>{
|
|
12465
|
+
const s = resizeStateRef.current;
|
|
12466
|
+
if (!s) return;
|
|
12467
|
+
const next = Math.max(120, Math.min(1200, s.startHeight + (ev.clientY - s.startY)));
|
|
12468
|
+
setPlotHeight(next);
|
|
12469
|
+
};
|
|
12470
|
+
const onUp = ()=>{
|
|
12471
|
+
resizeStateRef.current = null;
|
|
12472
|
+
document.removeEventListener('mousemove', onMove);
|
|
12473
|
+
document.removeEventListener('mouseup', onUp);
|
|
12474
|
+
document.body.style.cursor = '';
|
|
12475
|
+
document.body.style.userSelect = '';
|
|
12476
|
+
};
|
|
12477
|
+
document.body.style.cursor = 'row-resize';
|
|
12478
|
+
document.body.style.userSelect = 'none';
|
|
12479
|
+
document.addEventListener('mousemove', onMove);
|
|
12480
|
+
document.addEventListener('mouseup', onUp);
|
|
12481
|
+
};
|
|
12041
12482
|
const hasBrush = Object.keys(selections).length > 0;
|
|
12042
12483
|
const filteredRows = (0, $gXNCa$react.useMemo)(()=>{
|
|
12043
12484
|
if (!hasBrush) return rows;
|
|
@@ -12068,6 +12509,11 @@ const $1fd2507769d5bd00$var$TaxonPanel = ({ taxon: taxon, studies: studies, expr
|
|
|
12068
12509
|
...hidden
|
|
12069
12510
|
]);
|
|
12070
12511
|
} : undefined;
|
|
12512
|
+
const axisLabels = (0, $gXNCa$react.useMemo)(()=>$1fd2507769d5bd00$var$buildAxisLabels(visibleFields, studies, expressionSamples), [
|
|
12513
|
+
visibleFields,
|
|
12514
|
+
studies,
|
|
12515
|
+
expressionSamples
|
|
12516
|
+
]);
|
|
12071
12517
|
(0, $gXNCa$react.useEffect)(()=>{
|
|
12072
12518
|
if (rows.length === 0 && hasBrush) {
|
|
12073
12519
|
setSelections({});
|
|
@@ -12136,7 +12582,7 @@ const $1fd2507769d5bd00$var$TaxonPanel = ({ taxon: taxon, studies: studies, expr
|
|
|
12136
12582
|
size: "sm",
|
|
12137
12583
|
variant: "outline-secondary",
|
|
12138
12584
|
disabled: filteredRows.length === 0 || visibleFields.length === 0,
|
|
12139
|
-
onClick: ()=>$1fd2507769d5bd00$var$downloadTsv(`expression_${taxon}.tsv`, filteredRows, visibleFields),
|
|
12585
|
+
onClick: ()=>$1fd2507769d5bd00$var$downloadTsv(`expression_${taxon}.tsv`, filteredRows, visibleFields, studies, expressionSamples),
|
|
12140
12586
|
title: "Download the visible rows and columns as tab-delimited text",
|
|
12141
12587
|
children: "Download TSV"
|
|
12142
12588
|
}),
|
|
@@ -12175,6 +12621,9 @@ const $1fd2507769d5bd00$var$TaxonPanel = ({ taxon: taxon, studies: studies, expr
|
|
|
12175
12621
|
children: [
|
|
12176
12622
|
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
12177
12623
|
className: "exprviz-plot",
|
|
12624
|
+
style: {
|
|
12625
|
+
height: plotHeight
|
|
12626
|
+
},
|
|
12178
12627
|
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $caf32827df861c4e$export$2e2bcd8739ae039), {
|
|
12179
12628
|
rows: rows,
|
|
12180
12629
|
fields: visibleFields,
|
|
@@ -12182,9 +12631,18 @@ const $1fd2507769d5bd00$var$TaxonPanel = ({ taxon: taxon, studies: studies, expr
|
|
|
12182
12631
|
onBrushChange: setSelections,
|
|
12183
12632
|
onReorder: handleReorder,
|
|
12184
12633
|
clearVersion: clearVersion,
|
|
12185
|
-
hoveredId: hoveredId
|
|
12634
|
+
hoveredId: hoveredId,
|
|
12635
|
+
axisLabels: axisLabels
|
|
12186
12636
|
})
|
|
12187
12637
|
}),
|
|
12638
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
12639
|
+
className: "exprviz-resizer",
|
|
12640
|
+
role: "separator",
|
|
12641
|
+
"aria-orientation": "horizontal",
|
|
12642
|
+
"aria-label": "Resize plot",
|
|
12643
|
+
onMouseDown: startResize,
|
|
12644
|
+
title: "Drag to resize"
|
|
12645
|
+
}),
|
|
12188
12646
|
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
12189
12647
|
className: "exprviz-table",
|
|
12190
12648
|
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $4ab64e76c1caef59$export$2e2bcd8739ae039), {
|