diff --git a/client/src/components/brushableHistogram/footer.js b/client/src/components/brushableHistogram/footer.js
index d2cdc955..39e800f2 100644
--- a/client/src/components/brushableHistogram/footer.js
+++ b/client/src/components/brushableHistogram/footer.js
@@ -10,6 +10,7 @@ const HistogramFooter = React.memo(
rangeColorMax,
logFoldChange,
pvalAdj,
+ isObs,
}) => {
/*
Footer of each histogram. Will render range, title, and optionally
@@ -43,7 +44,7 @@ const HistogramFooter = React.memo(
data-testclass="brushable-histogram-field-name"
style={{ fontStyle: "italic" }}
>
- {displayName}
+ {isObs ? displayName : null}
: {rangeMin}
diff --git a/client/src/components/brushableHistogram/histogram.js b/client/src/components/brushableHistogram/histogram.js
index 38fa5786..0b777bbd 100644
--- a/client/src/components/brushableHistogram/histogram.js
+++ b/client/src/components/brushableHistogram/histogram.js
@@ -75,62 +75,64 @@ const Histogram = ({
);
}
- // BRUSH
- // Note the brushable area is bounded by the data on three sides, but goes down to cover the x-axis
- const brushX = d3
- .brushX()
- .extent([
- [x.range()[0], y.range()[1]],
- [x.range()[1], marginTop + height + marginBottom],
- ])
- /*
+ if (!mini) {
+ // BRUSH
+ // Note the brushable area is bounded by the data on three sides, but goes down to cover the x-axis
+ const brushX = d3
+ .brushX()
+ .extent([
+ [x.range()[0], y.range()[1]],
+ [x.range()[1], marginTop + height + marginBottom],
+ ])
+ /*
emit start so that the Undoable history can save an undo point
upon drag start, and ignore the subsequent intermediate drag events.
*/
- .on("start", onBrush(field, x.invert, "start"))
- .on("brush", onBrush(field, x.invert, "brush"))
- .on("end", onBrushEnd(field, x.invert));
+ .on("start", onBrush(field, x.invert, "start"))
+ .on("brush", onBrush(field, x.invert, "brush"))
+ .on("end", onBrushEnd(field, x.invert));
- const brushXselection = container
- .insert("g")
- .attr("class", "brush")
- .attr("data-testid", `${svgRef.current.dataset.testid}-brushable-area`)
- .call(brushX);
+ const brushXselection = container
+ .insert("g")
+ .attr("class", "brush")
+ .attr("data-testid", `${svgRef.current.dataset.testid}-brushable-area`)
+ .call(brushX);
- /* X AXIS */
- container
- .insert("g")
- .attr("class", "axis axis--x")
- .attr("transform", `translate(0,${marginTop + height})`)
- .call(
- d3
- .axisBottom(x)
- .ticks(4)
- .tickFormat(d3.format(maybeScientific(x)))
- );
+ /* X AXIS */
+ container
+ .insert("g")
+ .attr("class", "axis axis--x")
+ .attr("transform", `translate(0,${marginTop + height})`)
+ .call(
+ d3
+ .axisBottom(x)
+ .ticks(4)
+ .tickFormat(d3.format(maybeScientific(x)))
+ );
- /* Y AXIS */
- container
- .insert("g")
- .attr("class", "axis axis--y")
- .attr("transform", `translate(${marginLeft + width},0)`)
- .call(
- d3
- .axisRight(y)
- .ticks(3)
- .tickFormat(
- d3.format(
- y.domain().some((n) => Math.abs(n) >= 10000) ? ".0e" : ","
+ /* Y AXIS */
+ container
+ .insert("g")
+ .attr("class", "axis axis--y")
+ .attr("transform", `translate(${marginLeft + width},0)`)
+ .call(
+ d3
+ .axisRight(y)
+ .ticks(3)
+ .tickFormat(
+ d3.format(
+ y.domain().some((n) => Math.abs(n) >= 10000) ? ".0e" : ","
+ )
)
- )
- );
+ );
- /* axis style */
- svg.selectAll(".axis text").style("fill", "rgb(80,80,80)");
- svg.selectAll(".axis path").style("stroke", "rgb(230,230,230)");
- svg.selectAll(".axis line").style("stroke", "rgb(230,230,230)");
+ /* axis style */
+ svg.selectAll(".axis text").style("fill", "rgb(80,80,80)");
+ svg.selectAll(".axis path").style("stroke", "rgb(230,230,230)");
+ svg.selectAll(".axis line").style("stroke", "rgb(230,230,230)");
- setBrush({ brushX, brushXselection });
+ setBrush({ brushX, brushXselection });
+ }
}, [histogram, isColorBy]);
useEffect(() => {
diff --git a/client/src/components/brushableHistogram/histogramMini.js b/client/src/components/brushableHistogram/histogramMini.js
deleted file mode 100644
index 1b267215..00000000
--- a/client/src/components/brushableHistogram/histogramMini.js
+++ /dev/null
@@ -1,178 +0,0 @@
-import React, { useEffect, useRef, useState } from "react";
-import { interpolateCool } from "d3-scale-chromatic";
-import * as d3 from "d3";
-
-import maybeScientific from "./maybeScientific";
-import clamp from "./clamp";
-
-const Histogram = ({
- field,
- fieldForId,
- display,
- histogram,
- width,
- height,
- onBrush,
- onBrushEnd,
- margin,
- isColorBy,
- selectionRange,
-}) => {
- const svgRef = useRef(null);
- const [brush, setBrush] = useState(null);
-
- useEffect(() => {
- /*
- Create the d3 histogram
- */
- const { marginLeft, marginRight, marginBottom, marginTop } = margin;
- const { x, y, bins, binStart, binEnd, binWidth } = histogram;
- const svg = d3.select(svgRef.current);
-
- /* Remove everything */
- svg.selectAll("*").remove();
-
- /* Set margins within the SVG */
- const container = svg
- .attr("width", width + marginLeft + marginRight)
- .attr("height", height + marginTop + marginBottom)
- .append("g")
- .attr("class", "histogram-container")
- .attr("transform", `translate(${marginLeft},${marginTop})`);
-
- const colorScale = d3
- .scaleSequential(interpolateCool)
- .domain([0, bins.length]);
-
- const histogramScale = d3
- .scaleLinear()
- .domain(x.domain())
- .range([
- colorScale.domain()[1],
- colorScale.domain()[0],
- ]); /* we flip this to make colors dark if high in the color scale */
-
- if (binWidth > 0) {
- /* BINS */
- container
- .insert("g", "*")
- .selectAll("rect")
- .data(bins)
- .enter()
- .append("rect")
- .attr("x", (d, i) => x(binStart(i)) + 1)
- .attr("y", (d) => y(d))
- .attr("width", (d, i) => x(binEnd(i)) - x(binStart(i)) - 1)
- .attr("height", (d) => y(0) - y(d))
- .style(
- "fill",
- isColorBy ? (d, i) => colorScale(histogramScale(binStart(i))) : "#bbb"
- );
- }
-
- // BRUSH
- // Note the brushable area is bounded by the data on three sides, but goes down to cover the x-axis
- const brushX = d3
- .brushX()
- .extent([
- [x.range()[0], y.range()[1]],
- [x.range()[1], marginTop + height + marginBottom],
- ])
- /*
- emit start so that the Undoable history can save an undo point
- upon drag start, and ignore the subsequent intermediate drag events.
- */
- .on("start", onBrush(field, x.invert, "start"))
- .on("brush", onBrush(field, x.invert, "brush"))
- .on("end", onBrushEnd(field, x.invert));
-
- const brushXselection = container
- .insert("g")
- .attr("class", "brush")
- .attr("data-testid", `${svgRef.current.dataset.testid}-brushable-area`)
- .call(brushX);
-
- /* X AXIS */
- container
- .insert("g")
- .attr("class", "axis axis--x")
- .attr("transform", `translate(0,${marginTop + height})`)
- .call(
- d3
- .axisBottom(x)
- .ticks(4)
- .tickFormat(d3.format(maybeScientific(x)))
- );
-
- /* Y AXIS */
- container
- .insert("g")
- .attr("class", "axis axis--y")
- .attr("transform", `translate(${marginLeft + width},0)`)
- .call(
- d3
- .axisRight(y)
- .ticks(3)
- .tickFormat(
- d3.format(
- y.domain().some((n) => Math.abs(n) >= 10000) ? ".0e" : ","
- )
- )
- );
-
- /* axis style */
- svg.selectAll(".axis text").style("fill", "rgb(80,80,80)");
- svg.selectAll(".axis path").style("stroke", "rgb(230,230,230)");
- svg.selectAll(".axis line").style("stroke", "rgb(230,230,230)");
-
- setBrush({ brushX, brushXselection });
- }, [histogram, isColorBy]);
-
- useEffect(() => {
- /*
- paint/update selection brush
- */
- if (!brush) return;
- const { brushX, brushXselection } = brush;
- const selection = d3.brushSelection(brushXselection.node());
- if (!selectionRange && selection) {
- /* no active selection - clear brush */
- brushXselection.call(brushX.move, null);
- } else if (selectionRange) {
- const { x, domain } = histogram;
- const [min, max] = domain;
- const x0 = x(clamp(selectionRange[0], [min, max]));
- const x1 = x(clamp(selectionRange[1], [min, max]));
- if (!selection) {
- /* there is an active selection, but no brush - set the brush */
- brushXselection.call(brushX.move, [x0, x1]);
- } else {
- /* there is an active selection and a brush - make sure they match */
- const moveDeltaThreshold = 1;
- const dX0 = Math.abs(x0 - selection[0]);
- const dX1 = Math.abs(x1 - selection[1]);
- /*
- only update the brush if it is grossly incorrect,
- as defined by the moveDeltaThreshold
- */
- if (dX0 > moveDeltaThreshold || dX1 > moveDeltaThreshold) {
- brushXselection.call(brushX.move, [x0, x1]);
- }
- }
- }
- }, [brush, selectionRange]);
-
- return (
-
- );
-};
-
-export default Histogram;
diff --git a/client/src/components/brushableHistogram/index.js b/client/src/components/brushableHistogram/index.js
index fe06cce9..f59d9e54 100644
--- a/client/src/components/brushableHistogram/index.js
+++ b/client/src/components/brushableHistogram/index.js
@@ -54,11 +54,6 @@ class HistogramBrush extends React.PureComponent {
const marginBottom = 25; // space for X axis & labels
const marginTop = 3;
- const marginLeftMini = 10; // Space for 0 tick label on X axis
- const marginRightMini = 54; // space for Y axis & labels
- const marginBottomMini = 25; // space for X axis & labels
- const marginTopMini = 3;
-
this.state = {
margin: {
marginLeft,
@@ -69,13 +64,13 @@ class HistogramBrush extends React.PureComponent {
width: 340 - marginLeft - marginRight,
height: 135 - marginTop - marginBottom,
marginMini: {
- marginLeftMini,
- marginRightMini,
- marginBottomMini,
- marginTopMini,
+ marginLeft: 0, // Space for 0 tick label on X axis
+ marginRight: 0, // space for Y axis & labels
+ marginBottom: 0, // space for X axis & labels
+ marginTop: 0,
},
- widthMini: 340 - marginLeft - marginRight,
- heightMini: 135 - marginTop - marginBottom,
+ widthMini: 120,
+ heightMini: 15,
};
}
@@ -271,16 +266,20 @@ class HistogramBrush extends React.PureComponent {
// eslint-disable-next-line class-methods-use-this -- instance method allows for memoization per annotation
calcHistogramCache(col, margin, width, height) {
+ /* make this more structured and doing a forEach */
/*
recalculate expensive stuff, notably bins, summaries, etc.
*/
- const histogramCache = {};
- const summary = col.summarize();
+ 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 { marginTop, marginLeft } = margin;
+ const { marginTop, marginLeft } = margin; /* changes with mini */
- histogramCache.domain = [domainMin, domainMax];
+ histogramCache.domain = [
+ domainMin,
+ domainMax,
+ ]; /* doesn't change with mini */
histogramCache.x = d3
.scaleLinear()
@@ -290,7 +289,7 @@ class HistogramBrush extends React.PureComponent {
histogramCache.bins = histogramContinuous(col, numBins, [
domainMin,
domainMax,
- ]);
+ ]); /* memoized */
histogramCache.binWidth = (domainMax - domainMin) / numBins;
histogramCache.binStart = (i) => domainMin + i * histogramCache.binWidth;
@@ -377,11 +376,11 @@ class HistogramBrush extends React.PureComponent {
: "histogram-continuous-metadata"
}
style={{
- padding: globals.leftSidebarSectionPadding,
+ padding: mini ? 0 : globals.leftSidebarSectionPadding,
backgroundColor: zebra ? globals.lightestGrey : "white",
}}
>
- {!mini ? (
+ {!mini && isObs ? (
{!mini ? (
{
- return {};
+@connect((state, ownProps) => {
+ const { gene } = ownProps;
+
+ return {
+ isColorAccessor: state.colors.colorAccessor === gene,
+ isScatterplotXXaccessor: state.controls.scatterplotXXaccessor === gene,
+ isScatterplotYYaccessor: state.controls.scatterplotYYaccessor === gene,
+ };
})
class Gene extends React.Component {
constructor(props) {
@@ -26,10 +34,7 @@ class Gene extends React.Component {
onColorChangeClick = () => {
const { dispatch, gene } = this.props;
- dispatch({
- type: "color by expression",
- colorAccessor: gene,
- });
+ dispatch(actions.requestSingleGeneExpressionCountsForColoringPOST(gene));
};
handleGeneExpandClick = () => {
@@ -37,26 +42,42 @@ class Gene extends React.Component {
this.setState({ geneIsExpanded: !geneIsExpanded });
};
+ handleSetGeneAsScatterplotX = () => {
+ const { dispatch, gene } = this.props;
+ dispatch({
+ type: "set scatterplot x",
+ data: gene,
+ });
+ };
+
+ handleSetGeneAsScatterplotY = () => {
+ const { dispatch, gene } = this.props;
+ dispatch({
+ type: "set scatterplot y",
+ data: gene,
+ });
+ };
+
render() {
- const { gene } = this.props;
+ const {
+ gene,
+ isColorAccessor,
+ isScatterplotXXaccessor,
+ isScatterplotYYaccessor,
+ } = this.props;
const { geneIsExpanded } = this.state;
return (
-
{}}
style={{
cursor: "pointer",
+ display: "flex",
+ justifyContent: "space-between",
}}
>
+
+ {!geneIsExpanded ? (
+
+ ) : null}
+
+
+
+ x
+
+
+ y
+
}
+ style={{ marginRight: 2 }}
+ />
+ }
/>
- {/* */}
+ {/*
*/}
-
-
-
+ {geneIsExpanded ?
: null}
{/*
*/}
);
@@ -103,17 +172,6 @@ class Gene extends React.Component {
export default Gene;
-// {}}
-// active={false /* todo gene sets */}
-// intent="none"
-// icon={}
-// />
-
// {geneIsExpanded ? (
//