Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
a305753
WIP
alimourey Aug 11, 2025
6a2d809
wip
alimourey Aug 13, 2025
8b39d68
wip
alimourey Aug 14, 2025
8aabda3
WIP
alimourey Aug 15, 2025
bb4e5ab
WIP
alimourey Aug 15, 2025
88800ef
Some code organizing, add Clear and Select buttons to Multi select, a…
alimourey Aug 15, 2025
15e83da
Add handleClickSaveSelectionAsContext
alimourey Aug 15, 2025
845a09a
WIP
alimourey Aug 15, 2025
cb507c2
Added heatmap hover text
alimourey Aug 18, 2025
9f4eabf
Fix so that PlotControls will be visible
alimourey Aug 18, 2025
8861a8f
Fix styling so that the Plot Selections panel appears to the right of…
alimourey Aug 18, 2025
07e32c1
Move MultiSelectTextarea into its own component and clean up the styling
alimourey Aug 18, 2025
bec9363
Add validation display to the MultiSelectTextArea
alimourey Aug 18, 2025
aadf669
WIP some reorganizing and the beginning of adding the collapsible Plo…
alimourey Aug 18, 2025
08f4ceb
WIP setting up Term Options panel
alimourey Aug 18, 2025
1fa7b40
WIP reorganize search options state into GeneTeaContext
alimourey Aug 18, 2025
222f3ea
Add one more handler to the GeneTea context
alimourey Aug 18, 2025
a8734fb
Delete extra Section.tsx component
alimourey Aug 18, 2025
e6d207b
Implement Heatmap Bar Chart download button
alimourey Aug 18, 2025
af783bb
WIP styling and adding the main tabs and header
alimourey Aug 18, 2025
800d70f
WIP styling search options left panel
alimourey Aug 18, 2025
9c85062
More styling
alimourey Aug 18, 2025
ad29ac6
Add heatmap data ordering
alimourey Aug 19, 2025
fd2831a
Order barchart data to match heatmap data
alimourey Aug 19, 2025
8a7c9cd
Hook up the filter for sorting by Significance or Effect Size
alimourey Aug 19, 2025
d8d7594
Fix the clear button so that it stays unabled unless there are no gen…
alimourey Aug 19, 2025
950ebf4
WIP term options panel
alimourey Aug 19, 2025
77bdcfb
Hack to handle less than 2 character searchTerms the same as other in…
alimourey Aug 20, 2025
9e3060a
Add Coming Soon message to All Terms tab and give the NumberInputs mi…
alimourey Aug 20, 2025
94f0ccc
Fix and reorganize some of the GeneTEA styling
alimourey Aug 20, 2025
1c0b949
WIP
alimourey Aug 21, 2025
3bdb7a5
wip
alimourey Aug 21, 2025
8da570b
WIP
alimourey Aug 22, 2025
e6edd2c
Merge branch 'master' into genetea-page
alimourey Aug 25, 2025
b826ac1
Fix barchart should NOT zoom when heatmap zooms
alimourey Aug 25, 2025
519ebec
Add the number of top terms to the title
alimourey Aug 25, 2025
4a4a936
Adjust the tick marks so only the selected rows show their gene names…
alimourey Aug 25, 2025
3bacc6a
Clear the plot and table selections on Clear button click in the mult…
alimourey Aug 25, 2025
8f61e4b
Fix range selector so it cannot expand beyond the plot bounds
alimourey Aug 25, 2025
93871ab
WIP
alimourey Aug 27, 2025
1267a5e
wip
alimourey Aug 27, 2025
dfc42ac
Add Load Gene Context section
alimourey Aug 27, 2025
f4fd891
wip
alimourey Aug 28, 2025
8924ca6
Fix bug that prevented the FDR threshold from reseting on click of th…
alimourey Aug 28, 2025
df4f2d4
Fix range selector cuts off first and last column
alimourey Aug 29, 2025
235a70e
Add semi color separator to Synonyms table column and adjust table co…
alimourey Aug 29, 2025
445a564
Fix search bar bug
alimourey Aug 29, 2025
435aa2b
Changes to allow GeneTEA to use unrounded data for table download
alimourey Aug 29, 2025
1b277e2
Fix GeneTea bar chart to use stacked term data if group terms is turn…
alimourey Sep 3, 2025
6cb10e0
Adjust whether to show x axis tick marks on zoom in and out and zoom …
alimourey Sep 4, 2025
180de6c
Fix need to clear out validation when search list is fewer than 3 sel…
alimourey Sep 4, 2025
5752da9
Update width of stacked bar chart lines to be wider when using term g…
alimourey Sep 4, 2025
4ca22d5
Hide continuous alpha tab
alimourey Sep 4, 2025
65c589d
Fix issue with the MultiSelect component trying to process carriage r…
alimourey Sep 4, 2025
d2eb9c0
Fixed bug with plot selections panel that kept it from clearing out a…
alimourey Sep 4, 2025
2486027
Disable the Multiselect text area Select and Clear buttons while data…
alimourey Sep 5, 2025
e1f7e99
Keep track of the latest promise to avoid UI request race condition
alimourey Sep 5, 2025
899b67c
Hide range slider of heatmap if no data
alimourey Sep 5, 2025
d6c71de
Only show set selection from a context option in PlotSelections panel…
alimourey Sep 5, 2025
d32f6fa
Fix barchart stacking and hover text by keeping track of term origIndex
alimourey Sep 8, 2025
61e4930
Fix Load from Gene Context text does not clear on Clear button click
alimourey Sep 8, 2025
5e710f1
Fix ESLint errors for HeatmapBarChart
alimourey Sep 8, 2025
d0ceee8
Fix ESLinet errors in useData
alimourey Sep 8, 2025
e0fb50d
Fix ES Lint errors and crash on turning off doGroupByTerm
alimourey Sep 9, 2025
036febf
WIP
alimourey Sep 10, 2025
690ff93
Some layout improvements and styling. Fixed a bug that submitted carr…
alimourey Sep 10, 2025
4566551
wip
alimourey Sep 11, 2025
2d61703
Merge branch 'master' into genetea-page
alimourey Sep 11, 2025
86363d2
Hide from tools menu and add under development message to the title
alimourey Sep 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions frontend/packages/@depmap/api/src/legacyPortalAPI/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as data_page from "./resources/data_page";
import * as download from "./resources/download";
import * as entity_summary from "./resources/entity_summary";
import * as genetea from "./resources/genetea";
import * as experimental_genetea from "./resources/experimental_genetea";
import * as interactive from "./resources/interactive";
import * as tda from "./resources/tda";
import * as misc from "./resources/misc";
Expand All @@ -24,6 +25,7 @@ export const legacyPortalAPI = {
...interactive,
...tda,
...misc,
...experimental_genetea,
};

