How we made line jumps šŸ‡

Andrey Ozornin
Miro Engineering
Published in
12 min readFeb 20, 2024

In this story weā€™ll guide you along the whole development process of line jumps in Miro: a feature that might seem insignificant at first glance but is essential for diagramming. Likewise, its implementation seemed trivial at first but a closer look revealed many layers of hidden complexity. Weā€™ll show you a real example of what development process looks like in Miro, what precedes it and what goes after. Weā€™ll tell how decisions are being made along the way and how we think about things we do in terms of business, product and technology.

Animation of connector lines editing in a flowchart diagram

Martina was plotting an electrical diagram in Miro the other day but struggled with line crossings. The intersections were ambiguous: are the wires connected to each other or they just pass by? In different schematic conventions the plain cross has different meanings.

Two connection conventions: with jumps and with dots
Source: ā€œAll about Circuitsā€ EE Reference

To get rid of the ambiguity, Martina decided to add a ā€œjumpā€ in the intersection point for the bypassing wires but couldnā€™t find how, so she went to the Miro community forum to ask.

Hi Community! I would like to know if there is a way to easily create electrical block schemes with Miro. I need just basics shapes connected together, what I am currently missing is some utility like the possibility to create bypassing cables like this one:
There ia no easy way I know of to accomplish this. There is even a wish list idea post requesting this feature that you will likely want to upvote:

She was by far not alone. The ā€œLine jumpsā€ and its variations had been one of the most upvoted ideas in the Miro Communityā€™s Wish list, and the community members were actively sharing their workarounds (and a fair amount of frustration) with each other.

Seemingly a minor detail, line jumps were so important that our users emulated them in amazingly creative ways. My personal favourites, in no particular order, are:

  • Adding additional waypoints to guide a line around the intersection point
Line guided around another line using additional waypoints
  • Draw a circle outline and a rectangle filled with background color on top.
Line jump emulated using a circle outline and a rectangle
  • Use two connector lines and a transparent shape in the intersection point
Line jump emulated using a transparent shape in the intersection point and two connector lines connected to it
  • Add a text label with invisible unicode characters and place it at the intersection point (looks essentially the same as the one above).

Thatā€™s the power of Miro: unleashing the flow of creativity!

Marketing nonsense aside, emulation of such a basic feature is annoying manual labour that could be avoided if we at Miro had just implemented ā€œLine Jumpsā€ natively. But we hadnā€™t, though the users wanted it badly:

Line jumps are essential for directional flow [ā€¦] Please get this feature added, itā€™s so necessary!

ā€” Martha

Imperative feature for process designers. Iā€™ve gone back to draw.io to complete our more advanced processes.

ā€” ksumm

this is so important to network diagrams ā€¦. without this, the level of ambiguity in any non-trivial diagram is too high to be useful. It will absolutely drive me back to other tools.

ā€” Ben

Man, Iā€™d kill for this functionality in Miro.

ā€” Brian

Please donā€™t kill anyone, Brian! Weā€™ve implemented it and, possibly, saved a life or two.

Animation of line jumps in action

Project planning

It was a chill summer day when our team (Diagramming) didnā€™t have a big ongoing project and the leadership was figuring out the future strategy for our stream. In other words, it was perfect time to act on small annoying problems like this one. Our product manager Emil brought us the most urging feature requests from user interviews, from Miro Community and from sales. We, engineers, gathered on a call to read them through and roughly estimate in T-shirts. As you can guess, one of those features happened to be line jumps.

Some of us were not convinced that the line jumps were worth taking in or even possible given the performance issues potentially caused by the number of line intersections, given the complexity of the connector line code and the amount of legacy in its implementation. Another major objection was that our team had owned the line for only a month or two by then, which is practically nothing for such a big, complex, multi-layered widget. Some of us argued that there were lower hanging fruits that would bring us the same value. They all were fair points. And, neverthelessā€¦

Sveta hacked a prototype the next day. A perfect way of figuring out whether the thing is possible or not. To discover problems you did not think of. To learn the codebase in a pace no documentation can possibly provide. And, above all, itā€™s fun. Even if by prototyping you find out the task was not possible, itā€™s still worth it. But in our case, it turned out possible. Sveta showed us a working demo.

First prototype of the line jumnps: red circles around two intersection points
First prototype of the line intersection detector

We all were immediately convinced. Yes, we saw just red circles rather than proper visuals. Yes, they were not updated each in real time but all at once on end of editing, and, yes, they were implemented in the dirtiest way possible with a godlike object storing all the lines on the board and iterating through them in quadratic time on update. It was not the point. We knew the line jumps were feasible to implement in a reasonable time and we could figure the other details out later.

On the next sprint grooming we discussed our findings with our EM and PM. Our designer had already had some design mock-ups prepared, so we were unblocked to have a proper project kick-off. At its outcome, we decided to dedicate a team of one developer working on it full-time and two others stepping in from time to time. With such a workforce we defined a time box of two sprints (4 weeks) taking into account vacations and sick leaves, like adults do. We defined critical features and optional ones. We had all in place to get it started, and I volunteered to lead the project development.

Engineering kick-off

Every task is small until you look at it close enough. Line jumps definitely seemed small. Gotten used to the hybrid work, we arranged a Zoom call with the sub-team dedicated for line jumps and Sveta who volunteered to help and share her insights from prototyping. On the call, we realised that we all were in the same building, sitting in different meeting rooms. The situation seemed ridiculous, so two minutes fast-forward we were all brainstorming in the same meeting room next to a non-virtual whiteboard.

Marker whiteboard after a brainstorm: schemes, lines, keywords in random places
Tribute to Jean-Michel Basquiat by Diagramming team

The number of questions to resolve was growing so fast that we barely kept up writing them down. Just to give you an impression, here are some of the UI/UX questions we now had to address:

  • Enable line jumps as a global setting or per every line?
  • Where this setting should be placed?
  • Should we use levels of detalization for different zoom levels?
  • How should jumps over thick lines look like?
  • How do two line intersections look if they are very close to each other?
  • How should a line jump look like if itā€™s close to a line bend or its end?
  • Take two lines intersecting each other with a very acute angle. Does it still make sense to show a line jump?
  • Do the jumps have to be updated in real time as you move line?
  • Do we need to implement jumps over curve lines or only straight?
  • How should a line jump behave next to a line caption (text on a line)
  • Should the jumps be enabled by default?

The list is by far not exhaustive, and concerns only functional requirements. There were also non-functional, like

  • Accessibility requirements: line jump size, UI elements enabling
  • Performance requirements: how many line jumps on a board we want to support? How many inside the viewport?
  • Architectural considerations: what is the data model, where is it stored, where to locate the business logic?

And some less obvious functional requirements to keep in mind:

There were other questions farther from the code implementation:

  • Should we release it on its own or in pack with other line improvements?
  • Marketing communications: how do we announce the feature, who does that?
  • Tutorials: what tutorials need to be updated, who does that?
  • Miro templates: do we need to update any? Who does that?

Certainly, Miro has dedicated people to address most of these questions but we at least needed to make sure those people all were aware of us needing them to solve those questions.

Thus one deeper look revealed the whole iceberg below an initially seemingly small task. We knew that we needed to treat it as a project worth careful grooming and maybe even scope cut. Not great but way better than finding it out amid the implementation phase.

Implementation

Software engineering is all about making decisions, and the more senior you become as an engineer, the more complicated the decisions become. You learn more ways of doing the same thing, you start seeing consequences of the compromises you made years ago. You face dirty pieces of legacy code that are bound to be there forever and you watch beautiful technical solutions being abandoned because they did not reach product requirements or were released too late. You reflect on it all and you learn from it. Then, of course, you fail again, but with more elegance šŸ’ƒ

Many lines of complex shapes with jumps over each other
Merciless testing

LineJumpCalculator

There was a problem. Line, just as any other board object in Miro, is an independent entity unaware of other objects. This way it can be packaged, loaded lazily, well tested, et cetera. Isolation is good. However, in order to display a jump, line has to know where it intersects another line. Therefore, it should have some information about other lines and some logic in place to check whether it intersects them. But then we would lose the independence of each line. Not good. So we decided to have an external entity able to take any lines on the board and check some of them for intersections. That was the conception of LineJumpCalculator.

Solution:

  1. Line provides an API to set line jump positions from outside, so it could display them.
  2. Each line edit calls LineJumpCalculator to recalculate.
  3. LineJumpCalculator takes the changed lines, iterates over all other lines on board and checks for intersections with the changed ones.
  4. After all intersections for a line are collected, call its API to set line jumps.

Sounds okay for the first iteration. Probably itā€™s too much to check all other lines but we can optimise it later. In general, it should work fine, right? Or not? Do you see a flaw in this logic? We didnā€™t!

Boom! Jumps donā€™t disappear when they should. The algorithm included in the calculation only lines that are updated and, from all lines on the board, only the ones intersecting them, and call line jump recalculation only for them. The ones that used to be intersected before edit but are not anymore, were not included.

A bug: a line jump is displayed even though the lines arenā€™t intersecting
Lesson #1. Bugs are bad. Avoid them if possible.

Boom (2)! Jumps are updated only for the lines you edit, so if the jump was displayed on the other line, it would not update.

A line jump is not updated when itā€™s on a line other than the one being edited
Product thinking: not a bug but behaviour we havenā€™t found a good use case for

At this point we just temporarily stepped back to square-time algorithm and added a `todo: nĀ², optimise` to unblock further development. At least it worked.

