Dynamically update D3 half donut chart - how? - javascript

I have a d3.js half donut chart showing a progress in %, like this:
The chart gets the json data from a php script with jQuery getJSON function. What I would like is to update the chart every minute, but without having to do a setInterval on the whole script. Is it possible just to replace the chart data every minute?
My chart script:
var w = 300,
h = 300,
outerRadius = (w / 2) - 10,
innerRadius = 85;
var percent = Math.round(json.bhp * 100);
var ratio = percent / 100;
var pie = d3.layout.pie()
.value(function(d){return d})
.sort(null);
var color = ['#ececec','#f6c717','#888888'],
colorOld = '#F00',
colorNew = '#f6c717';
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)
.startAngle(0)
.endAngle(Math.PI);
var arcLine = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)
.startAngle(0);
var svg = d3.select('#bhp')
.append('svg')
.attr({
width :w,
height:160,
})
.append('g')
.attr({
transform:'translate('+w/2+','+h/2+')'
});
var path = svg.append('path')
.attr({
d :arc,
transform:'rotate(-90)'
})
.attr({
'stroke-width':"1",
stroke :"#666666"
})
.style({
fill:color[0]
});
var pathForeground = svg.append('path')
.datum({endAngle:0})
.attr({
d :arcLine,
transform:'rotate(-90)'
})
.style({
fill: function (d, i) {
return color[1];
}
});
var middleCount_bhp = svg.append('text')
.datum(0)
.text(function(d){
return d;
})
.attr({
class :'middleText',
'text-anchor':'middle',
dy :0,
dx :5
})
.style({
fill :d3.rgb('#000000'),
'font-size':'60px'
});
var oldValue = 0;
var arcTween = function(transition, newValue, oldValue) {
transition.attrTween('d', function (d) {
var interpolate = d3.interpolate(d.endAngle, ((Math.PI)) * (newValue / 100));
var interpolateCount = d3.interpolate(oldValue, newValue);
return function (t) {
d.endAngle = interpolate(t);
middleCount_bhp.text(Math.floor(interpolateCount(t)) + '%');
return arcLine(d);
};
});
};
pathForeground.transition()
.delay(200)
.duration(750)
.ease('cubic')
.call(arcTween, percent, oldValue);

Related

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>

D3JS: Animate a 2 arc donut

