Sunday, April 12, 2015

AxesDrawer.swift

I read once (Dave Winer?) that if you’re programming you should blog about what your doing so that when, in the future. you can’t understand what you did, there’s somewhere to turn to. My motivation is slightly different, I’d like other people to be able to find what I’ve done, and its seems to me that blogging about it will serve that need. As background, I’m following the Stanford University CS193p course (““Developing iOS 8 Apps in Swift “) on iTunesU, and I’ve found that one of sample pieces of code that Stanford hand out is bugged. I’ve failed to find a way of reporting the bug and now that I’ve fixed it, I thought it would be friendly to blog about it.

As part of Assignment III: “Graphing Calculator” you are given the code for an AxesDrawer class to use as part of the project. The AxesDrawer class provides a function 

   drawAxesInRect(bounds: CGRect, origin: CGPoint, pointsPerUnit: CGFloat)

which draw, and label, those portions of the x and y axes with origin “origin” which sit within the bounds “bounds” of the current view. 

The first thing to state clearly is that the code as supplied by Standford works functionally; if you use the code it draws axes correctly. I did not find the problem from using the code, only from reading the code to try and understand how the code worked. 

The tricky part of the code is labelling the axes. The code has to decide the x (or y) increment between labels, and it then has to draw all the labels which are visible. The decision about the increment between labels is made on the basis of the size of the labels and the current scaling of the axes. The code computes the increment in terms of the number of points (drawing elements) per label. The variable pointsPerHashMark is used to hold this value. 

The mechanism behind the labelling of the code is straightforward. I’ll first explain it with the assumption that the origin sits within the bounds of the current view, then I’ll explain how it works when the origin is not within the bounds. The idea is that the code works from the origin outwards. The first set of potential labels are at (pointsPerHaskMark, 0) and (-pointsPerHaskMark, 0) on the x-axis, and (0 ,pointsPerHaskMark) and (0, -pointsPerHaskMark) on the y-axis. The second set of potential labels are (2*pointsPerHaskMark, 0) and (-2*pointsPerHaskMark, 0) on the x-axis, and (0 ,2*pointsPerHaskMark) and
(0,-2*pointsPerHaskMark) on the y-axis. And so on. Of course, at given distance from the origin, not all of the labels may fall within the bounds, and so the code checks whether each potential label falls within the bounds before plotting it.

   if let leftHashmarkPoint = alignedPoint(x: bbox.minX, y: origin.y, insideBounds:bounds)
   {
      drawHashmarkAtLocation(leftHashmarkPoint, .Top("-\(label)"))
   }

Eventually, at some distance from the origin, all of the potential labels fall outside the bounds, and the labelling can stop. The way the code controls the loop which performs the labelling is interesting. Initially the code sets up a rectangle (CGRect) whose centre is at the origin and whose x and y dimensions are pointsPerHashMark * 2; that is the bounds of the rectangle (in fact a square) are +/-pointsPerHashMark.  

   var startingHashmarkRadius: CGFloat = 1

   …

   // now create a bounding box inside whose edges those four hashmarks lie
   let bboxSize = pointsPerHashmark * startingHashmarkRadius * 2            
   var bbox = CGRect(center: origin, size: CGSize(width: bboxSize, height: bboxSize))

On each iteration the size of the rectangle is increased to (by 2*pointsPerHaskMark in each dimension)) so as to include the next set of potential labels. 

   bbox.inset(dx: -pointsPerHashmark, dy: -pointsPerHashmark)

The code detects termination of the labelling process by testing whether the view (area to be plotted) sits entirely within this rectangle. Once it does, any further potential labels must sit outside the view and so do not need plotting.

   while !CGRectContainsRect(bbox, bounds)

Now lets looks at what happens when the origin is outside of the view. The mechanism used in the code will work, albeit inefficiently. Because labels are only plotted if they are in the view, no spurious labels are plotted. But more interestingly, if we start labelling from the origin outwards, and use the code’s termination test, all the necessary labels will appear and the code will terminate. As the size of the rectangle (bbox) increases on each iteration, it will eventually contain the view, at which point all labels will have been plotted and the loop will terminate.   

So to efficiency. The first potential inefficency is do any work when there are no axes to plot. This is dealt by a simple test of the start of drawAxesInRect

   if ((origin.x >= bounds.minX) && (origin.x <= bounds.maxX)) ||
      ((origin.y >= bounds.minY) && (origin.y <= bounds.maxY))
  
