d3js version 3 to 4 for toggling barcharts - javascript

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.

Related

How to set domain by word length for bar graph?

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) ]);

X scale function in D3.js keeps returning undefined

I am new to D3.js and trying to make a bar chart with GDP on Y axis and date on X axis.
I am trying to make a xScale with d3.timeScale() but for some reason the code keeps returning undefined when using it. Below is my code, what am I doing wrong?
var data = [
[
"2011-01-01",
15238.4 ]
,
["2015-07-01",
18064.7
]
];
var w = 1000;
var h = 300;
var barPadding = 1;
var svg = d3.select("#chart")
.append("svg")
.attr("height", h);
var maxDate = d3.max(data, function(d){
return d[0];
});
var minDate = d3.min(data, function(d){
return d[0];
});
var maxGDP = d3.max(data, function(d){
return d[1];
});
var minGDP = d3.min(data, function(d){
return d[1];
});
minDate = new Date(minDate);
maxDate = new Date(maxDate)
var xScale = d3.scaleTime()
.domain([minDate, maxDate])
.range(0,w)
var yScale = d3.scaleLinear()
.domain([minGDP, maxGDP])
.range(0,h)
.attr("width", w)
var bars = svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
var barAttributes = bars
.attr("x", function(d) { return xScale(d[0]);})
.attr("y", function(d) { return h - (d[1]/100);})
.attr("width", function(d,i) { return w/data.length;})
.attr("height", function(d) { return (d[1]/100) *4 ;})
.attr("fill", "#8e44ad");
In D3, both domain and range have to be an array:
If domain is specified, sets the scale’s domain to the specified array of numbers.
And also:
If range is specified, sets the scale’s range to the specified array of values.
Thus, instead of:
var xScale = d3.scaleTime()
.domain([minDate, maxDate])
.range(0,w);
It should be:
var xScale = d3.scaleTime()
.domain([minDate, maxDate])
.range([0,w]);//an array here

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/

d3.js referencing a single array column

I have an array of generated values to plot a line function. However, when I call the array, the function only returns single values of the array, rather than the column, and hence draws a straight line (http://tributary.io/inlet/8822590). What is the correct syntax here? Thanks in advance.
// Create data
var v1 = 4.137,
t = 10,
x = [],
y = [];
for (i = 0.1; i < 190; i += 0.1) {
x.push(i);
y.push((Math.pow((v1 / i), 1 / t) - 1) * 100);
}
var data = [x, y];
// Scale data
var xscale = d3.scale.linear()
.domain(d3.extent(data, function (d) {
return d[0];
}))
var yscale = d3.scale.linear()
.domain(d3.extent(data, function (d) {
return d[1];
}))
var line = d3.svg.line()
.x(function (d) {
return xscale(d[0])
})
.y(function (d) {
return yscale(d[1])
});
var svg = d3.select("svg");
var path = svg.append("path")
.data([data])
.attr("d", line) //this calls the line function with this element's data
.style("fill", "none")
.style("stroke", "#000000")
.attr("transform", "translate(" + [96, 94] + ")")
D3 expects the data for a line to be an array of array where each element determines one line and each element within the inner array the point of the line. You've passed in a single array (for one line) with two elements, so you get two points.
To plot all the points, push the coordinates as separate elements:
for (i = 0.1; i < 190; i += 0.1) {
data.push([i, (Math.pow((v1/i),1/t)-1)*102]);
}
The rest of your code can remain basically unchanged. Complete example here.

Categories

Resources