本文链接:https://blog.csdn.net/qq_26909801/article/details/96966372
数值型坐标轴刻度计算算法
前言
算法描述
上代码
代码运行效果
结语
前言
因实习的公司是做大数据的,而我的工作刚好又是需要绘制一些数据图表的。绘制图表有许多现成的组件可以使用,但是要想达到产品所需要的效果,只靠组件内部的一些功能是不太够的。一些细腻的要求必须在掌握组件原理方法的情况下,自己去写算法来完成。例如,本文要说的这个刻度计算算法,开始正文之前,我先描述遇到的问题。
echarts自身的刻度计算有时候并不好用,例如有时候你希望让图表只有5条刻度线,即分成4段,echarts提供了一个参数叫splitNumber,把splitNumber设为4可以让图表尽量地分成4段,然而当数据波动较大时,echarts会自动增加分割的段数,即即使大部分数据都能正常分出4段刻度,但仍有少部分数据实际分出的段数可能不止4段。如下面这样:
因此我们得出一个结论,echarts的splitNumber只是预估的分割段数。如果我们需要强制把刻度区间分为4段,则需要我们自己去写算法计算刻度。另外,即使段数正确的情况下,echarts自动计算出的刻度也可能存在区间过大,数据差异不明显的情况,如下面的图片,因为刻度区间太大,导致各个数据看起来像是差不多大的,看不出差异:
另外,echarts自动计算出的刻度也有一些其他的问题,例如当图表在柱状图和堆叠图中切换时,堆叠图可能出现刻度溢出问题。不过堆叠图的刻度计算这里就先不说明了,下面开始正文吧。
算法描述
刻度计算的算法之前我之前也写了一版,解决了分割段数的问题,但是仍无法解决刻度区间过大的问题。之前那一版算法的主要思想是取近似值,分别取最大值和最小值的最小近似整值得到刻度,虽然不是最优的算法,但是在构思和调整算法的时候我也学到了不少东西,而这一版的算法是在我们技术老大的点拨下结合网上的一些文章和项目的需求而写出来的,算法如下:
要求: 根据一组数的最大值、最小值来确定刻度值,确定刻度的最大值maxi、最小值mini和刻度间隔interval。当出现异号数据时可选择正负两边刻度是否需要对称,当存在异号数据时要求其中一条刻度分割线必须在0刻度上,可以选择是否允许段数误差。
确定分割段数splitNumber和魔数数组magic = [10,15,20,25,30,40,50,60,70,80,90,100];
从目标数组arr中算出最大值max和最小值min,确定初始间隔大小 tempGap = (max-min)/splitNumber;
设tempGap除以一个倍数multiple后,刚好处于魔数数组的区间[10,100]中,记录倍数multiple;
从魔数数组中取出第一个大于tempGap缩放值的魔数,用 魔数*multiple当做理想刻度间隔(estep)进行第一次计算,计算出max和min的邻近刻度maxi和mini,如果允许分割段数误差,则直接结束运算,取interval=estep;
当刻度需要正负两边对称且存在异号数据时,取maxi和mini中绝对值大的一方,将其相反数赋值给另外一方,计算interval=(maxi-mini)/splitNumber,结束运算;
当正负刻度不需要对称或不存在异号数据时,判断实际分割段数是否等于splitNumber,如果不相等,则重新取较大的魔数进行运算,当魔数取完或者分割段数相等时结束运算,得出interval=(maxi-mini)/splitNumber.
上代码
算法采用javascript语言描述,因为图表的绘制在前端完成。
/*
刻度计算算法,基于魔术数组 [10, 15, 20, 25, 30, 40, 50, 60, 70, 80, 90, 100];
解释:魔数数组是理想间隔数组,即我们希望每个刻度的间隔都是魔数数组中某个数的整数倍。(准确的来说是整10倍)
*/
//新增,解决js的浮点数存在精度问题,在计算出最后结果时可以四舍五入一次,因为刻度太小也没有意义,所以这里忽略设置精度为8位
function fixedNum(num){
if((""+num).indexOf('.')>=0) num = parseFloat(num.toFixed(8));
return num;
}
//1.初始化
var symmetrical = false;//是否要求正负刻度对称。默认为false,需要时请设置为true
var deviation = false;//是否允许误差,即实际分出的段数不等于splitNumber
var magic = [10, 15, 20, 25, 30, 40, 50, 60, 70, 80, 90, 100];//魔数数组经过扩充,放宽魔数限制避免出现取不到魔数的情况。
var arr = [1230, 320, 20, 304, 102, 234];//测试数据
var max,min,splitNumber;
splitNumber = 4;//理想的刻度间隔段数,即希望刻度区间有多少段
max = Math.max.apply(null,arr);//调用js已有函数计算出最大值
min = Math.min.apply(null,arr);//计算出最小值
//2.计算出初始间隔tempGap和缩放比例multiple
var tempGap = (max - min) / splitNumber;//初始刻度间隔的大小。
//设tempGap除以multiple后刚刚处于魔数区间内,先求multiple的幂10指数,例如当tempGap为120,想要把tempGap映射到魔数数组(即处理为10到100之间的数),则倍数为10,即10的1次方。
var multiple = Math.floor(Math.log10(tempGap)-1);//这里使用Math.floor的原因是,当Math.log10(tempGap)-1无论是正负数都需要向下取整。不能使用parseInt或其他取整逻辑代替。
multiple = Math.pow(10,multiple);//刚才是求出指数,这里求出multiple的实际值。分开两行代码避免有人看不懂
//3.取出邻近较大的魔数执行第一次计算
var tempStep = tempGap / multiple;//映射后的间隔大小
var estep;//期望得到的间隔
var lastIndex = -1;//记录上一次取到的魔数下标,避免出现死循环
for(var i = 0; i < magic.length;i++){
if(magic[i]>tempStep){
estep = magic[i]*multiple;//取出第一个大于tempStep的魔数,并乘以multiple作为期望得到的最佳间隔
break;
}
}
//4.求出期望的最大刻度和最小刻度,为estep的整数倍
var maxi,mini;
function countDegree(estep){
//这里的parseInt是我无意中写出来的,本来我是想对maxi使用Math.floor,对mini使用Math.ceil的。这样能向下取到邻近的一格,不过后面发现用parseInt好像画出来图的比较好看
maxi = parseInt(max/estep+1) * estep;//最终效果是当max/estep属于(-1,Infinity)区间时,向上取1格,否则取2格。
mini = parseInt(min/estep-1) * estep;//当min/estep属于(-Infinity,1)区间时,向下取1格,否则取2格。
//如果max和min刚好在刻度线的话,则按照上面的逻辑会向上或向下多取一格
if(max===0) maxi = 0;//这里进行了一次矫正,优先取到0刻度
if(min===0) mini = 0;
if(symmetrical&&maxi*mini<0){//如果需要正负刻度对称且存在异号数据
var tm = Math.max(Math.abs(maxi),Math.abs(mini));//取绝对值较大的一方
maxi = tm;
mini = -tm;
}
}
countDegree(estep);
if(deviation){//如果允许误差,即实际分段数可以不等于splitNumber,则直接结束
var interval = fixedNum(estep);
console.log(maxi,mini,interval);
return;
}
//5.当正负刻度不对称且0刻度不在刻度线上时,重新取魔数进行计算//确保其中一条分割线刚好在0刻度上。
else if(!symmetrical||maxi*mini>0){
outter:do{
//计算模拟的实际分段数
var tempSplitNumber = Math.round((maxi-mini)/estep);
//当趋势单调性发生变化时可能出现死循环,需要进行校正
if((i-lastIndex)*(tempSplitNumber-splitNumber)<0){//此处检查单调性变化且未取到理想分段数
//此处的校正基于合理的均匀的魔数数组,即tempSplitNumber和splitNumber的差值较小如1和2,始终取大刻度
while(tempSplitNumber<splitNumber){//让maxi或mini增大或减少一个estep直到取到理想分段数
if((mini-min)<=(maxi-max)&&mini!=0||maxi==0){//在尽量保留0刻度的前提下,让更接近最值的一边扩展一个刻度
mini-=estep;
}else{
maxi+=estep;
}
tempSplitNumber++;
if(tempSplitNumber==splitNumber)
break outter;
}
}
//当魔数下标越界或取到理想分段数时退出循环
if(i>=magic.length-1|| i<=0 || tempSplitNumber==splitNumber) break;
//记录上一次的魔数下标
lastIndex = i;
//尝试取符合趋势的邻近魔数
if(tempSplitNumber>splitNumber) estep = magic[++i]*multiple;
else estep = magic[--i]*multiple;
//重新计算刻度
countDegree(estep);
}while(tempSplitNumber!=splitNumber);
}
//6.无论计算始终把maxi-mini分成splitNumber段,得到间隔interval。不过前面的算法已经尽量的保证刻度最优了,即interval接近或等于理想刻度estep。
maxi = fixedNum(maxi);
mini = fixedNum(mini);
var interval = fixedNum((maxi-mini)/splitNumber);
console.log(maxi,mini,interval);
代码运行效果
1.如果不处理小数误差,且强制分为4段,出来的效果是这样的(20190722版):
2.处理了小数误差,并允许刻度误差出来的效果是这样的(20190723版)
可以看出:
采用基于魔数数组的新算法计算出的刻度区间是紧挨着最大值和最小值的,算是差强人意。
js中浮点数的精度问题是我们在设计一些通用性的算法时需要注意的,图片右边可以看到,当数据幅度较小时,算出的min和interval是存在误差的。
图片左边的刻度的精确度处理是我写的逻辑,当数据为非纯小数时最多只精确到3位小数,纯小数时精确到8位,避免出现刻度过长,echarts并没有自带这个功能。
再附上一张存在异号数据时的效果和一张需要正负刻度对称的效果
可以看出:
正负刻度不对称且其中一条分割线刚好在0刻度上
y轴刻度的40K其实是40000的缩写,算法也是要自己写的,echarts没有提供这个功能。
结语
好久没有写博客,可能是最近比较忙,如果有空的话其实前端有很多东西都可以写一下,今天写这个博客是因为文章篇幅较小,而且我刚好在检查算法,另外这个算法比较有参考意义,就忙里偷闲写出来给大家看看,也方便我自己以后查阅。目前我参与的项目还在开发完善中,关于echarts我也踩了很多坑,针对项目需求编写了一些东西,有机会再写出来大家讨论吧,另外,大家。
走过路过点个赞再走吧。
————————————————
版权声明:本文为CSDN博主「郑伟斌」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_26909801/article/details/96966372
因此我们得出一个结论,echarts的splitNumber只是预估的分割段数。如果我们需要强制把刻度区间分为4段,则需要我们自己去写算法计算刻度。另外,即使段数正确的情况下,echarts自动计算出的刻度也可能存在区间过大,数据差异不明显的情况,如下面的图片,因为刻度区间太大,导致各个数据看起来像是差不多大的,看不出差异:
————————————————
版权声明:本文为CSDN博主「郑伟斌」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_26909801/article/details/96966372
因此我们得出一个结论,echarts的splitNumber只是预估的分割段数。如果我们需要强制把刻度区间分为4段,则需要我们自己去写算法计算刻度。另外,即使段数正确的情况下,echarts自动计算出的刻度也可能存在区间过大,数据差异不明显的情况,如下面的图片,因为刻度区间太大,导致各个数据看起来像是差不多大的,看不出差异:
————————————————
版权声明:本文为CSDN博主「郑伟斌」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_26909801/article/details/96966372
因此我们得出一个结论,echarts的splitNumber只是预估的分割段数。如果我们需要强制把刻度区间分为4段,则需要我们自己去写算法计算刻度。另外,即使段数正确的情况下,echarts自动计算出的刻度也可能存在区间过大,数据差异不明显的情况,如下面的图片,因为刻度区间太大,导致各个数据看起来像是差不多大的,看不出差异:
————————————————
版权声明:本文为CSDN博主「郑伟斌」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_26909801/article/details/96966372
因此我们得出一个结论,echarts的splitNumber只是预估的分割段数。如果我们需要强制把刻度区间分为4段,则需要我们自己去写算法计算刻度。另外,即使段数正确的情况下,echarts自动计算出的刻度也可能存在区间过大,数据差异不明显的情况,如下面的图片,因为刻度区间太大,导致各个数据看起来像是差不多大的,看不出差异:
————————————————
版权声明:本文为CSDN博主「郑伟斌」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_26909801/article/details/96966372
坐标轴刻度取值算法-基于魔数数组-源于echarts的y轴刻度计算需求的更多相关文章
-
[原创][开源] SunnyUI.Net 开发日志:UIBarChart 坐标轴刻度取值算法
_ 在开发UIBarChart的过程中,需要绘制Y轴的刻度,数据作图时,纵横坐标轴刻度范围及刻度值的取法,很大程度上取决于数据的分布.对某一组数据,我们很容易就能知道如何选取这些值才能使图画得漂亮.但 ...
-
echarts 修改y轴刻度间隔问题
其中min.max可以自定义可以动态获取数据 yAxis : [ { type : 'value', axi ...
-
Android JNI编程(四)——C语言多级指针、数组取值、从控制台输入数组
版权声明:本文出自阿钟的博客,转载请注明出处:http://blog.csdn.net/a_zhon/. 目录(?)[+] 一:前面我们介绍了一级指针的相关概念和用发,今天我们就来说一说多级指针. 1 ...
-
Echart自定义y轴刻度信息2
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
-
Winform中自定义添加ZedGraph右键实现设置所有Y轴刻度的上下限
场景 Winforn中实现ZedGraph自定义添加右键菜单项(附源码下载): https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/10 ...
-
Echart自定义y轴刻度信息1
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
-
实用小工具不定期合集(textarea 高度自适应、自动计算Y轴刻度、json转table)
1.textarea高度自适应 这个非常有用,但是网上的解决方案都不尽人意,话不多说,上代码. function auto (elem) { var minHeight = 30 var change ...
-
Winform中设置多条Y轴时新增的Y轴刻度不显示问题解决
场景 Winform中实现ZedGraph的多条Y轴(附源码下载): https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/1001322 ...
-
highcharts 不显示X轴 Y轴 刻度
xAxis: { tickWidth:0, //设置刻度标签宽度 lineColor:'#ffffff',//设置坐标颜色 lineWidth:0, //设置坐标宽度 la ...
随机推荐
-
软件调试——CPU异常列表
CPU异常主要分为三类:错误类异常,陷阱类异常和终止类异常 1 错误类异常 Fault CPU遇到该类异常后,会先将CS和EIP(当前发生错误的指令,而不是下一条指令)压栈,然后跳到异常处理函数中,执 ...
-
3.openstack之mitaka搭建keystone认证服务
认证服务keystone部署 一:安装和配置服务 1.建库建用户 mysql -u root -p CREATE DATABASE keystone; GRANT ALL PRIVILEGES ON ...
-
OpenCV2学习笔记05:矩阵翻转
对图像进行翻转或旋转可以使用cv::flip()函数,可以实现将一个二维矩阵沿X轴.Y轴或者同时沿XY轴翻转.函数原型如下: C++: void flip(InputArray src, Output ...
-
linux环境下tcpdump源代码分析
Linux 环境下tcpdump 源代码分析 韩大卫@吉林师范大学 tcpdump.c 是tcpdump 工具的main.c, 本文旨对tcpdump的框架有简单了解,只展示linux平台使用的一部分 ...
-
关于Unix时间戳
如何在不同编程语言中获取现在的Unix时间戳(Unix timestamp)? Java time JavaScript Math.round(new Date().getTime()/1000)ge ...
-
(简单) POJ 1195 Mobile phones,二维树状数组。
Description Suppose that the fourth generation mobile phone base stations in the Tampere area operat ...
-
Bootstrap 常用组件汇总
Bootstrap 官方文档:http://www.bootcss.com/ Bootstrap Multiselect 多选下拉组件 官方文档:http://www.kuitao8.com/demo ...
-
Python内置函数(15)——dict
英文文档: class dict(**kwarg) class dict(mapping, **kwarg) class dict(iterable, **kwarg) Return a new di ...
-
Hbase CMS GC 调优。
export HBASE_OPTS="-XX:+UseConcMarkSweepGC" export HBASE_LOG_DIR=/app/hbase/logexport HBAS ...
-
flutter学习之二Material Design设计规范
前言: 最近在自学flutter跨平台开发,从学习的过程来看真心感觉不是那么一件特别容易的事.不但要了解语法规则, 还要知晓常用控件,和一些扩展性的外延知识,所以套一句古人的话“路漫漫其修远矣,无将上 ...