gramene-search 2.37.0 → 2.38.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 two field-catalog groups —
5240
- // Core identifiers and Expression attributes — and *all* of them are fetched up
5241
- // front. That keeps the column picker a pure visibility control: toggling a
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
@@ -13316,6 +13333,7 @@ function $76e15aea9236bd47$var$findRoots(tax) {
13316
13333
  }
13317
13334
  // Post-order: attach .leafIds (taxon_ids of maps in subtree) and .isLeaf
13318
13335
  function $76e15aea9236bd47$var$annotate(node, tax, maps) {
13336
+ maps = maps || {};
13319
13337
  const leafIds = [];
13320
13338
  if (maps[node._id]) {
13321
13339
  node.isLeaf = true;
@@ -13329,9 +13347,20 @@ function $76e15aea9236bd47$var$annotate(node, tax, maps) {
13329
13347
  });
13330
13348
  node.leafIds = leafIds;
13331
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
+ }
13332
13360
  // Walk down the spine through single-child internals until a branch point,
13333
13361
  // leaf, or node that is itself a genome.
13334
13362
  function $76e15aea9236bd47$var$compressChain(node, tax, maps) {
13363
+ maps = maps || {};
13335
13364
  const chain = [
13336
13365
  node
13337
13366
  ];
@@ -13483,17 +13512,11 @@ function $76e15aea9236bd47$var$computeOpenState(props) {
13483
13512
  class $76e15aea9236bd47$var$TaxonomyModal extends (0, ($parcel$interopDefault($gXNCa$react))).Component {
13484
13513
  constructor(props){
13485
13514
  super(props);
13486
- const { grameneMaps: grameneMaps, grameneTaxonomy: grameneTaxonomy } = props;
13487
- let roots = [];
13488
- if (grameneTaxonomy) {
13489
- roots = $76e15aea9236bd47$var$findRoots(grameneTaxonomy);
13490
- roots.forEach((r)=>$76e15aea9236bd47$var$annotate(r, grameneTaxonomy, grameneMaps));
13491
- }
13492
13515
  const { selected: selected, expanded: expanded } = $76e15aea9236bd47$var$computeOpenState(props);
13493
13516
  this.state = {
13494
13517
  selected: selected,
13495
13518
  expanded: expanded,
13496
- roots: roots,
13519
+ roots: $76e15aea9236bd47$var$buildRoots(props),
13497
13520
  query: '',
13498
13521
  highlightId: null,
13499
13522
  activeIdx: 0
@@ -13538,6 +13561,16 @@ class $76e15aea9236bd47$var$TaxonomyModal extends (0, ($parcel$interopDefault($g
13538
13561
  }
13539
13562
  }
13540
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
+ }
13541
13574
  // On each hidden→shown transition, reseed selection from current active
13542
13575
  // genomes and expand every ancestor so selected taxa are visible.
13543
13576
  const wasShown = prevProps.grameneGenomes && prevProps.grameneGenomes.show;
@@ -13609,7 +13642,7 @@ class $76e15aea9236bd47$var$TaxonomyModal extends (0, ($parcel$interopDefault($g
13609
13642
  }
13610
13643
  selectAll() {
13611
13644
  const s = new Set();
13612
- Object.values(this.props.grameneMaps).forEach((m)=>{
13645
+ Object.values(this.props.grameneMaps || {}).forEach((m)=>{
13613
13646
  if (!m.hidden) s.add(m.taxon_id);
13614
13647
  });
13615
13648
  this.setState({
@@ -13626,7 +13659,7 @@ class $76e15aea9236bd47$var$TaxonomyModal extends (0, ($parcel$interopDefault($g
13626
13659
  this.state.selected.forEach((id)=>{
13627
13660
  active[id] = true;
13628
13661
  });
13629
- 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)=>{
13630
13663
  if (!m.hidden) active[m.taxon_id] = true;
13631
13664
  });
13632
13665
  this.props.doUpdateGrameneGenomes(active);
@@ -13644,7 +13677,7 @@ class $76e15aea9236bd47$var$TaxonomyModal extends (0, ($parcel$interopDefault($g
13644
13677
  const someOn = selCount > 0 && !allOn;
13645
13678
  const visibleKids = (terminal.children || []).map((cid)=>grameneTaxonomy[cid]).filter((c)=>c && c.leafIds.length > 0);
13646
13679
  const hasKids = visibleKids.length > 0;
13647
- const terminalIsGenome = !!grameneMaps[terminal._id];
13680
+ const terminalIsGenome = !!(grameneMaps && grameneMaps[terminal._id]);
13648
13681
  const isOpen = expanded.has(terminal._id);
13649
13682
  // Deepest clade colour in the chain wins; falls back to inherited.
13650
13683
  const color = chain.reduce((acc, n)=>$76e15aea9236bd47$var$CLADE_COLORS[n.name] || acc, inheritedColor);
@@ -18705,6 +18738,84 @@ const $8f8e530286a37ce9$var$WIDTHS = {
18705
18738
  expr_enhanced_in__attr_ss: 150,
18706
18739
  expr_high_in__attr_ss: 150
18707
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
+ // AED gets an explanation; the QIs their catalog label, e.g. "QI1: Length of the 5' UTR".
18750
+ const $8f8e530286a37ce9$var$makerTooltip = (f, label)=>f === 'MAKER__AED__attr_f' ? 'MAKER Annotation Edit Distance: 0 = fully supported by evidence, 1 = no support' : label;
18751
+ // Summary-statistics rows, pinned above the genes. The header row collapses the
18752
+ // rest; stats cover every loaded gene, for each visible column whose values are
18753
+ // all numbers (taxon_id is an identifier, not a measurement).
18754
+ const $8f8e530286a37ce9$var$STAT_ROWS = [
18755
+ {
18756
+ key: 'n',
18757
+ label: 'N'
18758
+ },
18759
+ {
18760
+ key: 'min',
18761
+ label: 'Min'
18762
+ },
18763
+ {
18764
+ key: 'max',
18765
+ label: 'Max'
18766
+ },
18767
+ {
18768
+ key: 'median',
18769
+ label: 'Median'
18770
+ },
18771
+ {
18772
+ key: 'mean',
18773
+ label: 'Mean'
18774
+ },
18775
+ {
18776
+ key: 'sd',
18777
+ label: 'Std dev'
18778
+ }
18779
+ ];
18780
+ const $8f8e530286a37ce9$var$NOT_MEASUREMENTS = new Set([
18781
+ 'taxon_id'
18782
+ ]);
18783
+ const $8f8e530286a37ce9$var$numericFieldsOf = (rows, fields)=>fields.filter((f)=>{
18784
+ if ($8f8e530286a37ce9$var$NOT_MEASUREMENTS.has(f)) return false;
18785
+ let numbers = 0;
18786
+ for (const r of rows){
18787
+ const v = r[f];
18788
+ if (v == null || v === '') continue;
18789
+ if (typeof v !== 'number' || !Number.isFinite(v)) return false;
18790
+ numbers++;
18791
+ }
18792
+ return numbers > 0;
18793
+ });
18794
+ const $8f8e530286a37ce9$var$statsOf = (values)=>{
18795
+ const n = values.length;
18796
+ if (!n) return {
18797
+ n: 0
18798
+ };
18799
+ const sorted = values.slice().sort((a, b)=>a - b);
18800
+ const mean = values.reduce((a, b)=>a + b, 0) / n;
18801
+ const median = n % 2 ? sorted[(n - 1) / 2] : (sorted[n / 2 - 1] + sorted[n / 2]) / 2;
18802
+ const sd = n > 1 ? Math.sqrt(values.reduce((a, v)=>a + (v - mean) * (v - mean), 0) / (n - 1)) : null;
18803
+ return {
18804
+ n: n,
18805
+ min: sorted[0],
18806
+ max: sorted[n - 1],
18807
+ median: median,
18808
+ mean: mean,
18809
+ sd: sd
18810
+ };
18811
+ };
18812
+ // Mean and std dev of a whole-number column still need decimals.
18813
+ const $8f8e530286a37ce9$var$fmtDecimal = (v)=>{
18814
+ if (v == null || !Number.isFinite(v)) return '';
18815
+ const a = Math.abs(v);
18816
+ return a !== 0 && (a < 0.01 || a >= 1e6) ? v.toPrecision(3) : v.toFixed(2);
18817
+ };
18818
+ const $8f8e530286a37ce9$var$isIntegerField = (f, rows)=>f.endsWith('__attr_i') || rows.every((r)=>r[f] == null || Number.isInteger(r[f]));
18708
18819
  const $8f8e530286a37ce9$var$joinValues = (p)=>Array.isArray(p.value) ? p.value.map((v)=>String(v).replace(/_/g, ' ')).join(', ') : p.value ?? '';
18709
18820
  const $8f8e530286a37ce9$var$StressChip = ({ c: c, dir: dir })=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
18710
18821
  style: {
@@ -18775,6 +18886,7 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
18775
18886
  const [fieldQuery, setFieldQuery] = (0, $gXNCa$react.useState)('');
18776
18887
  const [popover, setPopover] = (0, $gXNCa$react.useState)(null);
18777
18888
  const popRef = (0, $gXNCa$react.useRef)(null);
18889
+ const [statsOpen, setStatsOpen] = (0, $gXNCa$react.useState)(true);
18778
18890
  const { docs: docs, total: total, truncated: truncated, status: status, error: error, visibleFields: visibleFields } = attrTable;
18779
18891
  const visibleSet = (0, $gXNCa$react.useMemo)(()=>new Set(visibleFields), [
18780
18892
  visibleFields
@@ -18809,15 +18921,13 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
18809
18921
  }, [
18810
18922
  docs
18811
18923
  ]);
18812
- // Only the Core identifiers + Expression attributes groups are offered.
18924
+ // Only the groups the bundle fetches are offered (Core identifiers, Expression
18925
+ // attributes, MAKER transcript metrics); the catalog drops groups a site lacks.
18813
18926
  const pickerCatalog = (0, $gXNCa$react.useMemo)(()=>{
18814
18927
  if (!fieldCatalog || !fieldCatalog.groups) return null;
18815
18928
  return {
18816
18929
  ...fieldCatalog,
18817
- groups: fieldCatalog.groups.filter((g)=>[
18818
- 'core',
18819
- 'exprattrs'
18820
- ].includes(g.id))
18930
+ groups: fieldCatalog.groups.filter((g)=>(0, $12feb7ca48271e17$export$53e90de8d659db95).includes(g.id))
18821
18931
  };
18822
18932
  }, [
18823
18933
  fieldCatalog
@@ -18844,10 +18954,11 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
18844
18954
  headerTooltip: (0, $4b8473139e48e77e$export$50c1f966fd3af83e)(o),
18845
18955
  width: 22,
18846
18956
  minWidth: 18,
18847
- valueGetter: (p)=>p.data && p.data._organ[o] || '',
18957
+ valueGetter: (p)=>p.data && p.data._organ && p.data._organ[o] || '',
18848
18958
  valueFormatter: ()=>'',
18849
18959
  tooltipValueGetter: (p)=>{
18850
- const lvl = p.data && p.data._organ[o];
18960
+ if (p.node && p.node.rowPinned) return undefined;
18961
+ const lvl = p.data && p.data._organ && p.data._organ[o];
18851
18962
  if (!lvl) return `${(0, $4b8473139e48e77e$export$50c1f966fd3af83e)(o)}: not assayed`;
18852
18963
  const sp = p.data._specific.has(o) ? " \xb7 specific" : p.data._enhanced.has(o) ? " \xb7 enhanced" : '';
18853
18964
  return `${(0, $4b8473139e48e77e$export$50c1f966fd3af83e)(o)}: ${(0, $4b8473139e48e77e$export$433053e2c141d410)[lvl] || lvl}${sp}`;
@@ -18900,6 +19011,18 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
18900
19011
  });
18901
19012
  return;
18902
19013
  }
19014
+ if ($8f8e530286a37ce9$var$MAKER_SET.has(f)) {
19015
+ cols.push({
19016
+ colId: f,
19017
+ field: f,
19018
+ headerName: labelOf(f),
19019
+ headerTooltip: $8f8e530286a37ce9$var$makerTooltip(f, labelOf(f)),
19020
+ width: 110,
19021
+ type: 'numericColumn',
19022
+ valueFormatter: (p)=>$8f8e530286a37ce9$var$fmtMaker(f, p.value)
19023
+ });
19024
+ return;
19025
+ }
18903
19026
  cols.push({
18904
19027
  colId: f,
18905
19028
  field: f,
@@ -18919,6 +19042,110 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
18919
19042
  tpmRange,
18920
19043
  fieldCatalogByName
18921
19044
  ]);
19045
+ // Visible columns whose values are all numbers get summary statistics.
19046
+ const numericFields = (0, $gXNCa$react.useMemo)(()=>{
19047
+ const fields = columnDefs.filter((c)=>c.field).map((c)=>c.field);
19048
+ return new Set($8f8e530286a37ce9$var$numericFieldsOf(rows, fields));
19049
+ }, [
19050
+ columnDefs,
19051
+ rows
19052
+ ]);
19053
+ const stats = (0, $gXNCa$react.useMemo)(()=>{
19054
+ const out = {};
19055
+ numericFields.forEach((f)=>{
19056
+ out[f] = $8f8e530286a37ce9$var$statsOf(rows.map((r)=>r[f]).filter((v)=>typeof v === 'number' && Number.isFinite(v)));
19057
+ });
19058
+ return out;
19059
+ }, [
19060
+ numericFields,
19061
+ rows
19062
+ ]);
19063
+ const pinnedTopRows = (0, $gXNCa$react.useMemo)(()=>{
19064
+ if (!numericFields.size || !rows.length) return [];
19065
+ const header = {
19066
+ _stat: 'header'
19067
+ };
19068
+ if (!statsOpen) return [
19069
+ header
19070
+ ];
19071
+ return [
19072
+ header,
19073
+ ...$8f8e530286a37ce9$var$STAT_ROWS.map(({ key: key, label: label })=>{
19074
+ const row = {
19075
+ _stat: key,
19076
+ _statLabel: label
19077
+ };
19078
+ numericFields.forEach((f)=>{
19079
+ row[f] = stats[f][key];
19080
+ });
19081
+ return row;
19082
+ })
19083
+ ];
19084
+ }, [
19085
+ numericFields,
19086
+ stats,
19087
+ statsOpen,
19088
+ rows.length
19089
+ ]);
19090
+ // The first column carries the stats rows' labels; numeric columns show their
19091
+ // statistics, formatted like the column (N as a count, mean/sd with decimals).
19092
+ const gridColumnDefs = (0, $gXNCa$react.useMemo)(()=>columnDefs.map((col, i)=>{
19093
+ const first = i === 0;
19094
+ const numeric = !!col.field && numericFields.has(col.field);
19095
+ if (!first && !numeric) return col;
19096
+ const intField = numeric && $8f8e530286a37ce9$var$isIntegerField(col.field, rows);
19097
+ const statText = (p)=>{
19098
+ const v = p.value;
19099
+ if (v == null || v === '') return '';
19100
+ const k = p.data._stat;
19101
+ if (k === 'n') return Number(v).toLocaleString();
19102
+ 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);
19103
+ return col.valueFormatter ? col.valueFormatter({
19104
+ ...p,
19105
+ value: v
19106
+ }) : String(v);
19107
+ };
19108
+ return {
19109
+ ...col,
19110
+ valueFormatter: (p)=>{
19111
+ if (!(p.node && p.node.rowPinned)) return col.valueFormatter ? col.valueFormatter(p) : p.value;
19112
+ return numeric ? statText(p) : '';
19113
+ },
19114
+ cellStyle: (p)=>p.node && p.node.rowPinned ? null : typeof col.cellStyle === 'function' ? col.cellStyle(p) : col.cellStyle,
19115
+ cellRenderer: (p)=>{
19116
+ if (p.node && p.node.rowPinned) {
19117
+ const k = p.data._stat;
19118
+ if (first && k === 'header') return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("span", {
19119
+ className: "attrtable-stats-toggle",
19120
+ title: statsOpen ? 'Hide summary statistics' : 'Show summary statistics',
19121
+ children: [
19122
+ statsOpen ? "\u25BE" : "\u25B8",
19123
+ " Summary statistics (",
19124
+ rows.length.toLocaleString(),
19125
+ " genes)"
19126
+ ]
19127
+ });
19128
+ if (first) {
19129
+ const value = numeric ? statText(p) : '';
19130
+ return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("span", {
19131
+ className: "attrtable-stats-label",
19132
+ children: [
19133
+ p.data._statLabel,
19134
+ value ? `: ${value}` : ''
19135
+ ]
19136
+ });
19137
+ }
19138
+ return k === 'header' ? '' : p.valueFormatted;
19139
+ }
19140
+ return col.cellRenderer ? /*#__PURE__*/ (0, ($parcel$interopDefault($gXNCa$react))).createElement(col.cellRenderer, p) : p.valueFormatted ?? p.value;
19141
+ }
19142
+ };
19143
+ }), [
19144
+ columnDefs,
19145
+ numericFields,
19146
+ rows,
19147
+ statsOpen
19148
+ ]);
18922
19149
  // Build the click-popover for a cell: a title (the column meaning) and one
18923
19150
  // filterable item per value. Each item carries the fq_field / fq_value the
18924
19151
  // "add filter" button hands to doAcceptGrameneSuggestion.
@@ -18987,6 +19214,27 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
18987
19214
  ]
18988
19215
  };
18989
19216
  }
19217
+ if ($8f8e530286a37ce9$var$MAKER_SET.has(colId)) {
19218
+ // AED: lower is better supported, so offer "at most"; the QI metrics "at least".
19219
+ const v = data[colId];
19220
+ if (!Number.isFinite(+v)) return {
19221
+ title: labelOf(colId),
19222
+ items: []
19223
+ };
19224
+ const shown = $8f8e530286a37ce9$var$fmtMaker(colId, +v);
19225
+ const short = $8f8e530286a37ce9$var$makerShortName(colId);
19226
+ return colId === 'MAKER__AED__attr_f' ? {
19227
+ title: labelOf(colId),
19228
+ items: [
19229
+ mk(`\u{2264} ${shown}`, colId, `[* TO ${v}]`, 'MAKER', `${short} \u{2264} ${shown}`)
19230
+ ]
19231
+ } : {
19232
+ title: labelOf(colId),
19233
+ items: [
19234
+ mk(`\u{2265} ${shown}`, colId, `[${v} TO *]`, 'MAKER', `${short} \u{2265} ${shown}`)
19235
+ ]
19236
+ };
19237
+ }
18990
19238
  // Generic (Expression class, and any core / added attribute).
18991
19239
  const label = labelOf(colId);
18992
19240
  const raw = data[colId];
@@ -18999,6 +19247,11 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
18999
19247
  };
19000
19248
  };
19001
19249
  const onCellClicked = (e)=>{
19250
+ if (e.node && e.node.rowPinned) {
19251
+ if (e.data && e.data._stat === 'header') setStatsOpen((v)=>!v);
19252
+ setPopover(null);
19253
+ return;
19254
+ }
19002
19255
  const desc = buildCellPopover(e.column.getColId(), e.data);
19003
19256
  if (!desc || !desc.items.length) {
19004
19257
  setPopover(null);
@@ -19161,7 +19414,9 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
19161
19414
  className: `ag-theme-quartz attrtable-aggrid${organShown ? ' attrtable-tall-header' : ''}`,
19162
19415
  children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$aggridreact.AgGridReact), {
19163
19416
  rowData: rows,
19164
- columnDefs: columnDefs,
19417
+ columnDefs: gridColumnDefs,
19418
+ pinnedTopRowData: pinnedTopRows,
19419
+ getRowClass: (p)=>p.node.rowPinned ? p.data._stat === 'header' ? 'attrtable-stats-header' : 'attrtable-stats-row' : undefined,
19165
19420
  defaultColDef: $8f8e530286a37ce9$var$DEFAULT_COL_DEF,
19166
19421
  animateRows: false,
19167
19422
  suppressFieldDotNotation: true,