Search code examples
maththree.jspie-chartclip

Piechart on a Hexagon


I wanna to produce a Pie Chart on a Hexagon. There are probably several solutions for this. In the picture are my Hexagon and two Ideas:

  1. My Hexagon (6 vertices, 4 faces)
  2. How it should look at the end (without the gray lines)

  3. Math: Can I get some informations from the object to dynamically calculate new vertices (from the center to each point) to add colored faces?

  4. Clipping: On a sphere a Pie-Chart is easy, maybe I can clip the THREE Object (WITHOUT SVG.js!) so I just see the Hexagon with the clipped Chart?

photo


Solution

  • Well the whole clipping thing in three.js is already solved here : Object Overflow Clipping Three JS, with a fiddle that shows it works and all.

    So I'll go for the "vertices" option, or rather, a function that, given a list of values gives back a list of polygons, one for each value, that are portions of the hexagon, such that

    • they all have the centre point as a vertex
    • the angle they have at that point is proportional to the value
    • they form a partition the hexagon

    Let us suppose the hexagon is inscribed in a circle of radius R, and defined by the vertices :

    {(R sqrt(3)/2, R/2), (0,R), (-R sqrt(3)/2, R/2), (-R sqrt(3)/2, -R/2), (0,-R), (R sqrt(3)/2, -R/2)}

    This comes easily from the values cos(Pi/6), sin(Pi/6) and various symmetries.

    Getting the angles at the centre for each polygon is pretty simple, since it is the same as for a circle. Now we need to know the position of the points that are on the hexagon.

    Note that if you use the symmetries of the coordinate axes, there are only two cases : [0,Pi/6] and [Pi/6,Pi/2], and you then get your result by mirroring. If you use the rotational symmetry by Pi/3, you only have one case : [-Pi/6,Pi/6], and you get the result by rotation.

    Using rotational symmetry

    Thus for every point, you can consider it's angle to be between [-Pi/6,Pi/6]. Any point on the hexagon in that part has x=R sqrt(3)/2, which simplifies the problem a lot : we only have to find it's y value.

    Now we assumed that we know the polar coordinate angle for our point, since it is the same as for a circle. Let us call it beta, and alpha its value in [-Pi/6,Pi/6] (modulo Pi/3). We don't know at what distance d it is from the centre, and thus we have the following system :

    system with alpha

    Which is trivially solved since cos is never 0 in the range [-Pi/6,Pi/6].

    Thus d=R sqrt(3)/( 2 cos(alpha) ), and y=d sin(alpha)

    So now we know

    • the angle from the centre beta
    • it's distance d from the centre, thanks to rotational symmetry

    So our point is (d cos(beta), d sin(beta))

    Code

    Yeah, I got curious, so I ended up coding it. Sorry if you wanted to play with it yourself. It's working, and pretty ugly in the end (at least with this dataset), see the jsfiddle : http://jsfiddle.net/vb7on8vo/5/

    var R = 100;
    var hexagon = [{x:R*Math.sqrt(3)/2, y:R/2}, {x:0, y:R}, {x:-R*Math.sqrt(3)/2, y:R/2}, {x:-R*Math.sqrt(3)/2, y:-R/2}, {x:0, y:-R}, {x:R*Math.sqrt(3)/2, y:-R/2}];
    var hex_angles = [Math.PI / 6, Math.PI / 2, 5*Math.PI / 6, 7*Math.PI / 6, 3*Math.PI / 2, 11*Math.PI / 6];
    
    function regions(values)
    {
        var i, total = 0, regions = [];
    
        for(i=0; i<values.length; i++)
            total += values[i];
    
        // first (0 rad) and last (2Pi rad) points are always at x=R Math.sqrt(3)/2, y=0
        var prev_point = {x:hexagon[0].x, y:0}, last_angle = 0;
    
        for(i=0; i<values.length; i++)
        {
            var j, theta, p = [{x:0,y:0}, prev_point], beta = last_angle + values[i] * 2 * Math.PI / total;
    
            for( j=0; j<hexagon.length; j++)
            {
                theta = hex_angles[j];
                if( theta <= last_angle )
                    continue;
                else if( theta >= beta )
                    break;
                else
                    p.push( hexagon[j] );
            }
    
            var alpha = beta - (Math.PI * (j % 6) / 3); // segment 6 is segment 0
            var d = hexagon[0].x / Math.cos(alpha);
            var point = {x:d*Math.cos(beta), y:d*Math.sin(beta)};
    
            p.push( point );
            regions.push(p.slice(0));
    
            last_angle = beta;
            prev_point = {x:point.x, y:point.y};
        }
    
        return regions;
    }