So I'm sort of new to Javascript and I am trying to create a histogram using d3.js. I've been following tutorials and examples of previously created histograms in d3 but cannot figure out how to make my rectangles appear.
My histogram currently contains 4 bins with the numbers 1, 2, 3 and 4 in each bin symbolizing a color attribute of each data point in my dataset. When I do console.log(d) in the .attr "x" function it will appear as an a kind of array with 4 different indices, each with the total number of data points in my dataset with that specific color. Now I'm trying to make that "array" into rectangles but my width and height functions aren't correct. If someone could explain what d.dx and d.y do any why they're wrong that would be helpful. I'm using d3.v3.min.js as my script src value
d3.csv("data.csv", function(data) {
var map = data.map(function (i) { return parseInt(i.color); })
var histogram = d3.layout.histogram()
.bins(4)(map)
var canvas = d3.select("body").append("svg")
.attr("width", 500)
.attr("height", 500);
var bars = canvas.selectAll(".bar")
.data(histogram)
.enter()
.append("g")
bars.append("rect")
.attr("x", function (d)
{
//console.log(d)
return d.x * 5; })
.attr("y", 0)
.attr("width",function(d) { return d.dx; })
.attr("height", function(d) { d.y; })
.attr("fill", "steelblue");
});
I updated your plunk as follows.
bars.append("rect")
.attr("x", function(d) { return d.x*100; })
.attr("y", 50)
.attr("height", function(d) { return d.y * 10;})
.attr("width", function(d) { return d.dx*50;})
.attr("fill", "steelblue")
.on("mouseout", function()
{
d3.select(this)
.attr("fill", "steelblue");
})
.on("mouseover", function()
{
d3.select(this)
.attr("fill", "orange");
});
Your code seems to work fine, only your elements are overlapping (also, d3 v4 was referenced instead of v3). What I did is:
multiply d.x by 50 to space the elements
multiplied d.dx by 50 to reduce the overlapping
As to your former questions:
d.x corresponds to the extent of a bin, in your case 0.75 (4 ranges make between 1 and 4 make 0.75: 1+(0.75*4)=4)
*d.y corresponds to the 'height' of a bin, i.e. the number of elements.
Related
2 part question:
I have a bar chart with created using multiple arrays. These arrays contain the % wins of baseball teams; the relevant team colours; and their names.
I can create one set of labels on the chart, either the names or the win %. However I can't get both on at the same time. See below.
The code I am using is:
let WinsLabel = svgContainer.selectAll("text")
.data(d3.zip(TeamArray, WinPercArray, Colours));
WinsLabel.enter()
.append("text")
.attr("fill", "black")
.attr("x", function(d, i) {
return 45 + (i * 50);
})
.attr("y", 700)
.transition()
.duration(1000)
.attr("x", function(d,i){
return 70 + (i*50);
})
.attr("y", function(d){
return 685 - d[1];
})
.attr("text-anchor","middle")
.attr("font-family", "sans-serif")
.attr("font-size", "15px")
.attr("fill", "black")
.text(function(d){
return d[1]/10 + "%";
});
let TeamLabel = svgContainer.selectAll("text")
.data(d3.zip(TeamArray, WinPercArray, Colours));
TeamLabel.enter()
.append("text")
.attr("fill", "black")
.attr("x", function(d, i) {
return 45 + (i * 50);
})
.attr("y", 700)
.transition()
.duration(1000)
.attr("x", function(d,i){
return 70 + (i*50);
})
.attr("y", function(d){
return 700 - d[1]/2;
})
.attr("text-anchor","middle")
.attr("font-family", "sans-serif")
.attr("font-size", "15px")
.attr("fill", "white")
.text(function(d){
return d[0];
});
When I run the code with both scripts, only the win % shows up, but the names don't. In order to get the names to show up I have to remove the first label.
The 2 parts to my question are:
How would I get both sets of labels to show up at the same time?
How can I get the names to be arranged vertically in the rectangles/bars?
D3 stands for data driven something; and it's core principle is based on linking elements / selection, with data. When you set data, (var selection = selectAll(...).data(...)), you get 3 cases to think about:
Some existing elements can be linked to certain item in new data. You access them using selection
Some elements cannot be linked to any item in new data. You access them using selection.exit()
Some items in new data cannot be linked to any element from selection. You access them by using selection.enter()
In its simplest case, the linking between data and elements is made by index -- ie first element in selection is linked with first item in data array, second with second, and so on. The d3 cannot find element for the data item (= gets put into .enter() selection) if and only if (in this by-index context) the index of that data item is bigger than the size of the selection.
On your initial select
let WinsLabel = svgContainer.selectAll("text")
.data(d3.zip(TeamArray, WinPercArray, Colours));
The selection is empty, since there are no text tags yet. And since its empty, all of the to-be-created placeholders are inside .enter() selection. However, on your next select for the other label type
let TeamLabel = svgContainer.selectAll("text")
.data(d3.zip(TeamArray, WinPercArray, Colours));
The selection is of the size of the passed data, and thus .enter() selection is empty; it's the TeamLabel selection that contains all of the old elements (percentage label text tags), but they got their data values reassigned.
Andrew proposed one solution to assign classes, but personally I'd take all elements that relate to same team and put it under one group.
var TeamArray = ["Yankees", "Rays", "RedSox", "Jays","Orioles", "Twin", "Indians", "WhiteSox", "Detroit", "Royals", "Astros", "Rangers", "A's", "Angels","Mariners"];
var WinPercArray = [653, 609, 540, 400, 300, 667, 521, 458, 383, 347, 660, 511, 500, 458, 442];
var Colours = ["#003087", "#092C5C", "#BD3039", "#134A8E", "#DF4601", "#002B5C", "#0C2340", "#C4CED4", "#FA4616", "#BD9B60", "#EB6E1F", "#C0111F", "#003831", "#003263", "#005C5C"];
var data = d3.zip(TeamArray, WinPercArray, Colours);
var svg = d3.select('body').append('svg').attr('height', 300).attr('width', 800);
var teams = svg.selectAll('g.teams')
.data(data);
var scale = d3.scaleLinear()
.domain([0, 1000])
.range([200, 0]);
var teamsEnter = teams.enter()
.append('g')
.classed('team', true)
.attr('transform', function(d, i){
return 'translate(' + (i*50) + ',0)';
})
teamsEnter.append('rect')
.attr('width', 50)
.attr('y', function(d) { return scale(d[1]); })
.attr('height', function(d) { return scale(0) - scale(d[1]); })
.style('fill', function(d) { return d[2]; });
teamsEnter.append('text')
.attr('x', 25)
.attr('y', function(d) { return scale(d[1]) - 30; })
.text(function(d){ return d[0]; });
teamsEnter.append('text')
.attr('x', 25)
.attr('y', function(d) { return scale(d[1]) - 15; })
.text(function(d){ return d[1]; });
text {
text-anchor: middle;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Groups in some way act as encapsulation of inner items, so you can mentally separate data binding to groups (ie when to create / update / delete it), from actual logic that takes place when working with its children
I'm trying to build out a simple color chart, as an introductory d3 exercise, and I'm already stuck.
I have the following:
var colors = ["#ffffcc","#c7e9b4","#7fcdbb","#41b6c4","#2c7fb8","#253494"];
var barHeight = 20,
barWidth = 20,
width = (barWidth + 5) * colors.length;
d3.select("body").selectAll("svg")
.data(colors)
.enter().append("rect")
.attr("class", "block")
.attr("width", barWidth)
.attr("height", barHeight - 1)
.text(function(d) { return d; })
.attr("fill", function(d) { return d; });
https://jsfiddle.net/xryamdkf/1/
The text works fine. I see the hex codes, but the height and width are definitely not respected, and I can't seem to set the color.
This works to set the color: .style("background", function(d) { return d; }) but I think that is the text background, not the rect fill.
What am I doing wrong here? How can I make 20x20 rectangles filled with color in d3?
As you are not giving any index and reference of colors array into your function the code will not understand from where to pick colors. try with below code it will help.
d3.select("body").selectAll("svg")
.data(colors).enter().append("rect")
.attr("class", "block")
.attr("width", barWidth)
.attr("height", barHeight - 1)
.text(function(d) {
return d;
})
.attr("fill", function(d,i) { return colors[i]; });
So, a few things. You should call data() on what will be an empty selection of the things you will be adding.
svg.selectAll("rect").data(colors)
.enter().append("rect")
The rect doesn't have a text property. There is an svg text node that shows text and you'll want to add it separately.
I hope this https://jsfiddle.net/xryamdkf/8/ gets you closer.
I am trying to get lines to change style on mouseover across multiple charts. In this example available here, I have two charts that both have five groups A,B,C,D,E. Each however is in a different csv (I am open to bringing the data in one csv or as one json array, but this is just how I have it set up right now).
I can get two charts each with five lines corresponding to the group. Using the below code, I get the hovered over line to change style whilst fading out the other lines in that chart.
// Fading and Selecting Lines
d3.selectAll('path.line.mainline')
.on("mouseover", function(d) {
var HoveredLine = this;
d3.selectAll('path.line.mainline').transition().duration(0)
.style('opacity',function () {
return (this === HoveredLine) ? 1.0 : 0.1;
})
.style('stroke-width',function () {
return (this === HoveredLine) ? 4 : 2;
})
;
})
This is achieved by giving the lines an id using classed. Using a different id, the lines in the other chart are selected similarly.
What I want to achieve is a way that if the line of e.g. group A is highlighted in one chart, it is also highlighted in the other chart also (and all other non-selected lines are faded in all charts). I thought maybe this could be done by getting the index of the selected line and somehow using that in the other chart.
We can solve it by having a single place where we handle mouseover and mouseout for both lines.
Primarily to avoid code repeat (DRY principle)
We will write mouse over and mouse out in a single place from where we can handle events in both svg.
So instead of attaching listeners individually like this
d3.selectAll('path.line.mainline')
.on("mouseover", function(d) {
and
d3.selectAll('path.line.mainlinel')
.on("mouseover", function(d) {
Do it like this:
d3.selectAll('path.line')//register this to all paths
.on("mouseover", function(d,i) {
Make use of filter to get the lines on which it is hovered.
d3.selectAll('path.line').filter(function(d1) {
return d.name == d1.name; all which have same name get it via filter
})
.style("opacity", 1)//show filtered links
.style("stroke-width", 4);
Full method will be like this:
function doHover() {
d3.selectAll('path.line')//register this to all paths
.on("mouseover", function(d,i) {
//first make all lines vanish
d3.selectAll('path.line')
.style("opacity", 0.1)
.style("stroke-width", 2)
//only show lines which have same name.
d3.selectAll('path.line').filter(function(d1) {
return d.name == d1.name
})
.style("opacity", 1)
.style("stroke-width", 4);
d3.select("div#chartw.container svg")
.append("text")
.attr("id", "cohorttext")
.html("Cohort " + d.name)
.attr("x", (width) / 1.2)
.attr("y", margin.top * 1.5)
.style("fill", color(d.name))
.style("font-weight", "bold")
.style("font-size", "18px");
d3.select("div#chartw.container svg")
.append("text")
.attr("id", "cohorttextx")
.html("Gini = " + giniw[i%giniw.length])//so that its always within the max length
.attr("x", (width) / 1.2)
.attr("y", 20 + margin.top * 1.5)
.style("fill", color(d.name))
.style("font-size", "14px");
d3.select("div#chartl.container svg")
.append("text")
.attr("id", "cohorttext")
.text("Cohort " + d.name)
.attr("x", (width) / 1.2)
.attr("y", margin.top * 1.5)
.style("fill", color(d.name))
.style("font-weight", "bold")
.style("font-size", "18px");
d3.select("div#chartl.container svg")
.append("text")
.attr("id", "cohorttextx")
.html("Gini = " + ginil[i%ginil.length])//so that its always within the max length
.attr("x", (width) / 1.2)
.attr("y", 20 + margin.top * 1.5)
.style("fill", color(d.name))
.style("font-size", "14px");
})
.on("mouseout", function() {
d3.selectAll('path.line')
.style("opacity", 1)
.style("stroke-width", 2);
//selectALL because we are giving same id to both text in 2 svgs
d3.selectAll("#cohorttext").remove()
d3.selectAll("#cohorttextx").remove()
})
}
Working code here
Please let me know if you have any queries on this.
I'm following the part III tutorial of "Let' Make Some Charts" as an introduction to D3. Part of the tutorial calls for data insertion via TSV. Given I don't see this being an eventual use case for me, I'm attempting to modify the tutorial with the code below using a simple javascript array. However, nothing shows up on the page when I render in the browser. Can anyone shed some light on this?
Here's the tutorial link for some reference to the original code: http://bost.ocks.org/mike/bar/3/
My JS code:
<script>
var data = [4,8,15,16,23,42,57,89,100,160];
var width = 960,
height = 500; // have to make sure variables are case sensitive
var y = d3.scale.linear()
.domain([0, d3.max(data)]) // scaling based on max value
.range([height, 0]);
var chart = d3.select(".chart")
.attr("width", width)
.attr("height", height);
var barWidth = width / data.length;
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d,i) { return "translate(" + i * barWidth + ",0)";});
bar.append("rect")
.attr("y", function(d) { return y(d.value); })
.attr("width", barWidth - 1)
.attr("height", function(d) { return height - y(d.value); });
bar.append("text")
.attr("x", barWidth / 2)
.attr("y", function(d) { return y(d.value) + 3; })
.attr("dy", ".75em")
.text(function(d) { return d.value; });
function type(d) {
d.value = +d.value;
return d;
}
</script>
The code you've copied references a named attribute value to determine what to draw. The data you've created doesn't have this but just the data. So everywhere you have d.value, you need to reference just d.
Complete demo here.
Your problem stems from the fact that you're using an Array of numbers for your data, while in Mike Bostock's example he was using an Array of Objects (for example, var data = [{value: 30}, ...]). Thus you need to change all cases of d.value to d in your code, since your data is not longer an Object but just a number.
bar.append("rect")
.attr("y", function(d) { return y(d); }) // <---- delete .value
.attr("width", barWidth - 1)
.attr("height", function(d) { return height - y(d); }); // <---- delete .value
bar.append("text")
.attr("x", barWidth / 2)
.attr("y", function(d) { return y(d) + 3; }) // <---- delete .value
.attr("dy", ".75em")
.text(function(d) { return d; }); // <---- delete .value
Making these changes produces the following bar chart:
I have a data variable which contains the following:
[Object { score="2.8", word="Blue"}, Object { score="2.8", word="Red"}, Object { score="3.9", word="Green"}]
I'm interested in modifying a piece of a D3 graph http://bl.ocks.org/3887051 to display the legend, which would be the list of the "word", for my data set.
The legend script looks like this (from link above):
var ageNames = d3.keys(data[0]).filter(function(key) { return key !== "State"; });
var legend = svg.selectAll(".legend")
.data(ageNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
How do I modify their ageNames function to display the "word" set from my data? I'm not sure how they're utilizing the d3.keys. Is there another way to do it?
This should work more or less, but you may need to reverse() (as the original example does) or otherwise rearrange the elements of words, in order to correctly map a word to the right color. Depends on how you've implemented your graph.
var words = yourDataArray.map(function(entry) { return entry.word; });
var legend = svg.selectAll(".legend")
.data(words)
// The rest stays the same