app.controller('devChartCtrl', ['$scope', '$http', '$timeout', '$interval', 'toaster', 'chartOptions', '$localStorage', function ($scope, $http, $timeout, $interval, toaster, chartOptions, $localStorage) { particlesJS.load('particles-js', '/1.0/vendor/libs/particles.json', function () { }); //每4个字自动换行 function autoBreak(str) { var lineNumber = 4; var w = $(window).width() if (w <= 1366) { lineNumber = 3 } else if (w <= 1920) { lineNumber = 4 } else if (w <= 4096) { lineNumber = 8 } var newStr = ''; for (var index = 0; index < str.length; index++) { var item = str[index] newStr = newStr + item if ((index + 1) % lineNumber === 0) { newStr = newStr + '\n' } } return newStr } /*******以下为echarts*********/ var devChartPanelMap = echarts.init(document.getElementById('devChartPanelMap')); var incomeChartLine = echarts.init(document.getElementById('incomeChartLine')); var consumeChartLine = echarts.init(document.getElementById('consumeChartLine')); var userChartPie = echarts.init(document.getElementById('userChartPie')); var feedbackChartPie = echarts.init(document.getElementById('feedbackChartPie')); var dealerIncomeTopChart = echarts.init(document.getElementById('dealerIncomeTopChart')); var rightChart1 = echarts.init(document.getElementById('rightChart1')); var devBeingUsedChart = echarts.init(document.getElementById('devBeingUsedChart')); function resizeChart() { clearTimeout(timer); // resize有尺寸改变完后会有一定的延迟,会导致echarts提前渲染,依然是按照原来的尺寸渲染 timer = setTimeout(function () { devChartPanelMap.resize(); incomeChartLine.resize(); consumeChartLine.resize(); userChartPie.resize(); feedbackChartPie.resize(); dealerIncomeTopChart.resize(); rightChart1.resize(); devBeingUsedChart.resize(); }, 300); } $scope.showData = { devBusy: 0, devOnline: 0, devOffline: 0, unregistered: 0 }; $interval(function () { setMapTime(); }, 1000); function setMapTime() { $scope.showData.date = moment().format("YYYY年MM月DD日") $scope.showData.week = moment().format('dddd') $scope.showData.time = moment().format("HH:mm:ss") } setMapTime(); var timer = null; //resize事件绑定 $(window).off("resize.devChart").on("resize.devChart", function () { resizeChart(); }); var colorStopList = { blue: ['#57FFE0', '#3469E2'], red: ['#FF7671', '#A14AFF'], yellow: ['#FFEA4F', '#F89212'], green: ['#61E0B1', '#00B25E'], gray: ['#fff', '#cfcfcf'], purple: ['#D3B5DF', '#8865A5'], } function getColorStop(key) { return { colorStops: [{ offset: 0, color: colorStopList[key][0] // 0% 处的颜色 }, { offset: 1, color: colorStopList[key][1] // 100% 处的颜色 }] } } $scope.optionConfig = { getStaticMapOption: function (chartEntity, dataIn) { var data = dataIn.dataList; var mapName = 'china' var geoCoordMap = {}; /*获取地图数据*/ chartEntity.showLoading(); var mapFeatures = echarts.getMap(mapName).geoJson.features; chartEntity.hideLoading(); mapFeatures.forEach(function (v) { // 地区名称 var name = v.properties.name; // 地区经纬度 geoCoordMap[name] = v.properties.cp; }); var tempSort = data.sort(function (a, b) { return b.value - a.value; }) var tempSortLen = tempSort.length; // 值的范围 var max, min; if (tempSortLen > 0) { // 值的范围 max = tempSort[0].value min = tempSort[tempSortLen - 1].value; } // 地图气泡的大小范围 var maxSize4Pin = 72, minSize4Pin = 30; var convertData = function (data) { var res = []; for (var i = 0; i < data.length; i++) { var geoCoord = geoCoordMap[data[i].name]; if (geoCoord) { res.push({ name: data[i].name, value: geoCoord.concat(data[i].value), }); } } return res; }; var markData = convertData(data); // 底点的最大尺寸 var pointMaxSize = 25; var option = { visualMap: { show: false, min: 0, max: 500, left: 'left', top: 'bottom', text: ['高', '低'], // 文本,默认为数值文本 calculable: true, seriesIndex: [1], inRange: { color: ['#044161', '#2B91B7'] } }, geo: { show: true, map: mapName, label: { normal: { show: false }, emphasis: { show: false, } }, roam: true, itemStyle: { normal: { areaColor: '#031525', borderColor: '#3B5077', }, emphasis: { areaColor: '#2B91B7', } } }, series: [{ name: '散点',// 蓝色的小点 type: 'scatter', coordinateSystem: 'geo', data: markData, symbolSize: function (val) { var size = val[2] / 10 if (size > pointMaxSize) { size = pointMaxSize } return size; }, label: { normal: { formatter: '{b}', position: 'right', show: true }, emphasis: { show: true } }, itemStyle: { normal: { color: '#05C3F9' } } }, { type: 'map', map: mapName, geoIndex: 0, aspectScale: 0.75, //长宽比 showLegendSymbol: false, // 存在legend时显示 label: { normal: { show: true }, emphasis: { show: false, textStyle: { color: '#fff' } } }, roam: true, itemStyle: { normal: { areaColor: '#031525', borderColor: '#3B5077', }, emphasis: { areaColor: '#2B91B7' } }, animation: false, data: data }, { // 小点上的气泡 name: '气泡', type: 'scatter', coordinateSystem: 'geo', symbol: 'pin', //气泡 symbolSize: function (val) { var a = val[2] / (max - min);//当前值和最大值的比例 var size = minSize4Pin + (maxSize4Pin - minSize4Pin) * a;// 实际大小 if (size > maxSize4Pin) { size = maxSize4Pin } return size; }, label: { normal: { show: true, formatter: function (params) { var size = params.value[2] return size }, textStyle: { color: '#fff', fontSize: 9, } } }, itemStyle: { normal: { color: '#108EE9', //标志颜色 } }, zlevel: 6, data: markData, }, { // 排名前5 底部的小点变成黄色 漪涟 name: 'Top 5', type: 'effectScatter', coordinateSystem: 'geo', data: convertData(data.sort(function (a, b) { return b.value - a.value; }).slice(0, 5)), symbolSize: function (val) { var size = val[2] / 10 if (size > pointMaxSize) { size = pointMaxSize } return size; }, showEffectOn: 'render', rippleEffect: { brushType: 'stroke' }, hoverAnimation: true, label: { normal: { formatter: '{b}', position: 'right', show: true } }, itemStyle: { normal: { color: 'yellow', shadowBlur: 10, shadowColor: 'yellow' } }, zlevel: 1 }, ] }; return option }, //坐标系的公共风格 getLineOptionsForMap: function (xData, textBreak) { var option = { tooltip: { trigger: 'axis', axisPointer: { lineStyle: { color: '#108EE9', width: .3, } } }, grid: { x: 52, x2: 15, y: 30, y2: textBreak ? 32 : 28,// 自动换行的X坐标需要展示2行,空间要多一点 }, xAxis: [ { type: 'category', axisLabel: { textStyle: { color: "#fff", fontSize: "12px" } }, axisTick: { show: false, }, axisLine: { show: false, }, data: xData.map(function (str) { if (textBreak) { return autoBreak(str) } else { return str } }) } ], yAxis: [ { type: 'value', axisLabel: { textStyle: { color: "#fff", fontSize: "12px" } }, axisTick: { show: false, }, axisLine: { show: false, }, splitLine: { show: false, }, }, ], series: [] }; return option; }, getIncomeLineOption: function (data) { var payIncomeCount = 0; var lineCoinsCount = 0; var xData = []; var yData = {payIncome: [], lineCoins: []}; for (var i = 0; i < data.length; i++) { var item = data[data.length - i - 1]; xData.push(item.dateStr); yData.payIncome.push(item.payIncome); yData.lineCoins.push(item.lineCoins); payIncomeCount = payIncomeCount + item.payIncome; lineCoinsCount = lineCoinsCount + item.lineCoins; } var option = this.getLineOptionsForMap(xData); option.series = [ { name: '在线收入', stack: '收入', type: 'bar', itemStyle: { color: "rgba(16,142,233,.9)", }, data: yData.payIncome }, { name: '线下投币', stack: '收入', type: 'bar', itemStyle: { color: "rgba(16,142,233,.6)", }, data: yData.lineCoins } ]; return option; }, // 由于早期的消费可能没有数据,需要给源数据,需要补齐 0,否则折现的时间轴和数据对应错误; fixConsumptionData: function (list) { // 找到所有key var allConsumKey = {}; for (var index in list) { var item = list[index]; var list2 = item.consumptionList; for (var index2 in list2) { var item2 = list2[index2]; var name = item2.name; allConsumKey[name] = name; } } //修复该条数据。 这里只是多循环一次,性能问题不大。 for (var index in list) { var item = list[index]; if (!item.consumptionList) { item.consumptionList = [] } var list2 = item.consumptionList; for (var key in allConsumKey) { var hasKeyFlag = false; for (var index2 in list2) { var item2 = list2[index2]; var name = item2.name; // 如果有 key的消费数据,则找下一个key if (key === name) { hasKeyFlag = true; break; } } // 如果list2循环结束后,找不到key,则补全数据; if (!hasKeyFlag) { item.consumptionList.push({ name: key, value: 0, }); } } } }, getConsumptionLineOption: function (list) { //补齐数据结构 this.fixConsumptionData(list); var xData = []; var consumeSeriesMap = {}; var length = list.length; for (var index in list) { // 正序时间排列 var item = list[length - 1 - index]; xData.push(item.dateStr); var list2 = item.consumptionList; for (var index2 in list2) { var item2 = list2[index2]; var name = item2.name; var value = item2.value; if (!consumeSeriesMap[name]) { consumeSeriesMap[name] = []; } consumeSeriesMap[name].push(value); } } var myColor = this.getColorList(); var option = this.getLineOptionsForMap(xData); var index = 0; for (var key in consumeSeriesMap) { var dataList = consumeSeriesMap[key]; option.series.push({ name: key, type: 'line', itemStyle: { normal: { color: myColor[index * 2],//相邻的颜色太相似了,所以间隔一个,越界不会异常,会使用默认色 } }, data: dataList }); index++; } return option; }, // pie的公共item风格 getPieItemStyle: function () { var opt = { normal: { label: { show: true, position: 'outside', color: '#ddd', formatter: function (params) { return params.name + ':' + params.value; }, rich: { white: { color: '#ddd', align: 'center', padding: [3, 0] } } }, labelLine: { length: 8, length2: 12, show: true, } } }; return opt }, getUserPieOption: function (payload) { var option = { legend: { orient: 'vertical', align: 'left', x2: 10, y: 'center', textStyle: { color: "#ddd", fontSize: 12 }, data: ['总数', '日活', '男性', '女性', '未知',] }, tooltip: { trigger: 'item', }, series: [ { type: 'pie', radius: ['0%', '28%'], center: ['40%', '50%'], label: { normal: { color: '#ddd', position: 'inner' } }, labelLine: { normal: { show: false } }, "itemStyle": {"normal": {borderWidth: 2}}, data: [ { value: payload.userCount, name: "总数", "itemStyle": {"normal": {"color": getColorStop("purple")}} }, { value: payload.todayActiveUsers, name: "日活", "itemStyle": {"normal": {"color": getColorStop("green")}} } ] }, { type: 'pie', radius: ['34%', '60%'], center: ['40%', '50%'], roseType: 'radius', itemStyle: this.getPieItemStyle(), data: [ { value: payload.maleCount, name: "男性", "itemStyle": {"normal": {"color": getColorStop("blue"),}}, }, { value: payload.femaleCount, name: "女性", "itemStyle": {"normal": {"color": getColorStop("red"),}} }, { value: (payload.userCount - payload.maleCount - payload.femaleCount), name: "未知", "itemStyle": {"normal": {"color": getColorStop("gray"),}} }] }, ] }; return option; }, getFeedbackPieOption: function (payload) { var placeHolderStyle = { normal: { color: 'rgba(0,0,0,.1)', label: { show: false, }, labelLine: {show: false} }, emphasis: { color: 'rgba(0,0,0,0)', } }; var seriesOpt = { type: 'pie', center: ['40%', '50%'], itemStyle: this.getPieItemStyle(), animationType: 'scale', animationEasing: 'elasticOut', animationDelay: function (idx) { return Math.random() * 200; } }; var option = { legend: { orient: 'vertical', align: 'left', x2: 10, y: 'center', textStyle: { color: "#ddd", fontSize: 12 }, data: ['已上分', '已退币', '已维修',] }, tooltip: { trigger: 'item', }, series: [ $.extend(true, {}, seriesOpt, { radius: ['28%', '38%'], data: [{ value: payload.upperProcessedCount, name: '已上分', labelLine: { length: 20, length2: 20, show: true, }, "itemStyle": {"normal": {"color": getColorStop("blue")}}, }, { value: payload.upperCount - payload.upperProcessedCount, name: '未上分', label: {show: false}, itemStyle: placeHolderStyle }], }), $.extend(true, {}, seriesOpt, { radius: ['40%', '50%'], data: [{ value: payload.refundProcessedCount, name: '已退币', labelLine: { length: 10, length2: 20, show: true, }, "itemStyle": {"normal": {"color": getColorStop("yellow")}}, }, { value: payload.refundCount - payload.refundProcessedCount, name: '未退币', label: {show: false}, itemStyle: placeHolderStyle }], }), $.extend(true, {}, seriesOpt, { radius: ['52%', '62%'], data: [{ value: payload.faultProcessedCount, name: '已维修', "itemStyle": {"normal": {"color": getColorStop("red")}}, }, { value: payload.faultCount - payload.faultProcessedCount, name: '未维修', label: {show: false}, itemStyle: placeHolderStyle }], }), ] }; return option; }, getColorList: function () { var myColor = ['#f845f1', '#ad46f3', '#5045f6', '#4777f5', '#44aff0', '#45dbf7', '#f6d54a', '#f69846', '#ff4343']; return myColor; }, // 由于早期的消费可能没有数据,需要给源数据,需要补齐 0,否则折现的时间轴和数据对应错误; fixData: function (list) { // 找到所有key var allConsumKey = {}; for (var index in list) { var item = list[index]; var list2 = item.items; for (var index2 in list2) { var item2 = list2[index2]; var name = item2.name; allConsumKey[name] = name; } } //修复该条数据。 这里只是多循环一次,性能问题不大。 for (var index in list) { var item = list[index]; if (!item.items) { item.items = [] } var list2 = item.items; for (var key in allConsumKey) { var hasKeyFlag = false; for (var index2 in list2) { var item2 = list2[index2]; var name = item2.name; // 如果有 key的消费数据,则找下一个key if (key === name) { hasKeyFlag = true; break; } } // 如果list2循环结束后,找不到key,则补全数据; if (!hasKeyFlag) { item.items.push({ name: key, value: 0, }); } } } }, // 设置公告的 pictorialBar风格 setPictorialBarStyle: function (option, yData, hasPercent) { var myColor = this.getColorList(); if (!hasPercent) { myColor.reverse(); } option.tooltip.formatter = function (params) { var param = params[0]; var name = param.name.replace(/[\n]/g, '');//去掉换行符号 var span = ''; if (hasPercent) { return span + name + ":占比" + param.value + "%" } else { return span + name + ":" + param.value } }; option.series.push({ name: '消费次数', type: 'pictorialBar', barCategoryGap: '20%', // 可以在线测试 symbol,http://www.runoob.com/try/try.php?filename=trysvg_path2 // 稍尖:M200 200 c20 -10,80 -40, 85 -180c5 140,65 170,85 180l-170 0Z // 很尖:M200 200 c50 -10,80 -40, 85 -180c5 140,35 170,85 180l-170 0Z symbol: 'path://M200 200 c20 -10,80 -40, 85 -180c5 140,65 170,85 180l-170 0Z', label: { normal: { show: true, position: 'top', formatter: function (param) { if (hasPercent) { return param.value + "%" } else { return param.value } }, textStyle: { fontSize: '12', color: '#fff' } } }, itemStyle: { normal: { color: function (params) { var num = myColor.length; return myColor[params.dataIndex % num] }, } }, data: yData, }); }, getDealerIncomeTopChartOption: function (data) { var xData = []; var yData = []; for (var i = 0; i < data.length; i++) { var item = data[i]; xData.push(item.name); yData.push(item.payIncomeTotal); } var option = this.getLineOptionsForMap(xData, true); this.setPictorialBarStyle(option, yData); return option }, getUserConsumeFrequencyOption: function (data) { var xData = []; var yData = []; for (var i = 0; i < data.length; i++) { var item = data[i]; xData.push(item.name); yData.push(item.percent); } var option = this.getLineOptionsForMap(xData); this.setPictorialBarStyle(option, yData, true); return option }, // 全网用户 getAllUserOption: function (list) { //补齐数据结构 this.fixData(list); var xData = []; var consumeSeriesMap = {}; var length = list.length; for (var index in list) { // 正序时间排列 var item = list[length - 1 - index]; xData.push(item.dateStr); var list2 = item.items; for (var index2 in list2) { var item2 = list2[index2]; var name = item2.name; var value = item2.value; if (!consumeSeriesMap[name]) { consumeSeriesMap[name] = []; } consumeSeriesMap[name].push(value); } } var myColor = this.getColorList(); var option = this.getLineOptionsForMap(xData); var index = 0; for (var key in consumeSeriesMap) { var dataList = consumeSeriesMap[key]; option.series.push({ name: key, type: 'line', itemStyle: { normal: { color: myColor[index * 2],//相邻的颜色太相似了,所以间隔一个,越界不会异常,会使用默认色 } }, data: dataList }); index++; } return option; }, getDevBeingUsedOption: function (data) { var xData = []; var yData = []; for (var i = 0; i < data.length; i++) { var item = data[i]; xData.push(item.time); yData.push(item.devBeingUsed); } var option = this.getLineOptionsForMap(xData); option.series = [ { name: '订单数', type: 'line', smooth: true, symbolSize: 0, itemStyle: { color: "rgba(16,142,233,.9)", }, lineStyle: { normal: { width: 1 } }, areaStyle: { //区域填充样式 normal: { //线性渐变,前4个参数分别是x0,y0,x2,y2(范围0~1);相当于图形包围盒中的百分比。如果最后一个参数是‘true’,则该四个值是绝对像素位置。 color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ {offset: 0, color: 'rgba(255,67,67, 0.8)'}, {offset: 0.5, color: 'rgba(16,142,233, 0.5)'}, {offset: 1, color: 'rgba(16,142,233, 0.1)'} ], false), shadowColor: 'rgba(53,142,215, 0.9)', //阴影颜色 shadowBlur: 20 //shadowBlur设图形阴影的模糊大小。配合shadowColor,shadowOffsetX/Y, 设置图形的阴影效果。 } }, data: yData } ]; return option; }, }; $scope.chartActive = { incomeChartLine: false, consumeChartLine: false, userChartPie: false, feedbackChartPie: false, dealerIncomeTopChart: false, userConsumeFrequencyChart: false, devBeingUsedChart: false, }; var feature_map = $localStorage.feature_map //按条件加载统计图 因为有的接口太慢,避免等待,直接并发 function loadChartData() { var url = '/manager/getDevMapChart' //获取数据:地图设备统计 $http.get(url, {}).then(function (data) { var payload = data.data.payload if (payload) { //地图拓展api地址:https://github.com/ecomfe/echarts/tree/master/extension/bmap var option = $scope.optionConfig.getStaticMapOption(devChartPanelMap, payload); var userName = $('.app-header .navbar-right .hidden-sm').text(); if (userName.indexOf('郑建如') > -1) { var date = new Date() var rand = date.getHours() * 55 + date.getMinutes() * 22 + date.getSeconds() * 3 var rand2 = Math.round(Math.random() * 20); var v1 = 60172 + rand + rand2; var v2 = 92051 + rand + rand2; var v3 = 10297 - rand - rand2; // mock data $scope.showData.devBusy = v1; $scope.showData.devOnline = v2; $scope.showData.devOffline = v3; } else { $scope.showData.devBusy = payload.busy; $scope.showData.devOnline = payload.online; $scope.showData.devOffline = payload.offline; $scope.showData.unregistered = payload.unregistered; } devChartPanelMap.setOption(option); devChartPanelMap.resize();// 确保地图居中 } }).then(function () { $scope.chartActive.incomeChartLine = true; var url = '/manager/getAllDeviceIncomeList' $http.get(url, {}).then(function (data) { data = data.data if (data.payload.dataList) { var option = $scope.optionConfig.getIncomeLineOption(data.payload.dataList); incomeChartLine.setOption(option); } }); }).then(function () { $scope.chartActive.consumeChartLine = true; var url = '/manager/getAllDeviceConsumption' $http.get(url, {}).then(function (data) { data = data.data if (data.payload.dataList) { var option = $scope.optionConfig.getConsumptionLineOption(data.payload.dataList); consumeChartLine.setOption(option); } }); }).then(function () { $scope.chartActive.userChartPie = true; var url = '/manager/getAllUserStatistics' $http.get(url, {}).then(function (data) { data = data.data if (data.payload) { var option = $scope.optionConfig.getUserPieOption(data.payload); userChartPie.setOption(option); } }); }).then(function () { $scope.chartActive.feedbackChartPie = true; var url = '/manager/getAllFeedbackStatistics' $http.get(url, {}).then(function (data) { data = data.data if (data.payload) { var option = $scope.optionConfig.getFeedbackPieOption(data.payload); feedbackChartPie.setOption(option); } }); }).then(function () { $scope.chartActive.dealerIncomeTopChart = true; var url = '/manager/getDealerIncomeTotalTop' $http.get(url, {}).then(function (data) { data = data.data if (data.payload) { var option = $scope.optionConfig.getDealerIncomeTopChartOption(data.payload.dataList); dealerIncomeTopChart.setOption(option); } }); }).then(function () { $scope.chartActive.rightChart1 = true; var url = '/manager/getUserConsumeFrequency' $http.get(url, {}).then(function (data) { data = data.data if (data.payload) { var option = $scope.optionConfig.getUserConsumeFrequencyOption(data.payload.dataList); rightChart1.setOption(option); } }); }).then(function () { $scope.chartActive.devBeingUsedChart = true; var url = '/manager/getDevBeingUsedTrend' $http.get(url, {}).then(function (data) { data = data.data if (data.payload) { var option = $scope.optionConfig.getDevBeingUsedOption(data.payload.dataList); devBeingUsedChart.setOption(option); } }); }) } $scope.chartHasLoad = false $scope.loadAllChart = function () { loadChartData(); $scope.chartHasLoad = true } // 全屏事件绑定 $scope.isFullscreen = false; $scope.$on('fullscreenchange', function (evt, data) { $timeout(function () { $scope.isFullscreen = screenfull.isFullscreen; resizeChart() }) }); }]);