Cluster Occupancy (n dim cube) (#513)

* create occupancy component

* occupancy working

* centering, width, flex spacing

* add is-number

* extend sort to cover string + int

* proof of concept sorted occupancy

* handle undefined occupancy entry, cleanup

* only render occupancy when colorby is cat

* cleanup unused vars

* cleanup
This commit is contained in:
Colin Megill
2018-12-17 14:19:48 -05:00
committed by GitHub
parent aaa60bc303
commit 0ef6c36f4c
6 changed files with 152 additions and 17 deletions
+14 -7
View File
@@ -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",
+1
View File
@@ -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",
@@ -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) => (
<Value
optTuples={optTuples}
key={tuple[1]}
metadataField={metadataField}
categoryIndex={tuple[1]}
@@ -0,0 +1,73 @@
// jshint esversion: 6
import React from "react";
import _ from "lodash";
import { connect } from "react-redux";
import * as d3 from "d3";
@connect()
class Occupancy extends React.Component {
render() {
const {
occupancy,
colorScale,
categoricalSelectionState,
colorAccessor,
schema
} = this.props;
const width = 100;
const height = 11;
const categories = _.filter(schema.annotations.obs, {
name: colorAccessor
})[0].categories;
const x = d3
.scaleLinear()
/* get all the keys d[1] as an array, then find the sum */
.domain([0, d3.sum(Array.from(occupancy, d => 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 (
<svg
style={{
marginRight: 5,
width,
height
}}
>
{stacks.map(d => (
<rect
key={d.key}
width={d.rectWidth}
height={height}
x={d.offset}
title={d.metadataField}
fill={d.fill}
/>
))}
</svg>
);
}
}
export default Occupancy;
+26 -2
View File
@@ -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;
+35 -6
View File
@@ -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 (
<div
key={i}
@@ -72,7 +86,10 @@ class CategoryValue extends React.Component {
style={{
margin: 0,
padding: 0,
userSelect: "none"
userSelect: "none",
width: globals.leftSidebarWidth - 130,
display: "flex",
justifyContent: "space-between"
}}
>
<label className="bp3-control bp3-checkbox">
@@ -86,6 +103,18 @@ class CategoryValue extends React.Component {
<span className="bp3-control-indicator" />
{displayString}
</label>
<span style={{ flexShrink: 0 }}>
{colorAccessor &&
!isColorBy &&
categoricalSelectionState[colorAccessor] ? (
<Occupancy
occupancy={occupancy.get(
category.categoryValues[categoryIndex]
)}
{...this.props}
/>
) : null}
</span>
</div>
<span>
<span>{count}</span>
@@ -95,7 +124,7 @@ class CategoryValue extends React.Component {
width: 11,
height: 11,
backgroundColor:
c && categories
isColorBy && categories
? colorScale(categories.indexOf(value))
: "inherit"
}}