Two of my favorite equations
Creating the US Popclock
I don’t remember when or where I first wrote this, but it’s still relevant, I think…though not as much with all the user interfaces libraries that do all sorts of weird math for you.
I started doing web development in 1998 as an idle passtime. In 2001 I became what I would call a “real” developer. HTML isn’t really a programming language at this point; instead it is a markup language designed to allow the computer to interpret plain text and spit something out that’s a little more meaningful. In 2001 I was doing development primarily in Flash using Actionscript, which I classify as a “true” programming language. I could do math, conditionals, and the rest. I became obsessed with developing user interfaces. Specifically, small, flexible components that could do interesting things.
That’s where this story begins.
One of the components I built was a scroll bar. This may not sound like a big deal, and even back then Flash came with a scroll view component. But, it was pretty big, all things considered (somewhere above 50 or 60 kilobytes). I thought I could do better. And I did. I created a full-featured capability that weighed in around 5 kilobytes, and it was applicable to so many other things.
Let’s talk about that.
A scroll view consists of two primary elements: a thumb and a track. These are something of misnomers. For a scroll bar, the “thumb” is the thing you drag, and the “track” is the visible area that constrains the movement of the thumb. For a document view, on the other hand, the “thumb” is the viewport (mask) and does not move, while the “track” is the content being viewed and does move. Consider the page you are reading now, chances are you have a scroll bar to the right that you drag up and down, and then you have part you reading that consists of a content view that is masked. When you move the scroll bar up or down, the content view moves up and down, respectively, and more importantly, in proportion to the view. When you scroll the content, the scroll bar adjusts accordingly.
I think we need a picture.
The view port and track (green) are considered constants of the universe in our context. The content view and thumb (blue) are variable. What that means is that the green things do not change size, shape, or position; however, the blue things do. What we are trying to do is translate the position (in this case along the Y axis) of the content view to the position of the thumb and vice versa. We will focus on the thumb to the content view for now. (I’m going to skip the math to figure certain things out for now, if you would like more details, please let me know and I will update.)
To do this translation we want to take the current Y position of the thumb compared to its maximum and minimum Y positions, relative to the constants (the green things), as a percentage.
Which brings us to the first equation:
percent = (target — min. value)/(max. value — min. value)
To break it down on what we’re doing:
range = max. value minus min. value
adjusted target = target minus min.value
percent = adjusted target divided by range
In the initial state the thumb will be at the top. The thumb’s X position will remain constant. We will use zero for the minimum Y position value, but it could be anything. We will calculate the maximum Y position based on the height of the track, less the height of the thumb (100 minus 10, for example, which is 90). Therefore, we get the following:
0 = (0–0)/(90–0)
Now, we want to use that to the content view what Y position to be in using that percentage, which brings us to the next equation.
value = ((max. value minus min. value) * percent) + min. value
To break is down on what we’re doing:
range = max. value minus min. value
adjustment to make = range * percent
value = min. value + adjustment to make
We will use zero for the maximum Y position value. We will calculate the minimum Y position based on the height of the content view, less the height of the viewport (500 minus 75, for example, which if 425) . Therefore, we get the following:
0 = ((425–0) * 0) + 0
Great; so, if the thumb is at zero, so is the content view, which is exactly what we’re looking for.
For those who can already see where this is going — here are the equations, put into methods for your convenience.
To handle the coordination between scroll bar and the content view, we will use an intermediary (controller). The thumb will tell the controller, “Hey, I moved!” and the controller will tell the content view, “Hey, move to this new position.” (This is sample code; so, architecturally would be different.)
And here’s where it starts getting interesting, and really starts showing the flexibility and power of these two equations.
What if we wanted to allow users to drag the content and have the scroll bar update accordingly? Easy enough. We just need to setup an event handler for grabbing the content and telling the scroll bar what to do; using our controller.
Now you have something like two-way-binding.
The height of the thumb within a a scroll bar is not static; how can we do that?
First we need the percent from the view port height compared to the content view height. Then we need to make the height of the thumb proportionally equivalent.
What about arrow keys? Not a problem, so to speak. Add an event handler to the scroll bar controller to respond to the arrow up and down event. Move the thumb whatever unit you need to, then notify the window controller via the
What about page up and down? Same thing, kind of. The number of “pages” is derived from either dividing the height of the content view by the height of the viewport; or, a little less reliably, dividing the scroll bar track height by the adjusted height of the thumb. When someone hits page up, move either the content or the thumb (or both) up or down by the height of the viewport or the height of the thumb, then notify the controller.
What about horizontal scrolling? Not a problem, just restrict the axis of travel for the content view and the thumb to the X axis instead of the Y. You can even make the content view and the thumb travel in the same direction instead of opposite directions; imaging a historically timeline where the slider controls a larger (or smaller) content view. If you’re feeling particularly daring, you can even have the content view be horizontal while the scroll bar is vertical, or vice versa. The capability is completely generic and has no idea about the outside world and its constraints.
Scroll wheels typically move the thumb. Gestures typically move the content.
What if I don’t want to (or more appropriately can’t) start at zero? Not a problem. However, I should not that the numbers passed in to the methods must be positive. Therefore, in some cases you will need to invert the value results by multiplying by negative one. In other words, saying “maximum” is something of a misnomer, because what we’re really talking about is the maximum distance of travel. 0 to 100 will have a max of 100. -100 to 0 will have a max of 100. And, -100 to 100 can be left as is, if memory serves (I’m not testing this as I go, I just remember it being possible).
You can even use the equations to animations using the timer to fire events.
When I worked on the update to the US Popclock, we used these equations, at least one of them to do so much. The progress bars aren’t HTML progress bar elements and their presentation is calculated using these methods. The speedometers use images for the rolling numbers and the position of each number is calculated using these methods. The age sex pyramid uses them for each of the bars. The ruler for the age sex pyramid is calculated in a similar fashion. Dragging the slider in the age sex pyramid. Hitting the play button to move the slide, which then updates the age sex pyramid uses them.
Anyway, back to the equations.
Seriously…if you’re creating user interfaces without extensive interaction libraries, these two equations are a must. Heck, even if all you’re trying to do is translate a known value from one context to another, these equations are invaluable!
Back to the world of developing working software. See ya’.