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({
+ type: "change graph interaction mode",
+ data: "select"
+ });
+ }}
/>
-
+
+ {
+ dispatch({
+ type: "change graph interaction mode",
+ data: "zoom"
+ });
+ }}
/>
-
- );
+
+
+
{
+ 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;