Multi donut graph with curved borders - javascript

I am trying to create a doughnut graph with curved ends. This graph has multiple series in it.
I have also found a fiddle which is similar to my needs: jsfiddle.net/freezystem/henr4ozn/
But this is in v3 and I have been trying to convert it to v4, but failing to render the graph.
As per the comments and complexity I have dropped the conversion of above jsfiddle to v4 and I am trying to convert this jsfiddle.net/minnie_mouse/033jrrz8/ to v4
But still I am not getting the same rendered image.
const d3 = this.d3;
const arcTween = function (transition, newAngle, arc) {
transition.attrTween('d', function (d) {
const interpolate = d3.interpolate(d.endAngle, newAngle);
return function (t) {
d.endAngle = interpolate(t);
return arc(d);
};
});
};
const createCircle = function (svg, outerRadius, innerRadius, color, percent) {
debugger;
const ratio = percent / 100;
const arcBackground = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)
.startAngle(0)
.endAngle(2 * Math.PI);
const pathBackground = svg.append('path')
.attr('d', arcBackground)
.style({
fill: '#585e65',
opacity: .2
});
const arcForeground = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)
.cornerRadius(20)
.startAngle(-0.05);
const pathForeground = svg.append('path')
.datum({ endAngle: 0 })
.attr('d', arcBackground)
.style({
fill: color
}).append('text')
.attr('transform', 'translate(10,10)')
.text('hi')
.attr('fill', 'white')
.attr('font-size', '20px');
pathForeground.transition()
.duration(1500)
.ease('elastic')
.call(arcTween, ((2 * Math.PI)) * ratio, arcForeground);
const chart = { path: pathForeground, arc: arcForeground };
return chart;
};
const addStartImage = function (svg, percent, outerRadius) {
svg.append('text')
.text(percent + '%')
.attr('font-size', '10px')
.attr('font-weight', 'bold')
.attr('fill', 'white')
.attr({
width: 20,
height: 20,
transform: 'translate(4,' + (-outerRadius + 20) + ')'
});
};
const w = 300, h = 300;
let outerRadius = (w / 2);
const width = 30, gap = 14;
const innerRadius = outerRadius - 30;
const color = ['#e90b3a', '#a0ff03', '#1ad5de'];
const svg = d3.select('#chart')
.append('svg')
.attr('width', w)
.attr('height', h)
.attr('class', 'shadow')
.append('g')
.attr('transform', 'translate(' + w / 2 + ',' + h / 2 + ')'
);
const circles = [
{ name: 'activity1', percent: 50, color: '#3e4eb7' },
{ name: 'activity2', percent: 66, color: '#50a7ff' }];
for (let i = 0; i < circles.length; ++i) {
if (i > 0) {
outerRadius = innerRadius - gap;
}
circles[i]['chart'] = createCircle(svg, outerRadius, innerRadius, circles[i].color, circles[i].percent);
addStartImage(svg, circles[i].percent, outerRadius);
}

