当前位置:   article > 正文

基于echarts开发的3D饼图_echarts 3d饼图

echarts 3d饼图

可以自动旋转,鼠标高亮选中

第一步

echarts-gl 装包

  1. "echarts": "^5.2.0",
  2. "echarts-gl": "^2.0.8",

我用的是上面两个版本,最开始因为echarts-gl 和echarts 版本不对应,报错找了半天,大坑,所以一定要下载对应的版本,建议直接复制到项目中package.json文件中,然后npm i 自动装包就行了

第二步

封装成了一个插件,可以直接复制到自己项目中查看,数据为模拟数据

  1. <template>
  2. <div style="width:100%;height:100%">
  3. <div ref="basicDataPie" style="width:100%;height:100%"></div>
  4. </div>
  5. </template>
  6. <script>
  7. import * as echarts from "echarts";
  8. import "echarts-gl";
  9. export default {
  10. data() {
  11. return {
  12. data: [
  13. { name: "视频", value: 20, itemStyle: { color: "#0783FA" } },
  14. { name: "作业", value: 18, itemStyle: { color: "#07D1FA" } },
  15. { name: "考试", value: 22, itemStyle: { color: "#20E6A4" } },
  16. { name: "课件", value: 15, itemStyle: { color: "#D18161" } },
  17. { name: "实验", value: 25, itemStyle: { color: "#FFE649" } },
  18. ],
  19. };
  20. },
  21. mounted() {
  22. this.$nextTick(() => {
  23. this.echartsPie();
  24. });
  25. },
  26. methods: {
  27. echartsPie() {
  28. let hoveredIndex = "";
  29. let chartDom = this.$refs["basicDataPie"];
  30. let myChart = echarts.init(chartDom);
  31. const getPie3D = (pieData, internalDiameterRatio) => {
  32. //internalDiameterRatio:透明的空心占比
  33. let series = [];
  34. let sumValue = 0;
  35. let startValue = 0;
  36. let endValue = 0;
  37. let k = 1;
  38. pieData.sort((a, b) => {
  39. return b.value - a.value;
  40. });
  41. // 为每一个饼图数据,生成一个 series-surface 配置
  42. for (let i = 0; i < pieData.length; i++) {
  43. sumValue += pieData[i].value;
  44. let seriesItem = {
  45. name:
  46. typeof pieData[i].name === "undefined"
  47. ? `series${i}`
  48. : pieData[i].name,
  49. type: "surface",
  50. parametric: true,
  51. wireframe: {
  52. show: false,
  53. },
  54. pieData: pieData[i],
  55. pieStatus: {
  56. selected: false,
  57. hovered: false,
  58. k: k,
  59. },
  60. radius: "50%",
  61. center: ["10%", "10%"],
  62. };
  63. if (typeof pieData[i].itemStyle != "undefined") {
  64. let itemStyle = {};
  65. typeof pieData[i].itemStyle.color != "undefined"
  66. ? (itemStyle.color = pieData[i].itemStyle.color)
  67. : null;
  68. typeof pieData[i].itemStyle.opacity != "undefined"
  69. ? (itemStyle.opacity = pieData[i].itemStyle.opacity)
  70. : null;
  71. seriesItem.itemStyle = itemStyle;
  72. }
  73. series.push(seriesItem);
  74. }
  75. // 使用上一次遍历时,计算出的数据和 sumValue,调用 getParametricEquation 函数,
  76. // 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation,也就是实现每一个扇形。
  77. for (let i = 0; i < series.length; i++) {
  78. endValue = startValue + series[i].pieData.value;
  79. series[i].pieData.startRatio = startValue / sumValue;
  80. series[i].pieData.endRatio = endValue / sumValue;
  81. series[i].parametricEquation = getParametricEquation(
  82. series[i].pieData.startRatio,
  83. series[i].pieData.endRatio,
  84. false,
  85. false,
  86. k,
  87. series[i].pieData.value
  88. );
  89. startValue = endValue;
  90. }
  91. let boxHeight = getHeight3D(series, 15); //通过传参设定3d饼/环的高度,26代表26px
  92. // 准备待返回的配置项,把准备好的 legendData、series 传入。
  93. let option = {
  94. backgroundColor: "#fff",
  95. tooltip: {
  96. backgroundColor: "#fff",
  97. formatter: (params) => {
  98. if (
  99. params.seriesName !== "mouseoutSeries" &&
  100. params.seriesName !== "pie3d"
  101. ) {
  102. let bfb = (
  103. (option.series[params.seriesIndex].pieData.endRatio -
  104. option.series[params.seriesIndex].pieData.startRatio) *
  105. 100
  106. ).toFixed(2);
  107. return (
  108. `${params.seriesName}<br/>` +
  109. `<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${params.color};"></span>` +
  110. `${bfb}%`
  111. );
  112. }
  113. },
  114. },
  115. legend: {
  116. bottom: "3%",
  117. itemGap: 20,
  118. icon: "path://M0,12L12,12C12,5.37258,6.62742,0,0,0L0,12Z",
  119. },
  120. title: {
  121. text: "课程总课时:68",
  122. left: "left",
  123. top: "2%",
  124. textStyle: {
  125. fontWeight: 500,
  126. fontSize: 16,
  127. color: "#3D3D3D",
  128. },
  129. },
  130. xAxis3D: {
  131. min: -1,
  132. max: 1,
  133. },
  134. yAxis3D: {
  135. min: -1,
  136. max: 1,
  137. },
  138. zAxis3D: {
  139. min: -1,
  140. max: 1,
  141. },
  142. grid3D: {
  143. show: false,
  144. boxHeight: boxHeight, //圆环的高度
  145. left: 0,
  146. top: 0, //3d饼图的位置
  147. viewControl: {
  148. //3d效果可以放大、旋转等,请自己去查看官方配置
  149. alpha: 25, //角度
  150. distance: 250, //调整视角到主体的距离,类似调整zoom
  151. rotateSensitivity: 0, //设置为0无法旋转
  152. zoomSensitivity: 0, //设置为0无法缩放
  153. panSensitivity: 0, //设置为0无法平移
  154. autoRotate: true, //自动旋转
  155. },
  156. },
  157. series: series,
  158. };
  159. return option;
  160. };
  161. //获取3d丙图的最高扇区的高度
  162. const getHeight3D = (series, height) => {
  163. series.sort((a, b) => {
  164. return b.pieData.value - a.pieData.value;
  165. });
  166. return (height * 15) / series[0].pieData.value;
  167. };
  168. // 生成扇形的曲面参数方程,用于 series-surface.parametricEquation
  169. const getParametricEquation = (
  170. startRatio,
  171. endRatio,
  172. isSelected,
  173. isHovered,
  174. k,
  175. h
  176. ) => {
  177. // 计算
  178. let midRatio = (startRatio + endRatio) / 2;
  179. let startRadian = startRatio * Math.PI * 2;
  180. let endRadian = endRatio * Math.PI * 2;
  181. let midRadian = midRatio * Math.PI * 2;
  182. // 如果只有一个扇形,则不实现选中效果。
  183. if (startRatio === 0 && endRatio === 1) {
  184. isSelected = false;
  185. }
  186. // 通过扇形内径/外径的值,换算出辅助参数 k(默认值 1/3)
  187. k = typeof k !== "undefined" ? k : 1 / 3;
  188. // 计算选中效果分别在 x 轴、y 轴方向上的位移(未选中,则位移均为 0)
  189. let offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0;
  190. let offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0;
  191. // 计算高亮效果的放大比例(未高亮,则比例为 1)
  192. let hoverRate = isHovered ? 1.05 : 1;
  193. // 返回曲面参数方程
  194. return {
  195. u: {
  196. min: -Math.PI,
  197. max: Math.PI * 3,
  198. step: Math.PI / 32,
  199. },
  200. v: {
  201. min: 0,
  202. max: Math.PI * 2,
  203. step: Math.PI / 20,
  204. },
  205. x: function (u, v) {
  206. if (u < startRadian) {
  207. return (
  208. offsetX +
  209. Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate
  210. );
  211. }
  212. if (u > endRadian) {
  213. return (
  214. offsetX +
  215. Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate
  216. );
  217. }
  218. return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate;
  219. },
  220. y: function (u, v) {
  221. if (u < startRadian) {
  222. return (
  223. offsetY +
  224. Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate
  225. );
  226. }
  227. if (u > endRadian) {
  228. return (
  229. offsetY +
  230. Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate
  231. );
  232. }
  233. return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate;
  234. },
  235. z: function (u, v) {
  236. if (u < -Math.PI * 0.5) {
  237. return Math.sin(u);
  238. }
  239. if (u > Math.PI * 2.5) {
  240. return Math.sin(u) * h * 0.1;
  241. }
  242. return Math.sin(v) > 0 ? 1 * h * 0.1 : -1;
  243. },
  244. };
  245. };
  246. let option = getPie3D(this.data, 0.8);
  247. //是否需要label指引线,如果要就添加一个透明的2d饼状图并调整角度使得labelLine和3d的饼状图对齐,并再次setOption
  248. option.series.push({
  249. // name: "pie3d",
  250. type: "pie",
  251. label: {
  252. show: false,
  253. position: "inside",
  254. formatter: "{c}%",
  255. },
  256. // startAngle: -25, //起始角度,支持范围[0, 360]。
  257. clockwise: false, //饼图的扇区是否是顺时针排布。上述这两项配置主要是为了对齐3d的样式
  258. radius: ["65%", "65%"],
  259. center: ["55%", "48%"], //指示线的位置
  260. itemStyle: {
  261. opacity: 0,
  262. },
  263. });
  264. option && myChart.setOption(option);
  265. myChart.on("mouseover", function (params) {
  266. // 准备重新渲染扇形所需的参数
  267. let isSelected;
  268. let isHovered;
  269. let startRatio;
  270. let endRatio;
  271. let k;
  272. let i;
  273. // 如果触发 mouseover 的扇形当前已高亮,则不做操作
  274. if (hoveredIndex === params.seriesIndex) {
  275. return;
  276. // 否则进行高亮及必要的取消高亮操作
  277. } else {
  278. // 如果当前有高亮的扇形,取消其高亮状态(对 option 更新)
  279. if (hoveredIndex !== "") {
  280. // 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 false。
  281. isSelected = option.series[hoveredIndex].pieStatus.selected;
  282. isHovered = false;
  283. startRatio = option.series[hoveredIndex].pieData.startRatio;
  284. endRatio = option.series[hoveredIndex].pieData.endRatio;
  285. k = option.series[hoveredIndex].pieStatus.k;
  286. i =
  287. option.series[hoveredIndex].pieData.value ===
  288. option.series[0].pieData.value
  289. ? 35
  290. : 10;
  291. // 对当前点击的扇形,执行取消高亮操作(对 option 更新)
  292. option.series[hoveredIndex].parametricEquation =
  293. getParametricEquation(
  294. startRatio,
  295. endRatio,
  296. isSelected,
  297. isHovered,
  298. k,
  299. i
  300. );
  301. option.series[hoveredIndex].pieStatus.hovered = isHovered;
  302. // 将此前记录的上次选中的扇形对应的系列号 seriesIndex 清空
  303. hoveredIndex = "";
  304. }
  305. // 如果触发 mouseover 的扇形不是透明圆环,将其高亮(对 option 更新)
  306. if (
  307. params.seriesName !== "mouseoutSeries" &&
  308. option.series[params.seriesIndex].pieStatus
  309. ) {
  310. // 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 true。
  311. isSelected = option.series[params.seriesIndex].pieStatus.selected;
  312. isHovered = true;
  313. startRatio = option.series[params.seriesIndex].pieData.startRatio;
  314. endRatio = option.series[params.seriesIndex].pieData.endRatio;
  315. k = option.series[params.seriesIndex].pieStatus.k;
  316. // 对当前点击的扇形,执行高亮操作(对 option 更新)
  317. option.series[params.seriesIndex].parametricEquation =
  318. getParametricEquation(
  319. startRatio,
  320. endRatio,
  321. isSelected,
  322. isHovered,
  323. k,
  324. option.series[params.seriesIndex].pieData.value + 5
  325. );
  326. option.series[params.seriesIndex].pieStatus.hovered = isHovered;
  327. // 记录上次高亮的扇形对应的系列号 seriesIndex
  328. hoveredIndex = params.seriesIndex;
  329. }
  330. // 使用更新后的 option,渲染图表
  331. myChart.setOption(option);
  332. }
  333. });
  334. },
  335. },
  336. };
  337. </script>
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/105379
推荐阅读