用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博客上。