-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #742 from hms-dbmi-cellenics/reorder-genes-plots
Reorder genes plots
- Loading branch information
Showing
5 changed files
with
362 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import React, { useEffect, useState } from 'react'; | ||
import { useDispatch, useSelector } from 'react-redux'; | ||
import PropTypes from 'prop-types'; | ||
|
||
import { arrayMoveImmutable } from 'utils/array-move'; | ||
import { updatePlotConfig } from 'redux/actions/componentConfig'; | ||
import { loadGeneExpression } from 'redux/actions/genes'; | ||
import HierarchicalTreeGenes from 'components/plots/hierarchical-tree-genes/HierarchicalTreeGenes'; | ||
|
||
import { Space, Button } from 'antd'; | ||
import { CloseOutlined } from '@ant-design/icons'; | ||
|
||
const GeneReorderTool = (props) => { | ||
const { plotUuid } = (props); | ||
|
||
const dispatch = useDispatch(); | ||
|
||
const config = useSelector((state) => state.componentConfig[plotUuid]?.config); | ||
|
||
const experimentId = useSelector((state) => state.componentConfig[plotUuid]?.experimentId); | ||
|
||
const loadedMarkerGenes = useSelector( | ||
(state) => state.genes.expression.views[plotUuid]?.data, | ||
); | ||
|
||
// Tree from antd requires format [{key: , title: }], made from gene names from loadedMarkerGenes and config | ||
const composeGeneTree = (treeGenes) => { | ||
if (!treeGenes) { | ||
return []; | ||
} | ||
|
||
const data = []; | ||
Object.entries(treeGenes).forEach(([key, value]) => { | ||
data.push({ key: `${key}`, title: `${value}` }); | ||
}); | ||
return data; | ||
}; | ||
|
||
const [geneTreeData, setGeneTreeData] = useState(composeGeneTree(loadedMarkerGenes)); | ||
|
||
useEffect(() => { | ||
setGeneTreeData(composeGeneTree(config?.selectedGenes)); | ||
}, [config?.selectedGenes]); | ||
|
||
// geneKey is equivalent to it's index, moves a gene from pos geneKey to newPosition | ||
// dispatches an action to update selectedGenes in config | ||
const onGeneReorder = (geneKey, newPosition) => { | ||
const oldOrder = geneTreeData.map((treeNode) => treeNode.title); | ||
|
||
const newOrder = arrayMoveImmutable(Object.values(oldOrder), geneKey, newPosition); | ||
|
||
dispatch(updatePlotConfig(plotUuid, { selectedGenes: newOrder })); | ||
}; | ||
|
||
const onNodeDelete = (geneKey) => { | ||
const genes = geneTreeData.map((treeNode) => treeNode.title); | ||
genes.splice(geneKey, 1); | ||
|
||
dispatch(loadGeneExpression(experimentId, genes, plotUuid)); | ||
}; | ||
|
||
const renderTitles = (data) => { | ||
// replace every title (gene name) in tree data with a modified title (name + button) | ||
const toRender = data.map((treeNode) => { | ||
// modified needs to be a copy of a given node | ||
const modified = { ...treeNode }; | ||
modified.title = ( | ||
<Space> | ||
{treeNode.title} | ||
<Button | ||
type='text' | ||
onClick={() => { | ||
onNodeDelete(treeNode.key); | ||
}} | ||
> | ||
<CloseOutlined /> | ||
</Button> | ||
</Space> | ||
); | ||
return modified; | ||
}); | ||
return toRender; | ||
}; | ||
|
||
const [renderedTreeData, setRenderedTreeData] = useState([]); | ||
|
||
useEffect(() => { | ||
setRenderedTreeData(renderTitles(geneTreeData)); | ||
}, [geneTreeData]); | ||
|
||
return ( | ||
<HierarchicalTreeGenes | ||
treeData={renderedTreeData} | ||
onGeneReorder={onGeneReorder} | ||
onNodeDelete={onNodeDelete} | ||
/> | ||
); | ||
}; | ||
|
||
GeneReorderTool.defaultProps = {}; | ||
|
||
GeneReorderTool.propTypes = { | ||
plotUuid: PropTypes.string.isRequired, | ||
}; | ||
|
||
export default GeneReorderTool; |
3 changes: 3 additions & 0 deletions
3
src/components/plots/hierarchical-tree-genes/HierarchicalTreeGenes.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.ant-tree-list-holder-inner { | ||
background-color: #f5f8fa; | ||
} |
51 changes: 51 additions & 0 deletions
51
src/components/plots/hierarchical-tree-genes/HierarchicalTreeGenes.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { Tree, Skeleton } from 'antd'; | ||
|
||
import 'components/plots/hierarchical-tree-genes/HierarchicalTreeGenes.css'; | ||
import { ConsoleLogger } from '@aws-amplify/core'; | ||
|
||
const HierarchicalTreeGenes = (props) => { | ||
const { | ||
treeData, | ||
onGeneReorder, | ||
} = props; | ||
|
||
const onDrop = (info) => { | ||
const { | ||
dragNode, node, dropPosition, dropToGap, | ||
} = info; | ||
|
||
// if dropped in place, ignore | ||
// dragNode.key is str, dropPosition is int | ||
if (dragNode.key == dropPosition) return; | ||
|
||
// If not dropped in gap, ignore | ||
if (!dropToGap) return; | ||
|
||
let newPosition = dropPosition - (dragNode.key < dropPosition ? 1 : 0); | ||
newPosition = Math.max(0, newPosition); | ||
|
||
onGeneReorder(dragNode.key, newPosition); | ||
}; | ||
|
||
if (!treeData) return <Skeleton active />; | ||
|
||
return ( | ||
<Tree | ||
data-testid='HierachicalTreeGenes' | ||
draggable | ||
treeData={treeData} | ||
onDrop={onDrop} | ||
/> | ||
); | ||
}; | ||
|
||
HierarchicalTreeGenes.defaultProps = {}; | ||
|
||
HierarchicalTreeGenes.propTypes = { | ||
treeData: PropTypes.array.isRequired, | ||
onGeneReorder: PropTypes.func.isRequired, | ||
}; | ||
|
||
export default HierarchicalTreeGenes; |
Oops, something went wrong.