Interchange time and linear scaling depending on user selection - javascript

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!

Related

Prevent clipping using domain in d3.js

I'm trying to prevent clipping at the top most part of my chart by increasing the domain on the yAxis like so:
mainHeight = 640;
yScale = d3.scale.linear()
.range([mainHeight, 0])
.domain(d3.extent([0, d3.max(data, function (d) {
return (d.total)+1000;
})]));
The idea is to get the max data for the yAxis and increase it by 1000.
The highest total is 14348 so with 1000 added on it creates 15348
However the top of the chart is still being clipped off and my axis hasn't increased to prevent the clipping. Even if I increase the number by 9999999999 it still doesn't happen.
The line is generated with:
var totalLine = d3.svg.line()
.interpolate('monotone')
.x(function (d) {
return xScale(d.date);
})
.y(function (d) {
return yScale(d.total);
});
Try throwing a .nice() at the end.
yScale = d3.scale.linear()
.range([mainHeight, 0])
.domain(d3.extent([0, d3.max(data, function (d) {
return (d.total)+1000;
})]))
.nice();
This will try to make the axis end on nice round numbers. You can read more here: https://github.com/d3/d3-3.x-api-reference/blob/master/Quantitative-Scales.md#linear_nice
Hope this helps.

What is causing d3js to stop drawing a line when x interval shortens?

This is a weird error; hard to explain so bear with me. I have some data that I query from a database, this data is normally 60 seconds part. However, if I insert a whole bunch of data that's not within 60 seconds (say 0-1 second apart); when this data is queried, d3js draws the lines just fine for the data that is 60 sec part, but when it encounters the new data that is not 60 seconds apart, it stops drawing the line; see below:
The red dot is a mouse over showing that there is indeed data; this occurs all along that "invisible" area (where the dot is). To the left where there is a clearly visible line; this data is separated by 60 seconds. If I slow down the data input back to 60 seconds the lines come back (but not for the area where it's 0-2 seconds apart).
Here is some of my source:
var xScale = d3.time.scale(); // time series
var yScale = d3.scale.linear(); // our float/int data points
var xAxis = d3.svg.axis();
var yAxis = d3.svg.axis();
var vline = d3.svg.line(); // our data will use this line
var varea = d3.svg.area(); // our data will fill this area
xScale
.domain(d3.extent(data, function(d) { return parseDate(d.x); }))
.range([0, width]);
yScale
.domain([0, d3.max(data, function(d) {
if (config.dtype == "%") {
return 100;
} else if (d.y >= 1) {
return d.y;
}
return 1;
})])
.range([height,0]);
xAxis
.scale(xScale)
.orient("bottom")
.ticks(12)
.innerTickSize(-height)
.outerTickSize(-height)
.tickPadding(3);
yAxis
.scale(yScale)
.orient("left")
.ticks(5)
.innerTickSize(-width)
.outerTickSize(-width)
.tickPadding(3)
.tickFormat(d3.format(",.2f"));
vline
.defined(function(d) { return d.y != null; })
.x(function(d) { return xScale(parseDate(d.x)); })
.y(function(d) { return yScale(d.y); });
varea
.defined(function(d) { return d.y != null; })
.x(function(d) { return xScale(parseDate(d.x)); })
.y0(height)
.y1(function(d) { return yScale(d.y); });
Would anyone have any idea why it's doing this?
You are using d3.line.defined which basically can make "hole" in your line. (See doc)
the generated path data will automatically be broken into multiple distinct subpaths, skipping undefined data.
See this example, looking exactly as the one you posted: http://bl.ocks.org/mbostock/3035090
Even if there is data where your mouse is, if it's a lone point surrounded by undefined values, the line / area won't draw, as it needs two consecutive data points to draw the line / area.

D3js and Variables Based on Percentages

I'm trying to create a responsive scatterplot with D3js using percentages for the width and height of the chart.
So, I have these variables declared up top:
var w = '100%';
var h = '100%';
My xScale and yScale work properly, along with the placement of my output circles in the SVG:
var xScale = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d["ttc"]; })])
.range([0, w]);
var yScale = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d["ctc"]; })])
.range([0, h]);
var svg = d3.select(el)
.append('svg')
.attr('height', h)
.attr('width', w);
svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx', function(d) { return xScale(d["ttc"]); })
.attr('cy', function(d) { return yScale(d["ctc"]); })
.attr('r', 10);
However, due to the nature of SVGs, the circles are displayed from the top left corner rather than the typical origin in the bottom left. In order to reverse this, usually I would switch the yScale range values, to be [h, 0] instead of [0, h]. When I do this, I get an error Error: Invalid value for <circle> attribute cy="NaN". What can I do to fix this and have the plot work with percentages?
You can declare width and height as numbers, like var h = 100; and then add percentages in the attr function:
.attr('cx', function (d) {
return xScale(d["ttc"]) + '%';
})
Then your svg could be styled with:
.attr('height', h + '%')
.attr('width', w + '%')
Or just use CSS for that. Here's a demo.

y scale on d3 bar chart

I am trying to build a stacked bar chart in d3.
Here's my jsfiddle:
http://jsfiddle.net/maneesha/gwjkgruk/4/
I'm trying to fix it so that the y-axis starts at zero.
I thought this is where that was set up and this is how it should be:
yScale = d3.scale.linear()
.domain([0, yMax])
.range([0, height]),
but it doesn't work. Changing domain to this gets the scale right but messes up all my rects.
yScale = d3.scale.linear()
.domain([yMax, 0])
.range([0, height]),
What am I doing wrong?
Try to modify your scale like that:
yScale = d3.scale.linear()
.domain([0,yMax])
.range([height,0]),
and then when you fill the rect:
.attr('y', function (d) {
return height - yScale(d.y0);
})
.attr('height', function (d) {
return height - yScale(d.y);
})

d3 timeseries, reading date from data, counting entries by 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/

Categories

Resources