Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import loadCellMeta from 'redux/actions/cellMeta';
import { generateDataProcessingPlotUuid } from 'utils/generateCustomPlotUuid';
import Loader from 'components/Loader';
import { getCellSets } from 'redux/selectors';
import { getEmbeddingInitialConfig } from 'utils/plotConfig/getEmbeddingInitialConfig';
import CalculationConfig from 'components/data-processing/ConfigureEmbedding/CalculationConfig';
import PlotLegendAlert, { MAX_LEGEND_ITEMS } from 'components/plots/helpers/PlotLegendAlert';
import EmptyPlot from 'components/plots/helpers/EmptyPlot';
Expand Down Expand Up @@ -408,6 +409,35 @@ const ConfigureEmbedding = (props) => {
if (showAlert) updatePlotWithChanges({ legend: { showAlert, enabled: !showAlert } });
}, [!selectedConfig, activePlotType, cellSets.accessible]);

// Apply cell-count-aware marker defaults for large datasets in embedding plots
// Only applies once when config is first loaded and hasn't been customized yet
useEffect(() => {
if (!selectedConfig || !cellSets.accessible || plotType !== 'embedding' || !currentPlot) return;

const initialConfig = getEmbeddingInitialConfig(currentPlot.plotType, cellSets);

// Check if we should apply large dataset defaults
if (initialConfig.defaultValues?.largeDatasetDefaults) {
// Get the standard (non-adjusted) initial config to check if marker was already customized
const standardConfig = initialPlotConfigStates[currentPlot.plotType];

// Only apply if marker config currently matches standard defaults (not yet customized)
const isUsingStandardDefaults = selectedConfig.marker.outline === standardConfig.marker.outline
&& selectedConfig.marker.size === standardConfig.marker.size;

if (isUsingStandardDefaults) {
dispatch(updatePlotConfig(activePlotUuid, {
marker: {
...selectedConfig.marker,
outline: false,
size: 1,
},
}));
debounceSave(activePlotUuid);
}
}
}, [activePlotUuid, currentPlot?.plotType, cellSets?.accessible]);

