How to draw a circle sector in CSS?
Well, drawing a circle with pure CSS is easy.
.circle {
width: 100px;
height: 100px;
border-radius: 100px;
border: 3px solid black;
background-color: green;
}
How do I draw a sector? Given a degree X [0-360] I want to draw a X degrees sector. Can I do that with pure CSS?
For example:
Thanks + Example
Thank you Jonathan, I used the first method. If it helps someone here's an example of a JQuery function that gets percentage and draw a sector. The sector is behind the percentage circle and this example shows how to achieve an arc around a circle from a start degree.
$(function drawSector() {
var activeBorder = $("#activeBorder");
var prec = activeBorder.children().children().text();
if (prec > 100)
prec = 100;
var deg = prec * 3.6;
if (deg <= 180) {
activeBorder.css('background-image', 'linear-gradient(' + (90 + deg) + 'deg, transparent 50%, #A2ECFB 50%),linear-gradient(90deg, #A2ECFB 50%, transparent 50%)');
} else {
activeBorder.css('background-image', 'linear-gradient(' + (deg - 90) + 'deg, transparent 50%, #39B4CC 50%),linear-gradient(90deg, #A2ECFB 50%, transparent 50%)');
}
var startDeg = $("#startDeg").attr("class");
activeBorder.css('transform', 'rotate(' + startDeg + 'deg)');
$("#circle").css('transform', 'rotate(' + (-startDeg) + 'deg)');
});
.container {
width: 110px;
height: 110px;
margin: 100px auto;
}
.prec {
top: 30px;
position: relative;
font-size: 30px;
}
.prec:after {
content: '%';
}
.circle {
position: relative;
top: 5px;
left: 5px;
text-align: center;
width: 100px;
height: 100px;
border-radius: 100%;
background-color: #E6F4F7;
}
.active-border {
position: relative;
text-align: center;
width: 110px;
height: 110px;
border-radius: 100%;
background-color: #39B4CC;
background-image: linear-gradient(91deg, transparent 50%, #A2ECFB 50%), linear-gradient(90deg, #A2ECFB 50%, transparent 50%);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<div class="container">
<div id="activeBorder" class="active-border">
<div id="circle" class="circle">
<span class="prec">66</span>
<span id="startDeg" class="90"></span>
</div>
</div>
</div>
JSFiddle demo
$(function drawSector() {
// Get degrees
...
// Draw a sector
if (deg <= 180) {
activeBorder.css('background-image', 'linear-gradient(' + (90+deg) + 'deg, transparent 50%, #A2ECFB 50%), linear-gradient(90deg, #A2ECFB 50%, transparent 50%)');
}
else {
activeBorder.css('background-image', 'linear-gradient(' + (deg-90) + 'deg, transparent 50%, #39B4CC 50%), linear-gradient(90deg, #A2ECFB 50%, transparent 50%)');
}
// Rotate to meet the start degree
activeBorder.css('transform','rotate(' + startDeg + 'deg)');
});
CSS and Multiple Background Gradients
Rather than trying to draw the green portion, you could draw the white portions instead:
pie {
border-radius: 50%;
background-color: green;
}
.ten {
background-image:
/* 10% = 126deg = 90 + ( 360 * .1 ) */
linear-gradient(126deg, transparent 50%, white 50%),
linear-gradient(90deg, white 50%, transparent 50%);
}
pie {
width: 5em;
height: 5em;
display: block;
border-radius: 50%;
background-color: green;
border: 2px solid green;
float: left;
margin: 1em;
}
.ten {
background-image: linear-gradient(126deg, transparent 50%, white 50%), linear-gradient(90deg, white 50%, transparent 50%);
}
.twentyfive {
background-image: linear-gradient(180deg, transparent 50%, white 50%), linear-gradient(90deg, white 50%, transparent 50%);
}
.fifty {
background-image: linear-gradient(90deg, white 50%, transparent 50%);
}
/* Slices greater than 50% require first gradient
to be transparent -> green */
.seventyfive {
background-image: linear-gradient(180deg, transparent 50%, green 50%), linear-gradient(90deg, white 50%, transparent 50%);
}
.onehundred {
background-image: none;
}
<pie class="ten"></pie>
<pie class="twentyfive"></pie>
<pie class="fifty"></pie>
<pie class="seventyfive"></pie>
<pie class="onehundred"></pie>
Demo: http://jsfiddle.net/jonathansampson/7PtEm/
Scalable Vector Graphics
If it's an option, you can accomplish a similar effect using SVG <circle>
and <path>
elements. Consider the following:
<svg>
<circle cx="115" cy="115" r="110"></circle>
<path d="M115,115 L115,5 A110,110 1 0,1 190,35 z"></path>
</svg>
The above is fairly straight forward. We have an element containing a circle and a path. The circle's center is at 115x115 (making the SVG element 230x230). The circle has a radius of 110, making it a total of 220 wide (leaving a border of 10).
We then add a <path>
element, which is the most complicated portion of this example. This element has one attribute which determines where, and how the path is drawn. It starts with the following value:
M115,115
This instructs the path to start in the center of the aforementioned circle. Next, we draw a line from this location to the next location:
L115,5
This draws a vertical line from the center of the circle up to the top of the element (well, five pixels from the top). It is at this point things get a little more complicated but still very much intelligible.
We now draw an arc from our present location (115,5):
A110,110 1 0,1 190,35 z
This creates our arc and gives it a radius matching that of our circle (110). The two values represent the x-radius and y-radius, and both are equal since we're dealing with a circle. The next set of important numbers are the last, 190,35
. This tells the arc where to complete.
As for the rest of the information (1 0,1
and z
) these control the curvature, direction, and terminal of the arc itself. You can learn more about them by consulting any online SVG path reference.
To accomplish a "slice" of a different size, merely change the 190,35
to reflect a larger or smaller set of coordinates. You may find that you'll need to create a second, arc if you want to span more than 180 degrees.
If you want to determine the x and y coordinates from an angle, you can use the following equations:
x = cx + r * cos(a)
y = cy + r * sin(a)
With the above example, a degree of 76 would be:
x = 115 + 110 * cos(76)
y = 115 + 110 * sin(76)
Which gives us 205.676,177.272
.
With some ease, you can create the following:
circle {
fill: #f1f1f1;
stroke: green;
stroke-width: 5;
}
path {
fill: green;
}
svg.pie {
width: 230px;
height: 230px;
}
<svg class="pie">
<circle cx="115" cy="115" r="110"></circle>
<path d="M115,115 L115,5 A110,110 1 0,1 190,35 z"></path>
</svg>
<svg class="pie">
<circle cx="115" cy="115" r="110"></circle>
<path d="M115,115 L115,5 A110,110 1 0,1 225,115 z"></path>
</svg>
<svg class="pie">
<circle cx="115" cy="115" r="110"></circle>
<path d="M115,115 L115,5 A110,110 1 0,1 115,225 A110,110 1 0,1 35,190 z"></path>
</svg>
Demo: http://jsfiddle.net/jonathansampson/tYaVW/
That is very well possible using overflow
and transform
properties without any need to do complex calculations.
> Rotate transform
For angles less than 180deg
Add an element with aspect ratio 2:1 and
overflow: hidden;
Add a pseudo-element with with top border radii same as the height of the element and bottom radii as 0.
Put
transform-origin: 50% 100%;
This transforms the pseudo-element from its middle bottom.Transform: rotate(); the pseudo element by supplement of the required angle,
i.e.,transform: rotate(180 - rqrd. angle);
See how it works :
EG :
A 40deg sector using this method : Fiddle
div {
...
overflow: hidden;
...
}
div:before {
...
border-radius: 100px 100px 0 0;
transform-origin: 50% 100%;
transform: rotate(140deg);
...
}
div {
height: 100px;
width: 200px;
overflow: hidden;
position: relative;
}
div:before {
height: inherit;
width: inherit;
position: absolute;
content: "";
border-radius: 100px 100px 0 0;
background-color: crimson;
-webkit-transform-origin: 50% 100%;
-moz-transform-origin: 50% 100%;
-ms-transform-origin: 50% 100%;
transform-origin: 50% 100%;
-webkit-transform: rotate(140deg);
-moz-transform: rotate(140deg);
-ms-transform: rotate(140deg);
transform: rotate(140deg);
}
<div></div>
> Skew transform
You can also put image inside sector!
This can be done using skew
transforms on parent and -ve skew on pseudoelement :
Fiddle
div {
...
overflow: hidden;
transform-origin: 0% 100%;
transform: skew(-50deg); /*Complement of rqrd angle*/
...
}
div:before {
...
transform-origin: 0% 100%;
transform: skew(50deg);
...
}
See how this works :
div {
height: 200px;
width: 200px;
overflow: hidden;
-webkit-transform-origin: 0% 100%;
-moz-transform-origin: 0% 100%;
-ms-transform-origin: 0% 100%;
transform-origin: 0% 100%;
-webkit-transform: skew(-50deg);
-moz-transform: skew(-50deg);
-ms-transform: skew(-50deg);
transform: skew(-50deg); /*Complement of rqrd angle or (90 - angle)*/
position: relative;
}
div:before {
height: inherit;
width: inherit;
position: absolute;
content: "";
border-radius: 0 200px 0 0;
background: url('http://www.placekitten.com/g/300/200/');
-webkit-transform-origin: 0% 100%;
-moz-transform-origin: 0% 100%;
-ms-transform-origin: 0% 100%;
transform-origin: 0% 100%;
-webkit-transform: skew(50deg);
-moz-transform: skew(50deg);
-ms-transform: skew(50deg);
transform: skew(50deg);
}
<div></div>
Acknowledgements : I don't want to be a self stealer, I used the ideas which I had previously used here and here.
Does this help?
.circle {
width: 16em;
height: 16em;
border-radius: 50%;
background: linear-gradient(36deg, #272b66 42.34%, transparent 42.34%) 0 0;
background-repeat: no-repeat;
background-size: 50% 50%;
}
<div class="circle"></div>
Working Fiddle
Actually, some geometry calculation is needed here. But Let me explain that in short:
Considering the 4 quarters in the circle, the angle of linear gradient can be calculated in each quarter. And the background-position
determines the quarter:
Q I => 100% 0
Q II => 100% 100%
Q III => 0 100%
Q IV => 0 0
The only thing that remains is where the used color-stop has came from:
Consider a 30-angled piece of circle in the 1st quarter.
As talented Ana Tudor has explained in her great article, If we take the length of the width of the square to be a
, then the length of the half diagonal is going to be a*sqrt(2)/2
.
If we take the gradient degree to be g
the difference between two gradient and diagonal angles to be d
then the length of color-stop
can be calculated by:
a*sin(g) / (a*sqrt(2)/2 * cos(d))
= sin(g) / (sqrt(2) /2 * cos(d))
So, in this case we have sin(30deg) / (sqrt(2)*cos((45-30)deg)) = 0.3660
, and the % value for the color stop is 36.60%
Since our shape is in the 1st quarter, the background-position
is 100% 0
.
and the linear-gradient would be like this:
linear-gradient(-30deg, orange 36.60%, transparent 36.60%) 100% 0;
.circle {
width: 16em;
height: 16em;
border-radius: 50%;
background: linear-gradient(-30deg, orange 36.60%, transparent 36.60%) 100% 0;
background-repeat: no-repeat;
background-size: 50% 50%;
}
<div class="circle"></div>
I recommend to read the Ana's article for more details.