Search code examples
jsond3.jsnestedtime-seriesstacked-chart

Complex JSON to D3js stacked time series


I have to create a visualisation with D3 like in the figure bellow, time-series, stacked bars:enter image description here

The dataset comes from a consultant in this format, nested arrays of json objects:

[{
        "period": "6/2017",
        "data": [
            {
                "partner": "TR",
                "val": 5201888581
            },
            {
                "partner": "CH",
                "val": 8509470105
            },
            {
                "partner": "RU",
                "val": 10677690328
            },
            {
                "partner": "GB",
                "val": 16086915825
            },
            {
                "partner": "US",
                "val": 17817589838
            },
            {
                "partner": "CN",
                "val": 26120939253
            },
            {
                "partner": "TOTAL",
                "val": 145385348496
            }
        ]
    },
    {
        "period": "7/2017",
        "data": [
            {
                "partner": "TR",
                "val": 4832746886
            },
            {
                "partner": "CH",
                "val": 8194483975
            },
            {
                "partner": "RU",
                "val": 10082530447
            },
            {
                "partner": "US",
                "val": 15251181551
            },
            {
                "partner": "GB",
                "val": 15515343080
            },
            {
                "partner": "CN",
                "val": 27480148190
            },
            {
                "partner": "TOTAL",
                "val": 142118881451
            }
        ]
    },
    {
        "period": "8/2017",
        "data": [
            {
                "partner": "TR",
                "val": 4827335758
            },
            {
                "partner": "CH",
                "val": 7087004314
            },
            {
                "partner": "RU",
                "val": 10372167568
            },
            {
                "partner": "GB",
                "val": 14555013893
            },
            {
                "partner": "US",
                "val": 16838219917
            },
            {
                "partner": "CN",
                "val": 27876046083
            },
            {
                "partner": "TOTAL",
                "val": 143363806063
            }
        ]
    }
...
]

More precisely, each object contains the period and an sub-array of objects, country code and value:

{ 
  period,
  data: [{country,value},{country,value},{country,value}...]
}

The data arrays contain different number of objects (country records), from 3 to 10 and different countries (they can be any of the UN countries).

I tried to use the standard layouts in D3 (v5) such as "stack" but, as you see, my data does not match the required data format in d3 layout, i.e. tabular data like:

time  field1  field2  field3
t1     v11     v12     v13
t2     v21     v22     v23
...

I don't know how to manage this with my data, please help. Tx.

LE: I tried also with chart.js but failed due to similar issues.


Solution

  • As the number of countries varies, you could create your own function to append the countries' rect elements. The example use d3.selection.each to call the new function, and within this function, a new data array is created with the y offsets for each rect.

        let data = [{
            "period": "6/2017",
            "data": [
                {
                    "partner": "TR",
                    "val": 5201888581
                },
                {
                    "partner": "CH",
                    "val": 8509470105
                },
                {
                    "partner": "RU",
                    "val": 10677690328
                },
                {
                    "partner": "GB",
                    "val": 16086915825
                },
                {
                    "partner": "US",
                    "val": 17817589838
                },
                {
                    "partner": "CN",
                    "val": 26120939253
                },
                {
                    "partner": "TOTAL",
                    "val": 145385348496
                }
            ]
        },
        {
            "period": "7/2017",
            "data": [
                {
                    "partner": "TR",
                    "val": 4832746886
                },
                {
                    "partner": "CH",
                    "val": 8194483975
                },
                {
                    "partner": "RU",
                    "val": 10082530447
                },
                {
                    "partner": "US",
                    "val": 15251181551
                },
                {
                    "partner": "GB",
                    "val": 15515343080
                },
                {
                    "partner": "CN",
                    "val": 27480148190
                },
                {
                    "partner": "TOTAL",
                    "val": 142118881451
                }
            ]
        },
        {
            "period": "8/2017",
            "data": [
                {
                    "partner": "TR",
                    "val": 4827335758
                },
                {
                    "partner": "CH",
                    "val": 7087004314
                },
                {
                    "partner": "RU",
                    "val": 10372167568
                },
                {
                    "partner": "GB",
                    "val": 14555013893
                },
                {
                    "partner": "US",
                    "val": 16838219917
                },
                {
                    "partner": "CN",
                    "val": 27876046083
                },
                {
                    "partner": "TOTAL",
                    "val": 143363806063
                }
            ]
        }]
        
        let width = 500
        let height = 500
        let margin = 50
        
        let x = d3.scaleBand()
        	.domain(["6/2017","7/2017","8/2017"])
        	.range([0, width])
        	.padding(0.3)
        
        let y = d3.scaleLinear()
        	.domain([0, 145385348496])
        	.range([height, 0])
          
        var svg = d3.select("body").append("svg")
          .attr("width", width + margin + margin)
          .attr("height", height + margin + margin)
    
        var g = svg.append("g")
          .attr("transform", "translate(" + margin + ", " + margin + ")")
        
        var periods = g.selectAll("g")
        	.data(data)
        	.enter()
        	.append("g")
        	.attr("class", "period")
        	.attr("transform", function(d){
            return "translate(" + x(d.period) + ", " + 0 + ")"
          })
        	.each(appendCountries)
        
        function appendCountries(period, i){
          
          let countries = []
          let offset = 0
          let colour = d3.scaleOrdinal(d3.schemeAccent);
          
          period.data.forEach(function(c){
            if (c.partner != "TOTAL") { //assuming that total should not be included
              
              offset = offset + c.val
              
              let obj = {}
              obj.y = offset
              obj.val = c.val
              obj.partner = c.partner
              countries.push(obj)
              
            }
          })
          
          let rects = d3.select(this).selectAll("rect")
          	.data(countries)
          	.enter()
          	.append("rect")
          	.attr("width", x.bandwidth)
          	.attr("x", 0)
          	.attr("y", d => y(d.y))
    				.attr("height", d => height - y(d.val))
          	.style("fill", d => colour(d.partner))
          	.style("stroke", "white")
        }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>