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
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:
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>
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();
}
}