svg、canvas、css3d實現數據可視化(僞3D效果)

前言:

此次項目用到了一些自定義的數據可視化組件,我把我作的部分抽出來幾個典型作個彙總。javascript

分爲以下:css

  • 星球環繞旋轉圖 -- 方法一: svg:animateMotion+ animateTransform 方法二:css3d
  • 地圖 -- svg渲染 + div懸浮框 + js事件
  • 二維餅圖(風車圖) -- canvas: dragCircle 、 stopDragging
  • 三棱錐  -- canvas + 對數排列
  • 長方體 -- css3d + 增量增加
  • 雨刮器(扇型餅圖) -- zrender  

星球環繞旋轉圖

效果展現:

有些圖片(例以下面這個jpg動圖)太大,進行了必定程度的壓縮,有點模糊(^_^)。html



個人碎碎念(*^3^):
之因此出現兩個版本的緣由:原本用svg實現了一版,結果後來我這個星球的svg和同事的其餘動畫svg衝突了(⁎⁍̴̛ᴗ⁍̴̛⁎),發生了巨大改變,自己svg又臭又長,改的太累,乾脆用css3d從新畫一個了( ´▽`)。

步驟:

方法一: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"
/>複製代碼

方法二:css3djava

參考連接:www.jianshu.com/p/2b85973ad…css3

  • 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;
}複製代碼

改進版:大小和亮暗用gap控制,近大遠小,近亮遠暗。canvas

const orbitStyle = {
  transform: `rotateX(70deg) rotateZ(${activeCircle * -72}deg) translateZ(0)`,
};
const planetStyle = (index, l) => {
  // l是數組的長度
  const average = l / 2; // 計算平均數
  const gap = 0.8 ** (average - Math.abs(Math.abs(index - (activeCircle % l)) - average)); // 先求不一樣球不一樣時間的絕對值來計算點在區間的距離,再根據距離計算改變值
  return {
    transform: `rotateX(-90deg) rotateY(${360 - activeCircle * 72}deg) rotateZ(0deg) scale(${gap})`,
    opacity: gap,
  };
};
複製代碼

♪───O(≧∇≦)O────♪可愛的分割線♪───O(≧∇≦)O────♪api


地圖

效果展現:


個人碎碎念(*^3^):
奇葩的需求(゚o゚;;, 由於甲方認爲百度地圖等位置不許確,不許使用百度地圖和高德地圖的api,又不滿意天地圖的樣式,因此咱們採用的方案是ui畫地圖,導出svg,再讓前端根據svg作各類效果展現。

步驟:

  • 文件內容

     地圖文件以下:index.js主文件包含懸浮事件,index.less樣式文件,mapStyle.js存放背景地圖,pathStyle.js數組格式存放表明地圖上小塊的路徑數組



  • 渲染地圖

      代碼以下:bash


根據接口給的數據,按照五個色系分別給不一樣的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^):
由於echarts的餅圖都是一個參數緯度的餅圖,而此次ui要求兩個參數緯度的餅圖,只能本身畫了(´;ω;`)。由於以前用canvas畫過餅圖,原本覺得仍是簡單的,結果甲方爸爸看了成果,說要加自定義懸浮事件(剛開始prd沒有的),廢了3天畫了一個夠用版的。
追加:有人說echarts也能夠實現,我去試了試,echarts的ZRender能夠實現。

步驟:

  • 傳入參數
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.reduce((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 ); } 複製代碼

增量增加--長方形高度(itemHeight)計算:

//求數據的和
const sum =data.reduce((a, b) => {
  return a + b;
}, 0);
itemHeight = x <= min ? min : min + (max-min) * x /sum;複製代碼


♪───O(≧∇≦)O────♪可愛的分割線♪───O(≧∇≦)O────♪


雨刮器(扇型餅圖)

效果展現:


步驟:

(1) 傳入傳輸

percent // 佔比
複製代碼

(2) 畫不一樣顏色的圓

const circles = [
      { r: 37, stroke: '#0A63D6', lineWidth: 1 },
      { r: 43, stroke: 'rgba(79, 4, 175, 1)', lineWidth: 10 },
      { r: 53, stroke: '#0A63D6', lineWidth: 15 },
      { r: 63, stroke: '#0088F3', lineWidth: 20 },
      { r: 70, stroke: 'rgba(11, 84, 166, 0.5)', lineWidth: 70 },
    ];
const startAngle = 0.5 * Math.PI;
const endAngle = Math.PI * 2 * percent + startAngle;
   
for (const item of circles) {
      const { r, stroke, lineWidth } = item;
      const circle = new zrender.Arc({
        shape: {
          cx,
          cy,
          r,
          startAngle,
          endAngle,
        },
        style: {
          fill: 'transparent',
          stroke,
          lineWidth,
        },
      });
      zr.add(circle);
    }
複製代碼

(3)畫園外面的藍色的邊:第一條位置固定,第一二條經過旋轉相應角度實現

const borderStyle={
      shape : {
        x1: cx,
        y1: cy + 37,
        x2: cx,
        y2: cy + 103.5,
      },
      style: {
        stroke: '#0A63D6',
        lineWidth: 1,
      },
    }
    const path1 = new zrender.Line(borderStyle);
    const path2 = new zrender.Line({
      origin: [cx, cy],
      rotation: -Math.PI * 2 * percent,
      ...borderStyle
    });複製代碼

總結

我第一次寫這麼多字的總結技術的文章,排版有點亂,(╯°□°)╯︵ ┻━┻。大部分的內容其實很簡單,用到的基本上是初中、高中裏面最基礎的數學(其實難了,我也不會了_φ(・_・)。

厚着臉皮說,我可能文字功底不咋地,可是每一個例子的中心思想應該都表達了。

最後的最後,看在我第一次寫了這麼多字的份上,給個讚唄(///▽///)。

相關文章
相關標籤/搜索