svg、canvas、css3d實現資料視覺化
前言:
這次專案用到了一些自定義的資料視覺化元件,我把我做的部分抽出來幾個典型做個彙總。
分為如下:
- 星球環繞旋轉圖 -- 方法一: svg:animateMotion+ animateTransform 方法二:css3d
- 地圖 -- svg渲染 + div懸浮框 + js事件
- 二維餅圖(風車圖) -- canvas: dragCircle 、 stopDragging
- 三稜錐 -- canvas + 對數排列
- 長方體 -- css3d + 增量增長
星球環繞旋轉圖
效果展示:
有些圖片(例如下面這個jpg動圖)太大,進行了一定程度的壓縮,有點模糊(^_^)。
我的碎碎念(*^3^):
步驟:
方法一:svg的animateMotion屬性 + animateTransform屬性
//舉例一個星球的動畫 <animateMotion dur="6s" begin="0" repeatCount="indefinite"> <mpath xlinkHref="#Path-12" /> //軌跡動畫 </animateMotion> <animateTransform //自身動畫,靠近我的時候星球變大,遠離我時變小 id="first" attributeType="XML" attributeName="transform" type="scale" begin="0;second.end " from="1" to="0.512" dur="3s" fill="freeze" /> <animateTransform id="second" attributeType="XML" attributeName="transform" type="scale" begin="first.end" from="0.512" to="1" dur="3s" fill="freeze" />複製程式碼
方法二:css3d
參考連結: ofollow,noindex">www.jianshu.com/p/2b85973ad…
- html:
<!-- 軌道 --> <div class="orbit"> <!-- 行星 --> <div class="planet planet1"> <!-- <span class="name"></span> --> </div> <div class="planet planet2"> <!-- <span class="name"></span> --> </div> </div>複製程式碼
- css:
.orbit { //軌道旋轉,公轉 border: 5px solid red; transform-style: preserve-3d; padding: 65px; width: 500px; height: 500px; border-radius: 50%; animation: orbit-rotate 10s linear infinite; } .planet { //星球自轉 width: 50px; height: 50px; background: url('../../img/ball1.png') no-repeat; background-size: 100% 100%; border-radius: 50%; animation: self-rotate 10s linear infinite; } // (1)rotateX 是為了讓整個面傾斜,translateZ是為了防止橢圓(border)因為傾斜發生鋸齒, // (2)停頓效果的產生,其實我是走了野路子的。五個球,根據360/5=72,寫了五個不同的關於orbit的class, // 0 + 72,....360依次增加72,直到360,利用setimeout每隔4秒,按順序切換一個class @keyframes orbit-rotate { 0% { transform: rotateX(70deg) rotateZ(0deg) translateZ(0); } 100% { transform: rotateX(70deg) rotateZ(-360deg) translateZ(0); } } @keyframes self-rotate { 0% { transform: rotateX(-90deg) rotateY(360deg) rotateZ(0deg); } 100% { transform: rotateX(-90deg) rotateY(0deg) rotateZ(0deg); } } .planet1 { //確定星球開始位置 position: absolute; top: 65px; right: 65px; } .planet2 { //確定星球開始位置 position: absolute; bottom: 65px; right: 65px; } 複製程式碼
==/**注意 **/==
參考連結: zh.wikipedia.org/wiki/%E6%A4…
如果為了讓星球排列五等分,並且大小根據數值改變星球大小,可以採用以下公式:中心位於點 (h,k)的主軸平行於 x 軸的橢圓由如下方程指定,改變top,left值
♪───O(≧∇≦)O────♪可愛的分割線♪───O(≧∇≦)O────♪
地圖
效果展示:
我的碎碎念(*^3^):
步驟:
- 檔案內容
地圖檔案如下:index.js主檔案包含懸浮事件,index.less樣式檔案,mapStyle.js存放背景地圖,pathStyle.js陣列格式存放代表地圖上小塊的路徑
- 渲染地圖
程式碼如下:
根據介面給的資料,按照五個色系分別給不同的path填充(fill)不同的顏色
const colorMap = [ 'rgba(89, 126, 247, 0.8)', 'rgba(0, 121, 254, 0.8)', 'rgba(0, 121, 254, 0.8)', 'rgba(38, 168, 254, 0.8)', 'rgba(192, 228, 253, 0.8)', ];複製程式碼
- 增加懸浮事件
render程式碼如下:
滑鼠移入事件:
滑鼠移出事件:
♪───O(≧∇≦)O────♪可愛的分割線♪───O(≧∇≦)O────♪
二維餅圖(風車圖)
效果展示:
我的碎碎念(*^3^):
步驟:
- 傳入引數
option.push=[{ color: color[i], //餅圖塊顏色 radius: item.revenueTaxAvg, //餅圖塊半徑 name: item.domainName, // 餅圖塊名稱 angle: item.companyCnt, //餅圖塊角度 }];複製程式碼
- 畫餅圖,PieCanvas.drawPieCanvas('econComposChart', option);
怎麼畫餅圖?,可以參考我以前寫的一篇文章: juejin.im/post/5b1e27…
==/* 注意 */==
這篇文章畫的是angle一個緯度,只要再增加另外一個緯度radius就好。
canvas畫的文字和圖,會有一定程度的模糊,解決方案:把畫布的寬高增加2倍。
- 懸浮事件
進行碰撞檢測,判斷滑鼠懸浮是落在哪個弧度的餅圖塊之間,如果不再餅圖塊裡面懸浮樣式消失。
數學裡主要判斷邏輯如下:
if(點到圓心的距離<圓的最大半徑
&&點到圓心的距離>圓的最小半徑
&&點到圓心的直線的角度>扇形的起始角度
&&點到圓心的直線的角度<扇形的結束角度){
點在扇形區域內
}
//使用勾股定理計算這個點與圓心之間的距離 distanceFromCenter = Math.sqrt(Math.pow(circle.x - clickX, 2) + Math.pow(circle.y - clickY, 2)) //α(弧度)= L (弧長)/ r(半徑),但是弧長,我求不出來。 (點到圓心的直線的角度)的範圍我主要使用sin(x),如下方法。 判斷不同區間的sin(x)值大小,推斷出懸浮區域所在的值是什麼。複製程式碼
♪───O(≧∇≦)O────♪可愛的分割線♪───O(≧∇≦)O────♪
三稜錐
效果展示:
步驟:
主要原理:兩個三角形 + 一個園 = 三稜錐
canvas.width = canvas.offsetWidth; //防止圖片變形 canvas.height = canvas.offsetHeight; ctx.clearRect(0, 0, canvas.width, canvas.height); 清除畫布 const { height } = canvas; // 計算等邊三角形的高 //如下圖,第一個三角形 A-B-C ctx.moveTo(100, 0); // 從A(100,0)開始 ctx.lineTo(0, height); // 從A(100,0)開始,畫到B (0,height)結束 ctx.lineTo(144, height); // B(0,height)-C(144, height) //第二個三角形 A-C-D ctx.moveTo(100, 0); // 從A(100,0)開始 ctx.lineTo(143, height); // A-C ctx.lineTo(210, height); // C-D //第三個畫圓 ctx.arc(100, 23 , 23, 0, Math.PI * 2, false); // 畫圓 <canvas id={`pyramid${id}`} height={itemHeight} /> //計算itemHeight複製程式碼
對數增長--三稜錐高度(itemHeight)計算:
假設輸入 data = [0, 1, 2, 3, 4, 5],x為其中任意值; maxHeight 為最大高度; 輸出 itemHeight(0 <= itemHeight< maxHeight),成對數增長 //求最大值 const max = MAX(data) //排除 x === 0 的情況 因為logmax(max)= 1,且x > 0 由上圖可得 0 < logmax(x)< 1 所以 0 < logmax(x) * maxHeight < maxHeight 可知 logmax(x) * maxHeight 成對數變化 又因為logmax(x) = loge(x) / loge(max) //寫成程式碼為 const max =data((a, b) => { return a > b ? a : b; }, 0); itemHeight = x===0 ? 0 : Math.log(x) / Math.log(max) * maxHeight 複製程式碼
==/* 注意 */==
y軸計算採用指數增長,因為任意max的0次方 = 1, 所以單獨判斷 i <= 0的情況
i > 0 ? Math.round(max ** (i * 0.25)) : 0
長方體
效果展示:
步驟:
html
<div id="cube"> <figure class="front">1</figure> <figure class="back">2</figure> <figure class="right">3</figure> <figure class="left">4</figure> <figure class="top">5</figure> <figure class="bottom">6</figure> </div>複製程式碼
css
#box.show-front{ transform: translateZ(-50px ) rotateY(0deg ); } #box.show-back{ transform: translateZ(-50px ) rotateX( -180deg ); } #box.show-right{ transform: translateZ( -150px ) rotateY(-90deg ); } #box.show-left{ transform: translateZ( -150px ) rotateY(90deg ); } #box.show-top{ transform: translateZ( -100px ) rotateX(-90deg ); } #box.show-bottom { transform: translateZ( -100px ) rotateX(90deg ); } 複製程式碼
♪───O(≧∇≦)O────♪可愛的分割線♪───O(≧∇≦)O────♪
總結
我第一次寫這麼多字的總結技術的文章,排版有點亂,(╯°□°)╯︵ ┻━┻。大部分的內容其實很簡單,用到的基本上是初中、高中裡面最基礎的數學(其實難了,我也不會了_φ(・_・)。
厚著臉皮說,我可能文字功底不咋地,但是每個例子的中心思想應該都表達了。
最後的最後,看在我第一次寫了這麼多字的份上,給個讚唄(///▽///)。