F2 图形标签(Shape)实战指南:用 JSX 自定义图形、添加标签并接入交互
F2 图形标签(Shape)实战指南:用 JSX 自定义图形、添加标签并接入交互
F2(@antv/f2)是一个面向移动端的交互式图表库,除了内置的柱状图、折线图、饼图等图表组件外,还提供了底层的 Shape 图形标签体系,允许开发者像写 JSX 一样直接绘制 rect、circle、line、text 等基础图形。本文以 site/examples/component/shape 示例文档为核心,结合仓库中的 demo 源码与相关教程,系统讲解如何用 Shape 自定义图形、为图形添加文本标签、配置完整样式、绑定移动端事件,并将自定义图形封装为可复用组件,最终在真实图表中实现数据标注与自定义装饰。
Shape 是什么:用 JSX 直接绘制基础图形
在 F2 中,图形标签(Shape)是一组可直接书写在 JSX 中的基础图形元素,包括矩形 rect、圆形 circle、直线 line、折线 polyline、多边形 polygon、文本 text、图片 image、弧形 arc、扇形 sector、分组容器 group 等。它解决的是"图表内置组件无法满足个性化需求"的问题:当需要自定义图例、注释、装饰、特殊标记时,可以直接用这些标签把想要的图形"画"出来。
Shape 的典型用法是把多个基础图形组合进一个 <group> 容器,再通过 style 属性控制每个图形的样式。仓库示例 shape.jsx 给出了一个同时包含矩形、圆形和文本的最简组合:
/** @jsx jsx */
import { jsx, Canvas } from '@antv/f2';
const context = document.getElementById('container').getContext('2d');
const Shape = () => {
return (
<group>
<rect
style={{
x: 10,
y: 10,
width: 40,
height: 40,
lineWidth: '2px',
stroke: '#000',
fill: 'red',
}}
/>
<circle style={{ cx: 80, cy: 30, r: 20, lineWidth: '2px', stroke: '#000', fill: 'red' }} />
<text
style={{
x: 120,
y: 30,
text: '文本',
fontSize: 20,
fill: '#000',
}}
/>
</group>
);
};
const { props } = (
<Canvas context={context} pixelRatio={window.devicePixelRatio}>
<Shape />
</Canvas>
);
const chart = new Canvas(props);
chart.render();
其中 <Canvas> 接收一个 2D 绘图上下文 context 与 pixelRatio(这里取 window.devicePixelRatio 以适配移动端高清屏),new Canvas(props) 创建实例后调用 chart.render() 完成绘制。上述代码即可在画布上渲染出一个描边矩形、一个红色圆形和一段文本,这就是自定义图形的最小闭环。
从源码结构看,F2 的图形标签体系与渲染引擎 f-engine 深度绑定:packages/f2/jsx-runtime.js 直接 export * from '@antv/f-engine/jsx-runtime',说明 JSX 语法糖由底层引擎提供,Shape 标签最终会解析为引擎中的图形对象并交由 Canvas 渲染。
为图形添加标签:<text /> 的用法与属性
<text /> 是 Shape 体系中用于数据标注、说明的核心标签。示例文档明确指出:通过 <text /> 元素,可以为自定义图形添加标签,实现数据标注、说明等功能。
例如在矩形中央叠加一个居中的白色文字标签:
<group>
<rect
style={{
x: 10,
y: 10,
width: 40,
height: 40,
lineWidth: '2px',
stroke: '#000',
fill: 'red',
}}
/>
{/* 直接用 <text /> 添加标签 */}
<text
style={{
x: 30,
y: 35,
text: '标签',
fontSize: 14,
fill: '#fff',
textAlign: 'center',
textBaseline: 'middle',
}}
/>
</group>
常用标签属性
<text /> 的核心属性如下(见 shape/index.zh.md):
x、y:文本锚点坐标text:文本内容fontSize、fill、fontWeight等:文本样式textAlign、textBaseline:对齐方式
配合 绘图属性教程 shape-attrs.zh.md 中给出的完整文本属性,可以精确控制标签外观:
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
textAlign |
string |
'start' |
水平对齐:'start'、'center'、'end'、'left'、'right' |
textBaseline |
string |
'alphabetic' |
垂直基线:'top'、'hanging'、'middle'、'alphabetic'、'ideographic'、'bottom' |
fontStyle |
string |
'normal' |
'normal'、'italic'、'oblique' |
fontSize |
number |
12 |
字号(像素) |
fontFamily |
string |
'sans-serif' |
字体系列 |
fontWeight |
string |
'normal' |
'normal'、'bold'、'bolder'、'lighter'、'100'~'900' |
fontVariant |
string |
'normal' |
'normal'、'small-caps' |
lineHeight |
number |
- | 行高(像素) |
需要说明的是,文本的 x/y 是锚点坐标而非包围盒左上角,这与 rect、image(左上角顶点)的位置语义不同。配合 textAlign: 'center'、textBaseline: 'middle' 即可让文字精确居中于指定点——这是给柱子、圆形等图形标注数值时最常用的组合。
使用场景:何时需要 Shape
示例文档明确了 Shape 的三个典型使用场景:
- 现有组件不满足,需要组合多种图形与文本:构建自定义图例、注释、装饰等;
withGuide、withLegend等自定义场景:Guide(辅助标记)与 Legend(图例)的内部渲染均基于图形标签,自定义其外观时可以直接操作底层 Shape;- 需要完全自定义标签内容和样式的场景:比如需要控制每个标注的颜色、字体、位置细节。
仓库中 Guide 与 Legend 组件的实现印证了这一点:withGuide.tsx 与 withLegend.tsx 都是通过组合多个 Shape 元素(rect、text、line 等)完成组件渲染的。
实战案例:TagGuide 数据标注
如果只是想在图表上添加"最高销量""最大值"这类带箭头的标签,F2 内置了基于 Shape 的 TagGuide 标签标注组件,可直接在 Chart 内使用:
import { Canvas, Chart, Interval, TagGuide } from '@antv/f2';
const data = [
{ genre: 'Sports', sold: 275 },
{ genre: 'Strategy', sold: 115 },
{ genre: 'Action', sold: 120 },
{ genre: 'Shooter', sold: 350 },
{ genre: 'Other', sold: 150 },
];
<Canvas context={context}>
<Chart data={data}>
<Interval x="genre" y="sold" />
<TagGuide
records={[{ genre: 'Sports', sold: 350 }]}
content="最高销量"
direct="tr"
background={{ fill: '#fff' }}
textStyle={{ fill: '#000' }}
/>
</Chart>
</Canvas>
TagGuide 的 background 支持 rect 图形属性(fill、stroke、radius、padding 等),textStyle 支持 text 图形属性,direct 可控制标签在标注点的八个方向(tl/tc/tr/cl/cr/bl/bc/br),records 甚至支持 'min'、'max'、'median'、'50%' 等特殊值直接定位。理解 Shape 的标签与样式体系,是灵活使用这类标注组件的前提。
让图形可交互:移动端事件属性
Shape 图形不仅负责"画",还可以直接绑定移动端事件。示例 event.jsx 展示了在 rect、circle、text 上分别绑定 onClick、onPan、onPress:
const Shape = () => {
return (
<group>
<rect
style={{ x: 10, y: 10, width: 40, height: 40, lineWidth: '2px', stroke: '#000', fill: 'red' }}
onClick={(e) => { console.log('click', e); }}
/>
<circle
style={{ cx: 80, cy: 30, r: 20, lineWidth: '2px', stroke: '#000', fill: 'red' }}
onPan={(e) => { console.log('pan', e); }}
/>
<text
style={{ x: 120, y: 30, text: '文本', fontSize: 20, fill: '#000' }}
onPress={(e) => { console.log('press', e); }}
/>
</group>
);
};
根据 事件属性教程 event.zh.md,F2 5.x 的事件系统基于 PointerEvent 标准封装了移动端事件,支持在图形标签上直接监听:
| 事件名 | 描述 |
|---|---|
onClick |
点击事件 |
onPanStart / onPan / onPanEnd |
手指在图形上触摸、移动、离开时触发 |
onTouchStart / onTouchMove / onTouchEnd / onTouchEndOutside |
原生触摸事件系列 |
onPressStart / onPress / onPressEnd |
手指按压系列 |
onSwipe |
手指快扫时触发 |
onPinchStart / onPinch / onPinchEnd |
手指缩放系列 |
事件可绑定在单个图形上,也可绑定在 <group> 容器上实现整组交互;结合底层引擎的拾取系统,只有命中图形区域的触摸才会触发回调。
样式体系:Shape 的 Style 属性详解
F2 中所有组件样式统一使用 Style 结构(绘图属性教程 中明确说明 axis 的 label 样式、legend marker 样式、自定义 shape 样式均如此),因此掌握 Shape 样式即可举一反三。
位置属性
不同图形 x/y 的几何意义不同:
| 图形 | 位置说明 | 使用的属性 |
|---|---|---|
| Circle / Arc / Sector | 圆心位置 | cx/cy |
| Group / Rect / Image | 左上角顶点位置 | x/y |
| Text | 文本锚点位置 | x/y |
| Line / Polyline / Polygon | 包围盒左上角顶点位置 | x/y |
另有一个 anchor: [number, number] 属性(默认 [0, 0])用于设置锚点位置。
通用属性
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
zIndex |
number |
0 |
控制图形显示层级,值越大越靠前 |
clip |
Clip |
- | 创建可显示区域,区域外隐藏,见下文"裁剪" |
visibility |
string |
- | 图形可见性 |
opacity |
number |
1 |
整体透明度,0.0(全透明)~1.0(不透明) |
fill |
string | Gradient | Pattern |
- | 填充色、渐变或纹理 |
fillOpacity |
number |
1 |
填充色透明度 |
stroke |
string | Gradient | Pattern |
- | 描边色、渐变或纹理 |
strokeOpacity |
number |
1 |
描边透明度 |
shadowType |
string |
- | 阴影类型:'outer' 外阴影、'inner' 内阴影 |
shadowColor / shadowBlur / shadowOffsetX / shadowOffsetY |
- | - | 阴影颜色、模糊程度、水平/垂直偏移 |
filter |
string |
- | 滤镜:blur、brightness、drop-shadow、contrast、grayscale、saturate、sepia、hue-rotate、invert 等 |
cursor |
string |
- | 鼠标样式 |
线条属性
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
lineCap |
string |
'butt' |
线段末端样式:'butt'、'round'、'square' |
lineJoin |
string |
'miter' |
线段连接样式:'bevel'、'round'、'miter' |
lineWidth |
number |
1 |
线段宽度 |
miterLimit |
number |
10 |
斜接面限制比例 |
lineDash |
number[] |
[] |
虚线样式,如 [5, 5] 表示 5px 实线 + 5px 空白 |
示例代码中的 lineWidth: '2px' 说明 lineWidth 支持带单位字符串。
渐变色与纹理
渐变可以直接作为 fill 或 stroke 的值,用法与 CSS 一致:
// 线性渐变(默认从左到右,可多段叠加)
<rect
style={{
x: 10, y: 10, width: 200, height: 100,
fill: 'linear-gradient(90deg, blue, green 40%, red)',
}}
/>
// 径向渐变(从中心向外)
<circle
style={{
cx: 100, cy: 100, r: 80,
fill: 'radial-gradient(circle at center, red, blue, green 100%)',
}}
/>
纹理 Pattern 支持图片 URL、HTMLImageElement、HTMLCanvasElement、HTMLVideoElement 和 Rect 等作为填充源,并可指定重复方向:
interface Pattern {
image: string | CanvasImageSource | Rect
repetition?: 'repeat' | 'repeat-x' | 'repeat-y' | 'no-repeat'
transform?: string
}
裁剪
clip 属性可定义任意图形的可视区域(Circle、Rect、Polygon 等),同一裁剪区域可被多个图形共享,且裁剪区域会影响图形的拾取区域:
<rect
style={{
x: 100, y: 100, width: 100, height: 100,
fill: 'blue',
clip: { type: 'circle', style: { cx: 150, cy: 150, r: 50 } },
}}
/>
TypeScript 类型定义
绘图属性教程 给出了完整的 ShapeStyle 类型定义,覆盖位置、通用、线条、文本四组属性,可供类型安全的自定义图形开发参考;其中 fill/stroke 的类型为 string | Gradient | Pattern,Gradient 即 'linear-gradient(...)' | 'radial-gradient(...)' 字符串。
进阶:把自定义图形封装为组件
当自定义图形需要在多处复用、拥有生命周期或监测数据变化时,可以将其封装为 F2 组件(组件介绍 component.zh.md 与 图形使用教程 graphic.zh.md)。
函数组件
接收 props 返回 JSX 即可:
const CustomRect = ({ x, y, width, height, color, text }) => {
return (
<group>
<rect style={{ x, y, width, height, fill: color, stroke: '#000', lineWidth: 2 }} />
<text style={{ x: x + width / 2 - 20, y: y + height / 2, text, fontSize: 16, fill: '#fff' }} />
</group>
);
};
Class 组件与 state 管理
继承 Component 可获得生命周期与 setState 能力,实现点击切换颜色的交互图形:
import { jsx, Canvas, Component } from '@antv/f2';
class InteractiveShape extends Component {
state = { color: '#1890ff', scale: 1 };
handleClick = () => {
this.setState({ color: this.state.color === '#1890ff' ? '#f5222d' : '#1890ff' });
};
render() {
const { x, y } = this.props;
const { color, scale } = this.state;
const size = 50 * scale;
return (
<group style={{ x, y, cursor: 'pointer' }} onTap={this.handleClick}>
<rect style={{ x: -size / 2, y: -size / 2, width: size, height: size, fill: color }} />
<text style={{ x: -15, y: 5, text: '点击', fontSize: 14, fill: '#fff' }} />
</group>
);
}
}
坐标变换与动画
- 旋转/缩放:在
<group>的 style 中使用transform: 'rotate(45deg)'即可围绕组坐标原点旋转整组图形; - 动画:在 style 中配置
animation属性,例如appear阶段以linear缓动、1000ms 内从{y: 200, height: 0}变化到{y: 10, height: 50}; - 与图表结合:Shape 可以直接书写在
<Chart>内部(如用<text>给图表加自定义标题),也可参考 自定义 View 教程 使用图表的计算逻辑。
常见问题
如何设置透明度?
用 opacity 设置整体透明度,或用 fillOpacity / strokeOpacity 分别控制填充与描边:
<circle style={{ cx: 100, cy: 100, r: 50, fill: 'red', fillOpacity: 0.5, stroke: 'blue', strokeOpacity: 0.8, lineWidth: 2 }} />
如何添加阴影?
组合 shadowType: 'outer'、shadowColor、shadowBlur、shadowOffsetX/Y 即可,仓库测试 graphic.test.tsx 中也有图形绘制相关的覆盖用例。
如何设置虚线?
使用 lineDash: [10, 5](10px 实线、5px 空白)。
如何控制图形层级?
使用 zIndex,值越大越靠前(参考 shape-attrs.zh.md 中的矩形叠加示例)。
如何让图形可点击?
在图形或 group 上绑定 onClick/onTap 等事件属性,事件对象 e 中包含命中信息。
小结
Shape 图形标签是 F2 自定义能力的基石:用 JSX 组合 rect/circle/line/text 等基础图形可以完成任意视觉元素的绘制;用 <text /> 及其文本属性可以实现数据标签;用统一的 Style 结构可以精细控制填充、描边、阴影、渐变与层级;用事件属性可以接入移动端手势;再进一步封装为函数组件或 Class 组件即可获得复用性与状态管理能力。理解这一体系后,自定义图例、标注、装饰乃至全新的图表形态都将变得简单直接。