From 382cc45035034c5af0a8a1332025370274f95983 Mon Sep 17 00:00:00 2001 From: Colin Megill Date: Thu, 16 Nov 2017 20:04:58 -0800 Subject: [PATCH] graph brush selection --- src/components/graph/drawGraph.js | 101 +++++++++++++++++++++++------- src/components/graph/graph.js | 54 ++++++++++++++-- src/reducers/controls.js | 5 ++ 3 files changed, 134 insertions(+), 26 deletions(-) diff --git a/src/components/graph/drawGraph.js b/src/components/graph/drawGraph.js index 95bc5dfe..61dcf7e5 100644 --- a/src/components/graph/drawGraph.js +++ b/src/components/graph/drawGraph.js @@ -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") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .attr("class", `${styles.graphSVG}`) - .append("g") - .attr("transform", "translate(" + margin.left + " " + margin.top + ")"); + // .append("g") + // .attr("transform", "translate(" + margin.left + " " + margin.top + ")"); - var chartArea = d3.select("#graphAttachPoint").append("div") - .style("left", margin.left + "px") - .style("top", margin.top + "px"); - - var canvas = chartArea.append("canvas") - .attr("width", width) - .attr("height", height) - .attr("class", `${styles.graphCanvas}`); - - var context = canvas.node().getContext("2d"); + setupGraphBrush( + svg, + handleBrushSelectAction, + handleBrushDeselectAction + ); return { svg, - context + ctx } } @@ -61,6 +67,7 @@ export const drawGraph = ( ranges, metadata, continuousSelection, + graphBrushSelection, ) => { /* clear canvas */ @@ -100,9 +107,48 @@ export const drawGraph = ( 0, /* sAngle */ 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(); - if (i < 20) { + // if (i < 20) { // console.log( // '0 to 1 scale', expressionToColorScale(expressionsCountsMap[p[0]]), // 'color', d3.interpolateViridis(expressionToColorScale( @@ -110,14 +156,27 @@ export const drawGraph = ( // )) // ) // console.log('meta', _.find(metadata, {CellName: p[0]}), color, p) - } + // } // context.fillStyle = d3.interpolateViridis(expressionToColorScale( // 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) + ); +} diff --git a/src/components/graph/graph.js b/src/components/graph/graph.js index c3c4906a..dfff3f82 100644 --- a/src/components/graph/graph.js +++ b/src/components/graph/graph.js @@ -18,6 +18,7 @@ import ColorControl from "../controls/color"; metadata, color: state.controls.color, continuousSelection: state.controls.continuousSelection, + graphBrushSelection: state.controls.graphBrushSelection } }) class Graph extends React.Component { @@ -27,33 +28,76 @@ class Graph extends React.Component { this.state = { drawn: false, 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) { /* maybe should do a check here to confirm ref exists and pass it? */ if ( - this.state.context && + this.state.ctx && nextProps.vertices // nextProps.expressions && // nextProps.expressionsCountsMap && ) { drawGraph( nextProps.vertices, - this.state.context, + this.state.ctx, nextProps.expressionsCountsMap, nextProps.color, nextProps.ranges, /* assumption that this exists if vertices does both are on cells */ nextProps.metadata, nextProps.continuousSelection, /* continuousSelected should probably inform a global 'is active' array rather than be consumed so specifically here */ + nextProps.graphBrushSelection, ) } } componentDidMount() { - const {svg, context} = setupGraphElements(); - this.setState({svg, context}); + const {svg, ctx} = setupGraphElements( + this.handleBrushSelectAction.bind(this), + this.handleBrushDeselectAction.bind(this) + ); + this.setState({svg, ctx}); } render() { diff --git a/src/reducers/controls.js b/src/reducers/controls.js index 0aebfc48..a67392de 100644 --- a/src/reducers/controls.js +++ b/src/reducers/controls.js @@ -1,6 +1,7 @@ const Controls = (state = { color: null, continuousSelection: null, + graphBrushSelection: null }, action) => { switch (action.type) { case "color changed": @@ -12,6 +13,10 @@ const Controls = (state = { continuousSelection: action.data, }); } + case "graph brush selection change": + return Object.assign({}, state, { + graphBrushSelection: action.brushCoords + }) default: return state; }