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 7f3d738097..45cd1a6a9d 100755 --- a/packages/libs/eda/src/lib/core/api/DataClient/types.ts +++ b/packages/libs/eda/src/lib/core/api/DataClient/types.ts @@ -758,6 +758,15 @@ export const MapMarkersOverlayResponse = type({ // OverlayConfig will be used for all next-gen visualizations eventually +export type BinDefinitions = TypeOf; +export const BinDefinitions = array( + type({ + binStart: string, + binEnd: string, + binLabel: string, + }) +); + export type OverlayConfig = TypeOf; export const OverlayConfig = intersection([ type({ @@ -771,13 +780,7 @@ export const OverlayConfig = intersection([ }), type({ overlayType: literal('continuous'), - overlayValues: array( - type({ - binStart: string, - binEnd: string, - binLabel: string, - }) - ), + overlayValues: BinDefinitions, }), ]), ]); diff --git a/packages/libs/eda/src/lib/core/components/visualizations/implementations/BarplotVisualization.tsx b/packages/libs/eda/src/lib/core/components/visualizations/implementations/BarplotVisualization.tsx index 788aa2f079..87148667a0 100755 --- a/packages/libs/eda/src/lib/core/components/visualizations/implementations/BarplotVisualization.tsx +++ b/packages/libs/eda/src/lib/core/components/visualizations/implementations/BarplotVisualization.tsx @@ -53,6 +53,7 @@ import { nonUniqueWarning, hasIncompleteCases, assertValidInputVariables, + substituteUnselectedToken, } from '../../../utils/visualization'; import { VariablesByInputName } from '../../../utils/data-element-constraints'; // use lodash instead of Math.min/max @@ -73,7 +74,10 @@ import PlotLegend from '@veupathdb/components/lib/components/plotControls/PlotLe import { LegendItemsProps } from '@veupathdb/components/lib/components/plotControls/PlotListLegend'; // import { gray } from '../colors'; -import { ColorPaletteDefault } from '@veupathdb/components/lib/types/plots/addOns'; +import { + ColorPaletteDefault, + SequentialGradientColorscale, +} from '@veupathdb/components/lib/types/plots/addOns'; // a custom hook to preserve the status of checked legend items import { useCheckedLegendItems } from '../../../hooks/checkedLegendItemsStatus'; @@ -452,26 +456,30 @@ function BarplotViz(props: VisualizationProps) { variable?.vocabulary, variable ); - const overlayVocabulary = fixLabelsForNumberVariables( - overlayVariable?.vocabulary, - overlayVariable - ); + const overlayVocabulary = + (overlayVariable && options?.getOverlayVocabulary?.()) ?? + fixLabelsForNumberVariables( + overlayVariable?.vocabulary, + overlayVariable + ); const facetVocabulary = fixLabelsForNumberVariables( facetVariable?.vocabulary, facetVariable ); return grayOutLastSeries( - reorderData( - barplotResponseToData( - response, - variable, - overlayVariable, - facetVariable - ), - vocabulary, - vocabularyWithMissingData(overlayVocabulary, showMissingOverlay), - vocabularyWithMissingData(facetVocabulary, showMissingFacet) + substituteUnselectedToken( + reorderData( + barplotResponseToData( + response, + variable, + overlayVariable, + facetVariable + ), + vocabulary, + vocabularyWithMissingData(overlayVocabulary, showMissingOverlay), + vocabularyWithMissingData(facetVocabulary, showMissingFacet) + ) ), showMissingOverlay ); @@ -675,6 +683,10 @@ function BarplotViz(props: VisualizationProps) { max: truncationConfigDependentAxisMax, }, }, + colorPalette: + options?.getOverlayType?.() === 'continuous' + ? SequentialGradientColorscale + : ColorPaletteDefault, ...neutralPaletteProps, }; diff --git a/packages/libs/eda/src/lib/core/components/visualizations/implementations/BoxplotVisualization.tsx b/packages/libs/eda/src/lib/core/components/visualizations/implementations/BoxplotVisualization.tsx index b5f519ca2e..ac514f1b69 100755 --- a/packages/libs/eda/src/lib/core/components/visualizations/implementations/BoxplotVisualization.tsx +++ b/packages/libs/eda/src/lib/core/components/visualizations/implementations/BoxplotVisualization.tsx @@ -61,6 +61,7 @@ import { fixVarIdLabel, getVariableLabel, assertValidInputVariables, + substituteUnselectedToken, } from '../../../utils/visualization'; import { VariablesByInputName } from '../../../utils/data-element-constraints'; import { StudyEntity, Variable } from '../../../types/study'; @@ -68,7 +69,10 @@ import { isFaceted } from '@veupathdb/components/lib/types/guards'; // custom legend import PlotLegend from '@veupathdb/components/lib/components/plotControls/PlotLegend'; import { LegendItemsProps } from '@veupathdb/components/lib/components/plotControls/PlotListLegend'; -import { ColorPaletteDefault } from '@veupathdb/components/lib/types/plots/addOns'; +import { + ColorPaletteDefault, + SequentialGradientColorscale, +} from '@veupathdb/components/lib/types/plots/addOns'; // a custom hook to preserve the status of checked legend items import { useCheckedLegendItems } from '../../../hooks/checkedLegendItemsStatus'; import { @@ -522,27 +526,31 @@ function BoxplotViz(props: VisualizationProps) { xAxisVariable?.vocabulary, xAxisVariable ); - const overlayVocabulary = fixLabelsForNumberVariables( - overlayVariable?.vocabulary, - overlayVariable - ); + const overlayVocabulary = + (overlayVariable && options?.getOverlayVocabulary?.()) ?? + fixLabelsForNumberVariables( + overlayVariable?.vocabulary, + overlayVariable + ); const facetVocabulary = fixLabelsForNumberVariables( facetVariable?.vocabulary, facetVariable ); return grayOutLastSeries( - reorderData( - boxplotResponseToData( - response, - xAxisVariable, - overlayVariable, - facetVariable, + substituteUnselectedToken( + reorderData( + boxplotResponseToData( + response, + xAxisVariable, + overlayVariable, + facetVariable, + entities + ), + vocabulary, + vocabularyWithMissingData(overlayVocabulary, showMissingOverlay), + vocabularyWithMissingData(facetVocabulary, showMissingFacet), entities - ), - vocabulary, - vocabularyWithMissingData(overlayVocabulary, showMissingOverlay), - vocabularyWithMissingData(facetVocabulary, showMissingFacet), - entities + ) ), showMissingOverlay, '#a0a0a0' @@ -720,6 +728,11 @@ function BoxplotViz(props: VisualizationProps) { truncatedDependentAxisWarning={truncatedDependentAxisWarning} setTruncatedDependentAxisWarning={setTruncatedDependentAxisWarning} dependentAxisMinMax={dependentAxisMinMax} + colorPalette={ + options?.getOverlayType?.() === 'continuous' + ? SequentialGradientColorscale + : ColorPaletteDefault + } {...neutralPaletteProps} /> ); diff --git a/packages/libs/eda/src/lib/core/components/visualizations/implementations/HistogramVisualization.tsx b/packages/libs/eda/src/lib/core/components/visualizations/implementations/HistogramVisualization.tsx index faf8281a2e..e473228da9 100755 --- a/packages/libs/eda/src/lib/core/components/visualizations/implementations/HistogramVisualization.tsx +++ b/packages/libs/eda/src/lib/core/components/visualizations/implementations/HistogramVisualization.tsx @@ -64,6 +64,7 @@ import { variablesAreUnique, nonUniqueWarning, assertValidInputVariables, + substituteUnselectedToken, } from '../../../utils/visualization'; import { useUpdateThumbnailEffect } from '../../../hooks/thumbnails'; // import variable's metadata-based independent axis range utils @@ -72,7 +73,10 @@ import PluginError from '../PluginError'; // for custom legend import PlotLegend from '@veupathdb/components/lib/components/plotControls/PlotLegend'; import { LegendItemsProps } from '@veupathdb/components/lib/components/plotControls/PlotListLegend'; -import { ColorPaletteDefault } from '@veupathdb/components/lib/types/plots/addOns'; +import { + ColorPaletteDefault, + SequentialGradientColorscale, +} from '@veupathdb/components/lib/types/plots/addOns'; // a custom hook to preserve the status of checked legend items import { useCheckedLegendItems } from '../../../hooks/checkedLegendItemsStatus'; @@ -521,24 +525,28 @@ function HistogramViz(props: VisualizationProps) { response.completeCasesTable ); - const overlayVocabulary = fixLabelsForNumberVariables( - overlayVariable?.vocabulary, - overlayVariable - ); + const overlayVocabulary = + (overlayVariable && options?.getOverlayVocabulary?.()) ?? + fixLabelsForNumberVariables( + overlayVariable?.vocabulary, + overlayVariable + ); const facetVocabulary = fixLabelsForNumberVariables( facetVariable?.vocabulary, facetVariable ); return grayOutLastSeries( - reorderData( - histogramResponseToData( - response, - xAxisVariable, - overlayVariable, - facetVariable - ), - vocabularyWithMissingData(overlayVocabulary, showMissingOverlay), - vocabularyWithMissingData(facetVocabulary, showMissingFacet) + substituteUnselectedToken( + reorderData( + histogramResponseToData( + response, + xAxisVariable, + overlayVariable, + facetVariable + ), + vocabularyWithMissingData(overlayVocabulary, showMissingOverlay), + vocabularyWithMissingData(facetVocabulary, showMissingFacet) + ) ), showMissingOverlay ); @@ -878,6 +886,10 @@ function HistogramViz(props: VisualizationProps) { max: truncationConfigDependentAxisMax, }, }, + colorPalette: + options?.getOverlayType?.() === 'continuous' + ? SequentialGradientColorscale + : ColorPaletteDefault, ...neutralPaletteProps, }; diff --git a/packages/libs/eda/src/lib/core/components/visualizations/implementations/LineplotVisualization.tsx b/packages/libs/eda/src/lib/core/components/visualizations/implementations/LineplotVisualization.tsx index 876a15a0d5..727315c8f1 100755 --- a/packages/libs/eda/src/lib/core/components/visualizations/implementations/LineplotVisualization.tsx +++ b/packages/libs/eda/src/lib/core/components/visualizations/implementations/LineplotVisualization.tsx @@ -85,11 +85,13 @@ import { vocabularyWithMissingData, hasIncompleteCases, assertValidInputVariables, + substituteUnselectedToken, } from '../../../utils/visualization'; import { gray } from '../colors'; import { AvailableUnitsAddon, ColorPaletteDefault, + SequentialGradientColorscale, } from '@veupathdb/components/lib/types/plots/addOns'; // import variable's metadata-based independent axis range utils import { VariablesByInputName } from '../../../utils/data-element-constraints'; @@ -320,6 +322,12 @@ function LineplotViz(props: VisualizationProps) { providedOverlayVariableDescriptor ); + const colorPaletteOverride = + neutralPaletteProps.colorPalette ?? + options?.getOverlayType?.() === 'continuous' + ? SequentialGradientColorscale + : undefined; + const findEntityAndVariable = useFindEntityAndVariable(filters); const { @@ -739,10 +747,12 @@ function LineplotViz(props: VisualizationProps) { xAxisVariable?.vocabulary, xAxisVariable ); - const overlayVocabulary = fixLabelsForNumberVariables( - overlayVariable?.vocabulary, - overlayVariable - ); + const overlayVocabulary = + (overlayVariable && options?.getOverlayVocabulary?.()) ?? + fixLabelsForNumberVariables( + overlayVariable?.vocabulary, + overlayVariable + ); const facetVocabulary = fixLabelsForNumberVariables( facetVariable?.vocabulary, facetVariable @@ -762,7 +772,7 @@ function LineplotViz(props: VisualizationProps) { showMissingFacet, facetVocabulary, facetVariable, - neutralPaletteProps.colorPalette + colorPaletteOverride ); }, [ outputEntity, @@ -1955,7 +1965,7 @@ export function lineplotResponseToData( })), }; return { - dataSetProcess, + dataSetProcess: substituteUnselectedToken(dataSetProcess!), // calculated y axis limits xMin, xMinPos, 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 653b082667..8d7c1ab61f 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 @@ -78,6 +78,7 @@ import { fixVarIdLabel, getVariableLabel, assertValidInputVariables, + substituteUnselectedToken, } from '../../../utils/visualization'; import { gray } from '../colors'; import { @@ -85,6 +86,7 @@ import { ColorPaletteDark, gradientSequentialColorscaleMap, gradientDivergingColorscaleMap, + SequentialGradientColorscale, } from '@veupathdb/components/lib/types/plots/addOns'; import { VariablesByInputName } from '../../../utils/data-element-constraints'; import { useRouteMatch } from 'react-router'; @@ -353,7 +355,11 @@ function ScatterplotViz(props: VisualizationProps) { vizConfig.overlayVariable, providedOverlayVariableDescriptor ); - + const colorPaletteOverride = + neutralPaletteProps.colorPalette ?? + options?.getOverlayType?.() === 'continuous' + ? SequentialGradientColorscale + : ColorPaletteDefault; const findEntityAndVariable = useFindEntityAndVariable(filters); const { @@ -779,7 +785,11 @@ function ScatterplotViz(props: VisualizationProps) { ? response.scatterplot.config.variables.find( (v) => v.plotReference === 'overlay' && v.vocabulary != null )?.vocabulary - : fixLabelsForNumberVariables( + : // TO DO: remove the categorical condition when https://github.com/VEuPathDB/EdaNewIssues/issues/642 is sorted + (overlayVariable && options?.getOverlayType?.() === 'categorical' + ? options?.getOverlayVocabulary?.() + : undefined) ?? + fixLabelsForNumberVariables( overlayVariable?.vocabulary, overlayVariable ); @@ -800,7 +810,7 @@ function ScatterplotViz(props: VisualizationProps) { // pass computation computation.descriptor.type, entities, - neutralPaletteProps.colorPalette + colorPaletteOverride ); return { ...returnData, @@ -2226,7 +2236,7 @@ export function scatterplotResponseToData( ); return { - dataSetProcess: dataSetProcess, + dataSetProcess: substituteUnselectedToken(dataSetProcess), xMin, xMinPos, xMax, diff --git a/packages/libs/eda/src/lib/core/components/visualizations/options/types.ts b/packages/libs/eda/src/lib/core/components/visualizations/options/types.ts index 9347f14270..275750918e 100644 --- a/packages/libs/eda/src/lib/core/components/visualizations/options/types.ts +++ b/packages/libs/eda/src/lib/core/components/visualizations/options/types.ts @@ -1,3 +1,4 @@ +import { OverlayConfig } from '../../../api/DataClient'; import { Filter } from '../../../types/filter'; import { VariableDescriptor } from '../../../types/variable'; import { Computation } from '../../../types/visualization'; @@ -11,6 +12,8 @@ export interface OverlayOptions { computeConfig: unknown ) => VariableDescriptor | undefined; getOverlayVariableHelp?: () => string; + getOverlayType?: () => OverlayConfig['overlayType'] | undefined; + getOverlayVocabulary?: () => string[] | undefined; getCheckedLegendItems?: (computeConfig: unknown) => string[] | undefined; } diff --git a/packages/libs/eda/src/lib/core/utils/visualization.ts b/packages/libs/eda/src/lib/core/utils/visualization.ts index c0a9e38b4c..314519fe1c 100644 --- a/packages/libs/eda/src/lib/core/utils/visualization.ts +++ b/packages/libs/eda/src/lib/core/utils/visualization.ts @@ -3,6 +3,7 @@ import { BarplotData, BoxplotData, FacetedData, + LinePlotData, } from '@veupathdb/components/lib/types/plots'; import { StudyEntity, Variable } from '../types/study'; import { CoverageStatistics } from '../types/visualization'; @@ -25,6 +26,7 @@ import { VariablesByInputName, } from './data-element-constraints'; import { isEqual } from 'lodash'; +import { UNSELECTED_DISPLAY_TEXT, UNSELECTED_TOKEN } from '../../map'; // was: BarplotData | HistogramData | { series: BoxplotData }; type SeriesWithStatistics = T & CoverageStatistics; @@ -67,6 +69,40 @@ export function grayOutLastSeries< } as SeriesWithStatistics; } +/** + * replace "__UNSELECTED__" with "All other values" in the `name` prop + * + */ + +type NamedSeries = { + series: { + name?: string; + }[]; +}; + +export function substituteUnselectedToken< + T extends NamedSeries, + Data extends T | FacetedData | MaybeFacetedSeriesWithStatistics +>(data: Data): Data { + if (isFaceted(data)) { + return { + ...data, + facets: data.facets.map((facet) => ({ + ...facet, + data: substituteUnselectedToken(data) as T, + })), + }; + } else { + return { + ...data, + series: data.series.map((s) => ({ + ...s, + name: s.name === UNSELECTED_TOKEN ? UNSELECTED_DISPLAY_TEXT : s.name, + })), + }; + } +} + /** * Calculates if there are any incomplete cases for the given variable * (usually overlay or facet variable) diff --git a/packages/libs/eda/src/lib/map/analysis/hooks/defaultOverlayConfig.ts b/packages/libs/eda/src/lib/map/analysis/hooks/defaultOverlayConfig.ts index 9d691b1632..a438e0798c 100644 --- a/packages/libs/eda/src/lib/map/analysis/hooks/defaultOverlayConfig.ts +++ b/packages/libs/eda/src/lib/map/analysis/hooks/defaultOverlayConfig.ts @@ -1,4 +1,5 @@ import { ColorPaletteDefault } from '@veupathdb/components/lib/types/plots'; +import { UNSELECTED_TOKEN } from '../..'; import { BinRange, Filter, @@ -7,7 +8,6 @@ import { Variable, } from '../../../core'; import { DataClient, SubsettingClient } from '../../../core/api'; -import { UNSELECTED_TOKEN } from './standaloneMapMarkers'; // This async function fetches the default overlay config. // For continuous variables, this involves calling the filter-aware-metadata/continuous-variable diff --git a/packages/libs/eda/src/lib/map/analysis/hooks/standaloneMapMarkers.tsx b/packages/libs/eda/src/lib/map/analysis/hooks/standaloneMapMarkers.tsx index 19c3267183..d3c3bb5037 100644 --- a/packages/libs/eda/src/lib/map/analysis/hooks/standaloneMapMarkers.tsx +++ b/packages/libs/eda/src/lib/map/analysis/hooks/standaloneMapMarkers.tsx @@ -27,11 +27,7 @@ import { defaultAnimationDuration } from '@veupathdb/components/lib/map/config/m import { LegendItemsProps } from '@veupathdb/components/lib/components/plotControls/PlotListLegend'; import { VariableDescriptor } from '../../../core/types/variable'; import { useDeepValue } from '../../../core/hooks/immutability'; - -// Back end overlay values contain a special token for the "Other" category: -export const UNSELECTED_TOKEN = '__UNSELECTED__'; -// This is what is displayed to the user instead: -const UNSELECTED_DISPLAY_TEXT = 'All other values'; +import { UNSELECTED_DISPLAY_TEXT, UNSELECTED_TOKEN } from '../..'; /** * Provides markers for use in the MapVEuMap component diff --git a/packages/libs/eda/src/lib/map/analysis/hooks/standaloneVizPlugins.ts b/packages/libs/eda/src/lib/map/analysis/hooks/standaloneVizPlugins.ts index c20e6d9c8a..81e3dee1fe 100644 --- a/packages/libs/eda/src/lib/map/analysis/hooks/standaloneVizPlugins.ts +++ b/packages/libs/eda/src/lib/map/analysis/hooks/standaloneVizPlugins.ts @@ -17,7 +17,7 @@ import { scatterplotVisualization } from '../../../core/components/visualization import { lineplotVisualization } from '../../../core/components/visualizations/implementations/LineplotVisualization'; import { barplotVisualization } from '../../../core/components/visualizations/implementations/BarplotVisualization'; import { boxplotVisualization } from '../../../core/components/visualizations/implementations/BoxplotVisualization'; -import { OverlayConfig } from '../../../core'; +import { BinDefinitions, OverlayConfig } from '../../../core'; import { boxplotRequest } from './plugins/boxplot'; import { barplotRequest } from './plugins/barplot'; import { lineplotRequest } from './plugins/lineplot'; @@ -41,7 +41,20 @@ export function useStandaloneVizPlugins({ hideFacetInputs: true, // will also enable table-only mode for mosaic hideShowMissingnessToggle: true, layoutComponent: FloatingLayout, + // why are we providing three functions to access the properties of + // one object? Because in the pre-SAM world, getOverlayVariable was already + // part of this interface. getOverlayVariable: (_) => selectedOverlayConfig?.overlayVariable, + getOverlayType: () => selectedOverlayConfig?.overlayType, + getOverlayVocabulary: () => { + const overlayValues = selectedOverlayConfig?.overlayValues; + if (overlayValues == null) return undefined; + if (BinDefinitions.is(overlayValues)) { + return overlayValues.map((bin) => bin.binLabel); + } else { + return overlayValues; + } + }, getOverlayVariableHelp: () => 'The overlay variable can be selected via the top-right panel.', }); diff --git a/packages/libs/eda/src/lib/map/index.ts b/packages/libs/eda/src/lib/map/index.ts index 8eff1a9302..4d824ee45d 100644 --- a/packages/libs/eda/src/lib/map/index.ts +++ b/packages/libs/eda/src/lib/map/index.ts @@ -11,3 +11,8 @@ export type SiteInformationProps = { export const mapNavigationBackgroundColor = 'white'; export const mapNavigationBorder: CSSProperties['border'] = '1px solid #D9D9D9'; + +// Back end overlay values contain a special token for the "Other" category: +export const UNSELECTED_TOKEN = '__UNSELECTED__'; +// This is what is displayed to the user instead: +export const UNSELECTED_DISPLAY_TEXT = 'All other values';