mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-05 07:18:12 +08:00
correctly handle selection of trunctated categories (#781)
This commit is contained in:
@@ -27,10 +27,10 @@ class Category extends React.Component {
|
|||||||
const cat = categoricalSelection[metadataField];
|
const cat = categoricalSelection[metadataField];
|
||||||
const categoryCount = {
|
const categoryCount = {
|
||||||
// total number of categories in this dimension
|
// total number of categories in this dimension
|
||||||
totalCatCount: cat.numCategories,
|
totalCatCount: cat.numCategoryValues,
|
||||||
// number of selected options in this category
|
// number of selected options in this category
|
||||||
selectedCatCount: _.reduce(
|
selectedCatCount: _.reduce(
|
||||||
cat.categorySelected,
|
cat.categoryValueSelected,
|
||||||
(res, cond) => (cond ? res + 1 : res),
|
(res, cond) => (cond ? res + 1 : res),
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
@@ -91,7 +91,7 @@ class Category extends React.Component {
|
|||||||
const { categoricalSelection, metadataField } = this.props;
|
const { categoricalSelection, metadataField } = this.props;
|
||||||
|
|
||||||
const cat = categoricalSelection[metadataField];
|
const cat = categoricalSelection[metadataField];
|
||||||
const optTuples = sortedCategoryValues([...cat.categoryIndices]);
|
const optTuples = sortedCategoryValues([...cat.categoryValueIndices]);
|
||||||
return _.map(optTuples, (tuple, i) => (
|
return _.map(optTuples, (tuple, i) => (
|
||||||
<Value
|
<Value
|
||||||
optTuples={optTuples}
|
optTuples={optTuples}
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ class CategoryValue extends React.Component {
|
|||||||
if (!categoricalSelection) return null;
|
if (!categoricalSelection) return null;
|
||||||
|
|
||||||
const category = categoricalSelection[metadataField];
|
const category = categoricalSelection[metadataField];
|
||||||
const selected = category.categorySelected[categoryIndex];
|
const selected = category.categoryValueSelected[categoryIndex];
|
||||||
const count = category.categoryCounts[categoryIndex];
|
const count = category.categoryValueCounts[categoryIndex];
|
||||||
const value = category.categoryValues[categoryIndex];
|
const value = category.categoryValues[categoryIndex];
|
||||||
const displayString = String(
|
const displayString = String(
|
||||||
category.categoryValues[categoryIndex]
|
category.categoryValues[categoryIndex]
|
||||||
|
|||||||
@@ -30,15 +30,15 @@ const CategoricalSelection = (
|
|||||||
/*
|
/*
|
||||||
Set the specific category in this field to false
|
Set the specific category in this field to false
|
||||||
*/
|
*/
|
||||||
const newCategorySelected = Array.from(
|
const newCategoryValueSelected = Array.from(
|
||||||
state[action.metadataField].categorySelected
|
state[action.metadataField].categoryValueSelected
|
||||||
);
|
);
|
||||||
newCategorySelected[action.categoryIndex] = true;
|
newCategoryValueSelected[action.categoryIndex] = true;
|
||||||
const newCategoricalSelection = {
|
const newCategoricalSelection = {
|
||||||
...state,
|
...state,
|
||||||
[action.metadataField]: {
|
[action.metadataField]: {
|
||||||
...state[action.metadataField],
|
...state[action.metadataField],
|
||||||
categorySelected: newCategorySelected
|
categoryValueSelected: newCategoryValueSelected
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return newCategoricalSelection;
|
return newCategoricalSelection;
|
||||||
@@ -48,15 +48,15 @@ const CategoricalSelection = (
|
|||||||
/*
|
/*
|
||||||
Set the specific category in this field to false
|
Set the specific category in this field to false
|
||||||
*/
|
*/
|
||||||
const newCategorySelected = Array.from(
|
const newCategoryValueSelected = Array.from(
|
||||||
state[action.metadataField].categorySelected
|
state[action.metadataField].categoryValueSelected
|
||||||
);
|
);
|
||||||
newCategorySelected[action.categoryIndex] = false;
|
newCategoryValueSelected[action.categoryIndex] = false;
|
||||||
const newCategoricalSelection = {
|
const newCategoricalSelection = {
|
||||||
...state,
|
...state,
|
||||||
[action.metadataField]: {
|
[action.metadataField]: {
|
||||||
...state[action.metadataField],
|
...state[action.metadataField],
|
||||||
categorySelected: newCategorySelected
|
categoryValueSelected: newCategoryValueSelected
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return newCategoricalSelection;
|
return newCategoricalSelection;
|
||||||
@@ -70,8 +70,9 @@ const CategoricalSelection = (
|
|||||||
...state,
|
...state,
|
||||||
[action.metadataField]: {
|
[action.metadataField]: {
|
||||||
...state[action.metadataField],
|
...state[action.metadataField],
|
||||||
categorySelected: Array.from(
|
categorySelected: false,
|
||||||
state[action.metadataField].categorySelected
|
categoryValueSelected: Array.from(
|
||||||
|
state[action.metadataField].categoryValueSelected
|
||||||
).fill(false)
|
).fill(false)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -86,8 +87,9 @@ const CategoricalSelection = (
|
|||||||
...state,
|
...state,
|
||||||
[action.metadataField]: {
|
[action.metadataField]: {
|
||||||
...state[action.metadataField],
|
...state[action.metadataField],
|
||||||
categorySelected: Array.from(
|
categorySelected: true,
|
||||||
state[action.metadataField].categorySelected
|
categoryValueSelected: Array.from(
|
||||||
|
state[action.metadataField].categoryValueSelected
|
||||||
).fill(true)
|
).fill(true)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -183,10 +183,12 @@ const CrossfilterReducer = (
|
|||||||
case "categorical metadata filter select":
|
case "categorical metadata filter select":
|
||||||
case "categorical metadata filter deselect": {
|
case "categorical metadata filter deselect": {
|
||||||
const { categoricalSelection } = nextSharedState;
|
const { categoricalSelection } = nextSharedState;
|
||||||
|
const { world } = prevSharedState;
|
||||||
const cat = categoricalSelection[action.metadataField];
|
const cat = categoricalSelection[action.metadataField];
|
||||||
|
const col = world.obsAnnotations.col(action.metadataField);
|
||||||
return state.select(obsAnnoDimensionName(action.metadataField), {
|
return state.select(obsAnnoDimensionName(action.metadataField), {
|
||||||
mode: "exact",
|
mode: "exact",
|
||||||
values: ControlsHelpers.selectedValuesForCategory(cat)
|
values: ControlsHelpers.selectedValuesForCategory(cat, col)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,16 +21,16 @@ Remember that option values can be ANY js type, except undefined/null.
|
|||||||
{
|
{
|
||||||
_category_name_1: {
|
_category_name_1: {
|
||||||
// map of option value to index
|
// map of option value to index
|
||||||
categoryIndices: Map([
|
categoryValueIndices: Map([
|
||||||
catval1: index,
|
catval1: index,
|
||||||
...
|
...
|
||||||
])
|
])
|
||||||
|
|
||||||
// index->selection true/false state
|
// index->selection true/false state
|
||||||
categorySelected: [ true/false, true/false, ... ]
|
categoryValueSelected: [ true/false, true/false, ... ]
|
||||||
|
|
||||||
// number of options
|
// number of options
|
||||||
numCategories: number,
|
numCategoryValues: number,
|
||||||
|
|
||||||
// isTruncated - true if the options for selection has
|
// isTruncated - true if the options for selection has
|
||||||
// been truncated (ie, was too large to implement)
|
// been truncated (ie, was too large to implement)
|
||||||
@@ -65,18 +65,21 @@ export function createCategoricalSelection(maxCategoryItems, world) {
|
|||||||
key !== "name" &&
|
key !== "name" &&
|
||||||
summary.categories.length < maxCategoryItems;
|
summary.categories.length < maxCategoryItems;
|
||||||
if (isSelectableCategory) {
|
if (isSelectableCategory) {
|
||||||
const [categoryValues, categoryCounts] = topNCategories(summary);
|
const [categoryValues, categoryValueCounts] = topNCategories(summary);
|
||||||
const categoryIndices = new Map(categoryValues.map((v, i) => [v, i]));
|
const categoryValueIndices = new Map(
|
||||||
const numCategories = categoryIndices.size;
|
categoryValues.map((v, i) => [v, i])
|
||||||
const categorySelected = new Array(numCategories).fill(true);
|
);
|
||||||
|
const numCategoryValues = categoryValueIndices.size;
|
||||||
|
const categoryValueSelected = new Array(numCategoryValues).fill(true);
|
||||||
const isTruncated = categoryValues.length < summary.numCategories;
|
const isTruncated = categoryValues.length < summary.numCategories;
|
||||||
res[key] = {
|
res[key] = {
|
||||||
categoryValues, // array: of natively typed category values
|
categoryValues, // array: of natively typed category values
|
||||||
categoryIndices, // map: category value (native type) -> category index
|
categoryValueIndices, // map: category value (native type) -> category index
|
||||||
categorySelected, // array: t/f selection state
|
categoryValueSelected, // array: t/f selection state
|
||||||
numCategories, // number: of categories
|
numCategoryValues, // number: of values in the category
|
||||||
isTruncated, // bool: true if list was truncated
|
isTruncated, // bool: true if list was truncated
|
||||||
categoryCounts // array: cardinality of each category
|
categoryValueCounts, // array: cardinality of each category,
|
||||||
|
categorySelected: true // bool - default state for entire category
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,12 +91,26 @@ export function createCategoricalSelection(maxCategoryItems, world) {
|
|||||||
given a categoricalSelection, return the list of all category values
|
given a categoricalSelection, return the list of all category values
|
||||||
where selection state is true (ie, they are selected).
|
where selection state is true (ie, they are selected).
|
||||||
*/
|
*/
|
||||||
export function selectedValuesForCategory(categorySelectionState) {
|
export function selectedValuesForCategory(categorySelectionState, dfColumn) {
|
||||||
const selectedValues = _([...categorySelectionState.categoryIndices])
|
const {
|
||||||
.filter(tuple => categorySelectionState.categorySelected[tuple[1]])
|
categorySelected,
|
||||||
.map(tuple => tuple[0])
|
categoryValueSelected,
|
||||||
.value();
|
categoryValueIndices
|
||||||
return selectedValues;
|
} = categorySelectionState;
|
||||||
|
let selectedValues;
|
||||||
|
if (categorySelected) {
|
||||||
|
selectedValues = new Set(dfColumn.summarize().categories);
|
||||||
|
} else {
|
||||||
|
selectedValues = new Set();
|
||||||
|
}
|
||||||
|
categoryValueIndices.forEach((catIndex, catValue) => {
|
||||||
|
if (!categoryValueSelected[catIndex]) {
|
||||||
|
selectedValues.delete(catValue);
|
||||||
|
} else {
|
||||||
|
selectedValues.add(catValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return [...selectedValues.values()];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
Reference in New Issue
Block a user