Highcharts using Directives in AngularJS - angularjs

I am new to Angular and just getting used to how everything fits together. I want to display a chart through a directive. This chart will be static data so will neither be dynamically loaded nor updated, it is taken from a JSON.
I have referenced here but the main difference is that I am not loading Highcharts in the view. I am loading it globally in the .js file from my bower_components directory. This is my code:
.js
/* global Highcharts */
angular.module("fusoDataLoggerChart", [])
.controller("fusoDataLoggerChartController", ["$http", "$scope", function($http, $scope) {
"use strict";
$scope.data = {
"activities": [
{},
{},
{
"title": "Engine speed",
"type": "DATA_LOGGER",
"result": {
"Engine speed": {
"data": [
{
"timestamp": 0,
"value": {
"type": "QUANTITY",
"unit": "rpm",
"value": 900
}
},
{
"timestamp": 1000,
"value": {
"type": "QUANTITY",
"unit": "rpm",
"value": 1000
}
},
{
"timestamp": 2000,
"value": {
"type": "QUANTITY",
"unit": "rpm",
"value": 2000
}
}
]
},
"Accelerator pedal position": {
"data": [
{
"timestamp": 0,
"value": {
"type": "QUANTITY",
"unit": "%",
"value": 0
}
},
{
"timestamp": 1000,
"value": {
"type": "QUANTITY",
"unit": "%",
"value": 10.6
}
},
{
"timestamp": 2000,
"value": {
"type": "QUANTITY",
"unit": "%",
"value": 11
}
}
]
}
}
}
]
}
}])
.directive("fusoDataLoggerChart", function() {
"use strict";
return {
scope: {},
restrict: 'E',
link: function(scope) {
scope.dataLoggerData = scope.data["activities"][2]["result"];
scope.timestamps = getData()[0];
scope.engineSpeeds = getData()[1];
scope.pedalPositions = getData()[2];
var chart = new Highcharts.Chart({
chart: {
type: 'line',
animation: false,
renderTo: "DataLoggerChartContainer",
zoomType: 'x'
},
credits: {
enabled: false
},
title: {
text: null
},
xAxis: {
type: "linear",
title: {
text: "Timestamp"
},
min: 0,
categories: scope.timestamps
},
yAxis: [{ //Engine Speed
title: {
text: 'Engine Speed'
},
labels: {
format: '{value} RPM'
},
opposite: true
}, { //Accelerator Pedal Position
title: {
text: 'Accelerator Pedal Position'
},
labels: {
format: '{value} %'
},
min: 0,
max: 100,
// FIXME: 'allowDecimal' may not work
allowDecimal: true
}],
series: [{
name: 'Engine Speed',
type: 'spline',
yAxis: 1,
tooltip: {
valueSuffix: ' RPM'
},
data: scope.engineSpeeds
}, {
name: 'Accelerator Pedal Position',
type: 'spline',
yAxis: 2,
tooltip: {
valueSuffix: ' %'
},
data: scope.pedalPositions
}]
});
function getData() {
var timestamps = [],
engineSpeeds = [],
pedalPositions = [];
var engineSpeedData = scope.dataLoggerData["Engine Speed"]["data"],
pedalPosData = scope.dataLoggerData["Accelerator Pedal Position"]["data"];
for (var i in engineSpeedData) {
timestamps.push(engineSpeedData[i].timestamp);
}
for (var j in engineSpeedData) {
engineSpeeds.push(engineSpeedData[j].value.value);
}
for (var k in pedalPosData) {
pedalPositions.push(pedalPosData[k].value.value);
}
return [timestamps, engineSpeeds, pedalPositions];
}
}
}
});
HTML
<fuso-data-logger-chart>
<div id="DataLoggerChartContainer"></div>
</fuso-data-logger-chart>
When I go to where the chart is to be rendered, nothing is shown (and only the HTML code set manually is displayed with nothing inside when I check in DevTools).
N.B: I realize I do not need the controller yet but it is there as in future the JSON will be got from a rest call from within the controller
Any help appreciated.

This isn't a full answer to your question, but I noticed a way you can make your HTML directive a little cleaner so that you could instead use this format:
<fuso-data-logger-chart></fuso-data-logger-chart>
You don't need the <div id="DataLoggerChartContainer">. You can embed that in the directive definition as a template:
.directive("fusoDataLoggerChart", function() {
"use strict";
return {
scope: {},
restrict: 'E',
transclude: true,
template: '<div id="DataLoggerChartContainer"></div>'
link: function(scope) {
... your other code ...
}
}
});

I have figured out the problem. There were multiple errors but I'll leave this here so maybe someone else can find it useful:
Highcharts and my FusoDataLoggerChart.js were not loaded into index.html (<script src="modules/readVehicleData/FusoDataLoggerChart.js"></script>) meaning that they couldn't be seen by Angular.
In my directive, I was trying to access the local scope (scope.data) in the link() function instead of trying to access the parent scope in the controller where the data is set. To access the parent scope, scope.$parent.data worked for me (N.B: was only 'data' for me as that's the value I set)

Related

Alexa smart home capabilities overriding one another

I have an Alexa project for smart home shades that include two range controllers, a brightness controller, and a toggle controller on each endpoint.
The two range controllers, toggle controller, and brightness controller all have different instances and friendlyNames.
When issuing commands to Alexa, anything containing a percentage is defaulting to the brightness controller regardless of how the command is phrased. If I fully remove the brightness controller and attempt to issue a rotation command (range controller), depending on the device, I get incredibly inconsistent results stating it doesn't know how to set the setting. Some devices will work around 90% at first and will then suddenly fail consistently. Other devices wont work at all. No device is consistently working.
If I reduce down to any one of the capabilities, it works nearly 100% of the time.
Has anyone else experienced this behavior and is there any solution?
Capability code:
export const ROTATE_CAPABILITY = {
type: "AlexaInterface",
interface: "Alexa.RangeController",
version: "3",
instance: "Blind.Rotate",
properties: {
supported: [{ name: "rangeValue" }],
proactivelyReported: false,
retrievable: true,
},
capabilityResources: {
friendlyNames: [
{
"#type": "text",
value: {
text: "Vane",
locale: "en-US"
}
},
{
"#type": "text",
value: {
text: "Tilt",
locale: "en-US"
}
},
{
"#type": "text",
value: {
text: "Angle",
locale: "en-US"
}
}
]
},
configuration: {
supportedRange: {
minimumValue: 0,
maximumValue: 100,
precision: 1,
},
unitOfMeasure: "Alexa.Unit.Percent"
}
}
export const BRIGHTNESS_CAPABILITY = {
type: "AlexaInterface",
interface: "Alexa.BrightnessController",
version: "3",
instance: "Blind.Brightness",
properties: {
supported: [
{
name: "brightness"
}
],
proactivelyReported: false,
retrievable: true
}
}
export const BRIGHTNESS_TOGGLE = {
type: "AlexaInterface",
interface: "Alexa.ToggleController",
instance: "Blind.Light",
version: "3",
properties: {
supported: [
{
name: "toggleState"
}
],
proactivelyReported: false,
retrievable: true,
nonControllable: false
},
capabilityResources: {
friendlyNames: [
{
"#type": "text",
value: {
text: "Shade Light",
locale: "en-US"
}
},
{
"#type": "text",
value: {
text: "Light",
locale: "en-US"
}
}
]
}
}
export const MAIN_CAPABILITY = {
type: "AlexaInterface",
interface: "Alexa.RangeController",
instance: "Blind.Lift",
version: "3",
properties: {
supported: [{
name: "rangeValue",
},],
proactivelyReported: false,
retrievable: true,
},
capabilityResources: {
friendlyNames: [
{
"#type": "text",
value: {
text: "Shade",
locale: "en-US"
}
}
],
},
configuration: {
supportedRange: {
minimumValue: 0,
maximumValue: 100,
precision: 1,
},
unitOfMeasure: "Alexa.Unit.Percent",
},
semantics: {
actionMappings: [{
"#type": "ActionsToDirective",
actions: ["Alexa.Actions.Close"],
directive: {
name: "SetRangeValue",
payload: {
rangeValue: 0,
},
},
},
{
"#type": "ActionsToDirective",
actions: ["Alexa.Actions.Open"],
directive: {
name: "SetRangeValue",
payload: {
rangeValue: 100,
},
},
},
{
"#type": "ActionsToDirective",
actions: ["Alexa.Actions.Lower"],
directive: {
name: "AdjustRangeValue",
payload: {
rangeValueDelta: -10,
rangeValueDeltaDefault: false,
},
},
},
{
"#type": "ActionsToDirective",
actions: ["Alexa.Actions.Raise"],
directive: {
name: "AdjustRangeValue",
payload: {
rangeValueDelta: 10,
rangeValueDeltaDefault: false,
},
},
},
],
stateMappings: [{
"#type": "StatesToValue",
states: ["Alexa.States.Closed"],
value: 0,
},
{
"#type": "StatesToRange",
states: ["Alexa.States.Open"],
range: {
minimumValue: 1,
maximumValue: 100,
},
},
],
},
}

Combining Column charts and line charts with the same same data in the same container(Highcharts)

I want to build a combination chart with a column chart with multiple series and a line chart. Problem is that I am getting High charts data from nested JSON response. For that I initialized array and that array is giving in series in plotoptions highcharts as you can see in the below code.
My code is like this:
var crime_data=[];
for(var i=0;i<result.themes.length;i++){
var crime={};
var test2 = result.themes[i];
var test = test2[Object.keys(test2)];
crime.name = Object.keys(result.themes[i]);
crime.data = [];
for(var k=0;k<test.yearTheme.length;k++){
var test3=test.yearTheme[k];
var test5=test3.individualValueVariable;
for(var j=0;j<test5.length;j++){
crime.data.push(test5[j].count);
};
};
crime_data.push(crime);
};
var crimeChart = new Highcharts.Chart({
chart: {
renderTo: 'container1',
type:'column'
},
title: {
text: 'Crime'
},
xAxis: {
categories: month,
crosshair: true
},
yAxis: {
min: 0,
title: {
text: 'Count'
}
},
credits: {
enabled: false
},
tooltip: {
shared: true,
},
plotOptions: {
column: {
pointPadding: 0.2,
borderWidth: 0,
depth: 25,
allowPointSelect: true,
cursor: 'pointer',
point: {
},
}
},
series: crime_data
});
This is Column chart I am getting when i write chart type column.
This is my Line Chart I am getting when i changed type column to spline in chart in highcharts.
And this is my JSON data(Highcharts data):
{
"boundaries": {
"boundary": [
{
"boundaryId": "55083021003",
"boundaryType": "USA_CITY",
"boundaryRef": "C1"
}
]
},
"themes": [
{
"AssaultCrimeTheme": {
"boundaryRef": "C1",
"individualValueVariable": [
{
"name": "2013 Assault Crime",
"description": "Assault Crime for 2013",
"count": 18901
},
{
"name": "2014 Assault Crime",
"description": "Assault Crime for 2014",
"count": 17707
}
]
}
},
{
"BurglaryCrimeTheme": {
"boundaryRef": "C1",
"individualValueVariable": [
{
"name": "2013 Burglary Crime",
"description": "Burglary Crime for 2013",
"count": 17743
},
{
"name": "2014 Burglary Crime",
"description": "Burglary Crime for 2014",
"count": 14242
}
]
}
}
]
}
I want to combine both of them in the same container with same data.The problem is in how to tell highcharts multiple series should be represented with line and with column type with same data.For this when i write series:[{ data: crime_data ,type: spline }] instead of series:crime_data In that case I am not getting Highcharts data. Can anyone Please help me how should i do this.Please suggest me.
Pass your data, like below format. add type of chart in each data series;
Here i replaced type value but with same data.
[{
type: 'line',
name: 'AssaultCrimeTheme',
data: [3, 2, 1, 3, 4]
}, {
type: 'line',
name: 'BurglaryCrimeTheme',
data: [2, 3, 5, 7, 6]
}, {
type: 'column',
name: 'AssaultCrimeTheme',
data: [3, 2, 1, 3, 4]
}, {
type: 'column',
name: 'BurglaryCrimeTheme',
data: [2, 3, 5, 7, 6]
},]
Here is fiddle for more details.
Here is a complete example using your data.
const json = {
"boundaries": {
"boundary": [{
"boundaryId": "55083021003",
"boundaryType": "USA_CITY",
"boundaryRef": "C1"
}]
},
"themes": [{
"AssaultCrimeTheme": {
"boundaryRef": "C1",
"individualValueVariable": [{
"name": "2013 Assault Crime",
"description": "Assault Crime for 2013",
"count": 18901
}, {
"name": "2014 Assault Crime",
"description": "Assault Crime for 2014",
"count": 17707
}]
}
}, {
"BurglaryCrimeTheme": {
"boundaryRef": "C1",
"individualValueVariable": [{
"name": "2013 Burglary Crime",
"description": "Burglary Crime for 2013",
"count": 17743
}, {
"name": "2014 Burglary Crime",
"description": "Burglary Crime for 2014",
"count": 14242
}]
}
}]
}
// Create categories object in order filter duplicates
const cats = {}
const series = json.themes.map((o) => {
const key = Object.keys(o)[0]
return {
name: key,
data: o[key].individualValueVariable.map((o) => {
cats[o.name] = 1
return { category: o.name, y: o.count }
})
}
})
// Convert categories object to array
const categories = Object.keys(cats)
// Chart options
const options = {
chart: {type: 'column'},
xAxis: {categories: categories},
series: series
}
// Create chart
const chart = Highcharts.chart('container', options)
console.log(series, categories)
Live example: https://jsfiddle.net/Lo323gq3/
Output below:

Add optgroups to angular-selectize asynchronously

I am using angular-selectize directive in my project. For this, I need to load optgroups asynchronously. So far I have tried multiple approaches but none of them works. The problem is, you cannot use the data returned by a promise synchronously. On the flip side, I have also been unable to initialize selectize from inside a promise callback. Given below is the code I currently have. Note that it is only to be used to get the idea of the data I'm playing with, not to present it as something you can consider right.
app.js
$http
.get('/get-categories')
.then(getCategoriesSCB, getCategoriesFCB);
function getCategoriesSCB(response) {
if(typeof(response.data) === 'object') {
posControl.menuCategories = response.data[0];
posControl.menuCategoryGroups = response.data[1];
}
else {
getCategoriesFCB(response);
}
}
function getCategoriesFCB(response) {
console.log(response);
}
posControl.menuConfig = {
valueField: 'id',
labelField: 'title',
placeholder: 'Select Category',
optgroupField: 'class',
optgroupLabelField: 'label',
optgroupValueField: 'value',
optgroups: posControl.menuCategoryGroups,
maxItems: 1,
searchField: ['title', 'category'],
onInitialize: function(selectize) {
console.log('selectize is here');
},
}
index.html
<selectize config="POSCtrl.menuConfig" options="POSCtrl.menuCategories" ng-model="POSCtrl.menuModel"></selectize>
data returned by ajax call
[
// this array has to be used for options.
[{
"class": "57b83830babb9",
"category": "Food Menu",
"id": "57b83855b23f9",
"title": "Beverages"
}, {
"class": "57b83830babb9",
"category": "Food Menu",
"id": "57b83855c05de",
"title": "Cuisines"
}, {
"class": "57b83830babb9",
"category": "Food Menu",
"id": "57b83855cdcb4",
"title": "Steaks"
}, {
"class": "57b83830d0899",
"category": "Wholesale Coffee",
"id": "57b83830d0899",
"title": "Wholesale Coffee"
}],
// this array has to be used for optgroups
[{
"value": "57b83830babb9",
"label": "Food Menu"
}, {
"value": "57b83830d0899",
"label": "Wholesale Coffee"
}]
]
You should be able to load a selectize asynchronously by setting the values directly on the posControl.menuConfig:
function getCategoriesSCB(response) {
if (typeof(response.data) === 'object') {
posControl.menuConfig.options = response.data[0];
posControl.menuConfig.optgroups = response.data[1];
}
}

Angular Directive: Pass event data to Page Controller Method when user Clicks Menu Item

I have a hierarchical menu system with nested categories that allows users to drill down the hierarchy to lower levels.
I have a click event that is used to drill down in to the menu when there are children.
When they reach the final level (known as Leaf), I want to fire a methods(data) on the page controller with information about the node they just selected.
Directive
angular.module(APP)
.directive('wkProductCategoryMenu', [function () {
return {
restrict: 'E',
require: "?ngModel",
scope: {
heading: '=',
onMenuLeafSelect: '&'
},
templateUrl: COMPONENTS_PATH + '/productCategoryMenu/productCategoryMenu.html',
controller: 'ProductCategoryMenuController',
link: function (scope, element, attrs, ngModel) {
if (!ngModel) {
return;
}
scope.parentCategories = [];
ngModel.$render = function () {
scope.focusCategories = ngModel.$modelValue;
};
}
};
}
])
;
Directive Controller
angular.module(APP)
.controller('ProductCategoryMenuController',
['$scope', '$log', function
($scope, $log)
{
$scope.selectMenu = function (menu) {
var name = menu.name;
$log.info('Select: ' + name);
for (var i = 0; i < $scope.focusCategories.length; i++) {
if (_.isEqual($scope.focusCategories[i].name, name)) {
if (menu.isLeaf) {
$log.warn('Trigger for menu: ' + name + " : " + menu.hierarchy);
if ($scope.onMenuLeafSelect) {
$scope.onMenuLeafSelect(menu);
}
} else {
$scope.parentCategories.push($scope.focusCategories);
$scope.focusCategories = $scope.focusCategories[i].categories;
}
}
}
}
$scope.back = function () {
$log.info('Back');
$scope.focusCategories = $scope.parentCategories.pop();
}
}]);
Use the Directive in Page
<div class="row">
<div class="col-lg-12">
<wk-product-category-menu
heading="model.filter.supplier.supplier"
ng-model="model.entity.categories"
on-menu-leaf-select="getProducts(productCategory)">
</wk-product-category-menu>
</div>
</div>
Page Events
// -------------------------------------------------------------------------------------------------------
// Page Events
// -------------------------------------------------------------------------------------------------------
$scope.getProducts = function (productCategory) {
$log.info("ProductCategory");
// THIS is UNDEFINED
$log.error(JSON.stringify(productCategory));
}
Sample Data
{
"_id": "561f6304512106cfa84920b0",
"active": true,
"supplier": "All",
"productCount": 6891,
"categoryCount": 13,
"categories": [
{
"isLeaf": false,
"isRoot": true,
"categories": [
{
"isLeaf": false,
"isRoot": false,
"categories": [
{
"isLeaf": true,
"isRoot": false,
"categories": [
],
"productCount": 15,
"name": "Premium"
},
{
"isLeaf": true,
"isRoot": false,
"categories": [
],
"productCount": 38,
"name": "Craft"
}
],
"productCount": 53,
"name": "Domestic Beer"
},
{
"isLeaf": false,
"isRoot": false,
"categories": [
{
"isLeaf": true,
"isRoot": false,
"categories": [
],
"productCount": 6,
"name": "Craft"
}
],
"productCount": 28,
"name": "International Beer"
},
{
"isLeaf": true,
"isRoot": false,
"categories": [
],
"productCount": 18,
"name": "Cider"
},
{
"isLeaf": true,
"isRoot": false,
"categories": [
],
"productCount": 4,
"name": "Bitters"
}
]
}
]
}
When you trigger the event pass the data with an object as below:
$scope.onMenuLeafSelect({productCategory: menu});

Angularjs, Amcharts do not work with json data object is assigned to data provider instead of actual data

How can I pass a JSON data object to dataProvider in my directive instead of hard-coding actual data to render a graph with Amcharts and Angularjs?
If JSON data is provided instead of a JavaScript object the graph is rendered otherwise the graph is not rendered at all. Although chart title is visible but axis are not created and also the data-points are not displayed in the graph.
angular.module('myApp').directive('activityChart',
function ( $timeout) {
return {
restrict: 'EA',
replace:true,
//scope :true,
template: '<div id="{{chartId}}" style="width: 100%; height: 400px; overflow: hidden; text-align: left;"></div>' ,
link: function ($scope, $element, $attrs) {
var chart = false;
var initChart = function() {
if (chart) chart.destroy();
$scope.chartId = $attrs.chartId;
$scope.chartUnit = $attrs.chartUnit;
$scope.chartData = $attrs.chartData;
console.log($scope.chartData);
$timeout(function(){var chart = AmCharts.makeChart($scope.chartId, {
"type": "serial",
"pathToImages": "/assets/amcharts/images/",
"categoryField": "time",
"dataDateFormat": "YYYY-MM-DD HH:NN:SS",
"categoryAxis": {
"minPeriod": "mm",
"parseDates": true
},
"chartCursor": {
"categoryBalloonDateFormat": "JJ:NN:SS"
},
"chartScrollbar": {},
"trendLines": [],
"graphs": [
{
"bullet": "round",
"bulletSize": 4,
"id": $scope.chartId,
"title": $scope.chartId,
"valueField": "value",
"type": "smoothedLine",
"lineThickness": 2,
"lineColor": "#637bb6"
}
],
"guides": [],
"valueAxes": [
{
"id": "ValueAxis-1",
"title": $scope.chartId + " (" + $scope.chartUnit + ")"
}
],
"allLabels": [],
"balloon": {},
"legend": {
"useGraphSettings": true
},
"titles": [
{
"id": "Title-1",
"size": 15,
"text": $scope.chartId
}
],
"dataProvider": $scope.chartData,
});
});
};
initChart();
}
}
}) ;
I made a generalized demo for your use case. Take a look at this fiddle.The key is to introduce an isolated scope for your directive. This allows you to use the directive multiple times.
If you do it like this:
scope: {
// bi directional binding will pass data array to isolated scope
data: '=',
title: '#'
},
you can assign the data array from the parent controller:
<activity-chart data="data" title="This is the title"></activity-chart>
Feel free to ask, if you don't understand how it works.

Resources