diff --git a/packages/libs/eda/src/lib/core/components/computations/plugins/dimensionalityReduction.tsx b/packages/libs/eda/src/lib/core/components/computations/plugins/dimensionalityReduction.tsx
new file mode 100644
index 0000000000..71ed6b531f
--- /dev/null
+++ b/packages/libs/eda/src/lib/core/components/computations/plugins/dimensionalityReduction.tsx
@@ -0,0 +1,176 @@
+import { useFindEntityAndVariableCollection } from '../../..';
+import { VariableCollectionDescriptor } from '../../../types/variable';
+import { scatterplotVisualization } from '../../visualizations/implementations/ScatterplotVisualization';
+import { ComputationConfigProps, ComputationPlugin } from '../Types';
+import {
+ useConfigChangeHandler,
+ assertComputationWithConfig,
+ isNotAbsoluteAbundanceVariableCollection,
+ partialToCompleteCodec,
+} from '../Utils';
+import * as t from 'io-ts';
+import { Computation } from '../../../types/visualization';
+import ScatterBetadivSVG from '../../visualizations/implementations/selectorIcons/ScatterBetadivSVG';
+import { ComputationStepContainer } from '../ComputationStepContainer';
+import './Plugins.scss';
+import { makeClassNameHelper } from '@veupathdb/wdk-client/lib/Utils/ComponentUtils';
+import { VariableCollectionSingleSelect } from '../../variableSelectors/VariableCollectionSingleSelect';
+import { IsEnabledInPickerParams } from '../../visualizations/VisualizationTypes';
+import { entityTreeToArray } from '../../../utils/study-metadata';
+
+const cx = makeClassNameHelper('AppStepConfigurationContainer');
+
+export type DimensionalityReductionConfig = t.TypeOf<
+ typeof DimensionalityReductionConfig
+>;
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const DimensionalityReductionConfig = t.partial({
+ collectionVariable: VariableCollectionDescriptor,
+});
+
+const CompleteDimensionalityReductionConfig = partialToCompleteCodec(
+ DimensionalityReductionConfig
+);
+
+export const plugin: ComputationPlugin = {
+ configurationComponent: DimensionalityReductionConfiguration,
+ configurationDescriptionComponent:
+ DimensionalityReductionConfigDescriptionComponent,
+ createDefaultConfiguration: () => ({}),
+ isConfigurationComplete: CompleteDimensionalityReductionConfig.is,
+ visualizationPlugins: {
+ scatterplot: scatterplotVisualization
+ .withOptions({
+ getComputedXAxisDetails(config) {
+ if (
+ DimensionalityReductionConfig.is(config) &&
+ config.collectionVariable
+ ) {
+ return {
+ entityId: config.collectionVariable.entityId,
+ placeholderDisplayName: 'PCA Axis 1',
+ variableId: 'PC1',
+ };
+ }
+ },
+ getComputedYAxisDetails(config) {
+ if (
+ DimensionalityReductionConfig.is(config) &&
+ config.collectionVariable
+ ) {
+ return {
+ entityId: config.collectionVariable.entityId,
+ placeholderDisplayName: 'PCA Axis 2',
+ variableId: 'PC2',
+ };
+ }
+ },
+ hideShowMissingnessToggle: true,
+ hideTrendlines: true,
+ hideFacetInputs: true,
+ hideLogScale: true,
+ returnPointIds: false,
+ sendComputedVariablesInRequest: true,
+ })
+ .withSelectorIcon(ScatterBetadivSVG),
+ },
+ isEnabledInPicker: isEnabledInPicker,
+ studyRequirements:
+ 'These visualizations are only available for studies with compatible assay data.',
+};
+
+function DimensionalityReductionConfigDescriptionComponent({
+ computation,
+}: {
+ computation: Computation;
+}) {
+ const findEntityAndVariableCollection = useFindEntityAndVariableCollection();
+ assertComputationWithConfig(computation, DimensionalityReductionConfig);
+ const { configuration } = computation.descriptor;
+ const collectionVariable =
+ 'collectionVariable' in configuration
+ ? configuration.collectionVariable
+ : undefined;
+ const updatedCollectionVariable =
+ findEntityAndVariableCollection(collectionVariable);
+
+ return (
+
+
+ Data:{' '}
+
+ {updatedCollectionVariable ? (
+ `${updatedCollectionVariable.entity.displayName} > ${updatedCollectionVariable.variableCollection.displayName}`
+ ) : (
+ Not selected
+ )}
+
+
+
+ );
+}
+
+export function DimensionalityReductionConfiguration(
+ props: ComputationConfigProps
+) {
+ const {
+ computationAppOverview,
+ computation,
+ analysisState,
+ visualizationId,
+ changeConfigHandlerOverride,
+ showStepNumber = true,
+ } = props;
+ assertComputationWithConfig(computation, DimensionalityReductionConfig);
+ const configuration = computation.descriptor.configuration;
+
+ const workspaceChangeConfigHandler = useConfigChangeHandler(
+ analysisState,
+ computation,
+ visualizationId
+ );
+
+ const changeConfigHandler = changeConfigHandlerOverride
+ ? changeConfigHandlerOverride
+ : workspaceChangeConfigHandler;
+
+ return (
+
+
+
+ Data
+ {
+ if (typeof value === 'string') return;
+ changeConfigHandler('collectionVariable', value);
+ }}
+ collectionPredicate={isNotAbsoluteAbundanceVariableCollection}
+ />
+
+
+
+ );
+}
+
+// Dimensionality reduction's only requirement of the study is that it contains
+// at least one collection.
+function isEnabledInPicker({
+ studyMetadata,
+}: IsEnabledInPickerParams): boolean {
+ if (!studyMetadata) return false;
+ const entities = entityTreeToArray(studyMetadata.rootEntity);
+
+ // Ensure there are collections in this study. Otherwise, disable app
+ const studyHasCollections = entities.some(
+ (entity) => !!entity.collections?.length
+ );
+
+ return studyHasCollections;
+}
diff --git a/packages/libs/eda/src/lib/core/components/computations/plugins/index.ts b/packages/libs/eda/src/lib/core/components/computations/plugins/index.ts
index bb1a091e04..9ba9bd4f22 100644
--- a/packages/libs/eda/src/lib/core/components/computations/plugins/index.ts
+++ b/packages/libs/eda/src/lib/core/components/computations/plugins/index.ts
@@ -6,18 +6,20 @@ import { plugin as distributions } from './distributions';
import { plugin as countsandproportions } from './countsAndProportions';
import { plugin as abundance } from './abundance';
import { plugin as differentialabundance } from './differentialabundance';
-// import { plugin as differentialexpression } from './differentialExpression';
+import { plugin as differentialexpression } from './differentialExpression';
import { plugin as correlationassaymetadata } from './correlationAssayMetadata'; // mbio
import { plugin as correlationassayassay } from './correlationAssayAssay'; // mbio
import { plugin as correlation } from './correlation'; // genomics (- vb)
import { plugin as selfcorrelation } from './selfCorrelation';
import { plugin as xyrelationships } from './xyRelationships';
+import { plugin as dimensionalityreduction } from './dimensionalityReduction';
export const plugins: Record = {
abundance,
alphadiv,
betadiv,
+ dimensionalityreduction,
differentialabundance,
- // differentialexpression,
+ differentialexpression,
correlationassaymetadata,
correlationassayassay,
correlation,
diff --git a/packages/libs/eda/src/lib/core/components/visualizations/implementations/ScatterplotVisualization.tsx b/packages/libs/eda/src/lib/core/components/visualizations/implementations/ScatterplotVisualization.tsx
index 668981259d..8ed978d7aa 100755
--- a/packages/libs/eda/src/lib/core/components/visualizations/implementations/ScatterplotVisualization.tsx
+++ b/packages/libs/eda/src/lib/core/components/visualizations/implementations/ScatterplotVisualization.tsx
@@ -286,6 +286,7 @@ interface Options
hideTrendlines?: boolean;
hideLogScale?: boolean;
returnPointIds?: boolean; // Determines whether the backend should return the ids of each point in the scatterplot
+ sendComputedVariablesInRequest?: boolean; // Determines whether computed variable descriptors should be sent to the backend in the data request.
}
function ScatterplotViz(props: VisualizationProps) {
@@ -332,6 +333,7 @@ function ScatterplotViz(props: VisualizationProps) {
computedYAxisDetails,
computedOverlayVariableDescriptor,
providedOverlayVariableDescriptor,
+ sendComputedVariablesInRequest,
] = useMemo(
() => [
options?.getComputedXAxisDetails?.(computation.descriptor.configuration),
@@ -340,6 +342,7 @@ function ScatterplotViz(props: VisualizationProps) {
computation.descriptor.configuration
),
options?.getOverlayVariable?.(computation.descriptor.configuration),
+ options?.sendComputedVariablesInRequest ?? false,
],
[computation.descriptor.configuration, options]
);
@@ -745,8 +748,14 @@ function ScatterplotViz(props: VisualizationProps) {
config: {
outputEntityId: outputEntity.id,
valueSpec: hideTrendlines ? undefined : valueSpecValue,
- xAxisVariable: vizConfig.xAxisVariable,
- yAxisVariable: vizConfig.yAxisVariable,
+ xAxisVariable:
+ sendComputedVariablesInRequest && computedXAxisDescriptor
+ ? computedXAxisDescriptor
+ : vizConfig.xAxisVariable,
+ yAxisVariable:
+ sendComputedVariablesInRequest && computedYAxisDescriptor
+ ? computedYAxisDescriptor
+ : vizConfig.yAxisVariable,
overlayVariable: vizConfig.overlayVariable,
facetVariable: vizConfig.facetVariable
? [vizConfig.facetVariable]
diff --git a/packages/libs/eda/src/lib/notebook/ComputeNotebookCell.tsx b/packages/libs/eda/src/lib/notebook/ComputeNotebookCell.tsx
index 059aabd431..dc93b01ada 100644
--- a/packages/libs/eda/src/lib/notebook/ComputeNotebookCell.tsx
+++ b/packages/libs/eda/src/lib/notebook/ComputeNotebookCell.tsx
@@ -15,6 +15,7 @@ export function ComputeNotebookCell(
const { analysisState, cell, isDisabled, wdkState, projectId } = props;
const { analysis } = analysisState;
if (analysis == null) throw new Error('Cannot find analysis.');
+ console.log('compute name', cell.computationName);
const {
computationName,
@@ -48,6 +49,7 @@ export function ComputeNotebookCell(
// Ideally it should also use the functional update form of setComputations.
//
// We'll use a special, simple changeConfigHandler for the computation configuration
+ console.log('analysis', analysis.descriptor.computations);
const changeConfigHandler = (propertyName: string, value?: any) => {
if (!computation || !analysis.descriptor.computations[0]) return;
@@ -61,7 +63,9 @@ export function ComputeNotebookCell(
const existingComputation =
analysisState.analysis?.descriptor.computations.find(
- (comp) => isEqual(comp.descriptor.configuration, updatedConfiguration) //&&
+ (comp) =>
+ isEqual(comp.computationId, computationId) &&
+ isEqual(comp.descriptor.configuration, updatedConfiguration)
);
if (existingComputation) return;
@@ -74,7 +78,13 @@ export function ComputeNotebookCell(
},
};
- analysisState.setComputations([updatedComputation]);
+ analysisState.setComputations((computations) => {
+ return computations.map((comp) =>
+ comp.computationId === computation.computationId
+ ? updatedComputation
+ : comp
+ );
+ });
};
const { jobStatus, createJob } = useComputeJobStatus(
diff --git a/packages/libs/eda/src/lib/notebook/EdaNotebookAnalysis.tsx b/packages/libs/eda/src/lib/notebook/EdaNotebookAnalysis.tsx
index a1292bd74b..8b94fac591 100644
--- a/packages/libs/eda/src/lib/notebook/EdaNotebookAnalysis.tsx
+++ b/packages/libs/eda/src/lib/notebook/EdaNotebookAnalysis.tsx
@@ -118,8 +118,11 @@ export function EdaNotebookAnalysis(props: Props) {
// This ensures that updates are queued and applied in order, even across
// multiple recursive calls.
notebookPreset.cells.forEach((cell) => processCell(cell));
+ console.log('createdcells');
}, [analysis, setComputations, addVisualization, notebookPreset]);
+ console.log('notebookpreset', notebookPreset);
+
//
// Now we render the notebook directly from the read-only `notebookPreset`,
// fetching computations and visualizations from analysisState.analysis where needed.
diff --git a/packages/libs/eda/src/lib/notebook/NotebookPresets.tsx b/packages/libs/eda/src/lib/notebook/NotebookPresets.tsx
index 5752e5dfca..5aed8168c1 100644
--- a/packages/libs/eda/src/lib/notebook/NotebookPresets.tsx
+++ b/packages/libs/eda/src/lib/notebook/NotebookPresets.tsx
@@ -97,6 +97,29 @@ export const presetNotebooks: Record = {
/>
),
},
+ {
+ type: 'compute',
+ title: 'Configure PCA',
+ computationName: 'dimensionalityreduction',
+ computationId: 'pca_1',
+ helperText: (
+
+ ),
+ cells: [
+ {
+ type: 'visualization',
+ title: 'PCA Plot',
+ visualizationName: 'scatterplot',
+ visualizationId: 'pca_1',
+ },
+ ],
+ },
{
type: 'compute',
title: 'Setup DESeq2 Computation',
@@ -104,9 +127,9 @@ export const presetNotebooks: Record = {
computationId: 'de_1',
helperText: (
@@ -154,7 +177,7 @@ export const presetNotebooks: Record = {
},
helperText: (
= {
title: 'Review and run search',
helperText: (
= {
if (!analysisState.analysis?.descriptor?.computations?.length) {
return No analysis configuration available
;
}
- const computation =
- analysisState.analysis.descriptor.computations[0];
- if (!computation?.visualizations?.length) {
+ const differentialExpressionComputation =
+ analysisState.analysis.descriptor.computations[1];
+ if (!differentialExpressionComputation?.visualizations?.length) {
return No visualization configuration available
;
}
const volcanoPlotConfig =
- analysisState.analysis?.descriptor.computations[0]
- .visualizations[0].descriptor.configuration;
+ differentialExpressionComputation.visualizations[0].descriptor
+ .configuration;
if (
!volcanoPlotConfig ||
@@ -239,7 +262,7 @@ export const presetNotebooks: Record = {
}}
>
To make adjustments, update the volcano plot settings in
- step 2.
+ step 3.
);
diff --git a/packages/libs/eda/src/lib/notebook/TextNotebookCell.tsx b/packages/libs/eda/src/lib/notebook/TextNotebookCell.tsx
index 1413484c70..123c0343f2 100644
--- a/packages/libs/eda/src/lib/notebook/TextNotebookCell.tsx
+++ b/packages/libs/eda/src/lib/notebook/TextNotebookCell.tsx
@@ -1,12 +1,18 @@
import ExpandablePanel from '@veupathdb/coreui/lib/components/containers/ExpandablePanel';
import { NotebookCellProps } from './NotebookCell';
import { TextCellDescriptor } from './NotebookPresets';
+import { useMemo } from 'react';
export function TextNotebookCell(props: NotebookCellProps) {
const { cell, isDisabled, analysisState } = props;
const { text, title, getDynamicContent } = cell;
+ const dynamicContent = useMemo(
+ () => getDynamicContent?.(analysisState),
+ [getDynamicContent, analysisState]
+ );
+
return (
<>
{cell.helperText && (
@@ -24,7 +30,7 @@ export function TextNotebookCell(props: NotebookCellProps) {
className={'NotebookCellContent' + (isDisabled ? ' disabled' : '')}
>
{text}
- {getDynamicContent && getDynamicContent(analysisState)}
+ {dynamicContent}
>