color by categorical

This commit is contained in:
Colin Megill
2017-12-23 15:24:16 -08:00
parent bc07ebd23d
commit ff7183d938
4 changed files with 121 additions and 54 deletions
+69 -36
View File
@@ -8,38 +8,63 @@ import SectionHeader from "../framework/sectionHeader"
import Value from "./value"; import Value from "./value";
import { alphabeticallySortedValues } from "./util"; import { alphabeticallySortedValues } from "./util";
const Category = ({metadataField, values}) => ( @connect()
<div style={{ class Category extends React.Component {
display: "flex", handleColorChange() {
alignItems: "baseline", this.props.dispatch({
maxWidth: globals.maxControlsWidth, type: "color by categorical metadata",
}}> colorAccessor: this.props.metadataField
<p style={{ })
flexShrink: 0, }
width: 200, render() {
textAlign: "right", return (
fontFamily: globals.accentFont, <div style={{
fontStyle: "italic", // display: "flex",
marginRight: 20, // alignItems: "baseline",
}}> maxWidth: globals.maxControlsWidth,
{metadataField}: }}>
</p> <div style={{
<div> display: "flex",
{ justifyContent: "space-between",
_.map(alphabeticallySortedValues(values), (v, i) => { alignItems: "baseline"
return ( }}>
<Value <p style={{
key={v} // flexShrink: 0,
metadataField={metadataField} width: 200,
count={values[v]} fontWeight: 500,
value={v} // textAlign: "right",
i={i} /> // fontFamily: globals.accentFont,
) // fontStyle: "italic",
}) marginRight: 20,
} }}>
</div> {this.props.metadataField}:
</div> </p>
) <button
onClick={this.handleColorChange.bind(this)}
style={{
font: globals.accentFont,
fontStyle: "italic",
fontSize: 10
}}> as color scale </button>
</div>
<div>
{
_.map(alphabeticallySortedValues(this.props.values), (v, i) => {
return (
<Value
key={v}
metadataField={this.props.metadataField}
count={this.props.values[v]}
value={v}
i={i} />
)
})
}
</div>
</div>
)
}
}
@connect((state) => { @connect((state) => {
@@ -59,14 +84,22 @@ class Categories extends React.Component {
render () { render () {
if (!this.props.ranges) return null if (!this.props.ranges) return null
return ( return (
<div style={{marginTop: 50}}> <div style={{
<SectionHeader text="Categorical Metadata"/> position: "fixed",
left: 40,
height: 200,
}}>
{ {
_.map(this.props.ranges, (value, key) => { _.map(this.props.ranges, (value, key) => {
const isColorField = key.includes("color") || key.includes("Color");
if ( if (
value.options && value.options &&
key !== "CellName" key !== "CellName" &&
!isColorField
) { ) {
return ( return (
<Category <Category
@@ -85,7 +118,7 @@ class Categories extends React.Component {
export default Categories; export default Categories;
/* /*
<SectionHeader text="Categorical Metadata"/>
[on off] toggle hide deselected filters (shows a menu vs shows what you ordered in compact/narrative form. fold out animation.) [on off] toggle hide deselected filters (shows a menu vs shows what you ordered in compact/narrative form. fold out animation.)
<p> Sort by: Alphabetical / Count || Show counts: true / false || Collapse: all / none</p> <p> Sort by: Alphabetical / Count || Show counts: true / false || Collapse: all / none</p>
+21 -16
View File
@@ -9,20 +9,11 @@ import actions from "../../actions";
} }
}) })
class CategoryValue extends React.Component { class CategoryValue extends React.Component {
toggleOn() {
this.props.dispatch( handleChange() {
actions.attemptCategoricalMetadataSelection( console.log("dispatch", this.props.metadataField, this.props.value)
this.props.metadataField,
this.props.value
))
}
toggleOff() {
this.props.dispatch(
actions.attemptCategoricalMetadataDeselection(
this.props.metadataField,
this.props.value
))
} }
render () { render () {
/* has this value for this category already been selected? */ /* has this value for this category already been selected? */
@@ -40,9 +31,7 @@ class CategoryValue extends React.Component {
return ( return (
<div <div
key={this.props.i} key={this.props.i}
onClick={selected ? this.toggleOff.bind(this) : this.toggleOn.bind(this)}
style={{ style={{
cursor: "pointer",
display: "flex", display: "flex",
fontWeight: selected ? 700 : 400, fontWeight: selected ? 700 : 400,
}}> }}>
@@ -52,7 +41,7 @@ class CategoryValue extends React.Component {
margin: 0, margin: 0,
lineHeight: "1em" lineHeight: "1em"
}}> }}>
{this.props.value} <input onChange={this.handleChange.bind(this)} checked={true} type="checkbox"/> {this.props.value}
</p> </p>
<p style={{ <p style={{
margin: 0, margin: 0,
@@ -66,3 +55,19 @@ class CategoryValue extends React.Component {
} }
export default CategoryValue; export default CategoryValue;
// onClick={selected ? this.toggleOff.bind(this) : this.toggleOn.bind(this)}
// toggleOn() {
// this.props.dispatch(
// actions.attemptCategoricalMetadataSelection(
// this.props.metadataField,
// this.props.value
// ))
// }
// toggleOff() {
// this.props.dispatch(
// actions.attemptCategoricalMetadataDeselection(
// this.props.metadataField,
// this.props.value
// ))
// }
+26 -2
View File
@@ -27,7 +27,8 @@ const updateCellSelectionMiddleware = (store) => {
/* this is a hardcoded map of the things we need to keep an eye on and update global cell selection in response to */ /* this is a hardcoded map of the things we need to keep an eye on and update global cell selection in response to */
const filterJustChanged = const filterJustChanged =
action.type === "color by expression" || action.type === "color by expression" ||
action.type === "color by continuous metadata" action.type === "color by continuous metadata" ||
action.type === "color by categorical metadata"
; ;
if ( if (
@@ -39,6 +40,7 @@ const updateCellSelectionMiddleware = (store) => {
let currentSelectionWithUpdatedColors = s.controls.currentCellSelection.slice(0); let currentSelectionWithUpdatedColors = s.controls.currentCellSelection.slice(0);
let colorScale; let colorScale;
/* /*
in plain language... in plain language...
@@ -48,6 +50,18 @@ const updateCellSelectionMiddleware = (store) => {
This is available to all the draw functions as cell["__color__"] This is available to all the draw functions as cell["__color__"]
*/ */
if (action.type === "color by categorical metadata") {
colorScale = d3.scaleOrdinal(d3.schemeCategory20);
_.each(currentSelectionWithUpdatedColors, (cell, i) => {
currentSelectionWithUpdatedColors[i]["__color__"] = colorScale(
cell[action.colorAccessor]
)
})
}
if (action.type === "color by continuous metadata") { if (action.type === "color by continuous metadata") {
colorScale = d3.scaleLinear() colorScale = d3.scaleLinear()
@@ -109,7 +123,17 @@ const updateCellSelectionMiddleware = (store) => {
} }
let modifiedAction = Object.assign({}, action, {currentSelectionWithUpdatedColors}) /* append the result of all the filters to the action the user just triggered */ /*
append the result of all the filters to the action the user just triggered
*/
let modifiedAction = Object.assign(
{},
action,
{
currentSelectionWithUpdatedColors,
colorScale
}
)
return next(modifiedAction); return next(modifiedAction);
} }
+5
View File
@@ -67,6 +67,11 @@ const Controls = (state = {
colorAccessor: action.gene, colorAccessor: action.gene,
currentCellSelection: action.currentSelectionWithUpdatedColors /* this comes from middleware */ currentCellSelection: action.currentSelectionWithUpdatedColors /* this comes from middleware */
}) })
case "color by categorical metadata":
return Object.assign({}, state, {
colorAccessor: action.colorAccessor, /* pass the scale through additionally, and it's a legend! */
currentCellSelection: action.currentSelectionWithUpdatedColors /* this comes from middleware */
})
default: default:
return state; return state;
} }