D3 Scatterplot legend overlapping - d3.js

I have a scatterplot that works fine, but the legend I add to it is overlapping the chart. My current approach is to make the chart DIV be 70% of the width and have the legend take up the remaining 30%. For some reason, the legend isn't showing up on the screen, even though the HTML is there.
This is the link to my initial problem: http://jsfiddle.net/chp5a09e/373/
Here is the link to what I'm currently trying: http://jsfiddle.net/chp5a09e/372/
var legend = d3.select("#legend").append("svg")
.attr("width", $("#legend").width())
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
legend.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(0," + i * 20 + ")";
});
legend.append("rect")
.attr("x", width - 12)
.attr("width", 12)
.attr("height", 12)
.style("fill", function(d) {
return color(d);
})
.on("click", function(d) {
d3.selectAll(".symbol").style("opacity", 1)
if (clicked !== d) {
d3.selectAll(".symbol")
.filter(function(e) {
return e.items[columns.indexOf("Channel")] !== d;
})
.style("opacity", 0.1)
clicked = d
} else {
clicked = ""
}
});
legend.append("text")
.attr("x", width - 16)
.attr("y", 6)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {
return d;
});

HTML is there
Only group (g) elements are there, and they are never visible themselves. In your original code
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
...
legend.append("rect")
the legend here is a selection of multiple g.legend elements, and thus a rect gets appended to each of them, as well as gets access to the datum bound to the parent g. However in you new code
var legend = d3.select("#legend").append("svg")
...
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
legend.selectAll(".legend")
.data(color.domain())
.enter().append("g")
...;
legend.append("rect")
The legend here refers only to the single g element that contains your whole legend. Your legend.selectAll(".legend") isn't saved into variable, so while inside the chain you set class and transform attributes properly, you don't use it to get rect appended to it -- again, legend at that point refers to the outter single g container.
Potential solution:
var legendCnt = d3.select("#legend").append("svg")
.attr("width", $("#legend").width())
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var legend = legendCnt.selectAll(".legend")
.data(color.domain())
...
You'll notice you'll need to remove or lower x attribute for text and rect, since 0 is now at the beginning of the legend instead of chart

Related

Mouseover event for barchart in D3

I am having trouble creating a mouseOver event for my D3 visualization for a class. I have a bar chart I created and want to make it so when I mouse over each bar, it displays a small div with the actual values of the bar inside. I have created the barchart I want and am trying integrate a section of code from one of our earlier labs in class, where we added this hover functionality to the barchart visualization but I am just not able to get anything to work.
Here is the code for my index.html with a working graph
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<div style ="float:right; padding-right:300px" id="tooltip"></div>
<script>
// set the dimensions and margins of the graph
var margin = {top: 30, right: 30, bottom: 70, left: 60},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
// Parse the Data
d3.csv("Embiid3pt.csv", function(data) {
// X axis
var x = d3.scaleBand()
.range([ 0, width ])
.domain(data.map(function(d) { return d.player; }))
.padding(0.2);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end");
svg.append("text")
.attr("transform",
"translate(" + (width/2) + " ," +
(height + margin.top + 25) + ")")
.style("text-anchor", "middle")
.text("Player Name");
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 0.7])
.range([ height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// text label for the y axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Three Point Percentage");
// Bars
svg.selectAll("mybar")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) { return x(d.player); })
.attr("y", function(d) { return y(d.percentage); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.percentage); })
.attr("fill", "#69b3a2")
})
</script>
And here is the CSV data I'm loading in:
player,percentage
Joel Embiid,0.377
Bam Adebayo,0.143
Clint Capela,0
Anthony Davis,0.26
Nikola Vucevic,0.339
Deandre Ayton,0.250
Jarrett Allen,0.189
Kristaps Porzingis,0.353
Finally, here is the section of code that we used earlier in the course to give the mouseover event to the bars of the bar chart:
let bars = chart.append('g')
.selectAll("rect")
.data(data)
.join("rect")
.attr("x", function (d) { return x(d.name); } )
.attr("y", function (d) { return y(d.value); } )
.attr("fill", function(d) { return ordinal(d.name) })
.attr("width", x.bandwidth()) //use the bandwidth returned from our X scale
.attr("height", function(d) { return height - y(+d.value); }) //full height - scaled y length
.style("opacity", 0.75)
bars //let's attach an event listener to points (all svg circles)
.on('mouseover', (event,d) => { //when mouse is over point
d3.select(event.currentTarget) //add a stroke to highlighted point
.style("stroke", "black");
d3.select('#tooltip2') // add text inside the tooltip div
.style('display', 'block') //make it visible
.html(`
<h1 class="tooltip-title">${d.name}</h1>
<div>Highway (HWY) MPG: ${d.value}</div>
`);
})
.on('mouseleave', (event) => { //when mouse isnt over point
d3.select('#tooltip2').style('display', 'none'); // hide tooltip
d3.select(event.currentTarget) //remove the stroke from point
.style("stroke", "none");
});
How do I integrate this final section of code into my index.html to get this mouseover event to work? I already created the tooltip div at the top of the index which will display the values once you mouse over.

