Skip to content

v7.0.0

Compare
Choose a tag to compare
@axelboc axelboc released this 10 Feb 14:38
· 665 commits to main since this release
e3ce4d8

πŸ‘‰ These release notes appear cropped on the Releases page. You'll find them in full on their dedicated page. πŸ‘ˆ

Full Changelog: v6.6.1...v7.0.0 β€” big thanks to @PeterC-DLS for #1311, #1324 and #1356 πŸ§‘β€πŸ€β€πŸ§‘

tldr; This release includes numerous breaking changes and new features to the component library, @h5web/lib. Highlights include: a simpler way to control the aspect ratio of the visualization; a more powerful SelectionTool component with support for validating and transforming selections; a more flexible API to render SVG shapes on top of the canvas.

@h5web/app

  • ❇️ Select-to-zoom interactions now zoom only on areas of at least 20x20px. This avoids inadvertently zooming on very small areas. Visual clues are provided to help users determine when selections being drawn become big enough to zoom on. #1345
  • πŸ› Fix error when switching from < NX Heatmap > to < NX Line > with an axis dataset longer than the signal dataset by one value (i.e. pixel boundaries) #1350 #1352
  • πŸ› When drawing a zoom selection, the stroke of the rectangle is no longer partially cropped by the canvas. #1346

@h5web/lib

Open the sections below to see the changes.

⚠️ Breaking changes
  • [HeatmapVis, RgbVis] Remove prop layout, now replaced with prop aspect (cf. New features). #1284
    • layout="cover" πŸ‘‰ replace with aspect="equal" (default)
    • layout="fill" πŸ‘‰ replace with aspect="auto"
    • layout="contain" πŸ‘‰ remove; equivalent behaviour can be achieved by constraining the size of the visualization's container. #1283
  • [VisCanvas] Remove prop visRatio, now replaced with prop aspect (cf. New features). #1284
    • visRatio={undefined} πŸ‘‰ replace with aspect="auto" (default)
    • visRatio={cols / rows} (i.e. orthonormal basis) πŸ‘‰ replace with aspect="equal" (the correct visRatio is computed internally)
    • visRatio={(cols / rows) / aspect} πŸ‘‰ replace with aspect={aspect}
  • [VisCanvas] Remove prop canvasRatio, which allowed restricting the canvas to a specific ratio. The canvas now always fills the available space (i.e. parent container minus axes). Migration: remove prop and constrain size of visualization container. #1283
  • [DefaultInteractions, SelectToZoom] Remove prop keepRatio. Interaction components now adjust their behaviours automatically when used inside a canvas with an aspect ratio (cf. New features). #1287
  • [SelectionTool, AxialSelectionTool] Callback props onSelectionChange and onSelectionEnd can now receive undefined when the user cancels the selection with Escape or releases the modifier key (if any). You may use the new onValidSelection callback instead of onSelectionEnd if you only care about successful selections. #1360
  • [SelectionTool, AxialSelectionTool] The shape of selection objects passed to callback props and to the render prop has changed - cf. the new interface vs. the old interface
  • Remove SelectionRect and SelectionLine. Use the new DataToHtml, SvgElement, SvgRect and SvgLine components instead. See code example below.
  • Rename useAxisSystemContext() to useVisCanvasContext() #1291
  • Utility getAxisValues now returns NumArray (i.e. TypedArray | number[]) instead of number[]. #1357
  • useCameraState The callback parameter no longer receives a context argument, and the second parameter is removed (i.e. the array dependencies Γ  la useCallback), which means that ESLint rule react-hooks/exhaustive-deps no longer has to be configured in consumer projects. #1361
  • dataToHtml, getVisibleDomains must now be retrieved from context and no longer require a context argument. #1342
  • Rename getWorldFOV to getFovBox and move to context; it no longer requires a context argument and returns a Box instance (cf. New features). #1361
  • Hook useCanvasEvents and context utilities dataToHtml, dataToWorld, worldToData now work solely with Vector3 instances; they no longer accept/return Vector2 instances. More generally, computations in all three coordinate spaces (HTML, world, data) are now performed with Vector3 only to avoid cluttering the codebase with Vector3 <-> Vector2 conversions (cf. notably, the new Box class, as well as the Rect type used for selection rectangles). We recommend you do the same when implementing your own interactivity features. #1300 #1341
  • Callbacks passed to useCanvasEvents no longer receive points in camera space (i.e. property cameraPt).
