版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
第基于JavaScript實(shí)現(xiàn)數(shù)值型坐標(biāo)軸刻度計(jì)算算法(echarts的y軸刻度計(jì)算)目錄前言算法描述代碼ts版本(2025/3/10補(bǔ)充)結(jié)語
前言
因?qū)嵙?xí)的公司是做大數(shù)據(jù)的,而我的工作剛好又是需要繪制一些數(shù)據(jù)圖表的。繪制圖表有許多現(xiàn)成的組件可以使用,但是要想達(dá)到產(chǎn)品所需要的效果,只靠組件內(nèi)部的一些功能是不太夠的。一些細(xì)膩的要求必須在掌握組件原理方法的情況下,自己去寫算法來完成。例如,本文要說的這個(gè)刻度計(jì)算算法,開始正文之前,我先描述遇到的問題。
echarts自身的刻度計(jì)算有時(shí)候并不好用,例如有時(shí)候你希望讓圖表只有5條刻度線,即分成4段,echarts提供了一個(gè)參數(shù)叫splitNumber,把splitNumber設(shè)為4可以讓圖表盡量地分成4段,然而當(dāng)數(shù)據(jù)波動(dòng)較大時(shí),echarts會(huì)自動(dòng)增加分割的段數(shù),即即使大部分?jǐn)?shù)據(jù)都能正常分出4段刻度,但仍有少部分?jǐn)?shù)據(jù)實(shí)際分出的段數(shù)可能不止4段。
如下面這樣:
因此我們得出一個(gè)結(jié)論,echarts的splitNumber只是預(yù)估的分割段數(shù)。如果我們需要強(qiáng)制把刻度區(qū)間分為4段,則需要我們自己去寫算法計(jì)算刻度。另外,即使段數(shù)正確的情況下,echarts自動(dòng)計(jì)算出的刻度也可能存在區(qū)間過大,數(shù)據(jù)差異不明顯的情況,如下面的圖片,因?yàn)榭潭葏^(qū)間太大,導(dǎo)致各個(gè)數(shù)據(jù)看起來像是差不多大的,看不出差異:
另外,echarts自動(dòng)計(jì)算出的刻度也有一些其他的問題,例如當(dāng)圖表在柱狀圖和堆疊圖中切換時(shí),堆疊圖可能出現(xiàn)刻度溢出問題。不過堆疊圖的刻度計(jì)算這里就先不說明了,下面開始正文吧。
算法描述
刻度計(jì)算的算法之前我之前也寫了一版,解決了分割段數(shù)的問題,但是仍無法解決刻度區(qū)間過大的問題。之前那一版算法的主要思想是取近似值,分別取最大值和最小值的最小近似整值得到刻度,雖然不是最優(yōu)的算法,但是在構(gòu)思和調(diào)整算法的時(shí)候我也學(xué)到了不少東西,而這一版的算法是在我們技術(shù)老大的點(diǎn)撥下結(jié)合網(wǎng)上的一些文章和項(xiàng)目的需求而寫出來的,算法如下
要求:根據(jù)一組數(shù)的最大值、最小值來確定刻度值,確定刻度的最大值maxi、最小值mini和刻度間隔interval。當(dāng)出現(xiàn)異號(hào)數(shù)據(jù)時(shí)可選擇正負(fù)兩邊刻度是否需要對(duì)稱,當(dāng)存在異號(hào)數(shù)據(jù)時(shí)要求其中一條刻度分割線必須在0刻度上,可以選擇是否允許段數(shù)誤差。
確定分割段數(shù)splitNumber和魔數(shù)數(shù)組magic=[10,15,20,25,30,40,50,60,70,80,90,100];從目標(biāo)數(shù)組arr中算出最大值max和最小值min,確定初始間隔大小tempGap=(max-min)/splitNumber;設(shè)tempGap除以一個(gè)倍數(shù)multiple后,剛好處于魔數(shù)數(shù)組的區(qū)間[10,100]中,記錄倍數(shù)multiple;從魔數(shù)數(shù)組中取出第一個(gè)大于tempGap縮放值的魔數(shù),用魔數(shù)*multiple當(dāng)做理想刻度間隔(estep)進(jìn)行第一次計(jì)算,計(jì)算出max和min的鄰近刻度maxi和mini,如果允許分割段數(shù)誤差,則直接結(jié)束運(yùn)算,取interval=estep;當(dāng)刻度需要正負(fù)兩邊對(duì)稱且存在異號(hào)數(shù)據(jù)時(shí),取maxi和mini中絕對(duì)值大的一方,將其相反數(shù)賦值給另外一方,計(jì)算interval=(maxi-mini)/splitNumber,結(jié)束運(yùn)算;當(dāng)正負(fù)刻度不需要對(duì)稱或不存在異號(hào)數(shù)據(jù)時(shí),判斷實(shí)際分割段數(shù)是否等于splitNumber,如果不相等,則重新取較大的魔數(shù)進(jìn)行運(yùn)算,當(dāng)魔數(shù)取完或者分割段數(shù)相等時(shí)結(jié)束運(yùn)算,得出interval=(maxi-mini)/splitNumber.
代碼
算法采用javascript語言描述,因?yàn)閳D表的繪制在前端完成:
/*
刻度計(jì)算算法,基于魔術(shù)數(shù)組[10,15,20,25,30,40,50,60,70,80,90,100];
解釋:魔數(shù)數(shù)組是理想間隔數(shù)組,即我們希望每個(gè)刻度的間隔都是魔數(shù)數(shù)組中某個(gè)數(shù)的整數(shù)倍。(準(zhǔn)確的來說是整10倍)
//新增,解決js的浮點(diǎn)數(shù)存在精度問題,在計(jì)算出最后結(jié)果時(shí)可以四舍五入一次,因?yàn)榭潭忍∫矝]有意義,所以這里忽略設(shè)置精度為8位
functionfixedNum(num){
if((""+num).indexOf('.')=0)num=parseFloat(num.toFixed(8));
returnnum;
//1.初始化
varsymmetrical=false;//是否要求正負(fù)刻度對(duì)稱。默認(rèn)為false,需要時(shí)請(qǐng)?jiān)O(shè)置為true
vardeviation=false;//是否允許誤差,即實(shí)際分出的段數(shù)不等于splitNumber
varmagic=[10,15,20,25,30,40,50,60,70,80,90,100];//魔數(shù)數(shù)組經(jīng)過擴(kuò)充,放寬魔數(shù)限制避免出現(xiàn)取不到魔數(shù)的情況。
vararr=[1230,320,20,304,102,234];//測(cè)試數(shù)據(jù)
varmax,min,splitNumber;
splitNumber=4;//理想的刻度間隔段數(shù),即希望刻度區(qū)間有多少段
max=Math.max.apply(null,arr);//調(diào)用js已有函數(shù)計(jì)算出最大值
min=Math.min.apply(null,arr);//計(jì)算出最小值
//2.計(jì)算出初始間隔tempGap和縮放比例multiple
vartempGap=(max-min)/splitNumber;//初始刻度間隔的大小。
//設(shè)tempGap除以multiple后剛剛處于魔數(shù)區(qū)間內(nèi),先求multiple的冪10指數(shù),例如當(dāng)tempGap為120,想要把tempGap映射到魔數(shù)數(shù)組(即處理為10到100之間的數(shù)),則倍數(shù)為10,即10的1次方。
varmultiple=Math.floor(Math.log10(tempGap)-1);//這里使用Math.floor的原因是,當(dāng)Math.log10(tempGap)-1無論是正負(fù)數(shù)都需要向下取整。不能使用parseInt或其他取整邏輯代替。
multiple=Math.pow(10,multiple);//剛才是求出指數(shù),這里求出multiple的實(shí)際值。分開兩行代碼避免有人看不懂
//3.取出鄰近較大的魔數(shù)執(zhí)行第一次計(jì)算
vartempStep=tempGap/multiple;//映射后的間隔大小
varestep;//期望得到的間隔
varlastIndex=-1;//記錄上一次取到的魔數(shù)下標(biāo),避免出現(xiàn)死循環(huán)
for(vari=0;imagic.length;i++){
if(magic[i]tempStep){
estep=magic[i]*multiple;//取出第一個(gè)大于tempStep的魔數(shù),并乘以multiple作為期望得到的最佳間隔
break;
//4.求出期望的最大刻度和最小刻度,為estep的整數(shù)倍
varmaxi,mini;
functioncountDegree(estep){
//這里的parseInt是我無意中寫出來的,本來我是想對(duì)maxi使用Math.floor,對(duì)mini使用Math.ceil的。這樣能向下取到鄰近的一格,不過后面發(fā)現(xiàn)用parseInt好像畫出來圖的比較好看
maxi=parseInt(max/estep+1)*estep;//最終效果是當(dāng)max/estep屬于(-1,Infinity)區(qū)間時(shí),向上取1格,否則取2格。
mini=parseInt(min/estep-1)*estep;//當(dāng)min/estep屬于(-Infinity,1)區(qū)間時(shí),向下取1格,否則取2格。
//如果max和min剛好在刻度線的話,則按照上面的邏輯會(huì)向上或向下多取一格
if(max===0)maxi=0;//這里進(jìn)行了一次矯正,優(yōu)先取到0刻度
if(min===0)mini=0;
if(symmetricalmaxi*mini0){//如果需要正負(fù)刻度對(duì)稱且存在異號(hào)數(shù)據(jù)
vartm=Math.max(Math.abs(maxi),Math.abs(mini));//取絕對(duì)值較大的一方
maxi=tm;
mini=-tm;
countDegree(estep);
if(deviation){//如果允許誤差,即實(shí)際分段數(shù)可以不等于splitNumber,則直接結(jié)束
varinterval=fixedNum(estep);
console.log(maxi,mini,interval);
return;
//5.當(dāng)正負(fù)刻度不對(duì)稱且0刻度不在刻度線上時(shí),重新取魔數(shù)進(jìn)行計(jì)算//確保其中一條分割線剛好在0刻度上。
elseif(!symmetrical||maxi*mini0){
outter:do{
//計(jì)算模擬的實(shí)際分段數(shù)
vartempSplitNumber=Math.round((maxi-mini)/estep);
//當(dāng)趨勢(shì)單調(diào)性發(fā)生變化時(shí)可能出現(xiàn)死循環(huán),需要進(jìn)行校正
if((i-lastIndex)*(tempSplitNumber-splitNumber)0){//此處檢查單調(diào)性變化且未取到理想分段數(shù)
//此處的校正基于合理的均勻的魔數(shù)數(shù)組,即tempSplitNumber和splitNumber的差值較小如1和2,始終取大刻度
while(tempSplitNumbersplitNumber){//讓maxi或mini增大或減少一個(gè)estep直到取到理想分段數(shù)
if((mini-min)=(maxi-max)mini!=0||maxi==0){//在盡量保留0刻度的前提下,讓更接近最值的一邊擴(kuò)展一個(gè)刻度
mini-=estep;
}else{
maxi+=estep;
tempSplitNumber++;
if(tempSplitNumber==splitNumber)
breakoutter;
//當(dāng)魔數(shù)下標(biāo)越界或取到理想分段數(shù)時(shí)退出循環(huán)
if(i=magic.length-1||i=0||tempSplitNumber==splitNumber)break;
//記錄上一次的魔數(shù)下標(biāo)
lastIndex=i;
//嘗試取符合趨勢(shì)的鄰近魔數(shù)
if(tempSplitNumbersplitNumber)estep=magic[++i]*multiple;
elseestep=magic[--i]*multiple;
//重新計(jì)算刻度
countDegree(estep);
}while(tempSplitNumber!=splitNumber);
//6.無論計(jì)算始終把maxi-mini分成splitNumber段,得到間隔interval。不過前面的算法已經(jīng)盡量的保證刻度最優(yōu)了,即interval接近或等于理想刻度estep。
maxi=fixedNum(maxi);
mini=fixedNum(mini);
varinterval=fixedNum((maxi-mini)/splitNumber);
console.log(maxi,mini,interval);
代碼運(yùn)行效果:
1.如果不處理小數(shù)誤差,且強(qiáng)制分為4段,出來的效果是這樣的(20250722版):
2.處理了小數(shù)誤差,并允許刻度誤差出來的效果是這樣的(20250723版)
可以看出:
采用基于魔數(shù)數(shù)組的新算法計(jì)算出的刻度區(qū)間是緊挨著最大值和最小值的,算是差強(qiáng)人意。js中浮點(diǎn)數(shù)的精度問題是我們?cè)谠O(shè)計(jì)一些通用性的算法時(shí)需要注意的,圖片右邊可以看到,當(dāng)數(shù)據(jù)幅度較小時(shí),算出的min和interval是存在誤差的。圖片左邊的刻度的精確度處理是我寫的邏輯,當(dāng)數(shù)據(jù)為非純小數(shù)時(shí)最多只精確到3位小數(shù),純小數(shù)時(shí)精確到8位,避免出現(xiàn)刻度過長,echarts并沒有自帶這個(gè)功能。
再附上一張存在異號(hào)數(shù)據(jù)時(shí)的效果和一張需要正負(fù)刻度對(duì)稱的效果:
可以看出:
正負(fù)刻度不對(duì)稱且其中一條分割線剛好在0刻度上y軸刻度的40K其實(shí)是40000的縮寫,算法也是要自己寫的,echarts沒有提供這個(gè)功能。
ts版本(2025/3/10補(bǔ)充)
exportinterfaceScaleOption{
*數(shù)據(jù)最大值
*@type{(number|null)}
*@memberofScaleOption
max:number|null;
*數(shù)據(jù)最小值
*@type{(number|null)}
*@memberofScaleOption
min:number|null;
*預(yù)期分成幾個(gè)區(qū)間
*@type{number}
*@memberofScaleOption
splitNumber:number;
*存在異號(hào)數(shù)據(jù)時(shí)正負(fù)區(qū)間是否需要對(duì)稱
*@type{boolean}
*@memberofScaleOption
symmetrical:boolean;
*是否允許實(shí)際分成的區(qū)間數(shù)有誤差
*@type{boolean}
*@memberofScaleOption
deviation:boolean;
*是否優(yōu)先取到0刻度
*@type{boolean}
*@memberofScaleOption
preferZero:boolean;
exportinterfaceScaleResult{
max:number;
min:number;
interval:number;
splitNumber:number;
//雙精度浮點(diǎn)數(shù)有效數(shù)字為15位
constmaxDecimal=15;
*解決js的浮點(diǎn)數(shù)存在精度問題,在計(jì)算出最后結(jié)果時(shí)可以四舍五入一次,刻度太小也沒有意義
*@export
*@param{(number|string)}num
*@param{number}[decimal=8]
*@returns{number}
exportfunctionfixedNum(num:number|string,decimal:number=maxDecimal):number{
letstr:string=""+num;
if(str.indexOf(".")=0)str=Number.parseFloat(str).toFixed(decimal);
returnNumber.parseFloat(str);
*判斷非Infinity非NaN的number
*@export
*@param{*}num
*@returns{numisnumber}
exportfunctionnumberValid(num:any):numisnumber{
returntypeofnum==="number"Number.isFinite(num);
*計(jì)算理想的刻度值,刻度區(qū)間大小一般是[10,15,20,25,30,40,50,60,70,80,90,100]中某個(gè)數(shù)字的整10倍
*@export
*@param{ScaleOption}option
*@returns{ScaleResult}
exportfunctionscaleCompute(option:ScaleOption):ScaleResult{
option={
max:null,
min:null,
splitNumber:4,//splitNumber建議取4或者5等這種容易被整除的數(shù)字
symmetrical:false,
deviation:false,
preferZero:true,
...option,
constmagics:number[]=[10,15,20,25,30,40,50,60,70,80,90,100,150];//加入150形成閉環(huán)
//tslint:disable-next-line:prefer-const
let{max:dataMax,min:dataMin,splitNumber,symmetrical,deviation,preferZero}=option;
if(!numberValid(dataMax)||!numberValid(dataMin)||dataMaxdataMin){
return{splitNumber};
}elseif(dataMax===dataMindataMax===0){
return{
max:fixedNum(magics[0]*splitNumber),
min:dataMin,
interval:magics[0],
splitNumber,
}elseif(dataMax===dataMin){
preferZero=true;
if(!numberValid(splitNumber)||splitNumber=0)splitNumber=4;
if(preferZerodataMax*dataMin0){
if(dataMax0)dataMax=0;
elsedataMin=0;
consttempGap:number=(dataMax-dataMin)/splitNumber;
letmultiple:number=Math.floor(Math.log10(tempGap)-1);//指數(shù)
multiple=Math.pow(10,multiple);
consttempStep:number=tempGap/multiple;
letexpectedStep:number=magics[0]*multiple;
letstoredMagicsIndex:number=-1;
letindex:number;//當(dāng)前魔數(shù)下標(biāo)
for(index=0;indexmagics.length;index++){
if(magics[index]tempStep){
expectedStep=magics[index]*multiple;//取出第一個(gè)大于tempStep的魔數(shù),并乘以multiple作為期望得到的最佳間隔
break;
letaxisMax:number=dataMax;
letaxisMin:number=dataMin;
functioncountDegree(step:number):void{
axisMax=parseInt(""+(dataMax/step+1))*step;//parseInt令小數(shù)去尾-1.8--1
axisMin=parseInt(""+(dataMin/step-1))*step;
if(dataMax===0)axisMax=0;//優(yōu)先0刻度
if(dataMin===0)axisMin=0;
if(symmetricalaxisMax*axisMin0){
consttm:number=Math.max(Math.abs(axisMax),Math.abs(axisMin));
axisMax=tm;
axisMin=-tm;
countDegree(expectedStep);
if(deviation){
return{
max:fixedNum(axisMax),
min:fixedNum(axisMin),
interval:fixedNum(expectedStep),
splitNumber:Math.round((axisMax-axisMin)/expectedStep),
}elseif(!symmetrical||axisMax*axisMin0){
lettempSplitNumber:number;
out:do{
tempSplitNumber=Math.round((axisMax-axisMin)/exp
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 魯濱遜題目及答案100道選擇題
- 藥劑科學(xué)習(xí)培訓(xùn)制度
- 阜寧縣中考題目及答案
- 臨考沖刺作文題目及答案
- 養(yǎng)老院老人心理輔導(dǎo)支持制度
- 高三電磁感應(yīng)題目及答案
- 養(yǎng)老院老人康復(fù)設(shè)施維修人員表彰制度
- 養(yǎng)老院老人健康監(jiān)測(cè)人員職業(yè)發(fā)展規(guī)劃制度
- 美團(tuán)酒店考試題目及答案
- 辦公室員工培訓(xùn)記錄與檔案制度
- 冬季交通安全測(cè)試題及答案解析
- 2025年國家能源局系統(tǒng)公務(wù)員面試模擬題及備考指南
- (2025年標(biāo)準(zhǔn))圈內(nèi)認(rèn)主協(xié)議書
- 2025年安徽省中考化學(xué)真題及答案
- 2025年軍隊(duì)文職人員統(tǒng)一招聘面試( 臨床醫(yī)學(xué))題庫附答案
- 海馬體核磁掃描課件
- 某電力股份企業(yè)同熱三期2×100萬千瓦項(xiàng)目環(huán)評(píng)報(bào)告書
- 2026屆上海市部分區(qū)中考一模語文試題含解析
- 中科大人類生態(tài)學(xué)課件2.0 地球·環(huán)境與人
- 數(shù)學(xué) 2024-2025學(xué)年人教版七年級(jí)數(shù)學(xué)下冊(cè)期末+試卷
- 高中英語必背3500單詞表完整版
評(píng)論
0/150
提交評(píng)論