I have created a linechart with D3.js, but can't seem to figure out how to control the X-axis completely.
You can see my example her: http://codepen.io/DesignMonkey/pen/KdOZNQ
I have sat the ticks count for the x-axis to 3 days, and I have an Array with a range of 31 days, that should show a day in each end of the x-axis and skip to every third day. But for some reason when the a-axis pass the 1st in the month, it shows both 2015-08-31 and 2015-09-01 and doesn't skip to 2015-09-03 as it is supposed to.
My code for the linechart is here:
// Set the dimensions of the canvas / graph
let margin = {top: 30, right: 20, bottom: 30, left: 40},
width = 330 - margin.left - margin.right,
height = 180 - margin.top - margin.bottom;
// Set the ranges
var x = d3.time.scale().range([0, width]).nice(10);
var y = d3.scale.linear().rangeRound([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom")
.ticks(d3.time.days, 3)
.tickFormat(d3.time.format('%e'))
.innerTickSize(-height)
.outerTickSize(0)
.tickPadding(10)
var yAxis = d3.svg.axis().scale(y)
.orient("left")
.ticks(5)
.innerTickSize(-width)
.outerTickSize(0)
.tickPadding(10)
// Define the line
var valueline = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.value); });
// Adds the svg canvas
let svg = d3.select(template.find(".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 + ")");
// For each line
data.forEach((item) => {
// Scale the range of the data
x.domain(d3.extent(item.data, function(d) {
if(d.value != undefined)
return d.date;
}));
// Create a perfect looking domainrange to the nearest 10th
y.domain([
Math.floor(d3.min(item.data, function(d) {
if(d.value != undefined)
return d.value;
})/10)*10
,
Math.ceil(d3.max(item.data, function(d) {
if(d.value != undefined)
return d.value;
})/10)*10
]);
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// Add only points that have value
svg.append("path")
.attr("class", "line color-"+ item.colorClass)
.attr("d", valueline(item.data.filter((pnt) => pnt.value != undefined)));
});
Can somebody tell me what I'm doing wrong here? :)
/Peter
UPDATE:
I found out it's like, that it want to show the new month. See this pen: http://codepen.io/DesignMonkey/pen/jbgYZx
It doesn't say "Septemper 1" but only "Septemper" like it wants to label the change of month. How do I disable this? :)
Solution:
var count = 0;
var tickRange = data[0].data.map((item) => {
count = (count == 3) ? 0 : count+1;
if(count == 1) {
return item.date;
}
})
.filter((d) => d != undefined);
and then:
var xAxis = d3.svg.axis().scale(x)
.orient("bottom")
.ticks(d3.time.days, 3)
.tickValues(tickRange)
.tickFormat(d3.time.format('%e'))
.innerTickSize(-height)
.outerTickSize(0)
.tickPadding(10)
Thanks :)
Related
Adapting a histogram with D3 (v3) I find two problems to solve (original code here):
<script type="text/javascript">
var faithfulData = [20,21,26,18,24,24,25,25,21,20,20,18,28,23,17,26,27,27,20,28,23,26,];
var datos_unicos = Array.from(new Set(faithfulData))
var margin = {top: 4, right: 10, bottom: 40, left: 40},
width = 360 - margin.left - margin.right,
height = 180 - margin.top - margin.bottom;
var cant_ticks = datos_unicos.length;
var edad_min = Math.min.apply(Math, datos_unicos) - 3;
var edad_max = Math.max.apply(Math, datos_unicos) + 3;
var vartickValues = []
var tope = (edad_max)+1;
for (var i =edad_min; i< tope; i++) {
vartickValues.push(i);
}
var x = d3.scale.linear()
.domain([edad_min, edad_max])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, .1])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.tickValues(vartickValues)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format("%"));
var line = d3.svg.line()
.x(function(d) { return x(d[0]); })
.y(function(d) { return y(d[1]); });
var histogram = d3.layout.histogram()
.frequency(false)
.bins(cant_ticks);
var svg = d3.select("#plantel_distribucion").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 plantel_axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", 34)
.style("text-anchor", "end")
.text("Edad de las jugadoras");
svg.append("g")
.attr("class", "y plantel_axis")
.call(yAxis);
var data = histogram(faithfulData),
kde = kernelDensityEstimator(epanechnikovKernel(7), x.ticks(100));
svg.selectAll(".plantel_bar")
.data(data)
.enter().insert("rect", ".axis")
.attr("class", "plantel_bar")
.attr("x", function(d) { return x(d.x) + 1; })
.attr("y", function(d) { return y(d.y); })
.attr("width", x(data[0].dx + data[0].x) - x(data[0].x) - 1)
.attr("height", function(d) { return height - y(d.y); });
svg.append("path")
.datum(kde(faithfulData))
.attr("class", "plantel_line")
.attr("d", line);
//});
function kernelDensityEstimator(kernel, x) {
return function(sample) {
return x.map(function(x) {
return [x, d3.mean(sample, function(v) { return kernel(x - v); })];
});
};
}
function epanechnikovKernel(scale) {
return function(u) {
return Math.abs(u /= scale) <= 1 ? .75 * (1 - u * u) / scale : 0;
};
}
</script>
1) How to place the labels on the x axis in the center of the bin? In other words, the tick mark and its label on the center of the bar.
2) How do I place the quantity (frequency) of each bin above its bar?
I appreciate your comments and leave an image with the current development:
Thanks
For question1, if you just want to add some ticks at the center of each bin, there are x and dx attributes in histogram that indicate the position and step of each bin. You can compute the x tick by xtick = x + dx / 2.
For question2, I think you can draw a line chart above the histogram, and set the z-index to be 2.
I hope the above helps. :)
Here is my code:
// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%d-%m-%y").parse;
// Set the ranges
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
// Define the line
var valueline = d3.svg.line()
.x(function(d) { return x(+d.AtTime); })
.y(function(d) { return y(d.Temperature); });
// Adds the svg canvas
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 + ")");
// Get the data
d3.csv("../datasets/test.csv", function(error, data) {
data.forEach(function(d) {
d.AtTime = parseDate(d.AtTime);
d.Temperature = +d.Temperature;
});
console.log(data);
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.AtTime; }));
y.domain([0, d3.max(data, function(d) { return d.Temperature; })]);
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
});
And the line chat just can not show...Here is my simple csv dataset:
Category,Temperature,Pressure,AtTime
A,76,400,1-1-2014
B,23,239,2-3-2013
C,38.6,378,2-6-2016
D,43.2,289,3-5-2015
E,49,501,3-5-2015
I used console.log(data) to check, and I found that AtTime has null value.... I do not why..Anyone could help me with this? Thanks!
It's one character in your time format string.
//var parseDate = d3.time.format("%d-%m-%y").parse; // lower-case Y expects two digits only
var parseDate = d3.time.format("%d-%m-%Y").parse; // use capital Y for 4 digit years
see https://github.com/d3/d3/wiki/Time-Formatting#format for all the different modifiers
There is a mistake in your dates, I parsed them and ordered them so the line would render correctly.
data.forEach(function(d) {
var dateArr = d.AtTime.split('-');
d.AtTime = new Date(+dateArr[2], +dateArr[1], +dateArr[0]);
d.Temperature = +d.Temperature;
})
data.sort(function(a, b) {
return new Date(b.AtTime) - new Date(a.AtTime);
});
Remove the implicit number cast in your valueLine function:
var valueline = d3.svg.line()
.x(function(d) {
return x(d.AtTime);
})
.y(function(d) {
return y(d.Temperature);
});
And finally if you don't have any css add a stroke to your line so it renders:
svg.append("path")
.attr("class", "line")
.attr('stroke', 'red')
.attr("d", valueline(data));
Working plnkr: https://plnkr.co/edit/ujFDtHkdtC2EeBwJ1foU?p=preview
I have been developing an area chart for year(x axis) vs Revenue (y axis) in D3 Js.The data is as:
localData=[
{"Revenue":"4.5","Year":"2011"},
{"Revenue":"5.5","Year":"2010"},
{"Revenue":"7.0","Year":"2012"},
{"Revenue":"6.5","Year":"2013"}
]
I want year at x axis and revenue at y axis for an area chart.Currently I am using time scale for x axis but i dont know how to use it as I have not date format I have only Years to represent.
My Current code is:
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = 500 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y").parse;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var area = d3.svg.area()
.x(function (d) { return x(d.Year); })
.y0(height)
.y1(function (d) { return y(d.Revenue); });
$("#chartArea").html("");
var svg = d3.select("#chartArea").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(localData, function (d) { return d.Year; }));
y.domain([0, d3.max(localData, function (d) { return d.Revenue; })]);
svg.append("path")
.datum(localData)
.attr("class", "area")
.attr("d", area)
.attr("fill",color);
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")
.text("Revenue (M)");
Currently I am getting on my X axis as .011,.012,013,.014 I need as 2011,2012,2013,2014
I am new to D3 js so dnt know much about how to use scales??Please Help anyone..Thanks in advance.
Just add tick Format to your x Axis definition:
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%Y")); // <-- format
In v4, it'd be something like this:
d3.axisBottom(x).ticks(d3.timeYear.every(1))
huan feng is right. The time scale is treating your year(implicitly converted to an int) as timestamps. To force the scale to operate on the year, create Date objects specifying the year in it. Basically change the following line:
x.domain(d3.extent(localData, function (d) { return d.Year; }));
to
x.domain(d3.extent(localData, function (d) { return new Date(parseInt(d.Year),0); }));
You may then use Klaujesi's solution to change the tickFormat.
So I'm dynamically creating a bunch of simple line charts with D3 and everything is going well, but for some reason charts with ranges that go from 9-10 get inverted and look absolutely terrible/do not function properly.
the first one, the values are upside down
this one is fine...
Here is some code...
var dataRange = d3.extent(quoteObjects, function(d){ return d.close });
var dateRange = d3.extent(quoteObjects, function(d){ return d.date });
// Set chart variables
var vis = d3.select("#"+type),
WIDTH = $('#chart-box').width(),
HEIGHT = $('#'+type).innerHeight(),
MARGINS = {
top: 20,
right: 20,
bottom: 20,
left: 60,
},
// set scales
xScale = d3.time.scale().range([MARGINS.left, WIDTH - MARGINS.right]).domain(dateRange),
yScale = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain(dataRange),
// create displayed axis
xAxis = d3.svg.axis()
.scale(xScale)
.tickValues( xScale.ticks(6) )
yAxis = d3.svg.axis()
.scale(yScale)
.tickValues( yScale.ticks(6) )
.orient("left");
console.log("WH", WIDTH, HEIGHT);
if (type == "intraday"){
xAxis.tickFormat(d3.time.format("%H"))
}
// append x axis
vis.append("svg:g")
.attr("class", "axis")
.attr("transform", "translate(0," + (HEIGHT - MARGINS.top) + ")")
.call(xAxis);
// append y axis
vis.append("svg:g")
.attr("class", "axis")
.attr("transform", "translate(" + (MARGINS.left) + ",0)")
.call(yAxis);
// create line based on "close" values
var lineGen2 = d3.svg.line()
.x(function(d) {
return xScale(d.date);
})
.y(function(d) {
return yScale(d.close);
})
.interpolate("basis");
// append "close" line
vis.append('svg:path')
.attr('d', lineGen2(quoteObjects))
.attr('stroke', '#931a28')
.attr('stroke-width', 3)
.attr('fill', '#222');
I am trying to essentially rotate this horizontal bar chart into a vertical bar chart, but can't figure out how to do so. I can create a normal column chart, but once I try to put in the negative values and compute the y and height, all hell breaks loose. Here's my fiddle. (At least I was able to create the y-axis (I think).)
What am I doing wrong here?
var data = [{"letter":"A",'frequency':10},{"letter":"B","frequency":-5},{"letter":"C","frequency":7}];
var margin = {top: 20, right: 20, bottom: 30, left: 40}, width = 750 - margin.left - margin.right, height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal().rangeRoundBands([0, width], .1);
var y = d3.scale.linear().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 + ")");
var x0 = Math.max(-d3.min(data), d3.max(data));
x.domain(data.map(function(d) { return d.letter; }));
y.domain([d3.min(data, function(d) { return d.frequency; }), d3.max(data, function(d) { return d.frequency; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.selectAll(".bar")
.data(data).enter().append("rect")
.attr("class", "bar")
.attr("x", function(d, i) { return x(d.letter); })
.attr("y", function(d, i) { return x(Math.min(0, d.frequency));})
.attr("width", x.rangeBand())
.attr("height", function(d) { return Math.abs(x(d.frequency) - x(0)); });
Looks like there are two problems here:
The typos: .attr("y", function(d, i) { return x(...);}) should now be .attr("y", function(d, i) { return y(...);}). Same is true for the scales in your height attribute.
The change from a 0 base on the X axis to a 0 base on the Y axis. With a zero-based bar on the X axis, the x attribute of the bar is x(0). With a 0 based bar on the Y axis, the y attribute of the bar is not y(0), but y(value) (because the "base" of the bar is no longer the leading edge of the rectangle) - so in this code you need to use Math.max(0, value) (which will give y(value) for positive values) instead of Math.min(0, value):
svg.selectAll(".bar")
// ...snip...
.attr("y", function(d, i) { return y(Math.max(0, d.frequency));})
.attr("height", function(d) { return Math.abs(y(d.frequency) - y(0)); });
See updated fiddle: http://jsfiddle.net/pYZn8/5/