d3 enter/append/exit/remove sequence only appending - javascript

A set of rectangles is drawn initially with the following enter/append/exit/remove sequence, no problem. When I pass different data (meant to replace the existing data entirely) the new rectangles are drawn on top of the existing rectangles.
I am selecting "lgnds" instead of rect, because I have drawn other rectangles that I don't wish to disturb.
var svg = d3.select("#graph").append("svg")
elements = svg
.selectAll("lgnds")
.data(data, function(d){return d;});
elements
.enter()
.append("rect")
.attr("width", 15)
.attr("height", rectHeight)
.attr("x", 5)
.attr("y", function (d,i){return ((i*rectHeight)+(gap*(i+1)));})
.style("fill", function(d){ return d.color;});
elements
.exit()
.remove();

Related

D3 - How can I show/hide a text element when hovering a circle element created from different attributes of the same data entry?

I have some data with 2 attributes: colour and value
I use the D3 enter selection to create circle elements, and append them to the body of the page. Their fill colour is determined by the "colour" attribute.
Then, I append text elements to the page. The text contents are determined by the "value" attribute.
Here is what I am working with:
// Set up svg element
var svg = d3.select("body")
.append("svg")
.attr("width", 300)
.attr("height", 300)
.style("background", "lightblue");
var dataset = [
{"colour":"red", "value":"First set of text"},
{"colour":"green", "value":"Second attempt"},
{"colour":"blue", "value":"Third and final!"}
];
// Create circles from the data
// On mouseover, give them a border (remove on mouseout)
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("r", 40)
.attr("cy", function(d, i) { return i*80 + 40; })
.attr("cx", 50)
.style("fill", function(d) {return d.colour;})
// HERE
// Can I somehow show and hide the text component that is
// associated with this circle when the circle is hovered, rather
// than the text itself?
.on("mouseover", function(d) {
d3.select(this).style("stroke", "black")
.style("stroke-width", 2)
})
.on("mouseout", function(d) {d3.select(this).style("stroke", "none")});
// Now add the text for each circle
// Same thing with mouseover and mouseout
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.attr("text-anchor", "middle")
.attr("y", function(d, i) { return i*80 + 40; })
.attr("x", 50)
.style("opacity", 0)
.on("mouseover", function(d) {d3.select(this).style("opacity", 1)})
.on("mouseout", function(d) {d3.select(this).style("opacity", 0)})
.text(function(d) { return d.value;});
I would like for the text to be hidden, until the associated circle is hovered over. How can I connect the text element with a particular circle, so that I can toggle whether the text is shown by hovering over the circle?
This fiddle below is an outline of what I am trying to do, and what I have got so far. I have the text showing up only when hovered, but not when the circle is hovered.
https://jsfiddle.net/aj4zpn6z/
There are several ways for achieving this. Since both circles and texts use the same dataset, my solution uses filter.
First, let's name the variables for the texts and circles:
var circles = svg.selectAll("circle")
//etc...
var texts = svg.selectAll("text")
//etc...
Then, inside the circles mouseover function, we filter the texts that have the same colour attribute:
.on("mouseover", function(d){
d3.select(this).style("stroke", "black").style("stroke-width", 2);
var tempTexts = texts.filter(function(e){
return e.colour === d.colour
});
tempTexts.style("opacity", 1);
});
This is your updated fiddle: https://jsfiddle.net/wxh95e9u/

How to add background shading to D3 line chart

I would like to add shading to the background of a D3 line graph. There would be different shades for different parts of the line. Here is an example
My approach is the add rectangle svg to the chart, but that doesn't seem to be working because I don't know how to make the width correspond with the data.
here is a jsfiddle
Here is an example of the rectangle creation:
svg.append("rect")
.attr("class", "shading")
.attr("x", d[1].date)
.attr("y", 80)
.attr("width", 20)
.attr("height", 20)
.attr("fill", "blue");
Am I on the right track? How do I find the width so that it corresponds with the data?
UPDATE: There will be multiple square of different widths, so I can't just grab the width of the entire svg.
You can do it like this:
//get all the ticks in x axis
//make a pair of it refer: d3.pair
var data = d3.pairs(svg.selectAll(".x .tick").data());
//make a color category
var c10 = d3.scale.category10();
//to svg append rectangles
svg.selectAll("rect")
.data(data)//for the tick pair
.enter()
.append("rect")
.attr("class", "shading")
.attr("x", function(d){return x(d[0])})//x will be the 1st tick
.attr("y", 0)
.attr("width", function(d){return (x(d[1]) - x(d[0]));})//width will be the diff of 1st and 2nd tick
.attr("height", height)
.attr("opacity", 0.2)
.attr("fill", function(d,i){return c10(i)});//use color category to color the rects.
working code here
fill the svg first with the data, then after that get the width property, it should automatically calculate it

Draw SVG image on paths but as big as needed

So I would like to show an image on a path. The pathes are created via topojson coordinates. The points are on the right position on my map. So the next thing is to show a SVG image on that point.
I tried that with appending svg:image, but no chance. I also tried to bring it into the path with the same result. I nowhere can see that image. Here an example with an PNG image. Because at least that should work to exclude SVG issues:
var featureCollection = topojson.feature(currentMap, currentMap.objects.points);
svgmap.append("path")
.attr("id", "points")
.selectAll("path")
.data(featureCollection.features)
.enter().append("path")
.attr("d", path);
svgmap.append("svg:image")
.attr("class","svgimage")
.attr("xlink:href", "pics/point.jpg" )
.attr("x", -20)
.attr("y", -20)
.attr("width", 13)
.attr("height", 13);
Edit
svgimage.append("pattern")
.attr("id","p1")
.attr("patternUnits","userSpaceOnUse")
.attr("width","32")
.attr("height","32")
.append("image")
.attr("xlink:href", "pics/point.jpg" )
.attr("width", 10)
.attr("height", 10);
svgmap.append("g")
.attr("id", "points")
.selectAll("path")
.data(featureCollection.features)
.enter().append("path")
.attr("d", path)
.attr("fill", "url(#p1)");
But still not working.
Edit2
I mentioned that it is an issue with the size. So I now played a bit with the sizes and there I can see some more, but most of them are not fully imaged. Just some pieces of the cirle somehow. Strange thing. I keep on testing:
svgimage.append("pattern")
.attr("id","p1")
.attr("patternUnits","userSpaceOnUse")
.attr("width","10")
.attr("height","10")
.append("image")
.attr("xlink:href", "pics/point.jpg" )
.attr("width", 15)
.attr("height", 15);
Here a picture of the current result (jpg): http://i.imgur.com/T58DA1j.png not yet perfect.
This is when I increase the pointRadius (this is now a SVG): http://i.imgur.com/Z7nZUWk.png
The solution is pretty easy. The size of the picture was just not correctly set. Also the userSpaceOnUse needs to be deleted and if needed you can set the creation position with x and y:
svgimage.append("pattern")
.attr("id","p1")
.attr("width","10")
.attr("height","10")
.append("image")
.attr("xlink:href", "pics/point.jpg" )
.attr("width", 5)
.attr("height", 5)
.attr("x", 1)
.attr("y", 2);
and in the second part it is important to set the pointRadius. You can set it directly on the path or in the definition. If you want to use different sizes later on it makes more sense to set it in the path directly:
.attr("d", path.pointRadius(3.5))

d3.js create objects on top of each other