type Api = typeof legacyPortalAPI;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
import qs from "qs";
import { enabledFeatures } from "@depmap/globals";
import { getJson } from "../client";
import { GeneTeaEnrichedTerms } from "@depmap/types/src/experimental_genetea";

// ❌ ❌ ❌ WARNING: THIS IS EXPERIMENTAL AND WILL LIKELY CHANGE ❌ ❌ ❌
// DO NOT USE THIS FOR ANYTHING OTHER THAN THE NEW GENETEA TEA PARTY PAGE!!!!!

// Do not use in production! For local development only.
const toCorsProxyUrl = (geneTeaUrl: string, params: object) => {
const query = qs.stringify(params, { arrayFormat: "repeat" });
const url = `https://cds.team/${geneTeaUrl}/?${query}`;
return "https://corsproxy.io/?" + encodeURIComponent(url);
};

export async function fetchGeneTeaEnrichmentExperimental(
plotSelections: string[] | null,
genes: string[],
doGroupTerms: boolean,
sortBy: "Significance" | "Effect Size",
maxFDR: number,
maxTopTerms: number | null,
maxMatchingOverall: number | null,
minMatchingQuery: number
// effectSizeThreshold: number,
): Promise<GeneTeaEnrichedTerms> {
if (!enabledFeatures.gene_tea) {
throw new Error("GeneTea is not supported in this environment!");
}

const geneTeaUrl = "genetea-api/enriched-terms-v2";

let params: any = {
gene_list: genes,
group_terms: doGroupTerms,
include_plotting_payload: "true",
sort_by: sortBy,
max_fdr: maxFDR,
min_genes: minMatchingQuery || -1,
max_n_genes: maxMatchingOverall,
n: plotSelections?.length === 0 ? maxTopTerms || -1 : -1,
};

if (plotSelections) {
params = { ...params, plot_selections: plotSelections };
}
console.log("For Sanity Checking", params);

interface RawResponse {
// TODO: Give the user feedback when some genes are invalid.
valid_genes: string[];
invalid_genes: string[];
total_n_enriched_terms: number;
total_n_term_groups: number;
enriched_terms: {
Term: string[];
"Matching Genes in List": string[];
"n Matching Genes Overall": number[];
"n Matching Genes in List": number[];
"p-val": number[];
FDR: number[];
Stopword: boolean[];
Synonyms: (string | null)[];
"Total Info": number[];
"Effect Size": number[];
"Term Group": string[];
"-log10 FDR": number[];
"Clipped Term": string[];
"Clipped Synonyms": (string | null)[];
"Clipped Matching Genes in List": string[];
};
plotting_payload: {
groupby: "Term" | "Term Group";
term_cluster: {
Term: string[] | null[];
"Term Group": string[] | null[];
Cluster: number[];
Order: number[];
};
gene_cluster: {
Gene: string[];
Cluster: number[];
Order: number[];
};
term_to_entity: any; // TODO update this type
frequent_terms: {
Term: string[];
"Matching Genes in List": string[];
"n Matching Genes Overall": number[];
"n Matching Genes in List": number[];
"p-val": number[];
FDR: number[];
Stopword: boolean[];
Synonyms: (string | null)[];
"Total Info": number[];
"Effect Size": number[];
};
all_enriched_terms: {
Term: string[];
"Matching Genes in List": string[];
"n Matching Genes Overall": number[];
"n Matching Genes in List": number[];
"p-val": number[];
FDR: number[];
Stopword: boolean[];
Synonyms: (string | null)[];
"Total Info": number[];
"Effect Size": number[];
"Term Group": string[];
};
};
}

const body =
process.env.NODE_ENV === "development"
? await getJson<RawResponse>(
toCorsProxyUrl(geneTeaUrl, params),
undefined,
{ credentials: "omit" }
)
: await getJson<RawResponse>(`/../../${geneTeaUrl}/`, params);

// `enriched_terms` can be null when there are no relevant terms. We'll
// return a wrapper object to distinguish this from some kind of error.
if (body.enriched_terms === null) {
return {
groupby: "Term",
validGenes: [],
invalidGenes: [],
enrichedTerms: null,
totalNEnrichedTerms: 0,
totalNTermGroups: 0,
termCluster: null,
geneCluster: null,
termToEntity: null,
frequentTerms: null,
allEnrichedTerms: null,
};
}

const plottingPayload = body.plotting_payload;
const allEt = plottingPayload.all_enriched_terms;
const et = body.enriched_terms;
const enrichedTerms = {
term: et.Term,
fdr: et.FDR,
totalEnrichedTerms: body.total_n_enriched_terms,
totalTermGroups: body.total_n_term_groups,
synonyms: et.Synonyms.map((list) => list?.split(";") || []),
matchingGenesInList: et["Matching Genes in List"],
nMatchingGenesInList: et["n Matching Genes in List"],
nMatchingGenesOverall: et["n Matching Genes Overall"],
termGroup: et["Term Group"],
effectSize: et["Effect Size"],
pVal: et["p-val"],
stopword: et.Stopword,
totalInfo: et["Total Info"],
negLogFDR: et["-log10 FDR"],
clippedTerm: et["Clipped Term"],
clippedSynonyms: et["Clipped Synonyms"].map(
(list) => list?.split(";") || []
),
clippedMatchingGenesInList: et["Clipped Matching Genes in List"],
};
const allEnrichedTerms = {
term: allEt.Term,
fdr: allEt.FDR,
synonyms: allEt.Synonyms.map((list) => list?.split(";") || []),
matchingGenesInList: allEt["Matching Genes in List"],
nMatchingGenesInList: allEt["n Matching Genes in List"],
nMatchingGenesOverall: allEt["n Matching Genes Overall"],
termGroup: allEt["Term Group"],
effectSize: allEt["Effect Size"],
pVal: allEt["p-val"],
stopword: allEt.Stopword,
totalInfo: et["Total Info"],
};

const termClusterTermOrGroup = doGroupTerms
? plottingPayload.term_cluster["Term Group"]
: plottingPayload.term_cluster.Term;

const termCluster = {
termOrTermGroup: termClusterTermOrGroup as string[],
cluster: plottingPayload.term_cluster.Cluster,
order: plottingPayload.term_cluster.Order,
};

const geneCluster = {
gene: plottingPayload.gene_cluster.Gene,
cluster: plottingPayload.gene_cluster.Cluster,
order: plottingPayload.gene_cluster.Order,
};

const termToEntity = {
termOrTermGroup: doGroupTerms
? (plottingPayload.term_to_entity["Term Group"] as string[])
: (plottingPayload.term_to_entity.Term as string[]),
gene: plottingPayload.term_to_entity.Gene,
count: plottingPayload.term_to_entity.Count,
nTerms: plottingPayload.term_to_entity["n Terms"],
fraction: plottingPayload.term_to_entity.Fraction,
};

const frequentTerms = {
term: plottingPayload.frequent_terms.Term,
matchingGenesInList:
plottingPayload.frequent_terms["Matching Genes in List"],
nMatchingGenesOverall:
plottingPayload.frequent_terms["n Matching Genes Overall"],
nMatchingGenesInList:
plottingPayload.frequent_terms["n Matching Genes in List"],
pVal: plottingPayload.frequent_terms["p-val"],
fdr: plottingPayload.frequent_terms.FDR,
stopword: plottingPayload.frequent_terms.Stopword,
synonyms: plottingPayload.frequent_terms.Synonyms.map(
(list) => list?.split(";") || []
),
totalInfo: plottingPayload.frequent_terms["Total Info"],
effectSize: plottingPayload.frequent_terms["Effect Size"],
};

return {
validGenes: body.valid_genes,
invalidGenes: body.invalid_genes,
totalNEnrichedTerms: body.total_n_enriched_terms,
totalNTermGroups: body.total_n_term_groups,
groupby: plottingPayload.groupby,
enrichedTerms,
termCluster,
geneCluster,
termToEntity,
frequentTerms,
allEnrichedTerms,
};
}

