JSFiddle - React, Tailwind, and code Playground
HTML
<script src="https://code.jquery.com/jquery-3.4.1.slim.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<div id="progress-bar-fiddle" class="container pt-4">
<div class="row">
<div class="col" style="position: relative;">
<div style="position: relative">
<div id="progress-bar" class="progress">
<div id="actual-progress" class="progress-bar actual"></div>
<div id="projected-progress" class="progress-bar projected"></div>
</div>
<div id="limits-container">
<div id="limits"></div>
</div>
</div>
</div>
</div>
</div>
CSS
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(https://example.com/MaterialIcons-Regular.eot); /* For IE6-8 */
src:
local('Material Icons'),
local('MaterialIcons-Regular'),
url(https://example.com/MaterialIcons-Regular.woff2) format('woff2'),
url(https://example.com/MaterialIcons-Regular.woff) format('woff'),
url(https://example.com/MaterialIcons-Regular.ttf) format('truetype');
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px; /* Preferred icon size */
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;
/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;
/* Support for IE. */
font-feature-settings: 'liga';
}
#progress-bar-fiddle {
--actual-colour: #b445f5;
--projected-colour: #d391fa;
--scaling: 1;
--progress-height: calc(30px * var(--scaling));
--checkpoint-scaling: 0.4;
--checkpoint-size: calc(var(--progress-height) * (1 + var(--checkpoint-scaling)));
--checkpoint-border-colour: #7307b3;
}
.progress-bar.actual {
background: var(--actual-colour);
}
.progress-bar.projected {
background: var(--projected-colour);
position: relative;
}
.projected.progress-overlap::before {
width: calc(var(--progress-height) / 2);
height: var(--progress-height);
border-radius: 0 500px 500px 0;
background-color: var(--actual-colour);
content: '';
}
.actual.progress-overlap {
border-radius: 0;
}
.progress-bar {
border-radius: 0 500px 500px 0;
}
.progress {
border-radius: 500px !important;
height: var(--progress-height) !important;
}
.limit {
height: var(--progress-height);
}
#limits-container .limit:last-of-type...
JavaScript
const data = {
actual: 50,
projected: 60,
limits: [10, 30, 75, 100]
};
const limitMax = Math.max(...data.limits);
const limitsDiv = document.getElementById("limits");
data.limits.forEach((limit, index, limits) => {
const limitSize = index ? limit - limits[index - 1] : limit;
const limitDiv = document.createElement("div");
limitDiv.classList.add("limit");
limitDiv.classList.add("d-inline-block");
limitDiv.style.position = "relative";
limitDiv.style.width = `${(limitSize / limitMax) * 100}%`;
const checkpoint = document.createElement("div");
checkpoint.classList.add("checkpoint");
checkpoint.style.position = "absolute";
if (data.actual >= limit) {
checkpoint.classList.add("checked");
const checkmark = document.createElement("i");
checkmark.classList.add("material-icons");
checkmark.innerText = "check_circle_outline";
checkpoint.appendChild(checkmark);
}
limitDiv.appendChild(checkpoint);
limitsDiv.appendChild(limitDiv);
})
const actualProgress = document.getElementById("actual-progress");
const projectedProgress = document.getElementById("projected-progress");
var actualPercentage;
var projectedPercentage;
if (data.actual === 0) {
actualPercentage = 0;
projectedPercentage = Math.min(data.projected, limitMax) / limitMax;
}
else if (data.actual >= limitMax) {
actualPercentage = 1;
projectedPercentage = 0;
}
else {
actualPercentage = data.actual / limitMax;
projectedPercentage = (Math.min(data.projected, limitMax) - data.actual) / limitMax;
}
actualPercentage *= 100;
projectedPercentage *= 100;
const overlapWidthPx = document.getElementById("progress-bar").clientHeight / 2;
if (data.actual > 0 && data.actual < limitMax) {
actualProgress.classList.add('progress-overlap');
projectedProgress.classList.add('progress-overlap');
actualProgress.style.width = `calc(${actualPercentage}% - ${overlapWidthPx}px)`;
projectedProgress.style.width = `calc(${projectedPercentage}% +...