From fa8588991cdcb938e13e18ba1a9910453152e7ef Mon Sep 17 00:00:00 2001
From: asizemore
Date: Tue, 14 May 2024 07:01:51 -0400
Subject: [PATCH 1/8] add self-correlations plugin
---
.../components/computations/plugins/index.ts | 2 +
.../computations/plugins/selfCorrelation.tsx | 340 ++++++++++++++++++
packages/libs/eda/src/lib/core/types/apps.ts | 12 +
3 files changed, 354 insertions(+)
create mode 100644 packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
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 c03060b752..58053638d3 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
@@ -9,6 +9,7 @@ import { plugin as differentialabundance } from './differentialabundance';
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';
export const plugins: Record = {
abundance,
@@ -18,6 +19,7 @@ export const plugins: Record = {
correlationassaymetadata,
correlationassayassay,
correlation,
+ selfcorrelation,
countsandproportions,
distributions,
pass,
diff --git a/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx b/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
new file mode 100644
index 0000000000..99f8f98819
--- /dev/null
+++ b/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
@@ -0,0 +1,340 @@
+import { useMemo } from 'react';
+import { VariableTreeNode, useFindEntityAndVariableCollection } from '../../..';
+import { ComputationConfigProps, ComputationPlugin } from '../Types';
+import { partial } from 'lodash';
+import {
+ useConfigChangeHandler,
+ assertComputationWithConfig,
+ isNotAbsoluteAbundanceVariableCollection,
+} from '../Utils';
+import { Computation } from '../../../types/visualization';
+import { ComputationStepContainer } from '../ComputationStepContainer';
+import './Plugins.scss';
+import { makeClassNameHelper } from '@veupathdb/wdk-client/lib/Utils/ComponentUtils';
+import { H6 } from '@veupathdb/coreui';
+import { bipartiteNetworkVisualization } from '../../visualizations/implementations/BipartiteNetworkVisualization';
+import { VariableCollectionSelectList } from '../../variableSelectors/VariableCollectionSingleSelect';
+import SingleSelect, {
+ ItemGroup,
+} from '@veupathdb/coreui/lib/components/inputs/SingleSelect';
+import {
+ entityTreeToArray,
+ findEntityAndVariableCollection,
+ isVariableCollectionDescriptor,
+} from '../../../utils/study-metadata';
+import { IsEnabledInPickerParams } from '../../visualizations/VisualizationTypes';
+import { ancestorEntitiesForEntityId } from '../../../utils/data-element-constraints';
+import { NumberInput } from '@veupathdb/components/lib/components/widgets/NumberAndDateInputs';
+import ExpandablePanel from '@veupathdb/coreui/lib/components/containers/ExpandablePanel';
+import { variableCollectionsAreUnique } from '../../../utils/visualization';
+import PluginError from '../../visualizations/PluginError';
+import {
+ CompleteSelfCorrelationConfig,
+ SelfCorrelationConfig,
+} from '../../../types/apps';
+
+const cx = makeClassNameHelper('AppStepConfigurationContainer');
+
+/**
+ * Self-Correlation
+ *
+ * The Correlation app takes all collections and visualizes the correlation between a collection and itself.
+ * For example, if the collection is a set of genes, the app will show the correlation between every pair of genes in the collection.
+ *
+ * As of 05/14/24, this app will only be available for mbio assay data.
+ */
+
+export const plugin: ComputationPlugin = {
+ configurationComponent: SelfCorrelationConfiguration,
+ configurationDescriptionComponent: SelfCorrelationConfigDescriptionComponent,
+ createDefaultConfiguration: () => ({
+ prefilterThresholds: {
+ proportionNonZero: DEFAULT_PROPORTION_NON_ZERO_THRESHOLD,
+ variance: DEFAULT_VARIANCE_THRESHOLD,
+ standardDeviation: DEFAULT_STANDARD_DEVIATION_THRESHOLD,
+ },
+ }),
+ isConfigurationComplete: (configuration) => {
+ // First, the configuration must be complete
+ // ANN CLEAN
+ if (!CompleteSelfCorrelationConfig.is(configuration)) return false;
+ return true;
+ },
+ visualizationPlugins: {
+ bipartitenetwork: bipartiteNetworkVisualization.withOptions({
+ getLegendTitle(config) {
+ if (SelfCorrelationConfig.is(config)) {
+ return ['absolute correlation coefficient', 'correlation direction'];
+ } else {
+ return [];
+ }
+ },
+ // makeGetNodeMenuActions(studyMetadata) {
+ // const entities = entityTreeToArray(studyMetadata.rootEntity);
+ // const variables = entities.flatMap((e) => e.variables);
+ // const collections = entities.flatMap(
+ // (entity) => entity.collections ?? []
+ // );
+ // const hostCollection = collections.find(
+ // (c) => c.id === 'EUPATH_0005050'
+ // );
+ // const parasiteCollection = collections.find(
+ // (c) => c.id === 'EUPATH_0005051'
+ // );
+ // return function getNodeActions(nodeId: string) {
+ // const [, variableId] = nodeId.split('.');
+ // const variable = variables.find((v) => v.id === variableId);
+ // if (variable == null) return [];
+
+ // // E.g., "qa."
+ // const urlPrefix = window.location.host.replace(
+ // /(plasmodb|hostdb)\.org/,
+ // ''
+ // );
+
+ // const href = parasiteCollection?.memberVariableIds.includes(
+ // variable.id
+ // )
+ // ? `//${urlPrefix}plasmodb.org/plasmo/app/search/transcript/GenesByRNASeqpfal3D7_Lee_Gambian_ebi_rnaSeq_RSRCWGCNAModules?param.wgcnaParam=${variable.displayName.toLowerCase()}&autoRun=1`
+ // : hostCollection?.memberVariableIds.includes(variable.id)
+ // ? `//${urlPrefix}hostdb.org/hostdb/app/search/transcript/GenesByRNASeqhsapREF_Lee_Gambian_ebi_rnaSeq_RSRCWGCNAModules?param.wgcnaParam=${variable.displayName.toLowerCase()}&autoRun=1`
+ // : undefined;
+ // if (href == null) return [];
+ // return [
+ // {
+ // label: 'See list of genes',
+ // href,
+ // },
+ // ];
+ // };
+ // },
+ // getParitionNames(studyMetadata, config) {
+ // if (CorrelationConfig.is(config)) {
+ // const entities = entityTreeToArray(studyMetadata.rootEntity);
+ // const partition1Name = findEntityAndVariableCollection(
+ // entities,
+ // config.data1?.collectionSpec
+ // )?.variableCollection.displayName;
+ // const partition2Name =
+ // config.data2?.dataType === 'collection'
+ // ? findEntityAndVariableCollection(
+ // entities,
+ // config.data2?.collectionSpec
+ // )?.variableCollection.displayName
+ // : 'Continuous metadata variables';
+ // return { partition1Name, partition2Name };
+ // }
+ // },
+ }), // Must match name in data service and in visualization.tsx
+ },
+ isEnabledInPicker: isEnabledInPicker,
+ studyRequirements:
+ 'These visualizations are only available for studies with compatible metadata.',
+};
+
+// Renders on the thumbnail page to give a summary of the app instance
+function SelfCorrelationConfigDescriptionComponent({
+ computation,
+}: {
+ computation: Computation;
+}) {
+ const findEntityAndVariableCollection = useFindEntityAndVariableCollection();
+ assertComputationWithConfig(computation, SelfCorrelationConfig);
+
+ const { data1, correlationMethod } = computation.descriptor.configuration;
+
+ const entityAndCollectionVariableTreeNode =
+ findEntityAndVariableCollection(data1);
+
+ const correlationMethodDisplayName = correlationMethod
+ ? CORRELATION_METHODS.find((method) => method.value === correlationMethod)
+ ?.displayName
+ : undefined;
+
+ return (
+
+
+ Data 1:{' '}
+
+ {entityAndCollectionVariableTreeNode ? (
+ `${entityAndCollectionVariableTreeNode.entity.displayName} > ${entityAndCollectionVariableTreeNode.variableCollection.displayName}`
+ ) : (
+ Not selected
+ )}
+
+
+ {/* The method should be disabled unti lthe data is chosen */}
+
+ Method:{' '}
+
+ {correlationMethod ? (
+ correlationMethodDisplayName
+ ) : (
+ Not selected
+ )}
+
+
+
+ );
+}
+
+const CORRELATION_METHODS = [
+ { value: 'spearman', displayName: 'Spearman' },
+ { value: 'pearson', displayName: 'Pearson' },
+ { value: 'sparcc', displayName: 'SparCC' },
+];
+const DEFAULT_PROPORTION_NON_ZERO_THRESHOLD = 0.05;
+const DEFAULT_VARIANCE_THRESHOLD = 0;
+const DEFAULT_STANDARD_DEVIATION_THRESHOLD = 0;
+
+// Shows as Step 1 in the full screen visualization page
+export function SelfCorrelationConfiguration(props: ComputationConfigProps) {
+ const {
+ computationAppOverview,
+ computation,
+ analysisState,
+ visualizationId,
+ } = props;
+
+ const configuration = computation.descriptor
+ .configuration as SelfCorrelationConfig;
+
+ assertComputationWithConfig(computation, SelfCorrelationConfig);
+
+ const changeConfigHandler = useConfigChangeHandler(
+ analysisState,
+ computation,
+ visualizationId
+ );
+
+ // Content for the expandable help section
+ // Note the text is dependent on the context, for example in genomics we'll use different
+ // language than in mbio.
+ const helpContent = (
+
+
What is correlation?
+
+ The correlation between two variables (genes, sample metadata, etc.)
+ describes the degree to which their presence in samples co-fluctuate.
+ For example, the Age and Shoe Size of children are correlated since as a
+ child ages, their feet grow.
+
+ {/* ANN FILL IN */}
+
+ );
+
+ const correlationMethodSelectorText = useMemo(() => {
+ if (configuration.correlationMethod) {
+ return (
+ CORRELATION_METHODS.find(
+ (method) => method.value === configuration.correlationMethod
+ )?.displayName ?? 'Select a method'
+ );
+ } else {
+ return 'Select a method';
+ }
+ }, [configuration.correlationMethod]);
+
+ return (
+
+
+
+
+
Input Data
+
+ Data 1
+
+
+
+
+
Correlation Method
+
+ Method
+ ({
+ value: method.value,
+ display: method.displayName,
+ }))}
+ onSelect={partial(changeConfigHandler, 'correlationMethod')}
+ />
+
+
+
+
Prefilter Data
+
+ Prevalence:
+
+ Keep if abundance is non-zero in at least{' '}
+
+ {
+ changeConfigHandler('prefilterThresholds', {
+ proportionNonZero:
+ // save as decimal point, not %
+ newValue != null
+ ? Number((newValue as number) / 100)
+ : DEFAULT_PROPORTION_NON_ZERO_THRESHOLD,
+ variance:
+ configuration.prefilterThresholds?.variance ??
+ DEFAULT_VARIANCE_THRESHOLD,
+ standardDeviation:
+ configuration.prefilterThresholds?.standardDeviation ??
+ DEFAULT_STANDARD_DEVIATION_THRESHOLD,
+ });
+ }}
+ containerStyles={{ width: '5.5em' }}
+ />
+ % of samples
+
+
+
+
+ {/* PluginError here if the method doesn't agree with the data */}
+
+
+
+
+ );
+}
+
+// The self-correlation app is only available for studies that have 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/types/apps.ts b/packages/libs/eda/src/lib/core/types/apps.ts
index b25b44c5c1..b950e89e88 100644
--- a/packages/libs/eda/src/lib/core/types/apps.ts
+++ b/packages/libs/eda/src/lib/core/types/apps.ts
@@ -25,3 +25,15 @@ export const CorrelationConfig = t.partial({
export const CompleteCorrelationConfig =
partialToCompleteCodec(CorrelationConfig);
+
+export type SelfCorrelationConfig = t.TypeOf;
+
+export const SelfCorrelationConfig = t.partial({
+ data1: VariableCollectionDescriptor,
+ correlationMethod: t.string,
+ prefilterThresholds: FeaturePrefilterThresholds,
+});
+
+export const CompleteSelfCorrelationConfig = partialToCompleteCodec(
+ SelfCorrelationConfig
+);
From 8e0e953542c2d4311de105f27f44b4d5fc97d812 Mon Sep 17 00:00:00 2001
From: asizemore
Date: Tue, 14 May 2024 08:10:51 -0400
Subject: [PATCH 2/8] succesful network response
---
.../eda/src/lib/core/api/DataClient/types.ts | 58 ++-
.../computations/plugins/selfCorrelation.tsx | 4 +-
.../implementations/NetworkVisualization.tsx | 468 ++++++++++++++++++
3 files changed, 526 insertions(+), 4 deletions(-)
create mode 100755 packages/libs/eda/src/lib/core/components/visualizations/implementations/NetworkVisualization.tsx
diff --git a/packages/libs/eda/src/lib/core/api/DataClient/types.ts b/packages/libs/eda/src/lib/core/api/DataClient/types.ts
index 4547887010..4afb613dc4 100755
--- a/packages/libs/eda/src/lib/core/api/DataClient/types.ts
+++ b/packages/libs/eda/src/lib/core/api/DataClient/types.ts
@@ -15,6 +15,7 @@ import {
keyof,
boolean,
literal,
+ any,
} from 'io-ts';
import { Filter } from '../../types/filter';
import {
@@ -394,10 +395,48 @@ export const NodeIdList = type({
// Bipartite network
export type BipartiteNetworkResponse = TypeOf;
-const NodeData = type({
- id: string,
+const NodeData = intersection([
+ type({
+ id: string,
+ }),
+ partial({
+ x: number,
+ y: number,
+ degree: number,
+ }),
+]);
+
+export const NetworkData = type({
+ nodes: array(NodeData),
+ links: array(
+ intersection([
+ type({
+ source: string,
+ target: string,
+ weight: number,
+ }),
+ partial({
+ color: number,
+ isDirected: boolean,
+ }),
+ ])
+ ),
});
+// @ANN clean your types!
+const NetworkConfig = partial({
+ variables: any,
+ correlationCoefThreshold: number,
+ significanceThreshold: number,
+});
+export const NetworkResponse = type({
+ network: type({
+ data: NetworkData,
+ config: NetworkConfig,
+ }),
+});
+
+// @ANN clean your types!
export const BipartiteNetworkData = type({
partitions: array(NodeIdList),
nodes: array(NodeData),
@@ -447,6 +486,21 @@ export interface BipartiteNetworkRequestParams {
significanceThreshold?: number;
};
}
+// @ANN can also maybe clean types here
+// Correlation Network
+// a specific flavor of the network that also includes correlationCoefThreshold and significanceThreshold
+export type CorrelationNetworkResponse = TypeOf<
+ typeof CorrelationNetworkResponse
+>;
+export const CorrelationNetworkResponse = NetworkResponse;
+export interface NetworkRequestParams {
+ studyId: string;
+ filters: Filter[];
+ config: {
+ correlationCoefThreshold?: number;
+ significanceThreshold?: number;
+ };
+}
export type FeaturePrefilterThresholds = TypeOf<
typeof FeaturePrefilterThresholds
diff --git a/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx b/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
index 99f8f98819..c2550236f1 100644
--- a/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
+++ b/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
@@ -12,7 +12,7 @@ import { ComputationStepContainer } from '../ComputationStepContainer';
import './Plugins.scss';
import { makeClassNameHelper } from '@veupathdb/wdk-client/lib/Utils/ComponentUtils';
import { H6 } from '@veupathdb/coreui';
-import { bipartiteNetworkVisualization } from '../../visualizations/implementations/BipartiteNetworkVisualization';
+import { networkVisualization } from '../../visualizations/implementations/NetworkVisualization';
import { VariableCollectionSelectList } from '../../variableSelectors/VariableCollectionSingleSelect';
import SingleSelect, {
ItemGroup,
@@ -61,7 +61,7 @@ export const plugin: ComputationPlugin = {
return true;
},
visualizationPlugins: {
- bipartitenetwork: bipartiteNetworkVisualization.withOptions({
+ unipartitenetwork: networkVisualization.withOptions({
getLegendTitle(config) {
if (SelfCorrelationConfig.is(config)) {
return ['absolute correlation coefficient', 'correlation direction'];
diff --git a/packages/libs/eda/src/lib/core/components/visualizations/implementations/NetworkVisualization.tsx b/packages/libs/eda/src/lib/core/components/visualizations/implementations/NetworkVisualization.tsx
new file mode 100755
index 0000000000..63ca3d3b6d
--- /dev/null
+++ b/packages/libs/eda/src/lib/core/components/visualizations/implementations/NetworkVisualization.tsx
@@ -0,0 +1,468 @@
+import * as t from 'io-ts';
+import { useUpdateThumbnailEffect } from '../../../hooks/thumbnails';
+import { VisualizationProps } from '../VisualizationTypes';
+import { createVisualizationPlugin } from '../VisualizationPlugin';
+import {
+ LayoutOptions,
+ TitleOptions,
+ LegendOptions,
+} from '../../layouts/types';
+import { RequestOptions } from '../options/types';
+
+// Network imports
+import NetworkPlot, {
+ NetworkPlotProps,
+} from '@veupathdb/components/lib/plots/NetworkPlot';
+import BipartiteNetworkSVG from './selectorIcons/BipartiteNetworkSVG';
+import {
+ CorrelationNetworkResponse,
+ NetworkRequestParams,
+ NetworkResponse,
+} from '../../../api/DataClient/types';
+import { twoColorPalette } from '@veupathdb/components/lib/types/plots/addOns';
+import { useCallback, useMemo } from 'react';
+import { scaleOrdinal } from 'd3-scale';
+import { uniq } from 'lodash';
+import { usePromise } from '../../../hooks/promise';
+import {
+ useDataClient,
+ useStudyEntities,
+ useStudyMetadata,
+} from '../../../hooks/workspace';
+import { fixVarIdLabel } from '../../../utils/visualization';
+import DataClient from '../../../api/DataClient';
+import { OutputEntityTitle } from '../OutputEntityTitle';
+import { scaleLinear } from 'd3';
+import PlotLegend from '@veupathdb/components/lib/components/plotControls/PlotLegend';
+import { LegendItemsProps } from '@veupathdb/components/lib/components/plotControls/PlotListLegend';
+import { gray } from '@veupathdb/coreui/lib/definitions/colors';
+import '../Visualizations.scss';
+import LabelledGroup from '@veupathdb/components/lib/components/widgets/LabelledGroup';
+import { NumberInput } from '@veupathdb/components/lib/components/widgets/NumberAndDateInputs';
+import { NumberOrDate } from '@veupathdb/components/lib/types/general';
+import { useVizConfig } from '../../../hooks/visualizations';
+import { FacetedPlotLayout } from '../../layouts/FacetedPlotLayout';
+import { H6 } from '@veupathdb/coreui';
+import { CorrelationConfig } from '../../../types/apps';
+import { StudyMetadata } from '../../..';
+import { NodeMenuAction } from '@veupathdb/components/lib/types/plots/network';
+import { LabelPosition } from '@veupathdb/components/lib/plots/Node';
+// end imports
+
+// Defaults
+const DEFAULT_CORRELATION_COEF_THRESHOLD = 0.5; // Ability for user to change this value not yet implemented.
+const DEFAULT_SIGNIFICANCE_THRESHOLD = 0.05; // Ability for user to change this value not yet implemented.
+const DEFAULT_LINK_COLOR_DATA = '0';
+const MIN_STROKE_WIDTH = 0.5; // Minimum stroke width for links in the network. Will represent the smallest link weight.
+const MAX_STROKE_WIDTH = 6; // Maximum stroke width for links in the network. Will represent the largest link weight.
+const DEFAULT_NUMBER_OF_LINE_LEGEND_ITEMS = 4;
+
+const plotContainerStyles = {
+ width: 800,
+ height: 600,
+ marginLeft: '0.75rem',
+ border: '1px solid #dedede',
+ boxShadow: '1px 1px 4px #00000066',
+};
+
+export const networkVisualization = createVisualizationPlugin({
+ selectorIcon: BipartiteNetworkSVG,
+ fullscreenComponent: NetworkViz,
+ createDefaultConfig: createDefaultConfig,
+});
+
+function createDefaultConfig(): NetworkConfig {
+ return {
+ correlationCoefThreshold: DEFAULT_CORRELATION_COEF_THRESHOLD,
+ significanceThreshold: DEFAULT_SIGNIFICANCE_THRESHOLD,
+ };
+}
+
+export type NetworkConfig = t.TypeOf;
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const NetworkConfig = t.partial({
+ correlationCoefThreshold: t.number,
+ significanceThreshold: t.number,
+});
+
+interface Options
+ extends LayoutOptions,
+ TitleOptions,
+ LegendOptions,
+ RequestOptions {}
+
+// Bipartite Network Visualization
+// The bipartite network takes no input variables, because the received data will complete the plot.
+// Eventually the user will be able to control the significance and correlation coefficient threshold values.
+function NetworkViz(props: VisualizationProps) {
+ const {
+ options,
+ computation,
+ visualization,
+ updateConfiguration,
+ updateThumbnail,
+ computeJobStatus,
+ filteredCounts,
+ filters,
+ hideInputsAndControls,
+ plotContainerStyleOverrides,
+ } = props;
+
+ const studyMetadata = useStudyMetadata();
+ const { id: studyId } = studyMetadata;
+ const entities = useStudyEntities(filters);
+ const dataClient: DataClient = useDataClient();
+
+ const computationConfiguration: CorrelationConfig = computation.descriptor
+ .configuration as CorrelationConfig;
+
+ const [vizConfig, updateVizConfig] = useVizConfig(
+ visualization.descriptor.configuration,
+ NetworkConfig,
+ createDefaultConfig,
+ updateConfiguration
+ );
+
+ // Get data from the compute job
+ const data = usePromise(
+ useCallback(async (): Promise => {
+ // Only need to check compute job status and filter status, since there are no
+ // viz input variables.
+ if (computeJobStatus !== 'complete') return undefined;
+ if (filteredCounts.pending || filteredCounts.value == null)
+ return undefined;
+
+ const params = {
+ studyId,
+ filters,
+ config: {
+ correlationCoefThreshold: vizConfig.correlationCoefThreshold,
+ significanceThreshold: vizConfig.significanceThreshold,
+ },
+ computeConfig: computationConfiguration,
+ };
+
+ const response = await dataClient.getVisualizationData(
+ computation.descriptor.type,
+ visualization.descriptor.type,
+ params,
+ CorrelationNetworkResponse
+ );
+
+ return response;
+ }, [
+ computeJobStatus,
+ filteredCounts.pending,
+ filteredCounts.value,
+ filters,
+ studyId,
+ computationConfiguration,
+ computation.descriptor.type,
+ dataClient,
+ visualization.descriptor.type,
+ vizConfig.correlationCoefThreshold,
+ vizConfig.significanceThreshold,
+ ])
+ );
+
+ // Determine min and max stroke widths. For use in scaling the strokes (weightToStrokeWidthMap) and the legend.
+ const dataWeights =
+ data.value?.network.data.links.map(
+ (link) => Number(link.weight) // link.weight will always be a number if defined, because it represents the continuous data associated with that link.
+ ) ?? [];
+ // Use Set to dedupe the array of dataWeights
+ const uniqueDataWeights = Array.from(new Set(dataWeights));
+ const minDataWeight = Math.min(...uniqueDataWeights);
+ const maxDataWeight = Math.max(...uniqueDataWeights);
+
+ // Determine min and max x and y positions for nodes. For use in scaling the nodes.
+ const dataXPositions =
+ data.value?.network.data.nodes.map((node) => Number(node.x)) ?? [];
+ const dataYPositions =
+ data.value?.network.data.nodes.map((node) => Number(node.y)) ?? [];
+ const minXPosition = Math.min(...dataXPositions);
+ const maxXPosition = Math.max(...dataXPositions);
+ const minYPosition = Math.min(...dataYPositions);
+ const maxYPosition = Math.max(...dataYPositions);
+
+ // Clean and finalize data format. Specifically, assign link colors, add display labels
+ const cleanedData = useMemo(() => {
+ if (!data.value) return undefined;
+
+ const scaleX = scaleLinear()
+ .domain([minXPosition, maxXPosition])
+ .range([80, 500]);
+ const scaleY = scaleLinear()
+ .domain([minYPosition, maxYPosition])
+ .range([80, 500]);
+
+ // Create map that will adjust each link's weight to find a stroke width that spans an appropriate range for this viz.
+ const weightToStrokeWidthMap = scaleLinear()
+ .domain([minDataWeight, maxDataWeight])
+ .range([MIN_STROKE_WIDTH, MAX_STROKE_WIDTH]);
+
+ // Assign color to links.
+ // Color palettes live here in the frontend, but the backend decides how to color links (ex. by sign of correlation, or avg degree of parent nodes).
+ // So we'll make assigning colors generalizable by mapping the values of the links.color prop to the palette. As we add
+ // different ways to color links in the future, we can adapt our checks and error messaging.
+ const uniqueLinkColors = uniq(
+ data.value?.network.data.links.map(
+ (link) => link.color?.toString() ?? DEFAULT_LINK_COLOR_DATA
+ )
+ );
+ if (uniqueLinkColors.length > twoColorPalette.length) {
+ throw new Error(
+ `Found ${uniqueLinkColors.length} link colors but expected only ${twoColorPalette.length}.`
+ );
+ }
+ // The link color sent from the backend should be either '-1' or '1', but we'll allow any two unique values. Assigning the domain
+ // in the following way preserves "1" getting mapped to the second color in the palette, even if it's the only
+ // unique value in uniqueLinkColors.
+ const linkColorScaleDomain = uniqueLinkColors.every((val) =>
+ ['-1', '1'].includes(val)
+ )
+ ? ['-1', '1']
+ : uniqueLinkColors;
+ const linkColorScale = scaleOrdinal()
+ .domain(linkColorScaleDomain)
+ .range(twoColorPalette); // the output palette may change if this visualization is reused in other contexts (ex. not a correlation app).
+
+ // Find display labels
+ const nodesWithLabels = data.value.network.data.nodes.map((node) => {
+ // node.id is the entityId.variableId
+ const displayLabel = fixVarIdLabel(
+ node.id.split('.')[1],
+ node.id.split('.')[0],
+ entities
+ );
+
+ return {
+ ...node,
+ x: scaleX(Number(node.x)),
+ y: scaleY(Number(node.y)),
+ id: node.id,
+ label: displayLabel,
+ labelPosition:
+ scaleX(Number(node.x)) > 200 ? 'right' : ('left' as LabelPosition),
+ };
+ });
+
+ return {
+ ...data.value.network.data,
+ nodes: nodesWithLabels,
+ links: data.value.network.data.links.map((link) => {
+ return {
+ source: { id: link.source },
+ target: { id: link.target },
+ strokeWidth: weightToStrokeWidthMap(Number(link.weight)),
+ color: link.color ? linkColorScale(link.color.toString()) : '#000000',
+ };
+ }),
+ };
+ }, [data.value, entities, minDataWeight, maxDataWeight]);
+
+ // plot subtitle
+ const plotSubtitle = (
+
+
+ {`Showing links with an absolute correlation coefficient above ${vizConfig.correlationCoefThreshold?.toString()} and a p-value below ${vizConfig.significanceThreshold?.toString()}`}
+
+
Click on a node to highlight its edges.
+
+ );
+
+ const finalPlotContainerStyles = useMemo(
+ () => ({
+ ...plotContainerStyles,
+ ...plotContainerStyleOverrides,
+ }),
+ [plotContainerStyleOverrides]
+ );
+
+ const plotRef = useUpdateThumbnailEffect(
+ updateThumbnail,
+ {
+ ...finalPlotContainerStyles,
+ height: 400, // no reason for the thumbnail to be as tall as the network (which could be very, very tall!)
+ },
+ [cleanedData]
+ );
+
+ // Have the bpnet component say "No nodes" or whatev and have an extra
+ // prop called errorMessage or something that displays when there are no nodes.
+ // that error message can say "your thresholds of blah and blah are too high, change them"
+ const emptyNetworkContent = (
+
+
No correlation results pass the configured thresholds.
+
+
+ Adjust the correlation coefficient and p-value thresholds to continue.
+
+ );
+
+ console.log('cleanedData', cleanedData);
+ const networkPlotProps: NetworkPlotProps = {
+ nodes: cleanedData ? cleanedData.nodes : undefined,
+ links: cleanedData ? cleanedData.links : undefined,
+ showSpinner: data.pending,
+ containerStyles: finalPlotContainerStyles,
+ labelTruncationLength: 40,
+ emptyNetworkContent,
+ };
+
+ const plotNode = (
+ //@ts-ignore
+
+ );
+
+ const controlsNode = <> >;
+
+ // Create legend for (1) Line/link thickness and (2) Link color.
+ // For (1), we'll do the following:
+ // - create a base array that is conditioned on the length of uniqueDataWeights since uniqueDataWeights is a deduped map of ALL data.links.weight
+ // -- if uniqueDataWeights.length is less than or equal to 4, let's use uniqueDataWeights as our base array sorted from greatest to least
+ // -- if uniqueDataWeights.length is greater than 4, create an array of a default length filled with 'undefined'
+ // - create lineLegendItems by mapping over lineLegendItemsBaseArray
+ // -- if the element (weight) is truthy, then we know we're dealing with a copy of the uniqueDataWeights array and can use this value for weightLabel
+ // -- if the element is falsy, fall back to previous calculation for weightLabel
+ const lineLegendItemsBaseArray =
+ uniqueDataWeights.length <= 4
+ ? [...uniqueDataWeights].sort((a, b) => b - a)
+ : Array(DEFAULT_NUMBER_OF_LINE_LEGEND_ITEMS).fill(undefined);
+ const lineLegendItems: LegendItemsProps[] = lineLegendItemsBaseArray.map(
+ (weight, index) => {
+ const weightLabel =
+ weight ??
+ maxDataWeight -
+ ((maxDataWeight - minDataWeight) /
+ (lineLegendItemsBaseArray.length - 1)) *
+ index;
+ return {
+ label: String(weightLabel.toFixed(4)),
+ marker: 'line',
+ markerColor: gray[900],
+ hasData: true,
+ lineThickness:
+ String(
+ MAX_STROKE_WIDTH -
+ ((MAX_STROKE_WIDTH - MIN_STROKE_WIDTH) /
+ (lineLegendItemsBaseArray.length - 1)) *
+ index
+ ) + 'px',
+ };
+ }
+ );
+
+ const lineLegendTitle = options?.getLegendTitle?.(
+ computation.descriptor.configuration
+ )
+ ? 'Link width (' +
+ options.getLegendTitle(computation.descriptor.configuration)[0] +
+ ')'
+ : 'Link width';
+
+ const colorLegendTitle = options?.getLegendTitle?.(
+ computation.descriptor.configuration
+ )
+ ? 'Link color (' +
+ options.getLegendTitle(computation.descriptor.configuration)[1] +
+ ')'
+ : 'Link color';
+
+ const colorLegendItems: LegendItemsProps[] = [
+ {
+ label: 'Positive',
+ marker: 'line',
+ markerColor: twoColorPalette[1],
+ hasData: true,
+ lineThickness: '3px',
+ },
+ {
+ label: 'Negative',
+ marker: 'line',
+ markerColor: twoColorPalette[0],
+ hasData: true,
+ lineThickness: '3px',
+ },
+ ];
+
+ const legendNode = cleanedData && cleanedData.nodes.length > 0 && (
+
+ );
+ const tableGroupNode = <> >;
+
+ // The bipartite network uses FacetedPlotLayout in order to position the legends
+ // atop the plot. The bipartite network plots are often so tall and so wide that
+ // with the normal PlotLayout component the legends are forced way, way down the screen
+ // below the plot.
+ const LayoutComponent = options?.layoutComponent ?? FacetedPlotLayout;
+
+ return (
+
+ {!hideInputsAndControls && (
+
+
+ updateVizConfig({ correlationCoefThreshold: Number(newValue) })
+ }
+ label={'Absolute correlation coefficient'}
+ minValue={0}
+ maxValue={1}
+ value={
+ vizConfig.correlationCoefThreshold ??
+ DEFAULT_CORRELATION_COEF_THRESHOLD
+ }
+ step={0.05}
+ applyWarningStyles={cleanedData && cleanedData.nodes.length === 0}
+ />
+
+
+ updateVizConfig({ significanceThreshold: Number(newValue) })
+ }
+ minValue={0}
+ maxValue={1}
+ value={
+ vizConfig.significanceThreshold ?? DEFAULT_SIGNIFICANCE_THRESHOLD
+ }
+ containerStyles={{ marginLeft: 10 }}
+ step={0.001}
+ applyWarningStyles={cleanedData && cleanedData.nodes.length === 0}
+ />
+
+ )}
+
+
+
+ );
+}
From 6e94ded7e51bc605306db0a026031f42160cba8c Mon Sep 17 00:00:00 2001
From: asizemore
Date: Fri, 21 Jun 2024 08:16:13 -0400
Subject: [PATCH 3/8] cleanup and address funny sizing issues
---
.../libs/components/src/plots/NetworkPlot.css | 2 +-
.../components/src/types/plots/network.ts | 2 +
.../computations/plugins/selfCorrelation.tsx | 141 ++++++++----------
.../implementations/NetworkVisualization.tsx | 45 +++---
4 files changed, 93 insertions(+), 97 deletions(-)
diff --git a/packages/libs/components/src/plots/NetworkPlot.css b/packages/libs/components/src/plots/NetworkPlot.css
index b1e796f578..a8a367a416 100644
--- a/packages/libs/components/src/plots/NetworkPlot.css
+++ b/packages/libs/components/src/plots/NetworkPlot.css
@@ -5,7 +5,7 @@
.network-plot-container {
width: 100%;
- height: 500px;
+ height: 800px;
overflow-y: scroll;
}
diff --git a/packages/libs/components/src/types/plots/network.ts b/packages/libs/components/src/types/plots/network.ts
index 25f5a3d8e3..c2c755a3ed 100755
--- a/packages/libs/components/src/types/plots/network.ts
+++ b/packages/libs/components/src/types/plots/network.ts
@@ -43,6 +43,8 @@ export type LinkData = {
color?: string;
/** Link opacity. Must be between 0 and 1 */
opacity?: number;
+ /** Boolean determining if the edge is directed */
+ isDirected?: boolean;
};
/** NetworkData is the same format accepted by visx's Graph component. */
diff --git a/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx b/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
index c2550236f1..b4c2a56ca2 100644
--- a/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
+++ b/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
@@ -1,5 +1,5 @@
import { useMemo } from 'react';
-import { VariableTreeNode, useFindEntityAndVariableCollection } from '../../..';
+import { useFindEntityAndVariableCollection } from '../../..';
import { ComputationConfigProps, ComputationPlugin } from '../Types';
import { partial } from 'lodash';
import {
@@ -14,20 +14,11 @@ import { makeClassNameHelper } from '@veupathdb/wdk-client/lib/Utils/ComponentUt
import { H6 } from '@veupathdb/coreui';
import { networkVisualization } from '../../visualizations/implementations/NetworkVisualization';
import { VariableCollectionSelectList } from '../../variableSelectors/VariableCollectionSingleSelect';
-import SingleSelect, {
- ItemGroup,
-} from '@veupathdb/coreui/lib/components/inputs/SingleSelect';
-import {
- entityTreeToArray,
- findEntityAndVariableCollection,
- isVariableCollectionDescriptor,
-} from '../../../utils/study-metadata';
+import SingleSelect from '@veupathdb/coreui/lib/components/inputs/SingleSelect';
+import { entityTreeToArray } from '../../../utils/study-metadata';
import { IsEnabledInPickerParams } from '../../visualizations/VisualizationTypes';
-import { ancestorEntitiesForEntityId } from '../../../utils/data-element-constraints';
import { NumberInput } from '@veupathdb/components/lib/components/widgets/NumberAndDateInputs';
import ExpandablePanel from '@veupathdb/coreui/lib/components/containers/ExpandablePanel';
-import { variableCollectionsAreUnique } from '../../../utils/visualization';
-import PluginError from '../../visualizations/PluginError';
import {
CompleteSelfCorrelationConfig,
SelfCorrelationConfig,
@@ -55,10 +46,7 @@ export const plugin: ComputationPlugin = {
},
}),
isConfigurationComplete: (configuration) => {
- // First, the configuration must be complete
- // ANN CLEAN
- if (!CompleteSelfCorrelationConfig.is(configuration)) return false;
- return true;
+ return CompleteSelfCorrelationConfig.is(configuration);
},
visualizationPlugins: {
unipartitenetwork: networkVisualization.withOptions({
@@ -69,67 +57,11 @@ export const plugin: ComputationPlugin = {
return [];
}
},
- // makeGetNodeMenuActions(studyMetadata) {
- // const entities = entityTreeToArray(studyMetadata.rootEntity);
- // const variables = entities.flatMap((e) => e.variables);
- // const collections = entities.flatMap(
- // (entity) => entity.collections ?? []
- // );
- // const hostCollection = collections.find(
- // (c) => c.id === 'EUPATH_0005050'
- // );
- // const parasiteCollection = collections.find(
- // (c) => c.id === 'EUPATH_0005051'
- // );
- // return function getNodeActions(nodeId: string) {
- // const [, variableId] = nodeId.split('.');
- // const variable = variables.find((v) => v.id === variableId);
- // if (variable == null) return [];
-
- // // E.g., "qa."
- // const urlPrefix = window.location.host.replace(
- // /(plasmodb|hostdb)\.org/,
- // ''
- // );
-
- // const href = parasiteCollection?.memberVariableIds.includes(
- // variable.id
- // )
- // ? `//${urlPrefix}plasmodb.org/plasmo/app/search/transcript/GenesByRNASeqpfal3D7_Lee_Gambian_ebi_rnaSeq_RSRCWGCNAModules?param.wgcnaParam=${variable.displayName.toLowerCase()}&autoRun=1`
- // : hostCollection?.memberVariableIds.includes(variable.id)
- // ? `//${urlPrefix}hostdb.org/hostdb/app/search/transcript/GenesByRNASeqhsapREF_Lee_Gambian_ebi_rnaSeq_RSRCWGCNAModules?param.wgcnaParam=${variable.displayName.toLowerCase()}&autoRun=1`
- // : undefined;
- // if (href == null) return [];
- // return [
- // {
- // label: 'See list of genes',
- // href,
- // },
- // ];
- // };
- // },
- // getParitionNames(studyMetadata, config) {
- // if (CorrelationConfig.is(config)) {
- // const entities = entityTreeToArray(studyMetadata.rootEntity);
- // const partition1Name = findEntityAndVariableCollection(
- // entities,
- // config.data1?.collectionSpec
- // )?.variableCollection.displayName;
- // const partition2Name =
- // config.data2?.dataType === 'collection'
- // ? findEntityAndVariableCollection(
- // entities,
- // config.data2?.collectionSpec
- // )?.variableCollection.displayName
- // : 'Continuous metadata variables';
- // return { partition1Name, partition2Name };
- // }
- // },
}), // Must match name in data service and in visualization.tsx
},
isEnabledInPicker: isEnabledInPicker,
studyRequirements:
- 'These visualizations are only available for studies with compatible metadata.',
+ 'These visualizations are only available for studies with compatible collections.',
};
// Renders on the thumbnail page to give a summary of the app instance
@@ -219,7 +151,65 @@ export function SelfCorrelationConfiguration(props: ComputationConfigProps) {
For example, the Age and Shoe Size of children are correlated since as a
child ages, their feet grow.
- {/* ANN FILL IN */}
+
+ Here we look for correlation between the abundance of different taxa at
+ a given taxonomic level
+
+
+ Inputs:
+
+
+ -
+ Taxonomic Level. The taxonomic abundance data to be
+ used in the calculation.
+
+ -
+ Method. The type of correlation to compute. The
+ Pearson method looks for linear trends in the data, while the
+ Spearman method looks for a monotonic relationship. For Spearman and
+ Pearson correlation, we use the rcorr function from the Hmisc
+ package. The SparCC method is a compositional correlation method
+ appropriate for taxonomic abundance data and any other compositional
+ data.
+
+ -
+ Prevalence Prefilter. Remove variables that do not
+ have a set percentage of non-zero abundance across samples. Removing
+ rarely occurring features before calculating correlation can prevent
+ some spurious results.
+
+
+
+
+ Outputs:
+
+ For each pair of variables, the correlation computation returns
+
+ -
+ Correlation coefficient. A value between [-1, 1] that describes the
+ similarity of the input variables. Positive values indicate that
+ both variables rise and fall together, whereas negative values
+ indicate that as one rises, the other falls.
+
+ -
+ P Value. A measure of the probability of observing the result by
+ chance.
+
+
+
+
+ More Questions?
+
+ Check out the{' '}
+
+ correlation function
+ {' '}
+ in our{' '}
+
+ microbiomeComputations
+ {' '}
+ R package.
+
);
@@ -308,9 +298,6 @@ export function SelfCorrelationConfiguration(props: ComputationConfigProps) {
-
- {/* PluginError here if the method doesn't agree with the data */}
-
{}
-// Bipartite Network Visualization
-// The bipartite network takes no input variables, because the received data will complete the plot.
-// Eventually the user will be able to control the significance and correlation coefficient threshold values.
+// Network Visualization
+// The network takes no input variables, because the received data will complete the plot.
+// The user can control the significance and correlation coefficient threshold values.
function NetworkViz(props: VisualizationProps) {
const {
options,
@@ -175,7 +172,7 @@ function NetworkViz(props: VisualizationProps) {
const minDataWeight = Math.min(...uniqueDataWeights);
const maxDataWeight = Math.max(...uniqueDataWeights);
- // Determine min and max x and y positions for nodes. For use in scaling the nodes.
+ // Determine min and max x and y positions for nodes. For use later in scaling the node positions to fit.
const dataXPositions =
data.value?.network.data.nodes.map((node) => Number(node.x)) ?? [];
const dataYPositions =
@@ -189,12 +186,14 @@ function NetworkViz(props: VisualizationProps) {
const cleanedData = useMemo(() => {
if (!data.value) return undefined;
+ // Note that after applying a buffer of 150px/100px to the x/y scales, the plot size should be a sqare!
+ // For example, if plotContainerStyles.width=900 and plotContainerStyles.height=800, the scaled network will span 600x600.
const scaleX = scaleLinear()
.domain([minXPosition, maxXPosition])
- .range([80, 500]);
+ .range([150, plotContainerStyles.width - 150]); // Add a little extra room in the x direction to accound for labels.
const scaleY = scaleLinear()
.domain([minYPosition, maxYPosition])
- .range([80, 500]);
+ .range([100, plotContainerStyles.height - 100]);
// Create map that will adjust each link's weight to find a stroke width that spans an appropriate range for this viz.
const weightToStrokeWidthMap = scaleLinear()
@@ -259,13 +258,22 @@ function NetworkViz(props: VisualizationProps) {
};
}),
};
- }, [data.value, entities, minDataWeight, maxDataWeight]);
+ }, [
+ data.value,
+ entities,
+ minDataWeight,
+ maxDataWeight,
+ maxXPosition,
+ minXPosition,
+ maxYPosition,
+ minYPosition,
+ ]);
// plot subtitle
const plotSubtitle = (
- {`Showing links with an absolute correlation coefficient above ${vizConfig.correlationCoefThreshold?.toString()} and a p-value below ${vizConfig.significanceThreshold?.toString()}`}
+ {`Showing links with an absolute correlation coefficient above ${vizConfig.correlationCoefThreshold?.toString()} and a p-value below ${vizConfig.significanceThreshold?.toString()}. Network layout computed using the igraph layout_nicely function.`}
Click on a node to highlight its edges.
@@ -283,14 +291,13 @@ function NetworkViz(props: VisualizationProps) {
updateThumbnail,
{
...finalPlotContainerStyles,
- height: 400, // no reason for the thumbnail to be as tall as the network (which could be very, very tall!)
+ height: 400,
},
[cleanedData]
);
- // Have the bpnet component say "No nodes" or whatev and have an extra
- // prop called errorMessage or something that displays when there are no nodes.
- // that error message can say "your thresholds of blah and blah are too high, change them"
+ // Content for the Network component to display when no nodes
+ // pass the correlation coeff and significance thresholds
const emptyNetworkContent = (
) {
);
const tableGroupNode = <> >;
- // The bipartite network uses FacetedPlotLayout in order to position the legends
- // atop the plot. The bipartite network plots are often so tall and so wide that
+ // The network uses FacetedPlotLayout in order to position the legends
+ // atop the plot. The network plots are often so tall and so wide that
// with the normal PlotLayout component the legends are forced way, way down the screen
// below the plot.
const LayoutComponent = options?.layoutComponent ?? FacetedPlotLayout;
From 15eb772f49797d68b23676abbb5b93f32a498ddc Mon Sep 17 00:00:00 2001
From: asizemore
Date: Fri, 21 Jun 2024 08:20:54 -0400
Subject: [PATCH 4/8] restrict selfcorrelation to taxa
---
.../libs/eda/src/lib/core/components/computations/Utils.ts | 3 ++-
.../core/components/computations/plugins/selfCorrelation.tsx | 5 +++--
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/packages/libs/eda/src/lib/core/components/computations/Utils.ts b/packages/libs/eda/src/lib/core/components/computations/Utils.ts
index 9f6fa7fa02..80314db7f0 100644
--- a/packages/libs/eda/src/lib/core/components/computations/Utils.ts
+++ b/packages/libs/eda/src/lib/core/components/computations/Utils.ts
@@ -95,7 +95,8 @@ export function isTaxonomicVariableCollection(
return (
isNotAbsoluteAbundanceVariableCollection(variableCollection) &&
(variableCollection.member
- ? variableCollection.member === 'taxon'
+ ? variableCollection.member === 'taxon' &&
+ !!variableCollection.isCompositional
: variableCollection.normalizationMethod === 'sumToUnity') // if we have a member annotation, use that. Old datasets may not have this annotation, hence the fall back normalizationMethod check.
);
}
diff --git a/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx b/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
index b4c2a56ca2..367cd7c55a 100644
--- a/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
+++ b/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
@@ -6,6 +6,7 @@ import {
useConfigChangeHandler,
assertComputationWithConfig,
isNotAbsoluteAbundanceVariableCollection,
+ isTaxonomicVariableCollection,
} from '../Utils';
import { Computation } from '../../../types/visualization';
import { ComputationStepContainer } from '../ComputationStepContainer';
@@ -111,9 +112,9 @@ function SelfCorrelationConfigDescriptionComponent({
}
const CORRELATION_METHODS = [
+ { value: 'sparcc', displayName: 'SparCC' },
{ value: 'spearman', displayName: 'Spearman' },
{ value: 'pearson', displayName: 'Pearson' },
- { value: 'sparcc', displayName: 'SparCC' },
];
const DEFAULT_PROPORTION_NON_ZERO_THRESHOLD = 0.05;
const DEFAULT_VARIANCE_THRESHOLD = 0;
@@ -241,7 +242,7 @@ export function SelfCorrelationConfiguration(props: ComputationConfigProps) {
From 3056b215aab8df33b78fd85eb3da009503b8bde2 Mon Sep 17 00:00:00 2001
From: asizemore
Date: Fri, 21 Jun 2024 09:12:50 -0400
Subject: [PATCH 5/8] fix empty network alignment
---
packages/libs/components/src/plots/NetworkPlot.tsx | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/packages/libs/components/src/plots/NetworkPlot.tsx b/packages/libs/components/src/plots/NetworkPlot.tsx
index f8dec667a1..74d9e9bc2e 100755
--- a/packages/libs/components/src/plots/NetworkPlot.tsx
+++ b/packages/libs/components/src/plots/NetworkPlot.tsx
@@ -50,15 +50,15 @@ export interface NetworkPlotProps {
annotations?: ReactNode[];
}
-const DEFAULT_PLOT_WIDTH = 500;
-const DEFAULT_PLOT_HEIGHT = 500;
+const DEFAULT_PLOT_WIDTH = 800;
+const DEFAULT_PLOT_HEIGHT = 900;
const emptyNodes: NodeData[] = [...Array(9).keys()].map((item, index) => ({
id: item.toString(),
color: gray[100],
stroke: gray[300],
- x: 230 + 200 * Math.cos(2 * Math.PI * (index / 9)),
- y: 230 + 200 * Math.sin(2 * Math.PI * (index / 9)),
+ x: 400 + 200 * Math.cos(2 * Math.PI * (index / 9)),
+ y: 300 + 200 * Math.sin(2 * Math.PI * (index / 9)),
}));
const emptyLinks: LinkData[] = [];
From 8434e2e3462523778f533a612a67e013810009e1 Mon Sep 17 00:00:00 2001
From: asizemore
Date: Fri, 21 Jun 2024 12:03:43 -0400
Subject: [PATCH 6/8] cleanup
---
.../libs/components/src/plots/NetworkPlot.css | 2 +-
.../libs/components/src/plots/NetworkPlot.tsx | 2 +-
.../eda/src/lib/core/api/DataClient/types.ts | 37 +++++++++----------
.../computations/plugins/selfCorrelation.tsx | 6 +--
.../implementations/NetworkVisualization.tsx | 22 +++++++----
5 files changed, 35 insertions(+), 34 deletions(-)
diff --git a/packages/libs/components/src/plots/NetworkPlot.css b/packages/libs/components/src/plots/NetworkPlot.css
index a8a367a416..ea9e806a4b 100644
--- a/packages/libs/components/src/plots/NetworkPlot.css
+++ b/packages/libs/components/src/plots/NetworkPlot.css
@@ -6,7 +6,7 @@
.network-plot-container {
width: 100%;
height: 800px;
- overflow-y: scroll;
+ /* overflow-y: scroll; */
}
.net-hover-dropdown {
diff --git a/packages/libs/components/src/plots/NetworkPlot.tsx b/packages/libs/components/src/plots/NetworkPlot.tsx
index 74d9e9bc2e..172f6bf3e1 100755
--- a/packages/libs/components/src/plots/NetworkPlot.tsx
+++ b/packages/libs/components/src/plots/NetworkPlot.tsx
@@ -73,7 +73,7 @@ function NetworkPlot(props: NetworkPlotProps, ref: Ref) {
svgStyleOverrides,
containerClass = 'web-components-plot',
showSpinner = false,
- labelTruncationLength = 20,
+ labelTruncationLength = 10,
emptyNetworkContent,
annotations,
} = props;
diff --git a/packages/libs/eda/src/lib/core/api/DataClient/types.ts b/packages/libs/eda/src/lib/core/api/DataClient/types.ts
index 4afb613dc4..5438ba64ca 100755
--- a/packages/libs/eda/src/lib/core/api/DataClient/types.ts
+++ b/packages/libs/eda/src/lib/core/api/DataClient/types.ts
@@ -392,9 +392,10 @@ export const NodeIdList = type({
nodeIds: array(string),
});
-// Bipartite network
-export type BipartiteNetworkResponse = TypeOf;
-
+// Network types (including bipartite network)
+// The network types are all built from nodes and links. Currently we've defined specific
+// flavors of networks, called "correlation" networks, that have extra information
+// about them based on their context.
const NodeData = intersection([
type({
id: string,
@@ -423,7 +424,6 @@ export const NetworkData = type({
),
});
-// @ANN clean your types!
const NetworkConfig = partial({
variables: any,
correlationCoefThreshold: number,
@@ -436,7 +436,15 @@ export const NetworkResponse = type({
}),
});
-// @ANN clean your types!
+export interface NetworkRequestParams {
+ studyId: string;
+ filters: Filter[];
+ config: {
+ correlationCoefThreshold?: number;
+ significanceThreshold?: number;
+ };
+}
+
export const BipartiteNetworkData = type({
partitions: array(NodeIdList),
nodes: array(NodeData),
@@ -465,6 +473,8 @@ export const BipartiteNetworkResponse = type({
}),
});
+export type BipartiteNetworkResponse = TypeOf;
+
// Correlation Bipartite Network
// a specific flavor of the bipartite network that also includes correlationCoefThreshold and significanceThreshold
export type CorrelationBipartiteNetworkResponse = TypeOf<
@@ -486,21 +496,8 @@ export interface BipartiteNetworkRequestParams {
significanceThreshold?: number;
};
}
-// @ANN can also maybe clean types here
-// Correlation Network
-// a specific flavor of the network that also includes correlationCoefThreshold and significanceThreshold
-export type CorrelationNetworkResponse = TypeOf<
- typeof CorrelationNetworkResponse
->;
-export const CorrelationNetworkResponse = NetworkResponse;
-export interface NetworkRequestParams {
- studyId: string;
- filters: Filter[];
- config: {
- correlationCoefThreshold?: number;
- significanceThreshold?: number;
- };
-}
+
+export type NetworkResponse = TypeOf;
export type FeaturePrefilterThresholds = TypeOf<
typeof FeaturePrefilterThresholds
diff --git a/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx b/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
index 367cd7c55a..98833e16cd 100644
--- a/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
+++ b/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
@@ -5,7 +5,6 @@ import { partial } from 'lodash';
import {
useConfigChangeHandler,
assertComputationWithConfig,
- isNotAbsoluteAbundanceVariableCollection,
isTaxonomicVariableCollection,
} from '../Utils';
import { Computation } from '../../../types/visualization';
@@ -30,10 +29,10 @@ const cx = makeClassNameHelper('AppStepConfigurationContainer');
/**
* Self-Correlation
*
- * The Correlation app takes all collections and visualizes the correlation between a collection and itself.
+ * The Correlation app takes a collection and visualizes the correlation between the collection and itself.
* For example, if the collection is a set of genes, the app will show the correlation between every pair of genes in the collection.
*
- * As of 05/14/24, this app will only be available for mbio assay data.
+ * As of 05/14/24, this app will only be available for mbio taxonomic data.
*/
export const plugin: ComputationPlugin = {
@@ -96,7 +95,6 @@ function SelfCorrelationConfigDescriptionComponent({
)}
- {/* The method should be disabled unti lthe data is chosen */}
Method:{' '}
diff --git a/packages/libs/eda/src/lib/core/components/visualizations/implementations/NetworkVisualization.tsx b/packages/libs/eda/src/lib/core/components/visualizations/implementations/NetworkVisualization.tsx
index e3ecf851e8..a146cdfe5d 100755
--- a/packages/libs/eda/src/lib/core/components/visualizations/implementations/NetworkVisualization.tsx
+++ b/packages/libs/eda/src/lib/core/components/visualizations/implementations/NetworkVisualization.tsx
@@ -15,7 +15,7 @@ import NetworkPlot, {
} from '@veupathdb/components/lib/plots/NetworkPlot';
import BipartiteNetworkSVG from './selectorIcons/BipartiteNetworkSVG';
import {
- CorrelationNetworkResponse,
+ NetworkResponse,
NetworkRequestParams,
} from '../../../api/DataClient/types';
import { twoColorPalette } from '@veupathdb/components/lib/types/plots/addOns';
@@ -63,7 +63,7 @@ const plotContainerStyles = {
};
export const networkVisualization = createVisualizationPlugin({
- selectorIcon: BipartiteNetworkSVG,
+ selectorIcon: BipartiteNetworkSVG, // Placeholder for now until ann has created a new one!
fullscreenComponent: NetworkViz,
createDefaultConfig: createDefaultConfig,
});
@@ -122,7 +122,7 @@ function NetworkViz(props: VisualizationProps) {
// Get data from the compute job
const data = usePromise(
- useCallback(async (): Promise => {
+ useCallback(async (): Promise => {
// Only need to check compute job status and filter status, since there are no
// viz input variables.
if (computeJobStatus !== 'complete') return undefined;
@@ -143,7 +143,7 @@ function NetworkViz(props: VisualizationProps) {
computation.descriptor.type,
visualization.descriptor.type,
params,
- CorrelationNetworkResponse
+ NetworkResponse
);
return response;
@@ -167,7 +167,7 @@ function NetworkViz(props: VisualizationProps) {
data.value?.network.data.links.map(
(link) => Number(link.weight) // link.weight will always be a number if defined, because it represents the continuous data associated with that link.
) ?? [];
- // Use Set to dedupe the array of dataWeights
+ // Use Set to deduplicate the array of dataWeights
const uniqueDataWeights = Array.from(new Set(dataWeights));
const minDataWeight = Math.min(...uniqueDataWeights);
const maxDataWeight = Math.max(...uniqueDataWeights);
@@ -241,8 +241,14 @@ function NetworkViz(props: VisualizationProps) {
y: scaleY(Number(node.y)),
id: node.id,
label: displayLabel,
+ // the following attempts to place the label in a "reasonable" position
+ // on the plot. So if a node is far on the left side, the label will be on the left side.
+ // This simple solution attempts to avoid overlapping labels and give us a cheap, clean-ish plot.
labelPosition:
- scaleX(Number(node.x)) > 200 ? 'right' : ('left' as LabelPosition),
+ scaleX(Number(node.x)) >
+ (plotContainerStyleOverrides?.width ?? 900) / 2
+ ? 'right'
+ : ('left' as LabelPosition),
};
});
@@ -267,6 +273,7 @@ function NetworkViz(props: VisualizationProps) {
minXPosition,
maxYPosition,
minYPosition,
+ plotContainerStyleOverrides?.width,
]);
// plot subtitle
@@ -314,13 +321,12 @@ function NetworkViz(props: VisualizationProps) {
);
- console.log('cleanedData', cleanedData);
const networkPlotProps: NetworkPlotProps = {
nodes: cleanedData ? cleanedData.nodes : undefined,
links: cleanedData ? cleanedData.links : undefined,
showSpinner: data.pending,
containerStyles: finalPlotContainerStyles,
- labelTruncationLength: 40,
+ labelTruncationLength: 30,
emptyNetworkContent,
};
From 907f550ebcc3ce87b0bbe5de4c5f0ef5fdd74bb1 Mon Sep 17 00:00:00 2001
From: Ann Sizemore Blevins
Date: Fri, 28 Jun 2024 06:57:16 -0400
Subject: [PATCH 7/8] Update
packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
Co-authored-by: Dave Falke
---
.../core/components/computations/plugins/selfCorrelation.tsx | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx b/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
index 98833e16cd..66d3ec6a6c 100644
--- a/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
+++ b/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
@@ -127,11 +127,10 @@ export function SelfCorrelationConfiguration(props: ComputationConfigProps) {
visualizationId,
} = props;
- const configuration = computation.descriptor
- .configuration as SelfCorrelationConfig;
-
assertComputationWithConfig(computation, SelfCorrelationConfig);
+ const { configuration } = computation.descriptor;
+
const changeConfigHandler = useConfigChangeHandler(
analysisState,
computation,
From e82f114da2a272b860440f4e278128528b63fa9b Mon Sep 17 00:00:00 2001
From: asizemore
Date: Fri, 28 Jun 2024 07:04:30 -0400
Subject: [PATCH 8/8] comment, clean types, remove unnecessary memo
---
.../eda/src/lib/core/api/DataClient/types.ts | 7 ++++++-
.../computations/plugins/selfCorrelation.tsx | 16 +++++-----------
2 files changed, 11 insertions(+), 12 deletions(-)
diff --git a/packages/libs/eda/src/lib/core/api/DataClient/types.ts b/packages/libs/eda/src/lib/core/api/DataClient/types.ts
index 5438ba64ca..9201806bc6 100755
--- a/packages/libs/eda/src/lib/core/api/DataClient/types.ts
+++ b/packages/libs/eda/src/lib/core/api/DataClient/types.ts
@@ -396,6 +396,11 @@ export const NodeIdList = type({
// The network types are all built from nodes and links. Currently we've defined specific
// flavors of networks, called "correlation" networks, that have extra information
// about them based on their context.
+
+// NOTE tech debt below! The `sourece` and `target` of the NetworkData links should be
+// NodeData, but the backend is sending us strings. At this point it is unlikely the backend
+// will change for a while to fix this issue. If it does, we can simplify the below and let
+// BipartiteNetworkData extend NetworkData.
const NodeData = intersection([
type({
id: string,
@@ -425,7 +430,7 @@ export const NetworkData = type({
});
const NetworkConfig = partial({
- variables: any,
+ variables: unknown,
correlationCoefThreshold: number,
significanceThreshold: number,
});
diff --git a/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx b/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
index 98833e16cd..9280642a07 100644
--- a/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
+++ b/packages/libs/eda/src/lib/core/components/computations/plugins/selfCorrelation.tsx
@@ -212,17 +212,11 @@ export function SelfCorrelationConfiguration(props: ComputationConfigProps) {
);
- const correlationMethodSelectorText = useMemo(() => {
- if (configuration.correlationMethod) {
- return (
- CORRELATION_METHODS.find(
- (method) => method.value === configuration.correlationMethod
- )?.displayName ?? 'Select a method'
- );
- } else {
- return 'Select a method';
- }
- }, [configuration.correlationMethod]);
+ const correlationMethodSelectorText = configuration.correlationMethod
+ ? CORRELATION_METHODS.find(
+ (method) => method.value === configuration.correlationMethod
+ )?.displayName ?? 'Select a method'
+ : 'Select a method';
return (