Echarts 绘制自定义图案(graphic)

4,064 阅读10分钟

注意,这里的 graphic 自定义图像和上一篇中的 series-custom 不一样,series-custom是根据数据项来绘制图像的,而这里要介绍的 graphic 则是原生图形元素组件,可以绘制水印、X、Y 轴坐标、饼图中在圆心位置绘制特别的图像等等这些不依赖 data 绘制的图像。如下图:

添加水印;

X 轴线被替换成了一个面;

如果你想了解如何将柱状图替换成 3D 类型的图案,可以点击这里

graphic

只配一个图形元素时的简写方法:

myChart.setOption({
    ...,
    graphic: {
        type: 'image',
        ...
    }
});

配多个图形元素:

myChart.setOption({
    ...,
    graphic: [
        { // 一个图形元素,类型是 image。
            type: 'image',
            ...
        },
        { // 一个图形元素,类型是 text,指定了 id。
            type: 'text',
            id: 'text1',
            ...
        },
        { // 一个图形元素,类型是 group,可以嵌套子节点。
            type: 'group',
            children: [
                {
                    type: 'rect',
                    id: 'rect1',
                    ...
                },
                {
                    type: 'image',
                    ...
                },
                ...
            ]
        }
        ...
    ]
});

组合类型

组合类型可以理解为有多个图形组合而成的,例如图一的水印,就是通过多个图形组合而成的,针对这种我们将其称之为组合类型。组合类型的 type 必须为 group,并通过 children 属性将多个图形组合成一个图形。

type

图形的类型,组合类型的值为 group

x、y

图像的 x、y 坐标(像素)位置

rotation

图像旋转。Math.PI / 4 就是 45 度角。

scaleX、scaleY

图像的缩放

originX、originY

图像旋转、缩放的原点

transition

可以通过'all'指定所有属性都开启过渡动画,也可以指定单个或一组属性。

Transform 相关的属性:'x''y''scaleX''scaleY''rotation''originX''originY'。例如:

{
  type: 'rect',
    x: 100,
    y: 200,
    transition: ['x', 'y']
}

还可以是这三个属性 'shape''style''extra'。表示这三个属性中所有的子属性都开启过渡动画。例如:

