Swift  Charts API
New in iOS 16

Pie Charts in iOS17

It is a bit weird that the iOS 17 update to Charts Framework introduces the simplest graphic - the Pie Chart.  But - Yes! In Xcode 15 we get the Pie Chart.  And it makes a wonderful intro to the powerful Charting Framework.  See Stewart Lynch's YouTube Example code to learn the Charting framework.  And just know the framework goes much deeper than a donut hole.


Excited to see the Companion for SwiftUI v 4.1 has the Charts API with code examples!  Just what I need to start exploring the new Charts features.  I've tried and tried to understand the documented syntax of Apple Docs... but I think that there is just too much implied but unstated verbiage in those terse document pages.  What will really help me is a good old fashion working example.  I can follow and understand an example.

A realization just struck me - I've got to upgrade my MacBook Pro (2016)...  it will not run Ventura, which the Companion needs to run LIVE Examples.  It just got deprecated to the used hardware shelf - so sad.  And expensive...  

Here's a scheme... buy an Apple TV with the A15 chip - grab some open source Compute Server -> install on Apple TV and off load my MacBook to the TV... for $150!


I'm really excited  to see the new Charts API - a declaritive framework for displaying  data in  various charts, and interactive  elements  just like  one would expect with a  SwiftUI look &  feel.

I'm dissapointed in the  developer documentation - poor and hardly usefull  appears  to be  par for the course...  with such a visual context a few pictures would go a  long  way toward explaination, Apple. 

 https://developer.apple.com/documentation/Charts

As a case in point - suppose  you are wanting  to make a simple bar chart of your store's monthly  sells, and your goal  is to see which item sells  best.  One would typically make a  monthly bar  chart grouped by product sells.    Search the documentation for "grouped by"  ... you will  get  lots of hits.   And you  can chase that rabbit  into  lots of reading  holes...  but you  are not  likely to  find   the info  you want.   

The  code below  has the magic... and it's not "grouped" anything...    you are searching for  the Position  modifier.   The image  on the  right  "Grouped Bar Chart" is the result of this code.  Reading the (Beta) developer  documentation will most likely  leave  you  wondering  if "position" is the modifier you are searching for.  An  image  on that page would do wonders toward  understanding.


struct  groupedBarView: View {

    var body: some View {

        VStack {

            Text("Grouped Bar Chart")

            Chart  {

                ForEach(quarterSales) { product in

                    BarMark(x: .value("name", product.name), y: .value("sales", product.count))

                        .foregroundStyle(by: .value("month", product.month))

                        .position(by: .value("month", product.month))

                }

            }

        }

        .padding()

    }

}

I can forsee a  wonderful  book by  Big  Mountain Studio  for  Charts - Updated for iOS 16 SwiftUI View Mastery.

See: Apple's WWDC videos:  Hello Swift Charts from WWDC 2022 and Swift Charts -  Raise  the bar


Apple's  Chart documentation example starts with toy shapes.  Here is that example's code.

I wish they would get their color names straight - that's not a PINK cube.  I tried to change the "Pink" to "Red" string within the dictionary of the chartForegroundStyleScale   but  got an interesting  error... pondering - try  it and  see if you get:  "ToyShapesCharts crashed due to implicitly unwrapped optional in ConcreteScale+Discrete.swift at line 102."



//

//  ToyShapesChartsApp.swift

//  ToyShapesCharts

//

//  Created by David on 7/2/22.

//

//  Implementation of Apple's Charts Documentation Example

//  see: https://developer.apple.com/documentation/charts/creating-a-chart-using-swift-charts


import SwiftUI

import Charts


struct ToyShape: Identifiable {

    var color: String

    var type: String

    var count: Double

    var id = UUID()

}


var stackedBarData: [ToyShape] = [

    .init(color: "Green", type: "Cube", count: 2),

    .init(color: "Green", type: "Sphere", count: 0),

    .init(color: "Green", type: "Pyramid", count: 1),

    .init(color: "Purple", type: "Cube", count: 1),

    .init(color: "Purple", type: "Sphere", count: 1),

    .init(color: "Purple", type: "Pyramid", count: 1),

    .init(color: "Pink", type: "Cube", count: 1),

    .init(color: "Pink", type: "Sphere", count: 2),

    .init(color: "Pink", type: "Pyramid", count: 0),

    .init(color: "Yellow", type: "Cube", count: 1),

    .init(color: "Yellow", type: "Sphere", count: 1),

    .init(color: "Yellow", type: "Pyramid", count: 2)

]



struct ContentView: View {

