#15實作Chart圖表與分析資料(包含MySQL)

實作Chart圖表與分析資料(包含MySQL)

面試中有題目是要實作圖表,面試官會先給你數據(excel檔案),然後請你當場做出來.時間是一小時.我都沒有做出來…….不過回家研究後就做出來了(五題)(茶).還是有一題不會

然後當場當然是被電慘……

這個面試的重點是要考你如何去分析資料,並把資料以合適的方式呈現給使用者.面試的重點是對資料呈現要有敏銳度.

資料的格式有很多種,以下就介紹txt和json的格式.

資料集可以丟進去APP,也就是存取在Assets中.

不過就會被問起App肥大的問題了.因此最完美的做法是把資料丟入資料庫,讓資料庫去做它該做的事情,並使用php去進行資料庫的抓取.如果能用php把資料轉成json格式,是最好的.你我都知道json格式有多好用.但在此我先求抓到資料就好.因此php的部分會是類似txt的方法.

此篇章的重點:

  • Txt的切割字串,整理數據
  • Json解析,使用字典來整理數據
  • Chart圖表的使用概念(主要格式與各個小功能等)(請用SPM安裝)
  • MySQL資料庫(Swift程式和php的程式碼)

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — -

以下是考試的題目,有興趣的人先挑戰在一小時內可以做出多少.數據我就放在Github中.可以按碼表計時,體驗實際壓力的狀況.

1.圓餅圖呈現Platform各別比例

2.畫出acount” kmmue813"每日bets & avgbet走勢圖

3.畫出IP分布圖

4.畫出每日不重複account數柱狀圖

5.畫出9月份Bet加總前10的Owner柱狀圖

6.畫出account”fokye185"各別platform的bet總數佔比

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —-

首先先來介紹Chart圖表的各式功能,以下用程式碼作為示範

首先圖表的程式碼大多都是這個格式

1.準備一個View,改變型別

從網路上去用api拉資料,丟進去陣列裡面

2.setChart(dataPoints: [String], values: [Double])

dataPoints:長條圖標籤名稱的陣列,values:對應的統計資料

3.把setChart函數的參數,裝在 BarChartDataEntry陣列中

4.資料再轉換成BarChartDataSet 資料集

5.資料集再轉換成ChartData (Data資料)

此步驟是最後一步,之後就裝載給 View

6.資料的顏色是從 BarChartDataSet進行設定

ChartData則可以處理資料群組分類

動畫則是從View 進行設定,也可以進行存圖,把view轉換成jpeg

  • Chart圖表的使用概念(主要格式與各小功能等)

上菜!

func setChart(dataPoints: [String], values: [Double],values1: [Double])
{
//如果沒有統計資料
if values.count == 0
{
//在統計圖上顯示沒有資料的訊息,直接離開函式
BarChartView.noDataText = "統計圖沒有取得資料"
return
}

//統計圖資料入口陣列
var dataEntries: [BarChartDataEntry] = []
var dataEntries1: [BarChartDataEntry] = []


for i in 0..<dataPoints.count{
//資料筆數
let dataEntry = BarChartDataEntry(x: Double(i), y: values[i])
let dataEntry1 = BarChartDataEntry(x: Double(i), y: values1[i])
//加入上方陣列
dataEntries1.append(dataEntry)
dataEntries.append(dataEntry1)
}
//實體化資料集,會出現在左下角的位置
let chartDataSet = BarChartDataSet(entries: dataEntries, label: "下注平台")
let chartDataSet1 = BarChartDataSet(entries: dataEntries1, label: "平台bet")

//兩筆資料裝取給BarChartDataSet
let dataSets: [BarChartDataSet] = [chartDataSet,chartDataSet1]

//給BarChartData
let chartData = BarChartData(dataSets: dataSets)
//給BarChartView
BarChartView.data = chartData

//數字的表示方式
let formatter = NumberFormatter()
//數字取到第幾位,0就是個位數
formatter.maximumFractionDigits = 0
//以比例方式呈現,也可以換為貨幣或是其他等方式
formatter.numberStyle = .percent
//乘數
formatter.multiplier = 1.0
//替chartData設定formatter,請參照以下格式撰寫
chartData.setValueFormatter(DefaultValueFormatter(formatter: formatter))

//長條圖的寬
chartData.barWidth = 0.3
//barSpace是長條圖的留白;groupSpace是總group的留白
chartData.groupBars(fromX: 0, groupSpace: 0.3, barSpace: 0.05)

//可採用公版配色
chartDataSet.colors = ChartColorTemplates.material()
//可自行設定顏色
chartDataSet1.colors = [UIColor(red: 230/255, green: 126/255, blue: 34/255, alpha: 1)]

//字要在圖表哪側.需要引用協定
//在viewdidload 引用 BarChartView.xAxis.valueFormatter = self
BarChartView.xAxis.labelPosition = .bottom
//Label的顯示數量
BarChartView.xAxis.labelCount = 6
//背景
BarChartView.backgroundColor = UIColor(red: 189/255, green: 195/255, blue: 199/255, alpha: 1)
//動畫
BarChartView.animate(xAxisDuration: 2.0, yAxisDuration: 2.0, easingOption: .easeInBounce)


//界線(limit line)顯示目標線
let ll = ChartLimitLine(limit: 10.0, label: "Target")
BarChartView.rightAxis.addLimitLine(ll)
}

//引用BarChartView.xAxis.valueFormatter 會出現該函式,是要設定xAxis的字串
func stringForValue(_ value: Double, axis: Charts.AxisBase?) -> String{
//months是使用者的數據陣列
return months[Int(value)]
}
//指定統計圖的觸控事件實作在此類別
//BarChartView.delegate = self
func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight){
print("點選:\(highlight)")
print("\(entry.y) in \(months[Int(entry.x)])")
}

如此就可以製作美美的圖表,想做成別種圖的話,只要換成對應的類型就好.

  • Txt的切割字串,整理數據
  • Json解析,使用字典來整理數據

拿到excel檔案的話,請先不要慌張,上網轉成json格式,或是直接存txt.這邊當然是比較推薦json檔,方便使用屬性來撰寫邏輯.

    //直接使用txt,可參考彼得潘男神的文章
let url = Bundle.main.url(forResource: "Data", withExtension: "txt")
let content = try! String(contentsOf: url!)
//遇上空白字串就切割,並裝成陣列
let lineArray = content.components(separatedBy: "\n")
for line in lineArray {
//遇上tab就換行
let bookInfoArray = line.components(separatedBy: "\t")
dataArray.append(bookInfoArray[5])
}

Json解碼

struct SearchResponse:Codable{
let data:[StoreItem]
}

struct StoreItem:Codable{

var date:String
let owner:String
let parent:String
let account:String
let IP:String
let platform:String
let bets:String
let avgBet:String
}

class jsonModel {
static func loadJsonData(_ filename: String) -> SearchResponse {
guard let data = NSDataAsset(name: filename)?.data else {
fatalError("Couldn't load \(filename) from asset")
}
do {
let decoder = JSONDecoder()
let searchResponseData = try decoder.decode(SearchResponse.self, from: data)
return searchResponseData
} catch {
fatalError("Couldn't parse \(filename) as \(SearchResponse.self):\n\(error)")
}
}
}

各題目的邏輯,我挑選比較麻煩的第4題和第5題

第4題

    var dicStoreItem:[String:[StoreItem]] = [:]
var dicGroup:[String] = []

@IBOutlet weak var BarChartView: BarChartView!
override func viewDidLoad() {
super.viewDidLoad()


var accountArray:[Double] = []
let fileName = "data(Json)"
let jsonData = loadJsonData(fileName)
var datas = jsonData.data

//迴圈取資料
for i in 0...datas.count-1{
//排序時間的日期,但該字串的時間格式沒有辦法很精確的排序,所以變換時間格式.
//生成時間dateFormatter
let dateFormatter = DateFormatter()
//設定格式來接取資料的時間格式
dateFormatter.dateFormat = "MM-dd-yy"
//轉換時間格式
let date = dateFormatter.date(from:datas[i].date)
//設定格式,此格式月份為0開頭,故可以進行排序
dateFormatter.dateFormat = "MM/dd"
//再次轉換
datas[i].date = dateFormatter.string(from: date!)
}
//把datas用date進行分類
dicStoreItem = Dictionary(grouping: datas, by: { data in
data.date
})
print(dicStoreItem)
//排序時間,因為是0開頭,所以可以排序
dicGroup = dicStoreItem.keys.sorted()
print(dicGroup)
//計算是否為1000筆
var number = 0
for j in dicGroup.indices{
//排序相同的時間出現過幾次,就能知道每日的帳號數量
let accountCount = dicStoreItem[dicGroup[j]]!.count
//字典的key只會有一個,為出現幾次的總數
//加進陣列
accountArray.append(Double(accountCount))
//計算筆數
number += accountCount
print(accountCount)
print(number)
}
}

第5題

var dicStoreItem:[String:[StoreItem]] = [:]
var dicGroup:[String] = []

//元組陣列
var ownBets:[(String,Double)] = []

var ownerTop10:[String] = []
var betsTop10:[Double] = []


override func viewDidLoad() {
super.viewDidLoad()

let fileName = "data(Json)"
let jsonData = jsonModel.loadJsonData(fileName)

let datas = jsonData.data

//字典使用grouping進行分類,分類標準是datas中的owner屬性
dicStoreItem = Dictionary(grouping: datas, by: { data in
data.owner
})
//把字典的key值進行排序後裝進陣列
dicGroup = dicStoreItem.keys.sorted()

//使用字典key的陣列跑迴圈
for i in dicGroup.indices{
var betAll:Double = 0
//跑迴圈,總數是迴圈對應的字典的key總數有幾個,會對應出相對應的資料
for j in 0...((dicStoreItem[dicGroup[i]]?.count) ?? 1)-1{
//因爲資料有空白,所以要用trimmingCharacters去空白
betAll += Double(dicStoreItem[dicGroup[i]]![j].bets.trimmingCharacters(in: .whitespaces)) ?? 0
//每次字典的key對應的vaule值
}
//使用元組裝進陣列
ownBets.append((dicGroup[i],betAll))
}

//進行排序分類
ownBets.sort(by: sorterForBet)
print(ownBets)
//迴圈取前十
for i in 0..<10{
ownerTop10.append(ownBets[i].0)
betsTop10.append(ownBets[i].1)
}
print(ownerTop10)
print(betsTop10)
}
//排序的函式
//排序 第一個參數跟第二個參數的型別要一樣
func sorterForBet(this:(String,Double), that:(String,Double)) -> Bool {
return this.1 > that.1
}

以下為MySQL的部分

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

拿到excel檔案的話,請先不要慌張,上網轉成Json格式,或是直接存txt.這邊當然是比較推薦json檔,方便使用屬性來撰寫邏輯.

接下來要示範連接MySQL的部分.眾所皆知MySQL是一套資料庫系統,為結構查詢語言.可以在資料庫進行查詢和增刪查改的動作.所以當然可以使用SQL語法,先進行查詢的動作,來省去些撰寫邏輯的麻煩.(不過邏輯概念很強的人,當然是寫得很快囉,但資料庫的優點是能讓App瘦身,畢竟資料放在App很危險啊).

最好的狀況是用php抓取資料,並製作成Json格式回傳給App,讓App用方便的Json格式解析,但我目前還沒有寫出如何用php做出Json格式,所以只能提供抓資料轉成陣列,再用txt的解法.基本上加上SQL語法的查詢,真的可以迅速取到想要的資料

使用資料庫的優點

  • App資料瘦身
  • 可用條件抓取所需的資料

微麻煩的事情是要寫php去抓取資料.畢竟直接讀取資料庫可是很危險的事情啊.上網搜尋就有php範例的寫法,但在此就直接提供給大家囉.

