Files
cellxgene/client/src/components/continuousLegend/index.js
T
eaae6df5e3 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>
2021-08-23 15:01:36 -07:00

175 lines
4.9 KiB
JavaScript

import React from "react";
import { connect } from "react-redux";
import * as d3 from "d3";
import { interpolateCool } from "d3-scale-chromatic";
import {
createColorTable,
createColorQuery,
} from "../../util/stateManager/colorHelpers";
// create continuous color legend
const continuous = (selectorId, colorScale, colorAccessor) => {
const legendHeight = 200;
const legendWidth = 80;
const margin = { top: 10, right: 60, bottom: 10, left: 2 };
const canvas = d3
.select(selectorId)
.style("height", `${legendHeight}px`)
.style("width", `${legendWidth}px`)
.append("canvas")
.attr("height", legendHeight - margin.top - margin.bottom)
.attr("width", 1)
.style("height", `${legendHeight - margin.top - margin.bottom}px`)
.style("width", `${legendWidth - margin.left - margin.right}px`)
.style("position", "absolute")
.style("top", `${margin.top + 1}px`)
.style("left", `${margin.left + 1}px`)
.style(
"transform",
"scale(1,-1)"
) /* flip it! dark is high value light is low.
we flip the color scale as well [1, 0] instead of [0, 1] */
.node();
const ctx = canvas.getContext("2d");
const legendScale = d3
.scaleLinear()
.range([1, legendHeight - margin.top - margin.bottom])
.domain([
colorScale.domain()[1],
colorScale.domain()[0],
]); /* we flip this to make viridis colors dark if high in the color scale */
// image data hackery based on http://bl.ocks.org/mbostock/048d21cf747371b11884f75ad896e5a5
const image = ctx.createImageData(1, legendHeight);
d3.range(legendHeight).forEach((i) => {
const c = d3.rgb(colorScale(legendScale.invert(i)));
image.data[4 * i] = c.r;
image.data[4 * i + 1] = c.g;
image.data[4 * i + 2] = c.b;
image.data[4 * i + 3] = 255;
});
ctx.putImageData(image, 0, 0);
// A simpler way to do the above, but possibly slower. keep in mind the legend
// width is stretched because the width attr of the canvas is 1
// See http://stackoverflow.com/questions/4899799/whats-the-best-way-to-set-a-single-pixel-in-an-html5-canvas
/*
d3.range(legendheight).forEach(function(i) {
ctx.fillStyle = colorscale(legendscale.invert(i));
ctx.fillRect(0,i,1,1);
});
*/
const legendAxis = d3
.axisRight(legendScale)
.ticks(6)
.tickFormat(
d3.format(
legendScale.domain().some((n) => Math.abs(n) >= 10000) ? ".0e" : ","
)
);
const svg = d3
.select(selectorId)
.append("svg")
.attr("height", `${legendHeight}px`)
.attr("width", `${legendWidth}px`)
.style("position", "absolute")
.style("left", "0px")
.style("top", "0px");
svg
.append("g")
.attr("class", "axis")
.attr(
"transform",
`translate(${legendWidth - margin.left - margin.right + 3},${margin.top})`
)
.call(legendAxis);
// text label for the y axis
svg
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 2)
.attr("x", 0 - legendHeight / 2)
.attr("dy", "1em")
.attr("data-testid", "continuous_legend_color_by_label")
.attr("aria-label", colorAccessor)
.style("text-anchor", "middle")
.style("fill", "white")
.text(colorAccessor);
};
@connect((state) => ({
annoMatrix: state.annoMatrix,
colors: state.colors,
genesets: state.genesets.genesets,
}))
class ContinuousLegend extends React.Component {
async componentDidUpdate(prevProps) {
const { annoMatrix, colors, genesets } = this.props;
if (!colors || !annoMatrix) return;
if (colors !== prevProps?.colors || annoMatrix !== prevProps?.annoMatrix) {
const { schema } = annoMatrix;
const { colorMode, colorAccessor, userColors } = colors;
const colorQuery = createColorQuery(
colorMode,
colorAccessor,
schema,
genesets
);
const colorDf = colorQuery ? await annoMatrix.fetch(...colorQuery) : null;
const colorTable = createColorTable(
colorMode,
colorAccessor,
colorDf,
schema,
userColors
);
const colorScale = colorTable.scale;
const range = colorScale?.range;
const [domainMin, domainMax] = colorScale?.domain?.() ?? [0, 0];
/* always remove it, if it's not continuous we don't put it back. */
d3.select("#continuous_legend").selectAll("*").remove();
if (colorAccessor && colorScale && range && domainMin < domainMax) {
/* fragile! continuous range is 0 to 1, not [#fa4b2c, ...], make this a flag? */
if (range()[0][0] !== "#") {
continuous(
"#continuous_legend",
d3.scaleSequential(interpolateCool).domain(colorScale.domain()),
colorAccessor
);
}
}
}
}
render() {
return (
<div
id="continuous_legend"
style={{
position: "absolute",
left: 8,
top: 35,
zIndex: 1,
pointerEvents: "none",
}}
/>
);
}
}
export default ContinuousLegend;