溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么在Django中利用Highcharts 實(shí)現(xiàn)數(shù)據(jù)可視化

發(fā)布時(shí)間:2021-03-24 15:53:10 來源:億速云 閱讀:250 作者:Leah 欄目:開發(fā)技術(shù)

本篇文章為大家展示了怎么在Django中利用Highcharts 實(shí)現(xiàn)數(shù)據(jù)可視化,內(nèi)容簡明扼要并且容易理解,絕對(duì)能使你眼前一亮,通過這篇文章的詳細(xì)介紹希望你能有所收獲。

開發(fā)環(huán)境

使用 Python 2.7.14,django 1.11.8,highcharts 4.0.1
直接命令行輸入以下語句,即可安裝django 1.11.8。

pip install django==1.11.8

至于Highcharts,可以去官網(wǎng)下載。我用的是之前前輩給的模板,js不是太懂,所以基本沒改,只是為了方便進(jìn)行拓展,對(duì)功能模塊進(jìn)行了注釋。

開發(fā)需求

手頭已有爬取的winstore不同app,不同榜單,不同地區(qū)的多天rank數(shù)據(jù)。這些rank數(shù)據(jù)存放在MySQL服務(wù)器中,庫名為winstore,表名為winstore_rank。

現(xiàn)在需要將這些rank數(shù)據(jù)用折線圖的方式展示出來。同時(shí)在網(wǎng)頁上需要可以根據(jù)選擇的日期,地區(qū),榜單來動(dòng)態(tài)產(chǎn)生折線圖。

問題解析

根據(jù)開發(fā)需求,可以將這次任務(wù)分為三個(gè)部分。

前端頁面

a. ajax動(dòng)態(tài)獲取地區(qū)列表、榜單列表,生成對(duì)應(yīng)的下拉列表,必要時(shí)需將傳統(tǒng)下拉列表轉(zhuǎn)換成多選下拉列表

b. 根據(jù)搜索結(jié)果,將符合條件的app的rank添加到折線圖中

服務(wù)器端

a. 接受前端的請(qǐng)求,與數(shù)據(jù)庫通信,返回所請(qǐng)求數(shù)據(jù)

MySQL數(shù)據(jù)庫

a. 根據(jù)服務(wù)器端傳輸?shù)膕ql語句進(jìn)行對(duì)應(yīng)的查詢

根據(jù)上述的分析,前端肯定是js + jQuery + Echarts + jquery.multiselect了,服務(wù)器端采用Django,數(shù)據(jù)庫方面Django有對(duì)應(yīng)的驅(qū)動(dòng)模塊,不用管。

1. 前端頁面

新建一個(gè)文件rank.html,內(nèi)容如下:

