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

52 Posts
Sort by:

Post

Replies

Boosts

Views

Activity

Creating a navigation link within a chart?
I’d like to create a simple Gantt chart where each horizontal BarMark is a navigation link to a detail view. When I embed a navigation link within a chart, I get the error “Static method 'buildExpression' requires that 'some ChartContent' conform to 'View’” NavigationLink(value: taskGroup) { BarMark( xStart: .value("Start", taskGroup.start), xEnd: .value("End", taskGroup.end), y: .value("Event", taskGroup.taskGroupName), height: barHeight ) } I could use a chart overlay and manage the navigation from there, but it appears I can only grab published chart data at a given tap gesture. I need the object itself to inject into the detail view (in this case TaskGroup) and the data I’m plotting in the chart isn’t unique - so no obvious way to identify which TaskGroup the user tapped.
3
0
637
Jun ’24
How can Charts display sales data for a full month and support monthly paging?
Due to the varying number of days in each month, I am unsure how to enable monthly paging in Charts. In Apple's official example, SwiftChartsExample, there is only an example showing the sales of the "last 30 days": .chartXVisibleDomain(length: 3600 * 24 * 30) I have tried using scrollPosition to calculate the number of days in the current month, like this: var days: Int { let current = Calendar.current let dateRange = current.range(of: .day, in: .month, for: scrollPosition) return dateRange?.count ?? 0 } ... .chartXVisibleDomain(length: 3600 * 24 * days) ... .chartScrollPosition(x: $scrollPosition) ... But I found that it does not work as expected. 😢
2
0
788
Jun ’24
SwiftUI chart - take screenshot of the chart view on macOS
Hello all, if I enable the .chartScrollableAxes(.horizontal) and .chartXVisibleDomain(length: length) for a chart view to zoom in the screenshot of the view misses the graphs. I use this extension: `extension View { @MainActor func snapshot() { let renderer = ImageRenderer(content: self) if let exportImage = renderer.nsImage { let pasteboard = NSPasteboard.general pasteboard.clearContents() pasteboard.writeObjects([exportImage]) } } }` The screenshot is taken with: Button("Snap") { let view = ChartView(text: $statusText, length: $chartLength) .padding() .frame(width: 1500, height: 500) view.snapshot() } If I omit .chartScrollableAxes(.horizontal) the snapshot is ok and the graphs are visible in the image but then a zoom is not possible and the whole range is shown. Any ideas?
1
1
644
Jun ’24
Multiple Swift Charts in List diplay inconsistently
I have multiple barmark Charts in a List for SwiftUI. For app running on iOS 17.4.1, and one running on MacOS Sonoma 14.4.1, by scrolling up and down on the list, the order of charts changes. Sometimes one chart will replace another chart, showing duplicate charts in the List. This did not happen for iOS 17.3, and earlier OS versions. Want to see if anyone else face the same issue. I have checked that correct chart model is used in onAppear, but what is displayed in Chart is not corresponding to the chart model.
4
0
1.3k
Jun ’24
How to I move the sectorMark annotation to the outside perimeter of the pie chart?
Hello Apple Developers, I am trying to get the annotation text (the percentages) to stay on the outside perimeter of the pie chart and not in the middle of the pie chart like it currently is. Is there possibly a way to increase the radius of the annotation text to be that of the pie chart edge and maybe a little more? I don't know. Please help me out. What I currently have:
2
0
1.4k
Apr ’24
Creating a live chart for real time ble data
Hi All, Please excuse my relatively basic question but I am new to swift programming and I am battling with a project. I currently have an app that receives data from an Arduino using ble and displays the data as an integer. I used this medium article From Arduino programming to iOS App development as a guide for most of the functionality but changed the sensor data being sent to better suit my project requirements. Based on the link above, I have all of the bluetooth handling in PeripheralUseCase.swift file and then I have the ConnectView file for the display: @ObservedObject var viewModel: ConnectViewModel @Environment(\.dismiss) var dismiss @State var isToggleOn: Bool = false @State var isPeripheralReady: Bool = false @State var lastPressure: Int = 0 var body: some View { VStack { Text(viewModel.connectedPeripheral.name ?? "Unknown") .font(.title) ZStack { CardView() VStack { Text("Surface") HStack { Button("Flats") { viewModel.flats() } .disabled(!isPeripheralReady) .buttonStyle(.borderedProminent) Button("FlatPoint") { viewModel.flatPoint() } .disabled(!isPeripheralReady) .buttonStyle(.borderedProminent) Button("Points") { viewModel.points() } .disabled(!isPeripheralReady) .buttonStyle(.borderedProminent) } } } ZStack { CardView() VStack { Text("\(lastPressure) kPa") .font(.largeTitle) HStack { Spacer() .frame(alignment: .trailing) Toggle("Notify", isOn: $isToggleOn) .disabled(!isPeripheralReady) Spacer() .frame(alignment: .trailing) } } } Spacer() .frame(maxHeight:.infinity) Button { dismiss() } label: { Text("Disconnect") .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .padding(.horizontal) } .onChange(of: isToggleOn) { newValue in if newValue == true { viewModel.startNotifyPressure() } else { viewModel.stopNotifyPressure() } let startTime = Date().timeIntervalSince1970 } .onReceive(viewModel.$state) { state in switch state { case .ready: isPeripheralReady = true case let .Pressure(temp): lastPressure = temp default: print("Not handled") } } } } struct PeripheralView_Previews: PreviewProvider { final class FakeUseCase: PeripheralUseCaseProtocol { var peripheral: Peripheral? var onWriteLedState: ((Bool) -> Void)? var onReadPressure: ((Int) -> Void)? var onPeripheralReady: (() -> Void)? var onError: ((Error) -> Void)? func writeLedState(isOn: String) {} func readPressure() { onReadPressure?(25) } func notifyPressure(_ isOn: Bool) {} } static var viewModel = { ConnectViewModel(useCase: FakeUseCase(), connectedPeripheral: .init(name: "iOSArduinoBoard")) }() static var previews: some View { ConnectView(viewModel: viewModel, isPeripheralReady: true) } } struct CardView: View { var body: some View { RoundedRectangle(cornerRadius: 16, style: .continuous) .shadow(color: Color(white: 0.5, opacity: 0.2), radius: 6) .foregroundColor(.init(uiColor: .secondarySystemBackground)) } } With the associated View Model: @Published var state = State.idle var useCase: PeripheralUseCaseProtocol let connectedPeripheral: Peripheral init(useCase: PeripheralUseCaseProtocol, connectedPeripheral: Peripheral) { self.useCase = useCase self.useCase.peripheral = connectedPeripheral self.connectedPeripheral = connectedPeripheral self.setCallbacks() } private func setCallbacks() { useCase.onPeripheralReady = { [weak self] in self?.state = .ready } useCase.onReadPressure = { [weak self] value in self?.state = .Pressure(value) } useCase.onWriteLedState = { [weak self] value in self?.state = .ledState(value) } useCase.onError = { error in print("Error \(error)") } } func startNotifyPressure() { useCase.notifyPressure(true) } func stopNotifyPressure() { useCase.notifyPressure(false) } func readPressure() { useCase.readPressure() } func flats() { useCase.writeLedState(isOn: "1") } func flatPoint() { useCase.writeLedState(isOn: "2") } func points() { useCase.writeLedState(isOn: "3") } } extension ConnectViewModel { enum State { case idle case ready case Pressure(Int) case ledState(Bool) } } What I am now trying to do is plot the data that is received from the Arduino in a line graph as it is received. Preferably the graph will scroll with time as well.
1
0
1.1k
Mar ’24
Half Donut Chart using SectorMark
Does anyone knows if it is possible to make a half donut chart using the new one SectorMark? I am exploring the initializers but so far no luck, this is my code like anyone else making a normal donut: struct Score: Identifiable { let id = UUID() let type: String let value: Int } @State private var scores: [Score] = [ .init(type: "Hits", value: 1), .init(type: "Misses", value: 9) ] Chart(scores) { score in SectorMark( angle: .value("Values", score.value), innerRadius: .ratio(0.9), outerRadius: .ratio(0.7) ) .foregroundStyle( by: .value("Type", score.type) ) }
2
0
1.3k
Mar ’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.6k
Jan ’25
SwiftChart with secondary Y Axis
I created a SwiftChart as below and I would like to have two YAxis, one for amount and the second for count. So, the amount YAxis is a different scale then the count YAxis. Does anybody have an example of this or shed some light on coding two different YAxis? Thanks ForEach(seriesArt) { series in ForEach(series.chartSeries.chartEntry) { BarMark( x: .value("Tier", $0.tier), y: .value("Price", $0.keyValue) ) } .foregroundStyle(by: .value("Count", series.chartCategory)) .position(by: .value("Price", series.chartCategory)) } } .frame(width: 400, height: 200) .chartXAxis { AxisMarks(position: .bottom, values: .automatic) { AxisValueLabel() .foregroundStyle(Color.white) } } .chartYAxis { AxisMarks(position: .leading, values: .automatic) { value in AxisGridLine(centered: true, stroke: StrokeStyle(lineWidth: 1)) AxisValueLabel() { if let intValue = value.as(Int.self) { Text("\(intValue)") .font(.system(size: 10)) .foregroundColor(.white) } } } .chartYAixs - for count sum by tier which needs to be a different scale from the amount YAxis } } }
1
0
889
Apr ’24
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
1
1.6k
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
3w