Atlast I got it to run successfully.Here is the code
const d3 = this.d3;
const arcTween = function (transition, newAngle, arc) {
transition.attrTween('d', function (d) {
const interpolate = d3.interpolate(d.endAngle, newAngle);
return function (t) {
d.endAngle = interpolate(t);
return arc(d);
};
});
};
const createCircle = function (svg, outerRadius, innerRadius, color, percent) {
const ratio = percent / 100;
const arcBackground = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)
.startAngle(0)
.endAngle(2 * Math.PI);
const pathBackground = svg.append('path')
.attr('d', arcBackground)
.style({
fill: '#585e65',
opacity: .2
});
const arcForeground = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)
.cornerRadius(20)
.startAngle(-0.05);
const pathForeground = svg.append('path')
.datum({ endAngle: 0 })
.attr('d', arcBackground)
.style('fill', color);
pathForeground.append('text')
.attr('transform', 'translate(10,10)')
.text('hi')
.attr('fill', 'white')
.attr('font-size', '20px');
pathForeground.transition()
.duration(2000)
.transition()
.ease(d3.easeElastic)
.call(arcTween, ((2 * Math.PI)) * ratio, arcForeground);
const chart = { path: pathForeground, arc: arcForeground };
return chart;
};
const addStartImage = function (svg, percent, outerRadius) {
svg.append('text')
.text(percent + '%')
.attr('font-size', '10px')
.attr('font-weight', 'bold')
.attr('fill', 'white')
.attr({
width: 20,
height: 20,
transform: 'translate(4,' + (-outerRadius + 20) + ')'
});
};
const w = 300, h = 300;
let outerRadius = (w / 2);
const width = 30, gap = 14;
const innerRadius = outerRadius - 30;
const color = ['#e90b3a', '#a0ff03', '#1ad5de'];
const svg = d3.select('#chart')
.append('svg')
.attr('width', w)
.attr('height', h)
.attr('class', 'shadow')
.append('g')
.attr('transform', 'translate(' + w / 2 + ',' + h / 2 + ')'
);
const circles = [
{ name: 'activity1', percent: 50, color: '#3e4eb7' },
{ name: 'activity2', percent: 66, color: '#50a7ff' }];
for (let i = 0; i < circles.length; ++i) {
if (i > 0) {
outerRadius = innerRadius - gap;
}
circles[i]['chart'] = createCircle(svg, outerRadius, innerRadius, circles[i].color, circles[i].percent);
addStartImage(svg, circles[i].percent, outerRadius);
}

Related

Dynamically Create Arrows between Circles (D3)

I have a d3 (v7) visualization where I have a variable number of circles being drawn on the screen depending on my data set.
How can I get arrows connecting these circles? I'm trying to follow this guide: https://observablehq.com/#harrylove/draw-an-arrow-between-circles-with-d3-links
However, this is only for a set number of circles (2) and I will have a variable number of them depending on my dataset.
Below is my current d3 code that draws circles:
var svgContainer = d3.select("body")
.append("svg")
.attr("width", 800)
.attr("height", 200);
var circles = svgContainer.selectAll("circle")
.data(nodeObjs)
.enter()
.append("circle");
circles
.attr("cx", function (d, i) {return i * 100 + 30})
.attr("cy", 60)
.attr("r", 30)
.style("fill", "steelblue");
To make the observable example dynamic there are a few factors to take into account:
you need 2 link functions; 1 for horizontal and 1 for vertical - below I have linkH and linkV instead of just link
the link function doesn't need to be called immediately so lose the ({ source: linkSource, target: linkTarget}); - you are going to need an array of links instead
some choice between linkH and linkV - you can test if the x-gap is greater than the y-gap between two circles and choose a horizontal link; and vice versa
in the example I've made the horizontal vs vertical decision in the link creation; then call linkH or linkV in the .attr("d", ...) section
in the case that the arrow runs right to left or top to bottom you need to reverse the sign on the adjustment of the link x and y
See the working example below:
const svgWidth = 480;
const svgHeight = 180;
const svg = d3.select("body")
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight);
// Define the arrowhead marker variables
const markerBoxWidth = 8;
const markerBoxHeight = 8;
const refX = markerBoxWidth / 2;
const refY = markerBoxHeight / 2;
const markerWidth = markerBoxWidth / 2;
const markerHeight = markerBoxHeight / 2;
const arrowPoints = [[0, 0], [0, 8], [8, 4]];
// Add the arrowhead marker definition to the svg element
svg
.append("defs")
.append("marker")
.attr("id", "arrow")
.attr("viewBox", [0, 0, markerBoxWidth, markerBoxHeight])
.attr("refX", refX)
.attr("refY", refY)
.attr("markerWidth", markerBoxWidth)
.attr("markerHeight", markerBoxHeight)
.attr("orient", "auto-start-reverse")
.append("path")
.attr("d", d3.line()(arrowPoints))
.attr("stroke", "black");
// horizontal link
const linkH = d3
.linkHorizontal()
.x(d => d.x)
.y(d => d.y);
// vertical link
const linkV = d3
.linkVertical()
.x(d => d.x)
.y(d => d.y);
// circle data
const n = (Math.floor(Math.random() * 12) * 2) + 2;
const circleRadius = 10;
const nodes = [];
const links = [];
for (let i=0; i<n; i++) {
nodes.push({
x: Math.floor(Math.random() * (svgWidth - 20)) + 20,
y: Math.floor(Math.random() * (svgHeight - 20)) + 20,
r: circleRadius
});
}
for (let i=0; i<n; i+=2) {
const xdelta = Math.abs(nodes[i + 1].x - nodes[i].x);
const ydelta = Math.abs(nodes[i + 1].y - nodes[i].y);
links.push({
source: { x: nodes[i].x, y: nodes[i].y },
target: { x: nodes[i + 1].x, y: nodes[i + 1].y },
arrowDirection: ydelta >= xdelta ? "V" : "H"
});
}
const circles = svg.selectAll(".node")
.data(nodes)
.enter()
.append("circle")
.attr("class", "node");
circles
.attr("cx", (d, i) => d.x)
.attr("cy", (d, i) => d.y)
.attr("r", d => d.r);
const arrows = svg.selectAll(".arrow")
.data(links)
.enter()
.append("path")
.attr("class", "arrow");
arrows
.attr("d", (d, i) => {
let reversed;
if (d.arrowDirection === "H") {
reversed = d.source.x < d.target.x ? 1 : -1;
d.source.x += circleRadius * reversed;
d.target.x -= (circleRadius + markerWidth) * reversed;
return linkH(d);
} else {
reversed = d.source.y > d.target.y ? 1 : -1;
d.source.y -= circleRadius * reversed;
d.target.y += (circleRadius + markerWidth) * reversed;
return linkV(d);
}
})
.attr("marker-end", "url(#arrow)");
.node {
fill: green;
stroke: steelblue;
}
.arrow {
stroke: black;
fill: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>

d3js : how to put circles at the end of an arc

I'm trying to create a donut chart in d3js where each arc has a circle at its end.
Circle's edge must fit on arc's one.
I tried both by appending a circle and a circle wrapped in marker but with no succes.
Trying to append a marker seems to be the closest solution to the desired one but I can't help the marker oveflowing the arc edges.
Code:
var data = [
{
name: "punti",
count: 3,
color: "#fff000"
},
{
name: "max",
count: 7,
color: "#f8b70a"
}
];
var totalCount = data.reduce((acc, el) => el.count + acc, 0);
var image_width = 32;
var image_height = 32;
var width = 540,
height = 540,
radius = 200,
outerRadius = radius - 10,
innerRadius = 100;
var cornerRadius = innerRadius;
var markerRadius = (outerRadius - innerRadius) / 2;
var arc = d3
.arc()
.outerRadius(outerRadius)
.innerRadius(innerRadius)
.cornerRadius(cornerRadius);
var pie = d3
.pie()
.sort(null)
.value(function(d) {
return d.count;
});
var svg = d3
.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var pieData = pie(data);
var g = svg
.selectAll(".arc")
.data(pieData)
.enter()
.append("g");
var path = g
.append("path")
.attr("d", arc)
.style("fill", function(d, i) {
return d.data.color;
});
var marker = svg
.append("defs")
.append("marker")
.attr("id", "endmarker")
.attr("overflow", "visible")
.append("circle")
.attr("cy", 0)
.attr("cx", 0)
.attr("r", markerRadius)
.attr("fill", "red");
g.attr("marker-end", "url(#endmarker)");
g
.append("circle")
.attr("cx", function(d) {
let path = d3.select(this.parentNode);
var x = arc.centroid(d)[0];
return x;
})
.attr("cy", function(d) {
var y = arc.centroid(d)[1];
console.log(d3.select(this).attr("cx"));
return y;
})
.attr("fill", d => d.data.color)
.attr("stroke", "black")
.attr("r", (outerRadius - innerRadius) / 2);
codepen here
Thanks to anyone who will help!
Assuming that you want your output like:
I found some code from Mike Bostock's Block here which shows how to add circles to rounded Arc Corners.
I adapted the following code for you which performs quite a bit of complex mathematics.
var cornerRadius = (outerRadius - innerRadius)/2;
svg.append("g")
.style("stroke", "#555")
.style("fill", "none")
.attr("class", "corner")
.selectAll("circle")
.data(d3.merge(pieData.map(function(d) {
return [
{angle: d.startAngle + d.padAngle / 2, radius: outerRadius - cornerRadius, start: +1},
{angle: d.endAngle - d.padAngle / 2, radius: outerRadius - cornerRadius, start: -1},
];
})))
.enter().append("circle")
.attr("cx", function(d) { return d.start * cornerRadius * Math.cos(d.angle) + Math.sqrt(d.radius * d.radius - cornerRadius * cornerRadius) * Math.sin(d.angle); })
.attr("cy", function(d) { return d.start * cornerRadius * Math.sin(d.angle) - Math.sqrt(d.radius * d.radius - cornerRadius * cornerRadius) * Math.cos(d.angle); })
.attr("r", cornerRadius);
Full snippet showing the output:
<div id="chart"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.12.0/d3.min.js"></script>
<script>
var data = [
{
name: "punti",
count: 3,
color: "#fff000"
},
{
name: "max",
count: 7,
color: "#f8b70a"
},
];
var totalCount = data.reduce((acc, el) => el.count + acc, 0);
var image_width = 32;
var image_height = 32;
var width = 540,
height = 540,
radius = 200,
outerRadius = radius - 10,
innerRadius = 100;
var cornerRadius = innerRadius;
var markerRadius = (outerRadius - innerRadius) / 2;
var arc = d3
.arc()
.outerRadius(outerRadius)
.innerRadius(innerRadius)
.cornerRadius(cornerRadius);
var pie = d3
.pie()
.sort(null)
.value(function(d) {
return d.count;
});
var svg = d3
.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var pieData = pie(data);
var g = svg
.selectAll(".arc")
.data(pieData)
.enter()
.append("g");
var path = g
.append("path")
.attr("d", arc)
.style("fill", function(d, i) {
return d.data.color;
});
var cornerRadius = (outerRadius - innerRadius)/2;
svg.append("g")
.style("stroke", "#555")
.style("fill", "none")
.attr("class", "corner")
.selectAll("circle")
.data(d3.merge(pieData.map(function(d) {
return [
{angle: d.startAngle + d.padAngle / 2, radius: outerRadius - cornerRadius, start: +1},
{angle: d.endAngle - d.padAngle / 2, radius: outerRadius - cornerRadius, start: -1},
];
})))
.enter().append("circle")
.attr("cx", function(d) { return d.start * cornerRadius * Math.cos(d.angle) + Math.sqrt(d.radius * d.radius - cornerRadius * cornerRadius) * Math.sin(d.angle); })
.attr("cy", function(d) { return d.start * cornerRadius * Math.sin(d.angle) - Math.sqrt(d.radius * d.radius - cornerRadius * cornerRadius) * Math.cos(d.angle); })
.attr("r", cornerRadius);
</script>

Set max height to an already responsive D3 donut chart

I have created a donut chart and I am trying to make it responsive. I want the height to have a maximum value. If the window size changes, the donut changes attributes (width and height). Now, I want to set a maximum height value and off course I need the ratio to remain the same. Note that I want the donut chart to be horizontally aligned (same margin from the right and left of the screen).
I have tried changing the values in the width by using the window.innerWidth and innerHeight with no success. I have also tried adding a max attribute for the height in the D3 part of my code.
var dataset = {
numbers: [3200, 5400, 8600]
};
var width = 500,
height = 500,
radius = Math.min(width, height) / 2;
var enterClockwise = {
startAngle: 0,
endAngle: 0
};
var enterAntiClockwise = {
startAngle: Math.PI * 2,
endAngle: Math.PI * 2
};
//var color = d3.scale.category20();
var color = d3.scale.ordinal().range([d3.rgb("#c7003b"), d3.rgb('#000'), d3.rgb('#ccc'),d3.rgb('transparent')])
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 80)
.outerRadius(radius - 40);
var arcThin = d3.svg.arc()
.innerRadius(radius - 65)
.outerRadius(radius - 55);
var svg = d3.select('#Donut-chart').append('svg')
.attr('id', 'Donut-chart-render')
.attr("width", '100%')
.attr("height", '100%')
.attr('viewBox', (-width / 2) + ' ' + (-height / 2) + ' ' + width + ' ' + height)
.attr('preserveAspectRatio', 'xMinYMin').append("g").attr("class", "parent");
var angleData = pie(dataset.numbers);
angleData[1].startAngle = 0;
angleData[1].endAngle = -angleData[1].endAngle + angleData[0].endAngle;
angleData[2].startAngle = angleData[0].endAngle;
angleData[2].endAngle = (2*Math.PI) + angleData[1].endAngle;
var path = svg.selectAll("path")
.data(angleData)
.enter().append("path")
.attr("fill", function (d, i) { return color(i); })
.attr("d", function(d){
return arc(enterClockwise);
})
.each(function (d) {
this._current = {
data: d.data,
value: d.value,
startAngle: enterClockwise.startAngle,
endAngle: enterClockwise.endAngle
}
});
path.transition()
.duration(750)
.attrTween("d", arcTween);
function createChart() {
path = path.data(pie(dataset[this.value]));
path.enter().append("path")
.attr("fill", function (d, i) {
return color(i);
})
.attr("d", arc(enterAntiClockwise))
.each(function (d) {
this._current = {
data: d.data,
value: d.value,
startAngle: enterAntiClockwise.startAngle,
endAngle: enterAntiClockwise.endAngle
};
});
}
function arcTween(a, j) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function (t) {
return (j === (dataset.numbers.length - 1)) ? arcThin(i(t)) : arc(i(t));
};
}
function type(d) {
d.value = +d.value;
return d;
}
#import url(https://fonts.googleapis.com/css?family=Karla);body{font-family:Karla,sans-serif;margin:auto;position:relative}.text{text-anchor:middle;color:#000;font-size:1.7em;font-weight:700;text-transform:uppercase}#legend{align-items:center;border-radius:5px;display:flex;height:0%;justify-content:space-around;width:95%;font-size:25px}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.0.0/d3.min.js"></script>
<div id="Donut-chart"></div>
Remove width, height and preserveAspectRatio attributes from SVG element and set max-height on it via CSS. Like so:
var dataset = {
numbers: [3200, 5400, 8600]
};
var width = 500,
height = 500,
radius = Math.min(width, height) / 2;
var enterClockwise = {
startAngle: 0,
endAngle: 0
};
var enterAntiClockwise = {
startAngle: Math.PI * 2,
endAngle: Math.PI * 2
};
//var color = d3.scale.category20();
var color = d3.scale.ordinal().range([d3.rgb("#c7003b"), d3.rgb('#000'), d3.rgb('#ccc'),d3.rgb('transparent')])
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 80)
.outerRadius(radius - 40);
var arcThin = d3.svg.arc()
.innerRadius(radius - 65)
.outerRadius(radius - 55);
var svg = d3.select('#Donut-chart').append('svg')
.attr('id', 'Donut-chart-render')
// .attr("width", '100%')
// .attr("height", '100%')
.attr('viewBox', (-width / 2) + ' ' + (-height / 2) + ' ' + width + ' ' + height)
//.attr('preserveAspectRatio', 'xMinYMin')
.append("g").attr("class", "parent");
var angleData = pie(dataset.numbers);
angleData[1].startAngle = 0;
angleData[1].endAngle = -angleData[1].endAngle + angleData[0].endAngle;
angleData[2].startAngle = angleData[0].endAngle;
angleData[2].endAngle = (2*Math.PI) + angleData[1].endAngle;
var path = svg.selectAll("path")
.data(angleData)
.enter().append("path")
.attr("fill", function (d, i) { return color(i); })
.attr("d", function(d){
return arc(enterClockwise);
})
.each(function (d) {
this._current = {
data: d.data,
value: d.value,
startAngle: enterClockwise.startAngle,
endAngle: enterClockwise.endAngle
}
});
path.transition()
.duration(750)
.attrTween("d", arcTween);
function createChart() {
path = path.data(pie(dataset[this.value]));
path.enter().append("path")
.attr("fill", function (d, i) {
return color(i);
})
.attr("d", arc(enterAntiClockwise))
.each(function (d) {
this._current = {
data: d.data,
value: d.value,
startAngle: enterAntiClockwise.startAngle,
endAngle: enterAntiClockwise.endAngle
};
});
}
function arcTween(a, j) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function (t) {
return (j === (dataset.numbers.length - 1)) ? arcThin(i(t)) : arc(i(t));
};
}
function type(d) {
d.value = +d.value;
return d;
}
#Donut-chart svg {
max-height: 300px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.0.0/d3.min.js"></script>
<div id="Donut-chart"></div>

animate arc start angle with d3.js

I am trying to animate the start angle of the arc using D3.js
Any help or link for reference would do.
I have tried the below:
http://jsfiddle.net/87e3d4tj/
d3.select('#my-path').datum({
startAngle: endAngle,
endAngle: ( 90 * (Math.PI/180) )
})
.transition()
.duration(duration)
.attrTween('d', d => {
var interpolate = d3.interpolate(d.startAngle, d.endAngle);
return function(t) {
d.endAngle = endAngle;
d.startAngle = interpolate(t);
return arc(d);
};
});
i have tried the below hope you are looking for the same.
https://jsfiddle.net/debasish007/eu7xo4mL/
var width = 400,
height = 400,
τ = (Math.PI/180);
var arc = d3.arc()
.innerRadius(130)
.outerRadius(150);
var svg = d3.select("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
var foreground = svg.append("path")
.datum({
endAngle: 90 * τ,
startAngle: -90 * τ
})
.style("fill", "blue")
.attr("d", arc);
setTimeout(function () {
foreground.transition()
.duration(750)
.call(arcTween, -30 * τ, 90 * τ);
}, 1500);
function arcTween(transition, newStartAngle, newFinishAngle) {
transition.attrTween("d", function (d) {
var interpolateStart = d3.interpolate(d.startAngle, newStartAngle);
return function (t) {
d.endAngle = newFinishAngle;
d.startAngle = interpolateStart(t);
return arc(d);
};
});
}

D3 - smooth radial chart transition animation

I've prepared D3 Radial Chart component to show percentage value of some target. It would be great to add smoothy transition effect when start drawing foreground circle from 0 to chartPercentage (eg. 70%).
The question is - how to prepare transition / delay / duration effect with code which is attached below ?
Second idea which I also want to implement is to count value inside of the circle (radial-content) with animation from 0 to chartValue. How to prepare such solution?
Thank you !
const chartPercentage = 70;
const chartValue = 1.1242
const radius = 75;
const border = 7;
const padding = 0;
const width = 400;
const height = 400;
const twoPi = Math.PI * 2;
const boxSize = (radius + padding) * 2;
let svg;
function setArc() {
return d3.arc()
.startAngle(0)
.innerRadius(radius)
.outerRadius(radius - border)
.cornerRadius(50);
}
function draw() {
svg = d3.select(".chart").append("svg")
.attr('width', width)
.attr('height', height);
svg.append("foreignObject")
.attr("width", boxSize)
.attr("height", boxSize)
.append("xhtml:div")
.attr('class', 'radial-wrapper')
.html(`<div class="radial-content">${chartValue}</div>`);
const field = svg.append('g')
.attr('transform', 'translate(' + boxSize / 2 + ',' + boxSize / 2 + ')');
const meter = field.append('g')
.attr('class', 'progress-meter');
const background = meter.append("path")
.datum({endAngle: twoPi})
.attr('class', 'background')
.attr('fill', '#2D2E2F')
.attr('fill-opacity', 0.1)
.attr("d", setArc());
const foreground = meter.append("path")
.datum({endAngle: (chartPercentage/100) * twoPi})
.attr('class', 'foreground')
.attr('fill', 'red')
.attr('fill-opacity', 1)
.attr('d', setArc());
}
draw();
body { margin:30px;position:fixed;top:0;right:0;bottom:0;left:0; }
.radial-wrapper{ display: flex; align-items: center; justify-content: center;width: 100%; height: 100%;}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div class="chart"></div>
I rewrote your code. When you need to animate some attribute, you should use attrTween not attr method.
const chartPercentage = 70;
const chartValue = 1.1242
const radius = 75;
const border = 7;
const padding = 0;
const width = 400;
const height = 400;
const twoPi = Math.PI * 2;
const boxSize = (radius + padding) * 2;
let svg;
const setArc = d3.arc()
.startAngle(0)
.innerRadius(radius)
.outerRadius(radius - border)
.cornerRadius(50);
const arcParams = {};
function draw() {
svg = d3.select(".chart").append("svg")
.attr('width', width)
.attr('height', height);
svg.append("foreignObject")
.attr("width", boxSize)
.attr("height", boxSize)
.append("xhtml:div")
.attr('class', 'radial-wrapper')
.html(`<div class="radial-content"></div>`);
const field = svg.append('g')
.attr('transform', 'translate(' + boxSize / 2 + ',' + boxSize / 2 + ')');
const meter = field.append('g')
.attr('class', 'progress-meter');
const background = meter
.append("path")
.attr('class', 'background')
.attr('fill', '#2D2E2F')
.attr('fill-opacity', 0.1)
.attr("d", setArc({ endAngle: twoPi }));
const foreground = meter
.append("path")
.transition()
.ease(d3.easeBounce)
.duration(1500)
.attr('class', 'foreground')
.attr('fill', 'red')
.attr('fill-opacity', 1)
.attrTween("d", function() {
return arcTween({ endAngle: 0 }, chartPercentage/100 )
})
}
function arcTween(d, new_score) {
var new_startAngle = 0
var new_endAngle = new_startAngle + new_score * 2 * Math.PI
var interpolate_start = d3.interpolate(d.startAngle, new_startAngle)
var interpolate_end = d3.interpolate(d.endAngle, new_endAngle)
return function(t) {
d.endAngle = interpolate_end(t)
d3.select('.radial-content')
.text((d.endAngle / new_endAngle * chartValue).toFixed(4));
return setArc(d)
}
}
draw();
body {
margin: 30px;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.radial-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div class="chart"></div>
Make a function for attribute tween.
function arcTween(a) {
var j = {"endAngle":0};//start angle
var i = d3.interpolateObject(j, a);
return function(t) {
d3.select(".radial-content").text(d3.format(".4n")(chartValue*t));
return arc(i(t));
};
}
In the above function
d3.select(".radial-content").text(d3.format(".4n")(chartValue*t));
this will change the text(and output it in the format) in the radial content as the transition runs.
now add the tween function to the foreground path.
const foreground = meter.append("path")
.datum({
endAngle: (chartPercentage / 100) * twoPi
})
.attr('class', 'foreground')
.attr('fill', 'red')
.attr('fill-opacity', 1)
.transition().duration(750).attrTween("d", arcTween);
working code here

Categories