d3 timeseries, reading date from data, counting entries by date - javascript

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/

Related

d3js version 3 to 4 for toggling barcharts

I am trying to get this old version barchart toggle to work with version 4. I'm also looking to reduce its reliance on underscore - is there a vanilla js version to handle the filtering/sorting of the data for the toggling of the legend?
version 3
https://jsfiddle.net/shashank2104/xhgew00y/16/
version 4 - current migration
https://jsfiddle.net/q6vu27w3/2/
The current example isn't rendering though.
I've changed d3.scale.ordinal().rangeRoundBand, but the conversion may not be correct.
v3
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
v4
var x0 = d3.scaleBand()
.domain([0, width])
var x1 = d3.scaleBand();
var y = d3.scaleLinear()
.range([height, 0]);
These are the changes in your code for it to work in version 4.
scaleBand() works for numeric range only.For Strings range(colour), we should use d3.scaleOrdinal() instead of d3.scaleBand().
var colorScale = d3.scaleOrdinal().range(["#f7b363", "#448875", "#c12f39", "#2b2d39", "#f8dd2f", "#8bf41b"]);
Next, in x0 declaration, it is not domain that you're specifying, that is range and also the rangeRoundBand() is converted to rangeRound().padding() in version 4. So x0 declaration becomes
var x0 = d3.scaleBand()
.rangeRound([0, width])
.padding(0.1);
Then barGroups variable should be changed from
var barGroups = chartHolder.selectAll("g.bars")
.data(data);
to
var barGroups = chartHolder.selectAll("g.bars")
.data(data)
.enter().append("g")
.attr("class", "bars")
.attr("transform", function(d) {
return "translate(" + x0(d.label) + ",0)";
});
Then barEnter variable also should be changed from
var barEnter = barGroups.selectAll("rect")
.data(function(d) {
return d.valores.filter(function(k) { return !k.hidden; }) ;
});
to
var barEnter = barGroups.selectAll("rect")
.data(function(d) {
return d.valores.filter(function(k) { return !k.hidden; }) ;
})
.enter().append("rect").attr('height', 0).attr('y', height).attr('x', 0).attr('width', 0).style('fill',function(d, i) {
return colorScale(d.name); //colores_google(i);
});
to apply animation to all appended rect elements.
If these changes are done,Then Bar chart in version 4 will work properly.
_.findWhere() can be replaced by
Array.prototype.getIndexBy = function (name, value) {
for (var i = 0; i < this.length; i++) {
if (this[i][name] == value) {
return i;
}
}
return -1;
}
Then in toggleBar() function you can just do:
data.forEach(function(d) {
var d.filteredValores = d.valores[d.valores.getIndexBy("hidden", state)];
});
Now filteredValores property consists of valores that satisfies the given state.We can then retreive name from that filtered property.Hope this helps.

d3.js Time Scale Axis

I have a scatter plot created using d3.js that I am trying to add an x axis to which will range from 12AM to 12AM i.e. spanning 24 hours. The nature of the graph is that the data will change depending on user input however I keep receiving the same error for however I try to append the axis.
I receive the error:
d3.v4.min.js:2 Error: attribute transform: Expected number,
"translate(NaN,0)".
Here is my code in which I have tried to include only that which is important for my query. (timeLog is my array of data)
var parseTime = d3.utcParse("%H:%M");
var midnight = parseTime("00:00");
var height = $("#scatter").height();
var width = $("#scatter").width();
var max = Math.max(timeLog);
var min = Math.min(timeLog);
var yScale = d3.scale.linear()
.domain([0,d3.max(timeLog)])
.range([0,height]);
var xScale = d3.scaleBand()
.domain(d3.range(0,timeLog.length))
.range([0,width]);
d3.select("#scatter").append('svg')
.attr('width',width)
.attr('height',height)
.style('background', '#f4f4f4')
.selectAll('circle')
.data(timeLog)
.enter().append('circle')
.style('fill', 'black')
.style('stroke','none')
.attr('cx',function(d, i){
return xScale(i);
})
.attr('cy',function(d){
return height - yScale(d);
})
.attr('r',2);
var hScale = d3.scaleUtc()
.domain([midnight,d3.time.day.offset(midnight,1)])
.range([0,width]);
var xAxis = d3.axisBottom()
.scale(hScale)
.tickFormat(d3.time.format.utc("%I %p"))
var xGuide = d3.select('svg')
.append('g')
.attr("class", "axis axis--x")
xAxis(xGuide)
xGuide.attr('transform','translate(0,' + height + ')')
xGuide.selectAll('path')
.style('fill','black')
.style('stroke','black')
xGuide.selectAll('line')
.style('stroke','black')
I am fairly new to d3.js and am attempting to teach myself the basics so any feedback that would help me identify the cause of the error would be greatly appreciated.
Edit
I have made some changes after discovering some of my syntax was outdated and updated the above code, I am now not receiving any error messages however the axis is still not displaying. Is there perhaps some attribute I am missing?
Thanks for any feedback.

