SwiftUI之深入解析如何使用组合矩形GeometryReader创建条形(柱状)图

news2024/11/24 15:19:46

一、图表布局

  • 条形(柱状)图以矩形条的形式呈现数据的类别,其宽度和高度与它们表示的值成比例。SwiftUI 对探索不同布局和预览实时视图结果是很友好的,很容易将部分内容提取到子视图中,以便每个部分都很小且易于维护。
  • 从包含 HistogramView 以及可能的其它文本或数据的视图开始,HistogramView 包含一个标题和一个图表区,它们由文本和圆角矩形表示。
  • HistogramView 的创建:
struct HistogramView: View {
    var title: String

    var body: some View {
        GeometryReader { gr in
            let headHeight = gr.size.height * 0.10
            VStack {
                HistogramHeaderView(title: title, height: headHeight)
                HistogramAreaView()
            }
        }
    }
}

struct HistogramHeaderView: View {
    var title: String
    var height: CGFloat

    var body: some View {
        Text(title)
            .frame(height: height)
    }
}

struct HistogramAreaView: View {
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 5.0)
                .fill(Color(#colorLiteral(red: 0.80, green: 0.90, blue: 0.80, alpha: 1)))
        }
    }
}
  • 在 ContentView 中调用 HistogramView:
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct ContentView: View {
    var body: some View {
        VStack {
            Text("数据分析")
                .font(.title)

            HistogramView(
                title: "柱状图")
                .frame(width: 300, height: 300, alignment: .center)

            Spacer()
        }
        .padding()
    }
}
  • 效果如下所示:

在这里插入图片描述

二、添加条形(柱状)图

  • 定义一些简单的数据类别,如一周内每天看手机的时间分钟数,以下表数据被作为主视图的项目数据,每一条数据包含一个键值对(名称和值)。在平常的 App 里,这些数据应该通过 ViewModel 从 model 里取数据:
DayTimes
星期一188
星期二209
星期三300
星期四150
星期五198
星期六488
星期日409
  • 修改图表区并设置数据:
struct ContentView: View {
    
    let chartData: [DataItem] = [
        DataItem(name: "星期一", value: 188),
        DataItem(name: "星期二", value: 209),
        DataItem(name: "星期三", value: 300),
        DataItem(name: "星期四", value: 150),
        DataItem(name: "星期五", value: 198),
        DataItem(name: "星期六", value: 488),
        DataItem(name: "星期日", value: 409)
    ]

    var body: some View {
        VStack {
            Text("屏幕使用时间")
                .font(.title)

            HistogramView(
                title: "一周数据分析", data: chartData)
                .frame(width: 350, height: 500, alignment: .center)

            Spacer()
        }
    }
}
  • 更新 HistogramView 使数据可以作为参数传递到 HistogramAreaView:
struct HistogramView: View {
    
    var title: String
    var data: [DataItem]

    var body: some View {
        GeometryReader { gr in
            let headHeight = gr.size.height * 0.10
            VStack {
                HistogramHeaderView(title: title, height: headHeight)
                HistogramAreaView(data: data)
            }
        }
    }
}
  • 更新后的 HistogramView 需要一个 DataItem 的列表,GeometryReader 被用来确定条形图的可用高度,数据中的最大值得到后并传递给每个 ColumnView,主图表区域保持原来的圆角矩形,并以水平堆叠的方式叠加一系列条形,每个 DataItem 对应一个条形:
struct HistogramAreaView: View {
    
    var data: [DataItem]
    
