dotplot proto full

This commit is contained in:
Colin Megill
2021-07-23 13:28:17 -07:00
parent c489221296
commit f0e9b1ab91
3 changed files with 320 additions and 246 deletions
+50 -246
View File
@@ -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>
);