JSFiddle - React, Tailwind, and code Playground

by jcubed111

HTML

<canvas id=main width=500 height=300></canvas>
<label>
    <div>Number of Groups</div>
    <input type=range value=10 min=1 max=50 step=1 id=numGroups />
    <span></span>
</label>
<label>
    <div>Number of Sub-Bars</div>
    <input type=range value=5 min=1 max=500 step=1 id=numSubBars />
    <span></span>
</label>
<label>
    <div>Major Gap Size (% of bar)</div>
    <input type=range value=50 min=0 max=100 step=1 id=majorGapSize />
    <span></span>
</label>
<label>
    <div>Minor Gap Size (% of bar)</div>
    <input type=range value=10 min=0 max=100 step=1 id=minorGapSize />
    <span></span>
</label>
<label>
    <div>Align Pixels</div>
    <input type=checkbox id=pixelAlign checked/>
    <span></span>
</label>

CSS

canvas{
    border-left: 1px solid #ccc;
    border-bottom: 1px solid #ccc;
}
label{
    display: block;
}
input, span{
    vertical-align: middle;
}
input:not([type=checkbox]){
    min-width: max(200px, 60vw);
}

JavaScript

for(const el of document.querySelectorAll('input')) {
	el.addEventListener('input', onInput);
}

function onInput() {
	const values = {};
    for(const el of document.querySelectorAll('input')) {
    	switch(el.type) {
        	case 'range':
				values[el.id] = +el.value;
                break;
            case 'checkbox':
            	values[el.id] = el.checked;
                break;
        }
        el.parentNode.querySelector('span').innerText = values[el.id];
    }
    render(values);
}

const values = Array(500).fill(0).map(v => 0.3 + 0.6 * Math.random());

function render(args) {
	const {numGroups, numSubBars, majorGapSize, minorGapSize} = args;
	const canvas = document.getElementById('main');
	const ctx = canvas.getContext('2d');
    ctx.clearRect(0, 0, 10000, 10000);
    const width = Math.floor((window.innerWidth - 40) * window.devicePixelRatio);
    const height = 300 * devicePixelRatio;
    canvas.width = width;
    canvas.height = height;
    canvas.style.width = `${width / window.devicePixelRatio}px`;
    canvas.style.height = `${height / window.devicePixelRatio}px`;
    
    const numSmallGaps = numGroups * (numSubBars - 1);
	const numBigGaps = numGroups;
    const numBarsTotal = numGroups * numSubBars;
    
    const estimatedBarSize_1 = width / (
    	minorGapSize / 100 * numSmallGaps 
        + majorGapSize / 100 * numBigGaps 
        + 1.0 * numBarsTotal
    );
    let minorGapWidth = round(args, 
    	estimatedBarSize_1 * minorGapSize / 100
    );
    
    const estimatedBarSize_2 = (width - minorGapWidth * numSmallGaps) / (
    	majorGapSize / 100 * numBigGaps 
        + 1.0 * numBarsTotal
    );
    let majorGapWidth = round(args, 
    	estimatedBarSize_2 * majorGapSize / 100
    );
    
    let barWidth = round(args, (
    	width
        - minorGapWidth * numSmallGaps
        - majorGapWidth * numBigGaps
    ) / numBarsTotal);
    
    let x = 0;
    ctx.fillStyle = '#f0f'
    for(let i = 0; i < numGroups; i++) {
    	if(i == 0) {
       ...