SwiftUI之深入解析如何使用SwiftUI Charts创建折线图

一、简单折线图

  • 苹果在 WWWDC 2022 上推出了 SwiftUI 图表,这使得在 SwiftUI 视图中创建图表变得异常简单。图表是以丰富的格式呈现可视化数据的一种很好的方式,而且易于理解。本文展示了如何用比以前从头开始创建同样的折线图少得多的代码轻松创建折线图,此外自定义图表的外观和感觉以及使图表中的信息易于访问也是非常容易的。
  • 从包含王者荣耀全部英雄的登场率开始,类似于在 SwiftUI 中创建折线图中使用的数据,定义一个结构来保存英雄和登场率的步数,并创建一个数组:
struct KingHeroWinRate: Identifiable {
    
    let id = UUID()
    let hero: String
    let winRate: Double
    
    init(hero: String, winRate: Double) {
        self.hero = hero;
        self.winRate = winRate;
    }
}

let currentHero: [KingHeroWinRate] = [
    KingHeroWinRate(hero: "孙悟空", winRate: 0.140),
    KingHeroWinRate(hero: "玄奘", winRate: 0.186),
    KingHeroWinRate(hero: "猪八戒", winRate: 0.111),
    KingHeroWinRate(hero: "嫦娥", winRate: 0.187),
    KingHeroWinRate(hero: "杨戬", winRate: 0.084)
]
  • 要创建一个折线图,为英雄登场率数据中的每个元素创建一个带有 LineMark 的图表,在 LineMark 的 X 值中指定英雄,在 Y 值中指定登场率,需要注意的是需要导入 Charts 框架。这就为英雄登场率数据创建了一个线形图,由于只有一个系列的数据,ForEach 可以省略,数据可以直接传递给 Chart 初始化器,两个部分都产生相同的折线图:
import SwiftUI
import Charts

struct ContentView: View {

    var body: some View {
        VStack {
            GroupBox("王者荣耀英雄登场率排行") {
                Chart {
                    ForEach(currentHero) {
                        LineMark(
                            x: .value("英雄", $0.hero),
                            y: .value("胜率", $0.winRate)
                        )
                    }
                }
            }
            
            GroupBox("王者荣耀英雄登场率排行") {
                Chart(currentHero) {
                    LineMark(
                        x: .value("英雄", $0.hero),
                        y: .value("胜率", $0.winRate)
                    )
                    
                }
            }
		}
    }
}
  • 使用 SwiftUI Charts 创建折线图显示英雄登场率的效果如下:

在这里插入图片描述

二、其它图表

  • SwiftUI Charts 有许多可用的图表选项,这些可以通过将图表标记从 LineMark 改为其他类型的标记(如 BarMark)来生成条形图:
struct ContentView: View {

    var body: some View {
        VStack {
            GroupBox("王者荣耀英雄登场率排行") {
                Chart {
                    ForEach(currentHero) {
                        LineMark(
                            x: .value("英雄", $0.hero),
                            y: .value("胜率", $0.winRate)
                        )
                    }
                }
            }
            GroupBox ("王者荣耀英雄登场率排行") {
                Chart(currentHero) {
                    BarMark(
                        x: .value("英雄", $0.hero),
                        y: .value("胜率", $0.winRate)
                    )
                }
            }
            GroupBox ("王者荣耀英雄登场率排行") {
                Chart(currentHero) {
                    PointMark(
                        x: .value("英雄", $0.hero),
                        y: .value("胜率", $0.winRate)
                    )
                }
            }
            GroupBox ("王者荣耀英雄登场率排行") {
                Chart(currentHero) {
                    RectangleMark(
                        x: .value("英雄", $0.hero),
                        y: .value("胜率", $0.winRate)
                    )
                }
            }
            GroupBox ("王者荣耀英雄登场率排行") {
                Chart(currentHero) {
                    AreaMark(
                        x: .value("英雄", $0.hero),
                        y: .value("胜率", $0.winRate)
                    )
                }
            }
        }
    }
}
  • 效果如下:

在这里插入图片描述

三、增加折线图的可访问性

  • 将图表植入 SwiftUI 的一个好处是,可以很容易地使用可访问性修饰符使图表变得可访问。为 KingHeroWinRate 添加一个计算属性,将数据返回为一个字符串,可由 accessibilityLabel 使用,然后为图表中的每个标记添加可访问性标签和值:
struct KingHeroWinRate: Identifiable {
    
    let id = UUID()
    let hero: String
    let winRate: Double
    
    init(hero: String, winRate: Double) {
        self.hero = hero;
        self.winRate = winRate;
    }
    
