做數據分析時,我們可能會很常使用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
架構圖如上~
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全部放上來,篇幅有點太長了
所以這次就先以折線圖來說~
這是官方示例的圖
接下來我們可以想一下,哪些部分是需要用到我們自己資料的
標題以及圖例的名稱
x軸的值以及y軸的值還有各自的名稱
最重要的就是我們的資料啦
這些都是等等我們需要注意的地方
HTML
:
<!DOCTYPE html>
<html lang="en">
<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>
<!--設置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 randrange
import pandas as pd
def 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_series
def trans_df_to_list(series):
"""因為echart的data需要接收list 所以需要轉格式"""
if isinstance(series, pd.Series):
return series.values[0]
else:
return
def create_data(*args):
res = [i for i in args]
return res
def 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之類的方式再去選取我要的series
create_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的部分便可以直接去操作測試
最後的成果如下:

甚至你可以點擊右上方的一些按鈕,會有很多不錯的特效
例如轉換成柱狀圖等等

但是這樣還不夠~
我們此時去更改視窗寬度,會發現圖表根本就沒有變化
而echarts有resize方法,可以讓圖表隨著視窗改變而改變
https://echarts.apache.org/zh/api.html#echartsInstance.resize

那一般我們沒有特別要求的話可以直接這樣調用
// 視窗調整時會更改echart圖表
window.onresize = function () {
myLine.resize()
這邊額外說一下,如果我們使用下圖這種圓餅圖

我們縮小的時候,會更希望由左右兩邊變成上下並行
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來建立DOM
echarts用DOM來實例化
$(function(){})等DOM載入後執行ajax
ajax拿到資料後
製作option變量
echarts對象使用setOption方法拿option渲染圖表
頁面監聽resize,並且echarts對象隨之調用resize方法改變大小
但是現在要修改成:
建立初始變量
獲取id來建立DOM
echarts用DOM來實例化
建立一個空變量rowData
建立一個fontMedia方法是根據當下視窗大小返回特定字體大小
$(function(){})等DOM載入後執行ajax
ajax拿到資料後
製作option變量
製作中會調用fontMedia來製作font-size
echarts對象使用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();
最後重整後,就可以發現我們的標題可以隨著視窗寬度變化而改變大小



有點懶得做gif所以直接丟圖XD
其他調整font-size就大同小異,就不示範了~
希望有幫助到想做圖的人~