πŸš€ New features
  • VisCanvas now renders an svg element that overlays the canvas. Use the new SvgElement component to append any SVG shape to it from anywhere inside the VisCanvas component tree. See code example and screen recording below. #1322
  • New SvgRect and SvgLine components to simplify rendering SVG rect and line elements from a tuple of Vector3 points. SvgRect also provides a way to simulate rendering the stroke outside or inside of the rectangle (instead of centred on the boundary). These components are for convenience only; don't hesitate to render rect, line and any other SVG element directly. #1332 #1346
  • New DataToHtml component to convert points from data space to HTML space directly in JSX. Useful to render SVG shapes on the canvas and update them automatically when the user zooms or pans. See code example below.
  • New Box utility class based on Three's Box 3, but with a more convenient API and some useful additional methods. Particularly handy when implementing interactivity features (zoom selection, SVG ROIs, etc.) #1323 #1359
  • [LineVis] Allow passing abscissa array longer than data array (i.e. the abscissa array gets cropped) #1352
  • [VisCanvas] Add prop aspect, which works similarly to matplotlib's Axes.set_aspect #1284:
    • aspect="auto": the visualization is to fill the entire canvas (default; i.e. the internal visRatio is set to undefined);
    • aspect="equal": one ordinate unit is to be displayed with the same number of pixels on screen as one abscissa unit (equivalent to aspect={1}; i.e. orthonormal basis);
    • aspect={2}: one ordinate unit is to be displayed with twice the number of pixels on screen as one abscissa unit.
  • [VisCanvas] Add nice property to abscissaConfig and ordinateConfig props. This passes through to D3 to allow it to extend the domains to nice values when appropriate. #1324
  • [SelectToZoom, AxialSelectToZoom] Enforce minimum selection size of 20x20px, with help of visual clues, to avoid inadvertently zooming when users mean to click. #1345
  • [XAxisZoom, YAxisZoom, AxialSelectToZoom] now disable themselves when used inside a canvas with an aspect ratio (equal or custom). #1287
  • [SelectToZoom] now configures its own behaviour automatically according to the canvas' aspect ratio (or lack thereof). #1287
  • [SelectionTool, AxialSelectionTool] Add props validate and onValidSelection to support validating selections. See code examples and screen recordings below. Callback props onSelectionChange and onSelectionEnd, as well as the children render prop, all receive a boolean flag conveying whether the selection they are invoked with is valid. #1345
  • [SelectionTool, AxialSelectionTool] The selection objects passed to the render prop and to the callback props now include the selection rectangle in HTML space (in addition to world and data space). This is likely what you want to use in the render prop to render the selection rectangle in SVG. See code examples below. #1343
  • [SelectionTool] Add prop transform to allow transforming the selection while it is being drawn. The render prop and onSelectionChange both receive the transformed selection object as first parameter and the raw, untransformed selection object as second parameter. This feature is used internally by AxialSelectionTool to transform the user's selection into a selection that spans the entire width or height of the canvas. For another example, check out story Rect With Transform. #1301 #1340 #1349
  • [Histogram] Add prop showLeftAxis, which defaults to true, to allow hiding the left axis (with showLeftAxis={false}) #1295
  • [DataCurve, ScatterPoints] Accept typed arrays for abscissas and ordinates #1357
  • getValueToIndexScale, getAxisDomain, getAxisValues Accept typed arrays as argument #1357
  • useCameraState Allow passing equality function as second parameter to skip unnecessary re-renders. #1361
