Search code examples
javascriptreactjsd3.jsbar-chartstacked-bar-chart

How to sort D3 Stacked Bar Chart over 2 object properties


I am fairly new to using D3 and have created a stacked horizontal bar chart based off of https://observablehq.com/@d3/stacked-horizontal-bar-chart.

This is my chart:

Graph

I am currently able to sort the chart by ascending and descending. What I need to do now is to first sort the graph alphabetically by the y-axis label, then ascendingly by the value on the x-axis and I unsure how to do this.

The chart needs to look something like this: Graph2

Where the chart is sorted first alphabetically by the scale name on the y-axis, then descending or ascendingly by the value on the x-axis.

This is the code I use to generate the chart and sort it ascendingly/descendingly:

chartGenerate = stackedBarChart(pandas, 
  {
    x: d => d.value,
    y: d => d.scale,
    z: d => d.key,
    yDomain: d3.groupSort(pandas, 
      D => d3.sum(D, d => (order === "ascending" ? -d.value : d.value)), 
      d => d.scale),
    zDomain: key,
  }
)

Where value is the value of one of the bars within the big bar on the x-axis, scale is the label on the y-axis, and key is the array:

var key = ["punnets_underweight", "punnets_overweight", "punnets_on_weight"]

Order is a state that changes between values 'ascending' and 'descending' when a button is pressed.

Is there anyway to modify the groupSort function so that it can sort over 2 object properties? I might have left out a lot of information out because I wasn't sure how to phrase everything as a whole, but this is the core of my issue.


Solution

  • You wrote:

    What I need to do now is to first sort the graph alphabetically by the y-axis label, then ascendingly by the value on the x-axis

    If I understand your example correctly, though, you don't want to sort on the complete y-label (for that would fully sort the yDomain) but just on the first portion - perhaps just up to Line ${n} or something. You then want to sort the within those groups based on size?

    I'm sure this could be done with d3.groupSort, using a more general comparator as the second argument. Sometimes, I find it difficult to find that special incantation for more specialized, higher level functions, though. This can definitely be done with d3.group and then d3.sort.

    Since I don't have access to your data, I forked the Observable notebook you referenced and built an example there. In the example, I broke the states up rather arbitrarily into the first half of the alphabet and then the second. Then, I sorted by population within those two groups. The code there looks like so:

    // An Array of objects with {state, population} keys:
    state_populations = Array.from(
      d3.group(stateages, (o) => o.state).values()
    ).map((a) => ({
      state: a[0].state,
      population: d3.sum(a, (o) => o.population)
    }));
    
    /// The yDomain
    yDomain = d3
      .sort(
        state_populations,
        (s) => (s.state < "M" ? 1 : 0),
        (s) => s.population
      )
      .map((s) => s.state)
    

    You can see more details in the notebook. Here's the top portion of the resulting image:

    enter image description here