    var heroNameString: String {
        return hero;
    }
}
  • 第一次尝试添加这两个系列的数据没有按预期显示:
struct ContentView: View {

    var body: some View {
        VStack {
            GroupBox("王者荣耀英雄登场率排行") {
                Chart {
                    ForEach(heroData, id: \.period) {
                        ForEach($0.data) {
                            LineMark(
                                x: .value("英雄", $0.hero),
                                y: .value("登场率", $0.winRate)
                            )
                            .accessibilityLabel($0.heroNameString)
                            .accessibilityValue("\($0.winRate) winRate")
                        }
                    }
                }
            }
        }
    }
}

在这里插入图片描述

四、为折线图添加多个数据序列

  • 折线图是比较两个不同系列数据的好方法,创建第二个系列,即另外一组英雄的登场率,并将这两个系列添加到折线图中:
let previousHero: [KingHeroWinRate] = [
    KingHeroWinRate(hero: "刘备", winRate: 0.063),
    KingHeroWinRate(hero: "关羽", winRate: 0.071),
    KingHeroWinRate(hero: "赵云", winRate: 0.079),
    KingHeroWinRate(hero: "张飞", winRate: 0.061),
    KingHeroWinRate(hero: "黄忠", winRate: 0.061)
]

let currentHero: [KingHeroWinRate] = [
    KingHeroWinRate(hero: "孙悟空", winRate: 0.140),
    KingHeroWinRate(hero: "玄奘", winRate: 0.186),
    KingHeroWinRate(hero: "猪八戒", winRate: 0.111),
    KingHeroWinRate(hero: "嫦娥", winRate: 0.187),
    KingHeroWinRate(hero: "杨戬", winRate: 0.084)
]

let heroData = [
    (period: "Current Hero", data: currentHero),
    (period: "Previous Hero", data: previousHero)
]
  • 第一次尝试添加这两个系列的数据没有按预期显示:
        GroupBox("王者荣耀英雄登场率排行") {
            Chart {
                ForEach(heroData, id: \.period) {
                    ForEach($0.data) {
                        LineMark(
                            x: .value("英雄", $0.heroBranching),
                            y: .value("登场率", $0.winRate)
                        )
                        .accessibilityLabel($0.heroBranching)
                        .accessibilityValue("\($0.winRate) winRate")
                    }
                }
            }
        }

在这里插入图片描述

五、显示登场率系列

  • 在折线图中显示多个基于英雄的登场率系列,最初尝试在折线图中显示多组数据的问题是 X 轴使用了英雄,当前的英雄紧接着上一部分英雄,因此每一个点都是沿着 X 轴线性递增绘制的。有必要只用分路作为 X 轴的数值,这样所有的英雄都在同一个 X 坐标上绘制。
  • 在 KingHeroWinRate 中添加另一个计算属性,以便以字符串格式返回英雄的分路:
struct KingHeroWinRate: Identifiable {
    
    let id = UUID()
    let hero: String
    let winRate: Double
    
    init(hero: String, winRate: Double) {
        self.hero = hero;
        self.winRate = winRate;
    }
    
    var heroNameString: String {
        return hero;
    }
    
    var heroBranching: String {
        if (heroNameString == "孙悟空" || heroNameString == "刘备") {
            return "打野";
        } else if (heroNameString == "玄奘" || heroNameString == "关羽") {
            return "中路";
        } else if (heroNameString == "猪八戒" || heroNameString == "赵云") {
            return "上路";
        } else if (heroNameString == "嫦娥" || heroNameString == "张飞") {
            return "辅助";
        } else if (heroNameString == "杨戬" || heroNameString == "黄忠") {
            return "下路";
        } else {
            return "";
        }
   }
    
}
  • 此 heroBranching 用于图表中 LineMarks 的 x 值。另外,前景的样式设置为基于 KingHeroWinRate 数组的周期,折线图使用 x 轴的分路来显示英雄,以便在分路之间进行比较:
struct ContentView: View {

    var body: some View {       
        VStack {
            GroupBox("王者荣耀英雄登场率排行") {
                Chart {
                    ForEach(heroData, id: \.period) { hero in
                        ForEach(hero.data) {
                            LineMark(
                                x: .value("英雄", $0.heroBranching),
                                y: .value("登场率", $0.winRate)
                            )
                            .foregroundStyle(by: .value("branch", hero.period))
                            .accessibilityLabel($0.heroBranching)
                            .accessibilityValue("\($0.winRate) winRate")
                        }
                    }
                }
                .frame(height:400)
            }
            .padding()
            Spacer()
        }
    }
}

在这里插入图片描述

  • 18
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

╰つ栺尖篴夢ゞ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值