gayathri
gayathri

Reputation: 15

d3 - unable to add select/option drop down to svg-g element

I am intending to add a circle pack, radio and drop down menu to a SVG and toggle between these (on the same svg).

I wasn't able to add the select/option dropdown menu to the SVG that is created with width/height of 600/600. In the element console, I am able to see the g-element and the drop down select option. But on the screen, the g#menu has a 0x0 size? Why is this so? Any pointers/inputs?

NOTE: Here I am trying to add the dropdown menu dynamically using D3.js instead of hardcoding a selectio/option or div in the body element. I do this to enable toggling between different views.

var svg1 = d3.select("body")
            .append("svg")
            .attr("width", 600)
            .attr("height", 600)
            .append("g")
            .attr("transform", 
              "translate(0,0)"));  
var buttonNames = ["button 1", "button 2", "button 3", "button 4"];
var menug = svg1.append("g")
                .attr("id","menug")
                .attr("transform","translate(60,60)");

var menu = d3.select("#menug")
             .append("select")
             .attr("id", "menu");                    


menu.selectAll("option")
        .data(buttonNames)
    .enter().append("option")
        .text(function(d){return d;});

Upvotes: 0

Views: 5083

Answers (2)

Gerardo Furtado
Gerardo Furtado

Reputation: 102174

This question is so common that I created this example in the documentation. The example deals with SVG elements incorrectly appended to an SVG, but the same reasoning applies to HTML elements being appended to an SVG: you have to know what elements can have children and what kind of children they can have. More details at the bottom of this post.

In a nutshell: you cannot append any HTML element to an SVG (unless using foreignObject). The element will show up in the console, but won't work.

Note: I'm answering your written question and ignoring your code because, funny enough, in your code, you are correctly appending the HTML element to the body.


Correctly appending an SVG element

This is a relatively common mistake: You created an rect element, in a bar chart for instance, and you want to add a text label (let's say, the value of that bar). So, using the same variable that you used to append the rect and define its x and y position, you append your text element. Very logic, you may think. But this will not work.

How does this mistake occur?

Let's see a concrete example, a very basic code for creating a bar chart (fiddle here):

var data = [210, 36, 322, 59, 123, 350, 290];

var width = 400, height = 300;

var svg = d3.select("body")
    .append("svg")
    .attr("width", width)
    .attr("height", height);

var bars = svg.selectAll(".myBars")
    .data(data)
    .enter()
    .append("rect");

bars.attr("x", 10)
    .attr("y", function(d,i){ return 10 + i*40})
    .attr("width", function(d){ return d})
    .attr("height", 30);

Which gives us this result:

enter image description here

But you want to add some text elements, maybe a simple value to each bar. So, you do this:

bars.append("text")
   .attr("x", 10)
   .attr("y", function(d,i){ return 10 + i*40})
   .text(function(d){ return d});

And, voilà: nothing happens! If you doubt it, here is the fiddle.

"But I'm seeing the tag!"

If you inspect the SVG created by this last code, you're gonna see this:

enter image description here

And at this point a lot of people say: "But I'm seeing the text tag, it's appended!". Yes, it is, but this doesn't mean it will work. You can append anything! See this, for example:

svg.append("crazyTag");

It will give you this result:

<svg>
    <crazyTag></crazyTag>
</svg>

But you don't expect any result just because the tag is there, do you?

Append SVG elements the correct way

Learn what SVG elements can hold children, reading the specifications. In our last example, the code doesn't work because rect elements cannot contain text elements. So, how to display our texts?

Create another variable, and append the text to the SVG:

var texts = svg.selectAll(".myTexts")
    .data(data)
    .enter()
    .append("text");

texts.attr("x", function(d){ return d + 16})
    .attr("y", function(d,i){ return 30 + i*40})
    .text(function(d){ return d});

And this is the outcome:

enter image description here

And here is the fiddle.

Upvotes: 4

Mr. A
Mr. A

Reputation: 1231

Try this out

var buttonNames = ["button 1", "button 2", "button 3", "button 4"];
var l=4;
for(i=0;i<buttonNames.length;i++){if(l<buttonNames[i].length)l=buttonNames[i].length};
var width = 400, height = 300;
l=l*10;
var svg = d3.select("body")
	.append("svg")
	.attr("width", width)
	.attr("height", height)
  .append("g")
  .attr("class","dropdown");
select = svg.append("g")
  .attr("class","select");
select.append("rect")
  .attr("x", 10)
	.attr("y",  10 )
	.attr("width", l)
	.attr("height", 30);
select.append("text")
  .attr("x", 15)
	.attr("y",30 )
  .attr("id","mydropdown")
	.text( buttonNames[0]);
  
var options = svg.selectAll(".myBars")
	.data(buttonNames)
	.enter()
	.append("g");
	
options.attr("class", "option").on("click", function() { document.getElementById("mydropdown").innerHTML=this.getElementsByTagName("text")[0].innerHTML;
  d3.event.stopPropagation();
});
options.append("rect").attr("x", 10)
	.attr("y", function(d,i){ return 40 + i*30})
	.attr("width", l)
	.attr("height", 30);

 options.append("text").attr("x", function(d){ return 15})
	.attr("y", function(d,i){ return 60 + i*30})
	.text(function(d){ return d});
rect {
	fill: teal;
}
.dropdown .option {
  display:none;
}
.dropdown rect{
  stroke-width:0.5;
  stroke:rgb(0,0,0)
}
.dropdown:hover .option {
  display:unset;
}
.dropdown {
  cursor:pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Upvotes: 2

Related Questions