How to draw a line in d3 v4 using javascript objects?

I have the following object
var data =[
{"steps":200,calories:200,distance:200,date:new Date(2012,09,1)},
{"steps":200,calories:200,distance:200,date:new Date(2012,09,2)},
{"steps":200,calories:200,distance:200,date:new Date(2012,09,3)},
{"steps":200,calories:200,distance:200,date:new Date(2012,09,4)},
{"steps":200,calories:200,distance:200,date:new Date(2012,09,5)},
]
I'd like to draw a graph between the steps and the date object in d3 v4
I'm doing something like this to draw a line. Here's the full code..
var dataLine = [
{"x":new Date(2012,0,1),"y":10},
{"x":new Date(2012,0,2),"y":9},
{"x":new Date(2012,0,3),"y":3},
{"x":new Date(2012,0,4),"y":2}
];
var parseTime = d3.timeParse("%d-%b-%y");
var svgContainer = d3.select(".dsh-svg-element");
var MARGIN = {left:50,right:20,top:20,bottom:30};
var WIDTH = 960 - (MARGIN.left + MARGIN.right);
var HEIGHT = 500 - (MARGIN.top + MARGIN.bottom);
svgContainer.attr("width",WIDTH+(MARGIN.left + MARGIN.right))
.attr("height",HEIGHT+(MARGIN.top+MARGIN.bottom))
.attr("transform","translate(" + MARGIN.left + "," + MARGIN.top + ")");
var xMax =100;
var yMax =100;
var x = d3.scaleTime().domain([new Date(2012,0,1), new Date(2012,0,31)]).range([0, WIDTH])
var y = d3.scaleLinear().domain([0,yMax]).range([HEIGHT,0]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
svgContainer.append("g").attr("transform", "translate(50," + (HEIGHT+MARGIN.top) + ")").call(xAxis)
svgContainer.append("g").attr("transform", "translate(50,20)").call(yAxis).attr("id","yAxis")
var lineFunction = d3.line().x(function(d){return x(d.y)}).y(function(d){return y(d.x)})
svgContainer.append("path")
.attr("d",lineFunction(dataLine))
.attr("stroke","blue").attr("stroke-width", 2).attr("fill", "none");
I checked the inspector, the x() and y() functions seem to be returning the right pixels to be drawn on the graph.
But the path of the line is "M-455079.8680521219,-5964102899550L-455079.86805246526,-5964491699550L-455079.86805452546,-5964880499550L-455079.8680548688,-5965269299550" in the inspector.. It seems to be drawing the line outside the svg element. Any idea how to fix this?
Any tips or simple code on drawing a line are appreciated.
Fiddle
You have 2 main problems:
First, your x domain is completely unrelated to your data array. Just use d3.extent to get the first and last date:
var x = d3.scaleTime()
.domain(d3.extent(dataLine, function(d) {
return d.x
}))
.range([MARGIN.left, WIDTH])
Second, your line generator is wrong, you're using d.x with the y scale and d.y with the x scale. It should be:
var lineFunction = d3.line()
.x(function(d) {
return x(d.x)
}).y(function(d) {
return y(d.y)
})
Here is your updated fiddle: https://jsfiddle.net/mxsLdntg/
Have in mind that the line in the fiddle is based on dataLine, not data. You have to decide which data array you want to use and set the domains accordingly.

Interchange time and linear scaling depending on user selection

I am building a widget to let users decide what quantities to plot against what quantities (building off this animated scatter plot on bl.ocks. This is working fine for numeric quantities, but I also have date quantities, and I want users to be able to plot these too, in the same way, and against non-date quantities.
The original linear scaling and axes are set up like so as global functions:
var xScale = d3.scale.linear() // xScale is width of graphic
.domain([0, d3.max(dataset, function(d) {
return d[0]; // input domain
})])
.range([padding, canvas_width - padding * 2]); // output range
var yScale = d3.scale.linear() // yScale is height of graphic
.domain([0, d3.max(dataset, function(d) {
return d[1]; // input domain
})])
.range([canvas_height - padding, padding]); // remember y starts on top going down so we flip
// Define X axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5);
// Define Y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5);
My hope was that I could modify these globals inside the click function and even change the nature of the scaling and that this would feed back into the axis variables as well, so I put this inside the click function:
if(types[xName]==3){
console.log("resetting x scale to time type");
xScale = d3.time.scale().range([padding, canvas_width - padding * 2]); // output range
}
else{
// Create scale functions
xScale = d3.scale.linear() // xScale is width of graphic
.domain([0, d3.max(dataset, function(d) {
return d[0]; // input domain
})])
.range([padding, canvas_width - padding * 2]); // output range
}
xScale.domain([0, d3.max(dataset, function(d) {
return d[0]; })]);
if(types[xName] == 1){
xScale.domain([d3.max(dataset, function(d) {
return d[0]; }), 0]);
}
if(types[yName]==3){
console.log("resetting y scale to time type");
yScale = d3.time.scale().range([canvas_height - padding, padding]); // remember y starts on top going down so we flip
}
else {
yScale = d3.scale.linear() // yScale is height of graphic
.domain([0, d3.max(dataset, function(d) {
return d[1]; // input domain
})])
.range([canvas_height - padding, padding]); // remember y starts on top going down so we flip
}
yScale.domain([0, d3.max(dataset, function(d) {
return d[1]; })]);
if(types[yName] == 1){
yScale.domain([d3.max(dataset, function(d) {
return d[1]; }), 0]);
}
I also use a parseDate as appropriate on the data when it's date data. The above (and full code is here with widget here, the problematic date type being stored in Created) puts all the points in some crazy location all in one straight line off the graph when I choose the date type, and worse still produces the following error:
Error: Invalid value for <circle> attribute cx="naN" where I assume this is giving an error from the following code:
svg.selectAll("circle")
.data(dataset) // Update with new data
.transition() // Transition from old to new
...
.attr("cx", function(d) {
return xScale(d[0]); // Circle's X
})
So I assume the xScale is simply not working when it's been converted to a time scale. What am I doing wrong? Thanks for any corrections or troubleshooting advice.
The cx is calculating as NaN because the data you are storing created, as time stamp example:"created":1447686953 and you are writing a parse date function.
var parseDate = d3.time.format("%Y%m%d").parse;
This is incorrect as the date is not in 20151223 format.
So the scale as you suggesting get calculated wrongly.
if(types[xName]== 3){
newNumber1 = parseDate(String(data[i][xName]));//this is wrong
}
var newNumber2 = data[i][yName]/divisor[types[yName]]//Math.floor(Math.random() * maxRange); // New random integer
if(types[yName]== 3){
newNumber2 = parseDate(String(data[i][yName]));//this is wrong
}
So you need to do this for converting into date:
if(types[xName]== 3){
newNumber1 = new Date(data[i][xName]*1000);
}
var newNumber2 = data[i][yName]/divisor[types[yName]]//Math.floor(Math.random() * maxRange); // New random integer
if(types[yName]== 3){
newNumber2 = new Date(data[i][yName]*1000);
}
Hope this helps!

D3: tickformat method to only show year

My dates for my d3 chart are month/day/year, for example: "10/1/2013"
And I'm parsing them this way: d3.time.format("%x").parse;
But how do I write a .tickFormat() method on my axis to only show the year (full year, with century)?
Just change %x to %Y in your parsing snippet.
You can find the full documentation at https://github.com/mbostock/d3/wiki/Time-Formatting
So something like this:
tickFormat: function(d) {
var dx = data[0].values[d];
return dx ? d3.time.format('%Y')(new Date(dx)) : '';
}
Of course your specifics of where to get your data etc will be different.
When defining your x axis you can modify it with tickformat and pass in a function to return the year.
Here is a full example.
var width = 200, height = 200;
var data = ["10/1/2013", "10/1/2014"].map(d3.time.format("%x").parse)
var xDomain = [Math.min.apply(null, data), Math.max.apply(null, data)];
var x = d3.time.scale().range([0, width]).domain(xDomain)
var xAxis = d3.svg.axis()
.scale(x).tickFormat(function(time, index) { return time.getUTCFullYear()); })
var svg = d3.select("body").append("svg")
.attr('width', 200)
.attr('height', 200);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + 180 + ")")
.call(xAxis);
The key line to note would be the place where we specify the tickformat of the xAxis:
var xAxis = d3.svg.axis()
.scale(x).tickFormat(function(time, index) { return time.getUTCFullYear()); })
Note that we specify the domain of var x using the parsed times in xDomain.

Categories

Resources