Automatic scrolling ECharts Package Based on the Gantt chart and

Need to use the project component Gantt chart, the chart has been based EChart before development, but itself is not EChart Gantt assembly requires its own package

After a fierce battle, finally finished ...

I refer in the project v-chart  encapsulates a set of charting components, so here only to realize Gantt chart components, chart initialization, data updating, adaptation, etc. are not presented here?

 

First, schedule data format

Gantt EChart itself does not, but the "Custom" method can be provided by EChart type: 'Custom ' Development

const option = {
  series: [{
    type: 'custom',
    renderItem: (params, api) => {
      // do sth
    },
    data,
  }]
}

Here the data is the data set, it is a two-dimensional array, requires two main parameters:

name: the name, can be displayed in the legend and tooltip

value: parameter set, from the parameters required when the table definition could be placed in this array

If other configurations, other fields may be added in accordance with the series configuration ECharts

I custom data structure like this:

{
  name,
  itemStyle: {
    normal: {
      color: color || defaultColor,
    },
  },
  // value 为约定写法,依序为“类目对应的索引”、“状态类型”、“状态名称”、“开始时间”、“结束时间”
  value: [
    index,
    type,
    name,
    new Date(start).getTime(),
    new Date(end || Date.now()).getTime(),
  ],
}

注意:series.data 中的元素需要根据状态划分,不能根据类目(Y轴)划分,这样才能保证图例 legend 的正常显示

最终的 data 结构如图:

自定义的核心是 renderItem 函数,这个函数的本质就是:将 data 中的参数 value 处理之后,映射到对应的坐标轴上,具体处理参数的逻辑完全自定义

甘特图就需要计算出各个数据块的高度和宽度,然后映射到对应的类目轴(Y轴)和时间轴(X轴)上

由于甘特图会用到时间轴(X轴),所以定义的 value 中需要开始时间和结束时间的时间戳

为了区分该数据属于类目轴(Y轴)的哪一条类目,还需要对应类目的索引 index

如果还有其它的需要,比如自定义 tooltip,还可以在 value 中添加其它的参数

但一定要约定好参数的顺序,因为 renderItem 函数是根据 value 的索引去取对应的参数

 

二、处理数据 Series

// 处理数据
function getGantSeries(args) {
  const { innerRows, columns } = args
  const baseItem = {
    type: 'custom',
    renderItem: (params, api) => renderGanttItem(params, api),
    dimensions: columns,
  };
  return innerRows.map(row => {
    return {
      ...baseItem,
      name: row[0].name,
      data: row,
    };
  });
}

当 type 指定为 'custom' 的时候,series 的元素可以添加 dimensions 字段,用来定义每个维度的信息

处理数据的核心是 renderItem 方法,该方法提供了 paramsapi 两个参数,最后需要返回对应的图形元素信息

const DIM_CATEGORY_INDEX = 0; // value 中类目标识的索引
const DIM_CATEGORY_NAME_INDEX = 1; // value 中对应元素类型的索引
const DIM_START_TIME_INDEX = 3; // value 中开始时间的索引
const DIM_END_TIME_INDEX = 4; // value 中结束时间的索引

const HEIGHT_RATIO = 0.6; // 甘特图矩形元素高度缩放比例
const CATEGORY_NAME_PADDING_WIDTH = 20; // 在甘特图矩形元素上展示文字时,左右 padding 的最小长度

/**
 * 计算元素位置及宽高
 * 如果元素超出了当前坐标系的包围盒,则剪裁这个元素
 * 如果元素完全被剪掉,会返回 undefined
 */
function clipRectByRect(params, rect) {
  return echarts.graphic.clipRectByRect(rect, {
    x: params.coordSys.x,
    y: params.coordSys.y,
    width: params.coordSys.width,
    height: params.coordSys.height,
  });
}

// 渲染甘特图元素
function renderGanttItem(params, api, extra) {
  const { isShowText, barMaxHeight, barHeight } = extra;
  // 使用 api.value(index) 取出当前 dataItem 的维度
  const categoryIndex = api.value(DIM_CATEGORY_INDEX);
  // 使用 api.coord(...) 将数值在当前坐标系中转换成为屏幕上的点的像素值
  const startPoint = api.coord([api.value(DIM_START_TIME_INDEX), categoryIndex]);
  const endPoint = api.coord([api.value(DIM_END_TIME_INDEX), categoryIndex]);
  // 使用 api.size(...) 取得坐标系上一段数值范围对应的长度
  const baseHeight = Math.min(api.size([0, 1])[1], barMaxHeight);
  const height = barHeight * HEIGHT_RATIO || baseHeight * HEIGHT_RATIO;
  const width = endPoint[0] - startPoint[0];
  const x = startPoint[0];
  const y = startPoint[1] - height / 2;

  // 处理类目名,用于在图形上展示
  const categoryName = api.value(DIM_CATEGORY_NAME_INDEX) + '';
  const categoryNameWidth = echarts.format.getTextRect(categoryName).width;
  const text = width > categoryNameWidth + CATEGORY_NAME_PADDING_WIDTH ? categoryName : '';

  const rectNormal = clipRectByRect(params, { x, y, width, height });
  const rectText = clipRectByRect(params, { x, y, width, height });

  return {
    type: 'group',
    children: [
      {
        // 图形元素形状: 'rect', circle', 'sector', 'polygon'
        type: 'rect',
        ignore: !rectNormal, // 是否忽略(忽略即不渲染)
        shape: rectNormal,
        // 映射 option 中 itemStyle 样式
        style: api.style(),
      },
      {
        // 在图形上展示类目名
        type: 'rect',
        ignore: !isShowText || !rectText,
        shape: rectText,
        style: api.style({
          fill: 'transparent',
          stroke: 'transparent',
          text: text,
          textFill: '#fff',
        }),
      },
    ],
  };
}

