文章来源Python Django+SQL+Pandas+Pyecharts自建在线数据分析平台(一)
作者ccpic
感谢:感谢作者 ccpic 分享的优质内容,本网页主要用于学习知识的存档备份,欢迎点击原网页支持作者。

(一)需求分析&技术实现

(二)初步搭建Django环境

(三)页面布局&Django模板

(四)SQL+Pandas初步处理数据

(五)前端表单交互

(六)Ajax异步传参与加载

(七)前端数据格式的处理

(八)DataTables接管前端表格

(十)静态图表的展示

(十一)“导出数据至Excel”功能

(十二)添加和配置缓存

(十三)用户登录系统

(十四)部署Django至生产环境

Echarts是一个由百度开源的纯JS的数据可视化库,而Pyecharts可以理解为一个Echarts的Python封装版本。我觉得Pyecharts的存在是很有必要的,因为Python作为近年来数据领域最常用的语言,数据处理后正好利用其无缝衔接到数据可视化这个后续步骤。

另外让人感到亲切的一点是,Echarts和Pyecharts都是国人开发和开源的。

在Pyecharts的官方文档中,就有和Django整合的例子:

虽然此例子中用的2种实现方式——渲染整体模板和rest API类视图,都不太适合我们这个项目。但是我们可以看出Pyecharts和Django整合的基本套路:

  1. 前端html部分准备一个空白的DOM
  2. JS部分用echarts初始化命令渲染这个元素
  3. 后端准备数据
  4. 使用数据用Pyecharts生成图表对象
  5. 利用Pyechart全局API dump_options()生成图表对象json格式的全局options
  6. 前端JS部分用AJAX通信获取后端json
  7. AJAX调用json成功后使用.setOption方法刷新图表元素呈现可视化结果

明白了以上这些步骤,可以在我们的项目里有条不紊地实现任何Pyecharts交互图表。本章的目标为创建一个条形图和折线图的组合图,呈现我们query方法返回的整体市场趋势及同比增长率。以下每个具体步骤都与上方对应:

1、在display.html模板内加入下方代码创造一个布局片段包含一个id为bar_total_trend的空白DOM:

<div class="ui tab segment active" data-tab="total">
...
<h3 class="ui header">
<div class="content">
定义市场总量趋势
<div class="sub header">柱状折线复合图</div>
</div>
</h3>
<div class="ui divider"></div>
<div class="ui container">
<div id="bar_total_trend" style="width:1000px; height:600px;"></div>
</div>
</div>

2、在filter.html的JS部分(注意因为Django模板的继承特性,他们是互通的,也就是filter.html的JS代码能控制display.html的DOM),初始化上面的DOM元素为echarts对象:

<script type="text/javascript">
$("#AJAX_get").click(function () {
...
// Pyecharts图表初始化
var chart = echarts.init(document.getElementById('bar_total_trend'), 'white', {renderer: 'canvas'});
chart.showLoading({
text : '正在加载数据'
}); //增加加载提示
...
})
</script>

3&4&5、后端views.py编写一个prepare_chart方法,可以根据字符串图表类型准备数据以及Pyecharts的图表对象:

from .charts import *

D_TRANS = {
'MAT': '滚动年',
'QTR': '季度',
'Value': '金额',
'Volume': '盒数',
'Volume (Counting Unit)': '最小制剂单位数',
'滚动年': 'MAT',
'季度': 'QTR',
'金额': 'Value',
'盒数': 'Volume',
'最小制剂单位数': 'Volume (Counting Unit)'
}

def prepare_chart(df, # 输入经过pivoted方法透视过的df,不是原始df
chart_type, # 图表类型字符串,人为设置,根据图表类型不同做不同的Pandas数据处理,及生成不同的Pyechart对象
form_dict, # 前端表单字典,用来获得一些变量作为图表的标签如单位
):
label = D_TRANS[form_dict['PERIOD_select'][0]] + D_TRANS[form_dict['UNIT_select'][0]]

if chart_type == 'bar_total_trend':
df_abs = df.sum(axis=1) # Pandas列汇总,返回一个N行1列的series,每行是一个date的市场综合
df_abs.index = df_abs.index.strftime("%Y-%m") # 行索引日期数据变成2020-06的形式
df_abs = df_abs.to_frame() # series转换成df
df_abs.columns = [label] # 用一些设置变量为系列命名,准备作为图表标签
df_gr = df_abs.pct_change(periods=4) # 获取同比增长率
df_gr.dropna(how='all', inplace=True) # 删除没有同比增长率的行,也就是时间序列数据的最前面几行,他们没有同比
df_gr.replace([np.inf, -np.inf, np.nan], '-', inplace=True) # 所有分母为0或其他情况导致的inf和nan都转换为'-'
chart = echarts_stackbar(df=df_abs,
df_gr=df_gr
) # 调用stackbar方法生成Pyecharts图表对象
return chart.dump_options() # 用json格式返回Pyecharts图表对象的全局设置
else:
return None

