diff --git a/src/actions/index.js b/src/actions/index.js index 7d9b7002..b3dc14bc 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -57,9 +57,9 @@ const regraph = () => { dispatch(requestCells(uri.search())).then(res => { if (res.error) { - dispatch({ type: "regraph success" }); - } else { dispatch({ type: "regraph error" }); + } else { + dispatch({ type: "regraph success" }); } }); }; diff --git a/src/components/app.js b/src/components/app.js index a4a1ec30..56ed2754 100644 --- a/src/components/app.js +++ b/src/components/app.js @@ -8,6 +8,7 @@ import PulseLoader from "halogen/PulseLoader"; import LeftSideBar from "./leftsidebar"; import Parallel from "./continuous/parallel"; +import Legend from "./continuousLegend"; import Joy from "./joy/joy"; import Graph from "./graph/graph"; import * as globals from "../globals"; @@ -92,6 +93,7 @@ class App extends React.Component { }} > + {/**/} diff --git a/src/components/continuousLegend/index.js b/src/components/continuousLegend/index.js new file mode 100644 index 00000000..e3f6f14a --- /dev/null +++ b/src/components/continuousLegend/index.js @@ -0,0 +1,142 @@ +// jshint esversion: 6 +import React from "react"; +import _ from "lodash"; +import { connect } from "react-redux"; +import * as globals from "../../globals"; + +// create continuous color legend +// http://bl.ocks.org/syntagmatic/e8ccca52559796be775553b467593a9f +const continuous = (selector_id, colorscale) => { + var legendheight = 200, + legendwidth = 80, + margin = { top: 10, right: 60, bottom: 10, left: 2 }; + + var canvas = d3 + .select(selector_id) + .style("height", legendheight + "px") + .style("width", legendwidth + "px") + // .style("position", "relative") + .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("border", "1px solid #000") + .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(); + + var ctx = canvas.getContext("2d"); + + var 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 + var image = ctx.createImageData(1, legendheight); + d3.range(legendheight).forEach(function(i) { + var 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); + }); + */ + + var legendaxis = d3 + .axisRight() + .scale(legendscale) + .tickSize(6) + .ticks(8); + + var svg = d3 + .select(selector_id) + .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); +}; + +@connect(state => { + return { + colorAccessor: state.controls.colorAccessor, + colorScale: state.controls.colorScale, + responsive: state.responsive + }; +}) +class ContinuousLegend extends React.Component { + constructor(props) { + super(props); + this.state = {}; + } + componentWillReceiveProps(nextProps) { + if (nextProps.colorAccessor !== this.props.colorAccessor) { + /* always remove it, if it's not continuous we don't put it back. */ + d3 + .select("#continuous_legend") + .selectAll("*") + .remove(); + } + + if (nextProps.colorAccessor && nextProps.colorScale) { + /* fragile! continuous range is 0 to 1, not [#fa4b2c, ...], make this a flag? */ + if (nextProps.colorScale.range()[0][0] !== "#") { + continuous( + "#continuous_legend", + d3 + .scaleSequential(d3.interpolateViridis) + .domain(nextProps.colorScale.domain()) + ); + } + } + } + drawScale() {} + render() { + return ( +
+ ); + } +} + +export default ContinuousLegend; diff --git a/src/reducers/cells.js b/src/reducers/cells.js index c17ee6d4..57c79044 100644 --- a/src/reducers/cells.js +++ b/src/reducers/cells.js @@ -1,9 +1,11 @@ // jshint esversion: 6 const Cells = ( state = { - cells: null, + cells: null /* world */, loading: null, - error: null + error: null, + + allCells: null /* this comes from cells endpoint, this is universe */ }, action ) => { @@ -17,13 +19,20 @@ const Cells = ( return Object.assign({}, state, { error: null, loading: false, - cells: action.data + cells: action.data, // most recently loaded cells + + /* Universe - initialize once */ + allCells: state.allCells ? state.allCells : action.data }); case "request cells error": return Object.assign({}, state, { loading: false, error: action.data }); + case "reset graph": + return Object.assign({}, state, { + cells: state.allCells + }); default: return state; } diff --git a/src/reducers/controls.js b/src/reducers/controls.js index b6796f54..c328e622 100644 --- a/src/reducers/controls.js +++ b/src/reducers/controls.js @@ -103,6 +103,7 @@ const Controls = ( allGeneNames: null, allCells: null /* this comes from cells endpoint, this is universe */, allCellsMetadata: null /* this comes from cells endpoint, and is just the metadata for universe */, + allCellsMetadataMap: null, /* View / World - all cells currently being displayed. May be a subset of Universe. */ cellsMetadata: null,