From a3df1a9f4a0776e7d83f8d76350a21aca7cbd33f Mon Sep 17 00:00:00 2001 From: Bruce Martin Date: Mon, 13 Apr 2020 20:03:46 -0700 Subject: [PATCH] performance and correctness fixes to brushable histogram (#1390) --- .../components/brushableHistogram/index.js | 162 +++++++++++------- .../src/components/continuous/continuous.js | 31 +--- client/src/reducers/continuousSelection.js | 2 +- client/src/reducers/crossfilter.js | 23 ++- .../src/util/typedCrossfilter/crossfilter.js | 53 +++--- 5 files changed, 152 insertions(+), 119 deletions(-) diff --git a/client/src/components/brushableHistogram/index.js b/client/src/components/brushableHistogram/index.js index f7de0d78..2570e70c 100644 --- a/client/src/components/brushableHistogram/index.js +++ b/client/src/components/brushableHistogram/index.js @@ -24,10 +24,10 @@ import { makeContinuousDimensionName } from "../../util/nameCreators"; ); return { world: state.world, - scatterplotXXaccessor: state.controls.scatterplotXXaccessor, - scatterplotYYaccessor: state.controls.scatterplotYYaccessor, + isScatterplotXXaccessor: state.controls.scatterplotXXaccessor === field, + isScatterplotYYaccessor: state.controls.scatterplotYYaccessor === field, continuousSelectionRange: state.continuousSelection[myName], - colorAccessor: state.colors.colorAccessor + isColorAccessor: state.colors.colorAccessor === field, }; }) class HistogramBrush extends React.PureComponent { @@ -47,7 +47,7 @@ class HistogramBrush extends React.PureComponent { return varData.col(field); } - calcHistogramCache = memoize(col => { + calcHistogramCache = memoize((col) => { /* recalculate expensive stuff, notably bins, summaries, etc. */ @@ -61,13 +61,17 @@ class HistogramBrush extends React.PureComponent { .domain([domainMin, domainMax]) .range([this.marginLeft, this.marginLeft + this.width]); - histogramCache.bins = histogramContinuous(col, numBins, [domainMin, domainMax]); + histogramCache.bins = histogramContinuous(col, numBins, [ + domainMin, + domainMax, + ]); histogramCache.binWidth = (domainMax - domainMin) / numBins; - histogramCache.binStart = i => domainMin + i * histogramCache.binWidth; - histogramCache.binEnd = i => domainMin + (i + 1) * histogramCache.binWidth; + 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); + const yMax = histogramCache.bins.reduce((l, r) => (l > r ? l : r)); histogramCache.y = d3 .scaleLinear() @@ -90,32 +94,47 @@ class HistogramBrush extends React.PureComponent { } componentDidMount() { - const { field, colorAccessor } = this.props; + const { field, isColorAccessor } = this.props; - this.renderHistogram(this._histogram, field, colorAccessor); + this.renderHistogram(this._histogram, field, isColorAccessor); } componentDidUpdate(prevProps) { - const { field, world, continuousSelectionRange: range, colorAccessor } = this.props; + const { + field, + world, + continuousSelectionRange: range, + isColorAccessor, + } = this.props; const { x } = this._histogram; let { brushX, brushXselection } = this.state; const dfColumn = HistogramBrush.getColumn(world, field); - const oldDfColumn = HistogramBrush.getColumn(prevProps.world, prevProps.field); + const oldDfColumn = HistogramBrush.getColumn( + prevProps.world, + prevProps.field + ); const rangeChanged = range !== prevProps.continuousSelectionRange; const dfChanged = dfColumn !== oldDfColumn; - const colorSelectionChanged = prevProps.colorAccessor !== colorAccessor; + const colorSelectionChanged = prevProps.isColorAccessor !== isColorAccessor; if (dfChanged || colorSelectionChanged) { - ({brushX, brushXselection} = this.renderHistogram(this._histogram, field, colorAccessor)); + ({ brushX, brushXselection } = this.renderHistogram( + this._histogram, + field, + isColorAccessor + )); } /* if the selection has changed, ensure that the brush correctly reflects the underlying selection. */ - if ((dfChanged || rangeChanged || colorSelectionChanged) && brushXselection) { + if ( + (dfChanged || rangeChanged || colorSelectionChanged) && + brushXselection + ) { const selection = d3.brushSelection(brushXselection.node()); if (!range && selection) { /* no active selection - clear brush */ @@ -160,9 +179,9 @@ class HistogramBrush extends React.PureComponent { continuousNamespace: { isObs, isUserDefined, - isDiffExp + isDiffExp, }, - range: [x(d3.event.selection[0]), x(d3.event.selection[1])] + range: [x(d3.event.selection[0]), x(d3.event.selection[1])], }); } else { dispatch({ @@ -171,9 +190,9 @@ class HistogramBrush extends React.PureComponent { continuousNamespace: { isObs, isUserDefined, - isDiffExp + isDiffExp, }, - range: null + range: null, }); } }; @@ -216,9 +235,9 @@ class HistogramBrush extends React.PureComponent { continuousNamespace: { isObs, isUserDefined, - isDiffExp + isDiffExp, }, - range: _range + range: _range, }); } else { dispatch({ @@ -227,8 +246,8 @@ class HistogramBrush extends React.PureComponent { continuousNamespace: { isObs, isUserDefined, - isDiffExp - } + isDiffExp, + }, }); } }; @@ -238,7 +257,7 @@ class HistogramBrush extends React.PureComponent { const { dispatch, field } = this.props; dispatch({ type: "set scatterplot x", - data: field + data: field, }); }; @@ -246,7 +265,7 @@ class HistogramBrush extends React.PureComponent { const { dispatch, field } = this.props; dispatch({ type: "set scatterplot y", - data: field + data: field, }); }; @@ -257,7 +276,7 @@ class HistogramBrush extends React.PureComponent { dispatch({ type: "color by continuous metadata", colorAccessor: field, - rangeForColorAccessor: ranges + rangeForColorAccessor: ranges, }); } else if (world.varData.hasCol(field)) { dispatch(actions.requestSingleGeneExpressionCountsForColoringPOST(field)); @@ -268,29 +287,29 @@ class HistogramBrush extends React.PureComponent { const { dispatch, field, - colorAccessor, - scatterplotXXaccessor, - scatterplotYYaccessor + isColorAccessor, + isScatterplotXXaccessor, + isScatterplotYYaccessor, } = this.props; dispatch({ type: "clear user defined gene", - data: field + data: field, }); - if (field === colorAccessor) { + if (isColorAccessor) { dispatch({ - type: "reset colorscale" + type: "reset colorscale", }); } - if (field === scatterplotXXaccessor) { + if (isScatterplotXXaccessor) { dispatch({ type: "set scatterplot x", - data: null + data: null, }); } - if (field === scatterplotYYaccessor) { + if (isScatterplotYYaccessor) { dispatch({ type: "set scatterplot y", - data: null + data: null, }); } }; @@ -300,11 +319,11 @@ class HistogramBrush extends React.PureComponent { const col = HistogramBrush.getColumn(world, field); this._histogram = { ...this.calcHistogramCache(col), - svgRef + svgRef, }; } - renderHistogram(histogram, field, colorAccessor) { + renderHistogram(histogram, field, isColorAccessor) { const { x, y, bins, svgRef, binStart, binEnd, binWidth } = histogram; const svg = d3.select(svgRef); @@ -319,14 +338,16 @@ class HistogramBrush extends React.PureComponent { .attr("class", "histogram-container") .attr("transform", `translate(${this.marginLeft},${this.marginTop})`); - const colorScale = d3.scaleSequential(interpolateCool).domain([0, bins.length]); + const colorScale = d3 + .scaleSequential(interpolateCool) + .domain([0, bins.length]); const histogramScale = d3 .scaleLinear() .domain(x.domain()) .range([ colorScale.domain()[1], - colorScale.domain()[0] + colorScale.domain()[0], ]); /* we flip this to make colors dark if high in the color scale */ if (binWidth > 0) { @@ -338,10 +359,15 @@ class HistogramBrush extends React.PureComponent { .enter() .append("rect") .attr("x", (d, i) => x(binStart(i)) + 1) - .attr("y", d => y(d)) + .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", colorAccessor === field ? (d, i) => colorScale(histogramScale(binStart(i))) : "#bbb"); + .attr("height", (d) => y(0) - y(d)) + .style( + "fill", + isColorAccessor + ? (d, i) => colorScale(histogramScale(binStart(i))) + : "#bbb" + ); } // BRUSH @@ -350,7 +376,7 @@ class HistogramBrush extends React.PureComponent { .brushX() .extent([ [x.range()[0], y.range()[1]], - [x.range()[1], this.marginTop + this.height + this.marginBottom] + [x.range()[1], this.marginTop + this.height + this.marginBottom], ]) /* emit start so that the Undoable history can save an undo point @@ -375,7 +401,11 @@ class HistogramBrush extends React.PureComponent { d3 .axisBottom(x) .ticks(4) - .tickFormat(d3.format(x.domain().some(n => Math.abs(n) >= 10000) ? ".2e" : ",")) + .tickFormat( + d3.format( + x.domain().some((n) => Math.abs(n) >= 10000) ? ".2e" : "," + ) + ) ); /* Y AXIS */ @@ -387,7 +417,11 @@ class HistogramBrush extends React.PureComponent { d3 .axisRight(y) .ticks(3) - .tickFormat(d3.format(y.domain().some(n => Math.abs(n) >= 10000) ? ".0e" : ",")) + .tickFormat( + d3.format( + y.domain().some((n) => Math.abs(n) >= 10000) ? ".0e" : "," + ) + ) ); /* axis style */ @@ -404,19 +438,19 @@ class HistogramBrush extends React.PureComponent { const { field, world, - colorAccessor, + isColorAccessor, isUserDefined, isDiffExp, logFoldChange, pvalAdj, - scatterplotXXaccessor, - scatterplotYYaccessor, - zebra + isScatterplotXXaccessor, + isScatterplotYYaccessor, + zebra, } = this.props; const fieldForId = field.replace(/\s/g, "_"); const { min: unclippedRangeMin, - max: unclippedRangeMax + max: unclippedRangeMax, } = HistogramBrush.getColumn(world, field, false).summarize(); const unclippedRangeMinColor = world.clipQuantiles.min === 0 ? "#bbb" : globals.blue; @@ -436,14 +470,14 @@ class HistogramBrush extends React.PureComponent { } style={{ padding: globals.leftSidebarSectionPadding, - backgroundColor: zebra ? globals.lightestGrey : "white" + backgroundColor: zebra ? globals.lightestGrey : "white", }} >
{isDiffExp || isUserDefined ? ( @@ -456,16 +490,16 @@ class HistogramBrush extends React.PureComponent { @@ -479,7 +513,7 @@ class HistogramBrush extends React.PureComponent { style={{ color: globals.blue, cursor: "pointer", - marginLeft: 7 + marginLeft: 7, }} > remove @@ -492,8 +526,8 @@ class HistogramBrush extends React.PureComponent { >