Search code examples
javascriptp5.jsdonut-chart

Show data labels inside donut pie chart p5js


I'm building a p5js donut chart, but I'm struggling to show the data labels in the middle. I think I have managed to get the boundaries right for it, but how would match the angle that I'm in? Or is there a way of matching just through the colours?

https://i.sstatic.net/enTBo.png

I have started by trying to match the boundaries of the chart to the pointer, which I managed to do using mouseX and mouseY. Any suggestions, please?

      if(mouseX >= width / 2 - width * 0.2  && mouseY >= height / 2 - width * 0.2 
        && mouseX <= width / 2 + width * 0.2 && mouseY <= height / 2 + width * 0.2)
        {
          //console.log("YAY!!! I'm inside the pie chart!!!");

        } 
      else 
      { 
        textSize(14);
        text('Hover over to see the labels', width / 2, height / 2); 
      }
    };


  [1]: https://i.sstatic.net/enTBo.png

Solution

  • While you could theoretically use the get() function to check the color of the pixel under the mouse cursor and correlate that with one of the entries in your dataset, I think you would be much better off doing the math to determine which segment the mouse is currently over. And conveniently p5.js provides helper functions that make it very easy.

    In the example you showed you are only checking if the mouse cursor is in a rectangular region. But in reality you want to check if the mouse cursor is within a circle. To do this you can use the dist(x1, y1, x2, y2) function. Once you've established that the mouse cursor is over your pie chart, you'll want to determine which segment it is over. This can be done by finding the angle between a line draw from the center of the chart to the right (or whichever direction is where you started drawing the wedges), and a line drawn from the center of the chart to the mouse cursor. This can be accomplished using the angleBetween() function of p5.Vector.

    Here's a working example:

    const colors = ['red', 'green', 'blue'];
    const thickness = 40;
    
    let segments = {
      foo: 34,
      bar: 55,
      baz: 89
    };
    
    let radius = 80, centerX, centerY;
    
    function setup() {
      createCanvas(windowWidth, windowHeight);
      noFill();
      strokeWeight(thickness);
      strokeCap(SQUARE);
      ellipseMode(RADIUS);
      textAlign(CENTER, CENTER);
      textSize(20);
    
      centerX = width / 2;
      centerY = height / 2;
    }
    
    function draw() {
      background(200);
    
      let keys = Object.keys(segments);
      let total = keys.map(k => segments[k]).reduce((v, s) => v + s, 0);
      let start = 0;
    
      // Check the mouse distance and angle
      let mouseDist = dist(centerX, centerY, mouseX, mouseY);
      // Find the angle between a vector pointing to the right, and the vector
      // pointing from the center of the window to the current mouse position.
      let mouseAngle =
        createVector(1, 0).angleBetween(
          createVector(mouseX - centerX, mouseY - centerY)
        );
      // Counter clockwise angles will be negative 0 to PI, switch them to be from
      // PI to TWO_PI
      if (mouseAngle < 0) {
        mouseAngle += TWO_PI;
      }
    
      for (let i = 0; i < keys.length; i++) {
        stroke(colors[i]);
        let angle = segments[keys[i]] / total * TWO_PI;
        arc(centerX, centerY, radius, radius, start, start + angle);
    
        // Check mouse pos
        if (mouseDist > radius - thickness / 2 &&
          mouseDist < radius + thickness / 2) {
    
          if (mouseAngle > start && mouseAngle < start + angle) {
            // If the mouse is the correct distance from the center to be hovering over
            // our "donut" and the angle to the mouse cursor is in the range for the
            // current slice, display the slice information
            push();
            noStroke();
            fill(colors[i]);
            text(`${keys[i]}: ${segments[keys[i]]}`, centerX, centerY);
            pop();
          }
        }
    
        start += angle;
      }
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.js"></script>