Search code examples
asp.netwpflivecharts

Why my 2 instances of Livecharts cartesian not working?


i am currently having a problem with my code.

Basically i need to show 2 graph in one grid of my application but it seems to be not working. The problem is one graph will show and the 2nd one will not plot the graph.

Below is the code that is used:

XAML:

                   <Grid>
                        <lvc:CartesianChart x:Name="cartchartdb" Series="{Binding SeriesCollection}" LegendLocation="Right" Margin="10,249,578.4,218.2" >
                            <lvc:CartesianChart.AxisY>
                                <lvc:Axis Title="Average Gap (Meter)" LabelFormatter="{Binding YFormatter}"></lvc:Axis>
                            </lvc:CartesianChart.AxisY>
                            <lvc:CartesianChart.AxisX>
                                <lvc:Axis Title="Time" Labels="{Binding Labels}"></lvc:Axis>
                            </lvc:CartesianChart.AxisX>
                        </lvc:CartesianChart>
                    </Grid>

                    <Grid>
                        <lvc:CartesianChart Series="{Binding SeriesCollection2}" LegendLocation="Right" Margin="792,160,9.8,238" >
                            <lvc:CartesianChart.AxisY>
                                <lvc:Axis Title="Sales" LabelFormatter="{Binding YFormatter2}"></lvc:Axis>
                            </lvc:CartesianChart.AxisY>
                            <lvc:CartesianChart.AxisX>
                                <lvc:Axis Title="Month" Labels="{Binding Labels2}"></lvc:Axis>
                            </lvc:CartesianChart.AxisX>
                        </lvc:CartesianChart>
                    </Grid>

C#:

public MainWindow(){
cartchartinit();
cartchartinit2();
}

private void cartchartinit2()
    {
        SeriesCollection2 = new SeriesCollection
        {
            new LineSeries
            {
                Title = "Series 1",
                Values = new ChartValues<double> { 4, 6, 5, 2 ,7 }
            },
            new LineSeries
            {
                Title = "Series 2",
                Values = new ChartValues<double> { 6, 7, 3, 4 ,6 }
            }
        };

        Labels2 = new[] { "Jan", "Feb", "Mar", "Apr", "May" };
        YFormatter2 = value => value.ToString("C");

        //modifying the series collection will animate and update the chart
        SeriesCollection2.Add(new LineSeries
        {
            Values = new ChartValues<double> { 5, 3, 2, 4 },
            LineSmoothness = 0 //straight lines, 1 really smooth lines
        });

        //modifying any series values will also animate and update the chart
        SeriesCollection2[2].Values.Add(5d);

        DataContext = this;
    }

    public SeriesCollection SeriesCollection2 { get; set; }
    public string[] Labels2 { get; set; }
    public Func<double, string> YFormatter2 { get; set; }

private void cartchartinit()
    {

        SeriesCollection = new SeriesCollection
        {
            new LineSeries
            {
                Title = "Average Vehicles Gap",
                Values = null
            },
           /* new LineSeries
            {
                Title = "Avg Gap (Metre)",
                Values = null



            },*/

            /*new LineSeries
            {
                Title = "Series 3",
                Values = new ChartValues<double> { 4,2,7,2,7 },
                PointGeometry = DefaultGeometries.Square,
                PointGeometrySize = 15
            }*/
        };

        Labels = null;
        YFormatter = value => value.ToString("");

        

        DataContext = this;

    }
    public SeriesCollection SeriesCollection { get; set; }
    public string[] Labels { get; set; }
    public Func<double, string> YFormatter { get; set; }

When i just use the cartchartinit() method only, it works. but when i add the cartchartinit2(), it only plot the graph for the later chart. Am i doing this wrong?

A help would be appreciated.

Thank you


