gramene-search 2.36.1 → 2.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -5
- package/dist/index.css +25 -0
- package/dist/index.css.map +1 -1
- package/dist/index.js +325 -55
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -5236,16 +5236,17 @@ const $4b8473139e48e77e$export$705aff68da3ddba0 = [
|
|
|
5236
5236
|
// Paging mirrors exprViz's doFetchExprVizData: a recursive fetchPage(offset)
|
|
5237
5237
|
// with a request-id guard so a superseded fetch can never write stale rows.
|
|
5238
5238
|
//
|
|
5239
|
-
// The offered columns are deliberately limited to
|
|
5240
|
-
// Core identifiers
|
|
5241
|
-
// front. That keeps the column picker a pure
|
|
5242
|
-
// column is instant and never triggers a refetch.
|
|
5239
|
+
// The offered columns are deliberately limited to a few field-catalog groups —
|
|
5240
|
+
// Core identifiers, Expression attributes and MAKER transcript metrics — and
|
|
5241
|
+
// *all* of them are fetched up front. That keeps the column picker a pure
|
|
5242
|
+
// visibility control: toggling a column is instant and never triggers a refetch.
|
|
5243
5243
|
const $12feb7ca48271e17$var$PAGE_SIZE = 1000;
|
|
5244
5244
|
const $12feb7ca48271e17$var$MAX_GENES = 5000;
|
|
5245
5245
|
// The catalog groups whose fields the column picker offers.
|
|
5246
5246
|
const $12feb7ca48271e17$export$53e90de8d659db95 = [
|
|
5247
5247
|
'core',
|
|
5248
|
-
'exprattrs'
|
|
5248
|
+
'exprattrs',
|
|
5249
|
+
'MAKER'
|
|
5249
5250
|
];
|
|
5250
5251
|
// The 'core' group's fields (bundles/../fieldCatalog.overlay.json).
|
|
5251
5252
|
const $12feb7ca48271e17$export$9d77d82cc773e55e = [
|
|
@@ -5260,12 +5261,28 @@ const $12feb7ca48271e17$export$9d77d82cc773e55e = [
|
|
|
5260
5261
|
'taxon_id',
|
|
5261
5262
|
'db_type'
|
|
5262
5263
|
];
|
|
5264
|
+
// The 'MAKER' group's fields: MAKER's annotation quality metrics (AED and the
|
|
5265
|
+
// quality index QI1-QI9). Only some releases have them (e.g. SorghumBase); Solr
|
|
5266
|
+
// returns nothing for them elsewhere, and the picker then hides the empty group.
|
|
5267
|
+
const $12feb7ca48271e17$export$3ae897f4a2495276 = [
|
|
5268
|
+
'MAKER__AED__attr_f',
|
|
5269
|
+
'MAKER__QI1__attr_i',
|
|
5270
|
+
'MAKER__QI2__attr_f',
|
|
5271
|
+
'MAKER__QI3__attr_f',
|
|
5272
|
+
'MAKER__QI4__attr_f',
|
|
5273
|
+
'MAKER__QI5__attr_f',
|
|
5274
|
+
'MAKER__QI6__attr_f',
|
|
5275
|
+
'MAKER__QI7__attr_i',
|
|
5276
|
+
'MAKER__QI8__attr_i',
|
|
5277
|
+
'MAKER__QI9__attr_i'
|
|
5278
|
+
];
|
|
5263
5279
|
// Always fetched, so visibility toggles never need the network. taxon_id is
|
|
5264
5280
|
// needed by the genome filter even when its column is hidden.
|
|
5265
5281
|
const $12feb7ca48271e17$var$FETCH_FIELDS = [
|
|
5266
5282
|
...new Set([
|
|
5267
5283
|
...$12feb7ca48271e17$export$9d77d82cc773e55e,
|
|
5268
|
-
...(0, $4b8473139e48e77e$export$705aff68da3ddba0)
|
|
5284
|
+
...(0, $4b8473139e48e77e$export$705aff68da3ddba0),
|
|
5285
|
+
...$12feb7ca48271e17$export$3ae897f4a2495276
|
|
5269
5286
|
])
|
|
5270
5287
|
];
|
|
5271
5288
|
// Default columns, in display order. The two stress fields are adjacent so the
|
|
@@ -8158,39 +8175,43 @@ const $9e29a4f60318db7a$var$editSearch = (url, edit)=>{
|
|
|
8158
8175
|
// (genes/<id>) that only some instances redirect to EBI, and atlasUrl itself is
|
|
8159
8176
|
// not a browsable page, so point the heatmap's links at EBI's Expression Atlas.
|
|
8160
8177
|
// JGI studies link to Phytozome, which takes no geneQuery. undefined keeps the
|
|
8161
|
-
// heatmap's own URL.
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
|
|
8178
|
+
// heatmap's own URL, null drops the link. isJgiExperiment(accession) tells the
|
|
8179
|
+
// JGI studies apart: they have no full data download (their payload's download
|
|
8180
|
+
// URL is Phytozome's page for the genome), so the Download dialog of a Paralogs
|
|
8181
|
+
// heatmap drawn from one leaves out "Full experiment data on Expression Atlas".
|
|
8182
|
+
const $9e29a4f60318db7a$var$isJgiUrl = (url)=>/^https?:\/\/([^/]+\.)?jgi\.doe\.gov\//i.test(url);
|
|
8183
|
+
const $9e29a4f60318db7a$var$makeResolveUrl = (isJgiExperiment)=>(kind, url, context)=>{
|
|
8184
|
+
const genes = $9e29a4f60318db7a$var$genesOf(context);
|
|
8185
|
+
const withGenes = (params)=>{
|
|
8186
|
+
if (genes.length) params.set('geneQuery', JSON.stringify(genes.map((value)=>({
|
|
8187
|
+
value: value
|
|
8188
|
+
}))));
|
|
8189
|
+
};
|
|
8190
|
+
switch(kind){
|
|
8191
|
+
case 'row':
|
|
8192
|
+
{
|
|
8193
|
+
const uri = context.row && context.row.uri;
|
|
8194
|
+
return uri && !/^https?:/i.test(uri) ? new URL(uri, $9e29a4f60318db7a$var$EBI_GXA).href : undefined;
|
|
8195
|
+
}
|
|
8196
|
+
case 'atlas':
|
|
8197
|
+
return $9e29a4f60318db7a$var$EBI_GXA;
|
|
8198
|
+
case 'experiment':
|
|
8199
|
+
return $9e29a4f60318db7a$var$isEbiUrl(url) ? $9e29a4f60318db7a$var$editSearch(url, withGenes) : undefined;
|
|
8200
|
+
case 'moreInformation':
|
|
8201
|
+
if (context.experiment) return $9e29a4f60318db7a$var$isEbiUrl(url) ? $9e29a4f60318db7a$var$editSearch(url, withGenes) : undefined;
|
|
8202
|
+
return genes.length ? `${$9e29a4f60318db7a$var$EBI_GXA}genes/${encodeURIComponent(genes[0])}` : undefined;
|
|
8203
|
+
case 'download':
|
|
8204
|
+
if (isJgiExperiment(context.experiment) || $9e29a4f60318db7a$var$isJgiUrl(url)) return null;
|
|
8205
|
+
return $9e29a4f60318db7a$var$editSearch(url, (params)=>params.delete('geneQuery'));
|
|
8206
|
+
default:
|
|
8207
|
+
return undefined;
|
|
8208
|
+
}
|
|
8168
8209
|
};
|
|
8169
|
-
switch(kind){
|
|
8170
|
-
case 'row':
|
|
8171
|
-
{
|
|
8172
|
-
const uri = context.row && context.row.uri;
|
|
8173
|
-
return uri && !/^https?:/i.test(uri) ? new URL(uri, $9e29a4f60318db7a$var$EBI_GXA).href : undefined;
|
|
8174
|
-
}
|
|
8175
|
-
case 'atlas':
|
|
8176
|
-
return $9e29a4f60318db7a$var$EBI_GXA;
|
|
8177
|
-
case 'experiment':
|
|
8178
|
-
return $9e29a4f60318db7a$var$isEbiUrl(url) ? $9e29a4f60318db7a$var$editSearch(url, withGenes) : undefined;
|
|
8179
|
-
case 'moreInformation':
|
|
8180
|
-
if (context.experiment) return $9e29a4f60318db7a$var$isEbiUrl(url) ? $9e29a4f60318db7a$var$editSearch(url, withGenes) : undefined;
|
|
8181
|
-
return genes.length ? `${$9e29a4f60318db7a$var$EBI_GXA}genes/${encodeURIComponent(genes[0])}` : undefined;
|
|
8182
|
-
case 'download':
|
|
8183
|
-
return $9e29a4f60318db7a$var$editSearch(url, (params)=>params.delete('geneQuery'));
|
|
8184
|
-
default:
|
|
8185
|
-
return undefined;
|
|
8186
|
-
}
|
|
8187
|
-
};
|
|
8188
8210
|
const $9e29a4f60318db7a$var$HEATMAP_OPTIONS = {
|
|
8189
8211
|
showAnatomogram: true,
|
|
8190
8212
|
isWidget: true,
|
|
8191
8213
|
showControlMenu: true,
|
|
8192
|
-
linkTarget: '_blank'
|
|
8193
|
-
resolveUrl: $9e29a4f60318db7a$var$resolveUrl
|
|
8214
|
+
linkTarget: '_blank'
|
|
8194
8215
|
};
|
|
8195
8216
|
const $9e29a4f60318db7a$var$studyLabel = (e)=>`${e.type}:${e.description || e._id}`;
|
|
8196
8217
|
const $9e29a4f60318db7a$var$jgiStudyLabel = (e)=>e.description ? `${e.name || e._id}: ${e.description}` : e.name || e._id;
|
|
@@ -8326,6 +8347,13 @@ const $9e29a4f60318db7a$var$Detail = (props)=>{
|
|
|
8326
8347
|
}, [
|
|
8327
8348
|
allJgiKey
|
|
8328
8349
|
]);
|
|
8350
|
+
// A new resolveUrl does not redraw a heatmap: it asks the latest one for its links.
|
|
8351
|
+
const resolveUrl = (0, $gXNCa$react.useMemo)(()=>{
|
|
8352
|
+
const accessions = new Set(allJgiStudies.map((e)=>e._id));
|
|
8353
|
+
return $9e29a4f60318db7a$var$makeResolveUrl((accession)=>accessions.has(accession));
|
|
8354
|
+
}, [
|
|
8355
|
+
allJgiKey
|
|
8356
|
+
]);
|
|
8329
8357
|
// The EBI Studies heatmap waits for the studies list, which names the JGI
|
|
8330
8358
|
// studies its filter drops: drawn before the list is in (a first visit; the
|
|
8331
8359
|
// list is persisted), it would show the JGI rows and then redraw without
|
|
@@ -8388,9 +8416,11 @@ const $9e29a4f60318db7a$var$Detail = (props)=>{
|
|
|
8388
8416
|
}),
|
|
8389
8417
|
activeTab === "paralogs" && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$grameneatlasheatmap.ExpressionAtlasHeatmap), {
|
|
8390
8418
|
...$9e29a4f60318db7a$var$HEATMAP_OPTIONS,
|
|
8419
|
+
resolveUrl: resolveUrl,
|
|
8391
8420
|
atlasUrl: atlasUrl,
|
|
8392
8421
|
query: paralogsQuery,
|
|
8393
|
-
experiment: atlasExperiment
|
|
8422
|
+
experiment: atlasExperiment,
|
|
8423
|
+
downloadFileName: `${geneQuery}-paralogs-${atlasExperiment}`
|
|
8394
8424
|
}, `${atlasUrl} ${atlasExperiment} ${paralogQuery}`)
|
|
8395
8425
|
]
|
|
8396
8426
|
}, "gxaparalogs"),
|
|
@@ -8406,10 +8436,12 @@ const $9e29a4f60318db7a$var$Detail = (props)=>{
|
|
|
8406
8436
|
}),
|
|
8407
8437
|
activeTab === "gene" && studiesSettled && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$grameneatlasheatmap.ExpressionAtlasHeatmap), {
|
|
8408
8438
|
...$9e29a4f60318db7a$var$HEATMAP_OPTIONS,
|
|
8439
|
+
resolveUrl: resolveUrl,
|
|
8409
8440
|
atlasUrl: atlasUrl,
|
|
8410
8441
|
query: allStudiesQuery,
|
|
8411
8442
|
experiment: false,
|
|
8412
|
-
filterRows: filterRows
|
|
8443
|
+
filterRows: filterRows,
|
|
8444
|
+
downloadFileName: `${geneQuery}-ebi-studies`
|
|
8413
8445
|
}, `${atlasUrl} all ${geneQuery}`)
|
|
8414
8446
|
]
|
|
8415
8447
|
}, "gxa"),
|
|
@@ -8453,8 +8485,9 @@ const $9e29a4f60318db7a$var$Detail = (props)=>{
|
|
|
8453
8485
|
rowFactor: jgiAxesOfStudy.rowFactor,
|
|
8454
8486
|
columnFactor: jgiAxesOfStudy.columnFactor,
|
|
8455
8487
|
onChangeFactors: setJgiAxes,
|
|
8488
|
+
downloadFileName: `${geneQuery}-${jgiExperiment}`,
|
|
8456
8489
|
linkTarget: "_blank",
|
|
8457
|
-
resolveUrl:
|
|
8490
|
+
resolveUrl: resolveUrl
|
|
8458
8491
|
}, `${atlasUrl} ${jgiExperiment} ${geneQuery}`)
|
|
8459
8492
|
]
|
|
8460
8493
|
}, "jgi"),
|
|
@@ -13300,6 +13333,7 @@ function $76e15aea9236bd47$var$findRoots(tax) {
|
|
|
13300
13333
|
}
|
|
13301
13334
|
// Post-order: attach .leafIds (taxon_ids of maps in subtree) and .isLeaf
|
|
13302
13335
|
function $76e15aea9236bd47$var$annotate(node, tax, maps) {
|
|
13336
|
+
maps = maps || {};
|
|
13303
13337
|
const leafIds = [];
|
|
13304
13338
|
if (maps[node._id]) {
|
|
13305
13339
|
node.isLeaf = true;
|
|
@@ -13313,9 +13347,20 @@ function $76e15aea9236bd47$var$annotate(node, tax, maps) {
|
|
|
13313
13347
|
});
|
|
13314
13348
|
node.leafIds = leafIds;
|
|
13315
13349
|
}
|
|
13350
|
+
// The taxonomy's root nodes, annotated with the genomes (maps) under them. The
|
|
13351
|
+
// modal is mounted with the search status, so the taxonomy and the maps can
|
|
13352
|
+
// arrive in either order: missing maps count as no genomes, and the roots are
|
|
13353
|
+
// rebuilt when either changes.
|
|
13354
|
+
function $76e15aea9236bd47$var$buildRoots({ grameneMaps: grameneMaps, grameneTaxonomy: grameneTaxonomy }) {
|
|
13355
|
+
if (!grameneTaxonomy) return [];
|
|
13356
|
+
const roots = $76e15aea9236bd47$var$findRoots(grameneTaxonomy);
|
|
13357
|
+
roots.forEach((r)=>$76e15aea9236bd47$var$annotate(r, grameneTaxonomy, grameneMaps || {}));
|
|
13358
|
+
return roots;
|
|
13359
|
+
}
|
|
13316
13360
|
// Walk down the spine through single-child internals until a branch point,
|
|
13317
13361
|
// leaf, or node that is itself a genome.
|
|
13318
13362
|
function $76e15aea9236bd47$var$compressChain(node, tax, maps) {
|
|
13363
|
+
maps = maps || {};
|
|
13319
13364
|
const chain = [
|
|
13320
13365
|
node
|
|
13321
13366
|
];
|
|
@@ -13467,17 +13512,11 @@ function $76e15aea9236bd47$var$computeOpenState(props) {
|
|
|
13467
13512
|
class $76e15aea9236bd47$var$TaxonomyModal extends (0, ($parcel$interopDefault($gXNCa$react))).Component {
|
|
13468
13513
|
constructor(props){
|
|
13469
13514
|
super(props);
|
|
13470
|
-
const { grameneMaps: grameneMaps, grameneTaxonomy: grameneTaxonomy } = props;
|
|
13471
|
-
let roots = [];
|
|
13472
|
-
if (grameneTaxonomy) {
|
|
13473
|
-
roots = $76e15aea9236bd47$var$findRoots(grameneTaxonomy);
|
|
13474
|
-
roots.forEach((r)=>$76e15aea9236bd47$var$annotate(r, grameneTaxonomy, grameneMaps));
|
|
13475
|
-
}
|
|
13476
13515
|
const { selected: selected, expanded: expanded } = $76e15aea9236bd47$var$computeOpenState(props);
|
|
13477
13516
|
this.state = {
|
|
13478
13517
|
selected: selected,
|
|
13479
13518
|
expanded: expanded,
|
|
13480
|
-
roots:
|
|
13519
|
+
roots: $76e15aea9236bd47$var$buildRoots(props),
|
|
13481
13520
|
query: '',
|
|
13482
13521
|
highlightId: null,
|
|
13483
13522
|
activeIdx: 0
|
|
@@ -13522,6 +13561,16 @@ class $76e15aea9236bd47$var$TaxonomyModal extends (0, ($parcel$interopDefault($g
|
|
|
13522
13561
|
}
|
|
13523
13562
|
}
|
|
13524
13563
|
componentDidUpdate(prevProps, prevState) {
|
|
13564
|
+
// Maps or taxonomy arrived (or changed): rebuild the tree and reseed the
|
|
13565
|
+
// selection, which was empty if the modal mounted before the maps loaded.
|
|
13566
|
+
if (prevProps.grameneMaps !== this.props.grameneMaps || prevProps.grameneTaxonomy !== this.props.grameneTaxonomy) {
|
|
13567
|
+
const { selected: selected, expanded: expanded } = $76e15aea9236bd47$var$computeOpenState(this.props);
|
|
13568
|
+
this.setState({
|
|
13569
|
+
roots: $76e15aea9236bd47$var$buildRoots(this.props),
|
|
13570
|
+
selected: selected,
|
|
13571
|
+
expanded: expanded
|
|
13572
|
+
});
|
|
13573
|
+
}
|
|
13525
13574
|
// On each hidden→shown transition, reseed selection from current active
|
|
13526
13575
|
// genomes and expand every ancestor so selected taxa are visible.
|
|
13527
13576
|
const wasShown = prevProps.grameneGenomes && prevProps.grameneGenomes.show;
|
|
@@ -13593,7 +13642,7 @@ class $76e15aea9236bd47$var$TaxonomyModal extends (0, ($parcel$interopDefault($g
|
|
|
13593
13642
|
}
|
|
13594
13643
|
selectAll() {
|
|
13595
13644
|
const s = new Set();
|
|
13596
|
-
Object.values(this.props.grameneMaps).forEach((m)=>{
|
|
13645
|
+
Object.values(this.props.grameneMaps || {}).forEach((m)=>{
|
|
13597
13646
|
if (!m.hidden) s.add(m.taxon_id);
|
|
13598
13647
|
});
|
|
13599
13648
|
this.setState({
|
|
@@ -13610,7 +13659,7 @@ class $76e15aea9236bd47$var$TaxonomyModal extends (0, ($parcel$interopDefault($g
|
|
|
13610
13659
|
this.state.selected.forEach((id)=>{
|
|
13611
13660
|
active[id] = true;
|
|
13612
13661
|
});
|
|
13613
|
-
if (Object.keys(active).length === 0) Object.values(this.props.grameneMaps).forEach((m)=>{
|
|
13662
|
+
if (Object.keys(active).length === 0) Object.values(this.props.grameneMaps || {}).forEach((m)=>{
|
|
13614
13663
|
if (!m.hidden) active[m.taxon_id] = true;
|
|
13615
13664
|
});
|
|
13616
13665
|
this.props.doUpdateGrameneGenomes(active);
|
|
@@ -13628,7 +13677,7 @@ class $76e15aea9236bd47$var$TaxonomyModal extends (0, ($parcel$interopDefault($g
|
|
|
13628
13677
|
const someOn = selCount > 0 && !allOn;
|
|
13629
13678
|
const visibleKids = (terminal.children || []).map((cid)=>grameneTaxonomy[cid]).filter((c)=>c && c.leafIds.length > 0);
|
|
13630
13679
|
const hasKids = visibleKids.length > 0;
|
|
13631
|
-
const terminalIsGenome = !!grameneMaps[terminal._id];
|
|
13680
|
+
const terminalIsGenome = !!(grameneMaps && grameneMaps[terminal._id]);
|
|
13632
13681
|
const isOpen = expanded.has(terminal._id);
|
|
13633
13682
|
// Deepest clade colour in the chain wins; falls back to inherited.
|
|
13634
13683
|
const color = chain.reduce((acc, n)=>$76e15aea9236bd47$var$CLADE_COLORS[n.name] || acc, inheritedColor);
|
|
@@ -18689,6 +18738,83 @@ const $8f8e530286a37ce9$var$WIDTHS = {
|
|
|
18689
18738
|
expr_enhanced_in__attr_ss: 150,
|
|
18690
18739
|
expr_high_in__attr_ss: 150
|
|
18691
18740
|
};
|
|
18741
|
+
// MAKER annotation metrics: AED and the QI fractions are 0-1 (2 decimals); the
|
|
18742
|
+
// other QIs are lengths or counts (integers).
|
|
18743
|
+
const $8f8e530286a37ce9$var$MAKER_SET = new Set((0, $12feb7ca48271e17$export$3ae897f4a2495276));
|
|
18744
|
+
const $8f8e530286a37ce9$var$fmtMaker = (f, v)=>{
|
|
18745
|
+
if (v == null || v === '' || !Number.isFinite(+v)) return '';
|
|
18746
|
+
return f.endsWith('__attr_f') ? (+v).toFixed(2) : String(Math.round(+v));
|
|
18747
|
+
};
|
|
18748
|
+
const $8f8e530286a37ce9$var$makerShortName = (f)=>f.replace(/^MAKER__/, '').replace(/__attr_[a-z]$/, '');
|
|
18749
|
+
const $8f8e530286a37ce9$var$makerTooltip = (f)=>f === 'MAKER__AED__attr_f' ? 'MAKER Annotation Edit Distance: 0 = fully supported by evidence, 1 = no support' : `MAKER quality index ${$8f8e530286a37ce9$var$makerShortName(f)}`;
|
|
18750
|
+
// Summary-statistics rows, pinned above the genes. The header row collapses the
|
|
18751
|
+
// rest; stats cover every loaded gene, for each visible column whose values are
|
|
18752
|
+
// all numbers (taxon_id is an identifier, not a measurement).
|
|
18753
|
+
const $8f8e530286a37ce9$var$STAT_ROWS = [
|
|
18754
|
+
{
|
|
18755
|
+
key: 'n',
|
|
18756
|
+
label: 'N'
|
|
18757
|
+
},
|
|
18758
|
+
{
|
|
18759
|
+
key: 'min',
|
|
18760
|
+
label: 'Min'
|
|
18761
|
+
},
|
|
18762
|
+
{
|
|
18763
|
+
key: 'max',
|
|
18764
|
+
label: 'Max'
|
|
18765
|
+
},
|
|
18766
|
+
{
|
|
18767
|
+
key: 'median',
|
|
18768
|
+
label: 'Median'
|
|
18769
|
+
},
|
|
18770
|
+
{
|
|
18771
|
+
key: 'mean',
|
|
18772
|
+
label: 'Mean'
|
|
18773
|
+
},
|
|
18774
|
+
{
|
|
18775
|
+
key: 'sd',
|
|
18776
|
+
label: 'Std dev'
|
|
18777
|
+
}
|
|
18778
|
+
];
|
|
18779
|
+
const $8f8e530286a37ce9$var$NOT_MEASUREMENTS = new Set([
|
|
18780
|
+
'taxon_id'
|
|
18781
|
+
]);
|
|
18782
|
+
const $8f8e530286a37ce9$var$numericFieldsOf = (rows, fields)=>fields.filter((f)=>{
|
|
18783
|
+
if ($8f8e530286a37ce9$var$NOT_MEASUREMENTS.has(f)) return false;
|
|
18784
|
+
let numbers = 0;
|
|
18785
|
+
for (const r of rows){
|
|
18786
|
+
const v = r[f];
|
|
18787
|
+
if (v == null || v === '') continue;
|
|
18788
|
+
if (typeof v !== 'number' || !Number.isFinite(v)) return false;
|
|
18789
|
+
numbers++;
|
|
18790
|
+
}
|
|
18791
|
+
return numbers > 0;
|
|
18792
|
+
});
|
|
18793
|
+
const $8f8e530286a37ce9$var$statsOf = (values)=>{
|
|
18794
|
+
const n = values.length;
|
|
18795
|
+
if (!n) return {
|
|
18796
|
+
n: 0
|
|
18797
|
+
};
|
|
18798
|
+
const sorted = values.slice().sort((a, b)=>a - b);
|
|
18799
|
+
const mean = values.reduce((a, b)=>a + b, 0) / n;
|
|
18800
|
+
const median = n % 2 ? sorted[(n - 1) / 2] : (sorted[n / 2 - 1] + sorted[n / 2]) / 2;
|
|
18801
|
+
const sd = n > 1 ? Math.sqrt(values.reduce((a, v)=>a + (v - mean) * (v - mean), 0) / (n - 1)) : null;
|
|
18802
|
+
return {
|
|
18803
|
+
n: n,
|
|
18804
|
+
min: sorted[0],
|
|
18805
|
+
max: sorted[n - 1],
|
|
18806
|
+
median: median,
|
|
18807
|
+
mean: mean,
|
|
18808
|
+
sd: sd
|
|
18809
|
+
};
|
|
18810
|
+
};
|
|
18811
|
+
// Mean and std dev of a whole-number column still need decimals.
|
|
18812
|
+
const $8f8e530286a37ce9$var$fmtDecimal = (v)=>{
|
|
18813
|
+
if (v == null || !Number.isFinite(v)) return '';
|
|
18814
|
+
const a = Math.abs(v);
|
|
18815
|
+
return a !== 0 && (a < 0.01 || a >= 1e6) ? v.toPrecision(3) : v.toFixed(2);
|
|
18816
|
+
};
|
|
18817
|
+
const $8f8e530286a37ce9$var$isIntegerField = (f, rows)=>f.endsWith('__attr_i') || rows.every((r)=>r[f] == null || Number.isInteger(r[f]));
|
|
18692
18818
|
const $8f8e530286a37ce9$var$joinValues = (p)=>Array.isArray(p.value) ? p.value.map((v)=>String(v).replace(/_/g, ' ')).join(', ') : p.value ?? '';
|
|
18693
18819
|
const $8f8e530286a37ce9$var$StressChip = ({ c: c, dir: dir })=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
18694
18820
|
style: {
|
|
@@ -18759,6 +18885,7 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
|
|
|
18759
18885
|
const [fieldQuery, setFieldQuery] = (0, $gXNCa$react.useState)('');
|
|
18760
18886
|
const [popover, setPopover] = (0, $gXNCa$react.useState)(null);
|
|
18761
18887
|
const popRef = (0, $gXNCa$react.useRef)(null);
|
|
18888
|
+
const [statsOpen, setStatsOpen] = (0, $gXNCa$react.useState)(true);
|
|
18762
18889
|
const { docs: docs, total: total, truncated: truncated, status: status, error: error, visibleFields: visibleFields } = attrTable;
|
|
18763
18890
|
const visibleSet = (0, $gXNCa$react.useMemo)(()=>new Set(visibleFields), [
|
|
18764
18891
|
visibleFields
|
|
@@ -18793,15 +18920,13 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
|
|
|
18793
18920
|
}, [
|
|
18794
18921
|
docs
|
|
18795
18922
|
]);
|
|
18796
|
-
// Only the
|
|
18923
|
+
// Only the groups the bundle fetches are offered (Core identifiers, Expression
|
|
18924
|
+
// attributes, MAKER transcript metrics); the catalog drops groups a site lacks.
|
|
18797
18925
|
const pickerCatalog = (0, $gXNCa$react.useMemo)(()=>{
|
|
18798
18926
|
if (!fieldCatalog || !fieldCatalog.groups) return null;
|
|
18799
18927
|
return {
|
|
18800
18928
|
...fieldCatalog,
|
|
18801
|
-
groups: fieldCatalog.groups.filter((g)=>
|
|
18802
|
-
'core',
|
|
18803
|
-
'exprattrs'
|
|
18804
|
-
].includes(g.id))
|
|
18929
|
+
groups: fieldCatalog.groups.filter((g)=>(0, $12feb7ca48271e17$export$53e90de8d659db95).includes(g.id))
|
|
18805
18930
|
};
|
|
18806
18931
|
}, [
|
|
18807
18932
|
fieldCatalog
|
|
@@ -18828,10 +18953,11 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
|
|
|
18828
18953
|
headerTooltip: (0, $4b8473139e48e77e$export$50c1f966fd3af83e)(o),
|
|
18829
18954
|
width: 22,
|
|
18830
18955
|
minWidth: 18,
|
|
18831
|
-
valueGetter: (p)=>p.data && p.data._organ[o] || '',
|
|
18956
|
+
valueGetter: (p)=>p.data && p.data._organ && p.data._organ[o] || '',
|
|
18832
18957
|
valueFormatter: ()=>'',
|
|
18833
18958
|
tooltipValueGetter: (p)=>{
|
|
18834
|
-
|
|
18959
|
+
if (p.node && p.node.rowPinned) return undefined;
|
|
18960
|
+
const lvl = p.data && p.data._organ && p.data._organ[o];
|
|
18835
18961
|
if (!lvl) return `${(0, $4b8473139e48e77e$export$50c1f966fd3af83e)(o)}: not assayed`;
|
|
18836
18962
|
const sp = p.data._specific.has(o) ? " \xb7 specific" : p.data._enhanced.has(o) ? " \xb7 enhanced" : '';
|
|
18837
18963
|
return `${(0, $4b8473139e48e77e$export$50c1f966fd3af83e)(o)}: ${(0, $4b8473139e48e77e$export$433053e2c141d410)[lvl] || lvl}${sp}`;
|
|
@@ -18884,6 +19010,18 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
|
|
|
18884
19010
|
});
|
|
18885
19011
|
return;
|
|
18886
19012
|
}
|
|
19013
|
+
if ($8f8e530286a37ce9$var$MAKER_SET.has(f)) {
|
|
19014
|
+
cols.push({
|
|
19015
|
+
colId: f,
|
|
19016
|
+
field: f,
|
|
19017
|
+
headerName: labelOf(f),
|
|
19018
|
+
headerTooltip: $8f8e530286a37ce9$var$makerTooltip(f),
|
|
19019
|
+
width: 110,
|
|
19020
|
+
type: 'numericColumn',
|
|
19021
|
+
valueFormatter: (p)=>$8f8e530286a37ce9$var$fmtMaker(f, p.value)
|
|
19022
|
+
});
|
|
19023
|
+
return;
|
|
19024
|
+
}
|
|
18887
19025
|
cols.push({
|
|
18888
19026
|
colId: f,
|
|
18889
19027
|
field: f,
|
|
@@ -18903,6 +19041,110 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
|
|
|
18903
19041
|
tpmRange,
|
|
18904
19042
|
fieldCatalogByName
|
|
18905
19043
|
]);
|
|
19044
|
+
// Visible columns whose values are all numbers get summary statistics.
|
|
19045
|
+
const numericFields = (0, $gXNCa$react.useMemo)(()=>{
|
|
19046
|
+
const fields = columnDefs.filter((c)=>c.field).map((c)=>c.field);
|
|
19047
|
+
return new Set($8f8e530286a37ce9$var$numericFieldsOf(rows, fields));
|
|
19048
|
+
}, [
|
|
19049
|
+
columnDefs,
|
|
19050
|
+
rows
|
|
19051
|
+
]);
|
|
19052
|
+
const stats = (0, $gXNCa$react.useMemo)(()=>{
|
|
19053
|
+
const out = {};
|
|
19054
|
+
numericFields.forEach((f)=>{
|
|
19055
|
+
out[f] = $8f8e530286a37ce9$var$statsOf(rows.map((r)=>r[f]).filter((v)=>typeof v === 'number' && Number.isFinite(v)));
|
|
19056
|
+
});
|
|
19057
|
+
return out;
|
|
19058
|
+
}, [
|
|
19059
|
+
numericFields,
|
|
19060
|
+
rows
|
|
19061
|
+
]);
|
|
19062
|
+
const pinnedTopRows = (0, $gXNCa$react.useMemo)(()=>{
|
|
19063
|
+
if (!numericFields.size || !rows.length) return [];
|
|
19064
|
+
const header = {
|
|
19065
|
+
_stat: 'header'
|
|
19066
|
+
};
|
|
19067
|
+
if (!statsOpen) return [
|
|
19068
|
+
header
|
|
19069
|
+
];
|
|
19070
|
+
return [
|
|
19071
|
+
header,
|
|
19072
|
+
...$8f8e530286a37ce9$var$STAT_ROWS.map(({ key: key, label: label })=>{
|
|
19073
|
+
const row = {
|
|
19074
|
+
_stat: key,
|
|
19075
|
+
_statLabel: label
|
|
19076
|
+
};
|
|
19077
|
+
numericFields.forEach((f)=>{
|
|
19078
|
+
row[f] = stats[f][key];
|
|
19079
|
+
});
|
|
19080
|
+
return row;
|
|
19081
|
+
})
|
|
19082
|
+
];
|
|
19083
|
+
}, [
|
|
19084
|
+
numericFields,
|
|
19085
|
+
stats,
|
|
19086
|
+
statsOpen,
|
|
19087
|
+
rows.length
|
|
19088
|
+
]);
|
|
19089
|
+
// The first column carries the stats rows' labels; numeric columns show their
|
|
19090
|
+
// statistics, formatted like the column (N as a count, mean/sd with decimals).
|
|
19091
|
+
const gridColumnDefs = (0, $gXNCa$react.useMemo)(()=>columnDefs.map((col, i)=>{
|
|
19092
|
+
const first = i === 0;
|
|
19093
|
+
const numeric = !!col.field && numericFields.has(col.field);
|
|
19094
|
+
if (!first && !numeric) return col;
|
|
19095
|
+
const intField = numeric && $8f8e530286a37ce9$var$isIntegerField(col.field, rows);
|
|
19096
|
+
const statText = (p)=>{
|
|
19097
|
+
const v = p.value;
|
|
19098
|
+
if (v == null || v === '') return '';
|
|
19099
|
+
const k = p.data._stat;
|
|
19100
|
+
if (k === 'n') return Number(v).toLocaleString();
|
|
19101
|
+
if (k === 'mean' || k === 'sd' || k === 'median' && intField && !Number.isInteger(v)) return col.field === 'expr_max_tpm__attr_f' ? (0, $4b8473139e48e77e$export$8eaa32ef86afdd9c)(v) : $8f8e530286a37ce9$var$fmtDecimal(v);
|
|
19102
|
+
return col.valueFormatter ? col.valueFormatter({
|
|
19103
|
+
...p,
|
|
19104
|
+
value: v
|
|
19105
|
+
}) : String(v);
|
|
19106
|
+
};
|
|
19107
|
+
return {
|
|
19108
|
+
...col,
|
|
19109
|
+
valueFormatter: (p)=>{
|
|
19110
|
+
if (!(p.node && p.node.rowPinned)) return col.valueFormatter ? col.valueFormatter(p) : p.value;
|
|
19111
|
+
return numeric ? statText(p) : '';
|
|
19112
|
+
},
|
|
19113
|
+
cellStyle: (p)=>p.node && p.node.rowPinned ? null : typeof col.cellStyle === 'function' ? col.cellStyle(p) : col.cellStyle,
|
|
19114
|
+
cellRenderer: (p)=>{
|
|
19115
|
+
if (p.node && p.node.rowPinned) {
|
|
19116
|
+
const k = p.data._stat;
|
|
19117
|
+
if (first && k === 'header') return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("span", {
|
|
19118
|
+
className: "attrtable-stats-toggle",
|
|
19119
|
+
title: statsOpen ? 'Hide summary statistics' : 'Show summary statistics',
|
|
19120
|
+
children: [
|
|
19121
|
+
statsOpen ? "\u25BE" : "\u25B8",
|
|
19122
|
+
" Summary statistics (",
|
|
19123
|
+
rows.length.toLocaleString(),
|
|
19124
|
+
" genes)"
|
|
19125
|
+
]
|
|
19126
|
+
});
|
|
19127
|
+
if (first) {
|
|
19128
|
+
const value = numeric ? statText(p) : '';
|
|
19129
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("span", {
|
|
19130
|
+
className: "attrtable-stats-label",
|
|
19131
|
+
children: [
|
|
19132
|
+
p.data._statLabel,
|
|
19133
|
+
value ? `: ${value}` : ''
|
|
19134
|
+
]
|
|
19135
|
+
});
|
|
19136
|
+
}
|
|
19137
|
+
return k === 'header' ? '' : p.valueFormatted;
|
|
19138
|
+
}
|
|
19139
|
+
return col.cellRenderer ? /*#__PURE__*/ (0, ($parcel$interopDefault($gXNCa$react))).createElement(col.cellRenderer, p) : p.valueFormatted ?? p.value;
|
|
19140
|
+
}
|
|
19141
|
+
};
|
|
19142
|
+
}), [
|
|
19143
|
+
columnDefs,
|
|
19144
|
+
numericFields,
|
|
19145
|
+
rows,
|
|
19146
|
+
statsOpen
|
|
19147
|
+
]);
|
|
18906
19148
|
// Build the click-popover for a cell: a title (the column meaning) and one
|
|
18907
19149
|
// filterable item per value. Each item carries the fq_field / fq_value the
|
|
18908
19150
|
// "add filter" button hands to doAcceptGrameneSuggestion.
|
|
@@ -18971,6 +19213,27 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
|
|
|
18971
19213
|
]
|
|
18972
19214
|
};
|
|
18973
19215
|
}
|
|
19216
|
+
if ($8f8e530286a37ce9$var$MAKER_SET.has(colId)) {
|
|
19217
|
+
// AED: lower is better supported, so offer "at most"; the QI metrics "at least".
|
|
19218
|
+
const v = data[colId];
|
|
19219
|
+
if (!Number.isFinite(+v)) return {
|
|
19220
|
+
title: labelOf(colId),
|
|
19221
|
+
items: []
|
|
19222
|
+
};
|
|
19223
|
+
const shown = $8f8e530286a37ce9$var$fmtMaker(colId, +v);
|
|
19224
|
+
const short = $8f8e530286a37ce9$var$makerShortName(colId);
|
|
19225
|
+
return colId === 'MAKER__AED__attr_f' ? {
|
|
19226
|
+
title: labelOf(colId),
|
|
19227
|
+
items: [
|
|
19228
|
+
mk(`\u{2264} ${shown}`, colId, `[* TO ${v}]`, 'MAKER', `${short} \u{2264} ${shown}`)
|
|
19229
|
+
]
|
|
19230
|
+
} : {
|
|
19231
|
+
title: labelOf(colId),
|
|
19232
|
+
items: [
|
|
19233
|
+
mk(`\u{2265} ${shown}`, colId, `[${v} TO *]`, 'MAKER', `${short} \u{2265} ${shown}`)
|
|
19234
|
+
]
|
|
19235
|
+
};
|
|
19236
|
+
}
|
|
18974
19237
|
// Generic (Expression class, and any core / added attribute).
|
|
18975
19238
|
const label = labelOf(colId);
|
|
18976
19239
|
const raw = data[colId];
|
|
@@ -18983,6 +19246,11 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
|
|
|
18983
19246
|
};
|
|
18984
19247
|
};
|
|
18985
19248
|
const onCellClicked = (e)=>{
|
|
19249
|
+
if (e.node && e.node.rowPinned) {
|
|
19250
|
+
if (e.data && e.data._stat === 'header') setStatsOpen((v)=>!v);
|
|
19251
|
+
setPopover(null);
|
|
19252
|
+
return;
|
|
19253
|
+
}
|
|
18986
19254
|
const desc = buildCellPopover(e.column.getColId(), e.data);
|
|
18987
19255
|
if (!desc || !desc.items.length) {
|
|
18988
19256
|
setPopover(null);
|
|
@@ -19145,7 +19413,9 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
|
|
|
19145
19413
|
className: `ag-theme-quartz attrtable-aggrid${organShown ? ' attrtable-tall-header' : ''}`,
|
|
19146
19414
|
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$aggridreact.AgGridReact), {
|
|
19147
19415
|
rowData: rows,
|
|
19148
|
-
columnDefs:
|
|
19416
|
+
columnDefs: gridColumnDefs,
|
|
19417
|
+
pinnedTopRowData: pinnedTopRows,
|
|
19418
|
+
getRowClass: (p)=>p.node.rowPinned ? p.data._stat === 'header' ? 'attrtable-stats-header' : 'attrtable-stats-row' : undefined,
|
|
19149
19419
|
defaultColDef: $8f8e530286a37ce9$var$DEFAULT_COL_DEF,
|
|
19150
19420
|
animateRows: false,
|
|
19151
19421
|
suppressFieldDotNotation: true,
|