mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-03 21:58:11 +08:00
color by categorical
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
// ))
|
||||||
|
// }
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+5
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user