Skip to content

Commit

Permalink
VS Mode (#141)
Browse files Browse the repository at this point in the history
* add ui elements filter select or flip cluster selection in vs-mode
* Added worker code for the versus mode. (#143)
* cleaning up css
* bump app version

Co-authored-by: LTLA <[email protected]>
  • Loading branch information
jkanche and LTLA authored Sep 16, 2022
1 parent ccd70cd commit 2038cb3
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 37 deletions.
2 changes: 1 addition & 1 deletion 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": "2.2.4",
"version": "2.3.0",
"private": true,
"author": {
"name": "Jayaram Kancherla",
Expand Down
53 changes: 38 additions & 15 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ const App = () => {
const [selectedClusterIndex, setSelectedClusterIndex] = useState([]);
// set Cluster rank-type
const [clusterRank, setClusterRank] = useState("cohen-min-rank");
// which cluster is selected for vsmode
const [selectedVSCluster, setSelectedVSCluster] = useState(null);

// Cluster Analysis
// cluster assignments
Expand Down Expand Up @@ -185,23 +187,40 @@ const App = () => {

// request worker for new markers
// if either the cluster or the ranking changes
// VS mode
useEffect(() => {
if (selectedCluster !== null && selectedModality != null) {

let type = String(selectedCluster).startsWith("cs") ?
"getMarkersForSelection" : "getMarkersForCluster";
scranWorker.postMessage({
"type": type,
"payload": {
"modality": selectedModality,
"cluster": selectedCluster,
"rank_type": clusterRank,
}
});
if (selectedModality !== null && clusterRank !== null) {
if (selectedVSCluster !== null && selectedCluster !== null) {
let type = String(selectedCluster).startsWith("cs") ?
"computeVersusSelections" : "computeVersusClusters";
scranWorker.postMessage({
"type": type,
"payload": {
"modality": selectedModality,
"left": selectedCluster,
"right": selectedVSCluster,
"rank_type": clusterRank,
}
});

add_to_logs("info", `--- ${type} sent ---`);
} else if (selectedCluster !== null) {

add_to_logs("info", `--- ${type} sent ---`);
let type = String(selectedCluster).startsWith("cs") ?
"getMarkersForSelection" : "getMarkersForCluster";
scranWorker.postMessage({
"type": type,
"payload": {
"modality": selectedModality,
"cluster": selectedCluster,
"rank_type": clusterRank,
}
});

add_to_logs("info", `--- ${type} sent ---`);
}
}
}, [selectedCluster, clusterRank, selectedModality]);
}, [selectedCluster, selectedVSCluster, clusterRank, selectedModality]);

// compute markers in the worker
// when a new custom selection of cells is made through the UI
Expand Down Expand Up @@ -618,7 +637,9 @@ const App = () => {
setTriggerAnimation(false);
setShowDimPlotLoader(false);
} else if (payload.type === "setMarkersForCluster"
|| payload.type === "setMarkersForCustomSelection") {
|| payload.type === "setMarkersForCustomSelection"
|| payload.type === "computeVersusSelections"
|| payload.type === "computeVersusClusters" ) {
const { resp } = payload;
let records = [];
let index = Array(resp.ordering.length);
Expand Down Expand Up @@ -849,6 +870,8 @@ const App = () => {
selectedClusterIndex={selectedClusterIndex}
selectedCluster={selectedCluster}
setSelectedCluster={setSelectedCluster}
selectedVSCluster={selectedVSCluster}
setSelectedVSCluster={setSelectedVSCluster}
setClusterRank={setClusterRank}
clusterData={clusterData}
customSelection={customSelection}
Expand Down
131 changes: 110 additions & 21 deletions src/components/Markers/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useContext, useState, useMemo } from 'react';
import {
Button, H4, H5, Icon, Collapse, InputGroup, Text,
Button, H4, H5, Icon, Collapse, InputGroup, Text, Switch,
RangeSlider, Tag, HTMLSelect, Classes, Card, Elevation, Label
} from "@blueprintjs/core";
import { Popover2, Tooltip2 } from "@blueprintjs/popover2";
Expand Down Expand Up @@ -40,6 +40,9 @@ const MarkerPlot = (props) => {
// records to show after filtering
const [prosRecords, setProsRecords] = useState(null);

// toggle for vs mode
const [vsmode, setVsmode] = useState(false);

// scale to use for detected on expression bar
const detectedScale = d3.interpolateRdYlBu; //d3.interpolateRdBu;
// d3.scaleSequential()
Expand Down Expand Up @@ -207,7 +210,7 @@ const MarkerPlot = (props) => {
</Popover2>
{
props?.modality ?
<Label style={{textAlign: "left"}}>
<Label style={{textAlign: "left", marginBottom:"5px"}}>
Select Modality
<HTMLSelect
onChange={(x) => {
Expand All @@ -223,30 +226,116 @@ const MarkerPlot = (props) => {
</Label>
: ""
}
{
<div className='marker-cluster-header'>
<Label style={{marginBottom:"0"}}>Select Cluster</Label>
<div className='marker-vsmode'>
<Popover2
popoverClassName={Classes.POPOVER_CONTENT_SIZING}
hasBackdrop={false}
interactionKind="hover"
placement='left'
hoverOpenDelay={500}
modifiers={{
arrow: { enabled: true },
flip: { enabled: true },
preventOverflow: { enabled: true },
}}
content={
<Card style={{
width: '450px'
}} elevation={Elevation.ZERO}
>
<p>
By default, the <strong>general</strong> mode will rank markers for a cluster or custom selection based on the comparison to all other clusters or cells.
<br /><br />Users can instead enable <strong>versus</strong> mode to compare markers between two clusters or between two custom selections.
This is useful for identifying subtle differences between closely related groups of cells.
</p>
</Card>
}
>
<Icon intent="warning" icon="comparison" style={{ paddingRight: '5px' }}></Icon>
</Popover2>
<Switch large={false} checked={vsmode}
innerLabelChecked="versus" innerLabel="general"
onChange={(e) => {
if (e.target.checked === false) {
props?.setSelectedVSCluster(null);
}
setVsmode(e.target.checked)
}} />
</div>
</div>
}
{
clusSel ?
<Label style={{textAlign: "left"}}>
Select Cluster
<HTMLSelect
onChange={(x) => {
let tmpselection = x.currentTarget?.value;
if (tmpselection.startsWith("Cluster")) {
tmpselection = parseInt(tmpselection.replace("Cluster ", "")) - 1
} else if (tmpselection.startsWith("Custom")) {
tmpselection = tmpselection.replace("Custom Selection ", "")
}
props?.setSelectedCluster(tmpselection);
<div className='marker-cluster-selection'>
<HTMLSelect
className='marker-cluster-selection-width'
onChange={(x) => {
let tmpselection = x.currentTarget?.value;
if (tmpselection.startsWith("Cluster")) {
tmpselection = parseInt(tmpselection.replace("Cluster ", "")) - 1
} else if (tmpselection.startsWith("Custom")) {
tmpselection = tmpselection.replace("Custom Selection ", "")
}
props?.setSelectedCluster(tmpselection);

setMarkerFilter({});
props?.setGene(null);
props?.setSelectedVSCluster(null);
}}>
{
clusSel.map((x, i) => (
<option
selected={String(props?.selectedCluster).startsWith("cs") ? x == props?.selectedCluster : parseInt(x) - 1 == parseInt(props?.selectedCluster)}
key={i}>{String(x).startsWith("cs") ? "Custom Selection" : "Cluster"} {x}</option>
))
}
</HTMLSelect>
{
vsmode &&
<>
<Button style={{margin: "0 3px"}} onClick={() => {
let mid = props?.selectedVSCluster;
props?.setSelectedVSCluster(props?.selectedCluster)
props?.setSelectedCluster(mid);

setMarkerFilter({});
props?.setGene(null);
}}>
{
clusSel.map((x, i) => (
<option key={i}>{String(x).startsWith("cs") ? "Custom Selection" : "Cluster"} {x}</option>
))
}
</HTMLSelect>
</Label>
}} icon="exchange" disabled={props?.selectedVSCluster == null} outlined={true} intent="primary"></Button>
<HTMLSelect
className='marker-cluster-selection-width'
onChange={(x) => {
let tmpselection = x.currentTarget?.value;
if (tmpselection.startsWith("Cluster")) {
tmpselection = parseInt(tmpselection.replace("Cluster ", "")) - 1
} else if (tmpselection.startsWith("Custom")) {
tmpselection = tmpselection.replace("Custom Selection ", "")
}
props?.setSelectedVSCluster(tmpselection);

setMarkerFilter({});
props?.setGene(null);
}}>
{
props?.selectedVSCluster == null && <option selected={true}>Choose a Cluster</option>
}
{
clusSel.filter((x,i) => String(props?.selectedCluster).startsWith("cs") ?
String(x).startsWith("cs") && String(x) !== String(props?.selectedCluster) :
!String(x).startsWith("cs") && parseInt(x) - 1 !== parseInt(props?.selectedCluster))
// .filter((x,i) => String(props?.selectedCluster) == String(x) )
.map((x, i) => (
<option
selected={String(props?.selectedVSCluster).startsWith("cs") ? x == props?.selectedVSCluster : parseInt(x) - 1 == parseInt(props?.selectedVSCluster)}
key={i}>{String(x).startsWith("cs") ? "Custom Selection" : "Cluster"} {x}</option>
))
}
</HTMLSelect>
</>
}
</div>
: ""
}
{
Expand Down
23 changes: 23 additions & 0 deletions src/components/Markers/markers.css
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,26 @@
font-weight: bold;
overflow-wrap: break-word;
}

.marker-cluster-header {
display: flex;
flex-direction: row;
justify-content: space-between;
height: 22px;
}

.marker-vsmode {
display: flex;
flex-direction: row;
}

.marker-cluster-selection {
display: flex;
flex-direction: row;
justify-content: stretch;
}

.marker-cluster-selection-width {
width: 100%;
margin: 0 2px;
}
29 changes: 29 additions & 0 deletions src/workers/scran.worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,35 @@ onmessage = function (msg) {
postError(type, err, fatal)
});

/**************** VERSUS MODE *******************/
} else if (type == "computeVersusClusters") {
loaded.then(x => {
let rank_type = payload.rank_type.replace(/-.*/, ""); // summary type doesn't matter for pairwise comparisons.
let res = superstate.marker_detection.computeVersus(payload.left, payload.right, rank_type, payload.modality);
postMessage({
type: "computeVersusClusters",
resp: res,
msg: "Success: COMPUTE_VERSUS_CLUSTERS done"
});
}).catch(err => {
console.error(err);
postError(type, err, fatal)
});

} else if (type == "computeVersusSelections") {
loaded.then(x => {
let rank_type = payload.rank_type.replace(/-.*/, ""); // summary type doesn't matter for pairwise comparisons.
let res = superstate.custom_selections.computeVersus(payload.left, payload.right, rank_type, payload.modality);
postMessage({
type: "computeVersusSelections",
resp: res,
msg: "Success: COMPUTE_VERSUS_SELECTIONS done"
});
}).catch(err => {
console.error(err);
postError(type, err, fatal)
});

/**************** OTHER EVENTS FROM UI *******************/
} else if (type == "getMarkersForCluster") {
loaded.then(x => {
Expand Down

0 comments on commit 2038cb3

Please sign in to comment.