Vue3項目頁面實現(xiàn)echarts圖表漸變色的動態(tài)配置的實現(xiàn)步驟
一、背景
在開發(fā)可配置業(yè)務(wù)平臺時,需要實現(xiàn)讓用戶對項目內(nèi)echarts圖表的動態(tài)配置,讓用戶脫離代碼也能實現(xiàn)簡單的圖表樣式配置。顏色作為圖表樣式的重要組成部分,其配置方式是項目要解決的重點問題。
二、難點整理
1.可以支持漸變的顏色選擇器。
2.漸變色選擇器取值結(jié)果與echarts圖表配置要求的轉(zhuǎn)換
3.圖表配置資源存儲與更新機(jī)制
4.漸變?nèi)∩魑恢门渲脤?dǎo)致取色器選中的色值發(fā)生變化觸發(fā)事件change頻繁被調(diào)用,引起最近選取顏色列表更新速率過快和圖表刷新過快造成的資源浪費(fèi)問題等。
三、解決方法
1.漸變?nèi)∩?/h3>
因為開發(fā)時間問題,這里直接選用了TDesign的顏色選擇器,具體可以參考TDesign官網(wǎng)
<div style="display: inline-block;vertical-align: middle;margin-right: 1rem;" > <t-color-picker v-model="lineColor" defaultValue="#409cff" :show-primary-color-preview="false" :recent-colors="recentcolorsList" @change="changeLineColorThrottle" /> </div>
2.漸變色值類型轉(zhuǎn)換
tdesign取色器獲取到的漸變色值為形如:linear-gradient(90deg,rgba(0, 0, 0, 1) 0%,rgb(73, 106, 220) 100%) 的css漸變色寫法
echarts圖表配置漸變色寫法為:
"color":{ "colorStops":[ {"offset":0,"color":"#2378f7"}, {"offset":0.5,"color":"#2378f7"}, {"offset":1,"color":"#83bff6"} ], "x":0, "y":0, "x2":1, "y2":0.5, "type":"linear", "global":false }
因此需要對兩種類型進(jìn)行轉(zhuǎn)換。
1) rgb與hex色值表達(dá)轉(zhuǎn)換
function hexToRgb(hex){ let str = hex.replace("#", ""); if (str.length % 3) { return "hex格式不正確!"; } //獲取截取的字符長度 let count = str.length / 3; //根據(jù)字符串的長度判斷是否需要 進(jìn)行冪次方 let power = 6 / str.length; let r = parseInt("0x" + str.substring(0 * count, 1 * count)) ** power; let g = parseInt("0x" + str.substring(1 * count, 2 * count)) ** power; let b = parseInt("0x" + str.substring(2 * count)) ** power; return `rgb(${r}, ${g}, $)`; } function rgbToHex(rgb){ let arr = rgb .replace("rgb", "") .replace("(", "") .replace(")", "") .split(","); // 轉(zhuǎn)十六進(jìn)制 let h = parseInt(arr[0]).toString(16); let e = parseInt(arr[1]).toString(16); let x = parseInt(arr[2]).toString(16); if(h.length<2){ h = '0' + h } if(e.length<2){ e = '0' + e } if(x.length<2){ x = '0' + x } return "#" + h + e + x; }
2)角度與坐標(biāo)參數(shù)x,x2,y,y2的轉(zhuǎn)化
css中角度是指水平線和漸變線之間的角度,逆時針方向計算。換句話說,0deg 將創(chuàng)建一個從下到上的漸變,90deg 將創(chuàng)建一個從左到右的漸變?!咀⒁夂芏酁g覽器(Chrome、Safari、firefox等)的使用了舊的標(biāo)準(zhǔn),即 0deg 將創(chuàng)建一個從左到右的漸變,90deg 將創(chuàng)建一個從下到上的漸變。換算公式 90 - x = y 其中 x 為標(biāo)準(zhǔn)角度,y為非標(biāo)準(zhǔn)角度?!?/p>
linear-gradient() 函數(shù)會繪制出一系列與漸變線垂直的彩色線,每條線都匹配與漸變線相交點的顏色。這條漸變線由包含漸變圖形的容器的中心點和一個角度來定義的。漸變線上的顏色值是由不同的點來定義,包括起始點、終點,以及兩者之間的可選的中間點(中間點可以有多個)。起點是漸變線上代表起始顏色值的點。終點是漸變線上代表最終顏色值的點。這兩個點都是由漸變線和從最近的頂點發(fā)出的垂直線之間的交叉點定義的。
echarts圖表實際上是基于canvas畫布展示的,因此需要將起點和終點轉(zhuǎn)換在canvas坐標(biāo)系中。
css漸變角度與canvas坐標(biāo)參數(shù)轉(zhuǎn)換過程
需要注意的是,漸變線與矩形的交點可能落在水平線上,也可能落在垂直線上,所以需要分情況進(jìn)行計算,實現(xiàn)代碼如下:
function calculateGradientCoordinate( width, height, angle = 180, ) { if (angle >= 360) angle = angle - 360; if (angle < 0) angle = angle + 360; angle = Math.round(angle); // 當(dāng)漸變軸垂直于矩形水平邊上的兩種結(jié)果 if (angle === 0) { return { x0: Math.round(width / 2), y0: height, x1: Math.round(width / 2), y1: 0, }; } if (angle === 180) { return { x0: Math.round(width / 2), y0: 0, x1: Math.round(width / 2), y1: height, }; } // 當(dāng)漸變軸垂直于矩形垂直邊上的兩種結(jié)果 if (angle === 90) { return { x0: 0, y0: Math.round(height / 2), x1: width, y1: Math.round(height / 2), }; } if (angle === 270) { return { x0: width, y0: Math.round(height / 2), x1: 0, y1: Math.round(height / 2), }; } // 從矩形左下角至右上角的對角線的角度 const alpha = Math.round( (Math.asin(width / Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2))) * 180) / Math.PI, ); // 當(dāng)漸變軸分別于矩形的兩條對角線重合情況下的四種結(jié)果 if (angle === alpha) { return { x0: 0, y0: height, x1: width, y1: 0, }; } if (angle === 180 - alpha) { return { x0: 0, y0: 0, x1: width, y1: height, }; } if (angle === 180 + alpha) { return { x0: width, y0: 0, x1: 0, y1: height, }; } if (angle === 360 - alpha) { return { x0: width, y0: height, x1: 0, y1: 0, }; } // 以矩形的中點為坐標(biāo)原點,向上為Y軸正方向,向右為X軸正方向建立直角坐標(biāo)系 let x0 = 0, y0 = 0, x1 = 0, y1 = 0; // 當(dāng)漸變軸與矩形的交點落在水平線上 if ( angle < alpha || // 處于第一象限 (angle > 180 - alpha && angle < 180) || // 處于第二象限 (angle > 180 && angle < 180 + alpha) || // 處于第三象限 angle > 360 - alpha // 處于第四象限 ) { // 將角度乘以(PI/180)即可轉(zhuǎn)換為弧度 const radian = (angle * Math.PI) / 180; // 當(dāng)在第一或第四象限,y是height / 2,否則y是-height / 2 const y = angle < alpha || angle > 360 - alpha ? height / 2 : -height / 2; const x = Math.tan(radian) * y; // 當(dāng)在第一或第二象限,l是width / 2 - x,否則l是-width / 2 - x const l = angle < alpha || (angle > 180 - alpha && angle < 180) ? width / 2 - x : -width / 2 - x; const n = Math.pow(Math.sin(radian), 2) * l; x1 = x + n; y1 = y + n / Math.tan(radian); x0 = -x1; y0 = -y1; } // 當(dāng)漸變軸與矩形的交點落在垂直線上 if ( (angle > alpha && angle < 90) || // 處于第一象限 (angle > 90 && angle < 180 - alpha) || // 處于第二象限 (angle > 180 + alpha && angle < 270) || // 處于第三象限 (angle > 270 && angle < 360 - alpha) // 處于第四象限 ) { // 將角度乘以(PI/180)即可轉(zhuǎn)換為弧度 const radian = ((90 - angle) * Math.PI) / 180; // 當(dāng)在第一或第二象限,x是width / 2,否則x是-width / 2 const x = (angle > alpha && angle < 90) || (angle > 90 && angle < 180 - alpha) ? width / 2 : -width / 2; const y = Math.tan(radian) * x; // 當(dāng)在第一或第四象限,l是height / 2 - y,否則l是-height / 2 - y const l = (angle > alpha && angle < 90) || (angle > 270 && angle < 360 - alpha) ? height / 2 - y : -height / 2 - y; const n = Math.pow(Math.sin(radian), 2) * l; x1 = x + n / Math.tan(radian); y1 = y + n; x0 = -x1; y0 = -y1; } // 坐標(biāo)系更改為canvas標(biāo)準(zhǔn),Y軸向下為正方向 x0 = Math.round(x0 + width / 2); y0 = Math.round(height / 2 - y0); x1 = Math.round(x1 + width / 2); y1 = Math.round(height / 2 - y1); return { x0, y0, x1, y1 }; }
echarts坐標(biāo)參數(shù)取值范圍為0~1,在傳參時可以直接將width和height設(shè)置為1。
坐標(biāo)參數(shù)轉(zhuǎn)換回角度時,需要注意角度所在象限區(qū)域進(jìn)行分情況討論。
x=0.5 y=0, x2=0.5, y2=1 從上到下
x=1 y=0.5, x2=0.5, y2=0 從下到上
x=0 y=0.5, x2=1, y2=0.5 從左到右
x=1 y=0.5, x2=0, y2=0.5 從右到左
實現(xiàn)代碼如下:
function getDeg(x1,y1,x2,y2){ var x = Math.abs(x1 - x2); var y = Math.abs(y1 - y2); var tan = x / y; var radina = Math.atan(tan); //用反三角函數(shù)求弧度 var deg = Math.floor(180 / (Math.PI / radina)) || 0; //將弧度轉(zhuǎn)換成角度 /** * 根據(jù)目標(biāo)點判斷象限(注意是笛卡爾坐標(biāo)) * 一: +,+ * 二: -,+ * 三: -,+ * 一: +,- */ // * 二、三象限要加 180° if (x2 < 0 && y2 >= 0) { deg = 180 + deg; } if (x2 < 0 && y2 < 0) { deg = 180 + deg; } // 一、二象限 === 0 就是 180° if (deg === 0) { if ((x2 >= 0 && y2 > 0) || (x2 <= 0 && y2 > 0)) { deg = 180 + deg; } } if ((x2 <= 0 && y2 > 0)) { deg = 180 + deg; } if ((x2 >= 0 && y2 > 0) ) { deg = - (180 + deg); } return deg; }
此外還需要對色值RGB和HEX進(jìn)行轉(zhuǎn)換,代碼如下:
function hexToRgb(hex){ let str = hex.replace("#", ""); if (str.length % 3) { return "hex格式不正確!"; } //獲取截取的字符長度 let count = str.length / 3; //根據(jù)字符串的長度判斷是否需要 進(jìn)行冪次方 let power = 6 / str.length; let r = parseInt("0x" + str.substring(0 * count, 1 * count)) ** power; let g = parseInt("0x" + str.substring(1 * count, 2 * count)) ** power; let b = parseInt("0x" + str.substring(2 * count)) ** power; return `rgb(${r}, ${g}, $)`; } function rgbToHex(rgb){ let arr = rgb .replace("rgb", "") .replace("(", "") .replace(")", "") .split(","); // 轉(zhuǎn)十六進(jìn)制 let h = parseInt(arr[0]).toString(16); let e = parseInt(arr[1]).toString(16); let x = parseInt(arr[2]).toString(16); if(h.length<2){ h = '0' + h } if(e.length<2){ e = '0' + e } if(x.length<2){ x = '0' + x } return "#" + h + e + x; }
以上就是css線性漸變格式與echarts線性漸變格式最為關(guān)鍵的轉(zhuǎn)換過程。
2.圖表配置資源存儲與更新機(jī)制
圖表的整體配置資源項目通過store進(jìn)行存儲,在頁面掛載時進(jìn)行讀取解析。因為onMounted生命周期內(nèi),chart配置數(shù)據(jù)可能還未存儲至store中,在此階段進(jìn)行初始化可能造成屬性值不存在等報錯。由于配置組件在配置時已經(jīng)記錄配置值,如果對chartOption進(jìn)行持續(xù)監(jiān)聽會造成沖突,導(dǎo)致取色器異常。因此需要設(shè)置flag值判斷頁面是否為初始化。初始化代碼如下:
watch(()=>chartOption,()=>{ //chartOption的反復(fù)修改和監(jiān)聽導(dǎo)致的沖突 if(pageUpdateFlag.value){ if('lineStyle' in chartOption.value['series'][0]){ if('type' in chartOption.value['series'][0]['lineStyle'] ){ lineType.value = chartOption.value['series'][0]['lineStyle']['type'] } if('width' in chartOption.value['series'][0]['lineStyle'] ){ lineWidth.value = chartOption.value['series'][0]['lineStyle']['width'] } if('color' in chartOption.value['series'][0]['lineStyle'] ){ let color = chartOption.value['series'][0]['lineStyle']['color'] if(typeof color !== 'string' && 'type' in color){ let x = color.x let y = color.y let x1 = color.x2 let y1 = color.y2 let deg = 90 deg = getDeg(x-0.5,y-0.5,x1-0.5,y1-0.5) let colorStr = '' //漸變顏色選擇器里的color要用rgb形式 for(let i =0 ; i < color.colorStops.length;i++){ let curColor = color.colorStops[i].color if( color.colorStops[i].color[0] == '#'){ curColor = hexToRgb(color.colorStops[i].color[0]) } colorStr = colorStr + ',' + color.colorStops[i].color + ' ' + color.colorStops[i].offset*100 + '%' } let str = "linear-gradient("+deg+'deg'+colorStr+')' lineColor.value = str }else{ lineColor.value = color } } } if('showBackground' in chartOption.value['series'][0]){ isArea.value = true if('color' in chartOption.value['series'][0]['areaStyle']){ let color = chartOption.value['series'][0]['areaStyle']['color'] if(typeof color !== 'string' && 'type' in color){ let x = color.x let y = color.y let x1 = color.x2 let y1 = color.y2 let deg = 90 deg = getDeg(x-0.5,y-0.5,x1-0.5,y1-0.5) let colorStr = '' //漸變顏色選擇器里的color要用rgb形式 for(let i =0 ; i < color.colorStops.length;i++){ let curColor = color.colorStops[i].color if( color.colorStops[i].color[0] == '#'){ curColor = hexToRgb(color.colorStops[i].color[0]) } colorStr = colorStr + ',' + color.colorStops[i].color + ' ' + color.colorStops[i].offset*100 + '%' } let str = "linear-gradient("+deg+'deg'+colorStr+')' areaColor.value = str }else{ areaColor.value = color } } }else { isArea.value = false } pageUpdateFlag.value = false } },{deep:true})
3.利用節(jié)流函數(shù)解決change事件頻繁調(diào)用導(dǎo)致的一系列沖突問題
在使用TDesign取色器時,通過滑動條調(diào)整漸變色偏移量會導(dǎo)致change事件頻繁調(diào)用,如果直接在change函數(shù)中獲取value添加到最近使用顏色列表就會造成最近使用列表顏色刷新過快,近似值添加過多的問題。因此需要使用節(jié)流函數(shù)來避免這一問題。同時change的頻繁調(diào)用也會導(dǎo)致圖表數(shù)據(jù)更新過快,造成資源浪費(fèi)等問題。因此本項目使用了節(jié)流函數(shù)對change的整體事件進(jìn)行了處理。
節(jié)流函數(shù):
import { ref } from 'vue'; export default function useThrottle(fn, delay) { const canRun = ref(true); return (...args) => { if (!canRun.value) return; canRun.value = false; setTimeout(() => { fn(...args); canRun.value = true; }, delay); }; };
取色器change事件:
const changeLineColorThrottle = useThrottle(changeLineColor,400) function changeLineColor(value,context){ addRecentColor(recentcolorsList,value) //不能直接value及其值進(jìn)行任何 //判斷取色為純色還是漸變,注意value為rgb(115, 171, 230)或linear-gradient(90deg,rgb(241, 29, 0) 0%,rgb(73, 106, 220) 100%) console.log('判斷取色為純色還是漸變',value,lineColor.value,lineColor.value.includes('linear')); let curColor = value if(curColor.includes('linear')){ //漸變色,需要解析轉(zhuǎn)換 let colorObj = parseLinearColor(curColor) chartOption.value['series'][0]['lineStyle']['color'] = deepcopy(colorObj) }else{ chartOption.value['series'][0]['lineStyle']['color'] = curColor } //??!注意:頻繁設(shè)置狀態(tài)可能紊亂,導(dǎo)致漸變?nèi)∩鳛楹谏? store.changeChartIsChange(true,"option") }
添加最近使用顏色函數(shù)和格式轉(zhuǎn)換函數(shù):
function addRecentColor(recentcolorsList,color){ //添加前應(yīng)判斷數(shù)組內(nèi)是否有重復(fù)的色值,有則刪除原色值 for(let i =0 ; i < recentcolorsList.value.length; i++){ if(recentcolorsList.value[i] == color){ recentcolorsList.value.splice(i,1) } } recentcolorsList.value.unshift(color) } function parseLinearColor(curColor){ //滑動取色時會造成顏色添加頻繁 let colorObj = { "colorStops": [], "x":0, "y":0, "x1":0, "y1":0, "type":"linear", "global":false } let deg = curColor.split('linear-gradient(')[1].split('deg')[0] let colorList = curColor.split('deg')[1].split(',r') for(let i = 1; i < colorList.length; i++ ){ let arr = colorList[i].split(") ") let color = 'r'+arr[0]+')' if(color.includes('rgba')){ color = color.replace('rgba','rgb') } color = rgbToHex(color) let offset = arr[1].split("%")[0]/100 colorObj.colorStops.push({"offset":offset,"color":color}) } let {x0,y0,x1,y1} = calculateGradientCoordinate(1,1,deg) colorObj.x = x0 colorObj.x2 = x1 colorObj.y = y0 colorObj.y2 = y1 console.log(deg,x0,y0,x1,y1,"deg轉(zhuǎn)化x0,y0,x1,y1"); return colorObj }
以上就是實現(xiàn)echarts圖表漸變色的動態(tài)配置的關(guān)鍵步驟。
以上就是Vue3項目頁面實現(xiàn)echarts圖表漸變色的動態(tài)配置的實現(xiàn)步驟的詳細(xì)內(nèi)容,更多關(guān)于Vue3 echarts圖表漸變配置的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
elementUI的table表格改變數(shù)據(jù)不更新問題解決
最近在做vue的項目時發(fā)現(xiàn)了一個問題,今天就來解決一下,本文主要介紹了elementUI的table表格改變數(shù)據(jù)不更新問題解決,感興趣的可以了解一下2022-02-02vue 中基于html5 drag drap的拖放效果案例分析
本文通過三個案例給大家介紹了vue 中基于html5 drag drap的拖放效果 ,需要的朋友可以參考下2018-11-11vue+element-ui?校驗開始時間與結(jié)束時間的實現(xiàn)代碼
這篇文章主要介紹了vue+element-ui?校驗開始時間與結(jié)束時間的代碼實現(xiàn),最主要的需求是開始時間不能早于當(dāng)前時間,感興趣的朋友跟隨小編一起看看吧2024-07-07