When I create a PointSeries chart, the point's color is determined at moment the chart is showing. But I want the point's color changed with the datasource's some content after the chart was shown.
So I make a new PlotInfo class shown as below, and I set DefaultVisualMaterialSelector bind to PlotInfo's ColorFlag, with a Converter.
Finally, the project doesn't work, the point's color didn't change when I modify the ColorFlag.
So does ChartView3D support this kind of abbility? And how can I get what I want?
public class PlotInfo : INotifyPropertyChanged{ public event PropertyChangedEventHandler PropertyChanged; public double XValue { get; set; } public double YValue { get; set; } public string ZValue { get; set; } private string m_sColorFlag; public string ColorFlag { get { return m_sColorFlag;} set { m_sColorFlag = value; if(this.PropertyChanged != null) { PropertyChanged.Invoke(this, new PropertyChangedEventArgs("ColorFlag")); } } }}MainWindow.xaml
<Window x:Class="VariableMaterial.MainWindow" xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation" xmlns:local="clr-namespace:VariableMaterial" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <local:ColorConvert x:Key="myColorConvert" /> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="30"/> <RowDefinition /> </Grid.RowDefinitions> <ToolBar Grid.Row="0" Height="auto"> <Button Content="SetColorFlag=A" Click="ButtonA_Click" Margin="10,0,0,0"/> <Button Content="SetColorFlag=B" Click="ButtonB_Click" Margin="10,0,0,0"/> </ToolBar> <telerik:RadCartesianChart3D Grid.Row="1"> <telerik:RadCartesianChart3D.XAxis> <telerik:LinearAxis3D /> </telerik:RadCartesianChart3D.XAxis> <telerik:RadCartesianChart3D.YAxis> <telerik:LinearAxis3D /> </telerik:RadCartesianChart3D.YAxis> <telerik:RadCartesianChart3D.ZAxis> <telerik:CategoricalAxis3D /> </telerik:RadCartesianChart3D.ZAxis> <telerik:RadCartesianChart3D.Series> <telerik:PointSeries3D PointSize="100" DefaultVisualMaterialSelector="{Binding ColorFlag, Converter={StaticResource myColorConvert}}" XValueBinding="XValue" YValueBinding="YValue" ZValueBinding="ZValue" ItemsSource="{Binding}"/> </telerik:RadCartesianChart3D.Series> <telerik:RadCartesianChart3D.Grid> <telerik:CartesianChart3DGrid /> </telerik:RadCartesianChart3D.Grid> <telerik:RadCartesianChart3D.Behaviors> <telerik:Chart3DCameraBehavior /> </telerik:RadCartesianChart3D.Behaviors> </telerik:RadCartesianChart3D> </Grid></Window>
MainWindow.xaml.cs
using System;using System.Collections.Generic;using System.Collections.ObjectModel;using System.Globalization;using System.Windows;using System.Windows.Data;using System.Windows.Media;using System.Windows.Media.Media3D;namespace VariableMaterial{ /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public ObservableCollection<PlotInfo> source = new ObservableCollection<PlotInfo>(); public MainWindow() { InitializeComponent(); PlotInfo info1 = new PlotInfo() { XValue = 1, YValue = 1, ZValue = "A", ColorFlag = "A", }; PlotInfo info2 = new PlotInfo() { XValue = 2, YValue = 2, ZValue = "B", ColorFlag = "B", }; source.Add(info1); source.Add(info2); this.DataContext = source; } private void ButtonA_Click(object sender, RoutedEventArgs e) { source[0].ColorFlag = "B"; } private void ButtonB_Click(object sender, RoutedEventArgs e) { source[1].ColorFlag = "A"; } } public class ColorConvert : IValueConverter { private Dictionary<Color, Material> colorToMaterialDict = new Dictionary<Color, Material>(); public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { string colorFlag = (string)value; Color color; if (colorFlag == "A") { color = (Color)ColorConverter.ConvertFromString("#007ACC"); } else { color = (Color)ColorConverter.ConvertFromString("#68217A"); } Material material; if (!this.colorToMaterialDict.TryGetValue(color, out material)) { material = new DiffuseMaterial(new SolidColorBrush(color)); colorToMaterialDict[color] = material; } return material; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }}
9 Answers, 1 is accepted
Thank you for the provided code snippet.
The reason why the DefaultVisualMaterialSelector is not working is that it expect object of type MaterialSelector. In the convert method the Material object is returned which will not work as expected. In order the approach to work you can again bind the DefaultVisualMaterialSelector property to a ColorFlag, but this time you can declare this property in a ViewModel class. Then you can create custom class which derives from MaterialSelector and perform your custom logic inside the SelectMaterial() method. The final step is to return new instance of this MaterialSelector in the Convert() method of the IValueConverter in order to reset the colors.
I have create a sample project based on the provided code snippet to demonstrate this approach. Hope this information is helpful.
Regards,
Dinko
Progress Telerik
Thanks Dinko !
Your solution is work well.
In your solution, a special property is defined and bind to DefaultVisualMaterialSelector with a converter, so eachtime this property's value was set, the Convert() method was called and a custom MaterialSelector was return. In the custom MaterialSelector class, a override function SelectMaterial() do some bussiness loggic and return material with the specific color.
So I call this special property a RedrawFlag, because eachtime it's value was set, a redraw event on 3Dpoints will trigered. And I also noticed, when I set one 3Dpoint's color, all 3Dpoints in the chart will be redraw. Is that right?
By using your solution, I create a project with a large points chart about 6000, and the point's color is changed as what I want. But when make the program running for about 2 hours (after press RandomColor button), the using memory keep on increasing from 40MB to 200MB, so is there some code wrong?
The code is as following.
Regards,
Star X.
MainWindow.xaml
<Window x:Class="Large3DPoint_ColorBind.MainWindow" xmlns:local="clr-namespace:Large3DPoint_ColorBind" xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation" mc:Ignorable="d" Title="MainWindow" Height="600" Width="1000"> <Window.Resources> <local:MyColorConvert x:Key="myColorConvert" /> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="30"/> <RowDefinition /> </Grid.RowDefinitions> <ToolBar Grid.Row="0" Height="auto"> <Button Content="SetToGreen" Click="ButtonGreen_Click" Margin="10,0,10,0" Background="LightGray"/> <Button Content="SetToRed" Click="ButtonRed_Click" Margin="10,0,10,0" Background="LightGray"/> <StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="40,0,0,0" Background="GreenYellow"> <TextBlock x:Name="textBlock" TextWrapping="NoWrap" Text="PointIndex(zeroBase):" Margin="10,0,10,0" VerticalAlignment="Center"/> <TextBox x:Name="pointIndex" Margin="10,0,10,0" TextWrapping="Wrap" Text="" Width="50" Background="AliceBlue"/> </StackPanel> <Button x:Name="buttonRandom" Content="RandomColor" VerticalAlignment="Center" Width="100" Background="LightGray" Click="buttonRandom_Click"/> </ToolBar> <telerik:RadCartesianChart3D Grid.Row="1" Background="DimGray"> <telerik:RadCartesianChart3D.XAxis> <telerik:LinearAxis3D /> </telerik:RadCartesianChart3D.XAxis> <telerik:RadCartesianChart3D.YAxis> <telerik:LinearAxis3D /> </telerik:RadCartesianChart3D.YAxis> <telerik:RadCartesianChart3D.ZAxis> <telerik:CategoricalAxis3D /> </telerik:RadCartesianChart3D.ZAxis> <telerik:RadCartesianChart3D.Series> <telerik:PointSeries3D PointSize="10" XValueBinding="XValue" YValueBinding="YValue" ZValueBinding="ZValue" ItemsSource="{Binding SourceData}" DefaultVisualMaterialSelector="{Binding RedrawFlag, Converter={StaticResource myColorConvert}}"/> </telerik:RadCartesianChart3D.Series> <telerik:RadCartesianChart3D.Grid> <telerik:CartesianChart3DGrid /> </telerik:RadCartesianChart3D.Grid> <telerik:RadCartesianChart3D.Behaviors> <telerik:Chart3DCameraBehavior /> <telerik:Chart3DTooltipBehavior/> </telerik:RadCartesianChart3D.Behaviors> </telerik:RadCartesianChart3D> </Grid></Window>
MainWindow.xaml.cs
using System;using System.Windows;namespace Large3DPoint_ColorBind{ public partial class MainWindow : Window { ViewModel vm; System.Timers.Timer timer; Random ra = new Random(); public MainWindow() { InitializeComponent(); vm = new ViewModel(); this.DataContext = vm; } private void ButtonGreen_Click(object sender, RoutedEventArgs e) { int iIndex = -1; if(int.TryParse(pointIndex.Text.ToString(), out iIndex) == true) { SetPointWithColor(iIndex, "green"); } } private void ButtonRed_Click(object sender, RoutedEventArgs e) { int iIndex = -1; if (int.TryParse(pointIndex.Text.ToString(), out iIndex) == true) { SetPointWithColor(iIndex, "red"); } } public void SetPointWithColor(int iIndex, string sColor) { if ((iIndex >= 0) && (iIndex < vm.SourceData.Count)) { vm.SourceData[iIndex].ColorFlag = sColor; //triger the redraw event vm.RedrawFlag = ""; } else { MessageBox.Show("Out of range!"); } } private void buttonRandom_Click(object sender, RoutedEventArgs e) { timer = new System.Timers.Timer() { }; timer.Interval = 100; timer.Elapsed += Timer_Elapsed; timer.Enabled = true; } private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { //throw new NotImplementedException int iColor = ra.Next(0, 100); string sColor = "yellow"; if (iColor%3 == 0) { sColor = "green"; } else if (iColor % 3 == 1) { sColor = "red"; } else { sColor = "yellow"; } int iIndex = ra.Next(0, vm.SourceData.Count); SetPointWithColor(iIndex, sColor); } }}
PlotInfo.cs
using Telerik.Windows.Controls;namespace Large3DPoint_ColorBind{ public class PlotInfo : ViewModelBase { public double XValue { get; set; } public double YValue { get; set; } public double ZValue { get; set; } private string m_sColorFlag; public string ColorFlag { get { return m_sColorFlag; } set { m_sColorFlag = value; this.OnPropertyChanged("ColorFlag"); } } }}
ViewModel.cs
using System.Collections.ObjectModel;using Telerik.Windows.Controls;namespace Large3DPoint_ColorBind{ public class ViewModel : ViewModelBase { private ObservableCollection<PlotInfo> _sourceData; public ObservableCollection<PlotInfo> SourceData { get { return _sourceData; } set { _sourceData = value; this.OnPropertyChanged("SourceData"); } } private string m_sRedrawFlag; public string RedrawFlag { get { return m_sRedrawFlag; } set { m_sRedrawFlag = value; this.OnPropertyChanged("RedrawFlag"); } } public ViewModel() { SourceData = new ObservableCollection<PlotInfo>(); this.GetData(); } private void GetData() { for (int k = 0; k < 60; k++) { for (int m = 0; m < 50; m++) { //1st layer SourceData.Add(new PlotInfo { XValue = k, YValue = m, ZValue = 5, ColorFlag = "white" }); } } for (int k = 0; k < 60; k++) { for (int m = 0; m < 50; m++) { //2nd layer SourceData.Add(new PlotInfo { XValue = k, YValue = m, ZValue =50, ColorFlag = "white" }); } } } }}
MyDefaultVisualMaterialSelector.cs
using System;using System.Collections.Generic;using System.Windows.Media;using System.Windows.Media.Media3D;using Telerik.Charting;using Telerik.Windows.Controls.ChartView;namespace Large3DPoint_ColorBind{ public class MyDefaultVisualMaterialSelector : MaterialSelector { private Dictionary<Color, Material> colorToMaterialDict = new Dictionary<Color, Material>(); public override Material SelectMaterial(object context) { var value = context as XyzDataPoint3D; var businessItem = value.DataItem as PlotInfo; string sColorRGB = "#000000"; switch (businessItem.ColorFlag) { case "yellow": sColorRGB = "#ffff00"; break; case "green": sColorRGB = "#008000"; break; case "red": sColorRGB = "#ff0000"; break; case "white": sColorRGB = "#FFFFFF"; break; default: break; } Color color = (Color)ColorConverter.ConvertFromString(sColorRGB); Material material; if (!this.colorToMaterialDict.TryGetValue(color, out material)) { material = new DiffuseMaterial(new SolidColorBrush(color)); colorToMaterialDict[color] = material; } return material; } }}
ColorConvert.cs
using System;using System.Collections.Generic;using System.Globalization;using System.Windows.Data;namespace Large3DPoint_ColorBind{ public class MyColorConvert : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return new MyDefaultVisualMaterialSelector(); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }}
Thanks Dinko !
Your solution is work well.
In your solution, a special property is defined and bind to DefaultVisualMaterialSelector with a converter, so eachtime this property's value was set, the Convert() method was called and a custom MaterialSelector was return. In the custom MaterialSelector class, a override function SelectMaterial() do some bussiness loggic and return material with the specific color.
So I call this special property a RedrawFlag, because eachtime it's value was set, a redraw event on 3Dpoints will trigered. And I also noticed, when I set one 3Dpoint's color, all 3Dpoints in the chart will be redraw. Is that right?
By using your solution, I create a project with a large points chart about 6000, and the point's color is changed as what I want. But when make the program running for about 2 hours (after press RandomColor button), the using memory keep on increasing from 40MB to 200MB, so is there some code wrong?
The code is as following.
Regards,
Star X.
MainWindow.xaml
<Window x:Class="Large3DPoint_ColorBind.MainWindow" xmlns:local="clr-namespace:Large3DPoint_ColorBind" xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation" mc:Ignorable="d" Title="MainWindow" Height="600" Width="1000"> <Window.Resources> <local:MyColorConvert x:Key="myColorConvert" /> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="30"/> <RowDefinition /> </Grid.RowDefinitions> <ToolBar Grid.Row="0" Height="auto"> <Button Content="SetToGreen" Click="ButtonGreen_Click" Margin="10,0,10,0" Background="LightGray"/> <Button Content="SetToRed" Click="ButtonRed_Click" Margin="10,0,10,0" Background="LightGray"/> <StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="40,0,0,0" Background="GreenYellow"> <TextBlock x:Name="textBlock" TextWrapping="NoWrap" Text="PointIndex(zeroBase):" Margin="10,0,10,0" VerticalAlignment="Center"/> <TextBox x:Name="pointIndex" Margin="10,0,10,0" TextWrapping="Wrap" Text="" Width="50" Background="AliceBlue"/> </StackPanel> <Button x:Name="buttonRandom" Content="RandomColor" VerticalAlignment="Center" Width="100" Background="LightGray" Click="buttonRandom_Click"/> </ToolBar> <telerik:RadCartesianChart3D Grid.Row="1" Background="DimGray"> <telerik:RadCartesianChart3D.XAxis> <telerik:LinearAxis3D /> </telerik:RadCartesianChart3D.XAxis> <telerik:RadCartesianChart3D.YAxis> <telerik:LinearAxis3D /> </telerik:RadCartesianChart3D.YAxis> <telerik:RadCartesianChart3D.ZAxis> <telerik:CategoricalAxis3D /> </telerik:RadCartesianChart3D.ZAxis> <telerik:RadCartesianChart3D.Series> <telerik:PointSeries3D PointSize="10" XValueBinding="XValue" YValueBinding="YValue" ZValueBinding="ZValue" ItemsSource="{Binding SourceData}" DefaultVisualMaterialSelector="{Binding RedrawFlag, Converter={StaticResource myColorConvert}}"/> </telerik:RadCartesianChart3D.Series> <telerik:RadCartesianChart3D.Grid> <telerik:CartesianChart3DGrid /> </telerik:RadCartesianChart3D.Grid> <telerik:RadCartesianChart3D.Behaviors> <telerik:Chart3DCameraBehavior /> <telerik:Chart3DTooltipBehavior/> </telerik:RadCartesianChart3D.Behaviors> </telerik:RadCartesianChart3D> </Grid></Window>
MainWindow.xaml.cs
using System;using System.Windows;namespace Large3DPoint_ColorBind{ public partial class MainWindow : Window { ViewModel vm; System.Timers.Timer timer; Random ra = new Random(); public MainWindow() { InitializeComponent(); vm = new ViewModel(); this.DataContext = vm; } private void ButtonGreen_Click(object sender, RoutedEventArgs e) { int iIndex = -1; if(int.TryParse(pointIndex.Text.ToString(), out iIndex) == true) { SetPointWithColor(iIndex, "green"); } } private void ButtonRed_Click(object sender, RoutedEventArgs e) { int iIndex = -1; if (int.TryParse(pointIndex.Text.ToString(), out iIndex) == true) { SetPointWithColor(iIndex, "red"); } } public void SetPointWithColor(int iIndex, string sColor) { if ((iIndex >= 0) && (iIndex < vm.SourceData.Count)) { vm.SourceData[iIndex].ColorFlag = sColor; //triger the redraw event vm.RedrawFlag = ""; } else { MessageBox.Show("Out of range!"); } } private void buttonRandom_Click(object sender, RoutedEventArgs e) { timer = new System.Timers.Timer() { }; timer.Interval = 100; timer.Elapsed += Timer_Elapsed; timer.Enabled = true; } private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { //throw new NotImplementedException int iColor = ra.Next(0, 100); string sColor = "yellow"; if (iColor%3 == 0) { sColor = "green"; } else if (iColor % 3 == 1) { sColor = "red"; } else { sColor = "yellow"; } int iIndex = ra.Next(0, vm.SourceData.Count); SetPointWithColor(iIndex, sColor); } }}
PlotInfo.cs
using Telerik.Windows.Controls;namespace Large3DPoint_ColorBind{ public class PlotInfo : ViewModelBase { public double XValue { get; set; } public double YValue { get; set; } public double ZValue { get; set; } private string m_sColorFlag; public string ColorFlag { get { return m_sColorFlag; } set { m_sColorFlag = value; this.OnPropertyChanged("ColorFlag"); } } }}
ViewModel.cs
using System.Collections.ObjectModel;using Telerik.Windows.Controls;namespace Large3DPoint_ColorBind{ public class ViewModel : ViewModelBase { private ObservableCollection<PlotInfo> _sourceData; public ObservableCollection<PlotInfo> SourceData { get { return _sourceData; } set { _sourceData = value; this.OnPropertyChanged("SourceData"); } } private string m_sRedrawFlag; public string RedrawFlag { get { return m_sRedrawFlag; } set { m_sRedrawFlag = value; this.OnPropertyChanged("RedrawFlag"); } } public ViewModel() { SourceData = new ObservableCollection<PlotInfo>(); this.GetData(); } private void GetData() { for (int k = 0; k < 60; k++) { for (int m = 0; m < 50; m++) { //1st layer SourceData.Add(new PlotInfo { XValue = k, YValue = m, ZValue = 5, ColorFlag = "white" }); } } for (int k = 0; k < 60; k++) { for (int m = 0; m < 50; m++) { //2nd layer SourceData.Add(new PlotInfo { XValue = k, YValue = m, ZValue =50, ColorFlag = "white" }); } } } }}
MyDefaultVisualMaterialSelector.cs
using System;using System.Collections.Generic;using System.Windows.Media;using System.Windows.Media.Media3D;using Telerik.Charting;using Telerik.Windows.Controls.ChartView;namespace Large3DPoint_ColorBind{ public class MyDefaultVisualMaterialSelector : MaterialSelector { private Dictionary<Color, Material> colorToMaterialDict = new Dictionary<Color, Material>(); public override Material SelectMaterial(object context) { var value = context as XyzDataPoint3D; var businessItem = value.DataItem as PlotInfo; string sColorRGB = "#000000"; switch (businessItem.ColorFlag) { case "yellow": sColorRGB = "#ffff00"; break; case "green": sColorRGB = "#008000"; break; case "red": sColorRGB = "#ff0000"; break; case "white": sColorRGB = "#FFFFFF"; break; default: break; } Color color = (Color)ColorConverter.ConvertFromString(sColorRGB); Material material; if (!this.colorToMaterialDict.TryGetValue(color, out material)) { material = new DiffuseMaterial(new SolidColorBrush(color)); colorToMaterialDict[color] = material; } return material; } }}
ColorConvert.cs
using System;using System.Collections.Generic;using System.Globalization;using System.Windows.Data;namespace Large3DPoint_ColorBind{ public class MyColorConvert : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return new MyDefaultVisualMaterialSelector(); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }}
oh my god, sorry i don't know why it was paste twice, maybe my network is going wrong.
Can the administrator of the web delete one of them?
Thank you for the provided code snippets.
I am currently investigating your scenario and I need more time. Will contact you tomorrow with further information about your case.
Regards,
Dinko
Progress Telerik
It seems that the material caching mechanism doesn't get cleaned in some case, but we will do some additional time to check why this happens and what alternative solution to suggest.
Regards,
Martin Ivanov
Progress Telerik
Thanks Dinko,
Thanks Martin Ivanov!
Hello Star X,
The memory increases because the material/shape cache of the chart populates with new materials each time a new instance of the custom material selector is created. This way each selector instance creates new instances of the materials. Even though the custom selector caches the materials, this happens only for the objects created in the current instance of the selector. The next time a color changes, a new instance of the selector is created along with the new materials.
To resolve this, use a static field for the "colorToMaterialDict" dictionary in the MyDefaultVisualMaterialSelector class. Here is an example in code:
public class MyDefaultVisualMaterialSelector : MaterialSelector
{
private static Dictionary<Color, Material> colorToMaterialDict = new Dictionary<Color, Material>();
// other code here
}Regards,
Martin Ivanov
Progress Telerik
Hello Martin Ivanov,
Your solution is very good!
After I apply your solution to my project, the program run with a beginning memory size of 38MB,and grow to a max memory size of 48MB in half hour, and it stabilized to this value during the next 2 hours.
So thanks you all, it help me a lot!
Thanks!
Regards,
Star X