    var body: some View {

        VStack {

            Image(systemName: "chart.bar.xaxis")

                .imageScale(.large)

                .foregroundColor(.accentColor)

            Text("Hello, Charts!")

            

            Chart {

                ForEach(stackedBarData) { shape in

                    BarMark(

                        x: .value("Shape Type", shape.type),

                        y: .value("Total Count", shape.count)

                    )

                    .foregroundStyle(by: .value("Shape Color", shape.color))

                }

            }

            .chartForegroundStyleScale([

                "Green": .green, "Purple": .purple, "Pink": .pink, "Yellow": .yellow

            ])

            

            Image("ToyShapes")

                .resizable().aspectRatio(contentMode: .fit)

                .frame(width: 250)

                .padding()

        }

        .padding()

    }

}


struct ContentView_Previews: PreviewProvider {

    static var previews: some View {

        ContentView()

    }

}


@main

struct ToyShapesChartsApp: App {

    var body: some Scene {

        WindowGroup {

            ContentView()

        }

    }

}



Apple has two great introduction videos in WWDC 2022. 

 Swift  Charts: Raise the  Bar  and  Hello Swift Charts.

I  found  that by default the  Chart does NOT clip the marks to the assumed plot  area of the  chart.  So  I had  to  go looking for a way to  turn  on this behavior.  It is the Chart modifier: 
.clipped(antialiased: true)
I didn't like that it  clipped the Y axis label text at the  top.  But it  works.  There may   be  a use of  padding()  modifier that  would help with this.

Swift Charts  Framework for iOS16+

A  cheet sheet  of useful  constructs

How will I test the Charts?

A really  great  Example

If you  are looking  for  a  real-world example; then checkout Stewart Lynch's  Charts Framework 2 - Visualizing Large Data Sets with Bar Charts

He  teaches by  _crafting_ the example  code - you  come  away with a greater understanding of why the  code is written this way.   As apposed to an instructor that  just  types  out the pre-written example code on  a  video.  Compare his  teaching style  to others -  if  you  are like  me, it is a  huge difference!

SW Dev Notes

Another great example page on  iOS Charts is https://swdevnotes.com/swift/2022/customise-a-line-chart-with-swiftui-charts-in-ios-16/

The examples are  well illustrated and  the Chart code is  well  written.

Simplest  Thing  that
Could Possibly  Work

With SwiftUI - there are  many ways to resolve an  issue - the real trick is finding the  Simplest Way.   For example  the  poor text wrapping on the "Microsoft" strings  over there.   OUCH! It hurts the  eyes...

What's the Simplest  solution?  It appears the graphic is getting the priority when horizontal spacing is concerned.  This is bound to  happen alot  in the  future.  I could reduce the  chart size... but that seems  to be only  a fix on  this instance.

Font  resizing  is an option in SwiftUI.    But  what modifiers to use?

I think  an option is to  use font rescaling, so finding some good articles on those options I found this https://malauch.com/posts/auto-resizable-text-size-in-swiftui/

This is  a whole sub-discilpline of font details I don't  care to learn.

Maybe: 

.lineLimit(1)

.minimumScaleFactor(0.01)

.truncationMode(.tail)

Note  the variable font size on the Watch List  company  name.

Next fix the currency displayed value.

Learning  about Chart  AxisMarks

OK  ... I'd call them Axis Labels...  but  I guess theres lots of value in  labeling  the Marks on  a Chart as... AxisMarks.  Now getting the  labels (marks) to  jump up  into  the plotting area  ... that's  a term called "preset" and  the value of preset  should be "inset" - I've learned to reverse engeneer the  Apple Docs thru pure GRIT.

This code example is Eric Callanan found on his GitHub account.  I managed   to  modify just enough to Inset the AxisMarks  ChartXAxis. It took multiple attempts; but having some  working code  to start from is  priceless.

Weekly Charts

I've substituted the Weekly  Chart in for the Daily Sparkline chart  &  fetching the data  now takes  20-40 seconds for  12 stocks.   I've  heard that constantly monitoring an app to know when to  optimize code is a smart dev  practice.    My first attempt with this was  on  a Sunday and I killed the  simulator right as it was  refreshing - I'd assumed the  worst, in reality  it was working as  fast as it could go.   Not  weekend related... but  just wait.

To save time...  I will configure up  some preview data to experiment with the  Weekly Charts.

An objective of  that  swap (Weekly for  Daily charts) was  to display the ChartXAxis AxisMarks in the  plotting area via the  "preset" of "inset" as learned above.

It is obvious reading  the  Apple Docs that I  want  to manipulate the AxisValueLabels struct.   But go read that part of  the Docs and tell me  how???  This  is  where  I  need  an  example  -  ok Apple?

Well one attempt is  seen to  the right  - just  getting labels for "T", "M",  "F",  "W" - wonder what the heck is  going on there.  I'm still   learning...

Hmm...  incrementally better after a whole  afternoon  of coding and  learning - with ONE Apple  Feedback report.
Cannot get  full  day  names  from Calendar().standaloneWeekdaySymbols
FB11335975

