Dynamically resizing of d3.js bar chart based on data - javascript

I'm building a set of bar charts that will be updated dynamically with json data.
There will be occasions where the x.domain value is equal to zero or null so in those cases I don't want to draw the rectangle, and would like the overall height of my chart to adjust. However, the bars are being drawn based on data.length which may contain 9 array values, but some of those values are zeros, but render a white space within the graph.
I've attached an image of what is happening. Basically there are 9 data entries and only one of those actually contains the positive value, but the bars are still being drawn for all 9 points.
Here is my code:
d3.json("data/sample.json", function(json) {
var data = json.cand;
var margin = {
top: 10,
right: 20,
bottom: 30,
left: 0
}, width = parseInt(d3.select('#graphic').style('width'), 10),
width = width - margin.left - margin.right -50,
bar_height = 55,
num_bars = (data.length),
bar_gap = 18,
height = ((bar_height + bar_gap) * num_bars);
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.ordinal()
.range([height, 0]);
var svg = d3.select('#graphic').append('svg')
.attr('width', width + margin['left'] + margin['right'])
.attr('height', height + margin['top'] + margin['bottom'])
.append('g')
.attr('transform', 'translate(' + margin['left'] + ',' + margin['top'] + ')');
var dx = width;
var dy = (height / data.length) + 8;
// set y domain
x.domain([0, d3.max(data, function(d) {
return d.receipts;
})]);
y.domain(0, d3.max(data.length))
.rangeBands([0, data.length * bar_height ]);
var xAxis = d3.svg.axis().scale(x).ticks(2)
.tickFormat(function(d) {
return '$' + formatValue(d);
});
var yAxis = d3.svg.axis()
.scale(y)
.orient('left')
.ticks(0)
.tickFormat(function(d) {
return '';
});
// set height based on data
height = y.rangeExtent()[1] +12;
d3.select(svg.node().parentNode)
.style('height', (height + margin.top + margin.bottom) + 'px');
// bars
var bars = svg.selectAll(".bar")
.data (data, function (d) {
if (d.receipts >= 1) {
return ".bar";
} else {
return null;
}
})
.enter().append('g');
bars.append('rect')
.attr("x", function(d, i) {
return 0;
})
.attr("y", function(d, i) {
if (d.receipts >= 1) {
return i * (bar_height + bar_gap - 4);
}else {
return null;
}
})
.attr("width", function(d, i) {
if (d.receipts >= 1) {
return dx * d.receipts;
} else {
return 0;
}
});
//y and x axis
svg.append('g')
.attr('class', 'x axis bottom')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis.orient('bottom'));
svg.append('g')
.call(yAxis.orient('left'));
});

Related

d3.js extend bottom axis line to origin

I'd like to space the first tick from the origin point, but would also like to have a line connecting them. I suppose I could append an svg to do this for me, but there has to be an easier way that I am missing in the documentation. An image example of what I'm aiming for can be found here
Here's an example of the issue I'm having:
var margin = {
top: 20,
right: 10,
bottom: 40,
left: 40
};
var padding = 20;
var height = 100;
var width = 400
var xAxisTimeScale = [];
for(var i = 8; i < 21; i++) {
xAxisTimeScale.push(i);
}
// scales
var xScale = d3.scaleLinear()
.domain([0, 12])
.range([padding, width - padding]);
var yScale = d3.scaleLinear()
.domain([0, 10])
.range([height, 0]);
function convertTimeToString(time) {
if(time > 12) {
return (time - 12) + "PM";
} else {
return time + "AM";
}
}
var xAxis = d3.axisBottom(xScale)
.ticks(13)
.tickFormat(function(d,i){ return convertTimeToString(xAxisTimeScale[i]);});
var yAxis = d3.axisLeft(yScale);
var svg = d3.select("body").append("svg")
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// add axes
svg.append('g')
.call(yAxis);
svg.append('g')
.attr('transform', 'translate(0, ' + height + ')')
.call(xAxis);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.6.0/d3.min.js"></script>
Here are the approaches I came up with:
Change x axis's path's d attribute to 'M0,0.5V0.5H'+(width-padding). Relevant code changes:
svg.select('.x.axis path.domain').attr('d', function() {
return 'M0,0.5V0.5H'+(width-padding);
});
How did I come up with 0.5 in there? I analyzed d3.js and came across the V0.5 (which was V0 in d3-version3 used for creating X-axis domain. For more details on how a path is formed, check out SVG path d attribute. Using this offset, here's a code snippet implementing the same:
var margin = {
top: 20,
right: 10,
bottom: 40,
left: 40
};
var padding = 20;
var height = 100;
var width = 400
var xAxisTimeScale = [];
for(var i = 8; i < 21; i++) {
xAxisTimeScale.push(i);
}
// scales
var xScale = d3.scaleLinear()
.domain([0, 12])
.range([padding, width - padding]);
var yScale = d3.scaleLinear()
.domain([0, 10])
.range([height, 0]);
function convertTimeToString(time) {
if(time > 12) {
return (time - 12) + "PM";
} else {
return time + "AM";
}
}
var xAxis = d3.axisBottom(xScale)
.ticks(13)
.tickFormat(function(d,i){ return convertTimeToString(xAxisTimeScale[i]);});
var yAxis = d3.axisLeft(yScale);
var svg = d3.select("body").append("svg")
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// add axes
svg.append('g')
.call(yAxis);
svg.append('g').classed('x axis', true)
.attr('transform', 'translate(0, ' + height + ')')
.call(xAxis);
svg.select('.x.axis path.domain').attr('d', function() {
return 'M0,0.5V0.5H'+(width-padding);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.6.0/d3.min.js"></script>
Add a line explicitly from origin to the start-point of X-axis with a simple line code. Relevant code changes:
svg.append('line').classed('connecting-line', true)
.attr('y1', height+0.5).attr('y2', height+0.5).attr('x1', 0).attr('x2', padding).style('stroke', '#000');
0.5 has the same reason as above. Rest attributes are just based on height and width. Here's a code snippet implementing this:
var margin = {
top: 20,
right: 10,
bottom: 40,
left: 40
};
var padding = 20;
var height = 100;
var width = 400
var xAxisTimeScale = [];
for(var i = 8; i < 21; i++) {
xAxisTimeScale.push(i);
}
// scales
var xScale = d3.scaleLinear()
.domain([0, 12])
.range([padding, width - padding]);
var yScale = d3.scaleLinear()
.domain([0, 10])
.range([height, 0]);
function convertTimeToString(time) {
if(time > 12) {
return (time - 12) + "PM";
} else {
return time + "AM";
}
}
var xAxis = d3.axisBottom(xScale)
.ticks(13)
.tickFormat(function(d,i){ return convertTimeToString(xAxisTimeScale[i]);});
var yAxis = d3.axisLeft(yScale);
var svg = d3.select("body").append("svg")
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// add axes
svg.append('g')
.call(yAxis);
svg.append('g').classed('x axis', true)
.attr('transform', 'translate(0, ' + height + ')')
.call(xAxis);
svg.append('line').classed('connecting-line', true)
.attr('y1', height+0.5).attr('y2', height+0.5).attr('x1', 0).attr('x2', padding).style('stroke', '#000');
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.6.0/d3.min.js"></script>
Adding an overlay rectangle either as a box OR as a rect styled with stroke-dasharray. But I think this wouldn't be that helpful as it would be a bit of overriding stuff.
Hope any of the above approaches serves the purpose. And I'm really sorry for not getting back on time (Friday night got me) :)
Given that you have a linear scale disguised as a time scale, the solution here will be an ad hoc one. Otherwise, the solution could be more idiomatic.
First, increase your xAxisTimeScale:
var xAxisTimeScale = d3.range(7, 23, 1)
Then, increase your domain...
.domain([0, 13])
... and remove the first tick:
.tickFormat(function(d, i) {
return i ? convertTimeToString(xAxisTimeScale[i]) : null;
});
Here is your code with those changes:
var margin = {
top: 20,
right: 10,
bottom: 40,
left: 40
};
var padding = 20;
var height = 100;
var width = 400
var xAxisTimeScale = d3.range(7, 23, 1)
// scales
var xScale = d3.scaleLinear()
.domain([0, 13])
.range([0, width - padding]);
var yScale = d3.scaleLinear()
.domain([0, 10])
.range([height, 0]);
function convertTimeToString(time) {
if (time > 12) {
return (time - 12) + "PM";
} else {
return time + "AM";
}
}
var xAxis = d3.axisBottom(xScale)
.ticks(13)
.tickFormat(function(d, i) {
return i ? convertTimeToString(xAxisTimeScale[i]) : null;
});
var yAxis = d3.axisLeft(yScale);
var svg = d3.select("body").append("svg")
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// add axes
svg.append('g')
.call(yAxis);
svg.append('g')
.attr('transform', 'translate(0, ' + height + ')')
.call(xAxis);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.6.0/d3.min.js"></script>

How do you rotate the x and y axis in D3 bar chart

I am trying to figure out how to have a horizontal bar chart instead of a vertical. I have watched a video to try to explain how to do this, as well as looked at forums and other D3 codes, but I still can't figure it out. Here is my code below.
<script>
var data = [
{
skill:"FBSO",
TEAM:57,
OPP:50
},
{
skill:"SO",
TEAM:73,
OPP:61
},
{
skill:"ModSO",
TEAM:69,
OPP:57
},
{
skill:"ErndSO",
TEAM:67,
OPP:52
},
{
skill:"FBPS",
TEAM:35,
OPP:25
},
{
skill:"PS",
TEAM:43,
OPP:29
}
];
var margin = {
top: 20,
right: 20,
bottom: 40,
left: 60
},
width = 450 - margin.left - margin.right,
height = 315 - margin.top - margin.bottom,
that = this;
var x = d3.scale.ordinal().rangeRoundBands([0, width], .3);
var x2 = d3.scale.ordinal().rangeRoundBands([0, width], .3);
var y = d3.scale.linear().rangeRound([height, 0]);
var color = d3.scale.category20();
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var xAxis2 = d3.svg.axis().scale(x).orient("top");
var yAxis = d3.svg.axis().scale(x).orient("left");
var yAxis2 = d3.svg.axis().scale(x).orient("right");
// var yAxis = d3.svg.axis().scale(y).orient("left").tickFormat(d3.format(".0%"));
var svg = d3.select(".viz-portfolio-delinquent-status").append("svg").attr("width", width + margin.left + margin.right).attr("height", height + margin.top + margin.bottom).append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
color.domain(d3.keys(data[0]).filter(function (key) {
return key !== "interest_rate";
}));
data.forEach(function (d) {
var y0 = 0;
d.rates = color.domain().map(function (name) {
// console.log();
// ;
return {
name: name,
y0: y0,
y1: y0 += +d[name],
amount: d[name]
};
});
d.rates.forEach(function (d) {
d.y0 /= y0;
d.y1 /= y0;
});
// console.log(data);
});
data.sort(function (a, b) {
return b.rates[0].y1 - a.rates[0].y1;
});
x.domain(data.map(function (d) {
return d.interest_rate;
}));
svg.append("g").attr("class", "x axis").attr("transform", "translate(0," + height + ")").call(xAxis);
svg.append('g').attr('class', 'x axis').attr('transform', 'translate(0,'+ height/375 +')').call(xAxis2);
svg.append("g").attr("class", "x axis").attr("transform", "translate(" + width + ",0)").call(yAxis2);
svg.append('g').attr('class', 'y axis').attr('transform', 'translate('+ height/375 +',0)').call(yAxis);
// svg.append("g").attr("class", "y axis").call(yAxis);
var interest_rate = svg.selectAll(".interest-rate").data(data).enter().append("g").attr("class", "interest-rate").attr("transform", function (d) {
// conosole.log(d)
return "translate(" + x(d.interest_rate) + ",0)";
});
interest_rate.selectAll("rect")
.data(function (d) {
return d.rates;
})
.enter()
.append("rect")
.attr("width", x.rangeBand())
.attr("y", function (d) {
return y(d.y1);
})
.attr("height", function (d) {
// console.log(d)
return y(d.y0) - y(d.y1);
})
.style("fill", function (d) {
return color(d.name);
})
.on('mouseover', function (d) {
var total_amt;
total_amt = d.amount;
// console.log('----');
// d3.select(".chart-tip").style('opacity', '1').html('Amount: <strong>$' + that.numberWithCommas(total_amt.toFixed(2)) + '</strong>');
}).on('mouseout', function () {
d3.select(".chart-tip").style('opacity', '0');
});
I am sure it isn't too difficult, I just can't figure which x's and y's I need to change. Thanks!
So I figured it out somewhat. I rotated the svg that my plot was on so that it visually showed the x axis as the y axis, and vice versa.
svg {
transform: rotate(90deg);
}
This will make things difficult, as far as adding text on top of the bar chart, and it will make your height act as width, but it is a decent temporary fix.

How do I use JSON and return data with D3

I am currently playing with learning D3 JS and am trying to make a bar chart from the live information I am pulling from the openweather API. I am trying to get the City Name and its respective temperature at the time of the query.
While trying to set up my chart, I keep running into the problem of being unable to return a number value for each bars height and width as I get NaN for each value. Currently, what I have is a function that iterates through the data and creates an object with the temperature and name of the city. I then push them into an array, weather, which I am calling for my data.
Can somebody explain or figure out why I am getting NaN and how I might be able to pull specifically the data's temperature value?
var weather =[];
var name;
var margin = {
top: 50,
right:30,
bottom:40,
left:50
}
var height = 500 - margin.top - margin.bottom ,
width = 600 - margin.left - margin.right,
barwidth = 50,
barOffset = 5;
var yScale = d3.scale.linear()
.domain([0, d3.max(weather)])
.range([0, height]);
var xScale = d3.scale.ordinal()
.domain(d3.range(0, weather.length))
.rangeBands([0, width], .2)
var tempColor;
//Load external data//
d3.json("http://api.openweathermap.org/data/2.5/group?id=192710,2643743,1850147,2988507,524901,5368361,1816670,2177052,1835847,3128760,7533612,292223,7870410,3451190,1275339,4904381,5856195,&units=metric", function(data){
var list = data.list
for (var i = 0; i<list.length; i++){
var country = list[i]
var nameOfCountry = list[i].name
var temperature = +country.main.temp
var countryWeather = [ nameOfCountry, temperature ]
weather.push({ "temperature":temperature, "nameOfCountry":nameOfCountry})
}
console.log(weather)
// Horizontal Color Gradient for bars
var colors = d3.scale.linear()
.domain([0, weather.length*.33, weather.length*.66, weather.length])
.range(['#FFB832','#C61C6F','#268BD2','#85992C'])
//Create the canvas//
var Canvas = d3.select('#chart').append('svg')
.style('background','#FFF')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform','translate(' + margin.left + ',' + margin.top + ')')
.style('background', '#C9D7D6')
.selectAll('rect').data(weather)
.enter().append('rect')
.style('fill', function(d,i){
return colors(i);
})
.attr('width', xScale.rangeBand())
.attr('height', 0)
.attr('x',function(d,i){
return xScale(i);
})
.attr('y', height)
.on('mouseover', function(d){
tooltip.transition()
.style('opacity', .9)
tooltip.html(d)
.style('left',(d3.event.pageX - 35) + 'px')
.style('top', (d3.event.pageY - 35) + 'px')
tempColor = this.style.fill;
d3.select(this)
.transition().delay(500).duration(800)
.style('opacity', .5)
.style('fill', 'yellow')
})
.on('mouseout',function(d){
d3.select(this)
.transition().delay(500).duration(800)
.style('opacity',1)
.style('fill', tempColor)
})
Canvas.transition()
.attr('height',function(d){
return yScale(d);
})
.attr('y', function(d){
return height - yScale(d);
})
.delay(function(d,i){
return i * 20;
})
.duration(800)
.ease('elastic')
var vGuideScale = d3.scale.linear()
.domain([0,d3.max(weather)])
.range([height,0])
var vAxis = d3.svg.axis()
.scale(vGuideScale)
.orient('left')
.ticks(10)
var vGuide = d3.select('svg').append('g')
vAxis(vGuide)
vGuide.attr('transform','translate(' + margin.left + ',' + margin.top +')')
vGuide.selectAll('path')
.style({
fill:'none',
stroke: '#000'
})
vGuide.selectAll('line')
.style({
stroke: '#000'
})
var hAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.tickValues(xScale.domain().filter(function(d,i){
return !(i % (weather.length/5))
}))
var hGuide = d3.select('svg').append('g')
hAxis(hGuide)
hGuide.attr('transform','translate(' + (margin.left-6) + ',' + (height + margin.top) +')')
hGuide.selectAll('path')
.style({
fill:'none',
stroke: "#000"
})
hGuide.selectAll('line')
.style({
stroke:"#000"
})
});
It looks like your domain is not set correctly
var yScale = d3.scale.linear()
//.domain([0, d3.max(weather)]) Remove this line
.range([0, height]);
Remove that line and insert it after weather has been populated. Use the d3.extent() method to get both the min and max of weather.temperature simultaneously
for (var i = 0; i<list.length; i++){
var country = list[i]
var nameOfCountry = list[i].name
var temperature = +country.main.temp
var countryWeather = [ nameOfCountry, temperature ]
weather.push({ "temperature":temperature, "nameOfCountry":nameOfCountry})
}
yScale.domain(d3.extent(weather, function(d) { return d.temperature; }));
console.log(weather)
Also remember you need to access the temperature attribute specifically
Canvas.transition()
.attr('height',function(d){
return yScale(d.temperature);
})
.attr('y', function(d){
return height - yScale(d.temperature);
})

Trying to transfer Tributary D3.js example into JSFiddle

I'm rather a beginner at both JS and D3.js; my JSFiddle is here.
JSHint shows no errors, so I think I'm doing the D3 wrong.
I'm trying to do the same thing as in these questions, adapting a Tributary example to run outside of that platform: "Exporting" a Tributary example that makes use of the tributary object - d3.js and Getting horizontal stack bar example to display using d3.js (I'm even adapting the same code as the latter) Unfortunately, there is no corrected JSFiddle or other example in either answer
Here's my code:
var VanillaRunOnDomReady = function() {
var margins = {
top: 12,
left: 48,
right: 24,
bottom: 24
};
var data = [
{"key":"Nonviolent", "cat1":0.69, "cat2":0.21, "cat3":0.10},
{"key":"Violent", "cat1":0.53, "cat2":0.29, "cat3":0.18}
];
var catnames = {cat1: "No mental illness",
cat2: "Mild mental illness",
cat3: "Moderate or severe mental illness"};
var svg = d3.select('body')
.append('svg')
.attr('width', width + margins.left + margins.right)
.attr('height', height + margins.top + margins.bottom)
.append('g')
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')');
var n = 3, // number of layers
m = data.length, // number of samples per layer
stack = d3.layout.stack(),
labels = data.map(function(d) {return d.key;}),
//go through each layer (cat1, cat2 etc, that's the range(n) part)
//then go through each object in data and pull out that objects's catulation data
//and put it into an array where x is the index and y is the number
layers = stack(d3.range(n).map(function(d) {
var a = [];
for (var i = 0; i < m; ++i) {
a[i] = {x: i, y: data[i]['cat' + (d+1)]};
}
return a;
})),
//the largest single layer
yGroupMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y; }); }),
//the largest stack
yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); });
var margin = {top: 40, right: 10, bottom: 20, left: 50},
width = 677 - margin.left - margin.right,
height = 533 - margin.top - margin.bottom;
var y = d3.scale.ordinal()
.domain(d3.range(m))
.rangeRoundBands([2, height], 0.08);
var x = d3.scale.linear()
.domain([0, yStackMax])
.range([0, width]);
var color = d3.scale.linear()
.domain([0, n - 1])
.range(["#aad", "#556"]);
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { return color(i); });
layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("y", function(d) { return y(d.x); })
.attr("x", function(d) { return x(d.y0); })
.attr("height", y.rangeBand())
.attr("width", function(d) { return x(d.y); });
var yAxis = d3.svg.axis()
.scale(y)
.tickSize(1)
.tickPadding(6)
.tickValues(labels)
.orient("left");
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
};
I think you are just missing a (); at the end of your function and before the semicolon. That would make it self executing. I forked your fiddle.
var VanillaRunOnDomReady = function() {
var margins = {
top: 12,
left: 48,
right: 24,
bottom: 24
};
var data = [
{"key":"Nonviolent", "cat1":0.69, "cat2":0.21, "cat3":0.10},
{"key":"Violent", "cat1":0.53, "cat2":0.29, "cat3":0.18}
];
var catnames = {cat1: "No mental illness",
cat2: "Mild mental illness",
cat3: "Moderate or severe mental illness"};
var svg = d3.select('body')
.append('svg')
.attr('width', width + margins.left + margins.right)
.attr('height', height + margins.top + margins.bottom)
.append('g')
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')');
var n = 3, // number of layers
m = data.length, // number of samples per layer
stack = d3.layout.stack(),
labels = data.map(function(d) {return d.key;}),
//go through each layer (cat1, cat2 etc, that's the range(n) part)
//then go through each object in data and pull out that objects's catulation data
//and put it into an array where x is the index and y is the number
layers = stack(d3.range(n).map(function(d) {
var a = [];
for (var i = 0; i < m; ++i) {
a[i] = {x: i, y: data[i]['cat' + (d+1)]};
}
return a;
})),
//the largest single layer
yGroupMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y; }); }),
//the largest stack
yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); });
var margin = {top: 40, right: 10, bottom: 20, left: 50},
width = 677 - margin.left - margin.right,
height = 533 - margin.top - margin.bottom;
var y = d3.scale.ordinal()
.domain(d3.range(m))
.rangeRoundBands([2, height], 0.08);
var x = d3.scale.linear()
.domain([0, yStackMax])
.range([0, width]);
var color = d3.scale.linear()
.domain([0, n - 1])
.range(["#aad", "#556"]);
var svg = d3.select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { return color(i); });
layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("y", function(d) { return y(d.x); })
.attr("x", function(d) { return x(d.y0); })
.attr("height", y.rangeBand())
.attr("width", function(d) { return x(d.y); });
var yAxis = d3.svg.axis()
.scale(y)
.tickSize(1)
.tickPadding(6)
.tickValues(labels)
.orient("left");
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
}();

d3.js ordinal axis is not updated correctly

I created a simple, updatedable d3.js bar chart.
Except for the x-axis, it works fine.
When I remove bars from the chart, the corresponding axis labels are not removed.
But when I remove bars for the second time, the labels of the bars that were removed in the first run are removed from the axis.
What is happening here, and why?
See a working demo here: http://jsfiddle.net/vACua/4/
The js code would be the following:
var BarChart = function (config) {
var data,
svg,
xScale,
yScale,
yAxis,
xAxis;
svg = d3.select('#' + config.targetElement).append('svg');
svg.attr("width", config.width + config.margin.left + config.margin.right)
.attr("height", config.height + config.margin.top + config.margin.bottom)
.attr("transform", "translate(" + config.margin.left + "," + config.margin.top + ")");
xScale = d3.scale.ordinal();
yScale = d3.scale.linear();
xAxis = d3.svg.axis().scale(xScale).orient("bottom").tickSize(-config.height, 0).tickPadding(6);
svg.append('g').classed('x-axis-group axis', true);
svg.append("g")
.classed("x axis", true)
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// All methods and members defined here are public
return {
setData: function (newData) {
data = newData;
this.updateChart();
},
updateChart: function () {
var xDomain = data.map(function (d) {
return d.name;
}),
yDomain = data.map(function (d) {
return d.value;
});
xScale.domain(xDomain)
.rangeRoundBands([config.margin.left, config.width], 0.05);
yScale.domain([0, d3.max(data, function (d) {
return d.value;
})])
.range([config.height, 0]);
xAxis.scale(xScale)
.orient("bottom")
.tickSize(-config.height, 0)
.tickPadding(6);
var dataSections = svg.selectAll("g.bar")
.data(data, function (d) {
return d.name;
});
// adding new bars
dataSections.enter()
.append('g')
.attr('class', 'bar')
.append("rect")
.attr("height", 0);
var transition = svg.transition().duration(750);
transition.selectAll("g.bar").select('rect')
.attr("x", function (d, i) {
return xScale(d.name);
})
.attr("y", function (d) {
return yScale(d.value);
})
.attr("width", xScale.rangeBand())
.attr("height", function (d) {
return height - yScale(d.value);
});
dataSections.exit().transition().remove();
svg.select('.x.axis')
.transition()
.duration(500)
.attr({
transform: 'translate(0,' + (config.height) + ')'
})
.call(xAxis);
}
};
};
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom;
amount = 15;
function init() {
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = 400 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom;
var chart = BarChart({
targetElement: 'foo',
margin: margin,
width: width,
height: height
});
setRandomData = function () {
var data = [];
for (var i = 0; i <= amount; i++) {
data.push({
name: "a" + i,
value: Math.floor((Math.random() * 300) + 1)
});
}
amount = amount - 5;
chart.setData(data);
}
}
init();
setRandomData();
setTimeout(setRandomData, 2000);
setTimeout(setRandomData, 4000);
Ok, I have absolutely no clue why but moving
svg.select('.x.axis')
.transition()
.duration(500)
.attr({transform: 'translate(0,' + (config.height) + ')'})
.call(xAxis);
before this line:
var transition = svg.transition().duration(750);
seems to work the way you intended: http://jsfiddle.net/Y274x/

Categories