d3.js - chain box with arrow lines

I would like to draw few text box and chain it with arrow lines. I use below code to draw the text box few issues there:
text box is black and no text show there.
One box is missing, it should be 5 box but only 4 can be seen.
how can I add a arrow line to connect each other!
test()
function test() {
var data = ["a","b","c","d","e"]
width = 800
height = 600
margin = 10
//var svg = d3.select("svg");
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom);
svg.style("border","5px solid red");
svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var group = svg.selectAll('g')
.data(data).enter()
.append('g')
.attr('transform',function(d,i) {
//console.log(i,d);
return 'translate('+(100*i)+',0)';
});
var box = group.selectAll('rect')
.data(function(d) {
return d;
});
box.enter()
.append("rect")
.attr("width", 30)
.attr("height", 30)
.attr('font-size',2)
.attr("x", function(d, i) {
//console.log(d);
return 60 + 2*d;
})
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
text box is black and no text shown.
You aren't appending any text. Text also can't be appended to a rectangle, so there is no need to apply font properties to a rectangle. Text can be appended to a g though. So we can use a parent g to hold both rectangle and text. Something like:
group.append("rect")...
group.append("text")...
The boxes are black because you haven't applied a fill. The default fill is black.
One box is missing, it should be 5 box but only 4 can be seen.
This is because when you enter the parent g elements, you select all g elements. This includes the one you've already appended (svg.append("g")). The enter selection is intended to create elements such that every item in the data array is paired with an element in the DOM. Since you already have a g in your selection, the enter selection will only create 4 new ones (representing data array items with indexes 1-4 but not 0).
Instead of selectAll("g") you could specify a class name or, in the event you simply want to enter everything and there isn't a need to ever update a selection: selectAll(null). The latter option will always return an empty selection, which will result in the enter selection containing one element per item in the data array.
Note, that the parent's datum is passed to appended children automagically, there is no need to use the .data method to pass this onward unless you are handling nested data.
Here's a snippet addressing issues in one and two:
test()
function test() {
var data = ["a","b","c","d","e"]
width = 800
height = 600
margin = 10
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.style("border","5px solid red");
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var group = svg.selectAll(null)
.data(data).enter()
.append('g')
.attr('transform',function(d,i) {
return 'translate('+(40*i)+',0)';
});
group
.append("rect")
.attr("width", 30)
.attr("height", 30)
.attr("fill","yellow")
group.append("text")
.text(function(d) { return d; })
.attr("y", 20)
.attr("x", 15)
.attr("text-anchor","middle");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
I also changed svg to refer to the parent g, the one with the margin applied. Before the g with the margin remained unused, along with the margin. I also modified the spacing to keep everything in view.
how can I add a arrow line to connect each other!
This can be done in many ways and really is a separate issue from the others, so I'll only quickly demonstrate one of many options. I'll modify your data structure a bit so that each datum has positional data and then add arrows using SVG markers:
test()
function test() {
var data = [{name:"a"},{name:"b"},{name:"c"},{name:"d"},{name:"e"}]
width = 800
height = 600
margin = 10
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.style("border","5px solid red")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("defs")
.append("marker")
.attr("id","pointer")
.attr("markerWidth", 10)
.attr("markerHeight", 10)
.attr("orient","auto")
.attr("refY", 5)
.append("path")
.attr("d", "M 0 0 L 10 5 L 0 10 z")
var group = svg.selectAll(null)
.data(data).enter()
.append('g')
.attr('transform',function(d,i) {
d.x = 40*i+15, d.y=30;
return 'translate('+(40*i)+',0)';
});
group
.append("rect")
.attr("width", 30)
.attr("height", 30)
.attr("fill","yellow")
group.append("text")
.text(function(d) { return d.name; })
.attr("y", 20)
.attr("x", 15)
.attr("text-anchor","middle");
links = [
{source: data[0], target: data[1]},
{source: data[0], target: data[2]}
]
svg.selectAll(null)
.data(links)
.enter()
.append("path")
.attr("d", function(d) {
var midX = (d.source.x+d.target.x)/2;
return "M"+d.source.x+" "+d.source.y+"Q"+midX+" "+200+" "+d.target.x+" "+(d.target.y+6);
})
.attr("fill","none")
.attr("stroke","black")
.attr("stroke-width",1)
.attr("marker-end","url(#pointer)");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

Behavior of chaining syntax in axes generation

I'm writing d3.js (d3 v4) code to generate a simple bar chart, which I managed to do, but I am confused about the behavior of the code in generating axes.
So basically, I encapsulated the chart code into a function that contains basic accessors.
Here is some code showing the core of my chart function.
function chart(selection){
selection.each(function(data){
var x = d3.scale.ordinal().rangeRoundBands([0, width], 0.5);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10);
x.domain(data.map(function(d) { return d.name; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
var div = d3.select(this)
var svg = div.selectAll("svg")
.data([data]).enter()
.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 + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-90)" );
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 5)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("fill", "#1CE6FF")
.attr("x", function(d) { return x(d.name); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); });
});
So I have two questions: 1) Is it okay to use multiple code blocks invoking svg.append("g") like shown above in the code?
2) When I replace the following code :
var svg = div.selectAll("svg")
.data([data]).enter()
.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 + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-90)" );
With this (which is basically merging the two blocks in a single chain code chunk):
var svg = div.selectAll("svg")
.data([data]).enter()
.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 + ")").append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-90)" );
...why is my y axis not showing at the right place? The <g class="y axis">...</g> tag appears nested into of a <text> tag itself nested in the x axis tag. Why the 2-block code vs single block code not behaving the same?
The structure of the code defines the structure of the resulting SVG. In the function chart() the variable svg holds the reference to the lastly appended <g> not to the previously appended <svg> itself. Note, how all statements after its first creation append to that same outer group of svg. That's one perfectly fine way of doing this.
If you concatenate the statements, however, like you did in your last snippet, the value of svg changes because the right hand side, i.e. the return value of the expression, changes. Since you last selected all text elements of the appended x-axis svg contains exactly those texts. Given that, it is pretty easy to understand that this breaks the rest of the code which will now append other contents to the text elements. Even if it was syntactically valid, which it is not, it would most likely still break the layout.
If you want to rearrange the code structure you have to make sure to have the correct references at the time they are needed.

