Data Visualisation with Swift : The Future of Ramadhan

Fasting in London during Ramadhan made me think about how long should other Muslims around the world fast. Motivated by my own curiosity, I created a visualisation to answer that question.

This also serves as an opportunity for me to practice Swift. Although using d3 might be the best way for data visualisation, just for the sake of having fun, why don’t we use Swift instead.

Data Collection

Ramadhan’s Dates

First thing that we need to know is the exact dates for future Ramadhan in 25 years time. This could be tackled with NSCalendar since the class acknowledges Islamic calendar. By composing dates using NSDateComponents, we could easily create Ramadhan dates for many years to come.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
extension NSDate {
    static func rmdn_ramadhanDaysForHijriYear(year: Int) -> [NSDate] {
        guard let hijriCalendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierIslamic) else {
          return []
        }

        let components = NSDateComponents()
        components.month = 9 // Ramadhan is the 9th month in Islamic calendar
        components.year = year

        // using flatMap instead of map to weed nil values,
        // because dateFromComponents returns an optional NSDate
        return (1..<30).flatMap { day in
            components.day = day
            return hijriCalendar.dateFromComponents(components)
        }
    }
}

Location

We need to know the exact coordinate for cities with only its name. Fortunately, CoreLocation has a function that transforms a String of city name into an object CLPlacemark (also know as geocoding).

1
2
3
4
5
6
7
CLGeocoder().geocodeAddressString("London") { p, error in
    guard let placemark = p?.first else {
        return
    }

    placemark // a CLPlacemark object containing London's location information
}

Prayer Time

The first thought on finding prayer times is by using external APIs such as MuslimSalat or xchanch. The disadvantage of this approach is once the API is inaccessible our visualisation will not work. Therefore, we need to minimise our application’s dependency to other applications as much as we could.

I use an app called Guidance for checking prayer times everyday. Luckily, the module for the prayer times calculation is opened by its author. Using BAPrayerTimes, we can calculate prayer times without making any network calls.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func prayerTimesForDate(date: NSDate, placemark: CLPlacemark) -> BAPrayerTimes? {
    guard let latitude = placemark.location?.coordinate.latitude,
        let longitude = placemark.location?.coordinate.longitude,
        let timeZone = placemark.timeZone else {
            return nil
    }

    return BAPrayerTimes(
        date: date,
        latitude: latitude,
        longitude: longitude,
        timeZone: timeZone,
        method: BAPrayerMethod.MCW,
        madhab: BAPrayerMadhab.Hanafi
    )
}

RamadhanSummary

After obtaining the necessary information, we could wrap one instance of Ramadhan as a struct named RamadhanSummary.

Data visualisation

There is nothing special on visualising Ramadhan as a graphic. We only need simple Core Graphic calls to draw coloured bars in the right width and shape.

Bar

An instance of RamadhanSummary is mapped as a UICollectionViewCell inside UICollectionView. The width of the bar depends on its fasting duration of fasting, whereas the positioning depends on the start time and end time of fasting. Just by using the correct scale, we could place and size the bar accordingly. We added ticks for every two hours to improve readability.

Colour

The colour of a bar also depends on its fasting duration, relative to the range of minimum and maximum duration from all Ramadhan in one place. Green (rgb(46, 204, 113)) for the shortest duration and orange (rgb(243, 156, 18)) for the longest.

We need two parameters to calculate the correct colour; the range of minimum and maximum fasting duration and a duration of a Ramadhan that we want to give a colour.

For example, London has a shortest duration of 14 hours and a longest duration of 20 hours. What colour should we chose for a 18 hours fasting duration of Ramadhan? To give a brief picture of what the question is, try to review the following sketch;

1
2
3
4
5
duration  14 |------------[18]--------| 20

red      46  |------------[??]--------| 243
green    204 |------------[??]--------| 156
blue     113 |------------{??}--------| 18

Below is how we calculate the colour in swift;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
extension UIColor {
    class func rmdn_colorForDuration(duration: Double, range: (min: Double, max: Double)) -> UIColor {
        let minColor: (Double, Double, Double) = (46, 204, 113)
        let maxColor: (Double, Double, Double) = (243, 156, 18)

        let r = minColor.0 + ((maxColor.0 - minColor.0) * ((duration - range.min) / (range.max - range.min)))
        let g = minColor.1 + ((maxColor.1 - minColor.1) * ((duration - range.min) / (range.max - range.min)))
        let b = minColor.2 + ((maxColor.2 - minColor.2) * ((duration - range.min) / (range.max - range.min)))

        return UIColor(
            colorLiteralRed: Float(r / 255.0),
            green: Float(g / 255.0),
            blue: Float(b / 255.0),
            alpha: 1.0
        )
    }
}

// colour calculation
let color = UIColor.rmdn_colorForDuration(18.0, range:(14.0, 20.0)) // rgb(177, 172, 49)

Saving UICollectionView as an image

After all the bar is placed and colored correctly, we need to convert all the bars as an image. We could transform a UIView as an image using Core Graphic’s UIGraphicsGetImageFromCurrentImageContext(). But be aware that we need to set its frame first as its contentSize, so that the whole collectionView is captured.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
extension UICollectionView {
    func rmdn_takeSnapshot() -> UIImage {
        let oldFrame = self.frame

        var frame = self.frame
        frame.size.height = self.contentSize.height // change before capturing the view
        self.frame = frame

        UIGraphicsBeginImageContextWithOptions(self.frame.size, self.opaque, 0)
        self.layer.renderInContext(UIGraphicsGetCurrentContext())
        let screenshot = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        self.frame = oldFrame // set the size back

        return screenshot
    }
}

After we got our collection view as an image, we save it in our document directory. The function returns a path to the image if saving is successful.

1
2
3
4
5
6
7
8
9
10
11
12
extension UIImage {
    func rmdn_saveImageWithName(name: String) -> String {
        guard let documentPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true).first,
            let image = UIImagePNGRepresentation(self) else {
                return ""
        }

        let filepath = documentPath.stringByAppendingPathComponent("\(name).png")
        let success = image.writeToFile(filepath, atomically: true)
        return success ? filepath : ""
    }
}

Result

Here are the visualisations of fasting duration during Ramadhan of London, Tokyo, Reykjavik and Wellington.

Ramadhan around the world

Summary

I really enjoyed using Swift as a data visualisation tool because there are many frameworks (either Apple’s or 3rd party) that we could use. Features like guard or collectionTypess functions made reading and writing code more joyful. To check the full source code, go to the repository on github.

In the future, it would be even cooler if we could run this code on playground or as a script. Currently, Core Location’s geocoding in simulator behaves differently than in playground or script. Once this is resolved, we should be able to run the same code from terminal.

Reference