您好,登錄后才能下訂單哦!
這篇“echarts的y軸刻度計(jì)算需求實(shí)例分析”文章的知識點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“echarts的y軸刻度計(jì)算需求實(shí)例分析”文章吧。
echarts自身的刻度計(jì)算有時候并不好用,例如有時候你希望讓圖表只有5條刻度線,即分成4段,echarts提供了一個參數(shù)叫splitNumber,把splitNumber設(shè)為4可以讓圖表盡量地分成4段,然而當(dāng)數(shù)據(jù)波動較大時,echarts會自動增加分割的段數(shù),即即使大部分?jǐn)?shù)據(jù)都能正常分出4段刻度,但仍有少部分?jǐn)?shù)據(jù)實(shí)際分出的段數(shù)可能不止4段。如下面這樣:
因此我們得出一個結(jié)論,echarts的splitNumber只是預(yù)估的分割段數(shù)。如果我們需要強(qiáng)制把刻度區(qū)間分為4段,則需要我們自己去寫算法計(jì)算刻度。另外,即使段數(shù)正確的情況下,echarts自動計(jì)算出的刻度也可能存在區(qū)間過大,數(shù)據(jù)差異不明顯的情況,如下面的圖片,因?yàn)榭潭葏^(qū)間太大,導(dǎo)致各個數(shù)據(jù)看起來像是差不多大的,看不出差異:
另外,echarts自動計(jì)算出的刻度也有一些其他的問題,例如當(dāng)圖表在柱狀圖和堆疊圖中切換時,堆疊圖可能出現(xiàn)刻度溢出問題。不過堆疊圖的刻度計(jì)算這里就先不說明了,下面開始正文吧。
刻度計(jì)算的算法之前我之前也寫了一版,解決了分割段數(shù)的問題,但是仍無法解決刻度區(qū)間過大的問題。之前那一版算法的主要思想是取近似值,分別取最大值和最小值的最小近似整值得到刻度,雖然不是最優(yōu)的算法,但是在構(gòu)思和調(diào)整算法的時候我也學(xué)到了不少東西,而這一版的算法是在我們技術(shù)老大的點(diǎn)撥下結(jié)合網(wǎng)上的一些文章和項(xiàng)目的需求而寫出來的,算法如下:
要求: 根據(jù)一組數(shù)的最大值、最小值來確定刻度值,確定刻度的最大值maxi、最小值mini和刻度間隔interval。當(dāng)出現(xiàn)異號數(shù)據(jù)時可選擇正負(fù)兩邊刻度是否需要對稱,當(dāng)存在異號數(shù)據(jù)時要求其中一條刻度分割線必須在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除以一個倍數(shù)multiple后,剛好處于魔數(shù)數(shù)組的區(qū)間[10,100]中,記錄倍數(shù)multiple;
從魔數(shù)數(shù)組中取出第一個大于tempGap縮放值的魔數(shù),用 魔數(shù)*multiple當(dāng)做理想刻度間隔(estep)進(jìn)行第一次計(jì)算,計(jì)算出max和min的鄰近刻度maxi和mini,如果允許分割段數(shù)誤差,則直接結(jié)束運(yùn)算,取interval=estep;
當(dāng)刻度需要正負(fù)兩邊對稱且存在異號數(shù)據(jù)時,取maxi和mini中絕對值大的一方,將其相反數(shù)賦值給另外一方,計(jì)算interval=(maxi-mini)/splitNumber,結(jié)束運(yùn)算;
當(dāng)正負(fù)刻度不需要對稱或不存在異號數(shù)據(jù)時,判斷實(shí)際分割段數(shù)是否等于splitNumber,如果不相等,則重新取較大的魔數(shù)進(jìn)行運(yùn)算,當(dāng)魔數(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ù)組,即我們希望每個刻度的間隔都是魔數(shù)數(shù)組中某個數(shù)的整數(shù)倍。(準(zhǔn)確的來說是整10倍) */ //新增,解決js的浮點(diǎn)數(shù)存在精度問題,在計(jì)算出最后結(jié)果時可以四舍五入一次,因?yàn)榭潭忍∫矝]有意義,所以這里忽略設(shè)置精度為8位 function fixedNum(num){ if((""+num).indexOf('.')>=0) num = parseFloat(num.toFixed(8)); return num; } //1.初始化 var symmetrical = false;//是否要求正負(fù)刻度對稱。默認(rèn)為false,需要時請?jiān)O(shè)置為true var deviation = false;//是否允許誤差,即實(shí)際分出的段數(shù)不等于splitNumber var magic = [10, 15, 20, 25, 30, 40, 50, 60, 70, 80, 90, 100];//魔數(shù)數(shù)組經(jīng)過擴(kuò)充,放寬魔數(shù)限制避免出現(xiàn)取不到魔數(shù)的情況。 var arr = [1230, 320, 20, 304, 102, 234];//測試數(shù)據(jù) var max,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 var tempGap = (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次方。 var multiple = 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ì)算 var tempStep = tempGap / multiple;//映射后的間隔大小 var estep;//期望得到的間隔 var lastIndex = -1;//記錄上一次取到的魔數(shù)下標(biāo),避免出現(xiàn)死循環(huán) for(var i = 0; i < magic.length;i++){ if(magic[i]>tempStep){ estep = magic[i]*multiple;//取出第一個大于tempStep的魔數(shù),并乘以multiple作為期望得到的最佳間隔 break; } } //4.求出期望的最大刻度和最小刻度,為estep的整數(shù)倍 var maxi,mini; function countDegree(estep){ //這里的parseInt是我無意中寫出來的,本來我是想對maxi使用Math.floor,對mini使用Math.ceil的。這樣能向下取到鄰近的一格,不過后面發(fā)現(xiàn)用parseInt好像畫出來圖的比較好看 maxi = parseInt(max/estep+1) * estep;//最終效果是當(dāng)max/estep屬于(-1,Infinity)區(qū)間時,向上取1格,否則取2格。 mini = parseInt(min/estep-1) * estep;//當(dāng)min/estep屬于(-Infinity,1)區(qū)間時,向下取1格,否則取2格。 //如果max和min剛好在刻度線的話,則按照上面的邏輯會向上或向下多取一格 if(max===0) maxi = 0;//這里進(jìn)行了一次矯正,優(yōu)先取到0刻度 if(min===0) mini = 0; if(symmetrical&&maxi*mini<0){//如果需要正負(fù)刻度對稱且存在異號數(shù)據(jù) var tm = Math.max(Math.abs(maxi),Math.abs(mini));//取絕對值較大的一方 maxi = tm; mini = -tm; } } countDegree(estep); if(deviation){//如果允許誤差,即實(shí)際分段數(shù)可以不等于splitNumber,則直接結(jié)束 var interval = fixedNum(estep); console.log(maxi,mini,interval); return; } //5.當(dāng)正負(fù)刻度不對稱且0刻度不在刻度線上時,重新取魔數(shù)進(jìn)行計(jì)算//確保其中一條分割線剛好在0刻度上。 else if(!symmetrical||maxi*mini>0){ outter:do{ //計(jì)算模擬的實(shí)際分段數(shù) var tempSplitNumber = Math.round((maxi-mini)/estep); //當(dāng)趨勢單調(diào)性發(fā)生變化時可能出現(xiàn)死循環(huán),需要進(jìn)行校正 if((i-lastIndex)*(tempSplitNumber-splitNumber)<0){//此處檢查單調(diào)性變化且未取到理想分段數(shù) //此處的校正基于合理的均勻的魔數(shù)數(shù)組,即tempSplitNumber和splitNumber的差值較小如1和2,始終取大刻度 while(tempSplitNumber<splitNumber){//讓maxi或mini增大或減少一個estep直到取到理想分段數(shù) if((mini-min)<=(maxi-max)&&mini!=0||maxi==0){//在盡量保留0刻度的前提下,讓更接近最值的一邊擴(kuò)展一個刻度 mini-=estep; }else{ maxi+=estep; } tempSplitNumber++; if(tempSplitNumber==splitNumber) break outter; } } //當(dāng)魔數(shù)下標(biāo)越界或取到理想分段數(shù)時退出循環(huán) if(i>=magic.length-1|| i<=0 || tempSplitNumber==splitNumber) break; //記錄上一次的魔數(shù)下標(biāo) lastIndex = i; //嘗試取符合趨勢的鄰近魔數(shù) if(tempSplitNumber>splitNumber) estep = magic[++i]*multiple; else estep = 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); var interval = fixedNum((maxi-mini)/splitNumber); console.log(maxi,mini,interval);
1.如果不處理小數(shù)誤差,且強(qiáng)制分為4段,出來的效果是這樣的(20190722版):
2.處理了小數(shù)誤差,并允許刻度誤差出來的效果是這樣的(20190723版)
可以看出:
采用基于魔數(shù)數(shù)組的新算法計(jì)算出的刻度區(qū)間是緊挨著最大值和最小值的,算是差強(qiáng)人意。
js中浮點(diǎn)數(shù)的精度問題是我們在設(shè)計(jì)一些通用性的算法時需要注意的,圖片右邊可以看到,當(dāng)數(shù)據(jù)幅度較小時,算出的min和interval是存在誤差的。
圖片左邊的刻度的精確度處理是我寫的邏輯,當(dāng)數(shù)據(jù)為非純小數(shù)時最多只精確到3位小數(shù),純小數(shù)時精確到8位,避免出現(xiàn)刻度過長,echarts并沒有自帶這個功能。
再附上一張存在異號數(shù)據(jù)時的效果和一張需要正負(fù)刻度對稱的效果
可以看出:
正負(fù)刻度不對稱且其中一條分割線剛好在0刻度上
y軸刻度的40K其實(shí)是40000的縮寫,算法也是要自己寫的,echarts沒有提供這個功能。
export interface ScaleOption { /** * 數(shù)據(jù)最大值 * * @type {(number | null)} * @memberof ScaleOption */ max: number | null; /** * 數(shù)據(jù)最小值 * * @type {(number | null)} * @memberof ScaleOption */ min: number | null; /** * 預(yù)期分成幾個區(qū)間 * * @type {number} * @memberof ScaleOption */ splitNumber?: number; /** * 存在異號數(shù)據(jù)時正負(fù)區(qū)間是否需要對稱 * * @type {boolean} * @memberof ScaleOption */ symmetrical?: boolean; /** * 是否允許實(shí)際分成的區(qū)間數(shù)有誤差 * * @type {boolean} * @memberof ScaleOption */ deviation?: boolean; /** * 是否優(yōu)先取到0刻度 * * @type {boolean} * @memberof ScaleOption */ preferZero?: boolean; } export interface ScaleResult { max?: number; min?: number; interval?: number; splitNumber?: number; } // 雙精度浮點(diǎn)數(shù)有效數(shù)字為15位 const maxDecimal = 15; /** * 解決js的浮點(diǎn)數(shù)存在精度問題,在計(jì)算出最后結(jié)果時可以四舍五入一次,刻度太小也沒有意義 * * @export * @param {(number | string)} num * @param {number} [decimal=8] * @returns {number} */ export function fixedNum(num: number | string, decimal: number = maxDecimal): number { let str: string = "" + num; if (str.indexOf(".") >= 0) str = Number.parseFloat(str).toFixed(decimal); return Number.parseFloat(str); } /** * 判斷非Infinity非NaN的number * * @export * @param {*} num * @returns {num is number} */ export function numberValid(num: any): num is number { return typeof num === "number" && Number.isFinite(num); } /** * 計(jì)算理想的刻度值,刻度區(qū)間大小一般是[10, 15, 20, 25, 30, 40, 50, 60, 70, 80, 90, 100]中某個數(shù)字的整10倍 * * @export * @param {ScaleOption} option * @returns {ScaleResult} */ export function scaleCompute(option: ScaleOption): ScaleResult { option = { max: null, min: null, splitNumber: 4, // splitNumber建議取4或者5等這種容易被整除的數(shù)字 symmetrical: false, deviation: false, preferZero: true, ...option, }; const magics: 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) || dataMax < dataMin) { return { splitNumber }; } else if (dataMax === dataMin && dataMax === 0) { return { max: fixedNum(magics[0] * splitNumber), min: dataMin, interval: magics[0], splitNumber, }; } else if (dataMax === dataMin) { preferZero = true; } if (!numberValid(splitNumber) || splitNumber <= 0) splitNumber = 4; if (preferZero && dataMax * dataMin > 0) { if (dataMax < 0) dataMax = 0; else dataMin = 0; } const tempGap: number = (dataMax - dataMin) / splitNumber; let multiple: number = Math.floor(Math.log10(tempGap) - 1); // 指數(shù) multiple = Math.pow(10, multiple); const tempStep: number = tempGap / multiple; let expectedStep: number = magics[0] * multiple; let storedMagicsIndex: number = -1; let index: number; // 當(dāng)前魔數(shù)下標(biāo) for (index = 0; index < magics.length; index++) { if (magics[index] > tempStep) { expectedStep = magics[index] * multiple; // 取出第一個大于tempStep的魔數(shù),并乘以multiple作為期望得到的最佳間隔 break; } } let axisMax: number = dataMax; let axisMin: number = dataMin; function countDegree(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 (symmetrical && axisMax * axisMin < 0) { const tm: 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), }; } else if (!symmetrical || axisMax * axisMin > 0) { let tempSplitNumber: number; out: do { tempSplitNumber = Math.round((axisMax - axisMin) / expectedStep); if ((index - storedMagicsIndex) * (tempSplitNumber - splitNumber) < 0) { // 出現(xiàn)死循環(huán) while (tempSplitNumber < splitNumber) { if ((axisMin - dataMin <= axisMax - dataMax && axisMin !== 0) || axisMax === 0) { axisMin -= expectedStep; } else { axisMax += expectedStep; } tempSplitNumber++; if (tempSplitNumber === splitNumber) break out; } } if (index >= magics.length - 1 || index <= 0 || tempSplitNumber === splitNumber) break; storedMagicsIndex = index; if (tempSplitNumber > splitNumber) expectedStep = magics[++index] * multiple; else expectedStep = magics[--index] * multiple; countDegree(expectedStep); } while (tempSplitNumber !== splitNumber); } axisMax = fixedNum(axisMax); axisMin = fixedNum(axisMin); const interval: number = fixedNum((axisMax - axisMin) / splitNumber); return { max: axisMax, min: axisMin, interval, splitNumber, }; }
以上就是關(guān)于“echarts的y軸刻度計(jì)算需求實(shí)例分析”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對大家有幫助,若想了解更多相關(guān)的知識內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。