ECharts实现移动端自适应

用户使用 Echarts 工作的时候所需要用到的 组件系列 都在指定高宽的 DOM 节点(容器)中,其中每个节点都可以由用户指定位置。

Echarts 图表库内部采用的是类似于绝对布局的易于理解的布局方式,因为实现 DOM 文档流布局是不合适的。但是图表库所采用的布局方式会受到容器尺寸的影响,出现组件重叠的情况。这就带来了一个问题:如果图表需要同时在 PC端和移动端展示,该怎么解决内部组件的布局?

上述问题需要 Echarts 内部组件能够自动地随着容器尺寸的改变而进行调整的能力。为了解决这个问题,ECharts 完善了组件的定位设置,并且实现了类似 CSS Media Query 的自适应能力。

ECharts 组件的定位和布局

Echarts 中大部分组件系列会遵循两种定位方式:

  • left/right/top/bottom/width/height 定位方式
  • center / radius 定位方式

left/right/top/bottom/width/height 定位方式

该定位方式中的六个量,每个量都可以当做 绝对值百分比 或者 位置描述

  • 绝对值的单位是浏览器像素(px),用 number 形式书写(不写单位)。例如 {left: 23, height: 400}。
  • 百分比表示占 DOM 容器高宽的百分之多少,用 string 形式书写。例如 {right: '30%', bottom: '40%'}。
  • 位置描述可以设置 left: 'center',表示水平居中。可以设置 top: 'middle',表示垂直居中。

这六个量的概念,和 CSS 中六个量的概念类似:

  • left:表示距离 DOM 容器左边界的距离。
  • right:表示距离 DOM 容器右边界的距离。
  • top:表示距离 DOM 容器上边界的距离。
  • bottom:表示距离 DOM 容器下边界的距离。
  • width:表示宽度。
  • height:表示高度。

小提示:

在表示横向的量的时候,由于组件的位置和大小可以由任意的两个量决定,所以在 left、right、width 三个量中,可以只提供两个量的值。至于取哪两个量的值就取决于用户,例如 left 和 right 或者 right 和 width 都可以决定组件的位置和大小。 

同理,在表示纵向的量 top、bottom、height 时,取值与横向量相同。

center / radius 定位方式

  • center 是一个数组,表示 [x, y],其中,x、y 可以是绝对值或者百分比,含义与前面描述的相同。
  • radius 是一个数组,表示 [内半径, 外半径],其中,内外半径可以是绝对值或者百分比,含义与前面描述的相同。

注意:在自适应容器大小时,百分比设置是很有用的。

横向(horizontal)和纵向(vertical)

ECharts 中像 legend、visualMap、dataZoom、timeline等狭长型的组件,大部分都有横向布局纵向布局这两种选择。例如,在宽少长多的移动端屏幕上,使用纵向布局更为合适;而在屏幕较宽的 PC 端上,则要选择使用横向布局

横纵向布局的设置:

  • 一般在组件或者系列的 orient 或者 layout 配置项上,设置为 'horizontal' 或者 'vertical'。

与 ECharts2 的兼容性:

ECharts2 中可以使用如 x/x2/y/y2 的命名方式,分别对应 left/right/top/bottom。但是写成 left/right/top/bottom 会更为规范。

为了兼容 ECharts2,在描述位置的时候可以支持一些看起来略奇怪的设置,例如:left: 'right'、left: 'left'、top: 'bottom'、top: 'top'。这些语句分别等效于:right: 0、left: 0、bottom: 0、top: 0,写成后者就不奇怪了。

Media Query

Media Query 提供了随着容器尺寸改变而改变的能力。

下面的例子,可以拖动右下角的圆点改变尺寸,随着尺寸变化,legend 和 系列会自动改变布局位置和方式:

在 option 中设置 Media Query 需要遵循下面的格式:

option = {
    baseOption: { // 这里是基本的『原子option』。
        title: {...},
        legend: {...},
        series: [{...}, {...}, ...],
        ...
    },
    media: [ // 这里定义了 media query 的逐条规则。
        {
            query: {...},   // 这里写规则。
            option: {       // 这里写此规则满足下的option。
                legend: {...},
                ...
            }
        },
        {
            query: {...},   // 第二个规则。
            option: {       // 第二个规则对应的option。
                legend: {...},
                ...
            }
        },
        {                   // 这条里没有写规则,表示『默认』,
            option: {       // 即所有规则都不满足时,采纳这个option。
                legend: {...},
                ...
            }
        }
    ]
};

上述例子中的 baseOption、以及 media 每个 option 都是原子 option,即普通的含有各组件、系列定义的 option。而由原子option组合成的整个 option,我们称为复合 option。baseOption 是必然被使用的,此外,满足了某个 query 条件时,对应的 option 会被使用 chart.mergeOption() 来 merge 进去。

query:

每个 query 可以写成下述形式:

{
    minWidth: 200,
    maxHeight: 300,
    minAspectRatio: 1.3
}

目前 query 支持三个属性:width、height、aspectRatio(长宽比)。每个属性都可以加上 min 或 max 前缀。比如,minWidth: 200 表示 大于等于200px宽度 。两个属性一起写表示   ,例如:{minWidth: 200, maxHeight: 300} 表示 大于等于200px宽度,且小于等于300px高度

option:

media 中的 option 既然是 原子 option ,理论上可以写任何 option 的配置项。但是一般我们只写跟布局定位相关的,例如截取上面例子中的一部分 query option:

media: [
    ...,
    {
        query: {
            maxAspectRatio: 1           // 当长宽比小于1时。
        },
        option: {
            legend: {                   // legend 放在底部中间。
                right: 'center',
                bottom: 0,
                orient: 'horizontal'    // legend 横向布局。
            },
            series: [                   // 两个饼图左右布局。
                {
                    radius: [20, '50%'],
                    center: ['50%', '30%']
                },
                {
                    radius: [30, '50%'],
                    center: ['50%', '70%']
                }
            ]
        }
    },
    {
        query: {
            maxWidth: 500               // 当容器宽度小于 500 时。
        },
        option: {
            legend: {
                right: 10,              // legend 放置在右侧中间。
                top: '15%',
                orient: 'vertical'      // 纵向布局。
            },
            series: [                   // 两个饼图上下布局。
                {
                    radius: [20, '50%'],
                    center: ['50%', '30%']
                },
                {
                    radius: [30, '50%'],
                    center: ['50%', '75%']
                }
            ]
        }
    },
    ...
]

具有多个 query 时的优先级:

注意,可以有多个 query 同时被满足,会都被 mergeOption,定义在后的后被 merge(即优先级更高)

默认 query:

如果 media 中有某项不写 query,则表示『默认值』,即所有规则都不满足时,采纳这个option。

容器大小实时变化时的注意事项:

如果容器DOM节点需要能任意随着拖拽变化大小,那么目前使用时需要注意这件事:某个配置项,如果在某一个 query option 中出现,那么在其他 query option 中也必须出现,这样才能够回归到原来的状态。(left/right/top/bottom/width/height 不受这个限制。)

复合 option 中的 media 不支持 merge

也就是说,当第二(或三、四、五 ...)次 chart.setOption(rawOption) 时,如果 rawOption 是 复合 option(即包含 media 列表),那么新的 rawOption.media 列表不会和老的 media 列表进行 merge,而是简单替代。当然,rawOption.baseOption 仍然会正常和老的 option 进行merge。

其实,很少有场景需要使用 复合 option 来多次 setOption,而我们推荐的做法是,使用 mediaQuery 时,第一次 setOption 使用 复合 option ,后面 setOption 时仅使用 原子 option,也就是仅仅用 setOption 来改变 baseOption。