Search code examples
javascripthtmlcsscss-transforms

Circular Table with HTML , CSS and JS


I'm trying to re-create the following with HTML, JS & CSS:

enter image description here

My goal is to be able to replace the content in each of the circles with whatever I want, just like a table.

So far I've read the following post - Circular HTML table. This one has an answer but I wasn't able to get it working and it uses some CSS extension

After reading the code I tried to make my own implementation, and was able to get to this point:

enter image description here

with the following code: codepen.io

    function createLayer(radius) {
        let circleGraph = document.createElement("div");
        circleGraph.classList.add("circlegraph");
        circleGraph.style.width = `${radius}px`;
        circleGraph.style.height = `${radius}px`;
        for (let j = 0; j < 12; j++) {
            let note = document.createElement("div");
            note.classList.add("circle");
            note.classList.add("center");
            let content = document.createTextNode(`${j}`)
            note.appendChild(content);
            circleGraph.appendChild(note);
        }
        let circles = circleGraph.querySelectorAll( '.circle' )
        let theta = 0, dtheta = Math.PI * 2 / circles.length
        for( let i = 0; i < circles.length; ++i ){
            let circle = circles[i]
            theta += dtheta
            circle.style.transform = `translate(-50%, -50%) rotate(${theta}rad) translate(${radius}px) `;
            circle.style.transform += `rotate(${Math.PI/2}rad)`;
        }
        document.body.appendChild(circleGraph)
    }

    createLayer(100);
    createLayer(150);
    createLayer(200);
    createLayer(250);
    html, body {
        margin: 0;
        height: 100%;
    }

    .circlegraph {
        margin: auto;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
    }


    .circlegraph .circle {
        position: absolute;
        top: 50%; left: 50%;
        width: 50%;
        height: 50px;
        transform-origin: center;
        border: 1px solid red;
        /*border-radius: 50%;*/
    }

    .center{
        display: flex;
        justify-content: center;
        align-items: center;
    }

I'm looking to get some help making it match the original image. If you know of a better way of doing this rather than translating each element by hand I would love to see it.


Solution

  • Taking inspiration from @HoratioNullbuilt's comment I was able to make an extensible modification of the example they had linked:

    http://blog.fofwebdesign.co.uk/16-circular-segment-pie-chart-menu-experimental

    The reason why it needed to be extended was that the many of the values were hardcoded, for example you couldn't have different layers with different numbers of elements in them.

    After the modification here is the result:

    https://codepen.io/cuppajoeman/pen/oNGwxzQ

    radial.js

    let data =  [
        Array.from(Array(15).keys()).map(String),
        Array.from(Array(13).keys()).map(String),
        Array.from(Array(10).keys()).map(String),
        Array.from(Array(7).keys()).map(String),
        Array.from(Array(4).keys()).map(String),
    ]
    
    function getRandomHTMLColor() {
        var r = 255*Math.random()|0,
            g = 255*Math.random()|0,
            b = 255*Math.random()|0;
        return 'rgb(' + r + ',' + g + ',' + b + ')';
    }
    
    function createPieMenu() {
        let pieMenu = document.createElement("div");
        pieMenu.id = "pie-menu"
        pieMenu.classList.add("pie-outer");
    
        let widthDelta = 100/data.length;
        let widthPercentage = 100;
    
        for (let i = 0; i < data.length; i ++) {
            let dataItem = data[i];
            let numSegments = dataItem.length;
            let segmentAngle = (Math.PI * 2)/numSegments;
            let skewAngle = (Math.PI/2) - segmentAngle;
    
            let pie = document.createElement("div");
            let pieRotateAngle = (Math.PI/2) - segmentAngle/2;
            pie.classList.add("pie");
            console.log(widthPercentage);
    
            pie.style.width = `${widthPercentage}%`;
            pie.style.background = getRandomHTMLColor();
    
            pie.style.transform = `translate(-50%,-50%) rotate(${pieRotateAngle}rad)`;
    
            let pieList = document.createElement("ul");
    
            for (let j = 0; j < dataItem.length; j ++) {
                let rotationAngle = segmentAngle * j;
                let dataContent = dataItem[j];
                let pieListItem = document.createElement('li'); // create a new list item
                let pieItemAnchor = document.createElement('a'); // create a new list item
    
                pieListItem.style.transform = `rotate(${rotationAngle}rad) skew(${skewAngle}rad)`;
    
                pieItemAnchor.appendChild(document.createTextNode(dataContent)); // append the text to the li
                let anchorRotate = segmentAngle/2 - Math.PI/2;
                let anchorSkew = segmentAngle - Math.PI/2;
                pieItemAnchor.style.transform = `skew(${anchorSkew}rad) rotate(${anchorRotate}rad)`;
    
                pieListItem.appendChild(pieItemAnchor);
                pieList.appendChild(pieListItem)
            }
            pie.appendChild(pieList);
            pieMenu.appendChild(pie);
            widthPercentage -= widthDelta;
        }
    
        document.body.appendChild(pieMenu);
    }
    
    createPieMenu();
    

    radial.css

    html, body {
        margin:0;
        padding:0;
        font:1em/1.75 Verdana, Arial, Helvetica, sans-serif;
        -ms-text-size-adjust:100%;
        -webkit-text-size-adjust:100%
    }
    
    .pie-outer *, .pie-outer {
        padding:0;
        margin:0
    }
    
    .pie-outer {
        position:relative;
        padding-top:100%;
        margin:auto
    }
    
    .pie {
        pointer-events:none;
        position:absolute;
        top:50%;
        left:50%;
        overflow:hidden;
        border:2px solid #000;
        border-radius:50%;
    }
    
    .pie ul {
        list-style-type:none
    }
    
    .pie ul:after {
        content:" ";
        display:block;
        width:100%;
        padding-top:100%
    }
    
    .pie li {
        position:absolute;
        top:-50%;
        left:-50%;
        width:100%;
        height:100%;
        margin-left:-2px;
        margin-top:-2px;
        overflow:hidden;
        border:1px solid #000;
        -webkit-transform-origin:100% 100%;
        -ms-transform-origin:100% 100%;
        transform-origin:100% 100%
    }
    
    
    .pie a {
        pointer-events:auto;
        font-size:2.125vw;
        /*line-height:170%;*/
        display:block;
        position:absolute;
        top:50%;
        left:50%;
        width:100%;
        height:100%;
        text-decoration:none;
        text-align:center;
        color:#fff;
        -webkit-tap-highlight-color:rgba(0,0,0,0);
    }
    
    .pie a:hover {
        background:rgba(255,255,255,0.25)
    }
    
    @media ( min-width:42em ) {
        /* #### - > 672px - #### */
        .pie-outer {
            padding-top:0;
            width:40em;
            height:40em
        }
        .pie a {
            font-size:1em
        }
    
    }
    

    radial.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Circular Menu</title>
        <link rel="stylesheet" href="radial.css">
    </head>
    <body>
        <script src="radial.js"></script>
    </body>
    </html>
    

    Some notes:

    The above code doesn't work when there your data list contains a list of size two, but I haven't gone into why that might happen (probably some edge case work for sizes 1 and 2).

    The mouse hover effect has a slight bug which is that if you hover on the crack between two adjacent sections, it will highlight the next layer outward (only seems to happen if the next layer outward has a different number of cells).

    The location of the text in the cells is not perfect, the problem is something to do with the line-height (which I commented out), modifications to fix the text position are welcome.