d3.js - Object doesn't support property or method 'map' - d3.js

I am getting this error in IE9. In all other browsers - chrome, Firefox, my charts work fine. Could anyone put their thoughts on this on how to resolve this error?
I am using d3 to create a piechart. I get the path dynamically and get appended in diDataUrlPrefix.
var width = 960,
height = 437,
radius = Math.min(width, height) / 2;
var mainfile = diDataUrlPrefix + "/appsec/csvs/Legal-RAG.csv";
var color = d3.scale.ordinal()
.range(["#a00000","#Ffb400","#78a22f"]);//Ffb400
var arc = d3.svg.arc()
.outerRadius(radius - 45)
.innerRadius(radius -200);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {return d.Components;});
var svg = d3.select("#mainchart").append("svg")
.attr("width", width)
.attr("height", height)
.attr("style","margin-right:100px;margin-left:20px;margin-top:-20px")
.append("g")
.attr("transform", "translate(" + width / 3.2 + "," + height /2.5 + ")");
d3.csv(mainfile, function(error, data) {
// Iterate through each status to determine if there are any components
// Do this avoiding the use of .forEach (IE9 error)
// VW 2013-09-29
var length = data.length;
for(var i=0; i< length; i++) {
var d = data[i];
d.Components = +d.Components;
if(d.Components >0) {
glblcount=1;
}
}
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc")
g.append("path")
.attr("d", arc)
.style("fill","#FFFFFF")
.transition()
.ease("bounce")
.duration(1000)
.delay(function(d, i) {return i * 500;})
.style("fill", function(d) {return color(d.data.Source);});
g.append("text")
.attr("transform", function(d)
{
var c = arc.centroid(d);
var param = c[1]- 20;
var param1= c[0]- 30;
return "translate(" + param1 + "," + param + ")";
//return "translate(" + arc.centroid(d) + ")";
})
.attr("dy", ".35em")
.style("text-anchor", "middle")
.attr("style","font-family: Arial;border: solid 1px" )
.style("font-color",function(d){
if ((d.data.Source) =="Amber")
{
//alert(d.data.Source);
return "#000000";
}
else
{
return "#ffffff";
}
})
.transition()
.ease("bounce")
.duration(1000)
.delay(function(d, i) {return i * 500;})
.text(function(d) {
if (eval(d.data.Components) >0)
{
return ((d.data.Status));
}
});
I am getting the error when d3.layout.pie() is called. It throws the following error:
SCRIPT438: Object doesn't support property or method 'map'
d3.v3.js, line 4351 character 7
function pie(data) {
var values = data.map(function(d, i) {
return +value.call(pie, d, i);
});
Thanks,
Krishna.V

map is a relatively recent addition to Javascript and as such not implemented in all browsers. This page has more information, along with an implementation for the browsers that don't support it. Including the code on this page in your code should solve the problem.

Map should be working on IE9 but I know that D3 has problems with IE8 and below. Maybe you'll need modify your functions for not use 'map' or 'foreach' javascript methods. Instead, try to use the same functions by jQuery.
function pie(data) {
var values = jQuery.map(data, function(d, i) {
return +value.call(pie, d, i);
});
You have more information here.

Related

How to change the size of dots in beeswarm plots in D3.js

I've been looking at this example of a beeswarm plot in d3.js and I'm trying to figure out how to change the size of the dots and without getting the circles to overlap. It seems if the radius of the dots change, it doesn't take this into account when running the calculations of where to place the dots.
This is a cool visualization.
I've made a plunk of it here: https://plnkr.co/edit/VwyXfbc94oXp6kXQ7JFx?p=preview and modified it to work a bit more like you're looking for (I think). The real key is changing the call to handle collision to vary based on the radius of the circles (in the original post it's hard coded to 4, which works well when r === 3 but fails as r grows). The changes:
Make the circle radius into a variable (line 7 of script.js, var r = 3;)
Change the d3.forceCollide call to use that radius and a multiplier - line 110 (.force("collide", d3.forceCollide(r * 1.333)))
Change the .enter() call to use that radius as well (line 130: .attr("r", r))
This works reasonably well for reasonable values of r - but you'll need to adjust the height, and it might even be nice to just change the whole thing so that r is based on height (e.g. var r = height * .01). You'll notice that as is now, the circles go off the bottom and top of the graph area.
This post might be of interest as well: Conflict between d3.forceCollide() and d3.forceX/Y() with high strength() value
Here's the whole of script.js for posterity:
var w = 1000, h = 280;
var padding = [0, 40, 34, 40];
var r = 5;
var xScale = d3.scaleLinear()
.range([ padding[3], w - padding[1] ]);
var xAxis = d3.axisBottom(xScale)
.ticks(10, ".0s")
.tickSizeOuter(0);
var colors = d3.scaleOrdinal()
.domain(["asia", "africa", "northAmerica", "europe", "southAmerica", "oceania"])
.range(['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#ffff33']);
d3.select("#africaColor").style("color", colors("africa"));
d3.select("#namericaColor").style("color", colors("northAmerica"));
d3.select("#samericaColor").style("color", colors("southAmerica"));
d3.select("#asiaColor").style("color", colors("asia"));
d3.select("#europeColor").style("color", colors("europe"));
d3.select("#oceaniaColor").style("color", colors("oceania"));
var formatNumber = d3.format(",");
var tt = d3.select("#svganchor").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var svg = d3.select("#svganchor")
.append("svg")
.attr("width", w)
.attr("height", h);
var xline = svg.append("line")
.attr("stroke", "gray")
.attr("stroke-dasharray", "1,2");
var chartState = {};
chartState.variable = "totalEmission";
chartState.scale = "scaleLinear";
chartState.legend = "Total emissions, in kilotonnes";
d3.csv("co2bee.csv", function(error, data) {
if (error) throw error;
var dataSet = data;
xScale.domain(d3.extent(data, function(d) { return +d.totalEmission; }));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (h - padding[2]) + ")")
.call(xAxis);
var legend = svg.append("text")
.attr("text-anchor", "middle")
.attr("x", w / 2)
.attr("y", h - 4)
.attr("font-family", "PT Sans")
.attr("font-size", 12)
.attr("fill", "darkslategray")
.attr("fill-opacity", 1)
.attr("class", "legend");
redraw(chartState.variable);
d3.selectAll(".button1").on("click", function(){
var thisClicked = this.value;
chartState.variable = thisClicked;
if (thisClicked == "totalEmission"){
chartState.legend = "Total emissions, in kilotonnes";
}
if (thisClicked == "emissionPerCap"){
chartState.legend = "Per Capita emissions, in metric tons";
}
redraw(chartState.variable);
});
d3.selectAll(".button2").on("click", function(){
var thisClicked = this.value;
chartState.scale = thisClicked;
redraw(chartState.variable);
});
d3.selectAll("input").on("change", filter);
function redraw(variable){
if (chartState.scale == "scaleLinear"){ xScale = d3.scaleLinear().range([ padding[3], w - padding[1] ]);}
if (chartState.scale == "scaleLog"){ xScale = d3.scaleLog().range([ padding[3], w - padding[1] ]);}
xScale.domain(d3.extent(dataSet, function(d) { return +d[variable]; }));
var xAxis = d3.axisBottom(xScale)
.ticks(10, ".0s")
.tickSizeOuter(0);
d3.transition(svg).select(".x.axis").transition().duration(1000)
.call(xAxis);
var simulation = d3.forceSimulation(dataSet)
.force("x", d3.forceX(function(d) { return xScale(+d[variable]); }).strength(2))
.force("y", d3.forceY((h / 2)-padding[2]/2))
.force("collide", d3.forceCollide(r * 1.333))
.stop();
for (var i = 0; i < dataSet.length; ++i) simulation.tick();
var countriesCircles = svg.selectAll(".countries")
.data(dataSet, function(d) { return d.countryCode});
countriesCircles.exit()
.transition()
.duration(1000)
.attr("cx", 0)
.attr("cy", (h / 2)-padding[2]/2)
.remove();
countriesCircles.enter()
.append("circle")
.attr("class", "countries")
.attr("cx", 0)
.attr("cy", (h / 2)-padding[2]/2)
.attr("r", r)
.attr("fill", function(d){ return colors(d.continent)})
.merge(countriesCircles)
.transition()
.duration(2000)
.attr("cx", function(d) { console.log(d); return d.x; })
.attr("cy", function(d) { return d.y; });
legend.text(chartState.legend);
d3.selectAll(".countries").on("mousemove", function(d) {
tt.html("Country: <strong>" + d.countryName + "</strong><br>"
+ chartState.legend.slice(0, chartState.legend.indexOf(",")) + ": <strong>" + formatNumber(d[variable]) + "</strong>" + chartState.legend.slice(chartState.legend.lastIndexOf(" ")))
.style('top', d3.event.pageY - 12 + 'px')
.style('left', d3.event.pageX + 25 + 'px')
.style("opacity", 0.9);
xline.attr("x1", d3.select(this).attr("cx"))
.attr("y1", d3.select(this).attr("cy"))
.attr("y2", (h - padding[2]))
.attr("x2", d3.select(this).attr("cx"))
.attr("opacity", 1);
}).on("mouseout", function(d) {
tt.style("opacity", 0);
xline.attr("opacity", 0);
});
d3.selectAll(".x.axis, .legend").on("mousemove", function(){
tt.html("This axis uses SI prefixes:<br>m: 10<sup>-3</sup><br>k: 10<sup>3</sup><br>M: 10<sup>6</sup>")
.style('top', d3.event.pageY - 12 + 'px')
.style('left', d3.event.pageX + 25 + 'px')
.style("opacity", 0.9);
}).on("mouseout", function(d) {
tt.style("opacity", 0);
});
//end of redraw
}
function filter(){
function getCheckedBoxes(chkboxName) {
var checkboxes = document.getElementsByName(chkboxName);
var checkboxesChecked = [];
for (var i=0; i<checkboxes.length; i++) {
if (checkboxes[i].checked) {
checkboxesChecked.push(checkboxes[i].defaultValue);
}
}
return checkboxesChecked.length > 0 ? checkboxesChecked : null;
}
var checkedBoxes = getCheckedBoxes("continent");
var newData = [];
if (checkedBoxes == null){
dataSet = newData;
redraw();
return;
};
for (var i = 0; i < checkedBoxes.length; i++){
var newArray = data.filter(function(d){
return d.continent == checkedBoxes[i];
});
Array.prototype.push.apply(newData, newArray);
}
dataSet = newData;
redraw(chartState.variable);
//end of filter
}
//end of d3.csv
});

Adding a legend to a pie chart in D3js

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!

D3 Pie Error: Invalid value for <text> attribute transform="translate(NaN,NaN)"

Keep getting a console error of
Error: Invalid value for attribute transform="translate(NaN,NaN)"
Re-created the error here.
http://jsfiddle.net/9f9wonoc/
var width = 360;
var height = 360;
var radius = Math.min(width, height) / 2;
var color = {
'Pass': '#66B51B',
'Fail': '#d03324'
}
var data = [
{ label: 'Pass', count: 12 },
{ label: 'Fail', count: 10 },
];
var svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + (width / 2) + ',' + (height / 2) + ')');
var arc = d3.svg.arc()
.outerRadius(radius);
var pie = d3.layout.pie()
.value(function(d) { return d.count; })
.sort(null);
var path = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
path.append("path")
.attr("d", arc)
.style("fill", function(d) { return color[d.data.label]; });
path.append("text")
.attr("transform", function(d, i) {
return "translate(" + arc.centroid(d, i) + ")";
})
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.data.count; });
What am I doing wrong?
You missed one small thing :
you need to set the inner radius 0 as below:
var arc = d3.svg.arc()
.outerRadius(radius).innerRadius(0);
Working code here
Hope this helps!

How can I solve Error: Invalid value for <text> attribute transform="translate(NaN,NaN)" on arc.centroid(d)?

I'm trying to append text on my multi-line donut charts. But it returns invalid value error on "translate" line.
gs
.append("text")
.attr("class", "pieNum")
.attr("transform", function(d, i, j){
console.log(d, i, j);
return "translate(" + arc.centroid(d[i]) + ")"
})
.text(function(d, i){
console.log(d);
return d.value
})
I found that it happens because on function(d, i, j), d gets array data, not a string or float.
because my data looks like
nextYear thisYear preYear
11118 10683 10892
28201 27358 28537
1473 1398 1399
0 0 1
so data like ["11118", "28201", "1473", "0"] are in d, and I'm trying to use numbers in the array, but keep failing.
Here's my code:
JS
d3.csv(filename, function(error,data){
var dataOuter = [], dataMid = [], dataInner = [];
var dataset = {
dataOuter,
dataMid,
dataInner,
};
var svgEle = document.getElementById("chart")
var width = window.getComputedStyle(svgEle, null).getPropertyValue("width")
height = window.getComputedStyle(svgEle, null).getPropertyValue("height")
cwidth = 50;
width = parseFloat(width) // remove px
height = parseFloat(height) // remove px
for (var i = 0; i < data.length; i++){
dataset.dataOuter.push(data[i].nextYear)
dataset.dataMid.push(data[i].thisYear)
dataset.dataInner.push(data[i].preYear)
}
var color = d3.scale.category10();
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc();
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var gs = svg.selectAll("g").data(d3.values(dataset)).enter().append("g");
var path = gs.selectAll("path")
.data(function (d) { return pie(d); })
.enter().append("path")
.attr("fill", function (d, i) { return color(i); })
.attr("d", function (d, i, j) { return arc.innerRadius(10 + cwidth * j).outerRadius(cwidth * (j + 1))(d); });
gs
.append("text")
.attr("class", "pieNum")
.attr("transform", function(d, i, j){
console.log(d, i, j);
return "translate(" + arc.centroid(d[i]) + ")"
})
.text(function(d, i){
console.log(d);
return d.value
})
})
From the code it looks like you are nesting 3 pie charts inside of each other. For each, you need to keep a reference to arc function it is using. Here's how I'd restructure your code:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
</head>
<body>
<div id="chart" style="width: 600px; height: 600px"></div>
<script>
// input data
var data = [{"nextYear":"11118","thisYear":"10683","preYear":"10892"},{"nextYear":"28201","thisYear":"27358","preYear":"28537"},{"nextYear":"1473","thisYear":"1398","preYear":"1399"},{"nextYear":"0","thisYear":"0","preYear":"1"}];
var dataOuter = [],
dataMid = [],
dataInner = [];
// make this array of arrays
var dataset = [
dataOuter,
dataMid,
dataInner,
];
var svgEle = document.getElementById("chart")
var width = window.getComputedStyle(svgEle, null).getPropertyValue("width")
height = window.getComputedStyle(svgEle, null).getPropertyValue("height")
cwidth = 50;
width = parseFloat(width) // remove px
height = parseFloat(height) // remove px
// again, array of arrays for data-binding
for (var i = 0; i < data.length; i++) {
dataset[0].push(+data[i].nextYear);
dataset[1].push(+data[i].thisYear);
dataset[2].push(+data[i].preYear);
}
var color = d3.scale.category10();
var pie = d3.layout.pie()
.sort(null);
// pre-make all the arcs you'll need
var arcs = [
d3.svg.arc()
.innerRadius(0)
.outerRadius(50),
d3.svg.arc()
.innerRadius(60)
.outerRadius(110),
d3.svg.arc()
.innerRadius(120)
.outerRadius(170)];
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
// g for each pie
var pies = svg.selectAll(".pie")
.data(dataset)
.enter().append("g")
.attr("class", "pie");
// pie up the data, add a g for each slice (arc + text)
var slices = pies.selectAll(".slice")
.data(function(d) {
return pie(d);
})
.enter()
.append("g")
.attr("class","slice");
// add our paths with our arcs
slices
.append("path")
.attr("fill", function(d, i, j) {
return color(i);
})
.attr("d", function(d, i, j) {
return arcs[j](d);
});
// add our text
slices
.append("text")
.attr("class", "pieNum")
.attr("transform", function(d, i, j) {
return "translate(" + arcs[j].centroid(d) + ")"
})
.text(function(d, i) {
return d.value
})
// })
</script>
</body>
</html>

D3js PieChart will not perform UPDATE section

This is probably the simplest graph possible to create using d3js. And yet I am struggling.
The graph runs everything given to it in enter() and exit(). But everything in ENTER + UPDATE is completely ignored. WHY?
// Setup dimensions
var width = 200,
height = 200,
radius = Math.min(width, height) / 2,
// Setup a color function with 20 colors to use in the graph
color = d3.scale.category20(),
// Configure pie container
arc = d3.svg.arc().outerRadius(radius - 10).innerRadius(0), // Define the arc element
svg = d3.select(".pie").append("svg:svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"),
// This is the layout manager for the pieGraph
pie = d3.layout.pie()
.sort(null)
.value(function (d) {
return d.answers;
}),
// Allow two groups in the container. One overlapping the other, just to make sure that
// text labels never get hidden below pie arcs.
graphGroup = svg.append("svg:g").attr("class", "graphGroup"),
textGroup = svg.append("svg:g").attr("class", "labelGroup");
// Data is loaded upon user interaction. On angular $scope update, refresh graph...
$scope.$watch('answers', function (data) {
// === DATA ENTER ===================
var g = graphGroup.selectAll("path.arc").data(pie(data)),
gEnter = g.enter()
.append("path")
.attr("d", arc)
.attr("class", "arc"),
t = textGroup.selectAll("text.label").data(data),
tEnter = t.enter()
.append("text")
.attr("class", "label")
.attr("dy", ".35em")
.style("text-anchor", "middle");
// === ENTER + UPDATE ================
g.select("path.arc")
.attr("id", function (d) {
return d.data.id + "_" + d.data.selection;
})
.attr("fill", function (d, i) {
return color(i);
})
.transition().duration(750).attrTween("d", function (d) {
var i = d3.interpolate(this._current, d);
this._current = i(0);
return function (t) {
return arc(i(t));
};
});
t.select("text.label")
.attr("transform", function (d) {
return "translate(" + arc.centroid(d) + ")";
})
.text(function (d) {
return d.data.opt;
});
// === EXIT ==========================
g.exit().remove();
t.exit().remove();
});
This one example of the json structure given to the update function as "data":
[{"selection":"0","opt":"1-2 timer","answers":"7"},
{"selection":"1","opt":"3-4 timer","answers":"13"},
{"selection":"2","opt":"5-6 timer","answers":"5"},
{"selection":"3","opt":"7-8 timer","answers":"8"},
{"selection":"4","opt":"9-10 timer","answers":"7"},
{"selection":"5","opt":"11 timer eller mer","answers":"11"},
{"selection":"255","opt":"Blank","answers":"8"}]
You don't need the additional .select() to access the update selection. This will in fact return empty selections in your case, which means that nothing happens. To make it work, simply get rid of the additional .select() and do
g.attr("id", function (d) {
return d.data.id + "_" + d.data.selection;
})
// etc
t.attr("transform", function (d) {
return "translate(" + arc.centroid(d) + ")";
})
// etc

Resources