Skip to content

Migrate from 0.2.0 to main

This page upgrades an application using published 0.2.0 to main (0.3 in development). Main has breaking API changes and is not a published 0.3 release. The release notes list the individual changes; 0.2.0 remains documented separately.

The examples labelled 0.2.0 use that release’s API. The replacement Rust examples are compiled with main’s documentation tests.

1. Update Rust, egui and features together

Section titled “1. Update Rust, egui and features together”
DependencyPublished 0.2.0Main
Rust minimum1.881.95
egui / eframe0.33.30.37
Default featuresiconsui, locales, timezones
Widget without application UIAvailable without default featuresEnable widget explicitly

For a chart with built-in studies and drawing tools, without the application UI:

[dependencies]
egui-charts = { git = "https://github.com/userFRM/egui-charts", branch = "main", default-features = false, features = ["widget", "studies", "drawings"] }
eframe = "0.37"
chrono = "0.4"

Upgrade direct egui dependencies too, so your host and the chart use the same egui types. See installation for the complete feature table. With no features, main is a headless engine. Remove the old backtest and scripting feature names. Main’s separate strategies feature provides the strategy runner, and custom studies provide a Rust extension point. The application supplies any study language and its compiler.

2. Replace history and update individual bars

Section titled “2. Replace history and update individual bars”

Keep constructing a chart with Chart::new(BarData::from_bars(bars)), and keep the instance between frames. Replace update_data and data with the main series API.

0.2.0:

use egui_charts::{Chart, model::BarData};
let mut chart = Chart::new(BarData::new());
let followed_live_edge = chart.update_data(BarData::from_bars(Vec::new()));
assert!(chart.data().bars.is_empty());

Main:

use chrono::DateTime;
use egui_charts::{Chart, model::{Bar, BarData}, widget::SeriesId};
let mut chart = Chart::new(BarData::new());
let time = DateTime::UNIX_EPOCH;
chart.set_data(SeriesId::MAIN, vec![
Bar::new(time, 100.0, 103.0, 99.0, 102.0, 10.0),
]);
chart.update(SeriesId::MAIN,
Bar::new(time, 100.0, 104.0, 99.0, 103.0, 12.0));
assert_eq!(chart.bars(SeriesId::MAIN).len(), 1);
assert_eq!(chart.bars(SeriesId::MAIN)[0].close, 103.0);

set_data replaces history. update replaces the latest bar at the same opening time or appends a later bar. There is no replacement for update_data’s boolean return: the chart follows the live edge according to ChartOptions::time_scale.shift_visible_range_on_new_bar. Remove the old validation enable/disable calls. Invalid bars are rejected, with the count in feed_state(SeriesId::MAIN).rejected_bars.

Datafeed answers requests through replies and streams updates through a sink. Its methods run on the UI thread: return promptly and do network work in your host’s worker or browser task. On native targets a feed must be Send + Sync.

0.2.0Main
DataSource::fetch_historical returning Result<Vec<Bar>, DataSourceError>Datafeed::history(request, reply), answering Reply<HistoryPage> once
HistoricalDataRequest::end_ts_millis, limitHistoryRequest::before, since, count, using UTC timestamps
DataSource::poll / DataUpdatesubscribe receives a Subscription; push bars or trades into its sink
Box<dyn DataSource> and with_data_srcArc<dyn Datafeed> and with_feed
DataSource::search_symbols, marksSeparate services::SymbolSearch and services::Marks services

This history-only adapter serves one symbol and interval from an ascending list:

use std::sync::Arc;
use egui_charts::{ChartBuilder, data::{Datafeed, HistoryPage, HistoryRequest, Reply},
model::{Bar, Timeframe}};
struct History(Vec<Bar>);
impl Datafeed for History {
fn history(&self, request: HistoryRequest, reply: Reply<HistoryPage>) {
let end = self.0.partition_point(|bar| {
request.before.is_none_or(|before| bar.time < before)
});
let first = self.0.partition_point(|bar| {
request.since.is_some_and(|since| bar.time < since)
}).min(end);
let start = end.saturating_sub(request.count).max(first);
reply.send(Ok(HistoryPage::new(self.0[start..end].to_vec(), start == 0)));
}
}
let chart = ChartBuilder::new()
.with_feed(Arc::new(History(Vec::new())))
.with_symbol("SAMPLE")
.with_timeframe(Timeframe::Min1)
.with_initial_bars(500)
.build();
assert_eq!(chart.symbol(), "SAMPLE");

Respect both bounds: since is inclusive, before exclusive, and None for before requests the latest bars. Return the newest count bars in that range. Set end_of_history only when no earlier history remains. A feed that pages by time windows uses HistoryPage::with_continue_before to cross empty windows. For live updates, retain the supplied sink and stop when Sink::is_active is false; implement unsubscribe for backend cleanup. See historical and streaming data and the network feed example. The host continues to own credentials, connections and market data.

