echarts的双X轴,父级居中的相关配置

前言:折腾了一个星期,在最后一天中午,都快要放弃了,后来坚持下来,才有下面结果。

这个效果就相当是复合表头,第一行是子级,第二行是父级。
子级是奇数个时,父级label居中很简单,但是,当子级是偶数个的时候,父级就很难居中

如图:
在这里插入图片描述

直接把以下源码,复制到这个链接去打开看效果:
链接:https://echarts.apache.org/examples/zh/editor.html?c=bar-simple
查看效果,注意设置实际宽度boxW





const boxW = 547; // 查看效果,一定要根据实际设置宽度,否则父级不会居中
const boxH = 803;
const grid = { left: '10%', right: '10%', bottom: '40%', top: '10%' }

// canvas的宽高
const canvasW = boxW * (1 - parseInt(grid.left) / 100 - parseInt(grid.right) / 100)
const canvasH = boxH * (1 - parseInt(grid.top) / 100 - parseInt(grid.bottom) / 100)

const seriesData = [
  {
    data: [120, 200, 150, 80, 70, 110, 130, 120, 200, 150, 80, 70, 110, 130],
    type: 'bar'
  },
  {
    data: [120, 200, 150, 80, 70, 110, 130, 120, 200, 150, 80, 70, 110, 130],
    type: 'bar'
  },
  {
    data: [120, 200, 150, 80, 70, 110, 130, 120, 200, 150, 80, 70, 110, 130],
    type: 'line'
  }
]


const textStr1 = '第一组123456'
const textStr2 = '第二组第二组第二组第二组1'
const textStr3 = '第三组哈'
const textStr4 = '第四组第四组第四组第四组123456'
const textStr5 = '第五组'
const chartGroups = [
  {
    grouplabel: textStr1,
    xAxis_datas: [textStr1, textStr1]
  },
  {
    grouplabel: textStr2,
    xAxis_datas: [textStr2, textStr2, textStr2]
  },
  {
    grouplabel: textStr3,
    xAxis_datas: [textStr3, textStr3]
  },
  {
    grouplabel: textStr4,
    xAxis_datas: [textStr4, textStr4, textStr4, textStr4, textStr4]
  },
  {
    grouplabel: textStr5,
    xAxis_datas: [textStr5, textStr5]
  },
]
const xAxisData = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', '日', 'Mon1', 'Tue1', 'Wed1', 'Thu1', 'Fri1', 'Sat1', '日1']
let item2DataArr = [] // x轴的第二行数据
const isShowLabelArr = [] // x轴的第二行 label的显示与隐藏规则
const axisTickArr = [] // 刻度线的显示与隐藏规则
const isExistObj = []
const isExistObj1 = []
const xObj = {}

// 计算x轴的第二行,单元格label的显示与隐藏
chartGroups.forEach(gItem => {
  const datas = gItem.xAxis_datas || []
  const grouplabel = gItem.grouplabel
  const len = datas.length

  datas.forEach((o, i) => {
    const isEsist = isExistObj1.some(v => v === grouplabel)
    // debugger
    // 是否显示的设置
    if (!isEsist) {
      if (len % 2 === 0) { // 当前分组,有偶数个子级
        const index = len / 2 - 1
        if (index === i) {
          // debugger
          isExistObj1.push(grouplabel)
          isShowLabelArr.push(1) // 1显示,0不显示(标签文字,刻度线)
        } else {
          isShowLabelArr.push(0) // 1显示,0不显示(标签文字,刻度线)
        }
      } else { // 当前分组,有奇数个子级
        let index = Math.ceil(len / 2) - 1
        if (index === i) {
          isExistObj1.push(grouplabel)
          isShowLabelArr.push(1) // 1显示,0不显示(标签文字,刻度线)
        } else {
          isShowLabelArr.push(0) // 1显示,0不显示(标签文字,刻度线)
        }

      }
    } else {
      isShowLabelArr.push(0) // 1显示,0不显示(标签文字,刻度线)
    }
  })

})

// 计算x轴的第二行,单元格刻度线的显示与隐藏
chartGroups.forEach(gItem => {
  const datas = gItem.xAxis_datas || []
  const grouplabel = gItem.grouplabel
  datas.forEach((o, i) => {
    item2DataArr.push(grouplabel)
    const isEsist = isExistObj.some(v => v === grouplabel)
    // 是否显示的设置
    if (!isEsist) {
      isExistObj.push(grouplabel)
      axisTickArr.push(1) // 1显示,0不显示(标签文字,刻度线
    } else {
      axisTickArr.push(0) // 1显示,0不显示(标签文字,刻度线)
    }
  })
})

