Changing an html attribute based on its current value in D3 - javascript

I feel like I'm trying to do something fairly straightforward, but I can't seem to figure out how. I have a page displaying several bar graphs, and I'm trying to build a button that makes certain bars appear and disappear. The bars have labels, and when the button is toggled, I want half the bars to dissappear, the remaining bars to double in size and the labels for the bars to move down a little bit.
I'm using D3.js to draw my graphs, and I've got almost everything working, except these little labels. All I'm trying to do is increment their "y" attribute by about 15 pixels, but I can't seem to find a way to request this value before changing it!
Initially I thought
d3.selectAll(".bar-value-label").attr("y", function(d,i){ return d + 15;})
should work. However, when I logged what this "d" was, it turned out to be a reference to the data object initially used to create this element.
So, basically I'm just trying to find a way to get the current value and in- or decrement it. Is there a nice way to do this within D3?
Regards,
Linus
EDIT:
Okay, so I've found a way that works. I'm very doubtfull that this is the best/most efficient way, so answers are still very much welcome :)
var resp_labels = d3.selectAll(".bar-value-label-resp")[0];
resp_labels.forEach(function(d,i){
var old_y = parseFloat(d.getAttribute("y"));
d3.select(d).transition().attr("y", function(i){ return old_y - barheight/4 ;});
});

I haven't had a chance to check that it works, but I think that this is a slightly simplified version of your solution:
var resp_label = d3.select(".bar-value-label-resp");
var old_y = +resp_label.attr("y");
resp_label.transition().attr("y", old_y - barheight/4);

Related

Why are the text labels in d3 transforming to zero on pan/zoom?

I'm fairly new at d3 and I've built the code from a couple of sources so it's probably just that I have it initializing incorrectly but I'm not seeing what it is. The page I'm building dynamically fetches geojson from a database and renders a simple map with a label. This works correctly but whe I added code to give the map zoom/pan functionality the map behaves correctly but the transform on the text moves the label to 0 postition with the <g> element and I'm not sure why? When I zoom in the map and text increases in size as expected but the text position stays at 0/0.
Instead of dumping a lot of code here I've put together this fiddle so that it could easily be seen and tested.
I think that anyone more familiar with d3 probably knows what I've got wrong here. I would sure appreciate someone pointing it out to me. Thanks
See your problem saved in the fiddle:
const items = g.selectAll('g.item')
.data(bb.features)
.enter()
.append('g')
.classed('item', true);
items.append('path').attr(...)
items.append('text').attr(...)
Instead of entering path and text elements separately, enter a g container and then append path and text under the g.

moving path to front changes selection?

i'm struggling to understand the following behaviour: i have two maps (based on topojson-data, visualised through d3), and on mouseover over certain parts of map1, the corresponding parts of map2 should light up. i got it to work with changing the style (opacity or fill), but now i wanted to highlight the borders of each map-part.
as seen for instance here one needs to move the specific path to the front to make all the borders visible. this is no problem for the area where i move the mouse across (using this), but when i select the corresponding part of the other map, it works one time and after that other parts get selected - so my guess is something is messing with the selection.
here is the code:
.on("mouseover",function(d){
var old=d.properties.iso; //this is the identifying number of the map-part(s)
sel=svg2.selectAll("path")
.data(datastore2015.features)
.filter(function(d){return d.properties.iso==old;})
.node(); //here the corresponding part(s) get filtered
d3.select(sel.parentNode.appendChild(sel)).classed("high2",true); //and this moves it to front and highlights the borders
on mouseout, it just resets:
.on("mouseout",function(d){
svg2.selectAll("path").classed("high2",false);
when i log the data to the console it seems that each mouseover moves +1 entry through the dataset, starting by the first entry the mouse moved over. i could not figure out why this happens and how to avoid it.
i'd appreciate any ideas you could give me, mainly i'd like to understand what's going wrong and why.
thanks
so i found my error, calling the data-variable once again seems to have messed things up - somehow i was under the impression that i need it, but it works just fine this way:
sel=svg2.selectAll("path").filter(function(d){return d.properties.iso==old;}).node();
d3.select(sel.parentNode.appendChild(sel)).classed("high2",true);
sorry for the bother, i didn't see this possibility before.

d3 venn diagram labels layering

Im using this great article to produce a venn diagram with D3.
http://www.benfrederickson.com/venn-diagrams-with-d3.js/
It looks great but on occasion I get bubbles overlapping the the labels become hidden. Is there a way to make sure the text element is always on top? (see the picture below.. label A needs to be on top of circle B.
I found this good article but im struggling in how to implement this in the venn.
How can I bring a circle to the front with d3?
You should grab the latest code from master: this commit should fix the issue you had there https://github.com/benfred/venn.js/commit/4cb3bbef65b5b3c3ce02aee7d913e8814e898baf
Instead of having the 'A' label be overtop of the 'B' circle - it willnow move the label so that its in the certain of the 'A' region that isn't overlapped with 'B'. Some details are in this issue here: https://github.com/benfred/venn.js/issues/18
You might find it easier to work in actual layers. You can use g elements to create them. For example:
var lowerLayer = svg.append('g');
var upperLayer = svg.append('g');
Now anything you append to upperLayer will appear above anything you append to lowerLayer because the two g elements have been added to the DOM and are in a specific order.
Also check out this answer I wrote up for a similar question.

Tracking mouse position of a jqplot vertical line chart

I want to create a jqPlot line chart which has the ability to change orientation between vertical and horizontal orientation. I was able to achieve this using CSS rules, by rotating the div element containing the chart.
My work up to now: http://jsfiddle.net/GayashanNA/A4V4y/14/
But the problem is I also want to track the mouse-pointer and mouse clicks on points on chart after the orientation is flipped because i want to annotate those points. I am unable to do this when the chart is in vertical orientation. Can anyone suggest a method to do this? Or am i approaching the problem in a wrong way?
(Note: I am able to do this in horizontal orientation, you can observe it if you try to click on a point on the above chart.)
Thanks and help is much appreciated.
I've never used jqPlot, but I guess your problem is trying to use css rotate(), since the cursor plugin is using the mouse position to determine where to draw the lines, and element's size doesn't change when transformed by rotate(), it still have the same width and height values.
If you take a look at the code, you will see:
if (c.showVerticalLine) {
c.shapeRenderer.draw(ctx, [[gridpos.x, 0], [gridpos.x, ctx.canvas.height]]);
}
if (c.showHorizontalLine) {
c.shapeRenderer.draw(ctx, [[0, gridpos.y], [ctx.canvas.width, gridpos.y]]);
}
So it seems like the library is always drawing the lines based on mouse position over the original element, which of course, won't match the position after being transformed by rotate(), and XY coordinates are going to be transformed to YX after rotate().
I would try to change the size of your original element, though I don't know if the library lets you specify in which sides are the labels going to be drawn.
I finally found a solution for the problem. But i had to change jqPlot library to achieve this. To help anyone else who run in to the same problem, i'll put my solution here.
First i had to insert the following code in to the jqPlot class of the jquery.jqplot.js file, which is the core library.
function jqPlot() {
//add the following code segment
var verticallyOriented = false;
this.setVertical = function(state){
verticallyOriented = state;
}
//don't change other code that isn't mentioned here
//now you have to change the logic in the getEventPosition function
//to make sure the new orientation is detected
function getEventPosition(ev) {
//change the line starting with var gridPos = ...
//to the following code segment
//depending on the orientation the event position calculating algorithm is changed
if(verticallyOriented){
var gridPos = {x:ev.pageY - go.top , y:plot.eventCanvas._elem.height() - ev.pageX + go.left};
} else {
var gridPos = {x:ev.pageX - go.left, y:ev.pageY - go.top};
}
//no change to other code is needed
}
}
You can view a working example here: http://jsfiddle.net/GayashanNA/yZwxu/
Gist for the changed library file: https://gist.github.com/3755694
Please correct me if i have done something wrong.
Thanks.

using clearRect() to overwrite text output on canvas?

I'm making a little game on jsbin and everything so far is going well but I'm have a slight problem. The goal of the game is to click the randomly appearing circle as many times as possible in one minute. I want it to output the time left and the score in the corners, and I have done so. The problem is that they are overwriting each other. This is because to prevent flickering I decided not to use
c.clearRect(0,0,canvas.width,canvas.height);
instead drawing a clearRect just over the circle when its clicked. I want to do a similar thing with text. I used this line:
c.clearRect(0,fontSize,c.measureText(timeLeft),fontSize);
this should work but it has no effect. I've tried everything, but I don't know what's wrong with this line. My only other theroy is that is in the wrong spot in the code, but I haven't found a problem with that.
Here is the link to the current version I'm working on:
http://jsbin.com/touchgame/10/edit
Thanks!
measureText() returns an object with a width property, so you need to use c.measureText(timeLeft).width.
Also, you decreased the timeLeft and then called clearRect, which will clear a rectangle based on the new width for timeLeft. You want to clear based on the width for the "old" timeLeft value and then decrease the timeLeft:
if (timeLeft < 1){
c.clearRect(0,fontSize,c.measureText(timeLeft).width + 5,fontSize*1.5);
timeLeft--;
//clear over time output
console.log(c.measureText(timeLeft));
}
This should work. Because the way drawing text works, it's not trivial to know exactly the bounding box of the text, so that's why we clear a larger area than fontSize. You could use c.textBaseline = 'top', which would place the text with the top coordinate at the y you specify instead of the baseline of the text at y.
Finally, I think it's an easier approach to clear the canvas completely and redraw everything when you want to update the graphics. If you clear the canvas and then immediately redraw evrything you won't get any flickering. The performance hit of redrawing everything is usually neglible in most cases, and it makes things a lot simpler.

Categories

Resources