So I have a piechart that all transitions will not work on with the message that they're not a function. Which is true when I dig in the console. The window.d3 har a transition function, but not d3.selectAll('path').transition
I'm a bit of a loss as to why this does not work. Obviously my selection to do the transition is wrong, but how?
(function(d3) {
'use strict';
var tooltip = d3.select('body')
.append('div')
.attr('class', 'pie-tooltip')
.style("opacity", 0);
/**
* Width and height has to be the same for a circle, the variable is in pixels.
*/
var width = 350;
var height = 350;
var radius = Math.min(width, height) / 2;
/**
* D3 allows colours to be defined as a range, beneath is input the ranges in same order as our data set above. /Nicklas
*/
var color = d3.scaleOrdinal()
.range(['#ff875e', '#f6bc58', '#eae860', '#85d280']);
var svg = d3.select('#piechart')
.append('svg')
.attr('width', width+20)
.attr('height', height+20)
.append('g')
.attr('transform', 'translate(' + ((width+20) / 2) +
',' + ((height+20) / 2) + ')');
var arc = d3.arc()
.innerRadius(0)
.outerRadius(radius);
/**
* bArc = biggerArc, this is the arc with a bigger outerRadius thats used when a user mouseovers.
*/
var bArc = d3.arc()
.innerRadius(0)
.outerRadius(radius*1.05);
var pie = d3.pie()
.value(function(d){
return d.value;
})
.sort(null);
var path = svg.selectAll('path')
.data(pie(data))
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d) {
return color(d.data.color);
});
path.transition()
.duration(600)
.attrTween("d", makePieAnimation);
path.on("mouseover", function(d){
d3.select(this)
.attr("width", width+10)
.attr("height", height+10);
tooltip.transition()
.duration(200)
.style("opacity", .9)
.style("display", null)
.text(d.data.label + ": " + d.data.value);
d3.select(this).transition()
.duration(300)
.style('fill', d.data.highlight).attr("d", bArc);
});
path.on("mousemove", function(){
tooltip.style("top", (event.pageY-10)+"px")
.style("left",(event.pageX+10)+"px");
});
path.on("mouseout", function(d){
d3.select(this).style('fill', d.data.color);
tooltip.transition()
.duration(300)
.style("opacity", 0);
d3.select(this).transition()
.duration(300)
.attr("d", arc);
});
/**
* makePieAnimation() animates the creation of the pie, setting startangles to 0, interpolating to full circle on creation in path.transition. D3 magic.
* b is an array of arc objects.
*/
function makePieAnimation(b) {
b.innerRadius = 0;
var angles = d3.interpolate({startAngle: 0, endAngle: 0}, b);
return function(t) {
return arc(angles(t));
};
}
})(window.d3);
$.each(data, function (index, value) {
$('#legend').append('<span class="label label-legend" style="background-color: ' + value['color'] + '">' + value['label'] + ': ' + value['value'] + '</span>');
});
EDIT:
After digging around Ive found that the d3 file used by typo3 is manually edited: https://forge.typo3.org/issues/83741
I cannot see how this impacts this issue, but it does. When using a CDN with d3 v4.12.2 the error disappears.
Related
I need to make my doughnut chart a horizontal graph like in this image >
this is the code that i use for other doughnut charts
var dataset = {
hddrives: [total - value, value],
};
var width = 460,
height = 300,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range([secondColor, mainColor]);
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 100)
.outerRadius(radius - 70);
var svg = d3.select(divName).append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
//Draw the Circle
svg.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", 65)
.attr("fill", "#F6FBF3");
var path = svg.selectAll("path")
.data(pie(dataset.hddrives))
.enter().append("path")
.attr("class", "arc")
.attr("fill", function (d, i) { return color(i); })
.attr("d", arc);
svg.append("text")
.attr("dy", "0em")
.style("text-anchor", "middle")
.attr("class", "inside")
.attr("font-size", "30px")
.text(function (d) { return value; });
svg.append("text")
.attr("dy", "1.5em")
.style("text-anchor", "middle")
.attr("class", "data")
.text(function (d) { return nomeGtin; });
}
I tried messing around with the attr values and the arc value, but without success, any ideas on how to approach this? Thanks
That isn't much of a donut chart, it's now a stacked bar chart (with a single bar). The pie and arc helpers aren't much help for that, they are concerned with calculating angles and circular things; you are now dealing with rectangles. d3.stack could help, but is probably overkill. Here's a quicky where I've just done the math (ie positioning) myself:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.17" data-semver="3.5.17" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>
</head>
<body>
<script>
var width = 500,
height = 200,
w = 300,
h = 100;
var svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height);
var total = 0,
l = 0,
// fake random data
raw = d3.range(5).map(function(d){
var v = Math.random() * 10;
total += v;
return v;
}),
// calculate percents and cumulative position
data = raw.map(function(d){
var rv = {
v: d,
l: l,
p: d/total
}
l += rv.p;
return rv;
});
// scale and color
var s = d3.scale.linear()
.range([0, w])
.domain([0, 1]),
c = d3.scale.category20();
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('x', function(d){
return s(d.l) + width/2 - w/2; // place based on cumulative
})
.attr('width', function(d){
return s(d.p); // width from scale
})
.attr('height', h)
.attr('y', height/2 - h/2)
.style('fill', function(d,i){
return c(i);
})
.style('stroke', 'white')
.style('stroke-width', '2px');
</script>
</body>
</html>
initiaa Bar chart
I want to convert this bar chart into pie chart. I have tried plaing around with the transition function and appending code. But it doesnot seem to work.
edited pen
Here's the code for my pie chart
var width = 150;
var height = 150;
var radius = Math.min(width, height) / 2;
var donutWidth = 75;
var legendRectSize = 18;
var legendSpacing = 4;
var color = d3.scale.category20b();
var svg = d3.select('#chart')
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + (width / 2) +
',' + (height / 2) + ')');
var arc = d3.svg.arc()
.innerRadius(radius - donutWidth)
.outerRadius(radius);
var pie = d3.layout.pie()
.value(function (d) {
return d.count;
})
.sort(null);
var tooltip = d3.select('#chart')
.append('div')
.attr('class', 'tooltip');
tooltip.append('div')
.attr('class', 'label');
tooltip.append('div')
.attr('class', 'count');
tooltip.append('div')
.attr('class', 'percent');
//d3.csv('weekdays.csv', function (error, dataset) {
dataset.forEach(function (d) {
d.count = +d.count;
d.enabled = true; // NEW
});
var path = svg.selectAll('path')
.data(pie(dataset))
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function (d, i) {
return color(d.data.label);
}) // UPDATED (removed semicolon)
.each(function (d) {
this._current = d;
}); // NEW
path.on('mouseover', function (d) {
var total = d3.sum(dataset.map(function (d) {
return (d.enabled) ? d.count : 0; // UPDATED
}));
var percent = Math.round(1000 * d.data.count / total) / 10;
tooltip.select('.label').html(d.data.label);
tooltip.select('.count').html(d.data.count);
tooltip.select('.percent').html(percent + '%');
tooltip.style('display', 'block');
});
path.on('mouseout', function () {
tooltip.style('display', 'none');
});
The data linkage seemd to be the issue. Everytime the console said invalid values.
Here;s the working fiddle
var data = [10,20,30,40,60, 80, 20, 50];
// the D3 bits...
var color = d3.scale.category10();
var width = 180;
var height = 180;
var pie = d3.layout.pie().sort(null);
var arc = d3.svg.arc()
.outerRadius(width / 2 * 0.9)
.innerRadius(width / 2 * 0.5);
var svg = d3.select(element[0]).append('svg')
.attr({width: width, height: height})
.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
// add the <path>s for each arc slice
svg.selectAll('path').data(pie(data)) // our data
.enter().append('path')
.style('stroke', 'white')
.attr('d', arc)
.attr('fill', function(d, i){ return color(i) });
I have a CSV file containing a hundreds of lines here's a sample :
city.csv:
City,JanTemp,Lat,Long
Indianapolis IN,21,39.8,86.9
Des_Moines IA,11,41.8,93.6
Wichita KS,22,38.1,97.6
Louisville KY,27,39,86.5
New_Orleans LA,45,30.8,90.2
Portland ME,12,44.2,70.5
Baltimore MD,25,39.7,77.3
Boston MA,23,42.7,71.4
Detroit MI,21,43.1,83.9
Minneapolis MN,2,45.9,93.9
St_Louis MO,24,39.3,90.5
Helena MT,8,47.1,112.4
Omaha NE,13,41.9,96.1
Concord NH,11,43.5,71.9
Atlantic_City NJ,27,39.8,75.3
Albuquerque NM,24,35.1,106.7
Albany NY,14,42.6,73.7
New_York NY,27,40.8,74.6
What I want to do is create a pie chart representing JanTemp for every 10 rows.
Here's my initial code to create a pie chart for all the rows :
script:
<script>
var width = 500;
var height = 500;
var radius = Math.min(width, height) / 2;
var donutWidth = 120;
var legendRectSize = 18;
var legendSpacing = 4;
var color = d3.scale.category20();
var svg = d3.select('#chart')
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + (width / 2) +
',' + (height / 2) + ')');
var arc = d3.svg.arc()
.innerRadius(radius - donutWidth)
.outerRadius(radius);
var pie = d3.layout.pie()
.value(function(d) { return d.JanTemp; })
.sort(null);
d3.csv('city.csv', function(error, dataset) {
dataset.forEach(function(d) {
d.JanTemp = +d.JanTemp;
});
var path = svg.selectAll('path')
.data(pie(dataset))
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d, i) {
return color(d.data.City);
});
var legend = svg.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * color.domain().length / 2;
var horz = -2 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) { return d; });
});
</script>
The code is working yet the visualization is bad.
The question is : How can I create a pie chart for every 10 rows in the csv file ? (Where also, can I add the property to only get rows by 10 ?) Is it even possible ?
You have two ways to do this, the first is to simply repeat what you have already and create several SVGs, one for each piechart.
The second is a bit more elegant, and involves a single SVG controlled by D3.
You'll first need to reorder your data into chunks of 10:
function( alldata ) {
var dataDivide = [], i, chunk = 10;
for (i=0; i<alldata.length; i+=chunk)
{
dataDivide.push(alldata.slice(i, i+chunk));
}
}
You can now use D3 to divide up your SVG and then set the chunks to be your data for each piechart:
var svg.selectAll("g")
.data( dataDivide )
.enter()
.append("g")
// position the g, etc.
.selectAll('path')
.data( function(d) {
return pie(d); // d is a chunk
})
.enter()
.append('path')
// etc.
I'm trying to plot a pie chart with a legend inside of it. And I got into troubles to get it plotted, since I get the errors abound undefined variables. I managed to draw the chart itself and the half of the legend, but not in the right colors, what should match the pie chart.
function drawPieChart(d3div, chart_data) {
// chart_data.data is a list of data elements.
// each should contain fields: val, col, name
d3div.html(""); // clear the div
var title = getopt(chart_data, 'title', '');
// desired width and height of chart
var w = getopt(chart_data, 'width', 300);
var h = getopt(chart_data, 'height', 300);
var pad = getopt(chart_data, 'pad', 50);
var textmargin = getopt(chart_data, 'textmargin', 20);
var r = Math.min(w, h) / 2 - pad; // radius of pie chart
var div = d3div.append('div');
if(title !== '') {
div.append('p').attr('class', 'pietitle').text(title);
}
var arc = d3.svg.arc()
.outerRadius(r)
.cornerRadius(20)
.innerRadius(150);
var arcLarge = d3.svg.arc()
.innerRadius(150)
.cornerRadius(20)
.outerRadius(r + 50);
var toggleArc = function(p){
p.state = !p.state;
var dest = p.state ? arcLarge : arc;
d3.select(this).select("path").transition()
.duration(160)
.attr("d", dest);};
var pie = d3.layout.pie()
.padAngle(.03)
.sort(null)
.value(function(d) { return d.val; });
var svg = d3.select("#piechart").append("svg")
.attr("width", w)
.attr("height", h)
.append("g")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
var g = svg.selectAll(".arc")
.data(pie(chart_data.data))
.enter().append("g")
.attr("class", "arc")
.attr("stroke", "#999")
.attr("id",function(d){return d.data;})
.on("mouseover",toggleArc)
.on("mouseout",toggleArc);
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return d.data.col; });
var color = d3.scale.category20b();
var legendRectSize = 18;
var legendSpacing = 4;
// FROM here the code is not produced the desired result
var legend = svg.selectAll('.legend')
.data(chart_data.data)
.enter()
.append('g')
.attr('class', 'legend')
.attr("id",function(d){return d.data;})
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * chart_data.data.length / 2;
var horz = -2 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.data(chart_data.data)
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style("fill", function(d) { return d.data.col; });
legend.append("text")
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) { return d.data.name; });
}
The code actually works fine untill the line var legend = svg.selectAll('.legend')
Then i start to define the legend, but D3 complains about undefined d.data every time i try to access d.data below the line I written above(also in the last line of the code).
I don't understand where i got on the wrong way.
If instead of defining the whole non working part(var legend...) i write this code:
g.append("text")
.attr("stroke", "none")
.attr("fill", function(d) { return d.data.col; })
.text(function(d) { return d.data.name; });
I'm able to access the d.data.name.
Unfortunately wrong colors of the boxes and not description.
Thanks!
I've got a simple donut chart in d3.js, which will only be used to compare 2 or 3 items.
http://jsfiddle.net/Ltqu2/
I want to combine the legend and values as text in the center of the chart.
In the current implementation, the text alignment is ok for 3 items, but for 2 items it doesn't adjust. The alignment is somewhat hard coded:
var l = svg.selectAll('.legend')
.data(data)
.enter().append('g')
.attr('class', 'legend');
l.append('text')
.attr('x', 0)
.attr('y', function(d, i) { return i * 40 - radius / 2 + 10; })
.attr('class', function(d, i){return 'legend-label data-label value-' + (i+1)})
.text(function(d, i) { return d + '%'; });
l.append('text')
.attr('x', 0)
.attr('y', function(d, i) { return i * 40 - radius / 2 + 22; })
.attr('class', function(d, i){return 'legend-label units-label value-' + (i+1)})
.text(function(d, i) { return legend[i]; });
How can I make the text alignment more flexible, so it distributes evenly for both 2 and 3 items? Is there something I can use .rangeround for?
Here's the full script
/**
* Donut chart for d3.js
*/
function donutChart() {
var width = 420,
height = 420,
radius = 0,
factor = 0.7;
var legend = ['Low', 'Medium', 'High'];
function chart(selection) {
selection.each(function(data) {
if (radius == 0) {
radius = Math.min(width, height) / 2 - 10;
}
var arc = d3.svg.arc()
.innerRadius(radius * factor)
.outerRadius(radius);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d; });
var svg = d3.select(this).append('svg')
.attr('width', width)
.attr('height', height)
.attr('class', 'donut')
.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
var g = svg.selectAll('.arc')
.data(pie(data))
.enter().append('g')
.attr('class', 'arc');
g.append('path')
.attr('d', arc)
.attr('class', function(d, i){return 'value-' + (i+1)})
.style('stroke', '#fff');
var l = svg.selectAll('.legend')
.data(data)
.enter().append('g')
.attr('class', 'legend');
l.append('text')
.attr('x', 0)
.attr('y', function(d, i) { return i * 40 - radius / 2 + 10; })
.attr('class', function(d, i){return 'legend-label data-label value-' + (i+1)})
.text(function(d, i) { return d + '%'; });
l.append('text')
.attr('x', 0)
.attr('y', function(d, i) { return i * 40 - radius / 2 + 22; })
.attr('class', function(d, i){return 'legend-label units-label value-' + (i+1)})
.text(function(d, i) { return legend[i]; });
});
}
chart.width = function(_) {
if (!arguments.length) return width;
width = _;
return chart;
};
chart.height = function(_) {
if (!arguments.length) return height;
height = _;
return chart;
};
chart.radius = function(_) {
if (!arguments.length) return radius;
radius = _;
return chart;
};
chart.factor = function(_) {
if (!arguments.length) return factor;
factor = _;
return chart;
};
chart.legend = function(_) {
if (!arguments.length) return legend;
legend = _;
return chart;
};
return chart;
}
d3.select('#chart')
.datum([78, 20])
.call(donutChart()
.width(220)
.height(220)
.legend(['This Thing', 'That Thing'])
);
d3.select('#chart2')
.datum([63, 20, 15])
.call(donutChart()
.width(220)
.height(220)
.legend(['This Thing', 'That Thing', 'Another Thing'])
);
I would put all the legend text elements in a common <g> element which can then be translated to center vertically. Determining actual text sizes in SVG is always a pain, you can look into getBBox() for more information on that. But using a predetermined text height will work fine. Here's a bit of calculation (separated into many steps for clarity):
// hardcoding 36 here, you could use getBBox to get the actual SVG text height using sample text if you really wanted.
var legendItemHeight = 36;
// calculated height of all legend items together
var actualLegendHeight = data.length * legendItemHeight;
// inner diameter
var availableLegendHeight = radius * factor * 2;
// y-coordinate of first legend item (relative to the center b/c the main svg <g> element is translated
var legendOffset = (availableLegendHeight - actualLegendHeight) / 2 - (radius*factor);
And then create a <g> element to hold the <text> elements:
// append all the legend items to a common group which is translated
var l = svg.selectAll('.legend')
.data(data)
.enter().append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
return 'translate(0, '+ (legendOffset + i * legendHeight)+')';
});
Modified jsFiddle: http://jsfiddle.net/rshKs/2/