ThreeDCharts4.vue 20 KB

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