Skip to content

Commit

Permalink
feat: better new-taxa display + red text if 0 selected for discovery
Browse files Browse the repository at this point in the history
  • Loading branch information
davidlougheed committed Oct 21, 2023
1 parent 2105232 commit 5056e04
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 85 deletions.
10 changes: 7 additions & 3 deletions src/lib/datasets.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import groupBy from "lodash/groupBy";
import { Popover } from "antd";
import Primer from "../bits/Primer";
import { formatTaxon } from "./utils";

export const PRIMER_GROUPINGS = ["Taxa_group", "Phylum", "Order", "Family", "Genus", "Final_ID"];

Expand All @@ -23,7 +24,7 @@ const taxaRecGroup = (arr, groupings, pathStr) => {

const baseRecord = {
title: isSpeciesLevel ? <span>
<em>{k.split("_").join(" ")}</em>{" "}
{formatTaxon(k)}{" "}
<Popover title="Primers" content={
v.map(p => <Primer key={`${k}-${p["Primer_name"]}`} name={p["Primer_name"]} />)
}>
Expand Down Expand Up @@ -54,12 +55,15 @@ const buildLeafKey = (rec) => `root-${PRIMER_GROUPINGS.map((g) => rec[g]).join("

export const createDataset = (records) => {
const tree = taxaRecGroup(records, PRIMER_GROUPINGS, "root");
const primers = new Set(records.map((x) => x["Primer_name"]));
const primers = new Set(records.map((rec) => rec["Primer_name"]));
const recordsWithKey = records.map((rec) => ({ ...rec, key: buildLeafKey(rec) }));
const recordsByKey = Object.fromEntries(recordsWithKey.map((rec) => [rec.key, rec]));

return {
tree,
primers: Array.from(primers),
records: recordsWithKey,
recordsByKey: Object.fromEntries(recordsWithKey.map((rec) => [rec.key, rec])),
recordsByKey,
recordsByFinalID: Object.fromEntries(recordsWithKey.map((rec) => [rec["FinalID"], rec])),
};
};
10 changes: 10 additions & 0 deletions src/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,13 @@ export const pluralize = (word, count) => {
if (count !== 1) return `${word}s`;
return word;
};

export const formatTaxon = (finalID) => {
const parts = finalID.split("_");
if (parts.length === 1) {
// Not a latin name
return <>{finalID}</>;
}
return <em>{parts.join(" ")}{parts.at(-1) === "sp" ? "." : ""}</em>;
};

160 changes: 78 additions & 82 deletions src/steps/DiscoverStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,76 @@ import {
Space,
Spin,
Tabs,
Tag,
Tree,
Typography
} from "antd";
import { ArrowLeftOutlined, ArrowRightOutlined, SearchOutlined } from "@ant-design/icons";

import Primer from "../bits/Primer";
import { pluralize } from "../lib/utils";
import { formatTaxon, pluralize } from "../lib/utils";


const ResultsTabs = ({ results }) => (
<Tabs items={results.map((res, i) => {
const { nPrimers, results: npResults } = res;

const nRes = npResults.length;

return {
label: <span>
{nPrimers} {pluralize("Primer", nPrimers)}: {(res.coverage * 100).toFixed(1)}%
{nRes > 1 ? <>{" "}({nRes} sets)</> : null}
</span>,
key: `tab-${nPrimers}-primers`,
children: (
<div>
<div style={{ display: "flex", gap: 16 }}>
{npResults.map((r, j) => {
const newTaxa = (i < results.length - 1)
? Array.from(difference(r.coveredTaxa, results[i + 1].results[0].coveredTaxa))
: null;

return (
<Card
key={`primers-${nPrimers}-primer-set-${j + 1}`}
title={`Primer set ${j + 1}`}
size="small"
style={{ width: "calc(50% - 8px)" }}
>
<Space direction="vertical">
<div>
<strong>Primers:</strong><br/>
{Array.from(r.primers).map((p) => <Primer key={p} name={p}/>)}
</div>
<div>
<strong>Taxa:</strong> {r.coveredTaxa.size}
{newTaxa
? <details open={newTaxa.length < 8}>
<summary>
Adds {newTaxa.length} new {" "}
{pluralize("taxon", newTaxa.length)}{" "}
vs. with {results[i + 1].nPrimers}{" "}
{pluralize("primer", results[i + 1].nPrimers)}
</summary>
<>
{newTaxa.map((t, ti) => <>
{formatTaxon(t)}
{ti < newTaxa.length - 1 ? ", " : ""}
</>)}
</>
</details>
: null}
</div>
</Space>
</Card>
);
})}
</div>
</div>
),
};
})}/>
);

const DiscoverStep = ({ visible, dataset, onBack, onFinish }) => {
const worker = useRef(null);
Expand Down Expand Up @@ -75,6 +137,7 @@ const DiscoverStep = ({ visible, dataset, onBack, onFinish }) => {

const onCheck = useCallback((keys) => {
setCheckedKeys(keys);
setProgress(0);
}, []);

const onExpand = useCallback((keys, e) => {
Expand All @@ -99,11 +162,7 @@ const DiscoverStep = ({ visible, dataset, onBack, onFinish }) => {
}, [dataset]);
const deselectAll = useCallback(() => setCheckedKeys([]), []);

/** @type React.ReactNode */
const selectedNode = useMemo(
() => <span>{checkedLeaves.length} entries selected</span>,
[checkedLeaves],
);
const nCheckedLeaves = checkedLeaves.length;

useEffect(() => {
if (!dataset) return;
Expand Down Expand Up @@ -142,7 +201,7 @@ const DiscoverStep = ({ visible, dataset, onBack, onFinish }) => {
searching.current = true;
}, [dataset, worker, checkedLeaves, nPrimers]);

if (!visible) return <Fragment />;
if (!visible) return <Fragment/>;
// noinspection JSCheckFunctionSignatures
return <>
<Row gutter={[24, 24]}>
Expand All @@ -152,7 +211,9 @@ const DiscoverStep = ({ visible, dataset, onBack, onFinish }) => {
<Form.Item label="Taxa">
<Space>
<Button onClick={showModal}>Select Taxa &hellip;</Button>
{selectedNode}
<span style={{ color: nCheckedLeaves === 0 ? "#EE4433" : undefined }}>
{checkedLeaves.length} entries selected
</span>
</Space>
</Form.Item>
<Form.Item
Expand All @@ -177,8 +238,8 @@ const DiscoverStep = ({ visible, dataset, onBack, onFinish }) => {
<div style={{ display: "flex", gap: 12, width: "100%", alignItems: "baseline" }}>
<Button
type="primary"
icon={<SearchOutlined />}
disabled={!checkedLeaves.length || searching.current}
icon={<SearchOutlined/>}
disabled={!nCheckedLeaves || searching.current}
loading={searching.current}
onClick={onSearch}
>Search</Button>
Expand Down Expand Up @@ -210,74 +271,9 @@ const DiscoverStep = ({ visible, dataset, onBack, onFinish }) => {
/>
)}
{searching.current && (
<Spin size="large" spinning={true} />
)}
{!!results && (
<Tabs items={results.map((res, i) => {
const { nPrimers, results: npResults } = res;

const nRes = npResults.length;

return {
label: <span>
{nPrimers} {pluralize("Primer", nPrimers)}: {(res.coverage * 100).toFixed(1)}%
{nRes > 1 ? <>{" "}({nRes} sets)</> : null}
</span>,
key: `tab-${nPrimers}-primers`,
children: (
<div>
<div style={{ display: "flex", gap: 16 }}>
{npResults.map((r, j) => {
const newTaxa = (i < results.length - 1)
? Array.from(difference(
r.coveredTaxa,
results[i + 1].results[0].coveredTaxa
))
: null;

return (
<Card
key={`primers-${nPrimers}-primer-set-${j + 1}`}
title={`Primer set ${j + 1}`}
size="small"
style={{ width: "calc(50% - 8px)" }}
>
<Space direction="vertical">
<div>
<strong>Primers:</strong><br />
{Array.from(r.primers).map((p) => (
<Primer key={p} name={p} />
))}
</div>
<div>
<strong>Taxa:</strong> {r.coveredTaxa.size}
{newTaxa
? <>
<br />
<span>
{newTaxa.length}{" "}
new {pluralize(
"taxon",
newTaxa.length,
)}{" "}
vs. {results[i + 1].nPrimers} primers
{newTaxa.length < 8
? `: ${newTaxa.join(", ")}`
: null}
</span>
</>
: null}
</div>
</Space>
</Card>
);
})}
</div>
</div>
),
};
})} />
<Spin size="large" spinning={true}/>
)}
{!!results && <ResultsTabs results={results} />}
</Col>
</Row>

Expand All @@ -291,7 +287,7 @@ const DiscoverStep = ({ visible, dataset, onBack, onFinish }) => {
>
<Space direction="vertical">
<Space direction="horizontal">
{selectedNode}
{nCheckedLeaves} entries selected
<Button size="small" onClick={selectAll}>Select All</Button>
<Button size="small" onClick={deselectAll}>Deselect All</Button>
</Space>
Expand All @@ -306,16 +302,16 @@ const DiscoverStep = ({ visible, dataset, onBack, onFinish }) => {
/>
</Space>
</Modal>
<Divider />
<Divider/>
<Row>
<Col flex={1}>
<Button size="large" icon={<ArrowLeftOutlined />} onClick={onBack}>Back</Button>
<Button size="large" icon={<ArrowLeftOutlined/>} onClick={onBack}>Back</Button>
</Col>
<Col flex={1} style={{ display: "flex", justifyContent: "flex-end" }}>
<Button
type="primary"
size="large"
icon={<ArrowRightOutlined />}
icon={<ArrowRightOutlined/>}
// disabled={!hasSearched}
disabled={true} // for now
onClick={() => onFinish()}
Expand Down

0 comments on commit 5056e04

Please sign in to comment.