mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-30 03:48:12 +08:00
centroid labels (#872)
* refactor reducer to no longer support hover state and hold many labels * refactor to generate centroidCoordinates for all values of a category * create hash for function and memoize export * create button to display all labels for a category * clear state * create label for each thing * calculate on each value * change to in place modification of map * switch to for loop with iterator instead of forEach * use map from centroidLabel instead of creating copy * adapt for map * utilize tarrays * begin documentation * disable centroids if in zoom mode * clean up * persist uncalc coordinates * document * clean up and document * cleanup and document * fix * fix undefined labels and document changes * fix first element skip * fix conditional recalc * rename centroidLabel -> centroidLabels * break out dilation on hover to new reducer * numerous styling changes for readability * change centroid icon * remove colorAccessor from parameters * recalc centroids on world change * make label toggle undoable * remove unused import * highlight labels on hover * remove special characters from svg id * lighten backdrop * only generate new centroids if they pre-exist * fix issue with spaces in catagorical value name * add label buttons to menubar * change reducer to use colorAccessor and have single toggle * fix check to see if svg should be rendered * move svg overlays onto a single svg layer * dilate on label hover * remove logs * allow centroid to update along side regl renders * allow actions to pass through svg if in zoom mode * remove artifact from circle * remove comment * remove disabling of centroid button * fix conditional map to screen * make styling label conditions stricter * prettier * refactor onto master * refactor computePointFlags() to use pointDilation store * notify when viewport changes * move svg attributes out of lasso setup and prevent rerenders/writes * begin playing with transform matrix * first solution for camera interaction * create transform using nested groups * semi-working method using nested groups with transforms * inversely scale text * properly do final transform * cleanup dead / test code * reinstate original functionality * breakout centroid labels labels into separate component * default toggle on for testing * separate lasso and centroid layers * remove unnecessary attributes, working hover * dilation on label hover * fix dilation on scatterplot * add dilation on label hover * break overlay into separate component * make overlay agnostic to children * move label mouse actions to centroidlabels component, add overlay state * remove lasso on switch to camera * disallow user selection * fix reducer * fix subset with continuous color error * reset labels on color by continuous * revert centroids on by default * refactor for nested restructuring * remove update checking * remove unused method * readd deleted hover delay * remove old centroid setup * remove centroid from undoable * cleanup dead code * remove dead code * rollback unnecessary changes * begin adding annotation functionality * add annotation functionality * add reset and undo functionality * change centroids on layout change * don't create label for unassigned * add comment pointing out POI for performance * touch up matrix transform comment * add comment explaining coordinate space and children's assumed space * remove dead code * switch to pure component * connect centroidLabels to redux * clean up camera check and null result * tool tip change * rename centroid toggle and the like * fix the misalignment of buttons, also make blueprint use consistent * fix comment spelling mistakes * introduce variable for cleaner logic expressions and state assignment * alter tooltip text to back color by interaction * remove manual iterator manipulation for forEach() * remove debounce * nit fix * tooltip wording fix * lint
This commit is contained in:
+101
-44
@@ -1,61 +1,118 @@
|
||||
import quantile from "./quantile";
|
||||
import { memoize } from "./dataframe/util";
|
||||
import { unassignedCategoryLabel } from "../globals";
|
||||
|
||||
/*
|
||||
Centroid coordinate calculation
|
||||
*/
|
||||
|
||||
/* Unused - please cleanup
|
||||
const calcMeanCentroid = (world, annoName, annoValue, layoutDimNames) => {
|
||||
const centroid = { x: 0, y: 0, size: 0 };
|
||||
const annoArray = world.obsAnnotations.col(annoName).asArray();
|
||||
const layoutXArray = world.obsLayout.col(layoutDimNames[0]).asArray();
|
||||
const layoutYArray = world.obsLayout.col(layoutDimNames[1]).asArray();
|
||||
/*
|
||||
calcMedianCentroid goes through a given metadata category
|
||||
fetches each cell's coordinates grouping by category value.
|
||||
|
||||
for (let i = 0, len = annoArray.length; i < len; i += 1) {
|
||||
if (annoArray[i] === annoValue) {
|
||||
centroid.x += layoutXArray[i];
|
||||
centroid.y += layoutYArray[i];
|
||||
centroid.size += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (centroid[2] !== 0) {
|
||||
centroid.x /= centroid.size;
|
||||
centroid.y /= centroid.size;
|
||||
}
|
||||
|
||||
return [centroid.x, centroid.y];
|
||||
};
|
||||
It then calculates the median value and puts that in the array
|
||||
*/
|
||||
|
||||
const calcMedianCentroid = (world, annoName, annoValue, layoutDimNames) => {
|
||||
const centroidX = [];
|
||||
const centroidY = [];
|
||||
let hasFinite = false;
|
||||
const calcMedianCentroid = (
|
||||
obsAnnotations,
|
||||
obsLayout,
|
||||
categoryName,
|
||||
layoutDimNames,
|
||||
categoricalSelection,
|
||||
schemaObsByName
|
||||
) => {
|
||||
const categoryArray = obsAnnotations.col(categoryName).asArray();
|
||||
|
||||
const annoArray = world.obsAnnotations.col(annoName).asArray();
|
||||
const layoutXArray = world.obsLayout.col(layoutDimNames[0]).asArray();
|
||||
const layoutYArray = world.obsLayout.col(layoutDimNames[1]).asArray();
|
||||
const layoutXArray = obsLayout.col(layoutDimNames[0]).asArray();
|
||||
const layoutYArray = obsLayout.col(layoutDimNames[1]).asArray();
|
||||
const coordinates = new Map();
|
||||
|
||||
// Iterate over all the cells in the category
|
||||
for (let i = 0, len = categoryArray.length; i < len; i += 1) {
|
||||
const categoryValue = categoryArray[i];
|
||||
|
||||
// Get the index of the categoryValue within the category
|
||||
// If the category is truncated and this value is removed,
|
||||
// it will not be assigned a category value and will not be
|
||||
// labeled on the graph
|
||||
const categoryValueIndex = categoricalSelection[
|
||||
categoryName
|
||||
].categoryValueIndices.get(categoryValue);
|
||||
|
||||
// Check to see if the current category is a user created annotation
|
||||
// if the user created this category, do not create a label for the `unassigned` value
|
||||
const isUserAnno = schemaObsByName[categoryName].writable;
|
||||
|
||||
if (
|
||||
categoryValueIndex !== undefined &&
|
||||
!(isUserAnno && categoryValue === unassignedCategoryLabel)
|
||||
) {
|
||||
// Get the number of cells which are in the category value
|
||||
const numInCategoryValue =
|
||||
categoricalSelection[categoryName].categoryValueCounts[
|
||||
categoryValueIndex
|
||||
];
|
||||
|
||||
// Create/fetch the valueArray,
|
||||
// which is what the key points to in the `coordinates` hashmap
|
||||
const valueArray = coordinates.get(categoryValue) || [
|
||||
false, // hasFinite
|
||||
0, // index
|
||||
new Float32Array(numInCategoryValue), // x coordinates
|
||||
new Float32Array(numInCategoryValue) // y coordinates
|
||||
];
|
||||
const index = valueArray[1];
|
||||
let hasFinite = valueArray[0];
|
||||
|
||||
for (let i = 0, len = annoArray.length; i < len; i += 1) {
|
||||
if (annoArray[i] === annoValue) {
|
||||
hasFinite =
|
||||
Number.isFinite(layoutXArray[i]) || Number.isFinite(layoutYArray[i])
|
||||
? true
|
||||
: hasFinite;
|
||||
centroidX.push(layoutXArray[i]);
|
||||
centroidY.push(layoutYArray[i]);
|
||||
hasFinite ||
|
||||
(Number.isFinite(layoutXArray[i]) && Number.isFinite(layoutYArray[i]));
|
||||
|
||||
valueArray[0] = hasFinite;
|
||||
valueArray[1] = index + 1;
|
||||
valueArray[2][index] = layoutXArray[i];
|
||||
valueArray[3][index] = layoutYArray[i];
|
||||
|
||||
coordinates.set(categoryValue, valueArray);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasFinite) {
|
||||
const medianX = quantile([0.5], Float64Array.from(centroidX));
|
||||
const medianY = quantile([0.5], Float64Array.from(centroidY));
|
||||
|
||||
return [medianX, medianY];
|
||||
}
|
||||
|
||||
return null;
|
||||
// Iterate over the recently created map
|
||||
coordinates.forEach((value, key) => {
|
||||
// If there are coordinates for this cateogrical value,
|
||||
// and there is a finite coordinate for the category value
|
||||
if (value[2].length > 0 && value[3].length > 0 && value[0]) {
|
||||
// Find the median x and y coordinate
|
||||
// and insert them into the first two indices
|
||||
value[0] = quantile([0.5], value[2])[0];
|
||||
value[1] = quantile([0.5], value[3])[0];
|
||||
// Remove the last two elements (where the arrays of coordinates were)
|
||||
value.pop();
|
||||
value.pop();
|
||||
} else {
|
||||
// remove the entry if not
|
||||
coordinates.delete(key);
|
||||
}
|
||||
});
|
||||
// return the map: categoricalValue -> [medianXCoordinate, medianYCoordinate]
|
||||
return coordinates;
|
||||
};
|
||||
|
||||
export default calcMedianCentroid;
|
||||
// A simple function to hash the parameters
|
||||
// (not 100% on world hash, Bruce will have to check this one out)
|
||||
const hashMedianCentroid = (
|
||||
obsAnnotations,
|
||||
obsLayout,
|
||||
categoryName,
|
||||
layoutDimNames,
|
||||
categorySelection,
|
||||
schemaObsByName
|
||||
) => {
|
||||
return `${obsAnnotations.__id}+${
|
||||
obsLayout.__id
|
||||
}:${categoryName}:${layoutDimNames}:${Object.keys(
|
||||
categorySelection
|
||||
)}:${Object.keys(schemaObsByName)}`;
|
||||
};
|
||||
// export the mmemoized calculation function
|
||||
export default memoize(calcMedianCentroid, hashMedianCentroid);
|
||||
|
||||
Reference in New Issue
Block a user