d3.js Array with categories and numbers - javascript

d3js experts
I have these array
var dados = [
["Brasil", 20],
["Canada", 31],
["Japao", 29],
["USA", 26],
["Inglaterra", 21],
["Nova Zelandia", 25],
["Turquia", 34]
];
I like to binding the array dados to my chart. Country Names to Texts and Values to Bars.
Is possible to do it directly? How to do it?
chart.selectAll("rect")
.data(function(d, i) { return d[i][1];})
.enter().append("rect")
.attr("y", function(d) {return d[0];})
.attr("width", x)
.attr("height", y.rangeBand()-6)
.attr("transform", "translate(4,0)");
chart.selectAll("text")
.data(function(d, i) {return d[i][0];})
.enter().append("text")
...
Thank you so much
I got the values with code bellow - in bold
var x = d3.scale.linear()
.domain([0, **d3.max(dados)[1]** ])
.range([0, 500]);
chart.selectAll("rect")
.data(**dados**)
.enter().append("rect")
.attr("y", function(d) {return d[0];})
.attr("width", **function(d) { return x(d[1]);}** )
.attr("height", y.rangeBand()-6)
.attr("transform", "translate(4,0)");

Correction inner ** **
var x = d3.scale.linear()
.domain([0, **d3.max(dados)[1]** ])
.range([0, 500]);
chart.selectAll("rect")
.data(**dados**)
.enter().append("rect")
.attr("y", function(d) {return d[0];})
.attr("width", **function(d) { return x(d[1]);}** )
.attr("height", y.rangeBand()-6)
.attr("transform", "translate(4,0)");

Related

How do you plot a line using scaleOrdinal in D3.js?

I'm trying to plot some numbers on the y axis that correspond to strings on the x axis using d3.js version 7. I'm not sure what x scale to use. I'm trying scaleOrdinal but the plot is not correct. I'd like the strings evenly spaced on the x axis with a little padding on the ends.
const data = [{str: 'a', num: 1}, {str: 'b', num: 3}, {str: 'c', num: 2}, {str: 'd', num: 0}];
const svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
const xScale = d3.scaleOrdinal()
.domain(d3.extent(data, function (d) {
return d.str;
}))
.range([0, width]);
var yScale = d3.scaleLinear()
.domain([0, 5])
.range([height, 0]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale))
.style('color', '#80ffffff')
.style('font-size', 20)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em");
svg.append("g")
.call(d3.axisLeft(yScale))
.style('color', '#80ffffff');
const valueline = d3.line()
.x(function (d) {
return xScale(d.str);
})
.y(function (d) {
return yScale(d.num);
});
svg.append("path")
.data([data])
.attr("class", "line")
.attr("d", valueline)
.style("fill", "none")
.style("stroke", '#ff0000ff')
.style("stroke-width", 1);
The ordinal scale is not the correct one here, mainly because ordinal scales need to have discrete (i.e. qualitative, categorical) domain and range.
What you want is a point scale. Also, pay attention to the fact that d3.extent only returns 2 values (in your case "a" and "d"), so what you want is a regular Array.prototype.map. That said, your scale is:
const xScale = d3.scalePoint()
.domain(data.map(function (d) {
return d.str;
}))
.range([0, width])
.padding(yourPaddingHere)

Append text after bars on horizontal bar chart

