Financial charts for egui, from a single chart widget to a full trading terminal.
Pure Rust, native and WebAssembly from the same code. Take it as far as you need: one chart in your own egui app, or the chart with studies, drawing tools, toolbars and dialogs around it.
The chart widget alone, without the toolbars, the built-in studies, the icons or egui_extras:
[dependencies]
egui-charts = { version = "0.2", default-features = false, features = ["widget"] }
eframe = "0.37"This example uses eframe 0.37, which matches the egui version egui-charts builds on; another eframe version brings a second egui whose types do not match.
This README describes the main branch, which is not published yet. Until the next release, use git = "https://github.com/userFRM/egui-charts" in place of version = "0.2", or cargo add egui-charts --git https://github.com/userFRM/egui-charts. The documentation of the published 0.2.0 is at https://docs.rs/egui-charts/0.2.0.
use eframe::egui;
use egui_charts::{Chart, model::{Bar, BarData}};
fn main() -> eframe::Result {
let bars: Vec<Bar> = Vec::new(); // your bars, oldest first
let mut chart = Chart::new(BarData::from_bars(bars));
eframe::run_ui_native("My chart", Default::default(), move |ui, _| {
egui::CentralPanel::default().show(ui, |ui| chart.show(ui));
})
}The chart pans, zooms and shows a crosshair out of the box. set_chart_type picks one of the chart types below, set_data replaces the bars of a series, and update merges a live bar into it. examples/bare_chart.rs streams sample ticks into this chart.
In the default build, the toolbars, panels and dialogs are components you place in your layout; each returns an action that your app applies to the chart. This one puts the drawing toolbar next to a chart that loads its bars from your Datafeed:
cargo add egui-charts
cargo add eframe@0.37use std::sync::Arc;
use eframe::egui;
use egui_charts::data::{Datafeed, HistoryPage, HistoryRequest, Reply, Subscription};
use egui_charts::model::Timeframe;
use egui_charts::ui::drawing_toolbar::DrawingToolbar;
use egui_charts::{Chart, ChartBuilder};
/// Your market data: history on request, then live updates.
struct MyFeed;
impl Datafeed for MyFeed {
fn history(&self, request: HistoryRequest, reply: Reply<HistoryPage>) {
// Up to `request.count` bars that open before `request.before`, or the latest bars
// when it is `None`. Fetch them on another thread or task and reply from there.
let _ = request;
reply.send(Ok(HistoryPage::default()));
}
fn subscribe(&self, subscription: Subscription) {
// Push live bars, trades and depth into `subscription.sink` from your connection
// until `Sink::is_active` turns false.
let _ = subscription;
}
}
struct Terminal {
chart: Chart,
tools: DrawingToolbar,
}
impl eframe::App for Terminal {
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
egui::Panel::left("tools").show(ui, |ui| {
if let Some(tool) = self.tools.show(ui) {
self.chart.drawings_mut().set_active_tool(Some(tool));
}
});
egui::CentralPanel::default().show(ui, |ui| self.chart.show(ui));
}
}
fn main() -> eframe::Result {
let chart = ChartBuilder::new()
.with_symbol("SAMPLE")
.with_timeframe(Timeframe::Min1)
.with_feed(Arc::new(MyFeed))
.build();
let tools = DrawingToolbar::default();
eframe::run_native(
"My terminal",
eframe::NativeOptions::default(),
Box::new(|_cc| Ok(Box::new(Terminal { chart, tools }))),
)
}The demo connects the top, drawing and timeframe toolbars, the dialogs, the side panels and the context menus this way. Controls that need a backend the demo does not have show a notice, and some context menu entries do nothing yet. An assembled terminal that handles these actions itself, so an app only supplies its services, is planned (#205).
ChartType offers these types, in the groups of ChartTypeCategory:
| Group | Types |
|---|---|
| Standard | Candles, Bars, Hollow Candles, Volume Candles |
| Line | Line, Line with Markers, Step Line |
| Area | Area, HLC Area, Baseline, Columns |
| Japanese | Heikin Ashi, Renko, Kagi, Line Break, Point and Figure |
| Range | High-Low, Range Bars |
| Advanced | Market Profile (TPO) |
A chart computes its studies, updates them as its bars change and draws them: overlays on the price pane, the others in panes below it. IndicatorFactory registers 119 indicator names, each a distinct study under a category such as Momentum or Volume, and the indicator dialog lists every study the factory registers, the host's own included. Six more types are built directly instead of registered: five compute the same study as a registered one under their defaults (CVD and A/D, CloseToCloseVol and Historical Volatility, HL2 and Median Price, HLC3 and Typical Price, PriceChannel and Donchian Channels), and EhlersFisher is a variant of the Fisher Transform.
| Group | Examples |
|---|---|
| Moving averages | SMA, EMA, WMA, DEMA, TEMA, HMA, ALMA, VWMA, LSMA, KAMA, VIDYA, McGinley Dynamic |
| Momentum | RSI, MACD, Stochastic, Williams %R, CCI, ROC, Aroon, Awesome Oscillator, Ultimate Oscillator, Connors RSI, TRIX, KDJ |
| Trend | ADX, SuperTrend, Ichimoku Cloud, Parabolic SAR, Vortex, Squeeze Momentum |
| Volatility | Bollinger Bands, ATR, Keltner Channels, Donchian Channels, Historical Volatility, Chandelier Exit, Parkinson Vol, Zero-Trend Vol |
| Volume | OBV, VWAP, MFI, A/D, Chaikin Money Flow, Klinger Oscillator, Volume Profile |
| Support and resistance | Pivot Points, ZigZag |
| Market structure | 52W HL%, 52W High/Low |
| Statistics | Linear Regression, Standard Error, Rank Correlation |
use egui_charts::studies::{BollingerBands, RSI, SMA};
# let mut chart = egui_charts::Chart::new(egui_charts::model::BarData::default());
let sma = chart.add_indicator(Box::new(SMA::new(20)));
chart.add_indicator(Box::new(RSI::new(14)));
chart.add_indicator(Box::new(BollingerBands::new(20, 2.0)));
chart.remove_series(sma);The built-in studies and IndicatorFactory are in the studies feature, which the default build includes and which needs no egui. The Indicator trait and IndicatorRegistry, which computes studies on a slice of bars without a chart, are in every build, so a study of your own needs neither.
DrawingToolType defines 94 drawing tool types, counting four cursor modes, which the drawing toolbar groups into cursors, lines, Fibonacci, patterns, projections, shapes, annotations and icons. Drawings are anchored to bar and price, so they stay in place through pan and zoom, and undo and redo are built in. Some tools are placeholders or approximations; ROADMAP.md lists the status of each.
Magnet snapping is not applied yet. drawings::persistence saves drawings to JSON, with their anchors as timestamps and every setting, and restores them; the chart does not call it, so the application decides when to save and load.
The Market Profile (TPO) chart type, fixed range and anchored volume profiles, and cumulative volume delta, estimated from each bar's prices. A Datafeed can already push trades and order book depth; a footprint chart (#231) and a liquidity heatmap of the order book (#235) are planned.
A Theme is one value with public fields: UI colours (theme.ui), chart colours (theme.chart), and the type, spacing and corner radius scales. It comes in dark, light and high-contrast presets. Start from one, override what you need, and apply it at startup and after every change:
use eframe::egui::Color32;
use egui_charts::theme::{Theme, apply_to_egui};
# let ctx = &eframe::egui::Context::default();
// The accent's hover and pressed shades, the text on it and the selection follow it.
let mut theme = Theme::dark().with_accent(Color32::from_rgb(255, 120, 0));
theme.chart.candle_up = Color32::from_rgb(0, 160, 120);
theme.chart.candle_down = Color32::from_rgb(220, 60, 60);
apply_to_egui(ctx, &theme);
// Light UI chrome with the dark chart.
let classic = Theme { chart: Theme::dark().chart, ..Theme::light() };With the serde feature, Theme implements Serialize and Deserialize, so a theme can be stored in any serde format; fields missing from the input take the dark theme's values. Some surfaces still keep dark colours in the light theme (#275).
| Layer | What it gives | Feature |
|---|---|---|
| Engine | Bars, symbols and timeframes, price and time scales, the Datafeed contract and the host service traits (broker, symbol search, storage, alerts, signals), and the Indicator trait with IndicatorRegistry. No egui dependency. |
always on |
| Studies | The built-in studies and IndicatorFactory, also without egui |
studies |
| Chart widget | Chart and ChartBuilder: rendering, pan and zoom, crosshair, chart types, drawings, studies on the chart, theme |
widget |
| Icons | Embedded SVG icons, drawn through egui_extras |
icons |
| Terminal UI | Top, drawing and timeframe toolbars, replay controls, side panels, dialogs, and the ui_kit widgets they are built from |
ui |
The default feature is ui, which enables icons, widget and studies.
# The chart with toolbars, panels, dialogs, icons and built-in studies (default)
egui-charts = "0.2"
# The chart widget alone: no toolbars, built-in studies, icons or egui_extras
egui-charts = { version = "0.2", default-features = false, features = ["widget"] }
# The chart widget with the built-in studies
egui-charts = { version = "0.2", default-features = false, features = ["widget", "studies"] }
# The engine with the built-in studies, without egui
egui-charts = { version = "0.2", default-features = false, features = ["studies"] }serde (off by default) adds Serialize and Deserialize to Theme. theme::apply_to_egui installs the bundled Roboto ahead of the application's proportional fonts. To replace it, call egui::Context::set_fonts after applying the theme. eframe enables egui's default_fonts as fallbacks by default. Since egui 0.37 those fonts have no emoji, which need egui's monochrome_emoji_fonts feature or fonts the application installs. The chart installs no image loader other than SVG; an application that shows PNG, JPEG, file or HTTP images calls egui_extras::install_image_loaders with the loader features it needs.
cargo bench --bench bare_chart --no-default-features --features widget measures the frame cost of the bare chart example.
Implement Datafeed to connect any market data provider, as in the terminal example; only history is required. The chart calls the feed from show on the UI thread and never waits for it: answer through the Reply and push into the Sink from any thread, runtime or browser task. The chart asks for older pages as the user scrolls back, and unsubscribes when the symbol or timeframe changes. examples/live_feed.rs serves history from memory and streams synthetic trades.
| Target | Status |
|---|---|
| Linux x86_64 | Built and linted in CI on stable and the MSRV; tests run on both |
| macOS aarch64 | Built and linted in CI on stable and the MSRV; tests run on stable |
| Windows x86_64 | Built and linted in CI on stable and the MSRV; tests run on stable |
| WebAssembly (wasm32-unknown-unknown) | The demo is built for it in CI and runs as the live demo |
Other targets are expected to work but are not built in CI. The chart is an egui widget, so it runs wherever egui runs: in an eframe window on Linux, macOS and Windows, or in the browser through eframe's web runner.
The MSRV is 1.95 (Rust edition 2024), set in Cargo.toml through rust-version.
Licensed under either of
at your option.
Some interface icons under assets/icons/ come from Lucide and are licensed under the ISC License (with a Feather-derived subset under the MIT License); see the third-party notices and the Lucide licence. The bundled Roboto Latin subset is licensed under the SIL Open Font License 1.1.
Contributions are welcome: bug fixes, docs, examples, and new studies, drawing tools, chart types and themes. Start with CONTRIBUTING.md, which covers the dev setup, the project layout, where each kind of addition lives, and the local checks that mirror CI.
Please also read the Code of Conduct, and report security issues privately per the Security Policy. Questions and ideas are welcome in Discussions.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.