简体   繁体   中英

Data joins in D3 sunburst chart

I'm trying to create the chart below:

在此处输入图片说明

Each 'fin' represents a single study, with each line being a brand found in that study.

I received the following code in order to add transitions (see this question ), but realized that is was built without using a data join (making updating the data difficult).

Array.prototype.max = function() { return Math.max.apply(null, this); };

Array.prototype.min = function() { return Math.min.apply(null, this); };

Number.prototype.map = function (in_min, in_max, out_min, out_max) {
  return (this - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

var colors = {
  'rank1' : '#3FA548',
  'rank2' : '#00B09E',
  'rank3' : '#8971B3',
  'rank4' : '#DFC423',
  'rank5' : '#E74341'
};

var angleSize;

d3.text('text.csv', ready);

var study = null;

function ready(err, text) {
  if (err) console.warn('Error', err);

  var csv = d3.csv.parse(text);

  function selectStudy(d) {
    study = $(this).attr('study');
    updateChart(study);
  } 

  function updateChart(study) {
    //Remove and replace with transition
    d3.select('#chart svg')
      .remove();

    if (study) {
      csv = csv.filter(function(d) { 
         return d.study_name === study; 
      });
    } else {
      csv = d3.csv.parse(text);
    }

    var svg = d3.select('#chart')
                .append('svg')
                .attr({
                  'width' : 1000,
                  'height' : 1000,
                  'id' : 'container'
                })
                .append('g')
                .attr('transform', 'translate(500, 500)');

    angleSize = (2 * Math.PI) / csv.length

    var group1Array = [],
        group2Array = [],
        group3Array = [],
        group4Array = [];

    for ( var i = 0; i < csv.length; i++ ) {
      group1Array[i] = Number(csv[i].group1_score);
      group2Array[i] = Number(csv[i].group2_score);
      group3Array[i] = Number(csv[i].group3_score);
      group4Array[i] = Number(csv[i].group4_score);
    }

    //Loop through every row of data
    for ( var i = 0; i < csv.length; i++ ) {
      var startRadius = 140,
          endRadius = startRadius;

      for ( var x = 0; x < 4; x++ ) {
        //Increment endRadius based on the data
        if ( x == 0 ) {
          endRadius += Number(csv[i].group1_score) * 4;
        } else if ( x == 1 ) {
          endRadius += Number(csv[i].group2_score) * 4;
        } else if ( x == 2 ) {
          endRadius += Number(csv[i].group3_score) * 4;
        } else {
          endRadius += Number(csv[i].group4_score) * 4;
        }

        //Create svg element (arc) with the calculated values
        var arc = d3.svg.arc()
                    .innerRadius(startRadius)
                    .outerRadius(endRadius)
                    .startAngle(angleSize * i)
                    .endAngle(angleSize * (i + 1));

        var className,
            ratingClass,
            studyName;

        if ( x == 0 ) {
          className = csv[i].group1_class;
          ratingClass = 'Group1';
        } else if ( x == 1 ) {
          className = csv[i].group2_class;
          ratingClass = 'Group2';
        } else if ( x == 2 ) {
          className = csv[i].group3_class;
          ratingClass = 'Group3';
        } else {
          className = csv[i].group4_class;
          ratingClass = 'Group4';
        }

        studyName = csv[i].study_name;

        var path = svg.append('path')
                      .attr({
                        'class' : className,
                        'd' : arc,
                        'company' : csv[i].brand_name,
                        'cat' : ratingClass,
                        'study' : studyName,
                        'startradius' : startRadius,
                        'endradius' : endRadius,
                        'startangle' : angleSize * i,
                        'endangle' : angleSize * (i + 1),
                        'companyid' : i
                      })
                     .on('click', selectStudy);

        startRadius = endRadius + 0.3;
      }
    }
  }
}

Below is my attempt at a refactored version that has a data join, but I have so far been unable to get it working (I get the following error: <path> attribute d: Expected moveto path command ('M' or 'm'), "function n(){var…" ).

var colors = {
  'rank1' : '#3FA548',
  'rank2' : '#00B09E',
  'rank3' : '#8971B3',
  'rank4' : '#DFC423',
  'rank5' : '#E74341'
};

var $container = $('.chart'),
    m = 40,
    width = $container.width() - m,
    height = $container.height() - m,
    r = Math.min(width, height) / 2;

var angleSize,
    study = null;

d3.csv('text.csv', ready);

function ready(err, data) {
  if (err) console.warn('Error', err);

  angleSize = (2 * Math.PI) / data.length;

  var dataByStudy = d3.nest()
                      .key(function(d) { return d.study_name; })
                      .entries(data); 

  var svg = d3.select('.chart')
              .append('svg')
              .attr({
                'width' : (r + m) * 2,
                'height' : (r + m) * 2,
                'class' : 'container'
              })
              .append('g')
              .attr('transform', 'translate(' + (width / 4) + ', ' + (height / 2) + ' )'); 

  var slice = svg.selectAll('.slice')
                 .data(dataByStudy)
                 .enter()
                 .append('g')
                 .attr('class', 'slice');

  var startRadius = 140,
      endRadius = startRadius;

  for ( var x = 0; x < 4; x++ ) {
    var path = slice.append('path')
                    .attr({
                      'd' : function(d, i) {
                        var arc = d3.svg.arc()
                                    .innerRadius(startRadius)
                                    .outerRadius(endRadius)
                                    .startAngle(angleSize * i)
                                    .endAngle(angleSize * (i + 1));

                        return arc;
                       },
                      'class' : function(d, i) {
                        if ( x == 0 ) {
                          return d.values[i].group1_class;
                        } else if ( x == 1 ) {
                          return d.values[i].group2_class;
                        } else if ( x == 2 ) {
                          return d.values[i].group3_class;
                        } else {
                          return d.values[i].group4_class;
                        }
                      },
                      'company' : function(d, i) {
                        return d.values[i].brand_name;
                      },
                      'cat' : function(d, i) {
                        if ( x == 0 ) {
                          return 'Mobile';
                        } else if ( x == 1 ) {
                          return 'Social';
                        } else if ( x == 2 ) {
                          return 'Digital Marketing';
                        } else {
                          return 'Site';
                        }
                      },
                      'study' : function(d, i) {
                        return d.values[i].study_name;
                      },
                      'endradius' : function(d, i) {
                        if ( x == 0 ) {
                          return endRadius += Number(d.values[i].group1_score) * 5;
                        } else if ( x == 1 ) {
                          return endRadius += Number(d.values[i].group2_score) * 5;
                        } else if ( x == 2 ) {
                          return endRadius += Number(d.values[i].group3_score) * 5;
                        } else {
                          return endRadius += Number(d.values[i].group4_score) * 5;
                        }
                      },
                      'startradius' : startRadius,
                      'startangle' : function(d, i) { 
                        return angleSize * i;
                      },
                      'endangle' : function(d, i) {
                        return angleSize * (i + 1);
                      },
                      'companyid' : function(d, i) {
                        return d.values[i].brand_id;
                      }
                    });

    startRadius = endRadius + 0.3
  }
}

Does anyone have any idea how I would be able to refactor Code Block B to work like Code Block A? Thank you.

In Code Block A, a for loop iterates over every row in the csv, and inside that another for loop iterates four times (one time for each of the four data groups). Moving all this functionality around (and especially declaring the arc function) was the most confusing part.

Researching further, I found that you can create the arc function: var arc = d3.svg.arc(); and pass it arguments once innerRadius, startAngle, etc. were processed.

slice.selectAll('path')
   .attr({
     'd' : function(d) {
        return arc({
          innerRadius : //value,
          outerRadius : //value,
          startAngle : //value,
          endAngle : //value
        })
      }
   });

Full code is below. Thanks, all.

Array.prototype.max = function() { return Math.max.apply(null, this); };

Array.prototype.min = function() { return Math.min.apply(null, this); };

Number.prototype.map = function (in_min, in_max, out_min, out_max) {
  return (this - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

var colors = {
  'rank1' : '#3FA548',
  'rank2' : '#00B09E',
  'rank3' : '#8971B3',
  'rank4' : '#DFC423',
  'rank5' : '#E74341'
};

var $container = $('.chart'),
    m = 40,
    width = $container.width() - m,
    height = $container.height() - m,
    r = Math.min(width, height) / 2;

var angleSize,
    study = null;

var arc = d3.svg.arc();

d3.csv('text.csv', ready);

function ready(err, data) {
  if (err) console.warn('Error', err);

  var svg = d3.select('.chart')
              .append('svg')
              .attr({
                'width' : (r + m) * 2,
                'height' : (r + m) * 2,
                'class' : 'container'
              })
              .append('g')
              .attr('transform', 'translate(' + (width / 4) + ', ' + (height / 2) + ' )'); 

  angleSize = (2 * Math.PI) / data.length; 

  var slice = svg.selectAll('.slice')
                 .data(theData)
                 .enter()
                 .append('g')
                 .attr('class', 'slice');

  var startRadArr = [],
      endRadArr = [];

  for ( var i = 0; i < data.length; i++ ) {
    var startRadius = 140,
        endRadius = startRadius;

    for ( var x = 0; x < 4; x++ ) {
      startRadArr.push(startRadius);

      if ( x == 0 ) {
        endRadius += Number(data[i].group1_score) * 5;
      } else if ( x == 1 ) {
        endRadius += Number(data[i].group2_score) * 5;
      } else if ( x == 2 ) {
        endRadius += Number(data[i].group3_score) * 5;
      } else {
        endRadius += Number(data[i].group4_score) * 5;
      }

      endRadArr.push(endRadius);

      startRadius = endRadius + 0.3;
    }
  }

  var startRadGroup = [],
      endRadGroup = [];

  for (i = 0; i < startRadArr.length; i += 4) { 
    startRadGroup.push(startRadArr.slice(i, i + 4)); 
  }

  for (i = 0; i < endRadArr.length; i += 4) { 
    endRadGroup.push(endRadArr.slice(i, i + 4)); 
  }

  for ( var x = 0; x < 4; x++ ) {
    var path = slice.append('path')
                    .attr({
                      'class' : function(d, i) {
                        if ( x == 0 ) {
                          return d.group1_class;
                        } else if ( x == 1 ) {
                          return d.group2_class;
                        } else if ( x == 2 ) {
                          return d.group3_class;
                        } else {
                          return d.group4_class;
                        }
                      },
                      'company' : function(d, i) { 
                        return d.brand_name; 
                      },
                      'cat' : function(d, i) {
                        if ( x == 0 ) {
                          return 'Mobile';
                        } else if ( x == 1 ) {
                          return 'Social';
                        } else if ( x == 2 ) {
                          return 'Digital Marketing';
                        } else {
                          return 'Site';
                        }
                      },
                      'study' : function(d, i) { 
                        return d.study_name; 
                      },
                      'companyid' : function(d, i) { 
                        return d.brand_id; 
                      },
                      'startradius' : function(d, i) {
                        return startRadGroup[i][x];
                      },
                      'endradius' : function(d, i) {
                        return endRadGroup[i][x];
                      },
                      'startangle' : function(d, i) {
                        return angleSize * i;
                      },
                      'endangle' : function(d, i) {
                        return angleSize * (i + 1);
                      }
                    });
  }

  slice.selectAll('path')
       .attr({
         'd' : function(d) {
            return arc({
              innerRadius : +d3.select(this)[0][0].attributes.startradius.nodeValue,
              outerRadius : +d3.select(this)[0][0].attributes.endradius.nodeValue,
              startAngle : +d3.select(this)[0][0].attributes.startangle.nodeValue,
              endAngle : +d3.select(this)[0][0].attributes.endangle.nodeValue
            })
          }
       });
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM