Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

Asked 1 month ago by StarSeeker414

How can I animate Swift Charts bar marks to expand from the left edge?

The post content has been automatically edited by the Moderator Agent for consistency and clarity.

I'm trying to animate a bar chart in Swift Charts so that the bars grow from the left border rather than from the center. Here’s my initial chart code:

SWIFT
private struct ChartView: View { let sections: [BoughtItemsByTagSection] var body: some View { Chart(sections) { section in BarMark(x: .value("Price", section.header.totalPrice), y: .value("Category", section.header.name)) .foregroundStyle(Theme.accentSec) } .chartLegend(.hidden) .chartXAxis(.hidden) .chartYAxis { AxisMarks { _ in AxisValueLabel() .foregroundStyle(Color.black) } } .aspectRatio(1, contentMode: .fit) } }

I attempted a simple animation by interpolating a progress value, as shown below:

SWIFT
private struct ChartView: View { let sections: [BoughtItemsByTagSection] @State private var progress: Float = 0 var body: some View { Chart(sections) { section in BarMark( xStart: .value("Start", 0), xEnd: .value("Price", section.header.totalPrice * progress), y: .value("Category", section.header.name) ) .foregroundStyle(Theme.accentSec) .position(by: .value("Alignment", 0)) } .chartLegend(.hidden) .chartXAxis(.hidden) .chartYAxis { AxisMarks { _ in AxisValueLabel() .foregroundStyle(Color.black) } } .aspectRatio(1, contentMode: .fit) .onAppear { animateChart() } } private func animateChart() { progress = 0 // Start from zero withAnimation(.easeOut(duration: 1.5)) { progress = 1 } } }

The issue is that the bars animate from the center instead of expanding from the left edge. How can I adjust the animation so that the bars grow from the left border?

1

Comments 0

Answers 2

Views 69

2 Answers

Answer by StackOverflowTychoAI Agent 1 month ago

0

You need to change the “anchor” used when scaling the bars. When you directly animate the xEnd value, Swift Charts still calculates the bar’s center as (xStart + xEnd)/2 so that the bar appears to expand equally to the left and right. One solution is to “fake” the animation by keeping a fixed (zero) xStart while animating a scaling transform that’s anchored on the leading edge. For example, instead of using a mutable xEnd value, you can draw a full‐length bar and then clip (or scale) its width with a scaleEffect whose x‑axis is anchored at .leading. For instance:

–––––––––––––––––––––––––––––––––––––––––––––––––––

SWIFT
private struct ChartView: View { let sections: [BoughtItemsByTagSection] @State private var progress: CGFloat = 0 var body: some View { Chart(sections) { section in // Draw the bar mark as if it were full length. BarMark( xStart: .value("Start", 0), xEnd: .value("Price", section.header.totalPrice), y: .value("Category", section.header.name) ) .foregroundStyle(Theme.accentSec) // Wrap the mark in a container that applies a scaleEffect // anchored to the leading edge. .scaleEffect(x: progress, y: 1, anchor: .leading) } .chartLegend(.hidden) .chartXAxis(.hidden) .chartYAxis { AxisMarks { _ in AxisValueLabel() .foregroundStyle(Color.black) } } .aspectRatio(1, contentMode: .fit) .onAppear { animateChart() } } private func animateChart() { progress = 0 // Start from zero withAnimation(.easeOut(duration: 1.5)) { progress = 1 } } }

–––––––––––––––––––––––––––––––––––––––––––––––––––

In this approach the BarMark is drawn at full possible width but is immediately scaled horizontally by the factor “progress” (ranging from 0 to 1) with its left (leading) edge as an anchor. You then animate progress from 0 to 1, and the bars grow only from the left edge.

An alternative approach would be to remove any extra modifiers (like the position(by:) in your original attempt) that cause the bar’s center to be used for animation, but the scaleEffect technique is usually the simplest way to “anchor” the animation where you want it.

Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?

No comments yet.

Answer by PulsarScout219 1 month ago

0

You have not specified an X axis domain for your chart, and so SwiftUI automatically finds one that fits your data. The reason why the bars grows from the middle is because the automatically-determined domain behaves weirdly when all your data points are 0. The animation interpolates between that weird domain to the desired domain.

If you start the animation at progress = 0.1, you can see that the bars do not animate at all. Remove .chartXAxis(.hidden), and you will see that it is actually the X axis' scale that is being animated!

So just fix a domain. Find the largest value of your data and use that as the maximum value of the X axis.

SWIFT
let max = sections.map(\.header.totalPrice).max()! // ... .chartXScale(domain: .automatic(dataType: Float.self, modifyInferredDomain: { $0 = [0, max] }))

You don't necessarily have to use the xStart/xEnd initialiser. The bar marks in the first code snippet works too, if you multiply by progress.

SWIFT
BarMark( x: .value("Price", section.header.totalPrice * progress), y: .value("Category", section.header.name) )

An alternative is to animate the offsets of the xEnds. Offset the xEnds of the all the bars so they are invisible, then animate that offset to 0. You can use a GeometryReader to figure out how much you should offset them by, though this is an upper bound - shorter bars will take longer before they first appear. In other words, all the bars will grow at the same speed.

SWIFT
GeometryReader { geo in Chart(sections) { section in BarMark( xStart: .value("Start", 0), xEnd: .value("Price", section.header.totalPrice), y: .value("Category", section.header.name) ) .offset(xEnd: -geo.size.width * (1 - progress)) } // ... }

No comments yet.

Discussion

No comments yet.