Find one solution to many problems

I’m working on a graphical editor in WPF. One feature of this editor is the ability to resize an object. As cool as that feature is, the comment I got from my partner on the project was even better.

In this editor, you can drag the mouse across the surface to pan. You can use your scroll wheel to zoom in and out. And you can select an object and use your scroll wheel to resize it. The trick is figuring out just how much to resize with every tick.

When the scale is zoomed out, you want each tick of the mouse to adjust the size by large amount. When the scale is zoomed in tight, you want to adjust by a proportionally smaller amount. This gives the user a chance to fine-tune by zooming in.

image

image

But on the other end of the spectrum, you have to prevent the size of the object from reaching zero. So when the object gets small, it no longer shrinks in proportion to the zoom.

image

Design an equation
The first problem is to change the size in proportion to the zoom. The easiest way to do this is to convert the object size into screen size, adjust by a constant amount, and then convert back. Here’s the math to do that:

  • Screen size = actual size * zoom factor
  • New screen size = screen size + ticks * pixels per tick
  • New actual size = new screen size / zoom factor

The second problem is to switch modes when the object appears small on the screen. Instead of changing screen size by a fixed number of pixels per tick, you want to change by fewer pixels as the object gets smaller. This way you will approach zero but never actually reach it.

The key to solving this second problem is that idea of approaching a target without reaching it. In mathematical terms, that target – or limit – is an asymptote. We want to design an equation that asymptotically approaches zero as we scale down. But at the same time, we want to asymptotically approach a constant growth as we scale up. Here are the two asymptotes:

  • y = 0
  • y = x

The first asymptote (y = 0) makes the size of our object to approach zero but never actually reach it. The second asymptote (y = x) makes the size of the object to increase by the same amount each time we tick the scroll wheel. Let y be the screen position, and all this happens relative to the zoom.

To design an equation with asymptotes, simply multiply to make each one a root of the equation:

  • (y – 0) * (y – x) = 0

The first factor represents the first asymptote (y = 0). The second represents the second (y = x). We just subtract one side from the other to turn the equation into a root (i.e. y = x, y – x = 0).

Study the equation
image Plug this equation into Wolfram Alpha and it tells you that it is a pair of intersecting lines. We already knew that. We need to pull back from those two lines and see what happens. Let’s change the equation to this:

  • y * (y – x) = 9

Now Wolfram Alpha tells us that it is a hyperbola. We can visually verify that it approaches zero on the left, and it approaches a 1-to-1 slope on the right.

We can also see that the equation crosses the y axis at 3. This is about where we “switch modes” from a big object to a little one. And finally, we can see the solution for y:

  • y = 1/2 (x-sqrt(x^2+36))

With a little work on the whiteboard, we can confirm that the numbers 3 and 36 are related to the arbitrarily chosen constant 9. The y intercept (3) is the square root, and the constant (36) is quadruple. With this knowledge, we can adjust the equation to “switch modes” at any screen size.

Complete the algorithm
With a little more help from Steven Wolfram, we can find the inverse of this equation. With that, we turn this equation into an algorithm for determining the new size of an object:

  • Screen size = actual size * zoom factor
  • Starting x = (screen size^2 – small object^2) / screen size
  • New x = starting x + ticks * pixels per tick
  • New screen size = 1/2 (new x-sqrt(new x^2 + small object * 4))
  • New actual size = new screen size / zoom factor

And with that we have one algorithm that operates one way on small objects, and another way on large objects. And it allows the user to fine tune by zooming in.

My partner’s comment
I walked through this code with my partner, and explained how it solved three problems at once. His reaction was, “You used math where I would have used an if statement.” I think that was the most telling result of the whole exercise.

When we write code using discrete concepts like conditions, we apply brute force. We decide exactly where the solution changes from one mode to another. We force a corner into the solution. A singularity at which the behavior of the system changes violently. Bugs gather at such singularities. And those corners poke the user in the eye, even if he can’t put his finger on them.

But when we find one simple, continuous, elegant solution to many problems, we allow that solution to take its own form. It emerges naturally from the problem space itself. The benefit is not simply more beautiful code. It is also fewer bugs, and a more pleasant user experience. So when faced with a multitude of problems, put them all together and see if a single solution emerges.

Leave a Reply

You must be logged in to post a comment.