110

软软的 Echarts - 知乎专栏

 6 years ago
source link: https://zhuanlan.zhihu.com/p/29951594
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

软软的 Echarts

一种 Echarts 的屏幕适配的方案。

欢迎关注专栏熵与单子的代码本

在 web 开发中,特别是 dashboard 类型的SPA中,常常要求各组件都能对不同的屏幕分辨率进行缩放适配,常见的解决方案之一是在CSS中使用 rem 和 em 的长度单位结合媒体查询。

最近做到的一些数据可视化需求中,图表组件用到 Echarts,发现由于 Echarts 是基于绘制 Canvas 的方式生成图表,在配置项中,所有的距离和大小只能设置单位为 px,这就使得做屏幕适配十分不便,分辨率一变,不仅图表里的字体大小、距离与整体界面不协调,而且经常会发生字体重叠到一起。

后来想了一想, Echarts 每次重绘是在 JavaScript 中通过 setOption 方法传入配置的,可以利用 window 对象获取到窗口的尺寸,动态设置一个变量起到根元素 font-size 的作用,模拟出 rem 与媒体查询的效果。

首先为组件搞一个样式配置文件,或添加在已有的配置文件中:

// styleProps.js

const viewPort = window.screen.width;
let rootFontSize = 100;
if (viewPort <= 1480) rootFontSize = 71;
if (viewPort >= 1481 && viewPort <= 1760) rootFontSize = 83;
if (viewPort >= 2241 && viewPort <= 3200) rootFontSize = 130;
if (viewPort >= 3201) rootFontSize = 200;

export default {
  titleFontColor: 'rgba(255,255,255,0.97)',
  bodyFontColor: 'rgba(255,255,255,0.91)',
  titleFontSize: rootFontSize * 0.18,
  bodyFontSize: rootFontSize * 0.16,

  titleFontFamily: 'FangZhengZhengZhongHeiJT',
  bodyFontFamily: 'FangZhengZhengXianHeiJT',
};

为了解决 Chrome 浏览器下字体最小为 12px 的问题,习惯上会把根元素的 font-size 设为 100px ,这里也将 rootFontSize 设为 100 。

通过变量 viewPort 获取屏幕宽度,针对以下常见的屏幕分辨率,按区间进行适配:

1366*768
1600*900
1920*1080
2560*1400
3840*2160

取宽度的话,基本上全屏、投影拼接屏等情况也不会影响,当然了,因为是 dashboard 类型的的应用,就不考虑用户拉小窗体之类奇奇怪怪的情况了。

这个 styleProps.js 配置,与我们在整个应用的 CSS 中的设置是“同构”的:

html{
  font-size: 625%;
}
@media (max-width: 1480px) {
  html{
    font-size: 444%;
  }
}
@media (min-width: 1481px) and (max-width: 1760px) {
  html{
    font-size: 521%;
  }
}
@media (min-width: 2241px) and (max-width: 3200px) {
  html{
    font-size: 810%;
  }
}
@media (min-width: 3201px) {
  html{
    font-size: 1250%;
  }
}

body{
  font-size: 0.2rem;
  font-family: FangZhengZhengXianHeiJT;
  color: rgba(255,255,255,0.91);
}

h1 {
  font-size: 0.44rem;
  font-family: FangZhengZhengZhongHeiJT;
  color: rgba(255,255,255,0.97);
}
h2 {
  font-size: 0.32rem;
  font-family: FangZhengZhengZhongHeiJT;
  color: rgba(255,255,255,0.97);
}
h3 {
  font-size: 0.24rem;
  font-family: FangZhengZhengZhongHeiJT;
  color: rgba(255,255,255,0.97);
}

然后为曲线和柱状图表简单的封装了一个组件:

import React from 'react';
import echarts from 'echarts';
import styleProps from '../styleProps';

