I am trying to add transition() and append("title") to allow animation and hover over tooltip at the same time on my scatterplot. But when I add transition, tooltip no longer pops out.
const circles = g.merge(gEnter).selectAll('circle').data(data);
circles
.enter().append('circle').merge(circles).transition().duration(2000)
.attr('cy', d => yScale(yValue(d)))
.attr('cx', d => xScale(xValue(d)))
.attr('r', d => (d.Score*1.29)+1)
.attr("fill", d=>colorArray(d.Type))
.append("title").text(d => (d.Country_or_region+ "\n" +"Rank: "+ d.Overall_rank))
Everything after .transition() is a transition, and there is no .append() method for a transition. If you inspect the console you'll probably see an error.
That said, if for whatever reason you want append the title only after the transition started, you can use the .on("start") method:
.on("start", function() {
d3.select(this).append("title").text("foo")
})
Here is the demo, there is no title at the beginning. After the transition starts you'll get the title when hovering over the circle:
const circle = d3.select("circle");
circle.transition()
.duration(20000)
.delay(2000)
.attr("cx", 100)
.on("start", function() {
d3.select(this).append("title").text("foo")
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg>
<circle r="20" cx="20" cy="100"></circle>
</svg>
Related
I am new to D3 and I am trying to add text inside rectangle using D3 v5. I have written following code for same.
rootSVG = d3.select('.rootSVG')
.selectAll('rect')
.data(data)
.enter()
.append('g')
.attr('transform', (d, i, elements) => {
return 'translate(0, ' + i * 21 + ')';
});
rootSVG.append('rect')
.attr('height', 20)
.attr('width', 100)
.style('fill', 'green')
.on('mouseover', (d, i, elements) => {
d3.select(elements[i])
.transition()
.duration(500)
.style('fill', 'red');
})
.on('mouseout', (d, i, elements) => {
d3.select(elements[i])
.transition()
.duration(500)
.style('fill', 'green');
});
rootSVG.append('text')
.attr('x', 10)
.text((d, i, elements) => {
return d.name;
});
I am getting the following results in the browser.
As you can see in above picture, rectangle elements are getting placed fine but text elements are off. Why this behaviour is happening even though they belong to same group? How to I make sure that text always stays inside the rectangle?
JSFiddle https://jsfiddle.net/ysm0hfzn/4/
This is due to how SVG draws text.
It's somewhat different from playing with divs and all in "traditional" HTML: you could think of it as an actual graphics framework.
The problem here is that the text element's baseline is, by default, its bottom edge. Which means that, when drawing text at (0, 0), the text element's bottom-left corner will be at (0, 0)
You could change your text elements' dominant baseline by adding the following CSS to your code:
g > text {
dominant-baseline: text-before-edge;
}
This would allow your texts to be drawn inside your gs, vertically.
As a side note, the horizontal-axis equivalent of the baseline for text is determined by the text-anchor property. If you wanted to center your text inside their g, you could simply:
anchor the text elements in to their middle: text-anchor: middle
center the anchor within your g: x="50"
Here's a code snippet demonstrating how to use the properties I mentionned in your example.
g > text {
dominant-baseline: text-before-edge;
text-anchor: middle;
}
<svg>
<g>
<rect width="100" height="20" fill="LimeGreen"></rect>
<text x="50">Letter</text>
</g>
<g transform="translate(0, 22)">
<rect width="100" height="20" fill="LimeGreen"></rect>
<text x="50">Number</text>
</g>
</svg>
I have a donut chart that I want to use, which is based on this.
I'm trying to create a function, when a user hovers over a certain path of the donut chart, the stroke color appears.
Nevertheless, I tried to edit a portion of the code but somehow the "mouseover" and "mouseout" handlers are ignored (not working)? I tried researching the Internet, but I couldn't find a solution.
Below is a portion of the code:
var path =
svg.select('.slices')
.datum(data)
.selectAll('path')
.data(pie)
.enter().append('path')
.attr('fill', function(d) {
return colour(d.data[category]);
})
.attr('d', arc)
.on('mouseover', function() {
console.log("mouseOver");
})
.on('mouseout', function(d) {
console.log("mouseOver");
});
I am trying to have a mouse hover event where the circle radius gets bigger, and the corresponding data label increases font size. The data labels are on the circle, in the graph itself. So my idea was to have two functions, one to style the circles nice and smooth with a transition, and use a separate function with tweening to style the text. I want to call both functions at the same time and have them only style objects with data binding matching its 'id'. Id is just an index that I parsed from the .tsv. The idea is both the text and the circle have the same 'id'.
!boring circle svg code above
.on('mouseenter', function(d) {circle_event(this); text_event(this);})
function circle_event(id) {
if (d3.select(this).data==id) {
d3.select(this)
.transition()
.attr('r', radius*1.5)
.duration(500);
}
}
function text_event(id) {
if (d3.select(this).data==id) {
d3.select(this)
.transition()
.styleTween('font', function() {return d3.interpolate('12px Calibri', '20px Calibri' )
.duration(500);
}
}
For some reason when I hover over the circles, nothing happens. Dev tools didn't have any errors. If I had to guess, I have misunderstood how to use d3's select functions.
Thank you
EDIT
Please note I need to call the circle and text style functions simultaneously I will accept the answer that shows how to style the circle and its corresponding data_label text JUST by mousing over the CIRCLE. It seems this cannot be used as a circle and a text object concurrently. Other solutions aside from this-based are welcome.
It looks like you edited your question which changes things considerably. Here is one approach that could work or be modified to make it work:
// Assumes circle & text objects have the same .x, .y and .id data bound to them and that
// when created, they each have classes "circle-class" and "text-class" respectively
!boring circle svg code above
.on('mouseenter', function(d) {circle_event(d.x, d.y, d.id); text_event(d.x, d.y, d.id);})
function circle_event(x, y, id) {
d3.selectAll(".circle-class")
.filter(function (d) {
return (d.x === x) && (d.y == y) && (d.id == id);
})
.transition()
.attr('r', radius * 1.5)
.duration(500);
}
function text_event(x, y, id) {
d3.selectAll(".text-class")
.filter(function (d) {
return (d.x === x) && (d.y == y) && (d.id == id);
})
.transition()
.styleTween('font', function() {return d3.interpolate('12px Calibri', '20px Calibri' )
.duration(500);
}
Alternatively, if you structure the creation of the circle and text DOM elements such that they have a sibling relationship, you could get a reference to the text selection using d3.select(this.previousElementSibling) where this is the circle node that is being moused over (See here - How to access "previous sibling" of `this` when iterating over a selection?). Such an approach could use the code in my previous reply above.
Your primary issue is that this is not what you want it to be in your nested function. It is no longer bound to your circle DOM node that was being hovered over but instead is referencing the global window.
More info on the scoping of this in javascript can be found here:
Javascript "this" pointer within nested function
http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/
Try changing to this:
!boring circle svg code above
.on('mouseenter', function(d) {circle_event(this, d.id)})
function circle_event(selection, id) {
if (d3.select(selection).data==id) {
d3.select(selection)
.transition()
.attr('r', radius*1.5)
.duration(500);
}
}
function text_event(selection, id) {
if (d3.select(selection).data==id) {
d3.select(selection)
.transition()
.styleTween('font', function() {return d3.interpolate('12px Calibri', '20px Calibri' )
.duration(500);
}
}
You should pass this in the function :
.on('mouseenter', function(d) {circle_event(this)})
function circle_event(item) {
d3.select(item)
.transition()
.attr('r', radius*1.5)
.duration(500);
}
function text_event(item) {
d3.select(item)
.transition()
.styleTween('font', function() {return d3.interpolate('12px Calibri', '20px Calibri' )
.duration(500);
}
You dont need d.id as this is already the selected circle
I'm testing a d3js treemap from a blog. Please see the live jsbin here.
I want to control the filling color of each rect for each small area. I don't know where can I control the color of rect. I found the following part is setting the color.
childEnterTransition.append("rect")
.classed("background", true)
.style("fill", function(d) {
return color(d.parent.name);
});
I try to remove the fill or change the color, the filling color is not working. For example I want to change all rect fill color to FFF, it is not working at all.
childEnterTransition.append("rect")
.classed("background", true)
.style("fill", function(d) {
return '#FFF';
});
You're setting the fill colour twice three times -- once in the "enter" chain for new elements, and then again in the "update" chain for all elements, and then a third time during the zoom transition. If you're only changing one of those pieces of code, the others may be replacing your setting.
Enter code (from your bl.ocks page):
childEnterTransition.append("rect")
.classed("background", true)
.style("fill", function(d) {
return color(d.parent.name); //change this
});
Update code: You can probably delete the entire update chain and just use the zoom function to update the values to the current zoom.
childUpdateTransition.select("rect")
.attr("width", function(d) {
return Math.max(0.01, d.dx);
})
.attr("height", function(d) {
return d.dy;
})
.style("fill", function(d) {
return color(d.parent.name); //change this
});
Zoom code:
zoomTransition.select("rect")
.attr("width", function(d) {
return Math.max(0.01, (kx * d.dx));
})
.attr("height", function(d) {
return d.children ? headerHeight : Math.max(0.01, (ky * d.dy));
})
.style("fill", function(d) {
return d.children ? headerColor : color(d.parent.name); //change this
});
Also, just to nitpick: Your "enter" selection (childEnterTransition) isn't actually a transition. If it was, there would be no point to setting the colour there and then re-setting it in update, because the update transition would just cancel the earlier transition. But because it isn't a transition, setting the colour there creates a starting value for the entering elements before you transition all the elements to the current value.
In Mike Bostocks example http://bost.ocks.org/mike/nations/ there is so much data that putting the names of the countries there would make it chaotic, but for a smaller project I would like to display it.
I found this in the source:
var dot = svg.append("g")
.attr("class", "dots")
.selectAll(".dot")
.data(interpolateData(2004))
.enter().append("circle")
.attr("class", "dot")
.style("fill", function(d) { return color(d); })
.text(function(d) { return d.name; })
.call(position)
.sort(order);
dot.append("title")
.text(function(d) { return d.name; });
But somehow a title never shows up. Does anybody have an idea, how to display the name, next to the bubble?
As the other answer suggests, you need to group your elements together. In addition, you need to append a text element -- the title element only displays as a tooltip in SVG. The code you're looking for would look something like this.
var dot = svg.append("g")
.attr("class", "dots")
.selectAll(".dot")
.data(interpolateData(2004))
.enter()
.append("g")
.attr("class", "dot")
.call(position)
.sort(order);
dot.append("circle")
.style("fill", function(d) { return color(d); });
dot.append("text")
.attr("y", 10)
.text(function(d) { return d.name; });
In the call to position, you would need to set the transform attribute. You may have to adjust the coordinates of the text element.
Unfortunately grouping the text and circles together will not help in this case. The bubbles are moved by changing their position attributes (cx and cy), but elements do not have x and y positions to move. They can only be moved with a transform-translate. See: https://www.dashingd3js.com/svg-group-element-and-d3js
Your options here are:
1) rewrite the position function to calculate the position difference (change in x and change in y) between the elements current position and its new position and apply that to the . THIS WOULD BE VERY DIFFICULT.
or 2) Write a parallel set of instructions to setup and move the tags. Something like:
var tag = svg.append("g")
.attr("class", "tag")
.selectAll(".tag")
.data(interpolateData(2004))
.enter().append("text")
.attr("class", "tag")
.attr("text-anchor", "left")
.style("fill", function(d) { return color(d); })
.text(function(d) { return d.name; })
.call(tagposition)
.sort(order);
You will need a separate tagposition function since text needs 'x' and 'y' instead of 'cx', 'cy', and 'r' attributes. Don't forget to update the "displayYear" function to change the tag positions as well. You will probably want to offset the text from the bubbles, but making sure the text does not overlap is a much more complicated problem: http://bl.ocks.org/thudfactor/6688739
PS- I called them tags since 'label' already means something in that example.
you have to wrap the circle element and text together , it should look like
<country>
<circle ></circle>
<text></text>
</country>