不過MySQL的建置和phpMyAdmin的設定要請大家自己上網安裝了.

以下為php程式碼,請在VsCode撰寫

<?PHP

//指定網頁的中文格式
header("Content-type: text/html; charset=utf-8");
//連接資料庫
$user = ""; //資料庫帳號
$password = ""; //資料庫密碼
$host = ""; //資料庫IP
$db = ""; //資料庫名稱
$conn=mysqli_connect($host, $user,$password) or die("資料庫連線錯誤!");
//指定連線的資料庫
mysqli_select_db($conn,$db);
//指定資料庫使用的編碼
mysqli_query($conn,"SET NAMES utf8");

//準備查詢的指令]

$getDataSQL = sprintf("select DISTINCT date ,count(account)
from iosTest
GROUP BY date
ORDER BY date;
"
);
//執行update指令
$result=mysqli_query($conn,$getDataSQL) or die(mysqli_error());

//有資料的話
if (mysqli_num_rows($result) > 0){
//取出資料加入陣列,用fetch_assoc()函式
while ($row = $result->fetch_assoc()){
// $row是 陣列的資料,這會依序裝在一個陣列裡面.中間加上逗號,方便進行切割.名稱要跟查詢的欄位對應
//最外層加上換行,加上這兩個設計就能減少取資料的麻煩.基本上加上SQL語法的查詢,真的可以迅速取到想要的資料
echo $row["owner"] . "," . $row["sum(bets)"] . "\n";
}
}

//關閉資料庫連結
mysqli_close($conn);
?>

以下要提供Swift連結php的部分,想找什麼資料,先用SQL進行查詢,App只負責畫面的呈現

    //localhost
let webDomain = "http://127.0.0.1/"
//紀錄目前處理中的網路服務
var strURL = "" //ex.select_data.php
//紀錄目前處理中的網路物件
var url:URL!
//取得預設的網路串流物件
var session = URLSession.shared
//宣告網路資料傳輸任務(通用型任務:同時適用於上傳和下載)
var dataTask:URLSessionDataTask!

var dataArray1:[String] = []
var dataArray2:[Double] = []

func getDataFromPhp() {
//指定提供網路服務的網址,此為檔案名稱
strURL = "iosTest.php"
//將WebService的完整網址形成URL實體
url = URL(string: webDomain+strURL)
//由網路串流物件來"準備"資料傳輸任務
dataTask = session.dataTask(with: url, completionHandler: {
Data, response, error
in
//當沒有error時,表示順利取得XML資料
if error == nil {
//呼叫執行緒,確保拿到資料.可以寫一個UIActivityIndicatorView來顯示網路正在運行
DispatchQueue.main.async {
//取網址
let content = try! String(contentsOf: self.url!)
// print(content)
//先切割換行
let lineArray = content.components(separatedBy: "\n")
for i in 0..<lineArray.count-1 {
//再切割逗號
let bookInfoArray = lineArray[i].components(separatedBy: ",")
self.dataArray1.append(bookInfoArray[0])
self.dataArray2.append(Double(bookInfoArray[1])!)
}
//設定圖表,放在viewDidload是設定不了的喔
self.setPieChart(dataPoints: self.dataArray1, values: self.dataArray2)
}
print("dataArray1",self.dataArray1)
print("dataArray2",self.dataArray2)
} else {
DispatchQueue.main.async {
//產生提示視窗
let alert = UIAlertController(title: "資料處理", message: "無法取得資料", preferredStyle: .alert)
//產生提示視窗內用的按鈕
let okAction = UIAlertAction(title: "確定", style: .destructive)
//將按鈕加入提示視窗
alert.addAction(okAction)
//顯示提示視窗
self.present(alert, animated: true)
}
}
})
//啟動資料傳輸任務
dataTask.resume()
}

數據:

參考資料:

https://medium.com/彼得潘的-swift-ios-app-開發問題解答集/將-google-表單或-excel-檔變成-json-ceffc3fc3542

--

--