用Flask和D3.js構建互動式圖表

語言: CN / TW / HK
ead>

多年來,資料分析對各行業的影響越來越大,因為它已經成為公司決策的關鍵。資料分析技術可以揭示趨勢、模式和指標,提供洞察力和優化。這就是為什麼開發人員必須瞭解如何構建能夠輕鬆實現資料視覺化的程式。

在這篇文章中,我們將使用Flask和D3.js來建立一個簡單的、互動式的資料儀表板,以瞭解與假設的客戶流失率有關的一些因素。

Flask是一個Python網路框架,提供工具、庫和技術來構建網路應用。D3.js是一個JavaScript庫,它使用資料操作DOM元素來渲染視覺元件。

專案設定和環境

建立一個隔離的本地環境,指定只適用於這個專案的各種軟體包和安裝的版本,以防止全域性安裝和軟體包碰撞,這是非常重要的。

我們將從建立一個虛擬的Python環境開始。像這樣使用 pip 安裝 virtualenv 包。

``` pip install virtualenv

```

導航到專案根目錄並建立虛擬環境。

``` virtualenv flask

```

在我們安裝包之前,虛擬環境必須被啟用。在專案根目錄下,執行。

``` source flask/bin/activate

```

接下來,我們安裝這個專案所需的包。這可以通過 pip 安裝所有的包,或者通過在專案的 GitHub 倉庫中找到的requirements.txt檔案來完成。

``` pip install -r requirements.txt

```

在成功安裝了所需的Python包後,我們轉而設定專案的檔案結構和所需檔案。

. ├── README.md ├── app.py ├── flask ├── static │ ├── Review.gif │ ├── css │ ├── data │ ├── js │ └── logo.jpeg └── templates └── index.html

專案工作流程概述

diagram of project workflow

客戶流失資料將被提供給Flask應用程式,在那裡將用Python進行資料處理操作。Flask應用程式將提供資料儀表板,D3.js將用JavaScript渲染相應的圖表。

剖析Flask的網路應用

app.py Python指令碼是一個Flask例項,包含入口、路由和終點。Python的Pandas和NumPy庫被用來進行資料處理操作。預處理的資料被序列化為JSON格式,以提供給index.html ,分析內容包括合同和任期特徵。

合同特徵描述了客戶與例項公司的合同條款,有三個等級:按月、一年和兩年。任期是一個連續的特徵,描述了客戶在該公司的停留月數。

``` from flask import Flask, jsonify, render_template import pandas as pd import numpy as np

app = Flask(name)

Reading data

data_df = pd.read_csv("static/data/Churn_data.csv") churn_df = data_df[(data_df['Churn']=="Yes").notnull()]

@app.route('/') def index(): return render_template('index.html')

def calculate_percentage(val, total): """Calculates the percentage of a value over a total""" percent = np.round((np.divide(val, total) * 100), 2) return percent

def data_creation(data, percent, class_labels, group=None): for index, item in enumerate(percent): data_instance = {} data_instance['category'] = class_labels[index] data_instance['value'] = item data_instance['group'] = group data.append(data_instance)

@app.route('/get_piechart_data') def get_piechart_data(): contract_labels = ['Month-to-month', 'One year', 'Two year'] _ = churn_df.groupby('Contract').size().values class_percent = calculate_percentage(, np.sum()) #Getting the value counts and total

piechart_data= [] data_creation(piechart_data, class_percent, contract_labels) return jsonify(piechart_data)

@app.route('/get_barchart_data') def get_barchart_data(): tenure_labels = ['0-9', '10-19', '20-29', '30-39', '40-49', '50-59', '60-69', '70-79'] churn_df['tenure_group'] = pd.cut(churn_df.tenure, range(0, 81, 10), labels=tenure_labels) select_df = churn_df[['tenure_group','Contract']] contract_month = select_df[select_df['Contract']=='Month-to-month'] contract_one = select_df[select_df['Contract']=='One year'] contract_two = select_df[select_df['Contract']=='Two year'] _ = contract_month.groupby('tenure_group').size().values mon_percent = calculate_percentage(, np.sum()) _ = contract_one.groupby('tenure_group').size().values one_percent = calculate_percentage(, np.sum()) _ = contract_two.groupby('tenure_group').size().values two_percent = calculate_percentage(, np.sum()) _ = select_df.groupby('tenure_group').size().values all_percent = calculate_percentage(, np.sum())

barchart_data = [] data_creation(barchart_data,all_percent, tenure_labels, "All") data_creation(barchart_data,mon_percent, tenure_labels, "Month-to-month") data_creation(barchart_data,one_percent, tenure_labels, "One year") data_creation(barchart_data,two_percent, tenure_labels, "Two year") return jsonify(barchart_data)

if name == 'main': app.run(debug=True)

```

