当前位置: 首页 > article >正文

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
};






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


http://www.kler.cn/a/421406.html

相关文章:

  • vulnhub靶场之momentum-2
  • 微服务的负载均衡可以通过哪些组件实现
  • Android 应用单元测试涉及 Telephony 环境初始化问题
  • 用Python做数据分析环境搭建及工具使用(Jupyter)
  • 数据结构4——栈和队列
  • 常见排序算法总结 (三) - 归并排序与归并分治
  • 如何参加华为欧拉考试?
  • 安装战网的时候提示“缺失libcef.dll”怎么办?libcef.dll文件丢失是什么原因?教你六大解决方法
  • Cad c#二次开发 常见错误
  • 小程序入门学习(四)之全局配置
  • EdDSA (Edwards-curve Digital Signature Algorithm)算法详解及python实现
  • 论文导读 I RAFT:使语言模型适应特定领域的RAG
  • C++小玩具1
  • 学习嵩山版《Java 开发手册》:编程规约 - 命名风格(P15 ~ P16)
  • Python HttpServer 的一个bug问题
  • The First项目报告:以太坊再质押赛道新星Swell Network
  • 2024年12月chrome131版本http自动跳转https解决
  • 【Unity基础】使用InputSystem实现物体跳跃
  • Zotero安装使用在线翻译Ubuntu
  • 数字运动_光影射箭(-->合作)
  • Vue前端页面内嵌套本项目iframe窗口的通信传输方式
  • 【深度学习|特征增强融合模块】MAF (Multi-scale Attention Fusion)一种多尺度的注意力融合模块
  • 原型模式的理解和实践
  • 小迪安全第四十二天笔记 简单的mysql注入 mysql的基础知识 用户管理数据库模式 mysql 写入与读取 跨库查询
  • 《从0到1常用Map集合核心摘要 + 不深不浅底层核心》
  • 网络安全内容整理二