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()函数或者…...
docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
Yolov8 目标检测蒸馏学习记录
yolov8系列模型蒸馏基本流程,代码下载:这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中,**知识蒸馏(Knowledge Distillation)**被广泛应用,作为提升模型…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
