在AppleWatch中使用Charts圖表

1、前言

Swift提供了Charts框架,使得我們可以通過簡(jiǎn)單的代碼即可在iOS、iPad、Mac和Apple watch等設(shè)備中顯示圖表,并且支持自定義,輕易實(shí)現(xiàn)各種風(fēng)格的圖表。這一篇我們繼續(xù)關(guān)注Apple watch,將數(shù)據(jù)以圖表的形式顯示在Apple Watch中。例如這是我最近做的項(xiàng)目截圖,以折線圖顯示用戶當(dāng)天Hrv數(shù)據(jù)的變化:


hrv變化折線圖

這是蘋果官方App中使用Charts的示例圖:


蘋果官方App

2、創(chuàng)建項(xiàng)目

同樣的這系列文章主要介紹Apple watch開發(fā),所以本章示例也只創(chuàng)建watch app:


創(chuàng)建watch項(xiàng)目

項(xiàng)目名為ChartsDemo,勾選Watch-only App:


項(xiàng)目名

3、顯示柱狀圖

每一個(gè)圖表,為一個(gè)Chart組件,如果是柱狀圖,每一柱形為一個(gè)BarMark組件,例如某奶茶店有廣州和深圳兩家店鋪,要用組狀圖顯示某天的銷售量,完整示例代碼如下:

import SwiftUI
import Charts

struct ContentView: View {
    var body: some View {
        Chart() {
            BarMark(
                x: .value("銷量", 916),
                y: .value("地區(qū)", "廣州")
            )
            BarMark(
                x: .value("銷量", 1190),
                y: .value("地區(qū)", "深圳")
            )
        }
    }
}

#Preview {
    ContentView()
}
銷售數(shù)據(jù)

框架會(huì)默認(rèn)會(huì)根據(jù)數(shù)據(jù),給我們?cè)O(shè)置xy軸的范圍。

在日常開發(fā)中,一般都是數(shù)據(jù)驅(qū)動(dòng)視圖,所以示例中的核心代碼應(yīng)該為:

let data = [
        (city:"廣州",sales:916),
        (city:"深圳",sales:1190),
        (city:"北京",sales:1890),
        (city:"上海",sales:1497)
    ]
    
    var body: some View {
        Chart() {
            ForEach(data,id: \.city) {
                BarMark(
                    x: .value("銷量", $0.sales),
                    y: .value("城市", $0.city)
                )
            }
        }
    }
數(shù)據(jù)圖表

我們可以進(jìn)一步簡(jiǎn)化上面的代碼:

var body: some View {
    Chart(data,id: \.city) {
        BarMark(
            x: .value("銷量", $0.sales),
            y: .value("城市", $0.city)
        )
    }
}

注意:如果有兩條city名稱一樣的數(shù)據(jù),框架會(huì)自動(dòng)合并成一條數(shù)據(jù)

3、顯示折線圖

先上代碼和運(yùn)行效果:

import SwiftUI
import Charts

struct ContentView: View {

    let data = [
        (day:Util.getDate(offset:1),sales:1666),
        (day:Util.getDate(offset:2),sales:1899),
        (day:Util.getDate(offset:3),sales:1254),
        (day:Util.getDate(offset:4),sales:1200),
        (day:Util.getDate(offset:5),sales:983),
        (day:Util.getDate(offset:6),sales:1101),
        (day:Util.getDate(offset:7),sales:801),
    ]
    
    var body: some View {
        Chart(data,id: \.day) {
            BarMark(
                x: .value("日期", $0.day,unit: .day),
                y: .value("銷售量", $0.sales)
            )
            .foregroundStyle(.pink)
        }
    }
}

class Util {
    static func getDate(offset:Int) -> Date {
        let calendar = Calendar.current
        return calendar.date(byAdding: .day, value: -offset, to: Date()) ?? Date()
    }
}

#Preview {
    ContentView()
}

最近一周銷量柱狀圖

如上,我們先實(shí)現(xiàn)了最近一周銷量柱狀圖,創(chuàng)建了Util工具類用于獲取過去某天的日期,然后以日期升序?yàn)閤坐標(biāo),日銷售量為y坐標(biāo)繪制出了對(duì)應(yīng)的柱狀圖。

我們現(xiàn)在想要以折線圖形式展示數(shù)據(jù),只需一個(gè)小改動(dòng),將上面代碼中的BarMark改為L(zhǎng)ineeMark即可,核心代碼塊:

    var body: some View {
        Chart(data,id: \.day) {
            LineMark(
                x: .value("日期", $0.day,unit: .day),
                y: .value("銷售量", $0.sales)
            )
            .foregroundStyle(.pink)
        }
    }

預(yù)覽效果:


最近一周銷量折線圖

我們還可以在同一個(gè)圖表中,顯示廣州、深圳兩個(gè)城市最近一周的銷售對(duì)比折線圖,核心代碼如下:

struct ContentView: View {
    