<head>
 {% load static %}
 <script type="text/javascript" src="{% static 'js/jquery.min.js' %}"></script>
 <script type="text/javascript" src="{% static 'js/highcharts.js' %}"></script>
 <script type="text/javascript" src="{% static 'js/jquery-ui.min.js' %}"></script>
 <script type="text/javascript" src="{% static 'js/exporting.js' %}"></script>
 <script type="text/javascript" src="{% static 'js/jquery.multiselect.min.js' %}"></script>

 <link rel="stylesheet" href="{% static 'css/jquery-ui.min.css' %}" rel="external nofollow" >
 <link rel="stylesheet" href="{% static 'css/jquery.multiselect.css' %}" rel="external nofollow" >
 <link rel="stylesheet" href="{% static 'css/screen1.css' %}" rel="external nofollow" >

 <style type="text/css">
  #set-content ul li #chart {
   width: 60px;
   font-size: 12px;
   height: 22px;
  }
 </style>
 <script type="text/javascript">

  // 設(shè)定開始日期和結(jié)束日期,默認(rèn)為最近10天
  $(function() {
   $("#beginDate").datepicker({dateFormat: "yy-mm-dd"});
   $("#endDate").datepicker({dateFormat: "yy-mm-dd"});
   var dateNow = new Date();
   var str_dateNow = dateNow.getFullYear() + "-" + (dateNow.getMonth() + 1) + "-" + dateNow.getDate();
   var dateBegin = new Date(dateNow - 10 * 1000 * 3600 * 24);
   var str_dateBegin = dateBegin.getFullYear() + "-" + (dateBegin.getMonth() + 1) + "-" + dateBegin.getDate();
   $("#beginDate").datepicker("setDate", str_dateBegin);
   $("#endDate").datepicker("setDate", str_dateNow);
  });
  // 動(dòng)態(tài)獲取數(shù)據(jù)庫中region數(shù)據(jù),填充入下拉列表
  $(function() {
   $.get("/getWinstoreRegions",
     {"limit": "0"},
     function(regionsDict) {
      for (var id in regionsDict) {
       regionOption = "<option value='" + id + "'>" + regionsDict[id] + "</option>";
       $("#region").append(regionOption);
      }
     },
     "json"
     )
  });

  // 動(dòng)態(tài)獲取數(shù)據(jù)庫中chart數(shù)據(jù),填充入下拉列表
  $(function() {
   $.get("/getWinstoreCharts",
     {"limit": "0"},
     function(chartsDict) {
      for (var id in chartsDict) {
       chartOption = "<option value='" + id + "'>" + chartsDict[id] + "</option>";
       $("#chart").append(chartOption);
      }
     },
     "json"
     )
  });
  // 動(dòng)態(tài)獲取數(shù)據(jù)庫中category數(shù)據(jù),填充入下拉列表
  $(function() {
   $.get("/getWinstoreCategories",
     {"limit": "0"},
     function(categoriesDict) {
      for (var id in categoriesDict) {
       categoryOption = "<option value='" + id + "'>" + categoriesDict[id] + "</option>";
       $("#category").append(categoryOption);
      }
     },
     "json"
     )
  });
  // 動(dòng)態(tài)獲取數(shù)據(jù)庫中app名字,填充入下拉列表
  $(function() {
   $.get( "/getWinstoreApps",
    {"limit":"0",},
    function(dataDict) {
     // 循環(huán)添加下拉列表的option
     for (var id in dataDict) {
      appOption = "<option value='" + id + "'>" + dataDict[id] + "</option>";
      $("#appName").append(appOption);
     }
     // 初始化多選
     $("#appName").multiselect({header: false,});
     // 選中所有下拉列表項(xiàng)
     $("#appName").multiselect("checkAll");
     // 動(dòng)態(tài)設(shè)置多選框的寬度
     var ulList = $(".ui-multiselect-checkboxes")[0];
     // 必須先單擊多選下拉列表,然后才可以獲取對(duì)應(yīng)元素的寬度值
     $(".ui-multiselect")[0].click();
     var maxWidth = 0;
     for (var i = 0; i < ulList.childElementCount; i++) {
      var currentInputWidth = $(ulList.childNodes[i]).find("input")[0].offsetWidth;
      var currentSpanWidth = $(ulList.childNodes[i]).find("span")[0].offsetWidth;
      var currentWidth = currentSpanWidth + currentInputWidth * 3;
      if (currentWidth > maxWidth) {
       maxWidth = currentWidth;
      }
     }
     // 設(shè)置對(duì)應(yīng)標(biāo)簽的寬度
     $($(".ui-multiselect")[0]).width(maxWidth);
     $($(".ui-multiselect-menu")[0]).width(maxWidth + 6);
     // 二次單擊
     $(".ui-multiselect")[0].click();
    },
    "json");
  });
  // 綁定query按鈕的單擊操作
  $(function() {
   $("#query").click(function() {
    var region = $("#region").val();
    var beginDate = $("#beginDate").val();
    var endDate = $("#endDate").val();
    var chart = $("#chart").val();
    var appNames = $("#appName").val();
    var category = $("#category").val();

    // 將appNames連接成字符串
    queryReport(region, beginDate, endDate, chart, category, appNames.join("@"));
   });
  })
  var lineChart;
  // 獲取繪圖數(shù)據(jù)
  function queryReport(region, beginDate, endDate, chart, category, appNames) {
   // 清空原有繪圖數(shù)據(jù)
   $("#container")[0].innerHTML = "";
   // 初始化折線圖參數(shù)
    var lineChart = new Highcharts.Chart({
            chart: {
             renderTo: 'container',             type: 'line'
            },
            title: {
             text: 'Daily Ranking',
             style: {fontFamily: 'Helvetica', fontWeight: '200'}
            },
            subtitle: {
             text: 'By Product',
             style: {fontFamily: 'Helvetica', fontWeight: '200'}
            },

            xAxis: [{ // master axis
             type: 'datetime',
             gridLineWidth:1,
             gridLineDashStyle: 'longdash',
             tickInterval: 24 * 3600 * 1000,
            }, { // slave axis
             type: 'datetime',
             linkedTo: 0,
             opposite: true,
             tickInterval: 24 * 3600 * 1000,
             labels: {
              formatter: function () {return Highcharts.dateFormat('%a', this.value);}
             }
            }],            tooltip: {
             headerFormat: '<span>{point.key}</span><br/>',
             pointFormat: '<span >\u25AC</span> {series.name}: <b>{point.y}</b><br/>',
            },
            yAxis: [{ // Primary yAxis
             min:1,
             reversed: true,
             labels: {
              format: 'No. {value}',
              style: {
               color: '#4572A7'
              }
             },
             title: {
              text: 'Ranking',
              style: {
               color: '#4572A7'
               }
              }
             },
             { // Secondary yAxis
             min:1,
             reversed: true,
             title: {
              text: 'Ranking',
              style: {
               color: '#4572A7'
              }
             },
             labels: {
             format: 'No. {value}',
             style: {
              color: '#4572A7'
              }
             },
             opposite: true,
            }],

            plotOptions: {
             column: {
              dataLabels: {
               enabled: true
              },
              enableMouseTracking: true
             },
             line: {
              dataLabels: {
               enabled: true
              },
              enableMouseTracking: true
             }
            },
            series: [],
           });
   // 構(gòu)造url參數(shù)
   parameters = {'region': region,
      'beginDate': beginDate,
      'endDate': endDate,
      'chart': chart,
      'category': category,
      'appNames': appNames
      };

   // 請(qǐng)求繪圖數(shù)據(jù)
   $.get("/getWinstoreRank",
     parameters,
     function(rankDict) {
      var ranksOfApp = new Array();
      for (var app in rankDict) {
       lineChart.addSeries({
        name: app,
        data: rankDict[app]
       });
      }
     },
     "json"
     );
  }
 </script>