該入口點有一個index.html 模板檔案,由資料儀表板佈局組成。index.html 模板由兩個容器組成:編寫部分和視覺化部分。

模板檔案包含了指令碼檔案的訪問點和一個CDN,將D3.js與CSS樣式表styles.css 一起連結到專案中。指令碼包括pieChart.jsbarChart.jsupdateBarChart.js 、和index.js ,它們做了以下工作。

  • 渲染餅狀圖和預設的柱狀圖
  • 根據餅圖的選擇來更新條形圖
  • 包括執行圖表功能的主指令碼,以便在儀表板上渲染。

index.html 模板還通過路由URL獲取JSON響應資料,有兩個變數:pieChartDataUrlbarChartDataUrl

```

<!DOCTYPE html>

Data Dashboard

Data Dashboard

Project: Interactive charts for frontend data visualization using flask and D3js

Author: Aboze Brain John

Bio: Aboze Brain John is a Technology Business Analyst. He has experience in Data Science and Analytics, Software Engineering, Product Research, and Technical Writing.

Project Overview: The project is focused on the analysis of churned customers. This analysis is achieved using Python's Flask library to serve the data and Javascript D3.js library to visualize the analysis. The use case is the Telco Customer Churn found on Kaggle here

The code can be found on Github here

The article can be found on Logrocket blog here

Logrocket logo

```

JavaScript指令碼利用函數語言程式設計正規化,用各種函式來建立在index.js 中執行的元件。index.js 檔案使用承諾來處理非同步操作,並表示最終完成(或失敗)和結果值。

``` const urls = [pieChartDataUrl, barChartDataUrl];

Promise.all(urls.map(url => d3.json(url))).then(run);

function run(dataset) { d3PieChart(dataset[0], dataset[1]); d3BarChart(dataset[1]); };

```

建立餅狀圖和柱狀圖的函式

接下來,我們有兩個函式,分別在pieChart.jsbarChart.js 靜態檔案中建立d3PieChartd3BarChart 。我們將利用SVG元素,因為它們提供不同的形狀,並提供更多的靈活性和力量。

d3PieChart 函式接受兩個引數:餅圖資料和資料集,以便在選擇餅圖的一個片斷時更新條形圖。pieChart.js 檔案包含以下內容。

``` function d3PieChart(dataset, datasetBarChart){ // Set up SVG dimensions and properties const margin = {top:20, right:20, bottom:20, left:20}; const width = 350 - margin.left - margin.right, height = 350 - margin.top - margin.bottom, outerRadius = Math.min(width, height) / 2, innerRadius = outerRadius * .5, color = d3.scaleOrdinal(d3.schemeAccent); //color scheme

// Selecting the div with id pieChart on the index.html template file const visualization = d3.select('#pieChart') .append("svg") //Injecting an SVG element .data([dataset]) //Binding the pie chart data .attr("width", width) .attr("height", height) .append("g") //Grouping the various SVG components
.attr("transform", "translate(" + outerRadius + "," + outerRadius + ")"); //Piechart tranformation and transition upon page loading

const data = d3.pie() //Creating the data object that will develop the various segment of the pie chart. .sort(null) .value(function(d){return d.value;})(dataset); // Retrieve the pie chart data values from our Flask app, the pie chart where tied to a 'value' key of a JSON object.

// Generate an arc generator that produces the circular chart (outer circle) const arc = d3.arc()
.outerRadius(outerRadius) .innerRadius(0);

// Generate an arc generator that produces the circular chart (inner circle)

const innerArc = d3.arc() .innerRadius(innerRadius) .outerRadius(outerRadius);

// Create pie chart slices based on the data object created const arcs = visualization.selectAll("g.slice") .data(data)
.enter() // creates the initial join of data to elements
.append("svg:g")
.attr("class", "slice") .on("click", click);

arcs.append("svg:path") // create path element .attr("fill", function(d, i) { return color(i); } ) //Add color to slice .attr("d", arc) // creates actual SVG path with associated data and the arc drawing function .append("svg:title") // Add title to each piechart slice .text(function(d) { return d.data.category + ": " + d.data.value+"%"; });

d3.selectAll("g.slice") // select slices in the group SVG element (pirchart) .selectAll("path") .transition() //Set piechart transition on loading .duration(200) .delay(5) .attr("d", innerArc);

arcs.filter(function(d) { return d.endAngle - d.startAngle > .1; }) //Define slice labels at certain angles .append("svg:text") //Insert text area in SVG .attr("dy", "0.20em") //shift along the y-axis on the position of text content .attr("text-anchor", "middle") //Position slice labels .attr("transform", function(d) { return "translate(" + innerArc.centroid(d) + ")"; }) //Positioning upon transition and transform .text(function(d) { return d.data.category; }); // Append category name on slices

visualization.append("svg:text") //Append the title of chart in the middle of the pie chart .attr("dy", ".20em") .attr("text-anchor", "middle") .text("churned customers") .attr("class","title");

// Function to update barchart when a piechart slice is clicked function click(d, i) { updateBarChart(d.data.category, color(i), datasetBarChart); } }

```

d3BarChart 函式定義了預設組,當頁面載入時,沒有選擇特定的合同類,就會被視覺化。預設組是流失客戶的任期分佈。

d3BarChart 只接受一個引數:所服務的條形圖資料。barChart.js 包含以下內容。

``` //Set up SVG dimensions and properties const margin = {top: 20, right: 10, bottom: 20, left: 20}, width = 350 - margin.left - margin.right, height = 350 - margin.top - margin.bottom, barPadding = 5, graph_misc = {ylabel: 4, xlabelH : 5, title:9};

// Setting the default group const group = "All";

// Function to get the percentage values for a specific selected group from the whole dataset. function get_percentage(group, datasetBarChart){ const _ = []; for (instance in datasetBarChart){ if (datasetBarChart[instance].group==group){ .push(datasetBarChart[instance]) } } return ; };

function d3BarChart(datasetBarChart){ defaultBarChart = get_percentage(group, datasetBarChart);

const xScale = d3.scaleLinear() // Barchart X axis scale .domain([0, defaultBarChart.length]) // Scale range from 0 to the length of data object .range([0, width]);

const yScale = d3.scaleLinear() // Barchart y axis scale .domain([0, d3.max(defaultBarChart, function(d) { return d.value; })]) //Scale range from 0 to the maximum value of the default bar chart data .range([height, 0]);

// // Selecting the div with id barChart on the index.html template file const bar = d3.select('#barChart') .append('svg') .attr('width', width + margin.left + margin.right) .attr('height', height + margin.top + margin.bottom) .attr('id', 'barChartPlot');

//Adding barchart title bar.append('text') .attr('x', (width + margin.left + margin.right)/2) .attr('y', graph_misc.title) .attr('class','title')
.attr('text-anchor', 'middle') .text('Tenure group for churned customers');

const visualization = bar.append('g') .attr("transform", "translate(" + margin.left + "," + (margin.top + graph_misc.ylabel) + ")");

visualization.selectAll("rect") .data(defaultBarChart) .enter() .append("rect") .attr("x", function(d, i) { return xScale(i); }) .attr("width", width / defaultBarChart.length - barPadding)
.attr("y", function(d) { return yScale(d.value); }) .attr("height", function(d) { return height-yScale(d.value); }) .attr("fill", "#757077");

//Adding barchart labels visualization.selectAll('text') .data(defaultBarChart) .enter() .append("text") .text(function(d) { return d.value+"%"; }) .attr("text-anchor", "middle")

   .attr("x", function(d, i) {
           return (i * (width / defaultBarChart.length)) + ((width / defaultBarChart.length - barPadding) / 2);
   })
   .attr("y", function(d) {
           return (yScale(d.value) - graph_misc.ylabel); //Setting the Y axis to represent the value in the served JSON data
   })
   .attr("class", "yAxis");

const xLabels = bar .append("g") .attr("transform", "translate(" + margin.left + "," + (margin.top + height + graph_misc.xlabelH) + ")");

xLabels.selectAll("text.xAxis") .data(defaultBarChart) .enter() .append("text") .text(function(d) { return d.category;}) .attr("text-anchor", "middle") .attr("x", function(d, i) { return (i * (width / defaultBarChart.length)) + ((width / defaultBarChart.length - barPadding) / 2); }) .attr("y", 15) .attr("class", "xAxis");
}

```

使圖表互動

我們已經成功地建立了預設的條形圖,但是為了使我們的專案具有互動性,我們將在選擇餅圖片時用新的數值更新條形圖。

updateBarChart.js 指令碼將啟用這一功能。它接受三個引數:在餅圖上選擇的組,餅圖片的顏色,以及更新的條形圖資料。

``` function updateBarChart(group, color, datasetBarChart){ const currentBarChart = get_percentage(group, datasetBarChart);

//Defining chart scale, same as the default bar chart const xScale = d3.scaleLinear() .domain([0, currentBarChart.length]) .range([0, width]);

const yScale = d3.scaleLinear() .domain([0, d3.max(currentBarChart, function(d) { return d.value; })]) .range([height,0]);

const bar = d3.select('#barChart svg'); //Selecting the div containing bar chart ID and creating an SVG element

// Add title to Barchart bar.selectAll("text.title") .attr("x", (width + margin.left + margin.right)/2) .attr("y", graph_misc.title) .attr("class","title")
.attr("text-anchor", "middle") .text("Tenure group for churned customers "+group);

const visualization = d3.select('barChartPlot') .datum(currentBarChart); //binding data to multiple SVG elements

visualization.selectAll('rect') .data(currentBarChart) .transition() .duration(750) .attr('x', (width + margin.left + margin.right)/2) .attr('y', graph_misc.title) .attr('class', 'title') .attr('text-anchor', 'middle') .text('Tenure group for churned customers '+group);

const plot = d3.select('#barChartPlot') .datum(currentBarChart); //binding data to multiple SVG elements

plot.selectAll('rect') .data(currentBarChart) .transition() //Setting bar chart change transition .duration(800) .attr('x', function(d,i){ return xScale(i); }) .attr('width', width/currentBarChart.length - barPadding) .attr('y', function(d){ return yScale(d.value) }) .attr("height", function(d) { return height-yScale(d.value); }) .attr("fill", color);

plot.selectAll("text.yAxis") .data(currentBarChart) .transition() .duration(750) .attr("text-anchor", "middle") .attr("x", function(d, i) { return (i * (width / currentBarChart.length)) + ((width / currentBarChart.length - barPadding) / 2);}) .attr("y", function(d) { return yScale(d.value) - graph_misc.ylabel;}) .text(function(d) { return d.value+'%';}) .attr("class", "yAxis"); };

```

新增樣式

最後,讓我們為我們的HTML模板新增一些樣式。樣式表應該連結到index.html 檔案,並在styles.css 靜態檔案中包含以下樣式。

``` / Reset default browser settings /

/ Box sizing rules / , ::before, *::after { box-sizing: border-box; }

/ Remove default padding and margin / * { padding: 0; margin: 0; }

/ Set core body defaults / body { position: fixed; display: flex; background: #fdfdfd; scroll-behavior: smooth; text-rendering: optimizeSpeed; font-family: "Roboto Mono", monospace; font-weight: bold; -webkit-font-smoothing: antialiased; overflow-x: hidden; }

/ Make images easier to work with / img { max-width: 100%; display: block; }

.about { margin: 10% 2%; width: 40%; text-align: justify;

} h1 { text-decoration: underline; margin: 0.5em 0em; }

p, h2, h6 { margin: 0.7em 0em; }

a { text-decoration: none; }

.visualization { display: flex; align-items: center; flex-direction: column; width:60%; }

pieChart {

margin-top: 4em; font-size: 12px; }

barChart {

font-size: 9px; margin-top: 4em; }

pieChart .title, #barChart .title{

font-weight: bold; }

.slice { font-size: 8px; font-family: "Roboto Mono", monospace; fill: white; font-weight: bold;
cursor: pointer; }

```

互動式資料儀表板已經成功建立最後,我們將在終端上執行我們的Flask應用,如下所示。

``` python run.py

```

網路應用將被託管在我們的localhost:5000上,可以通過任何瀏覽器訪問。

總結

在這篇文章中,我們介紹瞭如何使用Python的Flask的服務和預處理的資料來建立一個互動式的圖表儀表盤。我們操作了DOM元素,在網頁上用Javascript D3.js渲染視覺化效果。你可以使用這種技術來渲染柱狀圖或餅狀圖,並在你的下一個專案中輕鬆納入資料視覺化。

用Flask和D3.js構建互動式圖表》一文出現在LogRocket部落格上。