delint components/graph

This commit is contained in:
bkmartinjr
2018-09-21 15:28:06 -04:00
committed by Colin Megill
parent 4543b002e3
commit 967ea21d8a
4 changed files with 138 additions and 130 deletions
+2 -1
View File
@@ -31,6 +31,7 @@ module.exports = {
"spaced-comment": ["error", "always", { exceptions: ["*"] }], "spaced-comment": ["error", "always", { exceptions: ["*"] }],
"no-param-reassign": "off", "no-param-reassign": "off",
"object-curly-newline": ["error", { consistent: true }], "object-curly-newline": ["error", { consistent: true }],
"react/prop-types": [0] "react/prop-types": [0],
"space-before-function-paren": "off"
} }
}; };
@@ -38,9 +38,7 @@ export default function(regl) {
uniforms: { uniforms: {
distance: regl.prop("distance"), distance: regl.prop("distance"),
view: regl.prop("view"), view: regl.prop("view"),
projection: (context, props) => { projection: () => mat4.perspective([], Math.PI / 2, 1, 0.01, 1000)
return mat4.perspective([], Math.PI / 2, 1, 0.01, 1000);
}
}, },
count: regl.prop("count"), count: regl.prop("count"),
+133 -122
View File
@@ -1,37 +1,31 @@
// jshint esversion: 6 // jshint esversion: 6
import React from "react"; import React from "react";
import _ from "lodash"; 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 * as d3 from "d3";
import { connect } from "react-redux";
import mat4 from "gl-mat4"; import mat4 from "gl-mat4";
import fit from "canvas-fit";
import _camera from "../../util/camera.js";
import _regl from "regl"; import _regl from "regl";
import _drawPoints from "./drawPointsRegl";
import { scaleLinear } from "../../util/scaleLinear";
import FaCrosshair from "react-icons/lib/fa/crosshairs"; import FaCrosshair from "react-icons/lib/fa/crosshairs";
import FaZoom from "react-icons/lib/fa/search-plus"; 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 */ /* https://bl.ocks.org/mbostock/9078690 - quadtree for onClick / hover selections */
@connect(state => { @connect(state => ({
return { world: state.controls.world,
world: state.controls.world, crossfilter: state.controls.crossfilter,
crossfilter: state.controls.crossfilter, responsive: state.responsive,
responsive: state.responsive, colorRGB: _.get(state.controls, "colorRGB", null),
colorRGB: _.get(state.controls, "colorRGB", null), opacityForDeselectedCells: state.controls.opacityForDeselectedCells,
opacityForDeselectedCells: state.controls.opacityForDeselectedCells, selectionUpdate: _.get(state.controls, "crossfilter.updateTime", null)
selectionUpdate: _.get(state.controls, "crossfilter.updateTime", null) }))
};
})
class Graph extends React.Component { class Graph extends React.Component {
constructor(props) { constructor(props) {
super(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() { componentDidMount() {
// setup canvas and camera // setup canvas and camera
const camera = _camera(this.reglCanvas, { scale: true, rotate: false }); const camera = _camera(this.reglCanvas, { scale: true, rotate: false });
@@ -124,7 +83,7 @@ class Graph extends React.Component {
}); });
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps) {
const { const {
world, world,
crossfilter, crossfilter,
@@ -132,19 +91,25 @@ class Graph extends React.Component {
colorRGB, colorRGB,
responsive responsive
} = this.props; } = this.props;
const {
reglRender,
mode,
regl,
drawPoints,
camera,
pointBuffer,
colorBuffer,
sizeBuffer
} = this.state;
if ( if (reglRender && this.reglRenderState === "rendering" && mode !== "zoom") {
this.state.reglRender && reglRender.cancel();
this.reglRenderState === "rendering" &&
this.state.mode !== "zoom"
) {
this.state.reglRender.cancel();
this.reglRenderState = "paused"; this.reglRenderState = "paused";
} }
if (this.state.regl && world) { if (regl && world) {
/* update the regl state */ /* update the regl state */
const obsLayout = world.obsLayout; const { obsLayout } = world;
const cellCount = crossfilter.size(); const cellCount = crossfilter.size();
// X/Y positions for each point - a cached value that only // X/Y positions for each point - a cached value that only
@@ -152,23 +117,24 @@ class Graph extends React.Component {
// //
if ( if (
!this.renderCache.positions || !this.renderCache.positions ||
selectionUpdate != prevProps.selectionUpdate selectionUpdate !== prevProps.selectionUpdate
) { ) {
if (!this.renderCache.positions) if (!this.renderCache.positions) {
this.renderCache.positions = new Float32Array(2 * cellCount); this.renderCache.positions = new Float32Array(2 * cellCount);
}
const glScaleX = scaleLinear([0, 1], [-1, 1]); const glScaleX = scaleLinear([0, 1], [-1, 1]);
const glScaleY = scaleLinear([0, 1], [1, -1]); const glScaleY = scaleLinear([0, 1], [1, -1]);
for ( for (
let i = 0, positions = this.renderCache.positions; let i = 0, { positions } = this.renderCache;
i < cellCount; i < cellCount;
i++ i += 1
) { ) {
positions[2 * i] = glScaleX(obsLayout.X[i]); positions[2 * i] = glScaleX(obsLayout.X[i]);
positions[2 * i + 1] = glScaleY(obsLayout.Y[i]); positions[2 * i + 1] = glScaleY(obsLayout.Y[i]);
} }
this.state.pointBuffer({ pointBuffer({
data: this.renderCache.positions, data: this.renderCache.positions,
dimension: 2 dimension: 2
}); });
@@ -180,14 +146,15 @@ class Graph extends React.Component {
// could have changed for some other reason, but for now color is // could have changed for some other reason, but for now color is
// the only metadata that changes client-side. If this is problematic, // the only metadata that changes client-side. If this is problematic,
// we could add some sort of color-specific indicator to the app state. // 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; const rgb = colorRGB;
if (!this.renderCache.colors) if (!this.renderCache.colors) {
this.renderCache.colors = new Float32Array(3 * rgb.length); 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); 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 // 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 // most property upates are due to changes driving a crossfilter
// selection set change. // selection set change.
// //
if (!this.renderCache.sizes) if (!this.renderCache.sizes) {
this.renderCache.sizes = new Float32Array(cellCount); this.renderCache.sizes = new Float32Array(cellCount);
}
crossfilter.fillByIsFiltered(this.renderCache.sizes, 4, 0.2); 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.count = cellCount;
this.state.regl._refresh(); regl._refresh();
this.reglDraw( this.reglDraw(
this.state.regl, regl,
this.state.drawPoints, drawPoints,
this.state.sizeBuffer, sizeBuffer,
this.state.colorBuffer, colorBuffer,
this.state.pointBuffer, pointBuffer,
this.state.camera 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() { 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) { if (d3.event.sourceEvent !== null) {
/* /*
No idea why d3 event scope works like this No idea why d3 event scope works like this
@@ -252,18 +270,13 @@ class Graph extends React.Component {
*/ */
// compute inverse view matrix // compute inverse view matrix
const inverse = mat4.invert([], this.state.camera.view()); const inverse = mat4.invert([], camera.view());
// transform screen coordinates -> cell coordinates // transform screen coordinates -> cell coordinates
const invert = pin => { const invert = pin => {
const x = const x = (2 * pin[0]) / (responsive.height - this.graphPaddingTop) - 1;
(2 * pin[0]) / (this.props.responsive.height - this.graphPaddingTop) -
1;
const y = const y =
2 * 2 * (1 - pin[1] / (responsive.height - this.graphPaddingTop)) - 1;
(1 -
pin[1] / (this.props.responsive.height - this.graphPaddingTop)) -
1;
const pout = [ const pout = [
x * inverse[14] + inverse[12], x * inverse[14] + inverse[12],
y * inverse[14] + inverse[13] y * inverse[14] + inverse[13]
@@ -276,7 +289,7 @@ class Graph extends React.Component {
southeast: invert([s[1][0], s[1][1]]) southeast: invert([s[1][0], s[1][1]])
}; };
this.props.dispatch({ dispatch({
type: "graph brush selection change", type: "graph brush selection change",
brushCoords brushCoords
}); });
@@ -284,29 +297,38 @@ class Graph extends React.Component {
} }
handleBrushDeselectAction() { handleBrushDeselectAction() {
const { dispatch } = this.props;
const { svg, brush } = this.state;
if (d3.event && !d3.event.selection) { if (d3.event && !d3.event.selection) {
this.props.dispatch({ dispatch({
type: "graph brush deselect" type: "graph brush deselect"
}); });
} }
if (!d3.event) { 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 line clears the brush procedurally, ie., zoom button clicked,
this.props.dispatch({ not a click away from brush on svg
*/
svg.select(".graph_brush").call(brush.move, null);
dispatch({
type: "graph brush deselect" type: "graph brush deselect"
}); });
} }
} }
handleOpacityRangeChange(e) { handleOpacityRangeChange(e) {
this.props.dispatch({ const { dispatch } = this.props;
dispatch({
type: "change opacity deselected cells in 2d graph background", type: "change opacity deselected cells in 2d graph background",
data: e.target.value data: e.target.value
}); });
} }
render() { render() {
const { dispatch, responsive } = this.props;
const { mode } = this.state;
return ( return (
<div id="graphWrapper"> <div id="graphWrapper">
<div style={{ position: "fixed", right: 0, top: 0 }}> <div style={{ position: "fixed", right: 0, top: 0 }}>
@@ -319,8 +341,9 @@ class Graph extends React.Component {
}} }}
> >
<button <button
type="button"
onClick={() => { onClick={() => {
this.props.dispatch(actions.resetGraph()); dispatch(actions.resetGraph());
}} }}
style={{ style={{
fontSize: 14, fontSize: 14,
@@ -338,8 +361,9 @@ class Graph extends React.Component {
reset graph reset graph
</button> </button>
<button <button
type="button"
onClick={() => { onClick={() => {
this.props.dispatch(actions.regraph()); dispatch(actions.regraph());
}} }}
style={{ style={{
fontSize: 14, fontSize: 14,
@@ -359,15 +383,14 @@ class Graph extends React.Component {
<div> <div>
<span> <span>
<button <button
type="button"
onClick={() => { onClick={() => {
this.setState({ mode: "brush" }); this.setState({ mode: "brush" });
}} }}
style={{ style={{
cursor: "pointer", cursor: "pointer",
border: border:
this.state.mode === "brush" mode === "brush" ? "1px solid black" : "1px solid white",
? "1px solid black"
: "1px solid white",
backgroundColor: "white", backgroundColor: "white",
padding: 5, padding: 5,
marginRight: 10, marginRight: 10,
@@ -378,6 +401,7 @@ class Graph extends React.Component {
<FaCrosshair />{" "} <FaCrosshair />{" "}
</button> </button>
<button <button
type="button"
onClick={() => { onClick={() => {
this.handleBrushDeselectAction(); this.handleBrushDeselectAction();
this.restartReglLoop(); this.restartReglLoop();
@@ -386,9 +410,7 @@ class Graph extends React.Component {
style={{ style={{
cursor: "pointer", cursor: "pointer",
border: border:
this.state.mode === "zoom" mode === "zoom" ? "1px solid black" : "1px solid white",
? "1px solid black"
: "1px solid white",
backgroundColor: "white", backgroundColor: "white",
padding: 5, padding: 5,
marginRight: 10, marginRight: 10,
@@ -402,6 +424,7 @@ class Graph extends React.Component {
</div> </div>
<div> <div>
<button <button
type="button"
style={{ style={{
fontSize: 14, fontSize: 14,
fontWeight: 400, fontWeight: 400,
@@ -431,14 +454,14 @@ class Graph extends React.Component {
> >
<div <div
style={{ style={{
display: this.state.mode === "brush" ? "inherit" : "none" display: mode === "brush" ? "inherit" : "none"
}} }}
id="graphAttachPoint" id="graphAttachPoint"
/> />
<div style={{ padding: 0, margin: 0 }}> <div style={{ padding: 0, margin: 0 }}>
<canvas <canvas
width={this.props.responsive.height - this.graphPaddingTop} width={responsive.height - this.graphPaddingTop}
height={this.props.responsive.height - this.graphPaddingTop} height={responsive.height - this.graphPaddingTop}
ref={canvas => { ref={canvas => {
this.reglCanvas = canvas; this.reglCanvas = canvas;
}} }}
@@ -451,15 +474,3 @@ class Graph extends React.Component {
} }
export default Graph; export default Graph;
// <span style={{ marginRight: 10, fontSize: 12 }}>
// deselected opacity
// </span>
// <input
// style={{ position: "relative", top: 6, marginRight: 20 }}
// type="range"
// onChange={this.handleOpacityRangeChange.bind(this)}
// min={0}
// max={1}
// step="0.01"
// />
@@ -1,8 +1,6 @@
// jshint esversion: 6 // jshint esversion: 6
import styles from "./graph.css";
import _ from "lodash";
import * as globals from "../../globals";
import * as d3 from "d3"; import * as d3 from "d3";
import styles from "./graph.css";
/****************************************** /******************************************
******************************************* *******************************************
@@ -10,7 +8,7 @@ import * as d3 from "d3";
******************************************* *******************************************
******************************************/ ******************************************/
export const setupSVGandBrushElements = ( export default (
handleBrushSelectAction, handleBrushSelectAction,
handleBrushDeselectAction, handleBrushDeselectAction,
responsive, responsive,