数据可视化:Django + pandas + ECharts + 前后端分离

做数据分析时,我们可能会很常使用jupyter notebook来操作
并且可能依照个人喜好来使用像是seaborn、matplotlib等工具把资料视觉化
但是如果是要呈现在网页上的话,可能就要找JavaScript可视化库会更有弹性
因此我选择ECharts来作为这次的工具

Apache ECharts

https://echarts.apache.org/en/index.html

Apache ECharts的前身是百度的Echarts,在经过Apache Incubator孵化完成后变成Apache软体基金会的顶级专案。ECharts是一个使用JavaScript实现的视觉化图表库,可以在PC与其他装置上使用,且具有以下特点

丰富的图表类型:
https://echarts.apache.org/examples/zh/index.html
除了一般常见的折线图、柱状图、圆饼图、散布图等等,还有包含k线图、地理座标图等等,并且也有许多酷炫的动画呈现

方便:
Echarts内置的dataset属性支持直接传入array、key-value等多种格式的数据类型,省去很多时候数据还需要转换的步骤

主题设计系统:
在示例中找到喜欢的图点进去,就可以直接看每个图应该要怎么生成,并且也可以透过程式码编辑马上看到更改后的成果,非常强大与方便


我这边就不特别介绍Django跟pandas的用法,今天要写的语法都是很基础的,所以也会直接掉过架设环境的部分
网路上有很多资源马上找就有了。另外js的部分因为我对于js了解还很浅,觉得污染眼睛的话感到抱歉XDD

http://img2.58codes.com/2024/20161866pD2Myk5n9c.png
架构图如上~

HTML:

首先在我们的页面中使用CDN导入ECharts服务,当然你也可以安装到你的专案资料夹,只是我觉得使用CDN比较方便,并且也不用管自己的网页有没有使用CDN服务或是做静态资源的cache。但是直接在网页使用第三方CDN还是有安全问题,所以自己评估
https://echarts.apache.org/handbook/zh/get-started/
这里面有教学,可以自己参考然后做一个DOM元素,这边要设定好大小,或是你要在js再调整也没关係

Javascript:

首先要使用echart.init来初始化一个echart对象,接着指定好图表的内容(option变量)option里面需要配置好图表需要的一些配置项,以及资料资料透过ajax来呼叫Django的API,并且取得后端的资料使用stOption方法,让echart使用已经设定好的option,使html中的DOM元素渲染出图表样式

Django:

urls.py设置好路由,配置要连结的视图函式(views.py)views.py中使用pandas处理要呈现的资料,并返回Jsonresponse

以上就是大致的流程,其实每个图都大同小异,只要会了其中一种剩下的也不会到太困难
动态的那些图或是需要第三方计算回归线那些,我不确定做起来后用pagespeed分析后会不会分数很惨XD
所以我自己在使用上应该还是简单为主,另外我发现如果把几种基本图的code全部放上来,篇幅有点太长了
所以这次就先以折线图来说~

折线图

http://img2.58codes.com/2024/20161866kiWPp91lRT.png
这是官方示例的图
接下来我们可以想一下,哪些部分是需要用到我们自己资料的

标题以及图例的名称x轴的值以及y轴的值还有各自的名称最重要的就是我们的资料啦
这些都是等等我们需要注意的地方

HTML:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>测试echart</title>    <script src="https://cdn.bootcss.com/jquery/3.0.0/jquery.min.js"></script>    <!--引入ECharts CDN-->    <script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.3/echarts.min.js"></script></head><body><!--设置DOM元素 并设置大小--><div id="ecahrtLine" style="width: 100%; height:50vh;"></div><!--js档--><script src="/static/js/echart_theme/line.js"></script></body></html>

这边就是引入CDN跟设置元素,我自己是还有再额外引入jqery,因为我在js有用到相关语法

Django-views.py:

from random import randrangeimport pandas as pddef create_line_data():    math_score = {        "math_score": [randrange(50, 90) for _ in range(6)]    }    english_score = {        "english_score": [randrange(60, 100) for _ in range(6)]    }    m_series = pd.Series(math_score)    e_series = pd.Series(english_score)    return m_series, e_seriesdef trans_df_to_list(series):    """因为echart的data需要接收list 所以需要转格式"""    if isinstance(series, pd.Series):        return series.values[0]    else:        returndef create_data(*args):    res = [i for i in args]    return resdef show_line(request):    m_series, e_series = create_line_data()    m_list = trans_df_to_list(m_series)    e_list = trans_df_to_list(e_series)    data = {        "code": 200,        "msg": "success",        "data": create_data(m_list, e_list),    }    return JsonResponse(data)

