-
Notifications
You must be signed in to change notification settings - Fork 715
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Annotation label <foreignobject> render (#1383)
* Annotation label <foreignobject> render Add option to pass in an element to render as a label as a child. Uses <foreignObject> to allow user to pass in an HTML element. Related to this discussion: #1173 This has the benefit of letting the element handle text reflow, with the drawback being that the user need to manage min and max width of the container element they render. This approach seems much better imo, favouring composability over configuration which is much more in line with the philosophy of d3. * Fix child render for Safari Add witdth and height to <foreignObject>. Worked for Chrome but was not displaying anything for Safari. * Fix test cases Not sure how idomatic all the `dive()`s are, but I've heard complaints about them so I'm guess it's the way to go. * Remove console log * Address PR comments Rename ForeignObjectLabel -> HtmlLabel Split out HtmlLabel and LabelAnchorLine to separate components. Export HtmlLabel as a separate component as it uses a small subset of the shared props.
- Loading branch information
Showing
6 changed files
with
155 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import React, { useContext, useMemo } from 'react'; | ||
import cx from 'classnames'; | ||
import useMeasure from 'react-use-measure'; | ||
import Group from '@visx/group/lib/Group'; | ||
import AnnotationContext from '../context/AnnotationContext'; | ||
import AnchorLine from './LabelAnchorLine'; | ||
import { LabelProps } from './Label'; | ||
|
||
const wrapperStyle = { display: 'inline-block' }; | ||
|
||
export type HtmlLabelProps = Pick< | ||
LabelProps, | ||
| 'anchorLineStroke' | ||
| 'className' | ||
| 'horizontalAnchor' | ||
| 'resizeObserverPolyfill' | ||
| 'showAnchorLine' | ||
| 'verticalAnchor' | ||
| 'x' | ||
| 'y' | ||
> & { | ||
/** Pass in a custom element as the label to style as you like. Renders inside a <foreignObject>, be aware that most non-browser SVG renderers will not render HTML <foreignObject>s. See: https://github.com/airbnb/visx/issues/1173#issuecomment-1014380545. */ | ||
children?: React.ReactNode; | ||
}; | ||
export default function HtmlLabel({ | ||
anchorLineStroke = '#222', | ||
children, | ||
className, | ||
horizontalAnchor: propsHorizontalAnchor, | ||
resizeObserverPolyfill, | ||
showAnchorLine = true, | ||
verticalAnchor: propsVerticalAnchor, | ||
x: propsX, | ||
y: propsY, | ||
}: HtmlLabelProps) { | ||
// we must measure the rendered title + subtitle to compute container height | ||
const [labelRef, titleBounds] = useMeasure({ | ||
polyfill: resizeObserverPolyfill, | ||
}); | ||
const { width, height } = titleBounds; | ||
|
||
// if props are provided, they take precedence over context | ||
const { x = 0, y = 0, dx = 0, dy = 0 } = useContext(AnnotationContext); | ||
|
||
// offset container position based on horizontal + vertical anchor | ||
const horizontalAnchor = | ||
propsHorizontalAnchor || (Math.abs(dx) < Math.abs(dy) ? 'middle' : dx > 0 ? 'start' : 'end'); | ||
const verticalAnchor = | ||
propsVerticalAnchor || (Math.abs(dx) > Math.abs(dy) ? 'middle' : dy > 0 ? 'start' : 'end'); | ||
|
||
const containerCoords = useMemo(() => { | ||
let adjustedX: number = propsX == null ? x + dx : propsX; | ||
let adjustedY: number = propsY == null ? y + dy : propsY; | ||
|
||
if (horizontalAnchor === 'middle') adjustedX -= width / 2; | ||
if (horizontalAnchor === 'end') adjustedX -= width; | ||
if (verticalAnchor === 'middle') adjustedY -= height / 2; | ||
if (verticalAnchor === 'end') adjustedY -= height; | ||
|
||
return { x: adjustedX, y: adjustedY }; | ||
}, [propsX, x, dx, propsY, y, dy, horizontalAnchor, verticalAnchor, width, height]); | ||
|
||
return ( | ||
<Group | ||
top={containerCoords.y} | ||
left={containerCoords.x} | ||
pointerEvents="none" | ||
className={cx('visx-annotationlabel', className)} | ||
> | ||
{showAnchorLine && ( | ||
<AnchorLine | ||
anchorLineOrientation={Math.abs(dx) > Math.abs(dy) ? 'vertical' : 'horizontal'} | ||
anchorLineStroke={anchorLineStroke} | ||
verticalAnchor={verticalAnchor} | ||
horizontalAnchor={horizontalAnchor} | ||
width={width} | ||
height={height} | ||
/> | ||
)} | ||
<foreignObject width={width} height={height} overflow="visible"> | ||
<div ref={labelRef} style={wrapperStyle}> | ||
{children} | ||
</div> | ||
</foreignObject> | ||
</Group> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
packages/visx-annotation/src/components/LabelAnchorLine.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import React from 'react'; | ||
import { TextProps } from '@visx/text'; | ||
|
||
interface AnchorLineProps { | ||
anchorLineOrientation: 'horizontal' | 'vertical'; | ||
verticalAnchor: TextProps['verticalAnchor']; | ||
horizontalAnchor: TextProps['textAnchor']; | ||
anchorLineStroke: string; | ||
width: number; | ||
height: number; | ||
} | ||
|
||
export default function AnchorLine({ | ||
anchorLineOrientation, | ||
anchorLineStroke, | ||
verticalAnchor, | ||
horizontalAnchor, | ||
width, | ||
height, | ||
}: AnchorLineProps) { | ||
const backgroundOutline = { stroke: anchorLineStroke, strokeWidth: 2 }; | ||
|
||
return ( | ||
<> | ||
{anchorLineOrientation === 'horizontal' && verticalAnchor === 'start' && ( | ||
<line {...backgroundOutline} x1={0} x2={width} y1={0} y2={0} /> | ||
)} | ||
{anchorLineOrientation === 'horizontal' && verticalAnchor === 'end' && ( | ||
<line {...backgroundOutline} x1={0} x2={width} y1={height} y2={height} /> | ||
)} | ||
{anchorLineOrientation === 'vertical' && horizontalAnchor === 'start' && ( | ||
<line {...backgroundOutline} x1={0} x2={0} y1={0} y2={height} /> | ||
)} | ||
{anchorLineOrientation === 'vertical' && horizontalAnchor === 'end' && ( | ||
<line {...backgroundOutline} x1={width} x2={width} y1={0} y2={height} /> | ||
)} | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters