diff --git a/apps/storybook/src/Stacking.mdx b/apps/storybook/src/Stacking.mdx index 74bf72191..b05e3a334 100644 --- a/apps/storybook/src/Stacking.mdx +++ b/apps/storybook/src/Stacking.mdx @@ -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 diff --git a/packages/lib/src/vis/shared/Axis.tsx b/packages/lib/src/vis/shared/Axis.tsx index c13b02b80..ed5c60c68 100644 --- a/packages/lib/src/vis/shared/Axis.tsx +++ b/packages/lib/src/vis/shared/Axis.tsx @@ -88,13 +88,8 @@ function Axis(props: Props) { )} {showGrid && ( - - + + )} diff --git a/packages/lib/src/vis/shared/AxisSystem.module.css b/packages/lib/src/vis/shared/AxisSystem.module.css index 8c387408b..2007b1e72 100644 --- a/packages/lib/src/vis/shared/AxisSystem.module.css +++ b/packages/lib/src/vis/shared/AxisSystem.module.css @@ -1,21 +1,8 @@ -.axisSystem { - 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'] { @@ -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 { @@ -52,20 +40,8 @@ text-anchor: middle; } -.title { - 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 { diff --git a/packages/lib/src/vis/shared/AxisSystem.tsx b/packages/lib/src/vis/shared/AxisSystem.tsx index 3d2ef8baa..02f497b49 100644 --- a/packages/lib/src/vis/shared/AxisSystem.tsx +++ b/packages/lib/src/vis/shared/AxisSystem.tsx @@ -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, + ); + assertDefined(visCanvas); + assertNonNull(visCanvas); + const visibleDomains = useCameraState(getVisibleDomains, [getVisibleDomains]); return ( - // Append to `canvasWrapper` instead of `r3fRoot` - -
- {showAxes && title &&

{title}

} - - -
+ + {createPortal( + <> + + + , + visCanvas, + )} ); } diff --git a/packages/lib/src/vis/shared/VisCanvas.module.css b/packages/lib/src/vis/shared/VisCanvas.module.css index a726a9992..fc40f85c2 100644 --- a/packages/lib/src/vis/shared/VisCanvas.module.css +++ b/packages/lib/src/vis/shared/VisCanvas.module.css @@ -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 @@ -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 { diff --git a/packages/lib/src/vis/shared/VisCanvas.tsx b/packages/lib/src/vis/shared/VisCanvas.tsx index 96a1f2c16..ade7f1ddd 100644 --- a/packages/lib/src/vis/shared/VisCanvas.tsx +++ b/packages/lib/src/vis/shared/VisCanvas.tsx @@ -54,48 +54,46 @@ function VisCanvas(props: PropsWithChildren) { return (
- - - - {children} - - - {raycasterThreshold !== undefined && ( - - )} - + {showAxes && title &&

{title}

} - - setSvgOverlay(elem || undefined)} - className={styles.svgOverlay} - /> - - -
setFloatingToolbar(elem || undefined)} - className={styles.floatingToolbar} - /> - - +
+ + + + {children} + + + {raycasterThreshold !== undefined && ( + + )} + + + + setSvgOverlay(elem || undefined)} + className={styles.svgOverlay} + /> + + +
setFloatingToolbar(elem || undefined)} + className={styles.floatingToolbar} + /> + + +
); }