βš’οΈ New context properties
  • canvasSize – the size of the canvas (equivalent to useThree((state) => state.size)) #1288
  • canvasRatio – the aspect ratio of the canvas (i.e. width / height) #1288
  • canvasBox – a Box instance that spans the entire canvas; useful for HTML-space computations, like clipping/clamping other boxes/points to the bounds of the canvas. #1361
  • visRatio – the ratio of the visualization, computed internally by VisCanvas from the axis domains and aspect prop. Defined when the canvas has an equal/custom aspect ratio; undefined otherwise. #1287
  • worldToHtml, htmlToWorld, dataToHtml, htmlToData – space-conversion utilities; dataToHtml was previously available as a direct import #1302 #1342
  • getVisibleDomains, getFovBox – camera-based utilities previous available as direct imports #1361
πŸ› Bug fixes
  • [SelectionTool, AxialSelectionTool]:
    • Fix onSelectionStart callback called before user actually starts moving the mouse. #1318
    • Fix onSelectionChange callback not called on first pointer move. #1339
    • Fix onSelectionEnd callback not called when cancelling selection with Escape. #1360
    • Invoke callbacks asynchronously, after render prop, to avoid bugs such as a potential persisted selection being displayed on top of the active selection for one render. #1318
    • Wait for next available animation frame before re-rendering SelectionTool to avoid overloading the main thread when onPointerMove is triggered many times in a row. #1298

Examples

Drawing SVG shapes from data coordinates

Example taken from story Building Blocks / SvgElement / Default:

Peek 2023-02-08 10-19

See the code
<DataToHtml points={[new Vector3(2, 8), new Vector3(4, 6), new Vector3(3, 2), new Vector3(6, 4), new Vector3(6, 6), new Vector3(7, 7)]}>
  {(pt1, pt2, pt3, pt4, pt5, pt6) => (
    <SvgElement>
      <SvgRect className={styles.rect} coords={[pt1, pt2]} />
      <SvgLine
        coords={[pt3, pt4]}
        stroke="darkblue"
        strokeWidth={5}
        strokeDasharray={15}
        strokeLinecap="round"
      />
      <SvgRect
        coords={[pt5, pt6]}
        fill="none"
        stroke="teal"
        strokeWidth={10}
        strokePosition="outside"
      />
    </SvgElement>
  )}
</DataToHtml>

You can make your SVG elements interactive by simply registering event handlers on them (e.g. onPointerDown={handlePointerDown}) and by applying styles to them like :hover (i.e. via className or style). Just make sure to apply at least pointer-events: auto to the elements so they can respond to pointer events (the SVG overlay has pointer-events: none to let events through to the canvas). Note that pointer events intercepted by SVG elements do not currently "bubble up" to the canvas, since the SVG overlay is not a child of the canvas. This limitation should be resolved in a future release of H5Web.

Internally, DataToHtml retrieves space conversion utility dataToHtml from context and uses useCameraState to keep the HTML points up to date as the user interacts with the canvas. It then invokes the children render prop with those points on every render. For advanced use cases, especially when optimising for performance, don't hesitate to perform the conversions and invoke useCameraState yourself, perhaps with an equality function.

Validating selections

Example taken from story Building Blocks / SelectionTool / LineWithLengthValidation:

Peek 2023-02-08 14-21

See the code
<SelectionTool validate={({ html: [start, end] }) => start.distanceTo(end) >= 100}>
  {({ html: htmlSelection }, _, isValid) => (
    <SvgElement>
      <SvgLine
        coords={htmlSelection}
        stroke={isValid ? 'teal' : 'orangered'}
        strokeWidth={3}
        strokeLinecap="round"
      />
    </SvgElement>
  )}
</SelectionTool>

Example taken from story Building Blocks / AxialSelectionTool / WithValidation:

Peek 2023-02-08 14-24

See the code
<AxialSelectionTool
  axis="x"
  validate={({ html }) => Box.fromPoints(...html).hasMinSize(200)} // note the use of the new `Box` utility class
  onValidSelection={({ data: dataSelection }) => { /* save `dataSelection` somehow */ }}
>
  {({ html: htmlSelection }, _, isValid) => (
    <SvgElement>
      <SvgRect
        coords={htmlSelection}
        fill={isValid ? 'teal' : 'orangered'}
        fillOpacity={0.5}
      />
    </SvgElement>
  )}
</AxialSelectionTool>