# 细品Swift - 一目了然的图表Swift Charts(2)

细品Swift - 一目了然的图表Swift Charts(2)

Bar Chart 条形图

Bar Chart的维基百科解释如下:
条形图(英语:bar chart),或 条图(英语:bar graph),台湾常称为 长条图,又称为 柱状图、棒形图,是一种以长方形的长度为变量的统计图表。长条图用来比较两个或以上的价值(不同时间或者不同条件),只有一个变量,通常利用于较小的数据集分析。长条图亦可横向排列,或用多维方式表达。绘制长条图时,长条柱或柱组中线须对齐项目刻度。相较之下,折线图则是将数据代表之点对齐项目刻度。在数字大且接近时,两者皆可使用波浪形省略符号,以扩大表现数据间的差距,增强理解和清晰度。

长条图通常适用于较小的数据集分析,其每个数据标记通常表示一个分类的数量或者几个分类的数量总和。

基本BarChart

在一个图表中可以对同一数据使用不同的标记来描绘,如BarMark是条状图样式的标记,PointMark是点状样式的标记,LineMark是折线样式的标记。

BarChart可以从不同的维度来对数据进行可视化,比如本图表按照食物的名字或者食物的种类,分类来统计对应的食物总重量。

BarChart会将x值相同的数据堆叠在一个条形图上,通过修饰语句.foregroundStyle可以把同一条形上的不同数据按照指定的value分开。 这里指定的value会被当作图例使用。同样,可以指定图例的颜色,这里是通过对Chart的修饰语句.chartForegroundStyleScale来指定图例对应的颜色。

    var basicBarChart: some View {
        VStack {
            VStack {
                Divider()
                Text("用$0直接表示单个数据").font(.footnote)
                // Chart使用类似于List的方式,从foods中直接拆解处单个数据并绘制
                Chart(foods) {
                    BarMark(x: .value("name", $0.name), y: .value("weight", $0.weight))
                }
            }

            VStack {
                Divider()
                Text("数据标记的选择决定了图表的类别").font(.footnote)
                // 数据标记(mark)可以选择多种表示方式(点PointMark、条形BarMark、折线LineMark、矩形RectangleMark等)
                // 本图表对每个数据选择了两种标记方式: BarMark和LineMark
                Chart(foods) { food in
                    BarMark(x: .value("name", food.name), y: .value("weight", food.weight))
                    LineMark(x: .value("name", food.name), y: .value("weight", food.weight))
                        .foregroundStyle(.red)

                }
            }

            VStack {
                Divider()
                Text("从不同维度对数据进行观察的BarChart").font(.footnote)
                // 可以选择不同的数据属性作为X轴,以从不同的维度对数据进行观察
                // 如多个数据具有相同的x轴维度值,Chart会把他们直接合并,显示所有对应的y轴值之和。
                // 本例中把所有的数据按照type维度,分成4个条状图。
                Chart(foods) { food in
                    BarMark(x: .value("type", food.type) ,
                            y: .value("weight", food.weight))
                }
            }


            VStack {
                Divider()
                Text("合并后在每个Bar中显示原始数据所占部分").font(.footnote)
                // 使用修饰指令.foregroundStyle(by: PlottableValue)
                // 可以看出,图表中多了legend(图例)部分,指定每种颜色对应的数据,但可以看出
                Chart(foods) { food in
                    BarMark(x: .value("type", food.type) ,
                            y: .value("weight", food.weight))
                    // 在每个Bar上为每个color属性的值赋不同的颜色
                    // 同一bar上同颜色的数据再次合并, 如Avocado和GreenGrape的食物类型相同,所有都在Fruit Bar上,进一步,他们的颜色都是Green,所以以同样的颜色显示在Bar上。
                    .foregroundStyle(by: .value("color", food.color))
                }
            }

            VStack {
                Divider()
                Text("指定显示每种图例对应的颜色").font(.footnote)
                // 用chartForgroundStyleScale来指定每种图例对应的颜色

                Chart(foods) { food in
                    BarMark(x: .value("type", food.type) ,
                            y: .value("weight", food.weight))
                    // 在每个Bar上为每个color属性的值赋不同的颜色
                    // 同一bar上同颜色的数据再次合并, 如Avocado和GreenGrape的食物类型相同,所有都在Fruit Bar上,进一步,他们的颜色都是Green,所以以同样的颜色显示在Bar上。
                    .foregroundStyle(by: .value("color", food.color.description))
                }
                .chartForegroundStyleScale([
                    "yellow-color": .yellow,
                    "green-color": .green,
                    "red-color": .red,
                    "white-color": .gray.opacity(0.5), // 用半透明的gray来标识白色
                    "pink-color": .pink,
                    "gray-color": .gray
                ])
            }


            VStack {
                Divider()
                Text("从颜色维度来分类并显示每条Bar中食物种类分布").font(.footnote)

                Chart(foods) { food in
                    BarMark(x: .value("color", food.color) ,
                            y: .value("weight", food.weight))
                    // 在每个Bar上为每个color属性的值赋不同的颜色
                    // 同一bar上同颜色的数据再次合并, 如Avocado和GreenGrape的食物类型相同,所有都在Fruit Bar上,进一步,他们的颜色都是Green,所以以同样的颜色显示在Bar上。
                    .foregroundStyle(by: .value("type", food.type))
                }
                // 按照食物种类指定颜色
                .chartForegroundStyleScale([
                    "Meat": .pink,
                    "Vegetable": .green,
                    "Grain": .brown,
                    "Fruit": .orange
                ])
            }
            
            
        }
    }