export async function fetchGeneTeaTermContextExperimental(
term: string,
genes: string[]
): Promise<Record<string, string>> {
if (!enabledFeatures.gene_tea) {
throw new Error("GeneTea is not supported in this environment!");
}

const geneTeaUrl = "genetea-api/context";

const params = {
term,
gene_list: genes,
html: true,
};

type RawResponse =
| {
valid_genes: string[];
invalid_genes: string[];
remapped_genes: Record<string, string>;
context: Record<string, string>;
}
| { message: string }; // error message

const body =
process.env.NODE_ENV === "development"
? await getJson<RawResponse>(
toCorsProxyUrl(geneTeaUrl, params),
undefined,
{ credentials: "omit" }
)
: await getJson<RawResponse>(`/../../${geneTeaUrl}/`, params);

if ("message" in body) {
throw new Error(body.message);
}

return body.context;
}
83 changes: 83 additions & 0 deletions frontend/packages/@depmap/types/src/experimental_genetea.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
export interface GeneTeaTableRow {
term: string;
termGroup: string;
synonyms: string;
matchingGenesInList: string;
nMatchingGenesOverall: number;
nMatchingGenesInList: number;
fdr: number;
effectSize: number;
}

type GeneSymbol = string;
type FractionMatching = number | null;
type TermOrTermGroup = string;

