mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-10 21:10:55 +08:00
graph brush selection
This commit is contained in:
@@ -20,29 +20,35 @@ const y = d3.scaleLinear()
|
|||||||
*******************************************
|
*******************************************
|
||||||
******************************************/
|
******************************************/
|
||||||
|
|
||||||
export const setupGraphElements = (data) => {
|
export const setupGraphElements = (
|
||||||
|
handleBrushSelectAction,
|
||||||
|
handleBrushDeselectAction
|
||||||
|
) => {
|
||||||
|
|
||||||
|
var canvas = d3.select("#graphAttachPoint")
|
||||||
|
.append("canvas")
|
||||||
|
.attr("width", width + margin.left + margin.right)
|
||||||
|
.attr("height", height + margin.top + margin.bottom)
|
||||||
|
.attr("class", `${styles.graphCanvas}`);
|
||||||
|
|
||||||
|
var ctx = canvas.node().getContext("2d");
|
||||||
|
|
||||||
var svg = d3.select("#graphAttachPoint").append("svg")
|
var svg = d3.select("#graphAttachPoint").append("svg")
|
||||||
.attr("width", width + margin.left + margin.right)
|
.attr("width", width + margin.left + margin.right)
|
||||||
.attr("height", height + margin.top + margin.bottom)
|
.attr("height", height + margin.top + margin.bottom)
|
||||||
.attr("class", `${styles.graphSVG}`)
|
.attr("class", `${styles.graphSVG}`)
|
||||||
.append("g")
|
// .append("g")
|
||||||
.attr("transform", "translate(" + margin.left + " " + margin.top + ")");
|
// .attr("transform", "translate(" + margin.left + " " + margin.top + ")");
|
||||||
|
|
||||||
var chartArea = d3.select("#graphAttachPoint").append("div")
|
setupGraphBrush(
|
||||||
.style("left", margin.left + "px")
|
svg,
|
||||||
.style("top", margin.top + "px");
|
handleBrushSelectAction,
|
||||||
|
handleBrushDeselectAction
|
||||||
var canvas = chartArea.append("canvas")
|
);
|
||||||
.attr("width", width)
|
|
||||||
.attr("height", height)
|
|
||||||
.attr("class", `${styles.graphCanvas}`);
|
|
||||||
|
|
||||||
var context = canvas.node().getContext("2d");
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
svg,
|
svg,
|
||||||
context
|
ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -61,6 +67,7 @@ export const drawGraph = (
|
|||||||
ranges,
|
ranges,
|
||||||
metadata,
|
metadata,
|
||||||
continuousSelection,
|
continuousSelection,
|
||||||
|
graphBrushSelection,
|
||||||
) => {
|
) => {
|
||||||
|
|
||||||
/* clear canvas */
|
/* clear canvas */
|
||||||
@@ -100,9 +107,48 @@ export const drawGraph = (
|
|||||||
0, /* sAngle */
|
0, /* sAngle */
|
||||||
2 * Math.PI /* eAngle */
|
2 * Math.PI /* eAngle */
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let pointIsInsideBrushBounds;
|
||||||
|
|
||||||
|
if (graphBrushSelection) {
|
||||||
|
|
||||||
|
pointIsInsideBrushBounds = (
|
||||||
|
x(p[1]) >= graphBrushSelection.northwestX &&
|
||||||
|
x(p[1]) <= graphBrushSelection.southeastX &&
|
||||||
|
y(p[2]) >= graphBrushSelection.northwestY &&
|
||||||
|
y(p[2]) <= graphBrushSelection.southeastY
|
||||||
|
);
|
||||||
|
|
||||||
|
// if (i === 5) {
|
||||||
|
// console.log(
|
||||||
|
// pointIsInsideBrushBounds,
|
||||||
|
// x(p[1]),
|
||||||
|
// y(p[2]),
|
||||||
|
// graphBrushSelection,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pointIsInsideBrushBounds) {
|
||||||
|
console.log('inside', x(p[1]), y(p[2]), graphBrushSelection)
|
||||||
|
context.fillStyle = "rgb(255,0,0)";
|
||||||
|
} else {
|
||||||
|
context.fillStyle = "rgb(0,0,0)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (color) {
|
||||||
|
// if (i === 10) {
|
||||||
|
// console.log(p[0], x(p[1]), y(p[2]), _.find(metadata, {CellName: p[0]}))
|
||||||
|
// }
|
||||||
|
// context.fillStyle = d3.interpolateViridis(colorScale(
|
||||||
|
// _.find(metadata, {CellName: p[0]})[color] /* this.state.cells.metadata["23452345325"]["ERCC_reads"] = 20000 would be much faster as key value lookup */
|
||||||
|
// ));
|
||||||
|
// context.strokeStyle = "red";
|
||||||
|
// }
|
||||||
|
|
||||||
context.fill();
|
context.fill();
|
||||||
|
|
||||||
if (i < 20) {
|
// if (i < 20) {
|
||||||
// console.log(
|
// console.log(
|
||||||
// '0 to 1 scale', expressionToColorScale(expressionsCountsMap[p[0]]),
|
// '0 to 1 scale', expressionToColorScale(expressionsCountsMap[p[0]]),
|
||||||
// 'color', d3.interpolateViridis(expressionToColorScale(
|
// 'color', d3.interpolateViridis(expressionToColorScale(
|
||||||
@@ -110,14 +156,27 @@ export const drawGraph = (
|
|||||||
// ))
|
// ))
|
||||||
// )
|
// )
|
||||||
// console.log('meta', _.find(metadata, {CellName: p[0]}), color, p)
|
// console.log('meta', _.find(metadata, {CellName: p[0]}), color, p)
|
||||||
}
|
// }
|
||||||
// context.fillStyle = d3.interpolateViridis(expressionToColorScale(
|
// context.fillStyle = d3.interpolateViridis(expressionToColorScale(
|
||||||
// expressionsCountsMap[p[0]]
|
// expressionsCountsMap[p[0]]
|
||||||
// ));
|
// ));
|
||||||
if (color) {
|
|
||||||
context.fillStyle = d3.interpolateViridis(colorScale(
|
|
||||||
_.find(metadata, {CellName: p[0]})[color] /* this.state.cells.metadata["23452345325"]["ERCC_reads"] = 20000 would be much faster as key value lookup */
|
|
||||||
));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setupGraphBrush = (
|
||||||
|
svg,
|
||||||
|
handleBrushSelectAction,
|
||||||
|
handleBrushDeselectAction
|
||||||
|
) => {
|
||||||
|
svg.append("g")
|
||||||
|
.call(
|
||||||
|
d3.brush()
|
||||||
|
.extent([
|
||||||
|
[0, 0],
|
||||||
|
[width, height]
|
||||||
|
])
|
||||||
|
.on("brush", handleBrushSelectAction)
|
||||||
|
.on("end", handleBrushDeselectAction)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import ColorControl from "../controls/color";
|
|||||||
metadata,
|
metadata,
|
||||||
color: state.controls.color,
|
color: state.controls.color,
|
||||||
continuousSelection: state.controls.continuousSelection,
|
continuousSelection: state.controls.continuousSelection,
|
||||||
|
graphBrushSelection: state.controls.graphBrushSelection
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
class Graph extends React.Component {
|
class Graph extends React.Component {
|
||||||
@@ -27,33 +28,76 @@ class Graph extends React.Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
drawn: false,
|
drawn: false,
|
||||||
svg: null,
|
svg: null,
|
||||||
context: null,
|
ctx: null,
|
||||||
|
brush: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
handleBrushSelectAction() {
|
||||||
|
/*
|
||||||
|
No idea why d3 event scope works like this
|
||||||
|
but apparently
|
||||||
|
it does
|
||||||
|
https://bl.ocks.org/EfratVil/0e542f5fc426065dd1d4b6daaa345a9f
|
||||||
|
*/
|
||||||
|
const s = d3.event.selection;
|
||||||
|
/*
|
||||||
|
event describing brush position:
|
||||||
|
@-------|
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
|-------@
|
||||||
|
*/
|
||||||
|
const brushCoords = {
|
||||||
|
northwestX: s[0][0],
|
||||||
|
northwestY: s[0][1],
|
||||||
|
southeastX: s[1][0],
|
||||||
|
southeastY: s[1][1]
|
||||||
|
}
|
||||||
|
|
||||||
|
brushCoords.dx = brushCoords.southeastX - brushCoords.northwestX;
|
||||||
|
brushCoords.dy = brushCoords.southeastY - brushCoords.northwestY;
|
||||||
|
|
||||||
|
this.props.dispatch({
|
||||||
|
type: "graph brush selection change",
|
||||||
|
brushCoords
|
||||||
|
})
|
||||||
|
}
|
||||||
|
handleBrushDeselectAction() {
|
||||||
|
// function brushended() {
|
||||||
|
// if (!d3.event.selection) {
|
||||||
|
// console.log('graph brush deselect')
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
console.log('handle deselect')
|
||||||
|
}
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
/* maybe should do a check here to confirm ref exists and pass it? */
|
/* maybe should do a check here to confirm ref exists and pass it? */
|
||||||
if (
|
if (
|
||||||
this.state.context &&
|
this.state.ctx &&
|
||||||
nextProps.vertices
|
nextProps.vertices
|
||||||
// nextProps.expressions &&
|
// nextProps.expressions &&
|
||||||
// nextProps.expressionsCountsMap &&
|
// nextProps.expressionsCountsMap &&
|
||||||
) {
|
) {
|
||||||
drawGraph(
|
drawGraph(
|
||||||
nextProps.vertices,
|
nextProps.vertices,
|
||||||
this.state.context,
|
this.state.ctx,
|
||||||
nextProps.expressionsCountsMap,
|
nextProps.expressionsCountsMap,
|
||||||
nextProps.color,
|
nextProps.color,
|
||||||
nextProps.ranges, /* assumption that this exists if vertices does both are on cells */
|
nextProps.ranges, /* assumption that this exists if vertices does both are on cells */
|
||||||
nextProps.metadata,
|
nextProps.metadata,
|
||||||
nextProps.continuousSelection, /* continuousSelected should probably inform a global 'is active' array rather than be consumed so specifically here */
|
nextProps.continuousSelection, /* continuousSelected should probably inform a global 'is active' array rather than be consumed so specifically here */
|
||||||
|
nextProps.graphBrushSelection,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const {svg, context} = setupGraphElements();
|
const {svg, ctx} = setupGraphElements(
|
||||||
this.setState({svg, context});
|
this.handleBrushSelectAction.bind(this),
|
||||||
|
this.handleBrushDeselectAction.bind(this)
|
||||||
|
);
|
||||||
|
this.setState({svg, ctx});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
Vendored
+5
@@ -1,6 +1,7 @@
|
|||||||
const Controls = (state = {
|
const Controls = (state = {
|
||||||
color: null,
|
color: null,
|
||||||
continuousSelection: null,
|
continuousSelection: null,
|
||||||
|
graphBrushSelection: null
|
||||||
}, action) => {
|
}, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "color changed":
|
case "color changed":
|
||||||
@@ -12,6 +13,10 @@ const Controls = (state = {
|
|||||||
continuousSelection: action.data,
|
continuousSelection: action.data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
case "graph brush selection change":
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
graphBrushSelection: action.brushCoords
|
||||||
|
})
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user