I'm trying to add points to a line graph using d3 in this example:
http://bl.ocks.org/mbostock/3884955
I was also trying to follow this post
How do you get the points to look like this picture from the documentation?
http://github.com/mbostock/d3/wiki/line.png
The stroke of the circle should match the line color.
var color = d3.scale.category10();
d3.csv("data.csv", function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
data.forEach(function(d) {
d.date = parseDate(d.date);
});
var ranks = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {date: d.date, ranking: +d[name]};
})
};
});
var rank = svg.selectAll(".rank")
.data(ranks)
.enter().append("g")
.attr("class", "rank");
rank.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
var point = rank.append("g")
.attr("class", "line-point");
point.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(d.ranking) })
.attr("r", 3.5)
.style("fill", "white")
.style("stroke", function(d) { return color(d.name); });
.style("stroke", function(d) { return color(this.parentNode.__data__.name); })
See JSBin code
Found answer here
Related
Using this I was able to add names to the map: Add names of the states to a map in d3.js
Now, how do I rotate those text as it is overlapping with each other.
For those who are looking for the solution.
Adding this solved it. It produces composition matrix.
.attr('transform', function(d) {
return 'translate('+path.centroid(d)[0]+','+path.centroid(d)[1]+') rotate(-45);
})
Here is the updated code:
function draw(){
d3.json("readme.json", function(json) {
g.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.on("click", click);
g.selectAll("text")
.data(json.features)
.enter()
.append("svg:text")
.text(function(d){
return d.properties.name;
})
// .attr("x", function(d){
// return path.centroid(d)[0];
// })
// .attr("y", function(d){
// return path.centroid(d)[1];
// })
.attr("text-anchor","middle")
.attr('font-size','6pt')
// added
.attr('transform', function(d) {
return 'translate('+path.centroid(d)[0]+','+path.centroid(d)[1]+') rotate(-45);
})
});
}
I can use d3 to draw a pie chart or a graph, I can even draw a pie chart within each node of a graph as shown here.
Is it possible to create a reusable function that generate the pie chart and attach its result to the each node? That way the pie chart code could be reused, for instance in a gallery of charts.
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node");
// draw pie chart
node.selectAll("path")
.data(function(d, i) {return pie(d.proportions); })
.enter()
.append("svg:path")
.attr("d", arc)
.attr("fill", function(d, i) { return color(d.data.group); });;
From the above code, I tried the following code which doesn't work
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.call(drawPie(function(d) { return d.proportions; }));
function drawPie(d) {
this.selectAll("path")
.data(function(d, i) {return pie(d.proportions); })
.enter()
.append("svg:path")
.attr("d", arc)
.attr("fill", function(d, i) { return color(d.data.group); });;
}
Your original idea is much closer than the one recommended in the other answer, you just need to understand how selection.call works.
This is not tested but the general principle is like...
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.call(drawPie);
function drawPie(selection) {
this.selectAll("path")
.data(function(d, i) {return pie(d.proportions); })
.enter()
.append("svg:path")
.attr("d", arc)
.attr("fill", function(d, i) { return color(d.data.group); });;
}
In reference to your first attempt, if you stop and think about this line...
.call(drawPie(function(d) { return d.proportions; }));
...it's actually trying to call null because that's what is returned by drawPie. It's equivalent to...
.call(null);
Based on the recommendations, here is the modified code which still require some improvements. An error message report that "row 93 undefined is not an object evaluating d.proportions"
graph = { "nodes":[
{"proportions": [{"group": 1, "value": 1},
{"group": 2, "value": 2},
{"group": 3, "value": 3}]},
{"proportions": [{"group": 1, "value": 2},
{"group": 2, "value": 2},
{"group": 3, "value": 2}]}],
"links":[{"source": 0, "target": 1, "length": 500, "width": 1}]
}
var width = 960,
height = 500,
radius = 25,
color = d3.scale.category10();
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.value; });
var arc = d3.svg.arc()
.outerRadius(radius)
.innerRadius(10);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.charge(-120)
.linkDistance(4 * radius)
.size([width, height]);
force.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link");
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter()
.append(function(d) {return createPie(d);}) // .append(createPie) --- shorter version
.attr("class", "node");
// node.selectAll("path")
// .data(function(d, i) {return pie(d.proportions); })
// .enter()
// .append("svg:path")
// .attr("d", arc)
// .attr("fill", function(d, i) { return color(d.data.group); });;
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"});
});
function createPie(d) {
console.log(d);
var pie = d3.select(document.createElement('svg:g'));
pie.selectAll('path')
.data(function(d, i) {return pie(d.proportions); })
.enter()
.append("svg:path")
.attr("d", arc)
.attr("fill", function(d, i) { return color(d.data.group); });
return pie.node();
}
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter()
.append(function(d){return createPie(d);}) // .append(createPie) --- shorter version
.attr("class", "node");
function createPie(data) {
var pie = d3.select(document.createElement('svg:g'));
pie.selectAll('path')
...;
return pie.node();
}
UPDATE:
function createPie(d) {
console.log(d);
var p = d3.select(document.createElement('svg:g'));
p.selectAll('path')
.data(pie(d.proportions))
.enter()
.append("svg:path")
.attr("d", arc)
.attr("fill", function(d, i) { return color(d.data.group); });
return p.node();
}
the previous variable pie needs to be refactored because it overwrites the one in parent scope.
and the data call needs to be fixed as well
I am new to D3 and I am working with code from here. I changed the code so I can add new nodes (neighbors) and edges to them with data from MySql upon click on a node, which is why part of the node code is in start(). I want to append text labels to the nodes, and from some googling I understand that both the circle element and the text element needs to be within a g. However, when I do this, I get an error from d3.js on line 742:
Uncaught NotFoundError: Failed to execute 'insertBefore' on 'Node':
The node before which the new node is to be inserted is not a child of
this node
Why is this and how do I fix it to get what I want, while preserving the addNode functionality?
Here is my code:
var width = 960,
height = 500;
var color = d3.scale.category10();
var nodes = [],
links = [];
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.charge(-400)
.linkDistance(120)
.size([width, height])
.on("tick", tick);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var node = svg.selectAll(".node")/*.append("g")*/,
link = svg.selectAll(".link");
function start() {
link = link.data(force.links(), function(d) { return d.source.id + "-" + d.target.id; });
link.enter().insert("line", ".node").attr("class", "link");
link.exit().remove();
node = node.data(force.nodes(), function(d) { return d.id;});
node.enter()/*.append("g")*/
.append("circle")
.attr("class", function(d) { return "node " + d.id; })
.attr("id", function(d) { return d.id; })
.attr("r", 8)
.on("click", nodeClick)
.append("text")
.text(function(d) {return d.id; });
node.exit().remove();
force.start();
}
function nodeClick() {
var node_id = event.target.id;
handleClick(node_id, "node");
}
function tick() {
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
}
The commented-out append("g") indicates where I tried to place it (separate attempts).
You want to cache the d3 selector before appending the text.
node.enter().append("g")
.append("circle")
.attr("class", function(d) { return "node " + d.id; })
.attr("id", function(d) { return d.id; })
.attr("r", 8)
.on("click", nodeClick)
.append("text")
.text(function(d) {return d.id; });
That'll work but create a xml structure like this:
<g>
<circle>
<text>...</text>
</circle>
</g>
What you want is:
<g>
<circle>...</circle>
<text>...</text>
</g>
To achieve that, you must insert one step:
var g = node.enter().append("g");
g.append("circle")
.attr("class", function(d) { return "node " + d.id; })
.attr("id", function(d) { return d.id; })
.attr("r", 8)
.on("click", nodeClick);
g.append("text")
.text(function(d) {return d.id; });
I construct the "var cities" in such a way that beyond "some_date" the "at" turns from "0" to "1". Then I try to use this information to draw a line that beyond "some_date" turns dashed. Could please someone tell me what is wrong with the code?
var cities = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
if(d.date < some_date){
return {date: d.date, temperature: +d[name], vis:"0", at:"0"};
}else{
return {date: d.date, temperature: +d[name], vis:"0", at:"1"};
};
})
};
});
city.append("path")
.attr("class", "line")
.attr("d", function(d) {if(d.name.match(/pib_1t/)){
d.vis = "1";
return line(d.values);
}})
.style("stroke", function(d) { return color(d.name); })
.style("stroke-dasharray", function(d){
console.log(d.name);
d.values.forEach(function(e, i) {
if(e.at == "1"){
return ("2,2");
}else{
return ("2,0");
};
});
});
Well, I tried to use a "custom line generator" but I got an ugly result. So I solved the problem constructing two lines. The first one goes until "some_date" is reached and is a normal line. The second is a dashed line and it starts at "some_date". See the code bellow.
// first line
city.append("path")
.attr("class", "line")
.attr("d", function(d) {if(d.name.match(/pib_1t/)){
d.vis = "1";
return line(d.values.slice(0,[bisect(data,some_date)]));
}})
.style("stroke", function(d) { return color(d.name); });
// second line
city.append("path")
.attr("class", "line")
.style("stroke-dasharray", ("2, 2"))
.attr("d", function(d) {if(d.name.match(/pib_1t/)){
d.vis = "1";
return line(d.values.slice([bisect(data, some_date)-1],data.length));
}})
.style("stroke", function(d) { return color(d.name); });
I am able to populate a stacked bar chart first time, but my requirement is to update the stacked bar chart with new data on button click. On button click, i m making call to backend and getting the data, Could you please guide me as how to update the stacked bar char chart. My problem is passing the new data to bar chart.
d3.json("http://myhost/ITLabourEffortChart/effort/effort",function(error, data){
color.domain(d3.keys(data.effort[0]).filter(function(key) {
return key !== "qName"; }));
data.effort.forEach(function(d) {
var y0 = 0;
d.effortHr = color.domain().map(function(name) {
return {name: name, y0: y0, y1: y0 += +d[name]}; });
d.total = d.effortHr[d.effortHr.length - 1].y1;
});
x.domain(data.effort.map(function(d) { return d.qName; }));
y.domain([0, d3.max(data.effort, function(d) {
return d.total; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("FTE");
var state = svg.selectAll(".layer")
.data(data.effort)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x(d.qName) + ",0)"; });
rect = state.selectAll("rect")
.attr("id", "barchart")
.data(function(d) {
return d.effortHr; })
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function(d) {
return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.style("fill", function(d) { return color(d.name); });
On Update i am calling below method
function redraw() {
d3.json("http://localhost:8080/ITLabourEffortChart/effort/effort/YrReports",function(error, data){
color.domain(d3.keys(data.effort[0]).filter(function(key) {
return key !== "qName"; }));
data.effort.forEach(function(d) {
var y0 = 0;
d.ages = color.domain().map(function(name) {
return {name: name, y0: y0, y1: y0 += +d[name]}; });
d.total = d.ages[d.ages.length - 1].y1;
});
var updatebar = svg.selectAll("#barchart");
// Update…
updatebar
.transition()
.duration(500)
.delay(function(d, i) { return i * 10; })
.transition()
.attr("width", x.rangeBand())
.attr("y", function(d) {
return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.style("fill", function(d) { return color(d.name); }
.attr("x", function(d) {
return x(d.x); })
);
});
.attr("x", function(d) {
return x(d.x); })
);
});
To update your data you will just need to select the svg elements again and rebind the data. In your example you are already selecting the #barchart, now you just need to rebind the data. And you can do that in the same way you did it when you first created the svg Elements. So something like this should do the trick:
var updatebar = svg.selectAll("#barchart");
.data(newdata)
.transition()
.duration(500)
... (etc.)
Here you can find a more detailed explaination:
http://chimera.labs.oreilly.com/books/1230000000345/ch09.html#_updating_data
Update:
Ok, unfortunately I cannot use Fiddle so I just post my working code here. As far as I could see you have a problem with your selectAll, because there is no element called .effort. Here is the updated code for your redraw-function:
function redraw() {
var effort = [];
var obj = {
pfte: "20",
efte: "50",
qName: "Q1"
};
var obj2 = {
pfte: "10",
efte: "13",
qName: "Q2"
};
effort[0] = obj;
effort[1] = obj2;
var newDataSet = new Object();
newDataSet.effort = effort;
color.domain(d3.keys(newDataSet.effort[0]).filter(function (key) {
return key !== "qName";
}));
effortDataSet = newDataSet.effort;
effortDataSet.forEach(function (d) {
var y0 = 0;
d.effortHr = color.domain().map(function (name) {
return { name: name, y0: y0, y1: y0 += +d[name] };
});
d.total = d.effortHr[d.effortHr.length - 1].y1;
});
state = svg.selectAll(".g")
.data(effortDataSet)
.attr("class", "g")
.attr("transform", function (d) { return "translate(" + x(d.qName) + ",0)"; });
state = state.selectAll("rect")
.data(function (d) {
return d.effortHr;
})
.attr("width", x.rangeBand())
.attr("y", function (d) {
return y(d.y1);
})
.attr("height", function (d) {
//console.log(y(d.y0) - y(d.y1));
return y(d.y0) - y(d.y1);
})
.style("fill", function (d) { return color(d.name); });
}