SwiftUI中的水平条形图

2022-11-07 16:28:38 浏览数 (4)

SwiftUI中的水平条形图

水平条形图以矩形条的形式呈现数据类别,其宽度与它们所代表的数值成正比。本文展示了如何在垂直条形图的基础上创建一个水平柱状图。

水平条形图不是简单的垂直条形图的旋转。在Numbers 等应用程序中,水平条形图被定义为独立的图表类型,而不是垂直条形图。除了条形差异外,x轴和y轴的格式也需要不同。

相关文章
  1. How to create a Bar Chart in SwiftUI
  2. Add Axes to a Bar Chart in SwiftUI
  3. Hide Bar Chart Axes in SwiftUI
  4. Bar Chart with multiple data sets in SwiftUI
  5. SwiftUI 中的水平条形图

将条形图转换为水平

水平条形图不仅仅是在垂直条形图上的配置,有一些元素是可以重复使用的。对于垂直条形图组件和水平条形图组件来说,重复使用一些结构和SwiftUI视图并不简单。标题和关键区域可以原样重用。创建BarChartView的副本,并将其名称改为BarChartHView。它控制了图表的布局,其中的三个视图被改为YaxisHViewChartAreaHViewXaxisHView,它们最初只是垂直条形图中使用的视图的副本。maxTickHeight被改为maxTickWidth,因为它现在取决于可用的水平空间。

代码语言:javascript复制
struct BarChartHView: View {
    var title: String
    var chartData: BarChartData
    var isShowingYAxis = true
    var isShowingXAxis = true
    var isShowingHeading = true
    var isShowingKey = true

    var body: some View {
        let data = chartData.data
        GeometryReader { gr in
            let axisWidth = gr.size.width * (isShowingYAxis ? 0.15 : 0.0)
            let axisHeight = gr.size.height * (isShowingXAxis ? 0.1 : 0.0)
            let keyHeight = gr.size.height * (isShowingKey ? 0.1 : 0.0)
            let headHeight = gr.size.height * (isShowingHeading ? 0.14 : 0.0)
            let fullChartHeight = gr.size.height - axisHeight - headHeight - keyHeight
            let fullChartWidth = gr.size.width - axisWidth
            
            let maxValue = data.flatMap { $0.values }.max()!
            let tickMarks = AxisParameters.getTicks(top: Int(maxValue))
            let maxTickWidth = fullChartWidth * 0.95
            let scaleFactor = maxTickWidth / CGFloat(tickMarks[tickMarks.count-1])
            
            VStack(spacing:0) {
                if isShowingHeading {
                    ChartHeaderView(title: title)
                        .frame(height: headHeight)
                }
                ZStack {
                    Rectangle()
                        .fill(Color(#colorLiteral(red: 0.8906477705, green: 0.9005050659, blue: 0.8208766097, alpha: 1)))
                    
                    VStack(spacing:0) {
                        if isShowingKey {
                            KeyView(keys: chartData.keys)
                                .frame(height: keyHeight)
                        }
                        
                        HStack(spacing:0) {
                            if isShowingYAxis {
                                YaxisHView(ticks: tickMarks, scaleFactor: Double(scaleFactor))
                                    .frame(width:axisWidth, height: fullChartHeight)
                            }
                            ChartAreaHView(data: data, scaleFactor: Double(scaleFactor))
                                .frame(height: fullChartHeight)
                        }
                        HStack(spacing:0) {
                            Rectangle()
                                .fill(Color.clear)
                                .frame(width:axisWidth, height:axisHeight)
                            if isShowingXAxis {
                                XaxisHView(data: data)
                                    .frame(height:axisHeight)
                            }
                        }
                    }
                }
            }
        }
    }
}

ChartAreaHViewChartAreaView几乎相同,只是Bars被放置在一个垂直的堆栈中,而不是水平的堆栈。

代码语言:javascript复制
struct ChartAreaHView: View {
    var data: [DataItem]
    var scaleFactor: Double
    
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 5.0)
                .fill(Color(#colorLiteral(red: 0.8906477705, green: 0.9005050659, blue: 0.8208766097, alpha: 1)))
            
            VStack {
                VStack(spacing:0) {
                    ForEach(data) { item in
                        BarHView(
                            name: item.name,
                            values: item.values,
                            scaleFactor: scaleFactor)
                    }
                }
            }
        }
    }
}

BarHView是由原来的BarView改变的,以水平方式布局条形。矩形条的宽度与数据的值成正比。

代码语言:javascript复制
struct BarHView: View {
    var name: String
    var values: [Double]
    var scaleFactor: Double
    
    var body: some View {
        GeometryReader { gr in
            let padHeight = gr.size.height * 0.07
            VStack(spacing:0) {
                Spacer()
                    .frame(height:padHeight)
                
                ForEach(values.indices) { i in
                    let barSize = values[i] * scaleFactor
                    
                    ZStack {
                        HStack(spacing:0) {
                            Rectangle()
                                .fill(ChartColors.BarColor(i))
                                .frame(width: min(5.0, CGFloat(barSize)), alignment: .trailing)
                            Spacer()
                        }
                        
                        HStack(spacing:0) {
                            RoundedRectangle(cornerRadius:5.0)
                                .fill(ChartColors.BarColor(i))
                                .frame(width: CGFloat(barSize), alignment: .trailing)
                                .overlay(
                                    Text("(values[i], specifier: "%.0F")")
                                        .font(.footnote)
                                        .foregroundColor(.white)
                                        .fontWeight(.bold)
                                        .offset(x:-10, y:0)
                                    ,
                                    alignment: .trailing
                                )
                            Spacer()
                        }
                    }
                }

                Spacer()
                    .frame(height:padHeight)
            }
        }
    }
}

将条形图改为水平布局

更新Y轴

我们创建了一个YaxisHView视图,用于在水平条形图上显示Y轴和条形图中的数据类别。Y轴标签的Swift代码与垂直条形图的X轴代码相似,宽度设置与高度设置互换。两种图表类型的y轴线的代码都是一样的。

代码语言:javascript复制
struct YaxisHView: View {
    var data: [DataItem]
    
    var body: some View {
        GeometryReader { gr in
            let labelHeight = (gr.size.height * 0.9) / CGFloat(data.count)
            let padHeight = gr.size.height * 0.05 / CGFloat(data.count)
            
            ZStack {
                Rectangle()
                    .fill(Color(#colorLiteral(red: 0.8906477705, green: 0.9005050659, blue: 0.8208766097, alpha: 1)))
                
                // y-axis line
                Rectangle()
                    .fill(Color.black)
                    .frame(width:1.5)
                    .offset(x: (gr.size.width/2.0)-1, y: 1)
                
                VStack(spacing:0) {
                    ForEach(data) { item in
                        Text(item.name)
                            .font(.footnote)
                            .frame(height: labelHeight)
                    }
                    .padding(.vertical, padHeight)
                }
            }
        }
    }
}

y轴显示水平条形图上的数据类别

更新X轴

同样,创建了一个XaxisHView视图来显示水平条形图的X轴,并使用与垂直条形图的Y轴类似的代码来布置刻度线和刻度值。

代码语言:javascript复制
struct XaxisHView: View {
    var ticks: [Int]
    var scaleFactor: Double
    
    var body: some View {
        GeometryReader { gr in
            let fullChartWidth = gr.size.width
            ZStack {
                // x-axis line
                Rectangle()
                    .fill(Color.black)
                    .frame(height: 1.5)
                    .offset(x: 0, y: -(gr.size.height/2.0))
                
                // Tick marks
                ForEach(ticks, id:.self) { t in
                    VStack {
                        Rectangle()
                            .frame(width: 1, height: 10)
                        Text("(t)")
                            .font(.footnote)
                            .rotationEffect(Angle(degrees: -45))
                        Spacer()
                    }
                    .offset(x:  (CGFloat(t) * CGFloat(scaleFactor)) - (fullChartWidth/2.0) - 1)
                }
            }
        }
    }
}

X轴显示水平条形图上的比例和数值

水平和垂直条形图

一个iPad模拟器被用来比较垂直和水平条形图的使用,以显示2018年五岁以下儿童死亡率最高的国家。柱状图的多数据功能被用来比较男孩和女孩的死亡率。

2018年最高的5岁以下儿童死亡率显示在垂直和水平条形图中

水平条形图重用了垂直条形图的很多代码,所以显示或隐藏标题、键和轴的效果是有效的。在水平条形图中,显示条形图上的数值并隐藏X轴可以使图表更简洁。

显示和隐藏水平条形图上的元素

结论

创建水平条形图的SwiftUI代码与创建垂直条形图的代码不同。在创建垂直条形图时学到的技术可以重复使用,但最好将水平条形图视为与垂直条形图不同的图表。当我们深入到轴等组件时,可以看到两个图表中的轴线都是一样的,但是它们的标签和定位在x和y之间是换位的。这可能是将这些组件分解成更小的SwiftUI视图并通过组合来重用的原因。

gist file for Horizontal bar chart SwiftUI code

Horizontal Bar Chart in SwiftUI https://swdevnotes.com/swift/2021/horizontal-bar-chart-in-swiftui/

1 人点赞