when I upgrade d3.js from v3.5 to 5.9.2 (the latest actually), my graph display correctly, but the legend items doesn't display. In my code, the data exist, but I have only <div class="legend"></div> without inner <div data-id="DATA">DATA</div>. I don't know if the code below is correct for v5. Thank you for your help.
d3.select(".segLegend")
.insert("div", ".chart")
.attr("class", "legend")
.selectAll("div")
.data(names)
.sort()
.enter()
.append("div")
.attr("data-id", function(id) {
return id;
})
.each(function(id) {
d3.select(this)
.append("span")
.style(
"background-color",
$scope.openGraphModal.chart.color(id)
);
d3.select(this)
.append("span")
.html(id);
if (id !== findSupplier.commodity.supplier.name) {
$(this).toggleClass('c3-legend-item-hidden');
}
})
The DOM with V3
The DOM with V5
At a first and quick look this is a strange problem, hard to identify, because in a D3 code we don't use the sort method at the position you did: at that position, that sort is worthless. Have a look (using v3):
const data = [1,3,5,4,2];
const divs = d3.select("body")
.selectAll(null)
.data(data)
.sort()
.enter()
.append("div")
.html(Number)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
As you can see, nothing is sorted. The sort method should be used after appending the real DOM elements (again, v3):
const data = [1, 3, 5, 4, 2];
const divs = d3.select("body")
.selectAll(null)
.data(data)
.enter()
.append("div")
.sort()
.html(Number)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
Finally, sort at this correct position works with v5 as well:
const data = [1, 3, 5, 4, 2];
const divs = d3.select("body")
.selectAll(null)
.data(data)
.enter()
.append("div")
.sort()
.html(Number)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Your problem
When you put sort in that strange (and useless) position in the enter selection in a code that uses D3 v4/5, not only it doesn't sort anything but, the most important, it avoids the DOM elements being appended:
const data = [1, 3, 5, 4, 2];
const divs = d3.select("body")
.selectAll(null)
.data(data)
.sort()
.enter()
.append("div")
.html(Number)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
The snippet remains blank.
The solution is moving the sort to the correct position, not only because of the problem you're facing but also because, where it is right now, it's useless in v3 as well.
Related
this is an extension of this question from yesterday. I am attempting to draw and create multiple brushes. The bruteforce method i am using does ok, but i need additional functionality to programmatically control each of these newly created brushes,
createBrush(){
//Brushes
var brushGroups = []
for(var i=0; i<this.uniqueCats.length; i++){
//BRUSH CONTAINER
brushGroups[i] = d3.select('#brushContainer')
.append('g')
.attr('id',`${this.uniqueCats[i]}brush`)
.attr('transform', `translate(${this.margin.left},0)`)
.attr('class','brush');
//BRUSH
var brushID = this.uniqueCats[i]
this[`${this.uniqueCats[i]}Brush`] = d3.brushX()
.extent([[0, (i*135)+38], [this.width,(i*135)+170]])
.on('end',(i)=>{
console.log('hi')
this.updateBubbles(i)
})
//CALL BRUSH from the brush container
brushGroups[i].call(this[`${this.uniqueCats[i]}Brush`])
}
}
As you can see above I 1) create a brush container 2) create a brush, 3) call that brush from the container. The main issue i am having it passing relevant information to the on for this.updateBubbles(). The hope was for each brush to pass in specific information to updatebubbles() that would allow me to understand which brush was being activated.
From there I decided to use the more elegant d3 approach with enter() but, this method fails to invoke a brush at all,
createBrushv2(){var that =this
var brushContainer = d3.select('#brushContainer')
.selectAll('g')
.data(this.uniqueCats)
var brushContainerEnter = brushContainer
.enter()
.append('g')
.attr('id', d=> `${d}brush`)
brushContainer = brushContainerEnter.merge(brushContainer)
.attr('transform', `translate(${this.margin.left},0)`)
.attr('class','brush')
.each( function(d,i){
console.log(i)
d3.brushX()
.extent([[0,(i*135)+38], [that.width,(i*135)+170]])
.on('end',that.updateBubbles(d))
})
}
The updateBubbles() function is whats evoked from each brush, so this function needs to be able to understand which brush is selected.
There are several issues in your code, which makes your question too broad. Therefore, I'll only deal with the issue of the brush not being invoked.
Before anything else, congratulations for moving from approach #1 (for loop) to approach #2 (D3 selections). In a D3 code, it's almost a very bad idea using loops to append anything.
That being said, unlike your first snippet, you're not actually calling the brush in the second one.
Therefore, this...
d3.brushX()
.extent([[0,(i*135)+38], [that.width,(i*135)+170]])
.on('end',that.updateBubbles(d))
Should be:
d3.brushX()
.extent([[0,(i*135)+38], [that.width,(i*135)+170]])
.on('end', that.updateBubbles)(d3.select(this))
Even better, you should use a selection.call, just like in your first approach. Also, have in mind that that.updateBubbles(d) will invoke the function immediately, and that's not what you want.
Here is a very basic snippet as a demo:
const data = d3.range(5);
const svg = d3.select("svg");
createBrushv2();
function createBrushv2() {
var brushContainer = svg.selectAll(".brush")
.data(data)
var brushContainerEnter = brushContainer.enter()
.append('g');
brushContainer = brushContainerEnter.merge(brushContainer)
.attr('transform', function(d) {
return "translate(0," + (d * 25) + ")"
})
.attr('class', 'brush')
.each(function(d) {
d3.brushX()
.extent([
[0, 0],
[300, 20]
])
.on('end', updateBubbles)(d3.select(this))
})
.each(function() {
d3.brushX().move(d3.select(this), [0, 300])
})
};
function updateBubbles(brush) {
console.log(brush)
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>
This might be about the simplest D3 force layout ever:
const svg = main.append("svg")
.attr("width",500)
.attr("height",500)
.classed("SVG_frame",true)
.append("g")
const nodes = [{id:1},{id:2}];
const simulation = d3.forceSimulation(nodes)
.force("centering",d3.forceCenter([200,200]))
.force("collision",d3.forceCollide(20))
const node = svg
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r",20)
simulation.on("tick",function() {
console.log(nodes[0].x)
node
.attr("cx",d => d.x)
.attr("cy",d => d.y)
})
And yet, I get <circle> attribute cx: Expected length, "NaN". on the first frame. (cy displaces a tiny bit on the frame the simulation gives up moving)
I know this have been asked several times, but none seem to address version 4, where force simulation has presumably changed its inner workings. In fact, the docs even state now that when position is NaN, the position is automatically arranged in a "phyllotaxis arrangement" or whatever, so maybe this isn't supposed to happen, but it does.
Anyone has any clue?
The problem here is very simple: d3.forceCenter takes two values, not an array of values:
[d3.forceCenter] Creates a new centering force with the specified x- and y- coordinates. If x and y are not specified, they default to ⟨0,0⟩.
In an API, brackets around an argument mean that the argument is optional (see here). When you see something like this in the D3 API:
d3.forceCenter([x, y])
You have to take care for not mistaking those brackets for an array. Here, [x, y] means that the values are optional, it doesn't mean that they must be in an array.
So, instead of:
.force("centering",d3.forceCenter([200,200]))
It should be:
.force("centering",d3.forceCenter(200,200))
Here is a demo:
const svg = d3.select("body").append("svg")
.attr("width",500)
.attr("height",500)
.classed("SVG_frame",true)
.append("g")
const nodes = [{id:1},{id:2}];
const simulation = d3.forceSimulation(nodes)
.force("centering",d3.forceCenter(200,200))
.force("collision",d3.forceCollide(20))
const node = svg
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r",20);
simulation.on("tick",function() {
node.attr("cx",d => d.x).attr("cy",d => d.y)
});
<script src="https://d3js.org/d3.v4.min.js"></script>
Currently I am learning some "D3.js" and attempting to get my head around the way data is processed and selected.
I'm stuck on the following task I've created for myself.
Ideally, I want something that is functionally equivalent to:
<svg>
<circle r="20.5" cx="100" cy="200"></circle>
<circle r="20.5" cx="300" cy="10"></circle>
</svg>
What I have currently (with my logic) is:
var matrix = [ [{ "x": 100, "y": 200 }], [{ "x": 300, "y": 10 }]];
var result = d3.select("body").append("svg") // Append SVG to end of Body
.data(matrix) // select this data
.selectAll("g") //g is a svg grouping tag
.data(function (d) { return d; }) //Unwrap the first part of the array
.enter() // Grab all the data that is new to the selection in each array
.selectAll("g")
.data(function (d) { return d;}) // foreach each item inside the 1D array
.enter() // For all the data that doesn't exist already in the SVG
.append("circle") // Append Circle to the DOM with the following attributes
.attr("r", 20.5)
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; });
};
Weirdly enough the following :
var result = d3.select("body").append("svg")
.data(matrix)
.selectAll("g")
.enter()
.append("circle")
.attr("r", 20.5)
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; });
};
Seems somehow able to get the first item in the array but doesn't iterate correctly. I'm not quite sure how it's entering the array.
D3 seems to be quite a big step away from the programming paradigms I'm used to, and more difficult to debug so it would be awesome if someone could explain where I'm going wrong.
Oh, and while the example is quite useless and I could flatten it using the merge command - for the purposes of fully understanding D3 manipulation. I'd like to draw the couple of circles without the merge :)
Thanks!
Seeing you mention that you're new to d3 I'll make a few comments on the basics.
The first is that we're trying to place some svg elements on the DOM, so first we have to have a svg canvas to work on. Typically its set up early in the code and looks something like this:
var svg = d3.select("body")
.append("svg")
.attr("width", 350)
.attr("height", 250);
Note that it would be best to define variables for height and width (but I'm being lazy).
So now you have your canvas lets look at how d3 iterates. d3 iterates over an array, so you don't have to have an array within an array for your example as in:
var matrix = [ { "x": 100, "y": 200 }], [{ "x": 300, "y": 10 }];
Now you're second block of code is almost there, with just a bit of rearrangement. The first thing we need to do is t create placeholders for the circles in your svg canvas using svg.selectAll("circle"). Next we introduce the data to the empty placeholders using data(matrix) and this is bound using 'enter()`. Now all we have to do is append the circles and give them some attributes which is all the rest of the code does
svg.selectAll("circle")
.data(matrix)
.enter()
.append("circle")
.attr("r", 20.5)
.attr("cx", function (d) {
return d.x;
})
.attr("cy", function (d) {
return d.y;
});
You can see this all put together in this fiddle with some minor changes.
If you want to get to know d3 I'd really recommend get Scott Murray book on d3 it's an excellent introduction
m creating a scatter plot and I have problems plotting my circles (datapoints).
When appending the first set of circles it is drawn fine on the graph. However the next set I try to append won't be draw for some reason. How can this be? Am I using the Enter/append/select wrong the 2nd time?
I have a JSFiddle with my code: http://jsfiddle.net/4wptM/
I have uncommented the parts where I load and manipulate my data and created the same array with a smaller sample. As it can be seen only the first set of circles are shown and the 2nd ones aren't.
My code of the circle section is pasted below:
var circle = SVGbody
.selectAll("circle")
.data(graphData[0])
.enter()
.append("circle")
.attr("cx",function(d){return xScale(100);})
.attr("cy",function(d){return yScale(parseFloat(d))})
.attr("r",5);
circle = SVGbody
.selectAll("circle")
.data(graphData[1])
.enter()
.append("circle")
.attr("cx",function(d){return xScale(200);})
.attr("cy",function(d){return yScale(parseFloat(d))})
.attr("r",5);
I just have those 2 copied to try and figure out the problem. The actual code I have is in the following for loop. When I run it in this loop it draws the circles for when the index is 1 and a few of when the index is 3.
for(var i = 0;i < graphData.length;i++){
var circle = SVGbody
.selectAll("circle")
.data(graphData[i])
.enter()
.append("circle")
.attr("cx",function(d){return xScale((i*100)+100);})
.attr("cy",function(d){return yScale(parseFloat(d))})
.attr("r",20);
//console.log(i + " ::::::: " + graphData[i])
}
Some help would be so greatly appretiated. I really can't figure out what I'm doing wrong.
When operating on a existing set of elements (as it is the case here on the second .selectAll("circle") the .data() method uses only these elements from the array which have no corresponding index in the selecton made with .selectAll(). To prevent this behaviour you will have to add a key function as the second parameter of .data() as explained here and here.
Here this function only has to return each element of the array:
function (d) {
return d;
}
In the fiddle it would look something like this:
var circle = SVGbody
.selectAll("circle")
.data(graphData[0], function(d) { return d; }) // <-- this one
.enter()
.append("circle")
.attr("cx",function(d){return xScale(100);})
.attr("cy",function(d){return yScale(parseFloat(d))})
.attr("r",5);
fiddle
And to change the fill color of an element you have to use .style("fill", ...) and not .attr("fill", ...)
.style("fill", "green")
I've tried this different ways, but nothing seems to be working. Here is what I currently have:
var vis = d3.select("#chart").append("svg")
.attr("width", 1000)
.attr("height", 667),
scaleX = d3.scale.linear()
.domain([-30,30])
.range([0,600]),
scaleY = d3.scale.linear()
.domain([0,50])
.range([500,0]),
poly = [{"x":0, "y":25},
{"x":8.5,"y":23.4},
{"x":13.0,"y":21.0},
{"x":19.0,"y":15.5}];
vis.selectAll("polygon")
.data(poly)
.enter()
.append("polygon")
.attr("points",function(d) {
return [scaleX(d.x),scaleY(d.y)].join(",")})
.attr("stroke","black")
.attr("stroke-width",2);
I assume the problem here is either with the way I am defining the points data as an array of individual point objects, or something to do with how I'm writing the function for the .attr("points",...
I've been looking all over the web for a tutorial or example of how to draw a simple polygon, but I just can't seem to find it.
A polygon looks something like: <polygon points="200,10 250,190 160,210" />
So, your full poly array should produce one long string that will create one polygon. Because we are talking about one polygon the data join should also be an array with only one element. That is why the code below shows: data([poly]) if you wanted two identical polygons you would change this to data([poly, poly]).
The goal is to create one string from your array with 4 objects. I used a map and another join for this:
poly = [{"x":0.0, "y":25.0},
{"x":8.5,"y":23.4},
{"x":13.0,"y":21.0},
{"x":19.0,"y":15.5}];
vis.selectAll("polygon")
.data([poly])
.enter().append("polygon")
.attr("points",function(d) {
return d.map(function(d) {
return [scaleX(d.x),scaleY(d.y)].join(",");
}).join(" ");
})
.attr("stroke","black")
.attr("stroke-width",2);
See working fiddle: http://jsfiddle.net/4xXQT/
The above answers are needlessly complicated.
All you have to do is specify the points as a string and everything works fine. Something like this below will work.
var canvas = d3.select("body")
.append("svg")
.attr("height", 600)
.attr("width", 600);
canvas.append("polygon")
.attr("points", "200,10 250,190 160,210")
.style("fill", "green")
.style("stroke", "black")
.style("strokeWidth", "10px");
are you trying to draw polygon shapes? - like this. http://bl.ocks.org/2271944 The start of your code looks like a typical chart - which would usually conclude something like this.
chart.selectAll("line")
.data(x.ticks(10))
.enter().append("line")
.attr("x1", x)
.attr("x2", x)
.attr("y1", 0)
.attr("y2", 120)
.style("stroke", "#ccc");