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>
Related
I worked in the d3 library and created nested wheels. I have no idea how to add texts into the wheel, spirally from inside out. The number starting point doesn't matter, and numbers must spirally outwards according the previous position.
Codes
let allAxis = (data.map(function(i, j) {
return i.name
})),
total = allAxis.length,
radius = Math.min(options.width / 2, options.height / 2),
angleSlice = Math.PI * 2 / total,
Format = d3.format('');
let rScale = d3.scale.linear()
.domain([0, options.circles.maxValue])
.range([50, radius]);
let svg = d3.select("body").append("svg")
.attr("width", options.width + options.margins.left + options.margins.right)
.attr("height", options.height + options.margins.top + options.margins.bottom);
let g = svg.append("g")
.attr("transform", "translate(" + (options.width / 2 + options.margins.left) + "," + (options.height / 2 + options.margins.top) + ")");
let axisGrid = g.append("g")
.attr("class", "axisWraper");
let axis = axisGrid.selectAll(".axis")
.data(allAxis)
.enter()
.append("g")
.attr("class", "axis")
//append them lines
axis.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", function(d, i) {
let tempX2 = radius * Math.cos(angleSlice * i - Math.PI / 2);
return tempX2;
})
.attr("y2", function(d, i) {
let tempY = radius * Math.sin(angleSlice * i - Math.PI / 2);
return tempY;
})
.attr("class", "line")
.attr("stroke", "black")
.attr("fill", "none");
//Draw background circles
axisGrid.selectAll(".levels")
.data([12,11,10,9,8,7,6, 5, 4, 3, 2, 1])
.enter()
.append("circle")
.attr("class", function(d, i) {
return `gridCircle-${d}`
})
.attr("r", function(d, i) {
return parseInt(radius / options.circles.levels * d, 10);
})
.attr("stroke", "black")
.attr("fill-opacity", function(d, i) {
return options.circles.opacity;
});
axisGrid.select(".gridCircle-1").attr("fill-opacity", 1);
axisGrid.select(".gridCircle-2").attr("fill-opacity", 1);
Expected Result
Updated #1 (with PointRadial)
Here's the fiddle: http://jsfiddle.net/arcanabliss/8yjacdoz/73/
You need pointRadial:
Returns the point [x, y] for the given angle in radians, with 0 at -y
(12 o’clock) and positive angles proceeding clockwise, and the given
radius.
You can play around with the example below in order to fit to your arrangement e.g. in order to start from 3 o'clock and rotate counter-clockwise etc you just need to play with degrees and radius:
const degrees = (i % numbersPerRing) * (360 / numbersPerRing);
const radius = (Math.floor(i / numbersPerRing) + 1) * ringSize;
// numbers
const numbersPerRing = 8;
const ringSize = 30;
const originX = 120;
const originY = 120;
const numbers = [... new Array(18)].map((d, i) => i + 1);
const diameters = [... new Array(4)].map((d, i) => (i * ringSize) + 10);
const points = numbers.map((n, i) => {
const degrees = (i % numbersPerRing) * (360 / numbersPerRing);
const radius = (Math.floor(i / numbersPerRing) + 1) * ringSize;
const point = d3.pointRadial(degrees * (Math.PI / 180), radius);
return {
"value": n,
"degrees": degrees,
"point": point
}
});
// svg
const svg = d3.select("body")
.append("svg")
.attr("width", originX * 2)
.attr("height", originY * 2);
// circles
svg.selectAll("circles")
.data(diameters)
.enter()
.append("circle")
.attr("cx", originX)
.attr("cy", originY)
.attr("r", d => d)
.attr("fill", "none")
.attr("stroke", "#aaaaaa");
// labels
svg.selectAll("labels")
.data(points)
.enter()
.append("text")
.attr("x", (d, i) => d.point[0])
.attr("y", (d, i) => d.point[1])
.attr("transform", `translate(${originX},${originX})`)
.attr("text-anchor", (d) => (d.degrees > 180) ? "end" : "start")
.text(d => d.value);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
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);
};
});
}
My customized d3 radial chart is not rendering arc for least value.
Fiddle
var width = 360,
height = 300,
barHeight = height / 2 - 40;
var formatNumber = d3.format("s");
var color = d3.scale.ordinal()
.range(["#F15D5D","#FAD64B"]);
var svg = d3.select('#chart').append("svg")
.attr("width", width)
.attr("height", height)
.attr('class','radial')
.append("g")
.attr("transform", "translate(" + width/2 + "," + height/2 + ")");
var data = [{
"name": "ABC",
"value":4
},
{
"name": "XYZ",
"value":5
},{
"name": "DEF",
"value":2
},
{
"name": "GHI",
"value":3
},{
"name": "JKL",
"value":1
}];
data.sort(function(a,b) { return b.value - a.value; });
var extent = d3.extent(data, function(d) { return d.value; });
var barScale = d3.scale.linear()
.domain(extent)
.range([0, barHeight]);
var keys = data.map(function(d,i) { return d.name; });
var numBars = keys.length;
// X scale
var x = d3.scale.linear()
.domain(extent)
.range([0, -barHeight]);
// X axis
var xAxis = d3.svg.axis()
.scale(x).orient("left")
.ticks(3)
.tickFormat(formatNumber);
// Inner circles
var circles = svg.selectAll("circle")
.data(x.ticks(5))
.enter().append("circle")
.attr("r", function(d) {return barScale(d);})
.style("fill", "none")
//.style("stroke", "black")
//.style("stroke-dasharray", "2,2")
.style("stroke-width",".5px");
// Create arcs
var arc = d3.svg.arc()
.startAngle(function(d,i) {
var a = (i * 2 * Math.PI) / numBars;
var b = ((i + 1) * 2 * Math.PI) / numBars;
var d = (b-a) / 4;
var x = a+d;
var y = b-d;
return x;//(i * 2 * Math.PI) / numBars;
})
.endAngle(function(d,i) {
var a = (i * 2 * Math.PI) / numBars;
var b = ((i + 1) * 2 * Math.PI) / numBars;
var d = (b-a) / 4;
var x = a+d;
var y = b-d;
return y;//((i + 1) * 2 * Math.PI) / numBars;
})
.innerRadius(0);
// Render colored arcs
var segments = svg.selectAll("path")
.data(data)
.enter().append("path")
.each(function(d) { d.outerRadius = 0; })
.style("fill", function (d) { return color(d.name); })
.attr("d", arc);
segments.transition().ease("elastic").duration(1000).delay(function(d,i) {return (25-i)*50;})
.attrTween("d", function(d,index) {
var i = d3.interpolate(d.outerRadius, barScale(+d.value));
return function(t) { d.outerRadius = i(t); return arc(d,index); };
});
// Outer circle
svg.append("circle")
.attr("r", barHeight)
.classed("outer", true)
.style("fill", "none")
//.style("stroke", "black")
.style("stroke-width",".5px");
// Apply x axis
svg.append("g")
.attr("class", "x axis")
.call(xAxis);
// Labels
var labelRadius = barHeight * 1.025;
var labels = svg.append("g")
.classed("labels", true);
labels.append("def")
.append("path")
.attr("id", "label-path")
.attr("d", "m0 " + -labelRadius + " a" + labelRadius + " " + labelRadius + " 0 1,1 -0.01 0");
labels.selectAll("text")
.data(keys)
.enter().append("text")
.style("text-anchor", "middle")
.style("font-weight","bold")
.style("fill", function(d, i) {return "#555";})
.append("textPath")
.attr("xlink:href", "#label-path")
.attr("startOffset", function(d, i) {return i * 100 / numBars + 50 / numBars + '%';})
.text(function(d) {return d.toUpperCase(); });
Here the item JKL with value 1 is not rendered and the outerRadius is calculated as 0.
The problem lies here:
var extent = d3.extent(data, function(d) { return d.value; });
This line sets the domain of barScale and x. However, if you do this using d3.extent, you're actually saying that the domain goes from the minimum value to the maximum value...
[1, 5];
... meaning that the value 1 will be mapped to the minimum value of the range.
Instead of that, set the minimum value in the domain to zero:
var extent = [0, d3.max(data, d=>d.value)];
Which will have this result:
[0, 5];
Here is your updated fiddle: https://jsfiddle.net/a9w7c2dw/
I am trying to add a SVG circle within my d3 donut. My SVG circle displays the percentage as a fill of circle, for example, if the D3 donut is at 50%, the SVG will show 50% filled. I want to put the SVG circle within the inside of my D3 donut.
Here is my codepen for this. http://codepen.io/iamsok/pen/MwdPpx
class ProgressWheel {
constructor(patient, steps, container){
this._patient = patient;
this._steps = steps;
this.$container = $(container);
var τ = 2 * Math.PI,
width = this.$container.width(),
height = this.$container.height(),
innerRadius = Math.min(width,height)/4,
//innerRadius = (outerRadius/4)*3,
fontSize = (Math.min(width,height)/4);
var tooltip = d3.select(".tooltip");
var status = {
haveNot: 0,
taken: 1,
ignored: 2
}
var daysProgress = patient.progress
var percentComplete = Math.round(_.countBy(daysProgress)[status.taken] / daysProgress.length * 100);
var participation = 100;
var color = ["#CCC", "#FDAD42", "#EFD8B5"];
var pie = d3.layout.pie()
.value(function(d) { return 1; })
.sort(null);
var arc = d3.svg.arc();
var svg = d3.select(container).append("svg")
.attr("width", '100%')
.attr("height", '100%')
.attr('viewBox','0 0 '+Math.min(width,height) +' '+Math.min(width,height) )
.attr('preserveAspectRatio','xMinYMin')
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var innerCircle = d3.select("svg")
.append("svg")
.attr("width", 250)
.attr("height", 250);
var grad = innerCircle.append("defs")
.append("linearGradient").attr("id", "grad")
.attr("x1", "0%").attr("x2", "0%").attr("y1", "100%").attr("y2", "0%");
grad.append("stop").attr("offset", percentComplete + "%").style("stop-color", "lightblue");
grad.append("stop").attr("offset", percentComplete + "%").style("stop-color", "white");
innerCircle.append("circle")
.attr("r", 40)
.attr("cx", 70)
.attr("cy", 70)
.style("stroke", "black")
.style("fill", "url(#grad)");
var gs = svg.selectAll(".arc")
.data(pie(daysProgress))
.enter().append("g")
.attr("class", "arc");
var path = gs.append("path")
.attr("fill", function(d, i) { return color[d.data]; })
.attr("d", function(d, i, j) { return arc.innerRadius(innerRadius+(20*j)).outerRadius(innerRadius+20+(20*j))(d); })
.attr("class", function(d, i, j) { if (i>=participation && j<1) return "passed" ; })
svg.append("text")
.attr("dy", "0.5em")
.style("text-anchor", "middle")
.attr("class", "inner-circle")
.attr("fill", "#36454f")
.text(Math.round(_.countBy(daysProgress)[status.taken] / daysProgress.length * 100) + "%");
}
}
var patient = {progress: [0, 2, 2, 1, 0, 0, 0, 1, 1, 0, 1, 1, 2, 2]}
var progressWheel = new ProgressWheel(patient, 14, '.chart-container' )
Simply put the d3 donut and inner circle under the same svg so that they have the same coordinate system.
Check out here http://codepen.io/anon/pen/ojbQNE
Modified code is on codepen
I have a dataset that consists of the following data:
{
current: 5
expected: 8
gap: -3
id: 3924
name: "Forhandlingsevne"
progress: "0"
type: 2
}
Now then i have the following JavaScript code:
var data = scope.dataset;
var width = 500,
height = 500,
radius = Math.min(width, height) / 2,
innerRadius = 0.3 * radius;
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.width; });
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([0, 0])
.html(function(d) {
return d.data.name + ": <span style='color:orangered'>" + d.data.current + "</span>";
});
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(function (d) {
return (radius - innerRadius) * (d.data.current / 100.0) + innerRadius;
});
var outlineArc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(radius);
var svg = d3.select("#astroD3").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
svg.call(tip);
// for (var i = 0; i < data.score; i++) { console.log(data[i].id) }
var path = svg.selectAll(".solidArc")
.data(pie(data))
.enter().append("path")
.attr("fill", function(d) { return getColor(d.gap); })
.attr("class", "solidArc")
.attr("stroke", "gray")
.attr("d", arc)
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
var outerPath = svg.selectAll(".outlineArc")
.data(pie(data))
.enter().append("path")
.attr("fill", "none")
.attr("stroke", "gray")
.attr("class", "outlineArc")
.attr("d", outlineArc);
// calculate the weighted mean score
var score =
data.reduce(function(a, b) {
//console.log('a:' + a + ', b.score: ' + b.score + ', b.weight: ' + b.weight);
return a + (b.current * b.expected);
}, 0) /
data.reduce(function(a, b) {
return a + b.expected;
}, 0);
svg.append("svg:text")
.attr("class", "aster-score")
.attr("dy", ".35em")
.attr("text-anchor", "middle") // text-align: right
.text('');
function getColor(gap)
{
return gap > 0 ? '#5cb85c' : '#d9534f';
}
When running this i get multiple errors (1 for each of my data in my dataset) saying:
Error: Invalid value for <path> attribute d="MNaN,NaNA85.5,85.5 0 1,1 NaN,NaNLNaN,NaNA75,75 0 1,0 NaN,NaNZ"
When i debug i can see that my variables look like this:
Object {data: Object, value: NaN, startAngle: NaN, endAngle: NaN}
So my question is what am i doing wrong?
You're telling D3 to use the attribute width to determine the pie slices -- this attribute doesn't exist in your data. It looks like you want
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.current; });