任务
我正在使用SwiftUI创建条形图,我正在寻找定位平均指标的最佳方法,即水平黄线,如下图所示:
目标
我需要能够相对于图表的条形图(也包括矩形)放置黄线(高度为1点的矩形)。为简单起见,让我们想象一下最高可能的柱线是100点,平均值是75。我的目标是在柱线底部上方的75点处绘制黄线。
问题
在我的示例中,我使用ZStack将线放置在图表顶部。我可以使用GeometryReader垂直放置线条,但是我不知道如何相对于条形底部放置线条。
这是我的游乐场代码:
import SwiftUI
import PlaygroundSupport
struct BarChart: View {
var measurements : [Int]
var pastColor: Color
var todayColor: Color
var futureColor: Color
let labelsColor = Color(UIColor(white: 0.5, alpha: 1.0))
func monthAbbreviationFromInt(_ day: Int) -> String {
let da = Calendar.current.shortWeekdaySymbols
return da[day]
}
var body: some View {
ZStack {
VStack {
HStack {
Rectangle()
.fill(Color.yellow)
.frame(width: 12, height: 2, alignment: .center)
Text("Desired level")
.font(.custom("Helvetica", size: 10.0))
.foregroundColor(labelsColor)
}
.padding(.bottom , 50.0)
HStack {
ForEach(0..<7) { day in
VStack {
Spacer()
Text(String(self.measurements[day]))
.frame(width: CGFloat(40), height: CGFloat(20))
.font(.footnote)
.lineLimit(1)
.minimumScaleFactor(0.5)
.foregroundColor(self.labelsColor)
Rectangle()
.fill(day > 0 ? self.futureColor : self.pastColor)
.frame(width: 20, height: CGFloat(self.measurements[day]*2))
.transition(.slide)
.cornerRadius(3.0)
Text("\(self.monthAbbreviationFromInt(day))\n\(day)")
.multilineTextAlignment(.center)
.font(.footnote)
.frame(height: 20)
.lineLimit(2)
.minimumScaleFactor(0.2)
.foregroundColor(self.labelsColor)
}
.frame(height: 200, alignment: .bottom )
}
}
}
.padding()
VStack {
GeometryReader { geometry in
//Yellow line
Rectangle()
.path(in: CGRect(x: 0,
y: 350, //This is now a random value, but I should detect and use the bottom coordinate of the bars to place the line at exactly 75 points above that coordinate
width: geometry.size.width,
height: 1))
.fill(Color.yellow)
}
}
}
}
}
如何获取条形底部的相对位置,以便正确放置线条?
您会建议另一种方法吗?
提前谢谢
答案 0 :(得分:1)
示例
import SwiftUI
struct Data: Identifiable {
let id = UUID()
let value: CGFloat
let color: Color
}
struct ContentView: View {
let data = [Data(value: 35, color: .red),
Data(value: 100, color: .green),
Data(value: 70, color: .yellow),
Data(value: 25, color: .blue),
Data(value: 55, color: .orange)]
var maxValue: CGFloat {
data.map { (element) -> CGFloat in
element.value
}.max() ?? 1.0
}
var average: CGFloat {
guard !data.isEmpty else { return 0 }
let sum = data.reduce(into: CGFloat.zero) { (res, data) in
res += data.value
}
return sum / CGFloat(data.count)
}
var body: some View {
HStack {
ForEach(data) { (bar) in
Bar(t: bar.value / self.maxValue, color: bar.color, width: 20)
}
}
.background(Color.pink.opacity(0.1))
.overlay(
GeometryReader { proxy in
Color.primary.frame(height: 1).position(x: proxy.size.width / 2, y: proxy.size.height * ( 1 - self.average / self.maxValue))
}
).padding(.vertical, 100).padding(.horizontal, 20)
}
}
struct Bar: View {
let t: CGFloat
let color: Color
let width: CGFloat
var body: some View {
GeometryReader { proxy in
Path { (path) in
path.move(to: .init(x: proxy.size.width / 2, y: proxy.size.height))
path.addLine(to: .init(x: proxy.size.width / 2, y: 0))
}
.trim(from: 0, to: self.t)
.stroke(lineWidth: self.width)
.foregroundColor(self.color)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
或略微采用以显示标签