Skip to content

Commit

Permalink
feat(side-by-side): add buttons for collapse, remove, open, also reva…
Browse files Browse the repository at this point in the history
…mp embedding selector (#970)
  • Loading branch information
seve authored Jun 12, 2024
1 parent 5d35340 commit d46de12
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 72 deletions.
14 changes: 14 additions & 0 deletions client/src/actions/embedding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,17 @@ export const layoutChoiceAction: ActionCreator<
annoMatrix,
});
};

export const swapLayoutChoicesAction: ActionCreator<
ThunkAction<Promise<void>, RootState, never, Action<"set layout choice">>
> =
() =>
async (dispatch: AppDispatch, getState: GetState): Promise<void> => {
const { layoutChoice, panelEmbedding } = getState();
// get main and side layout choices
const mainLayoutChoice = layoutChoice.current;
const sideLayoutChoice = panelEmbedding.layoutChoice.current;

await dispatch(layoutChoiceAction(mainLayoutChoice, true));
await dispatch(layoutChoiceAction(sideLayoutChoice, false));
};
1 change: 1 addition & 0 deletions client/src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,7 @@ export default {
subsetAction: viewActions.subsetAction,
resetSubsetAction: viewActions.resetSubsetAction,
layoutChoiceAction: embActions.layoutChoiceAction,
swapLayoutChoicesAction: embActions.swapLayoutChoicesAction,
setCellSetFromSelection: selnActions.setCellSetFromSelection,
genesetDelete: genesetActions.genesetDelete,
genesetAddGenes: genesetActions.genesetAddGenes,
Expand Down
78 changes: 62 additions & 16 deletions client/src/components/PanelEmbedding/index.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,54 @@
import React, { useState } from "react";
import { connect } from "react-redux";
import { IconNames } from "@blueprintjs/icons";
import { Button } from "@blueprintjs/core";

import * as globals from "../../globals";
import { height, margin, width } from "./util";
import { height, width } from "./util";
import Graph from "../graph/graph";
import Controls from "../controls";
import Embedding from "../embedding";
import { AppDispatch, RootState } from "../../reducers";
import actions from "../../actions";

interface StateProps {
level: string;
minimized: boolean;
isMinimized: boolean;
isOpen: RootState["panelEmbedding"]["open"];
}

const mapStateToProps = (): StateProps => ({
level: "top",
minimized: false,
interface DispatchProps {
dispatch: AppDispatch;
}

const mapStateToProps = (state: RootState): StateProps => ({
isMinimized: state.panelEmbedding.minimized,
isOpen: state.panelEmbedding.open,
});

const PanelEmbedding = (props: StateProps) => {
const PanelEmbedding = (props: StateProps & DispatchProps) => {
const [viewportRef, setViewportRef] = useState<HTMLDivElement | null>(null);

const { level, minimized } = props;
const { isMinimized, isOpen, dispatch } = props;

const handleSwapLayoutChoices = async (): Promise<void> => {
await dispatch(actions.swapLayoutChoicesAction());
};

// TODO(seve): Connect to redux state
if (!isOpen) return null;

return (
<div
style={{
position: "fixed",
bottom:
level === "top"
? globals.bottomToolbarGutter * 2
: globals.bottomToolbarGutter,
borderRadius: "3px 3px 0px 0px",
right: globals.leftSidebarWidth + globals.scatterplotMarginLeft,
bottom: "12px",
padding: "0px 20px 20px 0px",
background: "white",
/* x y blur spread color */
boxShadow: "0px 0px 3px 2px rgba(153,153,153,0.2)",
width: `${width + margin.left + margin.right}px`,
height: `${(minimized ? 0 : height + margin.top) + margin.bottom}px`,
width: `${width}px`,
height: `${isMinimized ? 48 : height}px`,
zIndex: 5,
}}
id="side-viewport"
Expand All @@ -55,6 +64,7 @@ const PanelEmbedding = (props: StateProps) => {
flexWrap: "wrap",
justifyContent: "space-between",
width: "100%",
marginTop: "8px",
}}
>
<div
Expand All @@ -63,13 +73,49 @@ const PanelEmbedding = (props: StateProps) => {
flexDirection: "row",
flexWrap: "wrap",
justifyContent: "left",
gap: "8px",
}}
>
<Embedding isSidePanel />
<Button
type="button"
icon={IconNames.SWAP_VERTICAL}
onClick={handleSwapLayoutChoices}
/>
</div>
<div
style={{
display: "flex",
flexDirection: "row",
flexWrap: "wrap",
justifyContent: "right",
gap: "8px",
}}
>
<Button
type="button"
icon={isMinimized ? IconNames.MAXIMIZE : IconNames.MINIMIZE}
onClick={() => {
dispatch({
type: "toggle minimize panel embedding",
});
}}
/>
<Button
type="button"
icon={IconNames.CROSS}
onClick={() => {
dispatch({
type: "toggle panel embedding",
});
}}
/>
</div>
</div>
</Controls>
{viewportRef && <Graph viewportRef={viewportRef} isSidePanel />}
{viewportRef && (
<Graph isHidden={isMinimized} viewportRef={viewportRef} isSidePanel />
)}
</div>
);
};
Expand Down
3 changes: 1 addition & 2 deletions client/src/components/PanelEmbedding/util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export const margin = { top: 40, right: 5, bottom: 20, left: 60 };
export const width = 400;
export const height = 400 - margin.top - margin.bottom;
export const height = 400;
export const innerHeight = height - 2;

export const devicePixelRatio = window.devicePixelRatio || 1;
145 changes: 94 additions & 51 deletions client/src/components/embedding/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
RadioGroup,
Tooltip,
} from "@blueprintjs/core";
import { IconNames } from "@blueprintjs/icons";
import { Popover2 } from "@blueprintjs/popover2";
import * as globals from "../../globals";
import actions from "../../actions";
Expand All @@ -19,12 +20,15 @@ import { EVENTS } from "../../analytics/events";
import { AppDispatch, RootState } from "../../reducers";
import { Schema } from "../../common/types/schema";
import { AnnoMatrixObsCrossfilter } from "../../annoMatrix";
import { getFeatureFlag } from "../../util/featureFlags/featureFlags";
import { FEATURES } from "../../util/featureFlags/features";

interface StateProps {
layoutChoice: RootState["layoutChoice"];
schema?: Schema;
crossfilter: RootState["obsCrossfilter"];
imageUnderlay: RootState["controls"]["imageUnderlay"];
sideIsOpen: RootState["panelEmbedding"]["open"];
}

interface OwnProps {
Expand All @@ -44,13 +48,24 @@ const mapStateToProps = (state: RootState, props: OwnProps): StateProps => ({
schema: state.annoMatrix?.schema,
crossfilter: state.obsCrossfilter,
imageUnderlay: state.controls.imageUnderlay,
sideIsOpen: state.panelEmbedding.open,
});

function Embedding(props: Props) {
const { layoutChoice, schema, crossfilter, dispatch, imageUnderlay } = props;
const Embedding = (props: Props) => {
const {
layoutChoice,
schema,
crossfilter,
dispatch,
imageUnderlay,
isSidePanel,
sideIsOpen,
} = props;
const { annoMatrix } = crossfilter || {};
if (!crossfilter || !annoMatrix) return null;

const isSpatial = getFeatureFlag(FEATURES.SPATIAL);

const handleLayoutChoiceClick = (): void => {
track(EVENTS.EXPLORER_LAYOUT_CHOICE_BUTTON_CLICKED);
};
Expand All @@ -60,8 +75,6 @@ function Embedding(props: Props) {
): Promise<void> => {
track(EVENTS.EXPLORER_LAYOUT_CHOICE_CHANGE_ITEM_CLICKED);

const { isSidePanel } = props;

await dispatch(
actions.layoutChoiceAction(e.currentTarget.value, isSidePanel)
);
Expand All @@ -77,61 +90,91 @@ function Embedding(props: Props) {
}
};

const handleOpenPanelEmbedding = async (): Promise<void> => {
dispatch({
type: "toggle panel embedding",
});
};

return (
<ButtonGroup
<div
style={{
paddingTop: 8,
zIndex: 9999,
}}
>
<Popover2
// minimal /* removes arrow */
position={Position.TOP_LEFT}
content={
<div
style={{
display: "flex",
justifyContent: "flex-start",
alignItems: "flex-start",
flexDirection: "column",
padding: 10,
width: 400,
}}
>
<H4>Embedding Choice</H4>
<p style={{ fontStyle: "italic" }}>
There are {schema?.dataframe?.nObs} cells in the entire dataset.
</p>
<EmbeddingChoices
onChange={handleLayoutChoiceChange}
annoMatrix={annoMatrix}
layoutChoice={layoutChoice}
/>
</div>
}
>
<Tooltip
content="Select embedding for visualization"
position="top"
hoverOpenDelay={globals.tooltipHoverOpenDelay}
<ButtonGroup>
<Popover2
// minimal /* removes arrow */
position={Position.TOP_LEFT}
content={
<div
style={{
display: "flex",
justifyContent: "flex-start",
alignItems: "flex-start",
flexDirection: "column",
padding: 10,
width: 400,
}}
>
<H4>Embedding Choice</H4>
<p style={{ fontStyle: "italic" }}>
There are {schema?.dataframe?.nObs} cells in the entire dataset.
</p>
<EmbeddingChoices
onChange={handleLayoutChoiceChange}
annoMatrix={annoMatrix}
layoutChoice={layoutChoice}
/>
</div>
}
>
<Button
type="button"
data-testid="layout-choice"
id="embedding"
style={{
cursor: "pointer",
}}
onClick={handleLayoutChoiceClick}
<Tooltip
content="Select embedding for visualization"
position="top"
hoverOpenDelay={globals.tooltipHoverOpenDelay}
>
{layoutChoice?.current}: {crossfilter.countSelected()} of{" "}
{crossfilter.size()} cells
</Button>
</Tooltip>
</Popover2>
</ButtonGroup>
<Button
type="button"
data-testid="layout-choice"
id="embedding"
style={{
cursor: "pointer",
}}
icon={IconNames.GRAPH}
rightIcon={IconNames.CARET_DOWN}
onClick={handleLayoutChoiceClick}
>
{layoutChoice?.current}
</Button>
</Tooltip>
</Popover2>
{!isSidePanel && isSpatial && (
<Button
icon={IconNames.MULTI_SELECT}
onClick={handleOpenPanelEmbedding}
active={sideIsOpen}
/>
)}
</ButtonGroup>

{!isSidePanel && (
<span
style={{
color: "#767676",
fontWeight: 400,
marginTop: "8px",
position: "absolute",
top: "38px",
left: "64px",
}}
>
{crossfilter.countSelected()} of {crossfilter.size()} cells
</span>
)}
</div>
);
}
};

export default connect(mapStateToProps)(Embedding);

Expand Down
3 changes: 2 additions & 1 deletion client/src/components/graph/graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1171,6 +1171,7 @@ class Graph extends React.Component<GraphProps, GraphState> {
screenCap,
imageUnderlay,
isSidePanel = false,
isHidden = false,
} = this.props;

const {
Expand All @@ -1193,7 +1194,7 @@ class Graph extends React.Component<GraphProps, GraphState> {
position: "relative",
left: 0,
flexDirection: "column",
display: "flex",
display: isHidden ? "none" : "flex",
alignItems: "center",
}}
data-testid={sidePanelAttributeNameChange(`graph-wrapper`, isSidePanel)}
Expand Down
1 change: 1 addition & 0 deletions client/src/components/graph/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export interface StateProps {
export interface OwnProps {
viewportRef: HTMLDivElement;
isSidePanel?: boolean;
isHidden?: boolean;
}

interface DispatchProps {
Expand Down
1 change: 1 addition & 0 deletions client/src/components/menubar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ class MenuBar extends React.PureComponent<MenuBarProps, State> {
flexDirection: "row",
flexWrap: "wrap",
justifyContent: "left",
marginTop: 8,
}}
>
<Embedding isSidePanel={false} />
Expand Down
Loading

0 comments on commit d46de12

Please sign in to comment.