Plotting  the single letter values of day Names results in aggration  of "S" (Sat &  Sun) & "T" (Tue  &  Thur) along the X-axis.  I tried to  use  the  full  day name  - but got  caught up in a  bug/or understanding vortex.

Now with 3 letter abbreviations, I get the 7 days but my data has 8  point values - so  the wrapping continues.   Yes  - if my data  only contained  the 7 days of a week... but what will happen when I  move  to  a Month  view?  Wrapping!  Also  the  3 letters overflow the  room for the day  columns.   One tiny step forward... three  to the left...

Putting the day 3 letter abbrevation in my data - seems  to  help...  some. 

A 5 day  Chart

Still pondering how to draw the 5  day chart for a  stock.  Something  is fishy about those charts... in  a downward trending market, they seem so optimistic.   The  Yahoo Financial 5Day AAPL  Chart below for reference.  I wonder if I've sorted it  the wrong way?  And only  4 days?   

Yeap!  Swaping  the sort  order  and  removing the Date from the  X-axis and I get a  chart that is similar to the reference  from  Yahoo.

Human Interface Guidelines

I saw that Apple updated the HIG Docs with blurbs on Charts.  Take a look.

I've found some interesting... questions that I ponder...

How did they create those beautiful charts?  What do they mean data....

Where did the "PM" in that X-Axis Value Label come from?

Pictures++

The first dozen times I've read the Apple Docs for Charts Framework in iOS 16.  I came away with a big misconception about what the axis labels were.  With no visual guide I thought the modifier .chartXAxisLabel was setting the text for the axis that would decorate the tick marks on the axis.  WRONG.
With one well written example with code and a vizual Mark from BigMountainStudio has me straigntened out.  There are multiple general places an axis label might appear - but none were what I thought I was labeling.

I'm still very unsure how to specify the tick-mark labels that I need to set on the Stock chart above - but I'm learning.  And making progress now.

Apple states (rather curtly) AxisValueLabel
"A label that describes the value for an axis mark."

I'm sure someone can read into that definition, that the Axis Label floats around all over the plot and does NOT attach itself to any ONE Value.

That AxisValueLabel is a structure - not an instance method like the chartXAxisLabel() method.  It is the method that I'm using in the code.


I'm trying to add some X-Axis Grid Lines... now I'm searching Mark's book for that topic.  It doesn't seem he covers that detail.  I'm stuck with the Apple Docs.

"A line that a chart draws across its plot area to indicate a reference point along a particular axis."


I'm attempting to change the number of grid lines on my chart.  I did this some many example/tutorial programs ago.  Can not remember the trick. So I have been searching the Docs...
Maybe I've found it... deep deep into an initializer for: 

"Describes the values the axis markers will present (one for each value)."
It has two methods with keyword "stride".


static   func   stride<P>(by: P, roundLowerBound: Bool?, roundUpperBound: Bool?) -> AxisMarkValues

Creates values with the given number step.

One does strides by calendar components (dates and time) the other looks promising if I can decpipher the meaning and usage.
Standby as I parse this method.  The bounds are optional booleans - I can most likely leave them blank.  The by parameter is a generic P - that's not very helpful.

Clicking into the description I get the Declaration which tells me the P is a BinaryFloatingPoint - OK what could that be... reading that Doc page... I've got nothing...

Let's just try something and see if it works out.

Since I'm dealing with a plot in X-Y space the word Point denotes an X-Y two dimensional concept.  Yet the adjective Floating... before Point denotes a decimal value (floating-point number).  Placing another adjective Binary in front really throws me for a loop.  Maybe I'm overthinking this... is it two values or one... is it a boolean (binary) or a floating point number... definitely over-thinking... let's pass in a 5 and see if that works - let the compiler decide.

Well, this is starting out very poorly.  One cannot initialize the structure - it's all static methods.  Oh... well!

The STRIDER trick...

I don't know how I found an answer to my problem.  Not sure this is an answer.  Yet... But it's feeling Swifty and seems to pass the judgment of the compiler.


        .chartXAxis {

            AxisMarks(values: .stride(by: .hour, count: 24) ) { _ in

                AxisGridLine(centered: true, stroke: StrokeStyle(dash: [1, 2, 4]))

                    .foregroundStyle(Color.indigo)

                AxisTick(stroke: StrokeStyle(lineWidth: 2))

                    .foregroundStyle(Color.mint)

                AxisValueLabel(format: .dateTime.weekday(.narrow).hour(), centered: false)

            }

        }

OK - some notes on that.  Because the AxisMarkValues structure is hidden down in some code shorthand notation.
I think this is being used via that AxisMarks(values: .stride( )) call.  I could not parse this from the documentation - I need an example.  So I found this code that uses "stride" on 'Mobile.blog  Mobile development at Automattic' in article: An Adventure with Swift Charts by Huong Do July 4, 2022.  I often look at the publication dates when searching for Swift Charts info... if it is earlier than June of 2022 - it is typically about some other framework.

.chartXAxis {

    AxisMarks(values: .stride(by: xAxisStride, count: xAxisStrideCount)) { date in

        AxisValueLabel(format: xAxisLabelFormatStyle(for: date.as(Date.self) ?? Date()))

    }

}

There are a few computed properties in that (xAxisStride, xAxisStrideCount) these resolve to .day and int 5.  Which compares well to one of the AxisMarkValues static functions that return AxisMarkValues.

OK... but it did NOT work!

Well, that was really good in theory and passed the compiler's judgy complaints.  But all in all it didn't function.   See over there - no X-axis values/labels/text (whatever Apple wants to call it).

I had this problem before and it was caused by  .chartXAxis(.hidden) line of code that I checked - commented out now.

While we are here - if you want to limit your Y-axis to a range like this:
.chartYScale(domain: priceMin...priceMax)

Also notice whatever I've done with the grid lines has removed all - even the Y-axis grid lines.


This code will get back the axis labels for Days and Hour... but no grid lines!


        .chartXAxis {

            AxisMarks { value in

                AxisGridLine()

                AxisValueLabel(format: .dateTime.weekday(.narrow).hour(), centered: false)

                //AxisValueLabel(format: .dateTime.month().day().hour(), centered: false)

            }

        }




Sean Allen does Charts

YouTube channel https://www.youtube.com/watch?v=4utsyqhnS4g
SwiftUI Bar Chart with Customizations

It is a quick high-level intro, but he covers a few of the common APIs that I wanted to know how to use.

Adding a border to the chart

I've found that adding the typical border (which is inset) leaves just touches of color bleeding out behind the border.  See those screen shots over there.

Digging into the Apple Docs I found a nice comment that the solution is to add padding to the Plot with the same width as the border line.  And this works.

Plot GridLines Blue

I wanted to see the grid lines in my chart a bit better - so make them blue.  This is done in the .foregroundStyle() modifier.

Jump to selected ScrollView item.

How do I make the selected chart duration show up in the ScrollView when the Chart reappears on the screen?  I found several examples but they didn't appear to work in my case... hmm, what is wrong here...  I was not making my items identifiable.  I needed to specify the id parameter in ForEach.  Then use the proxy.scrollTo() in onAppear().

What I learned... the short answer...

Adding the id to the ForEach so that each item in the list had a unique ID (in my instance this is the Enum that allRangeTypes array is derived from.

struct DateRangePickerView: View {

    

    let allRangeTypes = ChartRange.allCases

    @Binding var selectedRange: ChartRange


I want to investigate the @Namespace trick used in some of the examples I saw... but didn't work out for my needs.

Search for the ScrollViewReader thats the start down the rabbit hole.

It has been a few weeks and I've forgotten most of the Charts API that I thought I should remember.  I guess I have not practice (with positive returns) enough.  Today I'm struggling again with the chart axis labels. 

Looking at some of the example code it appears one should be able to write:

            
  Chart  { ... }

.chartXAxis {

                AxisMarks(values: .automatic(desiredCount: 5)) { _ in

                    AxisGridLine()

                    AxisTick()

                    AxisValueLabel("Quarters.", position: .leading, alignment: .center, spacing: 8)

                }

            }

But Xcode has 4 errors for the above code - which looks very similar to example code I've copied and used before.  The most obvious error and one Xcode is willing to fix is: Incorrect argument labels in call (have 'values:_:', expected 'format:preset:position:values:stroke:')  for AxisMarks().

So why does the compiler allow AxisMarks(values: ...) in one instance and complain in the other?

I cannot figure out the docs meaning... but after reading all the init methods of AxisMarks - I feel this issue may have to do with:
init<Value>(preset: AxisMarkPreset, position: AxisMarkPosition, values: [Value], stroke: StrokeStyle?)

Can someone tell me more... why does this init sometimes get called...  (what does it mean to have the generic Value?


I've also now seen this idiom:

.chartXAxis(content: {

               AxisMarks(position: .top)

           })

StrokeStyle dashPhase

The Companion for SwiftUI helped me to understand the dashPhase parameter to the StrokeStyle.  I'm not sure I could put it into words but the interactive tooling allows me to intuitively understand the concept of a phase.

I think it may be a 0 - 1 percentage of the dash pattern specified.  Where 0 means the line will start at the very beginning of the dash pattern, whereas  0.5 means it will start 50% of the way into the dash pattern. 

Much easier to see - than to explain.

Combining Charts

A nice article on multiple charts by Danijela Vrzan
Combine charts to create stunning designs with Swift Charts

The overall chart consist of: