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

news2024/12/5 3:21:10

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

这个效果就相当是复合表头,第一行是子级,第二行是父级。
子级是奇数个时,父级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.coloradmin.cn/o/2253381.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CTF-PWN: WEB_and_PWN [第一届“吾杯”网络安全技能大赛 Calculator] 赛后学习(不会)

附件 calculate.html <!DOCTYPE html> <html lang"en"> <head><!-- 设置字符编码为 UTF-8&#xff0c;支持多语言字符集 --><meta charset"UTF-8"><!-- 设置响应式视图&#xff0c;确保页面在不同设备上自适应显示 --&…

STM32 PWM波形详细图解

目录 前言 一 PWM介绍 1.1 PWM简介 1.2 STM32F103 PWM介绍 1.3 时钟周期与占空比 二.引脚映像关系 2.1引脚映像与寄存器 2.2 复用功能映像 三. PWM 配置步骤 3.1相关原理图 3.2配置流程 3.2.1 步骤一二&#xff1a; 3.2.2 步骤三&#xff1a; 3.2.3 步骤四五六七&#xff1a; …

6.824/6.5840 Lab 1: MapReduce

宁静的夏天 天空中繁星点点 心里头有些思念 思念着你的脸 ——宁夏 完整代码见&#xff1a; https://github.com/SnowLegend-star/6.824 由于这个lab整体难度实在不小&#xff0c;故考虑再三还是决定留下代码仅供参考 6.824的强度早有耳闻&#xff0c;我终于也是到了挑战这座高…

东方隐侠网安瞭望台第8期

谷歌应用商店贷款应用中的 SpyLoan 恶意软件影响 800 万安卓用户 迈克菲实验室的新研究发现&#xff0c;谷歌应用商店中有十多个恶意安卓应用被下载量总计超过 800 万次&#xff0c;这些应用包含名为 SpyLoan 的恶意软件。安全研究员费尔南多・鲁伊斯上周发布的分析报告称&…

【python自动化一】pytest的基础使用

1.pytest简述 pytest‌ 是一个功能强大且灵活的Python测试框架&#xff0c;其主要是用于流程控制&#xff0c;具体用于UI还是接口自动化根据个人需要而定。且其具有丰富插件&#xff0c;使用时较为方便。咱们具体看下方的内容&#xff0c;本文按照使用场景展开&#xff0c;不完…

EasyDSS视频推拉流技术的应用与安防摄像机视频采集参数

安防摄像机的视频采集参数对于确保监控系统的有效性和图像质量至关重要。这些参数不仅影响视频的清晰度和流畅度&#xff0c;还直接影响存储和网络传输的需求。 安防摄像机图像效果的好坏&#xff0c;由DSP处理器和图像传感器sensor决定&#xff0c;如何利用好已有的硬件资源&…

GoReplay开源工具使用教程

目录 一、GoReplay环境搭建 1、Mac、Linux安装GoReplay环境 二、GoReplay录制与重播 1、搭建练习接口 2、录制命令 3、重播命令 三、GoReplay单个命令 1、常用命令 2、其他命令 3、命令示例 4、性能测试 5、正则表达式 四、gorepaly组合命令 1、组合命令实例 2、…

论文:IoU Loss for 2D/3D Object Detection

摘要&#xff1a;在2D/3D目标检测任务中&#xff0c;IoU (Intersection-over- Union)作为一种评价指标&#xff0c;被广泛用于评价不同探测器在测试阶段的性能。然而&#xff0c;在训练阶段&#xff0c;通常采用常见的距离损失(如L1或L2)作为损失函数&#xff0c;以最小化预测值…

CAD 文件 批量转为PDF或批量打印

CAD 文件 批量转为PDF或批量打印&#xff0c;还是比较稳定的 1.需要本地安装CAD软件 2.通过 Everything 搜索工具搜索&#xff0c;DWG To PDF.pc3 &#xff0c;获取到文件目录 &#xff0c;替换到代码中&#xff0c; originalValue ACADPref.PrinterConfigPath \ r"C:…

