Skip to content

Commit

Permalink
Linked selections and flexible layout in gallery (#104)
Browse files Browse the repository at this point in the history
* Linked selection and visualization of saved plots in gallery
* Responsive layout in gallery
* Gallery plots are interactive with lasso/zoom enabled
* restore state, download and remove plots from gallery
* dynamically update title in the gallery based on selections
* bump app version
  • Loading branch information
jkanche authored May 13, 2022
1 parent c7c0c36 commit 2a5f1b7
Show file tree
Hide file tree
Showing 14 changed files with 1,215 additions and 418 deletions.
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "kana",
"description": "Single cell data analysis in the browser",
"version": "1.1.0",
"version": "1.2.0",
"private": true,
"author": {
"name": "Jayaram Kancherla",
Expand All @@ -26,22 +26,26 @@
"@blueprintjs/core": "^3.51.3",
"@blueprintjs/popover2": "^0.13.0",
"@blueprintjs/table": "^3.9.12",
"@dnd-kit/core": "^5.0.3",
"@dnd-kit/modifiers": "^5.0.0",
"@dnd-kit/sortable": "^6.0.1",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"bakana": "^0.2.6",
"comlink": "^4.3.1",
"d3": "^7.1.1",
"d3-dsv": "^3.0.1",
"d3-scale": "^4.0.2",
"epiviz.gl": "^1.0.2",
"epiviz.scatter.gl": "^0.0.5",
"hash-wasm": "^4.9.0",
"pako": "^2.0.4",
"pub-sub-es": "^2.0.1",
"randomcolor": "^0.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "5.0.0",
"react-split-grid": "^1.0.4",
"react-virtuoso": "^2.3.1",
"web-vitals": "^1.0.1",
"workbox-background-sync": "^5.1.3",
Expand Down
41 changes: 33 additions & 8 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -41,34 +41,40 @@
display: grid;
grid-gap: 5px;
grid-template-columns: auto 400px;
grid-template-rows: auto 300px;
grid-template-rows: 100%;
background-color: #fff;
/* color: #444; */
height: calc(100vh - 70px);
}

.app-content-left {
grid-column: 1;
grid-row: 1 / -1;
}

.plot {
grid-column: 1;
grid-row: 1;
/* background-color: blue; */
}

.marker {
grid-column: 2;
grid-row: 1 / -1;
/* grid-column: 2;
grid-row: 1 / -1; */
/* background-color: red; */
border-left: 2px solid gainsboro;
}

.analysis {
grid-column: 1 / 2;
grid-row: 2;
/* grid-column: 1 / 2;
grid-row: 2; */
/* background-color: yellow; */
border-top: 2px solid gainsboro;
/* border-top: 2px solid gainsboro; */
display: flex;
flex-direction: row;
flex-wrap: wrap;
padding: 10px;
overflow: auto;
}

.spinner {
Expand All @@ -77,7 +83,6 @@
top: 40%;
}


.effect-opacity {
opacity: 0.6;
}
Expand All @@ -93,4 +98,24 @@
.effect-opacitygrayscale {
opacity: 0.6;
filter: grayscale(100%);
}
}

.grid {
display: grid;
grid-template-rows: 1fr 10px 1fr;
}

.gutter-row {
grid-column: 1/-1;
cursor: row-resize;
background-color: rgb(229, 231, 235);
background-repeat: no-repeat;
background-position: 50%;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==");
}

.gutter-row-1 {
grid-row: 2;
/* background-color: #fff; */
/* border: 2px solid black; */
}
223 changes: 144 additions & 79 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import Pong from './components/Spinners/Pong';
import Spinner2 from './components/Spinners/Spinner2';
import { getMinMax } from './components/Plots/utils';

import Split from 'react-split-grid'

import Logs from './components/Logs';

import { palette } from './components/Plots/utils';
Expand Down Expand Up @@ -76,6 +78,18 @@ const App = () => {
const [showAnimation, setShowAnimation] = useState(false);
// if a user manually triggers an animation (using the play button)
const [triggerAnimation, setTriggerAnimation] = useState(false);
// keeps track of what points were selected in lasso selections
const [selectedPoints, setSelectedPoints] = useState(null);
// keeps track of what points were selected in lasso selections
const [restoreState, setRestoreState] = useState(null);
// for highlight in uDimPlots
const [highlightPoints, setHighlightPoints] = useState(null);
// set which cluster to highlight, also for custom selections
const [clusHighlight, setClusHighlight] = useState(null);
// set which clusterlabel is highlighted
const [clusHighlightLabel, setClusHighlightLabel] = useState(null);
// selected colorBy
const [colorByAnnotation, setColorByAnnotation] = useState("clusters");

// PCA
const [pcaVarExp, setPcaVarExp] = useState(null);
Expand Down Expand Up @@ -337,6 +351,24 @@ const App = () => {
}
}
}

setGene(null);
setClusHighlight(null);
setHighlightPoints(null);
setClusHighlightLabel(null);
setSavedPlot([]);

setTimeout(() => {
let tmp = [];
savedPlot && savedPlot.forEach(x => {
if (x.config.annotation != undefined && x.config.annotation.toLowerCase() != "clusters" && x.config.highlight == undefined) {
tmp.push(x);
}
});

setSavedPlot(tmp);

}, 1000);
}, [inputFiles, params, wasmInitialized]);


