svg, canvas, css3d реализуют визуализацию данных (эффект псевдо3D)

внешний интерфейс SVG визуализация данных Canvas
svg, canvas, css3d реализуют визуализацию данных (эффект псевдо3D)

Предисловие:

В этом проекте используются некоторые пользовательские компоненты визуализации данных, и я извлек некоторые типичные для краткого обзора.

Разделены следующим образом:

  • Диаграмма вращения планеты на орбите -- Метод 1: svg: animateMotion+ animateTransform Метод 2: css3d
  • Карта -- рендеринг svg + плавающая рамка div + событие js
  • Двумерная круговая диаграмма (вертушка) -- холст: dragCircle , stopDragging
  • Пирамида -- холст + логарифмическое расположение
  • Кубоид -- css3d + пошаговый рост
  • Wiper (ломтик пирога) -- zrender

Диаграмма вращения планеты на орбите

Показать результаты:

Некоторые изображения (например, анимация jpg ниже) слишком велики, сжаты до определенной степени и немного размыты (^_^).



Мои мысли (*^3^):
Причина, по которой существует две версии: я использовал svg для реализации первой версии, но затем svg моей планеты конфликтовал с другой анимацией svg моих коллег (⁎⁍̴̛ᴗ⁍̴̛⁎), произошли огромные изменения, и Сам svg воняет. Он слишком длинный, и слишком утомительно его менять, поэтому я просто перерисовываю его с помощью css3d (´▽`).

шаг:

Способ 1: свойство animateMotion svg + свойство 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"
/>

Способ 2: css3d

Ссылка на ссылку:woo woo Краткое описание.com/afraid/2 no 85973 Ade…

  • 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;
}

Улучшенная версия: размер и яркость контролируются зазором, ближним большим и дальним маленьким, ближним ярким и дальним темным.

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────♪


карта

Показать результаты:


Мои мысли (*^3^):
Экзотические потребности (゚o゚;;, поскольку Сторона А считает, что местоположение карты Baidu является неточным, и ей не разрешено использовать API карты Baidu и карты AutoNavi, и ее не устраивает стиль карты Tian, ​​поэтому мы примите решение карты рисования пользовательского интерфейса, экспортируйте svg, а затем позвольте внешнему интерфейсу отображать различные эффекты в соответствии с svg.

шаг:

  • содержание документа

Файлы карты следующие: основной файл index.js содержит события наведения, файл стиля index.less, mapStyle.js хранит фоновую карту, формат массива pathStyle.js хранит пути, представляющие небольшие блоки на карте.



  • визуализировать карту

код показывает, как показано ниже:


В соответствии с данными, предоставленными интерфейсом, заполните разные пути разными цветами в соответствии с пятью цветовыми системами.

        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)',
        ];
  • Добавить события приостановки

Код рендера выглядит следующим образом:


Движение мыши в событии:


Событие отсутствия мыши:


♪───O(≧∇≦)O────♪ Симпатичная разделительная линия♪────O(≧∇≦)O────♪


2D круговая диаграмма (диаграмма Пинмилла)

Показать результаты:


Мои мысли (*^3^):
Поскольку все круговые диаграммы электронных диаграмм — это круговые диаграммы с параметром широты, и на этот раз пользовательскому интерфейсу требуется круговая диаграмма с двумя параметрами широты, поэтому я могу нарисовать ее только сам (´;ω;`). Поскольку я использовал холст для рисования круговой диаграммы раньше, я думал, что это просто.В результате отец Стороны А увидел результат и сказал, что он добавит пользовательское событие приостановки (которое не было доступно в prd в начале). Потребовалось 3 дня, чтобы нарисовать достаточную версию.
Дополнительно: Некоторые люди говорят, что диаграммы также могут быть достигнуты, я пробовал, и ZRender диаграмм может быть достигнут.

шаг:

  • Входящие параметры
option.push=[{
   color: color[i], //饼图块颜色
   radius: item.revenueTaxAvg, //饼图块半径
   name: item.domainName, // 饼图块名称
   angle: item.companyCnt, //饼图块角度  
}];

  • Нарисуйте круговую диаграмму PieCanvas.drawPieCanvas('econComposChart', option);
Как нарисовать круговую диаграмму? , вы можете обратиться к статье, которую я написал ранее:nuggets.capable/post/684490…
==/* Примечание */==

В этой статье показан угол одной широты, просто добавьте другой радиус широты.

Текст и изображения, нарисованные холстом, будут в некоторой степени размыты.Решение состоит в увеличении ширины и высоты холста в 2 раза.


  • Приостановленное мероприятие
Выполните обнаружение столкновений, чтобы определить, в каком радиане мышь находится между блоками круговой диаграммы. 
Основная логика суждения в математике выглядит следующим образом:

если (расстояние от точки до центра окружности

&& расстояние от точки до центра окружности > минимальный радиус окружности

&& Угол линии от точки до центра окружности > начальный угол сектора

&& Угол прямой из точки в центр окружности точка в секторе
}

//使用勾股定理计算这个点与圆心之间的距离   
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 использует экспоненциальный рост, потому что 0-я степень любого max = 1, поэтому случай i

 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
    });

Суммировать

Это был первый раз, когда я написал статью с таким количеством слов, обобщающих методы. Набор был немного беспорядочным, (╯°□°)╯︵ ┻━┻. Большая часть содержания на самом деле очень проста, и это в основном самое базовый в младших классах средней и старшей школы (на самом деле это сложно, и я не могу _φ(・_・).

Я имею наглость сказать, что у меня может не быть хороших навыков письма, но центральная идея каждого примера должна быть выражена.

И последнее, но не менее важное: я впервые пишу так много слов, поставьте мне лайк (///▽///).