这部分最后的.dump_options()是重点,返回的不是图表对象,而是json格式的图表全局设置。

因为views.py已经有点臃肿了,我们在相同目录新建一个charts.py,把生成Pyecharts图表对象的方法都写到这里。本例里只写了个柱状图和线图的组合:

from pyecharts.charts import Line, Bar
from pyecharts import options as opts


def echarts_stackbar(df, # 传入数据df,应该是一个行索引为date的时间序列面板数据
df_gr=None, # 传入同比增长率df,可以没有
datatype='ABS' # 主Y轴形式是绝对值,增长率还是份额,用来确定一些标签格式,默认为绝对值
) -> Bar:

axislabel_format = '{value}' # 主Y轴默认格式
max = df[df>0].sum(axis=1).max() # 主Y轴默认最大值
min = df[df<=0].sum(axis=1).min() # 主Y轴默认最小值
if datatype in ['SHARE', 'GR']: # 如果主数据不是绝对值形式而是份额或增长率如何处理
df = df.multiply(100).round(2)
axislabel_format = '{value}%'
max = 100
min = 0
if df_gr is not None:
df_gr = df_gr.multiply(100).round(2) # 如果有同比增长率,原始数*100呈现

if df.empty is False:
stackbar = (
Bar()
.add_xaxis(df.index.tolist())
)
for i, item in enumerate(df.columns): # 预留的枚举,这个方法以后可以根据输入对象不同从单一柱状图变成堆积柱状图
stackbar.add_yaxis(item,
df[item].values.tolist(),
stack='总量',
label_opts=opts.LabelOpts(is_show=False),
z_level=1, # 指定渲染图层,低版本pyecharts可能因为没有该参数报错
)
if df_gr is not None: # 如果有同比增长率数据则加入次Y轴
stackbar.extend_axis(
yaxis=opts.AxisOpts(
name="同比增长率",
type_="value",
axislabel_opts=opts.LabelOpts(formatter="{value}%"),
)
)
stackbar.set_global_opts(
legend_opts=opts.LegendOpts(pos_top='5%', pos_left='10%', pos_right='60%'),
toolbox_opts=opts.ToolboxOpts(is_show=True),
tooltip_opts=opts.TooltipOpts(trigger='axis',
axis_pointer_type='cross',
),
xaxis_opts=opts.AxisOpts(type_='category',
boundary_gap=True,
axislabel_opts=opts.LabelOpts(rotate=90), # x轴标签方向rotate有时能解决拥挤显示不全的问题
splitline_opts=opts.SplitLineOpts(is_show=False,
linestyle_opts=opts.LineStyleOpts(
type_='dotted',
opacity=0.5,
)
)
),
yaxis_opts=opts.AxisOpts(max_=max,
min_=min,
type_="value",
axislabel_opts=opts.LabelOpts(formatter=axislabel_format),
# axistick_opts=opts.AxisTickOpts(is_show=True),
splitline_opts=opts.SplitLineOpts(is_show=True,
linestyle_opts=opts.LineStyleOpts(
type_='dotted',
opacity=0.5,
)
)
),
)
if df_gr is not None:
line = (
Line()
.add_xaxis(xaxis_data=df_gr.index.tolist())
.add_yaxis(
series_name="同比增长率",
yaxis_index=1,
y_axis=df_gr.values.tolist(),
label_opts=opts.LabelOpts(is_show=False),
linestyle_opts=opts.LineStyleOpts(width=3),
symbol_size=8,
itemstyle_opts=opts.ItemStyleOpts(border_width=1, border_color='', border_color0='white'),
z_level=2 # 渲染图层大于柱状图,保证线图在上方,低版本pyecharts可能因为没有该参数报错
)
)
else:
stackbar = (Bar())

if df_gr is not None:
return stackbar.overlap(line) # 如果有次坐标轴最后要用overlap方法组合
else:
return stackbar

6&7、在views.py的query方法里的context增加之前步骤生成的图表options,切记一定要整合在query方法内,如果另起炉灶,读取数据的步骤要重复一遍,等于性能减半:

def query(request):
...

# Pyecharts交互图表
bar_total_trend = json.loads(prepare_chart(pivoted, 'bar_total_trend', form_dict))

context = {
...
'bar_total_trend': bar_total_trend
}

return HttpResponse(json.dumps(context, ensure_ascii=False), content_type="application/json charset=utf-8") # 返回结果必须是json格式

可以看看这时的query方法返回了什么,可以看到下方大段的echarts options:

回到filter.html的JS AJAX部分,在回调函数部分加入以下代码:

<script type="text/javascript">
$("#AJAX_get").click(function (event) {
...
$.ajax({
...
success: function (ret) { //成功执行
// 展示Pyecharts整体市场柱状组合图
chart.clear();
chart.setOption(ret['bar_total_trend']);
chart.hideLoading()
},
...
});
})
</script>

我们的第一个交互图表就诞生了: