基于 ECharts 封装甘特图并实现自动滚屏

项目中需要用到甘特图组件,之前的图表一直基于 EChart 开发,但 EChart 本身没有甘特图组件,需要自行封装

经过一番鏖战,终于完成了...

我在工程中参考 v-chart 封装了一套图表组件,所以这里只介绍甘特图组件的实现,图表的初始化、数据更新、自适应等不在这里介绍

一、约定数据格式

EChart 本身没有甘特图,但可以通过 EChart 提供的“自定义”方法 type: ‘custom‘ 开发

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

这里的 data 就是数据集,它是一个二维数组,主要需要两个参数:

name: 名称,可以在 legend 和 tooltip 中展示

value:参数集合,自定义的图表时需要的参数都可以放到这个数组里

如果需要其它的配置,也可以按照 ECharts 的 series 结构添加别的字段

我自定义的数据结构是这样的:

{
  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);
},

原文地址:https://www.cnblogs.com/wisewrong/p/11960267.html

时间: 2025-01-15 05:18:07

基于 ECharts 封装甘特图并实现自动滚屏的相关文章

自动滚屏代码

<html> <head> <title>自动滚屏代码丨轻质隔墙板|kiddy官网</title> </head> <body> <script language="JavaScript"> var position = 0; function scroller() { if (position !=700 ) { position++; scroll(0,position); clearTimeout

网页自动滚屏播放

好长时间不做网页了,今天老板说要做一个展示的demo,让这个demo 自动的播放 用js做了一个滚屏播放的代码 <script type="text/javascript"> var done = true; var speed = 4 //设置速度 var currentpos = 0, alt = 1, curpos1 = 0, curpos2 = -1 function initialize() { startit() } function scrollwindow(

FineReport中如何实现自动滚屏效果

对于一些特殊的模板,可能为了展示的更加丰富.全面会在一个页面放置很多图表.表格等内容.由于内容过多,超出了浏览器窗口的大小导致内容展示不全的情况.这样我们就需要用到JS滚屏效果来解决,这里主要介绍在FineReport中的具体制作方法. 添加加载结束事件 点击菜单模板>模板web属性>分页预览设置,选择"为该模板单独设置",添加一个"加载结束"后事件,如下图所示: JS代码如下: //从页面加载结束后延迟2000MS执行事件(滚动) setTimeout

jquery实现自动滚屏效果,适用用公告新闻等滚屏

从网络上找到的例子,自己做了下扩展,原示例是向上滚动,扩展了一个向下滚动的方法: <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=gb2312" /> <title>滚屏实验</title> <style ty

Echarts甘特图

Echarts原本提供的图形库已经很强大了. 但是无奈,坐标系图形必须要求X,Y轴必须有一个为数字. 最近有个需求是做甘特图. 整了几天,现在把代码发出来分享一下. option = {  title : {      text: 'test',      x:'center'  },  calculable:false,  tooltip : {      trigger: 'axis',      axisPointer:{   type : 'line',   lineStyle : {

关于ECharts甘特图的实现

对于使用ECharts图表的步骤,每种图表都是一致的,相信大家也都了解 此处只分享甘特图的option,代码如下: option: { title: { text: '项目实施进度表', left: 10 }, legend: { y: 'bottom', data: ['计划时间', '实际时间'] }, grid: { containLabel: true, left: 20 }, xAxis: { type: 'time' }, yAxis: { data: ['任务一', '任务二',

Twproject Gantt开源甘特图功能扩展

1.Twproject Gantt甘特图介绍 Twproject Gantt 是一款基于 jQuery 开发的甘特图组件,也可以创建其它图表,例如任务树(Task Trees).内置编辑.缩放和 CSS 皮肤等功能.更重要的是,它是免费开源的. 官网地址是:https://gantt.twproject.com/ 源码可以从github下载: 2.扩展功能一:code自动层级编码,满足wbs编码要求 工作分解结构 (WBS) 代码是项目的识别您的分级显示结构中的每个任务的唯一位置的字母数字代码.

教你读懂redmine中的甘特图

Redmine是用Ruby开发的基于web的项目管理软件,他可以自动绘制甘特图,对于我们了解项目进度有很重要的帮助.但很多新人小白在使用redmine时,就是当成一个简单的备忘录来使用,对于甘特图神马的根本就不care,那么如何正确的使用甘特图呢?如何读懂redmine中的甘特图呢? Redmine是可以根据建立的问题的开始时间.结束时间和完成百分比自动绘制甘特图的.所以我们要使用redmine的甘特图,必须在新建问题时根据项目计划设置好开始时间,预估结束时间,每天在结束一天的工作后,注意及时更

Java甘特图控件swing版免费下载地址 &nbsp; &nbsp; &nbsp;

FlexGantt 控件是现在Java 平台下最先进的甘特图解决方案,使用一个很高的抽象层次,能适用于多种不同的域,例如 ERP 系统.生产计划和日程安排.制造流程系统或项目公文管理程序等.这些使得 FlexGantt 能从其他有局限性的项目计划(资源.人.任务)甘特图库中脱颖而出. 具体功能: 原文来自http://www.51diaodu.com/pdt/2966 模型视图控制器:FlexGantt 遵从与 Swing 相同 MVC 方法.许多不同的模型用于各种各样的用途.一个日历模型跟踪假