ThreeDCharts5.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. <template>
  2. <div class="water-eval-container">
  3. <div class="cityGreenLand-charts" id="cityGreenLand-charts4">
  4. </div>
  5. <div id="number_1" class=num></div>
  6. </div>
  7. </template>
  8. <script>
  9. import echarts from 'echarts'
  10. import 'echarts-gl';
  11. export default {
  12. props: ['education'],
  13. name: "cityGreenLand",
  14. components: {},
  15. data() {
  16. return {
  17. optionData: [{
  18. name: '文盲',
  19. value: 0.98,
  20. itemStyle: {
  21. color: '#0754A1',
  22. }
  23. }, {
  24. name: '初中及以下',
  25. value: 37.76,
  26. itemStyle: {
  27. color: '#0A62C5'
  28. }
  29. },
  30. {
  31. name: '高中',
  32. value: 27.47,
  33. itemStyle: {
  34. color: '#5559FA'
  35. }
  36. },
  37. {
  38. name: '研究生及以上',
  39. value: 0.26,
  40. itemStyle: {
  41. color: '#7739D0'
  42. }
  43. },
  44. {
  45. name: '本科',
  46. value: 10.49,
  47. itemStyle: {
  48. color: '#FCD515'
  49. }
  50. },
  51. {
  52. name: '专科',
  53. value: 23.04,
  54. itemStyle: {
  55. color: '#5BF96D'
  56. }
  57. }
  58. ],
  59. }
  60. },
  61. mounted() {
  62. this.initData()
  63. },
  64. watch: {
  65. education(newVal,oldVal) {
  66. this.initData()
  67. }
  68. },
  69. methods: {
  70. //数据匹配
  71. initData() {
  72. if (this.education instanceof Array) {
  73. console.log("this.education",this.education)
  74. this.optionData.filter(result => result.name == '文盲')[0].value = +this.education.filter(result => result
  75. .name ==
  76. '文盲')[0].rate;
  77. this.optionData.filter(result => result.name == '初中及以下')[0].value = +this.education.filter(result => result
  78. .name == '初中及以下')[0].rate;
  79. this.optionData.filter(result => result.name == '高中')[0].value = +this.education.filter(result => result
  80. .name ==
  81. '高中')[0].rate;
  82. this.optionData.filter(result => result.name == '研究生及以上')[0].value = +this.education.filter(result => result
  83. .name == '研究生及以上')[0].rate;
  84. this.optionData.filter(result => result.name == '本科')[0].value = +this.education.filter(result => result
  85. .name ==
  86. '本科')[0].rate;
  87. this.optionData.filter(result => result.name == '专科')[0].value = +this.education.filter(result => result
  88. .name ==
  89. '专科')[0].rate;
  90. }
  91. this.init();
  92. },
  93. init() {
  94. //构建3d饼状图
  95. let myChart = echarts.init(document.getElementById('cityGreenLand-charts4'));
  96. // 传入数据生成 option
  97. this.option = this.getPie3D(this.optionData, 0.8);
  98. myChart.setOption(this.option);
  99. //是否需要label指引线,如果要就添加一个透明的2d饼状图并调整角度使得labelLine和3d的饼状图对齐,并再次setOption
  100. this.option.series.push({
  101. name: 'pie2d',
  102. type: 'pie',
  103. labelLine: {
  104. length: 5,
  105. length2: 10
  106. },
  107. label: {
  108. formatter: '{b|{b}}\n{c}%',
  109. rich: {
  110. b: {
  111. color: '#fff',
  112. fontSize: 8,
  113. }
  114. }
  115. },
  116. startAngle: -20, //起始角度,支持范围[0, 360]。
  117. clockwise: false, //饼图的扇区是否是顺时针排布。上述这两项配置主要是为了对齐3d的样式
  118. radius: ['20%', '50%'],
  119. center: ['50%', '50%'],
  120. data: this.optionData,
  121. itemStyle: {
  122. opacity: 0
  123. }
  124. });
  125. myChart.setOption(this.option);
  126. let optionData = this.optionData;
  127. myChart.on('mouseover', function (params) {
  128. let num = optionData.filter(result => result.name == params.seriesName);
  129. if (num.length != 0) {
  130. document.getElementById("number_1").innerText = num[0].value + "%"
  131. }
  132. });
  133. // this.bindListen(myChart);
  134. },
  135. getPie3D(pieData, internalDiameterRatio) {
  136. //internalDiameterRatio:透明的空心占比
  137. let that = this;
  138. let series = [];
  139. let sumValue = 0;
  140. let startValue = 0;
  141. let endValue = 0;
  142. let legendData = [];
  143. let legendBfb = [];
  144. let k = 1 - internalDiameterRatio;
  145. pieData.sort((a, b) => {
  146. return (b.value - a.value);
  147. });
  148. // 为每一个饼图数据,生成一个 series-surface 配置
  149. for (let i = 0; i < pieData.length; i++) {
  150. sumValue += pieData[i].value;
  151. let seriesItem = {
  152. name: typeof pieData[i].name === 'undefined' ? `series${i}` : pieData[i].name,
  153. type: 'surface',
  154. parametric: true,
  155. wireframe: {
  156. show: false
  157. },
  158. pieData: pieData[i],
  159. pieStatus: {
  160. selected: false,
  161. hovered: true,
  162. k: k
  163. },
  164. center: ['10%', '50%']
  165. };
  166. if (typeof pieData[i].itemStyle != 'undefined') {
  167. let itemStyle = {};
  168. typeof pieData[i].itemStyle.color != 'undefined' ? itemStyle.color = pieData[i].itemStyle.color : null;
  169. typeof pieData[i].itemStyle.opacity != 'undefined' ? itemStyle.opacity = pieData[i].itemStyle.opacity :
  170. null;
  171. seriesItem.itemStyle = itemStyle;
  172. }
  173. series.push(seriesItem);
  174. }
  175. // 使用上一次遍历时,计算出的数据和 sumValue,调用 getParametricEquation 函数,
  176. // 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation,也就是实现每一个扇形。
  177. legendData = [];
  178. legendBfb = [];
  179. for (let i = 0; i < series.length; i++) {
  180. endValue = startValue + series[i].pieData.value;
  181. series[i].pieData.startRatio = startValue / sumValue;
  182. series[i].pieData.endRatio = endValue / sumValue;
  183. // series[i].parametricEquation = this.getParametricEquation(series[i].pieData.startRatio, series[i].pieData.endRatio,
  184. // false, false, k, series[i].pieData.value);
  185. series[i].parametricEquation = this.getParametricEquation(series[i].pieData.startRatio, series[i].pieData
  186. .endRatio,
  187. false, false, k, 20);
  188. startValue = endValue;
  189. let bfb = that.fomatFloat(series[i].pieData.value / sumValue, 4);
  190. legendData.push({
  191. name: series[i].name,
  192. value: bfb
  193. });
  194. legendBfb.push({
  195. name: series[i].name,
  196. value: bfb
  197. });
  198. }
  199. let boxHeight = this.getHeight3D(series, 20); //通过传参设定3d饼/环的高度,26代表26px
  200. //let boxHeight =0.03;
  201. // 准备待返回的配置项,把准备好的 legendData、series 传入。
  202. let option = {
  203. // legend: {
  204. // data: legendData,
  205. // orient: 'horizontal',
  206. // left: 80,
  207. // bottom: 10,
  208. // itemGap: 10,
  209. // textStyle: {
  210. // color: '#A1E2FF',
  211. // },
  212. // show: true,
  213. // icon: "circle",
  214. // // formatter: function(param) {
  215. // // let item = legendBfb.filter(item => item.name == param)[0];
  216. // // let bfs = that.fomatFloat(item.value * 100, 2) + "%";
  217. // // return `${item.name} ${bfs}`;
  218. // // }
  219. // },
  220. labelLine: {
  221. show: true,
  222. lineStyle: {
  223. color: '#7BC0CB'
  224. // color: '#ffffff'
  225. }
  226. },
  227. label: {
  228. show: true,
  229. position: 'outside',
  230. rich: {
  231. b: {
  232. // color: '#7BC0CB',
  233. color: '#ffffff',
  234. fontSize: 10,
  235. lineHeight: 15
  236. },
  237. c: {
  238. fontSize: 16,
  239. },
  240. },
  241. // formatter: '{b|{b} \n}{c|{c}}{b| 亩}',
  242. formatter: '{b|{b} \n}{c|{c}}{b| }',
  243. },
  244. // tooltip: {
  245. // formatter: params => {
  246. // if (params.seriesName !== 'mouseoutSeries' && params.seriesName !== 'pie2d') {
  247. // let bfb = ((option.series[params.seriesIndex].pieData.endRatio - option.series[params.seriesIndex].pieData.startRatio) *
  248. // 100).toFixed(2);
  249. // return `${params.seriesName}<br/>` +
  250. // `<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${params.color};"></span>` +
  251. // `${ bfb }%`;
  252. // }
  253. // }
  254. // },
  255. xAxis3D: {
  256. min: -1,
  257. max: 1
  258. },
  259. yAxis3D: {
  260. min: -1,
  261. max: 1
  262. },
  263. zAxis3D: {
  264. min: -1,
  265. max: 1
  266. },
  267. grid3D: {
  268. show: false,
  269. boxHeight: boxHeight, //圆环的高度
  270. viewControl: { //3d效果可以放大、旋转等,请自己去查看官方配置
  271. alpha: 70, //角度
  272. distance: 300, //调整视角到主体的距离,类似调整zoom
  273. rotateSensitivity: 0, //设置为0无法旋转
  274. zoomSensitivity: 0, //设置为0无法缩放
  275. panSensitivity: 0, //设置为0无法平移
  276. autoRotate: false //自动旋转
  277. }
  278. },
  279. series: series
  280. };
  281. return option;
  282. },
  283. //获取3d丙图的最高扇区的高度
  284. getHeight3D(series, height) {
  285. series.sort((a, b) => {
  286. return (b.pieData.value - a.pieData.value);
  287. })
  288. return height * 25 / series[0].pieData.value;
  289. },
  290. // 生成扇形的曲面参数方程,用于 series-surface.parametricEquation
  291. getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h) {
  292. // 计算
  293. let midRatio = (startRatio + endRatio) / 2;
  294. let startRadian = startRatio * Math.PI * 2;
  295. let endRadian = endRatio * Math.PI * 2;
  296. let midRadian = midRatio * Math.PI * 2;
  297. // 如果只有一个扇形,则不实现选中效果。
  298. if (startRatio === 0 && endRatio === 1) {
  299. isSelected = false;
  300. }
  301. // 通过扇形内径/外径的值,换算出辅助参数 k(默认值 1/3)
  302. k = typeof k !== 'undefined' ? k : 1 / 3;
  303. // 计算选中效果分别在 x 轴、y 轴方向上的位移(未选中,则位移均为 0)
  304. let offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0;
  305. let offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0;
  306. // 计算高亮效果的放大比例(未高亮,则比例为 1)
  307. let hoverRate = isHovered ? 1.05 : 1;
  308. // 返回曲面参数方程
  309. return {
  310. u: {
  311. min: -Math.PI,
  312. max: Math.PI * 3,
  313. step: Math.PI / 32
  314. },
  315. v: {
  316. min: 0,
  317. max: Math.PI * 2,
  318. step: Math.PI / 20
  319. },
  320. x: function (u, v) {
  321. if (u < startRadian) {
  322. return offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
  323. }
  324. if (u > endRadian) {
  325. return offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
  326. }
  327. return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate;
  328. },
  329. y: function (u, v) {
  330. if (u < startRadian) {
  331. return offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
  332. }
  333. if (u > endRadian) {
  334. return offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
  335. }
  336. return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate;
  337. },
  338. z: function (u, v) {
  339. if (u < -Math.PI * 0.5) {
  340. return Math.sin(u);
  341. }
  342. if (u > Math.PI * 2.5) {
  343. return Math.sin(u) * h * .1;
  344. }
  345. return Math.sin(v) > 0 ? 1 * h * .1 : -1;
  346. }
  347. };
  348. },
  349. fomatFloat(num, n) {
  350. var f = parseFloat(num);
  351. if (isNaN(f)) {
  352. return false;
  353. }
  354. f = Math.round(num * Math.pow(10, n)) / Math.pow(10, n); // n 幂
  355. var s = f.toString();
  356. var rs = s.indexOf('.');
  357. //判定如果是整数,增加小数点再补0
  358. if (rs < 0) {
  359. rs = s.length;
  360. s += '.';
  361. }
  362. while (s.length <= rs + n) {
  363. s += '0';
  364. }
  365. return s;
  366. },
  367. bindListen(myChart) {
  368. // 监听鼠标事件,实现饼图选中效果(单选),近似实现高亮(放大)效果。
  369. let that = this;
  370. let selectedIndex = '';
  371. let hoveredIndex = '';
  372. // 监听点击事件,实现选中效果(单选)
  373. myChart.on('click', function (params) {
  374. // 从 option.series 中读取重新渲染扇形所需的参数,将是否选中取反。
  375. let isSelected = !that.option.series[params.seriesIndex].pieStatus.selected;
  376. let isHovered = that.option.series[params.seriesIndex].pieStatus.hovered;
  377. let k = that.option.series[params.seriesIndex].pieStatus.k;
  378. let startRatio = that.option.series[params.seriesIndex].pieData.startRatio;
  379. let endRatio = that.option.series[params.seriesIndex].pieData.endRatio;
  380. // 如果之前选中过其他扇形,将其取消选中(对 option 更新)
  381. if (selectedIndex !== '' && selectedIndex !== params.seriesIndex) {
  382. that.option.series[selectedIndex].parametricEquation = that.getParametricEquation(that.option.series[
  383. selectedIndex].pieData
  384. .startRatio, that.option.series[selectedIndex].pieData.endRatio, false, false, k, that.option
  385. .series[
  386. selectedIndex].pieData
  387. .value);
  388. that.option.series[selectedIndex].pieStatus.selected = false;
  389. }
  390. // 对当前点击的扇形,执行选中/取消选中操作(对 option 更新)
  391. that.option.series[params.seriesIndex].parametricEquation = that.getParametricEquation(startRatio,
  392. endRatio,
  393. isSelected,
  394. isHovered, k, that.option.series[params.seriesIndex].pieData.value);
  395. that.option.series[params.seriesIndex].pieStatus.selected = isSelected;
  396. // 如果本次是选中操作,记录上次选中的扇形对应的系列号 seriesIndex
  397. isSelected ? selectedIndex = params.seriesIndex : null;
  398. // 使用更新后的 option,渲染图表
  399. myChart.setOption(that.option);
  400. });
  401. // 监听 mouseover,近似实现高亮(放大)效果
  402. myChart.on('mouseover', function (params) {
  403. // 准备重新渲染扇形所需的参数
  404. let isSelected;
  405. let isHovered;
  406. let startRatio;
  407. let endRatio;
  408. let k;
  409. // 如果触发 mouseover 的扇形当前已高亮,则不做操作
  410. if (hoveredIndex === params.seriesIndex) {
  411. return;
  412. // 否则进行高亮及必要的取消高亮操作
  413. } else {
  414. // 如果当前有高亮的扇形,取消其高亮状态(对 option 更新)
  415. if (hoveredIndex !== '') {
  416. // 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 false。
  417. isSelected = that.option.series[hoveredIndex].pieStatus.selected;
  418. isHovered = false;
  419. startRatio = that.option.series[hoveredIndex].pieData.startRatio;
  420. endRatio = that.option.series[hoveredIndex].pieData.endRatio;
  421. k = that.option.series[hoveredIndex].pieStatus.k;
  422. // 对当前点击的扇形,执行取消高亮操作(对 option 更新)
  423. that.option.series[hoveredIndex].parametricEquation = that.getParametricEquation(startRatio, endRatio,
  424. isSelected,
  425. isHovered, k, that.option.series[hoveredIndex].pieData.value);
  426. that.option.series[hoveredIndex].pieStatus.hovered = isHovered;
  427. // 将此前记录的上次选中的扇形对应的系列号 seriesIndex 清空
  428. hoveredIndex = '';
  429. }
  430. // 如果触发 mouseover 的扇形不是透明圆环,将其高亮(对 option 更新)
  431. if (params.seriesName !== 'mouseoutSeries' && params.seriesName !== 'pie2d') {
  432. // 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 true。
  433. isSelected = that.option.series[params.seriesIndex].pieStatus.selected;
  434. isHovered = true;
  435. startRatio = that.option.series[params.seriesIndex].pieData.startRatio;
  436. endRatio = that.option.series[params.seriesIndex].pieData.endRatio;
  437. k = that.option.series[params.seriesIndex].pieStatus.k;
  438. // 对当前点击的扇形,执行高亮操作(对 option 更新)
  439. that.option.series[params.seriesIndex].parametricEquation = that.getParametricEquation(startRatio,
  440. endRatio,
  441. isSelected, isHovered, k, that.option.series[params.seriesIndex].pieData.value + 5);
  442. that.option.series[params.seriesIndex].pieStatus.hovered = isHovered;
  443. // 记录上次高亮的扇形对应的系列号 seriesIndex
  444. hoveredIndex = params.seriesIndex;
  445. }
  446. // 使用更新后的 option,渲染图表
  447. myChart.setOption(that.option);
  448. }
  449. });
  450. // 修正取消高亮失败的 bug
  451. myChart.on('globalout', function () {
  452. // 准备重新渲染扇形所需的参数
  453. let isSelected;
  454. let isHovered;
  455. let startRatio;
  456. let endRatio;
  457. let k;
  458. if (hoveredIndex !== '') {
  459. // 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 true。
  460. isSelected = that.option.series[hoveredIndex].pieStatus.selected;
  461. isHovered = false;
  462. k = that.option.series[hoveredIndex].pieStatus.k;
  463. startRatio = that.option.series[hoveredIndex].pieData.startRatio;
  464. endRatio = that.option.series[hoveredIndex].pieData.endRatio;
  465. // 对当前点击的扇形,执行取消高亮操作(对 option 更新)
  466. that.option.series[hoveredIndex].parametricEquation = that.getParametricEquation(startRatio, endRatio,
  467. isSelected,
  468. isHovered, k, that.option.series[hoveredIndex].pieData.value);
  469. that.option.series[hoveredIndex].pieStatus.hovered = isHovered;
  470. // 将此前记录的上次选中的扇形对应的系列号 seriesIndex 清空
  471. hoveredIndex = '';
  472. }
  473. // 使用更新后的 option,渲染图表
  474. myChart.setOption(that.option);
  475. });
  476. }
  477. }
  478. }
  479. </script>
  480. <style scoped>
  481. .water-eval-container {
  482. position: absolute;
  483. width: 100%;
  484. height: 100%;
  485. top: -20%;
  486. right: 80%
  487. }
  488. .cityGreenLand-charts {
  489. height: 300px;
  490. width: 600px;
  491. }
  492. .num {
  493. position: absolute;
  494. width: 30%;
  495. height: 15%;
  496. right: -46%;
  497. top: 55%;
  498. font-weight: bold;
  499. /* background-color: rgb(255, 0, 0,0.3); */
  500. font-size: 25px;
  501. }
  502. @media screen and (max-height: 1000px) {
  503. .water-eval-container {
  504. position: absolute;
  505. width: 100%;
  506. height: 100%;
  507. top: -40%;
  508. right: 130%
  509. }
  510. .cityGreenLand-charts {
  511. height: 400px;
  512. width: 800px;
  513. }
  514. .num {
  515. position: absolute;
  516. width: 30%;
  517. height: 15%;
  518. right: -84%;
  519. top: 85%;
  520. font-weight: bold;
  521. /* background-color: rgb(255, 0, 0,0.3); */
  522. font-size: 35px;
  523. }
  524. }
  525. </style>