{
  type: 'rect',
  shape: { // ... },
  // 表示 shape 中所有属性都开启过渡动画。
  transition: 'shape',
}

在自定义系列中,当 transition 没有指定时,'x''y' 会默认开启过渡动画。如果想禁用这种默认,可设定为空数组:transition: []

enterFrom

配置图形的入场属性用于入场动画。例如:

{
  type: 'circle',
    x: 100,
    enterFrom: {
    // 淡入
    style: { opacity: 0 },
    // 从左飞入
    x: 0
  }
}

leaveTo

配置图形的退场属性用于退场动画。例如:

{
  type: 'circle',
    x: 100,
    leaveTo: {
    // 淡出
    style: { opacity: 0 },
    // 向右飞出
    x: 200
  }
}

enterAnimation

入场动画配置

{
  // 动画时长,单位 ms
  duration: 300,
  // 动画缓动。不同的缓动效果可以参考 https://echarts.apache.org/examples/zh/editor.html?c=line-easing
  delay:300,
  // 动画延迟时长,单位 ms
  easing: 'linear'
}

updateAnimation、leaveAnimation

更新动画配置、离场动画配置

width

用于描述此 group 的宽。

这个宽只用于给子节点定位。

即便当宽度为零的时候,子节点也可以使用 left: 'center' 相对于父节点水平居中。

height

用于描述此 group 的高。

这个高只用于给子节点定位。

即便当高度为零的时候,子节点也可以使用 top: 'middle' 相对于父节点垂直居中。

注意,设置了 width & height 属性和没有设置 width & height 的定位是不一样的。

  1. 如果设置了 width & height,并使用 x、y(或者 left、top)进行定位。也就是说矩形的左上角就是 x、y (或 left、top)指定的位置;
  2. 如果设置了 width & height,并使用 right、bottom 进行定位。那么矩形的右下角就是 right、bottom 指定的位置。
  3. 如果没有设置 width & height,此时 group 下的所有子节点都将相对于 group 进行定位,换句话说,如果 group 指定了 right & bottom,那么所有的孩子节点都将相对于该位置进行定位。

left、right

描述怎么根据父元素进行定位。

『父元素』是指:如果是顶层元素,父元素是 echarts 图表容器。如果是 group 的子元素,父元素就是 group 元素。

值的类型可以是:

  • number:表示像素值。
  • 百分比值:如 '33%',用父元素的高和此百分比计算出最终值。
  • 'center':表示自动居中。

leftright 只有一个可以生效。

如果指定 leftright,则 shape 里的 xcx 等定位属性不再生效。

top、bottom

描述怎么根据父元素进行定位。

『父元素』是指:如果是顶层元素,父元素是 echarts 图表容器。如果是 group 的子元素,父元素就是 group 元素。

值的类型可以是:

  • number:表示像素值。
  • 百分比值:如 '33%',用父元素的宽和此百分比计算出最终值。
  • 'middle':表示自动居中。

topbottom 只有一个可以生效。

如果指定 topbottom,则 shape 里的 ycy 等定位属性不再生效。

注意:当 leftrighttopbottom的值为百分比或 centermiddle时,是这样计算的:

/**
 * @param container_width 表示父容器的宽度
 * @param container_height 表示父容器的高度
 * @param content_width 表示子节点的宽度
 * @param content_height 表示子节点的高度
 */

// left: '50%' | 'center' 的实际偏移量计算
const distX = (container_width - content_width) * 0.5;
// top: '50%' | 'middle' 的实际偏移量计算
const distY = (container_height - content_height) * 0.5

// right: '30%' 的实际偏移量计算
const distX = (container_width - content_width) * (1 - 0.3);
// bottom: '30%' 的实际偏移量计算
const distY = (container_height - content_height) * (1 - 0.3);

当组合类型没有设置 width、height 时,我们可以理解为 container_widthcontainer_height 都是 0。

这也就解释了为什么当 group 没有设置 width时,子节点也可以使用 <font style="background-color:rgb(244, 244, 244);">left: 'center'</font> 相对于父节点水平居中。

z

决定图像的层级关系

children

子节点列表,其中项都是一个图形元素定义。

单类型-text

我将其称之为文本类型,就是可以在图表上绘制文本内容,包括自定义文本样式等。

通用数据这里就不再介绍了,和组合类型是一样的。下面介绍文本类型独有的属性:

style

{
  // 文本块文字。可以使用 \n 来换行。
  text: 'hello world',
  // 图形元素的左上角在父节点坐标系(以父节点左上角为原点)中的横坐标值。
  x: 0,
  // 图形元素的左上角在父节点坐标系(以父节点左上角为原点)中的纵坐标值。
  y: 0,
  // 字体大小、字体类型、粗细、字体样式。
  font: '2em "STHeiti", sans-serif',
  // 水平对齐方式,取值:'left', 'center', 'right'。
  // 如果为 'left',表示文本最左端在 x 值上。如果为 'right',表示文本最右端在 x 值上。
  textAlign: 'left',
  // 垂直对齐方式,取值:'top', 'middle', 'bottom'。
  // 如果为 top,表示文本的顶部在 y 上,如果为 bottom,表示文本的底部在 y 上
  verticalTextAlign: 'top',
  // 限制文本的宽度
  width: 100,
  // 当文本内容超出 width 时的文本显示策略,取值:'break', 'breakAll', 'truncate', 'none'。
  // 'break': 尽可能保证完整的单词不被截断(类似 CSS 中的 word-break: break-word;)
  // 'breakAll': 可在任意字符间断行
  // 'truncate': 截断文本屏显示 '...',可以使用 ellipsis 来自定义省略号的显示
  // 'none': 不换行
  overflow:'none',
  // 当 overflow 设置为 'truncate' 时生效,默认为 ...。
  ellipse: '...',
  // 填充色。
  fill: '#fff',
  // 描边色
  stroke: '#ff4949',
  // 线条宽度。 
  lineWidth:3,
  // 线条样式。可选:'solid'、'dashed'、'dotted'
  lineDash: 'solid',
  // 阴影模糊半径
  shadowBlur: 10,
  // 阴影 X,Y 方向偏移。
  shadowOffsetX: 10,
  shadowOffsetY: 10,
  // 阴影的颜色
  shadowColor: 'rgba(0, 0, 0, 0.5)',
  // 透明度
  opacity: 1,
}

单类型-rect

绘制单个矩形,type 为 rect。特有的属性有 shape、style。

shape

{
  // 图形元素的左上角在父节点坐标系(以父节点左上角为原点)中的横坐标值。
  x: 100,
  // 图形元素的左上角在父节点坐标系(以父节点左上角为原点)中的纵坐标值。
  y: 100,
  // 图形元素的宽度。
  width: 100,
  // 图形元素的高度。
  height: 100,
  // 圆角
  r: [8],
  // 可以是一个属性名,或者一组属性名。 被指定的属性,在其指发生变化时,会开启过渡动画。 
  // 只可以指定本 shape 下的属性。
  transition: ['x', 'y'],
}

style

{
  // 填充色
  fill: '#fff',
  // 描边色
  stroke:'#ff4949',
  // 线的宽度
  lineWidth:3,
  // 笔触的类型,round、butt、square
  lineCap: 'round',
  // 设置线条转折点的样式。默认 miter
  lineJoin: 'miter',
  // 阴影模糊半径
  shadowBlur:5,
  // 阴影 X 方向偏移。
  shadowOffsetX: 5,
  // 阴影 Y 方向偏移。
  shadowOffsetY: 5,
  // 阴影颜色。
  shadowColor: 'rgba(0, 0, 0, 0.4)',
  //  透明度。
  opacity: 0.5,
}

单类型-image

绘图相片的,type 为 image

style

{ 
  // 图像 url
  image,
  // 图形元素的宽度。
  width: 100,
  // 图形元素的高度。
  height: 100,
  // 图形元素的左上角在父节点坐标系(以父节点左上角为原点)中的横坐标值。
  x: 0,
  // 图形元素的左上角在父节点坐标系(以父节点左上角为原点)中的纵坐标值。
  y: 0,
  // 填充色
  fill: '#fff',
  // 描边色
  stroke:'#ff4949',
  // 线的宽度
  lineWidth:3,
  // 笔触的类型,round、butt、square
  lineCap: 'round',
  // 设置线条转折点的样式。默认 miter
  lineJoin: 'miter',
  // 阴影模糊半径
  shadowBlur:5,
  // 阴影 X 方向偏移。
  shadowOffsetX: 5,
  // 阴影 Y 方向偏移。
  shadowOffsetY: 5,
  // 阴影颜色。
  shadowColor: 'rgba(0, 0, 0, 0.4)',
  //  透明度。
  opacity: 0.5,
}

案例一(图1)

graphic: {
  elements: [
    {
      type: 'group',
      right: 100,
      bottom: 100,
      bounding: 'raw',
      rotation: Math.PI / 4,
      children: [
        // group 没有设置 width、height、
        // 所以子节点是相对于 right: 100 & bottom: 100 进行定位
        {
          type: 'rect',
          // 图形相对于 group 居中
          left: 'center',
          top: 'middle',
          shape: {
            width: 400,
            height: 80,
          },
          style: {
            fill: '#fff',
            opacity: 0.4,
          },
          z: 100,
        },
        {
          type: 'text',
          // 文本相对于 group 居中
          top: 'middle',
          left: 'center',
          style: {
            // @ts-ignore
            textAlign: 'left',
            verticalAlign: 'top',
            fill: '#fff',
            text: 'ECHARTS LINE CHART',
            font: 'bold 24px sans-serif',
          },
          z: 100,
        }
      ],
    }
  ]
}

案例二(图2)

graphic: [
  {
    type: 'image',
    left: 55,
    bottom: 13,
    style: {
      image: bgIMG,
      width: 410,
      height: 39,
    },
  },
],

案例三

const options: EChartsOption = {
  legend: {
    show: true,
    right: 50,
    orient: 'vertical',
    icon: 'circle',
    textStyle: {
      color: '#fff',
    },
  },
  tooltip: {
    show: true,
    trigger: 'item',
  },
  grid: {
    left: 20,
    right: 20,
    bottom: 0,
    top: 50,
    containLabel: true,
  },
  series: {
    type: 'pie',
    name: '年产值',
    radius: ['40%', '70%'],
    itemStyle: {
      borderWidth: 2,
      borderColor: 'rgba(0, 0, 0, 0.5)',
      borderRadius: 10,
    },
    label: {
      show: false,
      // 设置 label 的宽高、以及背景
      width: 100,
      height: 100,
      fontSize: 24,
      formatter: '{title|{b}年产值}\n{value|{c}}',
      // 富文本样式
      rich: {
        title: {
          fontSize: 14,
          padding: [35, 0, 10, 0],
        },
        value: {
          fontSize: 20,
          fontWeight: 'bold',
        }
      },
      fontWeight: 'bold',
      color: '#fff',
      borderRadius: 50,
      position: 'center',
      backgroundColor: '#ff4949',
    },
    emphasis: {
      label: {
        show: true,
      }
    },
    data: [
      { value: 19992, name: '2019' },
      { value: 25908, name: '2020' },
      { value: 28009, name: '2021' },
      { value: 30790, name: '2022' },
      { value: 25499, name: '2023' },
      { value: 18994, name: '2024' },
    ]
  },
  graphic: {
    elements: [
      {
        type: 'group',
        right: 'center',
        bottom: 'center',
        width: 100,
        height: 100,
        bounding: 'raw',
        // 不要设置 z 属性,否则,series.emphasis.label 无法覆盖掉 graphic 绘制的图像。
        children: [
          {
           // 在圆心位置绘制一个大小为 100 的圆
            type: 'circle',
            shape: {
              cx: 50,
              cy: 50,
              r: 50,
            },
            style: {
              fill: '#f80',
            }
          },
          {
            // 绘制文本标题
            type: 'text',
            top: 26,
            left: 'left',
            style: {
              // @ts-ignore
              textAlign: 'center',
              verticalAlign: 'top',
              width: 100,
              overflow: 'truncate',
              ellipsis: '...',
              fill: '#fff',
              text: '总产值',
              font: 'bold 24px sans-serif',
            },
          },
          {
            // 绘制文本
            type: 'text',
            top: 60,
            left: 'left',
            style: {
              // @ts-ignore
              textAlign: 'center',
              verticalAlign: 'top',
              width: 100,
              overflow: 'truncate',
              ellipsis: '...',
              fill: '#fff',
              text: '149875',
              font: 'bold 16px sans-serif',
            },
          },
        ],
      }
    ]
  }
};