diff --git a/DynamicTargetLineSample/DynamicTargetLineSample.sln b/DynamicTargetLineSample/DynamicTargetLineSample.sln new file mode 100644 index 0000000..22ad887 --- /dev/null +++ b/DynamicTargetLineSample/DynamicTargetLineSample.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35506.116 d17.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicTargetLineSample", "DynamicTargetLineSample\DynamicTargetLineSample.csproj", "{59A4FB8C-77CC-40EF-87C4-4ECA9BBE9F8E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {59A4FB8C-77CC-40EF-87C4-4ECA9BBE9F8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59A4FB8C-77CC-40EF-87C4-4ECA9BBE9F8E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59A4FB8C-77CC-40EF-87C4-4ECA9BBE9F8E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59A4FB8C-77CC-40EF-87C4-4ECA9BBE9F8E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/DynamicTargetLineSample/DynamicTargetLineSample/App.xaml b/DynamicTargetLineSample/DynamicTargetLineSample/App.xaml new file mode 100644 index 0000000..e8e50d3 --- /dev/null +++ b/DynamicTargetLineSample/DynamicTargetLineSample/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/DynamicTargetLineSample/DynamicTargetLineSample/App.xaml.cs b/DynamicTargetLineSample/DynamicTargetLineSample/App.xaml.cs new file mode 100644 index 0000000..a54d7ab --- /dev/null +++ b/DynamicTargetLineSample/DynamicTargetLineSample/App.xaml.cs @@ -0,0 +1,14 @@ +using System.Configuration; +using System.Data; +using System.Windows; + +namespace DynamicTargetLineSample +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } + +} diff --git a/DynamicTargetLineSample/DynamicTargetLineSample/AssemblyInfo.cs b/DynamicTargetLineSample/DynamicTargetLineSample/AssemblyInfo.cs new file mode 100644 index 0000000..b0ec827 --- /dev/null +++ b/DynamicTargetLineSample/DynamicTargetLineSample/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/DynamicTargetLineSample/DynamicTargetLineSample/DynamicTargetLineSample.csproj b/DynamicTargetLineSample/DynamicTargetLineSample/DynamicTargetLineSample.csproj new file mode 100644 index 0000000..195e784 --- /dev/null +++ b/DynamicTargetLineSample/DynamicTargetLineSample/DynamicTargetLineSample.csproj @@ -0,0 +1,15 @@ + + + + WinExe + net9.0-windows + enable + enable + true + + + + + + + diff --git a/DynamicTargetLineSample/DynamicTargetLineSample/MainWindow.xaml b/DynamicTargetLineSample/DynamicTargetLineSample/MainWindow.xaml new file mode 100644 index 0000000..4ae070d --- /dev/null +++ b/DynamicTargetLineSample/DynamicTargetLineSample/MainWindow.xaml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DynamicTargetLineSample/DynamicTargetLineSample/MainWindow.xaml.cs b/DynamicTargetLineSample/DynamicTargetLineSample/MainWindow.xaml.cs new file mode 100644 index 0000000..7b624a9 --- /dev/null +++ b/DynamicTargetLineSample/DynamicTargetLineSample/MainWindow.xaml.cs @@ -0,0 +1,64 @@ +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace DynamicTargetLineSample +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + private string _previousValidText = string.Empty; + public MainWindow() + { + InitializeComponent(); + } + + private void TextBox_TextChanged(object sender, TextChangedEventArgs e) + { + if (Y_Axis == null) return; + var maxValue = Y_Axis.Maximum; + + if (sender is TextBox textBox) + { + textBox.TextChanged -= TextBox_TextChanged; + + if (string.IsNullOrWhiteSpace(textBox.Text)) + { + viewModel.Y1 = double.MinValue; + textBox.Text = string.Empty; + } + else + { + if (int.TryParse(textBox.Text, out int newValue)) + { + if (newValue > maxValue) + newValue = (int)maxValue; + else if (newValue < 0) + newValue = 0; + + viewModel.Y1 = newValue; + + textBox.Text = newValue.ToString(); + textBox.CaretIndex = textBox.Text.Length; + } + else + { + textBox.Text = ((int)viewModel.Y1).ToString(); + textBox.CaretIndex = textBox.Text.Length; + } + } + + textBox.TextChanged += TextBox_TextChanged; + } + } + } +} \ No newline at end of file diff --git a/DynamicTargetLineSample/DynamicTargetLineSample/Model.cs b/DynamicTargetLineSample/DynamicTargetLineSample/Model.cs new file mode 100644 index 0000000..929c208 --- /dev/null +++ b/DynamicTargetLineSample/DynamicTargetLineSample/Model.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DynamicTargetLineSample +{ + internal class Model + { + public string? Months { get; set; } + public double Revenue { get; set; } + } +} diff --git a/DynamicTargetLineSample/DynamicTargetLineSample/ViewModel.cs b/DynamicTargetLineSample/DynamicTargetLineSample/ViewModel.cs new file mode 100644 index 0000000..cd92b71 --- /dev/null +++ b/DynamicTargetLineSample/DynamicTargetLineSample/ViewModel.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media; + +namespace DynamicTargetLineSample +{ + internal class ViewModel:INotifyPropertyChanged + { + private double y1; + public double Y1 + { + get => y1; + set + { + if(y1 != value) + { + y1 = value; + OnPropertyChanged(nameof(Y1)); + } + } + } + + public event PropertyChangedEventHandler? PropertyChanged; + + protected void OnPropertyChanged(string name) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } + + public ObservableCollection Data { get; set; } + + public ViewModel() + { + Y1 = 12000; + Data = new ObservableCollection() + { + new Model() { Months = "January", Revenue = 10000 }, + new Model() { Months = "February", Revenue = 13500 }, + new Model() { Months = "March", Revenue = 16000 }, + new Model() { Months = "April", Revenue = 14000 }, + new Model() { Months = "May", Revenue = 12500 }, + new Model() { Months = "June", Revenue = 18000 }, + new Model() { Months = "July", Revenue = 11700 } + }; + } + } +} diff --git a/README.md b/README.md index 9354a2e..54fb29e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,214 @@ -# How-to-create-and-dynamically-update-target-line-for-WPF-Chart -Learn how to add and dynamically update a target line in WPF SfChart using Annotation. Customize its appearance and functionality effortlessly. +# How to create and dynamically update target line for WPF Chart +This article provides a detailed walkthrough on how to create and dynamically update target line in [WPF Chart](https://www.syncfusion.com/wpf-controls/charts). + +The [SfChart](https://help.syncfusion.com/cr/wpf/Syncfusion.UI.Xaml.Charts.SfChart.html) includes support for [Annotations](https://help.syncfusion.com/cr/wpf/Syncfusion.UI.Xaml.Charts.SfChart.html#Syncfusion_UI_Xaml_Charts_SfChart_Annotations), enabling the addition of various types of annotations to enhance chart visualization. Using [HorizontalLineAnnotation](https://help.syncfusion.com/cr/wpf/Syncfusion.UI.Xaml.Charts.HorizontalLineAnnotation.html), you can create and dynamically adjust the target line. + +The Horizontal Line Annotation includes following property: +* [Y1](https://help.syncfusion.com/cr/wpf/Syncfusion.UI.Xaml.Charts.Annotation.html#Syncfusion_UI_Xaml_Charts_Annotation_Y1) - Represents the Y1 Coordinate of the horizontal line Annotation. +* [Stroke](https://help.syncfusion.com/cr/wpf/Syncfusion.UI.Xaml.Charts.ShapeAnnotation.html#Syncfusion_UI_Xaml_Charts_ShapeAnnotation_Stroke) - Represents the brush for the horizontal line annotation outline. +* [StrokeThickness](https://help.syncfusion.com/cr/wpf/Syncfusion.UI.Xaml.Charts.ShapeAnnotation.html#Syncfusion_UI_Xaml_Charts_ShapeAnnotation_StrokeThickness) - Represents the thickness of the horizontal line annotation outline. +* [StrokeDashArray](https://help.syncfusion.com/cr/wpf/Syncfusion.UI.Xaml.Charts.ShapeAnnotation.html#Syncfusion_UI_Xaml_Charts_ShapeAnnotation_StrokeDashArray) - Represents the DashArray of the horizontal line annotation stroke. +* [Text](https://help.syncfusion.com/cr/wpf/Syncfusion.UI.Xaml.Charts.Annotation.html#Syncfusion_UI_Xaml_Charts_Annotation_Text) - Gets or sets the description text for horizontal line Annotation. + +Learn step-by-step instructions and gain insights to create and dynamically update the target line. + +**Step 1:** The layout is created using a grid with two columns. + +**XAML** + + ```xml + + + + + + + + + ``` + +**Step 2:** In first column of grid layout, initialize the [SfChart](https://help.syncfusion.com/wpf/charts/getting-started) and add the axes and series as shown below. + +**XAML** + + ```xml + + + + + + + + + + + + + + + ...... + + + + + + + ``` + +**Step 3:** The [HorizontalLineAnnotation](https://help.syncfusion.com/wpf/charts/annotations#vertical-and-horizontal-line-annotation) is initialized within the [Annotations](https://help.syncfusion.com/wpf/charts/annotations) collection of the [SfChart](https://help.syncfusion.com/wpf/charts/getting-started) to mark a dynamic target value on the Y-axis. The Y1 property is data-bound to the ViewModel, allowing the target line to adjust dynamically when the value changes. + + +**XAML** + + ```xml + + ..... + + + + + ..... + + ``` + +**C#** + + ```csharp +internal class ViewModel : INotifyPropertyChanged +{ + private double y1; + public double Y1 + { + get => y1; + set + { + if(y1 != value) + { + y1 = value; + OnPropertyChanged(nameof(Y1)); + } + } + } + + public event PropertyChangedEventHandler? PropertyChanged; + + protected void OnPropertyChanged(string name) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } + + ..... + + public ViewModel() + { + Y1 = 12000; + ..... + } +} + ``` + +**Step 4:** The second column of the grid layout contains a StackPanel with a Slider, TextBox and TextBlock, allowing the user to change the annotation value dynamically. The TextBox_TextChanged event ensures valid input by clamping values between 0 and the maximum of the Y_Axis. + +**XAML** + + ```xml + + + + + + + + ``` +This code handles the TextChanged event for a TextBox, dynamically updating the Y1 property in the ViewModel while ensuring the value stays within the axis’s maximum and minimum bounds. It also manages text formatting and prevents recursive event triggers. + +**C#** + + ```csharp +private void TextBox_TextChanged(object sender, TextChangedEventArgs e) +{ + if (Y_Axis == null) return; + var maxValue = Y_Axis.Maximum; + + if (sender is TextBox textBox) + { + textBox.TextChanged -= TextBox_TextChanged; + + if (string.IsNullOrWhiteSpace(textBox.Text)) + { + viewModel.Y1 = double.MinValue; + textBox.Text = string.Empty; + } + else + { + if (int.TryParse(textBox.Text, out int newValue)) + { + if (newValue > maxValue) + newValue = (int)maxValue; + else if (newValue < 0) + newValue = 0; + + viewModel.Y1 = newValue; + + textBox.Text = newValue.ToString(); + textBox.CaretIndex = textBox.Text.Length; + } + else + { + textBox.Text = ((int)viewModel.Y1).ToString(); + textBox.CaretIndex = textBox.Text.Length; + } + } + + textBox.TextChanged += TextBox_TextChanged; + } +} + ``` +**Step 5:** This code defines a [HorizontalLineAnnotation](https://help.syncfusion.com/wpf/charts/annotations#vertical-and-horizontal-line-annotation) for a [SfChart](https://help.syncfusion.com/wpf/charts/getting-started), representing a horizontal line at a specified Y-axis value. It includes custom styling such as dashed stroke, text with font formatting, and text alignment settings. + +**XAML** + + ``` + + + ..... + + + + + + + ..... + + + ``` + +**Output:** + +![DynamicTargetLine1](https://github.com/user-attachments/assets/aa0e643e-f62e-4d95-a596-7cd981484d47) + +**Troubleshooting** + +Path too long exception + +If you are facing a path too long exception when building this example project, close Visual Studio and rename the repository to a shorter name before building the project. + +For more details, refer to the KB on [how to create and dynamically update target line for WPF Chart](https://support.syncfusion.com/kb/article/18542/how-to-create--dynamically-update-target-line-for-wpf-chart).