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.grouplabelconst len = datas.lengthdatas.forEach((o, i) => {const isEsist = isExistObj1.some(v => v === grouplabel)// debugger// 是否显示的设置if (!isEsist) {if (len % 2 === 0) { // 当前分组,有偶数个子级const index = len / 2 - 1if (index === i) {// debuggerisExistObj1.push(grouplabel)isShowLabelArr.push(1) // 1显示,0不显示(标签文字,刻度线)} else {isShowLabelArr.push(0) // 1显示,0不显示(标签文字,刻度线)}} else { // 当前分组,有奇数个子级let index = Math.ceil(len / 2) - 1if (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.grouplabeldatas.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.lengthchartGroups.forEach((item, i) => {const len = item.xAxis_datas.length// debuggerconst centerNum = Math.floor(len / 2) // 当前组的中心const isOdd = len % 2 === 0xObj[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 = {}
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.textif (isTrue) { // 显示的才处理const isEsist = isExistArr.some(val1 => val1 === k)// 计算文字的总宽度const contextObj = measureTextWidth({ cxt: context, text: k });if (!context) {context = contextObj.context}o.txtW = contextObj.strWidth; // 文字的总宽度// debuggerif (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.tdCountWconst disItemW = o.txtW - itemW// debuggerif (disAllItemW > 0) { // 字的长度大于整个复合单元格的宽度txtAlign = 'center'paddingArr = [0, 0, 0, itemW]// debuggertxt = fixTxtMaxWidth({ item: o, context, perFontW }) // 字数长度大于复合单元格宽度(适配复合单元格的宽度,最多能显示多少个字符)// console.log('\n\n********', txt, 'paddingArr', paddingArr)} else if (disItemW > 0) { // 字的长度大于1个单元格的宽度txtAlign = 'center'txt = k// debuggerpaddingArr = [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 { // 奇数,直接显示中间的即可// debuggerif (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.textlet txtLen = item.txtWconst countW = item.tdCountW - perFontW // 超出最大宽度,要裁剪,然后添加省略号let symbol = ''// debuggerwhile (txtLen > countW) {txt = txt.substring(0, txt.length - 1)// debuggerconst txtObj = measureTextWidth({ cxt: context, text: txt }); // 文字的总宽度txtLen = txtObj.strWidthconsole.log('\nwhile:', txt, txtLen, item.tdCountW)symbol = '...'}txt += symbolreturn txt
}// 通过canvas计算文字宽度
function measureTextWidth ({ cxt, text, fontSize, fontFamily }) {fontSize = fontSize || 12;fontFamily = fontFamily || 'Arial';let context = cxtif (!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.textlet txtLen = item.txtWconst countW = itemW / dividendNum// debuggerwhile (txtLen < countW) {txt = ' ' + txtconst txtObj = measureTextWidth({ cxt: context, text: txt }); // 文字的总宽度txtLen = txtObj.strWidth.toFixed(2)// debuggerconsole.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
};
后记:记录这一刻的不易,同时希望能帮到有需要的人,觉得不错可以收藏!
相关文章:

echarts的双X轴,父级居中的相关配置
前言:折腾了一个星期,在最后一天中午,都快要放弃了,后来坚持下来,才有下面结果。 这个效果就相当是复合表头,第一行是子级,第二行是父级。 子级是奇数个时,父级label居中很简单&…...

RuoYi-Vue部署到Linux服务器(Jar+Nginx)
一、本地环境准备 源码下载、本地Jdk及Node.js环境安装,参考以下文章。 附:RuoYi-Vue下载与运行 二、服务器环境准备 1.安装Jdk 附:JDK8下载安装与配置环境变量(linux) 2.安装MySQL 附:MySQL8免安装版下载安装与配置(linux) 3.安装Redis 附:Redis下载安装与配置(…...

Linux firewalld常用命令
启动防火墙 systemctl start firewalld 停止防火墙 systemctl stop firewalld 防火墙开机自启动 systemctl enable firewalld 禁止防火墙开机自启动 systemctl disable firewalld 检查防火墙的状态 systemctl status firewalld 重新加载防火墙的配置 firewall-cmd -…...

Vue 组件之间的通信方式
Vue.js 中组件之间的通信是构建复杂应用的关键部分。以下是一些常见的Vue组件通信方式: 1. Props 和 Emit(父子组件通信) Props:父组件通过props向子组件传递数据。Emit:子组件通过emit触发事件,向父组件…...

el-select 修改样式
这样漂亮的页面,搭配的却是一个白色风格的下拉框 ,这也过于刺眼。。。 调整后样式为: 灯红酒绿总有人看着眼杂,但将风格统一终究是上上选择。下面来处理这个问题。 分为两部分。 第一部分:是修改触发框的样式 第二部…...

Java项目实战II基于微信小程序的亿家旺生鲜云订单零售系统的设计与实现(开发文档+数据库+源码)
目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发,CSDN平台Java领域新星创作者,专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 随着移动互联网技术的不断…...

算法训练营day27(回溯算法03:组合总和,组合总和2,分割回文串)
第七章 回溯算法part03● 39. 组合总和 ● 40.组合总和II ● 131.分割回文串详细布置 39. 组合总和 本题是 集合里元素可以用无数次,那么和组合问题的差别 其实仅在于 startIndex上的控制题目链接/文章讲解:https://programmercarl.com/0039.%E7%BB%84%E…...

【青牛科技】D8331 流量计电路芯片,兼容 CTs,电阻分流器和罗氏线圈传感器
概述: D8331 系列超低功耗混合信号处理器由多种设备组成,具有针对电能表应用的不 同外围设备。它们集成了模拟前端和固定功能 DSP 解决方案与一个增强型 8052 单片 机核心,RTC 和 LCD 驱动程序集成在一个单一部件中。测量内核包括有功、无功…...

R语言森林生态系统结构、功能与稳定性分析与可视化实践高级应用
在生态学研究中,森林生态系统的结构、功能与稳定性是核心研究内容之一。这些方面不仅关系到森林动态变化和物种多样性,还直接影响森林提供的生态服务功能及其应对环境变化的能力。森林生态系统的结构主要包括物种组成、树种多样性、树木的空间分布与密度…...

【IntelliJ IDEA 中 Run Dashboard 不显示端口号问题解决办法】
IntelliJ IDEA 中 Run Dashboard 不显示端口号问题解决办法 解决 IntelliJ IDEA Run Dashboard 不显示端口号问题方法一:删除临时文件方法二:设置启动参数方法三:编辑 Run/Debug Configurations方法四:检查端口占用情况方法五&…...

idea中git的将A分支某次提交记录合并到B分支
一 实操案例 1.1 背景描述 在开发过程中,有时候需要将A分支某次提交记录功能合并到B分支上。主要原理用到git的cherry pick功能。 1.2 案例 实现的功能: master分支的11.24提交记录合并到feature_A分支; 1.master分支提交的记录 2.fea…...

华为关键词覆盖应用市场ASO优化覆盖技巧
在我国的消费者群体当中,华为的品牌形象较高,且产品质量过硬,因此用户基数也大。与此同时,随着影响力的增大,华为不断向外扩张,也逐渐成为了海外市场的香饽饽。作为开发者和运营者,我们要认识到…...

蓝桥杯第 23 场 小白入门赛
一、前言 好久没打蓝桥杯官网上的比赛了,回来感受一下,这难度区分度还是挺大的 二、题目总览 三、具体题目 3.1 1. 三体时间【算法赛】 思路 额...签到题 我的代码 // Problem: 1. 三体时间【算法赛】 // Contest: Lanqiao - 第 23 场 小白入门赛 …...

rest-assured multiPart上传中文名称文件,文件名乱码
rest-assured是一个基于java语言的REST API测试框架,在使用rest-assured的multipart 上传文件后,后端获取的文件名称乱码。截图如下: 原因是rest-assured multipart/form-data默认的编码格式是US-ASCII,需要设置为UTF-8。 Befo…...

CSFramework.EF高级应用: ASP.NETCore/WebApi使用动态代理技术创建多个IDatabase数据库实例
通过DI依赖注入IDatabase扩展接口,在.NET项目中使用多个数据库实例 目录 内容简介创建数据库扩展接口(继承IDatabase接口)注入IDatabase扩展接口 AddDatabase 扩展方法UseDatabase 扩展方法数据库配置文件 appsettings.json 配置文件Databas…...

神经网络入门实战:(九)分类问题 → 神经网络模型搭建模版和训练四步曲
(一) 神经网络模型搭建官方文档 每一层基本都有权重和偏置,可以仔细看官方文档。 pytorch 官网的库:torch.nn — PyTorch 2.5 documentation Containers库:用来搭建神经网络框架(包含所有的神经网络的框架);…...

Unity网络框架对比 Mirror|FishNet|NGO
在Unity中制作非单机项目常用的免费网络框架,这里选取了三款比较火的网络框架,Mirror、FishNet和Netcode for GameObject(NGO)。 比较了最常用的免费网络解决方案。可能还有值得探索的付费选项。您需要对此进行自己的研究。数据表格更新日志截止到&#…...

深入了解阿里云 OSS:强大的云存储解决方案
在现代互联网应用中,数据存储是一个不可忽视的环节。随着数据量的不断增长,传统的存储方式已经无法满足高速、低成本、大容量的需求。阿里云 OSS(对象存储服务)作为一种高性能、低成本且具备高度扩展性的云存储服务,已…...

ansible使用说明
将安装包拷贝到主控端主机 在主控端主机安装ansible,sh setup.sh 确认安装成功后,编辑hosts文件(按步骤逐个添加主机组,不要一开始全部配置好) [site-init]下的主机列表为被控制的主机(按照当前ai建模方案…...

Qt 2D绘图之四:绘图中的其他问题
参考文章链接: Qt 2D绘图之四:绘图中的其他问题 重绘事件 前面讲到的所有绘制操作都是在重绘事件处理函数paintEvent()中完成的,是QWidget类中定义的函数。一个重绘事件用来重绘一个部件的全部或者部分区域,下面几个原因中的任意一个都会发生重绘事件: repaint()函数或者…...

启动中断函数HAL_TIM_Base_Start_IT()
一、这是定时器中断: 前面的NVIC是我们在正常使能定时器的中断,后面的HAL_TIM_Base_Start_IT(&htim2)才是我们真正启动中断。 启动定时器的中断并不会立刻进入中断函数。它只是启动定时器并使能定时器的中断。中断函数(例如 TIM2_IRQHan…...

Docker Buildx 与 CNB 多平台构建实践
一、Docker Buildx 功能介绍 docker buildx 是 Docker 提供的一个增强版构建工具,支持更强大的构建功能,特别是在构建多平台镜像和高效处理复杂 Docker 镜像方面。 1.1 主要功能 多平台构建支持 使用 docker buildx,可以在单台设备上构建…...

从Apache Solr 看 Velocity 模板注入
前言 学过 freemaker,学过 Thymeleaf 模板注入,但是还没有学过 Velocity 模板注入,然后学习一个知识最好的方法就是要找一个实际中的例子去学习,好巧不巧,前端时间还在分析 apache solr 的 cve,这次又搜到…...

Spring 事务和事务传播机制
Spring 事务和事务传播机制 一、Spring 事务的基本概念 事务是一组操作,被视为一个不可分割的工作单元,要么全部完成,要么全部失败回滚,以此来确保数据的一致性和完整性。Spring事务管理允许我们在应用程序中声明式地或编程式地…...

flutter 解决webview加载重定向h5页面 返回重复加载问题
long time no see. 如果觉得该方案helps,点个赞,评论打个call,这是我前进的动力~ 通常写法: 项目里用的webview_flutter 正常webview处理返回事件 if (await controller.canGoBack()) {controller.goBack(); } else {Navigator…...

STM32的寄存器是几位的?
STM32的“32”顾名思义就是32位的意思 但是STM32 的寄存器并不都是 32 位的,它们的位宽取决于具体的寄存器和处理器架构。STM32 是基于 ARM Cortex-M 系列内核的微控制器,而这些内核的寄存器通常有不同的位宽。 具体来说,STM32 微控制器的寄…...

基于python的汽车数据爬取数据分析与可视化
一、研究背景 基于提供的代码片段和讨论,我们可以得出一个与网络抓取、数据处理和数据可视化相关的研究背景,该背景涉及到汽车行业。以下是研究背景的陈述: "在迅速发展的汽车行业中,准确和及时的数据对各方利益相关者至关…...

使用mtools搭建MongoDB复制集和分页集群
mtools介绍 mtools是一套基于Python实现的MongoDB工具集,其包括MongoDB日志分析、报表生成及简易的数据库安装等功能。它由MongoDB原生的工程师单独发起并做开源维护,目前已经有大量的使用者。 mtools所包含的一些常用组件如下: mlaunch支…...

Redis(配置文件属性解析)
一、tcp-backlog深度解析 tcp-backlog是一个TCP连接的队列,主要用于解决高并发场景下客户端慢连接问题。配置文件中的“511”就是队列的长度,对联与TCP的三次握手有关,不同的linux内核,backlog队列中存放的元素(客户端…...

思维导图+实现一个登录窗口界面
QQ2024122-205851 import sys from PyQt6.QtGui import QIcon, QPixmap, QMovie from PyQt6.QtWidgets import QApplication, QWidget, QLineEdit, QPushButton, QLabel, QVBoxLayout# 封装我的窗口类 class LoginWidget(QWidget):# 构造函数def __init__(self):# 初始化父类su…...