Update LiveCharts from datatable dynamically

2.5k views Asked by At

I have been reading documentation for several days now but I can't get it working, no matter what I try. I have Basic Row chart and want to display as a graph time spent. My bar title and value are changing constantly (more items getting added). I am able to add bars with my current code, but I am not able to add title for each added bar. Only first title / first bar title is visible, all the others / coming are not visible.

How to add title and value in a proper way? (I am already familiar with documentation https://lvcharts.net/App/examples/v1/wf/Basic%20Row)

Here is my code (you can see from commented out sections what has been tried yet):

    public static SeriesCollection SeriesCollection { get; set; }
    public static string[] Labels { get; set; }
    public static List<string> LabelsList { get; set; }
    public static Func<double, string> Formatter { get; set; }

    public AppUsageBarGraph()
    {
        InitializeComponent();

        LabelsList = new List<string>();

        SeriesCollection = new SeriesCollection
        {
            new RowSeries
            {
                Values = new ChartValues<double> { },
                DataLabels = true
            }
        };

        DataContext = this;
    }

    public static void UpdateChart()
    {
        SeriesCollection[0].Values.Clear();
        LabelsList.Clear();

        //Labels = MainProcess.ActivityLogGrouped.Rows.Cast<DataRow>().Select(row => row["Window Title"].ToString()).ToArray();

        foreach (DataRow row in MainProcess.ActivityLogGrouped.Rows)
        {
            SeriesCollection[0].Values.Add(Convert.ToDouble(row["Time Spent"]));
            //SeriesCollection[0]. = row["Time Spent"].ToString());
            LabelsList.Add(row["Window Title"].ToString());
        }

        //MessageBox.Show(Labels[0].ToString());

        Labels = LabelsList.ToArray(); 


        //foreach (var item in Labels)
        //{
        //    MessageBox.Show(item);
        //}

        //Labels = new[]
        //        {
        //              "Shea Ferriera",
        //              "Maurita Powel",
        //              "Scottie Brogdon",
        //              "Teresa Kerman",
        //              "Nell Venuti",
        //              "Anibal Brothers",
        //              "Anderson Dillman"
        //           };

        //Formatter = value => value.ToString("N");
    }
1

There are 1 answers

8
BionicCode On BEST ANSWER

The key is to use a ObservableCollection<string> instead of a string[].

I also recommend to use a model to encapsulate the actual chart data points. I introduced the class DataModel for this reason.

The following example shows how to dynamically bind values and labels to the chart. I should say that making everything public static is a very bad smelling code design.

MainWindow.xaml

<Window>
  <Window.DataContext>
    <ViewModel />
  </Window.DataContext>

  <wpf:CartesianChart Height="500">
    <CartesianChart.Series>
      <RowSeries Values="{Binding ChartModel.RowSeries}"
                 Configuration="{Binding ChartModel.RowSeriesConfiguration}"/>
    </CartesianChart.Series>
    <CartesianChart.AxisY>
      <Axis Labels="{Binding ChartModel.RowSeriesLabels}" />
    </CartesianChart.AxisY>
  </CartesianChart>
</Window>

ViewModel.cs

public class ViewModel : INotifyPropertyChanged
{
  public ViewModel()
  {
    this.ChartModel = new ChartModel();
  }

  public void UpdateChart()
  {
    foreach (DataRow row in MainProcess.ActivityLogGrouped.Rows)
    {
      if (double.TryParse(row["Time Spent"], out double value)
      {
        string label = row["Window Title"];
        var newDataModel = new DataModel(value, label);

        this.ChartModel.RowSeries.Add(newDataModel);            
        this.ChartModel.RowSeriesLabels.Add(newDataModel.Label);
      }
    }
  }

  public ChartModel ChartModel { get; set; }

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

ChartModel.cs

public class ChartModel : INotifyPropertyChanged
{
  public ChartModel()
  {
    // Initialize chart
    this.RowSeries = new ChartValues<DataModel>()
    {
      new DataModel(20, "Shea Ferriera"),
      new DataModel(100, "Maurita Powel"),
      new DataModel(60, "Scottie Brogdon"),
    };

    // Create labels
    this.RowSeriesLabels = new ObservableCollection<string>();
    foreach (DataModel dataModel in this.RowSeries)
    {
      this.RowSeriesLabels.Add("dataModel.Label");
    }

    // DatModel to value mapping
    this.RowSeriesConfiguration = new CartesianMapper<DataModel>()
      .X(dataModel => dataModel.Value);
  }

  public CartesianMapper<DataModel> RowSeriesConfiguration { get; set; }
  public ChartValues<DataModel> RowSeries { get; set; }
  public ObservableCollection<string> RowSeriesLabels { get; set; }

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

DataModel.cs

public class DataModel
{
  public DataModel(double value, string label)
  {
    this.Value = value;
    this.Label = label;
  }

  public double Value { get; set; }
  public string Label { get; set; }
}