mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-23 15:48:12 +08:00
TS Revert (1) (#2402)
* revert all commits to before Typescript migration * update compat workflow to match latest deps (#2335) * update compat workflow to match latest deps * attempt to debug * attempt to debug * remove debugging code * typo * update deps to match desktop (#2340) * fix: don't run lint with `--fix` on push tests (#2273) * fix: don't run lint with `--fix` on push tests * npx Co-authored-by: maniarathi <mani.arathi@gmail.com> Co-authored-by: Madison Dunitz <madison.dunitz@chanzuckerberg.com> * rename X_approx_distribution to X_approximate_distribution (#2337) * Correctly handle non-finite numbers in heuristic determination of X distribution (#2342) * handle non-finites explicitly * improve and test edge case handling for distribution estimation * revert debugging changes * code readability * clean up type inferencing (#2332) * unit tests for 64 bit conversion * clean up type handling * type inference tests * more type inference fixes * use schema to determine user intent for data typing * stop using deprecated API * fbs type encoding test * add missing test * add more tests * correctly infer X type for CXG adaptor * lint * fix typo * ts migration * cleanup from PR review * lint * PR review changes * remove unused packages from client (#2359) * remove unused packages from client * add missing peer dep * fix: disable FE auth testing on compatibility tests (#2377) * update: release process (#2277) Co-authored-by: maniarathi <mani.arathi@gmail.com> * fix: remove spaces in param setup (#2380) * delete deploy workflow (#2396) * undo reformatting which now does not pass lint * fix snapshots which changed due to npm dep changes * add missing quoting to snapshot * another snapshot typo fix * TS Revert (2) - replay PR #2347 and #2354 (#2403) * replay edits from PR 2347 * TS Revert (3) - replay edits in PR #2327 (#2404) * replay edits in PR 2327 * TS Revert (4) - replay PR #2355 (#2405) * replay edits in PR 2355 * add additional babel config * reformat with new prettier config Co-authored-by: Severiano Badajoz <sbadajoz@chanzuckerberg.com> Co-authored-by: maniarathi <mani.arathi@gmail.com> Co-authored-by: Madison Dunitz <madison.dunitz@chanzuckerberg.com>
This commit is contained in:
co-authored by
maniarathi
Madison Dunitz
Severiano Badajoz
parent
295590a7c6
commit
eaae6df5e3
@@ -0,0 +1,15 @@
|
||||
import React from "react";
|
||||
import * as globals from "../../globals";
|
||||
|
||||
const ErrorLoading = ({ displayName, zebra }) => (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: zebra ? globals.lightestGrey : "white",
|
||||
fontStyle: "italic",
|
||||
}}
|
||||
>
|
||||
<span>{`Failure loading ${displayName}`}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default ErrorLoading;
|
||||
@@ -1,16 +0,0 @@
|
||||
import React from "react";
|
||||
import * as globals from "../../globals";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
const ErrorLoading = ({ displayName, zebra }: any) => (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: zebra ? globals.lightestGrey : "white",
|
||||
fontStyle: "italic",
|
||||
}}
|
||||
>
|
||||
<span>{`Failure loading ${displayName}`}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default ErrorLoading;
|
||||
@@ -0,0 +1,63 @@
|
||||
import React from "react";
|
||||
|
||||
const HistogramFooter = React.memo(
|
||||
({
|
||||
displayName,
|
||||
hideRanges,
|
||||
rangeMin,
|
||||
rangeMax,
|
||||
rangeColorMin,
|
||||
rangeColorMax,
|
||||
isObs,
|
||||
isGeneSetSummary,
|
||||
}) =>
|
||||
/*
|
||||
Footer of each histogram. Will render range and title.
|
||||
|
||||
Required props:
|
||||
* displayName - the displayName, aka "n_genes", "FOXP2", etc.
|
||||
* hideRanges - true/false, enables/disable rendering of ranges
|
||||
* range - length two array, [min, max], containing the range values to display
|
||||
* rangeColor - length two array, [mincolor, maxcolor], each a CSS color
|
||||
*/
|
||||
(
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: hideRanges ? "center" : "space-between",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: rangeColorMin,
|
||||
display: hideRanges ? "none" : "block",
|
||||
}}
|
||||
>
|
||||
min {rangeMin.toPrecision(4)}
|
||||
</span>
|
||||
<span
|
||||
data-testclass="brushable-histogram-field-name"
|
||||
style={{ fontStyle: "italic" }}
|
||||
>
|
||||
{isObs && displayName}
|
||||
{isGeneSetSummary && "gene set mean expression"}
|
||||
</span>
|
||||
<div style={{ display: hideRanges ? "block" : "none" }}>
|
||||
: {rangeMin}
|
||||
</div>
|
||||
<span
|
||||
style={{
|
||||
color: rangeColorMax,
|
||||
display: hideRanges ? "none" : "block",
|
||||
}}
|
||||
>
|
||||
max {rangeMax.toPrecision(4)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
);
|
||||
|
||||
export default HistogramFooter;
|
||||
@@ -1,69 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
const HistogramFooter = React.memo(
|
||||
({
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'displayName' does not exist on type '{ c... Remove this comment to see the full error message
|
||||
displayName,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'hideRanges' does not exist on type '{ ch... Remove this comment to see the full error message
|
||||
hideRanges,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'rangeMin' does not exist on type '{ chil... Remove this comment to see the full error message
|
||||
rangeMin,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'rangeMax' does not exist on type '{ chil... Remove this comment to see the full error message
|
||||
rangeMax,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'rangeColorMin' does not exist on type '{... Remove this comment to see the full error message
|
||||
rangeColorMin,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'rangeColorMax' does not exist on type '{... Remove this comment to see the full error message
|
||||
rangeColorMax,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isObs' does not exist on type '{ childre... Remove this comment to see the full error message
|
||||
isObs,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isGeneSetSummary' does not exist on type... Remove this comment to see the full error message
|
||||
isGeneSetSummary,
|
||||
}) => (
|
||||
/*
|
||||
Footer of each histogram. Will render range and title.
|
||||
|
||||
Required props:
|
||||
* displayName - the displayName, aka "n_genes", "FOXP2", etc.
|
||||
* hideRanges - true/false, enables/disable rendering of ranges
|
||||
* range - length two array, [min, max], containing the range values to display
|
||||
* rangeColor - length two array, [mincolor, maxcolor], each a CSS color
|
||||
*/
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: hideRanges ? "center" : "space-between",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: rangeColorMin,
|
||||
display: hideRanges ? "none" : "block",
|
||||
}}
|
||||
>
|
||||
min {rangeMin.toPrecision(4)}
|
||||
</span>
|
||||
<span
|
||||
data-testclass="brushable-histogram-field-name"
|
||||
style={{ fontStyle: "italic" }}
|
||||
>
|
||||
{isObs && displayName}
|
||||
{isGeneSetSummary && "gene set mean expression"}
|
||||
</span>
|
||||
<div style={{ display: hideRanges ? "block" : "none" }}>
|
||||
: {rangeMin}
|
||||
</div>
|
||||
<span
|
||||
style={{
|
||||
color: rangeColorMax,
|
||||
display: hideRanges ? "none" : "block",
|
||||
}}
|
||||
>
|
||||
max {rangeMax.toPrecision(4)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
export default HistogramFooter;
|
||||
-9
@@ -5,23 +5,14 @@ import * as globals from "../../globals";
|
||||
|
||||
const HistogramHeader = React.memo(
|
||||
({
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'fieldId' does not exist on type '{ child... Remove this comment to see the full error message
|
||||
fieldId,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isColorBy' does not exist on type '{ chi... Remove this comment to see the full error message
|
||||
isColorBy,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onColorByClick' does not exist on type '... Remove this comment to see the full error message
|
||||
onColorByClick,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onRemoveClick' does not exist on type '{... Remove this comment to see the full error message
|
||||
onRemoveClick,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isScatterPlotX' does not exist on type '... Remove this comment to see the full error message
|
||||
isScatterPlotX,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isScatterPlotY' does not exist on type '... Remove this comment to see the full error message
|
||||
isScatterPlotY,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onScatterPlotXClick' does not exist on t... Remove this comment to see the full error message
|
||||
onScatterPlotXClick,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onScatterPlotYClick' does not exist on t... Remove this comment to see the full error message
|
||||
onScatterPlotYClick,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isObs' does not exist on type '{ childre... Remove this comment to see the full error message
|
||||
isObs,
|
||||
}) => {
|
||||
/*
|
||||
+5
-24
@@ -2,11 +2,9 @@ import React, { useEffect, useRef, useState } from "react";
|
||||
import { interpolateCool } from "d3-scale-chromatic";
|
||||
import * as d3 from "d3";
|
||||
|
||||
import { AxisDomain } from "d3";
|
||||
import maybeScientific from "../../util/maybeScientific";
|
||||
import clamp from "../../util/clamp";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
const Histogram = ({
|
||||
field,
|
||||
fieldForId,
|
||||
@@ -19,8 +17,8 @@ const Histogram = ({
|
||||
margin,
|
||||
isColorBy,
|
||||
selectionRange,
|
||||
mini, // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
}: any) => {
|
||||
mini,
|
||||
}) => {
|
||||
const svgRef = useRef(null);
|
||||
const [brush, setBrush] = useState(null);
|
||||
|
||||
@@ -71,19 +69,14 @@ const Histogram = ({
|
||||
.data(bins)
|
||||
.enter()
|
||||
.append("rect")
|
||||
// @ts-expect-error ts-migrate(6133) FIXME: 'd' is declared but its value is never read.
|
||||
.attr("x", (d, i) => x(binStart(i)) + 1)
|
||||
.attr("y", (d) => y(d))
|
||||
// @ts-expect-error ts-migrate(6133) FIXME: 'd' is declared but its value is never read.
|
||||
.attr("width", (d, i) => x(binEnd(i)) - x(binStart(i)) - binPadding)
|
||||
.attr("height", (d) => y(0) - y(d))
|
||||
.style(
|
||||
"fill",
|
||||
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
|
||||
isColorBy
|
||||
? // @ts-expect-error ts-migrate(6133) FIXME: 'd' is declared but its value is never read.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(d: any, i: any) => colorScale(histogramScale(binStart(i)))
|
||||
? (d, i) => colorScale(histogramScale(binStart(i)))
|
||||
: defaultBarColor
|
||||
);
|
||||
}
|
||||
@@ -108,7 +101,6 @@ const Histogram = ({
|
||||
const brushXselection = container
|
||||
.insert("g")
|
||||
.attr("class", "brush")
|
||||
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||
.attr("data-testid", `${svgRef.current.dataset.testid}-brushable-area`)
|
||||
.call(brushX);
|
||||
|
||||
@@ -121,12 +113,7 @@ const Histogram = ({
|
||||
d3
|
||||
.axisBottom(x)
|
||||
.ticks(4)
|
||||
.tickFormat(
|
||||
d3.format(maybeScientific(x)) as (
|
||||
dv: AxisDomain,
|
||||
i: number
|
||||
) => string
|
||||
)
|
||||
.tickFormat(d3.format(maybeScientific(x)))
|
||||
);
|
||||
|
||||
/* Y AXIS */
|
||||
@@ -139,10 +126,8 @@ const Histogram = ({
|
||||
.axisRight(y)
|
||||
.ticks(3)
|
||||
.tickFormat(
|
||||
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
|
||||
d3.format(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
y.domain().some((n: any) => Math.abs(n) >= 10000) ? ".0e" : ","
|
||||
y.domain().some((n) => Math.abs(n) >= 10000) ? ".0e" : ","
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -152,7 +137,6 @@ const Histogram = ({
|
||||
svg.selectAll(".axis path").style("stroke", "rgb(230,230,230)");
|
||||
svg.selectAll(".axis line").style("stroke", "rgb(230,230,230)");
|
||||
|
||||
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ brushX: d3.BrushBehavior<unkno... Remove this comment to see the full error message
|
||||
setBrush({ brushX, brushXselection });
|
||||
}
|
||||
}, [histogram, isColorBy]);
|
||||
@@ -162,7 +146,6 @@ const Histogram = ({
|
||||
paint/update selection brush
|
||||
*/
|
||||
if (!brush) return;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'brushX' does not exist on type 'null'.
|
||||
const { brushX, brushXselection } = brush;
|
||||
const selection = d3.brushSelection(brushXselection.node());
|
||||
if (!selectionRange && selection) {
|
||||
@@ -179,9 +162,7 @@ const Histogram = ({
|
||||
} else {
|
||||
/* there is an active selection and a brush - make sure they match */
|
||||
const moveDeltaThreshold = 1;
|
||||
// @ts-expect-error ts-migrate(2363) FIXME: The right-hand side of an arithmetic operation mus... Remove this comment to see the full error message
|
||||
const dX0 = Math.abs(x0 - selection[0]);
|
||||
// @ts-expect-error ts-migrate(2363) FIXME: The right-hand side of an arithmetic operation mus... Remove this comment to see the full error message
|
||||
const dX1 = Math.abs(x1 - selection[1]);
|
||||
/*
|
||||
only update the brush if it is grossly incorrect,
|
||||
@@ -0,0 +1,440 @@
|
||||
import React from "react";
|
||||
import { connect, shallowEqual } from "react-redux";
|
||||
import * as d3 from "d3";
|
||||
import Async from "react-async";
|
||||
import memoize from "memoize-one";
|
||||
import * as globals from "../../globals";
|
||||
import actions from "../../actions";
|
||||
import { makeContinuousDimensionName } from "../../util/nameCreators";
|
||||
import HistogramHeader from "./header";
|
||||
import Histogram from "./histogram";
|
||||
import HistogramFooter from "./footer";
|
||||
import StillLoading from "./loading";
|
||||
import ErrorLoading from "./error";
|
||||
|
||||
const MARGIN = {
|
||||
LEFT: 10, // Space for 0 tick label on X axis
|
||||
RIGHT: 54, // space for Y axis & labels
|
||||
BOTTOM: 25, // space for X axis & labels
|
||||
TOP: 3,
|
||||
};
|
||||
const WIDTH = 340 - MARGIN.LEFT - MARGIN.RIGHT;
|
||||
const HEIGHT = 135 - MARGIN.TOP - MARGIN.BOTTOM;
|
||||
const MARGIN_MINI = {
|
||||
LEFT: 0, // Space for 0 tick label on X axis
|
||||
RIGHT: 0, // space for Y axis & labels
|
||||
BOTTOM: 0, // space for X axis & labels
|
||||
TOP: 0,
|
||||
};
|
||||
const WIDTH_MINI = 120 - MARGIN_MINI.LEFT - MARGIN_MINI.RIGHT;
|
||||
const HEIGHT_MINI = 15 - MARGIN_MINI.TOP - MARGIN_MINI.BOTTOM;
|
||||
|
||||
@connect((state, ownProps) => {
|
||||
const { isObs, isUserDefined, isGeneSetSummary, field } = ownProps;
|
||||
const myName = makeContinuousDimensionName(
|
||||
{ isObs, isUserDefined, isGeneSetSummary },
|
||||
field
|
||||
);
|
||||
return {
|
||||
annoMatrix: state.annoMatrix,
|
||||
isScatterplotXXaccessor: state.controls.scatterplotXXaccessor === field,
|
||||
isScatterplotYYaccessor: state.controls.scatterplotYYaccessor === field,
|
||||
continuousSelectionRange: state.continuousSelection[myName],
|
||||
isColorAccessor: state.colors.colorAccessor === field,
|
||||
};
|
||||
})
|
||||
class HistogramBrush extends React.PureComponent {
|
||||
static watchAsync(props, prevProps) {
|
||||
return !shallowEqual(props.watchProps, prevProps.watchProps);
|
||||
}
|
||||
|
||||
/* memoized closure to prevent HistogramHeader unecessary repaint */
|
||||
handleColorAction = memoize((dispatch) => (field, isObs) => {
|
||||
if (isObs) {
|
||||
dispatch({
|
||||
type: "color by continuous metadata",
|
||||
colorAccessor: field,
|
||||
});
|
||||
} else {
|
||||
dispatch(actions.requestSingleGeneExpressionCountsForColoringPOST(field));
|
||||
}
|
||||
});
|
||||
|
||||
onBrush = (selection, x, eventType) => {
|
||||
const type = `continuous metadata histogram ${eventType}`;
|
||||
return () => {
|
||||
const { dispatch, field, isObs, isUserDefined, isGeneSetSummary } =
|
||||
this.props;
|
||||
|
||||
// ignore programmatically generated events
|
||||
if (!d3.event.sourceEvent) return;
|
||||
// ignore cascading events, which are programmatically generated
|
||||
if (d3.event.sourceEvent.sourceEvent) return;
|
||||
|
||||
const query = this.createQuery();
|
||||
const range = d3.event.selection
|
||||
? [x(d3.event.selection[0]), x(d3.event.selection[1])]
|
||||
: null;
|
||||
const otherProps = {
|
||||
selection: field,
|
||||
continuousNamespace: {
|
||||
isObs,
|
||||
isUserDefined,
|
||||
isGeneSetSummary,
|
||||
},
|
||||
};
|
||||
dispatch(
|
||||
actions.selectContinuousMetadataAction(type, query, range, otherProps)
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
onBrushEnd = (selection, x) => () => {
|
||||
const { dispatch, field, isObs, isUserDefined, isGeneSetSummary } =
|
||||
this.props;
|
||||
const minAllowedBrushSize = 10;
|
||||
const smallAmountToAvoidInfiniteLoop = 0.1;
|
||||
|
||||
// ignore programmatically generated events
|
||||
if (!d3.event.sourceEvent) return;
|
||||
// ignore cascading events, which are programmatically generated
|
||||
if (d3.event.sourceEvent.sourceEvent) return;
|
||||
|
||||
let type;
|
||||
let range = null;
|
||||
if (d3.event.selection) {
|
||||
type = "continuous metadata histogram end";
|
||||
if (
|
||||
d3.event.selection[1] - d3.event.selection[0] >
|
||||
minAllowedBrushSize
|
||||
) {
|
||||
range = [x(d3.event.selection[0]), x(d3.event.selection[1])];
|
||||
} else {
|
||||
/* the user selected range is too small and will be hidden #587, so take control of it procedurally */
|
||||
/* https://stackoverflow.com/questions/12354729/d3-js-limit-size-of-brush */
|
||||
|
||||
const procedurallyResizedBrushWidth =
|
||||
d3.event.selection[0] +
|
||||
minAllowedBrushSize +
|
||||
smallAmountToAvoidInfiniteLoop; //
|
||||
|
||||
range = [x(d3.event.selection[0]), x(procedurallyResizedBrushWidth)];
|
||||
}
|
||||
} else {
|
||||
type = "continuous metadata histogram cancel";
|
||||
}
|
||||
|
||||
const query = this.createQuery();
|
||||
const otherProps = {
|
||||
selection: field,
|
||||
continuousNamespace: {
|
||||
isObs,
|
||||
isUserDefined,
|
||||
isGeneSetSummary,
|
||||
},
|
||||
};
|
||||
dispatch(
|
||||
actions.selectContinuousMetadataAction(type, query, range, otherProps)
|
||||
);
|
||||
};
|
||||
|
||||
handleSetGeneAsScatterplotX = () => {
|
||||
const { dispatch, field } = this.props;
|
||||
dispatch({
|
||||
type: "set scatterplot x",
|
||||
data: field,
|
||||
});
|
||||
};
|
||||
|
||||
handleSetGeneAsScatterplotY = () => {
|
||||
const { dispatch, field } = this.props;
|
||||
dispatch({
|
||||
type: "set scatterplot y",
|
||||
data: field,
|
||||
});
|
||||
};
|
||||
|
||||
removeHistogram = () => {
|
||||
const {
|
||||
dispatch,
|
||||
field,
|
||||
isColorAccessor,
|
||||
isScatterplotXXaccessor,
|
||||
isScatterplotYYaccessor,
|
||||
} = this.props;
|
||||
dispatch({
|
||||
type: "clear user defined gene",
|
||||
data: field,
|
||||
});
|
||||
if (isColorAccessor) {
|
||||
dispatch({
|
||||
type: "reset colorscale",
|
||||
});
|
||||
}
|
||||
if (isScatterplotXXaccessor) {
|
||||
dispatch({
|
||||
type: "set scatterplot x",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
if (isScatterplotYYaccessor) {
|
||||
dispatch({
|
||||
type: "set scatterplot y",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
fetchAsyncProps = async () => {
|
||||
const { annoMatrix, width } = this.props;
|
||||
|
||||
const { isClipped } = annoMatrix;
|
||||
|
||||
const query = this.createQuery();
|
||||
const df = await annoMatrix.fetch(...query);
|
||||
const column = df.icol(0);
|
||||
|
||||
// if we are clipped, fetch both our value and our unclipped value,
|
||||
// as we need the absolute min/max range, not just the clipped min/max.
|
||||
const summary = column.summarize();
|
||||
const range = [summary.min, summary.max];
|
||||
|
||||
let unclippedRange = [...range];
|
||||
if (isClipped) {
|
||||
const parent = await annoMatrix.viewOf.fetch(...query);
|
||||
const { min, max } = parent.icol(0).summarize();
|
||||
unclippedRange = [min, max];
|
||||
}
|
||||
|
||||
const unclippedRangeColor = [
|
||||
!annoMatrix.isClipped || annoMatrix.clipRange[0] === 0
|
||||
? "#bbb"
|
||||
: globals.blue,
|
||||
!annoMatrix.isClipped || annoMatrix.clipRange[1] === 1
|
||||
? "#bbb"
|
||||
: globals.blue,
|
||||
];
|
||||
|
||||
const histogram = this.calcHistogramCache(
|
||||
column,
|
||||
MARGIN,
|
||||
width || WIDTH,
|
||||
HEIGHT
|
||||
);
|
||||
const miniHistogram = this.calcHistogramCache(
|
||||
column,
|
||||
MARGIN_MINI,
|
||||
width || WIDTH_MINI,
|
||||
HEIGHT_MINI
|
||||
);
|
||||
|
||||
const isSingleValue = summary.min === summary.max;
|
||||
const nonFiniteExtent =
|
||||
summary.min === undefined ||
|
||||
summary.max === undefined ||
|
||||
Number.isNaN(summary.min) ||
|
||||
Number.isNaN(summary.max);
|
||||
|
||||
const OK2Render = !summary.categorical && !nonFiniteExtent;
|
||||
|
||||
return {
|
||||
histogram,
|
||||
miniHistogram,
|
||||
range,
|
||||
unclippedRange,
|
||||
unclippedRangeColor,
|
||||
isSingleValue,
|
||||
OK2Render,
|
||||
};
|
||||
};
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this -- instance method allows for memoization per annotation
|
||||
calcHistogramCache(col, newMargin, newWidth, newHeight) {
|
||||
/*
|
||||
recalculate expensive stuff, notably bins, summaries, etc.
|
||||
*/
|
||||
const histogramCache = {}; /* maybe change this so that it computes ... */
|
||||
const summary =
|
||||
col.summarize(); /* this is memoized, so it's free the second time you call it */
|
||||
const { min: domainMin, max: domainMax } = summary;
|
||||
const numBins = 40;
|
||||
const { TOP: topMargin, LEFT: leftMargin } =
|
||||
newMargin; /* changes with mini */
|
||||
|
||||
histogramCache.domain = [
|
||||
domainMin,
|
||||
domainMax,
|
||||
]; /* doesn't change with mini */
|
||||
|
||||
histogramCache.x = d3
|
||||
.scaleLinear()
|
||||
.domain([domainMin, domainMax])
|
||||
.range([leftMargin, leftMargin + newWidth]);
|
||||
|
||||
histogramCache.bins = col.histogram(numBins, [
|
||||
domainMin,
|
||||
domainMax,
|
||||
]); /* memoized */
|
||||
|
||||
histogramCache.binWidth = (domainMax - domainMin) / numBins;
|
||||
|
||||
histogramCache.binStart = (i) => domainMin + i * histogramCache.binWidth;
|
||||
histogramCache.binEnd = (i) =>
|
||||
domainMin + (i + 1) * histogramCache.binWidth;
|
||||
|
||||
const yMax = histogramCache.bins.reduce((l, r) => (l > r ? l : r));
|
||||
|
||||
histogramCache.y = d3
|
||||
.scaleLinear()
|
||||
.domain([0, yMax])
|
||||
.range([topMargin + newHeight, topMargin]);
|
||||
|
||||
return histogramCache;
|
||||
}
|
||||
|
||||
createQuery() {
|
||||
const { isObs, isGeneSetSummary, field, setGenes, annoMatrix } = this.props;
|
||||
const { schema } = annoMatrix;
|
||||
if (isObs) {
|
||||
return ["obs", field];
|
||||
}
|
||||
const varIndex = schema?.annotations?.var?.index;
|
||||
if (!varIndex) return null;
|
||||
|
||||
if (isGeneSetSummary) {
|
||||
return [
|
||||
"X",
|
||||
{
|
||||
summarize: {
|
||||
method: "mean",
|
||||
field: "var",
|
||||
column: varIndex,
|
||||
values: [...setGenes.keys()],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// else, we assume it is a gene expression
|
||||
return [
|
||||
"X",
|
||||
{
|
||||
where: {
|
||||
field: "var",
|
||||
column: varIndex,
|
||||
value: field,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
dispatch,
|
||||
annoMatrix,
|
||||
field,
|
||||
isColorAccessor,
|
||||
isUserDefined,
|
||||
isGeneSetSummary,
|
||||
isScatterplotXXaccessor,
|
||||
isScatterplotYYaccessor,
|
||||
zebra,
|
||||
continuousSelectionRange,
|
||||
isObs,
|
||||
mini,
|
||||
setGenes,
|
||||
} = this.props;
|
||||
|
||||
let { width } = this.props;
|
||||
if (!width) {
|
||||
width = mini ? WIDTH_MINI : WIDTH;
|
||||
}
|
||||
|
||||
const fieldForId = field.replace(/\s/g, "_");
|
||||
const showScatterPlot = isUserDefined;
|
||||
|
||||
let testClass = "histogram-continuous-metadata";
|
||||
if (isUserDefined) testClass = "histogram-user-gene";
|
||||
else if (isGeneSetSummary) testClass = "histogram-gene-set-summary";
|
||||
|
||||
return (
|
||||
<Async
|
||||
watchFn={HistogramBrush.watchAsync}
|
||||
promiseFn={this.fetchAsyncProps}
|
||||
watchProps={{ annoMatrix, setGenes }}
|
||||
>
|
||||
<Async.Pending initial>
|
||||
<StillLoading displayName={field} zebra={zebra} />
|
||||
</Async.Pending>
|
||||
<Async.Rejected>
|
||||
{(error) => (
|
||||
<ErrorLoading zebra={zebra} error={error} displayName={field} />
|
||||
)}
|
||||
</Async.Rejected>
|
||||
<Async.Fulfilled>
|
||||
{(asyncProps) =>
|
||||
asyncProps.OK2Render ? (
|
||||
<div
|
||||
id={`histogram_${fieldForId}`}
|
||||
data-testid={`histogram-${field}`}
|
||||
data-testclass={testClass}
|
||||
style={{
|
||||
padding: mini ? 0 : globals.leftSidebarSectionPadding,
|
||||
backgroundColor: zebra ? globals.lightestGrey : "white",
|
||||
}}
|
||||
>
|
||||
{!mini && isObs ? (
|
||||
<HistogramHeader
|
||||
fieldId={field}
|
||||
isColorBy={isColorAccessor}
|
||||
isObs={isObs}
|
||||
onColorByClick={this.handleColorAction(dispatch)}
|
||||
onRemoveClick={isUserDefined ? this.removeHistogram : null}
|
||||
isScatterPlotX={isScatterplotXXaccessor}
|
||||
isScatterPlotY={isScatterplotYYaccessor}
|
||||
onScatterPlotXClick={
|
||||
showScatterPlot ? this.handleSetGeneAsScatterplotX : null
|
||||
}
|
||||
onScatterPlotYClick={
|
||||
showScatterPlot ? this.handleSetGeneAsScatterplotY : null
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
<Histogram
|
||||
field={field}
|
||||
fieldForId={fieldForId}
|
||||
display={asyncProps.isSingleValue ? "none" : "block"}
|
||||
histogram={
|
||||
mini ? asyncProps.miniHistogram : asyncProps.histogram
|
||||
}
|
||||
width={width}
|
||||
height={mini ? HEIGHT_MINI : HEIGHT}
|
||||
onBrush={this.onBrush}
|
||||
onBrushEnd={this.onBrushEnd}
|
||||
margin={mini ? MARGIN_MINI : MARGIN}
|
||||
isColorBy={isColorAccessor}
|
||||
selectionRange={continuousSelectionRange}
|
||||
mini={mini}
|
||||
/>
|
||||
{!mini && (
|
||||
<HistogramFooter
|
||||
isGeneSetSummary={isGeneSetSummary}
|
||||
isObs={isObs}
|
||||
displayName={field}
|
||||
hideRanges={asyncProps.isSingleValue}
|
||||
rangeMin={asyncProps.unclippedRange[0]}
|
||||
rangeMax={asyncProps.unclippedRange[1]}
|
||||
rangeColorMin={asyncProps.unclippedRangeColor[0]}
|
||||
rangeColorMax={asyncProps.unclippedRangeColor[1]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
</Async.Fulfilled>
|
||||
</Async>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default HistogramBrush;
|
||||
@@ -1,547 +0,0 @@
|
||||
import React from "react";
|
||||
import { connect, shallowEqual } from "react-redux";
|
||||
import * as d3 from "d3";
|
||||
import Async from "react-async";
|
||||
import memoize from "memoize-one";
|
||||
import * as globals from "../../globals";
|
||||
import actions from "../../actions";
|
||||
import { makeContinuousDimensionName } from "../../util/nameCreators";
|
||||
import HistogramHeader from "./header";
|
||||
import Histogram from "./histogram";
|
||||
import HistogramFooter from "./footer";
|
||||
import StillLoading from "./loading";
|
||||
import ErrorLoading from "./error";
|
||||
import { Dataframe } from "../../util/dataframe";
|
||||
|
||||
const MARGIN = {
|
||||
LEFT: 10, // Space for 0 tick label on X axis
|
||||
RIGHT: 54, // space for Y axis & labels
|
||||
BOTTOM: 25, // space for X axis & labels
|
||||
TOP: 3,
|
||||
};
|
||||
const WIDTH = 340 - MARGIN.LEFT - MARGIN.RIGHT;
|
||||
const HEIGHT = 135 - MARGIN.TOP - MARGIN.BOTTOM;
|
||||
const MARGIN_MINI = {
|
||||
LEFT: 0, // Space for 0 tick label on X axis
|
||||
RIGHT: 0, // space for Y axis & labels
|
||||
BOTTOM: 0, // space for X axis & labels
|
||||
TOP: 0,
|
||||
};
|
||||
const WIDTH_MINI = 120 - MARGIN_MINI.LEFT - MARGIN_MINI.RIGHT;
|
||||
const HEIGHT_MINI = 15 - MARGIN_MINI.TOP - MARGIN_MINI.BOTTOM;
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state, ownProps) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isObs' does not exist on type '{}'.
|
||||
const { isObs, isUserDefined, isGeneSetSummary, field } = ownProps;
|
||||
const myName = makeContinuousDimensionName(
|
||||
{ isObs, isUserDefined, isGeneSetSummary },
|
||||
field
|
||||
);
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
annoMatrix: (state as any).annoMatrix,
|
||||
isScatterplotXXaccessor:
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(state as any).controls.scatterplotXXaccessor === field,
|
||||
isScatterplotYYaccessor:
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(state as any).controls.scatterplotYYaccessor === field,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
continuousSelectionRange: (state as any).continuousSelection[myName],
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
isColorAccessor: (state as any).colors.colorAccessor === field,
|
||||
};
|
||||
})
|
||||
class HistogramBrush extends React.PureComponent {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
static watchAsync(props: any, prevProps: any) {
|
||||
return !shallowEqual(props.watchProps, prevProps.watchProps);
|
||||
}
|
||||
|
||||
/* memoized closure to prevent HistogramHeader unecessary repaint */
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleColorAction = memoize((dispatch) => (field: any, isObs: any) => {
|
||||
if (isObs) {
|
||||
dispatch({
|
||||
type: "color by continuous metadata",
|
||||
colorAccessor: field,
|
||||
});
|
||||
} else {
|
||||
dispatch(actions.requestSingleGeneExpressionCountsForColoringPOST(field));
|
||||
}
|
||||
});
|
||||
|
||||
// @ts-expect-error ts-migrate(6133) FIXME: 'selection' is declared but its value is never rea... Remove this comment to see the full error message
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
onBrush = (selection: any, x: any, eventType: any) => {
|
||||
const type = `continuous metadata histogram ${eventType}`;
|
||||
return () => {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
dispatch,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'field' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
field,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isObs' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
isObs,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isUserDefined' does not exist on type 'R... Remove this comment to see the full error message
|
||||
isUserDefined,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isGeneSetSummary' does not exist on type... Remove this comment to see the full error message
|
||||
isGeneSetSummary,
|
||||
} = this.props;
|
||||
|
||||
// ignore programmatically generated events
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
if (!(d3 as any).event.sourceEvent) return;
|
||||
// ignore cascading events, which are programmatically generated
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
if ((d3 as any).event.sourceEvent.sourceEvent) return;
|
||||
|
||||
const query = this.createQuery();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const range = (d3 as any).event.selection
|
||||
? // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
[x((d3 as any).event.selection[0]), x((d3 as any).event.selection[1])]
|
||||
: null;
|
||||
const otherProps = {
|
||||
selection: field,
|
||||
continuousNamespace: {
|
||||
isObs,
|
||||
isUserDefined,
|
||||
isGeneSetSummary,
|
||||
},
|
||||
};
|
||||
dispatch(
|
||||
actions.selectContinuousMetadataAction(type, query, range, otherProps)
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
onBrushEnd =
|
||||
(
|
||||
_selection: any, // eslint-disable-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
x: any // eslint-disable-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
) =>
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
() => {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
dispatch,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'field' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
field,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isObs' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
isObs,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isUserDefined' does not exist on type 'R... Remove this comment to see the full error message
|
||||
isUserDefined,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isGeneSetSummary' does not exist on type... Remove this comment to see the full error message
|
||||
isGeneSetSummary,
|
||||
} = this.props;
|
||||
const minAllowedBrushSize = 10;
|
||||
const smallAmountToAvoidInfiniteLoop = 0.1;
|
||||
|
||||
// ignore programmatically generated events
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
if (!(d3 as any).event.sourceEvent) return;
|
||||
// ignore cascading events, which are programmatically generated
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
if ((d3 as any).event.sourceEvent.sourceEvent) return;
|
||||
|
||||
let type;
|
||||
let range = null;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
if ((d3 as any).event.selection) {
|
||||
type = "continuous metadata histogram end";
|
||||
if (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(d3 as any).event.selection[1] - (d3 as any).event.selection[0] >
|
||||
minAllowedBrushSize
|
||||
) {
|
||||
range = [
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
x((d3 as any).event.selection[0]),
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
x((d3 as any).event.selection[1]),
|
||||
];
|
||||
} else {
|
||||
/* the user selected range is too small and will be hidden #587, so take control of it procedurally */
|
||||
/* https://stackoverflow.com/questions/12354729/d3-js-limit-size-of-brush */
|
||||
const procedurallyResizedBrushWidth =
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(d3 as any).event.selection[0] +
|
||||
minAllowedBrushSize +
|
||||
smallAmountToAvoidInfiniteLoop; //
|
||||
range = [
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
x((d3 as any).event.selection[0]),
|
||||
x(procedurallyResizedBrushWidth),
|
||||
];
|
||||
}
|
||||
} else {
|
||||
type = "continuous metadata histogram cancel";
|
||||
}
|
||||
|
||||
const query = this.createQuery();
|
||||
const otherProps = {
|
||||
selection: field,
|
||||
continuousNamespace: {
|
||||
isObs,
|
||||
isUserDefined,
|
||||
isGeneSetSummary,
|
||||
},
|
||||
};
|
||||
dispatch(
|
||||
actions.selectContinuousMetadataAction(type, query, range, otherProps)
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleSetGeneAsScatterplotX = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, field } = this.props;
|
||||
dispatch({
|
||||
type: "set scatterplot x",
|
||||
data: field,
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleSetGeneAsScatterplotY = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, field } = this.props;
|
||||
dispatch({
|
||||
type: "set scatterplot y",
|
||||
data: field,
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
removeHistogram = () => {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
dispatch,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'field' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
field,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isColorAccessor' does not exist on type ... Remove this comment to see the full error message
|
||||
isColorAccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isScatterplotXXaccessor' does not exist ... Remove this comment to see the full error message
|
||||
isScatterplotXXaccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isScatterplotYYaccessor' does not exist ... Remove this comment to see the full error message
|
||||
isScatterplotYYaccessor,
|
||||
} = this.props;
|
||||
dispatch({
|
||||
type: "clear user defined gene",
|
||||
data: field,
|
||||
});
|
||||
if (isColorAccessor) {
|
||||
dispatch({
|
||||
type: "reset colorscale",
|
||||
});
|
||||
}
|
||||
if (isScatterplotXXaccessor) {
|
||||
dispatch({
|
||||
type: "set scatterplot x",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
if (isScatterplotYYaccessor) {
|
||||
dispatch({
|
||||
type: "set scatterplot y",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
fetchAsyncProps = async () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
const { annoMatrix, width } = this.props;
|
||||
|
||||
const { isClipped } = annoMatrix;
|
||||
|
||||
const query = this.createQuery();
|
||||
// @ts-expect-error ts-migrate(2488) FIXME: Type 'any[] | null' must have a '[Symbol.iterator]... Remove this comment to see the full error message
|
||||
const df: Dataframe = await annoMatrix.fetch(...query);
|
||||
const column = df.icol(0);
|
||||
|
||||
// if we are clipped, fetch both our value and our unclipped value,
|
||||
// as we need the absolute min/max range, not just the clipped min/max.
|
||||
const summary = column.summarizeContinuous();
|
||||
const range = [summary.min, summary.max];
|
||||
|
||||
let unclippedRange = [...range];
|
||||
if (isClipped) {
|
||||
const parent: Dataframe = await annoMatrix.viewOf.fetch(...query);
|
||||
const { min, max } = parent.icol(0).summarizeContinuous();
|
||||
unclippedRange = [min, max];
|
||||
}
|
||||
|
||||
const unclippedRangeColor = [
|
||||
!annoMatrix.isClipped || annoMatrix.clipRange[0] === 0
|
||||
? "#bbb"
|
||||
: globals.blue,
|
||||
!annoMatrix.isClipped || annoMatrix.clipRange[1] === 1
|
||||
? "#bbb"
|
||||
: globals.blue,
|
||||
];
|
||||
|
||||
const histogram = this.calcHistogramCache(
|
||||
column,
|
||||
MARGIN,
|
||||
width || WIDTH,
|
||||
HEIGHT
|
||||
);
|
||||
const miniHistogram = this.calcHistogramCache(
|
||||
column,
|
||||
MARGIN_MINI,
|
||||
width || WIDTH_MINI,
|
||||
HEIGHT_MINI
|
||||
);
|
||||
|
||||
const isSingleValue = summary.min === summary.max;
|
||||
const nonFiniteExtent =
|
||||
summary.min === undefined ||
|
||||
summary.max === undefined ||
|
||||
Number.isNaN(summary.min) ||
|
||||
Number.isNaN(summary.max);
|
||||
|
||||
const OK2Render = !summary.categorical && !nonFiniteExtent;
|
||||
|
||||
return {
|
||||
histogram,
|
||||
miniHistogram,
|
||||
range,
|
||||
unclippedRange,
|
||||
unclippedRangeColor,
|
||||
isSingleValue,
|
||||
OK2Render,
|
||||
};
|
||||
};
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- instance method allows for memoization per annotation
|
||||
calcHistogramCache(col: any, newMargin: any, newWidth: any, newHeight: any) {
|
||||
/*
|
||||
recalculate expensive stuff, notably bins, summaries, etc.
|
||||
*/
|
||||
const histogramCache = {}; /* maybe change this so that it computes ... */
|
||||
const summary =
|
||||
col.summarizeContinuous(); /* this is memoized, so it's free the second time you call it */
|
||||
const { min: domainMin, max: domainMax } = summary;
|
||||
const numBins = 40;
|
||||
const { TOP: topMargin, LEFT: leftMargin } = newMargin;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(histogramCache as any).domain = [domainMin, domainMax];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
/* doesn't change with mini */ (histogramCache as any).x = d3
|
||||
.scaleLinear()
|
||||
.domain([domainMin, domainMax])
|
||||
.range([leftMargin, leftMargin + newWidth]);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(histogramCache as any).bins = col.histogramContinuous(numBins, [
|
||||
domainMin,
|
||||
domainMax,
|
||||
]);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
/* memoized */ (histogramCache as any).binWidth =
|
||||
(domainMax - domainMin) / numBins;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(histogramCache as any).binStart = (i: any) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
domainMin + i * (histogramCache as any).binWidth;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(histogramCache as any).binEnd = (i: any) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
domainMin + (i + 1) * (histogramCache as any).binWidth;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const yMax = (histogramCache as any).bins.reduce((l: any, r: any) =>
|
||||
l > r ? l : r
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(histogramCache as any).y = d3
|
||||
.scaleLinear()
|
||||
.domain([0, yMax])
|
||||
.range([topMargin + newHeight, topMargin]);
|
||||
|
||||
return histogramCache;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
createQuery() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isObs' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
const { isObs, isGeneSetSummary, field, setGenes, annoMatrix } = this.props;
|
||||
const { schema } = annoMatrix;
|
||||
if (isObs) {
|
||||
return ["obs", field];
|
||||
}
|
||||
const varIndex = schema?.annotations?.var?.index;
|
||||
if (!varIndex) return null;
|
||||
|
||||
if (isGeneSetSummary) {
|
||||
return [
|
||||
"X",
|
||||
{
|
||||
summarize: {
|
||||
method: "mean",
|
||||
field: "var",
|
||||
column: varIndex,
|
||||
values: [...setGenes.keys()],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// else, we assume it is a gene expression
|
||||
return [
|
||||
"X",
|
||||
{
|
||||
where: {
|
||||
field: "var",
|
||||
column: varIndex,
|
||||
value: field,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
dispatch,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
annoMatrix,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'field' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
field,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isColorAccessor' does not exist on type ... Remove this comment to see the full error message
|
||||
isColorAccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isUserDefined' does not exist on type 'R... Remove this comment to see the full error message
|
||||
isUserDefined,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isGeneSetSummary' does not exist on type... Remove this comment to see the full error message
|
||||
isGeneSetSummary,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isScatterplotXXaccessor' does not exist ... Remove this comment to see the full error message
|
||||
isScatterplotXXaccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isScatterplotYYaccessor' does not exist ... Remove this comment to see the full error message
|
||||
isScatterplotYYaccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'zebra' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
zebra,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'continuousSelectionRange' does not exist... Remove this comment to see the full error message
|
||||
continuousSelectionRange,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isObs' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
isObs,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'mini' does not exist on type 'Readonly<{... Remove this comment to see the full error message
|
||||
mini,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'setGenes' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
setGenes,
|
||||
} = this.props;
|
||||
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'width' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
let { width } = this.props;
|
||||
if (!width) {
|
||||
width = mini ? WIDTH_MINI : WIDTH;
|
||||
}
|
||||
|
||||
const fieldForId = field.replace(/\s/g, "_");
|
||||
const showScatterPlot = isUserDefined;
|
||||
|
||||
let testClass = "histogram-continuous-metadata";
|
||||
if (isUserDefined) testClass = "histogram-user-gene";
|
||||
else if (isGeneSetSummary) testClass = "histogram-gene-set-summary";
|
||||
|
||||
return (
|
||||
<Async
|
||||
watchFn={HistogramBrush.watchAsync}
|
||||
promiseFn={this.fetchAsyncProps}
|
||||
watchProps={{ annoMatrix, setGenes }}
|
||||
>
|
||||
<Async.Pending initial>
|
||||
<StillLoading displayName={field} zebra={zebra} />
|
||||
</Async.Pending>
|
||||
<Async.Rejected>
|
||||
{(error) => (
|
||||
<ErrorLoading zebra={zebra} error={error} displayName={field} />
|
||||
)}
|
||||
</Async.Rejected>
|
||||
<Async.Fulfilled>
|
||||
{(asyncProps) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(asyncProps as any).OK2Render ? (
|
||||
<div
|
||||
id={`histogram_${fieldForId}`}
|
||||
data-testid={`histogram-${field}`}
|
||||
data-testclass={testClass}
|
||||
style={{
|
||||
padding: mini ? 0 : globals.leftSidebarSectionPadding,
|
||||
backgroundColor: zebra ? globals.lightestGrey : "white",
|
||||
}}
|
||||
>
|
||||
{!mini && isObs ? (
|
||||
<HistogramHeader
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ fieldId: any; isColorBy: any; isObs: any; ... Remove this comment to see the full error message
|
||||
fieldId={field}
|
||||
isColorBy={isColorAccessor}
|
||||
isObs={isObs}
|
||||
onColorByClick={this.handleColorAction(dispatch)}
|
||||
onRemoveClick={isUserDefined ? this.removeHistogram : null}
|
||||
isScatterPlotX={isScatterplotXXaccessor}
|
||||
isScatterPlotY={isScatterplotYYaccessor}
|
||||
onScatterPlotXClick={
|
||||
showScatterPlot ? this.handleSetGeneAsScatterplotX : null
|
||||
}
|
||||
onScatterPlotYClick={
|
||||
showScatterPlot ? this.handleSetGeneAsScatterplotY : null
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
<Histogram
|
||||
field={field}
|
||||
fieldForId={fieldForId}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
display={(asyncProps as any).isSingleValue ? "none" : "block"}
|
||||
histogram={
|
||||
mini
|
||||
? // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(asyncProps as any).miniHistogram
|
||||
: // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(asyncProps as any).histogram
|
||||
}
|
||||
width={width}
|
||||
height={mini ? HEIGHT_MINI : HEIGHT}
|
||||
onBrush={this.onBrush}
|
||||
onBrushEnd={this.onBrushEnd}
|
||||
margin={mini ? MARGIN_MINI : MARGIN}
|
||||
isColorBy={isColorAccessor}
|
||||
selectionRange={continuousSelectionRange}
|
||||
mini={mini}
|
||||
/>
|
||||
{!mini && (
|
||||
<HistogramFooter
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ isGeneSetSummary: any; isObs: any; display... Remove this comment to see the full error message
|
||||
isGeneSetSummary={isGeneSetSummary}
|
||||
isObs={isObs}
|
||||
displayName={field}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
hideRanges={(asyncProps as any).isSingleValue}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
rangeMin={(asyncProps as any).unclippedRange[0]}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
rangeMax={(asyncProps as any).unclippedRange[1]}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
rangeColorMin={(asyncProps as any).unclippedRangeColor[0]}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
rangeColorMax={(asyncProps as any).unclippedRangeColor[1]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
</Async.Fulfilled>
|
||||
</Async>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default HistogramBrush;
|
||||
@@ -0,0 +1,43 @@
|
||||
import React from "react";
|
||||
import { Button } from "@blueprintjs/core";
|
||||
|
||||
import * as globals from "../../globals";
|
||||
|
||||
const StillLoading = ({ zebra, displayName }) =>
|
||||
/*
|
||||
Render a loading indicator for the field.
|
||||
*/
|
||||
(
|
||||
<div
|
||||
data-testclass="gene-loading-spinner"
|
||||
style={{
|
||||
padding: globals.leftSidebarSectionPadding,
|
||||
backgroundColor: zebra ? globals.lightestGrey : "white",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
justifyItems: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<div style={{ minWidth: 30 }} />
|
||||
<div style={{ display: "flex", alignSelf: "center" }}>
|
||||
<span style={{ fontStyle: "italic" }}>{displayName}</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<Button minimal loading intent="primary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
;
|
||||
|
||||
export default StillLoading;
|
||||
@@ -1,41 +0,0 @@
|
||||
import React from "react";
|
||||
import { Button } from "@blueprintjs/core";
|
||||
|
||||
import * as globals from "../../globals";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
const StillLoading = ({ zebra, displayName }: any) => (
|
||||
/*
|
||||
Render a loading indicator for the field.
|
||||
*/
|
||||
<div
|
||||
data-testclass="gene-loading-spinner"
|
||||
style={{
|
||||
padding: globals.leftSidebarSectionPadding,
|
||||
backgroundColor: zebra ? globals.lightestGrey : "white",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
justifyItems: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<div style={{ minWidth: 30 }} />
|
||||
<div style={{ display: "flex", alignSelf: "center" }}>
|
||||
<span style={{ fontStyle: "italic" }}>{displayName}</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<Button minimal loading intent="primary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
export default StillLoading;
|
||||
Reference in New Issue
Block a user