// 每一柱子的宽度
const itemW = canvasW / item2DataArr.length

// 整合第二行X轴数据,并过滤重复label
chartGroups.forEach((item, i) => {
  const len = item.xAxis_datas.length
  // debugger
  const centerNum = Math.floor(len / 2) // 当前组的中心
  const isOdd = len % 2 === 0
  xObj[item.grouplabel] = {
    canvasW: boxW,
    canvasH: boxH,
    itemW,
    text: item.grouplabel,
    isOdd: isOdd ? '奇数个' : '偶数个',
    count: len, // 子级个数(x轴第一行个数)
    tdCountW: (len * itemW).toFixed(2) // 合并单元格的总宽度
  }
})

// console.log('itemW', itemW)
let richObj = {} // 富文本样式,通过echarts的富文本设置第二行X轴居中
let axisLabelFormat = [] // 富文本显示样式的规则
const spaceW = 4 // 1个空格字符站4px
const perFontW = 12 // 1个字符的宽度12px(根据你的实际情况定义)
let isExistArr = []
let context = null


// 第二行的文字长度区分奇数和偶数,并根据复合单元格宽度,适配文字最大长度
item2DataArr.forEach((k, index) => {
  const isTrue = isShowLabelArr[index]
  const o = xObj[k]
  let txt = o.text
  if (isTrue) { // 显示的才处理

    const isEsist = isExistArr.some(val1 => val1 === k)

    // 计算文字的总宽度
    const contextObj = measureTextWidth({ cxt: context, text: k });
    if (!context) {
      context = contextObj.context
    }
    o.txtW = contextObj.strWidth; // 文字的总宽度
    // debugger
    if (o.count % 2 === 0 && !isEsist) { //偶数,需要计算中心位置
      let txtAlign = 'left'
      let paddingArr = [0, 0, 0, 0]
      isExistArr.push(k)
      o.halfW = (o.tdCountW - o.txtW) / 2 // 文字在复合单元格中的中心点
      o.centerNum = Math.abs(itemW / 2 - o.halfW) // 一个单元格相对文字中心的中心点
      o.spaceNum = Math.floor(o.centerNum % spaceW) // 计算把字符从单元格中心移到复合表头中心,需要多少个空字符

      const disAllItemW = o.txtW - o.tdCountW
      const disItemW = o.txtW - itemW
      // debugger
      if (disAllItemW > 0) { // 字的长度大于整个复合单元格的宽度
        txtAlign = 'center'
        paddingArr = [0, 0, 0, itemW]
        // debugger
        txt = fixTxtMaxWidth({ item: o, context, perFontW }) // 字数长度大于复合单元格宽度(适配复合单元格的宽度,最多能显示多少个字符)
        // console.log('\n\n********', txt, 'paddingArr', paddingArr)
      } else if (disItemW > 0) { // 字的长度大于1个单元格的宽度
        txtAlign = 'center'
        txt = k
        // debugger
        paddingArr = [0, 0, 0, itemW]
        // console.log('\n\n----------', o.count, o.text, 'paddingArr', paddingArr)
      } else { // 字的长度小于1个单元格的宽度,则需要通过添加空字符来占位
        txtAlign = 'left'
        txt = fixTxtMinWidth({ item: o, context }) // 子级个数为偶数,且父级字数长度过小,通过给父级label加空格,把label居中显示
        // debugger
      }

      axisLabelFormat.push(`{${index}|${txt}}`)
      richObj[index] = {
        width: 0.5,
        height: 16,
        color: '#f00',
        padding: paddingArr,
        // backgroundColor: '#bbb',
        align: txtAlign
      }
    } else { // 奇数,直接显示中间的即可
      // debugger
      if (k) {
        txt = fixTxtMaxWidth({ item: o, context, perFontW }) // 字数长度大于复合单元格宽度(适配复合单元格的宽度,最多能显示多少个字符)
      }
      axisLabelFormat.push(`{${index}|${txt}}`)
      richObj[index] = {
        height: 16
      }

    }
  } else {
    axisLabelFormat.push(`{${index}|${txt}}`)
    richObj[index] = {
      height: 16
    }
  }

})