I am working on a horizontal bar chart and I want to move the y-labels after the bar.
d3.csv("mydata.csv", function(data) {
// Add X axis
var x = d3.scaleLinear()
.domain([0, 100])
.range([ 0, width])
svg.append("g")
.attr("transform", "translate(0,0)")
.call(d3.axisTop(x).ticks(10).tickFormat(function(d){return d+ "%"}))
.selectAll("text")
.attr("transform", "translate(10,-12)rotate(-45)")
.style("text-anchor", "end");
// Y axis
var y = d3.scaleBand()
.range([ 0, height ])
.domain(data.map(function(d) { return d.type; }))
.padding(.5);
svg.append("g")
.call(d3.axisRight(y))
.selectAll('text')
.attr("x", function(d) { return d.number; } )
//Bars
svg.selectAll("myRect")
.data(data)
.enter()
.append("rect")
.attr("x", x(0.5) )
.attr("y", function(d) { return y(d.type); })
.attr("width", function(d) { return x(d.number); })
.attr("height", y.bandwidth() )
.attr("fill", "#69b3a2")
})
The CSV file has two columns
type
number
On this part of the code
svg.append("g")
.call(d3.axisRight(y))
.selectAll('text')
.attr("x", function(d) { return d.number; } )
If I replace the line .attr("x", function(d) { return d.number; } ) with
.attr("x", 100 ) then it moves the labels towards right. But when I try to keep it dynamic by the width of the bar, the text stays a the start of the y-axis.
In your code, instead of creating a new selection with the labels as <text> elements, you're trying to move the axis' ticks! That's a quite uncommon approach, but it's certainly doable.
The main problem is just the data: in this line...
.attr("x", function(d) { return d.number; })
...the datum (d) is not the same datum that the rectangles have, which comes from your data. The datum here is just the strings in the axis' ticks.
Therefore, we have to get the real datum, so we can get the number value. Like this:
.attr("x", function(d) {
const datum = data.find(function(e){return e.type === d})
return x(datum.number);
})
Here is your code with that change (and some bogus data):
var width = 500,
height = 300,
padding = 20;
var data = [{
type: "foo",
number: 42
},
{
type: "bar",
number: 12
},
{
type: "baz",
number: 79
},
{
type: "foobar",
number: 17
},
{
type: "foobaz",
number: 36
}
];
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var x = d3.scaleLinear()
.domain([0, 100])
.range([padding, width - padding])
svg.append("g")
.attr("transform", "translate(0,20)")
.call(d3.axisTop(x).ticks(10).tickFormat(function(d) {
return d + "%"
}))
.selectAll("text")
.attr("transform", "translate(10,-12)rotate(-45)")
.style("text-anchor", "end");
// Y axis
var y = d3.scaleBand()
.range([padding, height - padding])
.domain(data.map(function(d) {
return d.type;
}))
.padding(0.5);
svg.append("g")
.attr("transform", "translate(20,0)")
.call(d3.axisRight(y))
.selectAll('text')
.attr("x", function(d) {
const datum = data.find(function(e) {
return e.type === d
})
return x(datum.number) + 6;
})
//Bars
svg.selectAll("myRect")
.data(data)
.enter()
.append("rect")
.attr("x", x(0.5))
.attr("y", function(d) {
return y(d.type);
})
.attr("width", function(d) {
return x(d.number);
})
.attr("height", y.bandwidth())
.attr("fill", "#69b3a2")
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

d3 v4 js : attribute error

I'm very new to data vizualisation and JavaScript and I'm trying to build a bar chart histogram using d3 v4.
I was working first working on d3 v3 and everything was going so well but I've got informed that I needed to work on v4.
Here is a piece of my code :
...
// create function for x-axis mapping.
var x = d3.scaleBand().rangeRound([0, hGDim.w]).padding(0.1)
.domain(fD.map(function(d) { return d[0]; }));
var xAxis = d3.axisBottom()
.scale(x);
// Create function for y-axis map.
var y = d3.scaleBand().rangeRound([0, hGDim.h])
.domain([0, d3.max(fD, function(d) { return d[1]; })]);
var yAxis = d3.axisBottom()
.scale(y);
// Create bars for histogram to contain rectangles and freq labels.
var bars = hGsvg.selectAll(".bar").data(fD).enter()
.append("g").attr("class", "bar");
//create the rectangles.
bars.append("rect")
.attr("x", function(d) { return x(d[0]); })
.attr("y", function(d) { return y(d[1]); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return hGDim.h - y(d[1]); })
.attr('fill',barColor)
.on("mouseover",mouseover)// mouseover is defined below.
.on("mouseout",mouseout);// mouseout is defined below.
//Create the frequency labels above the rectangles.
bars.append("text").text(function(d){ return d3.format(",")(d[1])})
.style("font-family", "sans-serif")
.attr("x", function(d) { return x(d[0])+x.bandwidth()/2; })
.attr("y", function(d) { return y(d[1])-5; })
.attr("text-anchor", "middle");
...
When trying to run this, I have this 2 errors :
Error: attribute height: Expected length, "NaN".
And it tells me that it's on this line :
.attr("height", function(d) { return hGDim.h - y(d[1]); })
hGDim.h being a number
I also have this error :
Error: attribute y: Expected length, "NaN".
And it tells me that it's on this line :
.attr("y", function(d) { return y(d[1])-5; })
I didn't put all my code (271 lines), I'm not sure it's needed here.
Do you have any idea from where could these errors come from ?
I feel that I'm trying to add 2 variables of different types... However, it was working well on v3.
You are treating your y scale like a continuous scale, but it needs to be ordinal like your x scale (scaleBand() is ordinal). Try this:
var y = d3.scaleBand()
.rangeRound([0, hGDim.h])
.domain(fD.map(function(d) {return d[1];}));
Then, you must modify the create rectangle code as follows:
bars.append("rect")
.attr("x", function(d) { return x(d[0]); })
.attr("y", function(d) { return y(d[1]); })
.attr("width", x.bandwidth())
.attr("height", y.bandwidth())
.attr('fill', barColor)
.on("mouseover", mouseover)
.on("mouseout", mouseout);
Here is a minimal example, assuming d[0] and d[1] are coordinates on the heatmap:
var data = [[1, 1, "red"], [1, 2, "blue"], [1, 3, "green"],
[2, 1, "navy"], [2, 2, "yellow"], [2, 3, "orange"],
[3, 1, "red"], [3, 2, "blue"], [3, 3, "red"]],
svg = d3.select("svg"),
w = 500,
h = 500,
x = d3.scaleBand()
.rangeRound([0, w])
.padding(0.1)
.domain(data.map(function(d) {return d[0];})),
y = d3.scaleBand()
.rangeRound([0, h])
.padding(0.1)
.domain(data.map(function(d) {return d[1]})),
xAxis = d3.axisBottom()
.scale(x),
yAxis = d3.axisBottom()
.scale(y),
bars = svg.selectAll(".bar")
.data(data).enter()
.append("g")
.attr("class", "bar");
bars.append("rect")
.attr("x", function(d) { return x(d[0]); })
.attr("y", function(d) { return y(d[1]); })
.attr("width", x.bandwidth())
.attr("height", y.bandwidth())
.style("fill", function(d) { return d[2]; });
<script src="https://d3js.org/d3.v4.js"></script>
<svg height="500px", width="500px"></svg>

