Related
I'm having trouble on this.. I'm working on a line chart using d3.js. I'm having trouble to display the X axis tick text that uses date values ONLY in the data spreadsheet. When I develop the line chart, I saw it automatic generated the date values between the values from the Data spreadsheet. Here is a quick example of the data.
date,close
16-Dec-12,53.98
16-Dec-12,67.00
16-Dec-12,89.70
16-Dec-12,99.00
16-Dec-12,130.28
23-Dec-12,166.70
23-Dec-12,234.98
23-Dec-12,345.44
23-Dec-12,443.34
23-Dec-12,543.70
23-Dec-12,580.13
30-Dec-12,605.23
30-Dec-12,622.77
30-Dec-12,626.20
30-Dec-12,628.44
30-Dec-12,636.23
30-Dec-12,633.68
So in this dataset, it has 3 different date values.
In the D3 line chart, I want to display those only the 3 different date values which are Weeks in the x axis tick text. However, the chart is generated other dates in between those data date values. Example below.
I'm trying to display like this that only display the date values from the Dataset.
I hope this makes sense. Is this possible? I tried to use .tick() but it only display '16-Dec-12' which it confuses me. I'm pretty new into line charts for d3.js =/
here is my snippet code. I hope this helps.
function getExtent(member) {
var extents = [];
dataset.forEach(function(arry){
extents = extents.concat(d3.extent(arry,function(d){return d[member]}));
});
return d3.extent(extents);
}
var xScale = d3.time.scale().domain(getExtent('x')).range([0,width]);
var yScale = d3.scale.linear().domain(getExtent('y')).range([height,0]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom');
xAxis.scale(xScale)
.tickFormat(d3.time.format('%b %d'));
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left');
var lineFunc = d3.svg.line()
.x(function(d){return xScale(d.x)})
.y(function(d){return yScale(d.y)})
.interpolate('linear');
var g = svg.append('g')
.attr('width',width)
.attr('height',height)
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Use this group for drawing the lines
g.append('g')
.attr('class', 'line-group');
// Axes
g.append('g')
.attr('class', 'usps-multiline axis axis--x')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis)
.selectAll("text")
.attr("transform", "translate(-40,20) rotate(315)");
g.append('g')
.attr('class', 'usps-multiline axis axis--y')
.call(yAxis);
**Please let me know if you can view the sample pic.
This is the expected behaviour for a time scale. In D3, the axis is automatically generated, you don't have much control on the ticks.
The easiest alternative seems to be passing an array of the dates you have in your data to tickValues:
var axis = d3.axisBottom(scale)
.tickValues(uniqueValues);
Here, uniqueValues is an array with the dates you have in your CSV, filtered to only unique dates (otherwise you'll have several ticks in the same position).
here is the demo with the CSV you shared:
var svg = d3.select("svg");
var csv = `date,close
16-Dec-12,53.98
16-Dec-12,67.00
16-Dec-12,89.70
16-Dec-12,99.00
16-Dec-12,130.28
23-Dec-12,166.70
23-Dec-12,234.98
23-Dec-12,345.44
23-Dec-12,443.34
23-Dec-12,543.70
23-Dec-12,580.13
30-Dec-12,605.23
30-Dec-12,622.77
30-Dec-12,626.20
30-Dec-12,628.44
30-Dec-12,636.23
30-Dec-12,633.68`;
var data = d3.csvParse(csv, function(d) {
d.date = d3.timeParse("%d-%b-%y")(d.date);
return d
});
var uniqueValues = [...new Set(data.map(function(d) {
return d.date.getTime()
}))].map(function(d) {
return new Date(d);
});
var scale = d3.scaleTime()
.range([30, 570])
.domain(d3.extent(data, function(d) {
return d.date
}));
var axis = d3.axisBottom(scale)
.tickValues(uniqueValues);
var gX = svg.append("g")
.attr("transform", "translate(0,50)")
.call(axis);
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="600" height="100"></svg>
PS: I'm using D3 v4 in the demo, but the principle is the same.
If .ticks(3) doesn't work, you can pass a custom function into .ticks to ensure you get the ticks you want.
Here is a fairly comprehensive axis tutorial.
For some reason my bars (rects) are drawing really wide- I think its because the dates are not parsing correctly. here is the code
var fakeData= [
{"date":2013-10,"shortFig":10},
{"date":2013-11,"shortFig":-15},
{"date":2013-12,"shortFig":15},
{"date":2014-01,"shortFig":39},
{"date":2014-02,"shortFig":-38},
{"date":2014-03,"shortFig":33},
{"date":2014-04,"shortFig":-35},
{"date":2014-05,"shortFig":-2},
{"date":2014-06,"shortFig":-39},
{"date":2014-07,"shortFig":-46},
{"date":2014-08,"shortFig":23},
{"date":2014-09,"shortFig":45}
]
..this data becomes "thedata" in my chart building function where I try to parse the data and build the x scales and x axis:
// parse dates
var parseDate = d3.time.format("%Y–%m").parse;
thedata.forEach(function(d) {
var date = d.date.toString();
d.date = parseDate(date);
});
//The X scale
var xScale=d3.scale.ordinal()
.rangeRoundBands([0, width], .1)
.domain(thedata.map(function(d) { return d.date; }));
//With the X scale, set up the X axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.tickValues([thedata[0].date]);
//Call the X axis
baseGroup.append("g")
.attr("class", "xaxis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
baseGroup.selectAll("rect")
.data(thedata)
.enter()
.append("rect")
.attr("class", function(d){ if(d.shortFig >= 0){return "green3"}else{return "red3"} })
.attr({
"x": function(d) {return xScale(d.date);},
"y": function(d) {return yScale(Math.max(0, d.shortFig));}, //Return the number with the highest value
"height": function(d) {return Math.abs(yScale(d.shortFig) - yScale(0));}, //Return the absolute value of a number, so negative numbers will be positive
"width": xScale.rangeBand()
});
Just a typo, the date parameters should be strings. Tested this, it works, just replace the fakeData array and you should be set.
var fakeData= [
{"date":"2013-10","shortFig":10},
{"date":"2013-11","shortFig":-15},
{"date":"2013-12","shortFig":15},
{"date":"2014-01","shortFig":39},
{"date":"2014-02","shortFig":-38},
{"date":"2014-03","shortFig":33},
{"date":"2014-04","shortFig":-35},
{"date":"2014-05","shortFig":-2},
{"date":"2014-06","shortFig":-39},
{"date":"2014-07","shortFig":-46},
{"date":"2014-08","shortFig":23},
{"date":"2014-09","shortFig":45}
];
var parseDate = d3.time.format("%Y-%m").parse;
fakeData.forEach(function(d){
console.log(parseDate(d.date));
});
I'm trying to to build a time-series line in d3, using date for the x axis and the number of entries per date as the y axis. I'm having trouble moving the date part of the data object through a date formatter, then a scale, then into my line.
See it in Codepen http://codepen.io/equivalentideas/pen/HaoIs/
Thanks in advance for your help!
var data = [{"title":"1","date":"20140509"},{"title":"2)","date":"20140401"},{"title":"3","date":"20140415"},{"title":"4","date":"20140416"},{"title":"5","date":"20140416"},{"title":"6","date":"20140422"},{"title":"7","date":"20140422"},{"title":"8","date":"20140423"},{"title":"9","date":"20140423"},{"title":"10","date":"20140423"},{"title":"11","date":"20140502"},{"title":"12","date":"20140502"}
var width = "100%",
height = "8em";
var parseDate = d3.time.format("%Y%m%d").parse;
// X Scale
var x = d3.time.scale()
.range([0, width]);
// Y Scale
var y = d3.scale.linear()
.range([height, 0]);
// define the line
var line = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(+d);
})
data.forEach(function(d) {
d.date = parseDate(d.date);
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain(d3.extent(data, function(d) { return d; }));
// build the svg canvas
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
// build the line
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
Currently I get a js console error
Error: Invalid value for <path> attribute d="MNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaN"
You have not used parseDate. You are missing this :
data.forEach(function(d) {
d.date = parseDate(d.date);
});
Have a look at this example.
Some obvious visible problems:
1) You are not appending your svg to any part of the body or div. You should have a line look like this:
d3.select("body").append("svg").attr("width", width).attr("height", height);
2) I doubt d3 can understand your definition for width and
height. The width and height is the definition of chart size
3) I think there has no need for the dateParse as d3 will internally do it for you.
Finally, check the example provided by Niranjan.
There's a few other issues going on here. First, the width/height are not numbers, so the yScale and xScale ranges are invalid (that's why you get the "NaN" in the line path).
This is bad:
var width = "100%",
height = "8em";
Because these will not have valid, numerical ranges as required by the following scale definitions:
// X Scale
var x = d3.time.scale().range([0, width]);
// Y Scale
var y = d3.scale.linear().range([height, 0]);
...what does "8em" to 0 mean in a numerical svg path coordinate? So, make them numbers instead:
var width = 500,
height = 100;
After you fix that, you'll still have errors because your mapping for the y values isn't going to work. You want a histogram of the counts for the different dates. You should generate the data that way and feed it into the line generator.
var generateData = function(data){
var newData = [];
var dateMap = {};
data.forEach(function(element){
var newElement;
if(dateMap[element.date]){
newElement = dateMap[element.date];
} else {
newElement = { date: parseDate(element.date), count: 0 };
dateMap[element.date] = newElement;
newData.push(newElement);
}
newElement.count += 1;
});
newData.sort(function(a,b){
return a.date.getTime() - b.date.getTime();
});
return newData;
};
Once you fix those two things it should work. Here's a jsFiddle: http://jsfiddle.net/reblace/j3LzY/
I'm new to d3 and I'm trying to do some data visualization with it. I found some examples about how to create a time scale in d3, but when I followed the examples and try to create the time scale, it failed. I was frustrated because I couldn't figure out where it went wrong... the example is like this:
how to format time on xAxis use d3.js
So my code is like this:
boxplotdata =[]
boxplotdata.push(
{"datetime":"2013-10-30 01:47",length: 500, start:100,deep1_a:130,deep1:50,deep2_a:200,deep2:60,deep3_a:280,deep3:50,deep4_a:350,deep4:60},
{"datetime":"2013-10-31 01:45",length: 600, start:200,deep1_a:230,deep1:60,deep2_a:300,deep2:60,deep3_a:380,deep3:50,deep4_a:450,deep4:60},
{"datetime":"2013-11-01 02:11",length: 550,start:150,deep1_a:180,deep1:50,deep2_a:250,deep2:60,deep3_a:350,deep3:50,deep4_a:410,deep4:60},
{"datetime":"2013-11-02 01:59",length: 500,start:160,deep1_a:190,deep1:80,deep2_a:300,deep2:60,deep3_a:370,deep3:50,deep4_a:430,deep4:60},
);
//SET MARGIN FOR THE CANVAS
var margin = {top: 30, right: 10, bottom: 10, left: 10},
width = 960 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y-%m-%d %H:%M").parse;
//SET X AND Y
var x = d3.time.scale()
.domain([0,11])
.range([50, width]);
var y = d3.time.scale()
.domain([new Date(boxplotdata[0].datetime),d3.time.day.offset(new Date(boxplotdata[boxplotdata.length-1].datetime),1)])
.rangeRound([20, height-margin.top- margin.bottom]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("top")
.tickFormat(d3.time.format("%H:%M"))
//.ticks(d3.time.minutes,15)
//.tickPadding(8);
var yAxis = d3.svg.axis()
.scale(y)
.orient('right')
.ticks(d3.time.days,1)
.tickFormat(d3.time.format('%m-%d'))
.tickSize(0)
.tickPadding(8);
var line = d3.svg.line()
.x(function(d) { return x(d.datetime); });
var w=960,h=1000;
d3.select("#chart").selectAll("svg").remove(); //Old canvas must be removed before creating a new canvas.
var svg=d3.select("#chart").append("svg")
//.attr("width",w).attr("height",h);
.attr("width",w+margin.right+margin.left).attr("height",h+margin.top+margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
boxplotdata.forEach(function(d) {
d.datetime = parseDate(d.datetime);
});
x.domain(d3.extent(boxplotdata, function(d) { return d.datetime; }));
bars = svg.selectAll("g")
.data(boxplotdata)
.enter()
.append("g");
some drawing codes here..., and at last:
svg.append("g")
.attr("class", "x axis")
//.attr('transform', 'translate(0, ' + (height - margin.top - margin.bottom) + ')')
.call(xAxis);
svg.append("g")
.attr('class', 'y axis')
.call(yAxis);
However, when I tried, I could only get a graph with all time on the xAxis shown as "00:00". What's going wrong here? Hope someone can help me out. Thanks!
Your example data is from different days, so what's happening here is that D3 is picking representative values (i.e. the boundaries between days) and making ticks for that. As your date format only shows hour and minute, 00:00 is all you get.
You have basically two options here. You could either change the date format to show days (which is what D3 intends), or you could set the tick values explicitly to whatever you want. For the first, you could use e.g. d3.time.format("%a"). For the second, see the documentation.
Hi You can use the tickFormat function on the axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%H"));
i am using d3.js charts to plot y axis and x axis. It's working fine, but the values in y axis you can say range is say 0 to 10000 and I want if the number is greater than thousand it will come with K.
if the number is 1000 it will show 1K, for 15000 it will show on y axis ticks 15K.
How to do that? I am not able to manupulate y.domain and range functions for the string values.
var y = d3.scale.linear().range([ height, 0 ]);
y.domain([
0,
d3.max(cities, function(c) { return d3.max(c.values, function(v) { return v.count; }); })
]);
From API Reference, we see d3.formatPrefix
var prefix = d3.formatPrefix(1.21e9);
console.log(prefix.symbol); // "G"
console.log(prefix.scale(1.21e9)); // 1.21
We can use it this way
var yAxis = d3.svg.axis()
.scale(y)
.ticks(5)
.tickFormat(function (d) {
var prefix = d3.formatPrefix(d);
return prefix.scale(d) + prefix.symbol;
})
.orient("left");
However, in case this is exactly what you mean, we can simplify using d3.format("s")
var yAxis = d3.svg.axis()
.scale(y)
.ticks(5)
.tickFormat(d3.format("s"))
.orient("left");
You're looking for tickFormat on the axis object.
var svgContainer = d3.select("body").append("svg")
.attr("width", 800)
.attr("height", 100);
//Create the Scale we will use for the Axis
var axisScale = d3.scale.linear()
.domain([0, 2000])
.range([0, 600]);
//Create the Axis
var xAxis = d3.svg.axis()
.scale(axisScale)
.tickFormat(function (d) {
if ((d / 1000) >= 1) {
d = d / 1000 + "K";
}
return d;
});
var xAxisGroup = svgContainer.append("g")
.call(xAxis);
Check it here: http://jsfiddle.net/3CmV6/2/
This will give you what you want, but I recommend checking the suggestion from robermorales to use d3.format('s') .
This is what i implemented
var yAxis = d3.svg.axis().scale(y).ticks(5).
tickFormat(function (d) {
var array = ['','k','M','G','T','P'];
var i=0;
while (d > 1000)
{
i++;
d = d/1000;
}
d = d+' '+array[i];
return d;}).
orient("left");
it will work for even more than thousand ...
A concise approach:
var myTickFormat = function (d) {//Logic to reduce big numbers
var limits = [1000000000000000, 1000000000000, 1000000000, 1000000, 1000];
var shorteners = ['Q','T','B','M','K'];
for(var i in limits) {
if(d > limits[i]) {
return (d/limits[i]).toFixed() + shorteners[i];
}
}
return d;
};
spenderRowChart
.xAxis().ticks(3)
.tickFormat(myTickFormat);
This returns 1K for 1000, 1M for 1,000,000, and so on. Note: Beyond quadrillion, you may wanna go through Number.MAX_SAFE_INTEGER.
Try this
axis: {
y: {
tick: {
format: d3.format("$.2s")
}
}
}
If you wish to edit the labels on the ticks you can use the tickFormat function.
For example, your y axis function would look something like this:
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(function(tickVal) {
return tickVal >= 1000 ? tickVal/1000 + "K" : tickVal;
});
Then you just have to call it. Assuming you have a variable called svg that references your svg chart:
svg.append("g)
.call(yAxis);
To make things a little clearer I've created this jsfiddle based on this tutorial, adapted to display axis tick labels in the way you describe above if the value is greater than or equal to 1000. If you run it a few times you will see how different axis values are handled.