Skip to content
Open
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
53 changes: 47 additions & 6 deletions src/containers/MainPlotContainer/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,14 @@ import {
getFilteredConnectByCategoryValues,
getLineMovingAverageWindow,
getConnectByFeature,
getConnectByCategory,
} from "../../state/selection/selectors";
import { MainPlotSettings, SelectedPointData, TickConversion } from "../../state/selection/types";
import {
ColorForPlot,
MainPlotSettings,
SelectedPointData,
TickConversion,
} from "../../state/selection/types";
import {
AnnotationData,
ContinuousPlotData,
Expand Down Expand Up @@ -191,8 +197,23 @@ export const getLinePlotData = createSelector(
getFilteredConnectByFeatureValues,
getShowConnectLines,
getLineMovingAverageWindow,
// Line group coloring
getCategoryGroupColorsAndNames,
getColorBySelection,
getConnectByCategory,
],
calculateLinePlotData
(xValues, yValues, catValues, featValues, showLines, window, colors, colorBy, connectBy) =>
calculateLinePlotData(
xValues,
yValues,
catValues,
featValues,
showLines,
window,
colors,
colorBy,
connectBy
)
);

export function calculateLinePlotData(
Expand All @@ -201,7 +222,10 @@ export function calculateLinePlotData(
connectByCategoryValues: (number | null)[],
connectByFeatureValues: (number | null)[],
showConnectingLines: boolean,
movingAverageWindow: number
movingAverageWindow: number,
colorsForPlot: ColorForPlot[],
colorByFeature: string | number,
connectByCategory: string
): LinePlotData[] | null {
if (!showConnectingLines) {
return null;
Expand All @@ -225,7 +249,7 @@ export function calculateLinePlotData(
}

const lineData: LinePlotData[] = [];
for (const indices of indicesByGroup.values()) {
for (const [group, indices] of indicesByGroup.entries()) {
const x: (number | null)[] = [];
const y: (number | null)[] = [];
// Sort each category's data by the feature values.
Expand All @@ -238,7 +262,7 @@ export function calculateLinePlotData(
x.push(xValues[i]);
y.push(yValues[i]);
}
lineData.push({ x: x, y: y });
lineData.push({ x: x, y: y, groupIndex: group });
}

// Apply moving average to each line
Expand All @@ -249,6 +273,17 @@ export function calculateLinePlotData(
}
}

if (colorByFeature === connectByCategory) {
// Apply group colors to the lines
const groupToColors: { [key: number]: string } = {};
for (const colorSetting of colorsForPlot) {
groupToColors[colorSetting.key] = colorSetting.color;
}
for (const line of lineData) {
line.color = groupToColors[line.groupIndex];
}
}

return lineData;
}

Expand Down Expand Up @@ -403,6 +438,12 @@ function makeScatterPlotData(

// TODO: Add the ability to adjust the line settings via an additional selector
function makeLinePlotTrace(data: LinePlotData, settings: MainPlotSettings): Partial<PlotData> {
let color = settings.connectionLineDefaultColor;
if (data.color) {
// Color is a hex string; apply transparency
const opacityHex = Math.round(settings.unselectedCircleOpacity * 255);
color = data.color + opacityHex.toString(16).padStart(2, "0");
}
return {
type: "scattergl",
mode: "lines",
Expand All @@ -412,7 +453,7 @@ function makeLinePlotTrace(data: LinePlotData, settings: MainPlotSettings): Part
showlegend: false,
line: {
width: settings.connectionLineWidth,
color: settings.connectionLineDefaultColor,
color: color,
},
};
}
Expand Down
133 changes: 113 additions & 20 deletions src/containers/MainPlotContainer/test/selectors.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, it, expect } from "vitest";

import { mockState, selectedCellFileInfo } from "../../../state/test/mocks";
import type { State, AnnotationData } from "../../../state/types";
import type { State, AnnotationData, LinePlotData } from "../../../state/types";
import {
calculateLinePlotData,
getAnnotations,
Expand All @@ -14,6 +14,7 @@ import {
} from "../selectors";
import type { PlotlyAnnotation } from "../../../components/MainPlot";
import { CELL_ID_KEY, PALETTE } from "../../../constants";
import type { ColorForPlot } from "../../../state/selection/types";

describe("MainPlotContainer selectors", () => {
const newMockState = mockState;
Expand Down Expand Up @@ -315,13 +316,66 @@ describe("MainPlotContainer selectors", () => {
});

describe("calculateLinePlotData", () => {
const defaultParams = {
xValues: [],
yValues: [],
connectByCategoryValues: [],
connectByFeatureValues: [],
showConnectingLines: true,
movingAverageWindow: 1,
colorsForPlot: [],
colorByFeature: "color_feature",
connectByCategory: "category_feature",
};

function calculate(
params: Partial<{
xValues: (number | null)[];
yValues: (number | null)[];
connectByCategoryValues: (number | null)[];
connectByFeatureValues: (number | null)[];
showConnectingLines: boolean;
movingAverageWindow: number;
colorsForPlot: ColorForPlot[];
colorByFeature: string;
connectByCategory: string;
}>
): LinePlotData[] | null {
const paramsWithDefaults = { ...defaultParams, ...params };
return calculateLinePlotData(
paramsWithDefaults.xValues,
paramsWithDefaults.yValues,
paramsWithDefaults.connectByCategoryValues,
paramsWithDefaults.connectByFeatureValues,
paramsWithDefaults.showConnectingLines,
paramsWithDefaults.movingAverageWindow,
paramsWithDefaults.colorsForPlot,
paramsWithDefaults.colorByFeature,
paramsWithDefaults.connectByCategory
);
}

it("handles empty data", () => {
const result = calculateLinePlotData([], [], [], [], true, 1);
const result = calculate({
xValues: [],
yValues: [],
connectByCategoryValues: [],
connectByFeatureValues: [],
showConnectingLines: true,
movingAverageWindow: 1,
});
expect(result).to.deep.equal([]);
});

it("returns null when connecting lines are disabled", () => {
const result = calculateLinePlotData([1, 2], [3, 4], [1, 1], [5, 6], false, 1);
const result = calculate({
xValues: [1, 2],
yValues: [3, 4],
connectByCategoryValues: [1, 1],
connectByFeatureValues: [5, 6],
showConnectingLines: false,
movingAverageWindow: 1,
});
expect(result).to.equal(null);
});

Expand All @@ -330,18 +384,18 @@ describe("MainPlotContainer selectors", () => {
const yValues = [6, 7, 8, 9, 10];
const connectByCategoryValues = [1, 1, 2, 2, 3];
const connectByFeatureValues = [1, 2, 3, 4, 5];
const result = calculateLinePlotData(
const result = calculate({
xValues,
yValues,
connectByCategoryValues,
connectByFeatureValues,
true,
1
);
showConnectingLines: true,
movingAverageWindow: 1,
});
expect(result).to.deep.equal([
{ x: [1, 2], y: [6, 7] },
{ x: [3, 4], y: [8, 9] },
{ x: [5], y: [10] },
{ x: [1, 2], y: [6, 7], groupIndex: 1 },
{ x: [3, 4], y: [8, 9], groupIndex: 2 },
{ x: [5], y: [10], groupIndex: 3 },
]);
});

Expand All @@ -350,31 +404,70 @@ describe("MainPlotContainer selectors", () => {
const yValues = [1, 2, 3, 4, 5];
const connectByCategoryValues = [1, 1, 1, 1, 1];
const connectByFeatureValues = [5, 4, 3, 2, 1];
const result = calculateLinePlotData(
const result = calculate({
xValues,
yValues,
connectByCategoryValues,
connectByFeatureValues,
true,
1
);
expect(result).to.deep.equal([{ x: [5, 4, 3, 2, 1], y: [5, 4, 3, 2, 1] }]);
showConnectingLines: true,
movingAverageWindow: 1,
});
expect(result).to.deep.equal([
{ x: [5, 4, 3, 2, 1], y: [5, 4, 3, 2, 1], groupIndex: 1 },
]);
});

it("applies moving average to x and y values", () => {
const xValues = [10, 20, 30, 40, 50];
const yValues = [10, 20, 30, 40, 50];
const connectByCategoryValues = [1, 1, 1, 1, 1];
const connectByFeatureValues = [1, 2, 3, 4, 5];
const result = calculateLinePlotData(
const result = calculate({
xValues,
yValues,
connectByCategoryValues,
connectByFeatureValues,
true,
3
);
expect(result).to.deep.equal([{ x: [15, 20, 30, 40, 45], y: [15, 20, 30, 40, 45] }]);
showConnectingLines: true,
movingAverageWindow: 3,
});
expect(result).to.deep.equal([
{ x: [15, 20, 30, 40, 45], y: [15, 20, 30, 40, 45], groupIndex: 1 },
]);
});

it("applies colors when colorby feature matches category", () => {
const xValues = [1, 2, 3, 4, 5];
const yValues = [1, 2, 3, 4, 5];
const connectByCategoryValues = [1, 2, 3, 4, 5];
const connectByFeatureValues = [1, 2, 3, 4, 5];
const connectByCategory = "category_feature";
const colorByFeature = "category_feature";
const colorsForPlot: ColorForPlot[] = [
{ color: "red", name: "Group 1", label: "Group 1", key: 1 },
{ color: "green", name: "Group 2", label: "Group 2", key: 2 },
{ color: "#FF00FF", name: "Group 5", label: "Group 5", key: 5 },
{ color: "#FFFF00", name: "Group 4", label: "Group 4", key: 4 },
{ color: "#0000FF", name: "Group 3", label: "Group 3", key: 3 },
];

const result = calculate({
xValues,
yValues,
connectByCategoryValues,
connectByFeatureValues,
showConnectingLines: true,
movingAverageWindow: 1,
connectByCategory,
colorByFeature,
colorsForPlot,
});
expect(result).to.deep.equal([
{ x: [1], y: [1], groupIndex: 1, color: "red" },
{ x: [2], y: [2], groupIndex: 2, color: "green" },
{ x: [3], y: [3], groupIndex: 3, color: "#0000FF" },
{ x: [4], y: [4], groupIndex: 4, color: "#FFFF00" },
{ x: [5], y: [5], groupIndex: 5, color: "#FF00FF" },
]);
});
});
});
2 changes: 2 additions & 0 deletions src/state/selection/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ export const getCategoryGroupColorsAndNames = createSelector(
color: option.color,
name: id,
label: option.name,
key: Number.parseFloat(key),

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.

Stores the key (value) of the group when calculating getCategoryGroupColorsAndNames

};
});

Expand All @@ -211,6 +212,7 @@ export const getCategoryGroupColorsAndNames = createSelector(
color: MISSING_CATEGORY_COLOR,
label: MISSING_CATEGORY_LABEL,
name: categoryToGroupBy === categoryToColorBy ? "" : "null",
key: NaN,
};
return [...colorForPlot, missingColorOption];
}
Expand Down
5 changes: 5 additions & 0 deletions src/state/selection/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ export interface ColorForPlot {
color: string;
name: string;
label: string;
/**
* Original numeric value that corresponds with this group in the feature
* data.
*/
key: number;
}

export interface RequestFileInfoByCellIDAction {
Expand Down
3 changes: 2 additions & 1 deletion src/state/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ export interface GroupedPlotData {
export interface LinePlotData {
x: (number | null)[];
y: (number | null)[];
// TODO: Add additional per-line options here, like group color
groupIndex: number;
color?: string;
}

export interface SelectedGroup {
Expand Down
Loading