I'm trying to create the below picture using CSS and javascript.
I'm successfully creating the semi-circle, but I have no idea how I can align the different line bars around the circumference of the circle. I pray to the CSS gods some of you can help me in my quest.
This is the code I have so far
.roll-degrees{
position: absolute;
top: 0px;
z-index: 1;
}
.roll-curve{
width:400px;
height:120px;
border:solid 3px #000;
border-color: black transparent transparent transparent;
border-radius: 50%/120px 120px 0px 0;
}
<div class="roll-degrees">
<div class="roll-curve"></div>
</div>
Updated based on the answer: Now I'm trying to display the arrow at the bottom of the circle. Code below. Is it possible I need to add/subtract the dimensions of the arrow?
.roll-degrees-arrow{
--width: 3px;
--height: 16px;
position: absolute;
width: 0;
height: 0;
border-left: 15px solid transparent;
border-right: 15px solid transparent;
border-bottom: 25px solid white;
z-index: 1;
--arrow-angle-direction: 1; /* default towards right*/
top: calc(50% - var(--height));
left: calc(50% - var(--width) / 2);
transform:
rotate(calc(var(--arrow-angle-direction) * var(--arrow-angle)))
translateY(calc(-1 * var(--circle-radius)))
;
}
<div class="roll-degrees-wrapper">
<div class="roll-degrees">
<div class="roll-degrees-arrow"></div>
...
</div>
</div>
Initial Setup
First, it's easier to do the math using a proper circle rather than an oval, so let's make the roll-curve element a circle (then we can crop the rest using a container). I've also added an additional wrapper around the whole thing that can be used to cut the size, while the original wrapper (roll-degrees) will be the appropriate size for the full circle in order to make tick positioning easier.
Ticks
Then the approach is to control the exact location of each tick by applying absolute positioning (starting at the circle's center) and moving the tick using transform: translateY(). Center all of the ticks in the middle of the roll-curve circle, rotate each, and translate each out to the perimeter of the circle from there. By rotating first, we can just translate each tick up by the radius of the circle and "up" will be in the appropriate direction.
Marker
As for setting the position of the marker dynamically, you can use largely the same approach as for the ticks (using the same CSS styling). You'll just need a way of translating a value to an angle (which can be done fairly simply---see the example below for details) and an angle direction.
The example below includes a range input, but you can provide a value to the marker any way you like.
Example
const minValue = 0;
const maxValue = 100;
function getAngleForValue(value) {
const angleRange = 90;
const angleCenter = angleRange / 2;
return value / maxValue * angleRange - angleCenter;
}
const marker = document.querySelector("#marker");
function setMarkerPosition(value) {
const angle = getAngleForValue(value);
const absAngle = Math.abs(angle);
const angleDirection = Math.sign(angle);
marker.style.setProperty("--tick-angle", `${absAngle}deg`);
marker.style.setProperty("--tick-angle-direction", angleDirection);
}
const markerRangeInput = document.querySelector("#marker-range-input");
markerRangeInput.min = minValue;
markerRangeInput.max = maxValue;
markerRangeInput.value = (maxValue - minValue) / 2;
markerRangeInput.addEventListener("change", (event) => {
setMarkerPosition(event.target.value)
})
* {
box-sizing: border-box;
}
body {
background-color: blue;
}
.roll-degrees-wrapper {
--circle-radius: 200px;
height: calc(var(--circle-radius) / 2);
position: relative;
overflow: hidden;
--tick-1-angle: 45deg;
--tick-2-angle: 30deg;
--tick-3-angle: 15deg;
--tick-4-angle: 0deg; /* middle */
}
.roll-degrees {
box-sizing: content-box;
--size: calc(var(--circle-radius) * 2);
width: var(--size);
height: var(--size);
padding: 16px;
position: relative;
}
.roll-curve {
--size: calc(var(--circle-radius) * 2);
width: var(--size);
height: var(--size);
border: solid 3px #000;
border-color: transparent;
border-top-color: white;
border-radius: 50%;
}
.tick {
--width: 3px;
--height: 16px;
width: var(--width);
height: var(--height);
background-color: white;
position: absolute;
--v-offset: var(--height);
--h-offset: var(--width);
top: calc(50% - var(--v-offset));
left: calc(50% - var(--h-offset) / 2);
--tick-angle-direction: 1;
transform:
rotate(calc(var(--tick-angle-direction) * var(--tick-angle)))
translateY(calc(-1 * var(--circle-radius)))
;
transform-origin: bottom;
}
.tick--tall {
--height: 24px;
}
.tick:nth-child(1),
.tick:nth-child(2),
.tick:nth-child(3) {
--tick-angle-direction: -1;
}
.tick:nth-child(1),
.tick:nth-child(7) {
--tick-angle: var(--tick-1-angle);
}
.tick:nth-child(2),
.tick:nth-child(6) {
--tick-angle: var(--tick-2-angle);
}
.tick:nth-child(3),
.tick:nth-child(5) {
--tick-angle: var(--tick-3-angle);
}
.tick:nth-child(4) {
--tick-angle: var(--tick-4-angle);
}
.tick.marker {
--marker-half-size: 14px;
--marker-full-size: calc(var(--marker-half-size) * 2);
--v-offset: var(--marker-half-size);
--h-offset: var(--marker-full-size);
--tick-angle: 0deg;
background-color: transparent;
width: 0;
height: 0;
border: var(--marker-half-size) solid transparent;
--marker-color: red;
border-top-color: var(--marker-color);
border-bottom-color: var(--marker-color);
transform-origin: center;
}
<div class="roll-degrees-wrapper">
<div class="roll-degrees">
<div class="tick tick--tall"></div>
<div class="tick"></div>
<div class="tick tick--tall"></div>
<div class="tick"></div>
<div class="tick tick--tall"></div>
<div class="tick"></div>
<div class="tick tick--tall"></div>
<div id="marker" class="marker tick"></div>
<div class="roll-curve"></div>
</div>
</div>
<input id="marker-range-input" type="range">
Note: You'll probably want to do a bit better styling on the marker or use an icon of some sort, but this serves as a simple example.
Related
I am using the following HTML/Javascipt code to make the classic percentage bar.
function update() {
var element = document.getElementById("myprogressBar");
var width = 1;
var identity = setInterval(scene, 10);
function scene() {
if (width >= 70) {
clearInterval(identity);
} else {
width++;
element.style.width = width + '%';
element.innerHTML = width * 1 + '%';
}
}
}
#Progress_Status {
width: 50%;
background-color: #ddd;
}
#myprogressBar {
width: 1%;
height: 35px;
background-color: #4CAF50;
text-align: center;
line-height: 32px;
color: black;
}
<!DOCTYPE html>
<html>
<body>
<h3>Example of Progress Bar Using JavaScript</h3>
<p>Download Status of a File:</p>
<div id="Progress_Status">
<div id="myprogressBar">1%</div>
</div>
<br>
<button onclick="update()">Start Download</button>
</body>
</html>
What I would like to obtain and I am trying to achieve with .innerHTML is the following situation
The vertical line has to appear at the same level of the specified percentage.
For the vertical bar I used an added div nested inside the #Progress_Status container. It's styled to be absolute positioned and to change its offset in % in sync with the progress bar width.
For it to work, its container was set to position:relative as the reference frame.
function update() {
//fetches the vertical bar elements
var vbar = document.querySelector("#Progress_Status .percverticalbar");
var element = document.getElementById("myprogressBar");
var width = 1;
var identity = setInterval(scene, 10);
function scene() {
if (width >= 70) {
clearInterval(identity);
} else {
width++;
//updates the left offset of the vertical bar
vbar.style.left = `${width}%`;
element.style.width = width + '%';
element.innerHTML = width * 1 + '%';
}
}
}
#Progress_Status {
width: 50%;
background-color: #ddd;
position: relative;
}
.percverticalbar{
position: absolute;
height: 100px;
width: 5px;
background: gray;
top: -25px;
left: 0;
}
#myprogressBar {
width: 1%;
height: 35px;
background-color: #4CAF50;
text-align: center;
line-height: 32px;
color: black;
margin: 50px 0;
}
<h3>Example of Progress Bar Using JavaScript</h3>
<p>Download Status of a File:</p>
<div id="Progress_Status">
<div id="myprogressBar">1%</div>
<div class="percverticalbar"></div>
</div>
<br>
<button onclick="update()">Start Download</button>
You could just add an :after pseudo element and add the following styles to it. Keep in mind that the parent, in the case #myprogressBar should be relatively positioned.
#myprogressBar {
width: 1%;
height: 35px;
background-color: #4CAF50;
text-align: center;
line-height: 32px;
color: black;
position: relative;
}
#myprogressBar:after {
width: 5px;
height: 80px;
background: #333;
content: '';
position: absolute;
right: -5px;
top: 50%;
transform: translateY(-50%);
border-radius: 5px;
}
Im not entirely sure what's wrong with my code. The eyes (iris) seem to be locked on the bottom right corner of the eyes and are rotating around on that fixed position. I've looked at tutorials online and tried variety of codes, but none seem to work. The end goal that I would like to achieve is to have the eyes follow the given block.
Thank you in advance!
window.addEventListener('mousemove', eyeball);
function eyeball() {
const eye = document.querySelectorAll('.eye');
eye.forEach(function(eye){
let x = (eye.getBoundingClientRect().left) + (eye.clientWidth / 2);
let y = (eye.getBoundingClientRect().top) + (eye.clientHeight / 2);
let radian = Math.atan2(event.pageX - x, event.pageY - y);
let rotation = (radian * (180 / Math.PI) * -1) + 270;
eye.style.transform = "rotate("+rotation+"deg)";
});
}
.eye-container {
position: absolute;
display: flex;
justify-content: center;
width: 100%;
margin-top: 20px;
background: pink;
}
.eyeball{
position: relative;
width: 100px;
height: 90px;
border-radius: 50%;
background: white;
margin-right: 20px;
box-shadow: inset 0 0 5px black;
overflow: hidden;
}
.pupil{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 50px;
height: 45px;
border-radius: 50%;
background: black;
border: 10px solid purple;
box-sizing: border-box;
}
.block{
position: absolute;
top: 50%;
width: 100px;
height: 100px;
background: red;
}
<section class="eye-container">
<div class="eyelids eyelid-left"></div>
<div class="eyelids eyelid-right"></div>
<div class=" eyeball left-eye">
<div class=" eye pupil left-pupil"></div>
</div>
<div class="eyeball right-eye">
<div class="eye pupil right-pupil"></div>
</div>
</section>
<div class="block"></div>
What you want is probably something more like this. This will translate the pupils into place, giving it that "following look".
function eyeball(target_x, target_y) {
const eye = document.querySelectorAll('.eye');
eye.forEach(function(eye){
let x = (eye.getBoundingClientRect().left) + (eye.clientWidth / 2);
let y = (eye.getBoundingClientRect().top) + (eye.clientHeight / 2);
let radian = Math.atan2(target_x - x, target_y - y);
let transform_x = Math.round(Math.sin(radian) * 100);
let transform_y = Math.round(Math.cos(radian) * 100);
eye.style.transform = "translate(" + transform_x + "%, " + transform_y + "%)";
});
}
You will also need to change the initial style of the pupils to get a correct offset for the position change.
.pupil{
margin-left: 25px;
margin-top: 25px;
width: 50px;
height: 45px;
border-radius: 50%;
background: black;
border: 10px solid purple;
box-sizing: border-box;
}
I'm making an app using JavaScript and JQuery, which will tell the user if there device is straight or not, basically like a spirit level. I want to draw a line a straight line across the middle of the screen and i want this to be responsive no matter the size of the device. This will be used on mobiles and tablets. I used a canvas to the draw a line and so far i'm not sure if this is the right way to approach this?
if anyone could give me any advice i would really appreciate it. Below is my canvas line so far. And I've included some rough drawing of what i mean.
const c = document.getElementById("LineCanvas");
const drw = c.getContext("2d");
drw.beginPath();
drw.moveTo(10,45);
drw.lineTo(180,47);
drw.lineWidth = 5;
drw.strokeStyle = '#006400';
drw.stroke();
If the phone is aligned straight the line will be green else red
to draw the line you can use a pseudo element from HTML or body or any specific tag that you want to use in a specific page or click , then update rotation via transform:rotate() ; or rotate3D()
example ( without javascript, rotate values will have to be taken from your device via your app ):
let level = document.querySelector("#level");
document.querySelector("#spirit").onclick = function() {
level.classList.toggle('show');
}
#level {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
display: none;
pointer-events: none;
}
#level.show {
display: block;
}
#level::before {
content: '';
position: absolute;
width: 200vmax;
margin: 0 -50vmax;
border-top: 1px solid;
box-shadow: 0 0 1px 5px #bee;
top: 50%;
transform: rotate(0deg);
}
#level.show~#spirit::before {
content: 'Hide';
}
#level:not(.show)~#spirit::before {
content: 'Show';
}
/* animation to fake phone device moving */
#level::before {
animation: rt 10s infinite;
}
#keyframes rt {
20% {
transform: rotate3d(1, -1, 1, -0.25turn);
}
40% {
transform: rotate3d(1, 1, 1, 0.5turn);
}
60% {
transform: rotate3d(1, -1, 1, -0.75turn);
}
80% {
transform: rotate3d(1, 1, -1, -0.5turn);
}
}
<div id="level">
<!-- to show on a single page or via js on user request -->
</div>
<button id="spirit" type=button> that spirit level</button>
While drawing a line with canvas can work you might find it more straightforward to draw it with a simple div element. When you sense a slope you can change its color to red and back to green if it's level.
Of course you will have to do some calculations to decide what angle you want the line to be - but I guess that is the whole point of your webapp to show people how far off they are.
When you know the angle you want the line to be call slope(n) where n is the number of degrees. I've also put in a simple button so the user can choose whether to show the line or not but I expect you'll have your own code for that.
On any page where you want the user to be able to show the line put this in the head:
<style>
* {
margin: 0;
padding: 0;
}
.linecontainer {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
overflow: hidden;
z-index: 99999;
}
#line {
width: 200vmax;
height: 5px;
position: relative;
top: 50%;
left: calc(50vw - 100vmax);
transform: rotate(45deg);
background-color:red;
}
.hideline {
display: none;
}
#showbtn {
font-size: 20px;
background-color: blue;
color: white;
height: 2em;
width: auto;
padding: 2px;
}
</style>
and put this in the main body of the page:
<div class="linecontainer">
<div id="showbtn" onclick="document.getElementById('line').classList.toggle('hideline');">
Click me to show/hide the line
</div>
<div id="line"></div>
</div>
<script>
function slope(deg) {
let line = document.getElementById('line');
line.style.backgroundColor = ( deg%180 == 0 ) ? 'green' : 'red';
line.style.transform = 'rotate(' + deg + 'deg)';
}
</script>
Here's a snippet where you can show the line at different angles.
function slope(deg) {
let line = document.getElementById('line');
line.style.backgroundColor = ( deg%180 == 0 ) ? 'green' : 'red';
line.style.transform = 'rotate(' + deg + 'deg)';
}
* {
margin: 0;
padding: 0;
}
.linecontainer {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
overflow: hidden;
z-index: 99999;
}
#line {
width: 200vmax;
height: 5px;
position: relative;
top: 50%;
left: calc(50vw - 100vmax);
transform: rotate(45deg);
background-color:red;
}
.hideline {
display: none;
}
#showbtn {
font-size: 20px;
background-color: blue;
color: white;
height: 2em;
width: auto;
padding: 2px;
}
<div class="linecontainer">
<div id="showbtn" onclick="document.getElementById('line').classList.toggle('hideline');">
Click me to show/hide the line
</div>
<div id="line"></div>
</div>
<!-- this is just for the demo -->
<div style="background-#eeeeee;font-size: 20px;position:fixed;z-index:100000;bottom:0;left:0;">How many degrees do you want me to rotate? <input style="font-size:20px;"value="45" onchange="slope(this.value);"/></div>
I searched a lot and finding nothing on it. I want to make a progress bar with round corners.progress bar need to have shadow. All I did as of now is here :
$(".progress-bar").each(function(){
var bar = $(this).find(".bar");
var val = $(this).find("span");
var per = parseInt( val.text(), 10);
$({p:0}).animate({p:per}, {
duration: 3000,
easing: "swing",
step: function(p) {
bar.css({
transform: "rotate("+ (45+(p*1.8)) +"deg)"
});
val.text(p|0);
}
});
});
body{
background-color:#3F63D3;
}
.progress-bar{
position: relative;
margin: 4px;
float:left;
text-align: center;
}
.barOverflow{
position: relative;
overflow: hidden;
width: 150px; height: 70px;
margin-bottom: -14px;
}
.bar{
position: absolute;
top: 0; left: 0;
width: 150px; height: 150px;
border-radius: 50%;
box-sizing: border-box;
border: 15px solid gray;
border-bottom-color: white;
border-right-color: white;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="progress-bar">
<div class="barOverflow">
<div class="bar"></div>
</div>
<span>100</span>%
</div>
I want to make corners round and having shadow. below given image represent what actually i want. Shadow is missing because i don't know to draw. :
I have tried Progressbar.js also, but I don't have much knowledge about SVG. Any answer would be appreciated.
#jaromanda for suggestion of learning SVG.
Yes is looks very hard to achieve from border-radius. So i looked into SVG and find it pretty handy. Here is my snippet:
// progressbar.js#1.0.0 version is used
// Docs: http://progressbarjs.readthedocs.org/en/1.0.0/
var bar = new ProgressBar.SemiCircle(container, {
strokeWidth: 10,
color: 'red',
trailColor: '#eee',
trailWidth: 10,
easing: 'easeInOut',
duration: 1400,
svgStyle: null,
text: {
value: '',
alignToBottom: false
},
// Set default step function for all animate calls
step: (state, bar) => {
bar.path.setAttribute('stroke', state.color);
var value = Math.round(bar.value() * 100);
if (value === 0) {
bar.setText('');
} else {
bar.setText(value+"%");
}
bar.text.style.color = state.color;
}
});
bar.text.style.fontFamily = '"Raleway", Helvetica, sans-serif';
bar.text.style.fontSize = '2rem';
bar.animate(0.45); // Number from 0.0 to 1.0
#container {
width: 200px;
height: 100px;
}
svg {
height: 120px;
width: 200px;
fill: none;
stroke: red;
stroke-width: 10;
stroke-linecap: round;
-webkit-filter: drop-shadow( -3px -2px 5px gray );
filter: drop-shadow( -3px -2px 5px gray );
}
<script src="https://rawgit.com/kimmobrunfeldt/progressbar.js/1.0.0/dist/progressbar.js"></script>
<link href="https://fonts.googleapis.com/css?family=Raleway:400,300,600,800,900" rel="stylesheet" type="text/css">
<div id="container"></div>
I want to suggest some stupid but quick solution since you're already using position: absolute. You can add background color to the circles when your animation starts.
html:
<div class="progress-bar">
<div class="left"></div>
<div class="right"><div class="back"></div></div>
<div class="barOverflow">
<div class="bar"></div>
</div>
<span>0</span>%
</div>
css:
/** all your css here **/
body{
background-color:#3F63D3;
}
.progress-bar{
position: relative;
margin: 4px;
float: left;
text-align: center;
}
.barOverflow{
position: relative;
overflow: hidden;
width: 150px; height: 70px;
margin-bottom: -14px;
}
.bar{
position: absolute;
top: 0; left: 0;
width: 150px; height: 150px;
border-radius: 50%;
box-sizing: border-box;
border: 15px solid gray;
border-bottom-color: white;
border-right-color: white;
transform: rotate(45deg);
}
.progress-bar > .left {
position: absolute;
background: white;
width: 15px;
height: 15px;
border-radius: 50%;
left: 0;
bottom: -4px;
overflow: hidden;
}
.progress-bar > .right {
position: absolute;
background: white;
width: 15px;
height: 15px;
border-radius: 50%;
right: 0;
bottom: -4px;
overflow: hidden;
}
.back {
width: 15px;
height: 15px;
background: gray;
position: absolute;
}
jquery:
$(".progress-bar").each(function(){
var bar = $(this).find(".bar");
var val = $(this).find("span");
var per = parseInt( val.text(), 10);
var $right = $('.right');
var $back = $('.back');
$({p:0}).animate({p:per}, {
duration: 3000,
step: function(p) {
bar.css({
transform: "rotate("+ (45+(p*1.8)) +"deg)"
});
val.text(p|0);
}
}).delay( 200 );
if (per == 100) {
$back.delay( 2600 ).animate({'top': '18px'}, 200 );
}
if (per == 0) {
$('.left').css('background', 'gray');
}
});
https://jsfiddle.net/y86qs0a9/7/
Same as the answers above, I found it much easier to implement using SVG instead of pure CSS.
However I couldn't find a single simplistic implementation using only HTML and CSS, or at least with no libraries, no external scripts or no dependencies. I found that given the math that needs to be calculated to make the SVG transformations to represent the percentage, JS needs to be included (if someone knows how to achieve this with only HTML and CSS I'd love to learn how). But what the JS script does is not long or complex enough to justify the overhead of adding yet another dependency to my codebase.
The JS calculations are pretty easy once you read through. You need to calculate the coordinate for the end point of the gauge in the coordinate system of the SVG. so basic trig.
Most of the CSS is not even needed and I added just to style it and to make it pretty. You can add shadow or gradients same as you could with any HTML pure shape.
Here is the codePen https://codepen.io/naticaceres/pen/QWQeyGX
You can easily tinker with this code to achieve any kind of shape of circular gauge (full circle, lower half of the semi-circle, or any variation including ellipsis).
Hope this is helpful.
// # Thanks to mxle for the first rounded corner CSS only solution https://stackoverflow.com/a/42478006/4709712
// # Thanks to Aniket Naik for the styling and the basic idea and implementation https://codepen.io/naikus/pen/BzZoLL
// - Aniket Naik has a library, linked to that codepen you should check out if you don't want to copy-paste or implement yourself
// the arc radius in the meter-value needs to stay the same, and must always be x=y, not lower than the possible circle that can connect the two points (otherwise the ratio is not preserved and the curvature doesn't match the background path).
// to style the gauge, make it bigger or smaller, play with its parent element and transform scale. don't edit width and height of SVG directly
function percentageInRadians(percentage) {
return percentage * (Math.PI / 100);
}
function setGaugeValue(gaugeElement, percentage, color) {
const gaugeRadius = 65;
const startingY = 70;
const startingX = 10;
const zeroBasedY = gaugeRadius * Math.sin(percentageInRadians(percentage));
const y = -zeroBasedY + startingY;
const zeroBasedX = gaugeRadius * Math.cos(percentageInRadians(percentage));
const x = -zeroBasedX + gaugeRadius + startingX;
// # uncomment this to log the calculations of the coordinates for the final point of the gauge value path.
//console.log(
// `percentage: ${percentage}, zeroBasedY: ${zeroBasedY}, y: ${y}, zeroBasedX: ${zeroBasedX}, x: ${x}`
//);
gaugeElement.innerHTML = `<path d="M ${startingX} ${startingY}
A ${gaugeRadius} ${gaugeRadius} 0 0 1 ${x} ${y}
" stroke="${color}" stroke-width="10" stroke-linecap="round" />`;
}
percentageChangedEvent = (gauge, newPercentage, color) => {
const percentage =
newPercentage > 100 ? 100 : newPercentage < 0 ? 0 : newPercentage;
setGaugeValue(gauge, percentage, color);
};
function initialGaugeSetup(gaugeElementId, inputId, meterColor, initialValue) {
const gaugeElement = document.getElementById(gaugeElementId);
setGaugeValue(gaugeElement, 0, meterColor);
const inputElement = document.getElementById(inputId);
inputElement.value = initialValue;
setGaugeValue(gaugeElement, initialValue, meterColor);
inputElement.addEventListener("change", (event) =>
percentageChangedEvent(gaugeElement, event.target.value, meterColor)
);
}
// Gauge Initial Config
initialGaugeSetup(
"svg-graph-meter-value",
"svg-gauge-percentage-2",
"rgb(227 127 215)",
40
);
body {
background-color: rgba(0, 0, 0, 0.8);
color: #999;
font-family: Hevletica, sans-serif;
}
/* SVG Path implementation */
.svg-container {
margin: 20px auto 10px;
height: 80px;
width: 150px;
}
svg {
fill: transparent;
}
.input-percent-container {
text-align: center;
}
.input-percent-container>* {
display: inline;
}
input {
text-align: right;
width: 40px;
margin: auto;
background-color: #5d5d5d;
color: white;
border-radius: 6px;
border: black;
}
<div class="svg-container">
<svg width="150" height="80" xmlns="http://www.w3.org/2000/svg">
<path d="M 10 70
A 65 65 0 1 1 140 70
" stroke="grey" stroke-width="3" stroke-linecap="round" />
<g id="svg-graph-meter-value">
</g>
</svg>
</div>
<div class="input-percent-container"><input id="svg-gauge-percentage-2" /><span>%<span/></div>
I'm trying to make a chart like this using JS, HTML and CSS only:
It must have all the things labels, colors and start from 0%. The value is based on user score from quiz in %. When a user gets 5/10 points, the value is 50%. If the user gets -5/10 points, the value is -50%.
This code is the last idea i had, but the negative value didn't show and it wasn't starting at 0% (center div). Also it was changing the width so the center div wasn`t in the center.
if (successRate > 0) {
plus.style.width = (successRate / 100) * 300 + "px";
minus.style.display = "hidden";
} else if (successRate < 0) {
minus.style.width = (successRate / 100) * 300 + "px";
plus.style.display = "hiden";
}
#chart {
display: table;
border: 1px solid black;
}
#chart>* {
height: 30px;
}
#left {
height: 29px;
background: red;
display: table-cell;
}
#center {
max-width: 1px !important;
background: #000;
display: table-cell;
}
#right {
height: 29px;
background: green;
display: table-cell;
}
#container {
width: 600px;
border: 1px solid black;
}
<section id="chart">
<div id="container">
<div id="left"> </div>
<div id="center"> </div>
<div id="right"> </div>
</div>
</section>
Note: In practical use, you need to change just two things in
javascript to make it work for your implementation.
First: var successRate = document.getElementById("rate").value; We're giving values using this
text input, but you can pass your value to successRate in anyway you
desire.
Second: You can change the maxRate which is set to 10 in the code(then use values between -10 and 10) to whatever value you desire
and it will compute the percentage width of the progress bar respect
to that value.
That's it!
You can make use of the following implementation, it is simple and yet effective for varying values.
We're making use of a center div , a bar div which will propagate and change color depending on the successrate and a percent container which dynamically displays the percentage below the bar.
By default we're positioning the center div in center with absolute positioning and bar at the left:50% with top:0 with relation to container having relative positioning.
Now how the does the javascript work?
We're obtaining the rate in a textbox and saving it in successRate and we're using bar as the variable to manipulate the progress bar.
We have set the maximum marks for this 10, in maxrate which can be changed making it flexible.
Then, using if/else condition we can compare when the value entered is positive or negative. If positive or negative we've cooked up a formula to increase % width in that direction.
We're making use of var prograte = (100*Math.abs(successRate))/(2*maxrate); to calculate the width of our progress bar ( the abs method incase the successrate is negative).
Eg, If score is 10/10 then 10 is the success rate and 10 is the max
rate. Using the formula:
prograte = (100*successRate)/(2*maxrate)
We obtain, prograte = 100*10 / 2*10 = 50% thus giving it 50% width from the center. If score is 2/10 we obtain,
100*2/2*10 = 10% of the width.
When successrate > 0, we're starting from the center to the right. So, we move the left to 50%.
And using the above computed value of forward we increase the width of bar to that and change the color to green.
But when the successrate < 0 (i.e the else condition we multiply it by -1 to get the absolute value of the width) then, we compute on how much further we have to move from the left using (50% - prograte) that we compute, which puts it to the right side of the center bar.
Eg, If score is -5/10 then -5 is the success rate and 10 is the max
rate. Using the formula:
prograte = (100*successRate*(-1))/(2*maxrate)
We obtain, prograte = 100*5 / 2*10 = 25% and using (50%-prograte) we obtain 25%. So, we move to 25% from the left and set the width to 25% which makes it look like its in the center decreasing backwards.
To display the percentage when the flag!=1 (which identifies if the limit for the value has been crossed), we make use of:
var percent = document.getElementById("percent");
percent.style.left = "percentage %";
percent.style.color = "color";
percent.innerHTML = prograte * 2 + "%";
function changerate() {
var successRate = document.getElementById("rate").value;
var bar = document.getElementById("bar");
var flag = 0;
var percent = document.getElementById("percent");
var maxrate = 10;
var prograte = (100 * Math.abs(successRate)) / (2 * maxrate);
if (successRate >= 0 && successRate <= maxrate) {
bar.style.left = "50%";
bar.style.background = "green";
bar.style.width = prograte + "%";
} else if (successRate < 0 && (-1 * maxrate) <= successRate) {
bar.style.background = "red";
bar.style.width = prograte + "%";
bar.style.left = (50 - prograte) + "%";
} else {
alert("Limit crossed");
bar.style.left = "50%";
bar.style.width = "0%";
flag = 1;
}
if (flag != 1) {
if (successRate > 0) {
percent.style.left = 50 + prograte + "%";
percent.style.color = "green";
} else {
percent.style.left = 50 - prograte + "%";
percent.style.color = "red";
}
percent.innerHTML = prograte * 2 + "%";
} else {
percent.style.left = prograte + "%";
percent.innerHTML = "";
}
}
#chart {
width: 500px;
position: relative;
}
#container {
height: 29px;
border: 2px solid black;
position: relative;
}
#percent {
height: 30px;
position: absolute;
top: 35px;
left: 50%;
font-weight:bold;
transition: all 0.5s ease-in-out;
}
#bar {
height: 100%;
background: red;
z-index: 1;
position: absolute;
top: 0;
left: 50%;
width: 0px;
transition: all 0.5s ease-in-out;
}
#center {
height: 100%;
max-width: 2px !important;
background: #000;
position: absolute;
left: 50%;
top: 0;
transform: translate(-50%);
z-index: 2;
}
Current max value : (10 which can be modified in javascript in maxrate)
<br><br> Input values between -10 and 10 : <br>
<input type="text" id="rate" />
<input type="button" value="update" onclick="changerate()" />
<section id="chart">
<div id="container">
<div id="bar"></div>
<div id="center"> </div>
</div>
<div id="percent"></div>
</section>
My solution, which certainly can be improved upon is as follows:
Create two container DIVs each containing one child DIV:
<div class="container" id="c1">
<div class="bar" id="contra">
</div>
</div>
<div class="container" id="c2">
<div class="bar" id="pro">
</div>
</div>
Assign them basic CSS properties, such as positioning and border:
.container {
background-color: transparent;
height: 50px;
width: 200px;
}
#c1 {
top: 0px;
left: 0px;
position: absolute;
border: 3px solid;
}
#c2 {
top: 0px;
left: 200px;
position: absolute;
border: 3px solid;
}
#pro {
top: 0px;
left: 0px;
position: absolute;
background-color: green;
width: 30px;
height: 30px;
z-index: -9999;
}
#contra {
top: 0px;
left: 50px;
position: absolute;
background-color: red;
width: 150px;
height: 30px;
z-index: -9999;
}
Now you already have all you need in terms of HTML/CSS. You have two DIVs that serve as containers and provide the black borders as shown in your image. You also have two bars inside those containers, each representing the percentage you want to display.
Obviously they would never be displayed as set with my demo-CSS-properties. As long as nothing has happened they should have zero width or be invisible and of course the bar-DIVs would never have a width above zero both at the same time.
Anyway, the dynamic part comes then with JavaScript:
var proBar = document.getElementById('pro');
var contraBar = document.getElementById('contra');
// ... do your quiz stuff and get the percentage from there
// ... calculate from the max-width of your container the position and
width of the respective bar
var result = 70; // <-- your calculations go here
proBar.setAttribute("style","width:" + result + "px")
console.log("Done.");
The bars are what you want to be dynamic here. So you can set their width to zero in the CSS properties and dynamically assign width and positioning.
So for example your container has a width, which is now the max-width of the child-bar. You get your positive or negative percentage value fro myour quiz and then calculate the width the bar should have from the percentage of the max-width. Example: Your max-width is 200px, because that is the width of the container-DIVs. Your percentage you got from your quiz is 50 percent. Thus the width of the pro bar is 100px and the width of the contra bar is zero, since the percentage value is larger than zero.
For the contra bar you would have to also calculate the left position, not just width. You can get the width from the percentage as described above and then the value of left can be described by subtracting the calculated width from max-width.
You can figure out how to do that and how to calculate the percentage. It's rather simple, but this is obviously an assignment and you should do some work yourself.
You can see the manipulation of "pro" by changing the value of result. All this should be more than plenty to solve the problem you are having. Please give some feedback when you're done.
var proBar = document.getElementById('pro');
var contraBar = document.getElementById('contra');
// ... do your quiz stuff and get the percentage from there
// ... calculate from the max-width of your container the position and width of the respective bar
var result = 70; // <-- your calculations go here
proBar.setAttribute("style","width:" + result + "px")
console.log("Done.");
.container {
background-color: transparent;
height: 50px;
width: 200px;
}
#c1 {
top: 0px;
left: 0px;
position: absolute;
border: 3px solid;
}
#c2 {
top: 0px;
left: 200px;
position: absolute;
border: 3px solid;
}
#pro {
top: 0px;
left: 0px;
position: absolute;
background-color: green;
width: 30px;
height: 50px;
z-index: -9999;
}
#contra {
top: 0px;
left: 50px;
position: absolute;
background-color: red;
width: 150px;
height: 50px;
z-index: -9999;
}
<div class="container" id="c1">
<div class="bar" id="contra">
</div>
</div>
<div class="container" id="c2">
<div class="bar" id="pro">
</div>
</div>
Another variant with less elements.
function calculate(input) {
var chart = document.getElementById('chart');
var bar = document.getElementById('bar');
var width = Math.abs(input.value) * (chart.clientWidth / 2) / 10;
bar.style.width = width + 'px';
if (input.value >= 0) {
bar.style.left = chart.clientWidth / 2 + 'px';
bar.style.background = 'green';
} else if (input.value < 0) {
bar.style.left = chart.clientWidth / 2 - width + 'px';
bar.style.background = 'red';
}
}
#chart {
border: 1px solid black;
height: 30px;
margin-top: 10px;
overflow: hidden;
position: relative;
}
#chart:after {
border-right: 1px solid black;
content: '';
display: block;
height: 30px;
left: 50%;
position: absolute;
top: 0;
}
#bar {
height: 30px;
position: absolute;
top: 0;
}
<input type="number" value="0" min="-10" max="10" onchange="calculate(this)">
<section id="chart">
<div id="bar"></div>
</section>