Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix position of overflowing annotations by refactoring VisCanvas #1467

Merged
merged 2 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/storybook/src/Stacking.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
A number of elements are rendered **on top of the WebGL canvas**: axis grid, annotations, SVG elements, etc.
Some of these elements must appear above or behind others, so it's important to control and understand their **stacking order**.

The closest container to all these elements, `canvasWrapper`, creates a new [stacking context](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context).
The closest container to all these elements, `visCanvas`, creates a new [stacking context](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context).
This allows its children (both direct and nested) to be stacked in relation with one another _within_ this stacking context (unless some of them define their own stacking context).

### Stacking order
Expand Down
Binary file modified cypress/snapshots/app.cy.ts/edit_domain.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_2D.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_2D_complex.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_2D_inverted_cmap.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_4d_default.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_4d_remapped.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_4d_sliced.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/nximage.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 2 additions & 7 deletions packages/lib/src/vis/shared/Axis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,8 @@ function Axis(props: Props) {
</svg>
)}
{showGrid && (
<svg className={styles.grid} style={canvasSize}>
<GridComponent
scale={scale}
width={width}
height={height}
{...ticksProp}
/>
<svg className={styles.grid} {...canvasSize}>
<GridComponent scale={scale} {...canvasSize} {...ticksProp} />
</svg>
)}
</>
Expand Down
36 changes: 6 additions & 30 deletions packages/lib/src/vis/shared/AxisSystem.module.css
Original file line number Diff line number Diff line change
@@ -1,21 +1,8 @@
.axisSystem {
axelboc marked this conversation as resolved.
Show resolved Hide resolved
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
pointer-events: none;
display: grid;
grid-template-areas:
'. top-axis .'
'left-axis grid right-axis'
'. bottom-axis .';
z-index: var(--h5w-zi-axisSystem);
}