Solution

  • The first chart is empty, because the binding source is too.
    You commented the initialization of the SeriesCollection property and set the LineSeries.Values property of the only series item to null ==> no data set to show here. You also have issues with property changes not being reported to the binding engine.

    There are several issues with your code. To address the most important:

    Layout

    Don't wrap each control into a Grid.
    This adds nothing but render performance costs. The wrapper grids are absolutely redundant. Grid is a layout Panel, but currently you are not using it for element arrangement.

    Don't use Margin to position your grids.
    This doesn't allow your application to scale and a layout based on margins is cumbersome to implement.
    If you need absolute positioning use Canvas. If you need relative positioning use a Panel like Grid e.g. for column and row based layout or a StackPanel for simple vertically or horizontally arranged elements.

    Use Margin only for small adjustments, preferably to their relative screen position.

    From your margins it looks like you want your charts displayed in two rows and two columns (diagonal from top left to bottom right). In this case use the Grid:

    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
      </Grid.ColumnDefinitions>
    
      <lvc:CartesianChart Grid.Row="0" Grid.Column="0"
                          Series="{Binding SeriesCollection}" 
                          LegendLocation="Right">
        ...
      </lvc:CartesianChart>
    
      <lvc:CartesianChart Grid.Row="1" Grid.Column="1"
                          Series="{Binding SeriesCollection2}" 
                          LegendLocation="Right">
        ...
      </lvc:CartesianChart>
    </Grid>
    

    If you want to align the charts next to each other simply use a StackPanel:

    <StackPanel Orientation="Horizontal">    
      <lvc:CartesianChart Series="{Binding SeriesCollection}" 
                          LegendLocation="Right">
        ...
      </lvc:CartesianChart>
    
      <lvc:CartesianChart Series="{Binding SeriesCollection2}" 
                          LegendLocation="Right">
        ...
      </lvc:CartesianChart>
    </StackPanel>
    

    Data binding

    You use data binding to assign the chart data to the chart. That's is fine. But you are setting up the binding source after the XAML binding expressions are initialized. Since all your source properties e.g., SeriesCollection and SeriesCollection2 are neither a DependencyProperty nor raising the INotifyPropertyChanged.PropertyChanged event, changes/assignments of those properties are not picked up by the binding engine. In your case it works, but only because you are refreshing the Binding.Source, by re-assigning this.DataContext = this after each property change.
    This will force every binding in your view to initialize again.

    In more complex applications this will noticeably increase startup time or lead to sluggish UI.
    WPF by default inherits the DataContext property value down the visual tree (with exceptions e.g. DataTemplate): every child element shares implicitly the same DataContext of their parent.
    This means refreshing this property affects the complete visual tree! ==> don't refresh the Binding.Source (especially the DataContext) just because the Binding.Path was updated.

    Let the binding engine handle those changes:

    Whenever the source property of a binding is expected to change dynamically, the source object must implement those properties as DependencyProperty or in case this object is not a DependencyObject, must implement INotifyPropertyChanged.

    See Data binding overview in WPF and Dependency properties overview

    Window is a DependencyObject, therefore MainWindow should implement all properties that serve as binding source or binding target as DependencyProerty:

    partial class MainWindow : Window
    {
      public static readonly DependencyProperty SeriesCollectionProperty = DependencyProperty.Register(
        "SeriesCollection",
        typeof(SeriesCollection),
        typeof(MainWindow),
        new PropertyMetadata(default(SeriesCollection)));
    
      public SeriesCollection SeriesCollection
      {
        get => (SeriesCollection) GetValue(MainWindow.SeriesCollectionProperty);
        set => SetValue(MainWindow.SeriesCollectionProperty, value);
      }
    
      public static readonly DependencyProperty SeriesCollection2Property = DependencyProperty.Register(
        "SeriesCollection2",
        typeof(SeriesCollection),
        typeof(MainWindow),
        new PropertyMetadata(default(SeriesCollection)));
    
      public SeriesCollection SeriesCollection2
      {
        get => (SeriesCollection) GetValue(MainWindow.SeriesCollection2Property);
        set => SetValue(MainWindow.SeriesCollection2Property, value);
      }
    
      ... // Do this for all properties that serve as binding source or target
    
      public MainwWindow()
      {
        InitializeComponent();
        this.DataContext = this;
      }
    
      private void cartchartinit()
      {
        // Aside from their slightly clumsy definition, 
        // dependency properties are used like common CLR properties
        SeriesCollection = new SeriesCollection();
      }
    }