~/overview/1.1.how it works.md

How it works

The library is divided into 3 main modules, the Core the Renderer and the View, the following diagram classifies some assemblies in the library in the corresponding module:

Core: the core contains an API that defines all the shapes the library needs to build a chart, using this API the core is able to build a representation of a chart, the core will generate all the geometries and sizes of every geometry that a chart needs.

Render: the renderer consumes the core API and is responsible to materialize the API geometries to an image in the user interface.

View: finally the view requires both, the Core and the Renderer, the view asks the core what to draw, then it ask the render how to draw it.

Lets explain the cycle of the library with a simple sample, for this case we need to draw a column series, that will only have 3 points.

The following image represents the expected result:

Imagine we added a WPF chart control to our application, when the chart loads in the user interface, it will ask the core:

hey! what do I need to draw?

The core will reply:

You need to draw 3 rectangles, the first one is located at (100, 50) with a width of 20, and a height of 100, the second one has ... dimensions and the third one has ... dimensions.

Now the chart knows what to draw, but it does not know how to draw it. The chart will now ask the Render to draw these rectangles, the Render will build an image with the information the chart requested.

And that's it, now we have our 3 rectangles in the user interface but there is a step missing: the library will needs to animate how these rectangles appear in the user interface, how will the chart animate them? Well the library is creating a lot of images, each image (frame) has a position in time and we replace the image of the chart as the time elapses.

The Core provides a simple (and enough) framework where we an IAnimatable object knows its position in a time line, an IAnimatable object defines Motion properties properties.

Take a look at the LineGeometry class, specially at the X1 property, notice the setter and the getter of the property are linked to the x1 FloatMotion property.

Motion properties are the key to generate animations easily for the library, it is actually extremely easy:

var line = new LineGeometry();
line.X1 = 0;

// ...

line.X1 = 100;

// that is it. 
// that means that the X1 property will move
// from 0 to 100 over a time line.

Notice the setter of the X1 property is calling the MotionProperty.SetMovement() method, it is actually scheduling the value of the property over a time line, this means that this properties might not behave as you would normally expect, actually if you call the getter immediately after the setter, you will not get the value you just set.

var line = new LineGeometry();
line.X1 = 0;

// ... 

// if we immediately call the X1 getter, we will actually get 0 instead of 100
line.X1 = 100;
var x1 = line.X1; // x1 is 0 at this point.

// but as the time elapses
// the getter will return the value of the property according to the IAnimatable.CurrentTime property
// so imagine in this case we are moving from 0 to 100 in 10 seconds using a lineal transition:

// if ONE SECOND has elapsed then:
var x1 = line.X1; // x1 is 10 at this time (1 second) = 100 * (1/10)

// if TWO SECONDS have elapsed then:
var x1 = line.X1; // x1 is 20 at this time (2 second) = 100 * (2/10)

// ...

// if SEVEN SECONDS have elapsed then:
var x1 = line.X1; // x1 is 70 at this time (7 second) = 100 * (7/10)

// finally the animation will complete at 10 seconds
var x1 = line.X1; // x1 is 100 at this time (100 second) = 100 * (10/10)

Thanks to this concept, the Core is able to let the View know how many images it needs to render to animate every shape in the user interface.

Live Charts is flexible and we could use anything we want to draw the shapes in the user interface, but there is an option that shines, SkiaSharp, it offers a a cross-platform 2D graphics API for .NET platforms based on Google's Skia graphics library, LiveCharts Renderer uses SkiaSharp's API to draw all the shapes for all the supported platforms.

Data Changes

LiveCharts changes detection is based on INotifyPropertyChanged and INotifyCollectionChanged interfaces, both provided by the .Net framework. Every time a property or a collection changes and the object implements any of these interfaces, the chart throttles a Measure request, this means that the library will not update every time a change happens, it will only update once every 10 ms (by default).

Once the Measure request actually runs, it will calculate the final size and position of every shape in our chart, then the Update cycle will start to draw every shape and will be in a loop until all the MotionProperties of our shapes are completed.

LiveCharts changes detection is handled by the CollectionDeepObserver class, this object makes it easy for the library to listen and stop listening to changes in all the objects that must fire an update in the library. Listeners must be removed automatically by the library, if not, then it is a bug, you can call the Dispose method if you are facing memory leak issues, but it is not the intention, the library should handle that automatically.

Normally all the objects defined by the library already implement INotifyPropertyChanged or INotifyCollectionChanged but lets take a look at the special cases of the Chart.Series and Series.Values properties, these are properties where the user passes the values the chart must draw in the user interface, so it is up to the developer to implement any of those interfaces, implementing both interfaces should not have a significant performance impact in most of the cases, but there might be cases where performance might be improved if none of these interfaces are implemented.

// CHART.SERIES 

// in this case, the chart will not fire an update
// since List.Add() is not invoking INotifyCollectionChanged.CollectionChanged event
myChart.Series = new List<ISeries>();
myChart.Series.Add(new LineSeries() { Values = new []{ 1, 2, 3} });

// in this case it will update the chart
// since the ObservableCollection class provided by .Net
// already implements the INotifyCollectionChanged interface
myChart.Series = new ObservableCollection<ISeries>();
myChart.Series.Add(new LineSeries() { Values = new []{ 1, 2, 3} });

// for both previous cases
// the LineSeries class, already implements INotifyPropertyChanged
// this means that updating any property in the series will redraw the chart.
myChart.Series[0].Values = new []{ 4, 5, 6}; // this will fire an update




// SERIES.VALUES

// the following sample will not fire an update
// since the City class does not implements INotifyPropertyChanged
public class City
{
  public double Population { get; set; }
}

var seriesCollection = new List<ISeries>();
var lineSeries = new LineSeries 
{ 
  Values = new List<City> 
  { 
    new City { Population = 10 }, 
    new City { Population = 20 } 
  } 
};

lineSeries.Values[0].Population = 20; // will NOT update the chart

myChart.Series = seriesCollection;




// finally the next sample will update the chart automatically
// since the City class does implements INotifyPropertyChanged
public class City : INotifyPropertyChanged
{
  private double? population;

  public double Population { get => population; set { x = population; OnPropertyChanged(); } }

  protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
  {
    PropertyChanged?.Invoke(propertyName, new PropertyChangedEventArgs(propertyName));
  }
}

var seriesCollection = new List<ISeries>();
var lineSeries = new LineSeries 
{ 
  Values = new List<City> 
  { 
    new City { Population = 10 }, 
    new City { Population = 20 } 
  } 
};

lineSeries.Values[0].Population = 20; // will UPDATE the chart

myChart.Series = seriesCollection;

// the library already provides many ready to use objects that will update the chart automatically
// for example, the ObservableValue class
// https://github.com/beto-rodriguez/LiveCharts2/blob/master/src/LiveChartsCore/Defaults/ObservableValue.cs