</head>


<body>
 <div id="set-content">
  <ul>
   <li>
    <label for="region">Country/Region: </label>
    <select id="region"></select>
   </li>
   <li>
    <label for="beginDate">Begin Date: </label>
    <input type="text" id="beginDate">
   </li>
   <li>
    <label for="endDate">End Date: </label>
    <input type="text" id="endDate">
   </li>
   <li>
    <label for="chart">Chart: </label>
    <select id="chart"></select>
   </li>
   <li>
    <label for="category">Category: </label>
    <select id="category"></select>
   </li>
   <li>
    <label for="appName">App:</label>
    <select id="appName" multiple="multiple" size="4"></select>
   </li>
   <li>
    <button id='query'>Query</button>
   </li>
  </ul>
 </div>
 <div id="container"></div>
</body>

這里稍微解釋下,在實(shí)際使用中,使用highcharts生成折線圖,根據(jù)不同的數(shù)據(jù),只需要修改series參數(shù)即可。而series參數(shù)是個(gè)啥,可以在上面的HTML代碼中搜索series即可。稍微觀察下,就明白了。

至于你想換個(gè)大餅圖,柱狀圖,可以 點(diǎn)擊這里 找現(xiàn)成的例子,稍作修改就可以使用了。當(dāng)然也許你有更多個(gè)性化的需求,那可以 點(diǎn)擊這里 找到對(duì)應(yīng)的配置項(xiàng)進(jìn)行修改。

2. 服務(wù)器端

1、首先命令行進(jìn)入到你想放置項(xiàng)目代碼的地方

django_admin startproject winstore

2、進(jìn)入剛剛新建的項(xiàng)目文件夾

cd winstore

3、創(chuàng)建新的應(yīng)用rank。這里的應(yīng)用可以理解成具有獨(dú)立功能的一組網(wǎng)頁的結(jié)合,當(dāng)然在本篇博客里,只有一個(gè)網(wǎng)頁了。

python manage.py startapp rank

4、在rank文件夾中新建文件夾templatesstatic,將剛剛新建的rank.html放入templates文件夾,同時(shí)將引用的js庫文件放入static文件夾下,注意文件夾層級(jí)。

