Related
So I've been trying to create a donut chart in d3.js and am having trouble adding labels to the chart. My chart data is in an array, but I think because of the "pie" variable, only the "value" from the data is being passed through and not the "text". Have tried multiple ways to try and bring the "text" in but with no luck. Hopefully a fresh set of eyes can see where my mistake is!
var margin = {top: 10, right: 30, bottom: 30, left: 60},
width = 750 - margin.left - margin.right,
height = 520 - margin.top - margin.bottom;
var r = height/3;
var aColor = [
'#0652DD',
'#C4E538',
'#F79F1F',
'#5758BB',
'#D980FA',
"#EA2027"
]
var piedata = [
{text:"Facebook", "value":76},
{text:"Website", "value":13},
{text:"HardwareZone", "value":4},
{text:"YouTube", "value":5},
{text:"Instagram", "value":1},
{text:"Twitter","value":1},
];
var vis = d3.select('#chart2')
.append("svg:svg")
.data([piedata])
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("svg:g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var pie = d3.layout.pie().sort(null).value(function(d){return d.value;});
// Declare an arc generator function
var arc = d3.svg.arc().innerRadius(r *0.5).outerRadius(r*0.8);
var outerArc = d3.svg.arc()
.innerRadius(r*0.95)
.outerRadius(r*0.95);
// Select paths, use arc generator to draw
var arcs = vis.selectAll("g.slice").data(pie).enter().append("svg:g").attr("class", "slice").attr("transform", "translate(" + width/2 + "," + height/2 + ")");
arcs.append("g:path")
.attr("fill", function(d, i){return aColor[i];})
.attr("d", function (d) {return arc(d);})
.attr("stroke", "white")
.style("stroke-width", "3px")
.style("opacity", 0.7)
;
// Add the polylines between chart and labels:
arcs.append("g:polyline")
.attr("stroke", "black")
.style("fill", "none")
.attr("stroke-width", "1px")
.attr('points', function(d) {
var posA = arc.centroid(d) // line insertion in the slice
var posB = outerArc.centroid(d) + 5 // line break: we use the other arc generator that has been built only for that
var posC = outerArc.centroid(d) + 5; // Label position = almost the same as posB
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 // we need the angle to see if the X position will be at the extreme right or extreme left
posC[0] = r * 0.95 * (midangle < Math.PI ? 1 : -1); // multiply by 1 or -1 to put it on the right or on the left
return [posA, posB, posC]
});
//Add text labels
arcs.append("g:label")
.attr('transform', function(d) {
var pos = outerArc.centroid(d);
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2
pos[0] = r * 0.99 * (midangle < Math.PI ? 1 : -1);
return 'translate(' + pos + ')';
})
.style('text-anchor', function(d) {
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2
return (midangle < Math.PI ? 'start' : 'end')
})
.text(function(d) { return d.text; }); //this is where the problem is!
Here is how you can add labels:
arcs.append('text')
.text(d => d.data.text)
.attr('dy', 4)
.attr('text-anchor', d => (d.startAngle + d.endAngle) / 2 > Math.PI ? 'end' : 'start')
.attr('x', d => outerArc.centroid(d)[0])
.attr('y', d => outerArc.centroid(d)[1]);
var margin = {top: 10, right: 30, bottom: 30, left: 60},
width = 750 - margin.left - margin.right,
height = 520 - margin.top - margin.bottom;
var r = height/3;
var aColor = [
'#0652DD',
'#C4E538',
'#F79F1F',
'#5758BB',
'#D980FA',
"#EA2027"
]
var piedata = [
{text:"Facebook", "value":76},
{text:"Website", "value":13},
{text:"HardwareZone", "value":4},
{text:"YouTube", "value":5},
{text:"Instagram", "value":1},
{text:"Twitter","value":1},
];
var vis = d3.select('#chart2')
.append("svg:svg")
.data([piedata])
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("svg:g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var pie = d3.layout.pie().sort(null).value(function(d){return d.value;});
// Declare an arc generator function
var arc = d3.svg.arc().innerRadius(r *0.5).outerRadius(r*0.8);
var outerArc = d3.svg.arc()
.innerRadius(r*0.95)
.outerRadius(r*1.1);
// Select paths, use arc generator to draw
var arcs = vis.selectAll("g.slice").data(pie).enter().append("svg:g").attr("class", "slice").attr("transform", "translate(" + width/2 + "," + height/2 + ")");
arcs.append("g:path")
.attr("fill", function(d, i){return aColor[i];})
.attr("d", function (d) {return arc(d);})
.attr("stroke", "white")
.style("stroke-width", "3px")
.style("opacity", 0.7)
;
// Add the polylines between chart and labels:
arcs.append("g:polyline")
.attr("stroke", "black")
.style("fill", "none")
.attr("stroke-width", "1px")
.attr('points', function(d) {
var posA = arc.centroid(d) // line insertion in the slice
var posB = outerArc.centroid(d) + 5 // line break: we use the other arc generator that has been built only for that
var posC = outerArc.centroid(d) + 5; // Label position = almost the same as posB
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 // we need the angle to see if the X position will be at the extreme right or extreme left
posC[0] = r * 0.95 * (midangle < Math.PI ? 1 : -1); // multiply by 1 or -1 to put it on the right or on the left
return [posA, posB, posC]
});
//Add text labels
arcs.append('text')
.text(d => d.data.text)
.attr('dy', 4)
.each(d => console.log(d))
.attr('text-anchor', d => (d.startAngle + d.endAngle) / 2 > Math.PI ? 'end' : 'start')
.attr('x', d => outerArc.centroid(d)[0])
.attr('y', d => outerArc.centroid(d)[1]);
text {
font-size: 16px;
font-family: "Ubuntu";
fill: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div id="chart2" />
I have code that should move a circle that's plotted on a graph in a svg using D3.js v6. The circle should be dragged to where the cursor is relative to the graph but I think the cursor position that is given is relative to the whole window and not the graph/svg. I'm not sure how to modify the mouse position to be relative to the svg. I have also tried using the suggestion from this answer here as well:
d3 v6 pointer function not adjusting for scale and translate
Edit:
If I were to start the circle at a position such as (0.5, 0.5) how would I go about making it so the circle only moves in a path along the circumference of a circle centered at (0, 0) with radius 0.5? I tried scaling the position of the mouse to be of magnitude 0.5 using:
x = x*(0.5/(Math.sqrt(x**2 + y**2)));
y = y*(0.5/(Math.sqrt(x**2 + y**2)));
As this is how you scale a vector to be a certain length while keeping the same direction as shown here:
https://math.stackexchange.com/questions/897723/how-to-resize-a-vector-to-a-specific-length
However this doesn't seem to work even though it should scale any point the mouse is at to be on the circle centered at the origin with radius 0.5.
var margin = {top: -20, right: 30, bottom: 40, left: 40};
//just setup
var svg = d3.select("#mysvg")
.attr("width", 300)
.attr("height", 300)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var xAxis = d3.scaleLinear()
.domain([-1.5, 1.5])
.range([0, 300]);
svg.append("g")
.attr("transform", "translate(0," + 300 + ")")
.call(d3.axisBottom(xAxis));
var yAxis = d3.scaleLinear()
.domain([-1.5, 1.5])
.range([300, 0]);
svg.append("g")
.call(d3.axisLeft(yAxis));
var circle1 = svg.append('circle')
.attr('id', 'circle1')
.attr('cx', xAxis(0))
.attr('cy', yAxis(0))
.attr('r', 10)
.style('fill', '#000000')
.call( d3.drag().on('drag', dragCircle) ); //add drag listener
function dragCircle(event) {
let x = d3.pointer(event, svg.node())[0];
let y = d3.pointer(event, svg.node())[1];
console.log("x: " + x + " y: " + y);
d3.select("#circle1")
.attr("cx", xAxis(x))
.attr("cy", yAxis(y));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.0.0/d3.min.js"></script>
<svg id="mysvg"></svg>
Two things,
D3.pointer returns units in pixels, these do not need to be scaled - the purpopse of the scale is to take an arbitrary unit and convert it to pixels:
So instead of:
d3.select("#circle1")
.attr("cx", xAxis(x))
.attr("cy", yAxis(y));
Try:
d3.select("#circle1")
.attr("cx", x)
.attr("cy", y);
Also, we want the drag to be relative to the g which holds the plot and has a transform applied to it. This part is detailed in question you link to. We can specify that we want the drag to be relative to the parent g with:
let x = d3.pointer(event,svg.node())[0];
let y = d3.pointer(event,svg.node())[1];
We can then use some trigonometry to constrain the point to a ring and have the drag be based on the angle to any arbitrary point:
let x = d3.pointer(event,svg.node())[0];
let y = d3.pointer(event,svg.node())[1];
let cx = xAxis(0);
let cy = yAxis(0);
let r = xAxis(0.5)-xAxis(0);
let dx = x- cx;
let dy = y-cy;
var angle = Math.atan2(dy,dx);
d3.select("#circle1")
.attr("cx", cx + Math.cos(angle)*r)
.attr("cy", cy + Math.sin(angle)*r);
Which gives us:
var margin = {top: -20, right: 30, bottom: 40, left: 40};
//just setup
var svg = d3.select("#mysvg")
.attr("width", 300)
.attr("height", 300)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var xAxis = d3.scaleLinear()
.domain([-1.5, 1.5])
.range([0, 300]);
svg.append("g")
.attr("transform", "translate(0," + 300 + ")")
.call(d3.axisBottom(xAxis));
var yAxis = d3.scaleLinear()
.domain([-1.5, 1.5])
.range([300, 0]);
svg.append("g")
.call(d3.axisLeft(yAxis));
var circle1 = svg.append('circle')
.attr('id', 'circle1')
.attr('cx', xAxis(0))
.attr('cy', yAxis(0))
.attr('r', 10)
.style('fill', '#000000')
.call( d3.drag().on('drag', dragCircle) ); //add drag listener
var dragCircle = svg.append('circle')
.attr('id', 'circle1')
.attr('cx', xAxis(0))
.attr('cy', yAxis(0))
.attr('r', xAxis(0.5)-xAxis(0))
.style('fill', 'none')
.style('stroke', 'black')
.style('stroke-line',1)
.call( d3.drag().on('drag', dragCircle) ); //add drag listener
function dragCircle(event) {
let x = d3.pointer(event,svg.node())[0];
let y = d3.pointer(event,svg.node())[1];
let cx = xAxis(0);
let cy = yAxis(0);
let r = xAxis(0.5)-xAxis(0);
let dx = x- cx;
let dy = y-cy;
var angle = Math.atan2(dy,dx);
d3.select("#circle1")
.attr("cx", cx + Math.cos(angle)*r)
.attr("cy", cy + Math.sin(angle)*r);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.0.0/d3.min.js"></script>
<svg id="mysvg"></svg>
What I'm trying to do is make two charts display in the same field, one to show the time spent working vs. the time spent idling, and the other chart to show whether the machine is currently working or idling.
I want the chart that shows the machine idling to be smaller than the first and fit inside it. I've been able to make both charts but I am unable to combine them in the way that I want them to.
[what I have right now]
[what I'd like to do]
Here is my code:
<!DOCTYPE html>
<html lang="en">
<div id="chart-center-jc1" align="center"></div>
<!--this line control location of the SVG chart-->
<script src="d3/d3.v3.min.js"></script>
<script>
var radius = 80,
padding = 10;
var radius2 = 25,
padding = 10;
var color = d3.scale.ordinal()
.range([ "#fc0303", "#21d525", "#d0cece", "#a05d56", "#d0743c", "#ff8c00"]);
var arc = d3.svg.arc()
.outerRadius(radius)
.innerRadius(radius - 30);
var arc2 = d3.svg.arc()
.outerRadius(radius2)
.innerRadius(radius2 - 25);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.population; });
var pie2 = d3.layout.pie()
.sort(null)
.value(function(d) { return d.population; });
d3.csv("M1 Summary.csv", function(error, data) {
if (error) throw error;
color.domain(d3.keys(data[0]).filter(function(key) { return key !=="Machine"; }));
data.forEach(function(d) {
d.ages = color.domain().map(function(name) {
return {name: name, population: +d[name]};
});
});
var legend = d3.select("#chart-center-jc1").append("svg")
.attr("class", "legend")
.attr("width", radius * 2)
.attr("height", radius * 2)
.selectAll("g")
.data(color.domain().slice().reverse())
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", 24)
.attr("y", 9)
.attr("dy", ".35em")
.text(function(d) { return d; });
var svg = d3.select("#chart-center-jc1").selectAll(".pie")
.data(data)
.enter().append("svg")
.attr("class", "pie2")
.attr("width", radius * 2)
.attr("height", radius * 3)
.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")");
svg.selectAll(".arc")
.data(function(d) { return pie(d.ages); })
.enter().append("path")
.attr("class", "arc")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.name); });
svg.selectAll(".arc2")
.data(function(d) { return pie2(d.ages); })
.enter().append("path")
.attr("class", "arc2")
.attr("d", arc2)
.style("fill", function(d) { return color(d.data.name); });
});
The key is to append one svg onto another:
var svg = d3.select("#chart-center-jc1").append("svg")
.attr("width", radius * 2)
.attr("height", radius * 3)
.attr("class","outerPie")
.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")");
var svg2 = d3.select(".outerPie").append("svg")
.attr("width", radius * 2)
.attr("height", radius * 3)
.attr("class","innerPie")
.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")");
Note that both svgs have the same height, width, and translate. This is because they are on top of one another, and you want to position the second in the center of the first.
See fiddle for complete solution.
I have code to plot pie chart. Problem is when i zoom Pie chart it goes out of Division inside which it is placed.I searched on google and got to know there is .zoom function for D3 charts to achieve this.Can anyone help me how can i do it?
Graph should be visible in all the media like in Desktop , mobile , ipad
var canvasWidth = this.getWidth(), //width
canvasHeight = this.getHeight(), //height
outerRadius = 75,
margin = {top: 20, right: 20, bottom: 30, left: 40},//radius
color = d3.scale.category20(); //builtin range of colors
var vis = d3.select("#"+this.htmlObject)
.append("svg:svg") //create the SVG element inside the <body>
.data([data]) //associate our data with the document
.attr("width", canvasWidth) //set the width of the canvas
.attr("height", canvasHeight) //set the height of the canvas
.append("svg:g") //make a group to hold our pie chart
.attr("transform", "translate(" + 1.5*outerRadius + "," + 1.5*outerRadius + ")") // relocate center of pie to 'outerRadius,outerRadius'
.attr('transform', 'translate(' + (canvasWidth/2 - 20) + ',' + canvasHeight/2 +')');
var arc = d3.svg.arc()
.outerRadius(outerRadius);
var pie = d3.layout.pie() //this will create arc data for us given a list of values
.value(function(d) { return d.magnitude; }); // Binding each value to the pie
var arcs = vis.selectAll("g.slice")
.data(pie)
.enter()
.append("svg:g")
.attr("class", "slice"); //allow us to style things in the slices (like text)
arcs.append("svg:path")
.attr("fill", function(d, i) { return color(i); } )
.attr("d", arc);
arcs.append("svg:text")
.attr("transform", function(d) { //set the label's origin to the center of the arc
d.outerRadius = outerRadius + 50; // Set Outer Coordinate
d.innerRadius = outerRadius + 45; // Set Inner Coordinate
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle") //center the text on it's origin
.style("fill", "Purple")
.style("font", "bold 12px Arial")
.text(function(d, i) { return data[i].legendLabel; }); //get the label from our original data array
arcs.filter(function(d) { return d.endAngle - d.startAngle > .2; }).append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.attr("transform", function(d) { //set the label's origin to the center of the arc
d.outerRadius = outerRadius; // Set Outer Coordinate
d.innerRadius = outerRadius/2; // Set Inner Coordinate
return "translate(" + arc.centroid(d) + ")rotate(" + angle(d) + ")";
})
.style("fill", "White")
.style("font", "bold 12px Arial")
.text(function(d) { return d.data.magnitude; });
function angle(d) {
var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
return a > 90 ? a - 180 : a;
}
I found one small code
var zoom = d3.behavior.zoom()
.x(xScale)
.on('zoom', zoomed);
You have not implemented a proper zoom function. D3 has this. Here is an example :
var zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
function zoomed() {
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); //the container here is the part of the SVG you wish to zoom into
}
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.right + ")")
.call(zoom); //here is the main call.
Here is an example : https://bl.ocks.org/mbostock/6123708
This is not a pie chart but it will work either way. Just where I have container, just put your pie chart container here. There are plenty of examples online for zooming in D3
I am creating a donut chart in d3.js and AngularJS.
I have a drawGraph function in directive link function. The data is an array of objects and is coming from server.
Here is what the function looks like.
Function:
scope.drawGraph = function(data){
console.log(data);
var width = 560,
height = 400,
radius = Math.min(width, height) / 2.6,
legendRectSize = 18,
legendSpacing = 4;
var color = d3.scale.category20();
var pie = d3.layout.pie()
.value(function(d) { return d.amount; })
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 90)
.outerRadius(radius - 70);
var svg = d3.select("#chart").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);
var ticks = svg.selectAll("line").data(pie(data)).enter().append("line");
ticks.attr("x1", 0)
.attr("x2", 0)
.attr("y1", -radius+10)
.attr("y2", -radius+70)
.attr("stroke", "gray")
.attr("transform", function(d) {
return "rotate(" + (d.startAngle+d.endAngle)/2 * (180/Math.PI) + ")";
});
var labels = svg.selectAll("text").data(pie(data)).enter().append("text");
labels.attr("class", "value")
.attr("transform", function(d) {
var dist=radius+5;
var winkel=(d.startAngle+d.endAngle)/2;
var x=dist*Math.sin(winkel);
var y=-dist*Math.cos(winkel);
return "translate(" + x + "," + y + ")";
})
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.style('fill', 'rgba(0, 0, 0, 0.498039)')
.text(function(d){
return d.data.category;
});
};
scope.drawGraph(scope.data); // Function call
scope.data:
[{amount: 100, category: 'food'},{amount: 150, category: 'fuel'}, {amount: 50, category: 'grocery'},{amount: 250, category: 'Entertainment'}]
Here is what the console returns:
Error: Invalid value for attribute d="M5.134096196425442e-15,-83.84615384615384A83.84615384615384,83.84615384615384 0 1,1 NaN,NaNLNaN,NaNA63.84615384615384,63.84615384615384 0 1,0 3.909449397278089e-15,-63.84615384615384Z"
And also:
Error: Invalid value for attribute d="MNaN,NaNA83.84615384615384,83.84615384615384 0 1,1 NaN,NaNLNaN,NaNA63.84615384615384,63.84615384615384 0 1,0 NaN,NaNZ"
Error: Invalid value for attribute transform="rotate(NaN)"
Error: Invalid value for attribute transform="translate(NaN,NaN)"
I am relatively new to d3.js.
Thanks in advance.
I used your code to create a fiddle, and didn't run into the issue you got.
fiddle
<div id="chart" style="height:500px;">
<svg></svg>
</div>