mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-27 21:28:13 +08:00
dotplot proto full
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
import React from "react";
|
||||
import { connect, shallowEqual } from "react-redux";
|
||||
|
||||
import memoize from "memoize-one";
|
||||
|
||||
import Async from "react-async";
|
||||
import ErrorLoading from "./err";
|
||||
import StillLoading from "./load";
|
||||
import Dot from "./dot";
|
||||
|
||||
import { createCategorySummaryFromDfCol } from "../../util/stateManager/controlsHelpers";
|
||||
|
||||
import {
|
||||
createColorTable,
|
||||
createColorQuery,
|
||||
} from "../../util/stateManager/colorHelpers";
|
||||
|
||||
@connect((state) => ({
|
||||
annoMatrix: state.annoMatrix,
|
||||
colors: state.colors,
|
||||
genesets: state.genesets.genesets,
|
||||
pointDilation: state.pointDilation,
|
||||
differential: state.differential,
|
||||
}))
|
||||
class Column extends React.Component {
|
||||
static watchAsync(props, prevProps) {
|
||||
return !shallowEqual(props.watchProps, prevProps.watchProps);
|
||||
}
|
||||
|
||||
createCategorySummaryFromDfCol = memoize(createCategorySummaryFromDfCol);
|
||||
|
||||
fetchAsyncProps = async (props) => {
|
||||
const { annoMatrix, colors, _geneSymbol, _geneIndex } = props.watchProps;
|
||||
|
||||
const [categoryData, categorySummary, colorData] = await this.fetchData(
|
||||
annoMatrix,
|
||||
"tissue",
|
||||
colors,
|
||||
_geneSymbol,
|
||||
_geneIndex
|
||||
);
|
||||
|
||||
return {
|
||||
categoryData,
|
||||
categorySummary,
|
||||
colorData,
|
||||
};
|
||||
};
|
||||
|
||||
async fetchData(annoMatrix, metadataField, colors, _geneSymbol) {
|
||||
/*
|
||||
fetch our data and the color-by data if appropriate, and then build a summary
|
||||
of our category and a color table for the color-by annotation.
|
||||
*/
|
||||
const { schema } = annoMatrix;
|
||||
const { colorMode } = colors;
|
||||
const { genesets, differential } = this.props;
|
||||
let colorDataPromise = Promise.resolve(null);
|
||||
if (_geneSymbol) {
|
||||
const query = createColorQuery(
|
||||
colorMode,
|
||||
_geneSymbol,
|
||||
schema,
|
||||
genesets,
|
||||
differential.diffExp
|
||||
);
|
||||
if (query) colorDataPromise = annoMatrix.fetch(...query);
|
||||
}
|
||||
const [categoryData, colorData] = await Promise.all([
|
||||
annoMatrix.fetch("obs", metadataField),
|
||||
colorDataPromise,
|
||||
]);
|
||||
|
||||
// our data
|
||||
const column = categoryData.icol(0);
|
||||
const colSchema = schema.annotations.obsByName[metadataField];
|
||||
|
||||
const categorySummary = this.createCategorySummaryFromDfCol(
|
||||
column,
|
||||
colSchema
|
||||
);
|
||||
|
||||
return [categoryData, categorySummary, colorData];
|
||||
}
|
||||
|
||||
updateColorTable(colors, colorDf) {
|
||||
const { annoMatrix } = this.props;
|
||||
const { schema } = annoMatrix;
|
||||
|
||||
/* update color table state */
|
||||
if (!colors || !colorDf) {
|
||||
return createColorTable(
|
||||
null, // default mode
|
||||
null,
|
||||
null,
|
||||
schema,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
const { colorAccessor, userColors, colorMode } = colors;
|
||||
return createColorTable(
|
||||
colorMode,
|
||||
colorAccessor /* TODO(colinmegill) #632 dotplot wiring */,
|
||||
colorDf,
|
||||
schema,
|
||||
userColors
|
||||
);
|
||||
}
|
||||
|
||||
createColorByQuery(colors) {
|
||||
const { annoMatrix, genesets, differential } = this.props;
|
||||
const { schema } = annoMatrix;
|
||||
const { colorMode, colorAccessor } = colors;
|
||||
|
||||
return createColorQuery(
|
||||
colorMode,
|
||||
colorAccessor /* TODO(colinmegill) #632 dotplot wiring */,
|
||||
schema,
|
||||
genesets,
|
||||
differential.diffExp
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
annoMatrix,
|
||||
pointDilation,
|
||||
colors,
|
||||
viewport,
|
||||
_geneSymbol,
|
||||
_geneIndex,
|
||||
rowColumnSize,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<g key={_geneSymbol}>
|
||||
<Async
|
||||
watchFn={Column.watchAsync}
|
||||
promiseFn={this.fetchAsyncProps}
|
||||
watchProps={{
|
||||
annoMatrix,
|
||||
pointDilation,
|
||||
colors,
|
||||
viewport,
|
||||
_geneSymbol,
|
||||
_geneIndex,
|
||||
}}
|
||||
>
|
||||
<Async.Pending initial>
|
||||
<StillLoading width={viewport.width} height={viewport.height} />
|
||||
</Async.Pending>
|
||||
<Async.Rejected>
|
||||
{(error) => (
|
||||
<ErrorLoading
|
||||
width={viewport.width}
|
||||
height={viewport.height}
|
||||
error={error}
|
||||
/>
|
||||
)}
|
||||
</Async.Rejected>
|
||||
<Async.Fulfilled persist>
|
||||
{(asyncProps) => {
|
||||
const {
|
||||
categoryData,
|
||||
width,
|
||||
height,
|
||||
categorySummary,
|
||||
colorData,
|
||||
} = asyncProps;
|
||||
|
||||
if (!_geneSymbol || !colorData) return null;
|
||||
|
||||
/* TODO(colinmegill) #632 wire to dotplot */
|
||||
|
||||
const metadataField = "tissue";
|
||||
|
||||
const groupBy = categoryData.col(metadataField);
|
||||
const col = colorData.icol(0);
|
||||
const range = col.summarize();
|
||||
|
||||
const histogramMap = col.histogram(
|
||||
100,
|
||||
[range.min, range.max],
|
||||
groupBy
|
||||
);
|
||||
|
||||
return categorySummary.categoryValues.map(
|
||||
(val, _categoryValueIndex) => {
|
||||
return (
|
||||
<Dot
|
||||
key={val}
|
||||
metadataField={metadataField}
|
||||
categoryData={categoryData}
|
||||
_geneSymbol={_geneSymbol}
|
||||
colorData={colorData}
|
||||
categoryValue={val}
|
||||
width={width}
|
||||
height={height}
|
||||
_categoryValueIndex={_categoryValueIndex}
|
||||
histogramMap={histogramMap}
|
||||
_geneIndex={_geneIndex}
|
||||
rowColumnSize={rowColumnSize}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
}}
|
||||
</Async.Fulfilled>
|
||||
</Async>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Column;
|
||||
@@ -0,0 +1,54 @@
|
||||
import React from "react";
|
||||
|
||||
const Dot = (props) => {
|
||||
const {
|
||||
categoryValue,
|
||||
_categoryValueIndex,
|
||||
histogramMap,
|
||||
_geneSymbol,
|
||||
_geneIndex,
|
||||
rowColumnSize,
|
||||
} = props;
|
||||
|
||||
const bins = histogramMap.has(categoryValue)
|
||||
? histogramMap.get(categoryValue)
|
||||
: new Array(100).fill(0);
|
||||
|
||||
const zeros = bins.shift(); /* MUTATES, REMOVES FIRST ELEMENT */
|
||||
const rest = bins.reduce(
|
||||
(acc, current) => acc + current
|
||||
); /* SUM REMAINING ELEMENTS */
|
||||
|
||||
/* TODO(colinmegill) #632 colorscale */
|
||||
|
||||
return (
|
||||
<g
|
||||
id={`row_${categoryValue}_${_geneSymbol}`}
|
||||
key={`${_categoryValueIndex}_${categoryValue}`}
|
||||
transform={`translate(${_geneIndex * rowColumnSize}, ${
|
||||
_categoryValueIndex * rowColumnSize
|
||||
})`}
|
||||
>
|
||||
{_geneIndex === 0 && (
|
||||
<text
|
||||
textAnchor="end"
|
||||
style={{ fill: "black", font: "12px Roboto Condensed" }}
|
||||
>
|
||||
{categoryValue}
|
||||
</text>
|
||||
)}
|
||||
<circle
|
||||
r={(rest / zeros) * 10 > 20 ? 20 : (rest / zeros) * 10}
|
||||
cx="11"
|
||||
cy="-3.5"
|
||||
style={{
|
||||
fill: "steelblue",
|
||||
fillOpacity: 0.3,
|
||||
stroke: "none",
|
||||
}}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dot;
|
||||
@@ -1,40 +1,20 @@
|
||||
import React from "react";
|
||||
import { connect, shallowEqual } from "react-redux";
|
||||
// import _regl from "regl";
|
||||
import Async from "react-async";
|
||||
import memoize from "memoize-one";
|
||||
// import * as d3 from "d3";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import ErrorLoading from "./err";
|
||||
import StillLoading from "./load";
|
||||
|
||||
import { createCategorySummaryFromDfCol } from "../../util/stateManager/controlsHelpers";
|
||||
|
||||
import {
|
||||
createColorTable,
|
||||
createColorQuery,
|
||||
} from "../../util/stateManager/colorHelpers";
|
||||
import Column from "./column";
|
||||
|
||||
@connect((state) => ({
|
||||
annoMatrix: state.annoMatrix,
|
||||
layoutChoice: state.layoutChoice,
|
||||
colors: state.colors,
|
||||
genesets: state.genesets.genesets,
|
||||
differential: state.differential,
|
||||
pointDilation: state.pointDilation,
|
||||
colors: state.colors,
|
||||
}))
|
||||
class Dotplot extends React.Component {
|
||||
static watchAsync(props, prevProps) {
|
||||
return !shallowEqual(props.watchProps, prevProps.watchProps);
|
||||
}
|
||||
|
||||
createCategorySummaryFromDfCol = memoize(createCategorySummaryFromDfCol);
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const viewport = this.getViewportDimensions();
|
||||
this.dotplotTopPadding = 150;
|
||||
this.dotplotLeftPadding = 200;
|
||||
this.dotplotTopPadding = 120;
|
||||
this.dotplotLeftPadding = 170;
|
||||
this.rowColumnSize = 15;
|
||||
|
||||
this.state = {
|
||||
viewport,
|
||||
@@ -49,8 +29,6 @@ class Dotplot extends React.Component {
|
||||
window.removeEventListener("resize", this.handleResize);
|
||||
}
|
||||
|
||||
// handleResize = () => {};
|
||||
|
||||
getViewportDimensions = () => {
|
||||
const { viewportRef } = this.props;
|
||||
return {
|
||||
@@ -59,183 +37,16 @@ class Dotplot extends React.Component {
|
||||
};
|
||||
};
|
||||
|
||||
createRow = (
|
||||
metadataField,
|
||||
categoryData,
|
||||
colorAccessor,
|
||||
colorData,
|
||||
categoryValue,
|
||||
width,
|
||||
height,
|
||||
index,
|
||||
histogramMap
|
||||
) => {
|
||||
const bins = histogramMap.has(categoryValue)
|
||||
? histogramMap.get(categoryValue)
|
||||
: new Array(100).fill(0);
|
||||
|
||||
const zeros = bins.shift(); /* MUTATES, REMOVES FIRST ELEMENT */
|
||||
const rest = bins.reduce(
|
||||
(acc, current) => acc + current
|
||||
); /* SUM REMAINING ELEMENTS */
|
||||
|
||||
return (
|
||||
<g
|
||||
key={`${index}_${categoryValue}`}
|
||||
transform={`translate(${this.dotplotLeftPadding}, ${
|
||||
index * 15 + this.dotplotTopPadding
|
||||
})`}
|
||||
>
|
||||
<text textAnchor="end">{categoryValue}</text>
|
||||
<circle
|
||||
r={(rest / zeros) * 10}
|
||||
cx="11"
|
||||
cy="-4"
|
||||
style={{ fill: "rgba(70,130,180,.3)", stroke: "none" }}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
dotplot = (
|
||||
metadataField,
|
||||
categoryData,
|
||||
categorySummary,
|
||||
colorAccessor = "tissue",
|
||||
colorData,
|
||||
width,
|
||||
height
|
||||
) => {
|
||||
if (!colorAccessor || !colorData) return null;
|
||||
|
||||
const groupBy = categoryData.col(metadataField);
|
||||
const col = colorData.icol(0);
|
||||
const range = col.summarize();
|
||||
|
||||
const histogramMap = col.histogram(100, [range.min, range.max], groupBy);
|
||||
|
||||
return categorySummary.categoryValues.map((val, index) => {
|
||||
return this.createRow(
|
||||
metadataField,
|
||||
categoryData,
|
||||
colorAccessor,
|
||||
colorData,
|
||||
val,
|
||||
width,
|
||||
height,
|
||||
index,
|
||||
histogramMap
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
dotplotColumns = () => {
|
||||
const { genesets } = this.props;
|
||||
/* TODO(colinmegill) #632 wire to dotplot reducer */
|
||||
if (genesets && genesets.get("bladder urothelial")) {
|
||||
const _geneset = genesets.get("bladder urothelial");
|
||||
_geneset.genes.forEach((a, b) => {
|
||||
console.log("bladder u", b);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
fetchAsyncProps = async (props) => {
|
||||
const { viewport, annoMatrix, colors } = props.watchProps;
|
||||
|
||||
const [categoryData, categorySummary, colorData] = await this.fetchData(
|
||||
annoMatrix,
|
||||
"tissue",
|
||||
colors
|
||||
);
|
||||
|
||||
const { width, height } = viewport;
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
categoryData,
|
||||
categorySummary,
|
||||
colorData,
|
||||
};
|
||||
};
|
||||
|
||||
async fetchData(annoMatrix, metadataField, colors) {
|
||||
/*
|
||||
fetch our data and the color-by data if appropriate, and then build a summary
|
||||
of our category and a color table for the color-by annotation.
|
||||
*/
|
||||
const { schema } = annoMatrix;
|
||||
const { colorAccessor, colorMode } = colors;
|
||||
const { genesets, differential } = this.props;
|
||||
let colorDataPromise = Promise.resolve(null);
|
||||
if (colorAccessor) {
|
||||
const query = createColorQuery(
|
||||
colorMode,
|
||||
colorAccessor,
|
||||
schema,
|
||||
genesets,
|
||||
differential.diffExp
|
||||
);
|
||||
if (query) colorDataPromise = annoMatrix.fetch(...query);
|
||||
}
|
||||
const [categoryData, colorData] = await Promise.all([
|
||||
annoMatrix.fetch("obs", metadataField),
|
||||
colorDataPromise,
|
||||
]);
|
||||
|
||||
// our data
|
||||
const column = categoryData.icol(0);
|
||||
const colSchema = schema.annotations.obsByName[metadataField];
|
||||
|
||||
const categorySummary = this.createCategorySummaryFromDfCol(
|
||||
column,
|
||||
colSchema
|
||||
);
|
||||
return [categoryData, categorySummary, colorData];
|
||||
}
|
||||
|
||||
updateColorTable(colors, colorDf) {
|
||||
const { annoMatrix } = this.props;
|
||||
const { schema } = annoMatrix;
|
||||
|
||||
/* update color table state */
|
||||
if (!colors || !colorDf) {
|
||||
return createColorTable(
|
||||
null, // default mode
|
||||
null,
|
||||
null,
|
||||
schema,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
const { colorAccessor, userColors, colorMode } = colors;
|
||||
return createColorTable(
|
||||
colorMode,
|
||||
colorAccessor,
|
||||
colorDf,
|
||||
schema,
|
||||
userColors
|
||||
);
|
||||
}
|
||||
|
||||
createColorByQuery(colors) {
|
||||
const { annoMatrix, genesets, differential } = this.props;
|
||||
const { schema } = annoMatrix;
|
||||
const { colorMode, colorAccessor } = colors;
|
||||
|
||||
return createColorQuery(
|
||||
colorMode,
|
||||
colorAccessor,
|
||||
schema,
|
||||
genesets,
|
||||
differential.diffExp
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { viewport } = this.state;
|
||||
const { annoMatrix, colors, pointDilation } = this.props;
|
||||
const { genesets } = this.props;
|
||||
|
||||
const _TESTgeneset = genesets.get("bladder urothelial");
|
||||
|
||||
/* TODO(colinmegill) #632 componetize */
|
||||
if (!_TESTgeneset) return null;
|
||||
|
||||
const _genes = Array.from(_TESTgeneset.genes.keys());
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -247,6 +58,7 @@ class Dotplot extends React.Component {
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
id="dotplot"
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
@@ -256,51 +68,43 @@ class Dotplot extends React.Component {
|
||||
width={viewport.width}
|
||||
height={viewport.height}
|
||||
>
|
||||
{this.dotplotColumns()}
|
||||
<Async
|
||||
watchFn={Dotplot.watchAsync}
|
||||
promiseFn={this.fetchAsyncProps}
|
||||
watchProps={{
|
||||
annoMatrix,
|
||||
pointDilation,
|
||||
colors,
|
||||
viewport,
|
||||
}}
|
||||
<g
|
||||
id="dotplot_interface_margin"
|
||||
transform={`translate(${this.dotplotLeftPadding},${this.dotplotTopPadding})`}
|
||||
>
|
||||
<Async.Pending initial>
|
||||
<StillLoading width={viewport.width} height={viewport.height} />
|
||||
</Async.Pending>
|
||||
<Async.Rejected>
|
||||
{(error) => (
|
||||
<ErrorLoading
|
||||
width={viewport.width}
|
||||
height={viewport.height}
|
||||
error={error}
|
||||
/>
|
||||
)}
|
||||
</Async.Rejected>
|
||||
<Async.Fulfilled persist>
|
||||
{(asyncProps) => {
|
||||
const {
|
||||
colorAccessor,
|
||||
categoryData,
|
||||
width,
|
||||
height,
|
||||
categorySummary,
|
||||
colorData,
|
||||
} = asyncProps;
|
||||
return this.dotplot(
|
||||
"tissue",
|
||||
categoryData,
|
||||
categorySummary,
|
||||
colorAccessor,
|
||||
colorData,
|
||||
width,
|
||||
height
|
||||
{/* Acaa1b, Mal, Foxq1 ... across the top of the dotplot */}
|
||||
<g id="dotplot_column_labels" transform="translate(14,-13)">
|
||||
{_genes.map((_geneSymbol, _geneIndexInGeneset) => {
|
||||
return (
|
||||
<text
|
||||
key={_geneSymbol}
|
||||
x={0}
|
||||
y={0}
|
||||
transform={`translate(${
|
||||
_geneIndexInGeneset * this.rowColumnSize
|
||||
}) rotate(270)`}
|
||||
style={{ fill: "black", font: "12px Roboto Condensed" }}
|
||||
>
|
||||
{_geneSymbol}
|
||||
</text>
|
||||
);
|
||||
}}
|
||||
</Async.Fulfilled>
|
||||
</Async>
|
||||
})}
|
||||
</g>
|
||||
{/* loop over genes in the geneset, */}
|
||||
<g id="dotplot_columns">
|
||||
{_genes.map((_geneSymbol, _geneIndexInGeneset) => {
|
||||
return (
|
||||
<Column
|
||||
key={_geneSymbol}
|
||||
_geneSymbol={_geneSymbol}
|
||||
_geneIndex={_geneIndexInGeneset}
|
||||
viewport={viewport}
|
||||
rowColumnSize={this.rowColumnSize}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user