Later we implemented a nice logic triggering jump recalculation only for the lines you edit and the lines it intersected before edit. The rest we just donā€™t touch. Works fast and looks beautiful but the story is boring.

QuadTree

Our QA engineer Anna quickly found a moderately-sized board where line jumps were causing visible performance problems. We already knew the root cause even before taking a look at the board. Quadratic time complexity of the line intersection testing. Yes, we perform the intersection point calculation only if their bounding boxes intersect, but before that we need to check intersection of every lineā€™s bounding box with every other lineā€™s bounding box. It turned a board with 300+ lines into a laptop-heater making 100k comparisons on every mouse move. Steps to reproduce were easy:

  1. Select all
  2. Drag around

Luckily, a sibling team had already implemented a QuadTree structure storing all board objects sizes and positions mapped to tiles.

QuadTree is a data structure recursively partitioning two-dimensional space in quadrants. If a quadrant has at least four children, they get divided into smaller quadrants, and and so forth until every node has no more than four children in total.

Points enclosed in nested rectangles QuadTree
Source: An interactive explanation of quadtrees by Jim Kang

the benefit is that you can search objects by location in logarithmic time. The drawback is that you have to re-build this structure every time an object is added, removed or moved.

In our case adopting QuadTree meant that updating 300 lines at once caused 300*log4(300) = 1200 intersection detections instead of 90,000 essentially turning lagging freezing hurting hell into a lovely smooth experience. Not bad, huh?

Next question is: how do we know if weā€™ve optimised it enough? Should it be 300 or 1000 lines working fine so that we can say ā€œitā€™s optimised enoughā€? The answer was 5000. This is what the dedicated performance team told us.

So we went further and added more optimisations. For instance, before looking for intersections, we first check for overlap of the pre-calculated bounding boxes of the lines. If they donā€™t overlap, we know the lines donā€™t intersect and continue to the next line.

Intersections with curve lines

This one is my favourite. We had spent some time with the squad discussing whether or not we needed to show jumps on top of curved lines as well. As part of an investigation, Aleksey chose a nice intersection calculation algorithm and created a good working prototype:

Animation of intersection point detector for curve lines

But I still insisted that we didnā€™t need to do that because, I said:

  • itā€™s not really a diagramming use case. Curve lines are rarely used in diagramming compared to other line types,
  • weā€™ll spend on it time that we could have spent on something more important,
  • weā€™ll introduce additional complexity,
  • it might impact performance.

So, after all, we decided to scope it out, at least for the internal release.

You guessed it right. On the day of the release we get a report: jumps donā€™t work. Weā€™re checking the video attached to the report. Yes, there was an intersection with a curve line and the user expected them to work. We explain that, hey, we donā€™t support curve line intersections yet because this and that. But the next day, we get another report. And one more the next day.

I had to admit, I was wrong. We needed to fix it. But how? Who should do that? What else should we sacrifice in order to take it in? All these questions remained unanswered when the same evening Andrei sent me a Slack message: Iā€™ve fixed the lines and youā€™re not gonna believe how.

The fix was, indeed, umm, well, let me better just show it.

The essence of the fix

We sat on a treasure chest all this time without knowing it. Our curve lines had already had the pre-computed segment data that is precise enough to approximate an intersection point. It was just us not knowing it was there.

Curve line and a straight line intersecting. A jump is shown over the intersection
Suprematist composition #3. Andrei K., canvas, Miro, 2023

+1 line, -1 problem. šŸ„·

Release

We had a reasonable number of bug reports and design feedback, but the general reception was warm, sometimes even borderline hot.

Itā€™s often the little things that get the SPARCK designers buzzing, like the launch of a new feature in Miro, our favourite virtual whiteboarding tool. ā€œThis might be the smallest update Iā€™ve ever been excited about,ā€ said Murray L. on our internal Slack chat the other day. ā€œMiro now has line jumps for flow diagrams, which Iā€™ve previously had to make manually with ellipses and outlines.ā€ā€Šā€”ā€ŠLinkedIn post by Sparck
ā€œThis might be the smallest change Iā€™ve ever been excited aboutā€ might be the cutest phrase Iā€™ve ever heard about the feature I worked on.

Conclusion

There is a unique set of skills and knowledge you can acquire only leading projects. At the very least, it makes you a better engineer as you start being more considerate of the business side of things. You see first-hand who stakeholders are, and why do they hold their stakes. You realise why some things that appear important donā€™t ever get done as you yourself have to make decisions not to do some things. You see how much small details turn out to matter: the jumps close to one another, next to a bend, intersections of lines with a very acute angleā€¦ all these things you donā€™t think about until you do. And then you make them work as intended together. Thatā€™s the beauty of engineering.

--

--

Andrey Ozornin
Miro Engineering

Frontend at Framer. Music, art, technology. 100% certified organic AI-free. https://ozorn.in