Issue scalling width of svg elements while updating D3 bar chart - javascript

I created a simple age pyramid bar chart with D3.js using this example as a guide: http://www.jasondavies.com/d3-pyramid/. This works fine but I want to dynamically update this chart based on a users selection of data. When I append the new data to the existing bars the < g > element and rects grow beyond the width of the svg container. My first thought was to set a max width of the < g > elements to the width of the container hoping that the rects would scale accordingly like they did in the initial rendering but that does not seem to be possible.
My biggest issue ( besides needing to clean up the code :) ) is that I don't understand why the bar widths look great in the initial rendering but then grow exceedingly in the update. I think this is probably some fundamental misunderstanding i have with D3/SVG but I could use some guidance.
Any help is appreciated!
Initial Chart Generation (this works)
var ageChart,
ageBar,
ageBars,
ageTotal,
dataRange,
yScale,
topMargin,
ageChartWidth,
ageLabelSpace,
ageInnerMargin,
commas = d3.format(",.0f");
function generateAgeChart(data) {
ageData = processAgeData(data);
ageLabelSpace = 25;
ageInnerMargin = width / 2 + ageLabelSpace;
var outerMargin = 30,
gap = 8,
leftLabel = "Female",
rightLabel = "Male",
height = 180;
barWidth = height / ageData.length;
width = 200;
ageChartWidth = width - ageInnerMargin - outerMargin;
topMargin = 25;
yScale = d3.scale.linear().domain([0, ageData.length]).range([0, height - topMargin]);
dataRange = d3.max(ageData.map(function (d) { return Math.max(d.female, d.male) }));
ageTotal = d3.scale.linear().domain([0, dataRange]).range([0, ageChartWidth - ageLabelSpace]);
/* main panel */
ageChart = d3.select("#chart-3").append("svg")
.attr("class", "d3-chart")
.attr("width", width)
.attr("height", height);
/* female label */
ageChart.append("text")
.attr("class", "bar-label")
.text(leftLabel)
.attr("x", width - ageInnerMargin)
.attr("y", topMargin - 3)
.attr("text-anchor", "end");
/* male label */
ageChart.append("text")
.attr("class", "bar-label")
.text(rightLabel)
.attr("x", ageInnerMargin)
.attr("y", topMargin - 3);
/* bars and data labels */
ageBar = ageChart.selectAll("g.bar")
.data(ageData)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function (d, i) {
return "translate(0," + (yScale(i) + topMargin) + ")";
});
var highlight = function (c) {
return function (d, i) {
ageBar.filter(function (d, j) {
return i === j;
}).attr("class", c);
};
};
ageBar
.on("mouseover", highlight("highlight bar"))
.on("mouseout", highlight("bar"));
ageBar.append("rect")
.attr("class", "femalebar")
.attr("height", barWidth - gap);
ageBar.append("text")
.attr("class", "femalebar")
.attr("dx", -3)
.attr("dy", "1.7em")
.attr("text-anchor", "end");
ageBar.append("rect")
.attr("class", "malebar")
.attr("height", barWidth - gap)
.attr("x", ageInnerMargin);
ageBar.append("text")
.attr("class", "malebar")
.attr("dx", 3)
.attr("dy", "1.7em");
/* sharedLabels */
ageBar.append("text")
.attr("class", "shared")
.attr("x", width / 2)
.attr("dy", "1.7em")
.attr("text-anchor", "middle")
.text(function (d) { return d.sharedLabel; });
// Draw the chart
ageBars = d3.selectAll("g.bar")
.data(ageData);
ageBars.selectAll("rect.malebar")
.transition()
.attr("width", function (d) { return ageTotal(d.male); });
ageBars.selectAll("rect.femalebar")
.transition()
.attr("x", function (d) { return ageInnerMargin - ageTotal(d.female) - 2 * ageLabelSpace; })
.attr("width", function (d) { return ageTotal(d.female); });
ageBars.selectAll("text.malebar")
.text(function (d) { return commas(d.male); })
.transition().attr("x", function (d) { return ageInnerMargin + ageTotal(d.male); });
ageBars.selectAll("text.femalebar")
.text(function (d) { return commas(d.female); })
.transition()
.attr("x", function (d) { return ageInnerMargin - ageTotal(d.female) - 2 * ageLabelSpace; });
// Title
ageChart.append("text")
.attr("x", (width / 2))
.attr("y", 10)
.attr("text-anchor", "middle")
.attr("font-size", "10pt")
.style("fill", "#333960")
.style("font-weight", "bold")
//.style("text-decoration", "underline")
.style("font-weight", "bold")
.text("Age");
}
Re-Draw Chart Function (the problem area)
function redrawAgeChart(data) {
// Get and process data
ageData = processAgeData(data);
width = 200;
height = 180;
outerMargin = 30;
topMargin = 25;
gap = 8;
height = 180;
barWidth = height / ageData.length;
ageLabelSpace = 25;
ageInnerMargin = width / 2 + ageLabelSpace;
ageChartWidth = width - ageInnerMargin - outerMargin;
yScale = d3.scale.linear().domain([0, ageData.length]).range([0, height - topMargin]);
dataRange = d3.max(ageData.map(function (d) { return Math.max(d.female, d.male) }));
ageTotal = d3.scale.linear().domain([0, dataRange]).range([0, ageChartWidth - ageLabelSpace]);
ageBars = d3.selectAll("g.bar")
.data(ageData);
ageBars.selectAll("rect.malebar")
.transition()
.attr("width", function (d) { return ageTotal(d.male); });
ageBars.selectAll("rect.femalebar")
.transition()
.attr("x", function (d) { return ageInnerMargin - ageTotal(d.female) - 2 * ageLabelSpace; })
.attr("width", function (d) { return ageTotal(d.female); });
ageBars.selectAll("text.malebar")
.text(function (d) { return commas(d.male); })
.transition().attr("x", function (d) { return ageInnerMargin + ageTotal(d.male); });
ageBars.selectAll("text.femalebar")
.text(function (d) { return commas(d.female); })
.transition().attr("x", function (d) { return ageInnerMargin - ageTotal(d.female) - 2 * ageLabelSpace; });
}
Get data function (you can hit this service... it is public)
function processAgeData(data) {
ageData = [];
var totalF_6_17 = 0;
var totalF_18_34 = 0;
var totalF_35_54 = 0;
var totalF_55_plus = 0;
var totalF_under5 = 0;
var totalM_6_17 = 0;
var totalM_18_34 = 0;
var totalM_35_54 = 0;
var totalM_55_plus = 0;
var totalM_under5 = 0;
// Loop through return to build a new array of values
$.each(data.features, function (key, val) {
var f_6_17 = val.properties.f_6_17;
var f_18_34 = val.properties.f_18_34;
var f_35_54 = val.properties.f_35_54;
var f_55_plus = val.properties.f_55_plus;
var f_under5 = val.properties.f_under5;
var m_6_17 = val.properties.m_6_17;
var m_18_34 = val.properties.m_18_34;
var m_35_54 = val.properties.m_35_54;
var m_55_plus = val.properties.m_55_plus;
var m_under5 = val.properties.m_under5;
totalF_6_17 = totalF_6_17 + f_6_17;
totalF_18_34 = totalF_18_34 + f_18_34;
totalF_35_54 = totalF_35_54 + f_35_54;
totalF_55_plus = totalF_55_plus + f_55_plus;
totalF_under5 = totalF_under5 + f_under5;
totalM_6_17 = totalM_6_17 + m_6_17;
totalM_18_34 = totalM_18_34 + m_18_34;
totalM_35_54 = totalM_35_54 + m_35_54;
totalM_55_plus = totalM_55_plus + m_55_plus;
totalM_under5 = totalM_under5 + m_under5;
});
var under5Obj = new Object();
under5Obj.sharedLabel = "< 5";
under5Obj.female = totalF_under5;
under5Obj.male = totalM_under5;
var age6_17Obj = new Object();
age6_17Obj.sharedLabel = "6 - 17";
age6_17Obj.female = totalF_6_17;
age6_17Obj.male = totalM_6_17;
var age18_34Obj = new Object();
age18_34Obj.sharedLabel = "18 - 34";
age18_34Obj.female = totalF_18_34;
age18_34Obj.male = totalM_18_34;
var age35_54Obj = new Object();
age35_54Obj.sharedLabel = "35 - 54";
age35_54Obj.female = totalF_35_54;
age35_54Obj.male = totalM_35_54;
var over55Obj = new Object();
over55Obj.sharedLabel = "55 +";
over55Obj.female = totalF_55_plus;
over55Obj.male = totalM_55_plus;
ageData.push(under5Obj);
ageData.push(age6_17Obj);
ageData.push(age18_34Obj);
ageData.push(age35_54Obj);
ageData.push(over55Obj);
return ageData;
}
// Age Chart Request
//// use this url to get the entire dataset which should display correctly
url = 'http://gis.drcog.org/geoserver/DRCOGPUB/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=DRCOGPUB:rea_demographics_age_county_view&maxFeatures=10000&outputFormat=json&propertyName=f_under5,f_6_17,f_18_34,f_35_54,f_55_plus,m_under5,m_6_17,m_18_34,m_35_54,m_55_plus,geoid&format_options=callback:redrawAgeChart'
//// use this for the update request
selectionUrl = "http://gis.drcog.org/geoserver/DRCOGPUB/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=DRCOGPUB:rea_demographics_age_county_view&maxFeatures=10000&outputFormat=json&propertyName=f_under5,f_6_17,f_18_34,f_35_54,f_55_plus,m_under5,m_6_17,m_18_34,m_35_54,m_55_plus,geoid&format_options=callback:redrawAgeChart&cql_filter=geoid%20IN%20('08059')"
$.ajax({
type: 'get',
url: url,
dataType: "jsonp",
crossDomain: true,
cache: false,
error: function (jqXHR, textStatus, errorThrown) { console.log(textStatus); }
});

