Canvas and D3 Charts

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.

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+’])’;

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

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

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

Brian

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

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

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.

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);
    })*/

}

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.

Hi @bknott,

How are you trying to call the query function from the HTML page?

The above looks alright, but it should not run yet. What the above code means is you are trying to declare 2 functions: query() and buildMDX(). And on query(), you are calling the function buildMDX().

And to actually make it run on the controller, you should normally need to have a line of code at the end that actually calls the function.

So what you are doing above is essentially just declaring the functions you wanted to use.

And just a side note, it would be preferable to place the $scope.buildMDX() function before $scope.query(). as this should be the same thing you would do with variables (declaring them first before using them down the line on the script).

Cheers!
Paul

Thanks Paul.

Function is called from the HTML page by the code below

<tm1-ui-subnm 	tm1-instance="MinePhysicals" 
							tm1-dimension="Equipment" 
							tm1-subset="Excavators_Thiess" 
							tm1-default-element="TPL EX61" 
							ng-model="page.filter.excavators" 
							tm1-change="query(data)">
			</tm1-ui-subnm>

That seems to work OK and I can see my MDX query changing when the select box changes.

I am then using Console.log to dump the resulting data, but its hard to understand what is returned. Is there a nice way to display or understand what is returned. Example of what is returned is below

Object
@odata.context
:
"$metadata#Cellsets(Axes(Hierarchies+(Name),Tuples+(Members+(Name,UniqueName,Ordinal,Attributes))),Cells(Value))/$entity"
Axes
:
Array[3]
Cells
:
Array[60]
ID
:
"6R1vK2cFAIACAAAg"
__proto__
:

Object

Hi @bknott,

You can parse the response and append a more readable format by constructing a table based on those Cells and Axes.There should be a lot of online json viewers out there that lets you view the response in a more interactive way.

Depending on what you wanted to do, the Canvas’ Named MDX with tm1-ui-table-mdx feature might help you out on this.

If you do not need the table, you can still do a portion of the above, up to setting till the named mdx part. You can then get the results of the named MDX by calling below function instead:

$tm1Ui.cubeExecuteNamedMdx(instance, mdxId, mdxParameters);

After which, the results can be transformed into an object where the Cells are now converted into an array of rows via:

$tm1Ui.resultsetTransform(instance, cube, result, options);


Paul

Paul. the end result is to get the data in the format below so I can build a chart.

$scope.data = [
        {
            "key" : "Volume" ,
            "bar": true,
            "values" : [ [ 'Jul 16' , 993703] , [ 'Aug 16' , 1315401] , [ 'Sep 16' , 1242833] , [ 'Oct 16', 1202793] , [ 'Nov 16' , 1086494] , [ 'Dec 16' , 1317897] , [ 'Jan 17' , 1086813] , [ 'Feb 17' , 0] , [ 'Mar 17' , 0] , [ 'Apr 17' , 0] , [ 'May 17' , 0] , [ 'Jun 17' , 0]]
        },
        {
            "key" : "UnitRate" ,
            "values" : [ [ 'Jul 16' , 0.669821740499928] , [ 'Aug 16' , 1.9949361723155143] , [ 'Sep 16' , 1.971028035946905] , [ 'Oct 16' , 1.9026132551486417] , [ 'Nov 16' , 2.564447042505527] , [ 'Dec 16' , 1.8277184969690348] , [ 'Jan 17' , 1.985839204358345] , [ 'Feb 17' , 0] , [ 'Mar 17' , 0] , [ 'Apr 17' , 0] , [ 'May 17' , 0] , [ 'Jun 17' , 0]]
        }
    ].map(function(series) {
            series.values = series.values.map(function(d) { return {x: d[0], y: d[1] } });
            return series;
        });

Hi @bknott,

Are those fix sets of cube data points? Meaning always look up for Jul current year to Jun next year?

If so, it might be easier with just the cellsetGet() function. That way, you do not need to worry about how to formulate the structure of the Cells (table size). You just need to construct dbrRequests object. Below is an example:

