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導致您公司的重大損失,本人一概不負責任。此話莫當真,純屬娛樂一下。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM