I am working on a d3 application - which features a hooped chart. I have a version which is a radial 360 type of chart - but I am unsure how you'd configure the path to arc in this fashion.
//old js fiddle of a crescent chart
http://jsfiddle.net/2wfktc3g/
var $this = $('.crescentchart');
var data = [{
label: 'Fudge Brownie',
value: 5,
},
{
label: 'Cherry Vanilla',
value: 60,
},
{
label: 'Pistachio',
value: 5,
},
{
label: 'Caramel',
value: 10,
}
];
var oldData = "";
var width = $this.data('width'),
height = $this.data('height'),
radius = $this.data('r'),
thickness = $this.data("thickness"),
spacing = $this.data("spacing");
var color = d3.scaleOrdinal()
.range(["#bc658d", "#82c4c3", "#f9d89c", "#f5a7a7"]);
var svg = d3.select($this[0])
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr('class', 'crescentchart')
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var segments = svg.append('g').attr('class', 'segments');
var data = setData(data, radius);
//append previous value to it.
$.each(data, function(index, value) {
if (oldData[index] != undefined) {
data[index]["previousEndAngle"] = oldData[index].endAngle;
} else {
data[index]["previousEndAngle"] = 0;
}
});
var arcpaths = segments.selectAll("path")
.data(data);
arcpaths.enter().append("path")
.style("fill", function(d, i) {
return color(i);
})
.transition()
.ease(d3.easeElastic)
.duration(750)
.attrTween("d", function(d) {
return arcTween(d, thickness, radius);
});
arcpaths.transition()
.ease(d3.easeElastic)
.style("fill", function(d, i) {
return color(i);
})
.duration(750)
.attrTween("d", function(d) {
return arcTween(d, thickness, radius);
});
arcpaths.exit().transition()
.ease(d3.easeBounce)
.duration(750)
.attrTween("d", function(d) {
return arcTween(d, thickness, radius);
})
.remove();
function arcTween(b, thickness, ir) {
var prev = JSON.parse(JSON.stringify(b));
prev.endAngle = b.previousEndAngle;
var i = d3.interpolate(prev, b);
return function(t) {
return getArc(thickness, ir)(i(t));
};
}
function getRadiusRing(ir, i) {
return ir - (i * (thickness + spacing));
}
function getArc(thickness, ir) {
var arc = d3.arc()
.innerRadius(function(d) {
return getRadiusRing(ir, d.index);
})
.outerRadius(function(d) {
return getRadiusRing(ir + thickness, d.index);
})
.startAngle(function(d, i) {
return d.startAngle;
})
.endAngle(function(d, i) {
return d.endAngle;
});
return arc;
}
function setData(data, r) {
var diameter = (2 * Math.PI) * r;
var segmentValueSum = 0;
$.each(data, function(ri, va) {
segmentValueSum += va.value;
});
$.each(data, function(ri, va) {
var segmentValue = va.value;
var fraction = segmentValue / segmentValueSum;
var arcBatchLength = fraction * (2 * Math.PI);
var arcPartition = arcBatchLength;
var startAngle = Math.PI;
var endAngle = startAngle + arcPartition;
data[ri]["startAngle"] = startAngle;
data[ri]["endAngle"] = endAngle;
data[ri]["index"] = ri;
});
return data;
}
//legend
var legendsvgw = 150;
var legendsvgh = 100;
var ringRadius = 5;
var vertical = 20;
var legendsvg = d3.select($this[0])
.append("svg")
.attr("width", legendsvgw)
.attr("height", legendsvgh)
.append("g")
.attr('class', 'legendsvg')
.attr("transform", "translate(" + 10 + "," + 10 + ")");
var legend = legendsvg.append("g")
.attr("class", "legend");
var labels = legend.selectAll("text.labels")
.data(data);
labels.enter().append("text")
.attr("class", "labels")
.attr("dx", 15)
.attr("dy", function(d, i) {
return (vertical * i) + ringRadius * 2;
})
.attr("text-anchor", function(d) {
return "start";
})
.text(function(d) {
return d.label;
});
labels.exit().remove();
var ring = legend.selectAll("circle")
.data(data);
ring.enter().append("circle")
.attr("cy", function(d, i) {
return (vertical * i) + ringRadius;
})
.attr("r", ringRadius)
.attr("width", ringRadius * 2)
.attr("height", ringRadius * 2)
.style("fill", function(d, i) {
return color(i);
});
ring.exit().remove();
body {
background: #eeeeee;
}
.arc path {
stroke: #fff;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<h1>CrescentChart I</h1>
<div class="crescentchart" data-width="300" data-height="300" data-r="90" data-thickness="6" data-spacing="10" />
Trying to keep it in line with your current implementation, I'd consider two different cases. One where d.fraction <= 0.25, so the arc part is the only thing required to draw the bar. In that case, just use the arc. The other, when d.fraction > 0.25, you can draw yourself, because you can assume already that the arc goes exactly from 180 to 270 degrees, and all you need it to draw the bar on top before closing it. I used d3.path() extensively for this example, I recommend you keep it next to you when reading the code.
var $this = $('.crescentchart');
var data = [{
label: 'Fudge Brownie',
value: 45,
},
{
label: 'Cherry Vanilla',
value: 60,
},
{
label: 'Pistachio',
value: 5,
},
{
label: 'Caramel',
value: 10,
}
];
var oldData = "";
var width = $this.data('width'),
height = $this.data('height'),
radius = $this.data('r'),
thickness = $this.data("thickness"),
spacing = $this.data("spacing");
var color = d3.scaleOrdinal()
.range(["#bc658d", "#82c4c3", "#f9d89c", "#f5a7a7"]);
var svg = d3.select($this[0])
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr('class', 'crescentchart')
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var segments = svg.append('g').attr('class', 'segments');
var data = setData(data, radius);
//append previous value to it.
$.each(data, function(index, value) {
if (oldData[index] != undefined) {
data[index]["previousFraction"] = oldData[index].fraction;
} else {
data[index]["previousFraction"] = 0;
}
});
var arcpaths = segments.selectAll("path")
.data(data);
arcpaths.enter().append("path")
.style("fill", function(d, i) {
return color(i);
})
.transition()
.ease(d3.easeElastic)
.duration(3000)
.attrTween("d", function(d) {
return arcTween(d, thickness, radius);
});
arcpaths.transition()
.ease(d3.easeElastic)
.style("fill", function(d, i) {
return color(i);
})
.duration(3000)
.attrTween("d", function(d) {
return arcTween(d, thickness, radius);
});
arcpaths.exit().transition()
.ease(d3.easeBounce)
.duration(3000)
.attrTween("d", function(d) {
return arcTween(d, thickness, radius);
})
.remove();
function arcTween(b, thickness, ir) {
var prev = JSON.parse(JSON.stringify(b));
prev.endAngle = b.previousEndAngle;
prev.fraction = b.previousFraction;
var i = d3.interpolate(prev, b);
const arc = getArc(thickness, ir);
return function(t) {
return arc(i(t));
};
}
function getRadiusRing(ir, i) {
return ir - (i * (thickness + spacing));
}
// The portion of the fraction that is drawn as an arc,
// instead of as a straight part
const arcPortion = 3 / 8;
function getArc(thickness, ir) {
var arc = d3.arc()
.innerRadius(function(d) {
return getRadiusRing(ir, d.index);
})
.outerRadius(function(d) {
return getRadiusRing(ir + thickness, d.index);
})
.startAngle(- arcPortion * 2 * Math.PI)
.endAngle(function(d, i) {
return -(d.fraction + arcPortion) * 2 * Math.PI;
});
// This function is only called when endAngle is greater than
// 3 * Math.PI, otherwise we simply draw an arc, because it makes
// no difference.
function jShape(d, i) {
const context = d3.path();
const innerRadius = getRadiusRing(ir, d.index);
const outerRadius = getRadiusRing(ir + thickness, d.index);
const startAngle = -arcPortion * 2 * Math.PI - Math.PI / 2;
const endAngle = 0;
// Start at the correct position on the inside
context.moveTo(
innerRadius * Math.cos(startAngle),
innerRadius * Math.sin(startAngle)
);
context.arc(0, 0, innerRadius, startAngle, endAngle, true);
// Now draw the straight part
const fullLength = innerRadius * 2 * Math.PI;
// The first 0.25 corresponds to the curved part drawn earlier.
const straightLength = (d.fraction - arcPortion) * fullLength
context.lineTo(
innerRadius * Math.cos(endAngle),
innerRadius * Math.sin(endAngle) - straightLength
);
// Move to the outside
context.lineTo(
outerRadius * Math.cos(endAngle),
innerRadius * Math.sin(endAngle) - straightLength
);
context.lineTo(
outerRadius * Math.cos(endAngle),
outerRadius * Math.sin(endAngle)
);
// And curve back
context.arc(0, 0, outerRadius, endAngle, startAngle, false);
context.closePath();
return context + "";
}
return function(d, i) {
return d.fraction <= arcPortion ? arc(d, i) : jShape(d, i);
}
}
function setData(data, r) {
var segmentValueSum = 0;
$.each(data, function(ri, va) {
segmentValueSum += va.value;
});
$.each(data, function(ri, va) {
var segmentValue = va.value;
var fraction = segmentValue / segmentValueSum;
data[ri]["index"] = ri;
data[ri]["fraction"] = fraction;
});
return data;
}
body {
background: #eeeeee;
}
.arc path {
stroke: #fff;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v4.js"></script>
<h1>CrescentChart I</h1>
<div class="crescentchart" data-width="300" data-height="300" data-r="90" data-thickness="6" data-spacing="10" />
Related
I'm trying to draw arcs corresponding to data. Plnkr
Actually there are two numbers in the data. Whenever I'm trying to create the two arcs, they are merging together.
var pi = Math.PI, arcRadius = 55, arcThickness = 2;
var gaugeData = [32, 57];
var svg = d3.select('.circles').append('svg');
var arcFunc = d3.svg.arc()
.innerRadius(55)
.outerRadius(57)
.startAngle(0)
.endAngle(function(d) { return d * (pi /180); });
svg.attr("width", "400").attr("height", "400")
.selectAll('path').data(gaugeData).enter().append('path')
.attr('d', function(d) { return arcFunc(d); })
.attr('fill', '#555')
.attr('cx', function(d, i) { return i * 100 + 30; })
.attr('cy', function(){ return 50; })
.attr("transform", "translate(200,200)");
The arcs would be like:
Please help. Thanks in advance.
I'm assuming that gaugeData is the degree value for the total sweep of your arc. So, the first step is to d3ify your data by creating an array of start/stop angles. Then the drawing becomes easy:
var pi = Math.PI, arcRadius = 55, arcThickness = 2;
var gaugeData = [32, 57, 180];
var svg = d3.select('body').append('svg');
var data = gaugeData.map(function(d,i){
var start = 0;
for (var j = 0; j < i; j++){
start += gaugeData[j]; // start angle value is the sum of all the sweeps before it
}
var end = start + d; // end angle is the start + sweep
return {
start: start,
end: end
};
});
var colors = d3.scale.category10();
var arcFunc = d3.svg.arc()
.innerRadius(55)
.outerRadius(57)
.startAngle(function(d) { return d.start * (pi /180); })
.endAngle(function(d) { return d.end * (pi /180); });
svg.attr("width", "400").attr("height", "400")
.selectAll('path').data(data).enter().append('path')
.attr('d', function(d) { return arcFunc(d); })
.attr('fill', function(d,i){ return colors(i); })
.attr("transform", "translate(200,200)");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
EDITS For Comments
Putting the arcs side-by-side is a lot easier, just change the transform for each one. Also note, that cx and cy are not valid for a path:
var pi = Math.PI, arcRadius = 55, arcThickness = 2;
var gaugeData = [32, 57, 180];
var svg = d3.select('body').append('svg');
var colors = d3.scale.category10();
var arcFunc = d3.svg.arc()
.innerRadius(55)
.outerRadius(57)
.startAngle(function(d) { return 0; })
.endAngle(function(d) { return d * (pi /180); });
svg.attr("width", "400").attr("height", "400")
.selectAll('path').data(gaugeData).enter().append('path')
.attr('d', function(d) { return arcFunc(d); })
.attr('fill', function(d,i){ return colors(i); })
.attr("transform", function(d,i){
return "translate("+ ((i * 75) + 10) + ",75)";
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I want to create rotate animation in a d3.js chart. With help of Gilsha I was able to create a chart like this :
Now on click of any of the outer circles i want to rotate all circles and align the one which was clicked in the place of "hllooo" circle.
I have copied one angletween which is rotating the circles but the problem is that text inside that circle also gets rotated and creates something like this:
As you can see the text is also rotated which i dont want. The code for transform function is:
function angleTween(d, i) {
var angle = 360 - ((i + 1) * 70);
var i = d3.interpolate(0, 90);
return function (t) {
return "rotate(" + i(t) + ")";
};
}
So how to keep text unrotated and just rotate the shape ?
Calulcate the x and y attributes of text elements by using SVGPoint matrixTransform.
earth.on('click', function() {
texts.style("opacity", 0);
earthLayer
.transition()
.duration(2000)
.attrTween("transform", angleTween)
.each("end", function() {
var svgEl = this.ownerSVGElement;
var angle = d3.select(this).attr("transform").match(/\d+/g)[0];
var matrix = svgEl.createSVGMatrix().rotate(angle);
texts.each(function(d, i) {
var point = this.ownerSVGElement.createSVGPoint();
point.x = +d.cx;
point.y = +d.cy;
point = point.matrixTransform(matrix);
d3.select(this).attr("x", point.x).attr("y", point.y);
});
texts.style("opacity", 1);
});
});
var now = d3.time.year.floor(new Date());
var spacetime = d3.select('body');
var width = 960,
height = 700,
radius = Math.min(width, height);
var radii = {
"sun": radius / 6,
"earthOrbit": radius / 2.5,
"earth": radius / 15,
"moonOrbit": radius / 16,
"moon": radius / 96
};
// Space
var svg = spacetime.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
// Sun
var sun = svg.append("circle")
.attr("class", "sun")
.attr("r", radii.sun)
//.style("fill", "rgba(255, 204, 0, 1.0)");
.style("stroke", "#f58c2e")
.style("stroke-width", "10")
.style("fill", "none");
// Earth's orbit
var orbit = svg.append("circle")
.attr("class", "earthOrbit")
.attr("r", radii.earthOrbit)
.style("fill", "none")
.style("stroke", "#bababa")
.style("stroke-width", "30");
// Current position of Earth in its orbit
var earthOrbitPosition = d3.svg.arc()
.outerRadius(radii.earthOrbit + 1)
.innerRadius(radii.earthOrbit - 1)
.startAngle(0)
.endAngle(0);
svg.append("path")
.attr("class", "earthOrbitPosition")
.attr("d", earthOrbitPosition)
.style("fill", "rgba(255, 204, 0, 0.75)");
// Time of day
var day = d3.svg.arc()
.outerRadius(radii.earth)
.innerRadius(0)
.startAngle(0)
.endAngle(0);
svg.append("path")
.attr("class", "day")
.attr("d", day)
.attr("transform", "translate(0," + -radii.earthOrbit + ")")
.style("fill", "rgba(53, 110, 195, 1.0)");
// Current position of the Moon in its orbit
var moonOrbitPosition = d3.svg.arc()
.outerRadius(radii.moonOrbit + 1)
.innerRadius(radii.moonOrbit - 1)
.startAngle(0)
.endAngle(0);
svg.append("path")
.attr("class", "moonOrbitPosition")
.attr("d", moonOrbitPosition)
.attr("transform", "translate(0," + -radii.earthOrbit + ")")
.style("fill", "rgba(113, 170, 255, 0.75)");
function getCirclePoints(points, radius, center) {
var circlePositions = [];
var slice = 2 * Math.PI / points;
for (var i = 0; i < points; i++) {
var angle = slice * i;
var newX = (center.X + radius * Math.cos(angle));
var newY = (center.Y + radius * Math.sin(angle));
circlePositions.push({
cx: newX,
cy: newY
});
}
return circlePositions;
}
var circlePositions = getCirclePoints(10, radii.earthOrbit, {
X: 0,
Y: 0
});
var earthLayer = svg.append("g").classed("earthLayer", true);
var textLayer = svg.append("g").classed("textLayer", true);
var earth = earthLayer.selectAll(".earth").data(circlePositions)
.enter()
.append("circle")
.attr("cx", function(d) {
return d.cx;
})
.attr("cy", function(d) {
return d.cy;
})
.attr("class", "earth")
.style("fill", "white")
.attr("r", radii.earth)
.style("stroke", "#bababa")
.style("stroke-width", "10");
texts = textLayer.selectAll("text").data(circlePositions).enter().append("text").attr("x", function(d) {
return d.cx
}).attr("dx", -radii.earth / 2).attr("y", function(d) {
return d.cy
}).text(function(d, i) {
if (i == 0) return "hllooo";
else return "hllooo" + i;
});
earth.on('click', function() {
texts.style("opacity", 0);
earthLayer
.transition()
.duration(2000)
.attrTween("transform", angleTween)
.each("end", function() {
var svgEl = this.ownerSVGElement;
var angle = d3.select(this).attr("transform").match(/\d+/g)[0];
var matrix = svgEl.createSVGMatrix().rotate(angle);
texts.each(function(d, i) {
var point = this.ownerSVGElement.createSVGPoint();
point.x = +d.cx;
point.y = +d.cy;
point = point.matrixTransform(matrix);
d3.select(this).attr("x", point.x).attr("y", point.y);
});
texts.style("opacity", 1);
});
});
function angleTween(d, i) {
var angle = 360 - ((i + 1) * 70);
var i = d3.interpolate(0, angle);
return function(t) {
return "rotate(" + i(t) + ")";
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Hope this helps.
Below is my code for the text. I just need to flip horizontally the "SAMPLE 1" text to make it more readable.
function computeTextRotation(d) {
var angle = (d.x + d.dx / 2) * 180 / Math.PI - 90;
return angle;
}
var texts = svg.selectAll("text")
.data(partitioned_data)
.enter().append("text")
.filter(filter_min_arc_size_text)
.attr("transform", function(d) {
return "rotate(" + computeTextRotation(d) + ")";
})
.attr("x", function(d) {
return radius / 3 * d.depth;
})
.attr("dx", "0.5em") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d, i) {
return d.name
})
.style("font-size", 11);
Here's one solution...
function computeTextRotation(d) {
var rotation = (d.startAngle + d.endAngle)/2 * 180 / Math.PI - 90;
return {
global: rotation,
correction: rotation > 90 ? 180 : 0
};
}
//...
text.attr("transform", function(d) {
var r = computeTextRotation(d);
return "rotate(" + r.global + ")"
+ "translate(" + radius / 3 * d.depth + ",0)"
+ "rotate(" + -r.correction + ")";
});
Here's a working example...
var crm = (function() {
var prevData = [];
return function crm(f) {
var max = 10000;
oldPieData = JSON.parse(JSON.stringify(prevData));
prevData = f([
{name: 'SMR', value: Math.random() * max},
{name: 'PSF', value: Math.random() * max},
{name: 'Insurance', value: Math.random() * max},
{name: 'Other', value: Math.random() * max}
])
}
})();
pieTween = function(d, i) {
var s0;
var e0;
if(oldPieData[i]){
s0 = oldPieData[i].startAngle;
e0 = oldPieData[i].endAngle;
} else if (!(oldPieData[i]) && oldPieData[i-1]) {
s0 = oldPieData[i-1].endAngle;
e0 = oldPieData[i-1].endAngle;
} else if(!(oldPieData[i-1]) && oldPieData.length > 0){
s0 = oldPieData[oldPieData.length-1].endAngle;
e0 = oldPieData[oldPieData.length-1].endAngle;
} else {
s0 = 0;
e0 = 0;
}
var i = d3.interpolate({startAngle: s0, endAngle: e0}, {startAngle: d.startAngle, endAngle: d.endAngle});
return function(t) {
var b = i(t);
return arc(b);
};
};
var pie = Math.PI * 2;
var w = 200,
h = 200;
var ir = 45;
var duration = 750;
var chart = d3.select('.chart')
.attr('width', w)
.attr('height',h)
.append('g')
.attr('transform', 'translate('+w/2+','+ h/2 + ')'),
pieChart = d3.layout.pie()
.value(function(d){ return d.value; })
.sort(null),
oldPieData = [],
groups = chart.append ("g")
.attr("class", "groups"),
// group at the center of donut
center_group = chart.append('g')
.attr("class", "center_group")
.append('text')
.attr({'text-anchor': 'middle', dy: "0.35em"});
createPieChart = function(data){
var radius = 95, innerRadius = radius - 70;
var pieData,
color = d3.scale.ordinal()
.range(['#469AB2', '#F0AD4E', '#5CB85C', '#D9534F'])
.domain(data.map(function(d){return d.name}));
// displaying total calls at the center
center_group.text(d3.format(",.1f")(d3.sum(data, function(d){return d.value})));
arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(radius);
var arcs = groups.selectAll('path')
.data((pieData = pieChart(data)), function(d){return d.data.name});
arcs.enter().append('path')
.attr('class', 'arc');
arcs.attr('fill', function(d){ return color(d.data.name)})
.transition()
.duration(duration)
.attrTween("d", pieTween)
.ease("bounce");
function computeTextRotation(d) {
var rotation = (d.startAngle + d.endAngle)/2 * 180 / Math.PI - 90;
return {
global: rotation,
correction: rotation > 90 ? 180 : 0
};
}
var labels = groups.selectAll("text")
.data(pieData);
labels.enter().append("text")
.attr({"text-anchor": "middle"})
.text(function(d) {
return d.data.name
})
.attr("dy", ".35em") // vertical-align
.style("font-size", 11);
labels
.transition()
.duration(duration)
.attr("transform", function(d) {
var r = computeTextRotation(d);
return "rotate(" + r.global + ")" + "translate(" + (radius + innerRadius) / 2 + ",0)" + "rotate(" + -r.correction + ")";
})
.call(endAll, function(){
window.requestAnimationFrame(
function(){crm(createPieChart)}
)
});
return pieData;
};
crm(createPieChart);
body {margin: 1px;}
.chart text {
/* fill: white;*/
font: 10px sans-serif;
}
#pie-chart-div {
position: relative;
top: 6em;
}
.chart {
position: relative;
/* top: 2em;*/
left: 5em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<script src="https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/transitions/end-all/endAll.js"></script>
<svg class="chart"></svg>
My goal is that given a value in seconds(resp_time), I want to create a counter in anticlock direction that would end once resp_time becomes 0.
I am following this tutorial: http://bl.ocks.org/mbostock/1096355 to create a polar clock. But I need the arc to decrease as in go anti-clockwise. I tried updating the endAngle value for the same, but it doesn't seem to work.
Here's my code:
var width = 960,
height = 800,
radius = Math.min(width, height) / 1.9,
spacing = .09;
var resp_time = 61;
var rh = parseInt(resp_time/3600), rm = parseInt((resp_time- rh*3600)/60), rs = parseInt((resp_time- rh*3600 - rm*60)%60);
var color = d3.scale.linear()
.range(["hsl(-180,50%,50%)", "hsl(180,50%,50%)"])
.interpolate(interpolateHsl);
var t;
var arc = d3.svg.arc()
.startAngle(0)
.endAngle(function(d) { return d.value * 2 * Math.PI; })
.innerRadius(function(d) { return d.index * radius; })
.outerRadius(function(d) { return (d.index + spacing) * radius; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var field = svg.selectAll("g")
.data(fields)
.enter().append("g");
field.append("path");
field.append("text");
d3.transition().duration(0).each(tick);
d3.select(self.frameElement).style("height", height + "px");
function tick() {
field = field
.each(function(d) { this._value = d.value; })
.data(fields)
.each(function(d) { d.previousValue = this._value; });
field.select("path")
.transition()
.ease("elastic")
.attrTween("d", arcTween)
.style("fill", function(d) { return color(d.value); });
field.select("text")
.attr("dy", function(d) { return d.value < .5 ? "-.5em" : "1em"; })
.text(function(d) { return d.text; })
.transition()
.ease("elastic")
.attr("transform", function(d) {
return "rotate(" + 360 * d.value + ")"
+ "translate(0," + -(d.index + spacing / 2) * radius + ")"
+ "rotate(" + (d.value < .5 ? -90 : 90) + ")"
});
if (resp_time > 0)
{
resp_time = resp_time - 1;
rh = parseInt(resp_time/3600), rm = parseInt((resp_time- rh*3600)/60), rs = parseInt((resp_time- rh*3600 - rm*60)%60);
t = setTimeout(tick, 1000);
}
}
function arcTween(d) {
console.log(d);
var i = d3.interpolateNumber(d.previousValue, d.value);
return function(t) { d.value = i(t); return arc(d); };
}
function fields() {
console.log(rs);
return [
{index: .3, text: rs+"s", value: rs},
{index: .2, text: rm+"m", value: rm},
{index: .1, text: rh+"h", value: rh}
];
}
function interpolateHsl(a, b) {
var i = d3.interpolateString(a, b);
return function(t) {
return d3.hsl(i(t));
};
}
This is just resulting in 3 static concentric circles(since I'm plotting only the minute, seconds and hours) with no transition.
I'm not sure how to proceed from here. What change should I make to get it working? Please help me out.
d.value in this code should be in the range [0, 1], so you need to divide by the maximum values in the fields generator:
function fields() {
console.log(rs);
return [
{index: .3, text: rs+"s", value: rs/60},
{index: .2, text: rm+"m", value: rm/60},
{index: .1, text: rh+"h", value: rh/24}
];
}
I noticed this because console.log(rs) was printing out values in the range of [0, 60] whereas arc.endAngle expects a radian value. If you substitute [0, 60] with the endAngle provider in the code, you get an over wound clock hand.
var arc = d3.svg.arc()
.startAngle(0)
.endAngle(function(d) { return d.value * 2 * Math.PI; })
var width = 960,
height = 800,
radius = Math.min(width, height) / 1.9,
spacing = .09;
var resp_time = 61;
var rh = parseInt(resp_time/3600), rm = parseInt((resp_time- rh*3600)/60), rs = parseInt((resp_time- rh*3600 - rm*60)%60);
var color = d3.scale.linear()
.range(["hsl(-180,50%,50%)", "hsl(180,50%,50%)"])
.interpolate(interpolateHsl);
var t;
var arc = d3.svg.arc()
.startAngle(0)
.endAngle(function(d) { return d.value * 2 * Math.PI; })
.innerRadius(function(d) { return d.index * radius; })
.outerRadius(function(d) { return (d.index + spacing) * radius; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var field = svg.selectAll("g")
.data(fields)
.enter().append("g");
field.append("path");
field.append("text");
d3.transition().duration(0).each(tick);
d3.select(self.frameElement).style("height", height + "px");
function tick() {
field = field
.each(function(d) { this._value = d.value; })
.data(fields)
.each(function(d) { d.previousValue = this._value; });
field.select("path")
.transition()
.ease("elastic")
.attrTween("d", arcTween)
.style("fill", function(d) { return color(d.value); });
field.select("text")
.attr("dy", function(d) { return d.value < .5 ? "-.5em" : "1em"; })
.text(function(d) { return d.text; })
.transition()
.ease("elastic")
.attr("transform", function(d) {
return "rotate(" + 360 * d.value + ")"
+ "translate(0," + -(d.index + spacing / 2) * radius + ")"
+ "rotate(" + (d.value < .5 ? -90 : 90) + ")"
});
if (resp_time > 0)
{
resp_time = resp_time - 1;
rh = parseInt(resp_time/3600), rm = parseInt((resp_time- rh*3600)/60), rs = parseInt((resp_time- rh*3600 - rm*60)%60);
t = setTimeout(tick, 1000);
}
}
function arcTween(d) {
console.log(d);
var i = d3.interpolateNumber(d.previousValue, d.value);
return function(t) { d.value = i(t); return arc(d); };
}
function fields() {
console.log(rs);
return [
{index: .3, text: rs+"s", value: rs/60},
{index: .2, text: rm+"m", value: rm/60},
{index: .1, text: rh+"h", value: rh/24}
];
}
function interpolateHsl(a, b) {
var i = d3.interpolateString(a, b);
return function(t) {
return d3.hsl(i(t));
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I've created this arc chart. I'd like to animate the labels better, have them tween with the arc animations. I've placed the labels inside to avoid being covered up.
jsFiddle
var arcGenerator = {
radius: 100,
oldData: "",
init: function(data){
var clone = jQuery.extend(true, {}, data);
this.oldData = this.setData(clone, false);
this.setup(this.setData(data, true));
},
update: function(data){
var clone = jQuery.extend(true, {}, data);
this.animate(this.setData(data, true));
this.oldData = this.setData(clone, false);
},
animate: function(data){
var that = this;
var chart = d3.select(".arcchart");
that.generateArcs(chart, data);
},
setData: function(data, isSorted){
var diameter = 2 * Math.PI * this.radius;
var localData = new Array();
var displacement = 0;
var oldBatchLength = 0;
$.each(data, function(index, value) {
var riseLevels = value.segments;
var riseLevelCount = riseLevels.length;
if(oldBatchLength !=undefined){
displacement+=oldBatchLength;
}
var arcBatchLength = 2*Math.PI;
var arcPartition = arcBatchLength/riseLevelCount;
$.each(riseLevels, function( ri, value ) {
var startAngle = (ri*arcPartition);
var endAngle = ((ri+1)*arcPartition);
if(index!=0){
startAngle+=displacement;
endAngle+=displacement;
}
riseLevels[ri]["startAngle"] = startAngle;
riseLevels[ri]["endAngle"] = endAngle;
});
oldBatchLength = arcBatchLength;
localData.push(riseLevels);
});
var finalArray = new Array();
$.each(localData, function(index, value) {
$.each(localData[index], function(i, v) {
finalArray.push(v);
});
});
return finalArray;
},
generateArcs: function(chart, data){
var that = this;
//_arc paths
//append previous value to it.
$.each(data, function(index, value) {
if(that.oldData[index] != undefined){
data[index]["previousEndAngle"] = that.oldData[index].endAngle;
}
else{
data[index]["previousEndAngle"] = 0;
}
});
var arcpaths = that.arcpaths.selectAll("path")
.data(data);
arcpaths.enter().append("svg:path")
.attr("class", function(d, i){
return d.machineType;
})
.style("fill", function(d, i){
return d.color;
})
.transition()
.ease("elastic")
.duration(750)
.attrTween("d", arcTween);
arcpaths.transition()
.ease("elastic")
.style("fill", function(d, i){
return d.color;
})
.duration(750)
.attrTween("d",arcTween);
arcpaths.exit().transition()
.ease("bounce")
.duration(750)
.attrTween("d", arcTween)
.remove();
function arcTween(b) {
var prev = JSON.parse(JSON.stringify(b));
prev.endAngle = b.previousEndAngle;
var i = d3.interpolate(prev, b);
return function(t) {
return that.getArc()(i(t));
};
}
//_arc paths
var r = that.radius - 50;
var ir = that.radius + 90;
//__labels
var labels = that.labels.selectAll("text")
.data(data);
labels.enter()
.append("text")
.attr("text-anchor", "middle")
labels
.attr("x", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cx = Math.cos(a) * (ir+((r-ir)/2));
return d.x = Math.cos(a) * (r + 20);
})
.attr("y", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cy = Math.sin(a) * (ir+((r-ir)/2));
return d.y = Math.sin(a) * (r + 20);
})
.text(function(d) {
return d.color;
})
.each(function(d) {
var bbox = this.getBBox();
d.sx = d.x - bbox.width/2 - 2;
d.ox = d.x + bbox.width/2 + 2;
d.sy = d.oy = d.y + 5;
})
.transition()
.duration(300)
labels
.transition()
.duration(300)
labels.exit().remove();
//__labels
//__pointers
that.pointers.append("defs").append("marker")
.attr("id", "circ")
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("refX", 3)
.attr("refY", 3)
.append("circle")
.attr("cx", 3)
.attr("cy", 3)
.attr("r", 3);
var pointers = that.pointers.selectAll("path.pointer")
.data(data);
pointers.enter()
.append("path")
.attr("class", "pointer")
.style("fill", "none")
.style("stroke", "black")
.attr("marker-end", "url(#circ)");
pointers
.attr("d", function(d) {
if(d.cx > d.ox) {
return "M" + d.sx + "," + d.sy + "L" + d.ox + "," + d.oy + " " + d.cx + "," + d.cy;
} else {
return "M" + d.ox + "," + d.oy + "L" + d.sx + "," + d.sy + " " + d.cx + "," + d.cy;
}
})
.transition()
.duration(300)
pointers
.transition()
.duration(300)
pointers.exit().remove();
//__pointers
},
setup: function(data){
var chart = d3.select("#threshold").append("svg:svg")
.attr("class", "chart")
.attr("width", 420)
.attr("height", 420)
.append("svg:g")
.attr("class", "arcchart")
.attr("transform", "translate(200,200)");
this.arcpaths = chart.append("g")
.attr("class", "arcpaths");
this.labels = chart.append("g")
.attr("class", "labels");
this.pointers = chart.append("g")
.attr("class", "pointer");
this.generateArcs(chart, data);
},
getArc: function(){
var that = this;
var arc = d3.svg.arc()
.innerRadius(function(d, i){
return that.radius;
})
.outerRadius(function(d){
var maxHeight = 100;
var ratio = (d.height/maxHeight * 100)+that.radius;
return ratio;
})
.startAngle(function(d, i){
return d.startAngle;
})
.endAngle(function(d, i){
return d.endAngle;
});
return arc;
}
}
$(document).ready(function() {
var dataCharts = [
{
"data": [
{
"segments": [
{
height: 10,
color: "grey"
},
{
height: 40,
color: "darkgrey"
},
{
height: 33,
color: "grey"
},
{
height: 50,
color: "darkgrey"
},
{
height: 33,
color: "grey"
},
{
height: 10,
color: "darkgrey"
},
{
height: 50,
color: "grey"
},
{
height: 45,
color: "darkgrey"
},
{
height: 10,
color: "grey"
},
{
height: 40,
color: "darkgrey"
}
]
}
]
},
{
"data": [
{
"segments": [
{
height: 50,
color: "red"
},
{
height: 100,
color: "yellow"
},
{
height: 10,
color: "green"
}
]
}
]
}
];
var clone = jQuery.extend(true, {}, dataCharts);
arcGenerator.init(clone[0].data);
$(".testers a").on( "click", function(e) {
e.preventDefault();
var clone = jQuery.extend(true, {}, dataCharts);
var pos = $(this).parent("li").index();
arcGenerator.update(clone[pos].data);
});
});
There are two parts for this. First, the animation of the pointer lines. This is relatively easy and the only thing you're missing is that the .transition() is in the wrong place:
pointers
.transition()
.duration(300)
.attr("d", function(d) {
// etc
The second part is the animation of the text labels. This is a bit more difficult because their computation includes some side effects that allow the correct computation of the pointer lines. This comes in two parts -- the computation of the position and the computation of the extent of the displayed text. With that in mind, the changes are relatively straightforward, we just need to make sure that those computations take place before the transition starts:
labels.text(function(d) {
return d.color;
}).each(function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cx = Math.cos(a) * (ir+((r-ir)/2));
d.cy = Math.sin(a) * (ir+((r-ir)/2));
d.x = d.x || Math.cos(a) * (r + 20);
d.y = d.y || Math.sin(a) * (r + 20);
var bbox = this.getBBox();
d.sx = d.x - bbox.width/2 - 2;
d.ox = d.x + bbox.width/2 + 2;
d.sy = d.oy = d.y + 5;
})
First, the text itself is set. This is required to be able to use .getBBox() to determine its dimensions. Then, all the values required by the pointer paths are computed -- these bits of code were previously in the computation of the position of the text, but that's what we want to transition to so those values are set later (except for new text labels that don't have coordinates set).
All that remains now is to animate the change of coordinates of the text in the same way as before:
.transition()
.duration(300)
.attr("x", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
return d.x = Math.cos(a) * (r + 20);
})
.attr("y", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
return d.y = Math.sin(a) * (r + 20);
});
Complete example here.