DXcharts is a financial charting library built for active trading, one of the toughest UI use cases out there.
With real-time data feeding in every second, and users drawing, panning, zooming, adding indicators, and trading directly from the chart; if it stutters or lags, they’re going to know about it, and then you’re going to know about it. :)
This article talks about how we approached building a fast, interactive charting engine that can handle that load from the ground up.
We’ll go over the early days and the current architecture, decisions we made, how we use functional programming principles, and how we go about optimization and testing.
So, if you’re curious about how to make your UIs render 10,000 points without glitching when money is on the line, this will hopefully be insightful.
The trading-specific genesis
For context, DXcharts falls under Devexperts’ umbrella of products and services, meaning it was not a generic charting tool from the outset. It was built from scratch by Devexperts specifically for powering online trading platforms.
From the beginning, it needed to handle live financial data, heavy user interaction, and offer trading-specific features, with solid reliability.
In one sense, this meant more complexities, but in another, DXcharts was able to take best practices for brokerage systems and feed that into the way they built and designed the interface.
Basically, it needed to be super fast and able to reliably integrate into trading platforms with heavy loads (some of Devexperts' SaaS clients have millions of traders on their platforms each day).
Which brings us to a third point. It also needed to look really good and have a stand-out UX that would attract traders and end-users. Or to put it differently, provide an experience that gives the user everything they could possibly need to prevent them from wondering if the grass is greener somewhere else.
The tech stack, then vs now
The stack has stayed very consistent. The core of the library was built with React, RxJS, TypeScript, and a functional programming mindset.
To summarize, we used:
- React for UI
- RxJS for reactive streams
- TypeScript for safety
- fp-ts for functional programming
- Styled-components for styling
React
React gave us a clean component-based structure that was easy to adopt and really popular among developers at the time (and still is), which also made sense for hiring and integration/onboarding support.
React also allowed us to break the complex chart UI into reusable pieces, which is a big bonus when you have a high volume of interactive elements on the screen.
Another reason we went with React was because its DOM and diffing algorithm can update the UI in response to streaming data without heavy manual DOM manipulations. It figures out the minimal changes needed to reflect the new state.
TypeScript
Typescript provided static typing to catch bugs early and keep the code base predictable and stable. Both React and Typescript allowed us to scale as we needed.
RxJS
For RxJS, it aligned with the real-time market data components and allowed us to manage asynchronous data streams effectively.
The chart’s state, such as the current view window, the data being displayed, or the cursor position, is represented and managed as a series of observables/stream events.
We have RxJS subjects that emit events for things like new data ticks, user actions, or crosshair movements. Our React components subscribe to these streams and react to them.
Whenever a RxJS observable emits a new value, let's say a price update stream pushes a new bar, it triggers the relevant part of the UI to update.
This reactive design means our components are mostly stateless UI renders, where the state lives in the RxJS layer and flows down to the components. A clear separation is provided so that the data flow is handled by RxJS and the rendering by React.
fp-ts and styled components
We used fp-ts to introduce functional patterns like immutability and monads into our code. Styled components helped make the UI design system maintainable.
The stack now
The stack has, of course, modernized over time. For example, as React introduced hooks and improvements, we refactored parts of our code to use them.
We also use the newer versions of TypeScript, which brings better features and smarter building tools.
But we didn’t scrap anything; the stack is essentially the same, just updated. Which is nice, given it doesn’t often happen that way. In this case, the foundation has served us very well, and we’ve stuck with it.
Other architecture decisions
On the architectural side, we separated the charting system into focused modules, with the most performance-critical part being our chart core that handles the rendering.
HTML 5 Canvas for rendering
Rather than relying on the DOM or SVG for plotting thousands of data points, we built the rendering engine around HTML5 Canvas for pixel-perfect drawing.
We chose Canvas because it excels at drawing large numbers of objects and points, without incurring the overhead of maintaining thousands of DOM nodes.
In a trading chart, it’s common to have tens of thousands of price bars or points on screen, plus annotations and technical indicators. With Canvas, we can draw all of that in a single GPU-accelerated surface.
Once the data is drawn to the canvas, it’s essentially just an image bitmap that the browser can blit to the screen very quickly. This means our charts stay ‘supersonic fast’ (as we market them ;)) even with thousands of bars, hundreds of drawing objects, and dozens of indicators rendered simultaneously.
Writing in a declarative functional style
We took a functional reactive programming approach to designing the chart library’s internals, which isn’t that common in the charting library space.
Most of our code is written in a declarative, functional style rather than imperative.
Our use of the fp-ts library brings in many patterns from functional languages into TypeScript. This gives us things like Option/Either types for safer handling of missing data, immutable data structures, and the ability to compose complex operations in a clear way.
In effect, we are aiming to write ‘pure’ functions for our calculations and state transformations, making the behavior easier to reason about.
Building for extensibility
We chose to build DXcharts as a toolkit. Everything is modular and typed. Clients can bring their own studies, drawing tools, and even tweak the core logic if they need to/want to.
We expose a deep API, and TypeScript helps client autocomplete their way through it.
Devs also have the advantage of IntelliSense and type safety. This helps lower integration errors and makes our library feel like a well-documented framework, rather than a ‘black box’, as they say.
The feedback from clients has been that this flexibility and effort to make the libraries dev-friendly for integrations was a significant reason why they chose us.
Optimization and testing, both for performance and visual
A notable benefit of these architecture decisions is testability. As mentioned, because of the way our core logic encompasses pure functions and streams, we can simulate sequences of events in tests and ensure the system responds correctly.
Over the years of building DXcharts, we’ve performed micro-optimizations in our code.
Examples are things like caching computations, reusing objects to avoid garbage collection churn during rapid updates, and minimizing React state updates by keeping most of the heavy data outside of React components.
We have a great team behind DXcharts that is taking a lot of care to write efficient code. Performance testing includes real scenarios to test at scale, like opening 10 charts with 10,000 data points each and ensuring interactions remain fluid.
Visual testing with Playwright and e2e
For visual testing, we use Playwright for both end-to-end flows and visual regression tests. We generate reference charts and diff them against current renders to try and catch even the tiniest layout or rendering bugs.
This level of testing might be considered a bit unique in the industry for a charting library, but as we laid out in the intro, given the financial nature of what’s at stake, it has been a standard for us.
Plus, it also gives the client’s devs confidence that our updates won’t unexpectedly change behavior or visuals.
Last thoughts
Building DXcharts has been a long process of thoughtful architecture, functional discipline, and constant performance tuning.
Upon reflection, the things that were instrumental in successfully achieving our goals were the core stack we chose and stuck with, focusing on modularity, investing in testability, and, of course, have to mention the dedicated team behind DXcharts.
If you're building anything that needs to move fast and scale cleanly, even if it’s not charts, hopefully you’ve found something useful in here that can help you succeed with your own project.
Feel free to check out the charts here. We also have other case studies that talk about specific features and different formats and iterations of DXcharts.
Top comments (0)