From 888f7305263dea191487b3d7e9a081bb0948ce13 Mon Sep 17 00:00:00 2001 From: Matt Weiden <538456+mweiden@users.noreply.github.com> Date: Wed, 6 May 2020 13:46:13 -0700 Subject: [PATCH] Refactor CSS and React logic for layout (#1424) * Refactor CSS layout and react logic for layout Fixes https://github.com/chanzuckerberg/cellxgene/issues/1022 * Menubar should wrap inside middle pane instead of overlapping left sidebar when window is scrunched * cellxgene should have a minimum width of 1240px 1. Replace absolute positioning and dimension calculation with css grid 2. Use flexbox for wrapping menubar buttons * Middle pane (graph) can calculate its own size * Removing components calculating their size/position relative to eachother increases modularity, decreases use of global variables * Improved some scrollbar behavior * Removed responsive reducer, propagating window size to components triggers unnecessary events and encourages breaking modularity; doing this made some components state agnostic Reference: https://css-tricks.com/snippets/css/complete-guide-grid/ * Reposition the continuous legend * Small fixes * Respond to feedback from @colinmegill * Respond to feedback from @colinmegill Add more documentation on the renderGraph method. --- client/__tests__/e2e/data.js | 10 +- client/src/components/app.js | 49 ++-- client/src/components/autosave/index.js | 7 +- .../components/brushableHistogram/index.js | 4 +- .../src/components/continuousLegend/index.js | 25 +-- client/src/components/framework/container.css | 3 - client/src/components/framework/container.js | 21 +- client/src/components/framework/layout.js | 86 +++++++ client/src/components/geneExpression/index.js | 6 +- client/src/components/graph/graph.css | 18 -- client/src/components/graph/graph.js | 209 +++++++++--------- .../graph/overlays/centroidLabels.js | 3 +- .../graph/overlays/graphOverlayLayer.js | 34 ++- .../src/components/graph/setupSVGandBrush.js | 7 +- client/src/components/leftSidebar/index.js | 24 +- .../leftSidebar/topLeftLogoAndTitle.js | 14 +- client/src/components/menubar/clip.js | 6 +- .../src/components/menubar/diffexpButtons.js | 3 +- client/src/components/menubar/embedding.js | 5 +- client/src/components/menubar/index.js | 201 ++++++++--------- client/src/components/menubar/infoMenu.js | 5 +- client/src/components/menubar/menubar.css | 4 + client/src/components/menubar/subset.js | 9 +- client/src/components/menubar/undoRedo.js | 5 +- client/src/components/rightSidebar/index.js | 28 +-- .../src/components/scatterplot/scatterplot.js | 53 ++++- client/src/components/termsPrompt/index.js | 5 +- client/src/globals.js | 1 + client/src/reducers/index.js | 2 - client/src/reducers/responsive.js | 21 -- 30 files changed, 451 insertions(+), 417 deletions(-) delete mode 100644 client/src/components/framework/container.css create mode 100644 client/src/components/framework/layout.js create mode 100644 client/src/components/menubar/menubar.css delete mode 100644 client/src/reducers/responsive.js diff --git a/client/__tests__/e2e/data.js b/client/__tests__/e2e/data.js index 4ec285f0..e0fda7a8 100644 --- a/client/__tests__/e2e/data.js +++ b/client/__tests__/e2e/data.js @@ -27,7 +27,7 @@ export const datasets = { lasso: [ { "coordinates-as-percent": { x1: 0.1, y1: 0.25, x2: 0.7, y2: 0.75 }, - count: "1181", + count: "1173" }, ], categorical: [ @@ -98,7 +98,7 @@ export const datasets = { }, lasso: { "coordinates-as-percent": { x1: 0.25, y1: 0.05, x2: 0.75, y2: 0.55 }, - count: "329", + count: "331", }, }, scatter: { @@ -121,8 +121,8 @@ export const datasets = { }, newCount: { bySubsetConfig: { - false: "600", - true: "591", + false: "599", + true: "594", }, }, }, @@ -130,7 +130,7 @@ export const datasets = { count: { bySubsetConfig: { false: "1161", - true: "856", + true: "852", }, }, }, diff --git a/client/src/components/app.js b/client/src/components/app.js index 93e63b4f..ad949228 100644 --- a/client/src/components/app.js +++ b/client/src/components/app.js @@ -4,6 +4,7 @@ import Helmet from "react-helmet"; import { connect } from "react-redux"; import Container from "./framework/container"; +import Layout from "./framework/layout"; import LeftSideBar from "./leftSidebar"; import RightSideBar from "./rightSidebar"; import Legend from "./continuousLegend"; @@ -20,10 +21,6 @@ import actions from "../actions"; graphRenderCounter: state.controls.graphRenderCounter, })) class App extends React.Component { - constructor(props) { - super(props); - this.state = {}; - } componentDidMount() { const { dispatch } = this.props; @@ -33,24 +30,7 @@ class App extends React.Component { this._onURLChanged(); dispatch(actions.doInitialDataLoad(window.location.search)); - - /* listen for resize events */ - window.addEventListener("resize", () => { - dispatch({ - type: "window resize", - data: { - height: window.innerHeight, - width: window.innerWidth, - }, - }); - }); - dispatch({ - type: "window resize", - data: { - height: window.innerHeight, - width: window.innerWidth, - }, - }); + this.forceUpdate(); } _onURLChanged() { @@ -88,15 +68,22 @@ class App extends React.Component { error loading ) : null} -
- {loading ? null : } - {loading ? null : } - {loading ? null : } - {loading ? null : } - {loading ? null : } - {loading ? null : } - -
+ {loading ? null : + + {viewportRef => + <> + + + + + + + } + + } ); } diff --git a/client/src/components/autosave/index.js b/client/src/components/autosave/index.js index cc37bf08..bffd1740 100644 --- a/client/src/components/autosave/index.js +++ b/client/src/components/autosave/index.js @@ -80,10 +80,11 @@ class Autosave extends React.Component { : "autosave-complete" } style={{ - position: "fixed", + position: "absolute", display: "inherit", - right: globals.leftSidebarWidth + 5, - bottom: 5, + right: 8, + bottom: 8, + zIndex: 1, }} > {this.statusMessage()} diff --git a/client/src/components/brushableHistogram/index.js b/client/src/components/brushableHistogram/index.js index c32ea322..0e4521cd 100644 --- a/client/src/components/brushableHistogram/index.js +++ b/client/src/components/brushableHistogram/index.js @@ -88,8 +88,8 @@ class HistogramBrush extends React.PureComponent { constructor(props) { super(props); - this.marginLeft = 12; // Space for 0 tick label on X axis - this.marginRight = 52; // space for Y axis & labels + this.marginLeft = 10; // Space for 0 tick label on X axis + this.marginRight = 54; // space for Y axis & labels this.marginBottom = 25; // space for X axis & labels this.marginTop = 3; diff --git a/client/src/components/continuousLegend/index.js b/client/src/components/continuousLegend/index.js index 634f533c..9ca3aa1d 100644 --- a/client/src/components/continuousLegend/index.js +++ b/client/src/components/continuousLegend/index.js @@ -3,7 +3,6 @@ import React from "react"; import { connect } from "react-redux"; import * as d3 from "d3"; import { interpolateCool } from "d3-scale-chromatic"; -import * as globals from "../../globals"; // create continuous color legend // http://bl.ocks.org/syntagmatic/e8ccca52559796be775553b467593a9f @@ -97,22 +96,12 @@ const continuous = (selectorId, colorscale, colorAccessor) => { @connect((state) => ({ colorAccessor: state.colors.colorAccessor, colorScale: state.colors.scale, - responsive: state.responsive, })) class ContinuousLegend extends React.Component { - constructor(props) { - super(props); - this.state = {}; - } componentDidUpdate(prevProps) { - const { colorAccessor, responsive, colorScale } = this.props; - if ( - prevProps.colorAccessor !== colorAccessor || - prevProps.colorScale !== colorScale || - prevProps.responsive.height !== responsive.height || - prevProps.responsive.width !== responsive.width - ) { + const { colorAccessor, colorScale } = this.props; + if (prevProps.colorAccessor !== colorAccessor || prevProps.colorScale !== colorScale) { /* always remove it, if it's not continuous we don't put it back. */ d3.select("#continuous_legend").selectAll("*").remove(); } @@ -130,15 +119,17 @@ class ContinuousLegend extends React.Component { } render() { - const { colorAccessor, responsive } = this.props; + const { colorAccessor } = this.props; return (
{this.ref = ref}} style={{ - position: "fixed", display: colorAccessor ? "inherit" : "none", - right: globals.leftSidebarWidth, - top: responsive.height / 2, + position: "absolute", + left: 8, + top: 35, + zIndex: 1, }} /> ); diff --git a/client/src/components/framework/container.css b/client/src/components/framework/container.css deleted file mode 100644 index 2b76dccb..00000000 --- a/client/src/components/framework/container.css +++ /dev/null @@ -1,3 +0,0 @@ -:local(.container) { - min-height: 100%; -} diff --git a/client/src/components/framework/container.js b/client/src/components/framework/container.js index 558df99f..7b80bb2e 100644 --- a/client/src/components/framework/container.js +++ b/client/src/components/framework/container.js @@ -1,11 +1,20 @@ // jshint esversion: 6 import React from "react"; -import styles from "./container.css"; - -const Container = (props) => { - const { children } = props; - return
{children}
; -}; +function Container(props) { + const {children} = props; + return
+ {children} +
+} export default Container; diff --git a/client/src/components/framework/layout.js b/client/src/components/framework/layout.js new file mode 100644 index 00000000..83f2eb5a --- /dev/null +++ b/client/src/components/framework/layout.js @@ -0,0 +1,86 @@ +// jshint esversion: 6 +import React from "react"; +import * as globals from "../../globals"; + + +class Layout extends React.Component { + + /* + Layout - this react component contains all the layout style and logic for the application once it has loaded. + + The layout is based on CSS grid: the left and right sidebars have fixed widths, the graph in the middle takes the + remaining space. + + Note, the renderGraph child is a function rather than a fully-instantiated element because the middle pane of the + app is dynamically-sized. It must have access to the containing viewport in order to know how large the graph + should be. + */ + + componentDidMount() { + /* + This is a bit of a hack. In order for the graph to size correctly, it needs to know the size of the parent + viewport. Unfortunately, it can only do this once the parent div has been rendered, so we need to render twice. + */ + this.forceUpdate(); + } + + render() { + const { children } = this.props; + const [ leftSidebar, renderGraph, rightSidebar ] = children; + return
+
+ {leftSidebar} +
+
{ this.viewportRef = ref; }} + > + {this.viewportRef ? renderGraph(this.viewportRef) : null} +
+
+ {rightSidebar} +
+
; + } +} + +export default Layout; diff --git a/client/src/components/geneExpression/index.js b/client/src/components/geneExpression/index.js index a088ca8e..16a91184 100644 --- a/client/src/components/geneExpression/index.js +++ b/client/src/components/geneExpression/index.js @@ -214,7 +214,11 @@ class GeneExpression extends React.Component { if (!varIndex) return null; return ( -
+
0) { + // eslint-disable-next-line react/no-did-update-set-state this.setState(stateChanges); } } - handleCanvasEvent = (e) => { + componentWillUnmount() { + window.removeEventListener("resize", this.handleResize); + } + + handleResize = () => { + const { state } = this.state; + const viewport = this.getViewportDimensions(); + this.setState({ + ...state, + viewport, + }); + }; + + getViewportDimensions = () => { + const { viewportRef } = this.props; + return { + height: viewportRef.clientHeight, + width: viewportRef.clientWidth + }; + }; + + + handleCanvasEvent = e => { const { camera, projectionTF } = this.state; if (e.type !== "wheel") e.preventDefault(); if (camera.handleEvent(e, projectionTF)) { @@ -385,7 +392,7 @@ class Graph extends React.Component { Called from componentDidUpdate. Create the tool SVG, and return any state changes that should be passed to setState(). */ - const { responsive, selectionTool, graphInteractionMode } = this.props; + const { viewport, selectionTool, graphInteractionMode } = this.props; /* clear out whatever was on the div, even if nothing, but usually the brushes etc */ @@ -419,8 +426,7 @@ class Graph extends React.Component { handleDrag, handleEnd, handleCancel, - responsive, - this.graphPaddingRightLeft + viewport ); return { toolSVG: newToolSVG, tool, container }; @@ -512,14 +518,12 @@ class Graph extends React.Component { accounting for current pan/zoom camera. */ - const { responsive } = this.props; - const { camera, projectionTF, modelInvTF } = this.state; + const { camera, projectionTF, modelInvTF, viewport } = this.state; const cameraInvTF = camera.invView(); /* screen -> gl */ - const x = - (2 * pin[0]) / (responsive.width - this.graphPaddingRightLeft) - 1; - const y = 2 * (1 - pin[1] / (responsive.height - this.graphPaddingTop)) - 1; + const x = (2 * pin[0]) / viewport.width - 1; + const y = 2 * (1 - pin[1] / viewport.height) - 1; const xy = vec2.fromValues(x, y); const projectionInvTF = mat3.invert(mat3.create(), projectionTF); @@ -535,23 +539,21 @@ class Graph extends React.Component { of mapScreenToPoint() */ - const { responsive } = this.props; - const { camera, projectionTF, modelTF } = this.state; + const { camera, projectionTF, modelTF, viewport } = this.state; const cameraTF = camera.view(); const xy = vec2.transformMat3(vec2.create(), xyCell, modelTF); vec2.transformMat3(xy, xy, cameraTF); vec2.transformMat3(xy, xy, projectionTF); - const pin = [ + return [ Math.round( - ((xy[0] + 1) * (responsive.width - this.graphPaddingRightLeft)) / 2 + (xy[0] + 1) * viewport.width / 2 ), Math.round( - -((xy[1] + 1) / 2 - 1) * (responsive.height - this.graphPaddingTop) - ), + -((xy[1] + 1) / 2 - 1) * viewport.height + ) ]; - return pin; } handleBrushDragAction() { @@ -696,7 +698,7 @@ class Graph extends React.Component { count: this.count, projView, nPoints: universe.nObs, - minViewportDimension: Math.min(width || 800, height || 600), + minViewportDimension: Math.min(width, height) }); regl._gl.flush(); } @@ -723,66 +725,65 @@ class Graph extends React.Component { }); render() { - const { responsive, graphInteractionMode } = this.props; - const { modelTF, projectionTF, camera } = this.state; - + const { graphInteractionMode } = this.props; + const { modelTF, projectionTF, camera, viewport } = this.state; const cameraTF = camera?.view()?.slice(); return ( -
-
+ -
- - - - - -
-
- { - this.reglCanvas = canvas; - }} - onMouseDown={this.handleCanvasEvent} - onMouseUp={this.handleCanvasEvent} - onMouseMove={this.handleCanvasEvent} - onDoubleClick={this.handleCanvasEvent} - onWheel={this.handleCanvasEvent} - /> -
-
+ + + + { this.reglCanvas = canvas; }} + onMouseDown={this.handleCanvasEvent} + onMouseUp={this.handleCanvasEvent} + onMouseMove={this.handleCanvasEvent} + onDoubleClick={this.handleCanvasEvent} + onWheel={this.handleCanvasEvent} + />
); } diff --git a/client/src/components/graph/overlays/centroidLabels.js b/client/src/components/graph/overlays/centroidLabels.js index 9487dd4d..4a3f12f0 100644 --- a/client/src/components/graph/overlays/centroidLabels.js +++ b/client/src/components/graph/overlays/centroidLabels.js @@ -13,7 +13,8 @@ export default })) class CentroidLabels extends PureComponent { // Check to see if centroids have either just been displayed or removed from the overlay - componentDidUpdate = (prevProps) => { + + componentDidUpdate(prevProps) { const { labels, overlayToggled } = this.props; const prevSize = prevProps.labels.size; const { size } = labels; diff --git a/client/src/components/graph/overlays/graphOverlayLayer.js b/client/src/components/graph/overlays/graphOverlayLayer.js index a10c3b49..0ecd8770 100644 --- a/client/src/components/graph/overlays/graphOverlayLayer.js +++ b/client/src/components/graph/overlays/graphOverlayLayer.js @@ -1,12 +1,8 @@ import React, { PureComponent, cloneElement } from "react"; -import { connect } from "react-redux"; import styles from "../graph.css"; export default -@connect((state) => ({ - responsive: state.responsive, -})) class GraphOverlayLayer extends PureComponent { /* This component takes its children (assumed in the data coordinate space ([0, 1] range, origin in bottom left corner)) @@ -16,7 +12,6 @@ class GraphOverlayLayer extends PureComponent { */ constructor(props) { super(props); - this.state = { display: {}, }; @@ -50,16 +45,16 @@ class GraphOverlayLayer extends PureComponent { cameraTF, modelTF, projectionTF, - responsive, - graphPaddingRightLeft, - graphPaddingTop, children, handleCanvasEvent, + width, + height, + style } = this.props; + const { display } = this.state; if (!cameraTF) return null; - const { display } = this.state; const displaying = Object.values(display).some((value) => value); // check to see if at least one overlay is currently displayed const inverseTransform = `${this.reverseMatrixScaleTransformString( @@ -68,9 +63,7 @@ class GraphOverlayLayer extends PureComponent { cameraTF )} ${this.reverseMatrixScaleTransformString( projectionTF - )} scale(1 2) scale(1 ${ - 1 / -(responsive.height - graphPaddingTop) - }) scale(2 1) scale(${1 / (responsive.width - graphPaddingRightLeft)} 1)`; + )} scale(1 2) scale(1 ${1 / (-height)}) scale(2 1) scale(${1 / width} 1)`; // Copy the children passed with the overlay and add the inverse transform and onDisplayChange props const newChildren = React.Children.map(children, (child) => @@ -83,11 +76,14 @@ class GraphOverlayLayer extends PureComponent { return ( { - const svg = d3.select("#graphAttachPoint").select("#lasso-layer"); + const svg = d3.select("#graph-wrapper").select("#lasso-layer"); if (selectionToolType === "brush") { const brush = d3 .brush() .extent([ [0, 0], - [responsive.width - graphPaddingRight, responsive.height], + [viewport.width, viewport.height] ]) .on("start", handleStartAction) .on("brush", handleDragAction) diff --git a/client/src/components/leftSidebar/index.js b/client/src/components/leftSidebar/index.js index 83d0d5da..f2cd9f94 100644 --- a/client/src/components/leftSidebar/index.js +++ b/client/src/components/leftSidebar/index.js @@ -7,41 +7,29 @@ import DynamicScatterplot from "../scatterplot/scatterplot"; import TopLeftLogoAndTitle from "./topLeftLogoAndTitle"; @connect((state) => ({ - responsive: state.responsive, scatterplotXXaccessor: state.controls.scatterplotXXaccessor, scatterplotYYaccessor: state.controls.scatterplotYYaccessor, })) class LeftSideBar extends React.Component { + render() { - const { - responsive, - scatterplotXXaccessor, - scatterplotYYaccessor, - } = this.props; - - /* - this magic number should be made less fragile, - if cellxgene logo or tabs change, this must as well - */ - const logoRelatedPadding = 50; - + const { scatterplotXXaccessor, scatterplotYYaccessor } = this.props; return (
diff --git a/client/src/components/leftSidebar/topLeftLogoAndTitle.js b/client/src/components/leftSidebar/topLeftLogoAndTitle.js index 89dcbceb..3aa7e271 100644 --- a/client/src/components/leftSidebar/topLeftLogoAndTitle.js +++ b/client/src/components/leftSidebar/topLeftLogoAndTitle.js @@ -4,8 +4,7 @@ import { connect } from "react-redux"; import * as globals from "../../globals"; import Logo from "../framework/logo"; -@connect((state) => ({ - responsive: state.responsive, +@connect(state => ({ datasetTitle: state.config?.displayNames?.dataset ?? "", aboutURL: state.config?.links?.["about-dataset"], scatterplotXXaccessor: state.controls.scatterplotXXaccessor, @@ -15,8 +14,6 @@ class LeftSideBar extends React.Component { render() { const { datasetTitle, aboutURL } = this.props; - const paddingToAvoidScrollBar = 15; - const displayTitle = datasetTitle.length > globals.datasetTitleMaxCharacterCount ? `${datasetTitle.substring( @@ -32,12 +29,9 @@ class LeftSideBar extends React.Component { style={{ paddingLeft: 8, paddingTop: 8, - width: globals.leftSidebarWidth - paddingToAvoidScrollBar, - position: "absolute", - backgroundColor: "white", - zIndex: 8888, - /* x y blur spread color */ - // boxShadow: "-5px -1px 4px 2px rgba(225,225,225,0.4)" + width: globals.leftSidebarWidth, + zIndex: 1, + borderBottom: `1px solid ${globals.lighterGrey}`, }} > diff --git a/client/src/components/menubar/clip.js b/client/src/components/menubar/clip.js index 1cf9f8c0..0017ba5c 100644 --- a/client/src/components/menubar/clip.js +++ b/client/src/components/menubar/clip.js @@ -9,6 +9,7 @@ import { Tooltip, } from "@blueprintjs/core"; import { tooltipHoverOpenDelay } from "../../globals"; +import styles from "./menubar.css"; function Clip(props) { const { @@ -35,10 +36,7 @@ function Clip(props) { return (
+ - {disableDiffexp ? null : } - { - dispatch(actions.setWorldToSelection()); - dispatch({ type: "increment graph render counter" }); - }} - handleSubsetReset={() => { - dispatch(actions.resetWorldToUniverse()); - dispatch({ type: "increment graph render counter" }); - }} + + - - - { - dispatch({ - type: "change graph interaction mode", - data: "select", - }); - }} - style={{ - cursor: "pointer", - }} - /> - - - { - dispatch({ - type: "change graph interaction mode", - data: "zoom", - }); - }} - style={{ - cursor: "pointer", - }} - /> - - - -
- ); + + + { + dispatch(actions.setWorldToSelection()); + dispatch({ type: "increment graph render counter" }); + }} + handleSubsetReset={() => { + dispatch(actions.resetWorldToUniverse()); + dispatch({ type: "increment graph render counter" }); + }} + /> + {disableDiffexp ? null : } +
+ ); } } diff --git a/client/src/components/menubar/infoMenu.js b/client/src/components/menubar/infoMenu.js index c063fd45..7dda0d34 100644 --- a/client/src/components/menubar/infoMenu.js +++ b/client/src/components/menubar/infoMenu.js @@ -1,11 +1,14 @@ // jshint esversion: 6 import React from "react"; import { Button, Popover, Menu, MenuItem, Position } from "@blueprintjs/core"; +import styles from "./menubar.css"; function InformationMenu(props) { const { libraryVersions, aboutLink, tosURL, privacyURL } = props; return ( -
+
diff --git a/client/src/components/menubar/menubar.css b/client/src/components/menubar/menubar.css new file mode 100644 index 00000000..1a935f4b --- /dev/null +++ b/client/src/components/menubar/menubar.css @@ -0,0 +1,4 @@ +:local(.menubarButton) { + margin-top: 8px; + margin-left: 8px; +} \ No newline at end of file diff --git a/client/src/components/menubar/subset.js b/client/src/components/menubar/subset.js index 6fa640e4..42a9b78d 100644 --- a/client/src/components/menubar/subset.js +++ b/client/src/components/menubar/subset.js @@ -1,5 +1,6 @@ import React from "react"; import { AnchorButton, ButtonGroup, Tooltip } from "@blueprintjs/core"; +import styles from "./menubar.css"; import * as globals from "../../globals"; function Subset(props) { @@ -11,18 +12,21 @@ function Subset(props) { } = props; return ( - + + /> +
({ - responsive: state.responsive, +@connect(state => ({ scatterplotXXaccessor: state.controls.scatterplotXXaccessor, scatterplotYYaccessor: state.controls.scatterplotYYaccessor, })) class RightSidebar extends React.Component { - render() { - const { responsive } = this.props; + render() { return (
-
- - -
+ +
); } diff --git a/client/src/components/scatterplot/scatterplot.js b/client/src/components/scatterplot/scatterplot.js index 73a826b0..9a4fc65a 100644 --- a/client/src/components/scatterplot/scatterplot.js +++ b/client/src/components/scatterplot/scatterplot.js @@ -53,8 +53,6 @@ function createProjectionTF(viewportWidth, viewportHeight) { expressionY, crossfilter, - - responsive: state.responsive, }; }) class Scatterplot extends React.PureComponent { @@ -140,6 +138,10 @@ class Scatterplot extends React.PureComponent { this.state = { svg: null, minimized: null, + viewport: { + height: null, + width: null, + } }; } @@ -177,6 +179,9 @@ class Scatterplot extends React.PureComponent { projectionTF ); + window.addEventListener("resize", this.handleResize); + const viewport = this.getViewportDimensions(); + this.setState({ regl, flagBuffer, @@ -185,6 +190,7 @@ class Scatterplot extends React.PureComponent { svg, drawPoints, projectionTF, + viewport }); } @@ -272,6 +278,28 @@ class Scatterplot extends React.PureComponent { } } + componentWillUnmount() { + window.removeEventListener("resize", this.updateViewportDimensions); + } + + getViewportDimensions = () => { + return { + viewport: { + height: window.height, + width: window.width + } + }; + }; + + handleResize = () => { + const { state } = this.state; + const viewport = this.getViewportDimensions(); + this.setState({ + ...state, + viewport, + }); + }; + static setupScales(expressionX, expressionY) { const xScale = d3 .scaleLinear() @@ -288,6 +316,10 @@ class Scatterplot extends React.PureComponent { }; } + updateViewportDimensions = () => { + this.setState(this.getViewportDimensions()); + }; + drawAxesSVG(xScale, yScale, svg) { const { scatterplotYYaccessor, scatterplotXXaccessor } = this.props; svg.selectAll("*").remove(); @@ -341,12 +373,8 @@ class Scatterplot extends React.PureComponent { projectionTF ) { if (!this.reglCanvas) return; - const { universe, responsive } = this.props; - // The viewport dimension is used to scale points, so we want to pass - // the dimension of the MAIN viewport, not the scatterplot viewport. - // Slightly hacky, but we want all points to scale uniformly. Perhaps - // this should move to the redux state and be shared? - const { width: cvWidth, height: cvHeight } = responsive; + const { universe } = this.props; + const { viewport } = this.state; regl.poll(); regl.clear({ depth: 1, @@ -360,9 +388,9 @@ class Scatterplot extends React.PureComponent { count: this.count, nPoints: universe.nObs, minViewportDimension: Math.min( - cvWidth - globals.leftSidebarWidth || width, - cvHeight || height - ), + viewport.width - globals.leftSidebarWidth || width, + viewport.height || height + ) }); regl._gl.flush(); } @@ -379,9 +407,10 @@ class Scatterplot extends React.PureComponent { borderRadius: "3px 3px 0px 0px", left: globals.leftSidebarWidth + globals.scatterplotMarginLeft, padding: "0px 20px 20px 0px", - backgroundColor: "white", + background: "white", /* x y blur spread color */ boxShadow: "0px 0px 6px 2px rgba(153,153,153,0.4)", + zIndex: 2, }} id="scatterplot_wrapper" > diff --git a/client/src/components/termsPrompt/index.js b/client/src/components/termsPrompt/index.js index ab345a69..ae1ad0c0 100644 --- a/client/src/components/termsPrompt/index.js +++ b/client/src/components/termsPrompt/index.js @@ -9,6 +9,7 @@ import { Icon, } from "@blueprintjs/core"; import * as globals from "../../globals"; +import { termsOfServiceToast } from "../framework/toasters"; const CookieDecision = "cxg.cookieDecision"; @@ -25,9 +26,7 @@ function storageGet(key, defaultValue = null) { function storageSet(key, value) { try { window.localStorage.setItem(key, value); - } catch { - return; - } + } catch {} } @connect((state) => ({ diff --git a/client/src/globals.js b/client/src/globals.js index 4d151418..e3c9a896 100644 --- a/client/src/globals.js +++ b/client/src/globals.js @@ -63,6 +63,7 @@ export const graphWidth = 700; export const graphHeight = 700; export const scatterplotMarginLeft = 25; +export const rightSidebarWidth = 365; export const leftSidebarWidth = 365; export const leftSidebarSectionHeading = { fontSize: 18, diff --git a/client/src/reducers/index.js b/client/src/reducers/index.js index 17c4e752..e0703c9e 100644 --- a/client/src/reducers/index.js +++ b/client/src/reducers/index.js @@ -13,7 +13,6 @@ import crossfilter from "./crossfilter"; import colors from "./colors"; import differential from "./differential"; import layoutChoice from "./layoutChoice"; -import responsive from "./responsive"; import controls from "./controls"; import resetCache from "./resetCache"; import annotations from "./annotations"; @@ -41,7 +40,6 @@ const Reducer = undoable( ["controls", controls], ["crossfilter", crossfilter], ["differential", differential], - ["responsive", responsive], ["centroidLabels", centroidLabels], ["pointDilation", pointDialation], ["reembedController", reembedController], diff --git a/client/src/reducers/responsive.js b/client/src/reducers/responsive.js deleted file mode 100644 index 926ed299..00000000 --- a/client/src/reducers/responsive.js +++ /dev/null @@ -1,21 +0,0 @@ -// jshint esversion: 6 -const Responsive = ( - state = { - width: null, - height: null, - }, - action -) => { - switch (action.type) { - case "window resize": - return { - ...state, - width: action.data.width, - height: action.data.height, - }; - default: - return state; - } -}; - -export default Responsive;