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,182 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.
6,977 questions
{count} votes

5 answers

Sort by: Most helpful
  1. DaisyTian-1203 11,506 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. Peter Fleischer (Freelancer) 17,571 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


  3. Ken Tucker 5,801 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.