I'm working with d3 v4 and struggling to create a simple chart, to use as a reference for further development, which both utilises the d3.zoom() function and the chart resizes to fit the browser window. Whenever I get one of these working the other breaks. The Typescript code is within an Angular 4 component but that shouldn't have an impact.
First I initialise some properties:
// Create a variable to hold the DOM Element which the chart will be attached to.
this.element = this.chartContainer.nativeElement;
// Set the width and height
this.width = this.svgWidth - this.margin.left - this.margin.right;
this.height = this.svgHeight - this.margin.top - this.margin.bottom;
// create scale objects
this.xScale = d3.scaleLinear()
.domain([0, 100])
.range([0, this.width]);
this.yScale = d3.scaleLinear()
.domain([-10, -20])
.range([this.height, 0]);
// create axis objects
this.xAxis = d3.axisBottom(this.xScale);
this.yAxis = d3.axisLeft(this.yScale);
Then I define my zoomFunction():
this.zoomFunction = () => {
if (d3.event.transform != null) {
//console.log(d3.event.transform);
if (this.xScale != null) {
// create new scale ojects based on event
let new_xScale = d3.event.transform.rescaleX(this.xScale);
let new_yScale = d3.event.transform.rescaleY(this.yScale);
// update axes
this.xChartAxis.call(this.xAxis.scale(new_xScale));
this.yChartAxis.call(this.yAxis.scale(new_yScale));
// update circle
this.circles.attr("transform", d3.event.transform)
};
};
};
Define my zoom() function:
this.zoom = d3.zoom().on("zoom", this.zoomFunction);
Define my windowResize() function:
this.onWindowResize =() => {
// Set the width and height.
// The element.offsetWidth is the total width of the DOM element including scrollbars, borders etc.
this.svgWidth = this.element.offsetWidth;
this.svgHeight = this.element.offsetHeight;
// Update the width and height which the chart fits into.
this.width = this.element.offsetWidth - this.margin.left - this.margin.right;
this.height = this.element.offsetHeight - this.margin.top - this.margin.bottom;
// Update the width of the svgViewport.
this.svgViewport.attr("width", this.svgWidth);
this.svgViewport.attr("height", this.svgHeight);
// Update the size of the zoomView
this.zoomView
.attr("width", this.width)
.attr("height", this.height);
// Update the Range of the x scale.
this.xScale.range([0, this.width]);
this.yScale.range([0, this.height]);
// Update the axis.
this.innerSpace.select(".axisY")
.call(this.yAxis);
this.innerSpace.select(".axisX")
.attr("transform", "translate(0," + this.height + ")")
.call(this.xAxis);
};
Add an EventListener:
window.addEventListener("resize", this.onWindowResize);
Finally create my chart:
createChart() {
// Create a circle
this.originalCircle = {
"cx": 50,
"cy": -15,
"r": 20
};
// Append an svg object to the chart element and save in the variable 'svgViewport'.
this.svgViewport = d3.select(this.element).append('svg')
.attr("class", "chart")
.attr("width", this.svgWidth)
.attr("height", this.svgHeight);
// Inner Drawing Space
this.innerSpace = this.svgViewport.append("g")
.attr("class", "inner-space")
.attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")")
.call(this.zoom);
// Add the circle.
this.circles = this.innerSpace.append('circle')
.attr("id", "circles")
.attr("cx", this.xScale(this.originalCircle.cx))
.attr("cy", this.yScale(this.originalCircle.cy))
.attr('r', this.originalCircle.r);
// Draw Axis
this.xChartAxis = this.innerSpace.append("g")
.attr("class", "axisX")
.attr("transform", "translate(0," + this.height + ")")
.call(this.xAxis);
this.yChartAxis = this.innerSpace.append("g")
.attr("class", "axisY")
.call(this.yAxis);
// append zoom area
this.zoomView = this.innerSpace.append("rect")
.attr("class", "zoom")
.attr("width", this.width)
.attr("height", this.height)
.call(this.zoom);
};
The curious thing is that if I refresh the browser and resize the window the axis move and resize as expected, but the circle does not move. When I pan/zoom the circle moves and scales correctly and the axis move correctly. Having pan/zoomed and then resize the window the axis do not move any more and the circle remains static.
I think I am close, but I can't see the next step. Any suggestions very welcome.
Related
I am starting out with d3 and would like to test it in my angular project. I've tried to run this doughnut chart from a reputable source: https://d3-graph-gallery.com/graph/donut_label.html
I am experiencing significant problems with incompatible types, even though I selected v6 on page and I am using d3 v6.0.0 on my machine. For example the line :
const data_ready = pie(Object.entries(data))
gives complaint that:
Argument of type [string, number][] is not assignable to parameter of type (number|{valueOf():number;})[]
Moving forward at
.attr('d', arc)
complains that no overload matches this call
in package.json I have:
dependencies:{
"d3": "6.0.0",
"d3-scale": "^4.0.2",
...
},
devDependencies:{
"#types/d3": "6.0.0",
"#types/d3-scale": "^4.0.2",
...
}
Usually it is a red flag when examples don't work, but the source seem reputable so I am asking for additional debugging help. Is this a problem with #types configuration? How should the code look like? Complete code:
ngAfterViewInit(){
// set the dimensions and margins of the graph
var width = 450
height = 450
margin = 40
// The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
var radius = Math.min(width, height) / 2 - margin
// append the svg object to the div called 'my_dataviz'
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
// Create dummy data
var data = {a: 9, b: 20, c:30, d:8, e:12, f:3, g:7, h:14}
// set the color scale
var color = d3.scaleOrdinal()
.domain(["a", "b", "c", "d", "e", "f", "g", "h"])
.range(d3.schemeDark2);
// Compute the position of each group on the pie:
var pie = d3.pie()
.sort(null) // Do not sort group by size
.value(function(d) {return d.value; })
var data_ready = pie(d3.entries(data))
// The arc generator
var arc = d3.arc()
.innerRadius(radius * 0.5) // This is the size of the donut hole
.outerRadius(radius * 0.8)
// Another arc that won't be drawn. Just for labels positioning
var outerArc = d3.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9)
// Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
svg
.selectAll('allSlices')
.data(data_ready)
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d){ return(color(d.data.key)) })
.attr("stroke", "white")
.style("stroke-width", "2px")
.style("opacity", 0.7)
// Add the polylines between chart and labels:
svg
.selectAll('allPolylines')
.data(data_ready)
.enter()
.append('polyline')
.attr("stroke", "black")
.style("fill", "none")
.attr("stroke-width", 1)
.attr('points', function(d) {
var posA = arc.centroid(d) // line insertion in the slice
var posB = outerArc.centroid(d) // line break: we use the other arc generator that has been built only for that
var posC = outerArc.centroid(d); // 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] = radius * 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 the polylines between chart and labels:
svg
.selectAll('allLabels')
.data(data_ready)
.enter()
.append('text')
.text( function(d) { console.log(d.data.key) ; return d.data.key } )
.attr('transform', function(d) {
var pos = outerArc.centroid(d);
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2
pos[0] = radius * 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')
})
}
I have 2 buttons that i want to use to control what data set I am using for my bar chart. Right now I can click on one and it shows my d3 graph without problems. But when I want to switch to the other graph, I click on the button and it shows me that graph on top of my previous graph. How do I make it so that when I switch between graphs, it only shows me one graph.
var djockey = 'top5jockey.csv'
var dtrainer = 'top5trainer.csv'
// Define SVG area dimensions
var svgWidth = 1500;
var svgHeight = 1000;
// Define the chart's margins as an object
var chartMargin = {
top: 30,
right: 30,
bottom: 130,
left: 30
};
// Define dimensions of the chart area
var chartWidth = svgWidth - chartMargin.left - chartMargin.right;
var chartHeight = svgHeight - chartMargin.top - chartMargin.bottom;
// Select body, append SVG area to it, and set the dimensions
var svg = d3.select("body")
.append("svg")
.attr("height", svgHeight)
.attr("width", svgWidth);
// Append a group to the SVG area and shift ('translate') it to the right and to the bottom
var chartGroup = svg.append("g")
.attr("transform", `translate(${chartMargin.left}, ${chartMargin.top})`);
var btnj = document.getElementById("Jockey")
btnj.addEventListener('click', function(e){
change(e.target.id)
})
var btnt = document.getElementById("Trainer")
btnt.addEventListener('click', function(e){
change(e.target.id)
})
function change(value){
if(value === 'Jockey'){
update(djockey);
}else if(value === 'Trainer'){
update(dtrainer);
}
}
function update(data){
d3.csv(data).then(function(data) {
console.log(data);
// Cast the hours value to a number for each piece of tvData
data.forEach(function(d) {
d.Count = +d.Count;
});
// Configure a band scale for the horizontal axis with a padding of 0.1 (10%)
var xScale = d3.scaleBand()
.domain(data.map(d => d.Name))
.range([0, chartWidth])
.padding(0.1);
// Create a linear scale for the vertical axis.
var yLinearScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.Count)])
.range([chartHeight, 0]);
// Create two new functions passing our scales in as arguments
// These will be used to create the chart's axes
var bottomAxis = d3.axisBottom(xScale);
var leftAxis = d3.axisLeft(yLinearScale).ticks(10);
// Append two SVG group elements to the chartGroup area,
// and create the bottom and left axes inside of them
chartGroup.append("g")
.call(leftAxis);
chartGroup.append("g")
.attr("class", "x_axis")
.attr("transform", `translate(0, ${chartHeight})`)
.call(bottomAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)");
// Create one SVG rectangle per piece of tvData
// Use the linear and band scales to position each rectangle within the chart
chartGroup.selectAll("#bar")
.data(data)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", d => xScale(d.Name))
.attr("y", d => yLinearScale(d.Count))
.attr("width", xScale.bandwidth())
.attr("height", d => chartHeight - yLinearScale(d.Count));
}).catch(function(error) {
console.log(error);
})
};
D3 has a function allowing you to remove all svg elements. Basically, you select the svg, then run .remove() at the top of your event listener. It will clear out all svg elements.
The problem i am facing is that i am not able to write the function following in my code because of version mismatch.
Code is
var zoom = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([1, 10])
.on("zoom", zoomed);
I have tried by this way
const zoom = d3.zoom()
.scaleExtent([1, 4])
.x(this.xScale)
.on('zoom', () => {})
But it does not work for me.
How to write same function in d3 version 5? I want to make line chart scrollable in x axis with y axis as fixed position using d3 version 5
This is my implementation Basic Code
private createLineChart() {
this.width = 2000 - this.margin.left - this.margin.right;
this.height = 600 - this.margin.top - this.margin.bottom;
// X AXIS
this.xScale = d3.scaleBand()
.domain(this.dataset[0].fluencyData.map((data) => {
return new Date(data.date);
}))
.range([0, this.width]);
// Y AXIS
this.yScale = d3.scaleLinear()
.domain([0, 110])
.range([this.height, 0]);
// Line Generator
this.line = d3.line()
.x((data) => this.xScale(new Date(data.date)))
.y((data) => this.yScale(data.wcpm));
// .curve(d3.curveMonotoneX);
// Add SVG to Div
this.svg = d3.select('#displayChart').append('svg')
.attr('preserveAspectRatio', 'xMinYMin meet')
.attr(
'viewBox',
'0 0 ' +
(this.width + this.margin.left + this.margin.right) +
' ' +
(this.height + this.margin.top + this.margin.bottom))
// .attr('width', this.width + this.margin.left + this.margin.right)
// .attr('height', this.height + this.margin.top + this.margin.bottom)
.append('g')
.attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');
// Define the div for the tooltip
this.toolTipDiv = d3.select('#displayChart').append('div')
.attr('class', 'tooltip')
.style('opacity', 0);
// Append XAXIS to the SVG
this.svg.append('g')
.attr('class', 'xAxis')
.attr('transform', 'translate(0,' + this.height + ')')
.call(d3.axisBottom(this.xScale).tickSizeOuter(0).tickFormat(d3.timeFormat('%b %d')));
const zoom = d3.zoom()
.scaleExtent([1, 4])
.extent([100, 100], [this.width - 100, this.height - 100])
.x(this.xScale)
.on('zoom', () => {
console.log(d3.event.transform);
// this.svg.select('#displayChart').attr('d', this.line);
});
this.svg.call(zoom);
// Append YAXIS to SVG
this.svg.append('g')
.attr('class', 'yAxis')
.call(d3.axisLeft(this.yScale).tickSize(-this.width)
);
// Make a Path for Dataset
this.svg.append('path')
.datum(this.dataset[0].fluencyData)
.attr('class', 'line')
.attr('d', this.line)
.attr('transform', 'translate(' + this.margin.left + ',0)');
// Text Heading of DATE in chart
this.svg.append('text')
.attr('transform', 'translate(' + (-20) + ',' + (this.height + 13) + ')')
.attr('dy', '.35em')
.attr('class', ' xAxis')
.text('Date');
}
}
Error I am getting is
LineChartComponent_Host.ngfactory.js? [sm]:1 ERROR TypeError: d3__WEBPACK_IMPORTED_MODULE_2__.zoom(...).scaleExtent(...).x is not a function
at LineChartComponent.push../src/app/line-chart/line-chart.component.ts
With d3v3 and before, the zoom could track a scale's state. From the documentation, scale.x(): "Specifies an x-scale whose domain should be automatically adjusted when zooming." (docs). This modifies the original scale.
D3v4+ does not have zoom.x or zoom.y methods.
With d3v4+, the zoom does not track or modifiy a d3 scale's state. Infact, for d3v4+, the zoom behavior doesn't even track the current zoom state: "Zoom behaviors no longer store the active zoom transform (i.e., the visible region; the scale and translate) internally. The zoom transform is now stored on any elements to which the zoom behavior has been applied.(change log)".
As part of this, and more importantly, "Zoom behaviors are no longer dependent on scales, but you can use transform.rescaleX, transform.rescaleY, transform.invertX or transform.invertY to transform a scale’s domain(change log)".
So rather than have the zoom update the d3 scale, we need to do this ourselves. The most common way this is done is through a reference scale, which remains unchanged, and a scale to which we apply the zoom transform:
var zoom = d3.zoom()
.on("zoom",zoomed)
var x = d3.scaleLinear().... // working scale
var x2 = x.copy(); // reference scale.
function zoomed() {
x = d3.event.transform.rescaleX(x2) // update the working scale.
// do something...
}
So, something like this:
var x = d3.scale.linear()
.domain([0,1])
.range([0,500]);
var zoom = d3.behavior.zoom()
.x(x)
.scaleExtent([1, 10])
.on("zoom", zoomed);
var svg = d3.select("svg")
.call(zoom);
var axis = d3.svg.axis()
.orient("bottom")
.scale(x);
var axisG = svg.append("g")
.attr("transform", "translate(0,30)")
.call(axis);
function zoomed() {
axisG.call(axis);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<svg width="500" height="200"></svg>
Becomes something like that:
var x = d3.scaleLinear()
.domain([0,1])
.range([0,500]);
var x2 = x.copy(); // reference.
var zoom = d3.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
var svg = d3.select("svg")
.call(zoom);
var axis = d3.axisBottom().scale(x)
var axisG = svg.append("g")
.attr("transform", "translate(0,30)")
.call(axis);
function zoomed() {
x = d3.event.transform.rescaleX(x2)
axis.scale(x);
axisG.call(axis);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="200"></svg>
Note that d3.event.transform.rescaleX is for continuous scales - you have an ordinal band scale, so we'll need to use a slightly modified approach for band and/or point scales:
var x = d3.scaleBand()
.domain(d3.range(10).map(function(d) { return d/10; }))
.range([0,500]);
var x2 = x.copy(); // reference.
var zoom = d3.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
var svg = d3.select("svg")
.call(zoom);
var axis = d3.axisBottom().scale(x)
var axisG = svg.append("g")
.attr("transform", "translate(0,30)")
.call(axis);
function zoomed() {
// Rescale the range of x using the reference range of x2.
x.range(x2.range().map(function(d) {
return d3.event.transform.applyX(d);
}))
axisG.call(axis);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="200"></svg>
This is band/point scale solution is based on this issue and Bostock's proposed solution to it
I have a D3.js map based on this: https://bl.ocks.org/mbostock/2374239.
I have added a custom marker and a text on the feature that a user zooms in to a county. However, the marker and text do not stay on the same position as the county when I zoom in or out. My zoom function is as follows:
function zoomToCounty(stname, cntyname) {
d3.json("/topo/us-wgs84.json", function (us) {
var t = projection.translate(); // the projection's default translation
var s = projection.scale() // the projection's default scale
//initialize marker
d3.selectAll(".mark").remove();
d3.selectAll("text").remove();
//reset active to inactive size and color
d3.select("#svgMap2").select("g").select(".active")
.style("stroke-width", "0.5px")
.style("stroke", "#808080");
d3.selectAll(".county")
.classed("active", function (d) {
if (d.properties.StateName === stname && d.properties.County === cntyname) {
var zoom = d3.zoom()
.scaleExtent([1, 6])
.on("zoom", zoomed3);
svg.select("rect")
.call(zoom);
var bounds = path.bounds(d),
dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = 0.9 / Math.max(dx / width, dy / height),
translate = [width / 2 - scale * x, height / 2 - scale * y];
//get centroid
var center = path.centroid(d);
//create marker
d3.select("#svgMap2").select("g")
.append("image")
.attr("xlink:href", "/images/marker2.png")
.attr("width", 14)
.attr("height", 20)
.attr("class", "mark")
.attr("transform", function (d) {
return "translate(" + center + ")";
});
//add text
d3.select("#svgMap2").select("g")
.append("text")
.style("fill", "#000")
.attr("x", x)
.attr("y", y)
.attr("dy", ".05em") //set offset y position
.attr("text-anchor", "middle") //set anchor y justification
.text(cntyname);
svg.transition()
.duration(750)
.call(zoom.transform, d3.zoomIdentity.translate(translate[0], translate[1]).scale(scale));
return true;
}
})
}); //end d3.json
Working website can be found at: http://realtimeceap.brc.tamus.edu/
Thanks in advance.
01-28-2018 Status: I'm still unable to fix this one. I just need help on how to keep my image marker and text on the same coordinates as the selected feature when I zoom in/out using the mouse wheel. Initial zoom is at the middle of the svg with scale = 8. How do I make the marker "STICK" to specified coordinates once a user moves the wheel? HELP!
Solved this problem by:
1. I called the zoom function on the "g" element then
2. I created a function for when user moves the wheel; called this function from the "rect" element.
View the working codes at: http://realtimeceap.brc.tamus.edu
I have created a simple pie chart using D3.js and I wish to pop out each element/path of the pie chart on click event of those elements.
Here is the pie chart I am talking about: jsfiddle.net/ankur881120/kt97oq57.
arcs.filter(function(d) { return d.endAngle - d.startAngle > .2; }).append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
//.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")rotate(" + angle(d) + ")"; })
.attr("transform", function(d) { //set the label's origin to the center of the arc
//we have to make sure to set these before calling arc.centroid
d.outerRadius = outerRadius; // Set Outer Coordinate
d.innerRadius = outerRadius/2; // Set Inner Coordinate
return "translate(" + arc.centroid(d) + ")rotate(" + angle(d) + ")";
Now I want to pop out say element in red color on click of red color element.
Looking for all of your suggestions, to solve this issue.
I just answered a very similar question about this yesterday. Your use case is different enough, so against my better judgement, I'll answer it again.
Essentially, add the click handler and transition your arc "group" (arc and text labels) together:
var arcs = vis.selectAll("g.slice")
// Associate the generated pie data (an array of arcs, each having startAngle,
// endAngle and value properties)
.data(pie)
// This will create <g> elements for every "extra" data element that should be associated
// with a selection. The result is creating a <g> for every object in the data array
.enter()
// Create a group to hold each slice (we will have a <path> and a <text>
// element associated with each slice)
.append("svg:g")
.attr("class", "slice") //allow us to style things in the slices (like text)
// ADDED CLICK HANDLER
.on('click',function(d,i){
d3.select(this)
.transition()
.duration(500)
.attr("transform",function(d){
// this this group expanded out?
if (!d.data._expanded){
d.data._expanded = true;
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
var x = Math.cos(a) * 20;
var y = Math.sin(a) * 20;
// move it away from the circle center
return 'translate(' + x + ',' + y + ')';
} else {
d.data._expanded = false;
// move it back
return 'translate(0,0)';
}
});
});
Updated fiddle.
Complete code:
var canvasWidth = 300, //width
canvasHeight = 300, //height
outerRadius = 100, //radius
color = d3.scale.category20(); //builtin range of colors
var dataSet = [
{"legendLabel":"One", "magnitude":20},
{"legendLabel":"Two", "magnitude":40},
{"legendLabel":"Three", "magnitude":50},
{"legendLabel":"Four", "magnitude":16},
{"legendLabel":"Five", "magnitude":50},
{"legendLabel":"Six", "magnitude":8},
{"legendLabel":"Seven", "magnitude":30}];
var vis = d3.select("body")
.append("svg:svg") //create the SVG element inside the <body>
.data([dataSet]) //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'
// This will create <path> elements for us using arc data...
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
.sort( function(d) { return null; } );
// Select all <g> elements with class slice (there aren't any yet)
var arcs = vis.selectAll("g.slice")
// Associate the generated pie data (an array of arcs, each having startAngle,
// endAngle and value properties)
.data(pie)
// This will create <g> elements for every "extra" data element that should be associated
// with a selection. The result is creating a <g> for every object in the data array
.enter()
// Create a group to hold each slice (we will have a <path> and a <text>
// element associated with each slice)
.append("svg:g")
.attr("class", "slice") //allow us to style things in the slices (like text)
.on('click',function(d,i){
d3.select(this)
.transition()
.duration(500)
.attr("transform",function(d){
if (!d.data._expanded){
d.data._expanded = true;
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
var x = Math.cos(a) * 20;
var y = Math.sin(a) * 20;
return 'translate(' + x + ',' + y + ')';
} else {
d.data._expanded = false;
return 'translate(0,0)';
}
});
});
arcs.append("svg:path")
//set the color for each slice to be chosen from the color function defined above
.attr("fill", function(d, i) { return color(i); } )
//this creates the actual SVG path using the associated data (pie) with the arc drawing function
.attr("d", arc);
// Add a legendLabel to each arc slice...
arcs.append("svg:text")
.attr("transform", function(d) { //set the label's origin to the center of the arc
//we have to make sure to set these before calling arc.centroid
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 dataSet[i].legendLabel; }); //get the label from our original data array
// Add a magnitude value to the larger arcs, translated to the arc centroid and rotated.
arcs.filter(function(d) { return d.endAngle - d.startAngle > .2; }).append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
//.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")rotate(" + angle(d) + ")"; })
.attr("transform", function(d) { //set the label's origin to the center of the arc
//we have to make sure to set these before calling arc.centroid
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; });
// Computes the angle of an arc, converting from radians to degrees.
function angle(d) {
var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
return a > 90 ? a - 180 : a;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>