Skip to content

Commit

Permalink
EDA in WDK: Integrate EDA scatterplot in WDK record page (#1325)
Browse files Browse the repository at this point in the history
* checkpoint

* Add type annotations to remove inferred `any` type.

* Allow an analysis descriptor to be provided to `useAnalysis`

* Only update state if the next param value is different than the previous
param value

* Extract useAnalysisState to allow external state tracking of analysis object

* Use useAnalysisState, add counts, etc

* Add stubbed question page with custom name

* Allow hiding starred variable feature

* fix typo

* Use global memoization

* Encapsulate creation of eda api clients

* Adhere to lastest "api" for GenesByEdaSubset

* Stub in eda scatterplot for phenotype dataset

* Allow html formatting in search page header

* Enable override for GenesByEdaSubset and set question page heading
dynamically.

* use more specific query name

* update query name

* use dynamic import for plotly, to keep it out of the main bundle

* replace "EdaSubsetting" with "EdaSubset"

* dont wrap sentence

* fix label in user comment upload form

* Fix volcano plot thumbnail out of sync (#1299)

* fix for thumnails not rendering

* replace mutating state with better check of filters

* Allow html formatting in search page header

* Enable override for GenesByEdaSubset and set question page heading
dynamically.

* use more specific query name

* Use alphabetic ordering when displaying a flattened list of eda entities (#1323)

* only order variable tree entities alphabetically (#1324)

* Add annotations to plotly plots (#1321)

* added plot annotations functionality

* improve scatter annotation story

* added histogram annotation story

* introduce VEuPathDBAnnotation type

* fix annotation types in histogram

* update query name

* use dynamic import for plotly, to keep it out of the main bundle

* replace "EdaSubsetting" with "EdaSubset"

* Revert to injecting script and link tags in document head

* Export type

* Expand type to include ReactHTML elements

* Allow className to be overridden

* Add EdaDatasetGraph component

* Use EdaDatasetGraph component with EdaPhenotypeGraphsDataTable

* simplify layout and add warning message for genes not in experiment

* Add parentheses

* Wire up highlightIds

* Highlight graph_ids

* Allow arbitrary variable values to be highlighted

* Specify gene variable values to highlight

---------

Co-authored-by: aurreco-uga <[email protected]>
Co-authored-by: Cristina Aurrecoechea <[email protected]>
Co-authored-by: Ann Sizemore Blevins <[email protected]>
  • Loading branch information
4 people authored Feb 24, 2025
1 parent 398f43d commit cdf3a33
Show file tree
Hide file tree
Showing 8 changed files with 412 additions and 32 deletions.
2 changes: 1 addition & 1 deletion packages/libs/coreui/src/components/Mesa/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { CSSProperties, ReactElement, ReactNode } from 'react';

type DefaultColumnKey<Row> = Extract<keyof Row, string>;

type ChildRowProps<Row> = {
export type ChildRowProps<Row> = {
rowIndex: number;
rowData: Row;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@ const CI95TEXT = '95% Confidence interval';
const CI95SUFFIX = `, ${CI95TEXT}`;
const BESTFITTEXT = 'Best fit';
const BESTFITSUFFIX = `, ${BESTFITTEXT}`;
const TESTHIGHLIGHTIDS = ['SRR7047967 (16S)', 'SRR7054402 (16S)'];

const plotContainerStyles = {
width: 750,
Expand Down Expand Up @@ -2047,7 +2046,7 @@ function ScatterplotViz(props: VisualizationProps<Options>) {
options?.getOverlayVariable != null
? providedOverlayVariableDescriptor
? variableDisplayWithUnit(providedOverlayVariable)
: 'None. ' + options?.getOverlayVariableHelp?.() ?? ''
: 'None. ' + (options?.getOverlayVariableHelp?.() ?? '')
: undefined,
} as const,
]),
Expand Down Expand Up @@ -2167,7 +2166,8 @@ export function scatterplotResponseToData(
facetVariable?: Variable,
computationType?: string,
entities?: StudyEntity[],
colorPaletteOverride?: string[]
colorPaletteOverride?: string[],
highlightIds?: string[]
): ScatterPlotDataWithCoverage {
const modeValue = 'markers';

Expand Down Expand Up @@ -2214,7 +2214,7 @@ export function scatterplotResponseToData(
computationType,
entities,
colorPaletteOverride,
TESTHIGHLIGHTIDS
highlightIds
);

return {
Expand Down
13 changes: 10 additions & 3 deletions packages/libs/eda/src/lib/workspace/WorkspaceContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ interface Props {
analysisId?: string;
children: ReactNode;
isStudyExplorerWorkspace?: boolean;
// overrides default class names
className?: string;
}

/** Allows a user to create a new analysis or edit an existing one. */
Expand All @@ -41,6 +43,7 @@ export function WorkspaceContainer({
edaServiceUrl,
children,
isStudyExplorerWorkspace = false,
className,
}: Props) {
const { url } = useRouteMatch();
const subsettingClient = useConfiguredSubsettingClient(edaServiceUrl);
Expand Down Expand Up @@ -72,13 +75,17 @@ export function WorkspaceContainer({
);
const classes = useStyles();

const finalClassName =
className ??
`${cx()} ${isStudyExplorerWorkspace ? 'StudyExplorerWorkspace' : ''} ${
classes.workspace
}`;

return (
<QueryClientProvider client={queryClient}>
<EDAWorkspaceContainer
studyId={studyId}
className={`${cx()} ${
isStudyExplorerWorkspace ? 'StudyExplorerWorkspace' : ''
} ${classes.workspace}`}
className={finalClassName}
analysisClient={analysisClient}
dataClient={dataClient}
subsettingClient={subsettingClient}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import { zipWith } from 'lodash';
import React, { useState, useEffect, useCallback } from 'react';
import React, { useState, useEffect, useCallback, ReactHTML } from 'react';
import { wrappable } from '../../Utils/ComponentUtils';
import './CollapsibleSection.css';

Expand All @@ -15,7 +15,7 @@ interface Props {
isCollapsed?: boolean;
onCollapsedChange: (isCollapsed: boolean) => void;
headerContent: React.ReactNode;
headerComponent?: React.ComponentType;
headerComponent?: React.ComponentType | keyof ReactHTML;
className?: string;
children: React.ReactNode;
}
Expand Down
20 changes: 20 additions & 0 deletions packages/libs/web-common/src/components/DatasetGraph.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { safeHtml } from '@veupathdb/wdk-client/lib/Utils/ComponentUtils';
import ExternalResource from './ExternalResource';
import { JbrowseIframe } from './JbrowseIframe';
import { EdaScatterPlot } from './eda/EdaScatterPlot';

/**
* Renders an Dataset graph with the provided rowData.
Expand Down Expand Up @@ -223,6 +224,25 @@ export default class DatasetGraph extends React.PureComponent {
<div className="eupathdb-DatasetGraphContainer">
<div className="eupathdb-DatasetGraph">
{visibleGraphs.map((index) => {
// Hardcoded to render an EDA Scatterplot
// TODO Replace hardcoded values with rowData attributes.
if (dataset_id === 'DS_d4745ea297') {
return (
<EdaScatterPlot
datasetId={dataset_id}
xAxisVariable={{
entityId: 'genePhenotypeData',
// Phenotype rank
variableId: 'VAR_9f0d6627',
}}
yAxisVariable={{
entityId: 'genePhenotypeData',
// Mean Phenotype score
variableId: 'VAR_40829b7e',
}}
/>
);
}
let { height, width, visible_part } = graphs[index];
let fullUrl = `${imgUrl}&vp=${visible_part}`;
return (
Expand Down
194 changes: 194 additions & 0 deletions packages/libs/web-common/src/components/EdaDatasetGraph.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import {
RecordClass,
RecordInstance,
TableField,
TableValue,
} from '@veupathdb/wdk-client/lib/Utils/WdkModel';
import * as t from 'io-ts';
import { useState } from 'react';
import { EdaScatterPlot } from './eda/EdaScatterPlot';
import { Link } from 'react-router-dom';
import { CollapsibleSection } from '@veupathdb/wdk-client/lib/Components';

const PlotConfig = t.type({
plotName: t.string,
plotType: t.string,
xAxisEntityId: t.string,
xAxisVariableId: t.string,
yAxisEntityId: t.string,
yAxisVariableId: t.string,
});

const PlotConfigs = t.array(PlotConfig);

type RowData = TableValue[number];

interface RecordTableProps {
record: RecordInstance;
recordClass: RecordClass;
value: TableValue;
table: TableField;
className?: string;
searchTerm?: string;
onSearchTermChange?: (searchTerm: string) => void;
}

interface DataTable {
value: TableValue;
table: TableField;
record: RecordInstance;
recordClass: RecordClass;
DefaultComponent: React.ComponentType<RecordTableProps>;
}

interface Props {
rowIndex: number;
rowData: RowData;
dataTable: DataTable;
}

export function EdaDatasetGraph(props: Props) {
const {
rowData: {
plot_configs_json,
dataset_id,
dataset_name,
graph_ids,
source_id,
default_graph_id,
},
dataTable,
} = props;

const plotConfigs = parseJson(plot_configs_json as string);

const [selectedPlotsIndex, setSelectedPlotsIndex] = useState([0]);
const [dataTableCollapsed, setDataTableCollapsed] = useState(true);

if (plotConfigs == null) {
return <div>Could not parse plot_configs_json</div>;
}

const graphIds = graph_ids?.toString().split(/\s*,\s*/);

const selectedPlotConfigs = plotConfigs.filter((_, index) =>
selectedPlotsIndex.includes(index)
);

return (
<div>
<h4>Choose graph(s) to display</h4>
{plotConfigs.map((plotConfig, index) => {
return (
<label key={plotConfig.plotName}>
<input
type="checkbox"
checked={selectedPlotsIndex.includes(index)}
onChange={(e) => {
setSelectedPlotsIndex((current) => {
return e.target.checked
? current.concat(index).sort()
: current.filter((i) => i !== index);
});
}}
/>{' '}
{plotConfig.plotName}{' '}
</label>
);
})}

{default_graph_id !== source_id ? (
<div>
<strong style={{ color: 'firebrick' }}>WARNING</strong>: This Gene (
{source_id as string}) does not have data for this experiment.
Instead, we are showing data for this same gene(s) from the reference
strain for this species. This may or may NOT accurately represent the
gene you are interested in.{' '}
</div>
) : null}

<div
style={{
display: 'flex',
flexWrap: 'wrap',
}}
>
{selectedPlotConfigs.map((plotConfig) => {
const xAxisVariable = {
entityId: plotConfig.xAxisEntityId,
variableId: plotConfig.xAxisVariableId,
};
const yAxisVariable = {
entityId: plotConfig.yAxisEntityId,
variableId: plotConfig.yAxisVariableId,
};
return (
<div style={{ width: 500 }}>
<EdaScatterPlot
datasetId={dataset_id as string}
xAxisVariable={xAxisVariable}
yAxisVariable={yAxisVariable}
highlightSpec={
graphIds && {
ids: graphIds,
// gene id
variableId: 'VAR_bdc8e679',
entityId: plotConfig.xAxisEntityId,
}
}
/>
</div>
);
})}
</div>
<div>
<div style={{ display: 'flex', gap: '3ex' }}>
<h4>
<Link
to={`/workspace/analyses/${dataset_id}/new/visualizations/new`}
>
Use the Study Explorer for more advanced plot options
</Link>
</h4>
<h4>
<Link to={`/record/dataset/${dataset_id}`}>
See the full dataset description
</Link>
</h4>
</div>

{props.dataTable && (
<CollapsibleSection
className={'eupathdb-' + props.dataTable.table.name + 'Container'}
headerContent="Data table"
headerComponent="h4"
isCollapsed={dataTableCollapsed}
onCollapsedChange={setDataTableCollapsed}
>
<dataTable.DefaultComponent
record={dataTable.record}
recordClass={dataTable.recordClass}
table={dataTable.table}
value={dataTable.value.filter(
(dat) => dat.dataset_id === dataset_name
)}
/>
</CollapsibleSection>
)}
</div>
</div>
);
}

function parseJson(json: string) {
try {
const object = JSON.parse(json);
if (PlotConfigs.is(object)) {
return object;
}
return undefined;
} catch (error) {
console.error(error);
return undefined;
}
}
Loading

0 comments on commit cdf3a33

Please sign in to comment.