【错误记录】jupyter notebook打开后服务器错误Forbidden问题

如题&#xff0c;在Anaconda Prompt里输入jupyter notebook后可以打开浏览器&#xff0c;但打开具体项目后就会显示“服务器错误&#xff1a;Forbidden”&#xff0c;终端出现&#xff1a; tornado.web.HTTPError: HTTP 403: Forbidden 查看jupyter-server和jupyter notebook版…

[MacOS] [kubernetes] MacOS玩转虚拟化最佳实践

❓ 为什么不在MacOS本机安装呢&#xff1f;因为M系列芯片是Arm架构&#xff0c;与生产环境或者在本地调试时候&#xff0c;安装虚拟镜像和X86不同&#xff0c;造成不必要的切换环境的额外成本&#xff0c;所以在虚拟化的x86调试 步骤 & 详情 一: 安装OrbStack & 并配置…

网络编程相关 API 学习

目录 1. 网络编程中的基本概念 2. UDP 的 socket api 的使用 (1) DatagramSocket API (2) DatagramPacket API (3) InetSocketAddress API (4) 使用 UDP 的 socket api 3. TCP 的 socket api 的使用 (1) ServerSocket API (2) Socket API 1. 网络编程中的基本概念 客…

【Android】View工作原理

View 是Android在视觉上的呈现在界面上Android提供了一套GUI库&#xff0c;里面有很多控件&#xff0c;但是很多时候我们并不满足于系统提供的控件&#xff0c;因为这样就意味这应用界面的同类化比较严重。那么怎么才能做出与众不同的效果呢&#xff1f;答案是自定义View&#…

burp2

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…

【阅读记录-章节5】Build a Large Language Model (From Scratch)

目录 5. Pretraining on unlabeled data5.1 Evaluating generative text models5.1.1 Evaluating generative text models5.1.2 Calculating the text generation loss评估模型生成文本的质量 5.1.3 Calculating the training and validation set losses 5.2 Training an LLM5.…

Qt Qtablewidget 标题 QHeaderView 增加可选框 QcheckBox

创建自定义QHeaderView #pragma once#include <QObject> #include <QHeaderView> #include <QPainter> #include <QMouseEvent>class SSHeaderView : public QHeaderView {Q_OBJECTprivate:bool isChecked;int m_checkColIdx; public:SSHeaderView(i…

DDD架构设计

今天的应用架构&#xff0c;意指软件系统中固定不变的代码结构、设计模式、规范和组件间的通信方式。在应用开发中架构之所以是最重要的第一步&#xff0c;因为一个好的架构能让系统安全、稳定、快速迭代。在一个团队内通过规定一个固定的架构设计&#xff0c;可以让团队内能力…

再来聊聊总线机制

背景 之前写过一篇《KafkaPostgreSql&#xff0c;构建一个总线服务》&#xff0c;近期在实践过程中又踩了一些坑&#xff0c;有了一些新的体验&#xff0c;拿出来再说道说道。 我们说EventBus 是一种设计模式和编程工具&#xff0c;它简化了应用程序组件之间的通信。通过使用…

怎么做DNS污染检测

DNS污染是指通过恶意手段篡改DNS解析结果&#xff0c;导致用户访问错误或恶意网站的行为。这种行为不仅影响用户体验&#xff0c;还可能带来安全风险。以下是几种检测DNS污染的方法&#xff1a; 1. 使用在线DNS检查工具 可以使用在线工具如帝恩思旗下的拨测在线DNS检测工具等…

视频融合×室内定位×数字孪生

随着物联网技术的迅猛发展&#xff0c;室内定位与视频融合技术在各行各业中得到了广泛应用。不仅能够提供精确的位置信息&#xff0c;还能通过实时视频监控实现全方位数据的可视化。 与此同时&#xff0c;数字孪生等技术的兴起为智慧城市、智慧工厂等应用提供了强大支持&#…