d3.js extend bottom axis line to origin - javascript

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>

Related

How to fix a multiple brush chart in d3

I am so close this is killing me. I've generated a simple brush for one column and it's generating the limits it's set to perfectly. The thing is I'd like multiple brushes for multiple columns ['A', 'B', 'C', 'D']. I could write this out four times, but I've put it in a function that doesn't appear to work. Please see the working code below, I've commented out the part that doesn't work. I know I need to somehow bind the data and loop through, but how do I do this efficiently?
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 180 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain(["A", "B", "C", "D"])
.rangeBands([0, 200])
var y = d3.scale.linear()
.range([height, 0])
.domain([0, 100])
var xAxis = d3.svg.axis()
.scale(x)
var svg = d3.select("#timeline").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 + ")");
// define brush control element and its events
var brush = d3.svg.brush().y(y)
.on("brushend", () => console.log('A Extent: ', brush.extent()))
// create svg group with class brush and call brush on it
var brushg = svg.append("g")
.attr("class", "brush")
.call(brush);
// set brush extent to rect and define objects height
brushg.selectAll("rect")
.attr("x", x("A"))
.attr("width", 20);
/*
var brush = (d) => {
var brush = d3.svg.brush().y(y)
.on("brushend", () => console.log(d, ' Extent: ', brush.extent()))
var brushg = svg.append("g")
.attr("class", "brush")
.call(brush1);
brushg.selectAll("rect")
.attr("x", x("A"))
.attr("width", 20);
}
*/
.brush {
fill: lightgray;
fill-opacity: .75;
shape-rendering: crispEdges;
}
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.6/d3.min.js'></script>
<div class="container">
<div id="timeline"></div>
</div>
Treat the brushes as data, as they map to each ordinal value on the x axis. Create the brushes with .enter and append all the necessary functionality. The .each function is similar to call, but runs on each element separately. This is very useful to contain the generation of the brushes.
xData = ["A", "B", "C", "D"];
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 180 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain(xData)
.rangeBands([0, 200])
var y = d3.scale.linear()
.range([height, 0])
.domain([0, 100])
var xAxis = d3.svg.axis()
.scale(x)
var svg = d3.select("#timeline").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 + ")");
const brushes = svg.selectAll('g.brush')
.data(xData)
.enter()
.append('g')
.attr('class', 'brush')
.each(function(d) {
const el = d3.select(this);
const brush = d3.svg.brush().y(y).on("brushend", () => console.log(d, ' Extent: ', brush.extent()));
el.call(brush);
el.selectAll("rect")
.attr("x", x(d))
.attr("width", 20);
});
.brush {
fill: lightgray;
fill-opacity: .75;
shape-rendering: crispEdges;
}
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.6/d3.min.js'></script>
<div class="container">
<div id="timeline"></div>
</div>

D3.js brush behaviour fires two 'end' events

So, implementing a brush behaviour inspired from M Bostock example I came across something I did not quite understand.
If set a callback for the 'end' event of the brush, this gets called as expected whenever you're interacting directly with the brush.
But whenever I recenter the brush, it seems that the end event is fired twice.
Why is that the case? Or, is it something I'm doing wrong here?
<!DOCTYPE html>
<style>
.selected {
fill: red;
stroke: brown;
}
</style>
<svg width="960" height="150"></svg>
<div>Event fired <span id="test"></span></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var fired=0;
var randomX = d3.randomUniform(0, 10),
randomY = d3.randomNormal(0.5, 0.12),
data = d3.range(800).map(function() { return [randomX(), randomY()]; });
var svg = d3.select("svg"),
margin = {top: 10, right: 50, bottom: 30, left: 50},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleLinear()
.domain([0, 10])
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var brush = d3.brushX()
.extent([[0, 0], [width, height]])
.on("start brush", brushed)
.on("end", brushend);
var dot = g.append("g")
.attr("fill-opacity", 0.2)
.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("transform", function(d) { return "translate(" + x(d[0]) + "," + y(d[1]) + ")"; })
.attr("r", 3.5);
g.append("g")
.call(brush)
.call(brush.move, [3, 5].map(x))
.selectAll(".overlay")
.each(function(d) { d.type = "selection"; }) // Treat overlay interaction as move.
.on("mousedown touchstart", brushcentered); // Recenter before brushing.
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
function brushcentered() {
var dx = x(1) - x(0), // Use a fixed width when recentering.
cx = d3.mouse(this)[0],
x0 = cx - dx / 2,
x1 = cx + dx / 2;
d3.select(this.parentNode).call(brush.move, x1 > width ? [width - dx, width] : x0 < 0 ? [0, dx] : [x0, x1]);
}
function brushed() {
var extent = d3.event.selection.map(x.invert, x);
dot.classed("selected", function(d) { return extent[0] <= d[0] && d[0] <= extent[1]; });
}
function brushend() {
document.getElementById('test').innerHTML = ++fired;
// console.log('end fired - ' + (++fired));
}
</script>
Whenever you want to stop an event from triggering multiple layers of actions, you can use:
d3.event.stopPropagation();
Here you can include it at the end of the brushcentered function:
function brushcentered() {
var dx = x(1) - x(0), // Use a fixed width when recentering.
cx = d3.mouse(this)[0],
x0 = cx - dx / 2,
x1 = cx + dx / 2;
d3.select(this.parentNode).call(brush.move, x1 > width ? [width - dx, width] : x0 < 0 ? [0, dx] : [x0, x1]);
d3.event.stopPropagation();
}
And the demo:
<style>
.selected {
fill: red;
stroke: brown;
}
</style>
<svg width="960" height="150"></svg>
<div>Event fired <span id="test"></span></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var fired=0;
var randomX = d3.randomUniform(0, 10),
randomY = d3.randomNormal(0.5, 0.12),
data = d3.range(800).map(function() { return [randomX(), randomY()]; });
var svg = d3.select("svg"),
margin = {top: 10, right: 50, bottom: 30, left: 50},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleLinear()
.domain([0, 10])
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var brush = d3.brushX()
.extent([[0, 0], [width, height]])
.on("start brush", brushed)
.on("end", brushend);
var dot = g.append("g")
.attr("fill-opacity", 0.2)
.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("transform", function(d) { return "translate(" + x(d[0]) + "," + y(d[1]) + ")"; })
.attr("r", 3.5);
g.append("g")
.call(brush)
.call(brush.move, [3, 5].map(x))
.selectAll(".overlay")
.each(function(d) { d.type = "selection"; }) // Treat overlay interaction as move.
.on("mousedown touchstart", brushcentered); // Recenter before brushing.
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
function brushcentered() {
var dx = x(1) - x(0), // Use a fixed width when recentering.
cx = d3.mouse(this)[0],
x0 = cx - dx / 2,
x1 = cx + dx / 2;
d3.select(this.parentNode).call(brush.move, x1 > width ? [width - dx, width] : x0 < 0 ? [0, dx] : [x0, x1]);
d3.event.stopPropagation();
}
function brushed() {
var extent = d3.event.selection.map(x.invert, x);
dot.classed("selected", function(d) { return extent[0] <= d[0] && d[0] <= extent[1]; });
}
function brushend() {
document.getElementById('test').innerHTML = ++fired;
}
</script>
-UPDATE-
For the purpose of this snippet, I can use a boolean flag to stop the first event and let the second go through. This means that I am still able to drag the brush after recentering, all in one go.
<!DOCTYPE html>
<style>
.selected {
fill: red;
stroke: brown;
}
</style>
<svg width="960" height="150"></svg>
<div>Event fired <span id="test"></span></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var fired=0;
var justcentered = false;
var randomX = d3.randomUniform(0, 10),
randomY = d3.randomNormal(0.5, 0.12),
data = d3.range(800).map(function() {
return [randomX(), randomY()];
});
var svg = d3.select("svg"),
margin = { top: 10, right: 50, bottom: 30, left: 50 },
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleLinear()
.domain([0, 10])
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var brush = d3.brushX()
.extent([[0, 0], [width, height]])
.on("start brush", brushed)
.on("end", brushend);
var dot = g.append("g")
.attr("fill-opacity", 0.2)
.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("transform", function(d) {
return "translate(" + x(d[0]) + "," + y(d[1]) + ")";
})
.attr("r", 3.5);
g.append("g")
.call(brush)
.call(brush.move, [3, 5].map(x))
.selectAll(".overlay")
.each(function(d) { d.type = "selection"; }) // Treat overlay interaction as move.
.on("mousedown touchstart", brushcentered); // Recenter before brushing.
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
function brushcentered() {
var dx = x(1) - x(0), // Use a fixed width when recentering.
cx = d3.mouse(this)[0],
x0 = cx - dx / 2,
x1 = cx + dx / 2;
justcentered = true;
d3.select(this.parentNode)
.call(brush.move, x1 > width ? [width - dx, width] : x0 < 0 ? [0, dx] : [x0, x1]);
}
function brushed() {
var extent = d3.event.selection.map(x.invert, x);
dot.classed("selected", function(d) { return extent[0] <= d[0] && d[0] <= extent[1]; });
}
function brushend() {
if(justcentered) {
justcentered = false;
return;
}
document.getElementById('test').innerHTML = ++fired;
}
</script>

D3 JS chart is cut off after adding labels for X and Y axis

I am making a scatterplot in D3 with the code below and the textlabels of the right circles are being cut off, after putting textlabels on the X and Y axis.
Here is a working jsFiddle:
https://jsfiddle.net/chemok78/1vcat0s8/4/
Adding the X and Y axis labels seems to move the whole chart to the right and up, making it move a bit out of the containing div. Anyone can help me how to fix this?
var url = "https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/cyclist-data.json";
d3.json(url, function(json) {
var data = json;
var margin = {
top: 40,
right: 100,
bottom: 80,
left: 80
};
var w = 1000 - margin.left - margin.right;
var h = 800 - margin.top - margin.bottom;
var svg = d3.select("#chart")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var maxRank = d3.max(data, function(d) {
return d.Place;
});
var minSeconds = d3.min(data, function(d) {
return d.Seconds;
});
var maxSeconds = d3.max(data, function(d) {
return d.Seconds;
});
var formatTime = d3.time.format("%M:%S");
var formatSeconds = formatMinutes = function(d) {
return formatTime(new Date(2016, 0, 0, 0, 1, d));
};
var xScale = d3.scale.linear()
.domain([maxSeconds + 5, minSeconds])
.range([0, w]);
var yScale = d3.scale.linear()
.domain([0, maxRank + 2])
.range([0, h]);
This has nothing to do with "moving the chart". Here is the problem.
When you do this:
var labels = svg.selectAll("text")
You're selecting text elements that already exist in your SVG. Because of that, your "enter" selection will have less elements compared to what it should contain.
The solution is simple: select something that doesn't exist:
var labels = svg.selectAll("foo")
Or, alternatively, move the blocks that append the axes' labels to the bottom of the code.
Here is your updated fiddle: https://jsfiddle.net/mp7LxsL4/

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);
})

Dynamically resizing of d3.js bar chart based on data

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'));
});

Categories