F2 图形标签(Shape)实战指南:用 JSX 自定义图形、添加标签并接入交互

原创2026-09-26 10:33:01309 阅读
文章标签:数据可视化前端

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 的三个典型使用场景:

  1. 现有组件不满足,需要组合多种图形与文本:构建自定义图例、注释、装饰等;
  2. withGuide、withLegend 等自定义场景:Guide(辅助标记)与 Legend(图例)的内部渲染均基于图形标签,自定义其外观时可以直接操作底层 Shape;
  3. 需要完全自定义标签内容和样式的场景:比如需要控制每个标注的颜色、字体、位置细节。

仓库中 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 组件即可获得复用性与状态管理能力。理解这一体系后,自定义图例、标注、装饰乃至全新的图表形态都将变得简单直接。

相关文档

登录后查看全文
F2