2

I have found a d3v3 stacked bar chart example that I would like to use because it uses json data.

There is also a d3v4 canonical bar chart example that loads and uses a csv.

I want to make a d3v4 stacked bar chart but I want to use json data to create it rather than load from a csv. I am not sure how to upgrade the v3 version or to modify the v4 version to accomplish this.

This is my data structure:

[{
     "hospitalName": "hospital1",
     "category": "Injury & Poisoning",        
     "Females": "0",
     "Males": "4",
     "Unknown": "0",
     "count": "4"
},
{
    "hospitalName": "hospital1",
    "category": "Symptoms, Signs, & Ill-Defined Conditions",
    "Females": "1",
    "Males": "1",
    "Unknown": "0",
    "count": "2"
},
{
    "hospitalName": "hospital2",
    "category": "Mental Disorders",
    "Females": "0",
    "Males": "1",
    "Unknown": "0",
    "count": "1"
}]

How do I use this data within a d3v4 stacked bar chart given the two examples?

7
  • This is the canonical d3v4 stacked bar chart. While it uses d3.csv to load the data rather than using inline data, this can be dropped and you can use json with the same format as in your example (*This answer may help if you are having difficulty adopting the canonical example to use inline json *). Commented Jul 27, 2018 at 22:30
  • Well, what if I was using an external JSON file instead?
    – LSRain
    Commented Jul 27, 2018 at 22:36
  • Then you could use d3.json - sorry, I had thought you meant inline as in the example. Using d3.json will load the json as is - this might require a couple changes from the canonical. Commented Jul 27, 2018 at 23:13
  • I see. Thank you for your response. If you don't mind, I have a current dataset and it contains multiple hospitals. As you noticed above, there are two hospitals with the same name, would this work in creating the chart or do I have to change my dataset structure?
    – LSRain
    Commented Jul 27, 2018 at 23:15
  • Thanks for the data, that will make things easier and clearer for an answerer. Can I clarify the question as "how do I adopt that v4 canonical for use with an external json file (given data with the shown structure)?" I'm out the door at the moment, but if this is the question will answer if no one else does in the meantime. Commented Jul 27, 2018 at 23:34

1 Answer 1

2

Given the choice between upgrading a v3 example that suits your data source type and modifying the v4 example to take json rather than csv data, the choice to convert the existing canonical v4 example should win.

d3.csv converts csv files to json. d3.csv creates json that looks just like your json from a source csv that has headers equal to your data items' properties. So both examples essentially use the same data format and structure. This is why using the d3v4 example is more straightforward.

To use your json data rather than the csv data in the v4 example you'll need to make two changes:

  1. Getting the right column data:

The columns from the canonical uses var keys = data.columns.slice(1); to get which columns in the csv data should be plotted with rectangles. columns is an attribute added to the data array, by d3.csv, that specifies the column headers. The removed value isn't plotted with rectangles, but idenifies the stack, it could be a stack label and used for x axis placement. As the columns property is added by d3.csv we need a slightly different approach.

In your case it looks like we want to get males, females, unknown from the data and your structure for each group looks like:

{
     "hospitalName": "hospital1",
     "category": "Injury & Poisoning",        
     "Females": "0",
     "Males": "4",
     "Unknown": "0",
     "count": "4"
}

So we can get the keys/properties (that will be plotted with rectangles) with a slight modification:

var columns = d3.keys(data[0]);  // get the properties of the first item in the data array
var keys = columns.slice(2,5); // extract keys with index 2,3,4. These will be the properties that are represented by rectangles in the chart.
  1. Accounting for multiple groups/stacks with the same name

As the scale for most examples will use the group name, these won't work. Instead we need something unique for each group, an index can work just fine:

x.domain(data.map(function(d,i) { return i; }));

You'll need to format the ticks a bit so you don't get the index as the label, lets say:

d3.axisBottom(x).tickFormat(function(d,i) { return data[i].hospitalName })

It should be easy enough to add the category to the tick with that.

  1. Modify the total attribute

Yes I said two steps, this is too short to warrant a whole bullet, but lists are better with three items. The original canonical uses d.total, your data uses d.count, this is used to determine the y scale's domain.

Altogether:

<!DOCTYPE html>
<style>

.axis .domain {
  display: none;
}

</style>
<svg width="600" height="200"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>

var svg = d3.select("svg"),
    margin = {top: 20, right: 20, bottom: 30, left: 40},
    width = +svg.attr("width") - margin.left - margin.right,
    height = +svg.attr("height") - margin.top - margin.bottom,
    g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var x = d3.scaleBand()
    .rangeRound([0, width])
    .paddingInner(0.05)
    .align(0.1);

var y = d3.scaleLinear()
    .rangeRound([height, 0]);

var z = d3.scaleOrdinal()
    .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);

  var data = [{
     "hospitalName": "hospital1",
     "category": "Injury & Poisoning",        
     "Females": "0",
     "Males": "4",
     "Unknown": "0",
     "count": "4"
},
{
    "hospitalName": "hospital1",
    "category": "Symptoms, Signs, & Ill-Defined Conditions",
    "Females": "1",
    "Males": "1",
    "Unknown": "0",
    "count": "2"
},
{
    "hospitalName": "hospital2",
    "category": "Mental Disorders",
    "Females": "0",
    "Males": "1",
    "Unknown": "0",
    "count": "1"
}]
	
	var columns = d3.keys(data[0]);

  var keys = columns.slice(2,5);

  data.sort(function(a, b) { return b.total - a.total; });
  x.domain(data.map(function(d,i) { return i; }));
  y.domain([0, d3.max(data, function(d) { return d.count; })]).nice();
  z.domain(keys);

  g.append("g")
    .selectAll("g")
    .data(d3.stack().keys(keys)(data))
    .enter().append("g")
      .attr("fill", function(d) { return z(d.key); })
    .selectAll("rect")
    .data(function(d) { return d; })
    .enter().append("rect")
      .attr("x", function(d,i) { return x(i); })
      .attr("y", function(d) { return y(d[1]); })
      .attr("height", function(d) { return y(d[0]) - y(d[1]); })
      .attr("width", x.bandwidth());

  g.append("g")
      .attr("class", "axis")
      .attr("transform", "translate(0," + height + ")")
      .call(d3.axisBottom(x).tickFormat(function(d,i) { return data[i].hospitalName}));

  g.append("g")
      .attr("class", "axis")
      .call(d3.axisLeft(y).ticks(null, "s"))
    .append("text")
      .attr("x", 2)
      .attr("y", y(y.ticks().pop()) + 0.5)
      .attr("dy", "0.32em")
      .attr("fill", "#000")
      .attr("font-weight", "bold")
      .attr("text-anchor", "start")
      .text("Population");

  var legend = g.append("g")
      .attr("font-family", "sans-serif")
      .attr("font-size", 10)
      .attr("text-anchor", "end")
    .selectAll("g")
    .data(keys.slice().reverse())
    .enter().append("g")
      .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });

  legend.append("rect")
      .attr("x", width - 19)
      .attr("width", 19)
      .attr("height", 19)
      .attr("fill", z);

  legend.append("text")
      .attr("x", width - 24)
      .attr("y", 9.5)
      .attr("dy", "0.32em")
      .text(function(d) { return d; });


</script>

If you want to use d3.json, then you can use:

d3.json("json.json", function(error,data) {
  if(error) throw error;

  // Parts that use the data here.

})
4
  • Hi, @Andrew Reid. Thanks for posting. However, in running the code snippet, it creates two bars of the same hospital name. If I do want to create only one bar of each unique hospital, how would I be able to execute it? I created the dataset by using a SQL query on a database management program.
    – LSRain
    Commented Jul 29, 2018 at 8:33
  • @LSRain my apologies, I misread the comment for a moment, then undid my edit to the question thinking I misread the question and subsequent clarification completely. I've undone that, It's very late and I'm half asleep. An issue with how to structure the data didn't appear to be part of the question, but it should also be a separate question. The v4 stack-json issue is pretty separate from the issue of how to organize your data to show certain data in each bar. If asking a new question on it you should indicate what bars need to be shown in the final version for each stack Commented Jul 29, 2018 at 9:00
  • Also it may be helpful to show the data for two full stacks, with the indication of what should be grouped in the data (for example are all males grouped regardless of category or is the female/male/unknown break down shown for each category, and are categories consistent for every hospital). Commented Jul 29, 2018 at 9:07
  • Alright. I will ask a separate question about the data structure for the D3 v4 Stacked Bar Chart on SO.
    – LSRain
    Commented Jul 29, 2018 at 17:53

Not the answer you're looking for? Browse other questions tagged or ask your own question.