I am trying to use the margin conventions described in http://bl.ocks.org/mbostock/3019563
when plotting bar charts. However, the bars do not align with the x-axis as you can see
in this basic example: http://bl.ocks.org/kyrre/bbd29f225173825797e3. What am I doing wrong?
var data = [
{x: "Differential Geometry", y: 10},
{x: "Statistical Physics", y: 5},
{x: "Music", y: 3}
]
var margin = {top: 20, right: 10, bottom: 20, left: 50};
var width = 500 - margin.left - margin.right;
var height = 320 - margin.top - margin.bottom;
var y = d3.scale.linear()
.domain([0, d3.max(data, function(x) {
return x.y;
})])
.range([0, height]);
var x = d3.scale.ordinal()
.domain(_.map(data, function(d) { return d.x;}))
.rangeRoundBands([0, width], 0.10);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
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 + ")");
var rect = svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) {
return x(d.x);
})
.attr("y", 0)
.attr("height", function(d) {
return y(d.y);
})
.attr("fill", function(d) { return "blue";})
.attr("width", 20);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(0,0)")
.call(yAxis);
The y coordinates are counted from the top (i.e. 0 is at the top of the image). It should work if you set y to the total minus height.
.attr("y", function(d) { return (height - y(d.y)); })
Related
The following code renders the axes on top of the graph and I can't seem to find where to add/subtract pixels to align the two.
I've spent weekend trying to solve this but I feel stuck. In my desperation, I've tried to add and subtract the padding in various places, add margins here and there to move things. It's like the graph and the axes are on two different scales but I can't see where I'm doing that either. This is a link to my codepen: http://codepen.io/piacoding/pen/amzoog?editors=0010
thank you,
var w = 780;
var h = 500;
var padding = 60;
var svg = d3.select("#graph")
.append("svg:svg")
.attr("width", w)
.attr("height", h )
.attr('class', 'gdp');
// define the x scale (horizontal)
var mindate = new Date(1947, 0, 1),
maxdate = new Date(2015, 6, 1);
var xScale = d3.time.scale()
.domain([mindate, maxdate])
.range([padding, w - padding]);
var maxnumber = d3.max(dataset, function(d) {
return d[1]
});
var yScale = d3.scale.linear()
.domain([0, maxnumber])
.range([0, h]);
var y = d3.scale.linear()
.domain([0, maxnumber])
.range([h - padding, padding]);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - (yScale(d[1]));
})
.attr("width", w / dataset.length)
.attr("height", function(d) {
return yScale(d[1]);
})
// define the y axis
var yAxis = d3.svg.axis()
.orient("left")
.scale(y);
// define the y axis
var xAxis = d3.svg.axis()
.orient("bottom")
.scale(xScale);
// draw y axis
svg.append("g")
.attr("transform", "translate(" + padding + ",0)")
.attr('class', 'y axis')
.call(yAxis);
// draw x axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
Take a look at Margin Convention which does exactly what you need. See http://codepen.io/anon/pen/JRovxV?editors=0010 for the updated version:
var margin = {top: 20, right: 10, bottom: 60, left: 60};
var width = 780 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var svg = d3.select("#graph")
.append("svg:svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr('class', 'gdp');
// define the x scale (horizontal)
var mindate = new Date(1947, 0, 1),
maxdate = new Date(2015, 6, 1);
// var firstDate = dataset[0];
// var lastDate = dataset[dataset.length - 1][0];
var xScale = d3.time.scale()
.domain([mindate, maxdate])
.range([0, width]);
var maxnumber = d3.max(dataset, function(d) {
return d[1]
});
var yScale = d3.scale.linear()
.domain([0, maxnumber])
.range([0, height]);
var y = d3.scale.linear()
.domain([0, maxnumber])
.range([height, 0]);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (width / dataset.length);
})
.attr("y", function(d) {
return height - (yScale(d[1]));
})
.attr("width", width / dataset.length)
.attr("height", function(d) {
return yScale(d[1]);
})
// define the y axis
var yAxis = d3.svg.axis()
.orient("left")
.scale(y);
// define the y axis
var xAxis = d3.svg.axis()
.orient("bottom")
.scale(xScale);
// draw y axis
svg.append("g")
//.attr("transform", "translate(" + padding + ",0)")
.attr('class', 'y axis')
.call(yAxis);
// draw x axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
I just created a linechart but what I would really like is that the line is not going from point to point, but that the line is more squared.
I don't know if this has a specific name, but these pictures should make it all a bit more clear:
This is what I have:
This is what I want:
Is there a way to do this in d3 without having to create a script which adds the 'extra' points?
This is the code I use for the line chart:
var maxDepth = graphObj[graphObj.length-1].maxDepth ;
$('#floodRiskChart').html('');
var margin = {top: 5, right: 5, bottom: 50, left: 65},
width = 410 - margin.left - margin.right,
height = 210 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.linear()
.domain([0, maxDepth ])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function(d) { return x(d.exceedance); })
.y(function(d) { return y(d.depth); });
var svg = d3.select("#chart").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 + ")");
graphObj.forEach(function(d) {
//d.date = parseDate(d.date);
d.exceedance = parseFloat(+d.exceedance);
d.depth= parseFloat(+d.depth);
});
x.domain(d3.extent(graphObj, function(d) { return parseFloat(d.exceedance); }));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("x", "25%")
.attr("dy", "3em")
.html("chance");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -50)
.attr("dx", 0)
.style("text-anchor", "end")
.html("depth");
svg.append("path")
.datum(graphObj)
.attr("class", "line")
.attr("d", line);
you need to add .interpolate('step-after') on the line generator, like so:
var line = d3.svg.line()
.interpolate('step-after')
.x(function(d) { return x(d.exceedance); })
.y(function(d) { return y(d.depth); });
this will give you that result, more info can be found here: enter link description here towards the end of the page
I am using d3 chart with zoom
i don't want negative values in y axis when zoom out. how can i avoid it.
Even i should avoid y axis values don't exceed to the actual value in zooming
For example:
My best y axis value is 5000
When i zoom out i don't want to see 5100, 5500 and 6000 like this
How can i avoid this.
thanks
Subash
Here is my code
var margin = { top: 0, right: 45, bottom: 20, left: 0 },
width = $('#realtimechart').width() - 40,
height = 300;
var x = d3.time.scale.utc()
.domain(d3.extent(data, function (d) {
return d.date;
}))
.range([0, width]);
var y = d3.scale.linear()
.domain([0, d3.max(data, function (d) {
return d.close;
})])
.range([height, 0]);
var line = d3.svg.line()
.x(function (d) {
return x(d.date);
})
.y(function (d) {
return y(d.close);
});
var xAxis = d3.svg.axis().scale(x).ticks(10).orient("bottom");
var yAxis = d3.svg.axis().scale(y).ticks(8).orient("right");
console.log(y)
var zoom = d3.behavior.zoom().x(x).y(y).translate([0,0]).scale(1).scaleExtent([0, 1000]).on("zoom", refresh);
var svg = d3.select("#realtimechart").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 + ")")
.call(zoom)
.append("g");
var make_x_axis = function () {
return d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5);
};
var make_y_axis = function () {
return d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5);
};
svg.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + ", 0)")
.call(yAxis);
svg.append("g")
.attr("class", "x grid")
.attr("transform", "translate(0," + height + ")")
.call(make_x_axis()
.tickSize(-height, 0, 0)
.tickFormat(""));
svg.append("g")
.attr("class", "y grid")
.call(make_y_axis()
.tickSize(-width, 0, 0)
.tickFormat(0));
var clip = svg.append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height);
var chartBody = svg.append("g")
.attr("clip-path", "url(#clip)");
chartBody.append("svg:path")
.datum(data)
.attr("class", "line")
.attr("d", line);
function refresh() {
//$('#realtimechart').yAxis.scale().domain();
console.log(d3.svg.axis().scale().domain());
//console.log(i);
if(i < 1000) {
i++;
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
svg.select(".x.grid")
.call(make_x_axis()
.tickSize(-height, 0, 0)
.tickFormat(""));
svg.select(".y.grid")
.call(make_y_axis()
.tickSize(-width, 0, 0)
.tickFormat(""));
svg.select(".line")
.attr("class", "line")
.attr("d", line);
}
}
I try to make a bar chart on the basis of 2D data array (I din`t want to use 2D array initially, so there is a function "mergingAr", which merges them) using d3.js. Here is the code:
.bar {
fill: steelblue;
}
.bar:hover {
fill: brown;
}
var arr1 = [399200,100000, 352108, 600150, 39000, 17005, 4278];
var arr2 = [839, 149, 146, 200, 200, 121, 63];
function mergingAr (array1, array2)
{
var i, out = [];//literal new array
for(i=0;i<array1.length;i++)
{
out.push([array1[i],array2[i]]);
}
return out;
}
var data = mergingAr(arr1, arr2);
margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([0, d3.max(data, function(d) {
return d[0]; })])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d[1]; })])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
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 + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d[0]); })
//.attr("width", x.rangeBand())
.attr("width", width/a1.length)
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return height - y(d[1]); });
Te problem is - the bars cover each other, there are no distance between them, even if I used rangeRoundBands.
There are 2 issues in your code.
The first one is that the data array is not sorted. In order to sort it you can do:
out = out.sort(function(a,b) { return d3.ascending(a[0],b[0]) })
before returning out in your mergeAt function. Sorting the array makes sure that you process bars in the right order.
The second issue is that your intervals are not equal. To remediate to this, I made the width of a block equal to the distance to the next one (but you might want to do something different):
.attr("width", function(d,i){
if(i!=(data.length-1)) {
return x(data[i+1][0])-x(data[i][0])
} else {
return 10; // the last block is of width 10. a cleaner way is to add a
// marker at the end of the array to know where to finish
// the axis
}
})
JsFiddle: http://jsfiddle.net/chrisJamesC/6WJPA/
Edit
In order to have the same interval between each bar and the same width, you have to change the scale to an ordinal one:
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1)
.domain(data.map(function(d){return d[0]}))
Then, you need to change the way you compute the width to:
.attr("width", x.rangeBand())
JsFiddle: http://jsfiddle.net/chrisJamesC/6WJPA/2/
Given this simple chart I created:
var data = [["2013-01-24 06:38:02.235191", 52], ["2013-01-23 06:38:02.235310", 54], ["2013-01-22 06:38:02.235330", 45], ["2013-01-21 06:38:02.235346", 53]],
maxValue = d3.max(data, function (d) { return d[1]; }),
margin = {top: 20, right: 20, bottom: 50, left: 50},
width = 500 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom,
svg, x, y, xAxis, yAxis, line;
$.each(data, function(index, val) {
val[0] = new Date(val[0]);
});
x = d3.time.scale()
.range([0, width])
y = d3.scale.linear()
.domain([0, maxValue])
.range([height, 0]);
xAxis = d3.svg.axis()
.scale(x)
.tickSize(4, 2, 0)
.ticks(d3.time.days, 1)
.tickFormat(d3.time.format("%m/%d"))
.orient("bottom");
yAxis = d3.svg.axis()
.scale(y)
// .ticks(5)
// .tickValues([0, maxValue * 0.25, maxValue * 0.5, maxValue * 0.75, maxValue])
.tickSize(4, 2, 0)
.orient("left");
line = d3.svg.line()
.x(function(d) { return x(d[0]); })
.y(function(d) { return y(d[1]); });
svg = d3.select("#chart-holder").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 + ")");
x.domain(d3.extent(data, function(d) { return d[0]; }));
y.domain(d3.extent(data, function(d) { return d[1]; }));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("transform", function(d) {
return "rotate(-60)translate(" + -this.getBBox().height * 1.7 + "," +
-this.getBBox().width/4 + ")";
});
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
svg
.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("fill", "#0b8da0")
.attr("r", 3.5)
.attr("cx", function(d) { return x(d[0]); })
.attr("cy", function(d) { return y(d[1]); });
How can I add a space between axis and line, so it won't touch the axis.
Also is there a way to force yAxis ticks to always start from 0, no matter what is the smallest value in the data set?
Working example can be found here: http://jsfiddle.net/n7Vmr/
You can just change the domain of the scale used to draw the y-axis, look for the line
y.domain(d3.extent(data, function(d) { return d[1]; }));
needs to be changed, if you want it one less than the smallest value in your dataset use
y.domain([d3.min(data, function (d) { return d[1]; })-1, maxValue]);
or if you want it to start from 0, regardless of the data
y.domain([0, maxValue]);