5.、打開winstore文件夾下的settings.py ,在INSTALL_APPS 下添加rank,添加之后如下:

INSTALLED_APPS = [
 'django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'rank' # 添加的部分
]

DATABASES修改成你自己的MySQL數(shù)據(jù)庫的控制信息。下面是我的數(shù)據(jù)庫設(shè)置:

DATABASES = {
 'default': {
  'ENGINE': 'django.db.backends.mysql',
  'NAME': 'winstore', # 數(shù)據(jù)庫名
  'HOST': '127.0.0.1', # IP
  'PORT': '3306',  # 端口號(hào)
  'USER': 'root',  # 用戶名
  'PASSWORD': '111111', # 密碼
 }
}

6、編輯rank文件夾下的views.py文件,在rank.html中加入必要的網(wǎng)頁動(dòng)態(tài)功能的實(shí)現(xiàn)。由于app的排名數(shù)據(jù)是根據(jù)其所處的榜單chart和應(yīng)用類別category,以及不同的地區(qū)region來確定的,所以這里里的功能實(shí)現(xiàn)就需要包括5個(gè)部分。分別對(duì)appName、chart、category、region 實(shí)現(xiàn)從數(shù)據(jù)庫動(dòng)態(tài)獲取其取值集合以及獲取排名數(shù)據(jù)。對(duì)應(yīng)的實(shí)現(xiàn)分別如下:

appName

def getWinstoreApps(request):
 """
 根據(jù)接收到的GET請(qǐng)求返回app的取值集合
 """
 # 構(gòu)造SQL語句
 sql = 'SELECT DISTINCT appName FROM winstore_rank'
 # 默認(rèn)appNames的key和value相同
 appNames = {}
 try:
  result = getDataFromSQL(sql)
  result = [r[0] for r in result]
  for key in result:
   appNames[key] = key
 except Exception as e:
  print('getWinstoreApps ERROR: ' + str(e))
  appNames['QQ'] = 'QQ'
 return JsonResponse(appNames)

chart

def getWinstoreCharts(request):
 """
 根據(jù)接收到的GET請(qǐng)求返回chart的取值集合
 """
 # 構(gòu)造SQL語句
 sql = 'SELECT DISTINCT chart FROM winstore_rank'
 # 默認(rèn)charts的key和value相同
 charts = {}
 try:
  result = getDataFromSQL(sql)
  result = [r[0] for r in result]
  for key in result:
   charts[key] = key
 except Exception as e:
  print('getWinstoreCharts ERROR: ' + str(e))
  charts['Free'] = 'Free'
 return JsonResponse(charts)

category

def getWinstoreCategories(request):
 """
 根據(jù)接收到的GET請(qǐng)求返回category的取值集合
 """
 # 構(gòu)造SQL語句
 sql = 'SELECT DISTINCT category FROM winstore_rank'
 # 默認(rèn)categories的key和value相同
 categories = {}
 try:
  result = getDataFromSQL(sql)
  result = [r[0] for r in result]
  for key in result:
   categories[key] = key
 except Exception as e:
  print('getWinstoreCategories ERROR: ' + str(e))
  categories['Education'] = 'Education'
 return JsonResponse(categories)

region

def getWinstoreRegions(request):
 """
 根據(jù)接收到的GET請(qǐng)求返回region的取值集合
 """
 # 構(gòu)造SQL語句
 sql = 'SELECT DISTINCT region FROM winstore_rank'
 # 默認(rèn)regions的key和value相同
 regions = {}
 try:
  result = getDataFromSQL(sql)
  result = [r[0] for r in result]
  for key in result:
   regions[key] = key
 except Exception as e:
  print('getWinstoreRegions ERROR: ' + str(e))
  regions['EN-US'] = 'EN-US'
 return JsonResponse(regions)

獲取排名數(shù)據(jù)

