0

I have been working off of this basic pie example to learn about writing pie charts in D3, but while I got the example to work when mimicking their data structure, I wanted to try it out with a JSON data structure that would be more native to how I would structure data for the visualizations. When switching the same data to this new structure I noticed that the black stroke appears and the annotations, but the slices aren't present and the annotation labels are referencing an index and object value.

I believe this is due to the .entries() method that converts it to a key-value data structure, but I'm curious if that is a necessary method to use in order to visualize the data points or if there is a simpler method to utilize the structure I have in place.

Working data structure:

var data = {
        deep: 22.37484390963787,
        light: 62.65183335225337,
        rem: 14.973322738108752
    }

JSON data structure:

var data = [
        { "label": "deep", "value": 22.37484390963787 },
        { "label": "light", "value": 62.65183335225337 },
        { "label": "rem", "value": 14.973322738108752 }
    ]

  var data = [
      { "label": "deep", "value": 22.37484390963787 },
      { "label": "light", "value": 62.65183335225337 },
      { "label": "rem", "value": 14.973322738108752 }
  ]

  // var data = {
  //     deep: 22.37484390963787,
  //     light: 62.65183335225337,
  //     rem: 14.973322738108752
  // }

  console.log(data)
  var width = 480;
  var height = 480;
  var margin = 40;
  var radius = Math.min(width, height) / 2 - margin;

  var svg = d3.select("#my_dataviz")
      .append("svg")
          .attr("width", width)
          .attr("height", height)
      .append("g")
          .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

  var color = d3.scaleOrdinal()
      .domain(data)
      .range(["#98abc5", "#8a89a6", "#7b6888"]);

  var pie = d3.pie()
      .value(function(d) { return d.value; });

  var data_ready = pie(d3.entries(data));

  console.log(data_ready);

  var arcGenerator = d3.arc()
      .innerRadius(0)
      .outerRadius(radius);

  svg.selectAll('viz')
      .data(data_ready)
      .enter()
      .append('path')
      .attr('d', arcGenerator)
      .attr('fill', function(d){ return color(d.data.key)})
      .attr("stroke", "black")
      .style("stroke-width", "2px")
      .style("opacity", 0.7);

  svg.selectAll('viz')
      .data(data_ready)
      .enter()
      .append('text')
      .text(function(d){  return d.data.key + ', ' + d.data.value})
      .attr("transform", function(d) { return "translate(" + arcGenerator.centroid(d) + ")";  })
      .style("text-anchor", "middle")
      .style("font-size", 17);
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>

<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
Jamiec
  • 133,658
  • 13
  • 134
  • 193
cphill
  • 5,596
  • 16
  • 89
  • 182
  • 1
    That's an array of objects, not JSON. See https://stackoverflow.com/questions/3975859/what-are-the-differences-between-json-and-javascript-object – Guy Incognito Aug 04 '20 at 11:44

2 Answers2

1

The other way to achieve this is to not use d3.entries and pass your data directly. A couple of other tweaks are required where you get the color and label text (ie use d.data.label in place of d.data.key).

var data = [
      { "label": "deep", "value": 22.37484390963787 },
      { "label": "light", "value": 62.65183335225337 },
      { "label": "rem", "value": 14.973322738108752 }
  ]

  // var data = {
  //     deep: 22.37484390963787,
  //     light: 62.65183335225337,
  //     rem: 14.973322738108752
  // }
  
  
  console.log(data)
  var width = 480;
  var height = 480;
  var margin = 40;
  var radius = Math.min(width, height) / 2 - margin;

  var svg = d3.select("#my_dataviz")
      .append("svg")
          .attr("width", width)
          .attr("height", height)
      .append("g")
          .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

  var color = d3.scaleOrdinal()
      .domain(data)
      .range(["#98abc5", "#8a89a6", "#7b6888"]);

  var pie = d3.pie()
      .value(function(d) { return d.value; });

  var data_ready = pie(data);

  console.log(data_ready);

  var arcGenerator = d3.arc()
      .innerRadius(0)
      .outerRadius(radius);

  svg.selectAll('viz')
      .data(data_ready)
      .enter()
      .append('path')
      .attr('d', arcGenerator)
      .attr('fill', function(d){ return color(d.data.label)})
      .attr("stroke", "black")
      .style("stroke-width", "2px")
      .style("opacity", 0.7);

  svg.selectAll('viz')
      .data(data_ready)
      .enter()
      .append('text')
      .text(function(d){  return d.data.label + ', ' + d.data.value})
      .attr("transform", function(d) { return "translate(" + arcGenerator.centroid(d) + ")";  })
      .style("text-anchor", "middle")
      .style("font-size", 17);
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>

<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
Jamiec
  • 133,658
  • 13
  • 134
  • 193
0

You can't just change the format of the data without changing something else too. The simplest solution is to reformat your structure into the structure that d3 expected in the first place:

var formatted_data = data.reduce((acc,i) => {acc[i.label] = i.value; return acc;},{});

And then pass that to entries:

var data = [
      { "label": "deep", "value": 22.37484390963787 },
      { "label": "light", "value": 62.65183335225337 },
      { "label": "rem", "value": 14.973322738108752 }
  ]

  // var data = {
  //     deep: 22.37484390963787,
  //     light: 62.65183335225337,
  //     rem: 14.973322738108752
  // }
  
  var formatted_data = data.reduce((acc,i) => {acc[i.label] = i.value; return acc;},{});

  console.log(formatted_data)
  var width = 480;
  var height = 480;
  var margin = 40;
  var radius = Math.min(width, height) / 2 - margin;

  var svg = d3.select("#my_dataviz")
      .append("svg")
          .attr("width", width)
          .attr("height", height)
      .append("g")
          .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

  var color = d3.scaleOrdinal()
      .domain(data)
      .range(["#98abc5", "#8a89a6", "#7b6888"]);

  var pie = d3.pie()
      .value(function(d) { return d.value; });

  var data_ready = pie(d3.entries(formatted_data));

  console.log(data_ready);

  var arcGenerator = d3.arc()
      .innerRadius(0)
      .outerRadius(radius);

  svg.selectAll('viz')
      .data(data_ready)
      .enter()
      .append('path')
      .attr('d', arcGenerator)
      .attr('fill', function(d){ return color(d.data.key)})
      .attr("stroke", "black")
      .style("stroke-width", "2px")
      .style("opacity", 0.7);

  svg.selectAll('viz')
      .data(data_ready)
      .enter()
      .append('text')
      .text(function(d){  return d.data.key + ', ' + d.data.value})
      .attr("transform", function(d) { return "translate(" + arcGenerator.centroid(d) + ")";  })
      .style("text-anchor", "middle")
      .style("font-size", 17);
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>

<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • This makes sense, but based on your answer are you saying that you should always shape data structures to D3? – cphill Aug 04 '20 at 11:58
  • I'm not that familiar with d3. You have 3 options 1) Always shape your data how d3 expects 2) shape your data how you like and remap it it how d3 expects (this answer) 3) determine how to instruct d3 to understand your format - I don't know if this option is possible without delving into the docs for d3 – Jamiec Aug 04 '20 at 12:00
  • @cphill see my other answer for an alternative to using `reduce` (ie, option 3 above) – Jamiec Aug 04 '20 at 12:09