I have an interactive tutorial about making interactive tutorials, showing how I made my line drawing tutorial. I wanted to describe how I made interactive diagrams in several other pages. The flow is:

Flow diagram: controls → input → algorithm → output → visualization

The controls and the visualization are what the user sees. The input, algorithm, and output are running behind the scenes.

Let's look at some examples.

Here's a diagram from my On the probability page, the controls are on the left and the visualization is on the right:

Screenshot of probability diagram
Screenshot from Probability of Damage Rolls

What's the flow? The controls let you change the parameters, and the parameters feed into the dice roll algorithm, which outputs an array of numbers, and that turns into a bar chart visualization:

Flow diagram for probability page

This is one of my earliest interactive articles. On each section of the page, I created the diagrams first, then if any of the controls were used, I would update all the diagrams.

My second example is this diagram from the hexagon page. The main controls at the bottom are the buttons that let you choose flat vs pointy topped hexagons, and hovering the mouse over a hexagon to highlight the rows and columns in the grid:

Screenshot of hexagon diagram
Screenshot from Hexagonal Grids

Changing either of the inputs means I need to redraw the diagram. However, I don't need to run both algorithms. I only run the algorithm necessary for that type of control. On mouseover, I only need to change the hex colors. On orientation change, I only need to change their position and shape, with some animation.

Flow diagram for hexagon page

This worked but it wasn't great. The mouseover was straightforward and didn't cause any trouble. The problem was the orientation flag. It affected diagrams all over the page. I had to keep track of which diagrams depended on the orientation flag, and update all of them whenever the orientation changed. If I added a diagram that depended on it, and forgot to change the code on those buttons, the diagram wouldn't be drawn when it should be. If I changed the diagram to not depend on orientation anymore, it would be redrawn when it shouldn't be. More on this in the next blog post.

For a third example, here are two side by side diagrams on the A* page. One of the controls is a map editor that lets you change the input graph (wall, forest, desert). The other control is the slider with buttons, which changes the input slider position and the input state of the animation (on/off).

Screenshot of pathfinding diagrams
Screenshot from Introduction to A*

Both diagrams use the same input graph and slider position, but separately run graph search. The play/pause button starts/stops a timer, which updates the slider state. The map displays the current map state, and the contour lines show the result of the graph search algorithm. This is getting complicated! And there are even more complicated diagrams on the page.

Flow diagram for pathfinding

I had learned my lesson from the hexagon page: it's error prone to manually link everything up, and I should write an abstraction for it. I used the Observer pattern. Both diagram objects register interest in the map state, and then whenever the map state changes, it notifies the diagram objects. That worked nicely.

However, as I added more features it still felt like I was manually linking things up. I ended up redrawing everything any time anything changed.

So let's get back to the original diagram:

Flow diagram: controls → input → algorithm → output → visualization

What are the main things to figure out?

  1. How do I update the visualization?
  2. Which algorithms do I need to run again?

The easiest answers:

  1. Erase everything and draw from scratch!
  2. Run the algorithm again each time! Either every frame or every mouse move.

I like easy. I recommend starting with this approach. Some of my pages were too slow when I did this, so I had to use more complicated approaches. Ideally:

  1. Reuse as much as possible of the previous visualization output for the new visualization output.
  2. When one of the inputs changes, run as little as possible (sometimes only parts of the algorithm have to run again)

However, this is a lot of work to implement manually. In the next post I'll describe the libraries I've been looking at, and what approaches can solve these problems.

Labels:

0 comments: