@visx/marker 使用指南:用 React 组件为 SVG 图形添加箭头、圆点与十字标记
【免费下载链接】visx🐯 visx | visualization components项目地址: https://gitcode.com/gh_mirrors/vi/visx
@visx/marker 是 visx 可视化组件库中专用于生成SVG<marker>图形对象的包。在 SVG 中,marker(标记)是一类依附于<path>、<line>、<polyline>或<polygon>元素的图形对象,最常见的形态就是折线/路径端点处的箭头。本指南将以 packages/visx-marker/Readme.md 为骨架,结合包内源码与测试,讲解@visx/marker的安装方式、底层Marker组件的全部可配置属性,以及内置的 5 种预制标记组件(箭头、圆、十字、叉号、竖线)各自的几何计算原理与使用场景,读完即可在任意 visx 图表中为连线/路径加上精确可控的端点标记。
什么是 SVG marker,为什么需要 @visx/marker
按照 SVG 规范,<marker>元素是一种可复用的图形模板,它本身不直接出现在画布上,而是通过marker-start、marker-mid、marker-end这三个引用属性挂接到路径类元素上,从而在路径的起点、每个中间顶点、终点处绘制箭头、圆点等图形。@visx/marker正是把这一底层 SVG 能力封装成 React 组件:每个组件渲染一个<defs>包裹的<marker>,你只需在路径元素上通过url(#id)引用即可,例如marker-end="url(#arrow)"。
该包对外导出统一的入口,见 src/index.ts:
export { default as Marker } from './markers/Marker'; export { default as MarkerArrow } from './markers/Arrow'; export { default as MarkerCross } from './markers/Cross'; export { default as MarkerX } from './markers/X'; export { default as MarkerCircle } from './markers/Circle'; export { default as MarkerLine } from './markers/Line'; export type { MarkerProps, MarkerComponentProps } from './markers/Marker';其中Marker是通用底层组件(可自定义内部图形),其余 5 个为开箱即用的预制标记。
安装
在项目中安装该包即可使用(peer 依赖为 React 18 或 19,见 package.json):
npm install --save @visx/marker由于包在package.json中声明了sideEffects: false,且同时提供 CommonJS(lib/)与 ES Module(esm/)两种产物,可以安全地被摇树(tree-shaking),打包器只会把实际用到的标记组件打进产物。
通用 Marker 组件与全部可配置属性
Marker是包裹原生<marker>的最小封装,源码位于 src/markers/Marker.tsx。它始终在<defs>中输出<marker>,其余任意 SVG 属性(如fill、stroke、orient)都会透传给原生元素。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
id | string | 必填 | <marker>的唯一 id,必须保证页面内全局唯一,因为后续通过url(#id)引用 |
size | number | — | 用于计算 marker 内容包围盒尺寸的数值,预制组件用它推导markerWidth/markerHeight/refX/refY |
markerWidth | string \| number | 3 | marker 视口宽度 |
markerHeight | string \| number | 3 | marker 视口高度 |
markerUnits | string | 'userSpaceOnUse' | marker 坐标系统:userSpaceOnUse表示按用户坐标(不受描边宽度影响),strokeWidth表示按引用它的路径的描边宽度缩放 |
refX | string \| number | — | 标记图形在 marker 坐标系中的 x 参考点(对齐到路径顶点) |
refY | string \| number | — | 标记图形在 marker 坐标系中的 y 参考点 |
strokeWidth | number | — | 描边宽度,源码注释明确指出被约束为number类型,因为要参与包围盒数值计算 |
children | ReactNode | 必填 | marker 内容,通常是<path>、<line>、<polyline>或<polygon> |
需要特别留意strokeWidth的类型约束:其余属性都允许string | number,唯独strokeWidth只能是number(src/markers/Marker.tsx),这是因为预制组件要用它做加减乘除的包围盒计算,字符串无法参与运算。
最基础的用法是自定义内容:
import { Marker } from '@visx/marker'; function Chart() { return ( <svg width={400} height={300}> {/* Marker 自带 <defs>,无需再包裹一层 */} <Marker id="custom-diamond" markerWidth={10} markerHeight={10} refX={5} refY={5} markerUnits="userSpaceOnUse"> <polygon points="5,0 10,5 5,10 0,5" fill="tomato" /> </Marker> <polyline points="20,60 120,60 120,180 220,180" fill="none" stroke="steelblue" strokeWidth={2} markerEnd="url(#custom-diamond)" /> </svg> ); }5 个预制标记组件与它们的几何计算
所有预制组件都遵循同一套设计模式:接收size与strokeWidth两个关键数值,在渲染前计算出<marker>的视口尺寸与参考点,并设置markerUnits="strokeWidth"(即随引用元素的描边宽度缩放)与合适的orient。下面逐一拆解。
MarkerArrow:路径端点箭头
源码见 src/markers/Arrow.tsx,默认size = 9、strokeWidth = 1。它的核心计算:
const max = size + strokeWidth * 2; // 视口边长 = size + 2 倍描边 const midX = size; // 参考点 x = size const midY = max / 2; // 参考点 y = 视口高的一半 const points = `0 0, ${size} ${size / 2}, 0 ${size}`; // 箭头 polyline即用一个polyline(0 0 → size, size/2 → 0 size)勾勒出朝右的 V 形箭头,内部用<g transform={translate(strokeWidth, strokeWidth)}>为描边留出 1 倍线宽的边距。orient="auto"让箭头自动沿路径方向旋转。测试 test/Arrow.test.tsx 验证了尺寸计算:size=8、strokeWidth=1时,markerWidth/markerHeight均为10、refX=8、refY=5、points="0 0, 8 4, 0 8"。
使用示例:
import { MarkerArrow } from '@visx/marker'; <MarkerArrow id="arrow" size={9} strokeWidth={1.5} stroke="rebeccapurple" /> <Line x1={0} y1={80} x2={200} y2={80} stroke="rebeccapurple" strokeWidth={1.5} markerEnd="url(#arrow)" />MarkerCircle:圆点标记
源码见 src/markers/Circle.tsx,默认size = 9、strokeWidth = 1。计算逻辑:
const diameter = size * 2; // 直径 = 2 * size const bounds = diameter + strokeWidth; // 包围盒边长 = 直径 + 描边 const mid = bounds / 2; // 圆心 = 包围盒中心渲染一个r={size}的<circle>,圆心放在包围盒正中心,refY = mid、refX = 0,orient="auto-start-reverse"意味着它既可以做起点标记也可以做终点标记(自动区分方向)。适合做散点路径上每个数据点的落点强调。
MarkerCross 与 MarkerX:十字与叉号
两者都位于 src/markers/Cross.tsx(X 只是其特殊形态)。MarkerCross默认size = 9、strokeWidth = 1:
const bounds = size + strokeWidth; // 包围盒边长 = size + 描边 const mid = size / 2; // 参考点 = 中心 const points = `0 ${mid}, ${mid} ${mid}, ${mid} 0, ${mid} ${size}, ${mid} ${mid}, ${size} ${mid}`;即一笔画出一个正十字(上、右、下、左四段折线)。而MarkerX的实现极其简洁(src/markers/X.tsx):
export default function MarkerX(props: MarkerComponentProps) { return <Cross orient={45} {...props} />; }它只是把十字绕中心旋转 45° 得到叉号——用orient={45}传入一个固定数字而非"auto",从而让标记始终保持 45° 倾角,不随路径方向旋转。
MarkerLine:竖向短线标记
源码见 src/markers/Line.tsx,它用<rect>画一条竖线而不是 polyline:
const max = Math.max(size, strokeWidth * 2); // 视口宽 = max(size, 2*描边) const midX = max / 2; // 参考点 x = 视口宽一半 const midY = size / 2; // 参考点 y = size 一半值得注意它的着色方式:fill={fill || stroke}、stroke="none",也就是说颜色既可以用fill传入,也可以直接用stroke传入(此时把描边色当作填充色使用),适合在路径末端画一条与线同色的短刻度线。
在 visx 图表中的组合方式
预制标记组件都可以和 visx 其他包自由组合。例如配合 @visx/shape 的LinePath使用:把MarkerArrow放进<defs>,再把markerEnd透传给LinePath内部的<path>,即可让折线/曲线的终点带上箭头:
import { LinePath } from '@visx/shape'; import { MarkerArrow } from '@visx/marker'; <svg width={500} height={300}> <MarkerArrow id="line-arrow" size={8} strokeWidth={1.2} stroke="orange" /> <LinePath data={data} x={(d) => xScale(d.x)} y={(d) => yScale(d.y)} stroke="orange" strokeWidth={1.2} markerEnd="url(#line-arrow)" /> </svg>在 React 中,markerEnd这类带连字符的 SVG 属性可以直接以 JSX 属性名书写(React 会正确渲染为marker-end)。因为Marker自带<defs>包裹,多个标记之间互不干扰,只要保证id全局唯一即可。
关键设计要点与注意事项
- id 必须全局唯一:
Marker的id属性是必填的,且整个页面内不能与其他元素的 id 冲突,否则url(#id)引用会指向错误的元素。 markerUnits的两种模式:通用Marker默认userSpaceOnUse(固定像素尺寸),而 5 个预制组件统一使用strokeWidth,标记会随引用路径的描边宽度成比例缩放,视觉上更协调。orient决定旋转行为:"auto"让标记沿路径方向自动旋转(MarkerArrow、MarkerCross、MarkerLine);"auto-start-reverse"额外支持起点/终点双向(MarkerCircle);传数字则固定角度(MarkerX的 45°)。size与strokeWidth是几何计算入口:从源码可以看到,预制组件的视口宽高与参考点全部由这两个数值推导,因此调整大小时推荐只改size与strokeWidth,而不是直接覆盖markerWidth/refX等计算出的属性。- 包围盒显式计算的必要性:
strokeWidth被刻意约束为number类型(Marker.tsx),正是为了保证这些加减乘除计算在运行时不出错;测试 test/Arrow.test.tsx 也把这一套计算作为断言对象,确保渲染出的markerWidth、markerHeight、refX、refY与预期完全一致。
至此,从安装、通用Marker的属性体系,到 5 个预制标记各自的几何推导与组合用法,@visx/marker的全部能力都已覆盖。将它用于 visx 图表的路径端点、散点强调或刻度装饰,都能以极少的样板代码获得精确可控的 SVG 标记。
【免费下载链接】visx🐯 visx | visualization components项目地址: https://gitcode.com/gh_mirrors/vi/visx
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考