1

EDIT

So I think I should delve into this a little more. I am currently working with HighCharts JS. For data to be shown in highcharts, I must have the final data as follows:

[
  {
    name: 'Performing',
    data: [1941404, 1028717, 697370, 0, 0, 0]
  },
  {
    name: 'Non performing',
    data: [0, 0, 0, 1759908, 890857, 280235]
  },
  {
    name: 'Substandard',
    data: [0, 0, 863825, 0, 0, 0]
  },
  {
    name: 'Written-off',
    data: [0, 0, 0, 0, 0, 77146]
  }
]

'Data' is an array of 6 objects which populate the xAxis of the chart.

However, I have the following data which is being supplied via MongoDb

[
  {
    "_id": {
      "data": "90 - 180",
      "status": "Non Performing"
    },
    "value": 1759908
  },
  {
    "_id": {
      "data": "360",
      "status": "Written-off"
    },
    "value": 77146
  },
  {
    "_id": {
      "data": "360",
      "status": "Non Performing"
    },
    "value": 280235
  },
  {
    "_id": {
      "data": "30 - 90",
      "status": "Substandard"
    },
    "value": 863825
  },
  {
    "_id": {
      "data": "30 - 90",
      "status": "Performing"
    },
    "value": 697370
  },
  {
    "_id": {
      "data": "180 - 360",
      "status": "Non Performing"
    },
    "value": 890857
  },
  {
    "_id": {
      "data": "0 - 30",
      "status": "Performing"
    },
    "value": 1028717
  },
  {
    "_id": {
      "data": "0",
      "status": "Performing"
    },
    "value": 1941404
  }
]

I need to filter through the latter code so it ends up like the former code. It is very important that in the data array, we end up with 6 objects to make sure we populate the entire xAxis of Highcharts, hence we see lots of zeros, where no data was supplied.

I really hope this clears things up. Thank you to all those who have help. I apologise for being so vague from the offset.

QUICK NOTE The order of the data array is as follows: 0, 0-30, 30-90, 90-180, 180-360, 360

Richy
  • 177
  • 2
  • 10
  • 3
    Can you post your code, please? – Pankaj Gadge Dec 18 '17 at 19:12
  • Starting point. https://stackoverflow.com/questions/14446511/what-is-the-most-efficient-method-to-groupby-on-a-javascript-array-of-objects – Aaron Dec 18 '17 at 19:16
  • 1
    Can you explain where does `data` come from in the resulting object. – ibrahim mahrir Dec 18 '17 at 19:23
  • Just a note for those that choose to post answers, please take the time to explain your code in order that people, including the OP, can learn from you and understand how to apply your posted solutions to their future problems. – David Thomas Dec 18 '17 at 19:26
  • @Richy see my edited answer, I believe that I explained it very well and got your desired shape. – asosnovsky Dec 18 '17 at 21:02

4 Answers4

2

Use the .reduce and .map methods to get there. You can join the data using the .reduce method, to achieve what you want, and then use the .map method to shape it back to an array.

See below:

const data = [
  {
    "_id": { "data": "90 - 180", "status": "Non Performing" }, "value": 1759908
  },
  {
    "_id": { "data": "360", "status": "Written-off" }, "value": 77146
  },
  {
    "_id": { "data": "360", "status": "Non Performing" }, "value": 280235
  },
  {
    "_id": { "data": "30 - 90", "status": "Substandard" }, "value": 863825
  },
  {
    "_id": { "data": "30 - 90", "status": "Performing" }, "value": 697370
  },
  {
    "_id": { "data": "180 - 360", "status": "Non Performing" }, "value": 890857
  },
  {
    "_id": { "data": "0 - 30", "status": "Performing" }, "value": 1028717
  },
  {
    "_id": { "data": "0", "status": "Performing" }, "value": 1941404
  }
]

const reducedMap = data.reduce((reducedMap, entry) => {
    if(!reducedMap[entry._id.status]) reducedMap[entry._id.status] = [];
    reducedMap[entry._id.status].push(entry.value);
    return reducedMap;
}, {});

const reducedArray = Object.keys(reducedMap).map( key => ({
    name: key,
    data: reducedMap[key]
}))

EDIT

So after reading your comments and other answers I came up with this solution which gets just what you need (please read the comment blocks in it to understand):

// Define How the data is structured
const orderIdx = ["0", "0 - 30", "30 - 90", "90 - 180", "180 - 360", "360"];
const allStatuses = ["Performing", "Non Performing", "Substandard" , "Written-off"];

// Construct the mapping
const mappedIdx = orderIdx.reduce((m, key)=> { return { ...m, [key]: 0 } }, {}) 
    // mappedIdx = { "0": 0, "0-30": 0, "30-90": 0, "90-180": 0, "180-360": 0, "360": 0 }
const mappedInput = allStatuses.reduce((m, name) => { 
    return {...m, [name]: Object.assign({},mappedIdx) };
}, {})
    // mappedInput = { 
    //  "Performing": { "0": 0, "0-30": 0, "30-90": 0, "90-180": 0, "180-360": 0, "360": 0 }, 
    //  "Non Performing": { "0": 0, "0-30": 0, "30-90": 0, "90-180": 0, "180-360": 0, "360": 0 }, 
    //  "Substandard" : { "0": 0, "0-30": 0, "30-90": 0, "90-180": 0, "180-360": 0, "360": 0 }, 
    //  "Written-off": { "0": 0, "0-30": 0, "30-90": 0, "90-180": 0, "180-360": 0, "360": 0 }, 
    // }

// Loop on data
data.forEach( row => {
    mappedInput[row._id.status][row._id.data] = row.value
})

const reducedArray = Object.keys(mappedInput).map( key => ({
    name: key,
    data: Object.keys(mappedInput[key]).map( dataKey => mappedInput[key][dataKey])
}))

// reducedArray = [
//   {
//     name: 'Performing',
//     data: [1941404, 1028717, 697370, 0, 0, 0]
//   },
//   {
//     name: 'Non performing',
//     data: [0, 0, 0, 1759908, 890857, 280235]
//   },
//   {
//     name: 'Substandard',
//     data: [0, 0, 863825, 0, 0, 0]
//   },
//   {
//     name: 'Written-off',
//     data: [0, 0, 0, 0, 0, 77146]
//   }
// ]

You can also simplify this to a short function:

function summarizeData(data, orderIdx, allStatuses){
    const mappedIdx = orderIdx.reduce((m, key)=> { return { ...m, [key]: 0 } }, {}) 
    const mappedInput = allStatuses.reduce((m, name) => { 
        return {...m, [name]: Object.assign({},mappedIdx) };
    }, {})

    data.forEach( row => {
        mappedInput[row._id.status][row._id.data] = row.value
    })

    return Object.keys(mappedInput).map( key => ({
        name: key,
        data: Object.keys(mappedInput[key]).map( dataKey => mappedInput[key][dataKey])
    }))
}
summarizeData(data, ["0", "0 - 30", "30 - 90", "90 - 180", "180 - 360", "360"], ["Performing", "Non Performing", "Substandard" , "Written-off"])

Note that you will need to parametize orderIdx and allStatuses as it is not very clear from just eyeballing it of how they should be. Also, make sure to put a process that validates that these are in fact the only values that these could take.

asosnovsky
  • 2,158
  • 3
  • 24
  • 41
  • I added a few more comments to expand on this. – asosnovsky Dec 18 '17 at 19:25
  • 1
    @David Thomas, i see similar answers from the users with very high reputation, but without downvotes and requests for explanation. So, if we don't ask them (always) for detailed explanation, why we do it when user doesn't have such high rating. ;) P.S. This answer is almost correct, output needs to be tweaked a little. – sinisake Dec 18 '17 at 19:45
  • 2
    @sinisake: I've personally left comments requesting explanations to answers written by people with reputations higher than my own; whether others choose to do so is entirely up to them. But my interpretation of this site is that it's a teaching resource for those that don't (yet or fully) understand the intricacies of a given language or use; so I down-vote answers that post code without explanation, and request explanations in comments. If an adequate - to my personal definition - explanation of the code is added to the answer then I'll retract the down-vote, and possibly up-vote instead. – David Thomas Dec 18 '17 at 19:48
  • 1
    @DavidThomas, ok, legit. I appreciate that attitude. – sinisake Dec 18 '17 at 19:49
0

Use the reduce method, just remember to specify the initial object.

newData = yourData.reduce(function(element, accumulator){
    if (accumulator already has element with name property equalling to element.status) {
        // push value to existing array
    } else {
        // add new element
    }
}, {}); // second argument is just an empty object
entio
  • 3,816
  • 1
  • 24
  • 39
0

In general you want your code to be as concise as possible while still telling a story about the objects it accepts and returns. Something like the following will describe the unformatted object accepted, describe the translations performed on it and implicitly describe the object that the function returns.

function generateFormattedArray(unformattedDataArray) {
  const flattenedDataArray = unformattedDataArray.forEach((obj) => { status: obj['_id'].status, value: obj.value });
  return [
    {
        name: 'Performing',
        data: flattenedDataArray.filter((obj) => obj.status === 'Performing').forEach((obj) => obj.value),
    },
    {
        name: 'Non performing',
        data: flattenedDataArray.filter((obj) => obj.status === 'Non performing').forEach((obj) => obj.value),
    },
    {
        name: 'Substandard',
        data: flattenedDataArray.filter((obj) => obj.status === 'Substandard').forEach((obj) => obj.value),
    },
    {
        name: 'Written-off',
        data: flattenedDataArray.filter((obj) => obj.status === 'Written-off').forEach((obj) => obj.value),
    },
  ];
}

Although to be honest, unless there is a specific use case for this nested formatting... I would probably rather work with the flattenedDataArray created on line 2.

ztech
  • 560
  • 5
  • 13
-1

You can do it like this:

var originalArray = [
  {
    "_id": {
      "data": "90 - 180",
      "status": "Non Performing"
    },
    "value": 1759908
  },
  {
    "_id": {
      "data": "360",
      "status": "Written-off"
    },
    "value": 77146
  },
  {
    "_id": {
      "data": "360",
      "status": "Non Performing"
    },
    "value": 280235
  },
  {
    "_id": {
      "data": "30 - 90",
      "status": "Substandard"
    },
    "value": 863825
  },
  {
    "_id": {
      "data": "30 - 90",
      "status": "Performing"
    },
    "value": 697370
  },
  {
    "_id": {
      "data": "180 - 360",
      "status": "Non Performing"
    },
    "value": 890857
  },
  {
    "_id": {
      "data": "0 - 30",
      "status": "Performing"
    },
    "value": 1028717
  },
  {
    "_id": {
      "data": "0",
      "status": "Performing"
    },
    "value": 1941404
  }
];

var newArray = [
    {
        'name': 'Performing',
    'data': []
    },
    {
        'name': 'Non Performing',
    'data': []
    },
    {
        'name': 'Substandard',
    'data': []
    },
    {
        'name': 'Written-off',
    'data': []
    }  
];

for (var x = 0; x < originalArray.length; x++) {
    if (originalArray[x]._id.status === 'Performing') {
    for (var y = 0; y < newArray.length; y++) {
        if (newArray[y].name === 'Performing') {
        newArray[y].data.push(originalArray[x]._id.data)
      }
    }
  } else if (originalArray[x]._id.status === 'Non Performing') {
    for (var y = 0; y < newArray.length; y++) {
        if (newArray[y].name === 'Non Performing') {
        newArray[y].data.push(originalArray[x]._id.data)
      }
    }
  } else if (originalArray[x]._id.status === 'Substandard') {
    for (var y = 0; y < newArray.length; y++) {
        if (newArray[y].name === 'Substandard') {
        newArray[y].data.push(originalArray[x]._id.data)
      }
    }
  } else if (originalArray[x]._id.status === 'Written-off') {
    for (var y = 0; y < newArray.length; y++) {
        if (newArray[y].name === 'Written-off') {
        newArray[y].data.push(originalArray[x]._id.data)
      }
    }
  }
}

console.log(newArray);

Working example: https://jsfiddle.net/extro3tw/

David Anthony Acosta
  • 4,766
  • 1
  • 19
  • 19