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
45 changes: 34 additions & 11 deletions src/containers/MainPlotContainer/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import {
SELECTIONS_PLOT_NAME,
THUMBNAIL_PATH,
} from "../../constants";
import { getCategoricalFeatureKeys, getMeasuredFeaturesDefs } from "../../state/metadata/selectors";
import {
getCategoricalFeatureKeys,
getMeasuredFeaturesDefs,
getPerCellDataForPlot,
} from "../../state/metadata/selectors";
import {
DataForPlot,
FileInfo,
Expand Down Expand Up @@ -46,6 +50,7 @@ import {
getLineMovingAverageWindow,
getConnectByFeature,
getConnectByCategory,
getFiltersToExclude,
} from "../../state/selection/selectors";
import {
ColorForPlot,
Expand Down Expand Up @@ -288,15 +293,25 @@ export function calculateLinePlotData(
}

const getAnnotationData = createSelector(
[getFilteredCellData, getClickedCellsFileInfo, getPlotByOnX, getPlotByOnY, getHoveredCardId],
[
getPerCellDataForPlot,
getClickedCellsFileInfo,
getFiltersToExclude,
getPlotByOnX,
getPlotByOnY,
getGroupingCategoryNamesAsArray,
getHoveredCardId,
],
(
filteredCellData: DataForPlot,
allCellData: DataForPlot,
clickedCellsFileInfo: FileInfo[],
filtersToExclude: string[],
xAxis,
yAxis,
groupByCategoryNames,
currentHoveredCellId
): AnnotationData[] => {
if (isEmpty(filteredCellData.values) || isEmpty(filteredCellData.labels)) {
if (isEmpty(allCellData.values) || isEmpty(allCellData.labels)) {
return [];
}
const initAcc: AnnotationData[] = [];
Expand All @@ -305,15 +320,23 @@ const getAnnotationData = createSelector(
const fovID = data[FOV_ID_KEY] || "";
const thumbnailPath = data[THUMBNAIL_PATH] || "";

const cellIds = filteredCellData.labels[ARRAY_OF_CELL_IDS_KEY];
// FileInfo is typed with `index` as optional because it gets added to the
// data from the database. However, at this point, index will always be defined, but since
// typescript doesn't know that, we still have this backup to find it in the
// id array but that code should never be executed.
const cellIds = allCellData.labels[ARRAY_OF_CELL_IDS_KEY];
// `FileInfo.index` is typed as optional because it is added from
// the database, but it will always be defined here. Note that
// `index` is the position in the full array of cell IDs, and does
// not account for filtering, so filtering needs to be done
// manually.
const pointIndex =
data.index !== undefined ? data.index : findIndex(cellIds, (id) => id === cellID);
const x = filteredCellData.values[xAxis][pointIndex];
const y = filteredCellData.values[yAxis][pointIndex];
Comment on lines -315 to -316

@ShrimpCryptid ShrimpCryptid Sep 16, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple bugs were happening here.

  1. data.index is an index into the full cell data array and not the filtered cell data array. This meant that, if some cells were hidden, the indices of the cells after those points would shift, leading to the wrong x and y values being derived here. (The annotation would point to the wrong cell.)
  2. Additionally, if the pointIndex was now outside of the range of the filteredCellData, x and y would be undefined. L317 only checks for null values, so the points would be set to (undefined, undefined) and show up in the dead center of the plot.

I fixed this by:

  1. Indexing into allCellData instead of filteredCellData.
  2. Adding in a ?? nullish coalescing operator to change undefined to null, allowing the check on L317 to complete.
  3. Applied group filtering so annotations that are part of hidden groups are not shown.

const x = allCellData.values[xAxis][pointIndex] ?? null;
const y = allCellData.values[yAxis][pointIndex] ?? null;

// Apply filtering
const groupName = groupByCategoryNames[pointIndex] ?? null;
if (filtersToExclude.includes(groupName)) {
Comment thread
ShrimpCryptid marked this conversation as resolved.
return acc;
}

if (pointIndex >= 0 && x !== null && y !== null) {
acc.push({
cellID,
Expand Down
26 changes: 22 additions & 4 deletions src/containers/MainPlotContainer/test/selectors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ describe("MainPlotContainer selectors", () => {
const result: PlotlyAnnotation[] = getAnnotations(state);
expect(result).to.have.lengthOf(2);
});
});
describe("getAnnotations selector", () => {

it("it filters out non visible points", () => {
const state: State = {
...newMockState,
Expand All @@ -66,8 +65,26 @@ describe("MainPlotContainer selectors", () => {
const result: PlotlyAnnotation[] = getAnnotations(state);
expect(result).to.have.lengthOf(0);
});
});
describe("getAnnotations selector", () => {

it("it excludes filtered groups from annotations", () => {
const state: State = {
...newMockState,
selection: {
...newMockState.selection,
plotByOnX: "apical-proximity",
plotByOnY: "apical-proximity",
// 2 cells are selected, but the first (cell ID 1) will be
// excluded by filtering
selectedPoints: selectedCellFileInfo,
filterExclude: ["Paxillin"],
},
};
const result: PlotlyAnnotation[] = getAnnotations(state);
expect(result).to.have.lengthOf(1);
expect(result[0].pointIndex).to.equal(1);
expect(result[0].cellID).to.equal("2");
});

it("it still shows points whose values are zero", () => {
const state: State = {
...newMockState,
Expand All @@ -82,6 +99,7 @@ describe("MainPlotContainer selectors", () => {
expect(result).to.have.lengthOf(2);
});
});

describe("getXDisplayName", () => {
it("returns the displayName for a continuous feature", () => {
const state: State = {
Expand Down
Loading