1

I'm working on a d3js donut chart and I'm trying to feed data in from a multidimensional array: fiddle

Output of topHoldersArray:

{
"1":{"address":"0xd35a2d8c651f3eba4f0a044db961b5b0ccf68a2d","amount":"309953166.54621424","percent":"30.9953%"},
"2":{"address":"0xe17c20292b2f1b0ff887dc32a73c259fae25f03b","amount":"200000001","percent":"20.0000%"},
"3":{"address":"0x0000000000000000000000000000000000000000","amount":"129336426","percent":"12.9336%"}
}

With this array I get the error:

Uncaught TypeError: Cannot read property 'startAngle' of undefined

How can I feed this array into the graph? It seems the issue is that it's a multidimensional array but I"m not sure how to access it for the data points

Here's a snippet illustrating the problem:

var topHoldersArray = [
{
  "1":{"address":"0xd35a2d8c651f3eba4f0a044db961b5b0ccf68a2d","amount":"309953166","percent":"30.9953%"},
  "2":{"address":"0xe17c20292b2f1b0ff887dc32a73c259fae25f03b","amount":"200000001","percent":"20.0000%"},
  "3":{"address":"0x0000000000000000000000000000000000000000","amount":"129336426","percent":"12.9336%"}
}
];

var data = topHoldersArray;
		  
var text = "";

var width = 260;
var height = 260;
var thickness = 40;
var duration = 750;

var radius = Math.min(width, height) / 2;
var color = d3.scaleOrdinal(d3.schemeCategory20);

var svg = d3.select("#topHoldersChart")
.append('svg')
.attr('class', 'pie')
.attr('width', width)
.attr('height', height);

var g = svg.append('g')
.attr('transform', 'translate(' + (width/2) + ',' + (height/2) + ')');

var arc = d3.arc()
.innerRadius(radius - thickness)
.outerRadius(radius);

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

var path = g.selectAll('path')
.data(pie(data))
.enter()
.append("g")
.on("mouseover", function(d) {
      let g = d3.select(this)
        .style("cursor", "pointer")
        .style("fill", "black")
        .append("g")
        .attr("class", "text-group");
 
      g.append("text")
        .attr("class", "name-text")
        .text(`${d.data.address}`)
        .attr('text-anchor', 'middle')
        .attr('dy', '-1.2em');
  
      g.append("text")
        .attr("class", "value-text")
        .text(`${d.data.amount}`)
        .attr('text-anchor', 'middle')
        .attr('dy', '.6em');
    })
  .on("mouseout", function(d) {
      d3.select(this)
        .style("cursor", "none")  
        .style("fill", color(this._current))
        .select(".text-group").remove();
    })
  .append('path')
  .attr('d', arc)
  .attr('fill', (d,i) => color(i))
  .on("mouseover", function(d) {
      d3.select(this)     
        .style("cursor", "pointer")
        .style("fill", "black");
    })
  .on("mouseout", function(d) {
      d3.select(this)
        .style("cursor", "none")  
        .style("fill", color(this._current));
    })
  .each(function(d, i) { this._current = i; });


g.append('text')
  .attr('text-anchor', 'middle')
  .attr('dy', '.35em')
  .text(text);
.pie {
  margin: 20px;
}

.pie text {
  font-family: "Verdana";
  fill: #888;
}

.pie .name-text{
  font-size: 1em;
}

.pie .value-text{
  font-size: 3em;
}
<div class="token-chart">
  <h6>Top Holders</h6>
  <div class="chart" id="topHoldersChart"></div>
</div>
    

<script src="https://d3js.org/d3.v4.min.js"></script>

1 Answer 1

1

Let's look at your d3.pie layout:

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

When we feed data to this (pie(data)), pie is expecting an array. But you are providing an object:

{
"1":{"address":"0xd35a2d8c651f3eba4f0a044db961b5b0ccf68a2d","amount":"309953166.54621424","percent":"30.9953%"},
"2":{"address":"0xe17c20292b2f1b0ff887dc32a73c259fae25f03b","amount":"200000001","percent":"20.0000%"},
"3":{"address":"0x0000000000000000000000000000000000000000","amount":"129336426","percent":"12.9336%"}
}

We need to convert this to an array to feed it to d3.pie(). For this we could use d3.entries() (though there are other ways to achieve this too).

d3.entries() takes an object, say:

{ a: value1, b: value2 }

And converts it to an array:

[ { key: "a", value: value1 }, {key: "b", value: value2 } ]

The values are now within a property called value. This requires us to look up the amount at d.value.amount. For example:

var topHoldersArray = 
{
  "1":{"address":"0xd35a2d8c651f3eba4f0a044db961b5b0ccf68a2d","amount":"309953166","percent":"30.9953%"},
  "2":{"address":"0xe17c20292b2f1b0ff887dc32a73c259fae25f03b","amount":"200000001","percent":"20.0000%"},
  "3":{"address":"0x0000000000000000000000000000000000000000","amount":"129336426","percent":"12.9336%"}
};

var data = d3.entries(topHoldersArray);

var text = "";

var width = 260;
var height = 260;
var thickness = 40;
var duration = 750;

var radius = Math.min(width, height) / 2;
var color = d3.scaleOrdinal(d3.schemeCategory20);

var svg = d3.select("#topHoldersChart")
.append('svg')
.attr('class', 'pie')
.attr('width', width)
.attr('height', height);

var g = svg.append('g')
.attr('transform', 'translate(' + (width/2) + ',' + (height/2) + ')');

var arc = d3.arc()
.innerRadius(radius - thickness)
.outerRadius(radius);

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

var path = g.selectAll('path')
.data(pie(data))
.enter()
.append("g")
.on("mouseover", function(d) {
      let g = d3.select(this)
        .style("cursor", "pointer")
        .style("fill", "black")
        .append("g")
        .attr("class", "text-group");
 
      g.append("text")
        .attr("class", "name-text")
        .text(`${d.data.value.address}`)
        .attr('text-anchor', 'middle')
        .attr('dy', '-1.2em')
  
      g.append("text")
        .attr("class", "value-text")
        .text(`${d.data.value.amount}`)
        .attr('text-anchor', 'middle')
        .attr('dy', '.6em')
    })
  .on("mouseout", function(d) {
      d3.select(this)
        .style("cursor", "none")  
        .style("fill", color(this._current))
        .select(".text-group").remove();
    })
  .append('path')
  .attr('d', arc)
  .attr('fill', (d,i) => color(i))
  .on("mouseover", function(d) {
      d3.select(this)     
        .style("cursor", "pointer")
        .style("fill", "black");
    })
  .on("mouseout", function(d) {
      d3.select(this)
        .style("cursor", "none")  
        .style("fill", color(this._current));
    })
  .each(function(d, i) { this._current = i; });


g.append('text')
  .attr('text-anchor', 'middle')
  .attr('dy', '.35em')
  .text(text);
.pie {
  margin: 20px;
}

.pie text {
  font-family: "Verdana";
  fill: #888;
}

.pie .name-text{
  font-size: 1em;
}

.pie .value-text{
  font-size: 3em;
}
<div class="token-chart">
  <h6>Top Holders</h6>
  <div class="chart" id="topHoldersChart"></div>
</div>
    

<script src="https://d3js.org/d3.v4.min.js"></script>

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for your answer. I tried it but the graph didn't populate. I updated the OP to show the output of the topHoldersArray. Is it the same as what you expected?
I had assumed you had an array (as in the provided fiddle), I'll adjust for an object.
"If you feed the pie function an array of the child objects without placing them in the parent object" ... how can I convert the array through code to do this? thx again
@joeshmoe, I've updated the snippet to use an input object containing child objects representing each arc.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.