Our Journey From a Library to a Custom Graph Component: Navigating the Sunk Cost Fallacy
Make or buy? That’s a question asked often when it comes to software development. Do we make everything ourselves or do we just buy a third party library? Most of the time the decision is to buy, but is this always the best choice?
In this blog, I take you on our journey from a library to a custom graph component. I explain why we chose to build our own graph component instead of using an existing library. This was a journey that I believe is both interesting and helpful as any developer/architect can end up in a position where a choice like this has to be made.
Our client
For our client, Bronkhorst High-Tech B.V., we work on a cross platform .NET solution with apps for Android, iOS and Windows. The software is used to communicate with – and monitor devices that are used to control or measure low flow rates of gases and liquids. A big part of the application is displaying the metrics of said devices. For this reason a graph had to be implemented for the Windows application (using the WinUI 3 SDK), as that platform has the main focus.
Since Bronkhorst specializes in precision, the graph had to reflect that. It had to perform well and still be accurate when gathering data with time intervals of 1-5ms so it can display measurement changes while operating the device. At the same time, it also had to be capable of running for days, weeks and even months to accumulate more long term data.
“Buy before hire, hire before build”
Bronkhorst has a “buy before hire, hire before build” policy. Their motivation is that the developing party also has to be the one to maintain it. Not only is this cheaper, but it also means that their developers can focus on the things that are essential to them.
Initially, we started with a comparison of multiple graphic libraries. WinUI3 was relatively new so there was not much choice of libraries and the few out there didn’t really focus on real-time charting yet. In the end we settled for a component that seemed to be the best available option. There was some doubt about the performance, but nothing we couldn’t optimize. Or so we thought.
We already had a feature flag implemented to prepare for an acceptance test. There was some doubt about the performance, but not in a way that would prevent us from releasing it to production. We almost released the app using this library.
However, when expanding our test groups and cases, it became clear that stability was a major issue. A lot of the testers reported freezing of the app after having the graph running for a while. Sometimes it happened within less than a minute and other times it happened after 3 days. And the cause of it was unclear.
Ever heard of the “Sunk cost fallacy”? That’s what happened here. Which is actually something most of us tend to do. You already spent a lot of time, effort and money on something, and you’re so close to the end result, that you don’t even consider going back to the starting point. You’re convinced it’s just a single stability fix away of a successful release.
So, we kept trying to improve our code to optimize and stabilize it. While successful to some extent, it didn’t really solve the underlying issues and at times even introduced more instability. So, we contacted support of the library we used.
They offered a couple of suggestions, but none of them solved our problems. There was even a meeting between us, our client and the developers from the library to see what could be done. It became apparent that the library itself required major changes to fit our use case. Our client wasn’t willing to wait much longer on the graphing feature, so we concluded that there was no other choice than going back to the drawing board. A shame, but we had to look forward and explore other ways to achieve our goal. Something everyone agreed on.
Creating our own graph component
The solution was simple, yet also the one that is discarded easily when it comes to software development; creating our own graph component. We discussed the possibility before we implemented the 3rd party library, but only briefly, given the client’s “buy before build” policy. And I see this happening often in our trade. Why reinvent the wheel? Why spend resources on building and supporting something that can be done by others? Especially when it comes to something as complex as a charting library. So, we ignored it then. It would cost too much time and effort to build it ourselves.
But now, it was the only viable option. Next to that we, together with our client, began to realize that graphing was in fact essential to their business, justifying the cost/effort to do this. So, we went for it. After some research we found a library called SkiaSharp. It was a high-performance drawing library for C#. We started with a limited proof of concept; just drawing the minimum required amount of datapoints we wanted to support, with a line between them, and it had to be “butter smooth” (That was the actual wording in the requirements). Turns out, it was surprisingly simple to achieve.
We could’ve planned the architecture a little better to avoid a couple of refactors, but overall it was an easy job. Our client, Bronkhorst was happy with the result, and we felt comfortable we could meet all the requirements, so that’s what we did. Eventually we added other features like panning/zooming, dynamic axis, multiple axis, scaling, toggle line visibility, etc. All while reaching the performance and accuracy that was required. Even better, we managed to display 920 datapoints at a consistent 30 fps, twice the amount of our initial target of 460.
Lessons learned: navigating the sunk cost fallacy
Developers like to keep things simple by using libraries maintained by others. Most of the time, this will most likely be the right approach. It’s easy, fast, and the ones writing it (hopefully) have more knowledge about the subject. But because of this we tend to ignore the possibility of writing things ourselves. In this case, that led us on the exact path that we wanted to avoid. It would’ve been easier, faster, and cheaper to build the library ourselves from the start. With the added benefit of having the knowledge, as well as tailoring to the specific needs of the client. Sure, it has to be maintained as well, but in the long run it could still be the better choice. Making this choice can be difficult, however.
Sometimes it’s obvious. You don’t want to implement a payment system yourself unless you work for a bank or are specialized in cyber security. Or when something is your core business, it will make more sense to build things yourself. Other times, it’s not as obvious, like for this project. It seemed like a third party project was the way to go, as is often the case when it comes to charting libraries. Perhaps even because it’s often the way to go with charting libraries.
In hindsight, we should’ve stopped with the 3rd party libraries after the proof of concept applications. The stability issues weren’t discovered back then, but the performance issues alone should’ve been enough to reconsider instead of assuming that “we could fix it”. Another thing that we should’ve done is simply reach out to the library developers and ask them beforehand whether their library could be used for our use case and if yes, whether they could show some metrics. It would have saved a lot of time and effort.
The key takeaway here is to not assume that third party libraries are always the best choice. Take time to evaluate your options. When in doubt, evaluate more. And be critical. In the end you want to deliver a good product and have happy customers. Third party libraries are not a requirement for that.
Want to know more about what we do?
We are your dedicated partner. Reach out to us.