console.log(' ')
console.log('itemW', itemW)
console.log('item2DataArr', item2DataArr)
console.log('isShowLabelArr', isShowLabelArr)
console.log('axisTickArr', axisTickArr)

// console.log('canvasW', canvasW)
// console.log('canvasH', canvasH)

console.log('xObj', xObj)
console.log('axisLabelFormat', axisLabelFormat)
console.log('richObj', richObj)
console.log(' ')

// 字数长度大于复合单元格宽度(适配复合单元格的宽度,最多能显示多少个字符)
function fixTxtMaxWidth ({ item, context, perFontW }) {
  // console.log('\n\nfixTxtMaxWidth111');
  let txt = item.text
  let txtLen = item.txtW
  const countW = item.tdCountW - perFontW // 超出最大宽度,要裁剪,然后添加省略号
  let symbol = ''
  // debugger
  while (txtLen > countW) {
    txt = txt.substring(0, txt.length - 1)
    // debugger
    const txtObj = measureTextWidth({ cxt: context, text: txt }); // 文字的总宽度
    txtLen = txtObj.strWidth
    console.log('\nwhile:', txt, txtLen, item.tdCountW)
    symbol = '...'
  }
  txt += symbol
  return txt
}

// 通过canvas计算文字宽度
function measureTextWidth ({ cxt, text, fontSize, fontFamily }) {
  fontSize = fontSize || 12;
  fontFamily = fontFamily || 'Arial';
  let context = cxt
  if (!context) {
    // 创建一个canvas元素
    const canvas = document.createElement('canvas');
    context = canvas.getContext('2d');
  }
  // 设置文本样式
  context.font = `${fontSize}px ${fontFamily}`;

  // 测量文本宽度
  const metrics = context.measureText(text);
  // console.log(text, metrics.width);
  return {
    strWidth: metrics.width,
    context
  }
}

// 子级个数为偶数,且父级字数长度过小,通过给父级label加空格,把label居中显示
function fixTxtMinWidth ({ item, context, dividendNum = 2 }) {
  let txt = item.text
  let txtLen = item.txtW
  const countW = itemW / dividendNum
  // debugger
  while (txtLen < countW) {
    txt = ' ' + txt
    const txtObj = measureTextWidth({ cxt: context, text: txt }); // 文字的总宽度
    txtLen = txtObj.strWidth.toFixed(2)
    // debugger
    console.log('fixTxtMinWidth111:', item.txtW, txtLen, itemW, ', tdCountW=', item.tdCountW, txt)
  }

  return txt
}



option = {
  grid,// 组件离容器下侧的距离,值可以是像 20 这样的具体像素值,也可以是像 '20%' 这样相对于容器高宽的百分比
  xAxis: [
    {
      type: 'category',
      axisLabel: {
        interval: 0,
        rotate: 0// 倾斜角度
      },
      axisTick: {
        show: true,
        length: 30,
      },// 是否显示坐标轴刻度
      data: xAxisData
    },

    // ******************************************************************************************************************************
    // 这个是X轴第二行,相当父级
    {
      type: 'category',
      axisLabel: { // 坐标轴文本标签
        align: 'center',
        formatter (value, index) {
          let val1 = axisLabelFormat[index]
          return val1 // 返回真,就会显示label
        },
        interval: function (index, value) {
          const val1 = isShowLabelArr[index]
          // 根据子级个数动态调整间隔, false则不显示
          return val1;
        },
        rich: richObj
      },
      position: 'bottom',// 很重要,如果没有这个设置,默认第二个x轴就会在图表的顶部
      offset: 30,// X 轴相对于默认位置的偏移,在相同的 position 上有多个 X 轴的时候有用
      axisTick: { // 刻度线
        show: true,
        length: 30,
        interval: function (index, value) {
          const val1 = axisTickArr[index]
          // 根据子级个数动态调整间隔
          return val1;
        }
      },// 是否显示坐标轴刻度
      axisLine: { // 是否显示坐标轴轴线
        show: true,
        onZeroAxisIndex: 2
      },
      data: item2DataArr
    },


    // ******************************************************************************************************************************
    // 这个设置只是在底部绘制一条线
    {
      type: 'category',
      position: 'bottom',// 很重要,如果没有这个设置,默认第二个x轴就会在图表的顶部
      offset: 60,// X 轴相对于默认位置的偏移,在相同的 position 上有多个 X 轴的时候有用
      axisLine: { // 是否显示坐标轴轴线
        show: true,
        onZeroAxisIndex: 2
      },
      data: []
    }
  ],
  yAxis: [
    {
      name: '人数',
      type: 'value'
    },
    // {
    //   name: '年龄',
    //   type: 'value'
    // }
  ],
  series: seriesData
};






后记:记录这一刻的不易,同时希望能帮到有需要的人,觉得不错可以收藏!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/928309.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

顶刊算法 | 鱼鹰算法OOA-BiTCN-BiGRU-Attention多输入单输出回归预测(Maltab)

顶刊算法 | 鱼鹰算法OOA-BiTCN-BiGRU-Attention多输入单输出回归预测&#xff08;Maltab&#xff09; 目录 顶刊算法 | 鱼鹰算法OOA-BiTCN-BiGRU-Attention多输入单输出回归预测&#xff08;Maltab&#xff09;效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实…

Agile VMO分享:海尔案例

海尔集团是全球最大的家电制造商之一&#xff0c;拥有超过76 000名员工。它获得了2018-2019年全球智能家电品牌前10名和2018-2019年全球消费电子品牌前50名的荣誉。 海尔利用价值流结构将自己组织成一些可以自管理的微型企业。这些微型企业拥有决策&#xff0c;设计和交付新产品…

第七课 Unity编辑器创建的资源优化_UI篇(UGUI)

上期我们学习了简单的Scene优化&#xff0c;接下来我们继续编辑器创建资源的UGUI优化 UI篇&#xff08;UGUI&#xff09; 优化UGUI应从哪些方面入手&#xff1f; 可以从CPU和GPU两方面考虑&#xff0c;CPU方面&#xff0c;避免触发或减少Canvas的Rebuild和Rebatch&#xff0c…

LabVIEW MathScript工具包对运行速度的影响及优化方法

LabVIEW 的 MathScript 工具包 在运行时可能会影响程序的运行速度&#xff0c;主要是由于以下几个原因&#xff1a; 1. 解释型语言执行方式 MathScript 使用的是类似于 MATLAB 的解释型语言&#xff0c;这意味着它不像编译型语言&#xff08;如 C、C 或 LabVIEW 本身的 VI&…

中国移动量子云平台:算力并网590量子比特!

在技术革新的浪潮中&#xff0c;量子计算以其独特的并行处理能力和指数级增长的计算潜力&#xff0c;有望成为未来技术范式变革和颠覆式创新应用的新源泉。中国移动作为通信行业的领军企业&#xff0c;致力于量子计算技术研究&#xff0c;推动量子计算产业的跨越式发展。 量子云…

pytest(二)excel数据驱动

一、excel数据驱动 excel文件内容 excel数据驱动使用方法 import openpyxl import pytestdef get_excel():excel_obj openpyxl.load_workbook("../pytest结合数据驱动-excel/data.xlsx")sheet_obj excel_obj["Sheet1"]values sheet_obj.valuescase_li…

文库 | 从嬴图的技术文档聊起

在技术的浩瀚海洋中&#xff0c;一份优秀的技术文档宛如精准的航海图。它是知识传承的载体&#xff0c;是团队协作的桥梁&#xff0c;更是产品成功的幕后英雄。然而&#xff0c;打造这样一份出色的技术文档并非易事。你是否在为如何清晰阐释复杂技术而苦恼&#xff1f;是否纠结…

flask的第一个应用

本文编写一个简单的实例来记录下flask的使用 文章目录 简单实例flask中的路由无参形式有参形式 参数类型本文小结 简单实例 flask的依赖包都安装好之后&#xff0c;我们就可以写一个最简单的web应用程序了&#xff0c;我们把这个应用程序命名为first.py: from flask import Fla…

【UE5 C++】判断两点连线是否穿过球体

目录 前言 方法一 原理 代码 测试 结果 方法二 原理 一、检查连线与球体的相交情况 二、检查距离与球体半径的关系 三、检查连线与球体的相交 代码 前言 通过数学原理判断空间中任意两点的连线是否穿过球体&#xff0c;再通过射线检测检验算法的正确性。 方法一 …

Python办公——openpyxl处理Excel每个sheet每行 修改为软雅黑9号剧中+边框线