def getWinstoreRank(request):
 """
 根據(jù)接收到的GET請(qǐng)求返回對(duì)應(yīng)app的排名數(shù)據(jù)
 """
 # 從GET請(qǐng)求中獲取參數(shù)
 region = request.GET.get("region", "EN-US")
 chart = request.GET.get("chart", "Free")
 category = request.GET.get("category", "Education")
 beginDate = request.GET.get("beginDate", "2018-01-22")
 endDate = request.GET.get("endDate", "2018-02-02")
 appNames = request.GET.get("appNames", "QQ").split("@")
 # 構(gòu)造SQL語句
 sqlTemp = 'SELECT the_date, rank FROM winstore_rank WHERE ' \
    'region="%s" AND chart="%s" AND category="%s" AND ' \
    'the_date BETWEEN "%s" AND "%s" AND ' \
    'appName=' % (region, chart, category, beginDate, endDate)

 # 以每個(gè)appName作為key,對(duì)應(yīng)的排名數(shù)據(jù)列表作為value
 appRank = {}
 for appName in appNames:
  sql = sqlTemp + '"' + appName + '"'
  try:
   result = getDataFromSQL(sql)
   # 根據(jù)數(shù)據(jù)庫返回的結(jié)果將缺少rank數(shù)據(jù)的日期補(bǔ)0
   result = addZeroToRank(beginDate, endDate, result)
   appRank[appName] = result
  except Exception as e:
   print('getWinstoreRank ERROR: ' + str(e))
 return JsonResponse(appRank)


def addZeroToRank(beginDate, endDate, result):
 """
 以beginDate和endDate為日期的起始,將result中缺少的日期補(bǔ)全,同時(shí)設(shè)定排名為0
 Param:
  beginDate: 開始日期字符串,“2018-01-23”
  endDate: 結(jié)束日期字符串, “2018-02-02”
  result: 形如[(date, 23L), (date, 12L), [date, 3L]......]
 Return:
  按照日期順序排列的排名數(shù)據(jù),缺省排名為0
 """
 # 將日期字符串轉(zhuǎn)變?yōu)閐ate類型數(shù)據(jù),方便日期加減
 y, m, d = [int(i) for i in beginDate.split("-")]
 begin = datetime.date(y, m, d)
 y, m, d = [int(i) for i in endDate.split("-")]
 end = datetime.date(y, m, d)
 current = begin
 # 獲取result中的日期,方便進(jìn)行判斷
 resultTemp = [r[0] for r in result]
 while current <= end:
  if not (current in resultTemp):
   result.append((current, 0))
  current += datetime.timedelta(days=1)
 result.sort(key=lambda x: x[0])
 return [int(r[1]) for r in result]

這里主要就是構(gòu)造SQL語句,然后訪問數(shù)據(jù)庫獲取對(duì)應(yīng)的數(shù)據(jù)集合。其中getDataFromSQL() 是對(duì)訪問MySQL數(shù)據(jù)庫的簡單封裝,具體代碼如下:

def getDataFromSQL(sql):
 """
 根據(jù)sql語句獲取數(shù)據(jù)庫的返回?cái)?shù)據(jù)
 """
 cursor = connection.cursor()
 cursor.execute(sql)
 return list(cursor.fetchall())

一些涉及到的引用可以參考文末給出的項(xiàng)目代碼。

最終綁定一下首頁

def index(request):
 """
 綁定網(wǎng)站首頁
 """
 return render(request, 'rank.html')

3. MySQL數(shù)據(jù)庫

實(shí)際應(yīng)用時(shí),相關(guān)的rank數(shù)據(jù)是通過爬蟲獲取的。在這里,就直接填充一些隨機(jī)的rank數(shù)據(jù)進(jìn)去了,不影響最終的結(jié)果。

最終成果展示

首頁

怎么在Django中利用Highcharts 實(shí)現(xiàn)數(shù)據(jù)可視化

rank數(shù)據(jù)展示

怎么在Django中利用Highcharts 實(shí)現(xiàn)數(shù)據(jù)可視化

可以看到雖然前端頁面很簡陋,但是功能是實(shí)現(xiàn)了。不過有個(gè)問題 就是重新點(diǎn)擊query按鈕后,highcharts提供的右側(cè)頁面中間下載圖片的那個(gè)三道杠會(huì)出現(xiàn)并排的兩個(gè)。

上述內(nèi)容就是怎么在Django中利用Highcharts 實(shí)現(xiàn)數(shù)據(jù)可視化,你們學(xué)到知識(shí)或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識(shí)儲(chǔ)備,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI