iOS-Charts Library: x-axis labels without backing data not showing - swift

I am using version 3.1.1 of the popular charts library for iOS. I have run into an issue with x-axis labeling that I can't seem to find the answer for online:
Let's say I want to have a chart with one x-axis label for every day of the week (namely: S, M, T, W, T, F, S). Lots of forums I've read suggest taking the approach of setting a custom value formatter on the x-axis as suggested here: https://github.com/danielgindi/Charts/issues/1340
This works for calculating labels on days for which I have data. The issue I'm running into with this approach is that if I don't have data for a specific day, then the label for that day won't get generated.
For example, if I were to use a custom value formatter that looked like this:
public class CustomChartFormatter: NSObject, IAxisValueFormatter {
var days: = ["S", "M", "T", "W", "T", "F", "S"]
public func stringForValue(value: Double, axis: AxisBase?) -> String {
return days[Int(value)]
}
}
and my backing data looked like this: [(0, 15.5), (1, 20.1), (6, 11.1)] where 0, 1, and 6 are representations of days, and 15.5, 20.1, and 11.1 are the data points on those days, then when stringForValue is called, some of the days will never get labels generated for them.
Since value is always based on that backing data, it will never be equal to 2, 3, 4, or 5 in this scenario. As such, labels for "T", "W", "T", and "F" are never generated.
Does anyone know how to force the library to generate 7 labels, one for each day of the week, regardless of what my backing data is? Thank you kindly.

Ok so thanks to #wingzero 's comment, I have been able to get this working. There are a few things required to do so. For simplicity's sake, I am going to explain how to get the "days of the week" labels working as I originally asked. If you follow these steps, however, you should be able to tweak them to format your chart however you like (for example, with months of the year).
1) Make sure that your chart's x-axis minimum and maximum values are set. In this case, you'd want to say: chartView.xAxis.axisMinimum = 0.0 and chartView.axisMaximum = 6.0. This is important for step 2.
2) As Wingzero alluded to, create a subclass of XAxisRenderer that allows us to grab the minimum and maximum values set in step one and determine what values should be passed to our IAxisValueFormatter subclass in step three. In this case:
class XAxisWeekRenderer: XAxisRenderer {
override func computeAxis(min: Double, max: Double, inverted: Bool) {
axis?.entries = [0, 1, 2, 3, 4, 5, 6]
}
}
Make sure to pass this renderer to your chart like this: chartView.xAxisRenderer = XAxisWeekRenderer()
3) Create a subclass of IAxisValueFormatter that takes the values we passed to the chart in step two ([0, 1, 2, 3, 4, 5, 6]) and gets corresponding label names. This is what I did in my original question here. To recap:
public class CustomChartFormatter: NSObject, IAxisValueFormatter {
var days: = ["S", "M", "T", "W", "T", "F", "S"]
public func stringForValue(value: Double, axis: AxisBase?) -> String {
return days[Int(value)]
}
}
4) Set the labelCount on your graph to be equal to the number of labels you want. In this case, it would be 7. I show how to do this, along with the rest of the steps, below the last step here.
5) Force the labels to be enabled
6) Force granularity on the chart to be enabled and set granularity to 1. From what I understand, setting the granularity to 1 means that if the data your chart passes to stringForValue is not in round numbers, the chart will essentially round said data or treat it like it is rounded. This is important since if you passed in 0.5, it's possible that your stringForValue might not produce the right strings for your labels.
7) Set the value formatter on the xAxis to be the custom formatter you created in step 3.
Steps 4-7 (plus setting the formatter created in step 3) are shown below:
chartView.xAxis.labelCount = 7
chartView.xAxis.forceLabelsEnabled = true
chartView.xAxis.granularityEnabled = true
chartView.xAxis.granularity = 1
chartView.xAxis.valueFormatter = CustomChartFormatter()

First, have you debugged return days[Int(value)] on your side? From your screenshot, it seems obvious that your value after int cast looses the precision. e.g. 2.1 and 2.7 will be 2, which always shows you T. You have to look at your value first.
If you are sure you only get 7 xaxis labels all the time, a tricky way is to force computeAxisValues to have [0,1,2,3,4,5,6] all the time.
Meaning, you make sure your data x range is [1,7] (or [0,6]), and in #objc open func computeAxisValues(min: Double, max: Double), you should be able to see min is 1 and max is 7.
Then you override this method to set axis.entries = [Double]() to be [0,1,2,3,4,5,6], without any calculation. This should gives you the correct mapping.
However, before doing this, I suggest you take some time to debug this method first, to understand why you didn't get the expected values.

Related

Returning exact change in SWIFT

I'm struggling with making sense of how to return the changeDue for my assignment. Trying to revise my incorrect code for class in prep for intro to programming final.
all I am trying to do is: create a method called quarters(). When I pass any double value (ChangeDue) into the method, I want to know precisely how many quarters there are as well as the partial quarter change returned.
Original code:
func getChange(Quarters: Double) -> Double {
var Change = Quarters
return Change;
}
var Quarters = 0.72;
var ChangeDue = getChange(Quarters / .25);
print(ChangeDue)
Slightly revised code which I seem to have made worse:
class changeDue {
var = quarters(.72)
func changeDue(Quarters: Double) {
var Change = Quarters
changeDue = changeDue - (quarters*.25)
}
var ChangeDue = getChange(int / .25);
print(changeDue)
}
notes/Feedback:
create a method called quarters(). When I pass any double value (ChangeDue) into the method, I want to know precisely how many quarters there are as well as the partial quarter change returned.
Create a class level variable, changeDue. This is where you will set your test input e.g. .78, 2.15.
In your method, calculate the number of quarters as the integer of changeDue/.25
Print the number of quarters.
Now you need the revised change after quarters are removed. changeDue=changeDue - (quarters*.25)
quarters = the integer of changedue/.25
changeDue is now = to the previous changeDue - (quarters times .25)
quarters(.72)
the integer of .72/.25 = 2
changedue=.72-(2 x .25) or .72 - .50 =.12
Print changeDue.
Any help would be appreciated. I've been working on this for longer than I want to admit.
Hint 1: Do not work with Double or fractional amounts. Turn dollars into pennies by multiplying everything by 100 before you start. Now you can do everything with integer arithmetic. After you get the answer, you can always divide by 100 to turn it back into dollars, if desired.
Hint 2: Do you know about the % operator? It tells you the remainder after a division.
I don't want to write your code for you (it's you who are being tested, not me, after all), but I'll just demonstrate with a different example:
51 / 7 is 7, because integer division throws away the remainder.
Sure, 7x7 is 49, with something left over. But what?
Answer: 51 % 7 is 2. Do you see?

How to reduce code in Swift when dealing with variables and if statements?

I am currently building an app to teach myself Swift so am still very new. I’ve encountered a problem. The app is a timetable creator. I have up to twelve subjects that all have points. These points are then spread across days. I have a concept for code that will allocate the points fine using loops but wondered whether there was something to reduce the amount of code rather than, what I currently have, something like this for each subject:
subject1.monpts = 20
subject1.tuepts = 20
subject1.wedpts = 20
subject1.thurpts = 20
subject1.fripts = 20
subject1.satpts = 20
subject1.sunpts = 20
This, x12, is a lot of code. But I need it so that each subject has an option for points for each day of the week (which the app will then allocate time based on these points). To print the timetable, I am putting each subjectX.daypts together but this means I’m writing out 12 subjects for each day.
I also need to only display something if the variable actually has a value. I plan to do this using if statements but that means writing a statement for every single variable which at the moment is 12 x 7 = 48! E.g. (ignore the formating - just for concept)
if subjects1.monpts = 0 {
subjects1monLabel = isHidden.false
// or just a print / don't print statement
}
I feel as if I'm missing an easier way to do this!
Here is a picture that explains the concept a bit better:
If you want to save information about those fields you can have a dictionary with keys of a enum and values of ints like so:
enum WeekDay: CaseIterable {
case monday, tuesday, wednesday, thursday, friday, saturday, sunday
}
struct Subject {
var pointsForWeekDay: [WeekDay: Int]
}
Now you could do:
var pointsForWeekDay = Dictionary(uniqueKeysWithValues:
WeekDay.allCases.map { weekDay in
return (weekDay, 20)
}
)
var subject = Subject(pointsForWeekDay: pointsForWeekDay)
CaseIterable allows you to access all values of your enum.
Map takes every weekDay and creates an array of tuples with your Int values.
And finally you can combine that to have a complete Dictionary with uniqueKeysWithValues initializer, which takes an array of the produced tuples.
Your whole vision of how to organize this material is upside down. Start by thinking about what all your subjects have in common: points for each of the seven days, label hidden for each of the seven days, and so forth. Now incorporate that into a type (a struct): Subject. Now instead of subjects1..., subjects2... and so forth, you have an Array of Subject.
So: any time you have variables named with a number, that should be an array instead. Any time you have clumps of repeated concepts, that should be a type instead.
Even the notion of the seven days of the week could itself be condensed in the same way. If all we're talking about is points per day, an array of seven numbers would do.
So we'd end up with a skeleton like this:
struct Subject {
var dayPoints : [Int]
}
var myTwelveSubjects : [Subject]
...and you can build that out as more requirements come online, such as whether a day is hidden.

Prepping info for a data grid in Swift

I'm making an app that will eventually have a second view controller that displays a grid of information from results of previous days. I've already worked out the app to gather the information I need, which would look as follows:
var s = 0
var d = 0
var h = 0.0
var m = 0.0
var date = NSDate()
So I'm wondering how I can run a function to prep these numbers to be used in a grid in a different view controller later on. There may be no other way to do it than to send it to an outside database, but I'd rather not do that. I think I'm going to want to use either a struct or a class maybe. Maybe I only need a dictionary? I don't know, but I'm going to want to save this information at the end of every day. I could write some psuedo code, but I'm not sure how to do this.
func saveDailyResults() {
??? date = {
s, d, h, m
}
allDates.append(){
date
}
}
I think what I'm looking for would be a way to make some thing like this:
var allResultsFromAllDays = [(2016-08-08, 2, 302, 2.3, 4.5),(2016-08-07, 1, 247, 2.4, 4.7),(2016-08-06, 3, 345, 2.9, 6.5)]
...Which I could append every day when new results come in.
I'd like to be able to later on build something that looks like this from the information.
Thanks for any help you can give me!

Is this possible to limited the variable scope in swift?

For example, I need a valuable which is an Int, but I limited it can only set from 1-10. Is this features build in in swift? Except from override the setter. Is this possible to do so?
btw, what is this feature named? I remember I come across some languages got this features, but I don't recall the name of that languages.
You might be looking for an enumeration. It allows you to set a range of values (not strictly numbers though), which are allowed as input. Do something like this:
enum NumsTill10 {
case 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
}
And to set it as a variable:
let number:NumsTill10 = NumsTill10.1
Or even
let number:NumsTill10 = .1
And then you can do:
if number == .7 {
//It's 7!
}

NSNumberFormatter to display custom labels for 10^n (10000 -> 10k)

I need to display numbers on a plot axis. The values could change but I want to avoid too long numbers that will ruin the readability of the graph.
My thought was to group every 3 characters and substitute them with K, M and so on (or a custom character).
So:
1 -> 1,
999 -> 999,
1.000 -> 1k,
1.200 -> 1.2k,
1.280 -> 1.2k,
12.800 -> 12.8k,
999.999 -> 999.9k,
1.000.000 -> 1M,
...
Note that probably I'll only need to format round numbers (1, 10, 1000, 1500, 2000, 10000, 20000, 30000, 100000, ...).
Is that possibile with NSNumberFormatter? I saw that it has a setFormat method but I don't know how much customizable it is.
I'm using NSNumberFormatter cause the graph object I use wants it to set label format and I want to avoid changing my data to set the label.
You can use this code:
let formatter = NSNumberFormatter()
formatter.multiplier = 0.001
formatter.positiveFormat = "#,###k"
formatter.zeroSymbol = "0"
return formatter
It helped me to convert the currency values:
2000 -> 2k
10000 -> 10k
No. The closest you can get is Scientific Notation. Have a look here for how to create a format for that. You could obviously quite easily do the k, M etc substitution yourself though.