diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 599bf3af..00000000 --- a/.babelrc +++ /dev/null @@ -1,23 +0,0 @@ -{ - "presets": [ - ["env", { - "targets": { "node": 7 }, - "useBuiltIns": true - }], - "stage-0", - "react" - ], - "plugins": ["add-module-exports"], - "env": { - "production": { - "presets": ["react-optimize"], - "plugins": ["dev-expression"] - }, - "development": { - "plugins": [ - "transform-class-properties", - "transform-es2015-classes", - ] - } - } -} diff --git a/.gitignore b/.gitignore index 0904f735..d8739b0c 100644 --- a/.gitignore +++ b/.gitignore @@ -93,3 +93,5 @@ typings/ # dotenv environment variables file .env +app/flagshipData/Earth Microbiome Project/.testdata 2.biom.icloud +app/flagshipData/Earth Microbiome Project/.testdata 2.biom.icloud diff --git a/app/components/About.js b/app/components/About.js index b70cfb86..109cfe31 100644 --- a/app/components/About.js +++ b/app/components/About.js @@ -31,10 +31,10 @@ export default class About extends Component { }; } - /*This function deals with when the mouse hovers over the edit icon on top right of + /* This function deals with when the mouse hovers over the edit icon on top right of the home screen and changes img src accordingly to correct svg file */ - handleMouseOver (title) { - switch(title) { + handleMouseOver(title) { + switch (title) { case 'New to Phinch?': this.setState({ link1: arrowHover }); break; @@ -53,10 +53,10 @@ export default class About extends Component { } } - /*This function deals with the mouse leaving an icon (no longer hovering) and + /* This function deals with the mouse leaving an icon (no longer hovering) and changed img src to correct svg file */ - handleMouseLeave (title) { - switch(title) { + handleMouseLeave(title) { + switch (title) { case 'New to Phinch?': this.setState({ link1: arrow }); break; @@ -86,29 +86,29 @@ export default class About extends Component {
this.setState({hoveringClose: true})} - onMouseOut={() => this.setState({hoveringClose: false})} + onMouseOver={() => this.setState({ hoveringClose: true })} + onMouseOut={() => this.setState({ hoveringClose: false })} > Home
-

About Phinch

-

- PHINCH is an open-source framework for visualizing biological data, funded by a grant from the Alfred P. Sloan foundation. This project represents an interdisciplinary collaboration between Pitch Interactive, a data visualization studio in Oakland, CA, and biological researchers at UC Davis. {/* eslint-disable-line max-len */} -

-

- Whether it's genes, proteins, or microbial species, Phinch provides an interactive visualization tool that allows users to explore and manipulate large biological datasets. Computer algorithms face significant difficulty in identifying simple data patterns; writing algorithms to tease out complex, subtle relationships (the type that exist in biological systems) is almost impossible. However, the human eye is adept at spotting visual patterns, able to quickly notice trends and outliers. It is this philosophy especially when presented with intuitive, well-designed software tools and user interfaces. {/* eslint-disable-line max-len */} -

-

- The sheer volume of data produced from high-throughput sequencing technologies will require fundamentally different approaches and new paradigms for effective data analysis. {/* eslint-disable-line max-len */} -

-

- Scientific visualization represents an innovative method towards tackling the current bottleneck in bioinformatics; in addition to giving researchers a unique approach for exploring large datasets, it stands to empower biologists with the ability to conduct powerful analyses without requiring a deep level of computational knowledge. {/* eslint-disable-line max-len */} -

-
- Alfred P. Sloan Logo - University of Georgia Logo - Pitch Interactive Logo -
+

About Phinch

+

+ PHINCH is an open-source framework for visualizing biological data, funded by a grant from the Alfred P. Sloan foundation. This project represents an interdisciplinary collaboration between Pitch Interactive, a data visualization studio in Oakland, CA, and biological researchers at UC Davis. {/* eslint-disable-line max-len */} +

+

+ Whether it's genes, proteins, or microbial species, Phinch provides an interactive visualization tool that allows users to explore and manipulate large biological datasets. Computer algorithms face significant difficulty in identifying simple data patterns; writing algorithms to tease out complex, subtle relationships (the type that exist in biological systems) is almost impossible. However, the human eye is adept at spotting visual patterns, able to quickly notice trends and outliers. It is this philosophy especially when presented with intuitive, well-designed software tools and user interfaces. {/* eslint-disable-line max-len */} +

+

+ The sheer volume of data produced from high-throughput sequencing technologies will require fundamentally different approaches and new paradigms for effective data analysis. {/* eslint-disable-line max-len */} +

+

+ Scientific visualization represents an innovative method towards tackling the current bottleneck in bioinformatics; in addition to giving researchers a unique approach for exploring large datasets, it stands to empower biologists with the ability to conduct powerful analyses without requiring a deep level of computational knowledge. {/* eslint-disable-line max-len */} +

+
+ Alfred P. Sloan Logo + University of Georgia Logo + Pitch Interactive Logo +
diff --git a/app/components/CheckBoxes.js b/app/components/CheckBoxes.js index 9e3c114e..e63d8b1c 100644 --- a/app/components/CheckBoxes.js +++ b/app/components/CheckBoxes.js @@ -4,7 +4,7 @@ import styles from './CheckBoxes.css'; import gstyle from './general.css'; export default class CheckBoxes extends Component { - render() { + render() { const buttons = this.props.filter.expanded ? (
{ + this.state.showLeftSidebar = + this.init.showLeftSidebar !== undefined + ? this.init.showLeftSidebar + : this.state.showLeftSidebar; + this.metrics.leftSidebar = this.state.showLeftSidebar + ? this.metrics.left.max + : this.metrics.left.min; + this.metrics.tableWidth = + this.state.width - + (this.metrics.leftSidebar + + this.metrics.filterWidth + + this.metrics.padding * 4); + + Object.keys(this.state.filters).forEach((k) => { if (!this.init.filters) { this.init.filters = {}; } if (this.init.filters[k]) { this.init.filters[k].values = this.state.filters[k].values; if (k.toLowerCase().trim().includes('date')) { - this.init.filters[k].range.max.value = new Date(this.init.filters[k].range.max.value); - this.init.filters[k].range.min.value = new Date(this.init.filters[k].range.min.value); + this.init.filters[k].range.max.value = new Date( + this.init.filters[k].range.max.value + ); + this.init.filters[k].range.min.value = new Date( + this.init.filters[k].range.min.value + ); } this.state.filters[k] = this.init.filters[k]; } @@ -177,18 +192,20 @@ export default class Filter extends Component { this.state.deleted = this.init.deleted ? this.init.deleted : []; this.state.names = this.init.names; if (this.init.sort) { - this.state.sortReverse = this.init.sort.sortReverse === undefined - ? this.state.sortReverse - : this.init.sort.sortReverse; - this.state.sortKey = this.init.sort.sortKey === undefined - ? this.state.sortKey - : this.init.sort.sortKey; + this.state.sortReverse = + this.init.sort.sortReverse === undefined + ? this.state.sortReverse + : this.init.sort.sortReverse; + this.state.sortKey = + this.init.sort.sortKey === undefined + ? this.state.sortKey + : this.init.sort.sortKey; } if (this.init && this.init.selectedVisualization) { - const validVisuals = ['sankey', 'stackedbar', 'bubblegraph'] + const validVisuals = ['sankey', 'stackedbar', 'bubblegraph']; if (validVisuals.includes(this.init.selectedVisualization)) { - this.state.selectedVisualization = this.init.selectedVisualization + this.state.selectedVisualization = this.init.selectedVisualization; } } DataContainer.setAttributes(this.state.filters); @@ -222,14 +239,16 @@ export default class Filter extends Component { } componentDidUpdate() { - ReactTooltip.rebuild() + ReactTooltip.rebuild(); } countUp() { - if(this.state.counter > 0) { + if (this.state.counter > 0) { const currCount = this.state.counter; const newCount = currCount + 1; - newCount > 8 ? this.setState({ counter: 1, }) : this.setState({ counter: newCount, }); + newCount > 8 + ? this.setState({ counter: 1 }) + : this.setState({ counter: newCount }); console.log(this.state.counter); } } @@ -251,15 +270,19 @@ export default class Filter extends Component { this.state.summary.dataKey, this.state.names, viewMetadata, - callback || (() => {}), + callback || (() => {}) ); - } + }; updateDimensions() { - this.metrics.leftSidebar = this.state.showLeftSidebar ? - this.metrics.left.max : this.metrics.left.min; - this.metrics.tableWidth = window.innerWidth - - (this.metrics.leftSidebar + this.metrics.filterWidth + (this.metrics.padding * 4)); + this.metrics.leftSidebar = this.state.showLeftSidebar + ? this.metrics.left.max + : this.metrics.left.min; + this.metrics.tableWidth = + window.innerWidth - + (this.metrics.leftSidebar + + this.metrics.filterWidth + + this.metrics.padding * 4); this.setState({ width: window.innerWidth, height: window.innerHeight, @@ -290,21 +313,33 @@ export default class Filter extends Component { name: 'Observations', }, ]; - return columns.map(c => { - const onClick = (c.id === 'order') ? (() => {}) : ( - () => { - const sortReverse = !this.state.sortReverse; - const sortKey = c.id; - const data = visSortBy(this.state.data, sortReverse, sortKey); - const deleted = visSortBy(this.state.deleted, sortReverse, sortKey); - this.setState({ - data, deleted, sortReverse, sortKey - }, () => this.save(this.setResult)); - } - ); - const arrow = (c.id !== 'order') - ? getSortArrow(this.state.sortReverse, this.state.sortKey, c.id) - : ''; + return columns.map((c) => { + const onClick = + c.id === 'order' + ? () => {} + : () => { + const sortReverse = !this.state.sortReverse; + const sortKey = c.id; + const data = visSortBy(this.state.data, sortReverse, sortKey); + const deleted = visSortBy( + this.state.deleted, + sortReverse, + sortKey + ); + this.setState( + { + data, + deleted, + sortReverse, + sortKey, + }, + () => this.save(this.setResult) + ); + }; + const arrow = + c.id !== 'order' + ? getSortArrow(this.state.sortReverse, this.state.sortKey, c.id) + : ''; return (
(e.key === ' ' ? onClick() : null)} + onKeyPress={(e) => (e.key === ' ' ? onClick() : null)} > {`${c.name} `} {arrow} @@ -336,15 +373,20 @@ export default class Filter extends Component { tableWidth={this.metrics.tableWidth} dragOver={this.dragOver} updatePhinchName={this.updatePhinchName} - removeDatum={() => { removeRows(this, [datum]); }} - restoreDatum={() => { restoreRows(this, [datum]); }} + removeDatum={() => { + removeRows(this, [datum]); + }} + restoreDatum={() => { + restoreRows(this, [datum]); + }} deleting={this.state.deleting} delete={() => this.setState({ deleting: true })} cancel={() => this.setState({ deleting: false })} /> ); - tableRow = ({ index, style }) => this.row(this.state.data[index], index, style.top, false) + tableRow = ({ index, style }) => + this.row(this.state.data[index], index, style.top, false); setResult(value) { const result = value; @@ -370,7 +412,10 @@ export default class Filter extends Component { }); } else { filter.range.min = Object.assign({}, filter.values[0]); - filter.range.max = Object.assign({}, filter.values[filter.values.length - 1]); + filter.range.max = Object.assign( + {}, + filter.values[filter.values.length - 1] + ); } filters[k] = filter; }); @@ -378,56 +423,67 @@ export default class Filter extends Component { } applyFilters(filters) { - const deletedSamples = this.state.deleted.map(d => d.sampleName); - let data = DataContainer.getSamples().map(d => { - if (this.state.names[d.sampleName]) { - d.phinchName = this.state.names[d.sampleName]; - } - return d; - }).filter(d => { - let include = true; - if (deletedSamples.includes(d.sampleName)) { - include = false; - } - Object.keys(filters).forEach((k) => { - let value = d.metadata[k]; - if (k.toLowerCase().trim().includes('date')) { - value = new Date(value); - if ( - !value.toString().toLowerCase().trim().includes('invalid date') - && - ( - value.valueOf() < new Date(filters[k].range.min.value).valueOf() - || - value.valueOf() > new Date(filters[k].range.max.value).valueOf() - ) - ) { - include = false; - } - } else if (filters[k].type === 'number' || filters[k].type === 'date') { - [value] = value.split(' '); - if (filterFloat(value) !== null) { - value = filterFloat(value); - if (value < filters[k].range.min.value || value > filters[k].range.max.value) { + const deletedSamples = this.state.deleted.map((d) => d.sampleName); + let data = DataContainer.getSamples() + .map((d) => { + if (this.state.names[d.sampleName]) { + d.phinchName = this.state.names[d.sampleName]; + } + return d; + }) + .filter((d) => { + let include = true; + if (deletedSamples.includes(d.sampleName)) { + include = false; + } + Object.keys(filters).forEach((k) => { + let value = d.metadata[k]; + if (k.toLowerCase().trim().includes('date')) { + value = new Date(value); + if ( + !value.toString().toLowerCase().trim().includes('invalid date') && + (value.valueOf() < + new Date(filters[k].range.min.value).valueOf() || + value.valueOf() > + new Date(filters[k].range.max.value).valueOf()) + ) { include = false; } + } else if ( + filters[k].type === 'number' || + filters[k].type === 'date' + ) { + [value] = value.split(' '); + if (filterFloat(value) !== null) { + value = filterFloat(value); + if ( + value < filters[k].range.min.value || + value > filters[k].range.max.value + ) { + include = false; + } + } + } else if (value !== 'no_data' && !filters[k].range[value]) { + include = false; } - } else if (value !== 'no_data' && !filters[k].range[value]) { - include = false; - } + }); + return include; }); - return include; - }); data = visSortBy(data, this.state.sortReverse, this.state.sortKey); const observations = countObservations(data); - this.setState({ filters, data, observations }, _debounce(() => { - this.save(this.setResult); - }), this.metrics.debounce, { leading: false, trailing: true }); + this.setState( + { filters, data, observations }, + _debounce(() => { + this.save(this.setResult); + }), + this.metrics.debounce, + { leading: false, trailing: true } + ); } toggleChecks(attribute, value) { const filters = _cloneDeep(this.state.filters); - Object.keys(filters[attribute].range).forEach(k => { + Object.keys(filters[attribute].range).forEach((k) => { filters[attribute].range[k] = value; }); this.applyFilters(filters); @@ -445,36 +501,37 @@ export default class Filter extends Component { number: 'Numeric Range', string: 'Categories', }; - return Object.keys(this.filters).map(k => { - const group = Object.keys(this.filters[k]).map(g => { + return Object.keys(this.filters).map((k) => { + const group = Object.keys(this.filters[k]).map((g) => { const { expanded } = this.state.filters[g]; const icon = expanded ? minus : plus; const height = expanded ? 60 : 20; - const filter = (this.state.filters[g].type === 'string') ? ( - - ) : ( - - ); + const filter = + this.state.filters[g].type === 'string' ? ( + + ) : ( + + ); const toggleExpand = () => { const filters = Object.assign({}, this.state.filters); filters[g].expanded = !filters[g].expanded; @@ -487,9 +544,11 @@ export default class Filter extends Component {
(e.key === ' ' ? toggleExpand() : null)} + onKeyPress={(e) => (e.key === ' ' ? toggleExpand() : null)} /> {filter}
@@ -500,15 +559,11 @@ export default class Filter extends Component { key={k} className={styles.bottom} style={{ - width: this.metrics.filterWidth + (this.metrics.padding * 4), + width: this.metrics.filterWidth + this.metrics.padding * 4, }} > -
- {SectionNames[k]} -
-
- {group} -
+
{SectionNames[k]}
+
{group}
); }); @@ -518,29 +573,34 @@ export default class Filter extends Component { return ( 0)} + spotlight={this.state.counter == 7 && this.state.deleted.length > 0} buttonTitle="Archived Samples" modalTitle="Archived Samples" buttonPosition={{ position: 'absolute', bottom: 0, }} - modalPosition={this.state.counter == 7 ? { - position: 'absolute', - bottom: '5rem', - width: this.metrics.tableWidth - 15, - } : { - position: 'absolute', - bottom: this.metrics.padding * 2, - width: this.metrics.tableWidth - 15, - }} + modalPosition={ + this.state.counter == 7 + ? { + position: 'absolute', + bottom: '5rem', + width: this.metrics.tableWidth - 15, + } + : { + position: 'absolute', + bottom: this.metrics.padding * 2, + width: this.metrics.tableWidth - 15, + } + } useList data={this.state.deleted} row={this.row} dataKey="sampleName" itemHeight={28} badge - />); + /> + ); } updatePhinchName(e, r) { @@ -552,9 +612,14 @@ export default class Filter extends Component { names[d.sampleName] = d.phinchName; return d; }); - this.setState({ data, names }, _debounce(() => { - this.save(this.setResult); - }), this.metrics.debounce, { leading: false, trailing: true }); + this.setState( + { data, names }, + _debounce(() => { + this.save(this.setResult); + }), + this.metrics.debounce, + { leading: false, trailing: true } + ); } dragEnd(e) { @@ -565,16 +630,15 @@ export default class Filter extends Component { this.over.style.outline = ''; if ( - Number.isNaN(source) - || - Number.isNaN(target) - || - (source === target && this.dragged.dataset.group === this.over.dataset.group) + Number.isNaN(source) || + Number.isNaN(target) || + (source === target && + this.dragged.dataset.group === this.over.dataset.group) ) { return; } - if ((e.clientY - this.over.offsetTop) > (this.over.offsetHeight / 2)) { + if (e.clientY - this.over.offsetTop > this.over.offsetHeight / 2) { target += 1; } if (source <= target) { @@ -586,7 +650,9 @@ export default class Filter extends Component { if (this.dragged.dataset.group === this.over.dataset.group) { const isRemoved = this.over.dataset.group === 'removed'; - let data = isRemoved ? _cloneDeep(this.state.deleted) : _cloneDeep(this.state.data); + let data = isRemoved + ? _cloneDeep(this.state.deleted) + : _cloneDeep(this.state.data); data.splice(target, 0, data.splice(source, 1)[0]); data = data.map((d, i) => { d.order = i; @@ -594,9 +660,11 @@ export default class Filter extends Component { }); data = visSortBy(data, sortReverse, sortKey); if (isRemoved) { - this.setState({ deleted: data, sortReverse, sortKey }, () => this.save(this.setResult)); + this.setState({ deleted: data, sortReverse, sortKey }, () => + this.save(this.setResult)); } else { - this.setState({ data, sortReverse, sortKey }, () => this.save(this.setResult)); + this.setState({ data, sortReverse, sortKey }, () => + this.save(this.setResult)); } } else { let data = _cloneDeep(this.state.data); @@ -608,19 +676,25 @@ export default class Filter extends Component { const [datum] = deleted.splice(source, 1); data.splice(target, 0, datum); } - data = data.map(d => { + data = data.map((d) => { d.order = 1; return d; }); - deleted = deleted.map(d => { + deleted = deleted.map((d) => { d.order = 1; return d; }); data = visSortBy(data, sortReverse, sortKey); deleted = visSortBy(deleted, sortReverse, sortKey); - this.setState({ - data, deleted, sortReverse, sortKey - }, () => this.save(this.setResult)); + this.setState( + { + data, + deleted, + sortReverse, + sortKey, + }, + () => this.save(this.setResult) + ); } this.over.style.background = ''; @@ -650,11 +724,14 @@ export default class Filter extends Component { toggleMenu() { const showLeftSidebar = !this.state.showLeftSidebar; - this.metrics.leftSidebar = showLeftSidebar ? - this.metrics.left.max : this.metrics.left.min; - this.metrics.tableWidth = this.state.width - ( - this.metrics.leftSidebar + this.metrics.filterWidth + (this.metrics.padding * 6) - ); + this.metrics.leftSidebar = showLeftSidebar + ? this.metrics.left.max + : this.metrics.left.min; + this.metrics.tableWidth = + this.state.width - + (this.metrics.leftSidebar + + this.metrics.filterWidth + + this.metrics.padding * 6); this.setState({ showLeftSidebar }); } @@ -670,93 +747,106 @@ export default class Filter extends Component { return (
{this.setState({ counter: 0 }); this.forceUpdate(); this.deleteBackdropTooltip()} } + role="button" + className={gstyle.helpIcons} + style={{ marginRight: '4em' }} + onClick={() => { + this.setState({ counter: 0 }); + this.forceUpdate(); + this.deleteBackdropTooltip(); + }} > close-walkthrough
this.setState({ counter: 8 })} + role="button" + tabIndex={0} + className={gstyle.helpIcons} + onClick={() => this.setState({ counter: 8 })} >
this.setState({ counter: 1 })} + role="button" + tabIndex={0} + className={gstyle.helpIcons} + onClick={() => this.setState({ counter: 1 })} >
this.setState({ counter: 2 })} + role="button" + tabIndex={0} + className={gstyle.helpIcons} + onClick={() => this.setState({ counter: 2 })} >
this.setState({ counter: 3 })} + role="button" + tabIndex={0} + className={gstyle.helpIcons} + onClick={() => this.setState({ counter: 3 })} >
this.setState({ counter: 4 })} - + role="button" + tabIndex={0} + className={gstyle.helpIcons} + onClick={() => this.setState({ counter: 4 })} >
this.setState({ counter: 5 })} + role="button" + tabIndex={0} + className={gstyle.helpIcons} + onClick={() => this.setState({ counter: 5 })} >
- To explore the 'Archived Samples' feature more, please use the{' '} - navigation bar in the bottom left to close the walkthrough. {' '} - Once closed, delete at least 1 sample row by clicking the 'X' on the far right{' '} - of the rows that is visible when the row is hovered. Then return to feature 7.
} - > + toolTipTitle={ +
+ To explore the 'Archived Samples' feature more, please use the{' '} + navigation bar in the bottom left to close the walkthrough. Once + closed, delete at least 1 sample row by clicking the 'X' on the + far right of the rows that is visible when the row is hovered. + Then return to feature 7. +
+ } + >
{this.setState({ counter: 6 }); this.renderModal();}} + role="button" + tabIndex={0} + className={gstyle.helpIcons} + onClick={() => { + this.setState({ counter: 6 }); + this.renderModal(); + }} >
{this.setState({ counter: 7 }); this.forceUpdate();}} + role="button" + tabIndex={0} + className={gstyle.helpIcons} + onClick={() => { + this.setState({ counter: 7 }); + this.forceUpdate(); + }} >
@@ -764,32 +854,37 @@ export default class Filter extends Component { ); } - /*This function deals with when the mouse hovers over the browse icon on top right of + /* This function deals with when the mouse hovers over the browse icon on top right of and changes img src accordingly to correct svg file */ - handleMouseOver (button) { - switch(button) { - case "help": - if(this.state.helpButton === needHelp) { - this.setState({helpButton: needHelpHover}); + handleMouseOver(button) { + switch (button) { + case 'help': + if (this.state.helpButton === needHelp) { + this.setState({ helpButton: needHelpHover }); } break; } } - /*This function deals with the mouse leaving an icon (no longer hovering) and + /* This function deals with the mouse leaving an icon (no longer hovering) and changed img src to correct svg file */ - handleMouseLeave (button) { - switch(button) { - case "help": - if(this.state.helpButton === needHelpHover) { - this.setState({helpButton: needHelp}); + handleMouseLeave(button) { + switch (button) { + case 'help': + if (this.state.helpButton === needHelpHover) { + this.setState({ helpButton: needHelp }); } break; } } render() { - const redirect = this.state.redirect === null ? '' : ; + const redirect = + this.state.redirect === null ? ( + '' + ) : ( + + ); const helpButtons = this.state.counter > 0 ? this.makeHelpButtons() : ''; if (redirect) { @@ -813,18 +908,20 @@ export default class Filter extends Component { border: 'none', borderRadius: '8px', padding: 0, - background: (this.state.result === 'error') ? '#ff2514' : '#00da3e', + background: this.state.result === 'error' ? '#ff2514' : '#00da3e', }} onClick={this.clearResult} - onKeyPress={e => (e.key === ' ' ? this.clearResult() : null)} + onKeyPress={(e) => (e.key === ' ' ? this.clearResult() : null)} > {this.state.result}
- ) : ''; + ) : ( + '' + ); const viewVisualization = () => { if (!this.state.selectedVisualization) { - return + return; } this.setState({ loading: true }, () => { setTimeout(() => { @@ -841,8 +938,8 @@ export default class Filter extends Component { const setSelectedVisualization = (visualization) => () => { this.setState({ selectedVisualization: visualization }, () => { this.save(this.setResult); - }) - } + }); + }; return (
@@ -858,10 +955,10 @@ export default class Filter extends Component { className={gstyle.help} // on click command is still undefined outside of home page, set to issues page for now until later onClick={() => this.setState({ counter: 8 })} - onMouseEnter={() => this.handleMouseOver("help")} - onMouseLeave={() => this.handleMouseLeave("help")} - > - needHelp + onMouseEnter={() => this.handleMouseOver('help')} + onMouseLeave={() => this.handleMouseLeave('help')} + > + needHelp
@@ -870,36 +967,67 @@ export default class Filter extends Component { observations={this.state.observations} datalength={this.state.data.length} helping={this.state.counter == 2} - /> + /> - The uploaded data file can be explored through a number{' '} - of distinct visualization types. -

- Click on one of the listed options to select that visualization type,{' '} - and then click “View Visualization” to see the graphs made by the option{' '} - you choose. -
} - style={{ backgroundColor: 'rgba(255, 255, 255, 0.2)', boxShadow: 'inset rgba(255, 255, 255, 0.5) 0px 0px 10px'}} + toolTipTitle={ +
+ The uploaded data file can be explored through a number of + distinct visualization types. +
+
+ Click on one of the listed options to select that + visualization type, and then click “View Visualization” to see + the graphs made by the option you choose. +
+ } + style={{ + backgroundColor: 'rgba(255, 255, 255, 0.2)', + boxShadow: 'inset rgba(255, 255, 255, 0.5) 0px 0px 10px', + }} >
Visualization Type
-
+
Stacked bargraph -
Stacked Bargraph
+
+ Stacked Bargraph +
-
- Sankey bargraph +
+ Sankey bargraph
Sankey Graph
+ className={classNames(styles.visOptionLabel, { + [styles.selected]: + this.state.selectedVisualization === 'sankey', + })} + > + Sankey Graph +
-
+
Bubble Graph
Bubble Graph
@@ -907,13 +1035,29 @@ export default class Filter extends Component {
(e.key === ' ' ? viewVisualization() : null)} - onMouseEnter={this.state.selectedVisualization ? () => this.handleMouseOver("viewViz") : null} - onMouseLeave={this.state.selectedVisualization ? () => this.handleMouseLeave("viewViz") : null} - data-tip={this.state.selectedVisualization ? null : 'Please select a visualization type'} - > + onKeyPress={(e) => + (e.key === ' ' ? viewVisualization() : null) + } + onMouseEnter={ + this.state.selectedVisualization + ? () => this.handleMouseOver('viewViz') + : null + } + onMouseLeave={ + this.state.selectedVisualization + ? () => this.handleMouseLeave('viewViz') + : null + } + data-tip={ + this.state.selectedVisualization + ? null + : 'Please select a visualization type' + } + > View Visualization
@@ -923,99 +1067,126 @@ export default class Filter extends Component {
+ width: + this.metrics.leftSidebar + + this.metrics.filterWidth + + this.metrics.padding * 5 - + 100, + }} + /> {this.renderHeader()}
-
+
+ /> -
- On the left panel, click the arrow button to view filtering{' '} - options for file metadata. There are three categories for{' '} - filtering: “Date Range” (to filter by time/date),{' '} - “Numerical Range” (for metadata categories that are exclusively{' '} - numerical, such as pH, temperature, etc), and “Categories”{' '} - (For metadata categories that are text only or alphanumeric{' '} - combinations). -

- “Date Range” and “Numerical Range” display the file data as histograms,{' '} - while “Categories” show the metadata values with associated checkboxes.{' '} - Histograms can be filtered using slider bars, while Categorical data can{' '} - be filtered by selecting or unselecting each checkbox. -

- All metadata populated in this panel is generated FROM THE FILE ITSELF,{' '} - and the app dynamically populates all this information after file upload. -

- Changing filter selections in this panel will cause the sample list{' '} - to automatically update, displaying only those samples that meet the{' '} - chosen filtering selections. -
- : -
- If you would like to reset the filters, scroll all the way down{' '} - on the left column to find the “Reset Filters” button. -
} - overlayStyle={{maxWidth: '600px'}} - style={{ boxShadow: 'none'}} - > -
- {this.displayFilters()} -
(e.key === ' ' ? this.resetFilters() : null)} - > - Reset Filters + toolTipTitle={ + this.state.counter == 4 ? ( +
+ On the left panel, click the arrow button to view + filtering options for file metadata. There are three + categories for filtering: “Date Range” (to filter by + time/date), “Numerical Range” (for metadata categories + that are exclusively numerical, such as pH, temperature, + etc), and “Categories” (For metadata categories that are + text only or alphanumeric combinations). +
+
+ “Date Range” and “Numerical Range” display the file data + as histograms, while “Categories” show the metadata values + with associated checkboxes. Histograms can be filtered + using slider bars, while Categorical data can be filtered + by selecting or unselecting each checkbox. +
+
+ All metadata populated in this panel is generated FROM THE + FILE ITSELF, and the app dynamically populates all this + information after file upload. +
+
+ Changing filter selections in this panel will cause the + sample list to automatically update, displaying only those + samples that meet the chosen filtering selections. +
+ ) : ( +
+ If you would like to reset the filters, scroll all the way + down on the left column to find the “Reset Filters” + button.
+ ) + } + overlayStyle={{ maxWidth: '600px' }} + style={{ boxShadow: 'none' }} + > +
+ {this.displayFilters()} +
+ (e.key === ' ' ? this.resetFilters() : null) + } + > + Reset Filters
+
= 1 ? 240 : 155)), + height: + this.state.height - (this.state.counter >= 1 ? 240 : 155), overflowY: 'overlay', }} onDragStart={this.dragStart} @@ -1024,29 +1195,38 @@ export default class Filter extends Component { - In the sample info panel, the graph shows the distribution of samples{' '} - (e.g. range of sequencing depth across samples in the uploaded file).{' '} - The red line indicates the position of the present sample row in the{' '} - overall dataset. -

- There will be icons appear for only one row at a time, on mouse over in{' '} - the filter page window. You can change the order of the samples by using{' '} - a long press of the mouse on the “up/down arrow” button on the left, or{' '} - remove the sample from the pool by the “delete” button on the right{' '} - (After the sample is manually removed, it will be listed under{' '} - “Archived Sample” at the bottom).
: ''} - overlayStyle={{width: '250px', paddingBottom: '5rem'}} - style={this.state.counter == 8 ? {zIndex: 0} : ''} - > + toolTipTitle={ + this.state.counter == 6 ? ( +
+ In the sample info panel, the graph shows the distribution + of samples (e.g. range of sequencing depth across samples + in the uploaded file). The red line indicates the position + of the present sample row in the overall dataset. +
+
+ There will be icons appear for only one row at a time, on + mouse over in the filter page window. You can change the + order of the samples by using a long press of the mouse on + the “up/down arrow” button on the left, or remove the + sample from the pool by the “delete” button on the right{' '} + (After the sample is manually removed, it will be listed + under “Archived Sample” at the bottom). +
+ ) : ( + '' + ) + } + overlayStyle={{ width: '250px', paddingBottom: '5rem' }} + style={this.state.counter == 8 ? { zIndex: 0 } : ''} + > this.state.data[index].sampleName} - > + itemKey={(index) => this.state.data[index].sampleName} + > {this.tableRow} {this.renderModal()} @@ -1054,16 +1234,14 @@ export default class Filter extends Component {
0} + isActive={this.state.counter > 0} inheritParentBackgroundColor={false} - toolTipTitle={" *mouse click anywhere to advance"} - overlayStyle={{zIndex: '1001'}} - innerStyle={{color: 'white', fontWeight: '400', fontSize: '14px'}} - style={{boxShadow: 'none'}} + toolTipTitle=" *mouse click anywhere to advance" + overlayStyle={{ zIndex: '1001' }} + innerStyle={{ color: 'white', fontWeight: '400', fontSize: '14px' }} + style={{ boxShadow: 'none' }} > -
- {helpButtons} -
+
{helpButtons}
diff --git a/app/components/FilterChart.js b/app/components/FilterChart.js index 169a77af..356265cd 100644 --- a/app/components/FilterChart.js +++ b/app/components/FilterChart.js @@ -8,7 +8,7 @@ import close from 'images/orangeX.svg'; import styles from './FilterChart.css'; import gstyle from './general.css'; -import classNames from 'classnames' +import classNames from 'classnames'; export default class FilterChart extends Component { constructor(props) { @@ -158,8 +158,8 @@ export default class FilterChart extends Component {
) :
; const style = { - width: (this.props.width - (this.padding * 2)), - margin: this.props.noMargin ? '0 8px' : '0 24px', + width: (this.props.width - (this.padding * 2)), + margin: this.props.noMargin ? '0 8px' : '0 24px', }; const brush = filter.expanded ? (
@@ -179,7 +179,7 @@ export default class FilterChart extends Component {
) : ''; const scaleToggle = this.props.showScale ? ( -
+
Percentage
@@ -188,7 +188,7 @@ export default class FilterChart extends Component { icons={false} defaultChecked={this.props.data.log} onChange={() => this.props.toggleLog(this.props.name)} - style={{backgroundColor: "red !important", height: "12px !important", }} + style={{ backgroundColor: 'red !important', height: '12px !important', }} />
Log Scale @@ -198,14 +198,19 @@ export default class FilterChart extends Component { const taxa = this.props.name.split(','); const name = taxa[taxa.length - 1].replace(/[a-zA-Z]__/g, ''); const circle = this.props.showCircle ? ( -
+ }} + /> ) : ''; return ( -
+
{circle} {remove}
{ this.setState({ deleting: true }); - } + }; cancel = () => { this.setState({ deleting: false }); - } + }; setFocus = () => { this.setState({ focused: true }); - } + }; removeFocus = () => { this.setState({ focused: false }); - } + }; render() { const action = this.props.isRemoved ? ( @@ -40,7 +40,7 @@ export default class FilterRow extends Component { role="button" tabIndex={0} onClick={this.props.restoreDatum} - onKeyPress={e => (e.key === ' ' ? this.props.restoreDatum() : null)} + onKeyPress={(e) => (e.key === ' ' ? this.props.restoreDatum() : null)} >
restore @@ -53,7 +53,7 @@ export default class FilterRow extends Component { role="button" tabIndex={0} onClick={this.delete} - onKeyPress={e => (e.key === ' ' ? this.delete() : null)} + onKeyPress={(e) => (e.key === ' ' ? this.delete() : null)} >
delete @@ -72,14 +72,17 @@ export default class FilterRow extends Component { ?

-

If yes, it can always be found and added back using the Archived Samples tab.

+

+ If yes, it can always be found and added back using the Archived + Samples tab. +

(e.key === ' ' ? this.cancel() : null)} + onKeyPress={(e) => (e.key === ' ' ? this.cancel() : null)} > Cancel
@@ -88,7 +91,9 @@ export default class FilterRow extends Component { tabIndex={0} className={`${gstyle.button} ${styles.button}`} onClick={this.props.removeDatum} - onKeyPress={e => (e.key === ' ' ? this.props.removeDatum() : null)} + onKeyPress={(e) => + (e.key === ' ' ? this.props.removeDatum() : null) + } > Archive
@@ -98,7 +103,7 @@ export default class FilterRow extends Component { ); } - const modal = (this.state.deleting) ? ( + const modal = this.state.deleting ? ( ) : null; + const className = + this.props.index % 2 === 0 ? `${styles.row} ${styles.grey}` : styles.row; - const className = (this.props.index % 2 === 0) ? ( - `${styles.row} ${styles.grey}` - ) : styles.row; - - const tableWidth = this.props.tableWidth - 300;//this scales down tableWidth for the drag, close, and frequency chart cells + const tableWidth = this.props.tableWidth - 300; // this scales down tableWidth for the drag, close, and frequency chart cells return (
- {(this.props.data.order !== undefined) ? this.props.data.order.toLocaleString() : ''} + > + {this.props.data.order !== undefined + ? this.props.data.order.toLocaleString() + : ''}
+ > this.setFocus()} - onBlur={e => this.removeFocus()} + onFocus={(e) => this.setFocus()} + onBlur={(e) => this.removeFocus()} onChange={(e) => this.props.updatePhinchName(e, this.props.data)} - /> + />
+ > {this.props.data.biomid}
+ > {this.props.data.sampleName}
- {(this.props.data.reads !== undefined) ? this.props.data.reads.toLocaleString() : ''} + > + {this.props.data.reads !== undefined + ? this.props.data.reads.toLocaleString() + : ''}
+ />
diff --git a/app/components/Home.js b/app/components/Home.js index 018d35f7..ab9d1271 100644 --- a/app/components/Home.js +++ b/app/components/Home.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import { Redirect } from 'react-router'; import _clonedeep from 'lodash.clonedeep'; -import Spotlight from "rc-spotlight"; +import Spotlight from 'rc-spotlight'; import 'antd/dist/antd.css'; import { Tooltip } from 'antd'; import ReactTooltip from 'react-tooltip'; @@ -12,19 +12,18 @@ import editOn from 'images/edit-on.svg'; import editHover from 'images/edit-hover.svg'; import fsIcon from 'images/flagshipIcons.svg'; import helpGo from 'images/needHelpDefault.png'; -import helpStop from 'images/needHelpHover.svg' +import helpStop from 'images/needHelpHover.svg'; import arrow from 'images/arrow.svg'; import arrowHover from 'images/arrowHover.svg'; import { pageView } from '../analytics'; import DataContainer from '../datacontainer'; -import { createProject } from '../projects'; -import { homedir, tmpdir } from 'os' -import {join} from 'path' -import fs from 'fs' +import { createProject, getFSProjects, getProjects, setProjectFilters, deleteProject } from '../projects'; +import { homedir, tmpdir } from 'os'; +import { join } from 'path'; +import fs from 'fs'; -import { getFSProjects, getProjects, setProjectFilters, deleteProject } from '../projects'; import SpotlightWithToolTip from './SpotlightWithToolTip'; import { FSProjectList, ProjectList } from './ProjectList'; @@ -34,6 +33,7 @@ import Modal from './Modal'; import styles from './Home.css'; import gstyle from './general.css'; + const phinchdir = join(homedir(), 'Documents', 'Phinch2.0'); export default class Home extends Component { @@ -104,7 +104,6 @@ export default class Home extends Component { this.cancelRemove = this.cancelRemove.bind(this); this.completeRemove = this.completeRemove.bind(this); this.countUpHelp = this.countUpHelp.bind(this); - } componentDidMount() { @@ -117,13 +116,13 @@ export default class Home extends Component { countUpHelp() { if (this.state.help1) { - this.setState({help2: true, help1: false}); + this.setState({ help2: true, help1: false }); } else if (this.state.help2) { - this.setState({help3: true, help2: false}); + this.setState({ help3: true, help2: false }); } else if (this.state.help3) { - this.setState({help4: true, help3: false}); + this.setState({ help4: true, help3: false }); } else if (this.state.help4) { - this.setState({help4: false, help1: true}); + this.setState({ help4: false, help1: true }); } } @@ -134,12 +133,12 @@ export default class Home extends Component { loading: false, }); } - //this reports errors if file uploaded for new project failed + // this reports errors if file uploaded for new project failed failure(type = 'file', path = '') { const error = this.errors[type](path); this.setState({ loading: false, erroring: true, error }); } - //This takes you to data view page when a project is selected at home screen + // This takes you to data view page when a project is selected at home screen view(project) { if (project.slug === 'newproject') { this.setState({ redirect: '/newproject' }); @@ -149,12 +148,11 @@ export default class Home extends Component { DataContainer.loadAndFormatData(project.data, this.success, this.failure); }, this.failure); } else if (project.flagship) { - const contents = electron.remote.getCurrentWindow().webContents - let existingFilePath = join(phinchdir, project.name, project.name + '.biom') + const contents = electron.remote.getCurrentWindow().webContents; + const existingFilePath = join(phinchdir, project.name, `${project.name}.biom`); if (fs.existsSync(existingFilePath)) { const matchingProject = this.state.projects.find(d => d.summary.name === project.name); if (matchingProject && matchingProject.data) { - this.setState({ loading: true }); DataContainer.setSummary(matchingProject, () => { DataContainer.loadAndFormatData(matchingProject.data, this.success, this.failure); @@ -166,42 +164,41 @@ export default class Home extends Component { // console.log(event) // console.log(item) // console.log(webContents) - let filePath = join(tmpdir(), item.getFilename()) - item.setSavePath(filePath) + const filePath = join(tmpdir(), item.getFilename()); + item.setSavePath(filePath); item.on('done', () => { // console.log('saved', project.name) - contents.session.removeListener('will-download', listener) - DataContainer.setSummary({ data: filePath}, () => { - console.log(project) + contents.session.removeListener('will-download', listener); + DataContainer.setSummary({ data: filePath }, () => { + console.log(project); const success = () => { const newProject = createProject({ name: project.name, data: DataContainer.getData() }); DataContainer.setSummary(newProject, () => { this.setState({ downloading: false, }, () => { - this.success() - }) + this.success(); + }); }, this.failure); - } + }; DataContainer.loadAndFormatData(filePath, success, this.failure); }, this.failure); - }) - } + }); + }; contents.session.on('will-download', listener); contents.downloadURL(project.link); this.setState({ downloading: true, - }) - - } catch(e) { - console.log(e) + }); + } catch (e) { + console.log(e); } } } else { this.failure(); } } - //This handels deletion and renaming of listed projects in home screen + // This handels deletion and renaming of listed projects in home screen edit() { const editing = !this.state.editing; Object.keys(this.shouldUpdate).forEach(k => { @@ -217,15 +214,13 @@ export default class Home extends Component { ); }); this.shouldUpdate = {}; - //this const will handle the deleting of data when projects are deleted + // this const will handle the deleting of data when projects are deleted const deleting = editing ? this.state.deleting : false; this.setState({ editing, deleting }); - if(this.state.iconSRC === editOn) { - this.setState({iconSRC: editOff}); - } - else - { - this.setState({iconSRC: editOn}); + if (this.state.iconSRC === editOn) { + this.setState({ iconSRC: editOff }); + } else { + this.setState({ iconSRC: editOn }); } } @@ -258,12 +253,12 @@ export default class Home extends Component { this.setState({ deleting: true }); } - /*This function deals with when the mouse hovers over the edit icon on top right of + /* This function deals with when the mouse hovers over the edit icon on top right of the home screen and changes img src accordingly to correct svg file */ - handleMouseOver (title) { - switch(title) { - case "edit": - if(this.state.iconSRC === editOff) { + handleMouseOver(title) { + switch (title) { + case 'edit': + if (this.state.iconSRC === editOff) { this.setState({ iconSRC: editHover }); } break; @@ -285,21 +280,17 @@ export default class Home extends Component { } } - /*This function deals with the mouse leaving an icon (no longer hovering) and + /* This function deals with the mouse leaving an icon (no longer hovering) and changed img src to correct svg file */ - handleMouseLeave (title) { - switch(title) { - case "edit": - if(this.state.iconSRC === editHover) { - this.setState({iconSRC: editOff}); - } - else if(this.state.iconSRC === editOn) - { - this.setState({iconSRC: editOn}); - } - else - { - this.setState({iconSRC: editOff}); + handleMouseLeave(title) { + switch (title) { + case 'edit': + if (this.state.iconSRC === editHover) { + this.setState({ iconSRC: editOff }); + } else if (this.state.iconSRC === editOn) { + this.setState({ iconSRC: editOn }); + } else { + this.setState({ iconSRC: editOff }); } break; case 'New to Phinch?': @@ -321,7 +312,7 @@ export default class Home extends Component { } render() { - console.log("render() method"); + console.log('render() method'); if (this.state.redirect !== null && this.state.redirect !== '/') { return ; } @@ -394,18 +385,18 @@ export default class Home extends Component { data={[modalContent]} /> ) : null; - const projects = this.setState({ editing: false }) } - type= 'projects' - />; + const projects = ( this.setState({ editing: false })} + type="projects" + />); const flagshipProjects = FSProjectList({ projectList: this.state.fsProjects, view: this.view, @@ -419,11 +410,11 @@ export default class Home extends Component {
(e.key === ' ' ? this.edit() : null)} - onMouseEnter={() => this.handleMouseOver("edit")} - onMouseLeave={() => this.handleMouseLeave("edit")} - style={{ padding: this.state.help4 || this.state.help3 ? '0.5em' : null, + onMouseEnter={() => this.handleMouseOver('edit')} + onMouseLeave={() => this.handleMouseLeave('edit')} + style={{ + padding: this.state.help4 || this.state.help3 ? '0.5em' : null, transform: this.state.help4 || this.state.help3 ? 'translate(0.5em, -0.5em)' : null }} > diff --git a/app/components/LinkList.js b/app/components/LinkList.js index 4b4a3a49..dba5b442 100644 --- a/app/components/LinkList.js +++ b/app/components/LinkList.js @@ -1,12 +1,12 @@ import { shell } from 'electron'; -import React, { MouseEventHandler, MouseEvent} from 'react'; +import React, { MouseEventHandler, MouseEvent } from 'react'; import styles from './LinkList.css'; const linkList = [ { name: 'New to Phinch?', - icon: (context) => (
right facing arrow
), + icon: (context) => (
right facing arrow
), action: () => { shell.openExternal('https://phinch.org/Tutorials'); }, info: 'Click here for data formatting instructions and visualization tutorials.', handleMouseOver: (context) => { context.handleMouseOver('New to Phinch?'); }, @@ -39,7 +39,7 @@ const linkList = [ { name: 'Find a software issue?', icon: (context) => (
right facing arrow
), - action: () => { shell.openExternal('https://github.com/PhinchApp/Phinch/issues' ); }, + action: () => { shell.openExternal('https://github.com/PhinchApp/Phinch/issues'); }, info: 'Report software bugs and errors on our Github issue tracker.', handleMouseOver: (context) => { context.handleMouseOver('Find a software issue?'); }, handleMouseLeave: (context) => { context.handleMouseLeave('Find a software issue?'); }, @@ -48,7 +48,6 @@ const linkList = [ function InfoLink(l, i, context) { - return (
{ - let colorChangeInterval = null + let colorChangeInterval = null; if (loading) { colorChangeInterval = setInterval(() => { - setLoaderColor(palette[Math.floor(Math.random() * palette.length)]) - }, 5000) + setLoaderColor(palette[Math.floor(Math.random() * palette.length)]); + }, 5000); } return () => { - clearTimeout(colorChangeInterval) - } - }, [loading]) - return loading ? ( + clearInterval(colorChangeInterval); // Changed clearTimeout to clearInterval + }; + }, [loading]); + + return loading ? (
-
-
-
-
-
+
+
+
+
+
+
+
+

File is parsing into Phinch.
This make take several minutes.

+
- ) : ''; + ) : ( + '' + ); } - diff --git a/app/components/Modal.js b/app/components/Modal.js index 4cd5b369..2a8799d6 100644 --- a/app/components/Modal.js +++ b/app/components/Modal.js @@ -79,7 +79,7 @@ export default class Modal extends Component { itemSize={this.props.itemHeight} itemCount={this.props.data.length} itemKey={index => this.props.data[index][this.props.dataKey]} - > + > {this.stackRow} ) : ( @@ -97,8 +97,8 @@ export default class Modal extends Component {
) :
; return ( -

The “Archived Samples” tab not be visible in the filter page{' '} window unless you choose to remove samples from the main list{' '} - using the “X” button.
+ using the “X” button. +
} - overlayStyle={{maxWidth: "700px"}} - > + overlayStyle={{ maxWidth: '700px' }} + > {modal} - {badge} - {button} + {badge} + {button}
); } diff --git a/app/components/NewProject.js b/app/components/NewProject.js index 24bc9cdd..09091d16 100644 --- a/app/components/NewProject.js +++ b/app/components/NewProject.js @@ -7,9 +7,11 @@ import logo from 'images/phinch.svg'; import back from 'images/back.svg'; import hoverBack from 'images/backArrowW.svg'; import upload from 'images/upload.svg'; -import browseOn from 'images/browseOn.svg'; -import browseOff from 'images/browseOff.svg'; +import btn_browse_rollover from 'images/btn_browse_rollover.png'; +import btn_browseOff from 'images/btn_browseOff.png'; import filterOn from 'images/filterOn.svg'; +import btn_filter_data from 'images/btn_filter_data.png'; +import btn_filterdata_default from 'images/btn_filterdata_default.png' import filterOff from 'images/filterOff.svg'; import flagshipOn from 'images/flagshipOn.png'; import flagshipOff from 'images/flagshipOff.png'; @@ -102,33 +104,33 @@ export default class NewProject extends Component { width: window.innerWidth, height: window.innerHeight, showLeftSidebar: true, - browse: browseOff, - filter: filterOff, + browse: btn_browseOff, + filter: btn_filterdata_default, flagship: flagshipOff, helpButton: needHelp, backArrow: back, }; - /*This sets the width of the sidemenu to 100px (same as logo) and it also has a min max set for + /* This sets the width of the sidemenu to 100px (same as logo) and it also has a min max set for the size of the sidebar to maximize other components screen size - NOTE: left min and max should be removed someday as the current formula is more effecient.*/ + NOTE: left min and max should be removed someday as the current formula is more effecient. */ this.metrics = { - leftSidebar: "100px", + leftSidebar: '100px', left: { min: -10, - max: (window.innerWidth*0.0672 + 30), + max: (window.innerWidth * 0.0672 + 30), }, }; this.menuItems = [ { - id: "back", + id: 'back', name: 'Back', action: () => { this.setState({ redirect: '/Home' }); this.rebuildTooltip(); }, - icon: back-arrow, + icon: back-arrow, }, ]; @@ -174,7 +176,7 @@ export default class NewProject extends Component { } rebuildTooltip() { - console.log("render() method"); + console.log('render() method'); } updateSummary(project) { @@ -282,28 +284,28 @@ export default class NewProject extends Component { return false; } - /*This function deals with when the mouse hovers over the browse icon on top right of + /* This function deals with when the mouse hovers over the browse icon on top right of and changes img src accordingly to correct svg file */ - handleMouseOver (button) { - switch(button) { - case "browse": - if(this.state.browse === browseOff) { - this.setState({browse: browseOn}); + handleMouseOver(button) { + switch (button) { + case 'browse': + if (this.state.browse === btn_browseOff) { + this.setState({ browse: btn_browse_rollover }); } break; - case "filter": - if(this.state.filter === filterOff) { - this.setState({filter: filterOn}); + case 'filter': + if (this.state.filter === btn_filterdata_default) { + this.setState({ filter: btn_filter_data }); } break; - case "flagship": - if(this.state.flagship === flagshipOff) { - this.setState({flagship: flagshipOn}); + case 'flagship': + if (this.state.flagship === flagshipOff) { + this.setState({ flagship: flagshipOn }); } break; - case "help": - if(this.state.helpButton === needHelp) { - this.setState({helpButton: needHelpHover}); + case 'help': + if (this.state.helpButton === needHelp) { + this.setState({ helpButton: needHelpHover }); } break; case 'back': @@ -312,35 +314,35 @@ export default class NewProject extends Component { } } - /*This function deals with the mouse leaving an icon (no longer hovering) and + /* This function deals with the mouse leaving an icon (no longer hovering) and changed img src to correct svg file */ - handleMouseLeave (button) { - switch(button) { - case "browse": - if(this.state.browse === browseOn) { - this.setState({browse: browseOff}); + handleMouseLeave(button) { + switch (button) { + case 'browse': + if (this.state.browse === btn_browse_rollover) { + this.setState({ browse: btn_browseOff }); } break; - case "filter": - if(this.state.filter === filterOn) { - this.setState({filter: filterOff}); + case 'filter': + if (this.state.filter === btn_filter_data) { + this.setState({ filter: btn_filterdata_default }); } break; - case "flagship": - if(this.state.flagship === flagshipOn) { - this.setState({flagship: flagshipOff}); + case 'flagship': + if (this.state.flagship === flagshipOn) { + this.setState({ flagship: flagshipOff }); } break; - case "help": - if(this.state.helpButton === needHelpHover) { - this.setState({helpButton: needHelp}); + case 'help': + if (this.state.helpButton === needHelpHover) { + this.setState({ helpButton: needHelp }); } break; case 'back': this.setState({ backArrow: back }); break; - } - } + } + } render() { const redirect = this.state.redirect === null ? '' : ; @@ -348,8 +350,8 @@ export default class NewProject extends Component { const result = (this.state.valid === 'Yes') ? (
Sample Datasets

Whether it's your first time here or if your just want to explore Phinch's capabilities before your're ready to upload your own BIOM file, use the links - below to download a dataset to your local HD that you can use

- + below to download a dataset to your local HD that you can use +

+
diff --git a/app/components/ProjectList.js b/app/components/ProjectList.js index e1532a38..4782d095 100644 --- a/app/components/ProjectList.js +++ b/app/components/ProjectList.js @@ -19,7 +19,7 @@ function ProjectThumb(props) { onClick={() => props.remove(props.project)} onKeyPress={e => (e.key === ' ' ? props.remove(props.project) : null)} > - remove + remove
) : null; const icon = isNew ? ( @@ -27,7 +27,7 @@ function ProjectThumb(props) { className={`${styles.info} ${styles.new}`} src={props.project.thumb} alt={props.project.summary.name} - data-tip='Click to load a new file' + data-tip="Click to load a new file" /> ) : (
@@ -79,11 +79,11 @@ function ProjectThumb(props) { const returnValue = isNew ? (
) : ( - Each saved project will be displayed with file size{' '} - (in Mb), number of biological samples, and{' '} - number of observations (number of ASVs, OTUs, Contigs, etc.).{' '} - This information will be calculated by Phinch during file upload. -

- The saved project is only saved locally on the user’s hard drive (never uploaded to the cloud). + isActive={props.help3 && props.index<2} + inheritParentBackgroundColor + toolTipPlacement="bottomLeft" + overlayStyle={{ maxWidth: '380px' }} + toolTipTitle={
+ Each saved project will be displayed with file size{' '} + (in Mb), number of biological samples, and{' '} + number of observations (number of ASVs, OTUs, Contigs, etc.).{' '} + This information will be calculated by Phinch during file upload. +

+ The saved project is only saved locally on the user’s hard drive (never uploaded to the cloud).
} - key={props.index} + key={props.index} >
); - return(returnValue); + return (returnValue); } function fsThumb(props) { - const icon = (flagship); + const icon = (flagship); const onClick = () => props.view(props.project); const name = (

{props.project.name}

); @@ -152,7 +152,7 @@ function fsThumb(props) { } export function ProjectList(props) { - const [newProjectTT, setNewProjectTT] = useState(false) + const [newProjectTT, setNewProjectTT] = useState(false); const editClass = props.editing ? styles.edit : ''; const projects = props.projectList.map((p, i) => ProjectThumb({ project: p, @@ -168,9 +168,9 @@ export function ProjectList(props) { return (
ReactTooltip.rebuild()} - onMouseLeave={() => setNewProjectTT(false)} + className={`${styles.projects} ${editClass}`} + onMouseOver={() => ReactTooltip.rebuild()} + onMouseLeave={() => setNewProjectTT(false)} > {projects}
diff --git a/app/components/Sankey/SankeyTooltip.js b/app/components/Sankey/SankeyTooltip.js index 6166aadc..d3752848 100644 --- a/app/components/Sankey/SankeyTooltip.js +++ b/app/components/Sankey/SankeyTooltip.js @@ -13,7 +13,9 @@ const percentFormatter = (value) => `${Math.round(value * 10000) / 100}%`; function Datum(props) { - const { name, counts, totalCounts, color, maxWidth } = props; + const { + name, counts, totalCounts, color, maxWidth + } = props; return (
Taxonomy:
diff --git a/app/components/Sankey/index.js b/app/components/Sankey/index.js index 7193eb76..75f23cdf 100644 --- a/app/components/Sankey/index.js +++ b/app/components/Sankey/index.js @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react' +import React, { useEffect, useMemo, useRef, useState } from 'react'; import styles from './styles.css'; import { sankey, sankeyJustify, sankeyLinkHorizontal } from 'd3-sankey'; import { scaleOrdinal } from 'd3'; @@ -7,25 +7,25 @@ import { debounce } from 'lodash'; import datacontainer from '../../datacontainer'; import SankeyTooltip from './SankeyTooltip'; import SpotlightWithToolTip from '../SpotlightWithToolTip'; -const nodeWidth = 15 // width of node rects -const nodePadding = 0 // vertical separation between adjacent nodes -const getNodeFullName = (node) => { +const nodeWidth = 15; // width of node rects +const nodePadding = 0; // vertical separation between adjacent nodes - const nameParts = [node.name] - let currentNode = node +const getNodeFullName = (node) => { + const nameParts = [node.name]; + let currentNode = node; while (true) { if (currentNode.targetLinks && currentNode.targetLinks.length) { - currentNode = currentNode.targetLinks[0].source - nameParts.push(currentNode.name) + currentNode = currentNode.targetLinks[0].source; + nameParts.push(currentNode.name); } else { - break + break; } } - nameParts.reverse() - const fullName = nameParts.join(',') - return fullName -} + nameParts.reverse(); + const fullName = nameParts.join(','); + return fullName; +}; function TextWithBackground(props) { const { children, renderSVG, ...restOfProps } = props; const ref = useRef(null); @@ -38,21 +38,21 @@ function TextWithBackground(props) { setWidth(dimensions.width); setHeight(dimensions.height); } - }, [children]) + }, [children]); const lrPadding = 5; const tbPadding = 2; return ( {width && height && !renderSVG ? : null} {children} @@ -61,50 +61,50 @@ function TextWithBackground(props) { } export default function Sankey(props) { - - let { data, width, height, colors, setRef, renderSVG, helpCounter, clickDatum, colorScale, highlightedDatum } = props - const levels = datacontainer.getLevels() - - const listRef = useRef() - const containerRef = useRef() - - const marginLeft = 5 - const marginRight = Math.max(300, width * 0.2) - const marginTop = 30 - const marginBottom = 28 - const connectingPathWidth = 50 - const connecingPathPadding = 5 + const { + data, width, height, colors, setRef, renderSVG, helpCounter, clickDatum, colorScale, highlightedDatum + } = props; + const levels = datacontainer.getLevels(); + + const listRef = useRef(); + const containerRef = useRef(); + + const marginLeft = 5; + const marginRight = Math.max(300, width * 0.2); + const marginTop = 30; + const marginBottom = 28; + const connectingPathWidth = 50; + const connecingPathPadding = 5; const sankeyData = useMemo(() => { - const nodes = [] - const links = [] - let maxNamePartsLength = 0 + const nodes = []; + const links = []; + let maxNamePartsLength = 0; data.forEach((sample) => { sample.sequences.forEach(sequence => { - const { name, reads } = sequence - const nameParts = name.split(',') - maxNamePartsLength = Math.max(maxNamePartsLength, nameParts.length) + const { name, reads } = sequence; + const nameParts = name.split(','); + maxNamePartsLength = Math.max(maxNamePartsLength, nameParts.length); for (let i = 0; i < nameParts.length - 1; i++) { - const source = nameParts[i] - const target = nameParts[i + 1] - const link = links.find(l => l.source === source && l.target === target) + const source = nameParts[i]; + const target = nameParts[i + 1]; + const link = links.find(l => l.source === source && l.target === target); if (nodes.find(n => n.name === source) === undefined) { - nodes.push({ name: source }) + nodes.push({ name: source }); } if (nodes.find(n => n.name === target) === undefined) { - nodes.push({ name: target }) + nodes.push({ name: target }); } if (link) { - link.value += reads + link.value += reads; } else { - links.push({ source, target, value: reads }) + links.push({ source, target, value: reads }); } } - }) - }) + }); + }); if (!nodes.length && !links.length) { - - return { nodes, links, maxNamePartsLength } + return { nodes, links, maxNamePartsLength }; } const sankeyLayout = sankey() .nodeId(d => d.name) @@ -113,156 +113,160 @@ export default function Sankey(props) { .nodePadding(nodePadding) // .width(width) // .height(height) - .extent([[0, 0], [width - marginRight - marginLeft, height - marginBottom - marginTop]]) + .extent([[0, 0], [width - marginRight - marginLeft, height - marginBottom - marginTop]]); - let graph = null + let graph = null; try { graph = sankeyLayout({ links, nodes, - }) - + }); } catch (e) { - console.error(e) + console.error(e); } graph.nodes.forEach(node => { - node.fullName = getNodeFullName(node) - }) - return graph - }, [data, width, height]) + node.fullName = getNodeFullName(node); + }); + return graph; + }, [data, width, height]); const depthOneSum = useMemo(() => { - const depthOneNodes = sankeyData.nodes.filter(n => n.depth === 1) - return depthOneNodes.reduce((sum, node) => sum + node.value, 0) - }, [sankeyData]) + const depthOneNodes = sankeyData.nodes.filter(n => n.depth === 1); + return depthOneNodes.reduce((sum, node) => sum + node.value, 0); + }, [sankeyData]); - const maxDepth = Math.max(...sankeyData.nodes.map(n => n.depth)) - const maxLayerName = isFinite(maxDepth) && levels[maxDepth] ? levels[maxDepth].name : '' - const listHeight = height - marginTop - marginBottom - const listItemHeight = 20 - const listItemsVisible = Math.floor(listHeight / listItemHeight) - const [scrollOffset, setScrollOffset] = useState(0) - const [hoveredListItem, setHoveredListItem] = useState(null) + const maxDepth = Math.max(...sankeyData.nodes.map(n => n.depth)); + const maxLayerName = isFinite(maxDepth) && levels[maxDepth] ? levels[maxDepth].name : ''; + const listHeight = height - marginTop - marginBottom; + const listItemHeight = 20; + const listItemsVisible = Math.floor(listHeight / listItemHeight); + const [scrollOffset, setScrollOffset] = useState(0); + const [hoveredListItem, setHoveredListItem] = useState(null); const hoverListItem = (node, hoverType = 'list') => (e) => { if (!node) { - setHoveredListItem(null) - return + setHoveredListItem(null); + return; } - const { name, value } = node + const { name, value } = node; // const listX = listRef.current.getBoundingClientRect().x - const containerPosition = containerRef.current.getBoundingClientRect() - let y = e.target.getBoundingClientRect().top - let x = 0 + const containerPosition = containerRef.current.getBoundingClientRect(); + let y = e.target.getBoundingClientRect().top; + let x = 0; if (hoverType === 'sankey') { x = e.target.getBoundingClientRect().left - listRef.current.getBoundingClientRect().left + - e.target.getBoundingClientRect().width + 5 - y = e.clientY + e.target.getBoundingClientRect().width + 5; + y = e.clientY; } const position = { x, y: y - containerPosition.top, - } - const color = colorScale(node.fullName) + }; + const color = colorScale(node.fullName); if (isNaN(position.y)) { - debugger + } - setHoveredListItem({position, name, counts: value, totalCounts: depthOneSum, color, hoverType}) - } + setHoveredListItem({ + position, name, counts: value, totalCounts: depthOneSum, color, hoverType + }); + }; const onListScroll = debounce(() => { - setHoveredListItem(null) - const listScroll = listRef.current.scrollTop - const listScrollAlignedToListItemHeight = Math.floor(listScroll / listItemHeight) * listItemHeight - const newOffset = listScrollAlignedToListItemHeight / listItemHeight - setScrollOffset(newOffset) - }, 200) - - const listItems = sankeyData.nodes.filter(node => { - return node.depth === maxDepth - }).sort((a, b) => a.y0 - b.y0) + setHoveredListItem(null); + const listScroll = listRef.current.scrollTop; + const listScrollAlignedToListItemHeight = Math.floor(listScroll / listItemHeight) * listItemHeight; + const newOffset = listScrollAlignedToListItemHeight / listItemHeight; + setScrollOffset(newOffset); + }, 200); + + const listItems = sankeyData.nodes.filter(node => node.depth === maxDepth).sort((a, b) => a.y0 - b.y0); listItems.forEach((listItem, i) => { - listItem.listItemVisible = i >= scrollOffset && i < scrollOffset + listItemsVisible - }) + listItem.listItemVisible = i >= scrollOffset && i < scrollOffset + listItemsVisible; + }); // console.log(listItems) const checkNodeVisibility = (_node) => { - let visibility = false + let visibility = false; const recurseNode = (node) => { if (node.listItemVisible) { - visibility = true - return + visibility = true; + return; } if (node.sourceLinks) { // source links are to the right of this node node.sourceLinks.forEach(link => { // target goes to the next item in the chain - recurseNode(link.target) - }) + recurseNode(link.target); + }); } - } - recurseNode(_node) - return visibility - } + }; + recurseNode(_node); + return visibility; + }; const checkLinkVisibility = (_link) => { - let visibility = _link.target.hasFinalNodeVisible + const visibility = _link.target.hasFinalNodeVisible; - return visibility - } + return visibility; + }; const checkNodeHoverVisibility = (_node) => { - let visibility = false + let visibility = false; const recurseNode = (node) => { if (highlightedDatum && (node.fullName === highlightedDatum.datum.name || node.fullName.split(',').pop() === highlightedDatum.datum.name.split(',').pop()) ) { - visibility = true - return + visibility = true; + return; } else if (hoveredListItem && node.name === hoveredListItem.name) { - visibility = true - return + visibility = true; + return; } if (node.sourceLinks) { // source links are to the right of this node node.sourceLinks.forEach(link => { // target goes to the next item in the chain - recurseNode(link.target) - }) + recurseNode(link.target); + }); } - } - recurseNode(_node) - return visibility - } + }; + recurseNode(_node); + return visibility; + }; sankeyData.nodes.forEach(node => { - node.hasFinalNodeVisible = checkNodeVisibility(node) - node.hasHoveredNodeVisible = checkNodeHoverVisibility(node) - }) + node.hasFinalNodeVisible = checkNodeVisibility(node); + node.hasHoveredNodeVisible = checkNodeHoverVisibility(node); + }); sankeyData.links.forEach(link => { - link.hasFinalNodeVisible = checkLinkVisibility(link) - link.hasHoveredNodeVisible = link.target.hasHoveredNodeVisible - }) + link.hasFinalNodeVisible = checkLinkVisibility(link); + link.hasHoveredNodeVisible = link.target.hasHoveredNodeVisible; + }); const pathGradients = colors === 'mix' && sankeyData && sankeyData.links.map((link, i) => { - const { source, target } = link - const gradientId = `gradient-${i}` - const sourceColor = colorScale(source.fullName) - const targetColor = colorScale(target.fullName) + const { source, target } = link; + const gradientId = `gradient-${i}`; + const sourceColor = colorScale(source.fullName); + const targetColor = colorScale(target.fullName); const gradient = ( - + - - + > + + - ) - return gradient - }) + ); + return gradient; + }); const nodes = ( {sankeyData && sankeyData.nodes.map(node => { - let opacity = node.listItemVisible || node.hasFinalNodeVisible ? 1 : 0.2 + let opacity = node.listItemVisible || node.hasFinalNodeVisible ? 1 : 0.2; if ((hoveredListItem || highlightedDatum) && !node.hasHoveredNodeVisible) { - opacity = 0.1 + opacity = 0.1; } return ( - )})} + ); +})} - ) + ); const paths = ( {sankeyData && sankeyData.links.map((link, linkIndex) => { - let strokeOpacity = link.hasFinalNodeVisible ? 0.5 : 0 + let strokeOpacity = link.hasFinalNodeVisible ? 0.5 : 0; if (hoveredListItem || highlightedDatum) { - if (!link.hasHoveredNodeVisible) { - strokeOpacity = strokeOpacity * 0.2 + strokeOpacity *= 0.2; } } - let stroke = null + let stroke = null; if (colors === 'mix') { - stroke = `url(#gradient-${linkIndex})` + stroke = `url(#gradient-${linkIndex})`; } else if (colors === 'left') { - stroke = colorScale(link.source.fullName) + stroke = colorScale(link.source.fullName); } else if (colors === 'right') { - stroke = colorScale(link.target.fullName) + stroke = colorScale(link.target.fullName); } return ( @@ -310,80 +314,79 @@ export default function Sankey(props) { style={{ fill: 'none', stroke, - strokeOpacity: strokeOpacity, - strokeWidth: Math.max(1,link.width) + strokeOpacity, + strokeWidth: Math.max(1, link.width) }} /> - ) + ); })} - ) + ); const nodeLabels = ( {sankeyData && sankeyData.nodes.map(node => { - const tryToShowLabel = node.depth !== maxDepth && node.hasFinalNodeVisible - const nodeHeight = node.y1 - node.y0 + const tryToShowLabel = node.depth !== maxDepth && node.hasFinalNodeVisible; + const nodeHeight = node.y1 - node.y0; if (!hoveredListItem && (!tryToShowLabel || nodeHeight < 10)) { - return null + return null; } else if (hoveredListItem || highlightedDatum) { if (node.depth === maxDepth) { - return null + return null; } if (!node.hasHoveredNodeVisible) { - return null + return null; } - } return ( {node.name} - ) + ); })} - ) + ); - const maxNumberLength = `${listItems.length}`.length - const listNumberWidth = `${maxNumberLength}ch` + const maxNumberLength = `${listItems.length}`.length; + const listNumberWidth = `${maxNumberLength}ch`; const clickListItem = (node) => () => { clickDatum({ name: node.fullName, - }) - } + }); + }; useEffect(() => { if (highlightedDatum && highlightedDatum.datum) { - let node = document.querySelector(`[data-fullname="${highlightedDatum.datum.name}"]`) + let node = document.querySelector(`[data-fullname="${highlightedDatum.datum.name}"]`); if (!node) { - node = document.querySelector(`[data-lastname="${highlightedDatum.datum.name.split(',').pop()}"]`) + node = document.querySelector(`[data-lastname="${highlightedDatum.datum.name.split(',').pop()}"]`); } if (node) { - node.parentNode.scrollTop = node.offsetTop - node.parentNode.offsetTop + node.parentNode.scrollTop = node.offsetTop - node.parentNode.offsetTop; // node.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' }) } else { - console.log('node not found', highlightedDatum.datum.name) + console.log('node not found', highlightedDatum.datum.name); } } - }, [highlightedDatum]) + }, [highlightedDatum]); const list = listItems.map((node, i) => { if (node.depth !== maxDepth) { - return null + return null; } - let opacity = 1 + let opacity = 1; if ((hoveredListItem && hoveredListItem.name !== node.name) || ( - highlightedDatum && (highlightedDatum.datum.name !== node.fullName + highlightedDatum && (highlightedDatum.datum.name !== node.fullName && highlightedDatum.datum.name.split(',').pop() !== node.fullName.split(',').pop() - ))) { - opacity = 0.2 + ))) { + opacity = 0.2; } // console.log(node) - const color = colorScale(node.fullName) + const color = colorScale(node.fullName); return (
- {i + 1}
{node.name} + {i + 1}
{node.name} {node.value.toLocaleString()}
- ) - }) + ); + }); const listConnectingLines = listItems.filter(d => d.listItemVisible) .map((node, nodeIndex) => { if ( (hoveredListItem && hoveredListItem.name !== node.name) - || (highlightedDatum && (highlightedDatum.datum.name !== node.fullName && highlightedDatum.datum.name.split(',').pop() !== node.fullName.split(',').pop() )) - ) { - return null + || (highlightedDatum && (highlightedDatum.datum.name !== node.fullName && highlightedDatum.datum.name.split(',').pop() !== node.fullName.split(',').pop())) + ) { + return null; } - const x1 = node.x1 + connecingPathPadding - const y1 = node.y0 + (node.y1 - node.y0) / 2 + const x1 = node.x1 + connecingPathPadding; + const y1 = node.y0 + (node.y1 - node.y0) / 2; - const x2 = width - marginRight + connectingPathWidth - const y2 = nodeIndex * listItemHeight + listItemHeight / 2 + (listItemHeight * 2) + const x2 = width - marginRight + connectingPathWidth; + const y2 = nodeIndex * listItemHeight + listItemHeight / 2 + (listItemHeight * 2); // construct a bezier between the two points - const path = `M ${x1} ${y1} C ${x1 + connectingPathWidth * 0.5} ${y1} ${x2 - connectingPathWidth * 0.5} ${y2} ${x2} ${y2}` + const path = `M ${x1} ${y1} C ${x1 + connectingPathWidth * 0.5} ${y1} ${x2 - connectingPathWidth * 0.5} ${y2} ${x2} ${y2}`; return ( - ) - }) - const listWidth = marginRight - connecingPathPadding * 2 - connectingPathWidth + ); + }); + const listWidth = marginRight - connecingPathPadding * 2 - connectingPathWidth; const svgList = renderSVG ? ( - + - - {maxLayerName} Layer + + {maxLayerName} Layer Sequences - Counts + Counts {listItems.filter(d => d.listItemVisible) .map((node, nodeIndex) => { - const rectFill = `rgba(178, 178, 178, ${nodeIndex % 2 === 0 ? '0.2' : '0.5'})` + const rectFill = `rgba(178, 178, 178, ${nodeIndex % 2 === 0 ? '0.2' : '0.5'})`; return ( {nodeIndex + scrollOffset} @@ -469,63 +474,63 @@ export default function Sankey(props) { x={listItemHeight + 20} dx={listNumberWidth} y={listItemHeight / 2} - dy={'.35em'} + dy=".35em" > {node.name} {node.value.toLocaleString()} - ) + ); }) } - ) : null + ) : null; const levelLabels = levels ? levels.map((level, i) => { if (i > maxDepth) { - return null + return null; } - const matchingNode = sankeyData.nodes.find(node => node.depth === i) - let x = 0 + const matchingNode = sankeyData.nodes.find(node => node.depth === i); + let x = 0; if (matchingNode) { - x = i === 0 ? matchingNode.x0 : (matchingNode.x1 + matchingNode.x0) / 2 + x = i === 0 ? matchingNode.x0 : (matchingNode.x1 + matchingNode.x0) / 2; } - const nameCapitalized = level.name.charAt(0).toUpperCase() + level.name.slice(1) - const textAnchor = i === 0 ? 'start' : i === levels.length - 1 ? 'end' : 'middle' + const nameCapitalized = level.name.charAt(0).toUpperCase() + level.name.slice(1); + const textAnchor = i === 0 ? 'start' : i === levels.length - 1 ? 'end' : 'middle'; return ( {nameCapitalized} - ) - }) : null + ); + }) : null; const tooltip = hoveredListItem ? ( - ) : null - const helpOpen = helpCounter !== 0 + ) : null; + const helpOpen = helpCounter !== 0; const containerStyle = { height: helpOpen ? height - 90 : height, borderRadius: helpOpen ? '0.5em' : null, pointerEvents: helpOpen ? 'none' : null, - } + }; return (
+ }} + > The Sankey diagram displays the “flow” of data across hierarchal levels (taxonomy, gene ontologies, etc.).
From left to right, data is shown flowing from highest to lowest levels.
@@ -563,35 +569,38 @@ export default function Sankey(props) { >
+ }} + > The subway chart at the top of the visual window can be used to display more or fewer Sankey levels. Typing in the autocomplete search box can also be used to find specific taxa/genes in your dataset Note: the scroll bar will only display information from the right-most Sankey level; use the subway chart to access data from different levels.
-
+
Choosing “Left” or “Right” from the drop down menu will change the coloring of the Sankey chart according to the data displayed on the left or right of the space between each level, respectively.
: null } - style={{ backgroundColor: 'rgba(255, 255, 255, 1)', boxShadow: 'inset rgba(255, 255, 255, 0.5) 0px 0px 10px'}} + style={{ backgroundColor: 'rgba(255, 255, 255, 1)', boxShadow: 'inset rgba(255, 255, 255, 0.5) 0px 0px 10px' }} > -
+
- - + + - + - - + + @@ -609,37 +618,41 @@ export default function Sankey(props) { - The data within in a selected Sankey level is shown on the right. - Users can scroll up and down to move up and down the sankey bar charts. - As you scroll, the corresponding data points (and their respective counts) will be highlighted in the main Sankey graph. -

- Clicking on a data point in the Sankey window will cause a sidebar to appear on the right, where users can use slider bars to filter out proportions of taxa if needed (similar functionality as in the taxonomy bar chart visualization). + }} + > + The data within in a selected Sankey level is shown on the right. + Users can scroll up and down to move up and down the sankey bar charts. + As you scroll, the corresponding data points (and their respective counts) will be highlighted in the main Sankey graph. +

+ Clicking on a data point in the Sankey window will cause a sidebar to appear on the right, where users can use slider bars to filter out proportions of taxa if needed (similar functionality as in the taxonomy bar chart visualization). -
} +
} > -
+ }} + >
{maxLayerName} Layer shown by scrolling:
- + Sequences Counts @@ -662,5 +675,5 @@ export default function Sankey(props) {
- ) + ); } diff --git a/app/components/Search.js b/app/components/Search.js index b53907c4..8de997aa 100644 --- a/app/components/Search.js +++ b/app/components/Search.js @@ -50,7 +50,7 @@ export default class Search extends Component { }; selected = (e, { suggestion }) => { - this.setState({ value: ''}) + this.setState({ value: '' }); this.props.onSuggestionSelected(e, { suggestion }); } render() { diff --git a/app/components/SideBar.js b/app/components/SideBar.js index 455e7c45..a3a6081c 100644 --- a/app/components/SideBar.js +++ b/app/components/SideBar.js @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import Spotlight from "rc-spotlight"; +import Spotlight from 'rc-spotlight'; import { remote } from 'electron'; import SpotlightWithToolTip from './SpotlightWithToolTip'; @@ -29,9 +29,9 @@ import { stackOffsetSilhouette } from 'd3'; import { findLastKey } from 'lodash-es'; export default function SideBar(props) { - const {inAboutPage} = props + const { inAboutPage } = props; // const version = remote.app.getVersion(); // this gives the electron version which isn't what we want - const version = '2.1' // hard code fow now.. + const version = '2.2'; // hard code fow now.. const links = LinkList(props.context); const helpIcon = props.context.state.helpIcon; const help1Icon = help1; @@ -61,24 +61,26 @@ export default function SideBar(props) { // } // } - const hideStep2 = inAboutPage - const hideStep3 = inAboutPage - const hideStep4 = inAboutPage || (props.context.state.projects && props.context.state.projects.length === 1) + const hideStep2 = inAboutPage; + const hideStep3 = inAboutPage; + const hideStep4 = inAboutPage || (props.context.state.projects && props.context.state.projects.length === 1); let helpButtons = null; - if(props.context.state.helping) { + if (props.context.state.helping) { helpButtons = (
props.context.setState({ help1: !props.context.state.help1, + onClick={() => props.context.setState({ + help1: !props.context.state.help1, help2: false, help3: false, - help4: false})} - //onMouseEnter={() => handleMouseOver("help1")} - //onMouseLeave={() => handleMouseLeave("help1")} + help4: false +})} + // onMouseEnter={() => handleMouseOver("help1")} + // onMouseLeave={() => handleMouseLeave("help1")} > help1
@@ -87,14 +89,16 @@ export default function SideBar(props) { tabIndex={0} className={styles.helpIcons} onClick={(e) => { - e.stopPropagation() + e.stopPropagation(); props.context.setState({ help1: false, help2: !props.context.state.help2, help3: false, - help4: false})}} - //onMouseEnter={() => handleMouseOver("help2")} - //onMouseLeave={() => handleMouseLeave("help2")} + help4: false +}); +}} + // onMouseEnter={() => handleMouseOver("help2")} + // onMouseLeave={() => handleMouseLeave("help2")} > help2
} @@ -103,36 +107,38 @@ export default function SideBar(props) { tabIndex={0} className={styles.helpIcons} onClick={(e) => { - e.stopPropagation() + e.stopPropagation(); props.context.setState({ help1: false, help2: false, help3: !props.context.state.help3, - help4: false})}} - //onMouseEnter={() => handleMouseOver("help3")} - //onMouseLeave={() => handleMouseLeave("help3")} + help4: false +}); +}} + // onMouseEnter={() => handleMouseOver("help3")} + // onMouseLeave={() => handleMouseLeave("help3")} > help3
} {hideStep4 ? null : -
{ - e.stopPropagation() +
{ + e.stopPropagation(); props.context.setState({ help1: false, help2: false, help3: false, - help4: !props.context.state.help4})}} - //onMouseEnter={() => handleMouseOver("help4")} - //onMouseLeave={() => handleMouseLeave("help4")} - > - help4 -
+ help4: !props.context.state.help4 +}); +}} + > + help4 +
} - * mouse click anywhere to advance + * mouse click anywhere to advance
); } @@ -156,25 +162,25 @@ export default function SideBar(props) {
-
+
+ toolTipTitle={'Find a software bug or need help?\r\n Click these links to access our tutorials,\r\n community resources, and Github issue tracker.'} + overlayStyle={{ zIndex: '1001' }} + > {links}
- +
{ + role="button" + tabIndex={0} + className={styles.help} + onClick={(e) => { e.stopPropagation(); props.context.setState({ @@ -183,15 +189,17 @@ export default function SideBar(props) { help2: false, help3: false, help4: false - }) + }); }} - > - setHelpHovered(true)} - onMouseLeave={() => setHelpHovered(false)} + > + setHelpHovered(true)} + onMouseLeave={() => setHelpHovered(false)} - style={{ cursor: 'pointer'}} - src={props.context.state.helping ? helpStop : helpHovered ? helpHover : helpGo } alt="help" /> + style={{ cursor: 'pointer' }} + src={props.context.state.helping ? helpStop : helpHovered ? helpHover : helpGo} + alt="help" + />
{helpButtons}
diff --git a/app/components/SideMenu.css b/app/components/SideMenu.css index 5e92ccdb..de59ec02 100644 --- a/app/components/SideMenu.css +++ b/app/components/SideMenu.css @@ -1,7 +1,7 @@ .menuToggle { position: relative; color: #fff; - background: #575A5C; + background: #575a5c; width: 24px; height: 24px; border-radius: 12px; @@ -13,25 +13,25 @@ padding: 0; text-align: center; cursor: pointer; - border: 3px solid #575A5C; + border: 3px solid #575a5c; transition: transform 0.5s ease-in-out; background-size: cover; } .menuToggle:hover { - background-color: #F09E6A; + background-color: #f09e6a; } .menuToggle.closeMenu { - background-image: url('../images/sideMenuToggleClose.svg'); + background-image: url("../images/sideMenuToggleClose.svg"); } .menuToggle.closeMenu:hover { - background-image: url('../images/sideMenuToggleCloseHover.svg'); + background-image: url("../images/sideMenuToggleCloseHover.svg"); } .menuToggle.openMenu { - background-image: url('../images/sideMenuToggleOpen.svg'); + background-image: url("../images/sideMenuToggleOpen.svg"); } .menuToggle.openMenu:hover { - background-image: url('../images/sideMenuToggleOpenHover.svg'); + background-image: url("../images/sideMenuToggleOpenHover.svg"); } .menuToggle.rotated { @@ -47,18 +47,18 @@ .toggleSquare { position: absolute; - background: #575A5C; + background: #575a5c; width: 9px; height: 24px; margin: 0; - margin-top: .5rem; + margin-top: 0.5rem; padding: 0; } .menu { min-width: 94px; /*This if offsetting for the 6px wide border, but whole sideMenu should always be 100px wide*/ color: #fff; - background: #575A5C; + background: #575a5c; font-weight: 400; font-size: 18px; text-transform: uppercase; @@ -79,35 +79,34 @@ padding-left: 2px; letter-spacing: 2px; text-transform: uppercase; - color:#F09E6A; + color: #f09e6a; } .menuBox { margin: auto; - width: 30px; - height: 30px; - border-color: #F09E6A; - border-style: solid; - border-width: 1px; - font-size: 24px; - text-align: center; - border-radius: 100px; + width: 30px; + height: 30px; + border-color: #f09e6a; + border-style: solid; + border-width: 1px; + font-size: 24px; + text-align: center; + border-radius: 100px; } - .menuItem:hover .menuBox.newProject { border-color: #fff; background-position: center; - background-image: url("images/backArrowW.svg"); - background-color: #F09E6A; + background-image: url("../images/backArrowW.svg"); + background-color: #f09e6a; background-repeat: no-repeat; } .menuItem:hover .menuBox.stackedBar { border-color: #fff; background-position: center; - background-image: url("images/exportHover.svg"); - background-color: #F09E6A; + background-image: url("../images/exportHover.svg"); + background-color: #f09e6a; background-size: 12px; background-repeat: no-repeat; } @@ -115,12 +114,11 @@ .menuItem .menuBox.Filter { border-color: #fff; background-position: center; - background-image: url("images/saveHover.svg"); - background-color: #F09E6A; + background-image: url("../images/saveHover.svg"); + background-color: #f09e6a; background-repeat: no-repeat; } - /* gives glow affect to menu buttons on hover, mostly helps with save button. */ .menuItem:hover .menuBox { color: white; @@ -131,7 +129,7 @@ width: 14px; height: 11px; margin: 0; - margin-bottom: .85rem; + margin-bottom: 0.85rem; padding: 0; border-color: white; border-style: inherit; @@ -153,4 +151,3 @@ .menuItem:hover .menuBox.stackedBar img { display: none !important; } - diff --git a/app/components/SideMenu.js b/app/components/SideMenu.js index bbbde9c5..fc95ee0f 100644 --- a/app/components/SideMenu.js +++ b/app/components/SideMenu.js @@ -15,16 +15,16 @@ import { documentElement } from 'min-document'; import { element } from 'prop-types'; import { fix } from 'prelude-ls'; -const defaultHelpText = +const defaultHelpText = ( Expand the side panel by clicking the menu button.{' '} After expanding the panel, you will see a button to{' '} save the data and another button to go back to the app homepage. - + ); export default class SideMenu extends Component { constructor(props) { super(props); this.state = { - sideMenuToggle: sideMenuToggle, + sideMenuToggle, closeMenu: sideMenuToggleClose, }; } @@ -35,18 +35,19 @@ export default class SideMenu extends Component { // the JSX element. ReactDOM and possible React version updates to allow use of // React useCases would be a possible fix. constructItem = (l) => { - if(l.id == "save") { + if (l.id == 'save') { return (
(e.key === ' ' ? l.action() : null)} + key={l.id} + role="button" + tabIndex={0} + className={styles.menuItem} + onClick={l.action} + onKeyDown={e => (e.key === ' ' ? l.action() : null)} >
+ className={`${styles.menuBox} ${styles.Filter}`} + > {/* done need image here as save is always highlighted */}
@@ -56,18 +57,19 @@ export default class SideMenu extends Component { ); } - if(l.id == "export") { + if (l.id == 'export') { return (
(e.key === ' ' ? l.action() : null)} + key={l.id} + role="button" + tabIndex={0} + className={styles.menuItem} + onClick={l.action} + onKeyDown={e => (e.key === ' ' ? l.action() : null)} >
+ className={`${styles.menuBox} ${styles.stackedBar}`} + > {l.icon}
@@ -77,54 +79,55 @@ export default class SideMenu extends Component { ); } - return (l.id == "back" || 'filter') ? ( -
(e.key === ' ' ? l.action() : null)} - > + return (l.id == 'back' || 'filter') ? ( +
(e.key === ' ' ? l.action() : null)} + >
- {l.icon} + {l.icon} +
+ + {l.name} +
- - {l.name} - -
- ) : ( -
(e.key === ' ' ? l.action() : null)} - > + ) : ( +
(e.key === ' ' ? l.action() : null)} + >
- {l.icon} + className={`${styles.menuBox}`} + > + {l.icon} +
+ + {l.name} +
- - {l.name} - -
- ); -} + ); + } - /*This function deals with when the mouse hovers over the edit icon on top right of + /* This function deals with when the mouse hovers over the edit icon on top right of the home screen and changes img src accordingly to correct svg file */ - handleMouseOver () { - if(this.state.sideMenuToggle === sideMenuToggle) { + handleMouseOver() { + if (this.state.sideMenuToggle === sideMenuToggle) { this.setState({ sideMenuToggle: sideMenuToggleHover }); - this.setState({ closeMenu: sideMenuToggleCloseHover}); + this.setState({ closeMenu: sideMenuToggleCloseHover }); } } - /*This function deals with the mouse leaving an icon (no longer hovering) and + /* This function deals with the mouse leaving an icon (no longer hovering) and changed img src to correct svg file */ - handleMouseLeave () { + handleMouseLeave() { // if(this.state.sideMenuToggle === sideMenuToggleHover) { // this.setState({ sideMenuToggle: sideMenuToggle }); // this.setState({ closeMenu: sideMenuToggleClose }); @@ -138,7 +141,7 @@ export default class SideMenu extends Component { className={`${gstyle.panel} ${styles.menu}`} style={{ width: `calc(${this.props.leftSidebar - this.props.leftMin}px ${this.props.spotlight ? '- 0.2em' : ''})`, - height: this.props.spotlight ? 250 :this.props.chartHeight, + height: this.props.spotlight ? 250 : this.props.chartHeight, margin: this.props.spotlight ? '0.1em' : null, minWidth: this.props.spotlight ? '0' : null, overflow: this.props.spotlight ? 'hidden' : null, @@ -154,7 +157,7 @@ export default class SideMenu extends Component { > + > {menuItems}
{this.props.helpText || defaultHelpText} -
} +
} style={{ position: 'fixed', transform: 'translateY(-1.5em)' }} - > + >
- - {children} - - - ); + return ( + + + {children} + + + ); } diff --git a/app/components/StackedBarRow.js b/app/components/StackedBarRow.js index 7fd2f074..2fb029fe 100644 --- a/app/components/StackedBarRow.js +++ b/app/components/StackedBarRow.js @@ -151,7 +151,7 @@ export default class StackedBarRow extends Component { position: 'absolute', marginTop: this.props.isLast ? -322 + (this.props.metrics.padding * 2) - : this.props.metrics.padding*2.5, + : this.props.metrics.padding * 2.5, marginLeft: this.props.metrics.idWidth + (6), width: '321px', height: '322px', @@ -199,14 +199,14 @@ export default class StackedBarRow extends Component { paddingTop: '3px', lineHeight: '7px', font: 'Open Sans', - fontSize: "9px", + fontSize: '9px', fontWeight: 600, letterSpacing: '1px', }} onClick={this._toggleTagMenu} onKeyPress={e => (e.key === ' ' ? this._toggleTagMenu() : null)} > - Edit Tag + Edit Tag
) : ''; const tagMenu = this.state.showTags ? ( @@ -282,7 +282,7 @@ export default class StackedBarRow extends Component { right: 0, marginTop: (this.props.metrics.lineHeight * 2) + 10, font: 'Open Sans', - fontSize: "9px", + fontSize: '9px', fontWeight: 600, letterSpacing: '1px', diff --git a/app/components/StackedBarTicks.js b/app/components/StackedBarTicks.js index 46311374..8883e1a3 100644 --- a/app/components/StackedBarTicks.js +++ b/app/components/StackedBarTicks.js @@ -75,7 +75,7 @@ export default function StackedBarTicks(props) { x2={-1} y2={props.svgHeight} stroke="#b2b2b2" - strokeOpacity={.85} + strokeOpacity={0.85} strokeWidth={0.5} /> diff --git a/app/components/StackedBarTooltip.js b/app/components/StackedBarTooltip.js index 48196186..1c1934d8 100644 --- a/app/components/StackedBarTooltip.js +++ b/app/components/StackedBarTooltip.js @@ -18,7 +18,7 @@ function Datum(props) { return ( + toolTipTitle={
On the top right corner, there is an overview information about the data file uploaded.

‘Observations’ refer to the data points in the uploaded sample matrix.{' '} - These will vary by file, but typically represent data such as{' '} - OTUs/ASVs or gene contigs. The ‘observation’ number reflects the{' '} - overall count in the filter page window after the end user has{' '} - manipulated the list of samples and applied any filters in the left{' '} - hand pane. Only the ‘Observations’ remaining in the filter page will{' '} - be carried through to the Phinch data visualizations. ‘Total’{' '} - reflects the original counts in the uploaded file, and this number{' '} - will not change during filtering. -
} - style={{ boxShadow: 'inset rgba(255, 255, 255, 0.5) 0px 0px 10px'}} + These will vary by file, but typically represent data such as{' '} + OTUs/ASVs or gene contigs. The ‘observation’ number reflects the{' '} + overall count in the filter page window after the end user has{' '} + manipulated the list of samples and applied any filters in the left{' '} + hand pane. Only the ‘Observations’ remaining in the filter page will{' '} + be carried through to the Phinch data visualizations. ‘Total’{' '} + reflects the original counts in the uploaded file, and this number{' '} + will not change during filtering. +
} + style={{ boxShadow: 'inset rgba(255, 255, 255, 0.5) 0px 0px 10px' }} >
diff --git a/app/components/Vis.js b/app/components/Vis.js index f41e974b..0944cdc1 100644 --- a/app/components/Vis.js +++ b/app/components/Vis.js @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import { Link, Redirect } from 'react-router-dom'; import { FixedSizeList as List } from 'react-window'; -import Spotlight from "rc-spotlight"; +import Spotlight from 'rc-spotlight'; import 'antd/dist/antd.css'; import { Tooltip } from 'antd'; import ReactTooltip from 'react-tooltip'; @@ -46,7 +46,7 @@ import { removeRows, restoreRows, visSortBy, - countObservations + countObservations, } from '../filterfunctions'; import { setProjectFilters, getProjectFilters } from '../projects'; import handleExportButton from '../export'; @@ -64,7 +64,7 @@ import StackedBarsSVG from './StackedBarsSVG'; import FilterChart from './FilterChart'; import Summary from './Summary'; import Modal from './Modal'; -import Sankey from './Sankey/' +import Sankey from './Sankey/'; import styles from './Vis.css'; import gstyle from './general.css'; import classNames from 'classnames'; @@ -108,7 +108,7 @@ export default class Vis extends Component { sankeyColors: 'right', helpCounter: 0, helpButton: needHelp, - overrideRightSidebar: null + overrideRightSidebar: null, }; this._inputs = {}; @@ -131,8 +131,7 @@ export default class Vis extends Component { id: 'filter', name: 'Back', action: () => { - this.save(() => ( - this.setState({ redirect: '/Filter' }))); + this.save(() => this.setState({ redirect: '/Filter' })); }, icon: back, }, @@ -143,7 +142,7 @@ export default class Vis extends Component { this.setState({ renderSVG: true }); }, icon: export, - } + }, ]; this.filters = {}; @@ -154,24 +153,20 @@ export default class Vis extends Component { }; // Move this to data - const tagColors = [ - '#ff4a14', - '#ffc400', - '#00adff', - '#2bfec3', - ]; + const tagColors = ['#ff4a14', '#ffc400', '#00adff', '#2bfec3']; this.state.tags = [ { id: 'none', color: null, name: 'No Tags', selected: true, - }, ...tagColors.map((c, i) => ({ + }, + ...tagColors.map((c, i) => ({ id: `tag-${i}`, name: `Tag ${i}`, color: c, selected: true, - })) + })), ]; this.readsBySequence = {}; @@ -203,9 +198,10 @@ export default class Vis extends Component { debounce: 350, }; - this.metrics.nonbarWidth = (this.metrics.padding * 3) + (this.metrics.barInfoWidth); - this.metrics.chartWidth = this.state.width - - (this.metrics.leftSidebar + this.metrics.nonbarWidth); + this.metrics.nonbarWidth = + this.metrics.padding * 3 + this.metrics.barInfoWidth; + this.metrics.chartWidth = + this.state.width - (this.metrics.leftSidebar + this.metrics.nonbarWidth); this.metrics.chartHeight = this.state.height - this.metrics.heightOffset; this.scales = { @@ -220,27 +216,32 @@ export default class Vis extends Component { } else { // Break this whole chunk into a function or something // - this.init = getProjectFilters(this.state.summary.path, this.state.summary.dataKey, 'vis'); + this.init = getProjectFilters( + this.state.summary.path, + this.state.summary.dataKey, + 'vis' + ); // this.state.names = this.init.names; // console.log('initial level', this.state.level) - this.state.level = (this.init.level !== undefined) ? this.init.level : this.state.level; + this.state.level = + this.init.level !== undefined ? this.init.level : this.state.level; // console.log(this.init, this.state.level) this.filters = this.init.filters ? this.init.filters : {}; this.state.deleted = this.init.deleted ? this.init.deleted : []; this.state.tags = this.init.tags ? this.init.tags : this.state.tags; // // Can probably lose this for release - this.state.tags = this.state.tags.map(t => { + this.state.tags = this.state.tags.map((t) => { if (t.id === 'none') { t.name = 'No Tags'; } return t; }); // - //this is to allow the programmer to track if all tags are unselected or or least one - //is for styling purposes. Likely a better way to do this using data but the function of - //all state variables need to be identified first which will take time. + // this is to allow the programmer to track if all tags are unselected or or least one + // is for styling purposes. Likely a better way to do this using data but the function of + // all state variables need to be identified first which will take time. // this.state.tagTracker = this.state.tags.map(t => { // if (t.selected === 'true') { // return true; @@ -249,33 +250,43 @@ export default class Vis extends Component { // return false; // }); // - this.state.rowTags = this.init.rowTags ? this.init.rowTags : this.state.rowTags; - this.state.selectedAttribute = this.init.selectedAttribute ? ( - this.init.selectedAttribute - ) : this.state.selectedAttribute; - this.state.showEmptyAttrs = this.init.showEmptyAttrs === undefined - ? true : this.init.showEmptyAttrs; + this.state.rowTags = this.init.rowTags + ? this.init.rowTags + : this.state.rowTags; + this.state.selectedAttribute = this.init.selectedAttribute + ? this.init.selectedAttribute + : this.state.selectedAttribute; + this.state.showEmptyAttrs = + this.init.showEmptyAttrs === undefined + ? true + : this.init.showEmptyAttrs; // // Ugly... - this.state.showLeftSidebar = (this.init.showLeftSidebar !== undefined) ? ( - this.init.showLeftSidebar - ) : this.state.showLeftSidebar; - this.metrics.leftSidebar = this.state.showLeftSidebar ? - this.metrics.left.max : this.metrics.left.min; + this.state.showLeftSidebar = + this.init.showLeftSidebar !== undefined + ? this.init.showLeftSidebar + : this.state.showLeftSidebar; + this.metrics.leftSidebar = this.state.showLeftSidebar + ? this.metrics.left.max + : this.metrics.left.min; // if (this.init.sort) { - this.state.mode = this.init.sort.mode === undefined - ? this.state.mode - : this.init.sort.mode; - this.state.labelKey = this.init.sort.labelKey === undefined - ? this.state.labelKey - : this.init.sort.labelKey; - this.state.sortReverse = this.init.sort.sortReverse === undefined - ? this.state.sortReverse - : this.init.sort.sortReverse; - this.state.sortKey = this.init.sort.sortKey === undefined - ? this.state.sortKey - : this.init.sort.sortKey; + this.state.mode = + this.init.sort.mode === undefined + ? this.state.mode + : this.init.sort.mode; + this.state.labelKey = + this.init.sort.labelKey === undefined + ? this.state.labelKey + : this.init.sort.labelKey; + this.state.sortReverse = + this.init.sort.sortReverse === undefined + ? this.state.sortReverse + : this.init.sort.sortReverse; + this.state.sortKey = + this.init.sort.sortKey === undefined + ? this.state.sortKey + : this.init.sort.sortKey; } // } @@ -292,22 +303,24 @@ export default class Vis extends Component { this.toggleRightMenu = this.toggleRightMenu.bind(this); this.toggleLog = this.toggleLog.bind(this); this.countUpHelp = this.countUpHelp.bind(this); - this._isMounted = false + this._isMounted = false; } componentDidMount() { - this._isMounted = true + this._isMounted = true; window.addEventListener('resize', this.updateDimensions); if (this.initdata) { this.formatTaxonomyData(this.initdata, this.state.level, (data) => { this.setState({ data, preData: data }, () => { - this.updateAttributeValues(this.state.selectedAttribute, this.state.data); + this.updateAttributeValues( + this.state.selectedAttribute, + this.state.data + ); this.setLevel(this.state.level); }); }); } window.addEventListener('click', this.countUpHelp); - } componentDidUpdate() { @@ -315,79 +328,109 @@ export default class Vis extends Component { this.setDialogVisible(); // console.log(this.state.summary.path) // console.log(this._svg) - handleExportButton(_cloneDeep(this.state.summary.path), this._svg, this.exportComplete, this._visType); + handleExportButton( + _cloneDeep(this.state.summary.path), + this._svg, + this.exportComplete, + this._visType + ); } - if (this.state.helpCounter === 6 && this._visType === 'stackedbar' && Object.keys(this.state.filters).length === 0) { + if ( + this.state.helpCounter === 6 && + this._visType === 'stackedbar' && + Object.keys(this.state.filters).length === 0 + ) { this._clickDatum(this.sequences[0]); this.setState({ datumClickedViaHelp: this.sequences[0] }); } - if (this.state.helpCounter !== 6 && this._visType === 'stackedbar' && this.state.datumClickedViaHelp) { + if ( + this.state.helpCounter !== 6 && + this._visType === 'stackedbar' && + this.state.datumClickedViaHelp + ) { this.removeFilter(this.state.datumClickedViaHelp.name); this.setState({ datumClickedViaHelp: null }); - } - if (this.state.helpCounter === 7 && this._visType === 'stackedbar' && !this.state.highlightedDatum && !this.state.highlightedDatumFromHelp) { + if ( + this.state.helpCounter === 7 && + this._visType === 'stackedbar' && + !this.state.highlightedDatum && + !this.state.highlightedDatumFromHelp + ) { this.setState({ highlightedDatum: { datum: this.state.data[0].sequences[0], sample: this.state.data[0], position: { - x: 400, y: 250 - } + x: 400, + y: 250, + }, }, showTooltip: true, highlightedDatumFromHelp: true, - }) - } else if (this.state.helpCounter !== 7 && this._visType === 'stackedbar' && this.state.highlightedDatum && this.state.highlightedDatumFromHelp) { + }); + } else if ( + this.state.helpCounter !== 7 && + this._visType === 'stackedbar' && + this.state.highlightedDatum && + this.state.highlightedDatumFromHelp + ) { this.setState({ highlightedDatum: null, showTooltip: false, highlightedDatumFromHelp: false, - }) + }); } - if (this.state.helpCounter === 8 && this._visType === 'stackedbar' && !this.state.selectedAttributeFromHelp) { + if ( + this.state.helpCounter === 8 && + this._visType === 'stackedbar' && + !this.state.selectedAttributeFromHelp + ) { const selectedAttribute = Object.keys(this.attributes)[0]; this.updateAttributeValues(selectedAttribute, this.state.data); this.setState({ selectedAttribute, selectedAttributeFromHelp: true, - - }) - } else if (this.state.helpCounter !== 8 && this._visType === 'stackedbar' && this.state.selectedAttributeFromHelp) { + }); + } else if ( + this.state.helpCounter !== 8 && + this._visType === 'stackedbar' && + this.state.selectedAttributeFromHelp + ) { this.setState({ selectedAttribute: '', selectedAttributeFromHelp: false, - }) + }); } } componentWillUnmount() { - this._isMounted = false + this._isMounted = false; clearTimeout(this.tooltip.handle); clearTimeout(this.timeout); window.removeEventListener('resize', this.updateDimensions); window.removeEventListener('click', this.countUpHelp); - } countUpHelp() { - - if(this.state.helpCounter > 0) { + if (this.state.helpCounter > 0) { const currCount = this.state.helpCounter; const newCount = currCount + 1; // 6 for sankey, 8 for bargraph const maxCount = this._visType === 'sankey' ? 6 : 9; - newCount > maxCount ? this.setState({ helpCounter: 2, }) : this.setState({ helpCounter: newCount, }); + newCount > maxCount + ? this.setState({ helpCounter: 2 }) + : this.setState({ helpCounter: newCount }); } } exportComplete = () => { this.setState({ renderSVG: false, dialogVisible: false }); - } + }; setDialogVisible = () => { this.setState({ dialogVisible: true }); - } + }; save = (callback) => { const viewMetadata = { @@ -414,7 +457,7 @@ export default class Vis extends Component { viewMetadata, (value) => { callback(value); - }, + } ); }; @@ -424,12 +467,12 @@ export default class Vis extends Component { this.clearResult(); }, 3000); this.setState({ result }); - } + }; clearResult = () => { const result = null; this.setState({ result }); - } + }; _toggleTag = (datum, tag, isRemoved) => { if (datum.tags[tag.id]) { @@ -461,12 +504,12 @@ export default class Vis extends Component { this.save(this.setResult); }); } - } + }; _toggleTags = () => { const showTags = !this.state.showTags; this.setState({ showTags }); - } + }; _hoverDatum = (datum, sample, position) => { if (datum == null) { @@ -483,27 +526,32 @@ export default class Vis extends Component { } this.setState({ highlightedDatum: { datum, sample, position } }); } - } + }; _clickDatum = (datum) => { if (!Object.prototype.hasOwnProperty.call(this.filters, this.state.level)) { this.filters[this.state.level] = {}; } const filters = _cloneDeep(this.state.filters); - if (!Object.prototype.hasOwnProperty.call(this.filters[this.state.level], datum.name)) { + if ( + !Object.prototype.hasOwnProperty.call( + this.filters[this.state.level], + datum.name + ) + ) { const sequences = []; - this.state.preData.forEach(d => { - d.sequences.forEach(s => { + this.state.preData.forEach((d) => { + d.sequences.forEach((s) => { if (datum.name === s.name && s.reads > 0) { sequences.push(s); } }); }); - const totalReads = sequences.map(s => s.reads).reduce((s, v) => s + v); + const totalReads = sequences.map((s) => s.reads).reduce((s, v) => s + v); const values = _sortBy(sequences, (s) => s.reads).map((s, i) => ({ index: i, value: s.reads, - count: (s.reads === 0) ? 1 : s.reads, + count: s.reads === 0 ? 1 : s.reads, percent: s.reads / totalReads, })); this.filters[this.state.level][datum.name] = { @@ -525,7 +573,7 @@ export default class Vis extends Component { this.topSequences = this.renderTopSequences(); this.save(this.setResult); }); - } + }; toggleLog(name) { const filters = _cloneDeep(this.state.filters); @@ -542,29 +590,46 @@ export default class Vis extends Component { this.filters[this.state.level] = filters; const showRightSidebar = Object.keys(filters).length > 0; this.updateChartWidth(showRightSidebar); - const data = this.filterData(filters, this.state.tags, this.state.preData, this.state.deleted); + const data = this.filterData( + filters, + this.state.tags, + this.state.preData, + this.state.deleted + ); const observations = countObservations(data); this.updateAttributeValues(this.state.selectedAttribute, data); - this.setState({ - data, observations, filters, showRightSidebar - }, () => { - this.topSequences = this.renderTopSequences(); - this.save(this.setResult); - }); + this.setState( + { + data, + observations, + filters, + showRightSidebar, + }, + () => { + this.topSequences = this.renderTopSequences(); + this.save(this.setResult); + } + ); } updateChartWidth(_showRightSidebar) { - const showRightSidebar = (_showRightSidebar || this.state.overrideRightSidebar === 'open') && !(_showRightSidebar && this.state.overrideRightSidebar === 'close') + const showRightSidebar = + (_showRightSidebar || this.state.overrideRightSidebar === 'open') && + !(_showRightSidebar && this.state.overrideRightSidebar === 'close'); // console.log('update chart width', { _showRightSidebar, showRightSidebar, overrideRightSidebar: this.state.overrideRightSidebar }) if (showRightSidebar) { - this.metrics.chartWidth = window.innerWidth - - (this.metrics.leftSidebar + this.metrics.rightSidebar + this.metrics.nonbarWidth); + this.metrics.chartWidth = + window.innerWidth - + (this.metrics.leftSidebar + + this.metrics.rightSidebar + + this.metrics.nonbarWidth); } else { - this.metrics.chartWidth = window.innerWidth - - (this.metrics.leftSidebar + this.metrics.nonbarWidth ); + this.metrics.chartWidth = + window.innerWidth - + (this.metrics.leftSidebar + this.metrics.nonbarWidth); } if (this._isMounted) { - this.setState({ chartWidth: this.metrics.chartWidth }) + this.setState({ chartWidth: this.metrics.chartWidth }); } } @@ -586,15 +651,29 @@ export default class Vis extends Component { this.updateChartWidth(showRightSidebar); this.updateTaxonomyData(this.state.preData, level, true, (preData) => { this.updateTaxonomyData(this.state.deleted, level, false, (deleted) => { - const data = this.filterData(filters, this.state.tags, preData, deleted); + const data = this.filterData( + filters, + this.state.tags, + preData, + deleted + ); const observations = countObservations(data); this.updateAttributeValues(this.state.selectedAttribute, data); - this.setState({ - level, data, observations, preData, deleted, filters, showRightSidebar - }, () => { - this.topSequences = this.renderTopSequences(); - this.save(this.setResult); - }); + this.setState( + { + level, + data, + observations, + preData, + deleted, + filters, + showRightSidebar, + }, + () => { + this.topSequences = this.renderTopSequences(); + this.save(this.setResult); + } + ); }); }); } @@ -603,15 +682,15 @@ export default class Vis extends Component { // Move to data container? formatTaxonomyData(data, level, callback) { let totalDataReads = 0; - const indata = data.columns.map(c => { + const indata = data.columns.map((c) => { const matches = data.data - .filter(d => d[1] === c.metadata.phinchID) - .map(d => { + .filter((d) => d[1] === c.metadata.phinchID) + .map((d) => { const row = data.rows[d[0]]; return { id: row.id, taxonomy: row.metadata.taxonomy, - count: d[2] + count: d[2], }; }); totalDataReads += c.reads; @@ -619,19 +698,23 @@ export default class Vis extends Component { if (this.state.names[c.sampleName]) { phinchName = this.state.names[c.sampleName]; } - const tags = this.state.rowTags[c.sampleName] ? this.state.rowTags[c.sampleName] : {}; - Object.keys(tags).forEach(k => { - const [tag] = this.state.tags.filter(t => t.id === k); + const tags = this.state.rowTags[c.sampleName] + ? this.state.rowTags[c.sampleName] + : {}; + Object.keys(tags).forEach((k) => { + const [tag] = this.state.tags.filter((t) => t.id === k); tags[k] = tag; }); const [dateAttribute] = Object.keys(this.attributes) - .map(k => this.attributes[k]) - .filter(a => a.type === 'date'); + .map((k) => this.attributes[k]) + .filter((a) => a.type === 'date'); let collectionDate = ''; if (dateAttribute) { - collectionDate = c.metadata[dateAttribute.key] ? ( - new Date(c.metadata[dateAttribute.key]).toLocaleString().split(', ')[0] - ) : ''; + collectionDate = c.metadata[dateAttribute.key] + ? new Date(c.metadata[dateAttribute.key]) + .toLocaleString() + .split(', ')[0] + : ''; } return { id: c.id, @@ -658,12 +741,12 @@ export default class Vis extends Component { if (updateSequences) { this.readsBySequence = {}; } - const taxonomyData = data.map(d => { + const taxonomyData = data.map((d) => { d.sequences = nest() - .key(s => s.taxonomy.slice(0, level + 1)) + .key((s) => s.taxonomy.slice(0, level + 1)) .entries(d.matches) - .map(s => { - const reads = s.values.map(v => v.count).reduce((a, v) => a + v); + .map((s) => { + const reads = s.values.map((v) => v.count).reduce((a, v) => a + v); if (updateSequences) { if (s.key in this.readsBySequence) { this.readsBySequence[s.key] += reads; @@ -686,27 +769,32 @@ export default class Vis extends Component { } filterData(filters, tags, preData, deleted) { - const deletedSamples = deleted.map(d => d.sampleName); - const samples = preData.filter(s => { + const deletedSamples = deleted.map((d) => d.sampleName); + const samples = preData.filter((s) => { let include = true; if (deletedSamples.includes(s.sampleName)) { include = false; } Object.keys(filters).forEach((k) => { - const [sequence] = s.sequences.filter(d => (d.name === k)); + const [sequence] = s.sequences.filter((d) => d.name === k); if (sequence) { const value = sequence.reads; - if (value < filters[k].range.min.value || value > filters[k].range.max.value) { + if ( + value < filters[k].range.min.value || + value > filters[k].range.max.value + ) { include = false; } } }); - const showNoneTags = (tags.filter(t => t.selected && t.id === 'none').length > 0); + const showNoneTags = + tags.filter((t) => t.selected && t.id === 'none').length > 0; const countTags = Object.keys(s.tags).length; - const countSelectedTags = Object.keys(s.tags).filter(t => !s.tags[t].selected).length; + const countSelectedTags = Object.keys(s.tags).filter( + (t) => !s.tags[t].selected + ).length; if ( - (!showNoneTags && countTags === 0) - || + (!showNoneTags && countTags === 0) || (countTags > 0 && countTags === countSelectedTags) ) { include = false; @@ -717,52 +805,87 @@ export default class Vis extends Component { } applyFilters(filters) { - const data = this.filterData(filters, this.state.tags, this.state.preData, this.state.deleted); + const data = this.filterData( + filters, + this.state.tags, + this.state.preData, + this.state.deleted + ); const observations = countObservations(data); this.updateAttributeValues(this.state.selectedAttribute, data); - this.setState({ filters, data, observations }, _debounce(() => { - this.save(this.setResult); - }), this.metrics.debounce, { leading: false, trailing: true }); + this.setState( + { filters, data, observations }, + _debounce(() => { + this.save(this.setResult); + }), + this.metrics.debounce, + { leading: false, trailing: true } + ); } renderSearch() { - return + return ( + + ); } renderFilters() { - let segments = null - if (Object.keys(this.state.filters).length && this.state.overrideRightSidebar !== 'close') { - segments = Object.keys(this.state.filters).map(k => ( + let segments = null; + if ( + Object.keys(this.state.filters).length && + this.state.overrideRightSidebar !== 'close' + ) { + segments = Object.keys(this.state.filters).map((k) => ( - After clicking a search result, a side bar will appear that shows the distribution of observations for each chosen search result. A mini bar chart for that search result will also appear underneath each main graph. -

- On the side bar, the circles and slider bar underneath each distribution graph can be used as a further filtering mechanisms for rows displayed in the taxonomy bar chart. Only samples meeting the sidebar filtering criteria will remain visible in the main visualization window. - The graphs are visualized based on users’ setting on data filtering page, which means the actions taken previously will affect the visualisation shown here. - The top sequences box below shows the most abundant observations in your TOTAL dataset, with numerical values calculated after filter page settings have been applied. -

- To remove the graph on the sidebar, simply click the “X” button on the upper right hand side of the sidebar detail. This will also cause the corresponding mini-bar chart to be removed in the main window. -
+
+ After clicking a search result, a side bar will appear that shows + the distribution of observations for each chosen search result. A + mini bar chart for that search result will also appear underneath + each main graph. +
+
+ On the side bar, the circles and slider bar underneath each + distribution graph can be used as a further filtering mechanisms + for rows displayed in the taxonomy bar chart. Only samples meeting + the sidebar filtering criteria will remain visible in the main + visualization window. The graphs are visualized based on users’ + setting on data filtering page, which means the actions taken + previously will affect the visualisation shown here. The top + sequences box below shows the most abundant observations in your + TOTAL dataset, with numerical values calculated after filter page + settings have been applied. +
+
+ To remove the graph on the sidebar, simply click the “X” button on + the upper right hand side of the sidebar detail. This will also + cause the corresponding mini-bar chart to be removed in the main + window. +
} - style={{ boxShadow: 'rgba(255, 255, 255, 0.4) 0 0 10px 3px', + style={{ + boxShadow: 'rgba(255, 255, 255, 0.4) 0 0 10px 3px', pointerEvents: 'none', padding: '0.25rem 0.5rem 0px', margin: '0.25rem 0.5rem 0px', }} - >
)); } - const rightSidebarOpen = (this.state.showRightSidebar || this.state.overrideRightSidebar === 'open') && !(this.state.showRightSidebar && this.state.overrideRightSidebar === 'close') + const rightSidebarOpen = + (this.state.showRightSidebar || + this.state.overrideRightSidebar === 'open') && + !( + this.state.showRightSidebar && + this.state.overrideRightSidebar === 'close' + ); return ( -
@@ -799,53 +939,69 @@ export default class Vis extends Component { tabIndex={0} className={` ${styles.menuToggle} - ${this.state.showRightSidebar || (this.state.overrideRightSidebar === 'open' && this.state.overrideRightSidebar !== 'close') ? styles.closeMenu : styles.openMenu}`} + ${ + this.state.showRightSidebar || + (this.state.overrideRightSidebar === 'open' && + this.state.overrideRightSidebar !== 'close') + ? styles.closeMenu + : styles.openMenu + }`} onClick={this.toggleRightMenu} style={{ - display: this.state.helpCounter === 6 && this._visType === 'stackedbar' ? 'none' : null, + display: + this.state.helpCounter === 6 && this._visType === 'stackedbar' + ? 'none' + : null, }} />
- {segments ? + {segments ? (
{segments}
- : null} + ) : null}
- ); } toggleMenu() { const showLeftSidebar = !this.state.showLeftSidebar; - this.metrics.leftSidebar = showLeftSidebar ? - this.metrics.left.max : this.metrics.left.min; + this.metrics.leftSidebar = showLeftSidebar + ? this.metrics.left.max + : this.metrics.left.min; this.updateChartWidth(this.state.showRightSidebar); this.setState({ showLeftSidebar }, () => { this.save(this.setResult); }); } toggleRightMenu() { - let newValue = this.state.showRightSidebar ? 'close' : 'open' + let newValue = this.state.showRightSidebar ? 'close' : 'open'; if (newValue === this.state.overrideRightSidebar) { - newValue = null + newValue = null; } - this.setState({ - overrideRightSidebar: newValue - }, () => { - this.updateChartWidth(this.state.showRightSidebar); - }) - + this.setState( + { + overrideRightSidebar: newValue, + }, + () => { + this.updateChartWidth(this.state.showRightSidebar); + } + ); } onSuggestionSelected(e, { suggestion }) { @@ -860,7 +1016,7 @@ export default class Vis extends Component { if (suggestion === null) { this.setState({ highlightedDatum: null, - }) + }); return; } const highlightedDatum = { @@ -871,8 +1027,8 @@ export default class Vis extends Component { const showTooltip = false; this.setState({ highlightedDatum, - showTooltip - }) + showTooltip, + }); } onValueCleared() { @@ -894,9 +1050,14 @@ export default class Vis extends Component { names[d.sampleName] = d.phinchName; return d; }); - this.setState({ deleted }, _debounce(() => { - this.save(this.setResult); - }), this.metrics.debounce, { leading: false, trailing: true }); + this.setState( + { deleted }, + _debounce(() => { + this.save(this.setResult); + }), + this.metrics.debounce, + { leading: false, trailing: true } + ); } else { const data = this.state.data.map((d) => { if (d.sampleName === r.sampleName) { @@ -905,32 +1066,47 @@ export default class Vis extends Component { names[d.sampleName] = d.phinchName; return d; }); - this.setState({ data, names }, _debounce(() => { - this.save(this.setResult); - }), this.metrics.debounce, { leading: false, trailing: true }); + this.setState( + { data, names }, + _debounce(() => { + this.save(this.setResult); + }), + this.metrics.debounce, + { leading: false, trailing: true } + ); } } updateTagName(event, tag) { - const tags = this.state.tags.map(t => { + const tags = this.state.tags.map((t) => { if (t.id === tag.id) { t.name = event.target.value; } return t; }); - this.setState({ tags }, _debounce(() => { - this.save(this.setResult); - }), this.metrics.debounce, { leading: false, trailing: true }); + this.setState( + { tags }, + _debounce(() => { + this.save(this.setResult); + }), + this.metrics.debounce, + { leading: false, trailing: true } + ); } filterByTag(event, tag) { - const tags = this.state.tags.map(t => { + const tags = this.state.tags.map((t) => { if (tag.id === t.id) { t.selected = event.target.checked; } return t; }); - const data = this.filterData(this.state.filters, tags, this.state.preData, this.state.deleted); + const data = this.filterData( + this.state.filters, + tags, + this.state.preData, + this.state.deleted + ); const observations = countObservations(data); this.updateAttributeValues(this.state.selectedAttribute, data); this.setState({ tags, data, observations }, () => { @@ -958,13 +1134,17 @@ export default class Vis extends Component { filters={this.state.filters} metrics={this.metrics} scales={this.scales} - tags={this.state.tags.filter(t => t.id !== 'none')} + tags={this.state.tags.filter((t) => t.id !== 'none')} toggleTag={this._toggleTag} - isPercent={(this.state.mode === 'percent')} + isPercent={this.state.mode === 'percent'} isRemoved={removed} highlightedDatum={this.state.highlightedDatum} - removeDatum={() => { removeRows(this, [datum]); }} - restoreDatum={() => { restoreRows(this, [datum]); }} + removeDatum={() => { + removeRows(this, [datum]); + }} + restoreDatum={() => { + restoreRows(this, [datum]); + }} hoverDatum={this._hoverDatum} clickDatum={this._clickDatum} updatePhinchName={this.updatePhinchName} @@ -972,7 +1152,8 @@ export default class Vis extends Component { /> ); - stackRow = ({ index, style }) => this.stack(this.state.data[index], index, style.top, false) + stackRow = ({ index, style }) => + this.stack(this.state.data[index], index, style.top, false); attr = (datum, index, yOffset) => ( ); - attrRow = ({ index, style }) => this.attr(this.attribute.displayValues[index], index, style.top) + attrRow = ({ index, style }) => + this.attr(this.attribute.displayValues[index], index, style.top); updateAttributeValues(attribute, data) { if (attribute !== '') { this.attributes[attribute].displayValues = visSortBy( - this.attributes[attribute].values.map(a => { - const datum = {}; - datum.name = (attribute === 'Year') ? a.value.toString() : a.value.toLocaleString(); - datum.samples = [...new Set(a.samples)]; - datum.sampleObjects = datum.samples.map(s => { - const [sample] = data.filter(d => d.sampleName === s); - return sample; - }).filter(s => s !== undefined); - datum.reads = datum.sampleObjects.map(s => s.reads).reduce((ac, v) => ac + v, 0); - datum.sequences = nest() - .key(s => s.name) - .entries(datum.sampleObjects - .map(s => s.sequences) - .reduce((ac, v) => ac.concat(v), [])) - .map(s => ({ - name: s.key, - reads: s.values.map(v => v.reads).reduce((ac, v) => ac + v, 0), - taxonomy: s.values[0].taxonomy, - })); - return datum; - }) - .filter(v => { + this.attributes[attribute].values + .map((a) => { + const datum = {}; + datum.name = + attribute === 'Year' + ? a.value.toString() + : a.value.toLocaleString(); + datum.samples = [...new Set(a.samples)]; + datum.sampleObjects = datum.samples + .map((s) => { + const [sample] = data.filter((d) => d.sampleName === s); + return sample; + }) + .filter((s) => s !== undefined); + datum.reads = datum.sampleObjects + .map((s) => s.reads) + .reduce((ac, v) => ac + v, 0); + datum.sequences = nest() + .key((s) => s.name) + .entries( + datum.sampleObjects + .map((s) => s.sequences) + .reduce((ac, v) => ac.concat(v), []) + ) + .map((s) => ({ + name: s.key, + reads: s.values + .map((v) => v.reads) + .reduce((ac, v) => ac + v, 0), + taxonomy: s.values[0].taxonomy, + })); + return datum; + }) + .filter((v) => { if (this.state.showEmptyAttrs) return true; return v.reads > 0; }), this.state.sortReverse, - this.state.sortKey, + this.state.sortKey ); } } renderAttributesSelect() { - const options = [] - .concat(Object.keys(this.attributes).map(a => )); + const options = [ + , + ].concat( + Object.keys(this.attributes).map((a) => ( + + )) + ); const onSelectChange = (event) => { const selectedAttribute = event.target.value; this.updateAttributeValues(selectedAttribute, this.state.data); @@ -1051,18 +1254,21 @@ export default class Vis extends Component { this.save(this.setResult); }); }; - const active = (this.state.selectedAttribute !== '') ? styles.selected : ''; + const active = this.state.selectedAttribute !== '' ? styles.selected : ''; return ( -
+