D3 won't append elements after external JSON is loaded - d3.js

So I've been trying to get my head around this, but somehow D3 won't append the g element to the SVG.
I created a function totalpoints and called it after the element I want it to add the SVG to. This all works fine, but it won't append any element to the SVG..
var chart = d3.select(id)
.append("svg") //append svg element inside #chart
.attr("width", width + margin.left + margin.right) //set width
.attr("height", height + margin.top + margin.bottom); //set height
d3.json("https://dl.dropboxusercontent.com//s/339pul17xrdsk6w/data.json?raw=1", function(error, data) {
var bar = chart.selectAll("g")
.append("g") //append svg element inside #chart
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
});
Here is a JSFiddle.

There are (at least) two problems:
No Data
In your d3.json, you're not using the loaded data anywhere.
You should have something like:
var bar = chart.selectAll("g")
.data(data)
.enter()
...
d3 works by matching visual elements with data for display, and from its point of view, you did not give it any.
Only Group Elements
The only added elements are css groups. They, by themselves, do not display anything.

The problem was simple. The original JSON was
{"section":"Totale punten voor","points":"1791"}
What worked was putting [] around it, like
[{"section":"Totale punten voor","points":"1791"}]
The g element appears.

Related

D3 - SVG elements being positioned from body, not SVG container

I am trying to us d3 inside of rails app.
There are other elements that are not part of the visualization taking space at the top and left of the page.
My problem is when I do positioning (x, y) to any svg element, it seems to be doing it based on the body of the page, not the svg. Is this typical? Is there a way I can always position from the (0,0) position of an svg, the top left corner ?
I am selecting an element, appending an svg and g tag then a text I guess I was expecting because everything was being appended it would be placed with/positioned in it's parent.
Example:
var g = d3.select("#chart-area").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
g.append("text")
.attr("class", "x axis-label")
.attr("x", width / 2)
.attr("y", height + 75)
.attr("font-size", "20px")
.attr("text-anchor", "middle")
.text("Months")
The result of this is that the label will show up underneath the UI of the gem.
This is not the actual project, its a tutorial, but I am using basically the same code:
https://jsfiddle.net/r6jkht9v/1/
My solution was mostly based around adding some bootstrap tags.
<div class="container">
<div class="row">
d3
</div>
</div>
After adding this svg elements I appended stayed with in the svg.

d3 Tree - How to auto adjust node spacing when zooming

I have a d3 tree with zoom and pan functionality. The tree can have a great number of child nodes for any parent. This can result in vertically squashed up nodes.
I would like the nodes to automatically adjust their spacing when zooming out, especially the vertical spacing.
The appending d3 svg and the zoom part of the d3 code:
var svg = d3.select("#tree_container")
.append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.call( d3.behavior.zoom().on( "zoom", function () {
svg.attr( "transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")" )
} ) )
.append("g");
Fiddle
Would it be enough to set the node sizes? Then d3 will automatically adjust positions of the nodes.
var radius = 10;
var tree = d3.layout.tree()
.size([height, width])
.nodeSize([radius*2+3]);
...
function update(source) {
...
nodeUpdate.select("circle")
.attr("r", radius)
As Fiddle

D3 clipping issues on zoom

I would like to use zoom behaviour in a simple d3 graph. The thing is that whenever I zoom in, the "main" group elements take up the whole svg space, which causes other existing elements such as axes and texts to get overlapped. I have read that clipping can be used to solve this problem but I didn't manage to get it working properly.
The following image (zoom in was applied) shows the problem:
Related example with what I have tried so far can be found here.
I was finally able to solve this problem. Key points were:
Use proper svg element for clipping, normally rect does the job with corresponding width/height (same as your "drawing" area).
Transform all elements drawn within the clip path (region), and not the parent group.
In code (omitting irrelevant parts), the result is:
// Scales, axis, etc.
...
// Zoom behaviour & event handler
let zoomed = function () {
let e = d3.event;
let tx = Math.min(0, Math.max(e.translate[0], width - width*e.scale));
let ty = Math.min(0, Math.max(e.translate[1], height - height*e.scale));
zoom.translate([tx,ty]);
main.selectAll('.circle').attr('transform', 'translate(' + [tx,ty] + ')scale(' + e.scale + ')');
svg.select('.x.axis').call(xAxis);
svg.select('.y.axis').call(yAxis);
}
let zoom = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([1,8])
.on('zoom', zoomed);
const svg = d3.select('body').append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.attr('pointer-events', 'all')
.call(zoom);
const g = svg.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Set clip region, rect with same width/height as "drawing" area, where we will be able to zoom in
g.append('defs')
.append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', width)
.attr('height', height);
const main = g.append('g')
.attr('class', 'main')
.attr('clip-path', 'url(#clip)');
let circles = main.selectAll('.circle').data(data).enter();

Removing single elements of svg in d3.js

I have the following code in d3.js
var svg = d3.select(".Canvas").append("svg").attr(
"width", width + margin.left + margin.right).attr("height",
height + margin.top + margin.bottom).append("g").attr(
"transform",
"translate(" + margin.left + "," + margin.top + ")");
svg.append("g").attr("class", "x axis").attr("transform",
"translate(0," + height + ")").call(xAxis);
svg.append("g").attr("class", "y axis").call(yAxisMajor).append("text")
.attr("transform", "rotate(-90)").attr("y", 6).attr("dy",
".71em").style("text-anchor", "end");
I tried the following to remove only the y-axis, not the whole svg:
d3.select(".Canvas").selectAll("svg").selectAll("g").select(".y axis").remove();
Why is the code not working?
Here is a little demo with a couple of simple ways to do it (see my comments in the fiddle).
d3.select("svg > g > #the_one_circle")
.transition().duration(1000)
.attr("r",1)
.remove();
Note how the svg and g elements are still there after removing the circle.
Because when you set class attribute as y axis , you actually set two classes. So in order to select and match both class names you need to call select with .y.axis. So that the result should be:
d3.select(".canvas").selectAll("svg").selectAll("g").select(".y.axis").remove();
at least worked for me, demo.

Updating line graph

I'm looking for the best way to update my line graph. The incoming data is not 'new', it's just switching between many different data sets. My issue is that .append('path') simply isn't working, and I'm not sure why. It may have to do with my grouping: I like to put margins around my graphs so axis and labels aren't clipped. To do this, my HTML looks like the following...
<svg w, h id='svg-container'>
<g translate(w - margin, h - margin) id='chart-container'>
<g id='x-axis'>
<g id='y-axis'>
In my code I would like to select chart-container and append a <path d='...' id='line'>. Later, when updating the graph, I would hope to be able to simply select('chart-container').select('path') and update that selection.
Initial setup of the chart:
var svgContainer = d3.select(element[0]).append('svg')
.attr({
width: width + margin.left + margin.right,
height: height + margin.top + margin.bottom
})
.classed('svg-container', true);
var chartContainer = svgContainer.append('g')
.classed('chart-container', true)
.attr('transform', "translate(" + margin.left + "," + margin.top + ")");
chartContainer.append('path')
.data(activeData)
.attr('d', lineFunc)
.attr('stroke', 'blue')
.attr('stroke-width',
.attr('fill', 'none');
Update the chart later on:
svgContainer = d3.select('#line-container').select('.svg-container')
.attr({
width: width + margin.left + margin.right,
height: height + margin.top + margin.bottom
});
chartContainer = svgContainer.select('.chart-container')
.attr('transform', "translate(" + margin.left + "," + margin.top + ")");
chartContainer.select('path')
.data(activeData)
.attr('d', lineFunc)
.attr('stroke', 'blue')
.attr('stroke-width',
.attr('fill', 'none');
Unfortunately this doesn't work, and I'm not sure why. The initial chartContainer.append('path') seem to be working (sometimes), and the chartContainer.select('path') doesn't actually return the correct item (othertimes). Might d3 be traversing my axis groups and returning one of their paths? Am I going about this the wrong way?
You don't show us your complete code, but from your description it sounds as if you may be selecting the wrong path. One way around this is to assign a special class to the path you want and select accordingly.
chartContainer.append("path")
.attr("class", "chart");
chartContainer.select("path.chart");
The other problem is that you can't really use .data() to update the data bound to the DOM element like you're using it. Use .datum() instead.
chartContainer.select('path')
.datum(activeData);

Resources