.axisSystem > svg {
display: block;
.axis,
.grid {
overflow: visible;
z-index: var(--h5w-zi-axisSystem);
pointer-events: none; /* let events through to canvas */
}

.axis[data-type='abscissa'] {
Expand All @@ -41,6 +28,7 @@
color: var(--h5w-tickLabels--color, #333);
font-family: var(--h5w-tickLabels--fontFamily, inherit);
font-size: var(--h5w-tickLabels--fontSize, 0.75em);
user-select: none; /* prevent unwanted selection during interaction */
}

.label {
Expand All @@ -52,20 +40,8 @@
text-anchor: middle;
}

.title {
axelboc marked this conversation as resolved.
Show resolved Hide resolved
display: flex;
justify-content: center;
align-items: center;
grid-area: top-axis;
margin: 0;
color: var(--h5w-plotTitle--color, inherit);
font-family: var(--h5w-plotTitle--fontFamily, inherit);
font-size: var(--h5w-plotTitle--fontSize, 1.125em);
font-weight: var(--h5w-plotTitle--fontWeight, inherit);
}

.grid {
grid-area: grid;
grid-area: canvas;
}

.grid line {
Expand Down
65 changes: 34 additions & 31 deletions packages/lib/src/vis/shared/AxisSystem.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,55 @@
import { assertDefined, assertNonNull } from '@h5web/shared';
import { useThree } from '@react-three/fiber';
import { createPortal } from 'react-dom';

import { useCameraState } from '../hooks';
import type { AxisOffsets } from '../models';
import Axis from './Axis';
import styles from './AxisSystem.module.css';
import Html from './Html';
import { useVisCanvasContext } from './VisCanvasProvider';

interface Props {
axisOffsets: AxisOffsets;
title?: string;
showAxes: boolean;
}

function AxisSystem(props: Props) {
const { axisOffsets, title, showAxes } = props;
const { axisOffsets, showAxes } = props;
const { canvasSize, abscissaConfig, ordinateConfig, getVisibleDomains } =
useVisCanvasContext();

const visCanvas = useThree(
(state) => state.gl.domElement.parentElement?.parentElement?.parentElement,
);
axelboc marked this conversation as resolved.
Show resolved Hide resolved
assertDefined(visCanvas);
assertNonNull(visCanvas);

const visibleDomains = useCameraState(getVisibleDomains, [getVisibleDomains]);

return (
// Append to `canvasWrapper` instead of `r3fRoot`
<Html overflowCanvas>
<div
className={styles.axisSystem}
style={{
gridTemplateColumns: `${axisOffsets.left}px 1fr ${axisOffsets.right}px`,
gridTemplateRows: `${axisOffsets.top}px 1fr ${axisOffsets.bottom}px`,
axelboc marked this conversation as resolved.
Show resolved Hide resolved
}}
>
{showAxes && title && <p className={styles.title}>{title}</p>}
<Axis
type="abscissa"
config={abscissaConfig}
domain={visibleDomains.xVisibleDomain}
canvasSize={canvasSize}
offset={axisOffsets.bottom}
showAxis={showAxes}
/>
<Axis
type="ordinate"
config={ordinateConfig}
domain={visibleDomains.yVisibleDomain}
canvasSize={canvasSize}
offset={axisOffsets.left}
showAxis={showAxes}
flipAxis
/>
</div>
<Html>
{createPortal(
axelboc marked this conversation as resolved.
Show resolved Hide resolved
<>
<Axis
type="abscissa"
config={abscissaConfig}
domain={visibleDomains.xVisibleDomain}
canvasSize={canvasSize}
offset={axisOffsets.bottom}
showAxis={showAxes}
/>
<Axis
type="ordinate"
config={ordinateConfig}
domain={visibleDomains.yVisibleDomain}
canvasSize={canvasSize}
offset={axisOffsets.left}
showAxis={showAxes}
flipAxis
/>
</>,
visCanvas,
)}
</Html>
);
}
Expand Down
42 changes: 32 additions & 10 deletions packages/lib/src/vis/shared/VisCanvas.module.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
.canvasWrapper {
.visCanvas {
flex: 1 1 0%;
overflow: hidden; /* prevent overflow when resizing */
position: relative; /* for axis system */
overflow: hidden; /* prevent overflow, notably when resizing */
z-index: 0; /* stacking context for anything rendered above the canvas (axis grid, SVG scene, floating toolbar, tooltip, etc.) */

display: grid;
grid-template-areas:
'. title'
'left-axis canvas'
'. bottom-axis';

/*
* Stacking order, from furthest to closest:
* 1. WebGL canvas
Expand All @@ -20,14 +25,31 @@
--h5w-zi-floatingToolbar: 2000;
}

.r3fRoot {
/* `r3fRoot` is implicitely stacked at `z-index: 0`, so it intercepts events before they reach the canvas.
* We can't stack it explicitly without creating a new stacking context, which breaks the stacking order
* (the axis system ends up either behind or in front of everything else, neither of which is acceptable).
* So we disable pointer events and restore them only on the `canvas` and on specific interactive elements,
* like the floating toolbar. */
pointer-events: none;
.title {
grid-area: title;
display: flex;
justify-content: center;
align-items: center;
margin: 0;
color: var(--h5w-plotTitle--color, inherit);
font-family: var(--h5w-plotTitle--fontFamily, inherit);
font-size: var(--h5w-plotTitle--fontSize, 1.125em);
font-weight: var(--h5w-plotTitle--fontWeight, inherit);
}

.canvasWrapper {
grid-area: canvas;
position: relative; /* for `.r3fRoot`, `.svgOverlay`, `.floatingToolbar`, overflowing annotations, etc. */
background-color: var(--h5w-canvas--bgColor, transparent);

/*
* `.canvasWrapper` and `.r3fRoot` are implicitely stacked at `z-index: 0`, so
* they intercept events before they can reach the canvas. We can't stack them
* explicitly without creating a new stacking context, which would break the
* stacking order. So we disable pointer events instead and restore them only
* on the `canvas` and on specific interactive elements, like the floating toolbar.
*/
pointer-events: none;
}

.r3fRoot > canvas {
Expand Down
74 changes: 36 additions & 38 deletions packages/lib/src/vis/shared/VisCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,48 +54,46 @@ function VisCanvas(props: PropsWithChildren<Props>) {

return (
<div
className={styles.canvasWrapper}
className={styles.visCanvas}
style={{
paddingBottom: axisOffsets.bottom,
paddingLeft: axisOffsets.left,
paddingTop: axisOffsets.top,
paddingRight: axisOffsets.right,
gridTemplateColumns: `${axisOffsets.left}px minmax(0, 1fr) ${axisOffsets.right}px`,
gridTemplateRows: `${axisOffsets.top}px minmax(0, 1fr) ${axisOffsets.bottom}px`,
}}
>
<R3FCanvas className={styles.r3fRoot} orthographic>
<VisCanvasProvider
visRatio={visRatio}
abscissaConfig={abscissaConfig}
ordinateConfig={ordinateConfig}
svgOverlay={svgOverlay}
floatingToolbar={floatingToolbar}
>
<AxisSystem
axisOffsets={axisOffsets}
title={title}
showAxes={showAxes}
/>
<InteractionsProvider>{children}</InteractionsProvider>
<ViewportCenterer />
<RatioEnforcer />
{raycasterThreshold !== undefined && (
<ThresholdAdjuster value={raycasterThreshold} />
)}
</VisCanvasProvider>
{showAxes && title && <p className={styles.title}>{title}</p>}

<Html>
<svg
ref={(elem) => setSvgOverlay(elem || undefined)}
className={styles.svgOverlay}
/>
</Html>
<Html>
<div
ref={(elem) => setFloatingToolbar(elem || undefined)}
className={styles.floatingToolbar}
/>
</Html>
</R3FCanvas>
<div className={styles.canvasWrapper}>
axelboc marked this conversation as resolved.
Show resolved Hide resolved
<R3FCanvas className={styles.r3fRoot} orthographic>
<VisCanvasProvider
visRatio={visRatio}
abscissaConfig={abscissaConfig}
ordinateConfig={ordinateConfig}
svgOverlay={svgOverlay}
floatingToolbar={floatingToolbar}
>
<AxisSystem axisOffsets={axisOffsets} showAxes={showAxes} />
<InteractionsProvider>{children}</InteractionsProvider>
<ViewportCenterer />
<RatioEnforcer />
{raycasterThreshold !== undefined && (
<ThresholdAdjuster value={raycasterThreshold} />
)}
</VisCanvasProvider>

<Html>
<svg
ref={(elem) => setSvgOverlay(elem || undefined)}
className={styles.svgOverlay}
/>
</Html>
<Html>
<div
ref={(elem) => setFloatingToolbar(elem || undefined)}
className={styles.floatingToolbar}
/>
</Html>
</R3FCanvas>
</div>
</div>
);
}
Expand Down