Hi,
I'm trying to display arbitrary number of charts using ItemsControl, using UniformGrid as ItemsPanelTemplate.
When I update the data which is bound to ItemsSource of the ItemsControl, all of the SfChart show empty data for a brief moment, then show actual correct data.
It looks as if SfChart get redraw twice, first when the data is cleared, second when actual data is set.
But my code is just replacing the whole data when a new data set is ready, never calls collection's Clear() method.
The attachment is the xaml for the UserControl.
You can see the empty chart more obviously if you change the FastLineBitmapSeries to FastLineSeries.
Thanks in advance.
<UserControl x:Class="VIEX.Views.PatrollingTrend"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VIEX"
xmlns:prism="http://prismlibrary.com/"
xmlns:sf="http://schemas.syncfusion.com/wpf"
xmlns:sfchart="clr-namespace:Syncfusion.UI.Xaml.Charts;assembly=Syncfusion.SfChart.WPF"
prism:ViewModelLocator.AutoWireViewModel="True">
<prism:Dialog.WindowStyle>
<Style TargetType="Window">
<Setter Property="prism:Dialog.WindowStartupLocation" Value="CenterOwner" />
<Setter Property="Width" Value="1200" />
<Setter Property="Height" Value="700" />
</Style>
</prism:Dialog.WindowStyle>
<UserControl.Resources>
<Style x:Key="ControlLabelStyle" TargetType="Label">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Right" />
</Style>
<Style x:Key="ControlUnitStyle"
BasedOn="{StaticResource ControlLabelStyle}"
TargetType="Label">
<Setter Property="HorizontalContentAlignment" Value="Left" />
</Style>
<Style x:Key="ButtonStyle" TargetType="Button">
<Setter Property="Margin" Value="2" />
<Setter Property="Padding" Value="6,2" />
</Style>
<Style x:Key="IntegerUpDownStyle" TargetType="sf:UpDown">
<Setter Property="ApplyZeroColor" Value="False" />
<Setter Property="NumberDecimalDigits" Value="0" />
<Setter Property="TextAlignment" Value="Center" />
</Style>
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<local:InverseBooleanToVisibilityConverter x:Key="InvBoolToVisibilityConverter" />
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="15*" />
</Grid.RowDefinitions>
<!-- Display Control -->
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal">
<Label Content="Update Interval" Style="{StaticResource ControlLabelStyle}" />
<sf:UpDown Name="UpdateIntervalUpDown"
MinWidth="60"
MaxValue="30"
MinValue="5"
Style="{StaticResource IntegerUpDownStyle}"
Value="{Binding UpdateInterval}" />
<Label Content="s" Style="{StaticResource ControlUnitStyle}" />
<Button Command="{Binding SetUpdateIntervalCommand}"
Content="Set"
Style="{StaticResource ButtonStyle}" />
</StackPanel>
<Button Grid.Column="2"
Command="{Binding ShowPatrollingPlotRangeDialog}"
Content="Plot Configuration"
Style="{StaticResource ButtonStyle}" />
</Grid>
<!-- Device and channel name -->
<Border Grid.Row="1"
BorderBrush="Gray"
BorderThickness="0">
<Viewbox>
<TextBlock Name="OnePaneTitleTextBox"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding DeviceAndChannelName, FallbackValue=Device Name}" />
</Viewbox>
</Border>
<!-- Plot Area -->
<ItemsControl Grid.Row="2" ItemsSource="{Binding FocusedChannelPlotData, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding ColumnCount}" Rows="{Binding RowCount}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Column" Value="{Binding Column}" />
<Setter Property="Grid.Row" Value="{Binding Row}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Data kind label -->
<Label Grid.Row="0"
Margin="6"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Background="LightGray"
Content="{Binding KindDisplayName}" />
<!-- Message when data does not exist -->
<Label Grid.Row="1"
Margin="10"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Content="No Data Available"
Visibility="{Binding HasData, Converter={StaticResource InvBoolToVisibilityConverter}}" />
<sfchart:SfChart Grid.Row="1"
MinWidth="300"
MinHeight="200"
Margin="10"
VerticalAlignment="Stretch"
Background="White"
Visibility="{Binding StandardPlotHasData, Converter={StaticResource BoolToVisibilityConverter}}">
<sfchart:SfChart.PrimaryAxis>
<sfchart:DateTimeAxis EnableScrollBar="False"
FontSize="12"
Header="Date"
LabelFormat="yyyy/MM/dd HH:mm"
RangePadding="Auto" />
</sfchart:SfChart.PrimaryAxis>
<sfchart:SfChart.SecondaryAxis>
<sfchart:NumericalAxis EnableScrollBar="False"
FontSize="12"
Header="{Binding KindUnitName}"
Maximum="{Binding YAxisMaximum}"
Minimum="{Binding YAxisMinimum}"
RangePadding="Additional" />
</sfchart:SfChart.SecondaryAxis>
<sfchart:SfChart.Series>
<sf:FastScatterBitmapSeries sf:ChartTooltip.EnableAnimation="False"
sf:ChartTooltip.ShowDuration="3000"
EnableAnimation="False"
Interior="DeepSkyBlue"
ItemsSource="{Binding Data}"
ListenPropertyChange="True"
ScatterHeight="3"
ScatterWidth="3"
ShapeType="Square"
ShowTooltip="True"
XBindingPath="X"
YBindingPath="Y">
<sf:FastScatterBitmapSeries.TooltipTemplate>
<DataTemplate>
<Border Padding="4"
Background="Black"
BorderBrush="Black"
BorderThickness="0">
<StackPanel HorizontalAlignment="Center" Orientation="Vertical">
<TextBlock Foreground="White"
Text="{Binding Item.TimestampString}"
TextAlignment="Center" />
<TextBlock Foreground="White"
Text="{Binding Item.TooltipValue}"
TextAlignment="Center" />
</StackPanel>
</Border>
</DataTemplate>
</sf:FastScatterBitmapSeries.TooltipTemplate>
</sf:FastScatterBitmapSeries>
<sfchart:FastLineBitmapSeries x:Name="ValueSeries"
EnableAnimation="False"
Interior="DeepSkyBlue"
ItemsSource="{Binding Data, Mode=OneTime}"
Label="{Binding KindDisplayName}"
ShowTooltip="True"
XBindingPath="X"
YBindingPath="Y">
</sfchart:FastLineBitmapSeries>
<sfchart:FastLineSeries EnableAnimation="False"
Interior="DeepPink"
ItemsSource="{Binding UpperThres}"
Label="Upper Limit"
ShowTooltip="False"
XBindingPath="X"
YBindingPath="Y" />
<sfchart:FastLineSeries EnableAnimation="False"
Interior="DarkOrange"
ItemsSource="{Binding LowerThres}"
Label="Lower Limit"
ShowTooltip="False"
XBindingPath="X"
YBindingPath="Y" />
</sfchart:SfChart.Series>
</sfchart:SfChart>
<!-- Custom Plot for I/O -->
<sfchart:SfChart Grid.Row="1"
MinWidth="300"
MinHeight="200"
Margin="10"
VerticalAlignment="Stretch"
Background="White"
Visibility="{Binding KowaIOPlotHasData, Converter={StaticResource BoolToVisibilityConverter}}">
<sfchart:SfChart.Resources>
<ResourceDictionary>
<local:BubbleInteriorConverter x:Key="ColorConverter" />
<DataTemplate x:Key="seriesTemplate">
<Canvas>
<Path Canvas.Left="{Binding RectX}"
Canvas.Top="{Binding RectY}"
Data="M0,0 20,0 20,20 0,20z"
Fill="{Binding Converter={StaticResource ColorConverter}}" />
</Canvas>
</DataTemplate>
</ResourceDictionary>
</sfchart:SfChart.Resources>
<sfchart:SfChart.Header>
<TextBlock>
<Run>All OK:</Run>
<Run Foreground="Aqua">■</Run>
<Run>Error:</Run>
<Run Foreground="DeepPink">■</Run>
<Run>Error:● Normal:○</Run>
</TextBlock>
</sfchart:SfChart.Header>
<sfchart:SfChart.PrimaryAxis>
<sfchart:DateTimeAxis EnableScrollBar="False"
FontSize="12"
Header="Date"
LabelFormat="yyyy/MM/dd HH:mm"
RangePadding="Auto" />
</sfchart:SfChart.PrimaryAxis>
<sfchart:SfChart.SecondaryAxis>
<sfchart:NumericalAxis EnableScrollBar="False"
FontSize="12"
Header="I/O"
Maximum="3"
Minimum="0"
RangePadding="Additional"
ShowGridLines="False"
Visibility="Collapsed" />
</sfchart:SfChart.SecondaryAxis>
<sfchart:ScatterSeries x:Name="BubbleSeries"
CustomTemplate="{StaticResource seriesTemplate}"
EnableAnimation="False"
ItemsSource="{Binding Data}"
Label="I/O Stat"
XBindingPath="X"
YBindingPath="Y">
<sfchart:ScatterSeries.AdornmentsInfo>
<sfchart:ChartAdornmentInfo LabelPosition="Outer"
SegmentLabelContent="LabelContentPath"
ShowLabel="True">
<sfchart:ChartAdornmentInfo.LabelTemplate>
<DataTemplate>
<TextBlock RenderTransformOrigin="0.5,0.5" Text="{Binding Path=Item.AdornmentString}">
<TextBlock.RenderTransform>
<TransformGroup>
<RotateTransform Angle="0" />
<TranslateTransform Y="0" />
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
</DataTemplate>
</sfchart:ChartAdornmentInfo.LabelTemplate>
</sfchart:ChartAdornmentInfo>
</sfchart:ScatterSeries.AdornmentsInfo>
</sfchart:ScatterSeries>
</sfchart:SfChart>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
|
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<Button Margin="20" Content="Clear Data" Click="Button_Click"/>
<Button Margin="20" Content="Update Data" Click="Button_Click_1"/>
</StackPanel>
<ItemsControl Grid.Row="1" ItemsSource="{Binding RootData}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="2" Rows="2"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<chart:SfChart>
<chart:SfChart.PrimaryAxis>
<chart:NumericalAxis/>
</chart:SfChart.PrimaryAxis>
<chart:SfChart.SecondaryAxis>
<chart:NumericalAxis/>
</chart:SfChart.SecondaryAxis>
<chart:SfChart.Series>
<chart:FastLineBitmapSeries ItemsSource="{Binding Data}"
XBindingPath="XValue"
YBindingPath="YValue"/>
</chart:SfChart.Series>
</chart:SfChart>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid> |
|
private void Button_Click(object sender, RoutedEventArgs e)
{
foreach(var item in (this.DataContext as ViewModel).RootData)
{
item.Data.Clear();
}
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Random random = new Random();
foreach (var item in (this.DataContext as ViewModel).RootData)
{
item.Data.Clear();
for (int i = 0; i < 20; i++)
{
var value = random.Next(0, 100);
item.Data.Add(new Model(i, value));
};
}
} |
Hi Yuvaraj,
Thank you for your sample code.
Indeed your code works flawlessly, if I change Button_Click_1() in UserControl1.xaml.cs in your code as follows, it shows empty plot for a brief moment. This is similar to what my code does.
Note: You would also need to change the chart series to FastLineSeries in xaml. FastLineBitmapSeries would be too fast to see the empty plot.
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Random random = new Random();
(DataContext as ViewModel).RootData.Clear();
for( int c = 0; c < 4; c++ ) {
var cd = new ObservableCollection<Model>();
for( int i = 0; i < 2000; i++ ) { // Data point must be large enough
var value = random.Next( 0, 100 );
cd.Add( new Model( i + 40000, value ) );
};
(DataContext as ViewModel).RootData.Add( new ChartViewModel() { Data = cd } );
}
}
In my app, because the number of plots to be displayed changes dynamically, I don't think I can pre-allocate the collections and reuse them.
If you know any workaround for this, it would be much appreciated.
Regards,
Hi Yuvaraj,
I think you posted your reply to wrong thread.
Please check.
|
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Random random = new Random();
for (int i = 0; i < User.Items.Count; i++)
{
var container = User.ItemContainerGenerator.ContainerFromIndex(i);
var chart = (container as ContentPresenter).ContentTemplate.LoadContent() as SfChart;
chart.SuspendSeriesNotification();
}
(DataContext as ViewModel).RootData.Clear();
for (int c = 0; c < 4; c++)
{
var cd = new ObservableCollection<Model>();
for (int i = 0; i < 2000; i++)
{ // Data point must be large enough
var value = random.Next(0, 100);
cd.Add(new Model(i + 40000, value));
};
(DataContext as ViewModel).RootData.Add(new ChartViewModel() { Data = cd });
}
for (int i = 0; i < User.Items.Count; i++)
{
var container = User.ItemContainerGenerator.ContainerFromIndex(i);
var chart = (container as ContentPresenter).ContentTemplate.LoadContent() as SfChart;
chart.ResumeSeriesNotification();
}
} |
Hi Yuvaraj,
Thank you for the info about Suspend/ResumeSeriesNotification().
As my app is based on Prism MVVM framework, all plot data are managed by a ViewModel class, from which I can't access the view (SfChart).
So I defined a SfChart subclass, which has DependencyProperty to call those methods by bindings:
public class SfChartEx : SfChart {
public SfChartEx() : base() {
SuspendSeriesNotification();
}
public static readonly DependencyProperty SuspendUpdateProperty =
DependencyProperty.Register(
"SuspendUpdate",
typeof( bool ),
typeof( SfChartEx ),
new PropertyMetadata( true, new PropertyChangedCallback( SuspendUpdatePropertyChanged ) ) );
public bool SuspendUpdate {
get { return (bool)GetValue( SuspendUpdateProperty ); }
set { SetValue( SuspendUpdateProperty, value ); }
}
private static void SuspendUpdatePropertyChanged( DependencyObject obj, DependencyPropertyChangedEventArgs args ) {
if( (bool)args.NewValue == true ) {
(obj as SfChart).SuspendSeriesNotification();
} else {
(obj as SfChart).ResumeSeriesNotification();
}
}
}
I modified the SfChart part of the xaml:
<local:SfChartEx SuspendUpdate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}, Path=DataContext.ChartUpdateSuspended, UpdateSourceTrigger=PropertyChanged}">
For test, I added a boolean property named ChartUpdateSuspended to your sample ViewModel class, and modified to update the plot data every 3 seconds using timer:
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<ChartViewModel> RootData { get; set; }
public ObservableCollection<Model> ChartData { get; set; }
public ObservableCollection<Model> ChartData1 { get; set; }
public ObservableCollection<Model> ChartData2 { get; set; }
public ObservableCollection<Model> ChartData3 { get; set; }
public ObservableCollection<Model> Data1 { get; set; }
private DispatcherTimer timer;
public int RowCount { get; set; }
public int ColumnCount { get; set; }
private bool _chartUpdateSuspended;
public bool ChartUpdateSuspended {
get { return _chartUpdateSuspended; }
set { _chartUpdateSuspended = value;
RaisePropertyChanged( nameof( ChartUpdateSuspended ) );
}
}
public ViewModel()
{
RootData = new ObservableCollection<ChartViewModel>();
ChartData = new ObservableCollection<Model>();
ChartData1 = new ObservableCollection<Model>();
ChartData2 = new ObservableCollection<Model>();
ChartData3 = new ObservableCollection<Model>();
Random random = new Random();
for( int i = 0; i < 2000; i++ ) {
var value = random.Next( 0, 100 );
var value1 = random.Next( 110, 200 );
var value2 = random.Next( 10, 20 );
var value3 = random.Next( 50, 100 );
ChartData.Add( new Model( i, value ) );
ChartData1.Add( new Model( i, value1 ) );
ChartData2.Add( new Model( i, value2 ) );
ChartData3.Add( new Model( i, value3 ) );
};
RootData.Add( new ChartViewModel() { Data = ChartData } );
RootData.Add( new ChartViewModel() { Data = ChartData1 } );
RootData.Add( new ChartViewModel() { Data = ChartData2 } );
RootData.Add( new ChartViewModel() { Data = ChartData3 } );
timer = new DispatcherTimer();
timer.Interval = new TimeSpan( 0, 0, 3 );
timer.Tick += new EventHandler( updatePlot );
timer.Start();
}
private void updatePlot(object sender, EventArgs e) {
ChartUpdateSuspended = true;
Random random = new Random();
RootData.Clear();
for( int c = 0; c < 4; c++ ) {
var cd = new ObservableCollection<Model>();
for( int i = 0; i < 2000; i++ ) {
var value = random.Next( 0, 100 );
cd.Add( new Model( i + 40000, value ) );
};
RootData.Add( new ChartViewModel() { Data = cd } );
}
ChartUpdateSuspended = false;
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public class ChartViewModel
{
public ObservableCollection<Model> Data { get; set; }
}
public class Model : INotifyPropertyChanged
{
private double xValue;
public double XValue
{
get { return xValue; }
set
{
if (value != xValue)
{
xValue = value;
RaisePropertyChanged(nameof(XValue));
}
}
}
private double yValue;
public double YValue
{
get { return yValue; }
set
{
if (value != this.yValue)
{
this.yValue = value;
RaisePropertyChanged(nameof(YValue));
}
}
}
private double yValue1;
public double YValue1
{
get { return yValue1; }
set
{
if (value != this.yValue1)
{
this.yValue1 = value;
RaisePropertyChanged(nameof(YValue1));
}
}
}
private double yValue2;
public double YValue2
{
get { return yValue2; }
set
{
if (value != this.yValue2)
{
this.yValue2 = value;
RaisePropertyChanged(nameof(YValue2));
}
}
}
public Model(double x, double y)
{
XValue = x;
YValue = y;
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Now the situation is improved, but not perfect and inconsistent. I still see empty plot sometimes, sometimes not.
Could you come up with any workaround for this?
Regards,
Hi Yuvaraj,
Any update to this? It's 9th September.
|
private void updatePlot(object sender, EventArgs e)
{
ChartUpdateSuspended = true;
Random random = new Random();
for (int c = 0; c < 4; c++)
{
var cd = new ObservableCollection<Model>();
for (int i = 0; i < 2000; i++)
{
var value = random.Next(0, 100);
cd.Add(new Model(i + 40000, value));
};
RootData[c] = new ChartViewModel() { Data = cd };
} |
Hi Yuvaraj,
As I stated before, the number of the charts dynamically changes in my actual app, so I just can't simply replace the member of the ObservableCollection by assigning new values to the specific slots.
If I can't use RootData.Clear(), do I have to this?
I'm just wondering an empty chart would appear when RemoveAt() is called.
Regards,
Hi Yuvaraj,
Your new sample code works, but I feel this is a bad workaround and changing a subject.
If I do RemoveAt() or Add() instead of Clear(), and that can avoid displaying the empty chart, doesn't it mean ChartUpdateSuspend is not working at all?
Actually, even If I comment out "ChartUpdateSuspend = false" in the new sample code, I still works and I don't see any empty chart.
Updating the whole elements (including the number of elements) of RootData without Clear() is just frustrating.
Is there any way to make ChartUpdateSuspend work properly?
Regards,
|
private void updatePlot(object sender, EventArgs e)
{
ChartUpdateSuspended = true;
Random random = new Random();
for (int c = 0; c < 3; c++)
{
for (int i = 0; i < 2000; i++)
{
RootData[c].Data.RemoveAt(0);
var value = random.Next(0, 100);
RootData[c].Data.Add(new Model(i + 40000, value));
};
}
ChartUpdateSuspended = false;
} |