useEffect(() => {
// if we change a plot and the config is not saved yet
if (outstandingChanges) {
Expand All @@ -431,8 +461,8 @@ const ConfigureEmbedding = (props) => {
embeddingPlotUuids.forEach((plotUuid) => {
if (plotUuid !== activePlotUuid) {
const otherPlotConfig = plotConfigs[plotUuid];
if (otherPlotConfig &&
JSON.stringify(otherPlotConfig.marker) !== JSON.stringify(selectedConfig.marker)) {
if (otherPlotConfig &&
JSON.stringify(otherPlotConfig.marker) !== JSON.stringify(selectedConfig.marker)) {
dispatch(updatePlotConfig(plotUuid, { marker: selectedConfig.marker }));
}
}
Expand All @@ -447,7 +477,7 @@ const ConfigureEmbedding = (props) => {
const plotActions = {
export: true,
};

if (cellSets.accessible && selectedConfig) {
setPlot(currentPlot.plot(selectedConfig, plotActions));
}
Expand Down Expand Up @@ -486,6 +516,8 @@ const ConfigureEmbedding = (props) => {
const isEqual = Object.keys(initialConfig).every((key) => {
// By pass plot data because we want to compare settings not data
if (key === 'plotData') return true;
// Skip defaultValues as it's metadata about defaults, not actual config
if (key === 'defaultValues') return true;
if (initialConfig.keepValuesOnReset?.includes(key)) return true;
if (currentConfig[key] && typeof currentConfig[key] === 'object' && initialConfig[key] && typeof initialConfig[key] === 'object') {
// For nested objects, exclude defaultValues from comparison as it's metadata about defaults
Expand All @@ -503,12 +535,15 @@ const ConfigureEmbedding = (props) => {
useEffect(() => {
if (!selectedConfig || !currentPlot) return;

const initialConfig = initialPlotConfigStates[currentPlot.plotType];
const initialConfig = getEmbeddingInitialConfig(currentPlot.plotType, cellSets);
setIsResetDisabled(isConfigEqual(selectedConfig, initialConfig));
}, [selectedConfig]);
}, [selectedConfig, cellSets]);

const onClickReset = () => {
dispatch(resetPlotConfig(experimentId, currentPlot.plotUuid, currentPlot.plotType));
// For embedding preview plots, use cell-count-aware defaults
const initialConfig = getEmbeddingInitialConfig(currentPlot.plotType, cellSets);
dispatch(updatePlotConfig(currentPlot.plotUuid, initialConfig));
debounceSave(currentPlot.plotUuid);
};

const renderExtraControlPanels = () => (
Expand Down
41 changes: 38 additions & 3 deletions src/components/data-processing/DataIntegration/DataIntegration.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import FrequencyPlot from 'components/plots/FrequencyPlot';
import ElbowPlot from 'components/plots/ElbowPlot';
import { generateDataProcessingPlotUuid } from 'utils/generateCustomPlotUuid';
import EmptyPlot from 'components/plots/helpers/EmptyPlot';
import { getEmbeddingInitialConfig } from 'utils/plotConfig/getEmbeddingInitialConfig';
import PlotStyling from 'components/plots/styling/PlotStyling';
import { getIsUnisample } from 'utils/experimentPredicates';
import PlotLegendAlert, { MAX_LEGEND_ITEMS } from 'components/plots/helpers/PlotLegendAlert';
Expand Down Expand Up @@ -246,6 +247,8 @@ const DataIntegration = (props) => {
const isEqual = Object.keys(initialConfig).every((key) => {
// By pass plot data because we want to compare settings not data
if (key === 'plotData') return true;
// Skip defaultValues as it's metadata about defaults, not actual config
if (key === 'defaultValues') return true;
if (initialConfig.keepValuesOnReset?.includes(key)) return true;
if (currentConfig[key] && typeof currentConfig[key] === 'object' && initialConfig[key] && typeof initialConfig[key] === 'object') {
// For nested objects, exclude defaultValues from comparison as it's metadata about defaults
Expand All @@ -263,12 +266,15 @@ const DataIntegration = (props) => {
useEffect(() => {
if (!selectedConfig || !plots[selectedPlot]) return;

const initialConfig = initialPlotConfigStates[activePlotType];
const initialConfig = getEmbeddingInitialConfig(activePlotType, cellSets);
setIsResetDisabled(isConfigEqual(selectedConfig, initialConfig));
}, [selectedConfig]);
}, [selectedConfig, cellSets]);

const onClickReset = () => {
dispatch(resetPlotConfig(experimentId, activePlotUuid, activePlotType));
// For embedding plots in data integration, use cell-count-aware defaults
const initialConfig = getEmbeddingInitialConfig(activePlotType, cellSets);
dispatch(updatePlotConfig(activePlotUuid, initialConfig));
debounceSave(activePlotUuid);
};

useEffect(() => {
Expand All @@ -281,6 +287,35 @@ const DataIntegration = (props) => {
if (showAlert) updatePlotWithChanges({ legend: { showAlert, enabled: !showAlert } });
}, [!selectedConfig, activePlotType, cellSets.accessible]);

// Apply cell-count-aware marker defaults for large datasets in embedding plots
// Only applies once when config is first loaded and hasn't been customized yet
useEffect(() => {
if (!selectedConfig || !cellSets.accessible || activePlotType !== 'dataIntegrationEmbedding') return;

const initialConfig = getEmbeddingInitialConfig(activePlotType, cellSets);

// Check if we should apply large dataset defaults
if (initialConfig.defaultValues?.largeDatasetDefaults) {
// Get the standard (non-adjusted) initial config to check if marker was already customized
const standardConfig = initialPlotConfigStates[activePlotType];

// Only apply if marker config currently matches standard defaults (not yet customized)
const isUsingStandardDefaults = selectedConfig.marker.outline === standardConfig.marker.outline
&& selectedConfig.marker.size === standardConfig.marker.size;

if (isUsingStandardDefaults) {
dispatch(updatePlotConfig(activePlotUuid, {
marker: {
...selectedConfig.marker,
outline: false,
size: 1,
},
}));
debounceSave(activePlotUuid);
}
}
}, [activePlotUuid, activePlotType, cellSets?.accessible]);

const completedSteps = useSelector(getBackendStatus(experimentId))
.status?.pipeline?.completedSteps;

Expand Down
2 changes: 2 additions & 0 deletions src/components/data-processing/PlotLayout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ const PlotLayout = ({
const isEqual = Object.keys(initialConfig).every((key) => {
// By pass plot data because we want to compare settings not data
if (key === 'plotData') return true;
// Skip defaultValues as it's metadata about defaults, not actual config
if (key === 'defaultValues') return true;
if (initialConfig.keepValuesOnReset?.includes(key)) return true;
if (currentConfig[key] && typeof currentConfig[key] === 'object' && initialConfig[key] && typeof initialConfig[key] === 'object') {
// For nested objects, exclude defaultValues from comparison as it's metadata about defaults
Expand Down
59 changes: 56 additions & 3 deletions src/components/plots/PlotContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import {
resetPlotConfig,
savePlotConfig,
} from 'redux/actions/componentConfig';
import { getCellSets } from 'redux/selectors';
import _ from 'lodash';
import PlotStyling from 'components/plots/styling/PlotStyling';
import MultiTileContainer from 'components/MultiTileContainer';
import { getEmbeddingInitialConfig, isEmbeddingPlotType } from 'utils/plotConfig/getEmbeddingInitialConfig';

const PLOT = 'Plot';
const CONTROLS = 'Controls';
Expand All @@ -39,6 +41,7 @@ const PlotContainer = (props) => {
const [tileDirection, setTileDirection] = useState(DEFAULT_ORIENTATION);

const { config } = useSelector((state) => state.componentConfig[plotUuid] || {});
const cellSets = useSelector(getCellSets());
const debounceSave = useCallback(
_.debounce(() => dispatch(savePlotConfig(experimentId, plotUuid)), saveDebounceTime), [plotUuid],
);
Expand All @@ -57,6 +60,8 @@ const PlotContainer = (props) => {
const isEqual = Object.keys(initialConfig).every((key) => {
// By pass plot data because we want to compare settings not data
if (key === 'plotData') return true;
// Skip defaultValues as it's metadata about defaults, not actual config
if (key === 'defaultValues') return true;
if (initialConfig.keepValuesOnReset?.includes(key)) return true;
if (currentConfig[key] && typeof currentConfig[key] === 'object' && initialConfig[key] && typeof initialConfig[key] === 'object') {
// For nested objects, exclude defaultValues from comparison as it's metadata about defaults
Expand Down Expand Up @@ -87,15 +92,63 @@ const PlotContainer = (props) => {

debounceSave();

// For embedding plots with cellSets available, use large-dataset-aware comparison
let initialConfig;
if (isEmbeddingPlotType(plotType) && cellSets?.properties && cellSets?.hierarchy) {
const embeddingConfig = getEmbeddingInitialConfig(plotType, cellSets);
initialConfig = embeddingConfig || initialPlotConfigStates[plotType];
} else {
initialConfig = initialPlotConfigStates[plotType];
}

setIsResetDisabled(
isConfigEqual(config, initialPlotConfigStates[plotType]),
isConfigEqual(config, initialConfig),
);
}, [config]);
}, [config, cellSets, plotType]);

// Auto-apply large-dataset defaults for embedding plots when config first loads with cellSets
useEffect(() => {
if (!isEmbeddingPlotType(plotType) || !config || !cellSets?.properties || !cellSets?.hierarchy) return;
if (config.defaultValues?.largeDatasetDefaults) return; // Already applied

const cellCount = cellSets.hierarchy?.find((node) => node.key === 'sample')
?.children?.reduce((sum, child) => {
const cellIds = cellSets.properties[child.key]?.cellIds;
return sum + (cellIds?.size || 0);
}, 0);

if (cellCount > 100000) {
const largeDatasetConfig = getEmbeddingInitialConfig(plotType, cellSets);
if (largeDatasetConfig && largeDatasetConfig !== initialPlotConfigStates[plotType]) {
dispatch(updatePlotConfig(plotUuid, largeDatasetConfig));
debounceSave();
}
}
}, [config, cellSets, plotType, plotUuid, experimentId]);

const onClickReset = () => {
// For embedding plots with large datasets, use optimized defaults
if (isEmbeddingPlotType(plotType)) {
const initialConfig = getEmbeddingInitialConfig(plotType, cellSets);
if (initialConfig && initialConfig !== initialPlotConfigStates[plotType]) {
// Preserve fields marked with keepValuesOnReset
const keysToPreserve = initialConfig.keepValuesOnReset || [];
const resetConfig = keysToPreserve.reduce((acc, key) => {
if (config?.[key] !== undefined) {
acc[key] = config[key];
}
return acc;
}, initialConfig);

dispatch(updatePlotConfig(plotUuid, resetConfig));
debounceSave();
onPlotReset();
return;
}
}

dispatch(resetPlotConfig(experimentId, plotUuid, plotType));
onPlotReset();
setIsResetDisabled(true);
};

const renderPlotToolbarControls = () => (
Expand Down
20 changes: 16 additions & 4 deletions src/redux/reducers/componentConfig/initialState.js
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,10 @@ const embeddingPreviewMitochondrialContentInitialConfig = {
fontSize: 20,
},
fontStyle: fontStyleBaseState,
colour: colourBaseState,
colour: {
...colourBaseState,
gradient: 'spectral',
},
marker: markerBaseState,
labels: labelBaseState,
shownGene: null,
Expand Down Expand Up @@ -559,7 +562,10 @@ const embeddingPreviewDoubletScoreInitialConfig = {
fontSize: 20,
},
fontStyle: fontStyleBaseState,
colour: colourBaseState,
colour: {
...colourBaseState,
gradient: 'spectral',
},
marker: markerBaseState,
labels: labelBaseState,
selectedSample: 'All',
Expand Down Expand Up @@ -589,7 +595,10 @@ const embeddingPreviewNumOfGenesInitialConfig = {
fontSize: 20,
},
fontStyle: fontStyleBaseState,
colour: colourBaseState,
colour: {
...colourBaseState,
gradient: 'spectral',
},
marker: markerBaseState,
labels: labelBaseState,
selectedSample: 'All',
Expand Down Expand Up @@ -618,7 +627,10 @@ const embeddingPreviewNumOfUmisInitialConfig = {
fontSize: 20,
},
fontStyle: fontStyleBaseState,
colour: colourBaseState,
colour: {
...colourBaseState,
gradient: 'spectral',
},
marker: markerBaseState,
labels: labelBaseState,
selectedSample: 'All',
Expand Down
Loading
Loading