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) {
...