I'm having an issue with a D3 pie chart where labels are cutoff when they appear. Here's a pic:
I'm new to D3, and am not sure exactly where a fix should be. The logic for making the pie chart is 400 lines, so I made a pastebin: https://pastebin.com/32CxpeDM
Maybe the issue is in this function?
function showDetails(layer, data) {
active.inner = !active.inner
active.outer = false
let lines = guideContainer.selectAll('polyline.guide-line-inner')
.data(pie(data.inner))
let text = guideContainer.selectAll('.inner-text')
.data(pie(data.inner))
if (active.inner) {
// LINES
lines.enter()
.append('polyline')
.attr('points', calcPoints)
.attr('class', 'guide-line-inner')
.style('opacity', 0)
.transition().duration(300)
.style('opacity', d => { return d.data.value <= 0 ? 0 : 1 })
lines.attr('points', calcPoints).attr('class', 'guide-line-inner')
// TEXT
text.enter()
.append('text')
.html(labelText)
.attr('class', 'inner-text label label-circle')
.attr('transform', labelTransform)
.attr('dy', '1.1em')
.attr('x', d => { return (midAngle(d) < Math.PI ? 15 : -15) })
.style('text-anchor', d => { return (midAngle(d)) < Math.PI ? 'start' : 'end' })
.style('opacity', 0)
.call(wrap, 300)
.on('click', d => { vm.$emit('details', d) })
.transition().duration(300)
.style('opacity', d => { return d.data.value <= 0 ? 0 : 1 })
} else {
lines.transition().duration(300)
.style('opacity', 0)
.remove()
text.transition().duration(300)
.style('opacity', 0)
.remove()
}
guideContainer.selectAll('polyline.guide-line-outer').transition().duration(300)
.style('opacity', 0)
.remove()
guideContainer.selectAll('.outer-text').transition().duration(300)
.style('opacity', 0)
.remove()
}
Like I said, I don't know D3 so I'm not sure what my options are for fixing this. Make the chart smaller, fix some problem with its div container, send it to the front, or edit the above code. Ideally this would be a clean fix and not a hack, which is what I've been trying.
What the easiest and cleanest fix for this problem?
Thanks!
It looks like your labels are getting transformed past the edge of your SVG boundary.
I would try to translate the label elements farther left in this function:
function labelTransform (d) {
var pos = guideArc.centroid(d)
pos[0] = (radius + 100) * (midAngle(d) < Math.PI ? 1 : -1)
return 'translate(' + pos + ')'
}
You'll have to chase down where the label line is being generated and shorten that as well.
You might also try braking the label text into more lines in this function:
function labelText (d) {
if ((radius + 100) * (midAngle(d) < Math.PI ? 1 : 0)) {
return '<tspan>' + d.data.display_name + ' </tspan><tspan x="15">' + d3.format('($,.0f')(d.data.value) + '</tspan>'
} else {
return '<tspan>' + d.data.display_name + ' </tspan><tspan x="-15" text-anchor="end">' + d3.format('($,.0f')(d.data.value) + '</tspan>'
}
}
One approach that would conserve space would be to use d3 tooltips instead of fixed-position labels as in this fiddle (created by another jsfiddle user, I just added the margin)
Javascript:
//Width/height
var w = 300;
var h = 300;
var dataset = [5, 10, 20, 45, 6, 25];
var outerRadius = w / 2;
var innerRadius = w / 3;
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var pie = d3.layout.pie();
var color = d3.scale.category10();
//Create SVG element
var svg = d3.select("#vis")
.append("svg")
.attr("width", w)
.attr("height", h);
//Set up groups
var arcs = svg.selectAll("g.arc")
.data(pie(dataset))
.enter()
.append("g")
.attr("class", "arc")
.attr("transform", "translate(" + outerRadius + "," + outerRadius + ")");
//Draw arc paths
arcs.append("path")
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc);
//Labels
arcs.append("text")
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.text(function(d) {
return d.value;
});
//
var tip = d3.tip()
.attr("class", "d3-tip")
.html(function(d, i) {
return d.value
})
svg.call(tip)
arcs
.on("mouseover", tip.show)
.on("mouseout", tip.hide)
This uses a d3 tip library by Justin Palmer, that can be found on Github.
Also, you could consider making the circle smaller?
Hope this helps
Related
I am trying to get Zoom to work, I am new to D3 and I find it very abstract and not intuitive. I recently finished a beginners course in JavaScript but D3 feels like a completely new language.
I found this topic which might help a bit.
D3 Zooming in graph
I also found the following code that created the graph on the web, the simplest I could find and I don't understand all of it. Now I wanna zoom in and used code that I also found on the web but which has to be adapted. I understood that much that the zoom variable at the top is calling a function called NeuerChart which has the actual zooming behaviour in it. It needs to zoom the graph and the axes when I spin the mousewheel.
In the end I need to implement this into a real problem, thanks. Using D3.v5.
<script>
let zoom = d3.zoom()
.scaleExtent([0.5, 10])
.extent([[0, 0], [width, height]])
.on('zoom', NeuerChart);
// Step 1
let min = 0;
let max = 100;
let x_arr = [];
let y_arr = [];
let s_arr = [];
let z_arr = [];
for (let i = 0; i < 360; i++) {
var r = Math.round(Math.random() * (max - min)) + min;
x_arr[i]= i;
y_arr[i]= r;
z_arr.push([x_arr[i],y_arr[i]]);
}
s_arr = y_arr.sort(function(a, b){return a - b});
let neu_arr = [];
let zz_arr = [];
for (let i = 0; i < 360; i++) {
neu_arr[i]= i;
zz_arr.push([neu_arr[i], s_arr[i]]);
}
console.log(z_arr);
console.log(zz_arr);
var dataset1 = zz_arr;
// Step 3
var svg = d3.select("svg"),
margin = 200,
width = svg.attr("width") - margin, //1700
height = svg.attr("height") - margin //700
// Step 4
var xScale = d3.scaleLinear().domain([0 , 365]).range([0, width]),
yScale = d3.scaleLinear().domain([0, 105]).range([height, 0]);
var g = svg.append("g")
.attr("transform", "translate(" + 100 + "," + 100 + ")");
// Step 5
// Title
svg.append('text')
.attr('x', width/2 + 100)
.attr('y', 100)
.attr('text-anchor', 'middle')
.style('font-family', 'Helvetica')
.style('font-size', 20)
.text('Line Chart');
// X label
svg.append('text')
.attr('x', width/2 + 100)
.attr('y', height - 15 + 150)
.attr('text-anchor', 'middle')
.style('font-family', 'Helvetica')
.style('font-size', 12)
.text('Zeitachse');
// Y label
svg.append('text')
.attr('text-anchor', 'middle')
.attr('transform', 'translate(60,' + 500 + ')rotate(-90)')
.style('font-family', 'Helvetica')
.style('font-size', 12)
.text('Wert');
// Step 6
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale).ticks(7).tickValues([0, 60, 120, 180, 240, 300, 360]));
g.append("g")
.call(d3.axisLeft(yScale));
// Step 7
svg.append('g')
.selectAll("dot")
.data(dataset1)
.enter()
.append("circle")
.attr("cx", function (d) { return xScale(d[0]); } )
.attr("cy", function (d) { return yScale(d[1]); } )
.attr("r", 3)
.attr("transform", "translate(" + 100 + "," + 100 + ")")
.style("fill", "#CC0000");
// Step 8
var line = d3.line()
.x(function(d) { return xScale(d[0]); })
.y(function(d) { return yScale(d[1]); })
.curve(d3.curveMonotoneX)
svg.append("path")
.datum(dataset1)
.attr("class", "line")
.attr("transform", "translate(" + 100 + "," + 100 + ")")
.attr("d", line)
.style("fill", "none")
.style("stroke", "#CC0000")
.style("stroke-width", "2")
svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.attr("transform", "translate(" + 100 + "," + 100 + ")")
.call(zoom);
function NeuerChart () {
// recover the new scale
var newX = d3.event.transform.rescaleX(xScale);
var newY = d3.event.transform.rescaleY(yScale);
// update axes with these new boundaries
xAxis.call(d3.axisBottom(newX))
yAxis.call(d3.axisLeft(newY))
}
</script>
I added the code here in Codepen:
https://codepen.io/Dvdscot/pen/zYjpzVP
This is how it should work:
https://codepen.io/Dvdscot/pen/BaxJdKN
Problem is solved, see the code at Codepen:
`
Reset zoom
`https://codepen.io/Dvdscot/pen/zYjpzVP
I want to create a visual whereby a swarm contains one big circle and a bunch of satellite circles clinging around it. For a simple demonstration, I have prepared a small version of the data set; each item in the array should have one big circle and then however many smaller circles clinging to it:
var data = [
{'wfoe':'wfoe1','products':d3.range(20)},
{'wfoe':'wfoe2','products':d3.range(40)},
{'wfoe':'wfoe3','products':d3.range(10)}
];
Here is a snippet of my progress:
var margins = {
top: 100,
bottom: 300,
left: 100,
right: 100
};
var height = 250;
var width = 900;
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 = [
{'wfoe':'wfoe1','products':d3.range(20)},
{'wfoe':'wfoe2','products':d3.range(40)},
{'wfoe':'wfoe3','products':d3.range(10)}
];
var columns = 4;
var spacing = 250;
var vSpacing = 250;
var fmcG = graphGroup.selectAll('.fmc')
.data(data)
.enter()
.append('g')
.attr('class', 'fmc')
.attr('id', (d, i) => 'fmc' + i)
.attr('transform', (d, k) => {
var horSpace = (k % columns) * spacing;
var vertSpace = ~~((k / columns)) * vSpacing;
return "translate(" + horSpace + "," + vertSpace + ")";
});
var xScale = d3.scalePoint()
.range([0, width])
.domain([0, 100]);
var rScale = d3.scaleThreshold()
.range([50,5])
.domain([0,1]);
data.forEach(function(d, i) {
d.x = (i % columns) * spacing;
d.y = ~~((i / columns)) * vSpacing;
});
var simulation = d3.forceSimulation(data)
.force("x", d3.forceX(function(d,i) {
return (i % columns) * spacing;
}).strength(0.1))
.force("y", d3.forceY(function(d,i) {
return ~~((i / columns)) * vSpacing;
}).strength(0.01))
.force("collide", d3.forceCollide(function(d,i) { return rScale(i)}))
.stop();
simulation.tick(75);
fmcG.selectAll(null)
.data(data)
.enter()
.append("circle")
.attr("r", function(d,i) {
return rScale(i)
})
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.style('fill',"#003366");
<script src="https://d3js.org/d3.v5.min.js"></script>
I want to quickly point out that the big circle doesn't represent any data point (they are just going to house a name / logo). I just thought that including it in the simulation data would be the easiest way to introduce the needed force logic for the swarm circles. I thought that an elegant solution would be to use a threshold scale and let the first (i=0) datum always be the biggest circle. Here is what I mean:
var rScale = d3.scaleThreshold()
.range([0, 1])
.domain([50, 5]);
fmcG.selectAll(null)
.data(data)
.enter()
.append("circle")
.attr("r", function(d,i) {
return rScale(i)
})
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.style('fill',"#003366");
The result I mentioned above (three big circles with little circles all around them) was not achieved, and in fact very few circles were appended and the variable radius component didn't seem to be working as I thought it would. (also no errors displayed in the log).
Question
How can I iteratively create swarms that start with one big circle and append subsequent smaller circles around the initial big circle, as applicable to the sample data set?
You could use a force simulation, like below, only this gives non-deterministic results. However, it's really good when you want to gradually add more nodes. In the below solution, I gave all related nodes a link to the center node, but didn't draw it. This made it possible for linked nodes to attract heavily.
On the other hand, you could also use a bubble chart if you want D3 to find the optimal packing solution for you, without the force working on them. Only downside is you'd have to call the packing function with all nodes every time, and the other nodes might shift because of the new one.
var margins = {
top: 100,
bottom: 300,
left: 100,
right: 100
};
var height = 250;
var width = 900;
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 = [{
'wfoe': 'wfoe1',
'products': d3.range(20).map(function(v) {
return v.toString() + '_wfoe1';
})
},
{
'wfoe': 'wfoe2',
'products': d3.range(40).map(function(v) {
return v.toString() + '_wfoe2';
})
},
{
'wfoe': 'wfoe3',
'products': d3.range(10).map(function(v) {
return v.toString() + '_wfoe3';
})
}
];
var columns = 4;
var spacing = 250;
var vSpacing = 250;
function dataToNodesAndLinks(d) {
// Create one giant array of points and
// one link between each wfoe and each product
var nodes = [{
id: d.wfoe,
center: true
}];
var links = [];
d.products.forEach(function(p) {
nodes.push({
id: p,
center: false
});
links.push({
source: d.wfoe,
target: p
});
});
return {
nodes: nodes,
links: links
};
}
var fmcG = graphGroup.selectAll('.fmc')
.data(data.map(function(d, i) {
return dataToNodesAndLinks(d, i);
}))
.enter()
.append('g')
.attr('class', 'fmc')
.attr('id', (d, i) => 'fmc' + i)
.attr('transform', (d, k) => {
var horSpace = (k % columns) * spacing;
var vertSpace = ~~((k / columns)) * vSpacing;
return "translate(" + horSpace + "," + vertSpace + ")";
});
var xScale = d3.scalePoint()
.range([0, width])
.domain([0, 100]);
var rScale = d3.scaleThreshold()
.range([50, 5])
.domain([0, 1]);
fmcG.selectAll("circle")
.data(function(d) {
return d.nodes;
})
.enter()
.append("circle")
.attr("id", function(d) {
return d.id;
})
.attr("r", function(d, i) {
return d.center ? rScale(i) * 5 : rScale(i);
})
.style('fill', function(d) { return d.center ? "darkred" : "#003366"; })
fmcG
.each(function(d, i) {
d3.forceSimulation(d.nodes)
.force("collision", d3.forceCollide(function(d) {
return d.center ? rScale(i) * 5 : rScale(i);
}))
.force("center", d3.forceCenter(0, 0))
.force("link", d3
.forceLink(d.links)
.id(function(d) {
return d.id;
})
.distance(0)
.strength(2))
.on('tick', ticked);
});
function ticked() {
fmcG.selectAll("circle")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
<script src="https://d3js.org/d3.v5.js"></script>
I don´t quite get the hang out of the bisect function in d3.js in order to highlight the values by a vertical line.
I already got it working for one line/path but the performance is poor, at least in google chrome. Probably because my function calculates every point on the path instead of the datapoints only, which is what I actually need.
Here is the code:
/*create svg element*/
var svg = d3.select('.linechart')
.append('svg')
.attr('width', w)
.attr('height', h)
.attr('id', 'chart');
/*x scale*/
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d[0];
})])
.range([padding, w - padding]);
/*y scale*/
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d[1];
})])
.range([h - padding, padding]);
/*x axis*/
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.ticks(20)
.tickSize(0, 0)
//.tickPadding(padding);
/*append x axis*/
svg.append('g')
.attr({
'class': 'xaxis',
//'transform': 'translate(0,' + (h - padding) + ')'
'transform': 'translate(0,' + 0 + ')'
})
.call(xAxis);
/*y axis*/
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left')
.tickSize(0, 0)
.tickValues([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]);
/*append y axis*/
svg.append('g')
.attr({
'class': 'yaxis',
'transform': 'translate(' + padding + ',0)'
})
.call(yAxis);
/*define line*/
var lines = d3.svg.line()
.x(function(d) {
return xScale(d[0])
})
.y(function(d) {
return yScale(d[1])
})
.interpolate('monotone');
/*append line*/
var path = svg.append('path')
.attr({
'd': lines(dataset),
'fill': 'none',
'class': 'lineChart'
});
/*get length*/
var length = svg.select('.lineChart').node().getTotalLength();
/*animate line chart*/
svg.select('.lineChart')
.attr("stroke-dasharray", length + " " + length)
.attr("stroke-dashoffset", length)
.transition()
.ease('linear')
.delay(function(d) {
return dataset.length * 100;
})
.duration(3000)
.attr("stroke-dashoffset", 0);
/*add points*/
var points = svg.selectAll('circle')
.data(dataset)
.enter()
.append('circle');
/*point attributes*/
points.attr('cy', function(d) {
return yScale(d[1])
})
.style('opacity', 0)
.transition()
.duration(1000)
.ease('elastic')
.delay(function(d, i) {
return i * 100;
})
.attr({
'cx': function(d) {
return xScale(d[0]);
},
'cy': function(d) {
return yScale(d[1]);
},
'r': 5,
'class': 'datapoint',
'id': function(d, i) {
return i;
}
})
.style('opacity', 1);
// LINES INDIVIDUAL
function drawIndividualLines (){
/*define line*/
var linesIndividual = d3.svg.line()
.x(function(d) {
return xScale(d[0])
})
.y(function(d) {
return yScale(d[1])
})
.interpolate('monotone');
/*append line*/
var pathIndividual = svg.append('path')
.attr({
//'d': linesIndividual(datasetIndividual),
'd': linesIndividual(datasetIndividual),
'fill': 'none',
'class': 'lineChartIndividual'
});
/*get length*/
var lengthIndividual = svg.select('.lineChartIndividual').node().getTotalLength();
/*animate line chart*/
svg.select('.lineChartIndividual')
.attr("stroke-dasharray", lengthIndividual + " " + lengthIndividual)
.attr("stroke-dashoffset", lengthIndividual)
.transition()
.ease('linear')
.delay(function(d) {
return datasetIndividual.length * 100;
})
.duration(3000)
.attr("stroke-dashoffset", 0);
/*add points*/
var pointsIndividual = svg.selectAll('circleIndividual')
.data(datasetIndividual)
.enter()
.append('circle');
/*point attributes*/
pointsIndividual.attr('cy', function(d) {
return yScale(d[1])
})
.style('opacity', 0)
.transition()
.duration(1000)
.ease('elastic')
.delay(function(d, i) {
return i * 100;
})
.attr({
'cx': function(d) {
return xScale(d[0]);
},
'cy': function(d) {
return yScale(d[1]);
},
'r': 5,
'class': 'datapointIndividual',
'id': function(d, i) {
return i;
}
})
.style('opacity', 1);
};
$(".individual").click(function() {
drawIndividualLines();
drawIndividualLegend();
swapShifts();
});
var mouseG = svg.append("g")
.attr("class", "mouse-over-effects");
mouseG.append("path") // this is the white vertical line to follow mouse
.attr("class", "mouse-line")
.style("stroke", "white")
.style("stroke-width", "1px")
.style("opacity", "0");
var linesForMouse = document.getElementsByClassName('lineChart');
var linesIndividualForMouse = document.getElementsByClassName('lineChartIndividual');
var mousePerLine = mouseG.selectAll('.mouse-per-line')
.data(dataset)
.enter()
.append("g")
.attr("class", "mouse-per-line");
mousePerLine.append("circle")
.attr("r", 7)
.style("stroke", "#A0B1AB")
.style("fill", "none")
.style("stroke-width", "1px")
.style("opacity", "0");
mousePerLine.append("text")
.attr("transform", "translate(10,3)");
mouseG.append('svg:rect') // append a rect to catch mouse movements on canvas
.attr('width', w) // can't catch mouse events on a g element
.attr('height', h)
.attr('fill', 'none')
.attr('pointer-events', 'all')
.on('mouseout', function() { // on mouse out hide line, circles and text
d3.select(".mouse-line")
.style("opacity", "0");
d3.selectAll(".mouse-per-line circle")
.style("opacity", "0");
d3.selectAll(".mouse-per-line text")
.style("opacity", "0");
})
.on('mouseover', function() { // on mouse in show line, circles and text
d3.select(".mouse-line")
.style("opacity", "1");
d3.selectAll(".mouse-per-line circle")
.style("opacity", "1");
d3.selectAll(".mouse-per-line text")
.style("opacity", "1");
})
.on('mousemove', function() { // mouse moving over canvas
var mouse = d3.mouse(this);
d3.select(".mouse-line")
.attr("d", function() {
var d = "M" + mouse[0] + "," + height;
d += " " + mouse[0] + "," + 0;
return d;
});
d3.selectAll(".mouse-per-line")
.attr("transform", function(d, i) {
//console.log(w/mouse[0])
//var xDate = xScale.invert(mouse[0]),
//bisect = d3.bisector(function(d) { return d.date; }).right;
// idx = bisect(d.values, xDate);
var beginning = 0,
end = length,
target = null
console.log(end);
while (true) {
target = Math.floor((beginning + end) / 2);
//pos = linesForMouse[i].getPointAtLength(target);
pos = svg.select('.lineChart').node().getPointAtLength(target);
//console.log(pos);
if ((target === end || target === beginning) && pos.x !== mouse[0]) {
break;
}
if (pos.x > mouse[0]) end = target;
else if (pos.x < mouse[0]) beginning = target;
else break; //position found
}
d3.select(this).select('text')
.text(yScale.invert(pos.y).toFixed(2))
.attr("fill", "#fff");
return "translate(" + mouse[0] + "," + pos.y + ")";
});
});
Here is a Fiddle:
https://jsfiddle.net/mindcraft/vk2w7k2f/2/
So my questions are:
How do I manage to highlight the datapoints only? (Through the bisect function I don´t understand yet, I guess…)
How can I apply the same functionality to the second line (visible after clicking the "Show individual"-button in a more efficient way?
Thank you in advance!
Let me see if I can explain d3.bisector() for you. It will give you a huge performance improvement over searching every value between your two data points. A bisector is a form of array search for use on sorted data. But instead of searching for a particular value, you are searching for the proper index at which to insert an arbitrary value x in order to maintain sort order (a bisector bisects your array into two halves, one with values less than or equal to x and one with values greater than x). This index also gives you the closest values in your dataset to the new value (namely, index and index-1. This is how bisector() is really useful in your example. It will basically return the two closest values in your dataset to the location of the mouse. All you have to do is the arithmetic to find which of the two is the closest.
Here is an example of the process from https://bl.ocks.org/micahstubbs/e4f5c830c264d26621b80b754219ae1b with my comments mapping to my above explanation:
function mousemove() {
//convert absolute coordinates to the proper scale
const x0 = x.invert(d3.mouse(this)[0]);
//bisect the data
const i = bisectDate(data, x0, 1);
//get the two closest elements to the mouse
const d0 = data[i - 1];
const d1 = data[i];
//check which one is actually the closest
const d = x0 - d0.date > d1.date - x0 ? d1 : d0;
//draw your lines
focus.attr('transform', `translate(${x(d.date)}, ${y(d.close)})`);
focus.select('line.x')
.attr('x1', 0)
.attr('x2', -x(d.date))
.attr('y1', 0)
.attr('y2', 0);
focus.select('line.y')
.attr('x1', 0)
.attr('x2', 0)
.attr('y1', 0)
.attr('y2', height - y(d.close));
focus.select('text').text(formatCurrency(d.close));
}
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; });
I am making an interactive tool for creating Sunburst diagrams like this one with d3.js, svg and JQuery. The code for drawing the diagram is from that page, with a few minor modifications. I'm trying to draw text labels on the sections of the diagram, but although the elements are showing up in the Web Inspector (Chrome), they aren't visible on screen. I have tried to adapt code from here, and to some extent this has worked (Web Inspector says the elements exist), but I am mystified as to why the elements themselves don't show up. This is my code - the section for drawing labels is near the bottom. I would just use the code from the example page with labels, but the layout is very different and I'd have to start from scratch again.
var width = 850,
height = 850,
radius = Math.min(width, height) / 2;
var svg = d3.select("#vis-wrap")
.insert("svg", "*")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height * 0.52 + ")");
var partition = d3.layout.partition()
.sort(null)
.size([2 * Math.PI, radius * radius])
.value(function(d) { return 1; });
var arc = d3.svg.arc()
.startAngle(function(d) { return d.x; })
.endAngle(function(d) { return d.x + d.dx; })
.innerRadius(function(d) { return Math.sqrt(d.y); })
.outerRadius(function(d) { return Math.sqrt(d.y + d.dy); });
var path = svg.datum(data).selectAll("path")
.data(partition.nodes)
.enter().append("path")
.attr("display", function(d) { return d.depth ? null : "none"; }) // hide inner ring
.attr("d", arc)
.style("stroke", "#fff")
.style("fill", function(d) {return d.color;} )
.style("fill-rule", "evenodd")
.each(stash);
// Problem here
path.insert("text", "*")
.attr("transform", function(d) { return "rotate(" + (d.x + d.dx / 2 - Math.PI / 2) / Math.PI * 180 + ")"; })
.attr("x", function(d) { return Math.sqrt(d.y); })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d) { return d.name; });
d3.selectAll("input[name=mode]").on("change", function change() {
var value = this.value === "count"
? function() { return 1; }
: function(d) { return d.size; };
path.data(partition.value(value).nodes)
.transition()
.duration(1500)
.attrTween("d", arcTween);
});
// Stash the old values for transition.
function stash(d) {
d.x0 = d.x;
d.dx0 = d.dx;
}
// Interpolate the arcs in data space.
function arcTween(a) {
var i = d3.interpolate({x: a.x0, dx: a.dx0}, a);
return function(t) {
var b = i(t);
a.x0 = b.x;
a.dx0 = b.dx;
return arc(b);
};
}
d3.select(self.frameElement).style("height", height + "px");
You're making the text a child of the path i.e.
<path ...>
<text>something</text>
</path>
I'm afraid that's not valid. You need to make the text element a sibling.
Confusingly you've called the <g> element you've created svg but it want's to be a child of that i.e.
svg.insert("text")
rather than path.insert("text")