How to set domain by word length for bar graph? - javascript

I have a csv which lists words and a corresponding number for each:
wordFile.csv
word,count
hello,3
to,4
there,6
I am creating a bar graph which has the word name on the x axis and the count is the size of the bar on the y axis. I want to ignore words whose length is < 3. How can I do this?
I tried:
var x = d3.scaleBand()
.range([0, width])
var y = d3.scaleLinear()
.range([height, 0]);
x.domain(self.data.map(function (d) {
return d.word;
}
));
y.domain([0, d3.max(self.data, function (d) {
if(d.word.length > 3) {
return d.count;
}
}
)]);
but this doesn't do the trick.
Is this something that's possible or do I have to edit the csv?

filter the data before you use it
self.data = self.data.filter( d => d.word.length > 3 );
y.domain([0, d3.max(self.data, d => d.count) ]);

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.

Stumped by d3 error I'm receiving - Error: <path> attribute d: Expected number, "M0,NaNL21.923076923…"

I'm trying to convert a d3 sparkline example on codepen (https://codepen.io/jakelear-1472051722/pen/bRbqBB) so that it pulls data in from a csv file. Here is my code:
d3.csv("sparkline.csv", function (error, data) {
if (error) throw error;
// format the data
data.forEach(function (d) {
currentValue = +d.currentValue;
});
var graph = d3.select('.sparkline').append("svg").attr("width", "100%").attr("height", "100%");
var height = parseInt(graph.style("height"));
var width = parseInt(graph.style("width"));
var x = d3.scaleLinear()
.domain([0, data.length])
.range([0, width]);
var y = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([height, 0]);
var line = d3.line()
.x(function(d,i) {
return x(i);
})
.y(function(d) {
return y(currentValue);
});
graph.append("path")
.data([data])
.attr("class", "line")
.attr("d", line);
});
I'm receiving the following error: d3.v4.js:1382 Error: attribute d: Expected number, "M0,NaNL21.923076923…" and have been struggling trying to figure this out.
The contents of my CSV file:
currentValue
2.0
2.3
1.9
1.7
1.7
2.2
2.3
2.2
2.0
2.1
1.9
2.0
1.6
Any help is greatly appreciated. I'm sure it's something small I'm just not seeing.
Thanks
data is not a simple, flat array, but an array of objects.
Therefore, it should be:
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d){ return d.currentValue})])
.range([height, 0]);
Besides that, remember to correctly provide a reference to the object. In the forEach loop:
data.forEach(function (d) {
d.currentValue = +d.currentValue;
});
And in the line generator:
var line = d3.line()
.x(function(d,i) {
return x(i);
})
.y(function(d) {
return y(d.currentValue);
});

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.

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 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