Map old errors to FeedError: ConnError, FetchError and IoError become Network; ParseError and InvalidTimeRange become InvalidData; SymbolNotFound and NotSupported retain their names. RateLimited carries retry_after: Option<Duration>.

4. Let the chart own studies, drawings and updates

Section titled “4. Let the chart own studies, drawings and updates”

ChartBuilder::build now returns a Chart, replacing TradingChart. Remove tc.update(); Chart::show applies feed updates. Replace tc.chart with the chart itself. For the lower-level widget, move externally owned studies and drawings onto the chart:

0.2.0:

use egui_charts::{Chart, model::BarData, studies::{IndicatorRegistry, SMA}};
let mut chart = Chart::new(BarData::new());
let mut studies = IndicatorRegistry::new();
studies.add(Box::new(SMA::new(20)));
studies.calculate_all(&chart.data().bars);
let mut drawings = egui_charts::drawings::DrawingManager::new();
// In the frame callback:
chart.show_with_indicators(ui, Some(&mut drawings), Some(&studies));

Main:

use egui_charts::{Chart, model::BarData, studies::SMA};
let mut chart = Chart::new(BarData::new());
let study = chart.add_indicator(Box::new(SMA::new(20)));
chart.set_indicator_visible(study, true);
// In the frame callback, call chart.show(ui).
// Later, remove the study by its stable series identifier:
chart.remove_series(study);

Use drawings_mut for chart-owned drawing edits. Replace study indices and ChartElementId::{OverlayIndicator, PaneIndicator} with SeriesId and ChartElementId::Indicator(id). Drop host calculate_all calls after bar updates; the chart schedules study calculations. Values may be unavailable until a later frame. If an export needs results immediately, calculate a clone of the study over chart.bars(SeriesId::MAIN).

Replace set_timeframe_label(label) with set_timeframe(timeframe). A feed-backed chart reloads on a symbol or interval change. Supply with_symbol explicitly; an empty symbol requests no history. Keep a ChartMenus beside the chart and call menus.show(ctx, &mut chart) instead of TradingChart::take_context_action.

5. Update imports, options and interval construction

Section titled “5. Update imports, options and interval construction”

Use public re-exports instead of implementation modules. These are paths that existed in 0.2.0:

0.2.0Main
chart::builder::ChartBuilderChartBuilder or widget::ChartBuilder
chart::selection::{SeriesId, ChartElementId}widget::{SeriesId, ChartElementId}
model::{ChartState, ZoomState}studies::{ChartState, ZoomState}
ext::UiExtui_kit::ext::UiExt (feature ui)
ui::top_toolbar::Timeframemodel::Timeframe

The release notes also cover intermediate development APIs. For example, widget::builder::ChartBuilder was introduced after 0.2.0 and is private now.

ChartConfig, ChartOptions and several other extensible records are non-exhaustive. Start with a constructor or Default and assign fields; external struct literals, including ..Default::default(), cannot construct non-exhaustive records. Add a fallback arm when matching non-exhaustive chart, interval, event and host-intent enums.

Main:

use egui_charts::{Chart, config::ChartOptions, model::{BarData, Timeframe}};
let mut options = ChartOptions::default();
options.time_scale.shift_visible_range_on_new_bar = true;
let mut chart = Chart::new(BarData::new()).with_chart_options(options);
let timeframe: Timeframe = "45s".parse().expect("positive interval");
chart.set_timeframe(timeframe);
assert_eq!(timeframe.duration_ms(), 45_000);

Timeframe::Custom(45) now takes NonZeroU64; parsing a positive label avoids manual construction. Use duration_ms() or duration() instead of the removed seconds helpers, which lost subsecond precision. See scales.

6. Preserve themes and saved drawings deliberately

Section titled “6. Preserve themes and saved drawings deliberately”

A default ChartBuilder follows the applied egui theme. Call with_theme(Theme::dark()) to keep a fixed dark chart. Replace theme.apply_to_config(&mut config) with config = config.with_theme(&theme); use Theme fields and theme.text instead of removed component styles and typography tokens. See theming.

DrawingStorage::from_json now returns (document, skipped_drawings), rather than just the document. Report skipped records and retain the original JSON if they need recovery: saving the migrated document writes only the records that loaded. Main reads version 1 files and saves its current version; 0.2.0 cannot read the newer format. Back up saved layouts and drawings before allowing main to rewrite them.

Finally, run your application’s checks and exercise history paging, a forming-bar update, symbol and interval changes, study removal and saved-document reloads. Consult Unreleased changes for the individual study constructor, rendering and UI intent changes your application uses.