Expand Down Expand Up @@ -606,67 +638,118 @@ const App = () => {
loadingStatus={inputFiles?.files ? !showQCLoader && !showPCALoader && !showNClusLoader && !showCellLabelLoader && !showMarkerLoader && !showDimPlotLoader : true}
/>
<div className="App-content">
{
inputFiles?.files && <div className={showDimPlotLoader ? "plot effect-opacitygrayscale" : "plot"}>
{
defaultRedDims && clusterData ?
<DimPlot
className={"effect-opacitygrayscale"}
tsneData={tsneData} umapData={umapData}
animateData={animateData}
redDims={redDims}
defaultRedDims={defaultRedDims}
setDefaultRedDims={setDefaultRedDims}
showAnimation={showAnimation}
setShowAnimation={setShowAnimation}
setTriggerAnimation={setTriggerAnimation}
selectedClusterSummary={selectedClusterSummary}
setSelectedClusterSummary={setSelectedClusterSummary}
selectedClusterIndex={selectedClusterIndex}
selectedCluster={selectedCluster}
savedPlot={savedPlot}
setSavedPlot={setSavedPlot}
clusterData={clusterData}
customSelection={customSelection}
setCustomSelection={setCustomSelection}
setGene={setGene}
gene={gene}
clusterColors={clusterColors}
setClusterColors={setClusterColors}
setDelCustomSelection={setDelCustomSelection}
setReqAnnotation={setReqAnnotation}
/> :
showGame ?
<div style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
paddingTop: '50px'
}}>
<Label>Get some coffee or play pong while you wait for the analysis to finish..</Label>
<Button onClick={() => { setShowGame(false) }}>I'm good, go back</Button>
<Pong />
</div>
:
<div style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
paddingTop: '50px'
}}>
<Spinner2 />
<Label>Get some coffee or play pong while you wait for the analysis to finish..</Label>
<Button onClick={() => { setShowGame(true) }}>Play Pong</Button>
</div>
}
</div>
}
<Split
minSize={200}
render={({
getGridProps,
getGutterProps,
}) => (
<div className="app-content-left grid" {...getGridProps()}>
{
inputFiles?.files && <div className={showDimPlotLoader ? "plot effect-opacitygrayscale" : "plot"}>
{
defaultRedDims && clusterData ?
<DimPlot
className={"effect-opacitygrayscale"}
tsneData={tsneData} umapData={umapData}
animateData={animateData}
redDims={redDims}
defaultRedDims={defaultRedDims}
setDefaultRedDims={setDefaultRedDims}
showAnimation={showAnimation}
setShowAnimation={setShowAnimation}
setTriggerAnimation={setTriggerAnimation}
selectedClusterSummary={selectedClusterSummary}
setSelectedClusterSummary={setSelectedClusterSummary}
selectedClusterIndex={selectedClusterIndex}
selectedCluster={selectedCluster}
savedPlot={savedPlot}
setSavedPlot={setSavedPlot}
clusterData={clusterData}
customSelection={customSelection}
setCustomSelection={setCustomSelection}
setGene={setGene}
gene={gene}
clusterColors={clusterColors}
setClusterColors={setClusterColors}
setDelCustomSelection={setDelCustomSelection}
setReqAnnotation={setReqAnnotation}
selectedPoints={selectedPoints}
setSelectedPoints={setSelectedPoints}
restoreState={restoreState}
setRestoreState={setRestoreState}
setHighlightPoints={setHighlightPoints}
clusHighlight={clusHighlight}
setClusHighlight={setClusHighlight}
clusHighlightLabel={clusHighlightLabel}
setClusHighlightLabel={setClusHighlightLabel}
colorByAnnotation={colorByAnnotation}
setColorByAnnotation={setColorByAnnotation}
/> :
showGame ?
<div style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
paddingTop: '50px'
}}>
<Label>Get some coffee or play pong while you wait for the analysis to finish..</Label>
<Button onClick={() => { setShowGame(false) }}>I'm good, go back</Button>
<Pong />
</div>
:
<div style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
paddingTop: '50px'
}}>
<Spinner2 />
<Label>Get some coffee or play pong while you wait for the analysis to finish..</Label>
<Button onClick={() => { setShowGame(true) }}>Play Pong</Button>
</div>
}
</div>
}
<div className="gutter-row gutter-row-1" {...getGutterProps('row', 1)} />
{
inputFiles?.files && <div className="analysis">
<Gallery
qcData={qcData}
pcaVarExp={pcaVarExp}
savedPlot={savedPlot}
setSavedPlot={setSavedPlot}
clusterData={clusterData}
clusterColors={clusterColors}
cellLabelData={cellLabelData}
gene={gene}
showQCLoader={showQCLoader}
showPCALoader={showPCALoader}
showNClusLoader={showNClusLoader}
showCellLabelLoader={showCellLabelLoader}
tsneData={tsneData} umapData={umapData}
redDims={redDims}
selectedPoints={selectedPoints}
setSelectedPoints={setSelectedPoints}
restoreState={restoreState}
setRestoreState={setRestoreState}
highlightPoints={highlightPoints}
clusHighlight={clusHighlight}
clusHighlightLabel={clusHighlightLabel}
setClusHighlight={setClusHighlight}
colorByAnnotation={colorByAnnotation}
/>
</div>
}
</div>
)}
/>
{
inputFiles?.files && <div className={showMarkerLoader ? "marker effect-opacitygrayscale" : "marker"}>
{
Expand Down Expand Up @@ -700,24 +783,6 @@ const App = () => {
}
</div>
}
{
inputFiles?.files && <div className="analysis">
<Gallery
qcData={qcData}
pcaVarExp={pcaVarExp}
savedPlot={savedPlot}
setSavedPlot={setSavedPlot}
clusterData={clusterData}
clusterColors={clusterColors}
cellLabelData={cellLabelData}
gene={gene}
showQCLoader={showQCLoader}
showPCALoader={showPCALoader}
showNClusLoader={showNClusLoader}
showCellLabelLoader={showCellLabelLoader}
/>
</div>
}
</div>
<Overlay
isOpen={loading}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Analysis/Analysis.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.inputs-container {
display: grid;
/* grid-template-columns: auto minmax(325px, 30%); */
grid-template-columns: auto 325px;
grid-template-columns: calc(97% - 325px) 325px;
/* grid-template-rows: auto auto auto auto; */
grid-auto-flow: row;
gap: 10px;
Expand Down
Loading

0 comments on commit 2a5f1b7

Please sign in to comment.