Arrows (At Length)


Love linking lines? Join me in considering the humble arrow as both a programming problem and a user experience challenge. In this talk, I'll present the many complex problems and solutions that I've encountered when working with arrows in my open source library perfect-arrows and later in tldraw ( With a narrow scope and plenty of animations to guide the way, we'll look at intersections, arrow heads, arrow head angles, and all of the different user interface issues behind authoring and adjusting arrows between shapes, other shapes, and points.



Hello and welcome to my talk, Arrows at Length. So my name is Steve Ruiz and I am on Twitter at SteveRuizOK. You can follow me using this QR code and if you like arrows, then you should probably follow me or if you like other kind of visual algorithm stuff. I'm the founder of a, well, it used to be a side project, now it's a startup called Teal Draw. And Teal Draw is a, it's a tiny little drawing app. You can go to it at and I'll show you what it looks like. It is, like I said, kind of a drawing app, diagramming app. You could use it for collaborative whiteboarding. It's free. It's file-based and kind of local first. And we are working on the new version of that app, which is kind of designed more to be a developer product platform that you can build these types of apps with. So if you have an idea for a spatial canvas, a kind of a Figma for X type of an app, then you could potentially build that on top of Teal Draw rather than starting from scratch. And it's pretty good. You get a lot for free. So who knows, maybe I'll be back next year and I can talk about that. But for today, yeah, we're going to be talking about Teal Draw and specifically we're going to be talking about arrows. There's a lot that we could talk about, like we could talk about the minimap or the digital ink. Both of those things are super complex and interesting, I think, developer stories to tell. But instead, we're going to just talk about the arrows. And I've spent a lot of time working on arrows. And if you've used Teal Draw, you might notice that the arrows are special. They're considered. Yeah, there's a lot of different ways that you could do an arrow. I'll show you the ways that we do it and also a little bit of background on why and why we don't do it a different way. So let's jump into it. And let's say hello to arrows. Arrows, whatever. What is an arrow? An arrow is a line. It's specifically it's like a linking line. It's linking two different things. It could be linking two points in space like we see here. Could be connecting two things like two shapes like these two rectangles. Could also be connecting a shape and a place or a place in a shape or a place and a place inside of a shape. So the idea of indicating a specific area of a specific thing rather than just the thing itself. Most arrows are directed in the sense that they go in a certain direction. And we indicate that direction with an arrowhead. It can go whatever, this way, that way, both ways. And arrowheads can also be used in different kind of diagramming frameworks to represent different things. So maybe you have like a graph of entities or servers. Different arrowheads could be used to represent different relationships between those two things. So they can also be labeled. Labels could look like this or like this. Kind of hard to get it right like this. So I do it like this. And then the arrows themselves can also have different shapes. So you don't always want a straight line. Maybe you want some sort of elbows. Maybe you want some curves along those elbows. Maybe you want an arc or a spline. And the place where this really gets complicated and interesting is when you consider how users author these types of arrows. How do you adjust an elbow line or a spline like this? That's where it really becomes a little tricky, as we'll see. Most arrows and most diagramming tools connect to specific things called connectors. There are specific places on either side of the shape where the arrows can connect. So let's call them northeast, southwest. In this case, we would have east connecting to west. That's pretty straightforward. You want to connect certain connectors to each other. You don't want situations like this where you're west, west, based on the layout of the two shapes. That doesn't look right. Instead, you probably want to have a line segment going between the two kind of anchor points of the two centers or whatever. See which edges those break or intersect with, and then use the connectors on those sides. And that'll work whether the shapes are different sizes or whether they're positioned in different ways. That's a pretty good rule for which sides to use. But even then, you might want to complicate things. You might want to mix it up a little bit and have elbows, so right angle arrows. Or maybe you want to kind of turn that into some sort of quadratic curve. Feel free to do so, but you'd still need to pick which connectors to connect. It's not always this. This is maybe like an L-shaped arrow, but you will also run into, if you go down this route, you'll run into C-shaped arrows and I-shaped arrows. So places where you want to, maybe if the shapes are directly in line, then you want to connect the two closest points. If they are in line, but they are like too close to each other, then maybe you want to connect the points off to the side. So you end up with this like C-shaped. And then yeah, if they're kind of like this, then you end up with like an L-shape. There are a lot of permutations to this and a lot of rules about when to pick which connector, much less where to pick, which elbow to pick and all that. It gets pretty gnarly and it also doesn't work super well in every case. You get these like edge cases, which is literally like at the edge of a decision space where it hasn't moved just far enough in order to trigger some other kind of like decision route, like, okay, let's connect to different side of arrows or connectors. But it's still wrong, like where you have, for example here, like the short end of the elbow, the arrow, elbow arrow, which is shorter than the arrowhead itself. And that'll come up in a lot of apps where this type of thing is used. It's also, you can kind of, especially if you have users authoring their own arrows, you get in situations like this where you want to preserve user decisions no matter what they've done. Sometimes that's, I want this to connect to this other shape, but if you have users editing the arrow as well, they may have pulled this side of the arrow off to the right. And then when you move the arrowhead into another shape or move that shape that it's connected to and you preserve that right arrowhead or the segment of the arrow that the user has moved, you end up with like arrows that look kind of goofy. We've used an app called Whimsical. Whimsical is a fantastic app, a wonderful set of, well, different utilities, really a couple of apps in one. But it's arrows, you can get them into some pretty weird states by doing this, by editing the arrow and then also connecting it to something. So it's a little unpredictable. Finally, connector arrows or using connectors, there's really no story for I want to connect this inside of this other shape. Normally yeah, if there's no connector there, then you just can't connect to it. And so the sort of the place in the thing story gets a little hairy. And also if the shape is irregular, then connectors can be a problem, namely because the connectors are, you know, Northeast, Southwest, they are based on the kind of the bounding box of the shape. If the shape is a box and that's perfect because it corresponds, if it's not a box and if it's not a circle and if it's not like equal on all four sides, then you get in situations where yeah, they're just kind of hanging out in space. The more organic the shape, the less the connector seem to actually connect to the shape. So what's an alternative? That's right. The alternative is intersections, or at least this is one alternative. This is the one that we ended up going with, with Tildrop. So here's how it works. If you can think of an arrow again, as kind of a line between two anchor points, maybe it's the middle. Let's think of that more as like a line segment that we can intersect at a specific place with the two shapes. So rather than just paying attention to which edge is broken, we actually look like, okay, where did this intersect with that shape? And then we just draw the line, the arrow in between those two points. Easy. So that looks fine. It looks a little bit less fine for the arrow head, the end of the arrow, rather than the beginning of the arrow. So what I did in Tildrop is that if there is an intersection and that side of the arrow has an arrow head, I kick the arrow head back a little bit. You can think of this as like intersecting against the expanded bounds of the shape, but it's not really. And I'll tell you why in a second, but this is what you want. This is good. I said we're not actually expanding the bounds and then intersecting with that. We're just kicking the arrow back the way it came, the direction it came. And the reason for that is that with more complicated shapes, yes, you could do the bounds offsetting stuff. You could do this. It's a little tricky. And the more organic a shape gets, the trickier it gets. You do not want to do Bezier curve offsetting just in order to get your arrow head to be 12 pixels back. You just reverse the arrow, push it back. It's easy. So don't do this. Just do this. Anyway, what you end up with is an arrow that points at the right place, intersects with the right place, and is pretty close to the type of arrow you would draw if you were drawing this yourself. And with these sorts of graphical algorithms and such, that's really what you're going for is to try and figure out the logic behind an aesthetic decision that you would have made as a designer or just if you had time to design every frame of that or draw every frame of that yourself. Sometimes those rules are a little tricky, but that's the goal is to get something that would be like, yeah, I would put the arrow there. If I did this myself, I would put the arrow there. So we talked about different types of lines, different aesthetic types of lines, and we want to get into curved lines. But before we do get into curved lines, let's also point out that this algorithm that we just talked about where we find the intersections, draw the arrow between that, kick the arrow head back a little bit, that works for the place in a thing. It also works if the shape moves and resizes. It'll still point towards the right place, the right anchor. And as we'll see, even with curved lines, the arrow will still work and it'll still work in the same situation if things resize or move. So let's talk about curved arrows. Curved arrows are a little tricky, and I'll show you why. So in tealdrawv1, the current, the way that we do curved arrows is we have handles at the start and the end, and we can use a middle handle, which sort of travels along the same distance, the same length of the arrow, but just sort of perpendicular. And it can travel along that. You can think of it kind of normalized at negative one is all the way to the left and one is all the way to the right. So you can grab the middle handle and pull it up to the top. That would be negative one, bendiness. This would be one bendiness. This would be like 0.5 bendiness. And either way, that's the way that you kind of author the curve. And the problem is that the curve doesn't look right when it's connected to two different shapes. I wouldn't draw that. And what's happening is while this algorithm that we had before, right, the little series of steps that would give us our straight arrow that works for straight arrows, but doesn't really work for bendy arrows. And I'll show you what that looks like exactly. And I'll show you why as well. But this is like what looks like. This is the arrows on Looks fine. Looks pretty good. But the arrows are kind of further apart. It doesn't look right. So let's talk about better curved arrows. What would be better? If our straight arrows look fine and our straight arrows are used to find the straight arrow, we use a straight line. We intersect that with the two shapes. But it doesn't work well with our curved arrows because we're still using that straight line to find the points where it intersects. We don't want to do that. We want to use a curved line to find where that intersects with the two shapes and then base our arrow on the body of, you know, space between those two intersections. So in other words, the model that we had before where we had the two handles of the arrow, the start and the end handle, and then the middle one, we were placing the start and the end handle at the intersection point. But instead we are going to put those handles at, you know, the whatever, the actual anchor point wherever the user has pointed the arrow. So rather than putting them at the actual arrow head, we're going to put them at the anchor point and still draw the arrow in between the two intersections. But the handles are no longer at the arrow head itself if it's bound to a shape. And what that allows us to do is create the arc between those three points. And then our arrow looks correct. We can draw the arrow as part of the body of that arc. And that still works if we are repositioning the end handle. So again, here we're not grabbing on the arrow head. The handle is kind of over here. We can still drag it and it'll look correct. The rest of the arc will happen automatically. We can also grab the middle handle in this situation. And then rather than having it just travel up and down along a path, we really just want to look at the distance between the handle and the midpoint of the line or cord between the start and the end point. And that's rather than like represented as like a normalized value, like a negative one to one, that's like an actual distance. We're going to preserve that actual distance. So based on those three points, we can get our arc and then we have our arrow. Let's look at what that looks like. Much like before, I have a straight arrow, but this time the handles are kind of in the middle of the shapes and I can still decide where the arrow points. And I can adjust it. Looking good. Even as I move things, that's what you want. And really the important thing is that no matter what I do, the arrow is still looking good. If it's almost straight, then I just make it straight. So let's talk about how we actually do this. What's the math involved and how do we find all these points? Because yeah, there's kind of a lot going on here in terms of like programmatically putting these things in the right place and creating this curve and creating the arc and all that and doing the sweep flags and long flags, all that. So you may remember from high school, I kind of missed this in high school, but I learned it late. If you have three points, you can make a circle. In our case, we have three points, the start and end of the arrow and the kind of the middle handle of the arrow. And that lets us make a circle. Any three points, as long as they're not in a perfectly straight line, can be used to create a circle. If they are in a straight line, then that doesn't work, but you get the idea. We intersect the circle against the first shape in order to get these two intersection points. And then we pick the intersection point that is closest to the middle handle and that becomes the start of the arrow. We repeat for the second shape, get those two intersection points, find the one that's closest to the center handle, middle handle, and that's the end of the arrow. Much like before, we want to push that end point back a little bit if it has an arrowhead, in which case we kind of get the length of the arc segment and however far we want to push it back from, et cetera, et cetera. Like, yeah, we just do it. And that we have all the information that we need really to draw our arrow. We have a circle, we have a center of the circle, we have a start and an end point, or at least the start and the end angle, which we can use with the radius to find the point on the circle and that gives us the body of our arrow. And all we have to do is smack an arrowhead on that and we are done. Perfect. It gets a little further with interactions. Because again, when we drag an end point, we still have three points, but we also need to move that middle handle. And to do that, we get the distance that we had before, the sort of like bend distance, the distance between the handle and the midpoint to that line between the two start and end shapes. And then when I drag this one, before I actually move this handle, I can find the midpoint and then apply that same distance at a perpendicular and that's where we put the middle handle. Sorry, this slide doesn't really communicate that. Either way, that's how we move the start and end points. When we move the center point, same thing as before, is that we've changed that bend distance, but that bend distance is what allows us to create the circle. Like we have, or at least drive the position of those three points, the middle point. So now we've changed this bend distance. We do want to put the middle handle back in the middle. Next time I select this thing, I'm going to expect the middle handle to be in the middle. So there's one last step that we have to do, which is to find the middle of the angle to the start, the angle to the end, get the middle angle of that. And then that's where the handle is going to be next time. And then we have our nice arrow that we've adjusted by dragging the middle. So I'll show you what this actually looks like. And I've kind of added some debug information here so that we can actually see it happening. As I move the two shapes, that's moving the start and end point to the handle. We're then using the kind of the bend distance to find the middle point of the handle. So finding the middle of the chord between the start and end, and then offsetting it by that bend distance at a perpendicular, that gives us the end handle, which allows us to create a circle, which allows us to create an arc, which allows us to create our arrow. And that is how much better looking arrow is born using intersections based on that circle. So in the end, you have an arrow tool in TealDRAW, which is pretty good. Like I said, when this works, it works because whatever you do, it's going to look good. And TealDRAW, as a kind of a diagramming tool, I suppose, is all about giving you a limited set of options that will always look good no matter what you do. Try as you might, it'll still be good. There are other things involved in arrows that we haven't talked about. We haven't talked about these arrowheads, like these little dots here that you see. We haven't talked about how we kind of pick the anchor points for shapes, although it's pretty straightforward. If you can guess, then you probably have the right answer. We haven't talked about the kind of the complex state management involved in making sure that the arrows update at the right time based on what's changed. It's also pretty gnarly. But yeah, that is arrows in TealDRAW. I feel like the hard parts, at least. So thanks again for watching. You can try out our arrows in two places. First, of course,, where you can try out the older set of arrows that I showed, including the kind of the bendy ones that didn't really bend the way that you'd expect them to. But since you have been kind enough to watch this talk video, you can also check out the new version of our arrows that are kind of, it says beta, it's more like an alpha. This is where we're building the new version of TealDRAW. And you can check that out, Try out both arrows. Let me know what you think. I hope it's an improvement. And once again, I'm Steve Ruiz. Follow me on Twitter. Lots more arrows content and lots, just a lot of tweets. So thank you. Take care. Bye.
24 min
24 Oct, 2022

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Workshops on related topic