Search code examples
javascriptarraystypescriptecmascript-6apexcharts

Javascript trying to sort array of objects based on inner object array values


I'm not sure if there is an easy solution to this problem, but I want to sort an array of objects, that have a nested array of objects, based on this array of objects y value and the parent x key. The difficult part is that both of the arrays get combined together to represent some data.

Here is the data structure

series: [
  {
    name: 'testData',
    data: [
      {x: abc, y: 488},
      {x: def, y: 65},
      {x: ghi, y: 380},
      {x: jkl, y: 190}
    ]
  },
  {
    name: 'testData2',
    data: [
      {x: abc, y: 241},
      {x: def, y: 232},
      {x: ghi, y: 72},
      {x: jkl, y: 724}
    ]
  }
]

At this stage, there is only the two parent objects, so I need to compare each object and reorder them, the end result would look like

[
  {
    name: 'testData',
    data: [
      {x: jkl, y: 190}, // total for key jkl y combined values is 914
      {x: abc, y: 488}, // total for key abc y combined values is 729
      {x: ghi, y: 380}, // total for key ghi y combined values is 452
      {x: def, y: 65}, // total for key def y combined values is 297
    ]
  },
  {
    name: 'testData2',
    data: [
      {x: jkl, y: 724}, // total for key jkl y combined values is 914
      {x: abc, y: 241}, // total for key abc y combined values is 729
      {x: ghi, y: 72}, // total for key ghi y combined values is 452
      {x: def, y: 232} // total for key def y combined values is 297
    ]
  }
]

I've tried a couple of ideas but can't seem to find a solution to my problem if you've seen a nice solution elsewhere please feel free to link it.

Here is something I tried

series.forEach(obj => {
  obj.data.sort((a, b) => {
    return b.y - a.y;
  });
});

But it just ends up moving the highest values in each array to the top, not the one which should match the other array. It's hard to give context but I'm using Apexcharts which uses the data to make a graph like this:

    | testData | testData2
---------------------------
abc  ------ GraphBar ------ 
def  ------ AnotherBar ----
ghi  ------ thirdBar ------
jkl  ------ fourthBar -----
---------------------------

So I need to figure out how to sort based on the bar's cumulative values, based on both the two objects data array y values.

This is what it looks like presorting

Here is how the data looks pre sorting

And after, as you can see its modifying the data

And after my sorting


Solution

  • Assuming that we name the variable series as in

    const series = [
        {
            name: 'testData',
            data: [
                { x: 'abc', y: 488 },
                { x: 'def', y: 65 },
                { x: 'ghi', y: 380 },
                { x: 'jkl', y: 190 }
            ]
        },
        {
            name: 'testData2',
            data: [
                { x: 'abc', y: 241 },
                { x: 'def', y: 232 },
                { x: 'ghi', y: 72 },
                { x: 'jkl', y: 724 }
            ]
        }
    ]
    

    (note that I've made the x properties have string values instead of bare identifiers like abc; I assume that's what you want)

    We can possibly tackle your sorting task like this. First, let's get an object whose keys are the x values and whose values are the sums of the y values:

    const sums = series.reduce(
        (s, r) => (r.data.forEach(xy => s[xy.x] = (s[xy.x] || 0) + xy.y), s),
        {} as Record<string, number>
    );
    
    console.log(sums); // { abc: 729, def: 297, ghi: 452, jkl: 914 }
    

    Note that I'm using the comma operator inside the reduce where the first part does the mutation and the last part hands back the object to be mutated. It's not necessarily any better than using a for loop, other than being terse.

    Now we can sort the data in series by using sums:

    series.forEach(v => v.data.sort((a, b) => sums[b.x] - sums[a.x]));
    
    console.log(JSON.stringify(series,null,2));
    /*
    [
      {
        "name": "testData",
        "data": [
          {
            "x": "jkl",
            "y": 190
          },
          {
            "x": "abc",
            "y": 488
          },
          {
            "x": "ghi",
            "y": 380
          },
          {
            "x": "def",
            "y": 65
          }
        ]
      },
      {
        "name": "testData2",
        "data": [
          {
            "x": "jkl",
            "y": 724
          },
          {
            "x": "abc",
            "y": 241
          },
          {
            "x": "ghi",
            "y": 72
          },
          {
            "x": "def",
            "y": 232
          }
        ]
      }
    ]
    */
    

    Looks reasonable to me. Okay, hope that helps; good luck!

    Link to code