gramene-search 1.4.1 → 1.4.3

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.
Files changed (41) hide show
  1. package/.parcel-cache/4987902b3f9787cc-BundleGraph-0 +0 -0
  2. package/.parcel-cache/70f1f7555dda250d-AssetGraph-0 +0 -0
  3. package/.parcel-cache/7b8e4611c0f03524-AssetGraph-0 +0 -0
  4. package/.parcel-cache/data.mdb +0 -0
  5. package/.parcel-cache/lock.mdb +0 -0
  6. package/.parcel-cache/requestGraph-369948a06d23ec44-0 +0 -0
  7. package/.parcel-cache/requestGraph-nodes-0-369948a06d23ec44-0 +0 -0
  8. package/.parcel-cache/snapshot-369948a06d23ec44.txt +2 -2
  9. package/dist/BAR-logo.15c36467.png +0 -0
  10. package/dist/{Study.b2ce28e8.js → Study.5ff9518c.js} +9 -4
  11. package/dist/Study.5ff9518c.js.map +1 -0
  12. package/dist/android-chrome-192x192.4d149c27.png +0 -0
  13. package/dist/apple-touch-icon-114x114.27e956ae.png +0 -0
  14. package/dist/apple-touch-icon-120x120.b209ed9f.png +0 -0
  15. package/dist/apple-touch-icon-144x144.9f8e2136.png +0 -0
  16. package/dist/apple-touch-icon-152x152.b6107a9b.png +0 -0
  17. package/dist/apple-touch-icon-180x180.58fbcc65.png +0 -0
  18. package/dist/apple-touch-icon-57x57.f670c755.png +0 -0
  19. package/dist/apple-touch-icon-60x60.3fbc15ae.png +0 -0
  20. package/dist/apple-touch-icon-72x72.a0fd991d.png +0 -0
  21. package/dist/apple-touch-icon-76x76.437b43b5.png +0 -0
  22. package/dist/expression-atlas-logo.2d957e5a.png +0 -0
  23. package/dist/favicon-16x16.4dd6b101.png +0 -0
  24. package/dist/favicon-32x32.a0d53b79.png +0 -0
  25. package/dist/favicon-96x96.6cd9b68e.png +0 -0
  26. package/dist/genetree.d75946eb.png +0 -0
  27. package/dist/index.js +121 -45
  28. package/dist/index.js.map +1 -1
  29. package/dist/results.a8da7555.png +0 -0
  30. package/dist/sorghum.html +39 -0
  31. package/dist/suggestions.099b7c0e.png +0 -0
  32. package/package.json +1 -1
  33. package/src/bundles/docs.js +15 -1
  34. package/src/bundles/views.js +3 -2
  35. package/src/components/results/Expression.js +18 -10
  36. package/src/components/results/Study.js +10 -3
  37. package/src/components/results/details/Expression.js +66 -8
  38. package/src/components/results/details/VEP.js +1 -1
  39. package/src/demo.js +1 -0
  40. package/src/static/atlasWidget.html +12 -4
  41. package/dist/Study.b2ce28e8.js.map +0 -1
Binary file
@@ -0,0 +1,39 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Gramene Search</title>
6
+ <link rel="stylesheet" href="/sorghum.d3289a0b.css"><script>window.gramene = {};
7
+ gramene.defaultServer = "https://data.sorghumbase.org/sorghum_v6/swagger";
8
+
9
+ </script>
10
+ <script async="" src="/sorghum.250fb7b9.js"></script>
11
+ <script async="" src="https://plantreactome.gramene.org/DiagramJs/diagram/diagram.nocache.js"></script>
12
+ <link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-57x57.f670c755.png">
13
+ <link rel="apple-touch-icon" sizes="60x60" href="/apple-touch-icon-60x60.3fbc15ae.png">
14
+ <link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-72x72.a0fd991d.png">
15
+ <link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-76x76.437b43b5.png">
16
+ <link rel="apple-touch-icon" sizes="114x114" href="/apple-touch-icon-114x114.27e956ae.png">
17
+ <link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-120x120.b209ed9f.png">
18
+ <link rel="apple-touch-icon" sizes="144x144" href="/apple-touch-icon-144x144.9f8e2136.png">
19
+ <link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.b6107a9b.png">
20
+ <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180x180.58fbcc65.png">
21
+ <link rel="icon" type="image/png" href="/favicon-32x32.a0d53b79.png" sizes="32x32">
22
+ <link rel="icon" type="image/png" href="/android-chrome-192x192.4d149c27.png" sizes="192x192">
23
+ <link rel="icon" type="image/png" href="/favicon-96x96.6cd9b68e.png" sizes="96x96">
24
+ <link rel="icon" type="image/png" href="/favicon-16x16.4dd6b101.png" sizes="16x16">
25
+ </head>
26
+ <body>
27
+ <div id="demo"></div>
28
+ <script src="/sorghum.b34cb912.js" defer=""></script>
29
+ <script>document.addEventListener("keyup", possiblyFocus);
30
+ function possiblyFocus(e) {
31
+ if (e.key === "/") {
32
+ var el = document.getElementById("search-input");
33
+ if (el) el.focus();
34
+ }
35
+ }
36
+
37
+ </script>
38
+ </body>
39
+ </html>
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gramene-search",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
4
4
  "description": "search wrapper for gramene",
