monica
monica

Reputation: 1484

Donut bubble chart in D3.js version 3

I need to plot bubble chart, where each bubble is a donut chart like in below image in d3 version 3. I am able to achieve something, but don't understand how to distribute the circles horizontally, as my widget will be rectangular.

Also, how to make the donut bubble like in the image below. Any help would be appreciated. Thanks.

enter image description here Code:

let colorCircles = {
  'a': '#59bcf9',
  'b': '#faabab',
  'd': '#ffde85'
};
let tooltip = d3.select("body")
  .append("div")
  .attr("class", "tooltip-inner")
  .style("position", "absolute")
  .style("min-width", "12rem")
  .style("visibility", "hidden")
  .style("color", "#627386")
  .style("padding", "15px")
  .style("stroke", '#b8bfca')
  .style("fill", "none")
  .style("stroke-width", 1)
  .style("background-color", "#fff")
  .style("border-radius", "6px")
  .style("text-align", "center")
  .text("");

let bubble = d3.layout.pack()
  .sort(null)
  .size([width, diameter])
  .padding(15)
  .value(function(d) {
    return d[columnForRadius];
  });

let svg = d3.select("body")
  .append("svg")
  .attr("width", width)
  .attr("height", diameter)
  .attr("class", "bubble");

let nodes = bubble.nodes({
  children: dataset
}).filter(function(d) {
  return !d.children;
});

let circles = svg.selectAll("circle")
  .data(nodes)
  .enter()
  .append("circle")
  .attr("r", function(d) {
    return d.r;
  })
  .attr("cx", function(d) {
    return d.x;
  })
  .attr("cy", function(d) {
    return d.y - 20;
  })

  .style("fill", function(d) {
    return colorCircles[d[columnForColors]]
  })
  .on("mouseover", function(d) {
    tooltip.style("visibility", "visible");
    tooltip.html('<p>' + d[columnForColors] + ": " + d[columnForText] + "</p><div class='font-bold displayInlineBlock'> $" + d[columnForRadius] + '</div>');
  })
  .on("mousemove", function() {
    return tooltip.style("top", (d3.event.offsetY - 10) + "px").style("left", (d3.event.offsetX + 10) + "px");
  })
  // .on("mouseout", function() {
  //     return tooltip.style("visibility", "hidden");
  // })
  .attr("class", "node");

circles.transition()
  .duration(1000)
  .attr("r", function(d) {
    return d.r;
  })
  .each('end', function() {
    display_text();
  });

function display_text() {
  let text = svg
    .selectAll(".text")
    .data(nodes, function(d) {
      return d[columnForText];
    });

  text.enter().append("text")
    .attr("class", "graphText")
    .attr("x", function(d) {
      return d.x;
    })
    .attr("y", function(d) {
      return d.y - 20;
    })
    .attr("dy", ".2em")
    .attr("fill", "white")
    .attr("font-size", function(d) {
      return d.r / 5;
    })
    .attr("text-anchor", "middle")
    .text(function(d) {
      console.log(d)
      return d[columnForText].substring(0, d.r / 3);
    });

  text.enter().append("text")
    .attr("class", "graphText")
    .attr("x", function(d) {
      return d.x;
    })
    .attr("y", function(d) {
      return d.y - 20;
    })
    .attr("dy", "1.3em")
    .style("text-anchor", "middle")
    .text(function(d) {
      return '$' + d[columnForRadius];
    })
    .attr("font-size", function(d) {
      return d.r / 5;
    })
    .attr("fill", "white");
}

function hide_text() {
  let text = svg.selectAll(".text").remove();
}

d3.select(self.frameElement)
  .style("height", diameter + "px");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>

<script type="text/javascript">
  var dataset = [
    { "Name": "Olives",          "Count": 4319, "Category": "d" },
    { "Name": "Tea",             "Count": 4159, "Category": "d" },
    { "Name": "Boiled Potatoes", "Count": 2074, "Category": "a" },
    { "Name": "Milk",            "Count": 1894, "Category": "a" },
    { "Name": "Chicken Salad",   "Count": 1809, "Category": "a" },
    { "Name": "Lettuce Salad",   "Count": 1566, "Category": "a" },
    { "Name": "Lobster Salad",   "Count": 1511, "Category": "a" },
    { "Name": "Chocolate",       "Count": 1489, "Category": "b" }
  ];

  var width = 300, diameter = 300;
  var columnForText = 'Name',
  columnForColors = 'Category', 
  columnForRadius = "Count";
</script>

Upvotes: 0

Views: 1364

Answers (2)

Uditya Singh
Uditya Singh

Reputation: 36

Here's my fiddle: http://jsfiddle.net/71s86zL7/

I created a compound bubble pie chart and specified the inner radius in the pie chart.

var arc = d3.svg.arc()
  .innerRadius(radius)
  .outerRadius(radius);

.attr("d", function(d) {
    arc.innerRadius(d.r+5);
    arc.outerRadius(d.r);
    return arc(d);
})

please let me know if there's any alternative solution to this problem.

Upvotes: 2

Aditya
Aditya

Reputation: 1377

I have a sorta hacky solution for this. What I did was:

  1. to use the d3.layout.pie to get the startAngles and endAngles for arcs and create the arcs on top of the circles.

  2. Give the circles a stroke line creating an effect of a donut chart.

  3. And then I just had to adjust the startAngles and the endAngles so that all the arcs start from the same position.

Here's the fiddle:

