Saturday, October 30, 2021

zoom / fisheye effect in 1D

I've been messing around with my timelines displays, the somewhat self-absorbed problem of trying to cram an entire life (thus far) into a screenish-sized-space. After messing around with arch displays I'm back to linear... except I want to make it interactive, so like the time line expands where the cursor or finger is pointing, but then when you move out, it returns and you still get the "full life" effect. I think this kind of interactivity might let you get a better sense of time passing relative to a scrolling screen, and more engaging that just cramming everything to fit.

But I wasn't sure what a good "zooming" algorithm would be! I defined the problem pretty well: I can normalize every date value on a number line to a value between 0 and 1, and do similar for where the pointer is relative to the full width of the time line. Then the new Value function takes the normalized date value and the normalized pointer value and returns the new (normalized) position for the date.

But, I'm not too bright with this kind of stuff! My first thought was values near the pointer are greatly shoved, so I should multiply the difference from the date value and the pointer value by how close they are, so values further away barely moved from their original position. But that led to something very jumpy, since values right AT the pointer hop from one side to another at an almost infinite degree, or whatever the max value allowed is. 

To get to a better algorithm, I made a nice interactive display toy for it. The new algorithm says, take a weighted average of the current value and one of the endpoints (1 or 0, whichever is on the opposite side of the pointer) - if the pointer is far away from the value, the weighting is biased toward the original value, but as the pointer is closer to the value the average is weighted more toward that far away endpoint. I tweaked the display so each dot showed a line pointing to where it started from, and the height corresponding to the strength of the push:

So, that was better, but not great. Luckily, my friend Jon (the same guy who helped me with working with a hex grid) is much better at this kind of geometry and math stuff than I am. (He does graphic tool work for a very very major tech company and also, like, attends origami conventions.) He was able to use my display tool and realize what I was looking for was a sigmoid function, and he graced me with the following:

// A simple sigmoid function
const f = (x, m, k) => .5 * (x - m) / (k + Math.abs(x - m)) + .5;
// The normalized date
const x = val;
// The normalized date to zoom in around
const m = zoompoint;
// How much to distort, lower = more distorted
const k = .5;
// Normalize the output of the sigmoid function to [0, 1]
return (f(x, m, k) - f(0, m, k)) / (f(1, m, k) - f(0, m, k));

You can see the result here - I tweaked the k constant to .2, and added back the visualization that shows how strong the push is. 

No comments:

Post a Comment