Calculate absolute position for d3 graph nodes - javascript

I have a d3 force layout. Each node in this force layout has a label, which is a <div> code(can have table inside) and should follow it all the time. I defined the <div>'s position to be absolute and set their position like this:
var updateNode = function() {
label_xOffset=10;
label_yOffset=10
this.attr("transform", function(d,i) {
//this is where I set their position
//d.num declare corresponding div in node_lables_divs array
node_labels_divs[parseInt(d.num)].style("left",(d.x+label_xOffset)+"px").style("top",(d.y+label_yOffset)+"px");
return "translate(" + d.x + "," + d.y + ")";
});
}
function tick(e) {
node.call(updateNode);
}
Everything is good now as long as I don't pan or zoom in my SVG. But I need those features too. So in my zoom function I handled it like this:
var zoomer = d3.behavior.zoom().scaleExtent([0.1,10]).
x(xScale).
y(yScale).on("zoom", redraw);
function redraw() {
var translation=" translate(" + d3.event.translate[0] + "px,"+d3.event.translate[1]+"px)";
d3.selectAll("div.node_lables_html").style("transform",translation)
.style("-webkit-transform",translation)
.style("-ms-transform",translation);
//vis is the <g> tag that contains the whole nodes and links
vis.attr("transform","translate(" + d3.event.translate + ")"+" scale(" + d3.event.scale + ")");
}
Now every thing good when I drag and pan the SVG. But it is very bad when I try to zoom in the SVG, lables are going in different direction and they are not following the nodes. I print out the d3.event.translate and it seems that it has different scale in times of zoom and panning.
I also tried the d3 foreignElement but it seems that the transform attribute doesn't work for the foreignElements. How should I calculate the correct value to transform htmls?

As per this solution to a similar question (it's not a duplicate, quite), you need to:
Put the div into a foreignObject SVG element (which allows arbitrary HTML inside an SVG)
add the foreignObject to a group, along with anything else that needs to be with the label
Only apply the tranforms to the group

Related

d3 + drawing svgs + incorrectly overwriting

this i my jsfiddle that shows 2 piecharts which gives me something like this in the html view:
<svg width="220" height="220">
<svg width="220" height="220">
this is my fiddle where I am trying to insert an svg before the 2 piecharts, but I am not writing it correctly, 1 of the pie charts gets over written.
Can anyone advise how I can have all 3 svgs showing?
All my code as it is in the 2nd fiddle
var width = $(document).width()-50 || 960,
height = $(document).height()-50 ||500;
//attaches/appends a svg tag to a body tag with a width and height
/* COMMENT IN */
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
data=[[10, 5],[32, 24]]
z = d3.scale.category20c();
//attaches/appends a svg tag to a body tag with a width and height
var svg2 = d3.select("body").selectAll("svg")
.data(data)
.enter().append("svg:svg")
.attr("width", 220)
.attr("height", 220)
.append("svg:g")
.attr("transform", "translate(" + 100 + "," + 100 + ")")
svg2.selectAll("path")
.data(d3.layout.pie())
.enter().append("svg:path")
.attr("d", d3.svg.arc()
.innerRadius(10)
.outerRadius(100))
.style("fill", function(d, i) { return z(i); });
NOTE: Some further background is that the first svg is a map that I have working. And What I am trying to do is create a layer on top of this which will be piecharts, in this case just 2, but you have to start somewhere :)
Your interpretation of the results is slightly wrong. The first pie chart doesn't get overwritten, it won't get rendered in the first place. In the commented in version you are appending the svg for your map, which works fine and can easily be identified by the width and height values assigned to it.
When trying to append the SVGs for your pie charts, you are selecting all SVGs in the body by doing d3.select("body").selectAll("svg"). This selection will include the previously appended SVG. You are then binding your data to the selection which will compute the data join. Since you are binding an array of data without any key function the join will be based on the index. Thus, the existing SVG will correspond to the first element in the data array and will be put to the update selection. As your code only works on the enter selection the first pie chart will never be drawn because it doesn't belong to this selection. The second one is output as expected.
One way to work around this is to use a CSS marker class for the pie charts. That way you could limit the second selection to the SVGs having the marker class:
var svg2 = d3.select("body").selectAll("svg.pie") // select with class "pie"
.data(data)
.enter().append("svg:svg")
.attr("width", 220)
.attr("height", 220)
.classed("pie", true) // set marker class for all pie charts
.append("svg:g")
.attr("transform", "translate(" + 100 + "," + 100 + ")")
Because no SVG is present having class pie, all your data will end up in the enter selection rendering an SVG containing a pie chart for every data element as expected. I have updated your JSFiddle to show the effect.

Get pixel width of d3.js SVG element after it's created with width as percentage

I want to place a svg bar chart into a certain div and fail to get the calculated svg width if I set the attribute "width" as a percentage. I would like to stick to giving the size as a percentage to be able to calculate bar widths etc. from the svg size.
The div for the chart:
<div id="chart"></div>
The code for appending the SVG, place in a function before that. The function is called to put data in a bootrap modal:
var prodChart = d3.select("#chart");
var w = "100%";
var h = "100%";
// remove existing svg in div
prodChart.select("svg").remove();
var chartSVG = prodChart.append("svg")
.attr(":xmlns:svg", "http://www.w3.org/2000/svg")
.attr("id","prodchartsvg")
.style("width", w)
.style("height", h)
.attr("class", "thumbnail");
To get the calculated size in pixels, I already tried this:
$("#prodchartsvg").width(); // not working, returns 100
$("#chart").width(); // not working, returns null
prodchartsvg.node().getBBox(); // prodchartsvg.node is not a function
d3.select("#prodchartsvg").style("width"); // returns 100%
chartSVG.style("width"); // returns auto
d3.select("#prodchartsvg").node().getBoundingClientRect(); // returns 0 for all parameters
d3.select("#chart").node().getBoundingClientRect(); // returns 0 for all parameters
I realise some of the above functions I tried are unsuitable in this context (DOM, object, ...).
Can you give me an idea how to get the svg width in pixels instead of a percentage?
Thank you for the help!
As #nrabinowitz commented, your problem is most probably caused by the fact that your SVG is not visible in the screen at the moment when you attempt to retrieve its width.
Test case
In the following snippet, we compare the value returned by $("#prodchartsvg").width() for two svgs, one hidden and one displayed.
var prodChart = d3.select("#chart");
var w = "100%";
var h = "100%";
// remove existing svg in div
prodChart.select("svg").remove();
var hiddenSVG = prodChart.append("svg")
.attr(":xmlns:svg", "http://www.w3.org/2000/svg")
.attr("id","hiddenSVG")
.style('display', 'none') // not shown on screen
.style("width", w)
.style("height", h)
.attr("class", "thumbnail");
$('#result').html('hiddenSVG width is ' + $("#hiddenSVG").width())
var shownSVG = prodChart.append("svg")
.attr(":xmlns:svg", "http://www.w3.org/2000/svg")
.attr("id","shownSVG")
.style("width", w)
.style("height", h)
.attr("class", "thumbnail");
$('#result').append('<br>shownSVG width is ' + $("#shownSVG").width())
svg {background-color: #eee}
<div id='chart'></div>
<div id='result'></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
How to fix
Try one of these solutions:
make sure the chart is displayed on screen whenever you compute the width
make sure the DOM is loaded whenever you compute the width. Either move you script at the end of the html body, or wrap your code inside a jQuery DOM ready function...

Drupal 7 + D3 - embed circle-pack visualization in a page

I'm trying to embed a D3 circle-pack in a Drupal 7 basic, page but it's appearing in the wrong section of the page - below the footer. See here: http://www.thedata.co/circlepackzoom10. I've tried different themes but make no difference.
I'm using the php include method with the PHP filter: <?php include './sites/thedata.co/files/html/circle_packZoom-10.html'; .
I'm able to embed a D3 chart in the node body so seems it can be done in some cases.
Using Chrome, JS console defines content area as: <div class="field-item odd" property="content:encoded">. Doesn't work with Firefox either.
Here's the complete code: http://jsfiddle.net/cajmcmahon/9a5xaovv/
var pack = d3.layout.pack()
.size([r, r])
.children(function(d) {
return d.values; // accessor for children
})
.value(function(d) {
return d.values; // accessor for values
});
//Declare SVG attributes
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", w)
.append("g")
.attr("transform", "translate(" + (w - r) / 2 + "," + (w - r) / 2 + ")");
Anyone any idea how to place it within the main content area of the node? Thanks.
Select the element where you want to put the chart instead of selecting "body" in d3.select("body").append("svg").
something like:
d3.select('#divID').append('svg') ... etc
As this method just appends it to the body element of the website.

dc.js responive geojson US map

Testing with following sample code:
http://dc-js.github.io/dc.js/vc/
I would like to make this map responsive to the container/window size.
However, examples I have seen utilized topojson and SVG to scale the map.
http://techslides.com/demos/d3/d3-worldmap-boilerplate.html
Is this not possible with Geojson to update the map size in a similar fashion?
d3.json("../geo/us-states.json", function (statesJson) {
usChart.width(990)
.height(500)
.dimension(states)
.group(stateRaisedSum)
.colors(d3.scale.quantize().range(["#E2F2FF", "#C4E4FF", "#9ED2FF", "#81C5FF", "#6BBAFF", "#51AEFF", "#36A2FF", "#1E96FF", "#0089FF", "#0061B5"]))
.colorDomain([0, 200])
.colorCalculator(function (d) { return d ? usChart.colors()(d) : '#ccc'; })
.overlayGeoJson(statesJson.features, "state", function (d) {
return d.properties.name;
})
.title(function (d) {
return "State: " + d.key + "\nTotal Amount Raised: " + numberFormat(d.value ? d.value : 0) + "M";
});
You can follow the exact same approach as the one you linked to (techSlides).
It consists in replacing the whole svg by a new one re-drawn after a resize of the window. On redraw, the only thing that changes is the width and height you use as dimensions of the svg element.
Whether your data is GeoJson ot TopoJson doesn't affect this.
A smoother variant of this technique is to resize the existing svg element on resize of the window, without doing a redraw. Just add an SVG viewBox and preserveAspectRatio="xMinYMin", so that projection inside the SVG is kept.
Example in this blog, and its javascript.

Convert an SVG element to a g element using d3.js

I am using D3 in a page where my objective is to superimpose two groups of paths : I have the earth coastlines as a background map (imported from a GeoJSON file), and I want to superimpose an old map on it, which is a separate SVG : http://bl.ocks.org/bperel/9943623/250fc9af2e09bf176f6d510e0b59fe643d792287
The SVG loading went well, however I would like my two maps to be included inside the same SVG element : it will be easier if I want the maps to be panned or zoomed. The issue here is that the external SVG has a specific viewbox, so if I move its <g> elements to the other SVG, the coordinates get messy. Hence my question : is there a way to convert the 2nd SVG to a <g> element that I could include in the first SVG ? Maybe d3 knows how to do that but I couldn't find such a tool in the documentation.
If you are going to zoom/pan the superimposed SVG's, Include both svg's in a <g> container element and attach zoom behavior to the <g> container.
Essentually, you will have:
<svg id=topSVG>
<g id=container>
<svg id=projection1>
....
</svg>
<svg id=projection2>
....
</svg>
</g>
</svg>
Below is a snippet relating to my mouswheel zoom for d3:
var myZoom=d3.behavior.zoom().scaleExtent([.1, 1000]).on("zoom", mouseWheelZoom)
function callZoomBehavior()
{
//---attach to root svg---
d3.select("#"+SVGId)
.call(myZoom)
//---reset from previous file call---
myZoom.translate([0,0])
myZoom.scale(1)
PrevScale=1
}
var PrevScale //--previous scale event---
function mouseWheelZoom()
{
//--place in d3 object--
var zoomG=d3.select("#topZoomG")
if(PrevScale!=d3.event.scale) //--transition on scale change--
{
zoomG.transition().attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")")
}
else //--no transition on pan---
{
zoomG.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")")
}
PrevScale=d3.event.scale
}
originalSVG = the SVG element you want to clone and convert into g element
quasiClone = the result of that
parentOfClone = parent below you want to put your new g element
var quasiClone = parentOfClone.append("g");
for (var i=0; i<originalSVG.childNodes.length; i++){
var child = originalSVG.childNodes[i];
var childClone = child.cloneNode(true);
quasiClone.node().appendChild(childClone);
}

Categories

Resources