I create rectangles in my SVG element using this code:
var rectangles = svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect");
rectangles.attr("x", function (d) {
return xScale(getDate(d));
//return xScale(d.start);
})
.attr("y", function (d, i) {
return (i * 33);
})
.attr("height", 30)
.transition()
.duration(1000)
.attr("width", function (d) {
return d.length;
})
.attr("rx", 5)
.attr("ry", 5)
.attr("class", "rectangle")
.attr("onclick", function (d) {
return "runaction(" + d.start + ")";
});
How can I create new rectangles on top of the previous ones?
This is an answer to this question I got from Scott Murray, author of great introductory tutorials for d3.js http://alignedleft.com/tutorials/d3/ which helped me a lot with understanding its functionality. I hope he won't mind me putting his answer here for everyone's benefit.
Thank you very much Scott!
And yes, that's absolutely possible. Taking your example, let's say you want to draw one set of circles with the dataset called "giraffeData" bound to them. You would use:
svg.selectAll("circle")
.data(giraffeData)
.enter()
.append("circle");
But then you have a second data set (really just an array of values) called "zebraData". So you could use the same code, but change which data set you reference here:
svg.selectAll("circle")
.data(zebraData)
.enter()
.append("circle");
Of course, this will inadvertently select all the circles you already created and bind the new data to them — which isn't really what you want. So you'll have to help D3 differentiate between the giraffe circles and the zebra circles. You could do that by assigning them classes:
svg.selectAll("circle.giraffe")
.data(giraffeData)
.enter()
.append("circle")
.attr("class", "giraffe");
svg.selectAll("circle.zebra")
.data(zebraData)
.enter()
.append("circle")
.attr("class", "zebra");
Or, you could group the circles of each type into a separate SVG 'g' element:
var giraffes = svg.append("g")
.attr("class", "giraffe");
giraffes.selectAll("circle")
.data(giraffeData)
.enter()
.append("circle");
var zebras = svg.append("g")
.attr("class", "zebra");
zebras.selectAll("circle")
.data(zebraData)
.enter()
.append("circle");
I'd probably choose the latter, as then your DOM is more cleanly organized, and you don't have to add a class to every circle. You could just know that any circle inside the g with class zebra is a "zebra circle".

how to use svg:defs to create rect with same height in d3.js?

I am working on a d3.js project where I am displaying a number of rectangles to be the same height. The rectangles are connected to a input[type=number] that adjust the height of each group of rectangles. To make animation easier (so I only have to manipulate the svg:defs onchange of the number input), I would like to be able to specify the height of a group of rectangles with a svg:def tag like this:
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500);
svg.append("defs").selectAll(".rectdef")
.data(data).enter()
.append("rect")
.attr("class", "rectdef")
.attr("id", function (d, i){return "rect" + d.name;})
.attr("x", 0) // overridden below
.attr("width", 0) // overridden below
.attr("y", 0) // overridden below
.attr("height", function (d, i){return d.height});
and then to be able to just refine placement x, y and width of the rectangles with something like this:
svg.selectAll(".bar")
.data(data).enter()
.append("use")
.attr("class", "bar")
.attr("xlink:href",function (d){return "#rect"+d.type;})
.attr("x", function(d) { return d.x })
.attr("width", function (d) {return d.w;}) // this does NOT adjust width!
.attr("y", function (d) {return 0;});
This snippet correctly changes the x and y coordinates but it does not properly change the width! Any ideas what's wrong here? Is this a browser issue (I'm using Chrome 24.0.1312.52)? Is width not editable like this on an svg:use tag?
There aren't any problems with the data (I've checked that) and I have been able to confirm that the animation does work correctly.
If you point a <use> element at a <rect> the width/height of the <use> are ignored according to the SVG specification
I recomment you put the <rect> in a <symbol>, and then have the use reference the symbol. That way the width/height of the use will apply to the rect. You probably want to make the rect's width/height 100% within the symbol.
In other words, something like this should work:
svg.append("defs").selectAll(".rectdef")
.data(data).enter()
.append("symbol")
.attr("class", "rectdef")
.attr("id", function (d, i){return "rect" + d.name;})
.append("rect")
.attr("x", 0) // overridden below
.attr("width", "100%") // overridden below
.attr("y", 0) // overridden below
.attr("height", function (d, i){return d.height});
svg.selectAll(".bar")
.data(data).enter()
.append("use")
.attr("class", "bar")
.attr("xlink:href",function (d){return "#rect"+d.type;})
.attr("x", function(d) { return d.x })
.attr("width", function (d) {return d.w;}) // this correctly adjusts width!
.attr("y", function (d) {return 0;});

Categories