最近还在忙着基于ABP的项目,但本篇博客和ABP无关,喜欢ABP框架的朋友请点击。
这不,最近项目基本功能做的差不多了,现在在做一个数据统计的功能,需要绘制区域图(或折线图)和饼图。一开始,楼主就去Google了一下最常用的绘图插件都有哪些,最后直接去Github上搜索关键词chart,搜索结果如下:
点了几个进去看了之后,楼主考虑到项目要以后肯定要维护,万一维护的开发者英文不咋地呢(其实楼主我是喜欢看英文文档的)?所以,我起初选择了某度出品的Echarts.js。但是选择了它之后,查看文档学习,虽然文档是中文的,但我感觉这文档比英文还难读懂,因为有些术语解释不详细,最后好不容易做了个demo,但还出现了bug,开了一个,维护人员简单地敷衍之后反而直接给关了,楼主表示很受伤也很气愤。心想,好吧,你某度牛逼,我不用你的Echarts行了吧,惹不起还躲不起嘛。
最后,经过几个朋友的介绍,他们都选择的Highcharts,去Highcharts官网看了下,感觉文档就是比Echarts详细,简单易懂,所以我也就选择了她。【这里建议新手朋友们先使用Highcharts,等对图表熟悉了再使用Echarts,毕竟Echarts的图表种类很丰富】而且,到现在,功能也都实现了,效果如下:
楼主在学习的时候,发现网上这方面的资料也不是很多,尤其是从服务端如何传数据到客户端,没有详细的解决方案,也是摸索了很久,这次,我直接将自己的解决方案拿出来和大家分享,供初学者参考,少走弯路,大神请绕道。
区域图
var dateSpan; Highcharts.setOptions({ lang: { printChart: '打印图表', downloadJPEG: '下载为JPEG图片', downloadPDF: '下载为PDF', downloadPNG: '下载为PNG图片', downloadSVG: '下载为SVG矢量图', months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"], weekdays: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"], shortMonths: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"], } }); var isByDay = true;//default by days var option = { chart: { type: 'area' }, title: { text: '收入趋势图' }, subtitle: { text: '没有选择时间范围的话,默认显示最近7天的数据' }, credits: { enabled: false }, xAxis: { type: 'datetime', tickmarkPlacement: 'on', title: { enabled: false }, dateTimeLabelFormats: { day: "%Y-%m-%d", week: "%A", month: "%Y-%m", year: "%Y" } }, yAxis: { title: { text: '单位:元' }, labels: { formatter: function () { return this.value; } } }, tooltip: { shared: true, valueSuffix: ' 元', dateTimeLabelFormats: { day: "%Y-%m-%d,%A", week: "%A开始, %Y-%m-%d", month: "%Y-%m", year: "%Y" } }, plotOptions: { area: { stacking: 'normal', lineColor: '#666666', lineWidth: 1, marker: { lineWidth: 1, lineColor: '#666666' } }, series: { //pointStart: Date.UTC(nowDate.getFullYear(), nowDate.getMonth(), nowDate.getDate() - 7), //pointInterval: 24 * 36e5 //一天 } }, series: [{}] } var url = getTargetUrl('Dashboard', "GetJsonResult");//这里是url var drp = $('#dateRange').data('daterangepicker'); if (!dateSpan) { dateSpan = { start: drp.startDate.format('YYYY-MM-DD'), end: drp.endDate.format('YYYY-MM-DD') } } rawChart(isByDay); $('#createChart').click(function (e) { if ($('#byMonth').attr('checked')) {//按月 isByDay = false; //alert('选择了' + $('#byMonth').attr('checked')); } e.preventDefault(); drawChart(isByDay); drawPieChart(isByDay); }); $('#defaultChart').click(function (e) { e.preventDefault(); drp.setStartDate(moment().subtract(7, "days")); drp.setEndDate(moment().subtract(1, "days")); dateSpan = { start: drp.startDate.format('YYYY-MM-DD'), end: drp.endDate.format('YYYY-MM-DD') }; $('#dateRange').val(''); isByDay = true; drawChart(isByDay); drawPieChart(isByDay); }); function drawChart(isByDay) { var year = moment(dateSpan.start).format('YYYY'); var month = moment(dateSpan.start).format('M') - 1;//js的date函数的月份是从0-11,所以这里减1 var day = moment(dateSpan.start).format('D'); //console.log(year,month,day); if (isByDay) { $.getJSON(url, dateSpan, function (datas) { option.series = datas; option.plotOptions.series.pointStart = Date.UTC(year, month, day); option.plotOptions.series.pointInterval = 24 * 36e5; $('#incomeTrend').highcharts(option); }); } else { var start = drp.startDate.format('YYYY-MM'); var end = drp.endDate.format('YYYY-MM'); if (start == end) { start = drp.startDate.subtract(5, "month").format('YYYY-MM'); } year = moment(start).format('YYYY'); month = moment(start).format('M')-1; dateSpan = { start: start, end: end }; $.getJSON(url, dateSpan, function (datas) { option.series = datas; option.plotOptions.series.pointStart = Date.UTC(year, month, 1); option.plotOptions.series.pointInterval = 1; option.plotOptions.series.pointIntervalUnit = "month"; $('#incomeTrend').highcharts(option); }); } }
注意: 区域图和饼图公用同一个action,所以代码一起放到最后。
饼图
var pieChartOption = { chart: { plotBackgroundColor: null, plotBorderWidth: null, plotShadow: false, type: 'pie' }, title: { text: '' }, credits: { enabled: false }, tooltip: { pointFormat: '{series.name}: {point.percentage:.1f}%' }, plotOptions: { pie: { allowPointSelect: true, cursor: 'pointer', dataLabels: { enabled: true, format: '{point.name}: {point.percentage:.1f}% {y}元 ', style: { color: (Highcharts.theme && Highcharts.theme.contrastTextColor) || 'black' } } } }, series: [ { name: '占比', colorByPoint: true, data: [] } ] }; function drawPieChart() { var year = moment(dateSpan.start).format('YYYY'); var month = moment(dateSpan.start).format('M') - 1;//js的date函数的月份是从0-11,所以这里减1 var day = moment(dateSpan.start).format('D'); //console.log(year,month,day); $.getJSON(url + "?chartType=pie", dateSpan, function (datas) { pieChartOption.series[0].data = datas; var sum=0; for (var i = 0; i < datas.length; i++) { sum += datas[i].y; } pieChartOption.title.text = "收入比例情况:(总收入"+sum+")元"; $('#incomeRatio').highcharts(pieChartOption); }); } drawPieChart();
服务端Web层的C#代码如下:
public async TaskGetJsonResult(string start, string end) { string dataJsonStr; var defaultStart = DateTime.Parse(start); var defaultEnd = DateTime.Parse(end); var timeSpan = new DateTimeSpan { Start = defaultStart, End = defaultEnd }; var totalIncomeList = await _orderAppService.GetDateIncome(new GetDateIncomeDto { Start = defaultStart, End = defaultEnd });//总收入 var scanCodeChargeIncomeList = await _orderAppService.GetDateIncome(new GetDateIncomeDto { Start = defaultStart, End = defaultEnd, IsScanCodeChargingIncome = true });//扫码充电收入 var lineSoldIncomeList = await _orderAppService.GetDateIncome(new GetDateIncomeDto { Start = defaultStart, End = defaultEnd, IsLineSoldIncome = true });//售线收入 var castCoinsIncomeList = await _castCoinsAppService.GetDateCoinsIncome(new GetDateCoinsIncomeDto { Start = defaultStart, End = defaultEnd });//投币收入 var allKindsOfIncomeList = new List { new DateIncomeListWithName { DateIncomeDtos = castCoinsIncomeList, Name = "投币" }, new DateIncomeListWithName { DateIncomeDtos = lineSoldIncomeList, Name = "售线" }, new DateIncomeListWithName { DateIncomeDtos = scanCodeChargeIncomeList, Name = "扫码充电" } }; if (Request.QueryString.Get("chartType") == "pie")//饼图 { var pieDataList = new List (); GetPieChartData(pieDataList, allKindsOfIncomeList); dataJsonStr = JsonConvert.SerializeObject(pieDataList, new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver() }); } else { var dataList = new List (); allKindsOfIncomeList.Add(new DateIncomeListWithName{DateIncomeDtos = totalIncomeList,Name = "总收入"}); GetData(dataList,allKindsOfIncomeList,timeSpan); dataJsonStr = JsonConvert.SerializeObject(dataList, new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver() }); } return Content(dataJsonStr); } private void GetData(List dataList, List incomeList, DateTimeSpan span) { var dateList = ConvertTimeSpanToList(span); foreach (DateIncomeListWithName dateIncomeListWithName in incomeList) { var newList = CheckoutIncomeList(dateIncomeListWithName.DateIncomeDtos, dateList); var list = newList.Select(dateIncomeDto => dateIncomeDto.Income).ToList(); dataList.Add(new ChartDataFormat { Name = dateIncomeListWithName.Name, Data = list }); } } private void GetPieChartData(List dataList, List incomeLists) { foreach (DateIncomeListWithName dateIncomeListWithName in incomeLists) { var total = dateIncomeListWithName.DateIncomeDtos.Sum(i => i.Income); var item = new PieChartDataFormat { Name = dateIncomeListWithName.Name, Y = total }; dataList.Add(item); } } List CheckoutIncomeList(List incomeList, List dateList) { var newIncomeList = new List (); newIncomeList = (from date in dateList join incomeDto in incomeList on date.Date equals incomeDto.Date into result from r in result.DefaultIfEmpty() select new DateIncomeDto { Date = date.Date, Income = r == null ? 0 : r.Income }).ToList(); return newIncomeList; } private List ConvertTimeSpanToList(DateTimeSpan span) { var list = new List (); for (DateTime i = span.Start; i <= span.End; i = i.AddDays(1)) { list.Add(i); } return list; }
上面这段代码,楼主自认为封装的还不错,很简洁(这已经成为楼主编程追求的目标),平均每个方法10行左右(除了第一个),仅供大家参考。
下面两个类定义了两种图表从Server端到Client端的数据格式 :
class ChartDataFormat { public string Name { get; set; } public ListData { get; set; } } class PieChartDataFormat { public string Name { get; set; } public decimal Y { get; set; } }
应用服务层也贴一个方法的代码,仅供参考
public async Task
> GetDateIncome(GetDateIncomeDto input) { var query= _orderRepository.GetAll() .Where(o => o.Status == OrderStatus.Freezen || o.Status == OrderStatus.Settled || o.Status == OrderStatus.HasInformedDevice) .Where(o => o.OrderDate >= input.Start && o.OrderDate <= input.End) .WhereIf(input.IsLineSoldIncome,o=>o.OrderType==OrderType.LineSold) .WhereIf(input.IsScanCodeChargingIncome,o=>o.OrderType==OrderType.Charge) .OrderBy(o => DbFunctions.TruncateTime(o.OrderDate)) .GroupBy(o => DbFunctions.TruncateTime(o.OrderDate)) .Select(group => new DateIncomeDto{Date=group.Key.Value,Income=group.Sum(item=>item.PayFee??0)}); var list = await query.ToListAsync(); return list; }
这些就是整个图表的实现方案,切记仅供参考,不可生搬硬套,如因程序bug导致您公司的重大损失,本人一概不负责任。此话莫当真,纯属娱乐一下。