D3.js - Apparent bug in line append using trig functions - javascript

I am setting about to manually append tick marks to a arc visual I'm working on. What remains is placing the axis tick marks about the outer edges of the chart at every 30 degrees. I have appended the 0 degree mark (which is easy, no math involved really) then I realized I may need to use Math.sin() and Math.cosin() to calculate the right x,y coordinates for the other ticks, since they are at angles. My 30 degree tick worked fine. So with 0 tick and 30 tick both appended correctly at the right size and place, one would assume that the math and code are both fine. However, mysteriously, the 60 degree tick is way off. Notice that it's in the inside of the chart and not where it should be, at the 60 degree mark, between the top and 30 degree tick. It's almost as if it thinks I'm giving it an entirely different adjacent (to use trig terminology). Code below:
var margins = {top:100, bottom:300, left:100, right:100};
var height = 600;
var width = 600;
var totalWidth = width+margins.left+margins.right;
var totalHeight = height+margins.top+margins.bottom;
var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);
var graphGroup = svg.append('g')
.attr('transform', "translate("+margins.left+","+margins.top+")");
var data = [
{'fmc':'xx','funds':30,'top':35,'bottom':-30},
{'fmc':'xx','funds':44,'top':5,'bottom':-40},
{'fmc':'xx','funds':2,'top':20,'bottom':-10},
{'fmc':'xx','funds':22,'top':10,'bottom':-25},
{'fmc':'xx','funds':35,'top':4,'bottom':-20},
];
var yScale = d3.scaleLinear()
.range([height,0])
.domain([-50,50]);
var realScale = d3.scaleLinear()
.range([0,height/2])
.domain([0,50]);
var dScale = d3.scaleLinear()
.domain([-50,50])
.range([Math.PI,0]);
let arc = d3.arc()
.innerRadius(0)
.outerRadius(function(d) {return realScale(d.funds)})
.startAngle(function(d) {return dScale(d.top)})
.endAngle(function(d) {return dScale(d.bottom)});
graphGroup.append('g')
.call(d3.axisRight(yScale).ticks(5))
.selectAll('text')
.attr('text-anchor','end')
.attr('transform','translate(-15,0)');
graphGroup.append('line')
.attr('x1', 0)
.attr('x2',width/2)
.attr('y1', yScale(0))
.attr('y2', yScale(0))
.style('stroke','#000')
.style('stroke-width',2)
.style('stroke-dasharray','2,2');
var arcAxis = d3.arc()
.innerRadius(0)
.outerRadius(width/2)
.startAngle(0)
.endAngle(Math.PI);
var grid1 = d3.arc()
.innerRadius(0)
.outerRadius(realScale(20))
.startAngle(0)
.endAngle(Math.PI);
var grid2 = d3.arc()
.innerRadius(0)
.outerRadius(realScale(40))
.startAngle(0)
.endAngle(Math.PI);
graphGroup.append("path")
.attr("d", grid1)
.style('fill','none')
.style('stroke','#a6a6a6')
.style('stroke-width','1px')
.style('stroke-dasharray','2,2')
.attr("transform", "translate(0,300)");
graphGroup.append("path")
.attr("d", grid2)
.style('fill','none')
.style('stroke','#a6a6a6')
.style('stroke-width','1px')
.style('stroke-dasharray','2,2')
.attr("transform", "translate(0,300)");
graphGroup.append("path")
.attr("d", arcAxis)
.style('fill','none')
.style('stroke','#000')
.style('stroke-width','1px')
.attr("transform", "translate(0,300)");
graphGroup.append('line')
.attr('x1',width/2)
.attr('x2',(width/2)+6)
.attr('y1',width/2)
.attr('y2',width/2)
.style('stroke','#000')
.style('stroke-width','1px');
graphGroup.append('line')
.attr('y1', Math.sin((Math.PI / 180)*30)*300)
.attr('y2', Math.sin((Math.PI / 180)*30)*294)
.attr('x1', Math.cos((Math.PI / 180)*30)*300)
.attr('x2', Math.cos((Math.PI / 180)*30)*306)
.style('stroke','#000')
.style('stroke-width','1px');
graphGroup.append('line')
.attr('y1', Math.sin((Math.PI / 180)*60)*300)
.attr('y2', Math.sin((Math.PI / 180)*60)*294)
.attr('x1', Math.cos((Math.PI / 180)*60)*300)
.attr('x2', Math.cos((Math.PI / 180)*60)*306)
.style('stroke','#000')
.style('stroke-width','1px');
graphGroup
.selectAll()
.data(data)
.enter()
.append("path")
.attr('transform','translate(0,300)')
.attr("class", "arc")
.attr("d", arc)
.style('fill', "#003366")
.style('opacity',.3);
<script src="https://d3js.org/d3.v5.min.js"></script>
Question
To me this looks like a bug, if we go just a few lines of code up, we can see the result rendered correctly, so why would changing 30 to 60 result in something totally different?

Here is a solution:
const addMark = (g, angle, fromRadius, toRadius) => {
const angleR = angle * Math.PI / 180;
const x1 = fromRadius * Math.sin(angleR);
const y1 = fromRadius * -Math.cos(angleR);
const x2 = toRadius * Math.sin(angleR);
const y2 = toRadius * -Math.cos(angleR);
console.log(x1, x2, y1, y2)
g.append('line')
.attr('y1', y1)
.attr('y2', y2)
.attr('x1', x1)
.attr('x2', x2)
.attr('transform', 'translate(0, 300)')
.style('stroke','red');
}
var margins = {top:100, bottom:300, left:100, right:100};
var height = 600;
var width = 600;
var totalWidth = width+margins.left+margins.right;
var totalHeight = height+margins.top+margins.bottom;
var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);
var graphGroup = svg.append('g')
.attr('transform', "translate("+margins.left+","+margins.top+")");
var data = [
{'fmc':'xx','funds':30,'top':35,'bottom':-30},
{'fmc':'xx','funds':44,'top':5,'bottom':-40},
{'fmc':'xx','funds':2,'top':20,'bottom':-10},
{'fmc':'xx','funds':22,'top':10,'bottom':-25},
{'fmc':'xx','funds':35,'top':4,'bottom':-20},
];
var yScale = d3.scaleLinear()
.range([height,0])
.domain([-50,50]);
var realScale = d3.scaleLinear()
.range([0,height/2])
.domain([0,50]);
var dScale = d3.scaleLinear()
.domain([-50,50])
.range([Math.PI,0]);
let arc = d3.arc()
.innerRadius(0)
.outerRadius(function(d) {return realScale(d.funds)})
.startAngle(function(d) {return dScale(d.top)})
.endAngle(function(d) {return dScale(d.bottom)});
graphGroup.append('g')
.call(d3.axisRight(yScale).ticks(5))
.selectAll('text')
.attr('text-anchor','end')
.attr('transform','translate(-15,0)');
graphGroup.append('line')
.attr('x1', 0)
.attr('x2',width/2)
.attr('y1', yScale(0))
.attr('y2', yScale(0))
.style('stroke','#000')
.style('stroke-width',2)
.style('stroke-dasharray','2,2');
var arcAxis = d3.arc()
.innerRadius(0)
.outerRadius(width/2)
.startAngle(0)
.endAngle(Math.PI);
var grid1 = d3.arc()
.innerRadius(0)
.outerRadius(realScale(20))
.startAngle(0)
.endAngle(Math.PI);
var grid2 = d3.arc()
.innerRadius(0)
.outerRadius(realScale(40))
.startAngle(0)
.endAngle(Math.PI);
graphGroup.append("path")
.attr("d", grid1)
.style('fill','none')
.style('stroke','#a6a6a6')
.style('stroke-width','1px')
.style('stroke-dasharray','2,2')
.attr("transform", "translate(0,300)");
graphGroup.append("path")
.attr("d", grid2)
.style('fill','none')
.style('stroke','#a6a6a6')
.style('stroke-width','1px')
.style('stroke-dasharray','2,2')
.attr("transform", "translate(0,300)");
graphGroup.append("path")
.attr("d", arcAxis)
.style('fill','none')
.style('stroke','#000')
.style('stroke-width','1px')
.attr("transform", "translate(0,300)");
graphGroup.append('line')
.attr('x1',width/2)
.attr('x2',(width/2)+6)
.attr('y1',width/2)
.attr('y2',width/2)
.style('stroke','#000')
.style('stroke-width','1px');
const addMark = (g, angle, fromRadius, toRadius) => {
const angleR = angle * Math.PI / 180;
const x1 = fromRadius * Math.sin(angleR);
const y1 = fromRadius * -Math.cos(angleR);
const x2 = toRadius * Math.sin(angleR);
const y2 = toRadius * -Math.cos(angleR);
console.log(x1, x2, y1, y2)
g.append('line')
.attr('y1', y1)
.attr('y2', y2)
.attr('x1', x1)
.attr('x2', x2)
.attr('transform', 'translate(0, 300)')
.style('stroke','red')
.style('stroke-width', 2)
}
graphGroup
.selectAll()
.data(data)
.enter()
.append("path")
.attr('transform','translate(0,300)')
.attr("class", "arc")
.attr("d", arc)
.style('fill', "#003366")
.style('opacity',.3);
addMark(graphGroup, 15, 300, 306);
addMark(graphGroup, 30, 300, 306);
addMark(graphGroup, 45, 300, 306);
addMark(graphGroup, 60, 300, 306);
addMark(graphGroup, 75, 300, 306);
addMark(graphGroup, 90, 300, 306);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Related

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>

How to scale y-Axis in d3.js according to data input correctly

I created a small Barchart. My problem is that the y-axis doesn't scale according to my dataset. Here is a screenshot:
So as you can see the 313 is a little bit above the 800 scale. I would like the 300 to be at the 300. I tweaked with the scalings but I just end up messing it up completely. I am very new to D3.js so I hope someone can help.
Here is my code:
var svgWidth = 1000, svgHeight = 800;
var barWidth = svgWidth / month_description.length;
var barPadding = 5;
var svg = d3.select('svg')
.attr("width", svgWidth)
.attr("height", svgHeight);
var barChart = svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("y", function(d) {
return svgHeight - d - 20
})
.attr("x", function(d, i) {
return i + 10;
})
.attr("height", function(d) {
return d;
})
.attr("width", barWidth - barPadding)
.attr("class", "bar")
.attr("transform", function (d, i) {
var translate = [barWidth * i, 0];
return "translate("+ translate +")";
});
var text = svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d) {
return d;
})
.attr("y", function(d, i) {
return svgHeight - 20;
})
.attr("x", function(d, i) {
return barWidth * i + 35;
})
.attr("fill", "white");
var xScale = d3.scaleLinear()
.domain([0, d3.max(month_description)])
.range([0, svgWidth]);
var yScale = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([svgHeight, 0]);
var x_axis = d3.axisBottom()
.scale(xScale);
var y_axis = d3.axisLeft()
.scale(yScale);
svg.append("g")
.attr("transform", "translate(50, 10)")
.call(y_axis);
var xAxisTranslate = svgHeight - 20;
svg.append("g")
.attr("transform", "translate(50, " + xAxisTranslate +")")
.call(x_axis);
Help is very much appreciated. Thanks very much in advance!!

SVG line out of arc starting position - d3.js