var dbrRequests = [];
		
var dbrRequest = {};
dbrRequest.instance = 'dev';
dbrRequest.cube = 'System Info';
dbrRequest.cubeElements = ['Current Date', 'Comment'];
dbrRequests.push(dbrRequest);
		

dbrRequest = {};
dbrRequest.instance = 'dev';
dbrRequest.cube = 'System Info';
dbrRequest.cubeElements = ['Logging Directory', 'Comment'];
dbrRequests.push(dbrRequest);
		
$tm1Ui.cellsetGet(dbrRequests).then(function(data){
	// get the Value property of each object returned and put the contents on the $data array here 
});

If you wanted to go via MDX route, you can also check if you can put most of your elements on the COLUMN axis and just one dimension on the ROW axis. That way, you can be sure that the Cells being returned is a flat list.


Paul

@plim OK got the NVD3 chart to work. I am positing the final code below. I am ending up using 3 tm1-ui-subnm as filters. I have watchers set on this drop downs that then run an MDX statement and rebuild the data object.

All works great. Going to look at refining the chart next.

<div ng-controller="TPLSH01_VolumesCtrl">

  <h4>
    <span style="float: left; width: 50px; ">
				<i ng-if="$root.isLoading"  class="fa fa-cog fa-spin" ></i>
				<!--<i ng-if="!$root.isLoading" class="fa fa-file-text-o"></i>-->
		</span> {{excavator}} Volumes and Unit Rate for {{Finyear}}
  </h4>
  
  <div class="row">
  	<div class="col-md-4">
			
			<tm1-ui-subnm 	tm1-instance="MinePhysicals" 
							tm1-dimension="Equipment" 
							tm1-subset="Excavators_Thiess_inc_shovel" 
							tm1-default-element="TPL EX61" 
							ng-model="excavator" >
			</tm1-ui-subnm>
  	</div>
  	<div class="col-md-4">
		<tm1-ui-subnm 	tm1-instance="MinePhysicals" 
							tm1-dimension="Date" 
							tm1-subset="Financial Years" 
							tm1-default-element="FY17" 
							ng-model="Finyear" >
			</tm1-ui-subnm>
  	</div>
  	<div class="col-md-4">
  			<tm1-ui-subnm 	tm1-instance="MinePhysicals" 
							tm1-dimension="Material" 
							tm1-subset="Summarised" 
							tm1-default-element="All Material" 
							ng-model="material" >
			</tm1-ui-subnm>
  	</div>
  </div>
    

	<nvd3 options="options" data="data"></nvd3>

</div>

JS Controller

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

//define variables
$scope.excavator = 'TPL EX61';
$scope.Finyear = 'FY17';
$scope.material = 'All Material';

$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: 'Period',
                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 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))
                    dx
                },
                showMaxMin: false
            },
            y1Axis: {
                axisLabel: 'Volume BCM (Kt)',
                tickFormat: function(d){
                    return d3.format('.3n')(d)/1000;
                },
                axisLabelDistance: 12
            },
            y2Axis: {
                axisLabel: 'Unit Rate',
                tickFormat: function(d) {
                    return '$' + d3.format(',.2f')(d)
                }
            },
            y3Axis: {
                tickFormat: function(d){
                    return d3.format(',f')(d);
                }
            },
            y4Axis: {
                tickFormat: function(d) {
                    return '$' + d3.format('.3n')(d)
                }
            }
        }
    };

    $scope.data = [
        {
            "key" : "Volume" ,
            "bar": true,
            "values" : [ ]
        },
        {
            "key" : "UnitRate" ,
            "values" : [ ]
        }
    ].map(function(series) {
            series.values = series.values.map(function(d) { return {x: d[0], y: d[1] } });
            return series;
        });

$scope.buildMDX = function() {
    var mdx1 = 'select  {DESCENDANTS([Date].['+$scope.Finyear+'],2)} on 0, ';
    mdx1 = mdx1 + '{[measure_costing_schedules].[volume_bcm], [measure_costing_schedules].[unit_rate]} on 1';
    mdx1 = mdx1 + 'from [Costing_schedules] where ([contract_version].[All],[scenario].[actual],[version].[base],[measure_costing_schedules].[volume_bcm], [shift].[all],[area].[all areas],[pit].[all pits],';
    mdx1 = mdx1 + '[equipment].['+$scope.excavator+'],[material].['+$scope.material+'])';
    $log.info('MDX statement: '+mdx1);

    $tm1Ui.cubeExecuteMdx('MinePhysicals', mdx1).then(function(data){

        var nRows = data.Axes[1].Cardinality;
        var nColumns = data.Axes[0].Cardinality;

        //define the data array to populate
        $scope.data

        $log.log('rows: ' + nRows + ' Columns: ' + nColumns);
        $log.log($scope.retData);

        $scope.retData = data;

        for (i = 0; i < nColumns; i++) {
                //set the x axis for the volumes
                //console.log(i)
                $scope.data[0].values[i] = new Object;
                $scope.data[0].values[i].x = $scope.retData.Axes[0].Tuples[i].Members[0].Attributes.Desc;
                
                //set tthe x axis for the unit rate
                $scope.data[1].values[i] = new Object;
                $scope.data[1].values[i].x = $scope.retData.Axes[0].Tuples[i].Members[0].Attributes.Desc;
                //console.log($scope.data[0].values[i].x);

                // set the volumes
                $scope.data[0].values[i].y = $scope.retData.Cells[i].Value==null?'0':$scope.retData.Cells[i].Value;
                //set the unit rate
                $scope.data[1].values[i].y = $scope.retData.Cells[i+nColumns].Value==null?'0':$scope.retData.Cells[i+nColumns].Value;
        };

    })

    

}


// watchers
$scope.$watchGroup(['excavator','Finyear','material'], function(newValue, oldValue){
    $scope.buildMDX();
});



}]); // end of app controller

Hi @bknott,

Great to know that chart is good now!

And here are a few more notes on the above for fine tuning and for future references too:

1.) Lines of JS statements should end with semicolons. For example:

should be:

same as with:

should be:

2.) Adopt/Prefer a camelCase naming convention.

For example:

//define variables
$scope.excavator = 'TPL EX61';
$scope.Finyear = 'FY17';
$scope.material = 'All Material';

A preferred naming convention would be:

//define variables
$scope.excavator = 'TPL EX61';
$scope.finYear = 'FY17';
$scope.material = 'All Material';

3.) Attaching variables to a property of $scope.

An example for now, instead of:

//define variables
$scope.excavator = 'TPL EX61';
$scope.finYear = 'FY17';
$scope.material = 'All Material';

Let us say we will group all of these variables and identify them as selections, it would now look like:

//define variables
$scope.selections = {};
$scope.selections.excavator = 'TPL EX61';
$scope.selections.finYear = 'FY17';
$scope.selections.material = 'All Material';

Overall, thanks for posting your working HTML and scripts!


Paul

@plim Couple of additional questions.

tm1-ui-sunmn has a tm1-attribute tag to allow you to show somethign like a description in your select box.

However this is not what is saved to the ng-model. Once I have a selection, is there a way to get attributes of that selection, like description and format?

Brian

Hi @bknott,

Yes, the subnm returns just the element name. As for getting attributes, you can pick from:

  1. tm1-ui-element
  2. tm1-ui-dbra
  3. Or through the $tm1Ui service as follows,
$tm1Ui.attributeGet(instance, dimension, element, attribute);


Paul

@plim Looking at the $tm1Ui function. Code is below

$tm1Ui.attributeGet('MinePhysicals', 'measure_costing_schedules', $scope.selections.measure1, 'Description').then(function(data){
            $scope.selections.temp = data.value; 
            console.debug('Returned by this function - %o', data);
            console.log($scope.selections.temp);
            });

Been looking at other posts on the promise concept. The function returns a object of data, i just can’t seem to assign it to anything. The value of $scope.seections.temp is undefined.

Brian