Krowi
Krowi

Reputation: 1615

Adding ChartSeries programmatically (MVVM) in Modern UI (Metro) Charts

I've been searching a while now for a good looking chart visualization solution for my project and I finally found it (https://modernuicharts.codeplex.com). I also posted this question on the discussion board there but I did not have a reply...

I've been playing around a little with the charts and I really love them. Very smooth and beautiful. There is 1 thing I need a little more information about and that is how I can add ChartSeries at runtime.

EDIT: I managed to add bars on runtime but there is some bug I can't seem to figure out. Let's start with an image:

Image

As you can see in the image, the titles on the axis are correct and scale when I scale the window. The problem here are the bars. The bars seem to have a fixed width and don't want to fit on the screen. The way I did this is as follow:

In XAML I placed the control like this:

<chart:StackedColumnChart ChartTitle="Total"
                          ChartSubTitle="(800)"
                          Series="{Binding Bars}">
</chart:StackedColumnChart>

In my code behind I added the Bars like this:

using System;
using System.Collections.Generic;
using De.TorstenMandelkow.MetroChart;
using System.Collections.ObjectModel;

namespace AutoShop
{
class OccupationChartGroupViewModel
{
    public OccupationChartGroupViewModel()
    {
        List<Tuple<string, int, double>> reqProdHoursPerWeek = DbServiceSegmentRequirement.GetProdSegReq();
        List<Tuple<string, int, double>> reqPlanHoursPerWeek = DbServiceSegmentRequirement.GetPlanSegReq();
        List<Tuple<string, int, double>> reqRepairHoursPerWeek = DbServiceSegmentRequirement.GetRepairSegReq();

        List<Tuple<string, int, double>> reqHoursPerWeek = new List<Tuple<string, int, double>>();
        reqHoursPerWeek.AddRange(reqProdHoursPerWeek);
        reqHoursPerWeek.AddRange(reqPlanHoursPerWeek);
        reqHoursPerWeek.AddRange(reqRepairHoursPerWeek);

        reqHoursPerWeek.Sort();

        switch (reqHoursPerWeek[0].Item2.ToString())
        {
            case "0":
                if (reqHoursPerWeek[1].Item2.ToString() != "1")
                    reqHoursPerWeek.Add(new Tuple<string, int, double>(reqHoursPerWeek[0].Item1, 1, 0.0));
                else
                    if (reqHoursPerWeek[1].Item1 != reqHoursPerWeek[0].Item1)
                        reqHoursPerWeek.Add(new Tuple<string, int, double>(reqHoursPerWeek[0].Item1, 1, 0.0));
                break;

            case "1":
                reqHoursPerWeek.Add(new Tuple<string, int, double>(reqHoursPerWeek[0].Item1, 0, 0.0));
                if (reqHoursPerWeek[1].Item2.ToString() != "2")
                    reqHoursPerWeek.Add(new Tuple<string, int, double>(reqHoursPerWeek[0].Item1, 2, 0.0));
                else
                    if (reqHoursPerWeek[1].Item1 != reqHoursPerWeek[0].Item1)
                        reqHoursPerWeek.Add(new Tuple<string, int, double>(reqHoursPerWeek[0].Item1, 2, 0.0));
                break;

            case "2":
                reqHoursPerWeek.Add(new Tuple<string, int, double>(reqHoursPerWeek[0].Item1, 0, 0.0));
                reqHoursPerWeek.Add(new Tuple<string, int, double>(reqHoursPerWeek[0].Item1, 1, 0.0));
                break;

            default: break;
        }

        reqHoursPerWeek.Sort();

        Bars = new ObservableCollection<De.TorstenMandelkow.MetroChart.ChartSeries>();

        ObservableCollection<TestClass> blocks = new ObservableCollection<TestClass>();

        foreach (Tuple<string, int, double> reqHours in reqHoursPerWeek)
        {
            string group = reqHours.Item2.ToString() == "2" ? "Order1" : reqHours.Item2.ToString() == "1" ? "Order2" : reqHours.Item2.ToString() == "0" ? "Order3" : "Unknown";

            blocks.Add(new TestClass() { Category = group, Number = reqHours.Item3 });

            if (reqHoursPerWeek.IndexOf(reqHours) + 1 < reqHoursPerWeek.Count)
            {
                if (reqHours.Item1 != reqHoursPerWeek[reqHoursPerWeek.IndexOf(reqHours) + 1].Item1)
                {
                    ChartSeries chartSerie = new ChartSeries();
                    chartSerie.SeriesTitle = reqHours.Item1;
                    chartSerie.DisplayMember = "Category";
                    chartSerie.ValueMember = "Number";
                    chartSerie.ItemsSource = blocks;
                    Bars.Add(chartSerie);

                    blocks = new ObservableCollection<TestClass>();
                }
            }
            else
            {
                ChartSeries chartSerie = new ChartSeries();
                chartSerie.SeriesTitle = reqHours.Item1;
                chartSerie.DisplayMember = "Category";
                chartSerie.ValueMember = "Number";
                chartSerie.ItemsSource = blocks;
                Bars.Add(chartSerie);
            }
        }
    }