I'm trying to achieve this
but this is currently what I have
Essentially all I need to do is figure out how to have the lines emanating out from the circle starting at the arc start.
My question is exactly that, how can I translate the arc starting position into the x1,y1 attribute of an svg line. Below is the code that I currently have pertaining to drawing the lines:
// Draw lines emanating out
g.append('line')
.attr('class', 'outer-line')
.attr('x1', function(d) {
return 0;
})
.attr('x2', 0)
.attr('y1', -radius)
.attr('y2', -radius-150)
.attr('stroke', function(d, i) {
return color(i);
})
.attr('stroke-width','2')
.attr("transform", function(d) {
return "rotate(" + (d.startAngle+d.endAngle)/2 * (180/Math.PI) + ")";
});
If I understand your problem correctly, you can use just d.startAngle:
g.attr("transform", function(d) {
return "rotate(" + d.startAngle * (180/Math.PI) + ")";
});
Check here an example (click on "run code snippet"):
var dataset = [300, 200, 400, 200, 300, 100, 50];
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("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var path = svg.selectAll("path")
.data(pie(dataset))
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc);
var g = svg.selectAll(".groups")
.data(pie(dataset))
.enter()
.append("g");
g.append('line')
.attr('class', 'outer-line')
.attr('x1', 0)
.attr('x2', 0)
.attr('y1', -radius + 50)
.attr('y2', -radius)
.attr('stroke', 'black')
.attr('stroke-width','2')
.attr("transform", function(d) {
return "rotate(" + d.startAngle * (180/Math.PI) + ")";
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

How do I increase the inner radius of a specific arc segment?

I have a pie chart and I want to increase the inner radius of the arc the user mouses over.
For example, if I mouse over the arc with 161, I want only the inner radius of that arc segment to increase.
How would I do this?
// arc radius
var radius = 200;
var p = Math.PI *2; //full circle
var data = [11,12,51,21,31,58,41,13,14,31,71,31,51,71,16,41,31,161];
var data1 = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18];
var width = 1000;
var height = 1000;
//========== color scale ==========//
var colorScale = d3.scale.ordinal()
.range(['#FFEBEB', '#FFC2C2', '#FFADAD', '#FF9999', '#FF7070', '#FF5C5C', '#FF4747',
'#FF1F1F', '#FF0A0A', '#F50000', '#CC0000', '#A30000', '#8F0000', '#660000', '#520000', '#3D0000', '#290000', '#140000']);
//========== Create canvas and arc ==========//
var canvas = d3.select("body")
.append("svg")
.attr("height", height)
.attr("width", width);
var group = canvas.append("g")
.attr("transform", "translate(" + width/2 + ",500)");
var arc = d3.svg.arc()
.innerRadius(radius-50)
.outerRadius(radius);
var pie = d3.layout.pie()
.value(function (d){return d;});
var sorted = data.sort(function(a,b){return a-b});
var arcs = group.selectAll(".arc")
.data(pie(sorted))
.enter()
.append("g")
.attr("class", "arc");
arcs.append("path")
.attr("d", arc)
.style('stroke', 'white')
.style('stroke-width', 2)
.attr("fill", function(d, i){return colorScale(d.data);});
arcs.append("text")
.attr("transform", function(d){
return "translate(" + arc.centroid(d) +")";
})
.attr("text-anchor", "middle")
.text(function(d){return d.data});
//======== mouse over/out =========//
var prevColor;
arcs.on("mouseover", function(d){
prevColor = d3.select(this).select("path").attr("fill");
d3.select(this).select("path")
.attr("fill", "black");
})
.on("mouseout", function(d){
d3.select(this).select("path")
.attr("fill", prevColor);
});
http://jsfiddle.net/64vFq/
If I understand you correctly, it looks like you can just change this line:
var arc = d3.svg.arc()
.innerRadius(radius-50)
.outerRadius(radius);
I changed it to this and it produced a decent result:
var arc = d3.svg.arc()
.innerRadius(radius-150)
.outerRadius(radius);
(increase the 50 to make the part the user can mouse over larger; decrease it for the opposite effect)
I found a solution that seems to work pretty well.
All you need to do is create another arc (a bigger one)
var biggerArc = d3.svg.arc().outerRadius(radius+10).innerRadius(radius - 40);
then add it to your mouseover
arcs.on("mouseover", function (d) {
prevColor = d3.select(this).select("path").attr("fill");
d3.select(this).select("path").attr("fill", "black");
d3.select(this).select("path").transition()
.duration(100)
.attr("d", biggerArc);
}).on("mouseout", function (d) {
d3.select(this).select("path").attr("fill", prevColor);
d3.select(this).select("path").transition()
.duration(100)
.attr("d", arc);
});

Using transform to position my shape the right way up

Random Data
var data = [53245];
var data2 = [28479, 234234];
var data3 = [19697];
var data4 = [41123,12313];
Width & Height of canvas
var w = 960,
h = 500,
r = Math.min(w, h) / 2,
donut = d3.layout.pie().sort(null),
Here is where i decide how big I wish a arc to be
arc = d3.svg.arc().innerRadius(r - 100).outerRadius(r - 20).startAngle(-2).endAngle(-1.34),
arc2 = d3.svg.arc().innerRadius(r - 100).outerRadius(r - 20).startAngle(-1.34).endAngle(-0.68),
arc3 = d3.svg.arc().innerRadius(r - 100).outerRadius(r - 20).startAngle(-0.68).endAngle(-0.02),
arc4 = d3.svg.arc().innerRadius(r - 100).outerRadius(r - 20).startAngle(-0.02).endAngle(0.64),
arc5 = d3.svg.arc().innerRadius(r - 100).outerRadius(r - 20).startAngle(0.64).endAngle(1.3),
arc6 = d3.svg.arc().innerRadius(r - 100).outerRadius(r - 20).startAngle(1.3).endAngle(1.96),
I have tried using transform, but my shape sits upside down. I would like it to be an arc not a U shape.
var svg = d3.select("body").append("svg:svg")
.append("svg:g")
.attr("transform", "translate(700,400) scale(1, -1)");
The paths for color etc
var arcs = svg.selectAll("path")
.data(donut(data))
.enter().append("svg:path")
.attr("fill", function(d, i) { return 'blue'; })
.attr("d", arc)
.data(donut(data2))
.enter().append("svg:path")
.attr("fill", function(d, i) { return 'green'; })
.attr("d", arc2)
.data(donut(data3))
.enter().append("svg:path")
.attr("fill", function(d, i) { return 'pink'; })
.attr("d", arc3)
.data(donut(data4))
.enter().append("svg:path")
.attr("fill", function(d, i) { return 'black'; })
.attr("d", arc4)
.data(donut(data4))
.enter().append("svg:path")
.attr("fill", function(d,i) {return 'yellow';})
.attr("d",arc5)
.data(donut(data4))
.enter().append("svg:path")
.attr("fill", function(d,i) {return 'orange';})
.attr("d",arc6)
I've solved it myself:
.attr("transform", "translate(700,450) rotate(180) scale(-1, -1)")
Simply added rotate & changed the scale :)

Categories