I tried to show my two Line Chart using D3.js,
I can display the first chart "blob",
But it doesn't work with "blub"!!
How can I fix it and how to simplify the for loop?
var canvas=d3.select("body")
.append("svg")
.attr("width",500).attr("height",300);
var donnees= {
blob:[
{x:10,y:20},{x:20,y:60}, {x:30,y:70},
{x:40,y:202},{x:50,y:260}, {x:60,y:70},
{x:70,y:75},{x:80,y:70}, {x:90,y:0}
],
blub:[
{x:100,y:20},{x:110,y:20},{x:120,y:60}, {x:130,y:70},
{x:140,y:32},{x:150,y:60}, {x:160,y:90},
{x:170,y:75},{x:180,y:100}, {x:190,y:20}
]
};
var groupe= canvas.append("g")
.attr("transform", "translate(20,20)");
var line= d3.svg.line()
.x (function(d){return d.x})
.y (function(d){return d.y});
var colors = d3.scale.category20();
var index = 0;
for (var i in donnees) {
groupe.selectAll("path")
.data([donnees[i]])
.enter()
.append("path")
.attr("d",line)
.attr("fill", "none")
.attr("stroke", colors(index))
.attr("stroke-width","1");
index++
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
First off, you don't need a loop here (you almost never do in D3), but you can use nested selections:
groupe.selectAll("g")
.data(d3.values(donnees))
.enter()
.append("g")
.selectAll("path")
.data(function(d) { return [d]; })
.enter()
.append("path")
...
The problem you're seeing specifically is because you're selecting path elements, which, after the first iteration of the loop, will be there already. That is, the data you provide in .data() is matched by the existing path element and hence the .enter() selection is empty, adding nothing. You can work around this e.g. by assigning a class to each path that allows you to distinguish them, but a better solution is to use nested selection as I've outlined above.
Complete demo here.
See this tutorial for more details on how selections work.
Instead of using data() and enter(), i directly appended the path tag to g tag which worked for me.
var canvas=d3.select("body")
.append("svg")
.attr("width",500).attr("height",300);
var donnees= {
blob:[
{x:10,y:20},{x:20,y:60}, {x:30,y:70},
{x:40,y:202},{x:50,y:260}, {x:60,y:70},
{x:70,y:75},{x:80,y:70}, {x:90,y:0}
],
blub:[
{x:100,y:20},{x:110,y:20},{x:120,y:60}, {x:130,y:70},
{x:140,y:32},{x:150,y:60}, {x:160,y:90},
{x:170,y:75},{x:180,y:100}, {x:190,y:20}
]
};
var groupe= canvas.append("g")
.attr("transform", "translate(20,20)");
var line= d3.svg.line()
.x (function(d){ return d.x})
.y (function(d){return d.y});
var colors = d3.scale.category20();
var index = 0;
for (var i in donnees) {
groupe
.append("path")
.attr("d", line(donnees[i]))
.attr("fill", "none")
.attr("stroke", colors(index))
.attr("stroke-width", 1);
index++
}
Related
I am trying to replicate this example of a multiline chart with dots. My data is basically the same, where I have an object with name and values in the first level, and then a couple of values in the second level inside values. For the most part, my code works, but for some reason, the j index in the anonymous function for the fill returns an array of repeated circle instead of returning the parent of the current element. I believe this may have something to do with the way I created the svg and selected the elements, but I can't figure it out. Below is an excerpt of my code that shows how I created the svg, the line path and the circles.
var svgb = d3.select("body")
.append("svg")
.attr("id","svg-b")
.attr("width", width)
.attr("height", height)
var gameb = svgb.selectAll(".gameb")
.data(games)
.enter()
.append("g")
.attr("class", "gameb");
gameb.append("path")
.attr("class", "line")
.attr("d", function(d) {return line_count(d.values); })
.style("stroke", function(d) { return color(d.name); })
.style("fill", "none");
gameb.selectAll("circle")
.data(function(d) {return d.values;})
.enter()
.append("circle")
.attr("cx", function(d) {return x(d.date);})
.attr("cy", function(d) {return y_count(d.count);})
.attr("r", 3)
.style("fill", function(d,i,j) {console.log(j)
return color(games[j].name);});
j (or more accurately, the third parameter) will always be the nodes in the selection (the array of circles here), not the parent. If you want the parent datum you can use:
.attr("fill", function() {
let parent = this.parentNode;
let datum = d3.select(parent).datum();
return color(datum.name);
})
Note that using ()=> instead of function() will change the this context and the above will not work.
However, rather than coloring each circle independently, you could use a or the parent g to color the circles too:
gameb.append("g")
.style("fill", function(d) { return color(d.name); })
.selectAll("circle")
.data(function(d) {return d.values;})
.enter()
.append("circle")
.attr("cx", function(d) {return x(d.date);})
.attr("cy", function(d) {return y_count(d.count);})
.attr("r", 3);
Here we add an intermediate g (though we could use the original parent with a few additional modifications), apply a fill color to it, and then the parent g will color the children circles for us. The datum is passed on to this new g behind the scenes.
I searched for this and found a lot of info on how to set scales, but I'm trying to color individual SVGs created in D3 using values from a column that's populated with hex values. In the code below "Color1" is the column populated with different hex color values, e.g., #000000;
Here's what I've tried that makes intuitive sense to me but isn't working, instead the chart populates with the fill as black:
var circles = svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx',function (d) { return xScale(d.xvalue) })
.attr('cy',function (d) { return yScale(d.yvalue) })
.attr('r','3')
.attr('stroke','black')
.attr('stroke-width',1)
.attr('fill', function (d) {return d.Color1})
I've also tried surrounding the function with "'" but was unsuccessful.
The color property should not include a semicolon:
var data = [
{Color1: "#aaaaaa;"},
{Color1: "#aaaaaa"}
]
var svg = d3.select("body").append("svg");
var circles = svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx',(d,i)=>i*100+50)
.attr('cy', 100)
.attr('r','10')
.attr('stroke','black')
.attr('stroke-width',1)
.attr('fill', function(d) { return d.Color1; })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
You could just slice the last character off if you have the semicolon hard coded in your data:
.attr("fill", function(d) { return d.Color1.slice(0,-1); })
I could locate rectangle using the following.
I want to replace these rectangles with image/icon(jpg/url)
for(var i=0; i< tempdata.length; i++)
{
var name = "rect"+i;
d3.select("#floor svg").selectAll('rect')
.data(tempdata).enter()
.append("svg:rect")
.attr("stroke", "black")
.attr("fill", (d,i)=>tempdata[i].SENSOR_COLOR)
//.attr("r", 5)
.attr("width", 20)
.attr("height", 20)
.attr("x", (d,i)=>tempdata[i].CX)
.attr("y", (d,i)=>tempdata[i].CY)
.attr("idCircle",(d,i)=>name)
}
First of all: don't use for loops to retrieve data in D3. D3 already provides all you need to bind and retrieve your data. Indeed, there are several situations where it's a good idea to use a for loop, but this is not one of them.
Regarding your question: instead of append("rect"), you have to use append("image"):
var images = svg.selectAll(".images")
.data(data)
.enter()
.append("image");
In this demo snippet, I set the url of the images in the data array, using a key conveniently named url. Then, you have to append them using:
.attr("xlink:href", function(d){return d.url})
Here is the demo snippet, using favicons:
var svg = d3.select("body")
.append("svg")
.attr("width", 200)
.attr("height", 200);
var data = [{url:"https://dab1nmslvvntp.cloudfront.net/wp-content/uploads/2009/02/amazon.gif", x:20, y:40},
{url:"https://dab1nmslvvntp.cloudfront.net/wp-content/uploads/2009/02/skype.gif", x:90, y:110},
{url: "https://dab1nmslvvntp.cloudfront.net/wp-content/uploads/2009/02/espn.gif", x:150, y:150},
{url: "https://dab1nmslvvntp.cloudfront.net/wp-content/uploads/2009/02/twitter.gif", x:180, y:50}];
var images = svg.selectAll(".images")
.data(data)
.enter()
.append("image");
images.attr("xlink:href", function(d){return d.url})
.attr("x", function(d){return d.x})
.attr("y", function(d){return d.y})
.attr("width", 16)
.attr("height", 16);
<script src="https://d3js.org/d3.v4.min.js"></script>
I'm having problems rendering doughnut charts in d3. I have 2 doughnuts, that I'm creating side-by-side inside of a for each function::
data.forEach(function(d,i){...}
The charts render fine. When I go to update the chart, paths are redrawn. Not sure why this happening because I'm using .enter()
Any advise?
var singleDonutData = [1];
var donutSections=singleDonut.selectAll(".donutSections")
.data(singleDonutData)
.enter()
.append("g")
.attr("class","donutSections");
var pathGroup = svg.select("." + donutName).select(".donutSections").selectAll("path.arc")
.data(pie(d));
var path = pathGroup
.enter()
.append('path')
.style('fill', function(d){ //give arc a color in SVG Scale
return color(d.data.label);
})
.attr("d", arc)
.each(function(d) { this._current = d; }); // store the initial angles;
You need to add corresponding class name on the generated path, for example:
var pathGroup = svg.select("." + donutName).select(".donutSections").selectAll("path.arc")
.data(pie(d));
var path = pathGroup
.enter()
.append('path')
.style('fill', function(d){
return color(d.data.label);
})
.attr("class", "arc") // corresponding to selectAll("path.arc")
.attr("d", arc)
.each(function(d) { this._current = d; }); angles;
So that when you update the chart, d3 can correctly select these already rendered path.
After this update, you also need to add code to handle the update selection. Something like this:
pathGroup.transition().duration(1000)
.attrTween("d", function(d){
// some transition animation code here if you need it.
})
I'm trying to edit the data of created circles in D3. Below my code is pasted of me creating a lot of circles based on some data from graphData.
Supposed I'd want to re-arrange my circles Y position with a new dataset, by transitioning them to their new destinations. How would perform this task? I've tried using attr.("cy", function(d){return yScale(parseFloat(d))} ) to update my Y-coordinates by adding data(graphData[i], function(d){return d;}) with my new data, but this does not work.
You can take a look at my JSFiddle: http://jsfiddle.net/RBr8h/1/
Instead of the for-loop in the following code I've created circles on 2 ticks of my X-axis. I have 3 sets of data and I've used to of them in the example in the fiddle. I'd like to able to use the 3rd dataset instead of the 2 first ones on both circles.
var circle;
for(var i = 0;i < graphData.length;i++){
circle = SVGbody
.selectAll("circle")
.data(graphData[i], function(d){return d;})
.enter()
.append("circle")
.attr("cx",xScale(0))
.attr("cy", yScale(minAxisY))
.attr("r",4)
.style('opacity', 0)
.transition()
.duration(1000)
.attr("cx", function(d){
return spreadCircles(i);
})
//.attr("cy", function (d, i){ return yScale(i); })
.style('opacity', 1)
.transition()
.duration(1500)
.attr("cy", function(d){return yScale(parseFloat(d))} );
Thank you for your help in advance!
To put some flesh on Lars comment, here is a FIDDLE leveraging the enter/update/exit pattern to help you out. I have altered and simplified your code (and data) just enough to demonstrate the principle.
function updateCircles(dataset,color) {
var circle = SVGbody
.selectAll("circle")
.data(dataset, function(d) { return d; });
circle
.exit()
.transition().duration(750)
.attr("r", 0)
.remove();
circle
.enter()
.append("circle");
circle
.attr("cx",function(d){return xScale(100);})
.attr("cy",function(d){return yScale(parseFloat(d))})
.attr("r",0)
.transition().duration(1500)
.attr("r",5)
.style("fill", color);
};
Update fiddle with data keyed off by index...so, circles just have their position updated.