Search code examples
c#wpflivecharts

Update LiveCharts from datatable dynamically


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");
    }

Solution

  • 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; }
    }