Drawing svg donut chart using circle. I need to connect all arcs, instead gap between them. is it possible?
I set stroke-dasharray="${item.percent} 100" but it doesn't work. also I set pathLength="${100+0*arr.length}" but it doesn't work either. See the code below
var DonutSlice = [{
id: 1,
percent: 60,
color: 'DarkSeaGreen',
label: 'Slice 1'
},
{
id: 2,
percent: 30,
color: 'DarkOrchid',
label: 'Slice 2'
},
{
id: 3,
percent: 10,
color: 'Tomato',
label: 'Slice 3'
}
];
var circlecontainer = document.getElementById('circlecontainer');
var output = document.getElementById('output');
circlecontainer.innerHTML = DonutSlice.map((item, i, arr) => {
let offset = 1 * i + arr.filter((item, j) => j < i)
.reduce((total, item) => total + item.percent, 0);
return `<circle data-id="${item.id}" stroke="${item.color}"
cx="80" cy="80" r="79" class="slice"
pathLength="${100+0*arr.length}"
stroke-dasharray="${item.percent} 100"
stroke-dashoffset="-${offset}" />`;
}).join('');
circlecontainer.addEventListener('mouseover', e => {
let slice = e.target.closest('.slice');
if (slice) {
item = DonutSlice.find(item => item.id == parseInt(slice.dataset.id));
output.innerHTML = `${item.label} was clicked`;
}
});
.slice {
/* stroke-linecap: round; */
stroke-width: 15;
fill: none;
cursor: pointer;
}
p#output {
text-align: center;
}
<div style="width: 200px">
<svg class="svg2" width="174" height="174" preserveAspectRatio="xMinYMin meet"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 174 174" version="1.1">
<circle fill="#000000" cx="87" cy="87" r="87" />
<g id="circlecontainer" transform="rotate(0 0 0)"></g>
</svg>
<p id="output"></p>
</div>
Instead of using stroke-dashoffset I prefer to use transform rotate. So the offset value needs to be a number of degrees. I added / 100 * 360 to the calculation.
If you use the circlecontainer group to position the chart you can skip the cx and cy attributes on the circle elements.
Update
There was a gap between the slices. I changed the pathLength to 360 so that is matches the rotation. Then I add 1 degree to the length of the slice and rotate the slice 1 degree. Now, the end of each slice will overlap the slice next to it.
var DonutSlice = [{
id: 1,
percent: 60,
color: 'DarkSeaGreen',
label: 'Slice 1'
},
{
id: 2,
percent: 30,
color: 'DarkOrchid',
label: 'Slice 2'
},
{
id: 3,
percent: 10,
color: 'Tomato',
label: 'Slice 3'
}
];
var circlecontainer = document.getElementById('circlecontainer');
var output = document.getElementById('output');
circlecontainer.innerHTML = DonutSlice.map((item, i, arr) => {
let offset = arr.filter((item, j) => j < i)
.reduce((total, item) => total + item.percent, 0) / 100 * 360;
return `<circle data-id="${item.id}" stroke="${item.color}"
r="72" class="slice"
pathLength="360"
stroke-dasharray="${item.percent / 100 * 360 + 1} 360"
transform="rotate(${offset - 1})" />`;
}).join('');
circlecontainer.addEventListener('mouseover', e => {
let slice = e.target.closest('.slice');
if (slice) {
item = DonutSlice.find(item => item.id == parseInt(slice.dataset.id));
output.innerHTML = `${item.label} was clicked`;
}
});
.slice {
/* stroke-linecap: round; */
stroke-width: 16;
fill: none;
cursor: pointer;
}
p#output {
text-align: center;
}
<div style="width: 200px">
<svg class="svg2" preserveAspectRatio="xMinYMin meet"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 164 164">
<circle fill="#000000" cx="84" cy="84" r="80" />
<g id="circlecontainer" transform="translate(80 80) rotate(-90)"></g>
</svg>
<p id="output"></p>
</div>
Your offset calculation adds a gap.
Change it to (delete the additional 1 * i +):
let offset = arr.filter((item, j) => j < i)
.reduce((total, item) => total + item.percent, 0);
var DonutSlice = [{
id: 1,
percent: 60,
color: 'DarkSeaGreen',
label: 'Slice 1'
},
{
id: 2,
percent: 30,
color: 'DarkOrchid',
label: 'Slice 2'
},
{
id: 3,
percent: 10,
color: 'Tomato',
label: 'Slice 3'
}
];
var circlecontainer = document.getElementById('circlecontainer');
var output = document.getElementById('output');
circlecontainer.innerHTML = DonutSlice.map((item, i, arr) => {
let offset = arr.filter((item, j) => j < i)
.reduce((total, item) => total + item.percent, 0);
return `<circle data-id="${item.id}" stroke="${item.color}"
cx="80" cy="80" r="79" class="slice"
pathLength="${100+0*arr.length}"
stroke-dasharray="${item.percent} 100"
stroke-dashoffset="-${offset}" />`;
}).join('');
circlecontainer.addEventListener('mouseover', e => {
let slice = e.target.closest('.slice');
if (slice) {
item = DonutSlice.find(item => item.id == parseInt(slice.dataset.id));
output.innerHTML = `${item.label} was clicked`;
}
});
.slice {
/* stroke-linecap: round; */
stroke-width: 15;
fill: none;
cursor: pointer;
}
p#output {
text-align: center;
}
<div style="width: 200px">
<svg class="svg2" width="174" height="174" preserveAspectRatio="xMinYMin meet" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 174 174" version="1.1">
<circle fill="#000000" cx="87" cy="87" r="87" />
<g id="circlecontainer" transform="rotate(0 0 0)"></g>
</svg>
<p id="output"></p>
</div>
Related
My question is:
I have a polygon which is drawn by taking some points and I am showing the points on the edges/outline of polygon. When I increase the stroke-width for the polygon the points getting aligned in the center of the outline. How do we achieve the points alignment on the outside of the edge/border?
Actual how it come is like below picture, the points are center aligned across the border/stroke
working area link for this is
https://codepen.io/jinata92/pen/JjKZeqE
I am looking for a solution like as below picture, The points should be aligned on the outer edges of border/stroke
var height = 100,
width = 100;
var polygon;
var arrVertexes = [
[6, 6],
[94, 6],
[94, 94],
[6, 94]
];
var svg, gContainer;
function config() {
svg = d3
.select(".main")
.attr("width", width)
.attr("height", height)
.attr("viewBox", `0 0 ${width} ${height}`);
gContainer = svg.select("g");
}
function drawPolygon() {
polygon = gContainer
.append("polygon")
.attr("points", arrVertexes)
.attr("class", "segment");
}
function drawCircle() {
gContainer
.selectAll("circle")
.data(arrVertexes)
.enter()
.append("circle")
.attr("class", "vertex")
.classed("handle", true)
.attr("cx", function(d) {
return d[0];
})
.attr("cy", function(d) {
return d[1];
})
.attr("r", 4);
}
config();
drawPolygon();
drawCircle();
body {
background-color: grey;
}
svg {
position: absolute;
overflow: visible;
}
.resize-div {
position: relative;
overflow: visible;
}
.polygon {
stroke: yellow;
fill: transparent;
}
.vertex,
.dot {
fill: black;
stroke: none;
}
.segment {
stroke-width: 30;
stroke: yellow;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div style="width:100px;height:100px; left: 400px;
top:50px;" class="resize-div">
<svg class="main" height="100%" width="100%">
<g class="polygon"></g>
</svg>
</div>
Stroke-width is always applied to both sides, so the centre of the line is still at the required place. But there are some workarounds. One is to just draw the shape, but also apply a clip path equal to the element, but without the stroke-width:
var height = 100,
width = 100;
var polygon;
var arrVertexes = [
[6, 6],
[94, 6],
[94, 94],
[6, 94]
];
var svg, gContainer;
function config() {
svg = d3
.select(".main")
.attr("width", width)
.attr("height", height)
.attr("viewBox", `0 0 ${width} ${height}`);
gContainer = svg.select("g");
}
function drawPolygon() {
polygon = gContainer
.append("polygon")
.attr("clip-path", "url('#my-clip-path')")
.attr("points", arrVertexes)
.attr("class", "segment");
// Append a clip path
svg.append("defs")
.append("clipPath")
.attr("id", "my-clip-path")
.append("polygon")
.attr("points", arrVertexes)
.attr("class", "segment");
}
function drawCircle() {
gContainer
.selectAll("circle")
.data(arrVertexes)
.enter()
.append("circle")
.attr("class", "vertex")
.classed("handle", true)
.attr("cx", function(d) {
return d[0];
})
.attr("cy", function(d) {
return d[1];
})
.attr("r", 4);
}
config();
drawPolygon();
drawCircle();
body {
background-color: grey;
}
svg {
position: absolute;
overflow: visible;
}
.resize-div {
position: relative;
overflow: visible;
}
.polygon {
stroke: yellow;
fill: transparent;
}
.vertex,
.dot {
fill: black;
stroke: none;
}
.segment {
stroke-width: 30;
stroke: yellow;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div style="width:100px;height:100px; left: 400px;
top:50px;" class="resize-div">
<svg class="main" height="100%" width="100%">
<g class="polygon"></g>
</svg>
</div>
Alternatively, you could draw a polygon on top of the other, but inwards a little bit. Use the bounding box to calculate the centre of the outer polygon, and move the coordinates of the inner polygon inwards by some number of pixels:
var height = 100,
width = 100;
var polygon;
var arrVertexes = [
[6, 6],
[94, 6],
[94, 94],
[6, 94]
];
var svg, gContainer;
function config() {
svg = d3
.select(".main")
.attr("width", width)
.attr("height", height)
.attr("viewBox", `0 0 ${width} ${height}`);
gContainer = svg.select("g");
}
function drawPolygon() {
polygon = gContainer
.append("polygon")
.attr("points", arrVertexes)
.attr("class", "segment-border");
// Get the bounding box so you can calculate the centre
const boundingBox = polygon.node().getBBox();
const centre = {
x: boundingBox.x + boundingBox.width / 2,
y: boundingBox.y + boundingBox.height / 2,
};
const innerVertexes = arrVertexes.map(d => [
d[0] < centre.x ? d[0] + 15 : d[0] - 15,
d[1] < centre.y ? d[1] + 15 : d[1] - 15,
]);
polygon = gContainer
.append("polygon")
.attr("points", innerVertexes)
.attr("class", "segment");
}
function drawCircle() {
gContainer
.selectAll("circle")
.data(arrVertexes)
.enter()
.append("circle")
.attr("class", "vertex")
.classed("handle", true)
.attr("cx", function(d) {
return d[0];
})
.attr("cy", function(d) {
return d[1];
})
.attr("r", 4);
}
config();
drawPolygon();
drawCircle();
body {
background-color: grey;
}
svg {
position: absolute;
overflow: visible;
}
.resize-div {
position: relative;
overflow: visible;
}
.polygon {
stroke: yellow;
fill: transparent;
}
.vertex,
.dot {
fill: black;
stroke: none;
}
.segment-border {
fill: yellow;
}
.segment {
fill: white;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div style="width:100px;height:100px; left: 400px;
top:50px;" class="resize-div">
<svg class="main" height="100%" width="100%">
<g class="polygon"></g>
</svg>
</div>
The problem I'm having with fabric.js seems simple, but I've been searching for a solution without any luck, so here I am.
I create a simple group to draw an arrow, consisting of a line object and a rotated triangle placed at its end point. This works fine, but when I dynamically update the stroke width of the line and the width/height of the triangle, the group's bounding box doesn't update to match the new dimensions. Here's an image of the result:
Here's a live demo. Just drag the slider to change the stroke width and triangle size and you'll see the issue.
let canvas = new fabric.Canvas(document.querySelector('canvas'), {
backgroundColor: 'white'
}),
object,
lineCoords = [100, 75, 250, 75],
strokeWidth = parseFloat(document.querySelector('.strokewidth').value),
createArrow = () => {
return new fabric.Group(
[
new fabric.Line(lineCoords, {
left: lineCoords[0],
top: lineCoords[1],
stroke: 'red',
strokeWidth: strokeWidth,
strokeLineCap: "round",
}),
new fabric.Triangle({
width: strokeWidth + 22,
height: strokeWidth + 22,
fill: 'red',
left: lineCoords[0] + 150 + strokeWidth + 22 + ((strokeWidth + 22) / 2) - 14,
top: lineCoords[1] - (Math.floor((strokeWidth + 22) / 2)) + (Math.floor(strokeWidth / 2)),
angle: 90,
})
],
{ objectCaching: false }
)
}
let arrow = createArrow()
canvas.add(arrow)
canvas.setActiveObject(arrow)
canvas.renderAll()
$('body').on('input change', '.strokewidth', e => {
if (object = canvas.getActiveObject()) {
let value = parseFloat(e.target.value),
[ line, triangle ] = object.getObjects()
line.set({
strokeWidth: value
})
triangle.set({
width: value + 22,
height: value + 22,
left: line.left + line.width + value + 22 + ((value + 22) / 2) - 14,
top: line.top - (Math.floor((value + 22) / 2)) + (Math.floor(value / 2))
})
canvas.renderAll()
}
})
body {
background: #f0f0f0;
padding: 0;
margin: 0;
}
canvas {
border: 1px solid lightgray;
}
div {
margin: 0.5em 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.0/fabric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas width="400" height="150"></canvas>
<div>
<input type="range" class="strokewidth" min="1" max="40" value="5">
</div>
I'm using version 3.6 of fabric.js. Apparently there was a way to refresh the bounding box in older versions, but none of the code works with 3.6 - the functions no longer exist. I've tried several kludges to fix it, including ungrouping and regrouping the objects, without any luck so far.
Does anyone know how to force a recalculation of the bounding box in this situation? Thanks for any help.
In FabricJS 3 the best way to scale a group of objects is to change the scale value of the whole group:
group.set({ scaleX: value, scaleY: value })
I assume that sometimes it is required to scale only some objects inside a group or scale them with a different proportion. I think historically it was decided that this heavy operation needs to be triggered by the developer. There is a method for doing that addWithUpdate and it can be called without passing an object to it.
let canvas = new fabric.Canvas(document.querySelector('canvas'), {
backgroundColor: 'white'
}),
object,
lineCoords = [100, 75, 250, 75],
strokeWidth = parseFloat(document.querySelector('.strokewidth').value),
createArrow = () => {
return new fabric.Group(
[
new fabric.Line(lineCoords, {
left: lineCoords[0],
top: lineCoords[1],
stroke: 'red',
strokeWidth: strokeWidth,
strokeLineCap: "round",
}),
new fabric.Triangle({
width: strokeWidth + 22,
height: strokeWidth + 22,
fill: 'red',
left: lineCoords[0] + 150 + strokeWidth + 22 + ((strokeWidth + 22) / 2) - 14,
top: lineCoords[1] - (Math.floor((strokeWidth + 22) / 2)) + (Math.floor(strokeWidth / 2)),
angle: 90,
})
],
{ objectCaching: false }
)
}
let arrow = createArrow()
canvas.add(arrow)
canvas.setActiveObject(arrow)
canvas.renderAll()
$('body').on('input change', '.strokewidth', e => {
if (object = canvas.getActiveObject()) {
let value = parseFloat(e.target.value),
[ line, triangle ] = object.getObjects()
line.set({
strokeWidth: value
})
triangle.set({
width: value + 22,
height: value + 22,
left: line.left + line.width + value + 22 + ((value + 22) / 2) - 14,
top: line.top - (Math.floor((value + 22) / 2)) + (Math.floor(value / 2))
})
canvas.renderAll()
arrow.addWithUpdate()
}
})
body {
background: #f0f0f0;
padding: 0;
margin: 0;
}
canvas {
border: 1px solid lightgray;
}
div {
margin: 0.5em 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.0/fabric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas width="400" height="150"></canvas>
<div>
<input type="range" class="strokewidth" min="1" max="40" value="5">
</div>
I have the following circle progress bar. Everything is fine except for the gradient. The circle is actually 2 arcs. And when the script draws the first arc the gradient is red->blue. So the start of the circle is red. And the tail is blue. But when the second arc is being drawn the start of the gradient switches to blue and I don't know how to fix it. I don't want it to switch colors. I want the gradient to always be the same
function update(percentage) {
var width = 160,
height = 160,
cx = width / 2,
cy = height / 2,
start_angle = 0,
barsize = 10;
var r = Math.min(cx, cy) - barsize / 2;
if (percentage === 100) {
percentage -= 0.0001;
}
var end_angle = start_angle + percentage * Math.PI * 2 / 100;
var x1 = cx + r * Math.sin(start_angle),
y1 = cy - r * Math.cos(start_angle),
x2 = cx + r * Math.sin(end_angle),
y2 = cy - r * Math.cos(end_angle);
// This is a flag for angles larger than than a half circle
// It is required by the SVG arc drawing component
var big = 0;
if (end_angle - start_angle > Math.PI) big = 1;
// This string holds the path details
var d = "M" + x1 + "," + y1 + // Start at (x1,y1)
" A" + r + "," + r + // Draw an arc of radius r
" 0 " + big + " 1 " + // Arc details...
x2 + "," + y2;
document.getElementById('path').setAttribute('d', d);
}
function animate(start, finish) {
setTimeout(function() {
update(start);
console.log(document.getElementsByClassName('progress__content'))
let element = document.getElementsByClassName('progress__content')[0];
element.textContent = start + '%';
start += 1;
if (start <= finish) {
animate(start, finish);
} else {
return;
}
}, 10);
}
function go() {
animate(0, 100);
}
.progress {
position: absolute;
top: 50%;
left: 50%;
margin-top: -100px;
margin-left: 100px;
width: 200px;
height: 200px;
}
.progress__content {
position: absolute;
left: 50%;
top: 50%;
margin-left: -50px;
margin-top: -23px;
font-family: Helvetica;
font-size: 40px;
width: 103px;
height: 47px;
text-align: center;
}
body {
background: #f1f1f1;
}
<button onclick="go()">Click me</button>
<div class="progress clip-svg">
<div class="progress__content">0%</div>
<svg width="160" height="160">
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop stop-color="#EE3028" offset="0" />
<stop stop-color="#067BC2" offset="1" />
</linearGradient>
</defs>
<ellipse rx="75" ry="75" cx="80" cy="80" stroke="#f2f2f2" fill="none" stroke-width="10"></ellipse>
<g>
<path id="path" stroke-width="10" stroke="url(#gradient)" fill="none" d="">
</path>
</g>
</svg>
</div>
The gradient changes because you define the gradient on the entire path, and as the path object grows down and left, the gradient stop positions are continuously redefined as well. The solution is to change your gradientUnits to userSpaceOnUse - so the gradient is defined vs. the drawing surface/viewBox vs. relative to the object. Possible implementation below (I'm not entirely sure what color scheme you're aiming for - but tweak the stop locations & colors until you have what you want).
function update(percentage) {
var width = 160,
height = 160,
cx = width / 2,
cy = height / 2,
start_angle = 0,
barsize = 10;
var r = Math.min(cx, cy) - barsize / 2;
if (percentage === 100) {
percentage -= 0.0001;
}
var end_angle = start_angle + percentage * Math.PI * 2 / 100;
var x1 = cx + r * Math.sin(start_angle),
y1 = cy - r * Math.cos(start_angle),
x2 = cx + r * Math.sin(end_angle),
y2 = cy - r * Math.cos(end_angle);
// This is a flag for angles larger than than a half circle
// It is required by the SVG arc drawing component
var big = 0;
if (end_angle - start_angle > Math.PI) big = 1;
// This string holds the path details
var d = "M" + x1 + "," + y1 + // Start at (x1,y1)
" A" + r + "," + r + // Draw an arc of radius r
" 0 " + big + " 1 " + // Arc details...
x2 + "," + y2;
document.getElementById('path').setAttribute('d', d);
}
function animate(start, finish) {
setTimeout(function() {
update(start);
console.log(document.getElementsByClassName('progress__content'))
let element = document.getElementsByClassName('progress__content')[0];
element.textContent = start + '%';
start += 1;
if (start <= finish) {
animate(start, finish);
} else {
return;
}
}, 10);
}
function go() {
animate(0, 100);
}
.progress {
position: absolute;
top: 50%;
left: 50%;
margin-top: -100px;
margin-left: 100px;
width: 200px;
height: 200px;
}
.progress__content {
position: absolute;
left: 50%;
top: 50%;
margin-left: -50px;
margin-top: -23px;
font-family: Helvetica;
font-size: 40px;
width: 103px;
height: 47px;
text-align: center;
}
body {
background: #f1f1f1;
}
<button onclick="go()">Click me</button>
<div class="progress clip-svg">
<div class="progress__content">0%</div>
<svg width="160" height="160">
<defs>
<linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="160" y2="0">
<stop stop-color="#EE3028" offset="0" />
<stop stop-color="#067BC2" offset="160" />
</linearGradient>
</defs>
<ellipse rx="75" ry="75" cx="80" cy="80" stroke="#f2f2f2" fill="none" stroke-width="10"></ellipse>
<g>
<path id="path" stroke-width="10" stroke="url(#gradient)" fill="none" d="">
</path>
</g>
</svg>
</div>
my problem is explained quite simply. I've gotten a screenshot of the situation and snippet a jsFiddle code.
The problem I have, is clearly visible on the screenshot, the circular sections look perfectly in the Chrome browser, but in FireFox & Edge etc. the sections are slightly offset.
Prior to the current status, I had set the r / cx / cy properties to css, but that was not compatible either. I found out that you have to write them directly into the circle tag.
Has anyone ever had the problem, I mean, but can anyone explain why it does not work as expected?
[EDIT] THANKS #Sphinxxx for answer the basic question of y doesn't work that.
Is there a hack / workaround to solve the problem?
Screenshot:
Browser on this Screen:
1. Chrome
2. FireFox
3. Edge
[UPDATE] (In the current version of FireFox that issue is fixed)
Now we have that problem only in the Edge browser
Here to the code example:
const duration = 1200
Array.from(document.getElementsByClassName('count')).forEach(el => {
const target = parseInt(el.innerText)
const step = (duration / target)
const increment = step < 10 ? Math.round(10 / step) : 1
let current = 0
console.log(el.innerText + ': ' + step)
el.innerText = current
window.addEventListener('load', _ => {
const timer = setInterval(_ => {
current += increment
if (current >= target) {
el.innerText = target
clearInterval(timer)
} else
el.innerText = current
}, step)
})
})
function getlength(number) {
return number.toString().length;
}
svg.chart {
width: 100px;
border-radius: 50%;
background: yellowgreen;
transform: rotate(-90deg);
animation: grow-up cubic-bezier(0, 0, 0.18, 1) 2s;
animation-delay: 0.3s;
}
.chart > circle {
fill: none;
stroke-width: 32;
}
.chart > circle.first {
stroke: deeppink;
}
.chart > circle.second {
stroke: mediumpurple;
}
.chart > circle.third {
stroke: #fb3;
}
.chart > circle.fourth {
stroke: #ce3b6a;
}
.legend-list li{
width: 40%;
}
.legend-list span.glyphicon {
color: yellowgreen;
}
.legend-list .first span.glyphicon {
color: deeppink;
}
.legend-list .second span.glyphicon {
color: mediumpurple;
}
.legend-list .third span.glyphicon {
color: #fb3;
}
.legend-list .fourth span.glyphicon {
color: #ce3b6a;
}
svg circle {
animation: rotate-in cubic-bezier(0, 0, 0.18, 1) .7s;
animation-delay: 0.3s;
transform-origin: 50% 50%
}
#keyframes rotate-in {
from {
opacity: 0;
stroke-dashoffset: 30;
}
to {
opacity: 1;
stroke-dashoffset: 0;
}
}
#keyframes grow-up {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
<svg class="chart" viewBox="0 0 32 32">
<!-- circle zero from 0 to 100 for filling yellowgreen --> <!-- 75 - 100 = 25 % -> realy 0 - 100 background color -->
<circle class='fourth' stroke-dasharray="75 100" r="16" cx="16" cy="16"></circle> <!-- 60 - 75 = 15 % -->
<circle class='third' stroke-dasharray="60 100" r="16" cx="16" cy="16"></circle> <!-- 40 - 60 = 20 % -->
<circle class='second' stroke-dasharray="40 100" r="16" cx="16" cy="16"></circle> <!-- 30 - 40 = 10 % -->
<circle class='first' stroke-dasharray="30 100" r="16" cx="16" cy="16"></circle> <!-- 0 - 30 = 30 % -->
</svg>
Both Edge and Firefox clearly do something wrong when drawing circles where the stroke meets itself in the circle center. Your example can be simplified to this:
<svg class="chart" width="320" height="340" viewBox="1 0 32 34">
<circle cx="16" cy="1" r="8" stroke-width="15.5" stroke="green" stroke-dasharray="20 999" fill="none"></circle>
<circle cx="16" cy="18" r="8" stroke-width="16" stroke="blue" stroke-dasharray="20 999" fill="none"></circle>
</svg>
The green circle has a stroke that's just a little bit too thin to reach the center, and looks like you would expect, with a tiny hole in the middle. The blue circle should perfectly close that gap, but somehow overshoots in a strange way:
The problem might be related to this: Paths: Stroking and Offsetting, but doesn't quite look the same.
There's a line between these paths; why?
(On my machine it looks like this: )
path.myshape {
stroke: gray;
fill: gray;
stroke-opacity:0.5;
fill-opacity:0.5;
}
<svg width="120px" height="120px" viewBox="0 0 120 120">
<path class="myshape" d="M0 0 L100 100 L100 0" />
<path class="myshape" d="M0 0 L100 100 L0 100" />
</svg>
A similar issue happens even without the stroke (it's harder to see but it's still there). I am confused why this is happening; if I have two triangles that are halves of a square, why don't I just see a square?
Is there a way to prevent this?
(On my machine it looks like this: )
path.myshape {
stroke: none;
fill: gray;
fill-opacity:0.5;
}
<svg width="120px" height="120px" viewBox="0 0 120 120">
<path class="myshape" d="M0 0 L100 100 L100 0" />
<path class="myshape" d="M0 0 L100 100 L0 100" />
</svg>
More realistic example (where I'm trying to get rid of the lines between triangles that have nearly the same fill/stroke attributes):
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// add the graph canvas to the body of the webpage
var svg = d3.select("div#plot1").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var axis = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var xsc = d3.scaleLinear()
.domain([-2, 2]) // the range of the values to plot
.range([ 0, width ]); // the pixel range of the x-axis
var ysc = d3.scaleLinear()
.domain([-2, 2])
.range([ height, 0 ]);
var closedLine = d3.line()
.x(function(d){ return xsc(d[0]); })
.y(function(d){ return ysc(d[1]); })
.curve(d3.curveLinearClosed);
function attrfunc(f,attr) {
return function(d) {
return f(d[attr]);
};
}
function doit(data)
{
var items = axis.selectAll("path.item")
.data(data);
items.enter()
.append("path")
.attr("class", "item")
.merge(items)
.attr("d", attrfunc(closedLine, "xy"))
.attr("stroke", "gray")
.attr("stroke-width", 1)
.attr("stroke-opacity", function(d) { return 1-d.age;})
.attr("fill", "gray")
.attr("fill-opacity", function(d) {return 1-d.age;});
items.exit().remove();
}
var state = {
t: 0,
theta: 0,
omega: 0.5,
A: 1.0,
N: 60,
history: []
}
d3.timer(function(elapsed)
{
var S = state;
if (S.history.length > S.N)
S.history.shift();
var dt = Math.min(0.1, elapsed*1e-3);
S.t += dt;
S.theta += S.omega * dt;
var sample = {
t: S.t,
x: S.A*(Math.cos(S.theta)+0.1*Math.cos(6*S.theta)),
y: S.A*(Math.sin(S.theta)+0.1*Math.sin(6*S.theta))
}
S.history.push(sample);
// Create triangular regions
var data = [];
for (var k = 0; k < S.history.length-1; ++k)
{
var pt1 = S.history[k];
var pt2 = S.history[k+1];
data.push({age: (S.history.length-1-k)/S.N,
xy:
[[0,0],
[pt1.x,pt1.y],
[pt2.x,pt2.y]]
});
}
doit(data);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.8.0/d3.min.js"></script>
<div id="plot1">
</div>