export interface HeatmapFormattedData {
x: GeneSymbol[];
y: TermOrTermGroup[];
z: FractionMatching[];
customdata: string[]; // For hover text
}

export interface BarChartFormattedData {
x: number[];
y: TermOrTermGroup[];
customdata: string[]; // For hover text
}

export interface TermCluster {
termOrTermGroup: string[];
cluster: number[];
order: number[];
}

export interface GeneCluster {
gene: string[];
cluster: number[];
order: number[];
}

export interface TermToEntity {
termOrTermGroup: string[];
gene: string[];
count: number[];
nTerms: number[];
fraction: number[];
}

export interface FrequentTerms {
term: string[];
matchingGenesInList: string[];
nMatchingGenesOverall: number[];
nMatchingGenesInList: number[];
pVal: number[];
fdr: number[];
stopword: boolean[];
synonyms: string[][];
totalInfo: number[];
effectSize: number[];
}

export interface AllEnrichedTerms extends FrequentTerms {
termGroup: string[];
}

export interface EnrichedTerms extends AllEnrichedTerms {
negLogFDR: number[];
clippedTerm: string[];
}

export interface GeneTeaEnrichedTerms {
validGenes: string[];
invalidGenes: string[];
totalNEnrichedTerms: number;
totalNTermGroups: number;
groupby: "Term" | "Term Group";
enrichedTerms: EnrichedTerms | null;
termCluster: TermCluster | null;
geneCluster: GeneCluster | null;
termToEntity: TermToEntity | null;
frequentTerms: FrequentTerms | null;
allEnrichedTerms: AllEnrichedTerms | null;
}
Loading