Javascript图表插件HighCharts用法案例

最近还在忙着基于ABP的项目,但本篇博客和ABP无关,喜欢ABP框架的朋友请点击传送门

这不,最近项目基本功能做的差不多了,现在在做一个数据统计的功能,需要绘制区域图(或折线图)和饼图。一开始,楼主就去Google了一下最常用的绘图插件都有哪些,最后直接去Github上搜索关键词chart,搜索结果如下:

点了几个进去看了之后,楼主考虑到项目要以后肯定要维护,万一维护的开发者英文不咋地呢(其实楼主我是喜欢看英文文档的)?所以,我起初选择了某度出品的Echarts.js。但是选择了它之后,查看文档学习,虽然文档是中文的,但我感觉这文档比英文还难读懂,因为有些术语解释不详细,最后好不容易做了个demo,但还出现了bug,开了一个Issue,维护人员简单地敷衍之后反而直接给关了,楼主表示很受伤也很气愤。心想,好吧,你某度牛逼,我不用你的Echarts行了吧,惹不起还躲不起嘛。

最后,经过几个朋友的介绍,他们都选择的Highcharts,去Highcharts官网看了下,感觉文档就是比Echarts详细,简单易懂,所以我也就选择了她。【这里建议新手朋友们先使用Highcharts,等对图表熟悉了再使用Echarts,毕竟Echarts的图表种类很丰富】而且,到现在,功能也都实现了,效果如下:

图片

图片

楼主在学习的时候,发现网上这方面的资料也不是很多,尤其是从服务端如何传数据到客户端,没有详细的解决方案,也是摸索了很久,这次,我直接将自己的解决方案拿出来和大家分享,供初学者参考,少走弯路,大神请绕道。

区域图

<div class="row">
    <div class="portlet light bordered">
        <div class="portlet-title">
            <div class="caption">
                <i class="fa fa-area-chart font-purple"></i>
                <span class="caption-subject  bold uppercase">收入趋势</span>
            </div>
        </div>
        <div class="portlet-body">
            <div id="incomeTrend" style="width:98%;height: 500px">

            </div>
        </div>

    </div>

</div>



		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,所以代码一起放到最后。

饼图

            <div class="row">
                <div class="portlet light bordered col-md-8">
                    <div class="portlet-title">
                        <div class="caption">
                            <i class="fa fa-adjust font-red"></i>
                            <span class="caption-subject  bold uppercase">收入比例</span>
                        </div>
                    </div>
                    <div class="portlet-body">
                        <div id="incomeRatio" style="width:90%;height: 500px">

                        </div>
                    </div>
                </div>



        var pieChartOption = {
            chart: {
                plotBackgroundColor: null,
                plotBorderWidth: null,
                plotShadow: false,
                type: 'pie'
            },
            title: {
                text: ''
            },
            credits: {
                enabled: false
            },
            tooltip: {
                pointFormat: '{series.name}: <b>{point.percentage:.1f}%</b>'
            },
            plotOptions: {
                pie: {
                    allowPointSelect: true,
                    cursor: 'pointer',
                    dataLabels: {
                        enabled: true,
                        format: '<b>{point.name}</b>: {point.percentage:.1f}%<br/> {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 Task<ContentResult> GetJsonResult(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<DateIncomeListWithName>
                {
                    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<PieChartDataFormat>();
                GetPieChartData(pieDataList, allKindsOfIncomeList);
                dataJsonStr = JsonConvert.SerializeObject(pieDataList, new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver() });
            }
            else
            {
                var dataList = new List<ChartDataFormat>();
                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<ChartDataFormat> dataList, List<DateIncomeListWithName> 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<PieChartDataFormat> dataList, List<DateIncomeListWithName> 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<DateIncomeDto> CheckoutIncomeList(List<DateIncomeDto> incomeList, List<DateTime> dateList)
        {
            var newIncomeList = new List<DateIncomeDto>();
            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<DateTime> ConvertTimeSpanToList(DateTimeSpan span)
        {
            var list = new List<DateTime>();
            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 List<decimal> Data { get; set; }
    }

    class PieChartDataFormat
    {
        public string Name { get; set; }
        public decimal Y { get; set; }
    }


应用服务层也贴一个方法的代码,仅供参考

        public async Task<List<DateIncomeDto>> 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导致您公司的重大损失,本人一概不负责任。此话莫当真,纯属娱乐一下。

posted @ 2016-03-30 17:52  tkbSimplest  阅读(2366)  评论(3编辑  收藏  举报