image.png

一维BarChart和区间BarChart

BarChart可以用于展示一维数据图以及区间图。

如果需要在一维上展示数据,只需要输入x或y中的一个数据,以及图例基于的数据。

区间图需要提供在一个维度上的数据的上下区间值,已经另外一个维度的数据属性。

    var advancedBarChart: some View {
        VStack {
            VStack {
                Divider()
                Text("一维条形图").font(.footnote)
                // 按照种类排序后,同种类的食物连续显示
                Chart(foods.sorted(by: {$0.type < $1.type})) {
                    BarMark(x: .value("weight", $0.weight))
                        .foregroundStyle(by: .value("type", $0.type))
                }
                
            }
            
            VStack {
                Divider()
                Text("区间条形图").font(.footnote)
                // 用条状图来标识一个区间,特别是时间区间,如甘特图。
                Chart(foods) { food in
                    
                    BarMark(x: .value("name", food.name),
                            yStart: .value("minGi", food.minGiValue),
                            yEnd: .value("maxGi", food.maxGiValue)
                    )
                }
                
                
            }
            
            
            VStack {
                Divider()
                Text("横向区间条形图").font(.footnote)
                Chart(foods) { food in
                    
                    BarMark(xStart: .value("minGi", food.minGiValue), xEnd: .value("maxGi", food.maxGiValue),
                            y: .value("color", food.color))
                    .foregroundStyle(by: .value("type", food.type))
                }
            }
        }
    }

image.png

完整演示代码


完整的演示代码如下:


struct Food: Identifiable {
    let id = UUID()
    let name: String
    let maxGiValue: Int
    let minGiValue: Int
    let maxLength: Double
    let minLength: Double
    let color: String
    let type: String
    let weight: Double
}

let foods: [Food] = [
    Food(name: "Cabbage", maxGiValue: 13, minGiValue: 8, maxLength: 23, minLength: 10, color: "green-color", type: "Vegetable", weight: 2),
    Food(name: "Beef", maxGiValue: 54, minGiValue: 18, maxLength: 53, minLength: 20, color: "pink-color", type: "Meat", weight: 1.8),
    Food(name: "Rice", maxGiValue: 93, minGiValue: 78, maxLength: 5, minLength: 2, color: "gray-color", type: "Grain", weight: 8),
    Food(name: "Tomato", maxGiValue: 33, minGiValue: 21, maxLength: 13, minLength: 5, color: "red-color", type: "Vegetable", weight: 5),
    Food(name: "Pork", maxGiValue: 63, minGiValue: 38, maxLength: 63, minLength: 10, color: "pink-color", type: "Meat", weight: 5.3),
    Food(name: "Fish", maxGiValue: 49, minGiValue: 28, maxLength: 53, minLength: 5, color: "white-color", type: "Meat", weight: 1.5),
    Food(name: "Flour", maxGiValue: 83, minGiValue: 3, maxLength: 0.1, minLength: 10, color: "white-color", type: "Grain", weight: 6.5),
    Food(name: "Banana", maxGiValue: 93, minGiValue: 78, maxLength: 33, minLength: 10, color: "yellow-color", type: "Fruit", weight: 3.8),
    Food(name: "Avocado", maxGiValue: 33, minGiValue: 18, maxLength: 25, minLength: 8, color: "green-color", type: "Fruit", weight: 8),
    Food(name: "GreenGrape", maxGiValue: 99, minGiValue: 68, maxLength: 23, minLength: 10, color: "green-color", type: "Fruit", weight: 8)
    
]


struct AllAboutBarChartTest: View {
    var body: some View {
       basicBarChart
       advancedBarChart
    }
    
    
    var basicBarChart: some View {
        VStack {
            VStack {
                Divider()
                Text("用$0直接表示单个数据").font(.footnote)
                // Chart使用类似于List的方式,从foods中直接拆解处单个数据并绘制
                Chart(foods) {
                    BarMark(x: .value("name", $0.name), y: .value("weight", $0.weight))
                }
            }

            VStack {
                Divider()
                Text("数据标记的选择决定了图表的类别").font(.footnote)
                // 数据标记(mark)可以选择多种表示方式(点PointMark、条形BarMark、折线LineMark、矩形RectangleMark等)
                // 本图表对每个数据选择了两种标记方式: BarMark和LineMark
                Chart(foods) { food in
                    BarMark(x: .value("name", food.name), y: .value("weight", food.weight))
                    LineMark(x: .value("name", food.name), y: .value("weight", food.weight))
                        .foregroundStyle(.red)

                }
            }

            VStack {
                Divider()
                Text("从不同维度对数据进行观察的BarChart").font(.footnote)
                // 可以选择不同的数据属性作为X轴,以从不同的维度对数据进行观察
                // 如多个数据具有相同的x轴维度值,Chart会把他们直接合并,显示所有对应的y轴值之和。
                // 本例中把所有的数据按照type维度,分成4个条状图。
                Chart(foods) { food in
                    BarMark(x: .value("type", food.type) ,
                            y: .value("weight", food.weight))
                }
            }


            VStack {
                Divider()
                Text("合并后在每个Bar中显示原始数据所占部分").font(.footnote)
                // 使用修饰指令.foregroundStyle(by: PlottableValue)
                // 可以看出,图表中多了legend(图例)部分,指定每种颜色对应的数据,但可以看出
                Chart(foods) { food in
                    BarMark(x: .value("type", food.type) ,
                            y: .value("weight", food.weight))
                    // 在每个Bar上为每个color属性的值赋不同的颜色
                    // 同一bar上同颜色的数据再次合并, 如Avocado和GreenGrape的食物类型相同,所有都在Fruit Bar上,进一步,他们的颜色都是Green,所以以同样的颜色显示在Bar上。
                    .foregroundStyle(by: .value("color", food.color))
                }
            }

            VStack {
                Divider()
                Text("指定显示每种图例对应的颜色").font(.footnote)
                // 用chartForgroundStyleScale来指定每种图例对应的颜色

                Chart(foods) { food in
                    BarMark(x: .value("type", food.type) ,
                            y: .value("weight", food.weight))
                    // 在每个Bar上为每个color属性的值赋不同的颜色
                    // 同一bar上同颜色的数据再次合并, 如Avocado和GreenGrape的食物类型相同,所有都在Fruit Bar上,进一步,他们的颜色都是Green,所以以同样的颜色显示在Bar上。
                    .foregroundStyle(by: .value("color", food.color.description))
                }
                .chartForegroundStyleScale([
                    "yellow-color": .yellow,
                    "green-color": .green,
                    "red-color": .red,
                    "white-color": .gray.opacity(0.5), // 用半透明的gray来标识白色
                    "pink-color": .pink,
                    "gray-color": .gray
                ])
            }


            VStack {
                Divider()
                Text("从颜色维度来分类并显示每条Bar中食物种类分布").font(.footnote)

                Chart(foods) { food in
                    BarMark(x: .value("color", food.color) ,
                            y: .value("weight", food.weight))
                    // 在每个Bar上为每个color属性的值赋不同的颜色
                    // 同一bar上同颜色的数据再次合并, 如Avocado和GreenGrape的食物类型相同,所有都在Fruit Bar上,进一步,他们的颜色都是Green,所以以同样的颜色显示在Bar上。
                    .foregroundStyle(by: .value("type", food.type))
                }
                // 按照食物种类指定颜色
                .chartForegroundStyleScale([
                    "Meat": .pink,
                    "Vegetable": .green,
                    "Grain": .brown,
                    "Fruit": .orange
                ])
            }
            
            
        }
    }
   
    var advancedBarChart: some View {
        VStack {
            VStack {
                Divider()
                Text("一维条形图").font(.footnote)
                // 按照种类排序后,同种类的食物连续显示
                Chart(foods.sorted(by: {$0.type < $1.type})) {
                    BarMark(x: .value("weight", $0.weight))
                        .foregroundStyle(by: .value("type", $0.type))
                }
                
            }
            
            VStack {
                Divider()
                Text("区间条形图").font(.footnote)
                // 用条状图来标识一个区间,特别是时间区间,如甘特图。
                Chart(foods) { food in
                    
                    BarMark(x: .value("name", food.name),
                            yStart: .value("minGi", food.minGiValue),
                            yEnd: .value("maxGi", food.maxGiValue)
                    )
                }
                
                
            }
            
            
            VStack {
                Divider()
                Text("横向区间条形图").font(.footnote)
                Chart(foods) { food in
                    
                    BarMark(xStart: .value("minGi", food.minGiValue), xEnd: .value("maxGi", food.maxGiValue),
                            y: .value("color", food.color))
                    .foregroundStyle(by: .value("type", food.type))
                }
            }
        }
    }
}

总结如下:

  • BarChart中文名称为条状图,柱状图等,是图形化少量数据的有效方法。
  • BarChart可以在多种维度上展示数据。
  • BarChart可以用图例更详细的演示堆叠在一个Bar上的多种数据。
  • BarChart可以以一维的方式展示数据。
  • BarChart可以展示数据区间。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容