5
5
  "source": "src/index.js",
6
6
  "main": "dist/index.js",
@@ -11,6 +11,7 @@ const grameneDocs = {
11
11
  rnaSequences: {},
12
12
  pepSequences: {},
13
13
  studies: {},
14
+ desiredSamples: {},
14
15
  consequences: {}
15
16
  };
16
17
  return (state = initialState, {type, payload}) => {
@@ -116,10 +117,22 @@ const grameneDocs = {
116
117
  newState.expression = Object.assign({}, state.expression);
117
118
  newState.expression[payload.id] = payload.paralogs;
118
119
  return newState;
120
+ case 'EXPRESSION_SAMPLE_TOGGLED':
121
+ newState = Object.assign({}, state);
122
+ newState.desiredSamples = Object.assign({}, state.desiredSamples);
123
+ if (newState.desiredSamples.hasOwnProperty(payload)) {
124
+ delete newState.desiredSamples[payload]
125
+ }
126
+ else {
127
+ newState.desiredSamples[payload] = {status: 'need'}
128
+ }
119
129
  }
120
130
  return state;
121
131
  }
122
132
  },
133
+ doToggleExpressionSample: id => ({dispatch}) => {
134
+ dispatch({type: 'EXPRESSION_SAMPLE_TOGGLED', payload: id});
135
+ },
123
136
  doRequestVEP: id => ({dispatch, store}) => {
124
137
  const consequences = store.selectGrameneConsequences();
125
138
  if (!consequences.hasOwnProperty(id)) {
@@ -323,7 +336,8 @@ const grameneDocs = {
323
336
  selectGeneSequences: state => state.grameneDocs.sequences,
324
337
  selectRnaSequences: state => state.grameneDocs.rnaSequences,
325
338
  selectPepSequences: state => state.grameneDocs.pepSequences,
326
- selectAtlasStudies: state => state.grameneDocs.studies
339
+ selectAtlasStudies: state => state.grameneDocs.studies,
340
+ selectDesiredSamples: state => state.grameneDocs.desiredSamples
327
341
  };
328
342
 
329
343
  export default grameneDocs;
@@ -25,7 +25,8 @@ const grameneViews = {
25
25
  id: 'expression',
26
26
  name: 'Gene expression',
27
27
  show: 'off',
28
- shouldScroll: false
28
+ shouldScroll: false,
29
+ desiredSamples: {}
29
30
  },
30
31
  {
31
32
  id: 'attribs',
@@ -86,7 +87,7 @@ const grameneViews = {
86
87
  doCancelShouldScroll: () => ({dispatch, getState}) => {
87
88
  dispatch({type: 'GRAMENE_VIEW_SCROLLED', payload: null})
88
89
  },
89
- selectGrameneViews: state => state.grameneViews,
90
+ selectGrameneViews: state => state.grameneViews
90
91
  };
91
92
 
92
93
  export default grameneViews;
@@ -1,6 +1,6 @@
1
1
  import React, { useState, Suspense } from 'react'
2
2
  import {connect} from "redux-bundler-react";
3
- import { Accordion } from 'react-bootstrap';
3
+ import { Accordion, Button } from 'react-bootstrap';
4
4
  import "./expression.css";
5
5
  const LazyStudy = React.lazy(() => import('./Study'));
6
6
 
@@ -32,15 +32,22 @@ const Expression = props => {
32
32
  .filter(tid => searchTaxa[tid] || searchTaxa[tid + '001'])
33
33
  .sort((a,b) => props.grameneMaps[a + '001'].left_index - props.grameneMaps[b + '001'].left_index);
34
34
  return availableTaxa && props.grameneTaxonomy &&
35
- <Accordion alwaysOpen defaultActiveKey={availableTaxa.length === 1 ? "tax_0" : undefined}>
36
- {availableTaxa.map((tid, idx) => {
37
- const n = props.expressionStudies[tid].length;
38
- return <Accordion.Item key={idx} eventKey={'tax_'+idx}>
39
- <Accordion.Header>{props.grameneTaxonomy[tid].name} - {n} {n === 1 ? 'study' : 'studies'}</Accordion.Header>
40
- <Accordion.Body><StudyList studies={props.expressionStudies[tid]}/></Accordion.Body>
41
- </Accordion.Item>
42
- })}
43
- </Accordion>
35
+ <div>
36
+ <div>This is where you can launch a component for the selected samples. props.desiredSamples lists them.
37
+ This component can request the data from the API
38
+ organize samples by factor metadata? One big table with all the studies?
39
+ <Button>Show Samples ({Object.keys(props.desiredSamples).length} selected)</Button>
40
+ </div>
41
+ <Accordion alwaysOpen defaultActiveKey={availableTaxa.length === 1 ? "tax_0" : undefined}>
42
+ {availableTaxa.map((tid, idx) => {
43
+ const n = props.expressionStudies[tid].length;
44
+ return <Accordion.Item key={idx} eventKey={'tax_'+idx}>
45
+ <Accordion.Header>{props.grameneTaxonomy[tid].name} - {n} {n === 1 ? 'study' : 'studies'}</Accordion.Header>
46
+ <Accordion.Body><StudyList studies={props.expressionStudies[tid]}/></Accordion.Body>
47
+ </Accordion.Item>
48
+ })}
49
+ </Accordion>
50
+ </div>
44
51
  };
45
52
 
46
53
  export default connect(
@@ -49,5 +56,6 @@ export default connect(
49
56
  'selectGrameneTaxonomy',
50
57
  'selectGrameneMaps',
51
58
  'selectExpressionStudies',
59
+ 'selectDesiredSamples', // current set of samples to fetch expression data for
52
60
  Expression
53
61
  );
@@ -10,10 +10,14 @@ const metaRenderer = params => {
10
10
  }
11
11
  return params.value.label
12
12
  }
13
+ const sampleRenderer = params => {
14
+ const sampleMeta = params.value;
15
+ return JSON.stringify(sampleMeta,null,2);
16
+ }
13
17
  const Study = props => {
14
18
  let samples = props.expressionSamples[props.id];
15
19
  let sampleMetadata = [];
16
- let metadataFields = [{ field: "sampleId" }];
20
+ let metadataFields = [{ field: "sampleId", cellRenderer: sampleRenderer }];
17
21
  let isFactor={};
18
22
  samples.forEach((sample, idx) => {
19
23
  if (idx === 0) {
@@ -37,7 +41,7 @@ const Study = props => {
37
41
  });
38
42
  metadataFields.push(characteristics)
39
43
  }
40
- let s_info = {sampleId: sample.group}
44
+ let s_info = {sampleId: sample}
41
45
  sample.factor.forEach(factor => {
42
46
  s_info[factor.type] = {label: factor.label};
43
47
  if (factor.ontology) {
@@ -70,4 +74,7 @@ const Study = props => {
70
74
  </div>
71
75
  );
72
76
  };
73
- export default connect('selectExpressionSamples', Study);
77
+ export default connect(
78
+ 'selectExpressionSamples',
79
+ 'doToggleExpressionSample',
80
+ Study);
@@ -1,8 +1,64 @@
1
- import React, { useState, useEffect } from 'react'
1
+ import React, { useRef, useEffect } from 'react'
2
2
  import {connect} from "redux-bundler-react";
3
3
  import {Tabs, Tab} from 'react-bootstrap';
4
4
  import BAR, {haveBAR} from "gramene-efp-browser";
5
5
 
6
+ function DynamicIframe(props) {
7
+ // Create a ref for the iframe element
8
+ const iframeRef = useRef(null);
9
+
10
+ // Function to resize iframe height
11
+ const resizeIframe = () => {
12
+ if (iframeRef.current) {
13
+ const iframe = iframeRef.current;
14
+ const innerDoc = iframe.contentDocument || iframe.contentWindow.document;
15
+ iframe.style.height = 44 + innerDoc.body.scrollHeight + 'px';
16
+ }
17
+ };
18
+
19
+ // Resize iframe when content loads
20
+ useEffect(() => {
21
+ resizeIframe();
22
+ }, []); // Empty dependency array ensures it only runs once after initial render
23
+
24
+ // Optional: Resize iframe when window is resized
25
+ useEffect(() => {
26
+ window.addEventListener('resize', resizeIframe);
27
+ return () => {
28
+ window.removeEventListener('resize', resizeIframe);
29
+ };
30
+ }, []); // Empty dependency array ensures it only runs once after initial render
31
+
32
+ // Resize iframe when content changes
33
+ useEffect(() => {
34
+ const iframe = iframeRef.current;
35
+ if (!iframe) return;
36
+
37
+ const observer = new MutationObserver(resizeIframe);
38
+ const checkElement = () => {
39
+ const innerDoc = iframe.contentDocument || iframe.contentWindow.document;
40
+ const targetElement = innerDoc.querySelector('#heatmapContainer');
41
+ if (targetElement) {
42
+ observer.observe(targetElement, { attributes: true, childList: true, subtree: true });
43
+ } else {
44
+ setTimeout(checkElement, 200); // Check again after 100 milliseconds
45
+ }
46
+ };
47
+ checkElement();
48
+
49
+ return () => observer.disconnect();
50
+ }, []);
51
+
52
+ return (
53
+ <iframe
54
+ ref={iframeRef}
55
+ src={props.url}
56
+ title="Dynamic Iframe"
57
+ style={{ width: '100%', border: 'none' }}
58
+ />
59
+ );
60
+ }
61
+
6
62
  const Detail = props => {
7
63
  const gene = props.geneDocs[props.searchResult.id];
8
64
  let paralogs_url;
@@ -18,13 +74,15 @@ const Detail = props => {
18
74
  props.doRequestParalogExpression(gene._id)
19
75
  }
20
76
  return <Tabs>
21
- {haveBAR(gene) && <Tab tabClassName="eFP" eventKey="eFP" title="eFP Browser"><BAR gene={gene}/></Tab>}
22
- <Tab tabClassName="gxa" eventKey="gene" title="All Studies">
23
- <iframe src={gene_url} frameBorder="0" width="100%" height="500px"></iframe>
24
- </Tab>
25
- {paralogs_url && <Tab tabClassName="gxa" eventKey="paralogs" title="Reference Study (all paralogs)">
26
- <iframe src={paralogs_url} frameBorder="0" width="100%" height="500px"></iframe>
27
- </Tab>}
77
+ {paralogs_url &&
78
+ <Tab tabClassName="gxa" eventKey="paralogs" title="Reference Study (all paralogs)">
79
+ <DynamicIframe url={paralogs_url}/>
80
+ </Tab>
81
+ }
82
+ <Tab tabClassName="gxa" eventKey="gene" title="All Studies"><DynamicIframe url={gene_url}/></Tab>
83
+ {haveBAR(gene) &&
84
+ <Tab tabClassName="eFP" eventKey="eFP" title="eFP Browser"><BAR gene={gene}/></Tab>
85
+ }
28
86
  </Tabs>
29
87
  };
30
88
 
@@ -88,7 +88,7 @@ const Detail = props => {
88
88
  <h5>Predicted loss-of-function alleles were detected in these germplasm.</h5>
89
89
  <div >Explore other variants within this gene in the <a target="_blank"
90
90
  href={`${props.configuration.ensemblURL}/${gene.system_name}/Gene/Variation_Gene/Image?db=core;g=${props.searchResult.id}`}>
91
- Variant image</a> page on the Ensembl site.</div>
91
+ Variant image</a> page in the Ensembl genome browser.</div>
92
92
  <div className="ag-theme-quartz" style={{height: `${44 * (accessionTable.length + 2)}px`}}>
93
93
  <AgGridReact rowData={accessionTable} columnDefs={tableFields} defaultColDef={defaultColDef}/>
94
94
  </div>
package/src/demo.js CHANGED
@@ -121,6 +121,7 @@ const panSites = [
121
121
  ga: 'G-L5KXDCCZ16',
122
122
  targetTaxonId: 4558003,
123
123
  alertText: 'Click the search icon in the menu bar or type / to search',
124
+ showViews: true,
124
125
  panSite : {
125
126
  vitis_vinifera : {
126
127
  url: "https://vitis.gramene.org?idList=",
@@ -10,9 +10,17 @@
10
10
  <body>
11
11
  <div id="heatmapContainer"></div>
12
12
  <script language="JavaScript" type="application/javascript">
13
- expressionAtlasHeatmapHighcharts.render({
14
- query: { gene: location.search.replace(/\?/,'') },
15
- target: "heatmapContainer"
16
- });
13
+ const queryString = location.search;
14
+ const urlParams = new URLSearchParams(queryString);
15
+ const genes = urlParams.get('genes');
16
+ const experiment = urlParams.get('reference');
17
+ const options = {
18
+ target: "heatmapContainer",
19
+ query: { gene: genes }
20
+ };
21
+ if (+experiment === 1) {
22
+ options["experiment"] = "reference"
23
+ }
24
+ expressionAtlasHeatmapHighcharts.render(options);
17
25
  </script>
18
26
  </body>
@@ -1 +0,0 @@
1
- {"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAMA,MAAM,qCAAe,CAAA;IACnB,IAAI,OAAO,KAAK,CAAC,QAAQ,EACvB,qBAAO,gCAAC;QAAE,MAAM,CAAC,+BAA+B,EAAE,OAAO,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,KAAI,KAAK,CAAC;QAAE,QAAO;kBAAU,OAAO,KAAK,CAAC,KAAK;;IAE3H,OAAO,OAAO,KAAK,CAAC,KAAK;AAC3B;AACA,MAAM,8BAAQ,CAAA;IACZ,IAAI,UAAU,MAAM,iBAAiB,CAAC,MAAM,EAAE,CAAC;IAC/C,IAAI,iBAAiB,EAAE;IACvB,IAAI,iBAAiB;QAAC;YAAE,OAAO;QAAW;KAAE;IAC5C,IAAI,WAAS,CAAC;IACd,QAAQ,OAAO,CAAC,CAAC,QAAQ;QACvB,IAAI,QAAQ,GAAG;YACb,IAAI,UAAU;gBACZ,YAAY;gBACZ,UAAU,EAAE;YACd;YACA,OAAO,MAAM,CAAC,OAAO,CAAC,CAAA;gBACpB,QAAQ,QAAQ,CAAC,IAAI,CAAC;oBAAC,OAAO,OAAO,IAAI;oBAAE,cAAc;gBAAY;gBACrE,QAAQ,CAAC,OAAO,IAAI,CAAC,GAAG;YAC1B;YACA,eAAe,IAAI,CAAC;YACpB,IAAI,kBAAkB;gBACpB,YAAY;gBACZ,UAAU,EAAE;YACd;YACA,OAAO,cAAc,CAAC,OAAO,CAAC,CAAA;gBAC5B,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,EACpB,gBAAgB,QAAQ,CAAC,IAAI,CAAC;oBAAC,OAAO,GAAG,IAAI;oBAAE,cAAc;gBAAY;YAE7E;YACA,eAAe,IAAI,CAAC;QACtB;QACA,IAAI,SAAS;YAAC,UAAU,OAAO,KAAK;QAAA;QACpC,OAAO,MAAM,CAAC,OAAO,CAAC,CAAA;YACpB,MAAM,CAAC,OAAO,IAAI,CAAC,GAAG;gBAAC,OAAO,OAAO,KAAK;YAAA;YAC1C,IAAI,OAAO,QAAQ,EAAE;gBACnB,MAAM,CAAC,OAAO,IAAI,CAAC,CAAC,WAAW,GAAG,OAAO,QAAQ;gBACjD,MAAM,CAAC,OAAO,IAAI,CAAC,CAAC,KAAK,GAAG,OAAO,EAAE;YACvC;QACF;QACA,OAAO,cAAc,CAAC,OAAO,CAAC,CAAA;YAC5B,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG;gBAAC,OAAO,GAAG,KAAK;YAAA;YAClC,IAAI,GAAG,QAAQ,EAAE;gBACf,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,WAAW,GAAG,GAAG,QAAQ;gBACzC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,KAAK,GAAG,GAAG,EAAE;YAC/B;QACF;QACA,eAAe,IAAI,CAAC;IACtB;IACA,MAAM,CAAC,SAAS,WAAW,GAAG,CAAA,GAAA,qBAAO,EAAE;IACvC,MAAM,CAAC,SAAS,WAAW,GAAG,CAAA,GAAA,qBAAO,EAAE;IACvC,MAAM,gBAAgB,CAAA,GAAA,oBAAM,EAAE;QAC5B,OAAO;YACL,QAAQ;QACV;IACF,GAAG,EAAE;IACL,qBACE,iCAAC;;0BACC,gCAAC;gBAAI,WAAU;gBAAkB,OAAO;oBAAC,QAAQ,CAAC,EAAE,KAAM,CAAA,QAAQ,MAAM,GAAG,CAAA,EAAG,EAAE,CAAC;gBAAA;0BAC/E,cAAA,gCAAC,CAAA,GAAA,8BAAU;oBAAE,SAAS;oBAAS,YAAY;oBAAS,eAAe;;;0BAErE,iCAAC;gBAAE,MAAM,CAAC,sCAAsC,EAAE,MAAM,EAAE,CAAC,CAAC;;oBAAE;oBAAuB,MAAM,EAAE;;;;;AAGnG;IACA,2CAAe,CAAA,GAAA,gCAAM,EAAE,2BAA2B","sources":["src/components/results/Study.js"],"sourcesContent":["import React, { useState, useMemo } from 'react'\nimport {connect} from \"redux-bundler-react\";\nimport { AgGridReact } from \"ag-grid-react\";\nimport \"ag-grid-community/styles/ag-grid.css\";\nimport \"ag-grid-community/styles/ag-theme-quartz.css\";\n\nconst metaRenderer = params => {\n if (params.value.ontology) {\n return <a href={`http://purl.obolibrary.org/obo/${params.value.id.replace(\":\",\"_\")}`} target='_blank'>{params.value.label}</a>\n }\n return params.value.label\n}\nconst Study = props => {\n let samples = props.expressionSamples[props.id];\n let sampleMetadata = [];\n let metadataFields = [{ field: \"sampleId\" }];\n let isFactor={};\n samples.forEach((sample, idx) => {\n if (idx === 0) {\n let factors = {\n headerName: 'Experimental Variables',\n children: []\n }\n sample.factor.forEach(factor => {\n factors.children.push({field: factor.type, cellRenderer: metaRenderer})\n isFactor[factor.type] = true;\n });\n metadataFields.push(factors);\n let characteristics = {\n headerName: 'Sample Characteristics',\n children: []\n }\n sample.characteristic.forEach(ch => {\n if (!isFactor[ch.type]) {\n characteristics.children.push({field: ch.type, cellRenderer: metaRenderer})\n }\n });\n metadataFields.push(characteristics)\n }\n let s_info = {sampleId: sample.group}\n sample.factor.forEach(factor => {\n s_info[factor.type] = {label: factor.label};\n if (factor.ontology) {\n s_info[factor.type]['ontology'] = factor.ontology\n s_info[factor.type]['id'] = factor.id;\n }\n })\n sample.characteristic.forEach(ch => {\n s_info[ch.type] = {label: ch.label};\n if (ch.ontology) {\n s_info[ch.type]['ontology'] = ch.ontology\n s_info[ch.type]['id'] = ch.id;\n }\n })\n sampleMetadata.push(s_info)\n })\n const [rowData, setRowData] = useState(sampleMetadata);\n const [colDefs, setColDefs] = useState(metadataFields);\n const defaultColDef = useMemo(() => {\n return {\n filter: true\n }\n }, []);\n return (\n <div>\n <div className=\"ag-theme-quartz\" style={{height: `${44 * (samples.length + 2)}px`}}>\n <AgGridReact rowData={rowData} columnDefs={colDefs} defaultColDef={defaultColDef}/>\n </div>\n <a href={`https://www.ebi.ac.uk/gxa/experiments/${props.id}`}>EBI Atlas Experiment: {props.id}</a>\n </div>\n );\n};\nexport default connect('selectExpressionSamples', Study);\n"],"names":[],"version":3,"file":"Study.b2ce28e8.js.map"}