Asynchronous Drill Down Options in FlexChart WPF Chart Controls
We often use charts for visualizing and analyzing our data. However, charts are only capable of visualizing flat (non-relational) data. But what if the data is relational or contains some hierarchy?
This is where Drill Down in charts steps in. A drill down is when you click on an individual data plot in the original or parent chart to open a new descendant chart containing additional data relevant to the plot. This additional/child data could be fetched from an API/Database on demand per your requirements. With a large enough data set, you can create endless levels of drill down.
Interested in other FlexChart controls? Download ComponentOne Today!
This blog will show how to implement asynchronous drill down with ComponentOne WPF FlexChart.
For our use case, we will be using cryptocurrency exchanges where we will be drilling down to exchange’s listed currencies and their price charts.
Creating Data Source
Our first step is to create some structures to represent exchange, currencies & price data. So, we will use the following classes for the same.
public class CryptoExchange
{
public int Id { get; set; }
public string ExchangeName { get; set; }
public int ListedCurrencies { get; set; }
}
public class CryptoCurrency
{
public int Id { get; set; }
public int ExchangeId { get; set; }
public string CurrencyName { get; set; }
public string Symbol { get; set; }
public double CurrentPrice { get; set; }
}
public class PriceData
{
public int Id { get; set; }
public int CurrencyId { get; set; }
public double High { get; set; }
public double Low { get; set; }
public double Open { get; set; }
public double Close { get; set; }
public DateTime Date { get; set; }
}
And for storing/fetching exchange, currencies & price data, we will use the following DataService class. In short, this class will mimic a REST API, which will fetch respective data during drill down.
public class DataService
{
public Task<IEnumerable<CryptoExchange>> FetchTop5CryptoExchanges() { }
public async Task<IEnumerable<CryptoCurrency>> FetchTop4CryptoCurrencies(int exchangeId) { }
public async Task<IEnumerable<PriceData>> FetchMonthlyPriceData(int currencyId) { }
public async Task<IEnumerable<PriceData>> FetchWeeklyPriceData(int currencyId, int month) { }
public async Task<IEnumerable<PriceData>> FetchDailyPriceData(int currencyId, int month, int week) { }
}
Creating Asynchronous Drill Down Manager
Now, we have our data source ready. We will implement a drill-down manager, allowing us to perform drill-down with FlexChart.
public interface IDrillDownManager
{
/// Fires on drill down.
event EventHandler<DrillEventArgs> DrilledDown;
/// Fires on drill up.
event EventHandler<DrillEventArgs> DrilledUp;
// Gets the Drill depth (Number of steps).
int Depth { get; }
// Gets the current drilled level.
int CurrentLevel { get; }
// Gets the path that has been navigated/drilled.
IEnumerable<StepResult> DrilledPath { get; }
// Registers a drill down step which will be executed in the order they are added.
void AddDrillDownStep(IDrillDownStep step);
// Drill down one level.
Task DrillDown(Point point);
// Drill up one level.
Task DrillUp();
// Drills to specific path.
Task DrillToPath(StepResult path);
// Resets the drill level and clears the drilling history.
void Reset();
}
As you can see in the above code, there is a method named AddDrillDownStep.
public void AddDrillDownStep(IDrillDownStep step)
{
_drillDownSteps.Add(step);
}
This method will allow us to register multiple drill-down steps (implementing IDrillDownStep) with the drill-down manager, which will be executed in the order they are registered. We will discuss the drill down steps later in this blog.
Moving to the most important part, we’ll be using IDrillDownManager’s DrillDown method to asynchronously drill down in FlexChart.
public virtual async Task DrillDown(Point point)
{
// perform hittest on chart
var hitTest = _flexChart.HitTest(point);
if (CanDrillDown(hitTest))
{
_currentLevel++;
var drillDownStep = _drillDownSteps[_currentLevel];
var result = await drillDownStep.Execute(hitTest); // execute step
// add to history
_drillDownHistory.Push(new DrillInfo()
{
HitTestInfo = hitTest,
StepPerformed = drillDownStep,
});
_drilledPath.Add(result);
OnDrilledDown(new DrillEventArgs() { Level = _currentLevel });
}
}
protected bool CanDrillDown(HitTestInfo hitTest)
{
if (_drillDownSteps.Count == 0)
return false;
return _currentLevel < Depth && _drillDownSteps[_currentLevel + 1].CanExecute(hitTest);
}
In the above code, you can see the CanDrillDown method, which basically checks if the drill down is allowed by using IDrillDownStep’s CanExecute method. And if the drill-down is allowed, the drill-down step gets executed and added to the stack containing the history (drilled steps) for drill-up operations.
Note: IDrillDownStep’s CanExecute method will contain the logic to allow/prevent drill down on FlexChart based on some conditions. For example, Drill down only if the mouse click is within the 5 pixels distance from the data point.
Similarly, we could implement the drill-up operation by executing the steps stored in the history stack. However, to keep this blog short, we won’t be including the code snippet for the DrillUp method. Please refer to the sample for full implementation.
Creating Drill Down Steps
By now, you understand that our drill-down steps will implement the IDrillDownStep interface. The implementation of this interface will contain the logic to configure FlexChart based on the inputs received at different drill levels.
public interface IDrillDownStep
{
/// Gets whether this step can execute or not.
bool CanExecute(HitTestInfo hitTest);
/// Executes the step.
Task<StepResult> Execute(HitTestInfo hitTest);
}
As mentioned in this blog, we will use cryptocurrency exchanges to drill down to exchange’s listed currencies and their price charts.
We will need the following steps as per our use case.
- Display a column chart of the top 5 currencies listed on the cryptocurrency exchange.
- Display a monthly candle chart of any selected currency from step 1.
- Display a weekly candle chart of any selected month candle from step 2.
- Display a daily candle chart of any selected week candle from step 3.
So, let's start by creating our first drill-down step, i.e., displaying the top 4 currencies listed on the exchange.
public class ExchangeTop4CurrenciesStep : IDrillDownStep
{
private DataService _dataService;
public ExchangeTop4CurrenciesStep(DataService dataService)
{
_dataService = dataService;
}
public bool CanExecute(HitTestInfo hitTest)
{
// hittest object recieved from drill down manager
return hitTest != null &&
hitTest.Distance < 5 &&
hitTest.ChartElement == ChartElement.PlotArea &&
hitTest.Series != null &&
hitTest.Item is CryptoExchange;
}
public async Task<StepResult> Execute(HitTestInfo hitTest)
{
var exchange = hitTest.Item as CryptoExchange;
var currencies = await _dataService.FetchTop4CryptoCurrencies(exchange.Id);
var chart = hitTest.Series.Chart as FlexChart;
// resets chart
Helpers.ResetChart(chart);
chart.ItemsSource = currencies.ToList();
chart.Header = string.Format(StringResources.Exchange, exchange.ExchangeName);
chart.DataLabel.Position = LabelPosition.Top;
chart.DataLabel.Content = "{value}";
chart.ToolTipContent = "Currency : {CurrencyName}\nSymbol : {Symbol}\nCurrent Price : {CurrentPrice}";
// series configuration
chart.Series.Clear();
chart.Series.Add(new Series()
{
Binding = "CurrentPrice",
BindingX = "CurrencyName"
});
// axis configuration
chart.AxisX.Title = "Crypto Currencies";
chart.AxisY.Title = "Current Price";
chart.AxisY.Format = "C";
chart.AxisX.TitleStyle = chart.HeaderStyle = chart.AxisY.TitleStyle = Helpers.AxisStyle;
return new StepResult() { Value = exchange.ExchangeName };
}
}
We have injected our DataService class into this step, which is used inside the Execute method to fetch the top 5 cryptocurrencies of an exchange. Later, we configured the FlexChart’s ItemsSource, Series, Axis, and many more properties.
You might be wondering what this StepResult is getting returned from the above code. StepResult acts as a unique identifier for each executed step.
We hope you have noticed the DrilledPath property and DrillToPath method of IDrillDownManager. DrilledPath property stores all the StepResults returned from the registered steps when executed. So, its instance can be passed to IDrillDownManager’s DrillToPath method to jump to a specific drilled-down step.
Note: Each step should return a new instance of StepResult.
Similarly, you can create the other three steps by following the abovementioned approach. (Please refer to the attached sample for the code.)
Integrating Drill Down Manager with FlexChart
We are ready with our drill down manager and steps, and now it's time to integrate it with FlexChart. Assuming you have already created a project containing FlexChart. We will now configure the initial view of FlexChart. For example, in our case, we will display a column chart showing the top 5 crypto exchanges as follows:
var exchanges = await _dataService.FetchTop5CryptoExchanges();
chart.ItemsSource = exchanges.ToList();
chart.Header = "Top 5 Crypto Currency Exchanges";
chart.DataLabel.Position = LabelPosition.Top;
chart.DataLabel.Content = "{value}";
chart.ToolTipContent = "Listed Currencies : {value}";
// series configuration
chart.Series.Clear();
chart.Series.Add(new Series()
{
Binding = "ListedCurrencies",
BindingX = "ExchangeName"
});
// axis configuration
chart.AxisX.Title = "Crypto Exchanges";
chart.AxisY.Title = "Listed Currencies";
chart.AxisX.TitleStyle = chart.HeaderStyle = chart.AxisY.TitleStyle = Helpers.AxisStyle;
chart.AxisY.AxisLine = true;
And the result will look as follows:
Once it is done, we will register the drill down steps with the drill down manager as follows:
_drillDownManager = new AsyncDrillDownManager(flexChart);
// register drill down steps
_drillDownManager.AddDrillDownStep(new ExchangeTop4CurrenciesStep(_dataService));
_drillDownManager.AddDrillDownStep(new MonthlyPriceChartStep(_dataService));
_drillDownManager.AddDrillDownStep(new WeeklyPriceChartStep(_dataService));
_drillDownManager.AddDrillDownStep(new DailyPriceChartStep(_dataService));
Now we have set up everything that is needed for the drill down. The only thing left is to use the drill-down manager with FlexChart’s mouse click events.
We will left-click the mouse to drill down so we can use IDrillDownManager’s DrillDown method inside FlexChart’s MouseLeftButtonDown event as follows:
private async void OnFlexChartMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
await _drillDownManager.DrillDown(e.GetPosition(flexChart));
}
Similarly, for drilling up, we will right-click the mouse, so we can use FlexChart’s MouseRightButtonDown event as follows:
private async void OnFlexChartMouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
await _drillDownManager.DrillUp();
}
We’re finished! We have implemented asynchronous drill-down with FlexChart.
Please feel free to comment below if you have any queries or suggestions.
Interested in other FlexChart controls? Download ComponentOne Today!