目录 专栏导读背景1、库的介绍①&#xff1a;openpyxl 2、库的安装3、核心代码4、完整代码5、最快的方法(50万行44秒)——表头其余单元格都修改样式总结 专栏导读 &#x1f338; 欢迎来到Python办公自动化专栏—Python处理办公问题&#xff0c;解放您的双手 &#x1f3f3;️‍…

Figma入门-约束与对齐

Figma入门-约束与对齐 前言 在之前的工作中&#xff0c;大家的原型图都是使用 Axure 制作的&#xff0c;印象中 Figma 一直是个专业设计软件。 最近&#xff0c;很多产品朋友告诉我&#xff0c;很多原型图都开始用Figma制作了&#xff0c;并且很多组件都是内置的&#xff0c…

8. Debian系统中显示屏免密码自动登录

本文介绍如何在Debian系统上&#xff0c;启动后&#xff0c;自动免密登录&#xff0c;不卡在登录界面。 1. 修改lightDM配置文件 嵌入式Debian系统采用lightDM显示管理器&#xff0c;所以&#xff0c;一般需要修改它的配置文件/etc/lightdm/lightdm.conf&#xff0c;找到[Seat…

Unity类银河战士恶魔城学习总结(P156 Audio Settings音频设置)

【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili 教程源地址&#xff1a;https://www.udemy.com/course/2d-rpg-alexdev/ 本章节实现了音频的大小设置与保存加载 音频管理器 UI_VolumeSlider.cs 定义了 UI_VolumeSlider 类&#xff0c;用于处理与音频设置相关的…

控制访问权限

Swift中的控制访问权限有5种&#xff0c;分别是private&#xff0c;fileprivate&#xff0c;public&#xff0c;open&#xff0c;intelnal。 如果我们没有写访问权限关键字时&#xff0c;默认的访问权限是intelnal 访问控制权限从高到低的顺序是&#xff1a;open > public…

单例模式的析构学习

1、例子 如果单例对象是类的static成员&#xff0c;那么在程序结束时不会调用类的析构函数&#xff0c;如下&#xff1a; #include <iostream> using namespace std;class A{ private:static A* m_ins;//声明&#xff0c;静态指针成员A(){} public:static A* getIns(){…

Function Arguments and Function Parameters (函数的实参和函数的形参)

Function Arguments and Function Parameters {函数的实参和函数的形参} 1. Object-Oriented Programming Using C2. Function Arguments and Function ParametersReferences 1. Object-Oriented Programming Using C https://icarus.cs.weber.edu/~dab/cs1410/textbook/index…

[SWPUCTF 2021 新生赛]gif好像有点大

[SWPUCTF 2021 新生赛]gif好像有点大 帧解一下 找到这个二维码用软件CQR解开一下 得到flag NSSCTF{The_G1F_ls_T00_b1g} [BJDCTF 2020]base?? 给了我们base64加密的密文 用python直接解密 import base64 dict{0: J, 1: K, 2: L, 3: M, 4: N, 5: O, 6: x, 7: y, 8: U, 9: …

嵌入式蓝桥杯学习1 点亮LED

cubemx配置 1.新建一个STM32G431RBT6文件 2.在System-Core中点击SYS&#xff0c;找到Debug&#xff08;设置为Serial Wire&#xff09; 3.在System-Core中点击RCC&#xff0c;找到High Speed Clock(设置为Crystal/Ceramic Resonator) 4.打开Clock Configuration &#xff0…

机器学习周志华学习笔记-第13章<半监督学习>

机器学习周志华学习笔记-第13章&#xff1c;半监督学习&#xff1e; 卷王&#xff0c;请看目录 13半监督学习13.1 生成式方法13.2 半监督SVM13.3 基于分歧的方法13.4 半监督聚类 13半监督学习 前面我们一直围绕的都是监督学习与无监督学习&#xff0c;监督学习指的是训练样本包…

安装MySQL 5.7 亲测有效

前言&#xff1a;本文是笔者在安装MySQL5.7时根据另一位博主大大的安装教程基础上做了一些修改而成 首先在这里表示对博主大大的感谢 下面附博主大大地址 下面的步骤言简意赅 跟着做就不会出错 希望各位读者耐下心来 慢慢解决安装中出现的问题~MySQL 5.7 安装教程&#xff08;全…