Creating arrays out of objects in AngularJS - arrays

Given the following object:
Object {November 2014: Object}
November 2014: Object
Cars: Object
324: Object
duration: 1417132808000
total: "00:00:08"
trips: 1
369: Object
duration: 5668531247000
total: "00:00:47"
trips: 4
391: Object
duration: 9919930257000
total: "00:10:57"
trips: 7
396: Object
duration: 9919929791000
total: "00:03:11"
trips: 7
KE-22: Object
duration: 5668531269000
total: "00:01:09"
trips: 4
I need to be able to create arrays from data extracted from it.
Something like this:
Labels: (Key from Objects inside Cars)
[324, 369, 391, 396, KE-22]
Series: (Properties from each object inside Cars)
[Trips, Total]
Data: (Values from properties for each object inside Cars)
[
[1,4,7,7,4], // Array of trips for each car, in order.
["00:00:08", "00:00:47", "00:10:57", "00:03:11", "00:01:09"] // Array of total duration for each car, in order.
]
With this arrays I intend to populate a chart for each month.
The collection of objects has been created using the following code, probably is relevant to the question:
var dataByMonth = _.groupBy($scope.recordlist, function(record) {
return moment(record.date, 'DD-MM-YYYY').format('MMMM YYYY');
});
dataByMonth = _.mapValues(dataByMonth, function(month) {
var obj = {};
obj.Cars = _.groupBy(month, 'car');
obj.Drivers = _.groupBy(month, 'driver');
_.each(obj, function(groupsValue, groupKey) {
obj[groupKey] = _.mapValues(groupsValue, function(groupValue) {
return _.reduce(groupValue, function(sum, trip) {
sum['trips']++;
sum['duration']+= moment.utc(trip.duration, 'HH:mm:ss');
sum['total'] = moment.utc(sum.duration). format('HH:mm:ss')
return sum;
}, {trips: 0, duration: 0, total:0})
});
})
return obj;
});
$scope.statistics = dataByMonth;
console.log($scope.statistics);
Any tips on how to proceed?

_.chain(cars).pairs().map(function(p) {
var id = p[0];
return [id].concat(_.values(p[1])); // produces an array of [{id, duration, trips, total}]
}).reduce(function(acc, as) {
// we assume properties are sorted
return _.map(as, function(a, i) {
return acc[i] = (acc[i] || []).concat([a]);
});
}, []).value();

A problem with objects is that they cannot guarantee the order of their properties.
This means that before converting to arrays, you need to somehow decide on a predifined sequence algorithm. In the code below this is the alphanumeric order of the property names.
var objPart = obj["November 2014"]["Cars"];
var ret = _.chain(objPart).transform(function(total, n , key){
//In this first iteration we collect all possible series that may exist,
total.series =_.union(total.series, _.keys(n) );
//and all labels
total.labels.push(key);
}, {labels:[], series:[], data:[]} ).mapValues(function(val){
//sorting labels, series. Data do not yet exist
return _.sortBy(val);
}).value();
//based on the above orders generate a multi dimensional array for data.
ret = _.transform(ret.labels, function(total, lbl){
var arr = _.transform(ret.series, function(partialTotal, ser){
partialTotal.push(objPart[lbl][ser]);
}, []);
total.data.push(arr);
}, ret);
console.log("Labels:",ret.labels);
console.log("Series:",ret.series);
console.log("Data:",JSON.stringify(ret.data));
The above solution will work even for the data below:
var obj = {
"November 2014": {
"Cars": {
"339": {
"trips": 1,
"total": "00:00:08",
"duration": 1417132809000,
},
"324": {
"trips": 1,
"extraProp1":"x",
"total": "00:00:08",
"duration": 1417132808000,
},
"369": {
"duration": 5668531247000,
"trips": 4,
"total": "00:00:47",
"extraProp2":"y"
},
"391": {
"duration": 9919930257000,
"trips": 7,
"total": "00:10:57"
},
"396": {
"duration": 9919929791000,
"total": "00:03:11",
"trips": 7
},
"KE-22": {
"duration": 5668531269000,
"total": "00:01:09",
"trips": 4
}
}
}
}
Output:
Labels: ["324", "339", "369", "391", "396", "KE-22"]
Series: ["duration", "extraProp1", "extraProp2", "total", "trips"]
Data: [[1417132808000,"x",null,"00:00:08",1],
[1417132808000,null,null,"00:00:08",1],
[5668531247000,null,"y","00:00:47",4],
[9919930257000,null,null,"00:10:57",7],
[9919929791000,null,null,"00:03:11",7],
[5668531269000,null,null,"00:01:09",4]]

Related

Filtering JSON object to get the average of 2 objects

I'm doing an React assignment for school but I'm a bit stuck and I can't find the right answer.
I have a data file with the following data:
const students = [
{
"name": "Evelyn",
"assignment": "SCRUM",
"difficultyRating": 3,
"funRating": 4
},
{
"name": "Evelyn",
"assignment": "W1D1-1",
"difficultyRating": 3,
"funRating": 3
},
{
"name": "Evelyn",
"assignment": "W1D2-1",
"difficultyRating": 1,
"funRating": 3
}
]
This goes on, there are 10 student and different assignments. What I need to do is get the average of the difficultyRating and funRating per assignment and use this data in a Victory Graph to display. Victory is working but it's not displaying the average.
I have this function already which takes all the assignments and fills it with the other data but I don't know what to do in the next step, how can I make a new Array of objects that I can use in the Victory Chart which displays the average difficulty/funrating per assignment.
The code I have so far is this:
const groupByAssignment = (objectArray, property) => {
return objectArray.reduce(function (total, obj) {
let key = obj[property];
if (!total[key]) {
total[key] = [];
}
total[key].push(obj);
return total;
}, {});
}
let groupedAssignments = groupByAssignment(students, 'assignment');
In the Victory Graph the output looks like this now:
<VictoryBar
style={{
data: {
fill: "#ff0b03",
},
}}
barWidth={2}
data={props.data}
x="assignment"
y="difficultyRating"
/>
)}
What I need is a data piece that has every assignment with the difficulty/rating averaged from all 10 students.
The following will give the average values of fun rating and difficulty rating. I have added more entries as there was only one record for each assignment.
const students = [ { name: "Evelyn", assignment: "SCRUM", difficultyRating: 3, funRating: 4, }, { name: "Pqr", assignment: "SCRUM", difficultyRating: 4, funRating: 2, }, { name: "Evelyn", assignment: "W1D1-1", difficultyRating: 3, funRating: 3, }, { name: "Evelyn", assignment: "W1D2-1", difficultyRating: 1, funRating: 3, }, { name: "Abc", assignment: "W1D2-1", difficultyRating: 5, funRating: 4, }, ];
const groupByAssignmentWithAverage = (objectArray, property) => {
return objectArray.reduce(
(prevValue, { difficultyRating, funRating, [property]: key }) => {
// key is the value of in obj with property as data name
if (!prevValue[key]) {
// create a new entry for each assignment type
prevValue[key] = {
difficultyRatingAvg: difficultyRating,
funRatingAvg: funRating,
count: 1,
};
} else {
// get the previous count and average values
const {count, difficultyRatingAvg, funRatingAvg} = prevValue[key];
prevValue[key] = {
difficultyRatingAvg:
(difficultyRatingAvg + difficultyRating) /
(count + 1),
funRatingAvg: (funRatingAvg + funRating) / (count + 1),
count: count + 1,
};
}
return prevValue;
},
{}
);
};
let output = groupByAssignmentWithAverage(students, "assignment");
console.log(output);
My solution would be to seprate the objects with same assignment name, and then just find the average of that particular assignment by mapping over individual arrays of objects which we get from initial step.
result = students.reduce(function (r, a) {
r[a.assignment] = r[a.assignment] || [];
r[a.assignment].push(a);
return r;
}, Object.create(null));
console.log(result);
Now you can easily format data according to the graph package you are using.

Combine two arrays by id

I have two arrays and I need to combine them by an id. This is how it looks like:
Every single Station has its own sensors. One, two, three or even none. We can find them thanks to ids. Station has an id property, and Sensors are owners of stationId properties. Unfortunately I can't to download them together in one array, so I have to use two arrays. I want to display them in the list view, so I have to create array of objects where the data of both lists form one list. I have created combined array, but there every Stations has only one Sensor and this my problem.
I can't make array where Stations are owners more than one Sensor. In the code below you can see, that there is a problem with accessing elements. It always takes the first index, but I don't know how to get another ones with same id. I'm stuck
Station:
[{
"id": 14,
"stationName": "Działoszyn",
"gegrLat": "50.972167",
"gegrLon": "14.941319",
"city": {
"id": 192,
"name": "Działoszyn",
"commune": {
"communeName": "Bogatynia",
"districtName": "zgorzelecki",
"provinceName": "DOLNOŚLĄSKIE"
}
},
"addressStreet": null
}]
Sensors:
[{
"id": 92,
"stationId": 14,
"param": {
"paramName": "pył zawieszony PM10",
"paramFormula": "PM10",
"paramCode": "PM10",
"idParam": 3
}
},
{
"id": 88,
"stationId": 14,
"param": {
"paramName": "dwutlenek azotu",
"paramFormula": "NO2",
"paramCode": "NO2",
"idParam": 6
}
}]
Edited code:
func setAddItemList(stations: [Station], sensors: [Sensor]) {
var stationItems = [StationItem]()
var sensorItems = [SensorItem]()
sensors.forEach { sensor in
guard let index = stations.firstIndex(where: { $0.id == sensor.stationId}) else {
print("Failed to find a station for sensor \(sensor.id)")
return
}
let sensorItem = SensorItem(
id: sensor.id,
stationId: sensor.stationId,
param: ParamItem(
paramName: sensor.param?.paramName ?? "",
paramFormula: sensor.param?.paramFormula ?? "",
paramCode: sensor.param?.paramCode ?? "",
idParam: sensor.param?.idParam ?? 0))
sensorItems.append(sensorItem)
if sensorItems.count == sensors.count {
let stationItem = stations.map { station in
StationItem(
id: station.id,
stationId: sensor.stationId,
cityName: station.city?.name ?? "",
addressStreet: station.addressStreet!,
sensor: stationItems[index].sensors?.append(sensorItem) ?? []
)
}
stationItems.append(contentsOf: stationItems)
}
}
I also tested Dictionaries, but I had the same problem.
First map Station to StationItem
var stationItems = stations.map { station in
StationItem(
id: station.id,
stationId: sensor.stationId,
cityName: station.city?.name ?? "",
addressStreet: station.addressStreet!,
sensor: [])
}
Then apply my solution from your previous question but instead of appending a Sensor object you first map it to a SensorItem object as you are doing in your code above

In an array of obects watched with Vue-watch, how to get index of object that was changed?

I have an array of objects, like this:
myArray: [{
name: "First",
price: 10,
rebate: 5,
listPrice: 15,
outcome: 0
},{
name: "Second",
price: 11,
rebate: 5,
listPrice: 16,
outcome: 0
}
I want to recalculate the outcome-value whenever any of the other values in the same object change.
I already have a setup like this, but it looks for changes in any object and then recalculates the whole array. I've managed to set this up by using a combination of computed and watch functions. However they watch the whole array for changes and then recalculate the outcome-value for all objects in the array.
How can I watch for changes and then recalculate only the changed object?
Below is my current functions for recalculating the whole array (watching another property), but what I'm looking for could be completely different.
computed:
myArrayWasChanged() {
return [this.myArray.reduce((a, {vendors}) => a + vendors, 0), this.myArray.filter(item => item.discounted == false).length]
watch:
myArrayWasChanged: {
handler: function (val, oldVal) {
this.recalculateIsVendor();
Given the outcome is completely dependent on the other properties, it isn't really part of the component's state. Thus, in the component's data you could store the array without the outcome, and then calculate a new version of the array with the outcome as a computed property.
data: function () {
return {
myArrayWithoutOutcome: [
{
name: "First",
price: 10,
rebate: 5,
listPrice: 15
},
{
name: "Second",
price: 11,
rebate: 5,
listPrice: 16
}]
}
},
computed: {
myArrayWithOutcome: function () {
return this.myArrayWithoutOutcome.map(x => {
return {...x, outcome: this.calculateOutcome(x)}
})
}
},
methods: {
calculateOutcome(item) {
// Logic to calculate outcome from item goes here
return 0
}
}

Two dimensions data, display no results

I'm a beginner with angularJs
I have json data on two dimensions :
[
{
"nom": "Name 1",
"subventions": [
{
"beneficiaire": "ben\u00e9f Phipippe C mars 2015",
"montant": "7<span class='space'><\/span>898,99",
"annee": 1,
"trimestre": 4,
"infoAdditionnelle": "<p>info mars - 2015<\/p>\r\n<div id=\"nested_deluminate_fullscreen_workaround\" style=\"background-color: rgba(0, 0, 0, 0);\">\u00a0<\/div>"
},
{
"beneficiaire": "cvbcvbn",
"projet": "<p>cvnnncbcnbcbn<\/p>",
"circonscription": "456sdxfvxc",
"montant": "131,00",
"annee": 3,
"trimestre": 2,
"infoAdditionnelle": "<p>test<\/p>"
}
]
},
{
"nom": "Name 2",
"subventions": [
{
"beneficiaire": "pierre m",
"montant": "1<span class='space'><\/span>000,00",
"annee": 3,
"trimestre": 1,
"infoAdditionnelle": "<p>avtil 2015-16<\/p>"}
]
},
{
"nom": "Name 3",
"subventions": [
{
"beneficiaire": "bene pierre p avril 2015-16",
"montant": "1<span class='space'><\/span>222,00",
"annee": 3,
"trimestre": 1,
"infoAdditionnelle": "<p>p avril 2015-16<\/p>"
}
]
}
]
As you can see, there is an array of persons containing the field "nom" and "subventions", "subventions is also an array
I have filters by the fields "annee" and "trimestre", and what i want to do is when there's not result to display, to display a "no results" message
To calculate the number of results, i use this function
$scope.filterArray = function(resultsPersonnes) {
var data = resultsPersonnes.filter(function(prop) {
return prop.subventions[0].annee == $scope.annee.id;
});
if ($scope.myFilter.trimestre > 0) {
return data.filter(function(prop) {
return prop.subventions[0].trimestre == $scope.myFilter.trimestre;
});
} else {
return data
}
}
It's doesn't work for all the cases, you can see an example here http://plnkr.co/edit/9TbO0Hl9LziUS2B6AsAr?p=preview
If you click on "Juillet - septembre" in "Trimestre, i got one result, but the "no results" message is displayed!!!
do you see the error ? please help!
Thanks a lot
As you already guessed in your comment, you were checking only the first row. The fix is to check all rows, for example like this, using Array.some (which returns true if at least one item in array returns true for the function given as parameter)
Edit: the filtering needs to be updated into a single filter
$scope.filterArray = function(resultsPersonnes) {
var data = resultsPersonnes.filter(function(prop) {
return prop.subventions.some(function (subvention) {
var anneeMatches = subvention.annee == $scope.annee.id;
var trimestreMatches = true;
if ($scope.myFilter.trimestre > 0) {
trimestreMatches = subvention.trimestre == $scope.myFilter.trimestre;
}
return anneeMatches && trimestreMatches;
});
});
return data;
};
See working plunker

Return an array from an Ember computed property

I have two related models - Task and Requirement. Requirements can be one of 3 types (Part, Tool, Material). A Task can have several requirements including several of the same type.
Task A
Requirement 1 (Part)
Requirement 2 (Part)
Requirement 3 (Tool)
Requirement 4 (Material)
Requirement 5 (Tool)
When viewing a single Task I want to group the list of requirements by type in a sort of summary view.
Task A
Parts Requirements (2)
Tooling Requirements (2)
Materials Requirements (1)
I have a computedProperty mostly functioning in my TaskController but I can't seem to get it to return back the array of requirement summaries that I'm building. All the fixture data is setup properly for both models (I can iterate each requirement and show it in the template with no issues).
Here are the models
Task model
App.Task = DS.Model.extend({
name: DS.attr()
requirements: DS.hasMany('requirement', { async: true})
});
Requirement model
App.Requirement = DS.Model.extend({
task_id: DS.belongsTo('task'),
type: DS.attr(),
description: DS.attr(),
quantity: DS.attr()
})
Here is the controller:
App.TaskController
App.TaskController = Em.ObjectController.extend({
requirementSummary: function () {
var self = this,
results = [];
self.get('requirements').then(function(requirements) {
var arrRequirements = requirements.get('content');
var parts = {
name: 'Parts',
description: '',
count: 0,
css_class: 'fa-cog'
},
tools = {
name: 'Tools',
description: '',
count: 0,
css_class: 'fa-wrench'
},
materials = {
name: 'Materials',
description: '',
count: 0,
css_class: 'fa-tint'
};
arrRequirements.forEach(function (requirement) {
if (requirement._data.name == 'Part') {
parts.description += requirement._data.description + ' (' + requirement._data.quantity + ')<br>';
parts.count++;
} else if (requirement._data.name == 'Material') {
materials.description += requirement._data.description + ' (' + requirement._data.quantity + ')<br>';
materials.count++;
} else if (requirement._data.name == 'Tooling') {
tools.description += requirement._data.description + ' (' + requirement._data.quantity + ')<br>';
tools.count++;
}
});
if (parts.description !== '') {
parts.description = parts.description.replace(/(<br>\s*)+$/);
} else {
parts.description = "No Parts requirements found";
}
if (materials.description !== '') {
materials.description = materials.description.replace(/(<br>\s*)+$/);
} else {
materials.description = "No Materials requirements found";
}
if (tools.description !== '') {
tools.description = tools.description.replace(/(<br>\s*)+$/);
} else {
tools.description = "No Tooling requirements found";
}
results.pushObject(parts);
results.pushObject(tools);
results.pushObject(materials);
});
return results;
}.property()
});
Currently it returns back the empty results array because it is waiting on the self.get promise to fulfill. If I return the result of self.get('requirements').then(...) then it returns the promise, not the results array and Ember isn't happy because it's not an array. What I want is for it to return back the populated results array.
The closest question I've found is here but it either doesn't solve the issue or I'm missing something.
You'll need to be using requirementSummary using an observable pattern, or after it's finished resolving.
Additionally this is not required due to requirements already being an iterable field:
var arrRequirements = requirements.get('content');
And you should be using a getter to get a property, not going to ._data.property
requirement.get('description')
Add this to your task template and you should see it populate (asynchronously):
{{#each requirement in requirementSummary}}
Name: {{requirement.name}} - Total: {{requirement.total}}
{{/each}}
The routes all seem to be working as well as the fixtures - I can use {{#each requirement in requirements}} and list each individual requirement with no issues. Just having the problem of generating the summarized property and accessing it after it's computed.
from routes/application_routes.js
this.resource( 'tasks', function () {
this.resource( 'task', { path: ':task_id' }, function () {
this.resource( 'task_requirements', { path: 'requirements' } );
} );
} );
from routes/task_routes.js
// List Tasks
App.TasksRoute = Em.Route.extend({
model: function () {
return this.store.find('task');
}
});
// Task Detail Route
App.TaskRoute = Em.Route.extend({
model: function(params) {
return this.store.find('task', params.task_id);
}
});
// Task Requirements Route
App.TaskRequirementsRoute = Em.Route.extend({
beforeModel: function () {
this.set('task', this.modelFor('task'));
}
});
Fixtures
// SAMPLE TASK FIXTURE.
App.Task.FIXTURES = [
{
"id": 1,
"name": "Test Task #1",
"description": "This is a test task. There are many like it but this one is mine.",
"requirements": [ 1, 2, 3, 4 ]
}
];
// SAMPLE REQUIREMENT FIXTURE
App.Requirement.FIXTURES = [
{
"id": 1,
"task_id": 1,
"type": "Part",
"description": "This is a Part requirement",
"quantity": 4
},
{
"id": 2,
"task_id": 1,
"type": "Part",
"description": "This is a Part requirement",
"quantity": 1
},
{
"id": 3,
"task_id": 1,
"type": "Material",
"description": "This is a Material requirement",
"quantity": 3
},
{
"id": 4,
"task_id": 1,
"type": "Tool",
"description": "This is a Tooling requirement",
"quantity": 1
}
];

Resources