ThreeDCharts4.vue 20 KB

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