首先我这边想要呈现的是两个科目中,这一个班级的6位学生他们各自的分数
这边可能要注意几个点:

因为最后echarts那边接收的资料形式是array,所以这边做好的资料要转换格式并且因为是array,如果资料量大的话,尽量避免for操作我这边是直接示範资料,所以用randrange。不然我会直接用read_csv之类的方式再去选取我要的seriescreate_data方法单纯是把那两条线的资料包起来所以没差

Javascript:

var lineDom = document.getElementById('ecahrtLine');var myLine = echarts.init(lineDom);$(    function () {        fetchData(myLine);    });function fetchData() {    $.ajax({        url: "/article/show_line",        type: "GET",        dataType: "json",        success: function (result) {            var option = createOption(result.data);            myLine.setOption(option);        }    });};function createOption (backendData) {    // 做出x轴的列表    var indexList = backendData[0].map(function(_, index) {        return index + 1;    });    var option;    option = {          title: {            text: '数学与英文成绩'          },          tooltip: {            trigger: 'axis'          },          legend: {},          toolbox: {            show: true,            feature: {              dataZoom: {                yAxisIndex: "none"              },              dataView: { readOnly: false },              magicType: { type: ['line', 'bar'] },              restore: {},              saveAsImage: {}            }          },          xAxis: {            type: 'category',            boundaryGap: false,            data: indexList,            name: "学生编号" // 设置名称          },          yAxis: {            type: 'value',            min: "dataMin", // 设置y轴最小值            axisLabel: {              formatter: '{value} 分'            },            name: "成绩" // 设置名称          },          series: [            {              name: '数学成绩',              type: 'line',              data: backendData[0], // 我们的资料              markPoint: {                data: [                  { type: 'max', name: 'Max' },                  { type: 'min', name: 'Min' }                ]              },              markLine: {                data: [{ type: 'average', name: 'Avg' }]              }            },            {              name: '英文成绩',              type: 'line',              data: backendData[1], // 我们的资料              markPoint: {                data: [                  { type: 'max', name: 'Max' },                  { type: 'min', name: 'Min' }                  ]              },              markLine: {                data: [                  { type: 'average', name: 'Avg' },                  [                    {                      symbol: 'none',                      x: '90%',                      yAxis: 'max'                    },                    {                      symbol: 'circle',                      label: {                        position: 'start',                        formatter: 'Max'                      },                      type: 'max',                      name: '最高点'                    }                  ]                ]              }            }          ]        };    return option}

因为我没有特别需要转换太多x轴的形式,所以我直接用索引来改编我的x轴,今天如果是x轴的资料格式比较特别,或是点超级多,建议在django那边解决掉
其中在设置一些参数的API,我自己有改的部分有加上注解
如果还是看不懂,可以参考:
https://echarts.apache.org/zh/option.html#xAxis.name
这个官方文档已经算是非常详细的解释API了,并且可以点“试一试”,再点code的部分便可以直接去操作测试

最后的成果如下:
http://img2.58codes.com/2024/20161866PCPz7gV16Y.png
甚至你可以点击右上方的一些按钮,会有很多不错的特效
例如转换成柱状图等等
http://img2.58codes.com/2024/20161866uZoH8BipFu.png

但是这样还不够~
我们此时去更改视窗宽度,会发现图表根本就没有变化
而echarts有resize方法,可以让图表随着视窗改变而改变
https://echarts.apache.org/zh/api.html#echartsInstance.resize
http://img2.58codes.com/2024/20161866D2cNkyYXXu.png

那一般我们没有特别要求的话可以直接这样调用

// 视窗调整时会更改echart图表window.onresize = function () {    myLine.resize()};

这边额外说一下,如果我们使用下图这种圆饼图
http://img2.58codes.com/2024/20161866PiT7Cm42fA.png
我们缩小的时候,会更希望由左右两边变成上下并行
echarts还有类似css中media的设置方法,可以参考官方文档:
https://echarts.apache.org/zh/tutorial.html#%E7%A7%BB%E5%8A%A8%E7%AB%AF%E8%87%AA%E9%80%82%E5%BA%94


好~ 我们回到我们的折线图,我们的确可以让图片的宽度自适应,但是文字不会因为图片变小而让佔比放大
这样对于手机或是平板的使用者会非常痛苦
所以我们需要修改原本的方法,让font-size也能够自适应
参考:https://blog.csdn.net/jingjing217/article/details/114015832

原本的js逻辑顺序如下

建立初始变量获取id来建立DOMecharts用DOM来实例化$(function(){})等DOM载入后执行ajaxajax拿到资料后製作option变量echarts对象使用setOption方法拿option渲染图表页面监听resize,并且echarts对象随之调用resize方法改变大小

但是现在要修改成:

建立初始变量获取id来建立DOMecharts用DOM来实例化建立一个空变量rowData建立一个fontMedia方法是根据当下视窗大小返回特定字体大小$(function(){})等DOM载入后执行ajaxajax拿到资料后製作option变量製作中会调用fontMedia来製作font-sizeecharts对象使用setOption方法渲染图表rowData来接后端传的资料页面监听resize重新製作option,不用再使用ajax跟后端要资料,直接拿rowData内的资料在製作过程中会调用fontMedia,达到字体大小自适应最后再调用resize方法更改图片大小

修改后的js

var lineDom = document.getElementById('ecahrtLine');var myLine = echarts.init(lineDom);var rowData;$(function () {        fetchData(myLine);    });// 因为文字不会更改 所以要自己写方法function fontMedia(fontSizePx){    var deviceWidth = window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth;    if (!deviceWidth) return;    var fontSize = 150 * (deviceWidth / 1920);    return fontSizePx*fontSize;}function fetchData() {    $.ajax({        url: "/article/show_line",        type: "GET",        dataType: "json",        success: function (result) {            rowData = result.data;            var option = createOption(result.data);            myLine.setOption(option);        }    });};function createOption (backendData) {    // 做出x轴的列表    var indexList = backendData[0].map(function(_, index) {        return index + 1;    });    var option;    option = {          title: {            text: '数学与英文成绩',            textStyle: {                fontSize: fontMedia(0.4) // 让标题可以随之改变            }          },          tooltip: {            trigger: 'axis'          },          legend: {},          toolbox: {            show: true,            feature: {              dataZoom: {                yAxisIndex: "none"              },              dataView: { readOnly: false },              magicType: { type: ['line', 'bar'] },              restore: {},              saveAsImage: {}            }          },          xAxis: {            type: 'category',            boundaryGap: false,            data: indexList,            name: "学生编号" // 设置名称          },          yAxis: {            type: 'value',            min: "dataMin", // 设置y轴最小值            axisLabel: {              formatter: '{value} 分'            },            name: "成绩" // 设置名称          },          series: [            {              name: '数学成绩',              type: 'line',              data: backendData[0],              markPoint: {                data: [                  { type: 'max', name: 'Max' },                  { type: 'min', name: 'Min' }                ]              },              markLine: {                data: [{ type: 'average', name: 'Avg' }]              }            },            {              name: '英文成绩',              type: 'line',              data: backendData[1],              markPoint: {                data: [                  { type: 'max', name: 'Max' },                  { type: 'min', name: 'Min' }                  ]              },              markLine: {                data: [                  { type: 'average', name: 'Avg' },                  [                    {                      symbol: 'none',                      x: '90%',                      yAxis: 'max'                    },                    {                      symbol: 'circle',                      label: {                        position: 'start',                        formatter: 'Max'                      },                      type: 'max',                      name: '最高点'                    }                  ]                ]              }            }          ]        };    return option}// 视窗调整时会更改echart图表window.onresize = function () {    var option = createOption(rowData);        myLine.setOption(option);        myLine.resize();};

最后重整后,就可以发现我们的标题可以随着视窗宽度变化而改变大小
http://img2.58codes.com/2024/20161866SBb2O8lDYv.pnghttp://img2.58codes.com/2024/20161866liQEjLz5tI.pnghttp://img2.58codes.com/2024/20161866DElbYHxSYM.png

有点懒得做gif所以直接丢图XD
其他调整font-size就大同小异,就不示範了~
希望有帮助到想做图的人~


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章