Skip to content

Commit cdf3a33

Browse files
dmfalkeaurreco-ugaCristina Aurrecoecheaasizemore
authored
EDA in WDK: Integrate EDA scatterplot in WDK record page (#1325)
* 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 <aurreco@uga.edu> Co-authored-by: Cristina Aurrecoechea <aurreco@palm.penn.apidb.org> Co-authored-by: Ann Sizemore Blevins <asizemore@users.noreply.github.com>
1 parent 398f43d commit cdf3a33

File tree

8 files changed

+412
-32
lines changed

8 files changed

+412
-32
lines changed

packages/libs/coreui/src/components/Mesa/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { CSSProperties, ReactElement, ReactNode } from 'react';
22

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

5-
type ChildRowProps<Row> = {
5+
export type ChildRowProps<Row> = {
66
rowIndex: number;
77
rowData: Row;
88
};

packages/libs/eda/src/lib/core/components/visualizations/implementations/ScatterplotVisualization.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,6 @@ const CI95TEXT = '95% Confidence interval';
160160
const CI95SUFFIX = `, ${CI95TEXT}`;
161161
const BESTFITTEXT = 'Best fit';
162162
const BESTFITSUFFIX = `, ${BESTFITTEXT}`;
163-
const TESTHIGHLIGHTIDS = ['SRR7047967 (16S)', 'SRR7054402 (16S)'];
164163

165164
const plotContainerStyles = {
166165
width: 750,
@@ -2047,7 +2046,7 @@ function ScatterplotViz(props: VisualizationProps<Options>) {
20472046
options?.getOverlayVariable != null
20482047
? providedOverlayVariableDescriptor
20492048
? variableDisplayWithUnit(providedOverlayVariable)
2050-
: 'None. ' + options?.getOverlayVariableHelp?.() ?? ''
2049+
: 'None. ' + (options?.getOverlayVariableHelp?.() ?? '')
20512050
: undefined,
20522051
} as const,
20532052
]),
@@ -2167,7 +2166,8 @@ export function scatterplotResponseToData(
21672166
facetVariable?: Variable,
21682167
computationType?: string,
21692168
entities?: StudyEntity[],
2170-
colorPaletteOverride?: string[]
2169+
colorPaletteOverride?: string[],
2170+
highlightIds?: string[]
21712171
): ScatterPlotDataWithCoverage {
21722172
const modeValue = 'markers';
21732173

@@ -2214,7 +2214,7 @@ export function scatterplotResponseToData(
22142214
computationType,
22152215
entities,
22162216
colorPaletteOverride,
2217-
TESTHIGHLIGHTIDS
2217+
highlightIds
22182218
);
22192219

22202220
return {

packages/libs/eda/src/lib/workspace/WorkspaceContainer.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ interface Props {
3333
analysisId?: string;
3434
children: ReactNode;
3535
isStudyExplorerWorkspace?: boolean;
36+
// overrides default class names
37+
className?: string;
3638
}
3739

3840
/** Allows a user to create a new analysis or edit an existing one. */
@@ -41,6 +43,7 @@ export function WorkspaceContainer({
4143
edaServiceUrl,
4244
children,
4345
isStudyExplorerWorkspace = false,
46+
className,
4447
}: Props) {
4548
const { url } = useRouteMatch();
4649
const subsettingClient = useConfiguredSubsettingClient(edaServiceUrl);
@@ -72,13 +75,17 @@ export function WorkspaceContainer({
7275
);
7376
const classes = useStyles();
7477

78+
const finalClassName =
79+
className ??
80+
`${cx()} ${isStudyExplorerWorkspace ? 'StudyExplorerWorkspace' : ''} ${
81+
classes.workspace
82+
}`;
83+
7584
return (
7685
<QueryClientProvider client={queryClient}>
7786
<EDAWorkspaceContainer
7887
studyId={studyId}
79-
className={`${cx()} ${
80-
isStudyExplorerWorkspace ? 'StudyExplorerWorkspace' : ''
81-
} ${classes.workspace}`}
88+
className={finalClassName}
8289
analysisClient={analysisClient}
8390
dataClient={dataClient}
8491
subsettingClient={subsettingClient}

packages/libs/wdk-client/src/Components/Display/CollapsibleSection.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
import { zipWith } from 'lodash';
9-
import React, { useState, useEffect, useCallback } from 'react';
9+
import React, { useState, useEffect, useCallback, ReactHTML } from 'react';
1010
import { wrappable } from '../../Utils/ComponentUtils';
1111
import './CollapsibleSection.css';
1212

@@ -15,7 +15,7 @@ interface Props {
1515
isCollapsed?: boolean;
1616
onCollapsedChange: (isCollapsed: boolean) => void;
1717
headerContent: React.ReactNode;
18-
headerComponent?: React.ComponentType;
18+
headerComponent?: React.ComponentType | keyof ReactHTML;
1919
className?: string;
2020
children: React.ReactNode;
2121
}

packages/libs/web-common/src/components/DatasetGraph.jsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import { safeHtml } from '@veupathdb/wdk-client/lib/Utils/ComponentUtils';
99
import ExternalResource from './ExternalResource';
1010
import { JbrowseIframe } from './JbrowseIframe';
11+
import { EdaScatterPlot } from './eda/EdaScatterPlot';
1112

1213
/**
1314
* Renders an Dataset graph with the provided rowData.
@@ -223,6 +224,25 @@ export default class DatasetGraph extends React.PureComponent {
223224
<div className="eupathdb-DatasetGraphContainer">
224225
<div className="eupathdb-DatasetGraph">
225226
{visibleGraphs.map((index) => {
227+
// Hardcoded to render an EDA Scatterplot
228+
// TODO Replace hardcoded values with rowData attributes.
229+
if (dataset_id === 'DS_d4745ea297') {
230+
return (
231+
<EdaScatterPlot
232+
datasetId={dataset_id}
233+
xAxisVariable={{
234+
entityId: 'genePhenotypeData',
235+
// Phenotype rank
236+
variableId: 'VAR_9f0d6627',
237+
}}
238+
yAxisVariable={{
239+
entityId: 'genePhenotypeData',
240+
// Mean Phenotype score
241+
variableId: 'VAR_40829b7e',
242+
}}
243+
/>
244+
);
245+
}
226246
let { height, width, visible_part } = graphs[index];
227247
let fullUrl = `${imgUrl}&vp=${visible_part}`;
228248
return (
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import {
2+
RecordClass,
3+
RecordInstance,
4+
TableField,
5+
TableValue,
6+
} from '@veupathdb/wdk-client/lib/Utils/WdkModel';
7+
import * as t from 'io-ts';
8+
import { useState } from 'react';
9+
import { EdaScatterPlot } from './eda/EdaScatterPlot';
10+
import { Link } from 'react-router-dom';
11+
import { CollapsibleSection } from '@veupathdb/wdk-client/lib/Components';
12+
13+
const PlotConfig = t.type({
14+
plotName: t.string,
15+
plotType: t.string,
16+
xAxisEntityId: t.string,
17+
xAxisVariableId: t.string,
18+
yAxisEntityId: t.string,
19+
yAxisVariableId: t.string,
20+
});
21+
22+
const PlotConfigs = t.array(PlotConfig);
23+
24+
type RowData = TableValue[number];
25+
26+
interface RecordTableProps {
27+
record: RecordInstance;
28+
recordClass: RecordClass;
29+
value: TableValue;
30+
table: TableField;
31+
className?: string;
32+
searchTerm?: string;
33+
onSearchTermChange?: (searchTerm: string) => void;
34+
}
35+
36+
interface DataTable {
37+
value: TableValue;
38+
table: TableField;
39+
record: RecordInstance;
40+
recordClass: RecordClass;
41+
DefaultComponent: React.ComponentType<RecordTableProps>;
42+
}
43+
44+
interface Props {
45+
rowIndex: number;
46+
rowData: RowData;
47+
dataTable: DataTable;
48+
}
49+
50+
export function EdaDatasetGraph(props: Props) {
51+
const {
52+
rowData: {
53+
plot_configs_json,
54+
dataset_id,
55+
dataset_name,
56+
graph_ids,
57+
source_id,
58+
default_graph_id,
59+
},
60+
dataTable,
61+
} = props;
62+
63+
const plotConfigs = parseJson(plot_configs_json as string);
64+
65+
const [selectedPlotsIndex, setSelectedPlotsIndex] = useState([0]);
66+
const [dataTableCollapsed, setDataTableCollapsed] = useState(true);
67+
68+
if (plotConfigs == null) {
69+
return <div>Could not parse plot_configs_json</div>;
70+
}
71+
72+
const graphIds = graph_ids?.toString().split(/\s*,\s*/);
73+
74+
const selectedPlotConfigs = plotConfigs.filter((_, index) =>
75+
selectedPlotsIndex.includes(index)
76+
);
77+
78+
return (
79+
<div>
80+
<h4>Choose graph(s) to display</h4>
81+
{plotConfigs.map((plotConfig, index) => {
82+
return (
83+
<label key={plotConfig.plotName}>
84+
<input
85+
type="checkbox"
86+
checked={selectedPlotsIndex.includes(index)}
87+
onChange={(e) => {
88+
setSelectedPlotsIndex((current) => {
89+
return e.target.checked
90+
? current.concat(index).sort()
91+
: current.filter((i) => i !== index);
92+
});
93+
}}
94+
/>{' '}
95+
{plotConfig.plotName}{' '}
96+
</label>
97+
);
98+
})}
99+
100+
{default_graph_id !== source_id ? (
101+
<div>
102+
<strong style={{ color: 'firebrick' }}>WARNING</strong>: This Gene (
103+
{source_id as string}) does not have data for this experiment.
104+
Instead, we are showing data for this same gene(s) from the reference
105+
strain for this species. This may or may NOT accurately represent the
106+
gene you are interested in.{' '}
107+
</div>
108+
) : null}
109+
110+
<div
111+
style={{
112+
display: 'flex',
113+
flexWrap: 'wrap',
114+
}}
115+
>
116+
{selectedPlotConfigs.map((plotConfig) => {
117+
const xAxisVariable = {
118+
entityId: plotConfig.xAxisEntityId,
119+
variableId: plotConfig.xAxisVariableId,
120+
};
121+
const yAxisVariable = {
122+
entityId: plotConfig.yAxisEntityId,
123+
variableId: plotConfig.yAxisVariableId,
124+
};
125+
return (
126+
<div style={{ width: 500 }}>
127+
<EdaScatterPlot
128+
datasetId={dataset_id as string}
129+
xAxisVariable={xAxisVariable}
130+
yAxisVariable={yAxisVariable}
131+
highlightSpec={
132+
graphIds && {
133+
ids: graphIds,
134+
// gene id
135+
variableId: 'VAR_bdc8e679',
136+
entityId: plotConfig.xAxisEntityId,
137+
}
138+
}
139+
/>
140+
</div>
141+
);
142+
})}
143+
</div>
144+
<div>
145+
<div style={{ display: 'flex', gap: '3ex' }}>
146+
<h4>
147+
<Link
148+
to={`/workspace/analyses/${dataset_id}/new/visualizations/new`}
149+
>
150+
Use the Study Explorer for more advanced plot options
151+
</Link>
152+
</h4>
153+
<h4>
154+
<Link to={`/record/dataset/${dataset_id}`}>
155+
See the full dataset description
156+
</Link>
157+
</h4>
158+
</div>
159+
160+
{props.dataTable && (
161+
<CollapsibleSection
162+
className={'eupathdb-' + props.dataTable.table.name + 'Container'}
163+
headerContent="Data table"
164+
headerComponent="h4"
165+
isCollapsed={dataTableCollapsed}
166+
onCollapsedChange={setDataTableCollapsed}
167+
>
168+
<dataTable.DefaultComponent
169+
record={dataTable.record}
170+
recordClass={dataTable.recordClass}
171+
table={dataTable.table}
172+
value={dataTable.value.filter(
173+
(dat) => dat.dataset_id === dataset_name
174+
)}
175+
/>
176+
</CollapsibleSection>
177+
)}
178+
</div>
179+
</div>
180+
);
181+
}
182+
183+
function parseJson(json: string) {
184+
try {
185+
const object = JSON.parse(json);
186+
if (PlotConfigs.is(object)) {
187+
return object;
188+
}
189+
return undefined;
190+
} catch (error) {
191+
console.error(error);
192+
return undefined;
193+
}
194+
}

0 commit comments

Comments
 (0)