    var body: some View {
        GeometryReader { gr in
            let fullBarHeight = gr.size.height * 0.90
            let maxValue = data.map { $0.value }.max()!
            
            ZStack {
                RoundedRectangle(cornerRadius: 5.0)
                    .fill(Color(#colorLiteral(red: 0.80, green: 0.90, blue: 0.80, alpha: 1)))
                VStack {
                    HStack(spacing:0) {
                        ForEach(data) { item in
                            ColumnView(
                                name: item.name,
                                value: item.value,
                                maxValue: maxValue,
                                fullBarHeight: Double(fullBarHeight))
                        }
                    }
                    .padding(4)
                }
                
            }
        }
    }
}
  • 为 ColumnView 创建一个新的视图,该视图为每条数据创建一个条形图,它需要每一条数据的名称和值以及最大值和可用的条形高度,每个条形图都表示为圆角矩形,条形高度相对于最大条形高度设置,条形的颜色设置为红色:
struct ColumnView: View {
    
    var name: String
    var value: Double
    var maxValue: Double
    var fullBarHeight: Double

    var body: some View {
        let barHeight = (Double(fullBarHeight) / maxValue) * value
        VStack {
            Spacer()
            ZStack {
                VStack {
                    Spacer()
                    RoundedRectangle(cornerRadius:5.0)
                        .fill(Color.red)
                        .frame(height: CGFloat(barHeight), alignment: .trailing)
                }

                VStack {
                    Spacer()
                    Text("\(value, specifier: "%.0F")")
                        .font(.footnote)
                        .foregroundColor(.white)
                        .fontWeight(.bold)
                }
            }
            Text(name).font(Font.system(size: 13))
        }
        .padding(.horizontal, 4)
    }
}
  • 最终效果如下:

在这里插入图片描述

三、横屏显示(屏幕旋转)

  • 条形(柱状)图在使用样本数据时看起来不错,图表会调整到适合它所处的容器视图之中。同样的图表可以放到任何没有其他视图的新视图上,当设备旋转时,图标将会充满空间并调整大小:
struct ContentView: View {
    
    let chartData: [DataItem] = [
        DataItem(name: "星期一", value: 188),
        DataItem(name: "星期二", value: 209),
        DataItem(name: "星期三", value: 300),
        DataItem(name: "星期四", value: 150),
        DataItem(name: "星期五", value: 198),
        DataItem(name: "星期六", value: 488),
        DataItem(name: "星期日", value: 409)
    ]

    var body: some View {
        
        VStack() {
            
            Text("屏幕使用时间")
                .font(.title)
            
            HistogramView(
               title: "一周数据分析", data: chartData)
            Spacer()
       }
       .padding()
    }
}
  • 效果如下:

在这里插入图片描述

四、王者荣耀英雄胜率的柱状图渲染分析

  • 王者荣耀里面有很多的英雄,根据版本的不同英雄的胜率也会有所不同,肯定有很多小伙伴儿好奇哪些英雄的胜率高,哪些英雄的胜率低? 现在我们就用条形图的形式来清晰的展示出 2022 王者荣耀英雄胜率排行。
  • 2022 王者荣耀的打野路英雄的最新胜率,如下所示:
位置热度英雄胜率
打野T0孙悟空49.40%
打野T1李信50.68%
打野T1亚瑟48.41%
打野T1典韦52.40%
打野T148.24%
打野T1李元芳49.53%
打野T148.76%
打野T2兰陵王47.08%
打野T2韩信50.94%
打野T2赵云50.34%
打野T2程咬金50.47%
打野T251.80%
打野T2李白50.49%
打野T2阿珂50.77%
打野T2钟无艳50.68%
打野T2宫本武藏49.17%
打野T3芈月50.17%
打野T3刘备52.85%
打野T3娜可露露48.39%
打野T3橘右京49.69%
打野T3云樱49.66%
打野T347.00%
打野T3司马懿52.20%
打野T3露娜49.88%
打野T3猪八戒48.41%
打野T3阿骨朵54.28%
打野T349.67%
打野T3达摩49.85%
打野T3杨戬52.44%
打野T3百里玄策51.75%
打野T3曹操48.91%
打野T3雅典娜56.35%
打野T3裴擒虎47.92%
打野T3云中君50.68%
打野T3盘古48.66%
  • 使用英雄名在条形图中绘制:
struct DataItem: Identifiable {
    let name: String
    let value: Double
    let id = UUID()
}

struct ContentView: View {
    
    let chartData: [DataItem] = [
        DataItem(name: "孙悟空", value: 49.40),
        DataItem(name: "李信", value: 50.68),
        DataItem(name: "亚瑟", value: 48.41),
        DataItem(name: "典韦", value: 52.40),
        DataItem(name: "凯", value: 48.24),
        DataItem(name: "李元芳", value: 49.53),
        DataItem(name: "澜", value: 48.76),
        DataItem(name: "兰陵王", value: 47.08),
        DataItem(name: "韩信", value: 50.94),
        DataItem(name: "赵云", value: 50.34),
        DataItem(name: "程咬金", value: 50.47),
        DataItem(name: "曜", value: 51.80),
        DataItem(name: "李白", value: 50.49),
        DataItem(name: "阿珂", value: 50.77),
        DataItem(name: "钟无艳", value: 50.68),
        DataItem(name: "宫本武藏", value: 49.17),
        DataItem(name: "芈月", value: 50.17),
        DataItem(name: "刘备", value: 52.85),
        DataItem(name: "娜可露露", value: 48.39),
        DataItem(name: "橘右京", value: 49.69),
        DataItem(name: "云樱", value: 49.66),
        DataItem(name: "镜", value: 47.00),
        DataItem(name: "司马懿", value: 52.20),
        DataItem(name: "露娜", value: 49.88),
        DataItem(name: "猪八戒", value: 48.41),
        DataItem(name: "阿骨朵", value: 54.28),
        DataItem(name: "暃", value: 49.67),
        DataItem(name: "达摩", value: 49.85),
        DataItem(name: "杨戬", value: 52.44),
        DataItem(name: "百里玄策", value: 51.75),
        DataItem(name: "曹操", value: 48.91),
        DataItem(name: "雅典娜", value: 56.35),
        DataItem(name: "裴擒虎", value: 47.92),
        DataItem(name: "云中君", value: 50.68),
        DataItem(name: "盘古", value: 48.66)
    ]

    var body: some View {
        
        VStack() {
            
            Text("2022王者荣耀英雄胜率排行榜")
                .font(.title)
            
            HistogramView(
                title: "打野路英雄胜率(%)", data: chartData)
            Spacer()
       }
       .padding()
    }
}
  • 对 HistogramView 做出了一些改动,条形图上的值使用叠加视图修改移到了条形图的顶部,这个值是偏移的,因此文本不会离条形图的顶部太近。数据名称的字体大小和字重也可以被设置,像部分英雄名称那样较长的文本,显示出条形图下面的文本将条形图推到了线外。文本视图的宽度被限制在条形图宽度的范围内,而且条形图的标签文本会被截断,条形图的文本视图也被限制在条形宽度的范围内,并且文本可以被隐藏起来:
struct HistogramView: View {
    
    var title: String
    var data: [DataItem]

    var body: some View {
        GeometryReader { gr in
            let headHeight = gr.size.height * 0.10
            VStack {
                HistogramHeaderView(title: title, height: headHeight)
                HistogramAreaView(data: data)
                    .frame(width: CGFloat(data.count) * 70)
            }
        }
    }
}
struct HistogramAreaView: View {
    
    var data: [DataItem]
    
    var body: some View {
        GeometryReader { gr in
            let fullBarHeight = gr.size.height * 0.90
            let maxValue = data.map { $0.value }.max()!
            
            ZStack {
                RoundedRectangle(cornerRadius: 5.0)
                    .fill(Color(#colorLiteral(red: 0.80, green: 0.90, blue: 0.80, alpha: 1)))
                VStack {
                    HStack(spacing:0) {
                        ForEach(data) { item in
                            ColumnView(
                                name: item.name,
                                value: item.value,
                                maxValue: maxValue,
                                fullBarHeight: Double(fullBarHeight))
                        }
                    }
                    .padding(4)
                }
            }
        }
    }
}
struct ColumnView: View {
    
    var name: String
    var value: Double
    var maxValue: Double
    var fullBarHeight: Double

    var body: some View {
        GeometryReader { gr in
            let barHeight = (Double(fullBarHeight) / maxValue) * value
            let textWidth = gr.size.width * 0.80
            VStack {
                Spacer()
                RoundedRectangle(cornerRadius: 6.0)
                    .fill(Color.red)
                    .frame(width: 60, height: CGFloat(barHeight), alignment: .trailing)
                    .overlay(
                        Text("\(value, specifier: "%.1f")")
                            .font(.footnote)
                            .foregroundColor(.white)
                            .fontWeight(.regular)
                            .frame(width: textWidth)
                            .offset(y:8)
                        ,
                        alignment: .top
                    )
                Text(name)
                    .font(.system(size: 11))
                    .fontWeight(.semibold)
                    .lineLimit(5)
                    .frame(width: textWidth, alignment: .center)
            }
            .padding(.horizontal, 0)
        }
    }
}
  • 效果如下:

在这里插入图片描述

  • 将视图被嵌入到 ScrollView 中,以便在设备旋转时滚动:
struct ContentView: View {

    let chartData: [DataItem] = [
        DataItem(name: "孙悟空", value: 49.40),
        DataItem(name: "李信", value: 50.68),
        DataItem(name: "亚瑟", value: 48.41),
        DataItem(name: "典韦", value: 52.40),
        DataItem(name: "凯", value: 48.24),
        DataItem(name: "李元芳", value: 49.53),
        DataItem(name: "澜", value: 48.76),
        DataItem(name: "兰陵王", value: 47.08),
        DataItem(name: "韩信", value: 50.94),
        DataItem(name: "赵云", value: 50.34),
        DataItem(name: "程咬金", value: 50.47),
        DataItem(name: "曜", value: 51.80),
        DataItem(name: "李白", value: 50.49),
        DataItem(name: "阿珂", value: 50.77),
        DataItem(name: "钟无艳", value: 50.68),
        DataItem(name: "宫本武藏", value: 49.17),
        DataItem(name: "芈月", value: 50.17),
        DataItem(name: "刘备", value: 52.85),
        DataItem(name: "娜可露露", value: 48.39),
        DataItem(name: "橘右京", value: 49.69),
        DataItem(name: "云樱", value: 49.66),
        DataItem(name: "镜", value: 47.00),
        DataItem(name: "司马懿", value: 52.20),
        DataItem(name: "露娜", value: 49.88),
        DataItem(name: "猪八戒", value: 48.41),
        DataItem(name: "阿骨朵", value: 54.28),
        DataItem(name: "暃", value: 49.67),
        DataItem(name: "达摩", value: 49.85),
        DataItem(name: "杨戬", value: 52.44),
        DataItem(name: "百里玄策", value: 51.75),
        DataItem(name: "曹操", value: 48.91),
        DataItem(name: "雅典娜", value: 56.35),
        DataItem(name: "裴擒虎", value: 47.92),
        DataItem(name: "云中君", value: 50.68),
        DataItem(name: "盘古", value: 48.66)
    ]


    var body: some View {
        
        ScrollView {
           VStack() {

               Text("2022王者荣耀英雄胜率排行榜")
                   .font(.title)
               Spacer().frame(height:20)
               
               Text("打野路英雄胜率(%)")
                    .font(.title3)
               HistogramView(
                   title: "打野路英雄胜率(%)", data: chartData)
               .frame(width: UIScreen.main.bounds.width - 60, height: 280, alignment: .center)
               Spacer().frame(height:20)
               Spacer()
           }
           .padding()
       }
    }
}
  • 效果如下:

请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/158072.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

给程序提速 | 多进程与多线程

目录 一、背景 1.1、前言 1.2、说明 二、线程与进程 2.1、什么是进程 2.2、什么是线程 2.3、进程与线程的关系 2.4、多进程与多线程的最佳使用条件 2.5、线程与进程的锁 2.6、特别注意 三、第一个线程、线程池 3.1、线程测试 3.2、执行结果 3.3、线程池测试 3.4…

华中科技大学计算机组成原理-计算机数据表示实验(全部通关)

计算机数据表示实验(HUST) 计算机数据表示目录 [建议收藏]计算机数据表示实验(HUST)第1关 汉字国标码转区位码实验第2关 汉字机内码获取实验第3关 偶校验编码设计第4关 偶校验解码电路设计第5关 16位海明编码电路设计第6关 16位海明解码电路设计第7关 海明编码流水传输实验第8关…

Leetcode:700. 二叉搜索树中的搜索(C++)

目录 问题描述: 实现代码与解析: 递归: 原理思路: 迭代: 原理思路: 问题描述: 给定二叉搜索树(BST)的根节点 root 和一个整数值 val。 你需要在 BST 中找到节点值…

CHAPTER 4 Docker仓库

docker仓库4.1 Docker Hub公共镜像市场4.2 第三方镜像市场4.2.1 daocloud4.2.2 阿里云4.3 *搭建本地私有仓库仓库(Repository)是集中存放镜像的地方,又分公共仓库和私有仓库。有时候容易把仓库与注册服务器(Registory)…

逆向-还原代码之continue (Interl 64)

// source code #include <stdio.h> int main() { int i; for (i 0; i < 10; i) { if (i 5) continue; printf("%d\n", i); } }

那年我双手离桌,被《剑指offer》打的还不了手(第八天)

跟着博主一起刷题 这里使用的是题库&#xff1a; https://leetcode.cn/problem-list/xb9nqhhg/?page1 目录剑指 Offer 55 - II. 平衡二叉树剑指 Offer 56 - I. 数组中数字出现的次数剑指 Offer 56 - II. 数组中数字出现的次数 II剑指 Offer 55 - II. 平衡二叉树 剑指 Offer 55…

缓存一致性问题解决方案(超全超易懂)

文章目录1、缓存模型和思路2、缓存更新策略3、两种解决方案3.1、先删除缓存&#xff0c;再更新数据库3.1.1延时双删&#xff08;解决先删除缓存&#xff0c;再更新数据库产生的缓存不一致问题&#xff09;1、什么是延时双删2、为什么要进行延迟双删&#xff1f;3、如何实现延迟…

【 uniapp - 黑马优购 | 购物车页面(2)】如何实现收货地址区域功能、常见问题解决方案

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大二在校生&#xff0c;讨厌编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;小新爱学习. &#x1f43c;个人WeChat&#xff1a;见文末 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc;…

JVM—类加载与字节码技术

目录一、类文件结构1、魔术2、版本3、常量池二、字节码指令1、javap工具2、图解方法执行流程3、通过字节码指令来分析问题4、构造方法5、方法调用6、多态原理——HSDB7、异常处理四、类加载阶段五、类加载器六、运行期优化一、类文件结构 以一个简单的HelloWord.java程序为例 …

聊聊VMware的三种网络模式

聊聊VMware的三种网络模式1.Bridged&#xff08;桥接模式&#xff09;2.NAT&#xff08;地址转换模式&#xff09;3.Host-Only&#xff08;仅主机模式&#xff09;VMware有三种虚拟网络工作方式&#xff0c;即&#xff1a; Briged&#xff08;桥接模式&#xff09;NAT&#xf…

实现内核线程

文章目录前言前置知识实验操作实验一实验二实验三前言 博客记录《操作系统真象还原》第九章实验的操作~ 实验环境&#xff1a;ubuntu18.04VMware &#xff0c; Bochs下载安装 实验内容&#xff1a; 在内核空间实现线程。实现双向链表。实现多线程在调度器的调度下轮流执行。…

【Nginx】Nginx配置实例-反向代理

1. 反向代理实例一 实现过程 1. 启动一个 tomcat&#xff0c;浏览器地址栏输入 127.0.0.1:8080&#xff0c;出现如下界面2. 通过修改本地 host 文件&#xff0c;将 www.123.com 映射到 127.0.0.13. 在 nginx.conf 配置文件中增加如下配置 2. 反向代理实例二 实现过程 1.准备两…

唤醒手腕 Go 语言学习笔记常见包 函数用法(更新中)

1. fmt 包 fmt 包的介绍&#xff1a;fmt format&#xff0c;是一种格式化输出函数汇总包&#xff0c;用于格式化输出。 Println、Print、Printf fmt.Print 原样输出 Print formats using the default formats for its operands and writes to standard output. start : ti…

Unity中的异步编程【5】——在Unity中使用 C#原生的异步(Task,await,async) - System.Threading.Tasks

一、UniTask&#xff08;Cysharp.Threading.Tasks&#xff09;和Task&#xff08;System.Threading.Tasks&#xff09;的区别 1、System.Threading.Tasks中的Task是.Net原生的异步和多线程包。2、UniTask(Cysharp.Threading.Tasks)是仿照.Net原生的Task&#xff0c;await&…

【C++】继承

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《吃透西嘎嘎》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;继承的概…

通俗易懂的java设计模式(6)-代理模式

1.什么是代理模式 为某个对象提供一个代理对象&#xff0c;通过这个代理对象&#xff0c;可以控制对原对象的访问。 通俗的解释&#xff1a;我们电脑桌面上的一个个快接方式&#xff0c;当我们点击这个快捷方式的时候&#xff0c;我们就间接地访问到了这个程序。 2.静态代理…

附录B:Standard Delay Format(SDF)(下)

文章目录B.4 映射实例(Mapping Examples)传播延迟(Propagation Delay)输入建立时间(Input Setup Time)输入保持时间(Input Hold Time)输入建立和保持时间(Input Setup and Hold Time)输入恢复时间(Input Recovery Time)输入撤销时间(Input Removal Time)周期(Period)脉宽(Pulse…

C#自动化物流仓库WMS系统源码

分享一套C#自动化仓库WMS管理系统源码 MF00426 需要源码学习可私信我获取。 WMS作为现代物流系统中的主要组成部分&#xff0c;是一种多层次存放货物的高架仓库系统&#xff0c;由自动控制与管理系统、货架、巷道式堆垛机、出入库输送机等设 备构成&#xff0c;能按指令自动完…

PHP多进程(二)

上篇文章我们说到父进程应该回收子进程结束之后产生的数据,这样才会不浪费系统资源。 一个程序启动之后&#xff0c;变成了一个进程&#xff0c;进程在以下情况会退出 1&#xff09;运行到最后一行语句 2) 运行时遇到return 时 3) 运行时遇到exit()函数的时候 4) 程序异常的时…

【Dash搭建可视化网站】项目13:销售数据可视化大屏制作步骤详解

销售数据可视化大屏制作步骤详解1 项目效果图2 项目架构3 文件介绍和功能完善3.1 assets文件夹介绍3.2 app.py和index.py文件完善3.3 header.py文件完善3.4 api.py文件和api.ipynb文件完善3.4.1 需求数据获取3.4.2 header.py文件中数据变更3.5 middle.py文件完善3.5.1 中间第一…