D3.js add label to bar chart

How can I add label to a bar chart? The bar chart extends along the x-axis and I want to append the labels along the y-axis.
I read some examples and they use an external .tsv or .csv file. Can I store them in an array instead? My code is below :
var data = [7, 8, 15, 16];
var label = ["as","bs","cs","ds"];
var width = 420,
barHeight = 20;
var x = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, width]);
var chart = d3.select(".chart")
.attr("width", width)
.attr("height", barHeight * data.length);
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; });
bar.append("rect")
.attr("width", x)
.attr("height", barHeight - 1);
bar.append("text")
.attr("x", function(d) { return x(d) - 3; })
.attr("y", barHeight / 2)
.attr("dy", ".35em")
.text(function(d) { return d; });
You can get the value of your label by the index like this :
bar.append("text")
.attr("x", function(d) { return x(d) - 3; })
.attr("y", barHeight / 2)
.attr("dy", ".35em")
.text(function(d, i) { return label[i]; });

Creating an average Y line for D3 visualization?

I am trying to create a visualization to help students that I work with learn about measures of fit, such as r-squared. For R-squared, I want to have both the regression line and a line for the mean of Y on my graph. (My end goal is to have lines between the points and/or lines to represent ESS, TSS, and SSR that students can click to see or not see).
My regression line is working fine, but when I try to add in my average line, it ends up with a strange start and end point and is noticeably NOT a flat line at the average (4.4). I also get the following error in my console:
Error: Invalid value for <path> attribute d="M112,235.71428571428572L194,119.14285714285712L276,NaNL358,NaNL440,NaN"
which corresponds to the line of code:
.attr({
within my avg.append("path") for my avgline (at least, I think that's why I'm specifying there):
svg.append("path")
.datum(avgdataset)
.attr({
d: avgline,
stroke: "green",
"stroke-width": 1,
fill: "none",
"stroke-dasharray": "5,5",
});
I've tried playing around with how avgline is specified to no end (this playing around normally ends up producing no line at all). I've also tried using data instead of datum, to no avail. I'm likely making a really basic mistake, since I'm new to javascript and D3.
Here's all of my code thus far, to put it in context:
//Width and height
var w = 500;
var h = 300;
var padding = 30;
var dataset = [
[1, 1],
[2, 5],
[3, 4],
[4, 7],
[5, 5]
];
//Create scale functions
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d[0];
})])
.range([padding, w - padding * 2]);
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d[1];
})])
.range([h - padding, padding]);
var rScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d[1];
})])
.range([2, 5]);
//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);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create circles
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", 4)
.append("svg:title")
.text(function(d) {
return d[0] + "," + d[1];
});;
//average stuff
var sum = 0,
average;
for (var i = 0; i < dataset.length; i++) {
sum += dataset[i][1];
}
average = sum / dataset.length;
console.log(average);
var avgdataset = [
[1, average],
[2, average],
[3, average],
[4, average],
[5, average]
];
console.log(avgdataset);
document.write(avgdataset);
//Create labels
/*svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d[0] + "," + d[1];
})
.attr("x", function(d) {
return xScale(d[0]);
})
.attr("y", function(d) {
return yScale(d[1]);
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "red");
*/
//Create X axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
//Create Y axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
var lr = ss.linear_regression().data(dataset).line();
var forecast_x = 20
console.log(lr)
var lrline = d3.svg.line()
.x(function(d, i) {
return xScale(i);
})
.y(function(d, i) {
return yScale(lr(i));
});
svg.append("path")
.datum(Array(dataset.length * forecast_x))
.attr({
d: lrline,
stroke: "black",
"stroke-width": 1,
fill: "none",
"stroke-dasharray": "5,5",
});
var avgline = d3.svg.line()
//.x(function(d, i) { return xScale(i); })
//.y(function(d, i) { return yScale(avgdataset(i)); });
.x(function(d, i) {
return xScale(d[0]);
})
.y(function(d, i) {
return yScale(d[i]);
});
svg.append("path")
.datum(avgdataset)
.attr({
d: avgline,
stroke: "green",
"stroke-width": 1,
fill: "none",
"stroke-dasharray": "5,5",
});
//to get the m and b for the equation line
var mvalue = ss.linear_regression().data(dataset).m();
console.log(mvalue);
var bvalue = ss.linear_regression().data(dataset).b();
console.log(bvalue);
//equation written out
svg.append("text")
.text("Y= " + mvalue + "x + " + bvalue)
.attr("class", "text-label")
.attr("x", 60)
.attr("y", 30);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://raw.github.com/tmcw/simple-statistics/master/src/simple_statistics.js"></script>
Similar to Kaiido, it looks to me that the avgline function was the issue. You were passing in an array of arrays and the x and y weren't accessing the correct part of the array. Most of the examples I've worked with pass an array of objects, so something like:
var data = [ {x: 1, y: 4.4}, {x:2, y:4.4}, etc];
If you construct an object like this you can simple pass this to the avgline which can then elegantly access the correct parts of the data with something like:
var avgline = d3.svg.line() //changed x and y function to reflect changed data
.x(function(d, i) {
return xScale(d.x);
})
.y(function(d, i) {
return yScale(d.y);
});
There are a number of advantages of this. For instance you could ensure that all your data corresponds to this structure and then you would only need one line constructor instead of two.
I think you almost got it, except that avgdataset is not a function but an array.
Simply replace
var avgline = d3.svg.line()
//.x(function(d, i) { return xScale(i); })
//.y(function(d, i) { return yScale(avgdataset(i)); });
.x(function(d, i) {
return xScale(d[0]);
})
.y(function(d, i) {
return yScale(d[i]);
});
with
var avgline = d3.svg.line()
.x(function(d, i) { return xScale(i); })
.y(function(d, i) { return yScale(avgdataset[i][1]); });
//Width and height
var w = 500;
var h = 300;
var padding = 30;
var dataset = [[1, 1], [2, 5], [3, 4], [4, 7], [5, 5]];
//Create scale functions
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) { return d[0]; })])
.range([padding, w - padding * 2]);
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) { return d[1]; })])
.range([h - padding, padding]);
var rScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) { return d[1]; })])
.range([2, 5]);
//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);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create circles
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", 4
)
.append("svg:title")
.text(function(d){return d[0] + "," + d[1];});;
//average stuff
var sum = 0, average;
for (var i = 0; i < dataset.length; i++) {
sum += dataset[i][1];
}
average = sum / dataset.length;
console.log(average);
var avgdataset = [[1, average], [2, average], [3, average], [4, average], [5, average]];
console.log(avgdataset);
document.write(avgdataset);
//Create labels
/*svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d[0] + "," + d[1];
})
.attr("x", function(d) {
return xScale(d[0]);
})
.attr("y", function(d) {
return yScale(d[1]);
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "red");
*/
//Create X axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
//Create Y axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
var lr = ss.linear_regression().data(dataset).line();
var forecast_x = 20
console.log(lr)
var lrline = d3.svg.line()
.x(function(d, i) { return xScale(i); })
.y(function(d, i) { return yScale(lr(i)); });
svg.append("path")
.datum(Array(dataset.length*forecast_x))
.attr({
d: lrline,
stroke: "black",
"stroke-width": 1,
fill: "none",
"stroke-dasharray": "5,5",
});
var avgline = d3.svg.line()
.x(function(d, i) { return xScale(i); })
.y(function(d, i) { return yScale(avgdataset[i][1]); });
svg.append("path")
.datum(avgdataset)
.attr({
d: avgline,
stroke: "green",
"stroke-width": 1,
fill: "none",
"stroke-dasharray": "5,5",
});
//to get the m and b for the equation line
var mvalue = ss.linear_regression().data(dataset).m();
console.log(mvalue);
var bvalue = ss.linear_regression().data(dataset).b();
console.log(bvalue);
//equation written out
svg.append("text")
.text("Y= " + mvalue + "x + " + bvalue)
.attr("class", "text-label")
.attr("x", 60)
.attr("y", 30);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://raw.github.com/tmcw/simple-statistics/master/src/simple_statistics.js"></script>

Categories

Resources