上面是我用的 renderItem 方法全貌,主要是使用 api 提供的工具函数计算出元素的视觉宽高

再使用 echarts 提供的 graphic.clipRectByRect 方法,结合参数 params 提供的坐标系信息,截取出元素的图形信息

 

三、自定义 tooltip

如果数据格式正确,到这里已经能渲染出甘特图了,但一个图表还需要其它的细节,比如 tooltip 的自定义

在 renderItem 中有一个字段 encode 可以用来自定义 tooltip,但只能定义展示的文字

具体的 tooltip 排版和图例颜色(特别是渐变色)无法通过 encode 实现自定义,最终还是得通过 formatter 函数

formatter: params => {
  const { value = [], marker, name, color } = params;
  const axis = this.columns; // 类目轴(Y轴)数据
  // 删除空标题
  let str = '';
  isArray(axis[value[0]]) && axis[value[0]].map(item => {
    item && (str += `${item}/`);
  });
  str = str.substr(0, str.length - 1);
  // 颜色为对象时,为渐变颜色,需要手动拼接
  let mark = marker;
  if (isObject(color)) {
    const { colorStops = [] } = color;
    const endColor = colorStops[0] && colorStops[0].color;
    const startColor = colorStops[1] && colorStops[1].color;
    const colorStr = `background-image: linear-gradient(90deg, ${startColor}, ${endColor});`;
    mark = `
      <span style="
        display:inline-block;
        margin-right:5px;
        border-radius:10px;
        width:10px;
        height:10px;
        ${colorStr}
      "></span>`;
  }
  // 计算时长
  const startTime = moment(value[3]);
  const endTime = moment(value[4]);
  let unit = '小时';
  let duration = endTime.diff(startTime, 'hours');
  return `
    <div>${str}</div>
    <div>${mark}${name}: ${duration}${unit}</div>
    <div>开始时间:${startTime.format('YYYY-MM-DD HH:mm')}</div>
    <div>结束时间:${endTime.format('YYYY-MM-DD HH:mm')}</div>
  `;
},
},

 

四、自动滚屏

如果甘特图的数据过多,堆在一屏展示就会显得很窄,这时候可以结合 dataZoom 实现滚屏

首先需要在组件中引入 dataZoom

import 'echarts/lib/component/dataZoom';

// 配置项
const option = {
  ...,
  dataZoom: {
    type: 'slider',
    id: 'insideY01',
    yAxisIndex: 0,
    zoomLock: true,
    bottom: -10,
    startValue: this.dataZoomStartVal,
    endValue: this.dataZoomEndVal,
    handleSize: 0,
    borderColor: 'transparent',
    backgroundColor: 'transparent',
    fillerColor: 'transparent',
    showDetail: false,
  },
  {
    type: 'inside',
    id: 'insideY02',
    yAxisIndex: 0,
    startValue: this.dataZoomStartVal,
    endValue: this.dataZoomEndVal,
    zoomOnMouseWheel: false,
    moveOnMouseMove: true,
    moveOnMouseWheel: true,
  }
}

然后需要设定甘特图每一行的高度 barHeight,同时获取甘特图组件的高度

通过这两个高度计算出每屏可以展示的甘特图数据的数量 pageSize

const GANT_ITEM_HEIGHT = 56;
const height = this.$refs.chartGantRef.$el.clientHeight;
this.pageSize = Math.floor(height / GANT_ITEM_HEIGHT);
// 设置 dataZoom 的起点
this.dataZoomStartVal = 0;
this.dataZoomEndVal = this.pageSize - 1;

然后通过定时器派发事件,修改 dataZoom 的 startValue 和 endValue,实现自动滚屏的效果

const Timer = null;
dataZoomAutoScoll() {
  Timer = setInterval(() => {
    const max = this.total - 1;
    if (
      this.dataZoomEndVal > max ||
      this.dataZoomStartVal > max - this.pageSize
    ) {
      this.dataZoomStartVal = 0;
      this.dataZoomEndVal = this.pageSize - 1;
    } else {
      this.dataZoomStartVal += 1;
      this.dataZoomEndVal += 1;
    }
    echarts.dispatchAction({
      type: 'dataZoom',
      dataZoomIndex: 0,
      startValue: this.dataZoomStartVal,
      endValue: this.dataZoomEndVal
    });
  }, 2000);
},

 

 

Guess you like

Origin www.cnblogs.com/wisewrong/p/11960267.html