Search code examples
sapui5visualization

How can I implement a linear regression line in a Vizframe Chart?


I have a scatter plot Vizframe chart in which I need to include a linear regression/trend line. Any idea how this can be done? It appears this is not something offered by vizframe 'out of box'? I can't find a solution for this!

Question:

Any suggestions on a feasible way to implement a regression line on a Scatter Plot Vizframe chart?

Here is the code I have for the setup. The scatter plot opens in a dialog/modal when a button is pressed.

sap.ui.define([
        'jquery.sap.global',
        'vizConcept/controller/BaseController',
        'sap/ui/model/json/JSONModel',
        'vizConcept/model/viewControls',
        'sap/m/Button',
        'sap/m/Dialog',
    ],

    function (jQuery, BaseController, JSONModel, viewControls, Button, Dialog) {

        "use strict";

        var controls;

        var mainController = BaseController.extend("vizConcept.controller.Main", {

            onInit: function(oEvent) {

                // Access/expose the defined model(s) configured in the Component.js or Manifest.json within the controller.
                this.getView().setModel(this.getOwnerComponent().getModel("products"), "products");
                var oModel = this.getView().getModel("products");
                this.getView().setModel(oModel);

                var sUrl = "#" + this.getOwnerComponent().getRouter().getURL("page2");

        $(function() {
            var dataset = new sap.viz.ui5.data.FlattenedDataset({ 
                dimensions : [
                 {
                   axis : 1,
                   name : 'Award Date',
                   value : "{AwdDate}"
                 }
                ],
                measures : [
                            {
                            group: 1,
                                name : 'Award Date',
                                value : '{Hist}'
                            },
                            {
                            group: 2,
                                name : 'Current PPI',
                                value : '{Current}'
                            }
                ],
                data : {
                  path : "/ProductCollection"
                }   
              });


        var scatterViz = new sap.viz.ui5.Scatter({
                id : "idscatter",
                width : "1000px",
                height : "400px",
                title : {
                  text : 'Pricing Tool Scatter Plot Example'
                },
                xAxis : {
                  title : {
                    visible : true
                  }
                },
                yAxis : {
                  title : {
                    visible : true
                  }
                },
                dataset : dataset
              });

          scatterViz.setModel(sap.ui.getCore().getModel());  
          scatterViz.setModel(oModel);


      var dlg = new sap.m.Dialog({
        id: 'vizModal',    
        title: 'Scatter Plot Example Viz',
        width : "1800px",  
        height : "600px",  
        content : [scatterViz],
        beginButton: new Button({
                            text: 'Close',
                            press: function () {
                                dlg.close();
                            }
                        })  
      });

      (new sap.m.Button({
            text: 'open',
            type: 'Accept',
            press: function() {
              dlg.open();
              scatterViz.invalidate();
            }
          })).placeAt('content');
        });
            },    

        onToPage2 : function () {
            this.getOwnerComponent().getRouter().navTo("page2");
        },


        });

        return mainController;
    });

Edit

Here is the 'products' model that is outputted on the vizframe chart. I have the products model defined in the manifest.json but the connection there is fine:

products model

          {
            "ProductCollection": [
                { 
                    "Item": "1",
                    "AwdDate": "20160715",
                    "Hist": 171.9,
                    "Current": 183

                },
                {
                    "Item": "2",
                    "AwdDate": "20160701",
                    "Hist" : 144.3,
                    "Current": 158.6
                },
                {
                    "Item": "3",
                    "AwdDate": "20150701",
                    "Hist": 160,
                    "Current": 165
                },
                { 
                    "Item": "1",
                    "AwdDate": "20160715",
                    "Hist": 201,
                    "Current": 167
                },
                {
                     "Item": "2",
                    "AwdDate": "20160801",
                    "Hist" : 175.3,
                    "Current": 178.2
                },
                {
                    "Item": "3",
                    "AwdDate": "20150721",
                    "Hist": 160,
                    "Current": 147
                },
                { 
                    "Item": "1",
                    "AwdDate": "20160715",
                    "Hist": 175.9,
                    "Current": 185.2
                },
                {
                    "Item": "2",
                    "AwdDate": "20161101",
                    "Hist" : 165.3,
                    "Current": 158.2
                },
                {
                    "Item": "3",
                    "AwdDate": "201700101",
                    "Hist": 160,
                    "Current": 165
                },
                {
                    "Item": "4",
                    "AwdDate": "201600401",
                    "Hist": 173,
                    "Current": 177
                }

            ]
        };    

Edit 2 Here is my attempt at the solution offered here. But nothing appears after this is included in the onInit() function of the controller.

var regressionData = [];
for (var i = 0; i < 10; i++) {
regressionData[i] = [oData.ProductCollection[i].Current, oData.ProductCollection[i].Hist];
            };

regression('linear', regressionData);  

Solution

  • Unfortunately, you have some limitations inherent to the viz charts. Namely:

    • You cannot add "oblique" reference lines (= the trend line) to the chart. You can only add vertical / horizontal ones via the Reference Line feature. Check out the (non-intuitive) documentation on this: Viz Charts Documentation (look at the plotArea.referenceLine.line; it is just a number = will be a horizontal / vertical line depending on your chart type or orientation).
    • You cannot combine a scatter plot with a line chart. If you look in the same Viz Charts Documentation, you will see that in the Combination "chapter" on the left hand side, there is no "Line - Scatter" combination.

    Also, your X values (AwDate) are ABAP-style dates as string. It is more appropriate to use a date / time chart to correctly display them. Anyway, regression makes a lot more sense when you use dates. Otherwise your data is "categorical" and the only way you can make regression is by sorting them and considering them equidistant on the X axis - not what you would normally want if you have non-equidistant data (like the one in your example).

    My proposal would be to:

    • Use a line chart instead of a scatter chart (such that you can also display the trend line).
    • Convert the ABAP-style strings into dates, such that you can use a timeseries chart. You have some "wrong" dates in your example data btw: "201600401".
    • Do the regression for whatever you want and add it as a separate "trend" series. You will need to compute a point on the "trend" series for each point on your other series (basically, you will have to add a "trend" attribute to each line in your ProductCollection). If you are using an OData model, then you will need to either switch to an JSON client model or do some ugly workarounds with formatters.

    As requested, I made an example implementation here: https://jsfiddle.net/93mx0yvt/23/. The regression algorithm I borrowed from here: https://dracoblue.net/dev/linear-least-squares-in-javascript/. The main points of the code are:

    // format for parsing the ABAP-style dates
    var oFormat = DateFormat.getDateInstance({
      pattern: "yyyyMMdd"
    });
    
    // maps an ProductCollection entry to a new object
    // which has the parsed date (and only the needed attributes)
    var fnMapData = function(oEntry) {
      return {
        date: oFormat.parse(oEntry.AwdDate),
        current: oEntry.Current,
        historical: oEntry.Hist
      };
    };
    
    var fnProcessData = function(oD) {
      var aEntries = oD.ProductCollection.map(fnMapData),
        aXs = aEntries.map(function(oE) { // get the Xs
          // we take the millis to be able to do arithmetics
          return oE.date.getTime(); 
        }),
        aYs = aEntries.map(function(oE) { // get the Ys (hist)
          return oE.historical;
        }),
        //changed the function to only return only result Ys
        aRs = findLineByLeastSquares(aXs, aYs);
    
      //save the Ys into the result
      for (var i = 0; i < aEntries.length; ++i) {
        aEntries[i].trend = aRs[i];
      }
      return {
        data: aEntries
      };
    };
    

    You can use then the data returned by the fnProcessData function inside a JSONModel and then build a simple multi-series date/time line chart based on it.