侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

使用 Django Highcharts 实现数据可视化过程解析

框架(架构)  /  管理员 发布于 7年前   405

概述

最近在一家公司实习,入职第一个大一点的需求是将公司开发的两个winstore app的排名信息进行可视化。大概挑选了下,排除了Flask和Echarts。最终选择使用Django和它的插件django-echarts来实现。文末有项目的完整代码,不想看的可以直接去下载,拆箱可用。

本篇博客主要用于记录整体的实现步骤,以及在实现过程中遇到的各个问题。

开发环境

本次搭建使用 Python 2.7.14,django 1.11.8,highcharts 4.0.1
直接命令行输入以下语句,即可安装django 1.11.8。

pip install django==1.11.8

至于Highcharts,可以去官网下载。我用的是之前前辈给的模板,js不是太懂,所以基本没改,只是为了方便进行拓展,对功能模块进行了注释。

开发需求

手头已有爬取的winstore不同app,不同榜单,不同地区的多天rank数据。这些rank数据存放在MySQL服务器中,库名为winstore,表名为winstore_rank。

现在需要将这些rank数据用折线图的方式展示出来。同时在网页上需要可以根据选择的日期,地区,榜单来动态产生折线图。

问题解析

根据开发需求,可以将这次任务分为三个部分。

前端页面

a. ajax动态获取地区列表、榜单列表,生成对应的下拉列表,必要时需将传统下拉列表转换成多选下拉列表

b. 根据搜索结果,将符合条件的app的rank添加到折线图中

服务器端

a. 接受前端的请求,与数据库通信,返回所请求数据

MySQL数据库

a. 根据服务器端传输的sql语句进行对应的查询

根据上述的分析,前端肯定是js + jQuery + Echarts + jquery.multiselect了,服务器端采用Django,数据库方面Django有对应的驱动模块,不用管。

1. 前端页面

新建一个文件rank.html,内容如下:

<head> {% load static %} <script type="text/javascript" src="https:/article/{% static 'js/jquery.min.js' %}"></script> <script type="text/javascript" src="https:/article/{% static 'js/highcharts.js' %}"></script> <script type="text/javascript" src="https:/article/{% static 'js/jquery-ui.min.js' %}"></script> <script type="text/javascript" src="https:/article/{% static 'js/exporting.js' %}"></script> <script type="text/javascript" src="https:/article/{% static 'js/jquery.multiselect.min.js' %}"></script> <link rel="stylesheet" href="https:/article/{% static 'css/jquery-ui.min.css' %}" rel="external nofollow" > <link rel="stylesheet" href="https:/article/{% static 'css/jquery.multiselect.css' %}" rel="external nofollow" > <link rel="stylesheet" href="https:/article/{% 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">  // 设定开始日期和结束日期,默认为最近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);  });  // 动态获取数据库中region数据,填充入下拉列表  $(function() {   $.get("/getWinstoreRegions",     {"limit": "0"},     function(regionsDict) {      for (var id in regionsDict) {       regionOption = "<option value='" + id + "'>" + regionsDict[id] + "</option>";       $("#region").append(regionOption);      }     },     "json"     )  });  // 动态获取数据库中chart数据,填充入下拉列表  $(function() {   $.get("/getWinstoreCharts",     {"limit": "0"},     function(chartsDict) {      for (var id in chartsDict) {       chartOption = "<option value='" + id + "'>" + chartsDict[id] + "</option>";       $("#chart").append(chartOption);      }     },     "json"     )  });  // 动态获取数据库中category数据,填充入下拉列表  $(function() {   $.get("/getWinstoreCategories",     {"limit": "0"},     function(categoriesDict) {      for (var id in categoriesDict) {       categoryOption = "<option value='" + id + "'>" + categoriesDict[id] + "</option>";       $("#category").append(categoryOption);      }     },     "json"     )  });  // 动态获取数据库中app名字,填充入下拉列表  $(function() {   $.get( "/getWinstoreApps",    {"limit":"0",},    function(dataDict) {     // 循环添加下拉列表的option     for (var id in dataDict) {      appOption = "<option value='" + id + "'>" + dataDict[id] + "</option>";      $("#appName").append(appOption);     }     // 初始化多选     $("#appName").multiselect({header: false,});     // 选中所有下拉列表项     $("#appName").multiselect("checkAll");     // 动态设置多选框的宽度     var ulList = $(".ui-multiselect-checkboxes")[0];     // 必须先单击多选下拉列表,然后才可以获取对应元素的宽度值     $(".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;      }     }     // 设置对应标签的宽度     $($(".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;  // 获取绘图数据  function queryReport(region, beginDate, endDate, chart, category, appNames) {   // 清空原有绘图数据   $("#container")[0].innerHTML = "";   // 初始化折线图参数    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 style="color:{series.color}">\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: [],           });   // 构造url参数   parameters = {'region': region,      'beginDate': beginDate,      'endDate': endDate,      'chart': chart,      'category': category,      'appNames': appNames      };   // 请求绘图数据   $.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>