is it possible to color groups differently

I am planning to have different colors for each element in the .I was just exploring if this is possible.
My code:
var svg = d3.select("#graphid").append("svg")
.style("margin-left","30px")
//.style("background-color","lavender")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var focus = svg.append("g")
.attr("class", "focus")
.attr("id","focusid")
.style("background-color","#F8FCFB")
//.call(zoom)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var fo= d3.select("#focusid").style("background-color","azure");
console.log(fo);
var context = svg.append("g")
.attr("id","contextid")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
var contx= d3.select("#contextid").style("background-color","lavender");
............../
..............//
.............///
the groups were not getting the colors set? What am i missing here?
To solve this you need to append a 'rect' to you SVG and fill that as you can't just fill a 'g'. So create a rectangle then append the focus to that rectangle. same with context :)
//create the rectangle to fill here
var rect = svg.append('rect')
.attr('id', 'rect')
.style('fill', 'red')
.attr("height", height)
.attr("width", width);
var focus = svg.append("g")
.attr("class", "focus")
.attr("id","focusid")
//.call(zoom)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
Ive done it for focus, leave it up to you to do context :)
Updated fiddle : http://fiddle.jshell.net/zua7L31d/6/
Finished both them off :
Added a new rectangle for the 'context'
var contextRect = svg.append('rect')
.attr('id', 'rect2')
//.style('fill', 'red')
.attr("height", rect2Height)
.attr("width", rect1Width)
.attr('x', 0)
.attr('y', rect1Height)
;
Final fiddle : http://fiddle.jshell.net/zua7L31d/7/
Hope that helped :)

How to fix shifted columns on histogram?

I am building a histogram with number of days as the x axis, but the columns are shifted. I want to bin the data on months, or more exactly, 30-day intervals. For some reason, when generating this graph the columns are not the right size, and the bars end up shifting a lot, as shown in the picture:
The bins are hardcoded like this:
var data = d3.layout.histogram()
.bins(20)
(values);
var y = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d.y; })])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.tickValues([0,30,60,90,120,150,180,210,240,270,300,330,360,390,420,450,480,510,540,570,600])
.orient("bottom");
var svg = d3.select("#graph").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 + ")");
var bar = svg.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
bar.append("rect")
.attr("x", 1)
.attr("width", x(data[0].dx) - 1)
.attr("height", function(d) { return height - y(d.y); });
bar.append("text")
.attr("dy", ".75em")
.attr("y", 6)
.attr("x", x(data[0].dx) / 2)
.attr("text-anchor", "middle")
.text(function(d) { return formatCount(d.y); });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
What is the correct way to solve this issue?

Resources