Reorder elements of SVG ( z-index ) in D3.js - javascript

I realise this question has been asked before but I can't get to the bottom of it.
Here is my chart... http://www.gogeye.com/financialnews/piechart/index3.html
All I want to do is have the coin render behind the graph. I know D3 renders in order they are appended.
I have tried to re-append the coin but can't seem to get it working.
I've tried reordering when things are appended in the DOM but keep getting errors probably because variables are getting called before being defined etc.
Can someone give me an example of how to fix this with my code? I don't want you to do the work for me but I've been pulling my hair out for so long, I can't seem to apply other peoples examples to mine.
thanks

I would recommend creating some "layers" using svg g elements which stands for "group".
When you render your chart, you can first define your layers:
var layer1 = svg.append('g');
var layer2 = svg.append('g');
var layer3 = svg.append('g');
// etc... for however many layers you need
Then when you append new elements, you can decide which layer you want them to be on, and it won't matter what order you assign them in, because the group elements have already been added to the DOM, and are ordered. For example:
var layer1 = svg.append('g');
var layer2 = svg.append('g');
var redCircle = layer2.append('circle')
.attr('cx', 50)
.attr('cy', 50)
.attr('r', 16)
.attr('fill', 'red')
var blueSquare = layer1.append('rect')
.attr('x', 25)
.attr('y', 25)
.attr('width', 50)
.attr('height', 50)
.attr('fill', 'blue');
In this case the red circle will be visible above the blue square even though the blue square was created last. This is because the circle and the square are children of different group elements, which are in a pre-defined order.
Here's a FIDDLE of the above example so you can see it in action.
Doing this should take a lot of the guesswork out of when to add certain elements to your chart, and it also helps to organize your elements into a more logical arrangement. Hope that helps, and good luck.

I am using the D3.js, and found that it has a built-in function for changing the z-order of SVG elements programmatically after the original drawing.
RipTutorial: svg--the-drawing-order covers the d3 builtin function
Quotes from this link:
selection.raise(): Re-inserts each selected element, in order, as the last child of its parent. selection.lower(): Re-inserts each selected element, in order, as the first child of its parent.
d3.selectAll("circle").on("mouseenter", function(){
d3.select(this).raise();
});
d3.selectAll("circle").on("mouseleave", function(){
d3.select(this).lower();
});
see live example their jsFiddle

Related

Multiple force-directed graphs in 1 svg (problems with text display)

I want to display multiple force directed graphs. Preferably with only using 1 svg element as well (as I think that increases performance as well as let me make a width and height for whole simulation as this may differ based on data).
My first thought and with some research I just did a for loop, re-evaluated my nodes/edges arrays and put that in a function that generates the force-directed graph.
for (var i = 0; i < sampleData.length; i++)
{
var nodes = [];
var edges = [];
x = x+100; //update position (i want to show grpahs side by side.)
root = sampleData[i];
nodes.push({"name": root, "x": x, "y": y, "fixed": true, "color": "purple"});
//These 2 recurisve functions generate my nodes and edges array just fine.
buildParents(childs, parents, test, counter);
buildChildren(childs, parents, test, counter);
//apply id to each node
nodes.forEach((d,i)=>d.generatedId='id'+i);
//build makes the force-directed layout.
build(nodes, edges);
}
This actually appears to work fine for me with my nodes and links. My issue is the text does not display for all nodes like it does if I only pass in one set of data. I have text defined as so in the force simulation:
var nodes_text = svg.selectAll(".nodetext")
.data(nodes)
.enter()
.append("text")
.attr("class", "nodetext slds-text-heading--label")
.attr("text-anchor", "middle")
.attr("dx", -20)
.attr("dy", 20)
.text(d=>d.name)
.attr('opacity',0)
I was able to reproduce this error by just making 2 arrays for nodes and 2 arrays for edges and passing it into a simulation. Here is a simple program that reproduces my error:
https://jsfiddle.net/mg8b46aj/9/
I think fixing this JFiddle would give me the right idea on how to put it in my program.
So I just call the build function twice (either order is error). The left node has text but one of them isn't the correct text field. Also dragging it around a little bit makes it "leave" its text behind. The right graph has nothing.
Edit: And clicking the a node on the right graph seems to reset the text positions.
The problem is with the d3 selector use for selecting labels. As you need two separate force layout diagrams, you should use a selector as shown below for labels.
var nodes_text = svg.append('g') //Append new group for labels in new diagram
.attr("class", "labels")
.selectAll(".nodetext")
.data(nodes)
.enter()
.append("text");
Updated Fiddle: https://jsfiddle.net/gilsha/qe7bbnwn/1/

How to add controls and buttons to map?

This is probably a really basic question, but I can't figure it out. I'm building a map using D3. My code creates and svg, appends a g element to it and then draws the map within it. What I want is to render a set of buttons and controls that are positioned inside the map viewer. They would be zoom buttons, a dropdown to display different sets of data and a timeline slider.
For example, with the dropdown selector I want placed I did this:
I tried using d3 as in:
svg.append("select")
.attr("class", "field_dropdown")
.data(['housing_unit', 'tenure', 'median_contract_rent', 'median_value', 'median_income'])
.enter()
.append("option")
.attr("value", function(d) {
return d
});
but this rendered the select item and option items separate from each other, and not even visible within the map container.
As mentioned, not only do I wanna add a dropdown, but also buttons for zoom and a slider, among other items. How do I render and position them in the map container?
Thanks
This has been asked several times (surely a duplicate): you cannot append HTML elements ("div", "p", "select", "h1" etc) to an SVG. It will simply not work.
The best solution, in your case, is creating the drop down menu and the other controls outside the SVG, in the HTML.
But, if you really want to create this drop down inside the SVG (which I don't advise), you can use foreignObject (which will not work on IE):
var foreign = svg.append("foreignObject")
.attr("width", 100)
.attr("height", 100)
.append("xhtml:body");
var select = foreign.append('select')
.attr("class", "field_dropdown")
//the rest of your code

Iterating throug SVG elements with D3.js

What I'm trying to do is relatively simple but I'm new to JS and D3.js.
I have created a bunch of rectangles using SVG through D3.js.
I added some code to handle a click event and in there I'd like to iterate through all drawn nodes and do something with them as long as a specific property matches the same property in the one that's been clicked.
Here's the code that draws the rectangles (only one of them here);
d3.select("svg")
.append("rect").attr("x", 50)
.attr("y", 10)
.attr("height", 20)
.attr("width", 200)
.attr("title", "catalog")
.style("fill", "#CB4B19")
.on("click", mouseClick)
And here's how I'm trying to retrieve the "title" property of each rectangle drawn and compare it to the clicked one (and in this case, just log it in the console). I know this is probably basic but I can't figure out what I'm doing wrong here.
function mouseClick(d) {
var t = d3.select(this).attr("title"); //store the "title" property of the clicked rectangle
d3.selectAll("rect").each(function(d, i){ //Select all rectangles drawn
if(d3.select(this).attr("title") == t){ //for each one, if the "title" property = the one initially chosen
console.log(t); //do something here
}
})
}
Your code actually seems to be working correctly. At least for me it did. One thing I will say is that d3 does mimic jQuery syntax in that it lets you select elements with attributes with the d3.select('element[attributeName="?"]') syntax. You can read more about selections here.
So for your example, you could do
var t = d3.select(this).attr("title");
// select all rectangles with the attribute title
d3.selectAll("rect[title='" + t + "']").each(function(d, i){
console.log(t);
});
You no longer need the if statement to check because you are only selecting them. I made a simple jsFiddle to show this. I made 3 different types of rectangles with different title attributes and when you click on them, it only selects rect that have the same title attribute.
http://jsfiddle.net/augburto/znqe8nqr/

Positioning SVG elements via .getBoundingClientRect() in variable-width div

I have a bit of an annoying problem.
I'm trying to position a bunch of SVG circle elements according to an existing bunch of SVG text elements that share similar properties.
The circle elements are created in a very separate process than the text elements, so positioning the new elements just using the same transforms etc. as the old one isn't a viable option.
I'm trying to use .getBoundingClientRect() to get the positions since the text elements are transformed into position (so .getBBox() isn't an option) rather than positioned by x and y attributes.
With .getBoundingClientRect(), I can get the correct size/arrangement of the new elements, but since the width of the svg-containing div is variable, there's always a bit of a weird offset that I can't quite account for.
I created a simplified example of my issue here. Resize and refresh the page to see the issue in action.
The code I use to position the circle elements is replicated below.
var circs = theSvg.selectAll("circle")
.data(theCircles)
.enter()
.append("circle")
.attr("r", 15)
.attr("fill", "#f00")
.style("opacity", 0.3)
.attr("transform", function(d){
var sizeDif = 800/(d3.select(".svgTestHolder")[0][0].getBoundingClientRect()["width"]);
var theNum = parseInt(d.split("&")[1]);
var thePosition = theSvg.selectAll("text").filter(function(e){
return e == theNum;})[0];
var theCoords = thePosition[0].getBoundingClientRect();
var leftOffset = d3.select(".svgTestHolder")[0][0].getBoundingClientRect()["left"];
var leftOffset2 = d3.select(".svgTest")[0][0].getBoundingClientRect()["left"];
var bottomOffset = d3.select(".svgTestHolder")[0][0].getBoundingClientRect()["top"];
var bottomOffset2 = d3.select(".svgTest")[0][0].getBoundingClientRect()["top"];
return
"translate(" + ((theCoords["left"] - leftOffset - leftOffset2)
* sizeDif) + "," + ((theCoords["top"] - bottomOffset - bottomOffset2)
* sizeDif) + ")";
})
EDIT:
This is a very delayed update just to note that while I was unable to answer my question as stated, I was able to make a workable solution based on Paul LeBeau's suggestion to extract the transforms from the target element.
In my case, I had to use a series of consecutive transforms rather than a combination of transforming and changing the x/y position (due to certain realities of the project not represented in the linked example). But I'm happy to have found an answer!
Your example works fine for me on Chrome. But really that's only because the SVG is the only thing on the page. If I add some text above the SVG everything goes wrong.
https://jsfiddle.net/rrpfmm6d/1/
Is this the problem you are talking about?
If so, the reason is because you are making the wrong choice in using getBoundingClientRect(). It provides coordinates in screen space. It's origin is the top left of the window (or iframe in the case of jsfiddle).
You should be using getBBox(). The values it returns are in the same coordinate space as the SVG elements. It's origin is (normally) at the top left of the SVG.
In summary, use the coordinates returned by calling getBBox() on your <text> element to calculate the position for your circle. If the circles are inserted into the same SVG as the text, there will be no need to do any adjusting with the div or svg offsets.

how to select parent div of svg in D3.js

I have read several articles and tutorials but couldn't find a sufficient answer on how to select the parent div of the svg using d3.select. I basically just want to append a tooltip to the div which contains my chart like this.
//this selection probably doesn't make sense...
var tooltip = d3.select("#pie-svg").select(this.parentNode).append("div")
.attr("class", "piechart-tooltip")
.style("opacity", 0);
Along the lines of this question:
var tooltip;
d3.select("#pie-svg").each(function() {
tooltip = d3.select(this.parentNode).append("div")
.attr("class", "piechart-tooltip")
.style("opacity", 0);
});
The problem with the code you've posted in the question is that this won't be defined (or set to the right element) in this context. When used with .each(), it will.

Categories

Resources