这里稍微解释下,在实际使用中,使用highcharts生成折线图,根据不同的数据,只需要修改series参数即可。而series参数是个啥,可以在上面的HTML代码中搜索series即可。稍微观察下,就明白了。

至于你想换个大饼图,柱状图,可以 点击这里 找现成的例子,稍作修改就可以使用了。当然也许你有更多个性化的需求,那可以 点击这里 找到对应的配置项进行修改。

2. 服务器端

1、首先命令行进入到你想放置项目代码的地方

django_admin startproject winstore 

2、进入刚刚新建的项目文件夹

cd winstore

3、创建新的应用rank。这里的应用可以理解成具有独立功能的一组网页的结合,当然在本篇博客里,只有一个网页了。

python manage.py startapp rank

4、在rank文件夹中新建文件夹templates和static,将刚刚新建的rank.html放入templates文件夹,同时将引用的js库文件放入static文件夹下,注意文件夹层级。

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数据库的控制信息。下面是我的数据库设置:

DATABASES = { 'default': {  'ENGINE': 'django.db.backends.mysql',  'NAME': 'winstore', # 数据库名  'HOST': '127.0.0.1', # IP  'PORT': '3306',  # 端口号  'USER': 'root',  # 用户名  'PASSWORD': '111111', # 密码 }}

6、编辑rank文件夹下的views.py文件,在rank.html中加入必要的网页动态功能的实现。由于app的排名数据是根据其所处的榜单chart和应用类别category,以及不同的地区region来确定的,所以这里里的功能实现就需要包括5个部分。分别对appName、chart、category、region 实现从数据库动态获取其取值集合以及获取排名数据。对应的实现分别如下:

appName

def getWinstoreApps(request): """ 根据接收到的GET请求返回app的取值集合 """ # 构造SQL语句 sql = 'SELECT DISTINCT appName FROM winstore_rank' # 默认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): """ 根据接收到的GET请求返回chart的取值集合 """ # 构造SQL语句 sql = 'SELECT DISTINCT chart FROM winstore_rank' # 默认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): """ 根据接收到的GET请求返回category的取值集合 """ # 构造SQL语句 sql = 'SELECT DISTINCT category FROM winstore_rank' # 默认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): """ 根据接收到的GET请求返回region的取值集合 """ # 构造SQL语句 sql = 'SELECT DISTINCT region FROM winstore_rank' # 默认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)

获取排名数据

def getWinstoreRank(request): """ 根据接收到的GET请求返回对应app的排名数据 """ # 从GET请求中获取参数 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("@") # 构造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) # 以每个appName作为key,对应的排名数据列表作为value appRank = {} for appName in appNames:  sql = sqlTemp + '"' + appName + '"'  try:   result = getDataFromSQL(sql)   # 根据数据库返回的结果将缺少rank数据的日期补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中缺少的日期补全,同时设定排名为0 Param:  beginDate: 开始日期字符串,“2018-01-23”  endDate: 结束日期字符串, “2018-02-02”  result: 形如[(date, 23L), (date, 12L), [date, 3L]......] Return:  按照日期顺序排列的排名数据,缺省排名为0 """ # 将日期字符串转变为date类型数据,方便日期加减 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中的日期,方便进行判断 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]

这里主要就是构造SQL语句,然后访问数据库获取对应的数据集合。其中getDataFromSQL() 是对访问MySQL数据库的简单封装,具体代码如下:

def getDataFromSQL(sql): """ 根据sql语句获取数据库的返回数据 """ cursor = connection.cursor() cursor.execute(sql) return list(cursor.fetchall())

一些涉及到的引用可以参考文末给出的项目代码。

最终绑定一下首页

def index(request): """ 绑定网站首页 """ return render(request, 'rank.html')

3. MySQL数据库

实际应用时,相关的rank数据是通过爬虫获取的。在这里,就直接填充一些随机的rank数据进去了,不影响最终的结果。

最终成果展示

首页

rank数据展示

可以看到虽然前端页面很简陋,但是功能是实现了。不过有个问题 就是重新点击query按钮后,highcharts提供的右侧页面中间下载图片的那个三道杠会出现并排的两个。

本文的完整项目代码 点击这里 就可以获取了。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


  • 上一条:
    Django 反向生成url实例详解
    下一条:
    Django上使用数据可视化利器Bokeh解析
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • Filament v3.1版本发布(0个评论)
    • docker + gitea搭建一个git服务器流程步骤(0个评论)
    • websocket的三种架构方式使用优缺点浅析(0个评论)
    • ubuntu20.4系统中宿主机安装nginx服务,docker容器中安装php8.2实现运行laravel10框架网站(0个评论)
    • phpstudy_pro(小皮面板)中安装最新php8.2.9版本流程步骤(0个评论)
    • 近期文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2018-05
    • 2020-02
    • 2020-03
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-11
    • 2021-03
    • 2021-09
    • 2021-10
    • 2021-11
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-08
    • 2023-08
    • 2023-10
    • 2023-12
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客