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

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/

Related

Looking for a more efficient way to remove d3 elements

I just started learning D3 and am having trouble understanding how to manipulate selections. My visualization is functioning very slowly and someone recommended to me to use Timeline to find the source of the problem. I looked over it and found that this snippet of code is most likely the culprit:
for (var i = 0; i < staticSvgLength; i++) {
var curritem = d3.select(svgChildren[0][i]);
if (curritem.attr("class") != "graphbutton") {
curritem.remove();
}
}
Here is a snapshot of the timeline that suggests to me that the problem is remove:
svgChildren elements are selected using:
var svgChildren = d3.selectAll(svgContainer[0][0].childNodes);
There are thousands of elements, most of which can be found within the following variable:
var rectangles = svgContainer.selectAll("svg")
.data(rawDataStore)
.enter()
.append("rect")
This is the graph I am trying to create:
The elements which I'm trying to prevent from being removed are the buttons in the upper left corner which are also stored in svgContainer. As a side question, is it bad practice to store the buttons in the same svg element I store the main graph?
EDIT: Here is where I define svgContainer:
var svgContainer = d3.select("body").append("svg")
.attr("Call", "svgContainer")
.attr("height", max_y)
.attr("width", max_x)
.append("g")
.attr("transform", "translate(" + margin.left + "," + (margin.top) + ")");
I select all the SVGs to be removed because when you click a button, it switches to a different graph. I don't want the two buttons to be removed, only the graphs, so I tried to exclude them from the selection by giving them a class and then removing all elements in svgContainer excluding the items that have the button class.

How exactly the bars(or rectangles) are created in d3 barChart demo part 1?

I understand every line of the barchart source code . However, besides code on setting each div's width using .style("width", function(d) { return x(d) + "px"; }), I don't see codes specifically saying "let's make bars or rectangles".
var data = [4, 8, 15, 16, 23, 42];
var x = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, 420]);
d3.select(".chart")
.selectAll("div")
.data(data)
.enter().append("div")
.style("width", function(d) { return x(d) + "px"; })
.text(function(d) { return d; });
My question:
Was the demo code a quick and dirty way of producing bars? Is there a formal or standard way of creating rectangles or bars using d3?
Thanks
First, those are not rectangles (as an SVG rectangle), but simply divs. Those divs have a rectangular shape and a background-color set in the CSS, so, they look like rectangles. Most of the D3 books (like Zhu's, Murray's etc) teach how to make charts with divs before moving to actual SVG rectangles.
But if what you don't understand is how these divs are created (judging by the title of your question), the code is right here:
d3.select(".chart")
.selectAll("div")
.data(data)
.enter()
.append("div");
What does it say? Let's see:
.selectAll("div"): This selects all the "div". But there is none so far... so, this is just a placeholder. Then:
.data(data). This binds the data: the data is data. data is an array of 6 numbers. So, recapitulating, right now, there is no div, and these inexistent divs are bound to 6 numbers. So, our "enter" selection will be a selection of 6 divs, one for each number in the array.
.enter(): this is the "enter" selection. We have 6 numbers in our data, and zero div in the chart. So, our enter selection has 6 (new) divs.
.append("div"): This creates the divs. With append, we create the actual DOM elements.
This is a way to visually understand the enter selection:
In the first selectAll, we selected DOM elements that didn't exist at that time (they are just placeholders). Then, we bound data to those elements. Once we have 6 data numbers and 0 elements, our enter selection (corresponding to data without elements) has 6 new elements.

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/

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

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

Implementing <div> tooltips via d3 within an <svg> container

I'm relatively new to D3, svg, and javascript in general, so please bear with me :]
I have been experimenting with D3 for creating plots and graphs. I have created a small plot using D3 and have been attempting to make it compatible with IE8. Here is a link to the more-or-less working build of my graph.
http://jsfiddle.net/kingernest/YDQR4/1/
After some research, I quickly realized that the only way running D3 on IE8 would be at all feasible is by using other APIs in conjunction with D3. Luckily, I found that someone had already put in some work into a project called "r2d3" which, from my understanding, uses raphael to paint the canvas on the IE8 window instead of using SVG (which apparenly was not supported in IE8).
I have been able to get items drawn on the screen, which is half the battle. However, I'm having many issues, particularly with my tooltip. My tooltip is written as a DIV container that floats and changes position/opacity on hover of the data circles. This seems to work fine in other browsers, but with r2d3, I have not been able to get it working. I suspect this is because of the fact that I am creating the div tooltip outside of the (in the #main div). However, I have tried placing tooltips inside of the SVG container with no avail. I then did more reseach and discovered I would have to wrap a div container inside a tag, but after some experimentation with this, I still wasn't able to get the tooltip to work correctly in IE. I attempted to wrap the in a SVG group (), and altered the positioning of this object instead, but this did not seem to work either, and simply through numerous exceptions when trying to append the foreignObject tag to a group.
At this point I'm sort of stuck, and was wondering if anyone had any suggestions as to how I may be able to successfully implement the tooltips. I've also noticed that using d3.select(this) inside my functions, when attempting to select a particular data point (in this case, a circle) seems to present a number of issues when attempting to access or modify that item's attributes, but I think this is a whole other issue entirely.
Any help is greatly appreciated!
Example of how I'm currently creating the tooltips:
//Create tooltip element
var tooltip = d3.select("#main")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("z-index", "10")
.style("opacity", 0);
function mousemove()
{ //Move tooltip to mouse location
return tooltip.style("top", (event.pageY-10)+"px").style("left",(event.pageX+10)+"px");
}
//Mouseover function for circles, displays shortened tooltip and causes other circles to become opaque
function mouseover()
{
var myCircle = d3.select(this);
d3.select(this).attr("class", "dataCircleSelected"); //Color circle green
tooltip.html( //Populate tooltip text
"Username: " + d3.select(this).attr("username") + "<br/>" +
"Session ID: " + d3.select(this).attr("sessionid") + "<br/>" +
"Impact CPU: " + d3.select(this).attr("impact")
)
.transition()
.duration(250)
.style("opacity", .7);
//After 1000ms, make other circle opaque
svg.selectAll("circle")
.filter(function(d, i){ //return every other circle
return !d.compare(myCircle[0][0].__data__);
})
.transition().delay(1000)
.style("opacity", .2);
}
Have you tried using foreignObjects AND explicitly using the xhtml namespace for html tags in the foreignObject (write xhtml:div instead of div) as explained here: HTML element inside SVG not displayed ?
This would give something like that for the tooltip definition
var tooltip = d3.select("#main").append("foreignObject")
.append("xhtml:div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("z-index", "10")
.style("opacity", 0);

Categories

Resources