Search code examples
c#wpfvisual-studiooxyplot

OxyPlot graph not rendering in child Grid


I'm making a statistics application using OxyPlot WPF where the user inputs the class limits and the frequencies for each class. Then you chose which of the 4 graphs you want displayed. Then the graph gets displayed in that white box (see image below).

Statistics app GUI

In the MainWindow constructor, I set the this.DataContext = this. I have a PlotModel property called "MyModel" which is binded to the the Model property of the PlotView control in the XAML code ( <oxy:PlotView Model="{Binding MyModel}"/> ) . This PlotView is inside a Grid called "Graph" which is inside the main Grid (it's at the bottom of all my xaml code).

So when the "Display" button is clicked, inside it's event handler, it'll go into a different if statement based on which radio button is checked. At the end of each if statement, this.MyModel gets set to the PlotModel that was created inside it. I think by writing this.MyModel = model it's supposed to display the graph but when i tested it, nothing gets rendered in the white part (see all code below).

I'm not sure if this problem is because the this.MyModel= model is inside a child grid or if there's binding issues.

I'm relatively new to WPF programming in general and any help would be appreciated. Thanks!

MainWindow.xaml :

<Window
    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:StatsApp"
    xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
    xmlns:oxy="http://oxyplot.org/wpf"
    xmlns:Properties="clr-namespace:StatsApp.Properties" x:Class="StatsApp.MainWindow"
    mc:Ignorable="d"
    Title="Frequency Distribution Graph Generator" Height="450" Width="800">

<Grid Background="Gray">

    <Grid x:Name="Graph" HorizontalAlignment="Left" Height="306" Margin="340,39,0,0" VerticalAlignment="Top" Width="421">
        <oxy:PlotView Model="{Binding MyModel}"/>
    </Grid>

    <!-- I left out all the unnecessary textboxes and buttons ..... -->

</Grid>

MainWindow.cs :

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;
    }

    //private bool isfirstclick = true;

    public PlotModel MyModel { get; private set; }

    private void DisplayBtn_Click(object sender, RoutedEventArgs e)
    {
        // ---------------Retrieving Input START----------------
        TextBox[] lowerLimitsTxtBoxes =
        {
            Lower1TxtBox,
            Lower2TxtBox,
            Lower3TxtBox,
            Lower4TxtBox,
            Lower5TxtBox,
            Lower6TxtBox,
            Lower7TxtBox,
            Lower8TxtBox
        };

        TextBox[] upperLimitsTxtBoxes =
        {
            Upper1TxtBox,
            Upper2TxtBox,
            Upper3TxtBox,
            Upper4TxtBox,
            Upper5TxtBox,
            Upper6TxtBox,
            Upper7TxtBox,
            Upper8TxtBox
        };

        TextBox[] freqsTxtBoxes =
        {
            Freq1TxtBox,
            Freq2TxtBox,
            Freq3TxtBox,
            Freq4TxtBox,
            Freq5TxtBox,
            Freq6TxtBox,
            Freq7TxtBox,
            Freq8TxtBox
        };

        double[] lowerLimits = new double[8];

        for (int i = 0; i < lowerLimits.Length; i++)
        {
            if (Double.TryParse(lowerLimitsTxtBoxes[i].Text, out double lower))
            {
                lowerLimits[i] = lower;
            }
            else
            {
                lowerLimits[i] = -1;
            }
        }


        double[] upperLimits = new double[8];

        for (int i = 0; i < upperLimits.Length; i++)
        {
            if (Double.TryParse(upperLimitsTxtBoxes[i].Text, out double upper))
            {
                upperLimits[i] = upper;
            }
            else
            {
                upperLimits[i] = -1;
            }
        }

        //IMPORTANT -> The array of frequencies
        int[] freqs = new int[8];

        for (int i = 0; i < freqs.Length; i++)
        {
            if (Int32.TryParse(freqsTxtBoxes[i].Text, out int freq))
            {
                freqs[i] = freq;
            }
            else
            {
                freqs[i] = -1;
            }
        }



        int numClasses = 0;

        for (int i = 0; lowerLimits[i] != -1 && i < 8; i++)
        {
            numClasses++;
        }

        if (numClasses < 2)
        {
            throw new ArgumentException("Must use at least 2 classes");
        }

        //IMPORTANT -> The class marks array: double
        double[] classMarks = new double[numClasses];
        for (int i = 0; i < classMarks.Length; i++)
        {
            classMarks[i] = (lowerLimits[i] + upperLimits[i]) / 2.0;
        }

        //IMPORTANT -> The class marks array: string
        string[] classMarksString = new string[numClasses];
        for (int i = 0; i < numClasses; i++)
        {
            classMarksString[i] = classMarks[i] + "";
        }

        //----------Retrieving Input END--------------------


        if ((bool)ScatterRBtn.IsChecked)
        {
            var model = new PlotModel { Title = "Scatter Plot" };
            var scatter = new ScatterSeries { MarkerType = MarkerType.Circle };
            model.Axes.Add(new LinearAxis { Position = AxisPosition.Left,  Title = "Class Marks" });
            model.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "Frequency" });

            for ( int i =0; i < numClasses; i++)
            {
                scatter.Points.Add(new ScatterPoint(classMarks[i], freqs[i]));
            }

            model.Series.Add(scatter);

            //This doesn't update the graph even though I binded it in XAML code
            this.MyModel = model;
        }
        else if ((bool)RelativeFqRBtn.IsChecked)
        {
            var model = new PlotModel { Title = "Relative Frequency Polygon" };
            model.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Maximum = 1, Title = "Class Marks" });
            model.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "Frequency (%)" });

            var relativeFQ = new LineSeries();

            int frequencyTotal = 0;

            for (int i=0;  i < 8 && freqs[i] != -1 ; i++)
            {
                frequencyTotal += freqs[i];
            }

            for (int i = 0; i < numClasses; i++)
            {
                relativeFQ.Points.Add(new DataPoint(classMarks[i], freqs[i]/frequencyTotal ));
            }

            model.Series.Add(relativeFQ);

            //This doesn't update the graph even though I binded it in XAML code
            this.MyModel = model;
        }
        else if ((bool)CummuFqRBtn.IsChecked)
        {
            var model = new PlotModel { Title = "Cummulative Frequency Polygon" };
            model.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title= "Frequency" });
            model.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title ="Class Boundaries" });

            var cummulativeFQ = new LineSeries();

            double[] classBoundaries = new double[numClasses + 1];

            double midpointDistance = (lowerLimits[1] - upperLimits[0]) / 2;

            classBoundaries[0] = lowerLimits[0] - midpointDistance;

            for (int i = 0; i < numClasses; i++)
            {
                classBoundaries[i + 1] = upperLimits[i] + midpointDistance;
            }

            cummulativeFQ.Points.Add(new DataPoint(classBoundaries[0], 0));

            for (int i = 0; i< numClasses ; i++)
            {
                cummulativeFQ.Points.Add(new DataPoint(classBoundaries[i+1], freqs[i]));
            }

            model.Series.Add(cummulativeFQ);

            //This doesn't update the graph even though I binded it in XAML code
            this.MyModel = model;
        }
        else
        {
            var model = new PlotModel { Title = "Histogram" };

            model.Axes.Add(new LinearAxis { Title = "Frequency", Position = AxisPosition.Left });

            model.Axes.Add(new CategoryAxis
            {
                Title = "Class Marks",
                ItemsSource = classMarksString

            });

            var histogram = new ColumnSeries();
            model.Series.Add(histogram);

            for (int i = 0; i < numClasses; i++)
            {
                histogram.Items.Add(new ColumnItem(freqs[i]));
            }

            //This doesn't update the graph even though I binded it in XAML code
            this.MyModel = model;

        }
    }
}

Solution

  • Fast fix for you code

    Instead <oxy:PlotView Model="{Binding MyModel}"/> use <oxy:PlotView x:Name="myPlot"/>

    Remove public PlotModel MyModel { get; private set; } and set Model directly after you created it.

    myPlot.Model=model;
    

    P.S. Don't use Binding if you don't know how it works