在 SwiftUI Chart 中显示 PointMark 的问题是,当我使用 PointMark 显示多个点图标时,并且 Y 轴代表 Date 类型数据,图标不完全可见。最上面的点图标被切掉了,一半不显示了。
为了解决这个问题,我尝试通过使用 .chartYAxisRange(min: adjustmentMinValue, max: adjustmentMaxValue) 计算代码中的最小值和最大值来向 Y 轴添加范围,但它不断抛出错误:“编译器是无法在合理的时间内对该表达式进行类型检查;尝试将表达式分解为不同的子表达式,我注释掉了该部分代码。
如果有人能帮忙看看这个问题,我将不胜感激。
import Foundation
import SwiftUI
import Charts
func convertFullDateToHMDate(_ fullDateString: String) -> Date {
let fullFormatter = DateFormatter()
fullFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let fullDate = fullFormatter.date(from: fullDateString)!
let calendar = Calendar.current
let components = calendar.dateComponents([.hour, .minute ,.second], from: fullDate)
return calendar.date(from: components)!
}
func formatDateToHMS(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss"
return formatter.string(from: date)
}
struct TemperatureData: Identifiable ,Codable {
var id = UUID()
var day: String
var detailDay: String // detail time
var temperature: Double = 0.0
}
struct PointChartView: View {
var lineChartViewData: LineChartViewData
@State private var selectedWeek: Int = 0
var body: some View {
VStack(alignment: .leading, spacing: 16) {
// let (minDate, maxDate) = lineChartViewData.getMinMaxDate()
TabView(selection: $selectedWeek) {
ForEach(0..<lineChartViewData.getWeeklyData().count, id: \.self) { weekIndex in
VStack{
Chart (lineChartViewData.getWeeklyData()[weekIndex]) { item in
PointMark(
x: .value("day", item.day),
y: .value("time", convertFullDateToHMDate(item.detailDay))
)
.symbol {
Image(systemName: "square.fill")
.resizable()
.frame(width: 15, height: 15)
.foregroundStyle(Color.green)
}
}
.chartYAxis {
AxisMarks(position: .leading) { value in
if let dateValue = value.as(Date.self) {
AxisTick()
AxisGridLine()
AxisValueLabel(formatDateToHMS(dateValue))
} else {
AxisValueLabel("\(value.as(Double.self) ?? 0.0)")
}
}/* .chartYAxisRange(min: minDate, max: maxDate)*/
}
}.tag(weekIndex)
}
}.frame(height: 150).tabViewStyle(PageTabViewStyle())
.onAppear {
selectedWeek = lineChartViewData.getWeeklyData().count - 1
}
}
.padding(16)
.background(
ZStack {
if lineChartViewData.isArtificial {
Rectangle()
.fill(Color.gray.opacity(0.5))
.cornerRadius(30)
.background(.ultraThinMaterial)
} else {
Rectangle()
.fill(Color.gray.opacity(0))
.background(.ultraThinMaterial) // 直接设置背景材质
.cornerRadius(30)
}
}
)
}
}
struct LineChartViewData : Identifiable ,Codable {
var id = UUID()
var imageName: String
var isArtificial: Bool
var dataArray: [TemperatureData] = []
func getWeeklyData()-> [[TemperatureData]] {
var twoDimensionalArray: [[TemperatureData]] = []
var subarray: [TemperatureData] = []
for element in self.dataArray {
subarray.append(element)
if subarray.count == 7 {
twoDimensionalArray.append(subarray)
subarray = []
}
}
if(!subarray.isEmpty) {
twoDimensionalArray.append(subarray)
}
return twoDimensionalArray
}
func getMinMaxDate() -> (min: Date, max: Date) {
let values = dataArray.map { convertFullDateToHMDate($0.detailDay) }
let minValue = values.min() ?? Date()
let maxValue = values.max() ?? Date()
let padding: TimeInterval = 60 * 10
let adjustedMinValue = minValue.addingTimeInterval(-padding)
let adjustedMaxValue = maxValue.addingTimeInterval(padding)
return (adjustedMinValue, adjustedMaxValue)
}
}
struct PointChartView_Previews: PreviewProvider {
static var previews: some View {
let temperatureData: [TemperatureData] = [
TemperatureData(day: "9-9", detailDay: "2024-12-01 10:31:00"),
TemperatureData(day: "9-9", detailDay: "2024-12-01 10:32:00"),
TemperatureData(day: "9-9", detailDay: "2024-12-01 10:33:00"),
]
PointChartView(lineChartViewData:LineChartViewData( imageName: "IconPoop" ,isArtificial:false,dataArray: temperatureData))
}
}
这是我的完整测试代码,非常适合我使用
.chartYScale(domain: ...)
。在真实设备(不是预览版)上测试,
使用 MacOS 15.2 和 XCode 16.2 的 iOS-18 和 MacCatalyst。
struct PointChartView: View {
var lineChartViewData: LineChartViewData
@State private var selectedWeek: Int = 0
@State private var drange: (min: Date, max: Date) = (Date(), Date()) // <--- here
var body: some View {
VStack(alignment: .leading, spacing: 16) {
TabView(selection: $selectedWeek) {
ForEach(0..<lineChartViewData.getWeeklyData().count, id: \.self) { weekIndex in
VStack{
Chart (lineChartViewData.getWeeklyData()[weekIndex]) { item in
PointMark(
x: .value("day", item.day),
y: .value("time", convertFullDateToHMDate(item.detailDay))
)
.symbol {
Image(systemName: "square.fill")
.resizable()
.frame(width: 15, height: 15)
.foregroundStyle(Color.green)
}
}
.chartYAxis {
AxisMarks(position: .leading) { value in
if let dateValue = value.as(Date.self) {
AxisTick()
AxisGridLine()
AxisValueLabel(formatDateToHMS(dateValue))
} else {
AxisValueLabel("\(value.as(Double.self) ?? 0.0)")
}
}
}
.chartYScale(domain: drange.min...drange.max) // <--- here
}.tag(weekIndex)
}
}
.frame(height: 150).tabViewStyle(PageTabViewStyle())
.onAppear {
selectedWeek = lineChartViewData.getWeeklyData().count - 1
drange = lineChartViewData.getMinMaxDate() // <--- here
}
}
.padding(16)
.background(
ZStack {
if lineChartViewData.isArtificial {
Rectangle()
.fill(Color.gray.opacity(0.5))
.cornerRadius(30)
.background(.ultraThinMaterial)
} else {
Rectangle()
.fill(Color.gray.opacity(0))
.background(.ultraThinMaterial) // 直接设置背景材质
.cornerRadius(30)
}
}
)
}
}
struct LineChartViewData : Identifiable ,Codable {
var id = UUID()
var imageName: String
var isArtificial: Bool
var dataArray: [TemperatureData] = []
func getWeeklyData()-> [[TemperatureData]] {
var twoDimensionalArray: [[TemperatureData]] = []
var subarray: [TemperatureData] = []
for element in self.dataArray {
subarray.append(element)
if subarray.count == 7 {
twoDimensionalArray.append(subarray)
subarray = []
}
}
if(!subarray.isEmpty) {
twoDimensionalArray.append(subarray)
}
return twoDimensionalArray
}
func getMinMaxDate() -> (min: Date, max: Date) {
let values = dataArray.map { convertFullDateToHMDate($0.detailDay) }
let minValue = values.min() ?? Date()
let maxValue = values.max() ?? Date()
let padding: TimeInterval = 60 * 10
let adjustedMinValue = minValue.addingTimeInterval(-padding)
let adjustedMaxValue = maxValue.addingTimeInterval(padding)
return (adjustedMinValue, adjustedMaxValue)
}
}
struct PointChartView_Previews: PreviewProvider {
static var previews: some View {
let temperatureData: [TemperatureData] = [
TemperatureData(day: "9-9", detailDay: "2024-12-01 10:31:00"),
TemperatureData(day: "9-9", detailDay: "2024-12-01 10:32:00"),
TemperatureData(day: "9-9", detailDay: "2024-12-01 10:33:00"),
]
PointChartView(lineChartViewData: LineChartViewData(imageName: "IconPoop" ,isArtificial: false, dataArray: temperatureData))
}
}
struct ContentView: View {
let temperatureData: [TemperatureData] = [
TemperatureData(day: "9-9", detailDay: "2024-12-01 10:31:00"),
TemperatureData(day: "8-8", detailDay: "2024-12-02 10:32:00"),
TemperatureData(day: "7-7", detailDay: "2024-12-03 10:33:00"),
]
var body: some View {
PointChartView(lineChartViewData: LineChartViewData(imageName: "IconPoop" ,isArtificial: false, dataArray: temperatureData))
}
}
func convertFullDateToHMDate(_ fullDateString: String) -> Date {
let fullFormatter = DateFormatter()
fullFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let fullDate = fullFormatter.date(from: fullDateString)!
let calendar = Calendar.current
let components = calendar.dateComponents([.hour, .minute ,.second], from: fullDate)
return calendar.date(from: components)!
}
func formatDateToHMS(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss"
return formatter.string(from: date)
}
struct TemperatureData: Identifiable ,Codable {
var id = UUID()
var day: String
var detailDay: String // detail time
var temperature: Double = 0.0
}