Making a bar graph in d3. I have 30+ bars, with 30+ corresponding labels on x-axis. I would like x-axis labels to be hidden when the page loads (this is working), AND APPEAR only if user cursors over the corresponding bar (svg rect object). To do this I am assigning an id to each rect and each text element. When user cursors over rect, text will appear for ONLY the selected (mouseover'd) rect.
I can assign id to rects, but not for text. Code:
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("id", function(d){
return d.slug; // slug = label downcased, this works
}); // each rect has unique id
However, similar code for my text element on x-axis doesn't assign an id?!
svg.append("g")
.call(xAxis)
.selectAll("text")
.attr("id", function (d){ // inspect text element shows no ID.
return d.slug; // text doesn't have any id
})
.style("text-anchor", "end")
.attr("opacity", 0.2);
How can I assign a unique id to my text elements in x-axis? Thank you!
The problem is that no data is bound to the x axis ticks and therefore d is undefined -- you should actually get an error message when running your code.
In this particular case, you can use the index to get the relevant data item like so.
svg.append("g").call(xAxis)
.selectAll("text")
.attr("id", function(d, i) { return dataset[i].slug; });
Note that this will only work if the number of axis ticks is the same as the number of data items.
Related
My implementation for Brush & Zoom functionality in my d3 line chart is not working as expected,
I followed this link - https://bl.ocks.org/EfratVil/92f894ac0ba265192411e73f633a3e2f,
Problems what I am facing is -
chart is not showing all the values, I have 4 data but it only shows 3 data
onClick of dot I am showing the rect which is not moving with the brush functionality
minor thing but chart always goes out of the box
My code sandbox - https://codesandbox.io/s/proud-firefly-xy1py
Can someone point out what I am doing wrong? thanks.
Please suggest me what I am doing wrong, thanks.
Your first point is going behind your clip area. For example, if you right click on the first visible circle and inspect element you will see all 4 circle elements are present in the dom. The first circle element is behind the axis.
This means you have to move your plot to the right. Unfortunately, the way you have coded the chart you have not appended a g element for the main chart and then appended the circles and path to that g element. As a result this has to be done in multiple places.
First we adjust your clip path as:
svg
.append("defs")
.append("SVG:clipPath")
.attr("id", "clip")
.append("SVG:rect")
.attr("width", containerWidth)
.attr("height", height)
.attr("x", 40)
.attr("y", 0);
next we adjust your circles
scatter
.selectAll(".foo")
.data(data)
.enter()
.append("circle")
.attr("class", "foo")
.attr("transform", "translate(40,0)")
and then your line
scatter
.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line)
.attr("transform", "translate(40,0)");
You will have to account for this 40 px translate for your other elements as well. Although I am having a hard time destructuring your svg. I think this should give you the idea though. Check the axis matches the time points as well.
Check the code sand box
Update
To make the rectangles move with the brush, you will have to add code to your brushed const function to recalculate the x, y, width and height using the updated scales.
Update2
After going through the codesandbox presented in the comments I was able to add the code to update the rectangles to the brushed const as below to make the rects also move with the brushing:
// update rectangles
scatter
.selectAll(".rect-elements")
.attr("x", d => {
console.log(d);
return xScale(d.startTime) - 12.5;
})
.attr("y", 0)
.attr("width", 24)
.attr("height", height + 5);
Full working Code Sandbox.
Currently learning d3.js v5
I am working with a graph that displays a tooltip when an area of the graph is hovered over, along with highlighting the graph area selected. The issue I have noticed is that whenever any bar or line is hovered over, the tooltip won't display, and the area is no longer highlighted until I move off that area.
I was wondering if there is a way to have the mouseover element read the SVG element under the bars and lines or if I also need to add event listeners to the bars and lines.
I could potentially add another rect element that is invisible over all the other elements so that it is triggered first, however I also want to add interactivity to the bars and lines in the graph.
d3.csv("test.csv").then(function(dataset) {
backBar.selectAll("rectBack")
.data(dataset)
.enter()
.append("rect")
.attr("class","rectBack")
.attr("x",(d)=>xScale((d.year)))
.attr("y",margin.top)
.attr("width",(w)/dataset.length)
.attr("height",h-topBottom)
.attr("opacity",0)
.attr("fill","lightgrey")
.on("mouseover",function(d){
d3.select(this)
.attr("opacity",0.3);
showTooltip(d);
})
.on("mouseout",function(){
d3.select(this)
.attr("opacity",0);
removeTooltip();
})
function showTooltip(d){
d3.select("body")
.append("div")
.attr("class","tooltip")
.attr("opacity",0.5)
.html("<h5>"+d.year+"</h5>")
.style("top", yLineScale(d.line1) + svgPos.offsetTop)
.style("left", xScale(d.year) + svgPos.offsetLeft)
console.log(d);
}
function removeTooltip(){
d3.selectAll(".tooltip")
.remove("*");
}
}
svgPos is a variable that holds the coordinates of the SVG
yLineScale and xScale are both scales for the X and Y axis respectively
Link to image of graph: https://imgur.com/a/7rJFQjd
tried to create a scatterplot which can be zoomed but only the axis is getting zoomed and not the data. Not able to figure out whats wrong anybody any help with this one?.
github link of project : scatterplot with zoom
I see two separate issues with the way the zoom is working:
You are not selecting the <circle>s (points in the scatter plot) correctly when zooming. Consequently when you zoom only the axes are changing (as you described). An easy way to fix this is to give each <circle> a class (e.g. class="dot"), and then use that to select them.
First add the class="dot" to each of your circles (line ~140):
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
Then update the function zoom to select them correctly (line ~195):
svg.selectAll(".dot") // <---- select all circles with class "dot"
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.close); });
Right now the zoom only occurs when you try to zoom in on an axis or individual point. If you want a user to be able to zoom in no matter where their mouse is over your scatter plot, you can add a background <rect> that will make sure the zoom event is detected for the SVG.
svg.append("rect")
.style("fill", "#fff")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
Making these two changes fixes the zoom.
I can successfully display some points on a openlayers basemap using d3.js however I want to actually display icons (at the moment maki png icons) instead of an svg point.
is it possible to load a png/jpg/svg image to a map using d3.js?
So far I can load the icon directly onto the svg but all the markers locate on the same spot so I think I'm having a problem converting the coordinates properly.
var feature = svg.selectAll("path")
.data(amenities.features)
.enter()
.append("svg:image")
.attr("xlink:href", "maki/renders/post-18#2x.png")
.attr("x", function(d, i) {return amenities.features[i].geometry.coordinates[0]})
.attr("y", function(d, i) {return amenities.features[i].geometry.coordinates[1]})
.attr("width", "20")
.attr("height", "20")
.attr("class", "amenity");
Previously I have been able to create an svg with image background inside it using a 'pattern' to show the image so that is also a possibility but I couldn't translate the code to use it with the geographic aspect of d3.
I know I'm writing the icons at the moment to the 'svg' div, so they don't transform correctly when I zoom in and out. I'm aiming to write the images to the 'g' div, as in have code like:
var feature = g.selectAll("path")
but when I use this line, the elements appear on the document but the icons don't actually render on the map.
Thanks in advance.
There a few issues here. First, I'm not sure you fully grasp how d3 selections works, as indicated by the fact that you are binding amenities.features to your selection and then accessing it for the x and y attributes via an index. See How Selections Work for more details on this. In addition, you need to translate the geographic coordinates of the features to screen coordinates by passing them through your geographic projection function. This should get you close:
// more projections: https://github.com/d3/d3-geo-projection/
var projection = d3.geoAlbers();
var amenities = svg.selectAll('.amenities')
.data(amenities.features);
amenities.enter().append('image');
amenities
.attr("class", "amenities")
.attr("xlink:href", "maki/renders/post-18#2x.png")
// The data is already bound so use it instead of the index. Als,
// you need to translate geo coordinates to screen coordinates by
// passing them through your projection function.
.attr("x", function(d,i) {return projection(d.geometry.coordinates)[0];})
.attr("y", function(d,i) {return projection(d.geometry.coordinates)[1];})
.attr("width", "20")
.attr("height", "20")
I don't think I appropriately used groups but I think the key is having the transform then translate thing in there.
So my example is http://bl.ocks.org/mpmckenna8/b87df1c44243aa1575cb.
But because I didn't use groups properly I don't know that the icons would handle zooming like you seem to want. In my example I just append the images to the circles I've created.
.attr("transform", function(d) { return "translate(" + projection(d.geometry.coordinates) + ")"; })
.attr('opacity',.3)
.attr('fill', '#fad959')
when i do this :
var link = svg.selectAll('.link')
.data(links)
.enter().append('path')
.attr('class', 'link')
.attr('d', diagonal)
There is no node with the .link class. So selectAll returns en empty selection. But i've found that, when you call this for the first time, you can selectAll('whaterverYouWant')
That is because D3 doesn't matter about what you select, as you provide the tag name and the classes later .append('path'), .attr(class ...).
And, if you want to select elements that already exist, i read in the doc that .enter returns a placeholder selection. But if it returns a selection of placeholders (anonymous tags with .link class ?), there is no point to append a path to a path.
When i call .append, it does what i want, i.e. append a path to svg. But i don't understand the logic behind that. (I'm glad it works though, because d3 is powerful)
So, ok i selectAll('anything') and append what i want, regardless of what i selected. But if i try this:
d3.select('#savestring-debug')
.selectAll('div')
.data(debugobjs)
.enter().append('span')
.attr('style', function(d) { return 'background:#'+d.color })
.text(function(d) { return d.aff });
This would create placeholders for divs, but i append spans. Actually spans are created but i'm still looking for my divs ;)
So, what is the principle behind selectAll >> data >> enter >> append ?
thanks
The principle behind selectAll > data > enter > append is explained pretty well by
Mike Bostock here: http://bost.ocks.org/mike/join/ where he explains the concept of the data-join. I can't speak with any authority on the right way to use selectAll, but the way I use it is to select all of the elements I am going to be modifying-appending-removing within the part of the SVG that I need to modify.
So if I'm working with "rects" in a certain area, I'll do something like this:
var svg = d3.select('#graphID')
.append("svg")
.attr("width", 300)
.attr("height", 500);
var graphGroup = self.svg.append("g");
//...Inside a render function
//just want all the "rect" elements in graphGroup
var rects = graphGroup.selectAll("rect")
.data(dataset);
//depending on dataset new rects will need to be appendend
rects.enter()
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 0)
.attr("height", 0)
//all rects are transitioned to new co-ordinates
rects.transition().duration(500)
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d);
})
.attr("width", xScale.rangeBand())
.attr("height", function(d){
return yScale(d);
})
//rects that have no data associated with them are removed
rects.exit()
.transition()
.duration(500)
.attr("x", -xScale.rangeBand())
.remove();
With the idea that I could have other rects in the SVG that do not belong to graphGroup. I just selectAll the rects in a certain area and work on them when needed.
This is a great question and a slightly odd property of D3. If you look carefully how anything is done in D3 you'll notice that everything is added by appending to what is previously created. So the logic behind having the svg.selectAll('whatever class of stuff you're going to add') is that you are kinda making a placeholder for where whatever you are about append to go. It's like the svg is a wall and you're hanging hooks on the upper ridge for you to THEN hang your paintings from. If you don't have the selectAll, I just tried this, you will still append whatever you were gonna make to the page, but it won't be appended to the svg.
The data-->enter-->append is basically saying for each element in the larger data file that you are passing into the data function, make a new element, and append this element to my selection with such and such properties (set when you use the .attr).