mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-01 18:58:11 +08:00
Merge branch 'master' into colinmegill/geneset-prototype
This commit is contained in:
@@ -21,7 +21,6 @@ import actions from "../actions";
|
||||
graphRenderCounter: state.controls.graphRenderCounter,
|
||||
}))
|
||||
class App extends React.Component {
|
||||
|
||||
componentDidMount() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
@@ -68,22 +67,21 @@ class App extends React.Component {
|
||||
error loading
|
||||
</div>
|
||||
) : null}
|
||||
{loading ? null : <Layout>
|
||||
<LeftSideBar/>
|
||||
{viewportRef =>
|
||||
<>
|
||||
<MenuBar/>
|
||||
<Autosave/>
|
||||
<TermsOfServicePrompt/>
|
||||
<Legend viewportRef={viewportRef}/>
|
||||
<Graph
|
||||
key={graphRenderCounter}
|
||||
viewportRef={viewportRef}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
<RightSideBar/>
|
||||
</Layout>}
|
||||
{loading ? null : (
|
||||
<Layout>
|
||||
<LeftSideBar />
|
||||
{(viewportRef) => (
|
||||
<>
|
||||
<MenuBar />
|
||||
<Autosave />
|
||||
<TermsOfServicePrompt />
|
||||
<Legend viewportRef={viewportRef} />
|
||||
<Graph key={graphRenderCounter} viewportRef={viewportRef} />
|
||||
</>
|
||||
)}
|
||||
<RightSideBar />
|
||||
</Layout>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,9 +6,13 @@ import {
|
||||
MenuItem,
|
||||
Popover,
|
||||
Position,
|
||||
Tooltip,
|
||||
Icon,
|
||||
PopoverInteractionKind,
|
||||
} from "@blueprintjs/core";
|
||||
|
||||
import * as globals from "../../../globals";
|
||||
|
||||
@connect((state) => ({
|
||||
annotations: state.annotations,
|
||||
}))
|
||||
@@ -56,46 +60,57 @@ class AnnoMenuCategory extends React.PureComponent {
|
||||
return (
|
||||
<>
|
||||
{isUserAnno ? (
|
||||
<Popover
|
||||
interactionKind={PopoverInteractionKind.HOVER}
|
||||
boundary="window"
|
||||
position={Position.RIGHT_TOP}
|
||||
content={
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon="tag"
|
||||
data-testclass="handleAddNewLabelToCategory"
|
||||
data-testid={`${metadataField}:add-new-label-to-category`}
|
||||
onClick={this.activateAddNewLabelMode}
|
||||
text={createText}
|
||||
/>
|
||||
<MenuItem
|
||||
icon="edit"
|
||||
disabled={annotations.isEditingCategoryName}
|
||||
data-testclass="activateEditCategoryMode"
|
||||
data-testid={`${metadataField}:edit-category-mode`}
|
||||
onClick={this.activateEditCategoryMode}
|
||||
text={editText}
|
||||
/>
|
||||
<MenuItem
|
||||
icon="delete"
|
||||
intent="danger"
|
||||
data-testclass="handleDeleteCategory"
|
||||
data-testid={`${metadataField}:delete-category`}
|
||||
onClick={this.handleDeleteCategory}
|
||||
text={deleteText}
|
||||
/>
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
style={{ marginLeft: 0 }}
|
||||
data-testclass="seeActions"
|
||||
data-testid={`${metadataField}:see-actions`}
|
||||
icon="more"
|
||||
minimal
|
||||
/>
|
||||
</Popover>
|
||||
<>
|
||||
<Tooltip
|
||||
content={createText}
|
||||
position="bottom"
|
||||
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
||||
>
|
||||
<Button
|
||||
style={{ marginLeft: 0, marginRight: 2 }}
|
||||
data-testclass="handleAddNewLabelToCategory"
|
||||
data-testid={`${metadataField}:add-new-label-to-category`}
|
||||
icon={<Icon icon="plus" iconSize={10} />}
|
||||
onClick={this.activateAddNewLabelMode}
|
||||
small
|
||||
minimal
|
||||
/>
|
||||
</Tooltip>
|
||||
<Popover
|
||||
interactionKind={PopoverInteractionKind.HOVER}
|
||||
boundary="window"
|
||||
position={Position.RIGHT_TOP}
|
||||
content={
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon="edit"
|
||||
disabled={annotations.isEditingCategoryName}
|
||||
data-testclass="activateEditCategoryMode"
|
||||
data-testid={`${metadataField}:edit-category-mode`}
|
||||
onClick={this.activateEditCategoryMode}
|
||||
text={editText}
|
||||
/>
|
||||
<MenuItem
|
||||
icon="delete"
|
||||
intent="danger"
|
||||
data-testclass="handleDeleteCategory"
|
||||
data-testid={`${metadataField}:delete-category`}
|
||||
onClick={this.handleDeleteCategory}
|
||||
text={deleteText}
|
||||
/>
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
style={{ marginLeft: 0, marginRight: 5 }}
|
||||
data-testclass="seeActions"
|
||||
data-testid={`${metadataField}:see-actions`}
|
||||
icon={<Icon icon="more" iconSize={10} />}
|
||||
small
|
||||
minimal
|
||||
/>
|
||||
</Popover>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -2,20 +2,18 @@ import React from "react";
|
||||
import _ from "lodash";
|
||||
import { connect } from "react-redux";
|
||||
import { FaChevronRight, FaChevronDown } from "react-icons/fa";
|
||||
import {
|
||||
AnchorButton,
|
||||
Button,
|
||||
Tooltip,
|
||||
Icon,
|
||||
Position,
|
||||
} from "@blueprintjs/core";
|
||||
import { AnchorButton, Button, Tooltip } from "@blueprintjs/core";
|
||||
import CategoryFlipperLayout from "./categoryFlipperLayout";
|
||||
import AnnoMenu from "./annoMenuCategory";
|
||||
import AnnoDialogEditCategoryName from "./annoDialogEditCategoryName";
|
||||
import AnnoDialogAddLabel from "./annoDialogAddLabel";
|
||||
import Truncate from "../../util/truncate";
|
||||
|
||||
import * as globals from "../../../globals";
|
||||
import maybeTruncateString from "../../../util/maybeTruncateString";
|
||||
|
||||
const LABEL_WIDTH = globals.leftSidebarWidth - 100;
|
||||
const ANNO_BUTTON_WIDTH = 50;
|
||||
const LABEL_WIDTH_ANNO = LABEL_WIDTH - ANNO_BUTTON_WIDTH;
|
||||
|
||||
@connect((state, ownProps) => {
|
||||
const { metadataField } = ownProps;
|
||||
@@ -120,10 +118,6 @@ class Category extends React.Component {
|
||||
We are still loading this category, so render a "busy" signal.
|
||||
*/
|
||||
const { metadataField } = this.props;
|
||||
const truncatedString = maybeTruncateString(
|
||||
metadataField,
|
||||
globals.categoryDisplayStringMaxLength
|
||||
);
|
||||
|
||||
const checkboxID = `category-select-${metadataField}`;
|
||||
|
||||
@@ -151,26 +145,17 @@ class Category extends React.Component {
|
||||
<input disabled id={checkboxID} checked type="checkbox" />
|
||||
<span className="bp3-control-indicator" />
|
||||
</label>
|
||||
<Tooltip
|
||||
content={metadataField}
|
||||
disabled={truncatedString === null}
|
||||
hoverOpenDelay={globals.tooltipHoverOpenDelayQuick}
|
||||
position={Position.LEFT}
|
||||
usePortal
|
||||
modifiers={{
|
||||
preventOverflow: { enabled: false },
|
||||
hide: { enabled: false },
|
||||
}}
|
||||
>
|
||||
<Truncate>
|
||||
<span
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
display: "inline-block",
|
||||
width: LABEL_WIDTH,
|
||||
}}
|
||||
>
|
||||
{truncatedString || metadataField}
|
||||
{metadataField}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Truncate>
|
||||
</div>
|
||||
<div>
|
||||
<Button minimal loading intent="primary" />
|
||||
@@ -205,21 +190,22 @@ class Category extends React.Component {
|
||||
false
|
||||
);
|
||||
|
||||
const truncatedString = maybeTruncateString(
|
||||
metadataField,
|
||||
globals.categoryDisplayStringMaxLength
|
||||
);
|
||||
|
||||
if (
|
||||
!isUserAnno &&
|
||||
schema?.annotations?.obsByName[metadataField]?.categories?.length === 1
|
||||
) {
|
||||
return (
|
||||
<div style={{ marginBottom: 10, marginTop: 4 }}>
|
||||
<span style={{ fontWeight: 700 }}>
|
||||
{truncatedString || metadataField}
|
||||
</span>
|
||||
: {schema.annotations.obsByName[metadataField].categories[0]}
|
||||
<Truncate>
|
||||
<span style={{ maxWidth: 150, fontWeight: 700 }}>
|
||||
{metadataField}
|
||||
</span>
|
||||
</Truncate>
|
||||
<Truncate>
|
||||
<span style={{ maxWidth: 150 }}>
|
||||
{`: ${schema.annotations.obsByName[metadataField].categories[0]}`}
|
||||
</span>
|
||||
</Truncate>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -252,49 +238,42 @@ class Category extends React.Component {
|
||||
/>
|
||||
<span className="bp3-control-indicator" />
|
||||
</label>
|
||||
<Tooltip
|
||||
content={metadataField}
|
||||
disabled={truncatedString === null}
|
||||
hoverOpenDelay={globals.tooltipHoverOpenDelayQuick}
|
||||
position={Position.LEFT}
|
||||
usePortal
|
||||
modifiers={{
|
||||
preventOverflow: { enabled: false },
|
||||
hide: { enabled: false },
|
||||
<span
|
||||
role="menuitem"
|
||||
tabIndex="0"
|
||||
data-testid={`${metadataField}:category-expand`}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
this.handleCategoryClick();
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={this.handleCategoryClick}
|
||||
>
|
||||
<span
|
||||
role="menuitem"
|
||||
tabIndex="0"
|
||||
data-testid={`${metadataField}:category-expand`}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
this.handleCategoryClick();
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
display: "inline-block",
|
||||
}}
|
||||
onClick={this.handleCategoryClick}
|
||||
>
|
||||
{isUserAnno ? (
|
||||
<Icon style={{ marginRight: 5 }} icon="tag" iconSize={16} />
|
||||
) : null}
|
||||
{truncatedString || metadataField}
|
||||
{isExpanded ? (
|
||||
<FaChevronDown
|
||||
data-testclass="category-expand-is-expanded"
|
||||
style={{ fontSize: 10, marginLeft: 5 }}
|
||||
/>
|
||||
) : (
|
||||
<FaChevronRight
|
||||
data-testclass="category-expand-is-not-expanded"
|
||||
style={{ fontSize: 10, marginLeft: 5 }}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Truncate>
|
||||
<span
|
||||
style={{
|
||||
maxWidth: isUserAnno ? LABEL_WIDTH_ANNO : LABEL_WIDTH,
|
||||
}}
|
||||
data-testid={`${metadataField}:category-label`}
|
||||
>
|
||||
{metadataField}
|
||||
</span>
|
||||
</Truncate>
|
||||
{isExpanded ? (
|
||||
<FaChevronDown
|
||||
data-testclass="category-expand-is-expanded"
|
||||
style={{ fontSize: 10, marginLeft: 5 }}
|
||||
/>
|
||||
) : (
|
||||
<FaChevronRight
|
||||
data-testclass="category-expand-is-not-expanded"
|
||||
style={{ fontSize: 10, marginLeft: 5 }}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
{<AnnoDialogEditCategoryName metadataField={metadataField} />}
|
||||
{<AnnoDialogAddLabel metadataField={metadataField} />}
|
||||
|
||||
@@ -7,17 +7,17 @@ import {
|
||||
MenuItem,
|
||||
Popover,
|
||||
Position,
|
||||
Icon,
|
||||
PopoverInteractionKind,
|
||||
Tooltip,
|
||||
} from "@blueprintjs/core";
|
||||
import Occupancy from "./occupancy";
|
||||
import * as globals from "../../../globals";
|
||||
import styles from "../categorical.css";
|
||||
import AnnoDialog from "../annoDialog";
|
||||
import LabelInput from "../labelInput";
|
||||
import Truncate from "../../util/truncate";
|
||||
|
||||
import { AnnotationsHelpers } from "../../../util/stateManager";
|
||||
import maybeTruncateString from "../../../util/maybeTruncateString";
|
||||
import { labelPrompt, isLabelErroneous } from "../labelUtil";
|
||||
|
||||
/* this is defined outside of the class so we can use it in connect() */
|
||||
@@ -316,13 +316,6 @@ class CategoryValue extends React.Component {
|
||||
categories = schema.annotations.obsByName[colorAccessor]?.categories;
|
||||
}
|
||||
|
||||
const truncatedString = maybeTruncateString(
|
||||
displayString,
|
||||
colorAccessor && !isColorBy
|
||||
? globals.categoryLabelDisplayStringShortLength
|
||||
: globals.categoryLabelDisplayStringLongLength
|
||||
);
|
||||
|
||||
const editModeActive =
|
||||
isUserAnno &&
|
||||
annotations.labelEditable.category === metadataField &&
|
||||
@@ -331,6 +324,26 @@ class CategoryValue extends React.Component {
|
||||
|
||||
const valueToggleLabel = `value-toggle-checkbox-${displayString}`;
|
||||
|
||||
const LEFT_MARGIN = 33;
|
||||
const CHECKBOX = 26;
|
||||
const CELL_NUMBER = 61;
|
||||
const ANNO_MENU = 26;
|
||||
const LABEL_MARGIN = 24;
|
||||
|
||||
const otherElementsWidth =
|
||||
LEFT_MARGIN +
|
||||
CHECKBOX +
|
||||
CELL_NUMBER +
|
||||
LABEL_MARGIN +
|
||||
(isUserAnno ? ANNO_MENU : 0);
|
||||
|
||||
const OCCUPANCY_WIDTH = 100;
|
||||
|
||||
const labelWidth =
|
||||
colorAccessor && !isColorBy
|
||||
? globals.leftSidebarWidth - otherElementsWidth - OCCUPANCY_WIDTH
|
||||
: globals.leftSidebarWidth - otherElementsWidth;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
@@ -343,7 +356,7 @@ class CategoryValue extends React.Component {
|
||||
}
|
||||
data-testclass="categorical-row"
|
||||
style={{
|
||||
padding: "4px 7px",
|
||||
padding: "4px 0px 4px 7px",
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
justifyContent: "space-between",
|
||||
@@ -383,21 +396,12 @@ class CategoryValue extends React.Component {
|
||||
onMouseLeave={this.handleMouseEnter}
|
||||
/>
|
||||
</label>
|
||||
<Tooltip
|
||||
content={displayString}
|
||||
disabled={truncatedString === null}
|
||||
hoverOpenDelay={globals.tooltipHoverOpenDelayQuick}
|
||||
position={Position.LEFT}
|
||||
usePortal
|
||||
modifiers={{
|
||||
preventOverflow: { enabled: false },
|
||||
hide: { enabled: false },
|
||||
}}
|
||||
>
|
||||
<Truncate>
|
||||
<span
|
||||
data-testid={`categorical-value-${metadataField}-${displayString}`}
|
||||
data-testclass="categorical-value"
|
||||
style={{
|
||||
width: labelWidth,
|
||||
color:
|
||||
displayString === globals.unassignedCategoryLabel
|
||||
? "#ababab"
|
||||
@@ -410,13 +414,13 @@ class CategoryValue extends React.Component {
|
||||
overflow: "hidden",
|
||||
lineHeight: "1.1em",
|
||||
height: "1.1em",
|
||||
wordBreak: "break-all",
|
||||
verticalAlign: "middle",
|
||||
marginRight: LABEL_MARGIN,
|
||||
}}
|
||||
>
|
||||
{truncatedString || displayString}
|
||||
{displayString}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Truncate>
|
||||
{editModeActive ? (
|
||||
<div>
|
||||
<AnnoDialog
|
||||
@@ -561,14 +565,14 @@ class CategoryValue extends React.Component {
|
||||
>
|
||||
<Button
|
||||
style={{
|
||||
marginLeft: 0,
|
||||
marginLeft: 2,
|
||||
position: "relative",
|
||||
top: -1,
|
||||
minHeight: 16,
|
||||
}}
|
||||
data-testclass="seeActions"
|
||||
data-testid={`${metadataField}:${displayString}:see-actions`}
|
||||
icon="more"
|
||||
icon={<Icon icon="more" iconSize={10} />}
|
||||
small
|
||||
minimal
|
||||
/>
|
||||
|
||||
@@ -168,7 +168,7 @@ class Occupancy extends React.PureComponent {
|
||||
else this.createHistogram();
|
||||
}}
|
||||
/>
|
||||
<div key="text" style={{ fontFamily: "Roboto", fontSize: "14px" }}>
|
||||
<div key="text" style={{ fontSize: "14px" }}>
|
||||
<p style={{ margin: "0" }}>
|
||||
This histograms shows the distribution of{" "}
|
||||
<strong>{colorAccessor}</strong> within{" "}
|
||||
|
||||
@@ -105,10 +105,12 @@ const continuous = (selectorId, colorscale, colorAccessor) => {
|
||||
colorScale: state.colors.scale,
|
||||
}))
|
||||
class ContinuousLegend extends React.Component {
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { colorAccessor, colorScale } = this.props;
|
||||
if (prevProps.colorAccessor !== colorAccessor || prevProps.colorScale !== colorScale) {
|
||||
if (
|
||||
prevProps.colorAccessor !== colorAccessor ||
|
||||
prevProps.colorScale !== colorScale
|
||||
) {
|
||||
/* always remove it, if it's not continuous we don't put it back. */
|
||||
d3.select("#continuous_legend").selectAll("*").remove();
|
||||
}
|
||||
@@ -130,7 +132,9 @@ class ContinuousLegend extends React.Component {
|
||||
return (
|
||||
<div
|
||||
id="continuous_legend"
|
||||
ref={ref => {this.ref = ref}}
|
||||
ref={(ref) => {
|
||||
this.ref = ref;
|
||||
}}
|
||||
style={{
|
||||
display: colorAccessor ? "inherit" : "none",
|
||||
position: "absolute",
|
||||
|
||||
@@ -2,19 +2,21 @@
|
||||
import React from "react";
|
||||
|
||||
function Container(props) {
|
||||
const {children} = props;
|
||||
return <div
|
||||
className="container"
|
||||
style={{
|
||||
height: "calc(100vh - (100vh - 100%))",
|
||||
width: "calc(100vw - (100vw - 100%))",
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
const { children } = props;
|
||||
return (
|
||||
<div
|
||||
className="container"
|
||||
style={{
|
||||
height: "calc(100vh - (100vh - 100%))",
|
||||
width: "calc(100vw - (100vw - 100%))",
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Container;
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
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.
|
||||
|
||||
@@ -26,60 +24,66 @@ class Layout extends React.Component {
|
||||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
const [ leftSidebar, renderGraph, rightSidebar ] = children;
|
||||
return <div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: `
|
||||
[left-sidebar-start] ${globals.leftSidebarWidth+1}px
|
||||
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]
|
||||
[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",
|
||||
gridTemplateRows: "[top] auto [bottom]",
|
||||
gridTemplateAreas: "left-sidebar | graph | right-sidebar",
|
||||
columnGap: "0px",
|
||||
justifyItems: "stretch",
|
||||
alignItems: "stretch",
|
||||
height: "inherit",
|
||||
overflowY: "auto"
|
||||
width: "inherit",
|
||||
position: "relative",
|
||||
top: 0,
|
||||
left: 0,
|
||||
minWidth: "1240px",
|
||||
}}
|
||||
>
|
||||
{leftSidebar}
|
||||
<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>
|
||||
<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>;
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ export default
|
||||
colorAccessor: state.colors.colorAccessor,
|
||||
dilatedValue: state.pointDilation.categoryField,
|
||||
labels: state.centroidLabels.labels,
|
||||
categoricalSelection: state.categoricalSelection,
|
||||
}))
|
||||
class CentroidLabels extends PureComponent {
|
||||
// Check to see if centroids have either just been displayed or removed from the overlay
|
||||
@@ -33,11 +34,21 @@ class CentroidLabels extends PureComponent {
|
||||
dilatedValue,
|
||||
dispatch,
|
||||
colorAccessor,
|
||||
categoricalSelection,
|
||||
} = this.props;
|
||||
|
||||
if (!colorAccessor || labels.size === undefined || labels.size === 0)
|
||||
return null;
|
||||
|
||||
const {
|
||||
categoryValueIndices,
|
||||
categoryValueSelected,
|
||||
} = categoricalSelection?.[colorAccessor];
|
||||
|
||||
const labelSVGS = [];
|
||||
let fontSize = "15px";
|
||||
let fontWeight = null;
|
||||
const deselectOpacity = 0.375;
|
||||
labels.forEach((coords, label) => {
|
||||
fontSize = "15px";
|
||||
fontWeight = null;
|
||||
@@ -46,6 +57,8 @@ class CentroidLabels extends PureComponent {
|
||||
fontWeight = "800";
|
||||
}
|
||||
|
||||
const selected = categoryValueSelected[categoryValueIndices.get(label)];
|
||||
|
||||
// Mirror LSB middle truncation
|
||||
let displayLabel = label;
|
||||
if (displayLabel.length > categoryLabelDisplayStringLongLength) {
|
||||
@@ -72,11 +85,11 @@ class CentroidLabels extends PureComponent {
|
||||
textAnchor="middle"
|
||||
data-label={label}
|
||||
style={{
|
||||
fontFamily: "Roboto Condensed",
|
||||
fontSize,
|
||||
fontWeight,
|
||||
fill: "black",
|
||||
userSelect: "none",
|
||||
opacity: selected ? 1 : deselectOpacity,
|
||||
}}
|
||||
onMouseEnter={(e) =>
|
||||
dispatch({
|
||||
|
||||
@@ -81,7 +81,7 @@ export default class GraphOverlayLayer extends PureComponent {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
zIndex: 1,
|
||||
zIndex: 2,
|
||||
backgroundColor: displaying ? "rgba(255, 255, 255, 0.55)" : "",
|
||||
}}
|
||||
onMouseMove={handleCanvasEvent}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default (
|
||||
handleDragAction,
|
||||
handleEndAction,
|
||||
handleCancelAction,
|
||||
viewport,
|
||||
viewport
|
||||
) => {
|
||||
const svg = d3.select("#graph-wrapper").select("#lasso-layer");
|
||||
|
||||
@@ -23,7 +23,7 @@ export default (
|
||||
.brush()
|
||||
.extent([
|
||||
[0, 0],
|
||||
[viewport.width, viewport.height]
|
||||
[viewport.width, viewport.height],
|
||||
])
|
||||
.on("start", handleStartAction)
|
||||
.on("brush", handleDragAction)
|
||||
|
||||
@@ -11,7 +11,6 @@ import TopLeftLogoAndTitle from "./topLeftLogoAndTitle";
|
||||
scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
|
||||
}))
|
||||
class LeftSideBar extends React.Component {
|
||||
|
||||
render() {
|
||||
const { scatterplotXXaccessor, scatterplotYYaccessor } = this.props;
|
||||
return (
|
||||
|
||||
@@ -3,6 +3,10 @@ import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import * as globals from "../../globals";
|
||||
import Logo from "../framework/logo";
|
||||
import Truncate from "../util/truncate";
|
||||
|
||||
const DATASET_TITLE_WIDTH = 190;
|
||||
const DATASET_TITLE_FONT_SIZE = 14;
|
||||
|
||||
@connect((state) => ({
|
||||
datasetTitle: state.config?.displayNames?.dataset ?? "",
|
||||
@@ -14,16 +18,6 @@ class LeftSideBar extends React.Component {
|
||||
render() {
|
||||
const { datasetTitle, aboutURL } = this.props;
|
||||
|
||||
const displayTitle =
|
||||
datasetTitle.length > globals.datasetTitleMaxCharacterCount
|
||||
? `${datasetTitle.substring(
|
||||
0,
|
||||
Math.floor(globals.datasetTitleMaxCharacterCount / 2)
|
||||
)}…${datasetTitle.slice(
|
||||
-Math.floor(globals.datasetTitleMaxCharacterCount / 2)
|
||||
)}`
|
||||
: datasetTitle;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@@ -60,26 +54,36 @@ class LeftSideBar extends React.Component {
|
||||
gene
|
||||
</span>
|
||||
<div
|
||||
data-testid="header"
|
||||
style={{
|
||||
fontSize: 14,
|
||||
fontSize: DATASET_TITLE_FONT_SIZE,
|
||||
position: "relative",
|
||||
top: -6,
|
||||
display: "inline-block",
|
||||
width: "190px",
|
||||
width: DATASET_TITLE_WIDTH,
|
||||
marginLeft: "7px",
|
||||
height: "1.2em",
|
||||
overflow: "hidden",
|
||||
wordBreak: "break-all",
|
||||
}}
|
||||
title={datasetTitle}
|
||||
>
|
||||
{aboutURL ? (
|
||||
<a href={aboutURL} target="_blank" rel="noopener noreferrer">
|
||||
{displayTitle}
|
||||
</a>
|
||||
<Truncate>
|
||||
<a
|
||||
style={{ width: 185 }}
|
||||
href={aboutURL}
|
||||
data-testid="header"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{datasetTitle}
|
||||
</a>
|
||||
</Truncate>
|
||||
) : (
|
||||
displayTitle
|
||||
<Truncate>
|
||||
<span style={{ width: 185 }} data-testid="header">
|
||||
{datasetTitle}
|
||||
</span>
|
||||
</Truncate>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -35,9 +35,7 @@ function Clip(props) {
|
||||
: "";
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`bp3-button-group ${styles.menubarButton}`}
|
||||
>
|
||||
<div className={`bp3-button-group ${styles.menubarButton}`}>
|
||||
<Popover
|
||||
target={
|
||||
<Tooltip
|
||||
|
||||
@@ -37,6 +37,7 @@ import DiffexpButtons from "./diffexpButtons";
|
||||
showCentroidLabels: state.centroidLabels.showLabels,
|
||||
tosURL: state.config?.parameters?.["about_legal_tos"],
|
||||
privacyURL: state.config?.parameters?.["about_legal_privacy"],
|
||||
categoricalSelection: state.categoricalSelection,
|
||||
}))
|
||||
class MenuBar extends React.Component {
|
||||
static isValidDigitKeyEvent(e) {
|
||||
@@ -203,9 +204,13 @@ class MenuBar extends React.Component {
|
||||
showCentroidLabels,
|
||||
privacyURL,
|
||||
tosURL,
|
||||
categoricalSelection,
|
||||
colorAccessor,
|
||||
} = this.props;
|
||||
const { pendingClipPercentiles } = this.state;
|
||||
|
||||
const isColoredByCategorical = !!categoricalSelection?.[colorAccessor];
|
||||
|
||||
// constants used to create selection tool button
|
||||
const [selectionTooltip, selectionButtonIcon] =
|
||||
selectionTool === "brush"
|
||||
@@ -267,6 +272,7 @@ class MenuBar extends React.Component {
|
||||
onClick={this.handleCentroidChange}
|
||||
active={showCentroidLabels}
|
||||
intent={showCentroidLabels ? "primary" : "none"}
|
||||
disabled={!isColoredByCategorical}
|
||||
/>
|
||||
</Tooltip>
|
||||
<ButtonGroup className={styles.menubarButton}>
|
||||
|
||||
@@ -6,9 +6,7 @@ import styles from "./menubar.css";
|
||||
function InformationMenu(props) {
|
||||
const { libraryVersions, aboutLink, tosURL, privacyURL } = props;
|
||||
return (
|
||||
<div
|
||||
className={`bp3-button-group ${styles.menubarButton}`}
|
||||
>
|
||||
<div className={`bp3-button-group ${styles.menubarButton}`}>
|
||||
<Popover
|
||||
content={
|
||||
<Menu>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
:local(.menubarButton) {
|
||||
margin-top: 8px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
margin-top: 8px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
@@ -12,9 +12,7 @@ function Subset(props) {
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<ButtonGroup
|
||||
className={styles.menubarButton}
|
||||
>
|
||||
<ButtonGroup className={styles.menubarButton}>
|
||||
<Tooltip
|
||||
content="Subset to currently selected cells and associated metadata"
|
||||
position="bottom"
|
||||
@@ -26,7 +24,7 @@ function Subset(props) {
|
||||
disabled={!subsetPossible}
|
||||
icon="pie-chart"
|
||||
onClick={handleSubset}
|
||||
/>
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content="Undo subset and show all cells and associated metadata"
|
||||
|
||||
@@ -7,9 +7,7 @@ import styles from "./menubar.css";
|
||||
function InformationMenu(props) {
|
||||
const { undoDisabled, redoDisabled, dispatch } = props;
|
||||
return (
|
||||
<div
|
||||
className={`bp3-button-group ${styles.menubarButton}`}
|
||||
>
|
||||
<div className={`bp3-button-group ${styles.menubarButton}`}>
|
||||
<Tooltip
|
||||
content="Undo"
|
||||
position="bottom"
|
||||
|
||||
@@ -5,12 +5,11 @@ import Continuous from "../continuous/continuous";
|
||||
import GeneExpression from "../geneExpression";
|
||||
import * as globals from "../../globals";
|
||||
|
||||
@connect(state => ({
|
||||
@connect((state) => ({
|
||||
scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
|
||||
scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
|
||||
}))
|
||||
class RightSidebar extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
@@ -22,7 +21,7 @@ class RightSidebar extends React.Component {
|
||||
position: "relative",
|
||||
overflowY: "inherit",
|
||||
height: "inherit",
|
||||
width: "inherit"
|
||||
width: "inherit",
|
||||
}}
|
||||
>
|
||||
<GeneExpression />
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import React, { cloneElement } from "react";
|
||||
import { Tooltip, Position } from "@blueprintjs/core";
|
||||
|
||||
import { tooltipHoverOpenDelayQuick } from "../../globals";
|
||||
|
||||
const SPLIT_STYLE = {
|
||||
display: "flex",
|
||||
overflow: "hidden",
|
||||
justifyContent: "flex-start",
|
||||
};
|
||||
|
||||
const FIRST_HALF_STYLE = {
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
flexShrink: 1,
|
||||
minWidth: "5px",
|
||||
};
|
||||
|
||||
const SECOND_HALF_INNER_STYLE = {
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
};
|
||||
const SECOND_HALF_STYLE = {
|
||||
color: "transparent",
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
};
|
||||
|
||||
export default (props) => {
|
||||
const { children } = props;
|
||||
// Truncate only support a single child with a text child
|
||||
|
||||
if (
|
||||
React.Children.count(children) !== 1 ||
|
||||
React.Children.count(children.props?.children) !== 1
|
||||
) {
|
||||
throw Error("Only pass a single child with text to Truncate");
|
||||
}
|
||||
const originalString = children.props.children;
|
||||
|
||||
const firstString = originalString.substr(0, originalString.length / 2);
|
||||
const secondString = originalString.substr(originalString.length / 2);
|
||||
|
||||
const inheritedColor = children.props.style.color;
|
||||
|
||||
const splitStyle = { ...children.props.style, ...SPLIT_STYLE };
|
||||
const secondHalfInnerStyle = {
|
||||
...SECOND_HALF_INNER_STYLE,
|
||||
color: inheritedColor || "initial",
|
||||
};
|
||||
|
||||
const truncatedJSX = (
|
||||
<span style={splitStyle}>
|
||||
<span style={FIRST_HALF_STYLE}>{firstString}</span>
|
||||
<span style={SECOND_HALF_STYLE}>
|
||||
{secondString}
|
||||
<span style={secondHalfInnerStyle}>{secondString}</span>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
// clone children, changing the children(text) to the truncated string
|
||||
const newChildren = React.Children.map(children, (child) =>
|
||||
cloneElement(child, {
|
||||
children: truncatedJSX,
|
||||
"aria-label": originalString,
|
||||
})
|
||||
);
|
||||
return (
|
||||
<Tooltip
|
||||
content={originalString}
|
||||
hoverOpenDelay={tooltipHoverOpenDelayQuick}
|
||||
position={Position.LEFT}
|
||||
usePortal
|
||||
modifiers={{
|
||||
preventOverflow: { enabled: false },
|
||||
hide: { enabled: false },
|
||||
}}
|
||||
>
|
||||
{newChildren}
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user