Skip to content

Commit

Permalink
Merge pull request #467 from VEuPathDB/feature-360-diff-abund-cont-vars
Browse files Browse the repository at this point in the history
Feature 360 diff abund cont vars
  • Loading branch information
asizemore authored Sep 11, 2023
2 parents e267f16 + 5cc5318 commit 44c61b0
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 26 deletions.
4 changes: 4 additions & 0 deletions packages/libs/coreui/src/components/inputs/SelectList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export interface SelectListProps<T> extends CheckboxListProps<T> {
/** A button's content if/when no values are currently selected */
defaultButtonDisplayContent: ReactNode;
isDisabled?: boolean;
/** Are contents loading? */
isLoading?: boolean;
}

export default function SelectList<T>({
Expand All @@ -18,6 +20,7 @@ export default function SelectList<T>({
children,
defaultButtonDisplayContent,
isDisabled = false,
isLoading = false,
...props
}: SelectListProps<T>) {
const [selected, setSelected] = useState<SelectListProps<T>['value']>(value);
Expand Down Expand Up @@ -67,6 +70,7 @@ export default function SelectList<T>({
margin: '0.5em',
}}
>
{isLoading && <div css={{ height: '20px' }}>Loading...</div>}
<CheckboxList
name={name}
items={items}
Expand Down
11 changes: 11 additions & 0 deletions packages/libs/eda/src/lib/core/api/DataClient/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,17 @@ export const BinRange = type({
binLabel: string,
});

export type LabeledRange = TypeOf<typeof LabeledRange>;
export const LabeledRange = intersection([
type({
label: string,
}),
partial({
max: string,
min: string,
}),
]);

export type ContinousVariableMetadataResponse = TypeOf<
typeof ContinousVariableMetadataResponse
>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { useCollectionVariables, useStudyMetadata } from '../../..';
import {
ContinuousVariableDataShape,
LabeledRange,
useCollectionVariables,
usePromise,
useStudyMetadata,
} from '../../..';
import { VariableDescriptor } from '../../../types/variable';
import { volcanoPlotVisualization } from '../../visualizations/implementations/VolcanoPlotVisualization';
import { ComputationConfigProps, ComputationPlugin } from '../Types';
Expand All @@ -7,8 +13,11 @@ import { useConfigChangeHandler, assertComputationWithConfig } from '../Utils';
import * as t from 'io-ts';
import { Computation } from '../../../types/visualization';
import SingleSelect from '@veupathdb/coreui/lib/components/inputs/SingleSelect';
import { useFindEntityAndVariable } from '../../../hooks/workspace';
import { useMemo } from 'react';
import {
useDataClient,
useFindEntityAndVariable,
} from '../../../hooks/workspace';
import { useCallback, useMemo } from 'react';
import { ComputationStepContainer } from '../ComputationStepContainer';
import VariableTreeDropdown from '../../variableTrees/VariableTreeDropdown';
import { ValuePicker } from '../../visualizations/implementations/ValuePicker';
Expand All @@ -19,6 +28,10 @@ import { SwapHorizOutlined } from '@material-ui/icons';
import './Plugins.scss';
import { makeClassNameHelper } from '@veupathdb/wdk-client/lib/Utils/ComponentUtils';
import { Tooltip } from '@material-ui/core';
import {
GetBinRangesProps,
getBinRanges,
} from '../../../../map/analysis/utils/defaultOverlayConfig';

const cx = makeClassNameHelper('AppStepConfigurationContainer');

Expand All @@ -44,8 +57,8 @@ export type DifferentialAbundanceConfig = t.TypeOf<

const Comparator = t.intersection([
t.partial({
groupA: t.array(t.string),
groupB: t.array(t.string),
groupA: t.array(LabeledRange),
groupB: t.array(LabeledRange),
}),
t.type({
variable: VariableDescriptor,
Expand Down Expand Up @@ -111,6 +124,7 @@ function DifferentialAbundanceConfigDescriptionComponent({
collectionVariable
)
);

return (
<div className="ConfigDescriptionContainer">
<h4>
Expand Down Expand Up @@ -150,6 +164,7 @@ export function DifferentialAbundanceConfiguration(
const configuration = computation.descriptor
.configuration as DifferentialAbundanceConfig;
const studyMetadata = useStudyMetadata();
const dataClient = useDataClient();
const toggleStarredVariable = useToggleStarredVariable(props.analysisState);
const filters = analysisState.analysis?.descriptor.subset.descriptor;
const findEntityAndVariable = useFindEntityAndVariable(filters);
Expand Down Expand Up @@ -215,10 +230,56 @@ export function DifferentialAbundanceConfiguration(
}
}, [configuration, findEntityAndVariable]);

// If the variable is continuous, ask the backend for a list of bins
const continuousVariableBins = usePromise(
useCallback(async () => {
if (
!ContinuousVariableDataShape.is(
selectedComparatorVariable?.variable.dataShape
)
)
return;

const binRangeProps: GetBinRangesProps = {
studyId: studyMetadata.id,
...configuration.comparator?.variable,
filters: filters ?? [],
dataClient,
binningMethod: 'quantile',
};
const bins = await getBinRanges(binRangeProps);
return bins;
}, [
dataClient,
configuration?.comparator?.variable,
filters,
selectedComparatorVariable,
studyMetadata.id,
])
);

const disableSwapGroupValuesButton =
!configuration?.comparator?.groupA && !configuration?.comparator?.groupB;
const disableGroupValueSelectors = !configuration?.comparator?.variable;

// Create the options for groupA and groupB. Organizing into the LabeledRange[] format
// here in order to keep the later code clean.
const groupValueOptions = continuousVariableBins.value
? continuousVariableBins.value.map((bin): LabeledRange => {
return {
min: bin.binStart,
max: bin.binEnd,
label: bin.binLabel,
};
})
: selectedComparatorVariable?.variable.vocabulary?.map(
(value): LabeledRange => {
return {
label: value,
};
}
);

return (
<ComputationStepContainer
computationStepInfo={{
Expand Down Expand Up @@ -290,21 +351,32 @@ export function DifferentialAbundanceConfiguration(
<span>Group A</span>
<ValuePicker
allowedValues={
selectedComparatorVariable?.variable.vocabulary
!continuousVariableBins.pending
? groupValueOptions?.map((option) => option.label)
: undefined
}
selectedValues={configuration?.comparator?.groupA}
disabledValues={configuration?.comparator?.groupB}
onSelectedValuesChange={(newValues) =>
selectedValues={configuration?.comparator?.groupA?.map(
(entry) => entry.label
)}
disabledValues={configuration?.comparator?.groupB?.map(
(entry) => entry.label
)}
onSelectedValuesChange={(newValues) => {
changeConfigHandler('comparator', {
variable:
configuration?.comparator?.variable ?? undefined,
groupA: newValues.length ? newValues : undefined,
groupA: newValues.length
? groupValueOptions?.filter((option) =>
newValues.includes(option.label)
)
: undefined,
groupB: configuration?.comparator?.groupB ?? undefined,
})
}
});
}}
disabledCheckboxTooltipContent="Values cannot overlap between groups"
showClearSelectionButton={false}
disableInput={disableGroupValueSelectors}
isLoading={continuousVariableBins.pending}
/>
<FloatingButton
icon={SwapHorizOutlined}
Expand Down Expand Up @@ -338,21 +410,32 @@ export function DifferentialAbundanceConfiguration(
<span>Group B</span>
<ValuePicker
allowedValues={
selectedComparatorVariable?.variable.vocabulary
!continuousVariableBins.pending
? groupValueOptions?.map((option) => option.label)
: undefined
}
selectedValues={configuration?.comparator?.groupB}
disabledValues={configuration?.comparator?.groupA}
selectedValues={configuration?.comparator?.groupB?.map(
(entry) => entry.label
)}
disabledValues={configuration?.comparator?.groupA?.map(
(entry) => entry.label
)}
onSelectedValuesChange={(newValues) =>
changeConfigHandler('comparator', {
variable:
configuration?.comparator?.variable ?? undefined,
groupA: configuration?.comparator?.groupA ?? undefined,
groupB: newValues.length ? newValues : undefined,
groupB: newValues.length
? groupValueOptions?.filter((option) =>
newValues.includes(option.label)
)
: undefined,
})
}
disabledCheckboxTooltipContent="Values cannot overlap between groups"
showClearSelectionButton={false}
disableInput={disableGroupValueSelectors}
isLoading={continuousVariableBins.pending}
/>
</div>
</Tooltip>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export type ValuePickerProps = {
disabledCheckboxTooltipContent?: ReactNode;
disableInput?: boolean;
showClearSelectionButton?: boolean;
/** Show loading spinner */
isLoading?: boolean;
};

const EMPTY_ALLOWED_VALUES_ARRAY: string[] = [];
Expand All @@ -25,6 +27,7 @@ export function ValuePicker({
disabledCheckboxTooltipContent,
disableInput = false,
showClearSelectionButton = true,
isLoading = false,
}: ValuePickerProps) {
const items = allowedValues.map((value) => ({
display: <span>{value}</span>,
Expand All @@ -41,6 +44,7 @@ export function ValuePicker({
value={selectedValues}
disabledCheckboxTooltipContent={disabledCheckboxTooltipContent}
isDisabled={disableInput}
isLoading={isLoading}
/>
{showClearSelectionButton && (
<ClearSelectionButton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ function VolcanoPlotViz(props: VisualizationProps<Options>) {
// Standard volcano plots have -log10(raw p value) as the y axis
const yAxisMin = -Math.log10(dataYMax);
const yAxisMax = -Math.log10(dataYMin);

// Add a little padding to prevent clipping the glyph representing the extreme points
return {
min: Math.floor(yAxisMin - (yAxisMax - yAxisMin) * AXIS_PADDING_FACTOR),
Expand Down Expand Up @@ -382,8 +383,14 @@ function VolcanoPlotViz(props: VisualizationProps<Options>) {
computationConfiguration.comparator?.groupA &&
computationConfiguration.comparator?.groupB
? [
'Up in ' + computationConfiguration.comparator.groupA.join(', '),
'Up in ' + computationConfiguration.comparator.groupB.join(', '),
'Up in ' +
computationConfiguration.comparator.groupA
.map((entry) => entry.label)
.join(','),
'Up in ' +
computationConfiguration.comparator.groupB
.map((entry) => entry.label)
.join(','),
]
: [];

Expand Down Expand Up @@ -562,17 +569,17 @@ function VolcanoPlotViz(props: VisualizationProps<Options>) {
markerColor: significanceColors['inconclusive'],
},
{
label: `Up regulated in ${computationConfiguration.comparator.groupB?.join(
', '
)} (${countsData[significanceColors['high']]})`,
label: `Up regulated in ${computationConfiguration.comparator.groupB
?.map((entry) => entry.label)
.join(',')} (${countsData[significanceColors['high']]})`,
marker: 'circle',
hasData: true,
markerColor: significanceColors['high'],
},
{
label: `Up regulated in ${computationConfiguration.comparator.groupA?.join(
', '
)} (${countsData[significanceColors['low']]})`,
label: `Up regulated in ${computationConfiguration.comparator.groupA
?.map((entry) => entry.label)
.join(',')} (${countsData[significanceColors['low']]})`,
marker: 'circle',
hasData: true,
markerColor: significanceColors['low'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ async function getMostFrequentValues({
: [...sortedValues.slice(0, numValues), UNSELECTED_TOKEN];
}

type GetBinRangesProps = {
export type GetBinRangesProps = {
studyId: string;
variableId: string;
entityId: string;
Expand All @@ -164,7 +164,7 @@ type GetBinRangesProps = {
};

// get the equal spaced bin definitions (for now at least)
async function getBinRanges({
export async function getBinRanges({
studyId,
variableId,
entityId,
Expand Down

0 comments on commit 44c61b0

Please sign in to comment.