Well, I learned a bit about D3 and SVG and managed to catch my rather silly mistakes. I was appending the data to the rect elements in the wrong way. I solved this by selecting out male/female bars directly from the chart svg separately and appending the data to each of those selections. This now works as planned.
yScale = d3.scale.linear().domain([0, ageData.length]).range([0, height - topMargin]);
dataRange = d3.max(ageData.map(function (d) { return Math.max(d.female, d.male) }));
ageTotal = d3.scale.linear().domain([0, dataRange]).range([0, ageChartWidth - ageLabelSpace]);
ageChart.selectAll("rect.malebar")
.data(ageData)
.transition()
.attr("width", function (d) { return ageTotal(d.male); });
ageChart.selectAll("rect.femalebar")
.data(ageData)
.transition()
.attr("x", function (d) { return ageInnerMargin - ageTotal(d.female) - 2 * ageLabelSpace; })
.attr("width", function (d) { return ageTotal(d.female); });
ageChart.selectAll("text.malebar")
.data(ageData)
.text(function (d) { return commas(d.male); });
});
ageChart.selectAll("text.femalebar")
.data(ageData)
.text(function (d) { return commas(d.female); });

Related

D3 stacked segment chart, sort by totals

I have been trying to create a stacked segment chart for the past few days and between all the trigonometry and key stacking it has truly been a nightmare for me to apply what seems to be the most basic of tweaks. Currently my segment chart has data that is sorted in descending order based on Active equity. I want to change this to have the data sorted by total.
const csvData = `State,Active equity,Passive equity,Fixed income,Mixed assets
BlackRock,1,17,0,0
Fidelity,13,2,0,0
SSgA,12,0,0,0
Hang Seng,11,0,0,0
UBS,9,0,0,1
Schroders,6,0,2,1
JP Morgan,5,2,0,1
Value Partners,1,0,6,0
First State,5,0,0,0
Invesco,4,1,0,0
HSBC,1,1,1,1
DBS,0,2,1,0
BOCI,1,1,1,0
CSOP,0,2,1,0
Principal,1,1,0,0
Allianz,2,1,0,0
Yuanta,0,2,1,0
Manulife,1,0,1,0
Aberdeen,2,0,0,0
Mirae,1,1,0,0
,0,0,0,0`;
//const data = d3.csvParse(csvData, d3.autoType);
var margins = {top:20, bottom:300, left:30, right:100};
var height = 600;
var width = 900;
var totalWidth = width+margins.left+margins.right;
var totalHeight = height+margins.top+margins.bottom;
var outerRadius = (400 / 2);
var innerRadius = 15;
var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);
var graphGroup = svg.append('g')
.attr('transform', "translate(250,250)");
var x = d3.scaleBand()
.range([0, 2 * Math.PI])
.align(0);
var y = d3.scaleRadial()
.range([innerRadius, outerRadius]);
var z = d3.scaleOrdinal()
.range(["#003366", "#4f81b9", "#95b3d7", "#f6d18b"]);
d3.csv("csvData", function(d, i, columns) {
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
d.total = t;
return d;
}, function(error, data) {
if (error) throw error;
weave(data, function(a, b) { return b[data.columns[6]] - a[data.columns[6]]; });
x.domain(data.map(function(d) { return d.State; }));
y.domain([0, d3.max(data, function(d) { return d.total; })*1.3]);
z.domain(data.columns.slice(1));
graphGroup.append('g')
.selectAll('g')
.data(d3.stack().keys(data.columns.slice(1))(data))
.enter().append("g")
.selectAll(".bg-arc2")
.data(function(d) { return d; })
.enter().append("path")
.attr("d", d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius+2)
.startAngle(function(d) { return x(d.data.State); })
.endAngle(function(d) { return x(d.data.State) + x.bandwidth()*.90; })
.padAngle(0.1)
.padRadius(innerRadius))
.attr('class','bg-arc2')
.attr('fill','none')
.attr('stroke-width','4px')
.attr('stroke','#003366');
graphGroup.append('circle')
.attr('cx',0)
.attr('cy',0)
.attr('r',200)
.style('fill','#d9d9d9');
graphGroup.append('g')
.selectAll('g')
.data(d3.stack().keys(data.columns.slice(1))(data))
.enter().append("g")
.selectAll(".bg-arc")
.data(function(d) { return d; })
.enter().append("path")
.attr("d", d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)
.startAngle(function(d) { return x(d.data.State); })
.endAngle(function(d) { return x(d.data.State) + x.bandwidth()*.95; })
.padAngle(0.1)
.padRadius(innerRadius))
.attr('class','bg-arc')
.attr('fill','#fff');
graphGroup.append('circle')
.attr('cx',0)
.attr('cy',0)
.attr('r',innerRadius)
.style('fill','#fff');
var stackedData = d3.stack().keys(data.columns.slice(1))(data);
var stackedData2 = stackedData.sort(function(a,b) { return d3.descending(a[0].data.total, b[0].data.total)});
console.log(stackedData[0][0].data.total)
console.log(stackedData2);
graphGroup.append("g")
.selectAll("g")
.data(stackedData)
.enter().append("g")
.attr("fill", function(d) { return z(d.key); })
.selectAll("path")
.data(function(d) { return d; })
.enter().append("path")
.attr("d", d3.arc()
.innerRadius(function(d) { return y(d[0]); })
.outerRadius(function(d) { return y(d[1]); })
.startAngle(function(d) { return x(d.data.State); })
.endAngle(function(d) { return x(d.data.State) + x.bandwidth()*.95; })
.padAngle(0.04)
.padRadius(innerRadius));
var label = graphGroup.append("g")
.selectAll("g")
.data(data)
.enter().append("g")
.attr("text-anchor", "middle")
.attr("transform", function(d) { return "rotate(" + ((x(d.State) + x.bandwidth() / 2) * 180 / Math.PI - 90) + ")translate(" + (outerRadius+25) + ",0)"; });
label.append("text")
.attr("transform", function(d) { return (x(d.State) + x.bandwidth() / 2 + Math.PI / 2) % (2 * Math.PI) < Math.PI ? "rotate(90)translate(0,16)" : "rotate(-90)translate(0,-9)"; })
.text(function(d) { return d.State; });
var yAxis = graphGroup.append("g")
.attr("text-anchor", "end");
var yTick = yAxis
.selectAll("g")
.data(y.ticks(10).slice(1))
.enter().append("g");
});
function weave(array, compare) {
var i = -1, j, n = array.sort(compare).length, weave = new Array(n);
while (++i < n) weave[i] = array[(j = i << 1) >= n ? (n - i << 1) - 1 : j];
while (--n >= 0) array[n] = weave[n];
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://gist.githubusercontent.com/mbostock/3686329aa6e1f5938df8eef12ec353fe/raw/1ab722df937c3ac86cac8292e34cfc7279b017f8/d3-scale-radial.js"></script>
Relevant code here:
var stackedData = d3.stack().keys(data.columns.slice(1))(data);
var stackedData2 = stackedData.sort(function(a,b) { return d3.descending(a[0].data.total, b[0].data.total)});
console.log(stackedData[0][0].data.total)
console.log(stackedData2);
I checked via the console log to make sure I was slicing at the correct place, which I was, as confirmed by console.log(stackedData[0][0].data.total) which returned the correct value of 18.
However I can't apply the sort as desired. The data is still sorted by Active equity and not total.
Question
The default sort for stacked radial charts seems to be whatever the first variable is. In my case, it's Active equity. With that in mind, based off my progress above, what is keeping me from applying the descending order sort by data.total as opposed to the default?
Your question is not clear: are you talking about sorting the segments within each slice, or are you talking about sorting the slices themselves?
For the first case, there is an method named order, that you can use with the stack generator (the link goes to v5 docs, which are a bit different from v4). However, because you said "I want to change [the order] to have the data sorted by total", it seems to me that you're talking about sorting the slices. If that is correct, two observations: it's not sorted by Active equity, right now it's just the order of the objects in the original data array.
For sorting by total you just need to change that array:
data.sort(function(a, b) {
return b.total - a.total;
});
Also, get rid of the weave function.
Here is the result:
(function(global, factory) {
typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("d3-scale")) :
typeof define === "function" && define.amd ? define(["exports", "d3-scale"], factory) :
(factory(global.d3 = global.d3 || {}, global.d3));
}(this, function(exports, d3Scale) {
'use strict';
function square(x) {
return x * x;
}
function radial() {
var linear = d3Scale.scaleLinear();
function scale(x) {
return Math.sqrt(linear(x));
}
scale.domain = function(_) {
return arguments.length ? (linear.domain(_), scale) : linear.domain();
};
scale.nice = function(count) {
return (linear.nice(count), scale);
};
scale.range = function(_) {
return arguments.length ? (linear.range(_.map(square)), scale) : linear.range().map(Math.sqrt);
};
scale.ticks = linear.ticks;
scale.tickFormat = linear.tickFormat;
return scale;
}
exports.scaleRadial = radial;
Object.defineProperty(exports, '__esModule', {
value: true
});
}));
const csvData = `State,Active equity,Passive equity,Fixed income,Mixed assets
BlackRock,1,17,0,0
Fidelity,13,2,0,0
SSgA,12,0,0,0
Hang Seng,11,0,0,0
UBS,9,0,0,1
Schroders,6,0,2,1
JP Morgan,5,2,0,1
Value Partners,1,0,6,0
First State,5,0,0,0
Invesco,4,1,0,0
HSBC,1,1,1,1
DBS,0,2,1,0
BOCI,1,1,1,0
CSOP,0,2,1,0
Principal,1,1,0,0
Allianz,2,1,0,0
Yuanta,0,2,1,0
Manulife,1,0,1,0
Aberdeen,2,0,0,0
Mirae,1,1,0,0
,0,0,0,0`;
const data = d3.csvParse(csvData, function(d, i, columns) {
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
d.total = t;
return d;
});
data.sort(function(a, b) {
return b.total - a.total;
});
var margins = {
top: 20,
bottom: 300,
left: 30,
right: 100
};
var height = 600;
var width = 900;
var totalWidth = width + margins.left + margins.right;
var totalHeight = height + margins.top + margins.bottom;
var outerRadius = (400 / 2);
var innerRadius = 15;
var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);
var graphGroup = svg.append('g')
.attr('transform', "translate(250,250)");
var x = d3.scaleBand()
.range([0, 2 * Math.PI])
.align(0);
var y = d3.scaleRadial()
.range([innerRadius, outerRadius]);
var z = d3.scaleOrdinal()
.range(["#003366", "#4f81b9", "#95b3d7", "#f6d18b"]);
x.domain(data.map(function(d) {
return d.State;
}));
y.domain([0, d3.max(data, function(d) {
return d.total;
}) * 1.3]);
z.domain(data.columns.slice(1));
graphGroup.append('g')
.selectAll('g')
.data(d3.stack().keys(data.columns.slice(1))(data))
.enter().append("g")
.selectAll(".bg-arc2")
.data(function(d) {
return d;
})
.enter().append("path")
.attr("d", d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius + 2)
.startAngle(function(d) {
return x(d.data.State);
})
.endAngle(function(d) {
return x(d.data.State) + x.bandwidth() * .90;
})
.padAngle(0.1)
.padRadius(innerRadius))
.attr('class', 'bg-arc2')
.attr('fill', 'none')
.attr('stroke-width', '4px')
.attr('stroke', '#003366');
graphGroup.append('circle')
.attr('cx', 0)
.attr('cy', 0)
.attr('r', 200)
.style('fill', '#d9d9d9');
graphGroup.append('g')
.selectAll('g')
.data(d3.stack().keys(data.columns.slice(1))(data))
.enter().append("g")
.selectAll(".bg-arc")
.data(function(d) {
return d;
})
.enter().append("path")
.attr("d", d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)
.startAngle(function(d) {
return x(d.data.State);
})
.endAngle(function(d) {
return x(d.data.State) + x.bandwidth() * .95;
})
.padAngle(0.1)
.padRadius(innerRadius))
.attr('class', 'bg-arc')
.attr('fill', '#fff');
graphGroup.append('circle')
.attr('cx', 0)
.attr('cy', 0)
.attr('r', innerRadius)
.style('fill', '#fff');
var stackedData = d3.stack()
.keys(data.columns.slice(1))
(data);
graphGroup.append("g")
.selectAll("g")
.data(stackedData)
.enter().append("g")
.attr("fill", function(d) {
return z(d.key);
})
.selectAll("path")
.data(function(d) {
return d;
})
.enter().append("path")
.attr("d", d3.arc()
.innerRadius(function(d) {
return y(d[0]);
})
.outerRadius(function(d) {
return y(d[1]);
})
.startAngle(function(d) {
return x(d.data.State);
})
.endAngle(function(d) {
return x(d.data.State) + x.bandwidth() * .95;
})
.padAngle(0.04)
.padRadius(innerRadius));
var label = graphGroup.append("g")
.selectAll("g")
.data(data)
.enter().append("g")
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "rotate(" + ((x(d.State) + x.bandwidth() / 2) * 180 / Math.PI - 90) + ")translate(" + (outerRadius + 25) + ",0)";
});
label.append("text")
.attr("transform", function(d) {
return (x(d.State) + x.bandwidth() / 2 + Math.PI / 2) % (2 * Math.PI) < Math.PI ? "rotate(90)translate(0,16)" : "rotate(-90)translate(0,-9)";
})
.text(function(d) {
return d.State;
});
var yAxis = graphGroup.append("g")
.attr("text-anchor", "end");
var yTick = yAxis
.selectAll("g")
.data(y.ticks(10).slice(1))
.enter().append("g");
function weave(array, compare) {
var i = -1,
j, n = array.sort(compare).length,
weave = new Array(n);
while (++i < n) weave[i] = array[(j = i << 1) >= n ? (n - i << 1) - 1 : j];
while (--n >= 0) array[n] = weave[n];
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

How to update d3.js pie chart with updated dataset

I've mixed and matched a lot of code from other sources.
Currently, the pie chart displays fine, but when I select a different server in the drop-down menu, it fails to update the picture. The console logs show that dataset is indeed being changed. I checked online and it seems like my elements are not being removed correctly? However, whenever I try to use path.exit().remove() it fails to display anything.
Here is my code and jsfiddle: http://jsfiddle.net/36q95k1c/2/
var ds1 = [1,1,1,1,1,1,1,3,0,0];
var ds2 = [0,0,0,1,1,1,1,1,1,1];
var ds3 = [0,0,1,1,0,0,0,0,0,0];
var ds4 = [0,0,2,0,5,3,0,0,0,0];
var ds5 = [0,0,0,0,0,0,0,0,0,0];
var ds6 = [0,0,0,0,0,0,0,0,0,0];
var ds7 = [0,0,0,0,0,0,0,0,0,0];
var ds8 = [0,0,0,0,0,0,0,0,0,0];
var ds9 = [0,0,0,0,0,0,0,0,0,0];
var ds10 = [0,0,0,0,0,0,0,0,0,0];
var ds11 = [0,0,0,0,0,0,0,0,0,0];
var ds12 = [0,0,0,0,0,0,0,0,0,0];
var ds13 = [0,0,0,0,0,0,0,0,0,0];
var ds14 = [0,0,0,0,0,0,0,0,0,0];
var dataset = {inner: [4,1,31,28,13,65,6,6,4,3],
middle: ds1,
outer: [1175,1802,8126,11926,37264,4267,2961,2909,850,12432]};
var victim_total = 4+1+31+28+13+65+6+6+4+3;
var killer_total = 22+4+37+72+2+20+2+11+3+3;
var general_total = 1175+1802+8126+11926+37264+4267+2961+2909+850+12432;
var legendRectSize = 25;
var legendSpacing = 6;
//Width and height
var w = 750;
var h = 750;
var r = 100;
var donutWidth = 225
var outerRadius = w / 2;
var innerRadius = donutWidth;
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var pie = d3.layout.pie()
.sort(null);
// Easy colors accessible via a 10-step ordinal scale
var color = d3.scale.category20();
// Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", 2*w)
.attr("height", h)
.append('g')
.attr('transform', 'translate(' + (w / 2) +
',' + (h / 2) + ')');
// Define the div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
function updateLegend(newData) {
//https://dl.dropboxusercontent.com/s/hfho50s0xd2dcpn/craftinggeneralstats1.csv?dl=1"
//https://dl.dropboxusercontent.com/s/i152v8ccetr5gj0/craftinggeneralstats.csv?dl=1
//Import CSV file
d3.csv("https://dl.dropboxusercontent.com/s/i152v8ccetr5gj0/craftinggeneralstats.csv?dl=1", function(data) {
data.forEach(function(d) {
d.outer = +d.count;
d.middle = +d.countkiller;
d.inner = +d.countvictim;
d.label = d.label;
});
// function updateLegend(newData) {
/*var step;
for (step = 0; step < 10; step++) {
// Runs 5 times, with values of step 0 through 4.
data[step].countkiller = 1;
}*/
//data[i].countkiller = 0;
console.log(dataset.middle);
// Set up groups
var arcs = svg.selectAll("g.arc")
.data(d3.values(dataset));
arcs.enter()
.append("g")
.attr("class", "arc")
.attr("transform", "translate(" +0+ "," +0+ ")");
var path = arcs.selectAll("path")
.data(function(d) { return pie(d); });
path.enter().append("path")
.on("mouseover", function(d, i, j) {
// console.log(d);
//console.log(dataset.middle[3]);
div.transition()
.duration(200)
.style("opacity", .9)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
if (j ==0)
div.html("Victim" + "<br/>" + Math.round(1000 * d.value / victim_total) / 10 +"%")
if (j ==1)
div.html("Killer" + "<br/>" + Math.round(1000 * d.value / killer_total) / 10 +"%")
if (j ==2)
{
div.html("Overall" + "<br/>" + Math.round(1000 * d.value / general_total) / 10 +"%")
}
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
})
.attr("fill", function(d, i) { return color(i); })
.attr("d", function(d, i, j) { return arc.innerRadius(10+r*j).outerRadius(r*(j+1))(d); });
// .attr("text-anchor", "middle")
/* .text(function (d, i) {
return data[i].label;
});*/
// Setup legend
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 +(w/2)) + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color)
.on('click', function(label) {
var rect = d3.select(this);
var enabled = true;
if (rect.attr('class') === 'disabled') {
rect.attr('class', '');
} else {
if (totalEnabled < 2) return;
rect.attr('class', 'disabled');
enabled = false;
}
pie.value(function(d) {
if (d.label === label) d.enabled = enabled;
return (d.enabled) ? d.count : 0;
});
arcs = arcs.data(d3.values(dataset))
});
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function (d, i) {
return data[i].label;
});
path.exit().remove();
d3.select('#opts')
.on('change', function(d) {
var server = eval(d3.select(this).property('value'));
dataset.middle = server;
updateLegend(dataset);
});
});
}
updateLegend(dataset);
In the current version, you just add declarations for the enter selection, which affects only arcs that are inserted. You need to guide D3 how to treat the update selection, which affects arcs that are updated and the exit selection, which affects arcs that are removed. You can add these lines of code before the path.enter() statement.
path.attr("d", function(d, i, j) {
return arc.innerRadius(10 + r * j).outerRadius(r * (j + 1))(d);
});
path.exit().remove();
The updated fiddle: http://jsfiddle.net/36q95k1c/5/
To add to Hieu Le's nice answer, for your query about animations, it can be a bit tricky to figure out the right point at which to add the transition() call. Try putting it on line 100 of Hieu Le's jsfiddle to see if it's what you're after:
path.transition().attr("d", function(d, i, j) {
return arc.innerRadius(10 + r * j).outerRadius(r * (j + 1))(d);
});
Once you've got it animating, check out the docs for information on different interpolations and tweens.
Cheers!

d3.js pie chart Invalid value for <path> attribute

I am begginer to d3.js and getting errors, I customized the example for my needs but gettings these in the console. Please help. Thank you in Advance.
Error: Invalid value for <path> attribute d="M-1.8369701987210297e-14,-100A100,100 0 1,1 NaN,NaNLNaN,NaNA60,60 0 1,0 -1.1021821192326178e-14,-60Z"a # d3.v3.min.js:1
d3.v3.min.js:1 Error: Invalid value for <path> attribute d="MNaN,NaNA100,100 0 1,1 NaN,NaNLNaN,NaNA60,60 0 1,0 NaN,NaNZ"
var getData = function () {
var size = 3;
var data =[];
var text = "";
data=[
{
label:"Saved",
value:200,
},{
label:"Ordered",
value:1255,
},{
label:"Shipped",
value:760,
},{
label:"Backordered",
value:150,
},
{
label:"Cancelled",
value:250,
},
];
d3.select("#data").html(text);
return data;
};
console.log(getData());
var chart = donut(250,200,16,40)
.$el(d3.select("#chart"))
.data(getData())
.render();
d3.select("#refresh").on("click", function () {
chart.data(getData()).render();
});
function donut(width,height,label_font_size,value_font_size) {
// Default settings
var $el = d3.select("body")
var data = {};
// var showTitle = true;
var width = width,
height = height,
radius = Math.min(width, height) / 2;
//var arc = d3.svg.arc().outerRadius(radius);
var arcOver = d3.svg.arc()
.innerRadius(radius + 20);
var currentVal;
var color = d3.scale.category20();
var pie = d3.layout.pie()
.sort(null)
.value(function (d) {
console.log(d);
return d.value.value;
});
var svg, g, arc;
var object = {};
// Method for render/refresh graph
object.render = function () {
if (!svg) {
arc = d3.svg.arc()
.outerRadius(radius)
.innerRadius(radius - (radius / 2.5));
svg = $el.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
g = svg.selectAll(".arc")
.data(pie(d3.entries(data)))
.enter().append("g")
.attr("class", "arc");
g.append("path")
// Attach current value to g so that we can use it for animation
.each(function (d) {
//this._current = d;
})
.attr("d", arc)
.style("fill", function (d,i) {
return color(i);
});
/*g.append("text")
.attr("transform", function (d,i) {
return "translate(" + arc.centroid(d,i) + ")";
})
.attr("dy", ".35em")
.style("text-anchor", "middle");
g.select("text").text(function (d) {
//return d.data.key;
});
*/
svg.append("text")
.datum(data)
.attr("x", 0)
.attr("y", 0)
.attr("class", "text-tooltip")
.style("text-anchor", "middle")
.attr("font-weight", "bold")
.style("font-size", radius / 2.5 + "px");
g.on("mouseover", function (obj) {
var tooltipText=svg.select("text.text-tooltip");
tooltipText.attr("fill", function (d,i) {
return color(i);
});
tooltipText.append("tspan")
.attr("dy", -15)
.attr("x",0)
.attr("class", "text-tooltip-label")
.style("font-size", label_font_size + "px")
.text(function(d) {return d[obj.data.key].label;});
tooltipText.append("tspan")
.attr("dy", ".9em") // offest by 1.2 em
.attr("x",0)
.attr("class", "text-tooltip-value")
.style("font-size", value_font_size + "px")
.text(function(d) {return d[obj.data.key].value;});
d3.select(this)
.attr("stroke","white")
.transition()
.duration(1000)
.attr("d", arcOver)
.attr("stroke-width",6);
});
g.on("mouseout", function (obj) {
svg.select("text.text-tooltip").text("");
d3.select(this).transition()
.attr("d", arc)
.attr("stroke","none");
});
} else {
g.data(pie(d3.entries(data))).exit().remove();
g.select("path")
.transition().duration(200)
.attrTween("d", function (a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function (t) {
return arc(i(t));
};
});
g.select("text")
.attr("transform", function (d,i) {
return "translate(" + arc.centroid(d,i) + ")";
});
svg.select("text.text-tooltip").datum(data);
}
return object;
};
// Getter and setter methods
object.data = function (value) {
if (!arguments.length) return data;
data = value;
return object;
};
object.$el = function (value) {
if (!arguments.length) return $el;
$el = value;
return object;
};
object.width = function (value) {
if (!arguments.length) return width;
width = value;
radius = Math.min(width, height) / 2;
return object;
};
object.height = function (value) {
if (!arguments.length) return height;
height = value;
radius = Math.min(width, height) / 2;
return object;
};
return object;
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart"></div>

exit().remove() doesn't remove old charts before update in d3.js

I have a code for multiple charts created on the same page using .each function with the first svg.
var svg = svg1.enter().append("svg:svg")
svg.attr("class", "r_chart")
.attr("width", left_width + width)
.attr("height", height)
.attr('viewBox', '0 0 350 310')
.attr('perserveAspectRatio', 'xMinYMid')
.each(function(d,no){
var base = d3.select(this);
base.selectAll("g").data(d.values);
....
})
Can anyone point out why the old charts aren't removed from the DOM before update on select using jQuery change function? I have svg1.exit().remove(); at the end of the function:
`svg1.exit().remove();`
Here's the JSON data to show up on page load
[{"name":"A","tid":"54","image":"img","measure":32,"group":"53"},
{"name":"B","tid":"55","image":"img","measure":45,"group":"53"},
{"name":"R","tid":"59","image":"img","measure":40,"group":"23"},
{"name":"T","tid":"29","image":"img","measure":20,"group":"23"}]
And on update
[{"name":"G","tid":"14","image":"img","measure":78,"group":"53"},
{"name":"S","tid":"85","image":"img","measure":25,"group":"55"},
{"name":"U","tid":"44","image":"img","measure":78,"group":"53"},
{"name":"K","tid":"29","image":"img","measure":32.6,"group":"55"},
{"name":"Y","tid":"53","image":"img","measure":30,"group":"24"},
{"name":"O","tid":"26","image":"img","measure":25,"group":"24"}]
Full Code:
var width = 350,
height = 310,
barHeight = 80,
left_width = 80,
fo_height = 70;
var x = d3.scale.linear().range([0, width / 1.5]);
y = d3.scale.ordinal().rangeBands([0, height]);
function chart(array){
array = JSON.parse(array);
console.log(array);
var colors = ['#222','red','blue'];
var nested_data = d3.nest().key(function(d) { return d.group; }).entries(array);
ymax = d3.max(nested_data,function(d){
return d.values.map(function(t){
return t.measure;
})
});
nested_data.forEach(function(nd){
nd.values.forEach(function(d){
d.measure = +d.measure;
})
})
var diff = 2;
var tickno = 5
var ymax = d3.max(nested_data, function(nd,k) {
return d3.max(nd.values,function(p,l){
return p.measure ;
});
});
var ymin = d3.min(nested_data, function(nd,k) {
return d3.min(nd.values,function(p,l){
return p.measure - 2;
});
});
x.domain([ymin ,ymax]);
console.log(nested_data);
var svg1 = d3.select('.chart').selectAll("svg").data(nested_data)
var svg = svg1.enter().append("svg:svg")
svg.attr("class", "r_chart")
.attr("width", left_width + width)
.attr("height", height)
.attr('viewBox', '0 0 350 310')
.attr('perserveAspectRatio', 'xMinYMid')
.each(function(d,no){
var count = 1;
var base = d3.select(this);
var bar1 = base.selectAll("g").data(d.values);
var bar = bar1.enter().append("g")
.attr("transform", function(t, i) {
return "translate("+left_width * count+"," + (i+0.5) * barHeight + ")";
count++;
});
var rule1 = base.selectAll(".rule")
.data(x.ticks(5));
var rule = rule1.enter().append("text");
rule.attr("class", "rule changeprice")
.attr("x", function(d) { return x(d) + left_width ; })
.attr("y", barHeight /2)
.attr("dy", -6)
.attr("text-anchor", "middle")
.attr("font-size", 10)
.text(String);
var rect = bar.append("rect")
.style("fill", function(v,l) {
return colors[l]
})
.attr("width", 0)
.attr("height", barHeight - 20)
.attr("rx", 2)
.attr("width", function(t){
return x(t.measure) + 'px';
})
bar.append("text")
.attr("dx", - left_width)
.attr("dy", fo_height )
.style("font-size",'10px')
.text(function(t) { return t.name; })
}) // each fucntion
svg1.exit().remove();
}
var array = $('#a2').text();
chart(array);
$('select').on('change',function(){
var get = $(this).val();
var data = $('#a'+get).text();
chart(data)
});
Here you go: jsfiddle
Please tell if this is what you need. I changed the way of creating the svg1 var. Removing items right after the change is mostly working way better. I also had issues with exit().remove() and found that out after a very long time. It's not the way d3 should be used tho, but it works well.
css:
.data{display:none}
html:
<select><option>Select</option><option value='1'>1</option><option value='2'>2</option></select>
<div class="chart"></div>
<div id='a2' class="data">[{"name":"A","tid":"54","image":"img","measure":32,"group":"53"},{"name":"B","tid":"55","image":"img","measure":45,"group":"53"},{"name":"R","tid":"59","image":"img","measure":40,"group":"23"},{"name":"T","tid":"29","image":"img","measure":20,"group":"23"}]</div>
<div id='a1' class='data'>[{"name":"G","tid":"14","image":"img","measure":78,"group":"53"},{"name":"S","tid":"85","image":"img","measure":25,"group":"55"},
{"name":"U","tid":"44","image":"img","measure":78,"group":"53"}, {"name":"K","tid":"29","image":"img","measure":32.6,"group":"55"}, {"name":"Y","tid":"53","image":"img","measure":30,"group":"24"},{"name":"O","tid":"26","image":"img","measure":25,"group":"24"}]</div>
js:
var width = 350,
height = 310,
barHeight = 80,
left_width = 80,
fo_height = 70;
var x = d3.scale.linear().range([0, width / 1.5]);
y = d3.scale.ordinal().rangeBands([0, height]);
function chart(array){
array = JSON.parse(array);
console.log(array);
var colors = ['#222','red','blue'];
var nested_data = d3.nest().key(function(d) { return d.group; }).entries(array);
ymax = d3.max(nested_data,function(d){
return d.values.map(function(t){
return t.measure;
})
});
nested_data.forEach(function(nd){
nd.values.forEach(function(d){
d.measure = +d.measure;
})
})
var diff = 2;
var tickno = 5
var ymax = d3.max(nested_data, function(nd,k) {
return d3.max(nd.values,function(p,l){
return p.measure ;
});
});
var ymin = d3.min(nested_data, function(nd,k) {
return d3.min(nd.values,function(p,l){
return p.measure - 2;
});
});
x.domain([ymin ,ymax]);
console.log(nested_data);
svg1 = d3.select('.chart').selectAll("svg").data(nested_data)
var svg = svg1.enter().append("svg:svg")
svg.attr("class", "r_chart")
.attr("width", left_width + width)
.attr("height", height)
.attr('viewBox', '0 0 350 310')
.attr('perserveAspectRatio', 'xMinYMid')
.each(function(d,no){
var count = 1;
var base = d3.select(this);
var bar1 = base.selectAll("g").data(d.values);
var bar = bar1.enter().append("g")
.attr("transform", function(t, i) {
return "translate("+left_width * count+"," + (i+0.5) * barHeight + ")";
count++;
});
var rule1 = base.selectAll(".rule")
.data(x.ticks(5));
var rule = rule1.enter().append("text");
rule.attr("class", "rule changeprice")
.attr("x", function(d) { return x(d) + left_width ; })
.attr("y", barHeight /2)
.attr("dy", -6)
.attr("text-anchor", "middle")
.attr("font-size", 10)
.text(String);
var rect = bar.append("rect")
.style("fill", function(v,l) {
return colors[l]
})
.attr("width", 0)
.attr("height", barHeight - 20)
.attr("rx", 2)
.attr("width", function(t){
return x(t.measure) + 'px';
})
bar.append("text")
.attr("dx", - left_width)
.attr("dy", fo_height )
.style("font-size",'10px')
.text(function(t) { return t.name; })
}) // each fucntion
}
var array = $('#a2').text();
var svg1;
chart(array);
$('select').on('change',function(){
var get = $(this).val();
var data = $('#a'+get).text();
svg1.remove();
chart(data)
});

implement tooltip in radar chart with d3.js

var margin = {top: 30, right: 20, bottom: 30, left: 70},
h= 500;
w = 960;
ruleColor = "#CCC";
minVal = 0;
maxVal = 100;
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var viz = d3.select("#radar")
.append('svg:svg')
.attr('width', w)
.attr('height', h)
.attr('class', 'vizSvg');
viz.append("svg:rect")
.attr('id', 'axis-separator')
.attr('x', 0)
.attr('y', 0)
.attr('height', 0)
.attr('width', 0)
.attr('height', 0);
vizBody = viz.append("svg:g")
.attr('id', 'body');
var heightCircleConstraint = 500 - margin.top - margin.bottom;
var widthCircleConstraint = width = 960 - margin.left - margin.right,
circleConstraint = d3.min([heightCircleConstraint, widthCircleConstraint]);
var radius = d3.scale.linear().domain([minVal, maxVal]).range([0, (circleConstraint / 2)]);
var radiusLength = radius(maxVal);
var centerXPos = widthCircleConstraint / 2 + margin.left;
var centerYPos = heightCircleConstraint / 2 + margin.top;
vizBody.attr("transform",
"translate(" + centerXPos + ", " + centerYPos + ")");
d3.json("data/radar.json", function(error, data) {
var hours = [];
var series = [[]];
data.QualitySummaryObject.forEach(function(d,i) {
series[0][i] = d.extractPercentage;
hours[i] = d.extractorName;
});
for (i = 0; i < series.length; i += 1) {
series[i].push(series[i][0]);
}
//console.log(series.length);
var radialTicks = radius.ticks(5);
vizBody.selectAll('.circle-ticks').remove();
vizBody.selectAll('.line-ticks').remove();
var circleAxes = vizBody.selectAll('.circle-ticks')
.data(radialTicks)
.enter().append('svg:g')
.attr("class", "circle-ticks");
circleAxes.append("svg:circle")
.attr("r", function (d, i) {
return radius(d);
})
.attr("class", "circle")
.style("stroke", ruleColor)
.style("fill", "none");
circleAxes.append("svg:text")
.attr("text-anchor", "middle")
.attr("dy", function (d) {
return -1 * radius(d);
})
.text(String);
var lineAxes = vizBody.selectAll('.line-ticks')
.data(hours)
.enter().append('svg:g')
.attr("transform", function (d, i) {
return "rotate(" + ((i / hours.length * 360) - 90) +
")translate(" + radius(maxVal) + ")";
})
.attr("class", "line-ticks");
lineAxes.append('svg:line')
.attr("x2", -1 * radius(maxVal))
.style("stroke", ruleColor)
.style("fill", "none");
lineAxes.append('svg:text')
.text(String)
.attr("text-anchor", "middle")
.attr("transform","translate(15) rotate(90)");
//var highlightedDotSize = 4;
var groups = vizBody.selectAll('.series').data(series);
//console.log(hours.length);
groups.enter().append("svg:g")
.attr('class', 'series')
.style('fill', "green")
.style('stroke',"#ccc");
//groups.exit().remove();
//console.log(Math.PI);
var lines = groups.append('svg:path')
.attr("class", "line")
/*.attr("d", d3.svg.line.radial()
.radius(function (d) {
return 10;
})
.angle(function (d, i) {
if (i == hours.length) {
i = 0;
} //close the line
return (i / hours.length) * 2 * Math.PI;
}))*/
.style("stroke-width", 1)
.style("fill", "rgba(124,240,10,0.1)");
/*groups.selectAll(".curr-point")
.data(function (d) {
return [d[0]];
})
.enter().append("svg:circle")
.attr("class", "curr-point")
.attr("r", 0);
groups.selectAll(".clicked-point")
.data(function (d) {
return [d[0]];
})
.enter().append("svg:circle")
.attr('r', 0)
.attr("class", "clicked-point");*/
lines.attr("d", d3.svg.line.radial()
.radius(function (d) {
return radius(d);
})
.angle(function (d, i) {
if (i === hours.length) {
i = 0;
} //close the line
return (i / hours.length) * 2 * Math.PI;
}));
});
i implemented this code to create radar chart with a json data here is the json data format
{
"QualitySummaryObject": [
{
"extractPercentage": 68.81964,
"extractorName": "Classification"
},
{
"extractPercentage": 74.09091,
"extractorName": "Keyword Match"
},
{
"extractPercentage": 54.62963,
"extractorName": "LocationBroadcast"
},
{
"extractPercentage": 98.91892,
"extractorName": "Qualification"
},
{
"extractPercentage": 98.76923,
"extractorName": "User Profile Location"
},
{
"extractPercentage": 80.15909,
"extractorName": "Valid Person Name"
},
]
}
Now i want to put tooltip on each node point .. but i am not able to get any idea how to do that any body can help?
Here is an example of Twitter Bootstrap tooltips running on SVGs with D3 http://markhansen.co.nz/stolen-vehicles-pt2/
To get it working on newer versions see Why doesn't Twitter Bootstrap popover work with SVG elements? You'll need to use a 2.3.0+ version of bootstrap or the fix I posted in that thread.

Categories