I'm kind of new to D3JS and I've made a 2 arc Donut (my data has only two values). I'm trying to figure out how to animate them so that the largest path get "self drawn" first and then the smallest one gets self drawn as well.
I know this can be achieved setting the start angle to 0 and then use the D3js attrTween() and play with the desired final angle. but since I can not declare the final angle by myself and it depends on the data, and due to my very little knowledge about djse I havenĀ“t been able to achieve it.
This is my JS code:
var width = 100,
height = 100,
radius = (Math.min(width, height) / 2);
function drawDonut(dataa, divchart) {
var sym = "%"
var color = ["#00338D", "#BC204B"];
var pie = d3.pie()
.value(function(d) {
return d
})(dataa);
var arc = d3.arc()
.outerRadius(radius - 10)
.innerRadius(radius - (radius / 1.9));
var labelArc = d3.arc()
.outerRadius(radius - 31)
.innerRadius(radius - 31);
var svg = d3.select(divchart)
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + 50 + "," + 50 + ")");
var g = svg.selectAll("arc")
.data(pie)
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.data(color)
.style("fill", function(d) {
return d
});
g.append("g")
.attr("transform", "translate(-17,-17) scale(0.7)")
.html(myGroup);
}
Using the data from your previous post and attrTween as a reference, here's a snippet with the large-path-first animation applied to
var myGroup = '<g><path class="st0" d="M15.6,10.9c1.3,0,2.4-1.2,2.4-2.7c0-1.5-1.1-2.7-2.4-2.7c-1.3,0-2.4,1.2-2.4,2.7C13.2,9.7,14.2,10.9,15.6,10.9L15.6,10.9z"/><path class="st0" d="M18.6,11.6h-1.2l-1.8,5.5l-1.8-5.5h-1.2c-1.3,0-2.4,1.2-2.4,2.7v13h2.4l1.2,16.4h3.6l1.2-16.4H21v-13C21,12.8,19.9,11.6,18.6,11.6L18.6,11.6z"/><path class="st0" d="M31.9,10.9c1.3,0,2.4-1.2,2.4-2.7c0-1.5-1.1-2.7-2.4-2.7c-1.3,0-2.4,1.2-2.4,2.7C29.5,9.7,30.6,10.9,31.9,10.9L31.9,10.9z"/><path class="st0" d="M39.8,25.2l-3.6-11.6c0,0-0.6-2-2.4-2h-3.6c-1.8,0-2.4,2-2.4,2l-3.6,11.6l1.2,0.7l4.2-9.5l-3.6,14.3h3.6l1.2,13h2.4l1.2-13H38l-3.6-14.3l4.2,9.5L39.8,25.2L39.8,25.2z"/></g>';
var data1 = [4, 96];
var data2 = [1, 99];
var data3 = [16, 84];
var data4 = [12, 88];
var data5 = [29, 71];
var data6 = [15, 85];
var data7 = [12, 88];
var data8 = [10, 90];
var width = 100,
height = 100,
radius = (Math.min(width, height) / 2);
function drawDonut(data, divchart) {
var sym = "%"
var color = ["#BC204B","#00338D"];
// sort data
data = data.sort(function (a, b) { return b-a; });
var pie = d3.pie()
.value(function(d) {
return d
})(data);
var arc = d3.arc()
.outerRadius(radius - 10)
.innerRadius(radius - (radius / 1.9));
var labelArc = d3.arc()
.outerRadius(radius - 31)
.innerRadius(radius - 31);
var svg = d3.select(divchart)
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + 50 + "," + 50 + ")");
var g = svg.selectAll("arc")
.data(pie)
.enter().append("g")
.attr("class", "arc");
g.append("path")
.style("fill", function(d, i) {
return color[i];
})
.transition().delay(function (d, i) { return i*800}).duration(800)
.attrTween("d", arcTween)
function arcTween(d) {
var i = d3.interpolate(d.startAngle, d.endAngle);
return function (t) {
d.endAngle = i(t);
return arc(d);
}
}
g.append("g")
.attr("transform", "translate(-17,-17) scale(0.7)")
.html(myGroup);
}
drawDonut(data1, "#pie1")
drawDonut(data2, "#pie2")
drawDonut(data3, "#pie3")
drawDonut(data4, "#pie4")
drawDonut(data5, "#pie5")
drawDonut(data6, "#pie6")
drawDonut(data7, "#pie7")
drawDonut(data8, "#pie8")
div {
display: inline;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="pie1"></div>
<div id="pie2"></div>
<div id="pie3"></div>
<div id="pie4"></div>
<div id="pie5"></div>
<div id="pie6"></div>
<div id="pie7"></div>
<div id="pie1"></div>
<div id="pie8"></div>
Relevant changes:
AttrTween function
function arcTween(d) {
var i = d3.interpolate(d.startAngle, d.endAngle);
return function (t) {
d.endAngle = i(t);
return arc(d);
}
}
Use the above for the paths along with a delay in the transition which btw is used to animate the paths in sequence.
g.append("path")
.style("fill", function(d, i) {
return color[i];
})
.transition().delay(function (d, i) { return i*800}).duration(800)
.attrTween("d", arcTween);
Sorted data in a descending order to draw the largest arc first.
// sort data
data = data.sort(function (a, b) { return b-a; });
Based on the answer by Shashank I like to propose a modification
do not sort the data because the order looks important (d[0] == man, d[1] == woman) and the arcs are colored in order
// sort data
//data = data.sort(function (a, b) { return b-a; });
the pie generator should also not sort the data based on value
var pie = d3.pie()
.sortValues(null) // both null means NO sort
.value(function(d) {
return d
})(data);
in Shashanks answer both parts are animated with easeCubic and each part (small or large) takes 800ms. To get a constant angular speed animation we must set the delay and duration based on the startAngle and the endAngle of the part. And the transition must be set to easeLinear. For the demo I have set the complete animation to 5000ms.
var msecPerRad = 5000 / (2*Math.PI);
g.append("path")
.style("fill", function(d, i) { return color[i]; })
.transition()
.ease(d3.easeLinear)
.delay(function (d) { return d.startAngle * msecPerRad;})
.duration(function (d) { return (d.endAngle - d.startAngle) * msecPerRad;})
.attrTween("d", arcTween);
Example with Linear easing
var myGroup = '<g><path class="st0" d="M15.6,10.9c1.3,0,2.4-1.2,2.4-2.7c0-1.5-1.1-2.7-2.4-2.7c-1.3,0-2.4,1.2-2.4,2.7C13.2,9.7,14.2,10.9,15.6,10.9L15.6,10.9z"/><path class="st0" d="M18.6,11.6h-1.2l-1.8,5.5l-1.8-5.5h-1.2c-1.3,0-2.4,1.2-2.4,2.7v13h2.4l1.2,16.4h3.6l1.2-16.4H21v-13C21,12.8,19.9,11.6,18.6,11.6L18.6,11.6z"/><path class="st0" d="M31.9,10.9c1.3,0,2.4-1.2,2.4-2.7c0-1.5-1.1-2.7-2.4-2.7c-1.3,0-2.4,1.2-2.4,2.7C29.5,9.7,30.6,10.9,31.9,10.9L31.9,10.9z"/><path class="st0" d="M39.8,25.2l-3.6-11.6c0,0-0.6-2-2.4-2h-3.6c-1.8,0-2.4,2-2.4,2l-3.6,11.6l1.2,0.7l4.2-9.5l-3.6,14.3h3.6l1.2,13h2.4l1.2-13H38l-3.6-14.3l4.2,9.5L39.8,25.2L39.8,25.2z"/></g>';
var data1 = [50, 50];
var data2 = [70, 30];
var data3 = [16, 84];
var data4 = [12, 88];
var data5 = [29, 71];
var data6 = [15, 85];
var data7 = [12, 88];
var data8 = [10, 90];
var width = 100,
height = 100,
radius = (Math.min(width, height) / 2);
function drawDonut(data, divchart) {
var sym = "%"
var color = ["#BC204B","#00338D"];
// sort data
//data = data.sort(function (a, b) { return b-a; });
var pie = d3.pie()
.sortValues(null) // both null means NO sort
.value(function(d) {
return d
})(data);
var arc = d3.arc()
.outerRadius(radius - 10)
.innerRadius(radius - (radius / 1.9));
var labelArc = d3.arc()
.outerRadius(radius - 31)
.innerRadius(radius - 31);
var svg = d3.select(divchart)
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + 50 + "," + 50 + ")");
var g = svg.selectAll("arc")
.data(pie)
.enter().append("g")
.attr("class", "arc");
var msecPerRad = 5000 / (2*Math.PI);
g.append("path")
.style("fill", function(d, i) { return color[i]; })
.transition()
.ease(d3.easeLinear)
.delay(function (d) { return d.startAngle * msecPerRad;})
.duration(function (d) { return (d.endAngle - d.startAngle) * msecPerRad;})
.attrTween("d", arcTween);
function arcTween(d) {
var i = d3.interpolate(d.startAngle, d.endAngle);
return function (t) {
d.endAngle = i(t);
return arc(d);
}
}
g.append("g")
.attr("transform", "translate(-17,-17) scale(0.7)")
.html(myGroup);
}
drawDonut(data1, "#pie1")
drawDonut(data2, "#pie2")
drawDonut(data3, "#pie3")
drawDonut(data4, "#pie4")
drawDonut(data5, "#pie5")
drawDonut(data6, "#pie6")
drawDonut(data7, "#pie7")
drawDonut(data8, "#pie8")
div {
display: inline;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="pie1"></div>
<div id="pie2"></div>
<div id="pie3"></div>
<div id="pie4"></div>
<div id="pie5"></div>
<div id="pie6"></div>
<div id="pie7"></div>
<div id="pie1"></div>
<div id="pie8"></div>
If you want the complete angle animation performed with an easeCubic it takes little more math. The main reason is that both arc segments are independent trasitions. This should work for more then 2 arc segments.
It only works for monotone increasing easing functions, not for things like bounce, elastic and back(in/out).
function easeInverse(ease) {
return function(e) {
var min = 0, max = 1;
while (max - min > 1e-3) {
var mid = (max + min) * 0.5;
emid = ease(mid);
if (emid > e) { max = mid; }
else { min = mid; }
}
return max;
}
}
var inverseCubic = easeInverse(d3.easeCubic);
var oneOver2Pi = 1.0 / (2*Math.PI);
var total_msec = 5000;
g.append("path")
.style("fill", function(d, i) { return color[i]; })
.transition()
.ease(d3.easeLinear)
.delay(function (d) { return total_msec * inverseCubic(d.startAngle * oneOver2Pi);})
.duration(function (d) { return total_msec * (inverseCubic(d.endAngle * oneOver2Pi) - inverseCubic(d.startAngle * oneOver2Pi));})
.attrTween("d", arcTween);
function arcTween(d) {
var i = d3.interpolate(inverseCubic(d.startAngle * oneOver2Pi), inverseCubic(d.endAngle * oneOver2Pi));
return function (t) {
d.endAngle = 2*Math.PI*d3.easeCubic(i(t));
return arc(d);
}
}
Example with Cubic easing for the full circle angle.
var myGroup = '<g><path class="st0" d="M15.6,10.9c1.3,0,2.4-1.2,2.4-2.7c0-1.5-1.1-2.7-2.4-2.7c-1.3,0-2.4,1.2-2.4,2.7C13.2,9.7,14.2,10.9,15.6,10.9L15.6,10.9z"/><path class="st0" d="M18.6,11.6h-1.2l-1.8,5.5l-1.8-5.5h-1.2c-1.3,0-2.4,1.2-2.4,2.7v13h2.4l1.2,16.4h3.6l1.2-16.4H21v-13C21,12.8,19.9,11.6,18.6,11.6L18.6,11.6z"/><path class="st0" d="M31.9,10.9c1.3,0,2.4-1.2,2.4-2.7c0-1.5-1.1-2.7-2.4-2.7c-1.3,0-2.4,1.2-2.4,2.7C29.5,9.7,30.6,10.9,31.9,10.9L31.9,10.9z"/><path class="st0" d="M39.8,25.2l-3.6-11.6c0,0-0.6-2-2.4-2h-3.6c-1.8,0-2.4,2-2.4,2l-3.6,11.6l1.2,0.7l4.2-9.5l-3.6,14.3h3.6l1.2,13h2.4l1.2-13H38l-3.6-14.3l4.2,9.5L39.8,25.2L39.8,25.2z"/></g>';
var data1 = [50, 50];
var data2 = [70, 30];
var data3 = [16, 84];
var data4 = [12, 88];
var data5 = [29, 71];
var data6 = [15, 85];
var data7 = [12, 88];
var data8 = [10, 90];
var width = 100,
height = 100,
radius = (Math.min(width, height) / 2);
function drawDonut(data, divchart) {
var sym = "%"
var color = ["#BC204B","#00338D"];
// sort data
//data = data.sort(function (a, b) { return b-a; });
var pie = d3.pie()
.sortValues(null) // both null means NO sort
.value(function(d) {
return d
})(data);
var arc = d3.arc()
.outerRadius(radius - 10)
.innerRadius(radius - (radius / 1.9));
var labelArc = d3.arc()
.outerRadius(radius - 31)
.innerRadius(radius - 31);
var svg = d3.select(divchart)
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + 50 + "," + 50 + ")");
var g = svg.selectAll("arc")
.data(pie)
.enter().append("g")
.attr("class", "arc");
function easeInverse(ease) {
return function(e) {
var min = 0, max = 1;
while (max - min > 1e-3) {
var mid = (max + min) * 0.5;
emid = ease(mid);
if (emid > e) { max = mid; }
else { min = mid; }
}
return max;
}
}
var inverseCubic = easeInverse(d3.easeCubic);
var oneOver2Pi = 1.0 / (2*Math.PI);
var total_msec = 5000;
g.append("path")
.style("fill", function(d, i) { return color[i]; })
.transition()
.ease(d3.easeLinear)
.delay(function (d) { return total_msec * inverseCubic(d.startAngle * oneOver2Pi);})
.duration(function (d) { return total_msec * (inverseCubic(d.endAngle * oneOver2Pi) - inverseCubic(d.startAngle * oneOver2Pi));})
.attrTween("d", arcTween);
function arcTween(d) {
var i = d3.interpolate(inverseCubic(d.startAngle * oneOver2Pi), inverseCubic(d.endAngle * oneOver2Pi));
return function (t) {
d.endAngle = 2*Math.PI*d3.easeCubic(i(t));
return arc(d);
}
}
g.append("g")
.attr("transform", "translate(-17,-17) scale(0.7)")
.html(myGroup);
}
drawDonut(data1, "#pie1")
drawDonut(data2, "#pie2")
drawDonut(data3, "#pie3")
drawDonut(data4, "#pie4")
drawDonut(data5, "#pie5")
drawDonut(data6, "#pie6")
drawDonut(data7, "#pie7")
drawDonut(data8, "#pie8")
div {
display: inline;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="pie1"></div>
<div id="pie2"></div>
<div id="pie3"></div>
<div id="pie4"></div>
<div id="pie5"></div>
<div id="pie6"></div>
<div id="pie7"></div>
<div id="pie1"></div>
<div id="pie8"></div>

Multi donut graph with curved borders

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);
}

Why the attrTween doesn't work

I trying to mimic an example from Mike Bostock Extending Arcs. My code is very similar to Mike's, but mines doesn't work like his.
Here below is the JavaScript code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Example</title>
<script src="static/d3.min.js"></script>
</head>
<body>
<script>
var Width = 500, Height = 400, innerRadius = 45, outerRadius = 100;
var colors = d3.scale.category10();
var svg = d3.select('body')
.append('svg')
.attr({ width: Width, height: Height });
var data = [40, 32, 35, 64, 83],
pieData = d3.layout.pie()(data)
var ArcGen = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)
.padAngle(0.02)
.startAngle(function(d){
return d.startAngle;
})
.endAngle(function(d){
return d.endAngle;
});
var group = svg.append('g')
.attr('transform', 'translate(' + [Width / 2.0, Height / 2.0] + ')');
var segment = group.selectAll('g')
.data(pieData)
.enter()
.append('g');
segment
.append('path')
.attr('d', ArcGen)
.attr('fill', function(d,i){ return colors(i); })
.on('mouseover', arcTween(outerRadius * 1.2, 0))
.on('mouseout', arcTween(outerRadius, 150));
function arcTween(oRadius, delay){
// closure function
return function(){
d3.select(this)
.transition()
.delay(delay)
.attrTween('d', function(d){
var i = d3.interpolate(d.outerRadius, oRadius);
return function(t){
d.outerRadius = i(t);
return ArcGen(d);
}
})
};
};
</script>
</body>
</html>
This routine is simple, no error no action either.
I have two questions:
If it's necessary to chain startAngle and endAngle attributes at every arc generator? I read some pros' code, such as Mike Bostock's, don't add this two attributes at the initial stage and their codes work fine when constructing path elements.
Where am I wrong? is anybody can give me more attrTween examples.
Thanks, everyone!
Your code has 2 problems, that you can easily find comparing it with Bostock's code.
First, this line:
var i = d3.interpolate(d.outerRadius, oRadius);
Uses the property outerRadius in the element's datum. But it has none. You can fix this with:
.each(function(d) { d.outerRadius = outerRadius; })
Second, your arc generator is setting the outer radius:
var ArcGen = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)
.padAngle(0.02)
.startAngle(function(d) {
return d.startAngle;
})
.endAngle(function(d) {
return d.endAngle;
});
Remove it:
var ArcGen = d3.svg.arc()
.innerRadius(innerRadius)
.padAngle(0.02)
.startAngle(function(d) {
return d.startAngle;
})
.endAngle(function(d) {
return d.endAngle;
});
Here is your working code with the 2 changes:
var Width = 500,
Height = 400,
innerRadius = 45,
outerRadius = 100;
var colors = d3.scale.category10();
var svg = d3.select('body')
.append('svg')
.attr({
width: Width,
height: Height
});
var data = [40, 32, 35, 64, 83],
pieData = d3.layout.pie()(data)
var ArcGen = d3.svg.arc()
.padAngle(0.02)
.innerRadius(innerRadius)
.startAngle(function(d) {
return d.startAngle;
})
.endAngle(function(d) {
return d.endAngle;
});
var group = svg.append('g')
.attr('transform', 'translate(' + [Width / 2.0, Height / 2.0] + ')');
var segment = group.selectAll('g')
.data(pieData)
.enter()
.append('g');
segment
.append('path')
.each(function(d) {
d.outerRadius = outerRadius;
})
.attr('d', ArcGen)
.attr('fill', function(d, i) {
return colors(i);
})
.on('mouseover', arcTween(outerRadius * 1.2, 0))
.on('mouseout', arcTween(outerRadius, 150));
function arcTween(oRadius, delay) {
// closure function
return function() {
d3.select(this)
.transition()
.delay(delay)
.attrTween('d', function(d) {
var i = d3.interpolate(d.outerRadius, oRadius);
return function(t) {
d.outerRadius = i(t);
return ArcGen(d);
}
})
};
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>

D3 Uncaught TypeError: path.data is not a function

I am quitte a newbie to D3.js and for a project i need to make a donutchart with data that changes every 2 seconds. But every time I try to change the data i get an error:
Uncaught TypeError: path.data is not a function
I used this code as an example for the change function
Code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3 donuttest</title>
<script src="//d3js.org/d3.v3.min.jss"></script>
<style>
body {
font: 10px sans-serif;
}
</style>
</head>
<body>
</body>
<div id="chartwrapper">
</div>
</body>
<script>
var wrapper="#chartwrapper";
var dummydata=70;
var data=[dummydata,100-dummydata];
var width = 460,
height = 300,
radius = Math.min(width, height) / 2;
var color = d3.scale.category20();
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 100)
.outerRadius(radius - 50);
var svg = d3.select(wrapper).append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var path = svg.selectAll("path")
.data(pie(data))
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc)
.transition()
.ease("bounce")
.duration(1000)
.attrTween("d", initTween);
var label=svg.append("g")
.attr("class", "display")
.attr("transform", "translate(-30,10)");
var labeltext=label.append("text")
.attr("font-size","2rem")
.text(dummydata + "%");
function change(data){
labeltext.text(data[0] + "%");
path.data(pie(data));
path.transition().duration(750).attrTween("d",arcTween);
};
//animation tween for when graph is drawn
function initTween(b) {
b.innerRadius = 0;
var i = d3.interpolate({startAngle: 0, endAngle: 0}, b);
return function(t) { return arc(i(t)); };
}
//animation for when data is changed
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
};
//simulating data changes
setInterval(function() {
dummydata = parseInt(Math.random() * 100);
data=[dummydata,100-dummydata];
console.log(data)
change(data);
},2000);
</script>
Here's a slight modification of your code that might help you. I used this answer as a guide.
The modifications include:
only store the result of append() in the path variable;
when first drawing the chart, store the initial angles in _current instead of using the initTween function.
var wrapper = "#chartwrapper";
var dummydata = 70;
var data = [dummydata, 100 - dummydata];
var width = 460,
height = 300,
radius = Math.min(width, height) / 2;
var color = d3.scale.category20();
var arc = d3.svg.arc()
.outerRadius(radius - 100)
.innerRadius(radius - 50);
var pie = d3.layout.pie()
.sort(null);
var svg = d3.select(wrapper).append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var path = svg.selectAll("path")
.data(pie(data))
.enter()
.append("path");
path.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc)
.transition()
.ease("bounce")
.duration(1000)
.each(function(d) {
this._current = d;
}); // store the initial angles
var label = svg.append("g")
.attr("class", "display")
.attr("transform", "translate(-30,10)");
var labeltext = label.append("text")
.attr("font-size", "2rem")
.text(dummydata + "%");
function change(data) {
labeltext.text(data[0] + "%");
path.data(pie(data));
path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
}
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
setInterval(function() {
dummydata = parseInt(Math.random() * 100);
data = [dummydata, 100 - dummydata];
console.log(data)
change(data);
}, 2000);
body {
font: 10px sans-serif;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chartwrapper"></div>

Categories