The second inefficiency occurs when there is an axis to plot but the origin is outside the view. Suppose the origin is at (-100, 0), the view has a minimum x value of 0 and we have decided that labels will be plotted every 10 points. In this case the view will contain a portion of the a-axis starting at 100 with labels at 100, 110, etc. If the code does nothing to deal with this case, it work out from the origin and try (and fail) to plot labels at distances of 10, 20 and up to 90 points from the origin until it starts to plot at 100 points distant from the origin. An optimisation that can be done is to detect the case that the origin is outside the view

   if !CGRectContainsPoint(bounds, origin) {

and then determine how far the first plottable label is from the origin. This is what the code attempts to do and where the bugs occur.

   let leftx = max(origin.x - bounds.maxX, 0)
   let rightx = max(bounds.minX - origin.x, 0)
   let downy = max(origin.y - bounds.minY, 0)
   let upy = max(bounds.maxY - origin.y, 0)
   startingHashmarkRadius = min(min(leftx, rightx), min(downy, upy)) / pointsPerHashmark + 1

The first bug is that one of left, right, downy or upy must be zero, hence min(min(leftx, rightx), min(downy, upy)) is also zero and startingHashmarkRadius is 1. To see that one value must be zero, without loss of generality, consider the case where the x axis will be plotted. Again, wlog, consider the case where the origin is to the left of the view. In this case origin.x - bounds.maxX will be negative and hence leftx will be zero. I won’t speculate about why the code got written like this, but unless testing beyond simple functional checks were done, this bug wouldn’t be caught.

There is a second bug also present which I ran into when I fixed the first one. I’ll leave it as an exercise until later.

Reusing the case above, for the x-axis, origin.x - bounds.maxX will be negative, but bounds.minX - origin.x, the distance of the origin to the view, will be positive. Looking at the y dimension, bounds.minY - origin.y will be negative, as will be origin.y - bounds.minY since the y-coordinate of the origin sits between the min and max y-coordinates of the view. The distance of the origin from the view is, therefore, the one positive value amongst the four. Can can therefore, take the maximum of the four values. Accordingly my first attempt at a fix was 

   let rightx =   origin.x - bounds.maxX
   let leftx  = -(origin.x - bounds.minX)
   let downy  =   origin.y - bounds.maxY
   let upy    = -(origin.y - bounds.minY)
   startingHashmarkRadius = (max(max(leftx, rightx), max(downy, upy) / pointsPerHashmark) + 1

When I used this version the effect was weird. I’d pan a view, pulling the graph and axes across the screen until one axis went out of bounds. At this point the other axis stopped panning (although the plotted graph continued panning) but the labels incremented each time I had panned by more than the distance between two labels. This was the second bug. The problem is that the code computes the distance of the origin to the view, whereas what is needed is the distance of the origin from the first label. The fix is to the floor of the division to get an integral number of labels and then to add 1. So, the code which fixes the second bug looks like

   let rightx =   origin.x - bounds.maxX
   let leftx  = -(origin.x - bounds.minX)
   let downy  =   origin.y - bounds.maxY
   let upy    = -(origin.y - bounds.minY)
   startingHashmarkRadius = floor(max(max(leftx, rightx), max(downy, upy) / pointsPerHashmark) + 1

Saturday, April 11, 2015

How to justify an Apple Watch purchase



I'm a watch wearer. I have a nice electromechanical watch which my wife gave me. It keeps time well, it shows me the time, the day of the week and the day of the month. It harvests energy from movements of my wrist and in the time I've owned the watch it's never run down. It has a nice enough dial, it is a little thicker than I'd like - it doesn't slip under shirt-sleeve cuffs as easily as it might, but all in all I'm very happy with it. I wear it from when I put it on in the morning until when I take it off at night - and it comes off when I'm bathing, exercising in the gym, or swimming. At night it sits on my bedside table.

I have an iPhone (5S). I have it on or about me most of the time. At night it sits next to my bed charging. I use the phone for the usual things, checking the news, sending and receiving messages, tracking my travel and my fitness, listening to podcasts and music. It would be better if the screen were larger, the size were smaller and the battery lasted better (yes, I see the conflict). Overall I'm pretty pleased with the phone.

I don't have a (working) heart monitor, nor a "fitness device" like a FitBit. I used to use a heart rate monitor when I started out doing exercise (mountain biking and going to the gym). I found it useful - it helped me learn to pace myself when biking. At one time I was using a gps to track my biking route; when I got home I'd correlate the route with the record from the heart-rate monitor, a laborious process at the time. I'd like to get a heart rate monitor which interfaced to my phone properly.

The phone tracks movement quite well. The problem is that I don't have the phone on my person when I exercise in the gym. It's with me so I can listen to whatever (worthy podcasts) while I exercise, but I don't wear it. As a result, the phone thinks I'm immobile while I'm pounding the treadmill, rowing, cycling, or whatever. I keep track of the time and "calories" that the gym machine report and dutifully record them in MapMyRide. However, this experience could be improved. It would be great not to have to take the phone with me into the gym, it would be great to have all the recording done for me, and it would be good to have my heart rate recorded.

Clearly an £8,000 Rose Gold Apple Watch Edition (with sports band - off course) is just what I need.... or perhaps not. But a £300 aluminium Sports Watch (38mm) just might be. Or maybe it should be the 42mm. And which strap? So off to the Apple store to find out. There seems to be a lot of interest in making try-on appointments, the Apple Concierge website crawled up and down (not as badly as did the Kate Bush pre-order website), and I eventually ended up with an appointment at 9:15am. 

I rolled up just after nine, was greeted on my way in, introduced to the guy who would show me the watch. The Apple store had a couple of tables devoted to the watch. One was a show case partly populated with an assortment of watches including a few Editions. The other was used used for the try-ons; it has a number of drawers under the table top, unlocked by the demonstrators' check-out device. I told my Apple guy that I was interested in the Sport model, I wasn't sure which size watch I wanted, and I'd like to see both the black and white straps. "No problem!" except there was; he couldn't open the drawer and had to seek a colleague's help. I tried on a 38mm white and a 42mm black. The rubber straps felt great and comfortable, nice and warm. Even though I'm sure that white (Ive-ory?) is the preferred colour, I think white always looks discoloured (an effect of ageing?) and so avoid it if possible. I must say that the white looks pretty good but it isn't discrete, so black it is for me; if I wanted my watch to stand out I'd go for pink or green. Then to size. The 42mm is about the same size, maybe slightly larger than my current watch, the 38mm seemed better. So, decision made, if I get a watch it will be the 38mm space grey aluminium with black rubber strap. "Can I take a look at the stainless steel ones as well?"; "Sure". The straps are great, really nicely engineered. The Milanese Loop is gorgeous, the Link Bracelet very butch and the Modern Buckle wonderful.

Function? Good question. The try-ons were running a demo loop; I can understand why display models should run the loop, but the try-ons would be better if they were running the actual watch software. With the loop I couldn't tell how well the display auto-on worked, nor could I really tell how good the watch faces looked on my wrist, nor could I really tell how easy the device was to operate on the wrist. There were some mounted watches running the standard software which I used. The force-touch seems easy to use, the digital crown might be a bit fiddly but I expect that with practice it gets to be easy. The display is very bright, there is some control available, I suspect I'll want the brightness down and the colours muted a little. The watch faces looked good; the home screen flowed really well, but one or two other things seemed a bit slow. I don't think it's possible to get a feel for how well a complex device like this works until you've lived with it - it wasn't obviously broken.

After my try-on I took a look at the display case. The Rose Gold looks good! So do the bright sports straps - perhaps I should get one as an accessory if I buy a watch?

And the if? I decided to order but shipping wasn't until June. So I didn't; I wanted instant gratification. But I placed an order that afternoon.

Finally - the new MacBook. There weren't any in the store. 


Wednesday, January 14, 2015

Tuesday, December 16, 2014

Book Hive

2014 saw "Book Hive" in the City Library here in Bristol. I don't know why I didn't get round to blogging about this at the time but better, late than never. This is a short video I put together from sequences taken at the library and a Dorkbot event where the artists talked about how the installation was put together.


Thursday, December 04, 2014

Pomplamoose

Pomplamoose are an interesting indie band. Some of their covers are great, some of their original songs are great, and some not so much to my taste. But they are nearly always entertaining and I really like the way their videos often expose the way way in which they are put together. The Pharrall Mashup (Happy Get Lucky) is a case in point.


I also find Pomplamoose interesting because they are an example of an indie band as a small business. Jack Conte (the half of the band that isn't Nataly Dawn) recently exposed the economics of their latest tour with this article on Medium.

Thursday, July 31, 2014

A Rainbow in Curved Air

Last night saw me in Bristol's Old Vic theatre for the third night in a row, this time to see and listen to a performance of Terry Riley's "A Rainbow in Curved Air" by Charles Hazlewood's All Star Collective and Danceroom Spectroscopy. I'd seen the All Stars perform the piece last year - in fact, embarrassingly, their performance was my introduction to this seminal work - and was keen to hear it again, and whatever else they'd play in the concert.

Charles Hazlewood educates the audience prior to the performance
I wasn't disappointed; not at all. Charles Hazlewood introduced the piece with a short lecture about the nature of minimalist music and explained that the night's performance would follow the form of Rilley's piece but would be improvised and that the musicians would be interacting with Danceroom Spectroscopy's visuals.

Charles Hazelwood, Will Gregory, Ross Hughes 
And what a performance it was. Danceroom Spectroscopy' visuals were spot on, a great blend of abstract, photographic and algorithmic, and I enjoyed seeing the band members interacting with the visuals. Sonically the performance was grounded in the late 1960s with parts coming straight from Rilley's original and parts sounding like they'd been sampled from the more experimental parts of Pink Floyd's The Piper at The Gates of Dawn or from the first two Soft Machine albums. The piece became very free-form at times, and just as it seemed on the edge of falling to apart Tony Orrell's drumming would bring structure back into the piece and bring the audience to edge of their seats. 

Overall a great evening. I hope someone has recorded it and a copy finds its way in to my hands. The Pit at The Old Vic was a great place to see the performance from - although the next time I'll opt for standing rather than sitting. The only other thing I'd change would be to project the visuals over the whole performance area rather than on a screen - then we'd really be back in the 60s. 

Search This Blog

Loading...