Swift Charts

RSS for tag

Visualize data with highly customizable charts across all Apple platforms using the compositional syntax of SwifUI.

Posts under Swift Charts tag

44 Posts
Sort by:

Post

Replies

Boosts

Views

Activity

Swift Charts: Changing chartXVisibleDomain changes chartScrollPosition
I'm building a Swift Chart that displays locations in a horizontal timeline. The chart is scrollable. Unfortunately, when chartScrollPosition is offset by some amount (i.e. the user has scrolled the chart), changing chartXVisibleDomain results in chartScrollPosition jumping backwards by some amount. This results in bad user experience. A minimum reproducible example is below. To reproduce: Run the code below Using the picker, change chartXVisibleDomain. ThechartScrollPosition remains the same, as expected. Scroll the chart on the horizontal axis. Using the picker, change chartXVisibleDomain. ThechartScrollPosition changes unexpectedly. You can verify this by watching the labels at the bottom of the chart. The chart simply ends up showing a different area of the domain. Tested on various iPhone and iPad simulators and physical devices, the issue appears everywhere. Is this a SwiftUI bug, or am I doing something wrong? struct ScrollableChartBug: View { /// Sample data let data = SampleData.samples let startDate = SampleData.samples.first?.startDate ?? Date() let endDate = Date() /// Scroll position of the chart, expressed as Date along the x-axis. @State var chartPosition: Date = SampleData.samples.first?.startDate ?? Date() /// Sets the granularity of the shown view. @State var visibleDomain: VisibleDomain = .year var body: some View { Chart(data, id: \.id) { element in BarMark(xStart: .value("Start", element.startDate), xEnd: .value("End", element.endDate), yStart: 0, yEnd: 50) .foregroundStyle(by: .value("Type", element.type.rawValue)) .clipShape(.rect(cornerRadius: 8, style: .continuous)) } .chartScrollableAxes(.horizontal) // enable scroll .chartScrollPosition(x: $chartPosition) // track scroll offset .chartXVisibleDomain(length: visibleDomain.seconds) .chartXScale(domain: startDate...endDate) .chartForegroundStyleScale { typeName in // custom colors for bars and for legend SampleDataType(rawValue: typeName)?.color ?? .clear } .chartXAxis { AxisMarks(values: .stride(by: .month, count: 1)) { value in if let date = value.as(Date.self) { AxisValueLabel { Text(date, format: .dateTime.year().month().day()) .bold() } AxisTick(length: .label) } } } .frame(height: 90) .padding(.bottom, 40) // for overlay picker .overlay { Picker("", selection: $visibleDomain.animation()) { ForEach(VisibleDomain.allCases) { variant in Text(variant.label) .tag(variant) } } .pickerStyle(.segmented) .frame(width: 240) .padding(.trailing) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing) } //: overlay } //: body } //: struct // MARK: - Preview #Preview { ScrollableChartBug() } // MARK: - Data enum SampleDataType: String, CaseIterable { case city, wood, field var color: Color { switch self { case .city: .gray case .wood: .green case .field: .brown } } var label: String { switch self { case .city: "City" case .wood: "Wood" case .field: "Field" } } } enum VisibleDomain: Identifiable, CaseIterable { case day case week case month case year var id: Int { self.seconds } var seconds: Int { switch self { case .day: 3600 * 24 * 2 case .week: 3600 * 24 * 10 case .month: 3600 * 24 * 40 case .year: 3600 * 24 * 400 } } var label: String { switch self { case .day: "Days" case .week: "Weeks" case .month: "Months" case .year: "Years" } } } struct SampleData: Identifiable { let startDate: Date let endDate: Date let name: String let type: SampleDataType var id: String { name } static let samples: [SampleData] = [ .init(startDate: Date.from(year: 2022, month: 3, day: 1), endDate: Date.from(year: 2022, month: 3, day: 10), name: "New York", type: .city), .init(startDate: Date.from(year: 2022, month: 3, day: 20, hour: 6), endDate: Date.from(year: 2022, month: 5, day: 1), name: "London", type: .city), .init(startDate: Date.from(year: 2022, month: 5, day: 4), endDate: Date.from(year: 2022, month: 7, day: 5), name: "Backcountry ABC", type: .field), .init(startDate: Date.from(year: 2022, month: 7, day: 5), endDate: Date.from(year: 2022, month: 10, day: 10), name: "Field DEF", type: .field), .init(startDate: Date.from(year: 2022, month: 10, day: 10), endDate: Date.from(year: 2023, month: 2, day: 10), name: "Wood 123", type: .wood), .init(startDate: Date.from(year: 2023, month: 2, day: 10), endDate: Date.from(year: 2023, month: 3, day: 20), name: "Paris", type: .city), .init(startDate: Date.from(year: 2023, month: 3, day: 21), endDate: Date.from(year: 2023, month: 10, day: 5), name: "Field GHI", type: .field), .init(startDate: Date.from(year: 2023, month: 10, day: 5), endDate: Date.from(year: 2024, month: 3, day: 5), name: "Wood 456", type: .wood), .init(startDate: Date.from(year: 2024, month: 3, day: 6), endDate: Date(), name: "Field JKL", type: .field) ] } extension Date { /** Constructs a Date from a given year (Int). Use like `Date.from(year: 2020)`. */ static func from(year: Int? = nil, month: Int? = nil, day: Int? = nil, hour: Int? = nil, minute: Int? = nil) -> Date { let components = DateComponents(year: year, month: month, day: day, hour: hour, minute: minute) guard let date = Calendar.current.date(from: components) else { print(#function, "Failed to construct date. Returning current date.") return Date() } return date } }
3
2
1.5k
Jul ’24
TabView and Swift Charts giving inconsistent behaviour when swiping between pages
Hi there, I have a TabView in page style. Inside that TabView I have a number of views, each view is populated with a model object from an array. The array is iterated to provide the chart data. Here is the code: TabView(selection: $displayedChartIndex) { ForEach((0..<data.count), id: \.self) { index in ZStack { AccuracyLineView(graphData: tabSelectorModel.lineChartModels[index]) .padding(5) } .tag((index)) } } .tabViewStyle(.page) .indexViewStyle(.page(backgroundDisplayMode: .always)) I am seeing odd behaviour, as I swipe left and right, occasionally the chart area shows the chart from another page in the TabView. I know the correct view is being shown as there are text elements. See the screenshot below. The screen on the right is running iOS 17.2 and this works correctly. The screen on the left is running iOS 17.4 and the date at the top is correct which tells me that the data object is correct. However the graph is showing a chart from a different page. When I click on the chart on the left (I have interaction enabled) then it immediately draws the correct chart. If I disable the interaction then I still get the behaviour albeit the chart never corrects itself because there is no interaction! I can reproduce this in the 17.4 simulator and it is happening in my live app on iOS17.4. This has only started happening since iOS 17.4 dropped and works perfectly in iOS 17.2 simulator and I didn't notice it in the live app when I was running 17.3. Is this a bug and/or is there a workaround? For info this is the chart view code, it is not doing anything clever: struct AccuracyLineView: View { @State private var selectedIndex: Int? let graphData: LineChartModel func calcHourMarkers (maxTime: Int) -> [Int] { let secondsInDay = 86400 // 60 * 60 * 24 var marks: [Int] = [] var counter = 0 while counter <= maxTime { if (counter > 0) { marks.append(counter) } counter += secondsInDay } return marks } var selectedGraphMark: GraphMark? { var returnMark: GraphMark? = nil var prevPoint = graphData.points.first for point in graphData.points { if let prevPoint { if let selectedIndex, let lastPoint = graphData.points.last, ((point.interval + prevPoint.interval) / 2 > selectedIndex || point == lastPoint) { if point == graphData.points.last { if selectedIndex > (point.interval + prevPoint.interval) / 2 { returnMark = point } else { returnMark = prevPoint } } else { returnMark = prevPoint break } } } prevPoint = point } return returnMark } var body: some View { let lineColour:Color = Color(AppTheme.globalAccentColour) VStack { HStack { Image(systemName: "clock") Text(graphData.getStartDate() + " - " + graphData.getEndDate()) // 19-29 Sept .font(.caption) .fontWeight(.light) Spacer() } Spacer() Chart { // Lines ForEach(graphData.points) { item in LineMark( x: .value("Interval", item.interval), y: .value("Offset", item.timeOffset), series: .value("A", "A") ) .interpolationMethod(.catmullRom) .foregroundStyle(lineColour) .symbol { Circle() .stroke(Color(Color(UIColor.secondarySystemGroupedBackground)), lineWidth: 4) .fill(AppTheme.globalAccentColour) .frame(width: 10) } } ForEach(graphData.trend) { item in LineMark ( x: .value("Interval", item.interval), y: .value("Offset", item.timeOffset) ) .foregroundStyle(Color(UIColor.systemGray2)) } if let selectedGraphMark { RuleMark(x: .value("Offset", selectedGraphMark.interval)) .foregroundStyle(Color(UIColor.systemGray4)) } } .chartXSelection(value: $selectedIndex) .chartXScale(domain: [0, graphData.getMaxTime()]) } } }
10
0
1.7k
Jan ’25
Swift Charts performance when displaying many data points
I'm currently evaluating Swift Charts to use in my macOS app, where I need to (potentially) display a few millions of data points, ideally all of them at one time. I want to give users the possibility to zoom in & out, so the entire range of values could be displayed at one moment. However, starting at around 20K data points (on my computer), the Chart takes a little bit to set up, but the window resizing is laggy. The performance seems to decrease linearly (?), when dealing with 100K data points you can barely resize the window and the Chart setup/creation is noticeable enough. Dealing with 500K data points is out of the question, the app is pretty much not useable. So I'm wondering if anybody else had a similar issue and what can be done? Is there any "magic" Swift Charts setting that could improve the performance? I have a "data decimation" algorithm, and given no choice I will use it, but somehow I was hoping for Swift Charts to gracefully handle at least 100K data points (there are other libs which do this!). Also, limiting the displayed data range is out of the question for my case, this is a crucial feature of the app. Here's the code that I'm using, but it's the most basic one: struct DataPoint: Identifiable { var id: Double { Double(xValue) } let xValue: Int let yValue: Double } let dataPoints: [DataPoint] = (0..<100_000).map { DataPoint(xValue: $0, yValue: Double($0)) } struct MyChart: View { var body: some View { Chart(dataPoints) { dataPoint in PointMark(x: .value("Index", dataPoint.xValue), y: .value("Y Value", dataPoint.yValue)) } } } Some additional info, if it helps: The Chart is included in a AppKit window via NSHostingController (in my sample project the window contains nothing but the chart) The computer is a MacBook Pro, 2019 and is running macOS 10.14
4
2
1.9k
Sep ’24
Swift charts displaying wrong theme through UIHostingController
Hi there, I'm currently using UIHostingController to display swift charts in uikit. The problem im facing is that the UIHostingController isn't outputting the intended theme. When the simulator/phone is on dark mode the view is still in light mode. Iv'e tried to force the view to use dark mode with: .environment(\.colorScheme, .dark) But it doesn't seem to help. Here's how I implement the UIHostingController to my view: let controller = UIHostingController(rootView: StatVC()) controller.view.translatesAutoresizingMaksIntoConstraints = false addChild(controller) controller.didMove(toParent: self) view.addSubview(controller.view) where StatVC() is the swiftui view which contains the swift chart.
1
0
1.2k
Feb ’25