diff --git a/client/package-lock.json b/client/package-lock.json index 823e7ed9..d6f7e5c4 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -5202,6 +5202,17 @@ "randomatic": "^3.0.0", "repeat-element": "^1.1.2", "repeat-string": "^1.5.2" + }, + "dependencies": { + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + } } }, "finalhandler": { @@ -6776,13 +6787,9 @@ "dev": true }, "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-obj": { "version": "1.0.1", diff --git a/client/package.json b/client/package.json index 872d5942..669d5ab0 100644 --- a/client/package.json +++ b/client/package.json @@ -37,6 +37,7 @@ "fuzzysort": "^1.1.4", "gl-mat4": "^1.1.4", "gl-matrix": "^2.7.1", + "is-number": "^7.0.0", "key-pressed": "0.0.1", "lodash": "^4.17.4", "memoize-one": "^4.0.0", diff --git a/client/src/components/categorical/category.js b/client/src/components/categorical/category.js index 5d56f350..4928e8a3 100644 --- a/client/src/components/categorical/category.js +++ b/client/src/components/categorical/category.js @@ -6,7 +6,7 @@ import { Button, Tooltip } from "@blueprintjs/core"; import * as globals from "../../globals"; import Value from "./value"; -import alphabeticallySortedValues from "./util"; +import sortedCategoryValues from "./util"; @connect(state => ({ colorAccessor: state.controls.colorAccessor, @@ -86,9 +86,10 @@ class Category extends React.Component { const { categoricalSelectionState, metadataField } = this.props; const cat = categoricalSelectionState[metadataField]; - const optTuples = alphabeticallySortedValues([...cat.categoryIndices]); + const optTuples = sortedCategoryValues([...cat.categoryIndices]); return _.map(optTuples, (tuple, i) => ( d[1]))]) + .range([0, width]); + + let currentOffset = 0; + + const stacks = categoricalSelectionState[colorAccessor].categoryValues.map( + d => { + const o = occupancy.get(d); + + const scaledValue = x(o); + + const stackItem = { + key: d, + value: o || 0, + rectWidth: o ? scaledValue : 0, + offset: currentOffset, + fill: o ? colorScale(categories.indexOf(d)) : "rgb(255,255,255)" + }; + currentOffset += o ? scaledValue : 0; + return stackItem; + } + ); + + return ( + + {stacks.map(d => ( + + ))} + + ); + } +} + +export default Occupancy; diff --git a/client/src/components/categorical/util.js b/client/src/components/categorical/util.js index 58707776..e645a5e4 100644 --- a/client/src/components/categorical/util.js +++ b/client/src/components/categorical/util.js @@ -3,9 +3,33 @@ // values is [ [optVal, optIdx], ...] // index is range array // return sorted index -export default values => - values.sort((a, b) => { + +import isNumber from "is-number"; +import _ from "lodash"; + +const sortedCategoryValues = values => { + /* this sort could be memoized for perf */ + + const strings = []; + const ints = []; + + _.forEach(values, v => { + if (isNumber(v[0])) { + ints.push(v); + } else { + strings.push(v); + } + }); + + strings.sort((a, b) => { const textA = String(a[0]).toUpperCase(); const textB = String(b[0]).toUpperCase(); return textA < textB ? -1 : textA > textB ? 1 : 0; }); + + ints.sort((a, b) => +a[0] - +b[0]); + + return ints.concat(strings); +}; + +export default sortedCategoryValues; diff --git a/client/src/components/categorical/value.js b/client/src/components/categorical/value.js index 444b1b3c..dd7fafe7 100644 --- a/client/src/components/categorical/value.js +++ b/client/src/components/categorical/value.js @@ -2,12 +2,16 @@ import { connect } from "react-redux"; import React from "react"; import _ from "lodash"; +import Occupancy from "./occupancy"; +import { countCategoryValues2D } from "../../util/stateManager/worldUtil"; +import * as globals from "../../globals"; @connect(state => ({ categoricalSelectionState: state.controls.categoricalSelectionState, colorScale: state.controls.colorScale, colorAccessor: state.controls.colorAccessor, - schema: _.get(state.controls.world, "schema", null) + schema: _.get(state.controls.world, "schema", null), + world: state.controls.world })) class CategoryValue extends React.Component { toggleOff() { @@ -36,7 +40,8 @@ class CategoryValue extends React.Component { colorAccessor, colorScale, i, - schema + schema, + world } = this.props; if (!categoricalSelectionState) return null; @@ -50,15 +55,24 @@ class CategoryValue extends React.Component { ).valueOf(); /* this is the color scale, so add swatches below */ - const c = metadataField === colorAccessor; + const isColorBy = metadataField === colorAccessor; let categories = null; + let occupancy = null; - if (c && schema) { + if (isColorBy && schema) { categories = _.filter(schema.annotations.obs, { name: colorAccessor })[0].categories; } + if (colorAccessor && !isColorBy) { + occupancy = countCategoryValues2D( + metadataField, + colorAccessor, + world.obsAnnotations + ); + } + return (
+ + {colorAccessor && + !isColorBy && + categoricalSelectionState[colorAccessor] ? ( + + ) : null} +
{count} @@ -95,7 +124,7 @@ class CategoryValue extends React.Component { width: 11, height: 11, backgroundColor: - c && categories + isColorBy && categories ? colorScale(categories.indexOf(value)) : "inherit" }}