    var body: some View {
        
        let gzData:[(day:Date,sales:Int)] = [
            (day:Util.getDate(offset:1),sales:1666),
            (day:Util.getDate(offset:2),sales:1899),
            (day:Util.getDate(offset:3),sales:1254),
            (day:Util.getDate(offset:4),sales:1200),
            (day:Util.getDate(offset:5),sales:983),
            (day:Util.getDate(offset:6),sales:1101),
            (day:Util.getDate(offset:7),sales:801),
        ]
        
        let szData:[(day:Date,sales:Int)] = [
            (day:Util.getDate(offset:1),sales:2287),
            (day:Util.getDate(offset:2),sales:1655),
            (day:Util.getDate(offset:3),sales:1598),
            (day:Util.getDate(offset:4),sales:1067),
            (day:Util.getDate(offset:5),sales:900),
            (day:Util.getDate(offset:6),sales:1201),
            (day:Util.getDate(offset:7),sales:540),
        ]
        
        let sericeData = [
            (city:"廣州",data: gzData),
            (city:"深圳",data: szData)
        ]
        
        Chart {
            ForEach(sericeData,id: \.city) { serice in
                ForEach(serice.data,id: \.day) {
                    LineMark(
                        x: .value("日期", $0.day,unit: .day),
                        y: .value("銷售量", $0.sales)
                    )
                }
                .foregroundStyle(by: .value("City", serice.city))
            }
        }
    }
}

預(yù)覽效果:


雙城銷量對(duì)比圖

其中代碼

.foregroundStyle(by: .value("City", serice.city))

表示我們需要不同城市以不同顏色進(jìn)行區(qū)分。同樣的,我們還可以設(shè)置折線為平滑曲線,只需添加以下一行配置:

.interpolationMethod(.catmullRom)

預(yù)覽效果:


曲線

為了使數(shù)據(jù)更明顯,也可以給線條加上符號(hào):
.symbol(.circle)
預(yù)覽效果:


添加符號(hào)

嘗試將LineMark改回BarMark,看下是什么效果?
再添加.position(by: .value("City", serice.city)),看下是什么效果?

4、其它類型

除了上面介紹了的柱狀圖和折線圖,Charts還支持其它標(biāo)記類型:


Charts支持的標(biāo)記類型

甚至能將他們相互組合,構(gòu)建更為復(fù)雜的圖表。如下圖,使用LineMark顯示某家店最近一年每個(gè)月的日均銷售量,同時(shí)通過AreaMark顯示當(dāng)月最高和最低銷售量情況:

組合使用

完整代碼如下:

import SwiftUI
import Charts

struct ContentView: View {
    
    var body: some View {
        
        let data = [
            (month:Util.getMonth(offset:1),dailyAverage:939,dailyMin: 550,daiyMax: 1209),
            (month:Util.getMonth(offset:2),dailyAverage:879,dailyMin: 399,daiyMax: 1127),
            (month:Util.getMonth(offset:3),dailyAverage:840,dailyMin: 422,daiyMax: 1009),
            (month:Util.getMonth(offset:4),dailyAverage:823,dailyMin: 439,daiyMax: 991),
            (month:Util.getMonth(offset:5),dailyAverage:797,dailyMin: 378,daiyMax: 965),
            (month:Util.getMonth(offset:6),dailyAverage:800,dailyMin: 380,daiyMax: 891),
            (month:Util.getMonth(offset:7),dailyAverage:820,dailyMin: 401,daiyMax: 911),
            (month:Util.getMonth(offset:8),dailyAverage:832,dailyMin: 390,daiyMax: 1001),
            (month:Util.getMonth(offset:9),dailyAverage:791,dailyMin: 356,daiyMax: 954),
            (month:Util.getMonth(offset:10),dailyAverage:801,dailyMin: 370,daiyMax: 959),
            (month:Util.getMonth(offset:11),dailyAverage:765,dailyMin: 311,daiyMax: 876),
            (month:Util.getMonth(offset:12),dailyAverage:789,dailyMin: 339,daiyMax: 991),

        ]

        Chart(data,id: \.month) {
            AreaMark(
                x: .value("月", $0.month, unit: .month),
                yStart: .value("日最低", $0.dailyMin),
                yEnd: .value("日最高", $0.daiyMax)
            )
            .opacity(0.3)
            
            LineMark(
                x: .value("月", $0.month, unit: .month),
                y: .value("日均銷量", $0.dailyAverage)
            )
        }
        
    }
}

class Util {
    static func getMonth(offset:Int) -> Date {
        let calendar = Calendar.current
        return calendar.date(byAdding: .month, value: -offset, to: Date()) ?? Date()
    }
}

#Preview {
    ContentView()
}

將AreaMark和LineMark改為BarMark和RectangleMark,可以得到完全不同的圖表樣式:


組合2

核心代碼:

Chart(data,id: \.month) {
    BarMark(
        x: .value("月", $0.month, unit: .month),
        yStart: .value("日最低", $0.dailyMin),
        yEnd: .value("日最高", $0.daiyMax),
        width: .ratio(0.6)
    )
    .opacity(0.3)
    
    RectangleMark(
        x: .value("月", $0.month, unit: .month),
        y: .value("日均銷量", $0.dailyAverage),
        width: .ratio(0.6),
        height: 2
    )
}

甚至,我們可以再添加一個(gè)RuleMark,且設(shè)置annotation屬性來顯示對(duì)應(yīng)文字 :

RuleMark(
    y: .value("平均", 825)
)
.lineStyle(StrokeStyle(lineWidth: 1))
.foregroundStyle(.red)
.annotation(position: .top, alignment: .leading) {
    Text("平均:825")
        .font(.headline)
        .foregroundStyle(.red)
}
三個(gè)組合

這一篇先到這里吧,下一篇將探索Charts的自定義屬性,打造更符合我們app風(fēng)格的個(gè)性化圖表。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容