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:
Severiano Badajoz
2020-01-22 13:40:22 -08:00
committed by GitHub
parent 69c639b14d
commit 8d725b1ad9
18 changed files with 541 additions and 233 deletions
+101 -44
View File
@@ -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);