How to find the right code to create a line chart

Hobby02 26 Reputation points
2021-05-02T20:41:19.457+00:00

In a Visual Studio 2019 C# WPF (.NET) project I like to add a line chart to visualise the measurement data of an amplifier as function of frequency.
In this project I am controlling a frequency generator, making a frequency sweep (X-Axis).
The voltage of the generator is the input of an amplifier under test.
The outputvoltage of the amplifier is measured (Y-axis) as function of the frequency.
I have meanwhile seen a lot of different codes on a lot of sites on the internet how to create a chart and come to the conlusion that there is missing some manual
which is describing exactly how you have to write the code to create a simple chart, explaining the properties of the chart and how to write their codes .
I like to find a website which describes these code and how to type them.
I found a Microsoft website how to create a basic chart step by step using the propertypanel. But I like to know the underlying code.
For example how do I find that to change the background color of the chart should be like Chartareas[0].Backcolor=Color.Blue;
or ChartAreas[0].AxisY.Enabled = AxisEnabled.True; and how do I have to write the code to get the measurement data (e.g. the frequency array and the outputvoltage array) in the chart?
I hope you may help me a bit further.

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,817 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
11,234 questions
{count} votes

5 answers

Sort by: Most helpful
  1. DaisyTian-1203 11,631 Reputation points
    2021-05-04T06:13:16.897+00:00

    I will show you a line chart with tracking cpu usage for you to refer.
    Xaml code:

     <Window.DataContext>  
            <local:MainViewModel/>  
        </Window.DataContext>  
        <Window.Resources>  
            <local:PolygonConverter x:Key="PolygonConverter"/>  
        </Window.Resources>  
        <Grid>  
            <Grid.RowDefinitions>  
                <RowDefinition Height="Auto"/>  
                <RowDefinition/>  
            </Grid.RowDefinitions>  
            <WrapPanel>  
                <TextBlock Margin="5" Text="CPU:"/>  
                <TextBlock Margin="0, 5" Text="{Binding LastCpuValue, StringFormat=##0.##}" FontWeight="Bold"/>  
                <TextBlock Margin="0, 5" Text="%" FontWeight="Bold"/>  
            </WrapPanel>  
            <Border Margin="5" Grid.Row="1" BorderThickness="1" BorderBrush="Red" SnapsToDevicePixels="True">  
                <Canvas ClipToBounds="True">  
                    <Polygon Stroke="LightBlue" Fill="AliceBlue">  
                        <Polygon.Resources>  
                            <Style TargetType="Polygon">  
                                <Setter Property="Points">  
                                    <Setter.Value>  
                                        <MultiBinding Converter="{StaticResource PolygonConverter}">  
                                            <Binding Path="ProcessorTime.Values"/>  
                                            <Binding Path="ActualWidth" RelativeSource="{RelativeSource AncestorType=Canvas}"/>  
                                            <Binding Path="ActualHeight" RelativeSource="{RelativeSource AncestorType=Canvas}"/>  
                                        </MultiBinding>  
                                    </Setter.Value>  
                                </Setter>  
                            </Style>  
                        </Polygon.Resources>  
                    </Polygon>  
                </Canvas>  
            </Border>  
        </Grid>  
    

    C# code:

     public class MainViewModel : NotifyPropertyChanged  
        {  
            public RoundRobinCollection ProcessorTime { get; }  
            private float _lastCpuValue;  
            public float LastCpuValue  
            {  
                get => _lastCpuValue;  
                set  
                {  
                    _lastCpuValue = value;  
                    OnPropertyChanged(nameof(LastCpuValue));  
                }  
            }  
      
            private async void ReadCpu()  
            {  
                try  
                {  
                    using (PerformanceCounter cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total"))  
                    {  
                        while (true)  
                        {  
                            LastCpuValue = cpuCounter.NextValue();  
                            ProcessorTime.Push(LastCpuValue);  
                            await Task.Delay(1000);  
                        }  
                    }  
                }  
                catch (Exception ex)  
                {  
                    Debug.WriteLine(ex.Message);  
                }  
            }  
      
            public MainViewModel()  
            {  
                ProcessorTime = new RoundRobinCollection(100);  
                ReadCpu();  
            }  
        }  
      
      
        public class RoundRobinCollection : NotifyPropertyChanged  
        {  
            private readonly List<float> _values;  
            public IReadOnlyList<float> Values => _values;  
      
            public RoundRobinCollection(int amount)  
            {  
                _values = new List<float>();  
                for (int i = 0; i < amount; i++)  
                    _values.Add(0F);  
            }  
      
            public void Push(float value)  
            {  
                _values.RemoveAt(0);  
                _values.Add(value);  
                OnPropertyChanged(nameof(Values));  
            }  
        }  
    public class NotifyPropertyChanged : INotifyPropertyChanged  
        {  
            public event PropertyChangedEventHandler PropertyChanged;  
            protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)  
                => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
        }  
     public class PolygonConverter : IMultiValueConverter  
        {  
            public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)  
            {  
                PointCollection points = new PointCollection();  
                if (values.Length == 3 && values[0] is IReadOnlyList<float> dataPoints && values[1] is double width && values[2] is double height)  
                {  
                    points.Add(new Point(0, height));  
                    points.Add(new Point(width, height));  
                    double step = width / (dataPoints.Count - 1);  
                    double position = width;  
                    for (int i = dataPoints.Count - 1; i >= 0; i--)  
                    {  
                        points.Add(new Point(position, height - height * dataPoints[i] / 100));  
                        position -= step;  
                    }  
                }  
                return points;  
            }  
      
            public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => null;  
        }  
    

    The result picture :
    93485-3.gif

    Did my answer give you any help? If it doesn't, could you show me the link of your Microsoft website how to create a basic chart step by step using the propertypanel for me to analyze ?


    If the response is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    2 people found this answer helpful.

  2. Peter Fleischer (former MVP) 19,331 Reputation points
    2021-05-06T07:22:56.767+00:00

    Hi,
    you can write yours own plot element like in following demo:

    XAML:

    <Window x:Class="WpfApp1.Window051"  
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
            xmlns:local="clr-namespace:WpfApp051"  
            xmlns:cc="clr-namespace:WpfApp051"  
            mc:Ignorable="d"  
            Title="Window051" Height="400" Width="400">  
      <Window.DataContext>  
        <local:ViewModel/>  
      </Window.DataContext>  
      <Grid>  
        <cc:Plot Margin="10" Data="{Binding Points}"/>  
      </Grid>  
    </Window>  
    

    And code:

    using System;  
    using System.ComponentModel;  
    using System.Runtime.CompilerServices;  
    using System.Threading.Tasks;  
    using System.Windows;  
    using System.Windows.Controls;  
    using System.Windows.Media;  
    using System.Windows.Shapes;  
      
    namespace WpfApp051  
    {  
      public class ViewModel : INotifyPropertyChanged  
      {  
        public ViewModel() => GeneratePoints();  
      
        private async void GeneratePoints()  
        {  
          Random rnd = new Random();  
          do  
          {  
            double[] db = new double[100];  
            for (int i = 0; i < Points.GetUpperBound(0); i++) db[i] = Points[i+1];  
            db[99] = rnd.Next(20, 200);  
            Points = db;  
            OnPropertyChanged(nameof(Points));  
            await Task.Delay(50);  
          } while (true);  
        }  
      
        public double[] Points { get; set; } = new double[100];  
      
        public event PropertyChangedEventHandler PropertyChanged;  
        private void OnPropertyChanged([CallerMemberName] string propName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));  
      }  
      
      class Plot : FrameworkElement  
      {  
        VisualCollection children;  
        Canvas plotArea;  
        Polyline poly;  
        Line xAxis, yAxis;  
        Point Origin = new Point(10, 10);  
        double[] Points { get; set; } = new double[0];  
      
        public Plot()  
        {  
          children = new VisualCollection(this);  
          initAxis();  
          initPlotArea();  
        }  
      
        public int XPoints { get; set; } = 100;  
        public int YPoints { get; set; } = 100;  
        public Brush LineColor { get; set; } = Brushes.Red;  
      
        public static readonly DependencyProperty DataProperty  
        = DependencyProperty.RegisterAttached("Data", typeof(double[]), typeof(Plot),  
          new FrameworkPropertyMetadata(null,FrameworkPropertyMetadataOptions.AffectsArrange,   
            new PropertyChangedCallback(DataChanged)));  
        public static double[] GetData(DependencyObject obj) => (double[])obj.GetValue(DataProperty);  
        public static void SetData(DependencyObject obj, double[] data) => obj.SetValue(DataProperty, data);  
      
        private static void DataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)  
        {  
          var plot = d as Plot;  
          if (plot == null) throw new InvalidOperationException("Error using Data property.");  
          var data = e.NewValue as double[];  
          plot.Points = data;  
        }  
      
        void initAxis()  
        {  
          xAxis = new Line() { Stroke = Brushes.LightBlue, StrokeThickness = 2 };  
          yAxis = new Line() { Stroke = Brushes.LightBlue, StrokeThickness = 2 };  
          children.Add(xAxis);  
          children.Add(yAxis);  
        }  
      
        void initPlotArea()  
        {  
          plotArea = new Canvas()  
          {  
            LayoutTransform = new ScaleTransform() { ScaleY = -1 },  
            RenderTransform = new TransformGroup() { Children = { new ScaleTransform(), new TranslateTransform() } }  
          };  
          children.Add(plotArea);  
          poly = new Polyline() { Stroke = LineColor, StrokeThickness = 1 };  
          plotArea.Children.Add(poly);  
        }  
      
        void rearrangeAxis(Size size)  
        {  
          xAxis.X1 = 0;  
          xAxis.X2 = size.Width;  
          xAxis.Y1 = xAxis.Y2 = size.Height - Origin.Y;  
          yAxis.X1 = yAxis.X2 = Origin.X;  
          yAxis.Y1 = 0;  
          yAxis.Y2 = size.Height;  
          xAxis.Measure(size);  
          xAxis.Arrange(new Rect(xAxis.DesiredSize));  
          yAxis.Measure(size);  
          yAxis.Arrange(new Rect(yAxis.DesiredSize));  
        }  
      
        void rearrangePlotArea(Size size)  
        {  
          plotArea.Width = size.Width - Origin.X;  
          plotArea.Height = size.Height - Origin.Y;  
          var translate = (TranslateTransform)((TransformGroup)plotArea.RenderTransform).Children[1];  
          translate.X = Origin.X;  
          translate.Y = 0;  
          plotArea.Measure(size);  
          plotArea.Arrange(new Rect(plotArea.DesiredSize));  
          var points = new PointCollection();  
          double xStep = plotArea.Width / XPoints;  
          double yFactor = plotArea.Height / YPoints;  
          for (int i = 0; i < YPoints && i <= Points.GetUpperBound(0); i++)  
            points.Add(new Point(i * xStep, Points[i]));  
          poly.Points = points;  
        }  
      
        protected override Size ArrangeOverride(Size finalSize)  
        {  
          rearrangeAxis(finalSize);  
          rearrangePlotArea(finalSize);  
          return finalSize;  
        }  
      
        protected override Visual GetVisualChild(int index) => children[index];  
        protected override int VisualChildrenCount => children.Count;  
      }  
    }  
    

    Result:

    94245-x.gif

    1 person found this answer helpful.

  3. Ken Tucker 5,856 Reputation points
    2021-05-03T09:26:46.51+00:00

  4. Hobby02 26 Reputation points
    2021-05-05T07:26:10.867+00:00

    Thank you for your message.
    It should have been fine if I had found these namespaces in your first message.

    Unfortunately "using System.Windows.Data;" and "using System.Windows.Media;"
    could not be found by the system.
    By searching on the internet I found that I had to add a Reference for these two namespaces.
    After adding those there are still errors:

    CS0012 The type 'Point' is defined in an assembly that is not referenced. You must add a reference to assembly 'WindowsBase, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 and
    CS0012 The type 'Freezable' is defined in an assembly that is not referenced. You must add a reference to assembly 'WindowsBase, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
    and
    CS1503 Argument : cannot convert from 'double' to 'int'

    These three errors refer to the lines:

    points.Add(new Point(0, height));
    points.Add(new Point(width, height));
    points.Add(new Point(position, height - height * dataPoints[i] / 100));

    Sorry, but your example to explain/show me a simple line chart seems to be too complex for me.


  5. Hobby02 26 Reputation points
    2021-05-06T19:53:16.887+00:00

    Also your example gives a lot of errors after loading in VS2019 (also by adding the concerning References w.r.t. using System.Windows.Controls;
    using System.Windows.Media; and using System.Windows.Shapes;)

    I think it is worth to mention first how you did create your project after selecting Create New Project.
    Supposing a chart is created, I then obviously have to startup in C# - Desktop with a template Windows Form App (.NET Framework)
    If I delete first the generated 25 lines in the file Form1.cs and then paste your text (132 lines), then 103 errors are generated.

    I like to know how I can create (by code in C#) a line chart where I can show the result of the measurement results which are received by the Serial Input Port (in my case).
    And I like to write code that the X- axis is getting the Start-frequency as X-min of the Frequency Sweep and the Stop-frequency as X-max.
    The vertical axis is initially auto set by the values of the measurement results, but there has to be an option to manually set Y-min and Y-max, derived from textboxes
    to zoom in to the measurement data.

    I like to find a document about Chart Control explaining how one can write code in C# to get the desired line chart . And visualised how the Line Chart look like.
    One can set a lot of properties of a chart, but it seems also to be possible to write it in code.
    So in my case the program knows the start- and stop frequency values of the frequency sweep and assigns these values to X-min and X-max.
    The Y-min and Y max values are found by calculating the min- and max values of the incoming data from the Serial Input Port.
    So it is needed to initally write some lines of code to setup a Line Chart and then after the measurement the curve is drawn.
    That is what I'm looking for.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.