export default class HArrayChart extends React.Component {
  static defaultProps = {
    title: '',
    /**
     * dataItem {
     *  data: [],
     *  type: '',
     *  name: '',
     *  axis: 'left' | 'right',
     * }
     */
    dataList: [],
    /**
     * {
     *  name: '',
     *  data: []
     * }
     */
    index: {},
    /**
     * ['发电量(kWh)','功率(kW)']
     */
    axes: []
  }
  componentDidMount() {
    this.chart = echarts.init(this.chart);
    this.updateChart();
  }
  componentDidUpdate() {
    this.updateChart();
  }
  updateChart = () => {
    const getSerisOption = (type) => {
      switch (type) {
        case 'line':
          return {
            symbol: 'none'
          };
        default:
          break;
      }
      return null;
    };
    this.chart.setOption({
      title: {
        text: this.props.title,
        x: 'center',
        textStyle: {
          color: styleProps.titleFontColor,
          fontFamily: styleProps.titleFontFamily,
          fontSize: styleProps.titleFontSize
        }
      },
      grid: {
        top: (styleProps.bodyFontSize * 2.4) + styleProps.titleFontSize,
        right: (styleProps.bodyFontSize * 3.8) + 8,
        bottom: (styleProps.bodyFontSize * 4.2) + 8,
        left: (styleProps.bodyFontSize * 3.8) + 8
      },
      yAxis: this.props.axes.map(name => ({
        name,
        nameGap: styleProps.bodyFontSize * 0.8,
        type: 'value',
        nameTextStyle: {
          color: styleProps.bodyFontColor,
          fontFamily: styleProps.bodyFontFamily,
          fontSize: styleProps.bodyFontSize
        },
        axisLine: {
          lineStyle: {
            color: styleProps.bodyFontColor
          }
        },
        axisLabel: {
          color: styleProps.bodyFontColor,
          fontFamily: styleProps.bodyFontFamily,
          fontSize: styleProps.bodyFontSize
        }
      })),
      legend: {
        data: this.props.dataList.map(dataItem => dataItem.name),
        icon: 'bar',
        bottom: 0,
        textStyle: {
          color: styleProps.bodyFontColor,
          fontFamily: styleProps.bodyFontFamily,
          fontSize: styleProps.bodyFontSize
        }
      },
      xAxis: {
        data: this.props.index.data,
        type: 'category',
        name: this.props.index.name,
        nameLocation: 'center',
        nameGap: (styleProps.bodyFontSize * 1.2) + 8,
        nameTextStyle: {
          color: styleProps.bodyFontColor,
          fontFamily: styleProps.bodyFontFamily,
          fontSize: styleProps.bodyFontSize
        },
        axisLine: {
          lineStyle: {
            color: styleProps.bodyFontColor
          }
        },
        axisLabel: {
          color: styleProps.bodyFontColor,
          fontFamily: styleProps.bodyFontFamily,
          fontSize: styleProps.bodyFontSize
        }
      },
      series: this.props.dataList.map(dataItem => ({
        data: dataItem.data,
        type: dataItem.type || 'line',
        name: dataItem.name,
        animation: false,
        yAxisIndex: dataItem.axis === 'right' ? 1 : 0,
        ...getSerisOption(dataItem.type || 'line')
      }))
    });
  }
  render() {
    return <div className={this.props.className} ref={(div) => { this.chart = div; }} />;
  }
}

之前的 styleProps.js 配置文件通过 import 引入,相关的参数会在组件生成之前就计算好,这样足以应对屏幕分辨率的适配了,当然如果希望能够更动态的调整,也可以设置一个 state 监视视口宽度,在每次 componentWillUpdate 里重算参数。

为了方便不熟悉 Echarts 配置的使用者,这个组件只暴露了4个参数:

title: '',
dataList: [],
index: {},
axes: []

dataList 通过数组可传入多条数据 series ,每条可独立配置曲线或柱状类型,以及关联到左还是右坐标轴,每条数据 series 的长度需与横坐标的 index 长度对应。当然这样的配置使得自由度很小,可以给使用者也暴露个 option 配置项,覆盖默认的配置。

由于在styleProps.js 配置中对字体大小的设置,与我们在整个应用的 CSS 中的设置是“同构”的,所以通过 titleFontSize 和 bodyFontSize 就使得当自适应匹配屏幕分辨率变化时, Echarts 与整个界面的行为保持一致,字体大小、距离等变化协调。

图表中各个元素之间的距离,比如 Legend 和 xAxis 的距离、 grid 与四边的距离等,也用bodyFontSize的比例进行设置,这样当图表中字体变化后也不会发生相互覆盖的情况了:

yAxis: this.props.axes.map(name => ({
  nameGap: styleProps.bodyFontSize * 0.8,        
})),

由于项目中使用了 styled-components ,所以组件根元素要传入 className={this.props.className} 。

最后组件使用的时候大致是这个样子:

import styled from 'styled-components';
import { HArrayChart } from './hyphix';

const Chart = styled(HArrayChart)`
  width: 5rem;
  height: 3rem;
`;
const dataList = [
  {
    data: [2, 4, 5, 80, 22, 27, 39, 0, 12],
    name: '今日功率',
  },
  {
    data: [32, 33, 3, 8, 2, 27, 3, 3, 15],
    name: '昨日功率',
    type: 'bar'
  },
  {
    data: [837, 3099, 122, 3999, 100, 299, 3987, 3298, 1500],
    name: '昨日效率',
    axis: 'right'
  },
];
const index = { data: [1, 2, 3, 4, 5, 6, 7, 8, 9], name: '时间' };const Chart = styled(HArrayChart)`
  width: 500px;
  height: 300px;
`;
const dataList = [
  {
    data: [2, 4, 5, 80, 22, 27, 39, 0, 12],
    name: '今日功率',
  },
  {
    data: [32, 33, 3, 8, 2, 27, 3, 3, 15],
    name: '昨日功率',
    type: 'bar'
  },
  {
    data: [837, 3099, 122, 3999, 100, 299, 3987, 3298, 1500],
    name: '昨日效率',
    axis: 'right'
  },
];
const index = { data: [1, 2, 3, 4, 5, 6, 7, 8, 9], name: '时间' };

<Route path="/test" exact component={() => <Chart title={'今日发电功率曲线'} dataList={dataList} index={index} axes={['发电量(kWh)', '功率(kW)']} />} />

由于没有好看的数据,图就不上了,组件只是个大致的思路,要做的好看细节上还需要再完善。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK