Canvas and D3 Charts


#1

Hi Canvas people.

I’m looking at creating graphs and charts in Canvas. Currently there is the tag TM1-UI-Chart that allow you do create 5 or 6 different charts.

Has anyone create different charts using charts from D3 or elsewhere.

I’m not looking at anything extreme, currently just something like a 2 Y axis line chart, to compare 2 different measures.

Does anyone have coding example that I can use as a starting point?

Thanks


#2

Hi @bknott,

There are some examples of charts that have been built with D3. It is quite technical so you are better off seeing if you can find an existing chart that suits your needs first. We use this library for the built-in charts and it is therefore included in Canvas:

http://krispo.github.io/angular-nvd3/

Do any of these existing charts fit your needs?


#3

Thanks Tim.

Line + Bar with focus chart would be a good start for me. When you say they are included in Canvas, how are they accessed?

Brian


#4

Hi @bknott,

You just need to copy the parts you need from the site given above.

For the above chart for example, in your HTML file, you would have a line in there with the following:

Then in the controller / js related to your HTML file, you would need to create/update the definition of ‘options’ and ‘data’. You can get an idea on this usually by picking your graph, then click on the following:

It will redirect you to a page where you can see assignments for data and options in the form of $scope.data = … and $scope.options = ‘…’; As an example, below is for the line with bar and a focus chart with its sample data.


    $scope.options = {
            chart: {
                type: 'linePlusBarChart',
                height: 500,
                margin: {
                    top: 30,
                    right: 75,
                    bottom: 50,
                    left: 75
                },
                bars: {
                    forceY: [0]
                },
                bars2: {
                    forceY: [0]
                },
                color: ['#2ca02c', 'darkred'],
                x: function(d,i) { return i },
                xAxis: {
                    axisLabel: 'X Axis',
                    tickFormat: function(d) {
                        var dx = $scope.data[0].values[d] && $scope.data[0].values[d].x || 0;
                        if (dx > 0) {
                            return d3.time.format('%x')(new Date(dx))
                        }
                        return null;
                    }
                },
                x2Axis: {
                    tickFormat: function(d) {
                        var dx = $scope.data[0].values[d] && $scope.data[0].values[d].x || 0;
                        return d3.time.format('%b-%Y')(new Date(dx))
                    },
                    showMaxMin: false
                },
                y1Axis: {
                    axisLabel: 'Y1 Axis',
                    tickFormat: function(d){
                        return d3.format(',f')(d);
                    },
                    axisLabelDistance: 12
                },
                y2Axis: {
                    axisLabel: 'Y2 Axis',
                    tickFormat: function(d) {
                        return '$' + d3.format(',.2f')(d)
                    }
                },
                y3Axis: {
                    tickFormat: function(d){
                        return d3.format(',f')(d);
                    }
                },
                y4Axis: {
                    tickFormat: function(d) {
                        return '$' + d3.format(',.2f')(d)
                    }
                }
            }
        };

        $scope.data = [
            {
                "key" : "Quantity" ,
                "bar": true,
                "values" : [ [ 1136005200000 , 1271000.0] , [ 1138683600000 , 1271000.0] , [ 1141102800000 , 1271000.0] , [ 1143781200000 , 0] , [ 1146369600000 , 0] , [ 1149048000000 , 0] , [ 1151640000000 , 0] , [ 1154318400000 , 0] , [ 1156996800000 , 0] , [ 1159588800000 , 3899486.0] , [ 1162270800000 , 3899486.0] , [ 1164862800000 , 3899486.0] , [ 1167541200000 , 3564700.0] , [ 1170219600000 , 3564700.0] , [ 1172638800000 , 3564700.0] , [ 1175313600000 , 2648493.0] , [ 1177905600000 , 2648493.0] , [ 1180584000000 , 2648493.0] , [ 1183176000000 , 2522993.0] , [ 1185854400000 , 2522993.0] , [ 1188532800000 , 2522993.0] , [ 1191124800000 , 2906501.0] , [ 1193803200000 , 2906501.0] , [ 1196398800000 , 2906501.0] , [ 1199077200000 , 2206761.0] , [ 1201755600000 , 2206761.0] , [ 1204261200000 , 2206761.0] , [ 1206936000000 , 2287726.0] , [ 1209528000000 , 2287726.0] , [ 1212206400000 , 2287726.0] , [ 1214798400000 , 2732646.0] , [ 1217476800000 , 2732646.0] , [ 1220155200000 , 2732646.0] , [ 1222747200000 , 2599196.0] , [ 1225425600000 , 2599196.0] , [ 1228021200000 , 2599196.0] , [ 1230699600000 , 1924387.0] , [ 1233378000000 , 1924387.0] , [ 1235797200000 , 1924387.0] , [ 1238472000000 , 1756311.0] , [ 1241064000000 , 1756311.0] , [ 1243742400000 , 1756311.0] , [ 1246334400000 , 1743470.0] , [ 1249012800000 , 1743470.0] , [ 1251691200000 , 1743470.0] , [ 1254283200000 , 1519010.0] , [ 1256961600000 , 1519010.0] , [ 1259557200000 , 1519010.0] , [ 1262235600000 , 1591444.0] , [ 1264914000000 , 1591444.0] , [ 1267333200000 , 1591444.0] , [ 1270008000000 , 1543784.0] , [ 1272600000000 , 1543784.0] , [ 1275278400000 , 1543784.0] , [ 1277870400000 , 1309915.0] , [ 1280548800000 , 1309915.0] , [ 1283227200000 , 1309915.0] , [ 1285819200000 , 1331875.0] , [ 1288497600000 , 1331875.0] , [ 1291093200000 , 1331875.0] , [ 1293771600000 , 1331875.0] , [ 1296450000000 , 1154695.0] , [ 1298869200000 , 1154695.0] , [ 1301544000000 , 1194025.0] , [ 1304136000000 , 1194025.0] , [ 1306814400000 , 1194025.0] , [ 1309406400000 , 1194025.0] , [ 1312084800000 , 1194025.0] , [ 1314763200000 , 1244525.0] , [ 1317355200000 , 475000.0] , [ 1320033600000 , 475000.0] , [ 1322629200000 , 475000.0] , [ 1325307600000 , 690033.0] , [ 1327986000000 , 690033.0] , [ 1330491600000 , 690033.0] , [ 1333166400000 , 514733.0] , [ 1335758400000 , 514733.0]]
            },
            {
                "key" : "Price" ,
                "values" : [ [ 1136005200000 , 71.89] , [ 1138683600000 , 75.51] , [ 1141102800000 , 68.49] , [ 1143781200000 , 62.72] , [ 1146369600000 , 70.39] , [ 1149048000000 , 59.77] , [ 1151640000000 , 57.27] , [ 1154318400000 , 67.96] , [ 1156996800000 , 67.85] , [ 1159588800000 , 76.98] , [ 1162270800000 , 81.08] , [ 1164862800000 , 91.66] , [ 1167541200000 , 84.84] , [ 1170219600000 , 85.73] , [ 1172638800000 , 84.61] , [ 1175313600000 , 92.91] , [ 1177905600000 , 99.8] , [ 1180584000000 , 121.191] , [ 1183176000000 , 122.04] , [ 1185854400000 , 131.76] , [ 1188532800000 , 138.48] , [ 1191124800000 , 153.47] , [ 1193803200000 , 189.95] , [ 1196398800000 , 182.22] , [ 1199077200000 , 198.08] , [ 1201755600000 , 135.36] , [ 1204261200000 , 125.02] , [ 1206936000000 , 143.5] , [ 1209528000000 , 173.95] , [ 1212206400000 , 188.75] , [ 1214798400000 , 167.44] , [ 1217476800000 , 158.95] , [ 1220155200000 , 169.53] , [ 1222747200000 , 113.66] , [ 1225425600000 , 107.59] , [ 1228021200000 , 92.67] , [ 1230699600000 , 85.35] , [ 1233378000000 , 90.13] , [ 1235797200000 , 89.31] , [ 1238472000000 , 105.12] , [ 1241064000000 , 125.83] , [ 1243742400000 , 135.81] , [ 1246334400000 , 142.43] , [ 1249012800000 , 163.39] , [ 1251691200000 , 168.21] , [ 1254283200000 , 185.35] , [ 1256961600000 , 188.5] , [ 1259557200000 , 199.91] , [ 1262235600000 , 210.732] , [ 1264914000000 , 192.063] , [ 1267333200000 , 204.62] , [ 1270008000000 , 235.0] , [ 1272600000000 , 261.09] , [ 1275278400000 , 256.88] , [ 1277870400000 , 251.53] , [ 1280548800000 , 257.25] , [ 1283227200000 , 243.1] , [ 1285819200000 , 283.75] , [ 1288497600000 , 300.98] , [ 1291093200000 , 311.15] , [ 1293771600000 , 322.56] , [ 1296450000000 , 339.32] , [ 1298869200000 , 353.21] , [ 1301544000000 , 348.5075] , [ 1304136000000 , 350.13] , [ 1306814400000 , 347.83] , [ 1309406400000 , 335.67] , [ 1312084800000 , 390.48] , [ 1314763200000 , 384.83] , [ 1317355200000 , 381.32] , [ 1320033600000 , 404.78] , [ 1322629200000 , 382.2] , [ 1325307600000 , 405.0] , [ 1327986000000 , 456.48] , [ 1330491600000 , 542.44] , [ 1333166400000 , 599.55] , [ 1335758400000 , 583.98]]
            }
        ].map(function(series) {
                series.values = series.values.map(function(d) { return {x: d[0], y: d[1] } });
                return series;
            });

Check the NVD3 Quick Start on the above site for further details.

Cheers!
Paul


#5

Hi Paul. I think its more of an understanding issue of how Canvas works.

For example if I was creating a normal HTML page, I would have a section in the file, where the NVD3 libraries would be included. Canvas seems to have a common head that it includes, VSS files don’t contain the section, just the section. What I’m trying to understand how does Canvas create the HTML pages that it sends to the browser.

With controllers, It looks like canvas creates a controller for each page and puts them into the js/controllers directory, is this correct? If I have pages called fixed-costs.html, then there should be a controller called fixed-costs.js ? Which I assume the section will include this file, or is it automatically included by Canvas.

So for the NVD3 charts I would expect to see the libraries in the head, similar to those below, and additional code in the controller file to load the data.

Does this sound correct?

<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/d3/d3.js"></script>
<script src="bower_components/nvd3/build/nv.d3.js"></script> <!-- or use another assembly -->
<script src="bower_components/angular-nvd3/dist/angular-nvd3.js"></script>
<link rel="stylesheet" href="bower_components/nvd3/build/nv.d3.css">

#6

Hi @bknott,

You do not have to worry about those declarations as they have been included already within Canvas. So you just need to write the codes. As an additional guide, on the “About” page, there is a tab that lists the libraries included within Canvas and their version.

As for your questions about the controller, as long as you have created it via the Page Creator in the admin console of Canvas, you should have it in there. And this is the preferred way too, so you can then just focus on creating the HTML (and updating the JS file if still needed).

Cheers!
Paul


#7

Hi Everyone.

I got the graph to load so that’s a win.

Whats the best way to go about getting data. Should I use a cubeExecuteMdx function. If I do how do I relate it to scope.data.

Are there any example of this around?

Brian


#8

Hi @bknott,

You can use that or use the cellsetGet() function, whichever you are more comfortable with.

Once you have the result, you just need to update the data part like below:

$tm1Ui.cubeExecuteMdx(instance, mdx, restPath).then(function(data){
   console.debug('cubeExecuteMdx() %o ', data);
            
  // manipulate result here
  var processedData = myFunctionProcessMyData(data);  

  // assign it back to the data
  $scope.data = processedData;
});

Let us know how it goes.


Paul


#9

Thanks Paul and others.

Question re scope. When I run the $tm1Ui.cubeExecuteMdx function it returns data.

I’m assuming this data is local to the function?

Does using the $ before a variable name make the data available to the rest of the controller?

i.e. Line 6 of your code sets the variable $scope.data = processedData.

Brian


#10

Hi @bknott,

Yes that is local to that function.

The $ prefix for variable is more for naming convention to let it be known that it is a bit different from normal JS variables. Also, this is to usually indicate too that there are helper functions or it has significant impact on some other parts.

As for below,

There is a few things to take note here.

  1. $scope is a special variable injected into the JS, that is shared between your controller and your HTML page where this controller was declared.

  2. $scope.data means, the scope that is being shared between your JS (controller) and your HTML page has a property called ‘data’.

  3. processedData is a private/local variable within that cubeExecuteMdx’s returned promise where it theoretically stores the formatted data for the graph.

So the above is trying to format and to push the data back into the page by assigning it into the $scope.data property (this property is available between the JS and HTML).

Hope this help clarifies.


Paul


#11

Great slowly working it out.

I have a that gives me a list of equipment. See below

<tm1-ui-subnm tm1-instance="MinePhysicals" tm1-dimension="Equipment" tm1-subset="Excavators_Thiess" tm1-default-element="TPl SH01" ng-model="page.filters['Equipment']"></tm1-ui-subnm>

How do I reference this in my controller so I can add this to my MDX query. Similar to below.

var mdx1 = 'select {[measure_fixed_costs].[All Costs].CHILDREN} on 1,';
mdx1 = mdx1 + '{[Date].[Fy17].CHILDREN} on 0 from [Fixed_costs]';
mdx1 = mdx1 + 'where [equipment].' + page.filters['Equipment'];

Should I be using the $scope to pass htis value.


#12

In MDX you should start with columns first, which is ‘0’, followed by rows which is ‘1’.

mdx=‘select {[Date].[Fy17].CHILDREN} on 0, {[measure_fixed_costs].[All Costs].CHILDREN} on 1 from [Fixed_costs] where ([equipment].[’+$scope.page.filters.Equipment+’])’;


#13

Hi.
The MDX statement I pasted was an older version. I have changed the query around, however the original issue of using variables from the ng-model still exists.

My html page code includes
<tm1-ui-subnm tm1-instance="MinePhysicals" tm1-dimension="Equipment" tm1-subset="Excavators_Thiess" tm1-default-element="TPl SH01" ng-model="$scope.page.filters['Equipment']"></tm1-ui-subnm>
And i’m trying to pass this to the MDX statement so I can filter. MDX statement construction is below.
var mdx1 = 'select {[measure_fixed_costs].[All Costs].CHILDREN} on 0,'; mdx1 = mdx1 + '{[Date].[Fy17].CHILDREN} on 1 from [Fixed_costs]'; mdx1 = mdx1 + '([equipment].['+$scope.page.filters.Equipment+'])';

When I run this I get the following error on the console.

libs.js:29341 TypeError: Cannot read property ‘filters’ of undefined

Any ideas?

Brian


#14

You don’t need to use $scope in html
ng-model=“page.filters[‘Equipment’]”


#15

I have tried is with and without the $scope in the NG-Model. The error is the same.

Brian


#16

Hi @bknott,

What most is happening most likely is that you have not initialized $scope.page.filters.Equipment.

Can you briefly explain what were you trying to achieve on the page?


Paul


#17

Hi Paul.

What I am trying to do is pass a selected value from a drop down box on the page to an MDX query to filter the data it returns.

What I have is a tm1-ui.subnm that gives me the drop down list on the HTML page. Next step is when someone selects from this drop down, the MDX statement, and thus the data returned is filtered by whatever the user has selected.

Brian


#18

Hi Brian,

How Angular works is different than normal JavaScript, I suggest you have a look at some Angular tutorials. The reason you are having the TypeError is because the variable doesn’t exist at that point in time. The controller is loaded BEFORE the HTML template, what you need to do is either use a $scope.$watch on the variable or use the tm1-change event on the tm1-ui-subnm.


#19

Slowly getting a handle on Angular JS.

I have a function in the controller that gets and sets data on the HTML page. This works fine.

This function then calls anther function in the controller $scope.buildMDX(somedata).

This work but for some reason both functions are run twice. If I comment out the call the buildMDX function, then only the first function is run once and the buildMDX function is not run.

So a couple of questions.
Should I be calling functions from functions? Normally not an issue in JS
Do all functions in the controller run when it is loaded?

Thanks

app.controller('TPLSH01_VolumesCtrl', ['$scope', '$rootScope', '$log', '$tm1Ui', function($scope, $rootScope, $log,$tm1Ui) {
//	$log.info('TPLSH01_VolumesCtrl is Ready!');

$scope.query = function(data) {
    $scope.firstName = "John";
    $scope.lastName = "Doe";
    $scope.excavator = data;
    console.log('function to build MDX');
    $scope.buildMDX(data);
}


$scope.buildMDX = function(somedata) {
    var mdx1 = 'select {[measure_fixed_costs].[All Costs].CHILDREN} on 0,';
    mdx1 = mdx1 + '{[Date].[Fy17].CHILDREN} on 1 from [Fixed_costs]';
    mdx1 = mdx1 + 'where ([equipment].['+$scope.excavator+'])';
    console.log('MDX statement: '+mdx1);

    /*$tm1Ui.cubeExecuteMdx('MinePhysicals', mdx1).then(function(returnData){
        console.debug(returnData);
    })*/

}

#20

From my understanding controller js runs at first when page is loading first, and next time it runs each $digest cycle update. So If you watch parameters of your functions, that means when page is updating at first these parameters are usually null, and as only you get data from tm1, your scope updates and your function runs second time. Normally it’s not an issue in JS, but Angularjs works differently.