let colorCircles = {
  'a': '#59bcf9',
  'b': '#faabab',
  'd': '#ffde85'
};
let tooltip = d3.select("body")
  .append("div")
  .attr("class", "tooltip-inner")
  .style("position", "absolute")
  .style("min-width", "12rem")
  .style("visibility", "hidden")
  .style("color", "#627386")
  .style("padding", "15px")
  .style("stroke", '#b8bfca')
  .style("fill", "none")
  .style("stroke-width", 1)
  .style("background-color", "#fff")
  .style("border-radius", "6px")
  .style("text-align", "center")
  .text("");

let bubble = d3.layout.pack()
  .sort(null)
  .size([width, diameter])
  .padding(15)
  .value(function(d) {
    return d[columnForRadius];
  });

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

var arc = d3.svg.arc()

let svg = d3.select("body")
  .append("svg")
  .attr("width", width)
  .attr("height", diameter)
  .attr("class", "bubble");

let nodes = bubble.nodes({
  children: dataset
}).filter(function(d) {
  return !d.children;
});

let g = svg.append('g')

let circles = g.selectAll("circle")
  .data(nodes)
  .enter()
  .append("circle")
  .attr("r", function(d) {
    return d.r;
  })
  .attr("cx", function(d) {
    return d.x;
  })
  .attr("cy", function(d) {
    return d.y - 20;
  })

  .style("fill", function(d) {
    return colorCircles[d[columnForColors]]
  })
  .attr("class", "node")
  .on("mouseover", function(d) {
    tooltip.style("visibility", "visible");
    tooltip.html('<p>' + d[columnForColors] + ": " + d[columnForText] + "</p><div class='font-bold displayInlineBlock'> $" + d[columnForRadius] + '</div>');
  })
  .on("mousemove", function() {
    return tooltip.style("top", (d3.event.offsetY - 10) + "px").style("left", (d3.event.offsetX + 10) + "px");
  })
  .on("mouseout", function() {
    return tooltip.style("visibility", "hidden");
  });

arcs = g.selectAll(".arc")
  .data(pie(dataset))
  .enter().append("g")
  .attr("class", "arc");

arcs.append("path")
  .attr('transform', function(d) {
    return 'translate(' + d['data']['x'] + ',' + (d['data']['y'] - 20) + ')';
  })
  .attr("d", function(d) {
    return arc({
      startAngle: 0,
      endAngle: d.startAngle - d.endAngle,
      innerRadius: d['data']['r'] - 2,
      outerRadius: d['data']['r'] + 2,
    })
  }).on("mouseover", function(d) {
    tooltip.style("visibility", "visible");
    tooltip.html('<p>' + d['data'][columnForColors] + ": " + d['data'][columnForText] + "</p><div class='font-bold displayInlineBlock'> $" + d['data'][columnForRadius] + '</div>');
  })
  .on("mousemove", function() {
    return tooltip.style("top", (d3.event.offsetY - 10) + "px").style("left", (d3.event.offsetX + 10) + "px");
  })
  .on("mouseout", function() {
    return tooltip.style("visibility", "hidden");
  });

circles.transition()
  .duration(1000)
  .attr("r", function(d) {
    return d.r;
  })
  .each('end', function() {
    display_text();
  });

function display_text() {
  let text = svg
    .selectAll(".text")
    .data(nodes, function(d) {
      return d[columnForText];
    });

  text.enter().append("text")
    .attr("class", "graphText")
    .attr("x", function(d) {
      return d.x;
    })
    .attr("y", function(d) {
      return d.y - 20;
    })
    .attr("dy", ".2em")
    .attr("fill", "white")
    .attr("font-size", function(d) {
      return d.r / 3;
    })
    .attr("text-anchor", "middle")
    .text(function(d) {
      return d[columnForText].substring(0, d.r / 3);
    });

  text.enter().append("text")
    .attr("class", "graphText")
    .attr("x", function(d) {
      return d.x;
    })
    .attr("y", function(d) {
      return d.y - 20;
    })
    .attr("dy", "1.3em")
    .style("text-anchor", "middle")
    .text(function(d) {
      return '$' + d[columnForRadius];
    })
    .attr("font-size", function(d) {
      return d.r / 5;
    })
    .attr("fill", "white");
}

function hide_text() {
  let text = svg.selectAll(".text").remove();
}

d3.select(self.frameElement)
  .style("height", diameter + "px");
path {
  fill: orange;
  stroke-width: 1px;
  stroke: crimson;
}

path:hover {
  fill: yellow;
}

circle {
  fill: white;
  stroke: slategray;
  stroke-width: 4px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.3.13/d3.min.js"></script>

<script type="text/javascript">
  var dataset = [{
      "Name": "Olives",
      "Count": 4319,
      "Category": "d"
    },
    {
      "Name": "Tea",
      "Count": 4159,
      "Category": "d"
    },
    {
      "Name": "Boiled Potatoes",
      "Count": 2074,
      "Category": "a"
    },
    {
      "Name": "Milk",
      "Count": 1894,
      "Category": "a"
    },
    {
      "Name": "Chicken Salad",
      "Count": 1809,
      "Category": "a"
    },
    {
      "Name": "Lettuce Salad",
      "Count": 1566,
      "Category": "a"
    },
    {
      "Name": "Lobster Salad",
      "Count": 1511,
      "Category": "a"
    },
    {
      "Name": "Chocolate",
      "Count": 1489,
      "Category": "b"
    }
  ];

  var width = 300,
    diameter = 300;
  var columnForText = 'Name',
    columnForColors = 'Category',
    columnForRadius = "Count";
</script>

Upvotes: 0

Related Questions