    public ObservableCollection<ChartSeries> Bars { get; private set; }
}

// class which represent a data point in the chart
public class TestClass
{
    public string Category { get; set; }

    public double Number { get; set; }
}
}

Lastly, the error I get on design time in the XAML viewer is this one:

**NullReferenceException: Object reference not set to an instance of an object.**

**Stacktrace**
at De.TorstenMandelkow.MetroChart.ChartBase.UpdateDataContextOfSeries()
at De.TorstenMandelkow.MetroChart.ChartBase.InternalDataContextChanged()
at De.TorstenMandelkow.MetroChart.ChartBase.DataContextWatcher_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs args)
at System.Windows.DependencyObject.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
at System.Windows.FrameworkElement.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
at System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs args)
at System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType)
at System.Windows.DependencyObject.InvalidateProperty(DependencyProperty dp, Boolean preserveCurrentValue)
at System.Windows.Data.BindingExpressionBase.Invalidate(Boolean isASubPropertyChange)
at System.Windows.Data.BindingExpression.TransferValue(Object newValue, Boolean isASubPropertyChange)
at System.Windows.Data.BindingExpression.Activate(Object item)
at System.Windows.Data.BindingExpression.AttachToContext(AttachAttempt attempt)
at System.Windows.Data.BindingExpression.MS.Internal.Data.IDataBindEngineClient.AttachToContext(Boolean lastChance)
at MS.Internal.Data.DataBindEngine.Task.Run(Boolean lastChance)
at MS.Internal.Data.DataBindEngine.Run(Object arg)
at MS.Internal.Data.DataBindEngine.OnLayoutUpdated(Object sender, EventArgs e)
at System.Windows.ContextLayoutManager.fireLayoutUpdateEvent()
at System.Windows.ContextLayoutManager.UpdateLayout()
at System.Windows.UIElement.UpdateLayout()

**InnerException: None**

Upvotes: 2

Views: 1263

Answers (1)

Taha Rehman Siddiqui
Taha Rehman Siddiqui

Reputation: 2533

You are using Series instead of SeriesSource property (I tried using SeriesSource but for some reason the chart is coming out empty). Anyways, the error is actually occurring because the above method is called before MVVM calls the get method of the Series binding, hence getting Series as null in the below definition in the ChartBase.cs . I hope that you have the project inside your solution, else open the code provided by them, update the code, compile and reference the generated dll.

    private void UpdateDataContextOfSeries()
    {
        onApplyTemplateFinished = false;

        //ADDED CODE STARTS
        if(this.Series != null)
        {
        //ADDED CODE ENDS
        foreach (var newItem in this.Series)
        {
            if (newItem is FrameworkElement)
            {
                (newItem as FrameworkElement).DataContext = this.DataContext;
            }
        }
        onApplyTemplateFinished = true;
        UpdateSeries();            
        //ADDED CODE STARTS
        }
        //ADDED CODE ENDS
    }    

Just need to check if the series is null before running the loop and applying updates. This method is also called once the series is Get'ed.

Upvotes: 3

Related Questions