前段時間遇到一個需求,需要畫一個 7日年化利率 的曲線圖。
UI 提了幾個需求,說實話,一開始看到時都懵逼了,然後我回了句 “你說,我不一定實現。”
一開始看了網上的一些開源圖表,看是能實現,但是又覺得比較重,沒必要爲了一個圖表而引入整個庫。而後下定決心自己擼一個。
當然了一開始並不打算寫一個 npm 庫,只是在項目中寫一個 js 來實現圖表繪製。在效果實現之後覺得需要整理一番,於是在五一期間進行了重構,將結構整清楚,提高擴展性,以便應對日後不同的需求。
先來看看 demo
爲什麼叫 lw-chart
當然了 lw 不是我名字的縮寫,lw 是指 lightweight 輕量的意思,我希望這 lw-chart 能夠真正做到輕量化,當我只使用其中某一個類型的圖表時,那麼只需要引入這個類型的圖表,其他的不相關的代碼不要來增加項目的體積。
lw-chart 是如何做的呢?
lw-chart 內部通過類繼承的方式提高代碼的複用性,同時在編譯打包時,輸出多個文件,當實際項目中使用了其中某一個類型的圖表,那麼可以再項目中只 import
一個文件,避免把所有類型的圖表都都入到項目中而不使用的情況。
具體的使用方式可以查看文檔,這裏就不在贅述。
踩過的坑
佈局
在開始編碼前一定要先考慮好佈局,不然在寫代碼時會很混亂,不知道座標該加還是該減。
第一次寫的時候,因爲參考了網上的代碼,導致沒有清晰的佈局,雖然能繪製出一些東西,但是遇到要計算座標時,就會很混亂,導致就都無法達到預期的效果。
最後自己畫了一個佈局的圖,再開始編碼。下面是我的一個佈局結構
我分了兩個類來實現這個佈局,一個是基類 LWChart
,定義了 canvas
,titleBar
,chart
。
而 x座標 和 y座標 則定義在 Axis
座標軸類中,因爲有些圖表可能不需要座標軸,所以通過 Axis
繼承 LWChart
達到更靈活的配置。
高清屏下變模糊的處理
獲取屏幕 dpi,尺寸及位置參數在繪製時乘以 dpi
。
dpi
的計算方式如下:
let n = (ctx.backingStorePixelRatio ||
ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio ||
1);
let a = (window.devicePixelRatio || 1) / n;
if (a < 1) {
a = 1;
}
const dpi = a / n;
獲取貝塞爾曲線控制點
可能大部分都是使用現成的圖表庫,網上比較少關於貝塞爾曲線控制點計算的文章。
/** 給原始座標點添加三次貝塞爾曲線控制點 */
export const getCurveList = function (pointList: IPos[]): ICurvePoint[] {
if (pointList.length <= 0) return [];
// 長度比例係數
const lenParam = 1 / 3;
const len = pointList.length;
return pointList.map((curPoint, index) => {
const nextPoint = index === len - 1 ? curPoint : pointList[index + 1];
const curX = curPoint.x;
const curY = curPoint.y;
const nextX = nextPoint.x;
const nextY = nextPoint.y;
const deltaX = Math.abs(nextX - curX) * lenParam;
return {
start: curPoint,
end: nextPoint,
control1: {
x: curX + deltaX,
y: curY
},
control2: {
x: nextX - deltaX,
y: nextY
}
};
});
};
以上是我的計算方式,主要通過前後兩個點的 x座標 進行加減生成兩個控制點,使得貝塞爾曲線相對平滑。
動畫處理
動畫主要通過 requestAnimationFrame
api 進行實現。
將需要繪製的數據存入一個數組中,將數組中的前 n - 1 個數據使用 bezierCurveTo
繪製,最後一段曲線使用貝塞爾曲線函數進行繪製
// 動畫執行中,使用貝塞爾函數繪製最後一段曲線
for (let t = 0; t <= percent / 100; t += 0.1) {
lastX = curveWithTime(lastPoint.start.x, lastPoint.control1.x, lastPoint.control2.x, lastPoint.end.x, t);
lastY = curveWithTime(lastPoint.start.y, lastPoint.control1.y, lastPoint.control2.y, lastPoint.end.y, t);
this.ctx.lineTo(lastX, lastY);
}
使用 ctx.lineTo
繪製出來的爲直線,所以需要小間隙繪製多個點才能使得曲線相對平滑。
動畫時間計算
首先通過計算每段曲線應該執行的時間,
結合 requestAnimationFrame
和 performance.now();
獲取每一幀的時間差,通過時間差與每一段曲線的單位時間進行對比,計算出百分比,再通過上面的 for
循環進行繪製。
拓展
目前 lw-chart 中實現的圖表繪製的只有 Area
,可以通過參數配置控制顯示線條或區域。但是這也許還是還是不夠好用,也不可能滿足到各個需求。
所以各位開發者可以在 lw-chart 的基礎上進行定製開發。通過繼承 LWChart
或者 Axis
這個類。
關於如何開發,後期整理後會在文檔上更新。
歡迎各位大佬 pull request,一同開發。
如果覺得 lw-chart 不錯的話,不妨下載體驗一番,順便到 Github 點個 Star。
如果覺得內容還不錯的話,希望小夥伴可以幫忙點贊轉發,給更多的同學看到,感謝感謝!
如果你喜歡我的文章,還可以關注我的公衆號【前端develop】