correctly handle selection of trunctated categories (#781)

This commit is contained in:
Bruce Martin
2019-05-23 08:46:22 -07:00
committed by GitHub
parent 49af278de7
commit ef7c26e799
5 changed files with 56 additions and 35 deletions
@@ -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}
+2 -2
View File
@@ -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]
+14 -12
View File
@@ -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)
} }
}; };
+3 -1
View File
@@ -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)
}); });
} }
+34 -17
View File
@@ -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()];
} }
/* /*