diff --git a/.github/workflows/push_tests.yml b/.github/workflows/push_tests.yml
index b4cfe599..70fcf947 100644
--- a/.github/workflows/push_tests.yml
+++ b/.github/workflows/push_tests.yml
@@ -31,9 +31,10 @@ jobs:
- name: Install dependencies
run: |
pip install flake8
+ pip install black
cd client
npm install
- - name: Lint with flake8
+ - name: Format with black and lint with flake8
run: |
make lint-server
- name: Lint src with eslint
diff --git a/LICENSE.txt b/LICENSE.txt
index e0bb8c7d..a34ab341 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2013
+Copyright (c) 2017-2020 Chan Zuckerberg Initiative
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
@@ -17,4 +17,4 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
index 5b588b77..ca8754f7 100644
--- a/Makefile
+++ b/Makefile
@@ -82,8 +82,9 @@ fmt-py:
lint: lint-server lint-client
.PHONY: lint-server
-lint-server:
- flake8 server
+lint-server: fmt-py
+ flake8 server --per-file-ignores='server/test/fixtures/dataset_config_outline.py:F821 server/test/fixtures/server_config_outline.py:F821'
+
.PHONY: lint-client
lint-client:
diff --git a/README.md b/README.md
index e2cb3215..ef313e53 100644
--- a/README.md
+++ b/README.md
@@ -81,7 +81,7 @@ If you believe you have found a security issue, we would appreciate notification
# Inspiration
-We've been heavily inspired by several other related single-cell visualization projects, including the [UCSC Cell Browswer](http://cells.ucsc.edu/), [Cytoscape](http://www.cytoscape.org/), [Xena](https://xena.ucsc.edu/), [ASAP](https://asap.epfl.ch/), [Gene Pattern](http://genepattern-notebook.org/), and many others. We hope to explore collaborations where useful as this community works together on improving interactive visualization for single-cell data.
+We've been heavily inspired by several other related single-cell visualization projects, including the [UCSC Cell Browser](http://cells.ucsc.edu/), [Cytoscape](http://www.cytoscape.org/), [Xena](https://xena.ucsc.edu/), [ASAP](https://asap.epfl.ch/), [GenePattern](http://genepattern-notebook.org/), and many others. We hope to explore collaborations where useful as this community works together on improving interactive visualization for single-cell data.
We were inspired by Mike Bostock and the [crossfilter](https://github.com/crossfilter) team for the design of our filtering implementation.
diff --git a/client/__tests__/e2e/__snapshots__/e2eAnnotations.test.js.snap b/client/__tests__/e2e/__snapshots__/e2eAnnotations.test.js.snap
index 01ed89a8..56a349b2 100644
--- a/client/__tests__/e2e/__snapshots__/e2eAnnotations.test.js.snap
+++ b/client/__tests__/e2e/__snapshots__/e2eAnnotations.test.js.snap
@@ -3,14 +3,14 @@
exports[`annotations stacked bar graph renders 1`] = `
Array [
"
Your annotations are stored in this file:
-
+
{filenameText}-{idhash}.csv
-
+
(We added a unique ID to your filename)
diff --git a/client/src/components/brushableHistogram/index.js b/client/src/components/brushableHistogram/index.js
index 71175b2d..a18608b2 100644
--- a/client/src/components/brushableHistogram/index.js
+++ b/client/src/components/brushableHistogram/index.js
@@ -5,12 +5,13 @@ https://bl.ocks.org/SpaceActuary/2f004899ea1b2bd78d6f1dbb2febf771
https://bl.ocks.org/mbostock/3019563
*/
import React, { useEffect, useRef, useState, useCallback } from "react";
-import { Button, ButtonGroup, Tooltip } from "@blueprintjs/core";
+import { Button, ButtonGroup, Icon, Tooltip } from "@blueprintjs/core";
import { connect } from "react-redux";
import * as d3 from "d3";
import { interpolateCool } from "d3-scale-chromatic";
import Async from "react-async";
import memoize from "memoize-one";
+import { IconNames } from "@blueprintjs/icons";
import * as globals from "../../globals";
import actions from "../../actions";
import { histogramContinuous } from "../../util/dataframe/histogram";
@@ -26,7 +27,7 @@ function maybeScientific(x) {
const _ticks = x.ticks(4);
if (x.domain().some((n) => Math.abs(n) >= 10000)) {
- /*
+ /*
heuristic: if the last tick d3 wants to render has one significant
digit ie., 2000, render 2e+3, but if it's anything else ie., 42000000 render
4.20e+n
@@ -99,7 +100,7 @@ const HistogramFooter = React.memo(
pvalAdj,
}) => {
/*
- Footer of each histogram. Will render range, title, and optionally
+ Footer of each histogram. Will render range, title, and optionally
differential expression info.
Required props:
@@ -214,10 +215,7 @@ const HistogramHeader = React.memo(
>
{onScatterPlotXClick && onScatterPlotYClick ? (
-
+
{
alignItems: "flex-start",
}}
>
-
+
-
+
-
+
-
+
- !schema.annotations.obsByName[catName].writable ? (
+ !schema.annotations.obsByName[catName].writable &&
+ (schema.annotations.obsByName[catName].categories?.length > 1 ||
+ !schema.annotations.obsByName[catName].categories) ? (
@@ -461,14 +460,12 @@ class CategoryValue extends React.Component {
return (
diff --git a/client/src/components/categorical/value/occupancy.js b/client/src/components/categorical/value/occupancy.js
index 934822a9..dae4a0ed 100644
--- a/client/src/components/categorical/value/occupancy.js
+++ b/client/src/components/categorical/value/occupancy.js
@@ -3,10 +3,10 @@ import React from "react";
import { connect } from "react-redux";
import * as d3 from "d3";
import {
+ Classes,
Popover,
PopoverInteractionKind,
Position,
- Classes,
} from "@blueprintjs/core";
@connect((state) => ({
@@ -18,8 +18,8 @@ class Occupancy extends React.PureComponent {
_HEIGHT = 11;
createHistogram = () => {
- /*
- Knowing that colorScale is based off continous data,
+ /*
+ Knowing that colorScale is based off continous data,
createHistogram fetches the continous data in relation to the cells releveant to the catagory value.
It then seperates that data into 50 bins for drawing the mini-histogram
*/
@@ -75,8 +75,8 @@ class Occupancy extends React.PureComponent {
};
createOccupancyStack = () => {
- /*
- Knowing that the color scale is based off of catagorical data,
+ /*
+ Knowing that the color scale is based off of catagorical data,
createOccupancyStack obtains a map showing the number if cells per colored value
Using the colorScale a stack of colored bars is drawn representing the map
*/
@@ -155,7 +155,7 @@ class Occupancy extends React.PureComponent {
popoverClassName={Classes.POPOVER_CONTENT_SIZING}
>
- Embedding Choice
+ Embedding Choice
There are {schema?.dataframe?.nObs} cells in the entire dataset.
diff --git a/client/src/components/geneExpression/addGenes.js b/client/src/components/geneExpression/addGenes.js
index bc3e2f82..2d1ca1b4 100644
--- a/client/src/components/geneExpression/addGenes.js
+++ b/client/src/components/geneExpression/addGenes.js
@@ -6,11 +6,12 @@ import fuzzysort from "fuzzysort";
import { connect } from "react-redux";
import { Suggest } from "@blueprintjs/select";
import {
- MenuItem,
Button,
+ ControlGroup,
FormGroup,
InputGroup,
- ControlGroup,
+ Intent,
+ MenuItem,
} from "@blueprintjs/core";
import * as globals from "../../globals";
import actions from "../../actions";
@@ -278,7 +279,7 @@ class AddGenes extends React.Component {
popoverProps={{ minimal: true }}
/>
this.handleClick(activeItem)}
diff --git a/client/src/components/graph/setupLasso.js b/client/src/components/graph/setupLasso.js
index 526d47a5..6610e04c 100644
--- a/client/src/components/graph/setupLasso.js
+++ b/client/src/components/graph/setupLasso.js
@@ -10,6 +10,7 @@ const Lasso = () => {
let lassoPolygon;
let lassoPath;
let closePath;
+ let lassoInProgress;
const polygonToPath = (polygon) =>
`M${polygon.map((d) => d.join(",")).join("L")}`;
@@ -25,8 +26,18 @@ const Lasso = () => {
lassoPolygon = [d3.mouse(svg.node())]; // current x y of mouse within element
if (lassoPath) {
+ // If the existing path is in progress
+ if (lassoInProgress) {
+ // cancel the existing lasso
+ handleCancel();
+ // Don't continue with current drag start
+ return;
+ }
+
lassoPath.remove();
}
+ // We're starting a new drag
+ lassoInProgress = true;
lassoPath = g
.append("path")
@@ -67,25 +78,33 @@ const Lasso = () => {
}
};
+ const handleCancel = () => {
+ lassoPath.remove();
+ closePath = closePath?.remove();
+ lassoPath = null;
+ lassoPolygon = null;
+ closePath = null;
+ dispatch.call("cancel");
+ };
+
const handleDragEnd = () => {
// remove the close path
closePath.remove();
closePath = null;
- // succesfully closed
+ // successfully closed
if (
distance(lassoPolygon[0], lassoPolygon[lassoPolygon.length - 1]) <
closeDistance
) {
+ lassoInProgress = false;
+
lassoPath.attr("d", `${polygonToPath(lassoPolygon)}Z`);
dispatch.call("end", lasso, lassoPolygon);
// otherwise cancel
} else {
- lassoPath.remove();
- lassoPath = null;
- lassoPolygon = null;
- dispatch.call("cancel");
+ handleCancel();
}
};
diff --git a/client/src/components/infoDrawer/infoDrawer.js b/client/src/components/infoDrawer/infoDrawer.js
index 49189ff2..57bfd77b 100644
--- a/client/src/components/infoDrawer/infoDrawer.js
+++ b/client/src/components/infoDrawer/infoDrawer.js
@@ -38,6 +38,8 @@ class InfoDrawer extends PureComponent {
const singleValueCategories = (
await Promise.all(nonUserAnnoCategories)
).reduce((acc, categoryData, i) => {
+ // Actually check to see if it is null(user anno)
+ if (!categoryData) return acc;
const catName = allCategoryNames[i];
const column = categoryData.icol(0);
diff --git a/client/src/components/leftSidebar/topLeftLogoAndTitle.js b/client/src/components/leftSidebar/topLeftLogoAndTitle.js
index 6224c1a1..edfc8cef 100644
--- a/client/src/components/leftSidebar/topLeftLogoAndTitle.js
+++ b/client/src/components/leftSidebar/topLeftLogoAndTitle.js
@@ -7,11 +7,19 @@ import * as globals from "../../globals";
import Logo from "../framework/logo";
import Truncate from "../util/truncate";
import InfoDrawer from "../infoDrawer/infoDrawer";
+import AuthButtons from "../menubar/authButtons";
+import InformationMenu from "../menubar/infoMenu";
const DATASET_TITLE_FONT_SIZE = 14;
@connect((state) => ({
datasetTitle: state.config?.displayNames?.dataset ?? "",
+ auth: state.config?.authentication,
+ userinfo: state.userinfo,
+ libraryVersions: state.config?.["library_versions"],
+ aboutLink: state.config?.links?.["about-dataset"],
+ tosURL: state.config?.parameters?.["about_legal_tos"],
+ privacyURL: state.config?.parameters?.["about_legal_privacy"],
}))
class LeftSideBar extends React.Component {
handleClick = () => {
@@ -20,7 +28,16 @@ class LeftSideBar extends React.Component {
};
render() {
- const { datasetTitle } = this.props;
+ const {
+ datasetTitle,
+ auth,
+ userinfo,
+ libraryVersions,
+ aboutLink,
+ privacyURL,
+ tosURL,
+ dispatch,
+ } = this.props;
return (
-
-
- cell
+
+
- ×
-
- gene
-
-
-
-
- {datasetTitle}
+ cell
+
+ ×
-
-
-
+ gene
+
+
+
+
+
+
+ {datasetTitle}
+
+
+
+
+
+ {!userinfo.is_authenticated ? (
+
+ ) : null}
+
);
}
diff --git a/client/src/components/menubar/authButtons.js b/client/src/components/menubar/authButtons.js
index 7eb4c4c0..985b3da3 100644
--- a/client/src/components/menubar/authButtons.js
+++ b/client/src/components/menubar/authButtons.js
@@ -1,5 +1,5 @@
import React from "react";
-import { AnchorButton, Tooltip } from "@blueprintjs/core";
+import { AnchorButton, ButtonGroup, Tooltip } from "@blueprintjs/core";
import * as globals from "../../globals";
import styles from "./menubar.css";
@@ -9,9 +9,9 @@ const Auth = React.memo((props) => {
if (!auth || (auth && !auth.requires_client_login)) return null;
return (
-
+
@@ -19,13 +19,12 @@ const Auth = React.memo((props) => {
type="button"
data-testid="auth-button"
disabled={false}
- icon={!userinfo.is_authenticated ? "log-in" : "log-out"}
href={!userinfo.is_authenticated ? auth.login : auth.logout}
>
{!userinfo.is_authenticated ? "Log In" : "Log Out"}
-
+
);
});
diff --git a/client/src/components/menubar/clip.js b/client/src/components/menubar/clip.js
index 5b866bd0..5bc1d18d 100644
--- a/client/src/components/menubar/clip.js
+++ b/client/src/components/menubar/clip.js
@@ -1,12 +1,16 @@
import React from "react";
import {
- Position,
Button,
- Popover,
- NumericInput,
+ ButtonGroup,
Icon,
+ Intent,
+ NumericInput,
+ Popover,
+ Position,
Tooltip,
} from "@blueprintjs/core";
+import { IconNames } from "@blueprintjs/icons";
+
import { tooltipHoverOpenDelay } from "../../globals";
import styles from "./menubar.css";
@@ -28,13 +32,13 @@ const Clip = React.memo((props) => {
pendingClipPercentiles?.clipPercentileMin ?? clipPercentileMin;
const clipMax =
pendingClipPercentiles?.clipPercentileMax ?? clipPercentileMax;
- const activeClipClass =
+ const intent =
clipPercentileMin > 0 || clipPercentileMax < 100
- ? " bp3-intent-warning"
- : "";
+ ? Intent.INTENT_WARNING
+ : Intent.NONE;
return (
-
}
/>
-
+
);
});
diff --git a/client/src/components/menubar/index.js b/client/src/components/menubar/index.js
index 3fd840c0..b4271302 100644
--- a/client/src/components/menubar/index.js
+++ b/client/src/components/menubar/index.js
@@ -6,8 +6,7 @@ import * as globals from "../../globals";
import styles from "./menubar.css";
import actions from "../../actions";
import Clip from "./clip";
-import AuthButtons from "./authButtons";
-import InformationMenu from "./infoMenu";
+
import Subset from "./subset";
import UndoRedoReset from "./undoRedo";
import DiffexpButtons from "./diffexpButtons";
@@ -204,7 +203,6 @@ class MenuBar extends React.PureComponent {
render() {
const {
dispatch,
- libraryVersions,
disableDiffexp,
undoDisabled,
redoDisabled,
@@ -212,17 +210,12 @@ class MenuBar extends React.PureComponent {
clipPercentileMin,
clipPercentileMax,
graphInteractionMode,
- aboutLink,
showCentroidLabels,
- privacyURL,
- tosURL,
categoricalSelection,
colorAccessor,
subsetPossible,
subsetResetPossible,
enableReembedding,
- auth,
- userinfo,
} = this.props;
const { pendingClipPercentiles } = this.state;
@@ -248,10 +241,6 @@ class MenuBar extends React.PureComponent {
zIndex: 3,
}}
>
-