user4864716
user4864716

Reputation:

The correct method to populate a Highcharts box plot using JSON

While I have had success with creating a Highcharts Box-and-Whisker plot by writing static values into my MVC 5 view page, now I am trying to do the same thing by populating the values using a JsonResult function in the controller.

The Highcharts website has a section on custom preprocessing data using JSON, but I have been so far unsuccessful using the method shown in that URL, only changing the chart type to 'boxplot' and defining parameters. An alert box shows that the data values are successfully read from the JsonResult function, but the chart appears without the data I expect to see.

I checked with Firebug and saw no jQuery errors when viewing the page in a browser.

Am I missing something obvious here? I am using MVC 5 C# in Visual Studio.

@section scripts
{
    <script src="https://code.highcharts.com/highcharts.js"></script>
    <script src="https://code.highcharts.com/highcharts-more.js"></script>
    <script>
        $(function () {

            var options = {
                chart: {
                    renderTo: 'container',
                    type: 'boxplot'                    
                },                
                series: [{}]
            };                       

            $.ajax({
                dataType: "json",
                type: "POST",
                url: "@Url.Action("GetChartData")",                
                cache: false,
                async: false,
                data: { _campus: "@ViewBag.SelectedCampus", 
                       _semester: "@ViewBag.SelectedSemester", 
                       _fy: "FY12" },
                success: function (data) {

                    options.series[0].data = data;
                    var chart = new Highcharts.Chart(options);
                    alert(data); // I see the correct array set in the alert box:
                                 // as written literally:   395,441,457,479,532
                },
                error: function (jqXHR, textStatus, errorThrown) {
                    alert("oops: " + textStatus + ": " + jqXHR.responseText);
                }
            });
        });
    </script>
}

This is a screenshot of what it looks like:

Screenshot

In case it makes a difference, here is the JsonResult function:

public JsonResult GetChartData(string _campus, string _semester, string _fy)
        {
            IEnumerable<MathAimsScaleScore> query = db.MathAimsScaleScores
                .Where(m => m.Campus == _campus)
                .Where(m => m.Semester == _semester)
                .Where(m=>m.FY==_fy);

            var FyList = query.Select(m => Convert.ToDouble(m.ScaleScore));
            var jsonList = new double[5];

            for (int i = 0; i < 5; i++)
            {
                jsonList[i] = Statistics.FiveNumberSummary(FyList)[i];
            }

            return Json(jsonList.ToArray());
        }

Upvotes: 0

Views: 1050

Answers (2)

user4864716
user4864716

Reputation:

I found a different approach by restructuring both my JsonResult and jQuery block.

@jlbriggs was helpful because of the "Array of Array" comment, which I could then see as being the culprit.

I have gone through a few more iterations, making the code progressively simpler and less repetitive. This current iteration allows me populate the entire chart with a single ajax call.

// Called from JsonResult (and other functions in the controller).
public int[] GetFiveNumberSummary(string _campus, string _semester, string _fy)
        {
            IEnumerable<MathAimsScaleScore> query = db.MathAimsScaleScores
                .Where(m => m.Campus == _campus)
                .Where(m => m.Semester == _semester)
                .Where(m => m.FY == _fy);

            var FyList = query.Select(m => Convert.ToDouble(m.ScaleScore)).ToList();

            var reply = new int[5];
            for (int i = 0; i < 5; i++)
            {
                reply[i] = Convert.ToInt32(Statistics.FiveNumberSummary(FyList)[i]);
            }
            return reply;       
        }

        public JsonResult GetChartData(string _campus, string _semester)
        {           

            var FyArray0 =  GetFiveNumberSummary(_campus,_semester,"FY12").ToArray();
            var FyArray1 = GetFiveNumberSummary(_campus, _semester, "FY13").ToArray();
            var FyArray2 = GetFiveNumberSummary(_campus, _semester, "FY14").ToArray();
            var FyArray3 = GetFiveNumberSummary(_campus, _semester, "FY15").ToArray();

            int [][] FyArrayBox = new int[4][];
            FyArrayBox[0] = FyArray0;
            FyArrayBox[1] = FyArray1;
            FyArrayBox[2] = FyArray2;
            FyArrayBox[3] = FyArray3;

            foreach (var item in FyArrayBox)
            {
                Debug.WriteLine(item);
            }

            return Json(FyArrayBox.ToArray());
        }

I restructured the jQuery as well. What makes this different from the demo on Highcharts is that I'm defining the array variables and the "array of arrays" at the very beginning of the script and explicitly populating them in the $.ajax function.

the options variable refers to the predefined "array of arrays" -- which has values at the time the chart is created.

@section scripts
{
    <script src="https://code.highcharts.com/highcharts.js"></script>
    <script src="https://code.highcharts.com/highcharts-more.js"></script>
    <script>
        $(function () {
            // Create empty arrays to hold the data sets

            var DataFy12 = [];
            var DataFy13 = [];
            var DataFy14 = [];
            var DataFy15 = [];
            var DataAll = [];

            // This is the chart definition that will be called once $.ajax completes

            var options = {
                chart: {
                    renderTo: 'container',
                    type: 'boxplot'
                },
                title: { text: 'Box-and-Whisker Plot' },
                legend: { enabled: false },
                xAxis: {
                    title: { text: 'School Year' },
                    categories: ['FY12', 'FY13', 'FY14', 'FY15']
                },
                yAxis: {
                    title: { text: 'Scale Score' }
                },
                series: [{
                    name: "Scale Scores",
                    data: DataAll
                    }]
            };

            $.ajax({
                dataType: "json",
                type: "POST",
                url: "@Url.Action("GetChartData")",
                cache: false, // I want to make sure the most recent data is read
                async: false, // For some reason this needs to be set false
                data: {
                    _campus: "@ViewBag.SelectedCampus",
                    _semester: "@ViewBag.SelectedSemester"
                },
                success: function (data) {

                    // the function calls an "array of arrays",                    
                    // where y is the array at location x

                    $.each(data, function (x, y) {                        
                        // divert array elements to correct jQuery array

                        switch (x) {
                            case 0:
                                for (i = 0; i < 5; i++)
                                {
                                    DataFy12.push(y[i]);
                                }                                
                                break;
                            case 1:
                                for (i = 0; i < 5; i++) {
                                    DataFy13.push(y[i]);
                                }
                                break;
                            case 2:
                                for (i = 0; i < 5; i++) {
                                    DataFy14.push(y[i]);
                                }
                                break;
                            case 3:
                                for (i = 0; i < 5; i++) {
                                    DataFy15.push(y[i]);
                                }
                                break;
                            default:
                                alert('case else');
                        }
                    });       

                    // The "DataAll" array is the "array of arrays"
                    // Pushing the each array into DataAll 
                    //    gives Highcharts the data set in a form it understands

                    DataAll.push(DataFy12);
                    DataAll.push(DataFy13);
                    DataAll.push(DataFy14);
                    DataAll.push(DataFy15);

                    // alert(DataAll) -- Data is verified to be correct

                    var chart = new Highcharts.Chart(options);
                },
                error: function (jqXHR, textStatus, errorThrown) {
                    alert("oops: " + textStatus + ": " + jqXHR.responseText);
                }
            });            

        });
    </script>
}

Screenshot

Upvotes: 0

jlbriggs
jlbriggs

Reputation: 17791

You are supplying a data array of [395,441,457,479,532] to your chart, which it is interpreting as 5 individual data points.

This results in what you have posted: boxplot1

(so, if you make that to a line chart with the same exact data array, you see what the chart is trying to do with the data you provided:

boxplot3

but, since you can't draw a box plot with only one point, nothing is displayed)

.

What you need to provide is an array of arrays, where each point has 5 values:

data: [[395,441,457,479,532]]

This way, the inner brackets contain a single data point, with the information the chart needs to draw a boxplot:

boxplot2

Fiddle example:

Upvotes: 2

Related Questions