Hop on the waitlist to be the first to get the book. I'm in.
I've found so many articles and video introductions to Swift Charts, and almost all have been helpful, some extremely helpful. But what I've yet to find is an article that goes well beyond the basics, something that could help me build not just a chart... but a charting system that could be embedded within my App. A system that would consistently produce nice visual charts with all the accouterments like labeled axis values with grid lines where appropriate.
See Also - the Basics...
When I first started with the iOS 16 beta Charts framework... not even Apple had any GOOD documents... that's changed a bit. But for the life in me... I cannot turn their docs into compiling functional code. So I turn to the Fab Four: Hacking With Swift, Stewart Lynch, Sean Allen, and Chris.
Rendering Charts in SwiftUI - by Paul (Hacking with Swift) https://www.hackingwithswift.com/plus/rendering-charts-in-swiftui
SwiftUI Pie & Donut Charts - New in iOS 17 by Sean Allen https://www.youtube.com/watch?v=8M3N4HWUc0U
Charts Framework in SwiftUI by Stewart Lynch https://www.youtube.com/watch?v=csd7pyfEXgw
SwiftUI Charts Basics Tutorial by Code With Chris https://www.youtube.com/watch?v=MyQy1E-bzEM
There are many others...
https://github.com/jordibruin/Swift-Charts-Examples
I'm going to start this VIBE Blogging post... right here - right now... it is NOT the article you are looking for...
Maybe one day in a far far future...
If you came here looking for a better Charts framework... not going to build it. If you are frustrated with the lack of good how to do Charts properly... wait on Big Mountain Studio.
This is my wish list for a how to do charts. It might contain some resources... it might contain some lies or AI hallucinations or maybe a true fact.
A List of Needs:
Rounding Numbers
Heuristic for Axis Labels
Legend
Grid Lines and Axis tick marks
How to handle date values and properly display axis labels
Rounding Numbers to good values.
If you have played around with Swift Charts for any time, you will know it can provide you with default Axis values and labels. But, will these values be good human understandable values... maybe not.
The goto standard for "nice" round numbers is in a book in the library; The Graphics Gems Series by Andrew Glassner (1990). Since you and I are too cheap to buy the book... go to this Stack Overflow page to get the algorithm in many various programming languages.
Don't just use the first one you find - test it first.
In the StackOverflow article: Styling chart axes
We find a nice heuristic for Swift Chart Labels. "Sweeper" suggests a 3-layer approach for AxisMarks that seems to be well-conceived.
1 - AxisMarks for major number values (every one in the example);
2 - AxisMarks for minor grid lines or minor tick marks;
3 - AxisMarks for the outer bounds (the bottom gridline).
The Tick marks are offset so as to cross the axis lines.
It required a lot of experimentation for me to learn why the 3rd set of marks is "required." It is because, in many cases, the top or maybe the bottom label (boundary condition) will not display. This is generally because of an alignment issue at that boundary. So, a specific label with a unique alignment is often the solution.
This has been the BEST example of explaining the configuration of an axis that I've found.
An Example Y Axis Label set:
.chartYAxisLabel(position: .leading, alignment: .center) { Text("Y Label") }
.chartYScale(domain: [-2, 2])
.chartYAxis {
AxisMarks(position: .leading, values: .stride(by: 1)) {
AxisValueLabel()
}
AxisMarks(position: .leading, values: .stride(by: 0.2)) { value in
if value.index != 0 { // the bottommost value does not need a tick
AxisTick(length: 8, stroke: .init(lineWidth: 1))
.offset(x: 4)
}
}
AxisMarks(position: .leading, values: [-2]) {
AxisGridLine(stroke: .init(lineWidth: 1))
}
}
The reason we are all here... it is not because someone caught Mr Mustard in the act with the candlestick over Mr. Body. It is the data - Smarty... we want to see the data. And if you already have the data then start with it.
But many times the App's data must be manipulated and formatted to look well presented in a chart. So you may want to extract the App's data into a specific Chart ViewModel. I've not seen this advice in the videos and articles... but it seems like a good thing(TM).
I've wondered how to do this extraction on real-life data... and Swift has several tools that come to mind. Array Slices is one tool. When charting the stock earnings and dividends I didn't want to show years of data - just the last 2 years of quarterly dividends. So an Array Slice of 8 was required.
var divArraySlice: [Dividend] = []
divArraySlice = Array(fullArray.prefix(8)) // <=== EIGHT points
The prefix() function returns a Subsequence type. Therefore a need to wrap it in an Array() to get back to an array of Dividends that can be handed off to the Chart().
There is quite a lot of data processing that must be done and charting will help you find out what is missing. For example, with my array of Dividends, I needed a sort operation to get them sequenced in chronological order.
// Add computed property to sort dividends by Ex Dividend Date descending
var sortedDividends: [Dividend] {
dividendList?.sorted { first, second in
guard let date1 = first.exDividendDate,
let date2 = second.exDividendDate else {
return false
}
return date1 > date2 // Sort descending (newest first)
} ?? [] // or empty list
}
Yet that is not the order I wanted the Dividends to be plotted. The order is reversed - so that the newest dividend is on the rightmost of the chart.
GroupBox("Dividends") {
Chart(data.reversed() ) {
// flow data from old date to -> new date on x-axis
This is where the Apple docs have a good set of examples... go look. I counted 32 ChartContent modifiers... lots to choose from. Adding any of a number of modifiers to the content will brighten and color your chart.
I've been struggling all morning trying to get a Chart Legend to show up in an example chart... no luck...
Then I find a StackOverflow that appers to suggest that adding a
ForegroundStyleScale array will fix it. And it does.
The "Any Junk text" never displays but the Legend pops into vision. It is all wonky and thats not 4 points of spacing... but visible is good.
Why would a mapping of key value pairs turn on the Legend visibility?
Seems like a bug to me.
Examining what I did, I mapped a text value to the color green in a Dictionary passed to a Style SCALE? WTF!
FB17044094
Chart Legend not visible until adding .chartForegroundStyleScale
When I started messing with Charts I noticed all this extra data about the chart. We can either hardcode it... or hope there is a smart configuration that works most of the time... I've heard it said - hope is not a strategy!
So I'm looking for a strategy to deal with all the configuration data about the charts... there is a tooling gap here. And all the beginning tutorials ignore it... and there are not any advanced Swift Chart articles...
Some ideas: Could one extend the Chart struct to allow a newly passed in configuration struct? No, extending in Swift land does not allow new initializers. Nor does an extension allow storing new properties.
So that idea is kind of busted... But I could create a new Chart_configuration Struct with all the needed metadata. And every time I've started working on this... I get so wrapped up in all the variations it never gets to working code... so I'm not going down that rabbit hole AGAIN! The answer is to bite off just a small but useful chunk. That was the Chart Legend... and look at the rabbit hole I just came out of... OUCH!
But there it is working out. The ChartLegend_Config() struct with all the Legend metadata assigned to the proper Chart functions. And the hack to make Legends visible in the code. The title displays in the iOS simulator in BLUE for some reason. More work to figure that blue text out. Let's work on the axis config! The proof is working.
As a proof of concept I wrote this set of Chart configuration structures that just define the most used option/values for Charts. Then I have a template to define all these various pieces of meta data to configure a typical chart.
Still it from this GitHub Gist: ExampleConfigChart.swift
And let me know how to improve it.
What's next?
I'm attempting to create a Stock App that displays my transaction on the chart. Here is a favorite stock Chipotle Mexican Grill, Inc. showing the effects of internal padding. It is not what I had in mind.
Guess what I just learned from reading the pre-release draft version of Big Mt. Studio's Chart Mastry book? There is a way to add padding inside the plot area of the chart. I would never have understood this from reading the Apple Docs (in fact I have read them looking for a padding parameter).
After failing to effect the chart on the first few attempts... I started to debug my code by changing the chart's axis... I commented out modifiers to simplify the code and the target behavior started to appear... Then knowing that sometimes modifier order matters... I played with the order and found that putting the chartXscale(range:) call before the chartXscale(domain:) call results in both being applied to the chart.
And that the units are scaled differently for the various calls.
.chartXScale(range: .plotDimension(startPadding: 0, endPadding: 25)
The more I learn... the less I know...
It appears to me that so many of the modifiers for charts are connected and have effects on the others... it's complex. So if you find that YOUR results are not what others are reporting/telling... then you can almost bet it is a undocumented relationship that is interfering with the results. Experiment! Simplify! Create a very small targeted example. It's all about learning.
That is an Example that has taken me Weeks of effort to get to. It is an annotation on a stock sell date. Sold 45 shares at $58. The purple dot was originally orange and I could never find it on the chart. Turns out it was hiding behind the orange Stop sign symbol... of course that's where it was. I made the dot purple... could not find it... so I made it's size 250... ah-ha found you - what a little TWERP hiding in the background... now that I know where it is... I can move it to where it should be.
It turns out that the basic chart (plot area with a graph of the line and the green gradient is the easy part. The transaction annotation is months of effort in the making... lots of trials, many errors... and the desired end state has constantly shifted. Sometimes the shift is because of what I think is a limitation and a capability. Other times, it is an evolution of a happy accident, where I now see something that might just work out better.
For example, in the URBN stock chart, that label bubble (white background with green text "95 @ $53") is what is intended to be centered on the black price horizontal line. But when I got the sell bubble also working... the location at the price line's start and end just makes much more sense.
You have to be open to relearning what your requirements are. If they are static and created when you know the least about your product, you will miss out on the benefits of learning.
Hey - see those labels (purple number) up at the top of the chart? Those labels are AWESOME when dealing with a TimeSeries. One of the Greatest help to me in learning how to deal with dates in the art of graphing has been the understanding that plotting dates is all about a FAKE series of points. Instead of having dates.... I use a "time-index" (e.g. 1, 2, 3, 4.... N) for each price point. Then if I want my chart to SKIP the weekends and holidays... I just leave them out of the series... skipping from Friday the 13th as index 10, to Monday as index 11. Oh yeah one needs lots of control over there data - but if you try to plot the actual date values... you are going to get Saturday & Sunday on your Stock price chart (not the desired behavior).
So, for design/build/debug purposes, I've added that top row of Axis labels. That is the "time-index" label. And in this chart we see a funky large spacing for the month of April... and then May & Jun labels are overlapping. OH - it's always something! If you read Marks book SwiftUI Charts Mastery ... you will learn of several very specific tools to overcome this type of overlapping in the chart.
Here is the overflowResolution parameter to Annotation. A special bit of knowledge.
I didn't find in a search the AxisValueLabelCollisionResolution which I think might be helpful in this case.