Search code examples
c#wpflivecharts

Live chart in wpf


I have to create wpf application for some statistic. Data will come from DB. Now I try to use LiveCharts basic stacked CartesianChart. I would like to have 2 different charts in main window. So I download a example put it as first chart and for second I make copy.

But when I click on run button to generate charts (data in chart is some random values, later it will come from LIST) I see just first chart second is empty.

Here xaml:

<ScrollViewer x:Name="ScrollViewerDay"
              VerticalScrollBarVisibility="Auto"
              HorizontalScrollBarVisibility="Hidden"
              Grid.Row="2"
              Grid.RowSpan="20"
              Grid.ColumnSpan="20">
    <StackPanel x:Name="StackDay"
                Grid.Column="0"
                Grid.Row="2"
                Grid.ColumnSpan="20"
                Grid.RowSpan="21"
                Margin="10,5,10,10">
        <Label x:Name="LabelDayTitle1"
               Content="Scrap Top10 (sorted by A2C number)"
               HorizontalAlignment="Center"
               HorizontalContentAlignment="Center"/>
        <lvc:CartesianChart x:Name="ChartDayA2C"
                            Series="{Binding SeriesCollectionDayA2C}"
                            LegendLocation="Bottom" MinHeight="280">
            <lvc:CartesianChart.AxisX>
                <lvc:Axis Title="Component scrap (Top10 A2C numbers) "
                          Labels="{Binding LabelsDayA2C}"
                          Separator="{x:Static lvc:DefaultAxes.CleanSeparator}" />
            </lvc:CartesianChart.AxisX>
            <lvc:CartesianChart.AxisY>
                <lvc:Axis Title="Usage"
                          LabelFormatter="{Binding FormatterDayA2C}">
                </lvc:Axis>
            </lvc:CartesianChart.AxisY>
        </lvc:CartesianChart>
        <Label x:Name="LabelDayTitle2"
               Content="Scrap Top10 (sorted by shape) "
               HorizontalContentAlignment="Center"
               HorizontalAlignment="Center"/>
        <lvc:CartesianChart x:Name="ChartDayShape"
                            Series="{Binding SeriesCollectionDayShape}"
                            LegendLocation="Bottom"
                            MinHeight="280">
            <lvc:CartesianChart.AxisX>
                <lvc:Axis Title="Component scrap (Top10 Shapes)"
                          Labels="{Binding LabelsDayShape}"
                          Separator="{x:Static lvc:DefaultAxes.CleanSeparator}" />
            </lvc:CartesianChart.AxisX>
            <lvc:CartesianChart.AxisY>
                <lvc:Axis Title="Usage"
                          LabelFormatter="{Binding FormatterDayShape}">
                </lvc:Axis>
            </lvc:CartesianChart.AxisY>
        </lvc:CartesianChart>
    </StackPanel>
</ScrollViewer>

and here code behind:

private void BtDailyShow_Click(object sender, RoutedEventArgs e)
{
    SeriesCollectionDayA2C = new SeriesCollection
    {
        new StackedColumnSeries
        {
            Values = new ChartValues<double> {4, 5, 6, 8},
            StackMode = StackMode.Values, // this is not necessary, values is the default stack mode
            DataLabels = true
        },
        new StackedColumnSeries
        {
            Values = new ChartValues<double> {200, 5, 6, 7},
            StackMode = StackMode.Values,
            DataLabels = true
        }
    };

    //adding series updates and animates the chart
    SeriesCollectionDayA2C.Add(new StackedColumnSeries
    {
        Values = new ChartValues<double> { 6, 2, 7 },
        StackMode = StackMode.Values
    });

    //adding values also updates and animates
    SeriesCollectionDayA2C[2].Values.Add(4d);

    LabelsDayA2C = new[] { "Chrome", "Mozilla", "Opera", "IE" };
    FormatterDayA2C = value => value + " Mill";

    DataContext = this;

    SeriesCollectionDayShape = new SeriesCollection
    {
        new StackedColumnSeries
        {
            Values=new ChartValues<double> {20,40,60,80 },
            StackMode=StackMode.Values,
            DataLabels =true
        },
        new StackedColumnSeries
        {
            Values = new ChartValues<double> {100,200,300,400 },
            StackMode=StackMode.Values,
            DataLabels=true
        }
    };
    SeriesCollectionDayShape.Add(new StackedColumnSeries
    {
        Values = new ChartValues<double> { 30, 50, 60, 90 },
        StackMode = StackMode.Values
    });
    SeriesCollectionDayShape[2].Values.Add(4d);
    LabelsDayShape = new[] { "aaaa", "aass", "eeee", "laka" };
    FormmatterDayShape= value => value + " Mill";
    DataContext = this;
}

public SeriesCollection SeriesCollectionDayShape { get; set; }
public SeriesCollection SeriesCollectionDayA2C { get; set; }
public string[] LabelsDayA2C { get; set; }
public string[] LabelsDayShape { get; set; }
public Func<double, string> FormatterDayA2C { get; set; }
public Func<double,string> FormatterDayShape { get; set; }
public Func<object, object> FormmatterDayShape { get; set; }

Can somebody help me where is a mistake?


Solution

  • The mistake is first line with

    DataContext = this;
    

    It should work if you remove it. Set DataContext once at the end of constructor.

    Why?

    Bindings in wpf are smart, they are able to monitor for changes of properties. For this you have to implement INotifyPropertyChanged for each type, and rise PropertyChanged event with name of property what has its value changed. You could make your code working by doing all said.

    For properties which are never changed notifications is not needed, but that means what you have to set DataContext after all such properties have their value set.

    WPF has many optimizations, so typically inside property setter there is a check if value is a new, which cause many problems, including your case. Another possibility to fix your issue is to perform refresh of value in a hacky way:

    DataContext = this; // first time
    ...
    
    DataContext = null;
    DataContext = this; // refresh the value