diff --git a/client/configuration/eslint/eslint.js b/client/configuration/eslint/eslint.js index e0bb5c42..4fcea456 100644 --- a/client/configuration/eslint/eslint.js +++ b/client/configuration/eslint/eslint.js @@ -31,6 +31,7 @@ module.exports = { "spaced-comment": ["error", "always", { exceptions: ["*"] }], "no-param-reassign": "off", "object-curly-newline": ["error", { consistent: true }], - "react/prop-types": [0] + "react/prop-types": [0], + "space-before-function-paren": "off" } }; diff --git a/client/src/components/graph/drawPointsRegl.js b/client/src/components/graph/drawPointsRegl.js index b4adf8c9..b5f9d6da 100644 --- a/client/src/components/graph/drawPointsRegl.js +++ b/client/src/components/graph/drawPointsRegl.js @@ -38,9 +38,7 @@ export default function(regl) { uniforms: { distance: regl.prop("distance"), view: regl.prop("view"), - projection: (context, props) => { - return mat4.perspective([], Math.PI / 2, 1, 0.01, 1000); - } + projection: () => mat4.perspective([], Math.PI / 2, 1, 0.01, 1000) }, count: regl.prop("count"), diff --git a/client/src/components/graph/graph.js b/client/src/components/graph/graph.js index 32cfaec9..679a7274 100644 --- a/client/src/components/graph/graph.js +++ b/client/src/components/graph/graph.js @@ -1,37 +1,31 @@ // jshint esversion: 6 import React from "react"; import _ from "lodash"; -import * as globals from "../../globals"; -import styles from "./graph.css"; -import { setupSVGandBrushElements } from "./setupSVGandBrush"; -import SectionHeader from "../framework/sectionHeader"; -import { connect } from "react-redux"; -import actions from "../../actions"; import * as d3 from "d3"; - +import { connect } from "react-redux"; import mat4 from "gl-mat4"; -import fit from "canvas-fit"; -import _camera from "../../util/camera.js"; import _regl from "regl"; -import _drawPoints from "./drawPointsRegl"; -import { scaleLinear } from "../../util/scaleLinear"; import FaCrosshair from "react-icons/lib/fa/crosshairs"; import FaZoom from "react-icons/lib/fa/search-plus"; -import FaSave from "react-icons/lib/fa/download"; + +import * as globals from "../../globals"; +import setupSVGandBrushElements from "./setupSVGandBrush"; +import actions from "../../actions"; +import _camera from "../../util/camera"; +import _drawPoints from "./drawPointsRegl"; +import { scaleLinear } from "../../util/scaleLinear"; /* https://bl.ocks.org/mbostock/9078690 - quadtree for onClick / hover selections */ -@connect(state => { - return { - world: state.controls.world, - crossfilter: state.controls.crossfilter, - responsive: state.responsive, - colorRGB: _.get(state.controls, "colorRGB", null), - opacityForDeselectedCells: state.controls.opacityForDeselectedCells, - selectionUpdate: _.get(state.controls, "crossfilter.updateTime", null) - }; -}) +@connect(state => ({ + world: state.controls.world, + crossfilter: state.controls.crossfilter, + responsive: state.responsive, + colorRGB: _.get(state.controls, "colorRGB", null), + opacityForDeselectedCells: state.controls.opacityForDeselectedCells, + selectionUpdate: _.get(state.controls, "crossfilter.updateTime", null) +})) class Graph extends React.Component { constructor(props) { super(props); @@ -51,41 +45,6 @@ class Graph extends React.Component { }; } - reglDraw(regl, drawPoints, sizeBuffer, colorBuffer, pointBuffer, camera) { - regl.clear({ - depth: 1, - color: [1, 1, 1, 1] - }); - drawPoints({ - size: sizeBuffer, - distance: camera.distance, - color: colorBuffer, - position: pointBuffer, - count: this.count, - view: camera.view() - }); - } - - restartReglLoop() { - const reglRender = this.state.regl.frame(() => { - this.reglDraw( - this.state.regl, - this.state.drawPoints, - this.state.sizeBuffer, - this.state.colorBuffer, - this.state.pointBuffer, - this.state.camera - ); - this.state.camera.tick(); - }); - - this.reglRenderState = "rendering"; - - this.setState({ - reglRender - }); - } - componentDidMount() { // setup canvas and camera const camera = _camera(this.reglCanvas, { scale: true, rotate: false }); @@ -124,7 +83,7 @@ class Graph extends React.Component { }); } - componentDidUpdate(prevProps, prevState) { + componentDidUpdate(prevProps) { const { world, crossfilter, @@ -132,19 +91,25 @@ class Graph extends React.Component { colorRGB, responsive } = this.props; + const { + reglRender, + mode, + regl, + drawPoints, + camera, + pointBuffer, + colorBuffer, + sizeBuffer + } = this.state; - if ( - this.state.reglRender && - this.reglRenderState === "rendering" && - this.state.mode !== "zoom" - ) { - this.state.reglRender.cancel(); + if (reglRender && this.reglRenderState === "rendering" && mode !== "zoom") { + reglRender.cancel(); this.reglRenderState = "paused"; } - if (this.state.regl && world) { + if (regl && world) { /* update the regl state */ - const obsLayout = world.obsLayout; + const { obsLayout } = world; const cellCount = crossfilter.size(); // X/Y positions for each point - a cached value that only @@ -152,23 +117,24 @@ class Graph extends React.Component { // if ( !this.renderCache.positions || - selectionUpdate != prevProps.selectionUpdate + selectionUpdate !== prevProps.selectionUpdate ) { - if (!this.renderCache.positions) + if (!this.renderCache.positions) { this.renderCache.positions = new Float32Array(2 * cellCount); + } const glScaleX = scaleLinear([0, 1], [-1, 1]); const glScaleY = scaleLinear([0, 1], [1, -1]); for ( - let i = 0, positions = this.renderCache.positions; + let i = 0, { positions } = this.renderCache; i < cellCount; - i++ + i += 1 ) { positions[2 * i] = glScaleX(obsLayout.X[i]); positions[2 * i + 1] = glScaleY(obsLayout.Y[i]); } - this.state.pointBuffer({ + pointBuffer({ data: this.renderCache.positions, dimension: 2 }); @@ -180,14 +146,15 @@ class Graph extends React.Component { // could have changed for some other reason, but for now color is // the only metadata that changes client-side. If this is problematic, // we could add some sort of color-specific indicator to the app state. - if (!this.renderCache.colors || colorRGB != prevProps.colorRGB) { + if (!this.renderCache.colors || colorRGB !== prevProps.colorRGB) { const rgb = colorRGB; - if (!this.renderCache.colors) + if (!this.renderCache.colors) { this.renderCache.colors = new Float32Array(3 * rgb.length); - for (let i = 0, colors = this.renderCache.colors; i < rgb.length; i++) { + } + for (let i = 0, { colors } = this.renderCache; i < rgb.length; i += 1) { colors.set(rgb[i], 3 * i); } - this.state.colorBuffer({ data: this.renderCache.colors, dimension: 3 }); + colorBuffer({ data: this.renderCache.colors, dimension: 3 }); } // Sizes for each point - this is presumed to change each time the @@ -195,21 +162,22 @@ class Graph extends React.Component { // most property upates are due to changes driving a crossfilter // selection set change. // - if (!this.renderCache.sizes) + if (!this.renderCache.sizes) { this.renderCache.sizes = new Float32Array(cellCount); + } crossfilter.fillByIsFiltered(this.renderCache.sizes, 4, 0.2); - this.state.sizeBuffer({ data: this.renderCache.sizes, dimension: 1 }); + sizeBuffer({ data: this.renderCache.sizes, dimension: 1 }); this.count = cellCount; - this.state.regl._refresh(); + regl._refresh(); this.reglDraw( - this.state.regl, - this.state.drawPoints, - this.state.sizeBuffer, - this.state.colorBuffer, - this.state.pointBuffer, - this.state.camera + regl, + drawPoints, + sizeBuffer, + colorBuffer, + pointBuffer, + camera ); } @@ -233,8 +201,58 @@ class Graph extends React.Component { } } + reglDraw(regl, drawPoints, sizeBuffer, colorBuffer, pointBuffer, camera) { + regl.clear({ + depth: 1, + color: [1, 1, 1, 1] + }); + drawPoints({ + size: sizeBuffer, + distance: camera.distance, + color: colorBuffer, + position: pointBuffer, + count: this.count, + view: camera.view() + }); + } + + restartReglLoop() { + const { + regl, + drawPoints, + sizeBuffer, + colorBuffer, + pointBuffer, + camera + } = this.state; + const reglRender = regl.frame(() => { + this.reglDraw( + regl, + drawPoints, + sizeBuffer, + colorBuffer, + pointBuffer, + camera + ); + camera.tick(); + }); + + this.reglRenderState = "rendering"; + + this.setState({ + reglRender + }); + } + handleBrushSelectAction() { - /* This conditional handles procedural brush deselect. Brush emits an event on procedural deselect because it is move: null */ + /* + This conditional handles procedural brush deselect. Brush emits + an event on procedural deselect because it is move: null + */ + + const { camera } = this.state; + const { dispatch, responsive } = this.props; + if (d3.event.sourceEvent !== null) { /* No idea why d3 event scope works like this @@ -252,18 +270,13 @@ class Graph extends React.Component { */ // compute inverse view matrix - const inverse = mat4.invert([], this.state.camera.view()); + const inverse = mat4.invert([], camera.view()); // transform screen coordinates -> cell coordinates const invert = pin => { - const x = - (2 * pin[0]) / (this.props.responsive.height - this.graphPaddingTop) - - 1; + const x = (2 * pin[0]) / (responsive.height - this.graphPaddingTop) - 1; const y = - 2 * - (1 - - pin[1] / (this.props.responsive.height - this.graphPaddingTop)) - - 1; + 2 * (1 - pin[1] / (responsive.height - this.graphPaddingTop)) - 1; const pout = [ x * inverse[14] + inverse[12], y * inverse[14] + inverse[13] @@ -276,7 +289,7 @@ class Graph extends React.Component { southeast: invert([s[1][0], s[1][1]]) }; - this.props.dispatch({ + dispatch({ type: "graph brush selection change", brushCoords }); @@ -284,29 +297,38 @@ class Graph extends React.Component { } handleBrushDeselectAction() { + const { dispatch } = this.props; + const { svg, brush } = this.state; + if (d3.event && !d3.event.selection) { - this.props.dispatch({ + dispatch({ type: "graph brush deselect" }); } if (!d3.event) { - /* this line clears the brush procedurally, ie., zoom button clicked, not a click away from brush on svg */ - this.state.svg.select(".graph_brush").call(this.state.brush.move, null); - this.props.dispatch({ + /* + this line clears the brush procedurally, ie., zoom button clicked, + not a click away from brush on svg + */ + svg.select(".graph_brush").call(brush.move, null); + dispatch({ type: "graph brush deselect" }); } } handleOpacityRangeChange(e) { - this.props.dispatch({ + const { dispatch } = this.props; + dispatch({ type: "change opacity deselected cells in 2d graph background", data: e.target.value }); } render() { + const { dispatch, responsive } = this.props; + const { mode } = this.state; return (
@@ -319,8 +341,9 @@ class Graph extends React.Component { }} >