[Echarts]解决dataZoom当startValue 和 endValue 的值相等(一页只显示一个group bar),页面无法滚动。start和end值设置讲解。思路全讲解!

目录

需求背景

技术缺陷

解决思路遇到的问题:

解决方案之前的技术准备:

两个解决方案:

我的项目中的解决方案 

需求背景

柱状图/条形图用于大数据表的展示。

屏幕视图容器长度有限。

单个分组(或者理解为柱子群需要展示庞大的数据量。

技术缺陷

在Echarts中,我们使用datazoom进行滑动。

参考我的推文:

(43条消息) 【柱状图】全网唯一 图表组件与仪表盘的全屏滚动设计思路_AI3D_WebEngineer的博客-CSDN博客https://blog.csdn.net/weixin_42274805/article/details/125514048?spm=1001.2014.3001.5502
(43条消息) 【Echarts】解决[柱状图设置barMinWidth导致的柱体重叠]与解决[柱状图设置barMinHeight导致数值0也有高度]_AI3D_WebEngineer的博客-CSDN博客_echarts柱状图的高度https://blog.csdn.net/weixin_42274805/article/details/125265161

return userOPT.dataZoom.map(item => ({
    id: item.id,
    type: item.type,
    xAxisIndex: item.xAxisIndex,
    filterMode: item.filterMode,
    zoomLock: false,
    moveOnMouseWheel: true,
    moveOnMouseMove: false,
    preventDefaultMouseMove: true,
    zoomOnMouseWheel: false,
    startValue: 0,
    endValue,
  }));

可以看到。datazoom是由startValue和endValue来决定滑动的初屏和滑动区间。(startValue和endValue可以看做柱群的下标)

但是,

当一个柱子群需要展示的柱子数量过多(一屏无法展示完)还需要整数n屏或者比n屏还要多出半屏或部分屏。

此时

startValue - endValue = 0

 顾名思义,起始和终止的下标都是相等(只指向一个柱子群)

此时滑动被冻结,无法进行正常滑动

解决思路遇到的问题:

因为startValue 和 endValue是指向下标,所以当他们相等时,一定无法被滑动。在不修改源码的情况下,我们可以这么解决:

通过设置start和end,取代startValue和endValue,从而实现滑动。

start和end是百分比,取值是0-100

当start为0时,无法上滑。

当end为100时,无法下滑。

 

此时未设置barMinWidth的柱状图可以正常滑动。但是数据堆在一起根本看不清。

那么我们设置一下barMinWidth

barMaxWidth: 300,
barMinHeight: 10,
barMinWidth: 10,
barCategoryGap: 20,
barGap: '10%',

滑动前

滑动后

 可以看到设置了minWidth后,使用start和end滑动会错位,造成柱子重叠。

柱状图,出于对视图大小的兼容,最好还是自己计算最大容纳柱子数,然后通过series进行数据控制。

解决方案之前的技术准备:

start和value,文档介绍的很简单。就是数据范围的百分比,那么真的有那么简单吗?

 当我把slider打开时,start和end的概念是不是就呼之欲出了?

XAxis的data是: ['华东', '华南', '华中', '西南', '东北', '华北', '西北'],

 那我们是不是可以这么算?

start和end与XAxis的data对应

[0 -1/7 *100)

[1/7*100 -2/7*100)

[2/7*100-3/7*100)

[3/7*100-4/7*100)

[4/7*100-5/7*100)

[5/7*100-6/7*100)

[6/7*100-100*100)

 [0 -1/7 *100)大概是0 - 14.2

填进去!

 错误!出现两个柱子群

笔者填值猜测,大概得到如下对应数据:

start和end与XAxis的data对应

[0 -8)

[9-24)

[25-41)

[42-58)

[59-74)

[75-91)

[92-100)

 这完全是一个不规则数组!!!

可以看到差距既有8又有15又有16,那么会不会!

start:

0 + n

(0 + n) + 2n

(0+ n + 2n) + 2n

.....

 开头结尾都是+1个步进值

中间是2个步进值(步进值是 100 / n个 也就是7)

export function createEchartsDataZoomModel(n) {
  if (!n) {
    return [];
  }
  if (n === 1) {
    return [0];
  }
  // 首尾为n步进,中间是2n步进
  const stepNumber = 100 / ((n - 2) * 2 + 2);
  const dataZoomModel = [];
  times(n, index => {
    if (!index) {
      dataZoomModel.push(0);
    } else if (index === n.length - 1) {
      dataZoomModel.push(100 - stepNumber);
    } else if (index === 1) {
      dataZoomModel.push(dataZoomModel[index - 1] + stepNumber);
    } else {
      dataZoomModel.push(dataZoomModel[index - 1] + stepNumber * 2);
    }
  });
  return dataZoomModel;
}
export function sizeMathSmaller(startOne, startTwo, n) {
  let multiplier = 0.9;
  if (n) {
    times(n, index => {
      multiplier += Math.pow(0.1, index + 1) * 0.9;
    });
  }
  const result = startTwo * multiplier;
  if (result > startOne) {
    return result;
  }
  sizeMathSmaller(startOne, startTwo, n + 1);
}

 sizeMathSmaller是什么?

我们知道,start是闭合区间,end是非闭合区间,它无限接近下一个start值

export function sizeMathSmaller(startOne, startTwo, n) {
  let multiplier = 0.9;
  if (n) {
    times(n, index => {
      multiplier += Math.pow(0.1, index + 1) * 0.9;
    });
  }
  const result = startTwo * multiplier;
  if (result > startOne) {
    return result;
  }
  sizeMathSmaller(startOne, startTwo, n + 1);
}

递归,算出一个合理的end值。

虽然start和end在上文说到,是个区间,但是它最后还是会被转换成startValue和endValue进行下标绑定(无关数据多少是否完整) 

两个解决方案:

为什么有两个解决方案,因为要看业务需求的要求。

当我们业务需求要求展示空数值柱子或null值要预留柱子位置时。便只能使用【我的项目中的解决方案 】(在下面会详述。)因为我们无法将series剪切成我们想要的数据(掐头去尾不可用,二维表定位置空或null不可用。)

如果我们业务需求不要求展示空数值柱子或null值要预留柱子位置时。我们可以实现与官方datazoom滑动的效果。思路如下:

代理datazoom事件-判断上下滚-定位数据下标-计算出下标对应的start和end-通过记录movetimes获悉所在的当前柱群的数据范围-为每次滑动设置一个数据滚动步进值(可以是半屏或者是一屏幕)-重新赋值start和end(在当前数据下标对应的start和end里计算步进率并获取步进后的start和end)以达到挟持start和end的目的-利用步进的start和end剪切数据,并把非当前屏幕的数据置空(null)-渲染echarts

我的项目中的解决方案 

设计思路:一个维度独占一屏幕或整数屏。同个屏幕不会出现两个柱子群。如果维度翻页后不足一屏也以最终数量倒推使其占满一屏。

代理datazoom事件-判断上下滚-定位数据下标-计算出下标对应的start和end-通过记录movetimes获悉所在的当前柱群的数据范围重新赋值start和end以达到挟持start和end的目的-利用start和end和movetimes剪切数据-渲染echarts

....
if (!nowPageNum) {
    // 为0时是最后一屏
    end = seriesData.length + 1;
    start = end - maxPageBar;
  } else {
    start = maxPageBar * (nowPageNum - 1);
    end = start + maxPageBar;
  }
  return seriesData.slice(start, end);

设置start和value,并用Echart.on 监听代理datazoom事件,并通过返回的event事件里包含的start和end值对比原先的start和end判断上下滑。

/**
 * 监听dataZoom鼠标事件上下滚动
 * @param { string } zoomId: dataZoom的id
 * @param { Object } eventData echarts事件对象
 * @param { Object } preEventData echarts事件对象
 * @return { Number }
 */
export function listenDataZoomEvent(zoomId, eventData, preData) {
  const { batch = [] } = eventData;
  // 错误事件
  if (batch.length < 1) return 0;
  const batchDataZoom = batch.find(item => item.dataZoomId === zoomId);
  if (!batchDataZoom || !preData) return 0;
  if (batchDataZoom.end > preData.end && batchDataZoom.start > preData.start) {
    // 向下
    return 1;
  } else if (
    preData.end > batchDataZoom.end &&
    preData.start > batchDataZoom.start
  ) {
    // 向上
    return -1;
  } else {
    return 0;
  }
}

可以开始监听datazoom事件了

this.myChart.off('datazoom');
      // 代理datazoom事件
      this.myChart.on('datazoom', event => {
        const options = this.myChart.getOption();
        // 判断上下滚动
        let moveTime = 0;
        if (!this.preViewZoomEvent) {
          moveTime = listenDataZoomEvent(options.dataZoom[0].id, event, {
            start: printData.dataZoom[0].start,
            end: printData.dataZoom[0].end,
          });
        } else {
          moveTime = listenDataZoomEvent(
            options.dataZoom[0].id,
            event,
            this.preViewZoomEvent,
          );
        }
        const newOption = deepClone(options);
        const { zoomPageTime, groupNum, zoomTimes } = options.customDataZoom;
        if (zoomTimes + moveTime < 0) {
          newOption.customDataZoom.zoomTimes = 0;
        } else if (zoomTimes + moveTime >= groupNum * zoomPageTime) {
          newOption.customDataZoom.zoomTimes = groupNum * zoomPageTime - 1;
        } else {
          newOption.customDataZoom.zoomTimes += moveTime;
        }
        newOption.series = handleSeries(
          newOption.customDataZoom,
          printData.series,
        );
        const newZoom = countNewDataZoom(newOption.customDataZoom);
        newOption.dataZoom = newOption.dataZoom.map(item => {
          if (item.id === options.dataZoom[0].id) {
            item.start = newZoom.start;
            item.end = newZoom.end;
            return item;
          } else {
            return item;
          }
        });
        this.drawDataZoom(newOption);
        this.preViewZoomEvent = {
          start: newZoom.start,
          end: newZoom.end,
        };
      });
    } else {
      this.myChart.setOption(printData, {
        notMerge: true,
      });
    }

dataZoom的作用是帮我们判断上下滑,除此之外无任何实际意义,我们通过movetimes来记录翻了多少页,(并把它塞到chartoption里进行储存)从而计算数据的定位范围

  customDataZoom: {
        inCustomDataZoom,
        zoomTimes: 0,
        // 每一个柱群有多少页
        zoomPageTime,
        // 一共有多少柱群
        groupNum,
        // 每一屏的最大柱子叔
        maxPageBar: barColumn,
        dataZoomModel,
      },

剪切数据和强制绑定datazoom的start和end从而劫持datazoom event事件

/**
 * 计算customDataZoom的start和value
 * @param { Object } customDataZoom 自定义滚动信息
 * @return { Object }
 */
export function countNewDataZoom(customDataZoom) {
  const { zoomTimes, zoomPageTime, dataZoomModel, groupNum } = customDataZoom;
  const nowPageNum = zoomTimes + 1;
  const seriesIndex = Math.ceil(nowPageNum / zoomPageTime);
  const start = dataZoomModel[seriesIndex - 1];
  let end;
  if (seriesIndex === dataZoomModel.length) {
    end = 100;
  } else {
    end = sizeMathSmaller(start, dataZoomModel[seriesIndex], 0);
  }
  // const end =
  //   ((realSeries.length * seriesIndex - 1) /
  //     (realSeries.length * realSeries[0].data.length)) *
  //   100;
  // const start =
  //   (seriesIndex === 1
  //     ? 0
  //     : (realSeries.length * (seriesIndex - 1)) /
  //       (realSeries.length * realSeries[0].data.length)) * 100;
  // let start = ((seriesIndex - 1) / groupNum) * 100;
  // let end = (seriesIndex / groupNum) * 100 - 1;
  // 加一个步进值否则无法向上滚
  let stepPercent;
  if (zoomPageTime === 1) {
    // 写死固定值
    stepPercent = (end - start) * 0.1;
  } else {
    stepPercent = (end - start) / zoomPageTime;
  }
  // 避免到了最后一个维度无法翻页
  if (groupNum * zoomPageTime > nowPageNum && end === 100) {
    end = sizeMathSmaller(dataZoomModel[dataZoomModel.length - 1], 100, 0);
  }
  // const ceilNum = nowPageNum % zoomPageTime;
  // 为0时刚好某个维度最后一页
  // if (!ceilNum) {
  //   start = end - stepPercent + 1;
  // } else {
  //   start += (ceilNum - 1) * stepPercent;
  //   end = start + stepPercent - 1;
  // }
  return { start: start + stepPercent, end };
}
/**
 * 计算customDataZoom的start和value
 * @param { Object } customDataZoom 自定义滚动信息
 * @param { Object } seriesData 数据源
 * @return { Object }
 */
export function handleSeries(customDataZoom, seriesData) {
  const { zoomTimes, maxPageBar, zoomPageTime } = customDataZoom;
  if (zoomPageTime === 1) {
    return seriesData;
  }
  const nowPageNum = (zoomTimes + 1) % zoomPageTime;
  let end;
  let start;
  if (!nowPageNum) {
    // 为0时是最后一屏
    end = seriesData.length + 1;
    start = end - maxPageBar;
  } else {
    start = maxPageBar * (nowPageNum - 1);
    end = start + maxPageBar;
  }
  return seriesData.slice(start, end);
}

思路大概如此

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值