mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-08 10:48:12 +08:00
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.
This commit is contained in:
@@ -27,7 +27,7 @@ export const datasets = {
|
|||||||
lasso: [
|
lasso: [
|
||||||
{
|
{
|
||||||
"coordinates-as-percent": { x1: 0.1, y1: 0.25, x2: 0.7, y2: 0.75 },
|
"coordinates-as-percent": { x1: 0.1, y1: 0.25, x2: 0.7, y2: 0.75 },
|
||||||
count: "1181",
|
count: "1173"
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
categorical: [
|
categorical: [
|
||||||
@@ -98,7 +98,7 @@ export const datasets = {
|
|||||||
},
|
},
|
||||||
lasso: {
|
lasso: {
|
||||||
"coordinates-as-percent": { x1: 0.25, y1: 0.05, x2: 0.75, y2: 0.55 },
|
"coordinates-as-percent": { x1: 0.25, y1: 0.05, x2: 0.75, y2: 0.55 },
|
||||||
count: "329",
|
count: "331",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
scatter: {
|
scatter: {
|
||||||
@@ -121,8 +121,8 @@ export const datasets = {
|
|||||||
},
|
},
|
||||||
newCount: {
|
newCount: {
|
||||||
bySubsetConfig: {
|
bySubsetConfig: {
|
||||||
false: "600",
|
false: "599",
|
||||||
true: "591",
|
true: "594",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -130,7 +130,7 @@ export const datasets = {
|
|||||||
count: {
|
count: {
|
||||||
bySubsetConfig: {
|
bySubsetConfig: {
|
||||||
false: "1161",
|
false: "1161",
|
||||||
true: "856",
|
true: "852",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Helmet from "react-helmet";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import Container from "./framework/container";
|
import Container from "./framework/container";
|
||||||
|
import Layout from "./framework/layout";
|
||||||
import LeftSideBar from "./leftSidebar";
|
import LeftSideBar from "./leftSidebar";
|
||||||
import RightSideBar from "./rightSidebar";
|
import RightSideBar from "./rightSidebar";
|
||||||
import Legend from "./continuousLegend";
|
import Legend from "./continuousLegend";
|
||||||
@@ -20,10 +21,6 @@ import actions from "../actions";
|
|||||||
graphRenderCounter: state.controls.graphRenderCounter,
|
graphRenderCounter: state.controls.graphRenderCounter,
|
||||||
}))
|
}))
|
||||||
class App extends React.Component {
|
class App extends React.Component {
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
@@ -33,24 +30,7 @@ class App extends React.Component {
|
|||||||
this._onURLChanged();
|
this._onURLChanged();
|
||||||
|
|
||||||
dispatch(actions.doInitialDataLoad(window.location.search));
|
dispatch(actions.doInitialDataLoad(window.location.search));
|
||||||
|
this.forceUpdate();
|
||||||
/* 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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onURLChanged() {
|
_onURLChanged() {
|
||||||
@@ -88,15 +68,22 @@ class App extends React.Component {
|
|||||||
error loading
|
error loading
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div>
|
{loading ? null : <Layout>
|
||||||
{loading ? null : <LeftSideBar />}
|
<LeftSideBar/>
|
||||||
{loading ? null : <RightSideBar />}
|
{viewportRef =>
|
||||||
{loading ? null : <MenuBar />}
|
<>
|
||||||
{loading ? null : <Graph key={graphRenderCounter} />}
|
<MenuBar/>
|
||||||
{loading ? null : <Autosave />}
|
<Autosave/>
|
||||||
{loading ? null : <TermsOfServicePrompt />}
|
<TermsOfServicePrompt/>
|
||||||
<Legend />
|
<Legend viewportRef={viewportRef}/>
|
||||||
</div>
|
<Graph
|
||||||
|
key={graphRenderCounter}
|
||||||
|
viewportRef={viewportRef}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
<RightSideBar/>
|
||||||
|
</Layout>}
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,10 +80,11 @@ class Autosave extends React.Component {
|
|||||||
: "autosave-complete"
|
: "autosave-complete"
|
||||||
}
|
}
|
||||||
style={{
|
style={{
|
||||||
position: "fixed",
|
position: "absolute",
|
||||||
display: "inherit",
|
display: "inherit",
|
||||||
right: globals.leftSidebarWidth + 5,
|
right: 8,
|
||||||
bottom: 5,
|
bottom: 8,
|
||||||
|
zIndex: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{this.statusMessage()}
|
{this.statusMessage()}
|
||||||
|
|||||||
@@ -88,8 +88,8 @@ class HistogramBrush extends React.PureComponent {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.marginLeft = 12; // Space for 0 tick label on X axis
|
this.marginLeft = 10; // Space for 0 tick label on X axis
|
||||||
this.marginRight = 52; // space for Y axis & labels
|
this.marginRight = 54; // space for Y axis & labels
|
||||||
this.marginBottom = 25; // space for X axis & labels
|
this.marginBottom = 25; // space for X axis & labels
|
||||||
this.marginTop = 3;
|
this.marginTop = 3;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import React from "react";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
import { interpolateCool } from "d3-scale-chromatic";
|
import { interpolateCool } from "d3-scale-chromatic";
|
||||||
import * as globals from "../../globals";
|
|
||||||
|
|
||||||
// create continuous color legend
|
// create continuous color legend
|
||||||
// http://bl.ocks.org/syntagmatic/e8ccca52559796be775553b467593a9f
|
// http://bl.ocks.org/syntagmatic/e8ccca52559796be775553b467593a9f
|
||||||
@@ -97,22 +96,12 @@ const continuous = (selectorId, colorscale, colorAccessor) => {
|
|||||||
@connect((state) => ({
|
@connect((state) => ({
|
||||||
colorAccessor: state.colors.colorAccessor,
|
colorAccessor: state.colors.colorAccessor,
|
||||||
colorScale: state.colors.scale,
|
colorScale: state.colors.scale,
|
||||||
responsive: state.responsive,
|
|
||||||
}))
|
}))
|
||||||
class ContinuousLegend extends React.Component {
|
class ContinuousLegend extends React.Component {
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
const { colorAccessor, responsive, colorScale } = this.props;
|
const { colorAccessor, colorScale } = this.props;
|
||||||
if (
|
if (prevProps.colorAccessor !== colorAccessor || prevProps.colorScale !== colorScale) {
|
||||||
prevProps.colorAccessor !== colorAccessor ||
|
|
||||||
prevProps.colorScale !== colorScale ||
|
|
||||||
prevProps.responsive.height !== responsive.height ||
|
|
||||||
prevProps.responsive.width !== responsive.width
|
|
||||||
) {
|
|
||||||
/* always remove it, if it's not continuous we don't put it back. */
|
/* always remove it, if it's not continuous we don't put it back. */
|
||||||
d3.select("#continuous_legend").selectAll("*").remove();
|
d3.select("#continuous_legend").selectAll("*").remove();
|
||||||
}
|
}
|
||||||
@@ -130,15 +119,17 @@ class ContinuousLegend extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { colorAccessor, responsive } = this.props;
|
const { colorAccessor } = this.props;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
id="continuous_legend"
|
id="continuous_legend"
|
||||||
|
ref={ref => {this.ref = ref}}
|
||||||
style={{
|
style={{
|
||||||
position: "fixed",
|
|
||||||
display: colorAccessor ? "inherit" : "none",
|
display: colorAccessor ? "inherit" : "none",
|
||||||
right: globals.leftSidebarWidth,
|
position: "absolute",
|
||||||
top: responsive.height / 2,
|
left: 8,
|
||||||
|
top: 35,
|
||||||
|
zIndex: 1,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
:local(.container) {
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,20 @@
|
|||||||
// jshint esversion: 6
|
// jshint esversion: 6
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import styles from "./container.css";
|
function Container(props) {
|
||||||
|
const {children} = props;
|
||||||
const Container = (props) => {
|
return <div
|
||||||
const { children } = props;
|
className="container"
|
||||||
return <div className={styles.container}>{children}</div>;
|
style={{
|
||||||
};
|
height: "calc(100vh - (100vh - 100%))",
|
||||||
|
width: "calc(100vw - (100vw - 100%))",
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
export default Container;
|
export default Container;
|
||||||
|
|||||||
@@ -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 <div
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: `
|
||||||
|
[left-sidebar-start] ${globals.leftSidebarWidth+1}px
|
||||||
|
[left-sidebar-end graph-start] auto
|
||||||
|
[graph-end right-sidebar-start] ${globals.rightSidebarWidth+1}px [right-sidebar-end]
|
||||||
|
`,
|
||||||
|
gridTemplateRows: "[top] auto [bottom]",
|
||||||
|
gridTemplateAreas: "left-sidebar | graph | right-sidebar",
|
||||||
|
columnGap: "0px",
|
||||||
|
justifyItems: "stretch",
|
||||||
|
alignItems: "stretch",
|
||||||
|
height: "inherit",
|
||||||
|
width: "inherit",
|
||||||
|
position: "relative",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
minWidth: "1240px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
gridArea: "top / left-sidebar-start / bottom / left-sidebar-end",
|
||||||
|
position: "relative",
|
||||||
|
height: "inherit",
|
||||||
|
overflowY: "auto"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{leftSidebar}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
zIndex: 0,
|
||||||
|
gridArea: "top / graph-start / bottom / graph-end",
|
||||||
|
position: "relative",
|
||||||
|
height: "inherit",
|
||||||
|
}}
|
||||||
|
ref={ref => { this.viewportRef = ref; }}
|
||||||
|
>
|
||||||
|
{this.viewportRef ? renderGraph(this.viewportRef) : null}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
gridArea: "top / right-sidebar-start / bottom / right-sidebar-end",
|
||||||
|
position: "relative",
|
||||||
|
height: "inherit",
|
||||||
|
overflowY: "auto"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{rightSidebar}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
@@ -214,7 +214,11 @@ class GeneExpression extends React.Component {
|
|||||||
if (!varIndex) return null;
|
if (!varIndex) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div
|
||||||
|
style={{
|
||||||
|
borderBottom: `1px solid ${globals.lighterGrey}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -1,21 +1,3 @@
|
|||||||
:local(.graphCanvas) path {
|
|
||||||
shape-rendering: crispEdges;
|
|
||||||
}
|
|
||||||
|
|
||||||
#graphWrapper {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
#graphAttachPoint,
|
|
||||||
.graphSVG,
|
|
||||||
.graphCanvas {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
#graphSVG {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
:local(.axis) path,
|
:local(.axis) path,
|
||||||
:local(.axis) line {
|
:local(.axis) line {
|
||||||
fill: none;
|
fill: none;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { mat3, vec2 } from "gl-matrix";
|
|||||||
import _regl from "regl";
|
import _regl from "regl";
|
||||||
import memoize from "memoize-one";
|
import memoize from "memoize-one";
|
||||||
|
|
||||||
import * as globals from "../../globals";
|
|
||||||
import setupSVGandBrushElements from "./setupSVGandBrush";
|
import setupSVGandBrushElements from "./setupSVGandBrush";
|
||||||
import _camera from "../../util/camera";
|
import _camera from "../../util/camera";
|
||||||
import _drawPoints from "./drawPointsRegl";
|
import _drawPoints from "./drawPointsRegl";
|
||||||
@@ -77,7 +76,6 @@ function renderThrottle(callback) {
|
|||||||
universe: state.universe,
|
universe: state.universe,
|
||||||
world: state.world,
|
world: state.world,
|
||||||
crossfilter: state.crossfilter,
|
crossfilter: state.crossfilter,
|
||||||
responsive: state.responsive,
|
|
||||||
colorRGB: state.colors.rgb,
|
colorRGB: state.colors.rgb,
|
||||||
selectionTool: state.graphSelection.tool,
|
selectionTool: state.graphSelection.tool,
|
||||||
currentSelection: state.graphSelection.selection,
|
currentSelection: state.graphSelection.selection,
|
||||||
@@ -177,9 +175,8 @@ class Graph extends React.Component {
|
|||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
const viewport = this.getViewportDimensions();
|
||||||
this.count = 0;
|
this.count = 0;
|
||||||
this.graphPaddingTop = 0;
|
|
||||||
this.graphPaddingRightLeft = globals.leftSidebarWidth * 2;
|
|
||||||
this.renderCache = {
|
this.renderCache = {
|
||||||
X: null,
|
X: null,
|
||||||
Y: null,
|
Y: null,
|
||||||
@@ -193,10 +190,13 @@ class Graph extends React.Component {
|
|||||||
tool: null,
|
tool: null,
|
||||||
container: null,
|
container: null,
|
||||||
cameraRender: 0,
|
cameraRender: 0,
|
||||||
|
viewport
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
window.addEventListener("resize", this.handleResize);
|
||||||
|
|
||||||
// setup canvas, webgl draw function and camera
|
// setup canvas, webgl draw function and camera
|
||||||
const camera = _camera(this.reglCanvas);
|
const camera = _camera(this.reglCanvas);
|
||||||
const regl = _regl(this.reglCanvas);
|
const regl = _regl(this.reglCanvas);
|
||||||
@@ -209,10 +209,7 @@ class Graph extends React.Component {
|
|||||||
|
|
||||||
// create all default rendering transformations
|
// create all default rendering transformations
|
||||||
const modelTF = createModelTF();
|
const modelTF = createModelTF();
|
||||||
const projectionTF = createProjectionTF(
|
const projectionTF = createProjectionTF(this.reglCanvas.width, this.reglCanvas.height);
|
||||||
this.reglCanvas.width,
|
|
||||||
this.reglCanvas.height
|
|
||||||
);
|
|
||||||
|
|
||||||
// initial draw to canvas
|
// initial draw to canvas
|
||||||
this.renderPoints(
|
this.renderPoints(
|
||||||
@@ -238,13 +235,12 @@ class Graph extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
const { renderCache } = this;
|
const { renderCache } = this;
|
||||||
const {
|
const {
|
||||||
world,
|
world,
|
||||||
crossfilter,
|
crossfilter,
|
||||||
colorRGB,
|
colorRGB,
|
||||||
responsive,
|
|
||||||
selectionTool,
|
selectionTool,
|
||||||
currentSelection,
|
currentSelection,
|
||||||
layoutChoice,
|
layoutChoice,
|
||||||
@@ -252,26 +248,20 @@ class Graph extends React.Component {
|
|||||||
pointDilation,
|
pointDilation,
|
||||||
colorAccessor,
|
colorAccessor,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { regl, toolSVG, camera, modelTF } = this.state;
|
const { regl, toolSVG, camera, modelTF, viewport } = this.state;
|
||||||
|
let { projectionTF } = this.state;
|
||||||
|
const hasResized = prevState.viewport.height !== this.reglCanvas.height ||
|
||||||
|
prevState.viewport.width !== this.reglCanvas.width;
|
||||||
let stateChanges = {};
|
let stateChanges = {};
|
||||||
|
let needsRepaint = hasResized;
|
||||||
|
|
||||||
if (regl && world && crossfilter) {
|
if (regl && world && crossfilter) {
|
||||||
/* update the regl and point rendering state */
|
/* update the regl and point rendering state */
|
||||||
const { obsLayout, nObs } = world;
|
const { obsLayout, nObs } = world;
|
||||||
const { drawPoints, pointBuffer, colorBuffer, flagBuffer } = this.state;
|
const { drawPoints, pointBuffer, colorBuffer, flagBuffer } = this.state;
|
||||||
|
|
||||||
let { projectionTF } = this.state;
|
if (hasResized) {
|
||||||
let needsRepaint = false;
|
projectionTF = createProjectionTF(this.reglCanvas.width, this.reglCanvas.height);
|
||||||
|
|
||||||
if (
|
|
||||||
prevProps.responsive.height !== responsive.height ||
|
|
||||||
prevProps.responsive.width !== responsive.width
|
|
||||||
) {
|
|
||||||
projectionTF = createProjectionTF(
|
|
||||||
this.reglCanvas.width,
|
|
||||||
this.reglCanvas.height
|
|
||||||
);
|
|
||||||
needsRepaint = true;
|
|
||||||
stateChanges = {
|
stateChanges = {
|
||||||
...stateChanges,
|
...stateChanges,
|
||||||
projectionTF,
|
projectionTF,
|
||||||
@@ -326,19 +316,13 @@ class Graph extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (hasResized) {
|
||||||
prevProps.responsive.height !== responsive.height ||
|
|
||||||
prevProps.responsive.width !== responsive.width
|
|
||||||
) {
|
|
||||||
// If the window size has changed we want to recreate all SVGs
|
// If the window size has changed we want to recreate all SVGs
|
||||||
stateChanges = {
|
stateChanges = {
|
||||||
...stateChanges,
|
...stateChanges,
|
||||||
...this.createToolSVG(),
|
...this.createToolSVG(),
|
||||||
};
|
};
|
||||||
} else if (
|
} else if ((viewport.height && viewport.width && !toolSVG) || selectionTool !== prevProps.selectionTool ) {
|
||||||
(responsive.height && responsive.width && !toolSVG) ||
|
|
||||||
selectionTool !== prevProps.selectionTool
|
|
||||||
) {
|
|
||||||
// first time or change of selection tool
|
// first time or change of selection tool
|
||||||
stateChanges = { ...stateChanges, ...this.createToolSVG() };
|
stateChanges = { ...stateChanges, ...this.createToolSVG() };
|
||||||
} else if (prevProps.graphInteractionMode !== graphInteractionMode) {
|
} else if (prevProps.graphInteractionMode !== graphInteractionMode) {
|
||||||
@@ -365,11 +349,34 @@ class Graph extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (Object.keys(stateChanges).length > 0) {
|
if (Object.keys(stateChanges).length > 0) {
|
||||||
|
// eslint-disable-next-line react/no-did-update-set-state
|
||||||
this.setState(stateChanges);
|
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;
|
const { camera, projectionTF } = this.state;
|
||||||
if (e.type !== "wheel") e.preventDefault();
|
if (e.type !== "wheel") e.preventDefault();
|
||||||
if (camera.handleEvent(e, projectionTF)) {
|
if (camera.handleEvent(e, projectionTF)) {
|
||||||
@@ -385,7 +392,7 @@ class Graph extends React.Component {
|
|||||||
Called from componentDidUpdate. Create the tool SVG, and return any
|
Called from componentDidUpdate. Create the tool SVG, and return any
|
||||||
state changes that should be passed to setState().
|
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 */
|
/* 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,
|
handleDrag,
|
||||||
handleEnd,
|
handleEnd,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
responsive,
|
viewport
|
||||||
this.graphPaddingRightLeft
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return { toolSVG: newToolSVG, tool, container };
|
return { toolSVG: newToolSVG, tool, container };
|
||||||
@@ -512,14 +518,12 @@ class Graph extends React.Component {
|
|||||||
accounting for current pan/zoom camera.
|
accounting for current pan/zoom camera.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { responsive } = this.props;
|
const { camera, projectionTF, modelInvTF, viewport } = this.state;
|
||||||
const { camera, projectionTF, modelInvTF } = this.state;
|
|
||||||
const cameraInvTF = camera.invView();
|
const cameraInvTF = camera.invView();
|
||||||
|
|
||||||
/* screen -> gl */
|
/* screen -> gl */
|
||||||
const x =
|
const x = (2 * pin[0]) / viewport.width - 1;
|
||||||
(2 * pin[0]) / (responsive.width - this.graphPaddingRightLeft) - 1;
|
const y = 2 * (1 - pin[1] / viewport.height) - 1;
|
||||||
const y = 2 * (1 - pin[1] / (responsive.height - this.graphPaddingTop)) - 1;
|
|
||||||
|
|
||||||
const xy = vec2.fromValues(x, y);
|
const xy = vec2.fromValues(x, y);
|
||||||
const projectionInvTF = mat3.invert(mat3.create(), projectionTF);
|
const projectionInvTF = mat3.invert(mat3.create(), projectionTF);
|
||||||
@@ -535,23 +539,21 @@ class Graph extends React.Component {
|
|||||||
of mapScreenToPoint()
|
of mapScreenToPoint()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { responsive } = this.props;
|
const { camera, projectionTF, modelTF, viewport } = this.state;
|
||||||
const { camera, projectionTF, modelTF } = this.state;
|
|
||||||
const cameraTF = camera.view();
|
const cameraTF = camera.view();
|
||||||
|
|
||||||
const xy = vec2.transformMat3(vec2.create(), xyCell, modelTF);
|
const xy = vec2.transformMat3(vec2.create(), xyCell, modelTF);
|
||||||
vec2.transformMat3(xy, xy, cameraTF);
|
vec2.transformMat3(xy, xy, cameraTF);
|
||||||
vec2.transformMat3(xy, xy, projectionTF);
|
vec2.transformMat3(xy, xy, projectionTF);
|
||||||
|
|
||||||
const pin = [
|
return [
|
||||||
Math.round(
|
Math.round(
|
||||||
((xy[0] + 1) * (responsive.width - this.graphPaddingRightLeft)) / 2
|
(xy[0] + 1) * viewport.width / 2
|
||||||
),
|
),
|
||||||
Math.round(
|
Math.round(
|
||||||
-((xy[1] + 1) / 2 - 1) * (responsive.height - this.graphPaddingTop)
|
-((xy[1] + 1) / 2 - 1) * viewport.height
|
||||||
),
|
)
|
||||||
];
|
];
|
||||||
return pin;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBrushDragAction() {
|
handleBrushDragAction() {
|
||||||
@@ -696,7 +698,7 @@ class Graph extends React.Component {
|
|||||||
count: this.count,
|
count: this.count,
|
||||||
projView,
|
projView,
|
||||||
nPoints: universe.nObs,
|
nPoints: universe.nObs,
|
||||||
minViewportDimension: Math.min(width || 800, height || 600),
|
minViewportDimension: Math.min(width, height)
|
||||||
});
|
});
|
||||||
regl._gl.flush();
|
regl._gl.flush();
|
||||||
}
|
}
|
||||||
@@ -723,66 +725,65 @@ class Graph extends React.Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { responsive, graphInteractionMode } = this.props;
|
const { graphInteractionMode } = this.props;
|
||||||
const { modelTF, projectionTF, camera } = this.state;
|
const { modelTF, projectionTF, camera, viewport } = this.state;
|
||||||
|
|
||||||
const cameraTF = camera?.view()?.slice();
|
const cameraTF = camera?.view()?.slice();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="graphWrapper">
|
<div
|
||||||
<div
|
id="graph-wrapper"
|
||||||
style={{
|
style={{
|
||||||
zIndex: -9999,
|
position: "relative",
|
||||||
position: "fixed",
|
top: 0,
|
||||||
top: this.graphPaddingTop,
|
left: 0,
|
||||||
right: globals.leftSidebarWidth,
|
}}
|
||||||
}}
|
>
|
||||||
|
<GraphOverlayLayer
|
||||||
|
width={viewport.width}
|
||||||
|
height={viewport.height}
|
||||||
|
cameraTF={cameraTF}
|
||||||
|
modelTF={modelTF}
|
||||||
|
projectionTF={projectionTF}
|
||||||
|
handleCanvasEvent={graphInteractionMode === "zoom" ? this.handleCanvasEvent : undefined}
|
||||||
>
|
>
|
||||||
<div id="graphAttachPoint">
|
<CentroidLabels/>
|
||||||
<GraphOverlayLayer
|
</GraphOverlayLayer>
|
||||||
cameraTF={cameraTF}
|
<svg
|
||||||
modelTF={modelTF}
|
id="lasso-layer"
|
||||||
projectionTF={projectionTF}
|
data-testid="layout-overlay"
|
||||||
graphPaddingRightLeft={this.graphPaddingRightLeft}
|
className="graph-svg"
|
||||||
graphPaddingTop={this.graphPaddingTop}
|
style={{
|
||||||
responsive={responsive}
|
position: "absolute",
|
||||||
handleCanvasEvent={
|
top: 0,
|
||||||
graphInteractionMode === "zoom"
|
left: 0,
|
||||||
? this.handleCanvasEvent
|
zIndex: 1
|
||||||
: undefined
|
}}
|
||||||
}
|
width={viewport.width}
|
||||||
>
|
height={viewport.height}
|
||||||
<CentroidLabels />
|
pointerEvents={
|
||||||
</GraphOverlayLayer>
|
graphInteractionMode === "select" ? "auto" : "none"
|
||||||
|
}
|
||||||
<svg
|
/>
|
||||||
id="lasso-layer"
|
<canvas
|
||||||
data-testid="layout-overlay"
|
width={viewport.width}
|
||||||
className={styles.graphSVG}
|
height={viewport.height}
|
||||||
width={responsive.width - this.graphPaddingRightLeft}
|
style={{
|
||||||
height={responsive.height}
|
position: "absolute",
|
||||||
pointerEvents={
|
top: 0,
|
||||||
graphInteractionMode === "select" ? "auto" : "none"
|
left: 0,
|
||||||
}
|
padding: 0,
|
||||||
style={{ zIndex: 89 }}
|
margin: 0,
|
||||||
/>
|
shapeRendering: "crispEdges",
|
||||||
</div>
|
}}
|
||||||
<div style={{ padding: 0, margin: 0 }}>
|
className="graph-canvas"
|
||||||
<canvas
|
data-testid="layout-graph"
|
||||||
width={responsive.width - this.graphPaddingRightLeft}
|
ref={canvas => { this.reglCanvas = canvas; }}
|
||||||
height={responsive.height - this.graphPaddingTop}
|
onMouseDown={this.handleCanvasEvent}
|
||||||
data-testid="layout-graph"
|
onMouseUp={this.handleCanvasEvent}
|
||||||
ref={(canvas) => {
|
onMouseMove={this.handleCanvasEvent}
|
||||||
this.reglCanvas = canvas;
|
onDoubleClick={this.handleCanvasEvent}
|
||||||
}}
|
onWheel={this.handleCanvasEvent}
|
||||||
onMouseDown={this.handleCanvasEvent}
|
/>
|
||||||
onMouseUp={this.handleCanvasEvent}
|
|
||||||
onMouseMove={this.handleCanvasEvent}
|
|
||||||
onDoubleClick={this.handleCanvasEvent}
|
|
||||||
onWheel={this.handleCanvasEvent}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ export default
|
|||||||
}))
|
}))
|
||||||
class CentroidLabels extends PureComponent {
|
class CentroidLabels extends PureComponent {
|
||||||
// Check to see if centroids have either just been displayed or removed from the overlay
|
// 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 { labels, overlayToggled } = this.props;
|
||||||
const prevSize = prevProps.labels.size;
|
const prevSize = prevProps.labels.size;
|
||||||
const { size } = labels;
|
const { size } = labels;
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
import React, { PureComponent, cloneElement } from "react";
|
import React, { PureComponent, cloneElement } from "react";
|
||||||
import { connect } from "react-redux";
|
|
||||||
|
|
||||||
import styles from "../graph.css";
|
import styles from "../graph.css";
|
||||||
|
|
||||||
export default
|
export default
|
||||||
@connect((state) => ({
|
|
||||||
responsive: state.responsive,
|
|
||||||
}))
|
|
||||||
class GraphOverlayLayer extends PureComponent {
|
class GraphOverlayLayer extends PureComponent {
|
||||||
/*
|
/*
|
||||||
This component takes its children (assumed in the data coordinate space ([0, 1] range, origin in bottom left corner))
|
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) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
display: {},
|
display: {},
|
||||||
};
|
};
|
||||||
@@ -50,16 +45,16 @@ class GraphOverlayLayer extends PureComponent {
|
|||||||
cameraTF,
|
cameraTF,
|
||||||
modelTF,
|
modelTF,
|
||||||
projectionTF,
|
projectionTF,
|
||||||
responsive,
|
|
||||||
graphPaddingRightLeft,
|
|
||||||
graphPaddingTop,
|
|
||||||
children,
|
children,
|
||||||
handleCanvasEvent,
|
handleCanvasEvent,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
style
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
const { display } = this.state;
|
||||||
|
|
||||||
if (!cameraTF) return null;
|
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 displaying = Object.values(display).some((value) => value); // check to see if at least one overlay is currently displayed
|
||||||
|
|
||||||
const inverseTransform = `${this.reverseMatrixScaleTransformString(
|
const inverseTransform = `${this.reverseMatrixScaleTransformString(
|
||||||
@@ -68,9 +63,7 @@ class GraphOverlayLayer extends PureComponent {
|
|||||||
cameraTF
|
cameraTF
|
||||||
)} ${this.reverseMatrixScaleTransformString(
|
)} ${this.reverseMatrixScaleTransformString(
|
||||||
projectionTF
|
projectionTF
|
||||||
)} scale(1 2) scale(1 ${
|
)} scale(1 2) scale(1 ${1 / (-height)}) scale(2 1) scale(${1 / width} 1)`;
|
||||||
1 / -(responsive.height - graphPaddingTop)
|
|
||||||
}) scale(2 1) scale(${1 / (responsive.width - graphPaddingRightLeft)} 1)`;
|
|
||||||
|
|
||||||
// Copy the children passed with the overlay and add the inverse transform and onDisplayChange props
|
// Copy the children passed with the overlay and add the inverse transform and onDisplayChange props
|
||||||
const newChildren = React.Children.map(children, (child) =>
|
const newChildren = React.Children.map(children, (child) =>
|
||||||
@@ -83,11 +76,14 @@ class GraphOverlayLayer extends PureComponent {
|
|||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
className={styles.graphSVG}
|
className={styles.graphSVG}
|
||||||
width={responsive.width - graphPaddingRightLeft}
|
width={width}
|
||||||
height={responsive.height}
|
height={height}
|
||||||
pointerEvents="none"
|
pointerEvents="none"
|
||||||
style={{
|
style={{
|
||||||
zIndex: 99,
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
zIndex: 1,
|
||||||
backgroundColor: displaying ? "rgba(255, 255, 255, 0.55)" : "",
|
backgroundColor: displaying ? "rgba(255, 255, 255, 0.55)" : "",
|
||||||
}}
|
}}
|
||||||
onMouseMove={handleCanvasEvent}
|
onMouseMove={handleCanvasEvent}
|
||||||
@@ -95,15 +91,11 @@ class GraphOverlayLayer extends PureComponent {
|
|||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
id="canvas-transformation-group-x"
|
id="canvas-transformation-group-x"
|
||||||
transform={`scale(${
|
transform={`scale(${width} 1) scale(.5 1) translate(1 0)`}
|
||||||
responsive.width - graphPaddingRightLeft
|
|
||||||
} 1) scale(.5 1) translate(1 0)`}
|
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
id="canvas-transformation-group-y"
|
id="canvas-transformation-group-y"
|
||||||
transform={`scale(1 ${-(
|
transform={`scale(1 ${-height}) translate(0 -1) scale(1 .5) translate(0 1)`}
|
||||||
responsive.height - graphPaddingTop
|
|
||||||
)}) translate(0 -1) scale(1 .5) translate(0 1)`}
|
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
id="projection-transformation-group"
|
id="projection-transformation-group"
|
||||||
|
|||||||
@@ -14,17 +14,16 @@ export default (
|
|||||||
handleDragAction,
|
handleDragAction,
|
||||||
handleEndAction,
|
handleEndAction,
|
||||||
handleCancelAction,
|
handleCancelAction,
|
||||||
responsive,
|
viewport,
|
||||||
graphPaddingRight
|
|
||||||
) => {
|
) => {
|
||||||
const svg = d3.select("#graphAttachPoint").select("#lasso-layer");
|
const svg = d3.select("#graph-wrapper").select("#lasso-layer");
|
||||||
|
|
||||||
if (selectionToolType === "brush") {
|
if (selectionToolType === "brush") {
|
||||||
const brush = d3
|
const brush = d3
|
||||||
.brush()
|
.brush()
|
||||||
.extent([
|
.extent([
|
||||||
[0, 0],
|
[0, 0],
|
||||||
[responsive.width - graphPaddingRight, responsive.height],
|
[viewport.width, viewport.height]
|
||||||
])
|
])
|
||||||
.on("start", handleStartAction)
|
.on("start", handleStartAction)
|
||||||
.on("brush", handleDragAction)
|
.on("brush", handleDragAction)
|
||||||
|
|||||||
@@ -7,41 +7,29 @@ import DynamicScatterplot from "../scatterplot/scatterplot";
|
|||||||
import TopLeftLogoAndTitle from "./topLeftLogoAndTitle";
|
import TopLeftLogoAndTitle from "./topLeftLogoAndTitle";
|
||||||
|
|
||||||
@connect((state) => ({
|
@connect((state) => ({
|
||||||
responsive: state.responsive,
|
|
||||||
scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
|
scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
|
||||||
scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
|
scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
|
||||||
}))
|
}))
|
||||||
class LeftSideBar extends React.Component {
|
class LeftSideBar extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { scatterplotXXaccessor, scatterplotYYaccessor } = this.props;
|
||||||
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;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: "fixed",
|
|
||||||
backgroundColor: "white",
|
|
||||||
/* x y blur spread color */
|
/* x y blur spread color */
|
||||||
borderRight: `1px solid ${globals.lightGrey}`,
|
borderRight: `1px solid ${globals.lightGrey}`,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
height: "100%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TopLeftLogoAndTitle />
|
<TopLeftLogoAndTitle />
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
height: responsive.height - logoRelatedPadding,
|
height: "100%",
|
||||||
marginTop: logoRelatedPadding,
|
|
||||||
width: globals.leftSidebarWidth,
|
width: globals.leftSidebarWidth,
|
||||||
overflowY: "auto",
|
overflowY: "auto",
|
||||||
overflowX: "hidden",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Categorical />
|
<Categorical />
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import { connect } from "react-redux";
|
|||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
import Logo from "../framework/logo";
|
import Logo from "../framework/logo";
|
||||||
|
|
||||||
@connect((state) => ({
|
@connect(state => ({
|
||||||
responsive: state.responsive,
|
|
||||||
datasetTitle: state.config?.displayNames?.dataset ?? "",
|
datasetTitle: state.config?.displayNames?.dataset ?? "",
|
||||||
aboutURL: state.config?.links?.["about-dataset"],
|
aboutURL: state.config?.links?.["about-dataset"],
|
||||||
scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
|
scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
|
||||||
@@ -15,8 +14,6 @@ class LeftSideBar extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
const { datasetTitle, aboutURL } = this.props;
|
const { datasetTitle, aboutURL } = this.props;
|
||||||
|
|
||||||
const paddingToAvoidScrollBar = 15;
|
|
||||||
|
|
||||||
const displayTitle =
|
const displayTitle =
|
||||||
datasetTitle.length > globals.datasetTitleMaxCharacterCount
|
datasetTitle.length > globals.datasetTitleMaxCharacterCount
|
||||||
? `${datasetTitle.substring(
|
? `${datasetTitle.substring(
|
||||||
@@ -32,12 +29,9 @@ class LeftSideBar extends React.Component {
|
|||||||
style={{
|
style={{
|
||||||
paddingLeft: 8,
|
paddingLeft: 8,
|
||||||
paddingTop: 8,
|
paddingTop: 8,
|
||||||
width: globals.leftSidebarWidth - paddingToAvoidScrollBar,
|
width: globals.leftSidebarWidth,
|
||||||
position: "absolute",
|
zIndex: 1,
|
||||||
backgroundColor: "white",
|
borderBottom: `1px solid ${globals.lighterGrey}`,
|
||||||
zIndex: 8888,
|
|
||||||
/* x y blur spread color */
|
|
||||||
// boxShadow: "-5px -1px 4px 2px rgba(225,225,225,0.4)"
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Logo size={30} />
|
<Logo size={30} />
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
import { tooltipHoverOpenDelay } from "../../globals";
|
import { tooltipHoverOpenDelay } from "../../globals";
|
||||||
|
import styles from "./menubar.css";
|
||||||
|
|
||||||
function Clip(props) {
|
function Clip(props) {
|
||||||
const {
|
const {
|
||||||
@@ -35,10 +36,7 @@ function Clip(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="bp3-button-group"
|
className={`bp3-button-group ${styles.menubarButton}`}
|
||||||
style={{
|
|
||||||
marginRight: 10,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Popover
|
<Popover
|
||||||
target={
|
target={
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
Position,
|
Position,
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
|
import styles from "./menubar.css";
|
||||||
import actions from "../../actions";
|
import actions from "../../actions";
|
||||||
import CellSetButton from "./cellSetButtons";
|
import CellSetButton from "./cellSetButtons";
|
||||||
|
|
||||||
@@ -85,7 +86,7 @@ class DiffexpButtons extends React.Component {
|
|||||||
diffexpCellcountMax;
|
diffexpCellcountMax;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonGroup style={{ marginRight: 10 }}>
|
<ButtonGroup className={styles.menubarButton} >
|
||||||
<CellSetButton
|
<CellSetButton
|
||||||
{...this.props} // eslint-disable-line react/jsx-props-no-spreading
|
{...this.props} // eslint-disable-line react/jsx-props-no-spreading
|
||||||
eitherCellSetOneOrTwo={1}
|
eitherCellSetOneOrTwo={1}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
|
import styles from "./menubar.css";
|
||||||
import { World } from "../../util/stateManager";
|
import { World } from "../../util/stateManager";
|
||||||
import actions from "../../actions";
|
import actions from "../../actions";
|
||||||
|
|
||||||
@@ -69,9 +70,7 @@ class Embedding extends React.PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
style={{
|
className={styles.menubarButton}
|
||||||
marginRight: 10,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Popover
|
<Popover
|
||||||
target={
|
target={
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Button, ButtonGroup, AnchorButton, Tooltip } from "@blueprintjs/core";
|
import { Button, ButtonGroup, AnchorButton, Tooltip } from "@blueprintjs/core";
|
||||||
|
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
|
import styles from "./menubar.css";
|
||||||
import actions from "../../actions";
|
import actions from "../../actions";
|
||||||
import Clip from "./clip";
|
import Clip from "./clip";
|
||||||
import Embedding from "./embedding";
|
import Embedding from "./embedding";
|
||||||
@@ -33,8 +35,8 @@ import DiffexpButtons from "./diffexpButtons";
|
|||||||
disableDiffexp: state.config?.parameters?.["disable-diffexp"] ?? false,
|
disableDiffexp: state.config?.parameters?.["disable-diffexp"] ?? false,
|
||||||
diffexpMayBeSlow: state.config?.parameters?.["diffexp-may-be-slow"] ?? false,
|
diffexpMayBeSlow: state.config?.parameters?.["diffexp-may-be-slow"] ?? false,
|
||||||
showCentroidLabels: state.centroidLabels.showLabels,
|
showCentroidLabels: state.centroidLabels.showLabels,
|
||||||
tosURL: state.config?.parameters?.about_legal_tos,
|
tosURL: state.config?.parameters?.["about_legal_tos"],
|
||||||
privacyURL: state.config?.parameters?.about_legal_privacy,
|
privacyURL: state.config?.parameters?.["about_legal_privacy"],
|
||||||
}))
|
}))
|
||||||
class MenuBar extends React.Component {
|
class MenuBar extends React.Component {
|
||||||
static isValidDigitKeyEvent(e) {
|
static isValidDigitKeyEvent(e) {
|
||||||
@@ -205,100 +207,35 @@ class MenuBar extends React.Component {
|
|||||||
const { pendingClipPercentiles } = this.state;
|
const { pendingClipPercentiles } = this.state;
|
||||||
|
|
||||||
// constants used to create selection tool button
|
// constants used to create selection tool button
|
||||||
let selectionTooltip;
|
const [selectionTooltip, selectionButtonIcon] = selectionTool === "brush"
|
||||||
let selectionButtonIcon;
|
? ["Brush selection", "Lasso selection"]
|
||||||
if (selectionTool === "brush") {
|
: ["select", "polygon-filter"];
|
||||||
selectionTooltip = "Brush selection";
|
|
||||||
selectionButtonIcon = "select";
|
|
||||||
} else {
|
|
||||||
selectionTooltip = "Lasso selection";
|
|
||||||
selectionButtonIcon = "polygon-filter";
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: "fixed",
|
position: "absolute",
|
||||||
right: globals.leftSidebarWidth + 8,
|
right: 8,
|
||||||
top: 8,
|
top: 0,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
flexDirection: "row-reverse",
|
||||||
|
alignItems: "flex-start",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
zIndex: 3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{disableDiffexp ? null : <DiffexpButtons />}
|
<InformationMenu
|
||||||
<Subset
|
libraryVersions={libraryVersions}
|
||||||
subsetPossible={this.subsetPossible()}
|
aboutLink={aboutLink}
|
||||||
subsetResetPossible={this.subsetResetPossible()}
|
tosURL={tosURL}
|
||||||
handleSubset={() => {
|
privacyURL={privacyURL}
|
||||||
dispatch(actions.setWorldToSelection());
|
/>
|
||||||
dispatch({ type: "increment graph render counter" });
|
<UndoRedoReset
|
||||||
}}
|
dispatch={dispatch}
|
||||||
handleSubsetReset={() => {
|
undoDisabled={undoDisabled}
|
||||||
dispatch(actions.resetWorldToUniverse());
|
redoDisabled={redoDisabled}
|
||||||
dispatch({ type: "increment graph render counter" });
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<ButtonGroup style={{ marginRight: "10px" }}>
|
|
||||||
<Tooltip
|
|
||||||
content={selectionTooltip}
|
|
||||||
position="bottom"
|
|
||||||
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
|
||||||
>
|
|
||||||
<AnchorButton
|
|
||||||
type="button"
|
|
||||||
data-testid="mode-lasso"
|
|
||||||
icon={selectionButtonIcon}
|
|
||||||
active={graphInteractionMode === "select"}
|
|
||||||
onClick={() => {
|
|
||||||
dispatch({
|
|
||||||
type: "change graph interaction mode",
|
|
||||||
data: "select",
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip
|
|
||||||
content="Drag to pan, scroll to zoom"
|
|
||||||
position="bottom"
|
|
||||||
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
|
||||||
>
|
|
||||||
<AnchorButton
|
|
||||||
type="button"
|
|
||||||
data-testid="mode-pan-zoom"
|
|
||||||
icon="zoom-in"
|
|
||||||
active={graphInteractionMode === "zoom"}
|
|
||||||
onClick={() => {
|
|
||||||
dispatch({
|
|
||||||
type: "change graph interaction mode",
|
|
||||||
data: "zoom",
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</ButtonGroup>
|
|
||||||
<Tooltip
|
|
||||||
content="When a category is colored by, show labels on the graph"
|
|
||||||
position="bottom"
|
|
||||||
disabled={graphInteractionMode === "zoom"}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
data-testid="centroid-label-toggle"
|
|
||||||
icon="property"
|
|
||||||
onClick={this.handleCentroidChange}
|
|
||||||
active={showCentroidLabels}
|
|
||||||
intent={showCentroidLabels ? "primary" : "none"}
|
|
||||||
style={{
|
|
||||||
marginRight: 10,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<Embedding />
|
|
||||||
<Clip
|
<Clip
|
||||||
pendingClipPercentiles={pendingClipPercentiles}
|
pendingClipPercentiles={pendingClipPercentiles}
|
||||||
clipPercentileMin={clipPercentileMin}
|
clipPercentileMin={clipPercentileMin}
|
||||||
@@ -308,26 +245,80 @@ class MenuBar extends React.Component {
|
|||||||
handleClipCommit={this.handleClipCommit}
|
handleClipCommit={this.handleClipCommit}
|
||||||
isClipDisabled={this.isClipDisabled}
|
isClipDisabled={this.isClipDisabled}
|
||||||
handleClipOnKeyPress={this.handleClipOnKeyPress}
|
handleClipOnKeyPress={this.handleClipOnKeyPress}
|
||||||
handleClipPercentileMaxValueChange={
|
handleClipPercentileMaxValueChange={this.handleClipPercentileMaxValueChange}
|
||||||
this.handleClipPercentileMaxValueChange
|
handleClipPercentileMinValueChange={this.handleClipPercentileMinValueChange}
|
||||||
}
|
|
||||||
handleClipPercentileMinValueChange={
|
|
||||||
this.handleClipPercentileMinValueChange
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<UndoRedoReset
|
<Embedding />
|
||||||
dispatch={dispatch}
|
<Tooltip
|
||||||
undoDisabled={undoDisabled}
|
content="When a category is colored by, show labels on the graph"
|
||||||
redoDisabled={redoDisabled}
|
position="bottom"
|
||||||
|
disabled={graphInteractionMode === "zoom"}
|
||||||
|
>
|
||||||
|
<AnchorButton
|
||||||
|
className={styles.menubarButton}
|
||||||
|
type="button"
|
||||||
|
data-testid="centroid-label-toggle"
|
||||||
|
icon="property"
|
||||||
|
onClick={this.handleCentroidChange}
|
||||||
|
active={showCentroidLabels}
|
||||||
|
intent={showCentroidLabels ? "primary" : "none"}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<ButtonGroup
|
||||||
|
className={styles.menubarButton}
|
||||||
|
>
|
||||||
|
<Tooltip
|
||||||
|
content={selectionTooltip}
|
||||||
|
position="bottom"
|
||||||
|
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
||||||
|
>
|
||||||
|
<AnchorButton
|
||||||
|
type="button"
|
||||||
|
data-testid="mode-lasso"
|
||||||
|
icon={selectionButtonIcon}
|
||||||
|
active={graphInteractionMode === "select"}
|
||||||
|
onClick={() => {
|
||||||
|
dispatch({
|
||||||
|
type: "change graph interaction mode",
|
||||||
|
data: "select"
|
||||||
|
});
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<InformationMenu
|
</Tooltip>
|
||||||
libraryVersions={libraryVersions}
|
<Tooltip
|
||||||
aboutLink={aboutLink}
|
content="Drag to pan, scroll to zoom"
|
||||||
tosURL={tosURL}
|
position="bottom"
|
||||||
privacyURL={privacyURL}
|
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
||||||
|
>
|
||||||
|
<AnchorButton
|
||||||
|
type="button"
|
||||||
|
data-testid="mode-pan-zoom"
|
||||||
|
icon="zoom-in"
|
||||||
|
active={graphInteractionMode === "zoom"}
|
||||||
|
onClick={() => {
|
||||||
|
dispatch({
|
||||||
|
type: "change graph interaction mode",
|
||||||
|
data: "zoom"
|
||||||
|
});
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Tooltip>
|
||||||
);
|
</ButtonGroup>
|
||||||
|
<Subset
|
||||||
|
subsetPossible={this.subsetPossible()}
|
||||||
|
subsetResetPossible={this.subsetResetPossible()}
|
||||||
|
handleSubset={() => {
|
||||||
|
dispatch(actions.setWorldToSelection());
|
||||||
|
dispatch({ type: "increment graph render counter" });
|
||||||
|
}}
|
||||||
|
handleSubsetReset={() => {
|
||||||
|
dispatch(actions.resetWorldToUniverse());
|
||||||
|
dispatch({ type: "increment graph render counter" });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{disableDiffexp ? null : <DiffexpButtons/>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
// jshint esversion: 6
|
// jshint esversion: 6
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Button, Popover, Menu, MenuItem, Position } from "@blueprintjs/core";
|
import { Button, Popover, Menu, MenuItem, Position } from "@blueprintjs/core";
|
||||||
|
import styles from "./menubar.css";
|
||||||
|
|
||||||
function InformationMenu(props) {
|
function InformationMenu(props) {
|
||||||
const { libraryVersions, aboutLink, tosURL, privacyURL } = props;
|
const { libraryVersions, aboutLink, tosURL, privacyURL } = props;
|
||||||
return (
|
return (
|
||||||
<div style={{}} className="bp3-button-group">
|
<div
|
||||||
|
className={`bp3-button-group ${styles.menubarButton}`}
|
||||||
|
>
|
||||||
<Popover
|
<Popover
|
||||||
content={
|
content={
|
||||||
<Menu>
|
<Menu>
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
:local(.menubarButton) {
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { AnchorButton, ButtonGroup, Tooltip } from "@blueprintjs/core";
|
import { AnchorButton, ButtonGroup, Tooltip } from "@blueprintjs/core";
|
||||||
|
import styles from "./menubar.css";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
|
|
||||||
function Subset(props) {
|
function Subset(props) {
|
||||||
@@ -11,18 +12,21 @@ function Subset(props) {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonGroup style={{ marginRight: "10px" }}>
|
<ButtonGroup
|
||||||
|
className={styles.menubarButton}
|
||||||
|
>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content="Subset to currently selected cells and associated metadata"
|
content="Subset to currently selected cells and associated metadata"
|
||||||
position="bottom"
|
position="bottom"
|
||||||
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
||||||
>
|
>
|
||||||
<AnchorButton
|
<AnchorButton
|
||||||
|
type="button"
|
||||||
data-testid="subset-button"
|
data-testid="subset-button"
|
||||||
disabled={!subsetPossible}
|
disabled={!subsetPossible}
|
||||||
icon="pie-chart"
|
icon="pie-chart"
|
||||||
onClick={handleSubset}
|
onClick={handleSubset}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content="Undo subset and show all cells and associated metadata"
|
content="Undo subset and show all cells and associated metadata"
|
||||||
@@ -30,6 +34,7 @@ function Subset(props) {
|
|||||||
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
||||||
>
|
>
|
||||||
<AnchorButton
|
<AnchorButton
|
||||||
|
type="button"
|
||||||
data-testid="reset-subset-button"
|
data-testid="reset-subset-button"
|
||||||
disabled={!subsetResetPossible}
|
disabled={!subsetResetPossible}
|
||||||
icon="full-circle"
|
icon="full-circle"
|
||||||
|
|||||||
@@ -2,11 +2,14 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { AnchorButton, Tooltip } from "@blueprintjs/core";
|
import { AnchorButton, Tooltip } from "@blueprintjs/core";
|
||||||
import { tooltipHoverOpenDelay } from "../../globals";
|
import { tooltipHoverOpenDelay } from "../../globals";
|
||||||
|
import styles from "./menubar.css";
|
||||||
|
|
||||||
function InformationMenu(props) {
|
function InformationMenu(props) {
|
||||||
const { undoDisabled, redoDisabled, dispatch } = props;
|
const { undoDisabled, redoDisabled, dispatch } = props;
|
||||||
return (
|
return (
|
||||||
<div style={{ marginRight: 10 }} className="bp3-button-group">
|
<div
|
||||||
|
className={`bp3-button-group ${styles.menubarButton}`}
|
||||||
|
>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content="Undo"
|
content="Undo"
|
||||||
position="bottom"
|
position="bottom"
|
||||||
|
|||||||
@@ -5,36 +5,28 @@ import Continuous from "../continuous/continuous";
|
|||||||
import GeneExpression from "../geneExpression";
|
import GeneExpression from "../geneExpression";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
|
|
||||||
@connect((state) => ({
|
@connect(state => ({
|
||||||
responsive: state.responsive,
|
|
||||||
scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
|
scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
|
||||||
scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
|
scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
|
||||||
}))
|
}))
|
||||||
class RightSidebar extends React.Component {
|
class RightSidebar extends React.Component {
|
||||||
render() {
|
|
||||||
const { responsive } = this.props;
|
|
||||||
|
|
||||||
|
render() {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: "fixed",
|
|
||||||
right: 0,
|
|
||||||
backgroundColor: "white",
|
|
||||||
/* x y blur spread color */
|
/* x y blur spread color */
|
||||||
borderLeft: `1px solid ${globals.lightGrey}`,
|
borderLeft: `1px solid ${globals.lightGrey}`,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
position: "relative",
|
||||||
|
overflowY: "inherit",
|
||||||
|
height: "inherit",
|
||||||
|
width: "inherit"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<GeneExpression />
|
||||||
style={{
|
<Continuous />
|
||||||
height: responsive.height,
|
|
||||||
width: globals.leftSidebarWidth,
|
|
||||||
overflowY: "auto",
|
|
||||||
overflowX: "hidden",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<GeneExpression />
|
|
||||||
<Continuous />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,8 +53,6 @@ function createProjectionTF(viewportWidth, viewportHeight) {
|
|||||||
expressionY,
|
expressionY,
|
||||||
|
|
||||||
crossfilter,
|
crossfilter,
|
||||||
|
|
||||||
responsive: state.responsive,
|
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
class Scatterplot extends React.PureComponent {
|
class Scatterplot extends React.PureComponent {
|
||||||
@@ -140,6 +138,10 @@ class Scatterplot extends React.PureComponent {
|
|||||||
this.state = {
|
this.state = {
|
||||||
svg: null,
|
svg: null,
|
||||||
minimized: null,
|
minimized: null,
|
||||||
|
viewport: {
|
||||||
|
height: null,
|
||||||
|
width: null,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,6 +179,9 @@ class Scatterplot extends React.PureComponent {
|
|||||||
projectionTF
|
projectionTF
|
||||||
);
|
);
|
||||||
|
|
||||||
|
window.addEventListener("resize", this.handleResize);
|
||||||
|
const viewport = this.getViewportDimensions();
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
regl,
|
regl,
|
||||||
flagBuffer,
|
flagBuffer,
|
||||||
@@ -185,6 +190,7 @@ class Scatterplot extends React.PureComponent {
|
|||||||
svg,
|
svg,
|
||||||
drawPoints,
|
drawPoints,
|
||||||
projectionTF,
|
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) {
|
static setupScales(expressionX, expressionY) {
|
||||||
const xScale = d3
|
const xScale = d3
|
||||||
.scaleLinear()
|
.scaleLinear()
|
||||||
@@ -288,6 +316,10 @@ class Scatterplot extends React.PureComponent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateViewportDimensions = () => {
|
||||||
|
this.setState(this.getViewportDimensions());
|
||||||
|
};
|
||||||
|
|
||||||
drawAxesSVG(xScale, yScale, svg) {
|
drawAxesSVG(xScale, yScale, svg) {
|
||||||
const { scatterplotYYaccessor, scatterplotXXaccessor } = this.props;
|
const { scatterplotYYaccessor, scatterplotXXaccessor } = this.props;
|
||||||
svg.selectAll("*").remove();
|
svg.selectAll("*").remove();
|
||||||
@@ -341,12 +373,8 @@ class Scatterplot extends React.PureComponent {
|
|||||||
projectionTF
|
projectionTF
|
||||||
) {
|
) {
|
||||||
if (!this.reglCanvas) return;
|
if (!this.reglCanvas) return;
|
||||||
const { universe, responsive } = this.props;
|
const { universe } = this.props;
|
||||||
// The viewport dimension is used to scale points, so we want to pass
|
const { viewport } = this.state;
|
||||||
// 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;
|
|
||||||
regl.poll();
|
regl.poll();
|
||||||
regl.clear({
|
regl.clear({
|
||||||
depth: 1,
|
depth: 1,
|
||||||
@@ -360,9 +388,9 @@ class Scatterplot extends React.PureComponent {
|
|||||||
count: this.count,
|
count: this.count,
|
||||||
nPoints: universe.nObs,
|
nPoints: universe.nObs,
|
||||||
minViewportDimension: Math.min(
|
minViewportDimension: Math.min(
|
||||||
cvWidth - globals.leftSidebarWidth || width,
|
viewport.width - globals.leftSidebarWidth || width,
|
||||||
cvHeight || height
|
viewport.height || height
|
||||||
),
|
)
|
||||||
});
|
});
|
||||||
regl._gl.flush();
|
regl._gl.flush();
|
||||||
}
|
}
|
||||||
@@ -379,9 +407,10 @@ class Scatterplot extends React.PureComponent {
|
|||||||
borderRadius: "3px 3px 0px 0px",
|
borderRadius: "3px 3px 0px 0px",
|
||||||
left: globals.leftSidebarWidth + globals.scatterplotMarginLeft,
|
left: globals.leftSidebarWidth + globals.scatterplotMarginLeft,
|
||||||
padding: "0px 20px 20px 0px",
|
padding: "0px 20px 20px 0px",
|
||||||
backgroundColor: "white",
|
background: "white",
|
||||||
/* x y blur spread color */
|
/* x y blur spread color */
|
||||||
boxShadow: "0px 0px 6px 2px rgba(153,153,153,0.4)",
|
boxShadow: "0px 0px 6px 2px rgba(153,153,153,0.4)",
|
||||||
|
zIndex: 2,
|
||||||
}}
|
}}
|
||||||
id="scatterplot_wrapper"
|
id="scatterplot_wrapper"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
Icon,
|
Icon,
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
|
import { termsOfServiceToast } from "../framework/toasters";
|
||||||
|
|
||||||
const CookieDecision = "cxg.cookieDecision";
|
const CookieDecision = "cxg.cookieDecision";
|
||||||
|
|
||||||
@@ -25,9 +26,7 @@ function storageGet(key, defaultValue = null) {
|
|||||||
function storageSet(key, value) {
|
function storageSet(key, value) {
|
||||||
try {
|
try {
|
||||||
window.localStorage.setItem(key, value);
|
window.localStorage.setItem(key, value);
|
||||||
} catch {
|
} catch {}
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@connect((state) => ({
|
@connect((state) => ({
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export const graphWidth = 700;
|
|||||||
export const graphHeight = 700;
|
export const graphHeight = 700;
|
||||||
export const scatterplotMarginLeft = 25;
|
export const scatterplotMarginLeft = 25;
|
||||||
|
|
||||||
|
export const rightSidebarWidth = 365;
|
||||||
export const leftSidebarWidth = 365;
|
export const leftSidebarWidth = 365;
|
||||||
export const leftSidebarSectionHeading = {
|
export const leftSidebarSectionHeading = {
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import crossfilter from "./crossfilter";
|
|||||||
import colors from "./colors";
|
import colors from "./colors";
|
||||||
import differential from "./differential";
|
import differential from "./differential";
|
||||||
import layoutChoice from "./layoutChoice";
|
import layoutChoice from "./layoutChoice";
|
||||||
import responsive from "./responsive";
|
|
||||||
import controls from "./controls";
|
import controls from "./controls";
|
||||||
import resetCache from "./resetCache";
|
import resetCache from "./resetCache";
|
||||||
import annotations from "./annotations";
|
import annotations from "./annotations";
|
||||||
@@ -41,7 +40,6 @@ const Reducer = undoable(
|
|||||||
["controls", controls],
|
["controls", controls],
|
||||||
["crossfilter", crossfilter],
|
["crossfilter", crossfilter],
|
||||||
["differential", differential],
|
["differential", differential],
|
||||||
["responsive", responsive],
|
|
||||||
["centroidLabels", centroidLabels],
|
["centroidLabels", centroidLabels],
|
||||||
["pointDilation", pointDialation],
|
["pointDilation", pointDialation],
|
||||||
["reembedController", reembedController],
|
["reembedController", reembedController],
|
||||||
|
|||||||
@@ -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;
|
|
||||||
Reference in New Issue
Block a user