I am experimenting with D3js Hive plots for the first time and am struggling to understand how to position labels alongside each node on the axes. Please see my fiddle, here:
http://jsfiddle.net/NovasTaylor/jgkqoasm/#base
Using this code I can place the labels for each node on its proper axis. The label (name) is not near its respective node. What code do I need to position the label adjacent to its node?
nodes.append("text")
.attr("class", "text")
.text(function(d) {return d.name;})
// Following is needed to place E,F on the vertical axis toward the top
// of the graph (group=3)
.attr("y", function(d) {
if (d.group === 3) {
return -radius(d.y);
} else {
return radius(d.y);
}
})
.attr("transform", function(d) {
if (d.group !== 3) {
return "rotate(" + (degrees(angle(d.group)) - 90) + ")";
}
})
Any help would be greatly appreciated!
Cheers,
Tim
You should be applying the same transform that's on your node circle elements: a rotation of degrees(angle(d.x)) and an x position of radius(d.y). Further, I would wrap the text in a g and then position that g using those attributes then append a text and rotate it counter to the g so the text is in a proper position:
nodes.append("g")
.attr("class", "text")
.attr("transform", function(d) {
var t = "rotate(" + degrees(angle(d.x)) + ")";
t += "translate(" + radius(d.y) + ",0)";
return t;
})
.append("text")
.attr("transform", function(d) {
var t = "rotate(" + -degrees(angle(d.x)) + ")";
return t;
})
.text(function(d) {return d.name;})
Full working code:
var width = 960,
height = 500,
innerRadius = 40,
outerRadius = 240;
var angle = d3.scale.ordinal().domain(d3.range(4)).rangePoints([0, 2 * Math.PI]),
radius = d3.scale.linear().range([innerRadius, outerRadius]),
color = d3.scale.category10().domain(d3.range(20));
var nodes = [
{name:"A", group:1, x: 0, y: .1},
{name:"B", group:1, x: 0, y: .9},
{name:"C", group:2, x: 1, y: .2},
{name:"D", group:2, x: 1, y: .3},
{name:"E", group:3, x: 2, y: .1},
{name:"F", group:3, x: 2, y: .8}
];
var links = [
{source: nodes[0], target: nodes[2]},
{source: nodes[1], target: nodes[3]},
{source: nodes[2], target: nodes[4]},
{source: nodes[2], target: nodes[5]},
{source: nodes[3], target: nodes[5]},
{source: nodes[4], target: nodes[0]},
{source: nodes[5], target: nodes[1]}
];
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
svg.selectAll(".axis")
.data(d3.range(3))
.enter().append("line")
.attr("class", "axis")
.attr("transform", function(d) { return "rotate(" + degrees(angle(d)) + ")"; })
.attr("x1", radius.range()[0])
.attr("x2", radius.range()[1]);
svg.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", d3.hive.link()
.angle(function(d) { return angle(d.x); })
.radius(function(d) { return radius(d.y); }))
.style("stroke", function(d) { return color(d.source.x); });
var nodes = svg.selectAll("g.node")
.data(nodes, function (d) {
return d.name;
});
nodes.enter()
.append("g");
nodes.append("circle")
.attr("class", "node")
.attr("transform", function(d) { return "rotate(" + degrees(angle(d.x)) + ")"; })
.attr("cx", function(d) { return radius(d.y); })
.attr("r", 5)
.style("fill", function(d) { return color(d.x); });
// Append name as label to each node
nodes.append("g")
.attr("class", "text")
.attr("transform", function(d) {
var t = "rotate(" + degrees(angle(d.x)) + ")";
t += "translate(" + radius(d.y) + ",0)";
return t;
})
.append("text")
.attr("transform", function(d) {
var t = "rotate(" + -degrees(angle(d.x)) + ")";
return t;
})
.text(function(d) {return d.name;})
function degrees(radians) {
return radians / Math.PI * 180 - 90;
}
.link {
fill: none;
stroke-width: 1.5px;
}
.axis, .node {
stroke: #000;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="http://d3js.org/d3.hive.v0.min.js"></script>
I think it would be more easy to put the circles and texts in a group. So there wont need additional calculations for text positions.
var nodes = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.name;
});
var group = nodes.enter()
.append("g")
.attr("transform", function(d) {
return "rotate(" + degrees(angle(d.x)) + ")";
});
group.append("circle")
.attr("class", "node")
.attr("cx", function(d) {
return radius(d.y);
})
.attr("r", 5)
.style("fill", function(d) {
return color(d.x);
});
// Append name as label to each node
group.append("text")
.attr("class", "text")
.attr("dy", "1.2em")
.text(function(d) {
return d.name;
})
.attr("x", function(d, i) {
return radius(d.y);
});
var width = 960,
height = 500,
innerRadius = 40,
outerRadius = 240;
var angle = d3.scale.ordinal().domain(d3.range(4)).rangePoints([0, 2 * Math.PI]),
radius = d3.scale.linear().range([innerRadius, outerRadius]),
color = d3.scale.category10().domain(d3.range(20));
var nodes = [{
name: "A",
group: 1,
x: 0,
y: .1
}, {
name: "B",
group: 1,
x: 0,
y: .9
}, {
name: "C",
group: 2,
x: 1,
y: .2
}, {
name: "D",
group: 2,
x: 1,
y: .3
}, {
name: "E",
group: 3,
x: 2,
y: .1
}, {
name: "F",
group: 3,
x: 2,
y: .8
}];
var links = [{
source: nodes[0],
target: nodes[2]
}, {
source: nodes[1],
target: nodes[3]
}, {
source: nodes[2],
target: nodes[4]
}, {
source: nodes[2],
target: nodes[5]
}, {
source: nodes[3],
target: nodes[5]
}, {
source: nodes[4],
target: nodes[0]
}, {
source: nodes[5],
target: nodes[1]
}];
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
svg.selectAll(".axis")
.data(d3.range(3))
.enter().append("line")
.attr("class", "axis")
.attr("transform", function(d) {
return "rotate(" + degrees(angle(d)) + ")";
})
.attr("x1", radius.range()[0])
.attr("x2", radius.range()[1]);
svg.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", d3.hive.link()
.angle(function(d) {
return angle(d.x);
})
.radius(function(d) {
return radius(d.y);
}))
.style("stroke", function(d) {
return color(d.source.x);
});
var nodes = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.name;
});
var group = nodes.enter()
.append("g")
.attr("transform", function(d) {
return "rotate(" + degrees(angle(d.x)) + ")";
});
group.append("circle")
.attr("class", "node")
.attr("cx", function(d) {
return radius(d.y);
})
.attr("r", 5)
.style("fill", function(d) {
return color(d.x);
});
// Append name as label to each node
group.append("text")
.attr("class", "text")
.attr("dy", "1.2em")
.text(function(d) {
return d.name;
})
.attr("x", function(d, i) {
return radius(d.y);
});
function degrees(radians) {
return radians / Math.PI * 180 - 90;
}
.link {
fill: none;
stroke-width: 1.5px;
}
.axis,
.node {
stroke: #000;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="http://d3js.org/d3.hive.v0.min.js"></script>
Related
I am new to using d3.js. I am trying to append multiple text elements in a svg group. With that, I want to be able to drag the group of multiple text.
So for an example:
export function testNode (config = {}) {
let { svg, rectX, rectY, text1, text2 } = config
var data = [{ x: rectX, y:rectY, label: text1, label2: text2, labelX: rectX + 100, labelY: rectY + 200, labelX2: rectX + 300, labelY2: rectY + 300 }]
var group = svg.append("g")
.selectAll("g")
.data(data)
.enter()
.append("g")
.attr("transform",
"translate(" + 0 + "," + 0 + ")")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
group.append("text")
.data(data)
.attr("x", (d) => { return d.labelX })
.attr("y", (d) => { return d.labelY })
.attr("font-size", "1em")
.attr("color", "black")
.text((d) => { return d.label });
group.append("text")
.data(data)
.attr("x", (d) => { return d.labelX2 })
.attr("y", (d) => { return d.labelY2 })
.attr("font-size", ".75em")
.attr("color", "black")
.attr("class", "label")
.text((d) => { return d.metricValue_01 });
function dragStarted() {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this).select("text")
.attr("x", d.labelX = d3.event.x + 10)
.attr("y", d.labelY = d3.event.y + 20);
d3.select(this).select("text")
.attr("x", d.labelX2 = d3.event.x + 10)
.attr("y", d.labelY2 = d3.event.y + 20);
function dragended() {
d3.select(this).classed("active", false);
}
If I use the selectAll method, the text gets clumped together. Therefore, I was wondering if I can drag the text group in their proper position based on the coordinate I give it (while being dragged). Anyways please let me know if I need to provide any further information. Thank you
If you want to select the second label, but don't want to use selectAll as it selects both, you can give the labels classes when appending and select those when dragging:
d3.select(this).select(".bigLabel")
.attr("x", d.labelX = d3.event.x + 10)
.attr("y", d.labelY = d3.event.y + 20);
d3.select(this).select(".smallLabel")
.attr("x", d.labelX2 = d3.event.x + 10)
.attr("y", d.labelY2 = d3.event.y + 20);
Though of course this will set the same coordinate for both labels unless you specify an offset, as below:
var data = [
{ x: 100, x2: 100, y: 100, y2: 120, label: "label1", value: "17%" },
{ x: 300, x2: 300, y: 200, y2: 220, label: "label2", value: "83%" },
{ x: 100, x2: 100, y: 200, y2: 220, label: "label3", value: "11%" },
{ x: 300, x2: 300, y: 100, y2: 120, label: "label4", value: "96%" }
];
var svg = d3.select("svg");
var labels = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.call(d3.drag()
.on("drag",drag));
labels.append("text")
.attr("font-size", "1em")
.attr("x", function(d) { return d.x;})
.attr("y", function(d) { return d.y;})
.text(function(d) { return d.label; })
.attr("class","label1");
labels.append("text")
.attr("font-size", ".75em")
.attr("x", function(d) { return d.x2;})
.attr("y", function(d) { return d.y2;})
.text(function(d) { return d.value; })
.attr("class","label2");
function drag(d) {
var x = d3.event.x;
var y = d3.event.y;
d3.select(this)
.select(".label1")
.attr("x", function(d) { return d.x = x; })
.attr("y", function(d) { return d.y = y; })
d3.select(this)
.select(".label2")
.attr("x", function(d) { return d.x2 = x; })
.attr("y", function(d) { return d.y2 = y + 20; })
}
text {
text-anchor:middle;
cursor:pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="400"></svg>
I won't dive into the alternative in too much depth, but it applies the drag to the g, positioning both text labels at the same time. This can handle irregular spacing between sibling labels easier than the above:
var data = [
{ x: 100, y: 100, label: "label1", value: "17%" },
{ x: 300, y: 200, label: "label2", value: "83%" },
{ x: 100, y: 200, label: "label3", value: "11%" },
{ x: 300, y: 100, label: "label4", value: "96%" }
];
var svg = d3.select("svg");
var labels = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.attr("transform",function(d) {
return "translate("+[d.x,d.y]+")";
})
.call(d3.drag().on("drag", drag));
labels.append("text")
.attr("font-size", "1em")
.text(function(d) { return d.label; });
labels.append("text")
.attr("font-size", ".75em")
.text(function(d) { return d.value; })
.attr("dy", "1em")
function drag(d) {
d3.select(this)
.attr("transform","translate("+[d.x=d3.event.x,d.y=d3.event.y]+")");
}
text {
text-anchor: middle;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="400"></svg>
Also, there is no need to use append("text").data(data), this is not doing anything - your datum is already bound to the newly appended element
Lastly, you can make this work with selectAll() if you set attributes using the second parameter of any provided function when setting attributes: (d,i)=>... i is the index of the element, so if your sibling labels are regularly spaced, you can use something like:
var data = [
{ x: 100, y: 100, label: "label1", value: "17%" },
{ x: 300, y: 200, label: "label2", value: "83%" },
{ x: 100, y: 200, label: "label3", value: "11%" },
{ x: 300, y: 100, label: "label4", value: "96%" }
];
var svg = d3.select("svg");
var labels = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.call(d3.drag()
.on("drag",drag));
labels.append("text")
.attr("font-size", "1em")
.attr("x", function(d) { return d.x;})
.attr("y", function(d) { return d.y;})
.text(function(d) { return d.label; })
labels.append("text")
.attr("font-size", ".75em")
.attr("x", function(d) { return d.x;})
.attr("y", function(d) { return d.y + 20;})
.text(function(d) { return d.value; })
function drag(d) {
var x = d3.event.x;
var y = d3.event.y;
d3.select(this)
.selectAll("text")
.attr("x", function(d) { return d.x = x; })
.attr("y", function(d,i) { d.y = y;
return d.y + i * 20;
})
}
text {
text-anchor:middle;
cursor:pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="400"></svg>
[EDIT] Outputted Graph: https://imgur.com/a/D0wtP
I am trying to shade in the area between 2 lines in a graph. The plots shows commits made by software engineers and shows their tt100 lines of raw and productive code, and i am trying to shade the delta region between the 2 lines. Some of the solutions I have found do not seem to match my approach. I appreciate any help. My code is the following:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.axis--x path {
display: none;
}
.axis--y path {
display: none;
}
.line {
fill: none;
stroke-width: 1.5px;
}
.grid line {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}
</style>
<svg width="1080" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 80, bottom: 30, left: 50},
width = svg.attr("width") - margin.left - margin.right - 50,
height = svg.attr("height") - margin.top - margin.bottom;
svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "#f5f5f1")
var parseTime = d3.timeParse("%Y%m");
var x = d3.scaleTime().range([0, width]),
y = d3.scaleLinear().range([height, 0]),
z = d3.scaleOrdinal(d3.schemeCategory10);
var line = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.time); });
var g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// gridlines in y axis function
function make_y_gridlines() {
return d3.axisLeft(y)
.ticks(5)
}
d3.csv("data.csv", type, function(error, data) {
if (error) throw error;
var employees1 = data.columns.slice(1,3).map(function(id) {
return {
id: id,
values: data.map(function(d) {
return {date: d.date, time: d[id]};
})
};
});
var employees2 = data.columns.slice(3).map(function(id) {
return {
id: id,
values: data.map(function(d) {
return {date: d.date, time: d[id]};
})
};
});
var employees = data.columns.slice(1).map(function(id) {
return {
id: id,
values: data.map(function(d) {
return {date: d.date, time: d[id]};
})
};
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
0,
d3.max(employees, function(c) { return d3.max(c.values, function(d) { return d.time; }); })
]);
z.domain(employees.map(function(c) { return c.id; }));
// add the Y gridlines
g.append("g")
.attr("class", "grid")
.call(make_y_gridlines()
.tickSize(-width)
.tickFormat("")
)
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -45)
.attr("dy", "0.71em")
.attr("fill", "#000")
.text("HOURS TO PRODUCE 100 LINES OF CODE (PRODUCTIVE VS RAW)");
g.append("text")
.attr("x", (width / 4))
.attr("y", 0 - (margin.top / 8))
.attr("text-anchor", "middle")
.style("font-size", "24px")
.text("Churn Over Time");
var employee1 = g.selectAll(".employee1")
.data(employees1)
.enter().append("g")
.attr("class", "employee1");
var employee2 = g.selectAll(".employee2")
.data(employees2)
.enter().append("g")
.attr("class", "employee2");
employee1.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", "A057AE");
employee1.append("text")
.datum(function(d) { return {id: d.id, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.time) + ")"; })
.attr("x", 3)
.attr("dy", "0.35em")
.style("font", "10px sans-serif")
.text(function(d) { return d.id; });
employee2.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", "91BF50");
employee2.append("text")
.datum(function(d) { return {id: d.id, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.time) + ")"; })
.attr("x", 3)
.attr("dy", "0.35em")
.style("font", "10px sans-serif")
.text(function(d) { return d.id; });
});
function type(d, _, columns) {
d.date = parseTime(d.date);
for (var i = 1, n = columns.length, c; i < n; ++i) d[c = columns[i]] = +d[c];
return d;
}
</script>
And the data.csv file is the following:
date,Paul Productive Code,Paul Raw Code,Michelle Productive Code,Michelle Raw Code
201801,4.1,3.2,2.2,1.9
201802,4.2,3.5,3.4,1.9
201803,4.1,3.1,3.1,1.9
201804,4.5,3.8,3.2,2.3
201805,6.4,4.7,3.7,2.7
201806,8.6,5.5,3.2,2.2
What I'm running now is showing a root node and bunch of nodes connected to it via d3 line element. I'd like to add a caption to the line, since I have it defined and can freely grab it from d.label (in the nodes section in JSON). I'm using the following code:
var width = 500,
height = 500;
var force = d3.layout.force()
.size([width, height])
.charge(-800)
.linkDistance(d => d.distance)
.on("tick", tick);
var svg = d3.select("#target").append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "mainsvg");
var link = svg.selectAll(".link"),
node = svg.selectAll(".node");
d3.json("test.json", function(error, graph) {
if (error) throw error;
force
.nodes(graph.nodes)
.links(graph.links)
.start();
link = link.data(graph.links)
.enter().append("line")
.attr("class", "link");
node = node.data(graph.nodes)
.enter().append("a")
node.append("g")
.attr("class", "node");
node.append("circle")
.attr("class", "circle")
.attr("r", function(d) { return d.r })
.attr("fill", function(d) { return d.color })
.attr("stroke", function(d){ return d.color });
});
function tick() {
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; });
svg.selectAll(".circle").attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
svg.selectAll(".text").attr("x", function(d) { return d.x+d.xoffs; })
.attr("y", function(d) { return d.y+d.yoffs; });
}
Now I've tried using normal <text> elements appended to the edgepath in this fashion:
var edgepaths = svg.selectAll(".edgepath")
.data(graph.links)
.enter()
.append('path')
.attr({'d': function(d) {return 'M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y},
'class':'edgepath',
'fill-opacity':0,
'stroke-opacity':0,
'fill':'blue',
'stroke':'red',
'id':function(d,i) {return 'edgepath'+i}})
.style("pointer-events", "none");
var edgelabels = svg.selectAll(".edgelabel")
.data(graph.links)
.enter()
.append('text')
.style("pointer-events", "none")
.attr({'class':'edgelabel',
'id':function(d,i){return 'edgelabel'+i},
'dx':80,
'dy':10,
'font-size':10,
'fill':'black'});
edgelabels.append('textPath')
.attr('xlink:href',function(d,i) {return '#edgepath'+i})
.style("pointer-events", "none")
.text(function(d){return d.label});
However, the results I get are weird to say at best. Some labels are too close together, some are missing, but all of them are in the wrong place. How would I move them where they should be? To keep something in mind, I'm looking for a simple way to add caption to the paths I already have drawn out, nothing else. So the method I tried may have been too much, I'm not sure.
The problem is the dx in your edgelabels, that's always the same:
'dx': 80,
Assuming that you're using the code with the link distance (as in your last question), the solution is simple:
'dx':function(d){return d.distance/2},
Check the demo:
var nodes = [{
name: "a"
}, {
name: "b"
}, {
name: "c"
}, {
name: "d"
}, {
name: "e"
}, {
name: "f"
}];
var links = [{
source: 1,
target: 0,
distance: 80,
label: "foo"
}, {
source: 2,
target: 0,
distance: 40,
label: "bar"
}, {
source: 3,
target: 0,
distance: 180,
label: "baz"
}, {
source: 4,
target: 0,
distance: 80,
label: "foobar"
}, {
source: 5,
target: 0,
distance: 90,
label: "foobaz"
}]
var width = 400
height = 300;
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([width, height])
.linkDistance(d => d.distance)
.charge(-1000)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var link = svg.selectAll(".link")
.data(force.links())
.enter().append("line")
.attr("stroke", "black")
.attr("class", "link");
var edgepaths = svg.selectAll(".edgepath")
.data(force.links())
.enter()
.append('path')
.attr({
'd': function(d) {
return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y
},
'class': 'edgepath',
'fill-opacity': 0,
'stroke-opacity': 0,
'fill': 'blue',
'stroke': 'red',
'id': function(d, i) {
return 'edgepath' + i
}
})
.style("pointer-events", "none");
var edgelabels = svg.selectAll(".edgelabel")
.data(force.links())
.enter()
.append('text')
.style("pointer-events", "none")
.attr({
'class': 'edgelabel',
'id': function(d, i) {
return 'edgelabel' + i
},
'dx': function(d) {
return d.distance / 2
},
'dy': 10,
'font-size': 12,
'fill': 'black'
});
edgelabels.append('textPath')
.attr('xlink:href', function(d, i) {
return '#edgepath' + i
})
.style("pointer-events", "none")
.text(function(d) {
return d.label
});
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("r", (d, i) => i ? 10 : 16)
.style("fill", (d, i) => i ? "teal" : "brown");
function tick() {
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("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
edgepaths.attr('d', function(d) {
var path = 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
return path
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
text cannot be a child of path see this question path is not a container... per SVG spec.
best practice would be to first append g elements to your SVG as containers for the bound data, then append the path and text elements to the g so that they are siblings. Then you have complete control over relative positioning, etc.
I want to make D3 bar chart with additional vertical lines on the x axis..
I checked one of the web sites in that below example.
I want make it like that graph, but I want an additional bottom line.
Can you please show me what to change?
var margin = {
top: 20,
right: 20,
bottom: 60,
left: 40
},
width = 560 - margin.left - margin.right,
height = 360 - margin.top - margin.bottom;
var color = {
Mechanical: '#4A7B9D',
Electrical: '#54577C',
Hydraulic: '#ED6A5A'
};
var barPadding = 40;
var data = [{
key: 'Mechanical',
values: [{
key: 'Gear',
value: 11
}, {
key: 'Bearing',
value: 8
}, {
key: 'Motor',
value: 3
}]
}, {
key: 'Electrical',
values: [{
key: 'Switch',
value: 19
}, {
key: 'Plug',
value: 12
}, {
key: 'Cord',
value: 11
}, {
key: 'Fuse',
value: 3
}, {
key: 'Bulb',
value: 2
}]
}, {
key: 'Hydraulic',
values: [{
key: 'Pump',
value: 4
}, {
key: 'Leak',
value: 3
}, {
key: 'Seals',
value: 1
}]
}];
var rangeBands = [];
var cummulative = 0;
data.forEach(function(val, i) {
val.cummulative = cummulative;
cummulative += val.values.length;
val.values.forEach(function(values) {
values.parentKey = val.key;
rangeBands.push(i);
})
});
//console.log(data);
var x_category = d3.scale.linear()
.range([0, width]);
var x_defect = d3.scale.ordinal().domain(rangeBands).rangeRoundBands([0, width], .1);
var x_category_domain = x_defect.rangeBand() * rangeBands.length;
x_category.domain([0, x_category_domain]);
var y = d3.scale.linear()
.range([height, 0]);
y.domain([0, d3.max(data, function(cat) {
return d3.max(cat.values, function(def) {
return def.value;
});
})]);
var category_axis = d3.svg.axis()
.scale(x_category)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style('background-color', 'EFEFEF')
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
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("Value");
var category_g = svg.selectAll(".category")
.data(data)
.enter().append("g")
.attr("class", function(d) {
return 'category category-' + d.key;
})
.attr("transform", function(d) {
return "translate(" + x_category((d.cummulative * x_defect.rangeBand())) + ",0)";
})
.attr("fill", function(d) {
return color[d.key];
});
var category_label = category_g.selectAll(".category-label")
.data(function(d) {
return [d];
})
.enter().append("text")
.attr("class", function(d) {
//console.log(d)
return 'category-label category-label-' + d.key;
})
.attr("transform", function(d) {
var x_label = x_category((d.values.length * x_defect.rangeBand() + barPadding) / 2);
var y_label = height + 30;
return "translate(" + x_label + "," + y_label + ")";
})
.text(function(d) {
return d.key;
})
.attr('text-anchor', 'middle');
var defect_g = category_g.selectAll(".defect")
.data(function(d) {
return d.values;
})
.enter().append("g")
.attr("class", function(d) {
return 'defect defect-' + d.key;
})
.attr("transform", function(d, i) {
return "translate(" + x_category((i * x_defect.rangeBand())) + ",0)";
});
var defect_label = defect_g.selectAll(".defect-label")
.data(function(d) {
return [d];
})
.enter().append("text")
.attr("class", function(d) {
//console.log(d)
return 'defect-label defect-label-' + d.key;
})
.attr("transform", function(d) {
var x_label = x_category((x_defect.rangeBand() + barPadding) / 2);
var y_label = height + 10;
return "translate(" + x_label + "," + y_label + ")";
})
.text(function(d) {
return d.key;
})
.attr('text-anchor', 'middle');
var rects = defect_g.selectAll('.rect')
.data(function(d) {
return [d];
})
.enter().append("rect")
.attr("class", "rect")
.attr("width", x_category(x_defect.rangeBand() - barPadding))
.attr("x", function(d) {
return x_category(barPadding);
})
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return height - y(d.value);
});
/* Styles go here */
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.x.axis path {
display: none;
}
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h1>Hello Plunker!</h1>
</body>
</html>
Try this way: -
category_g
.append("line")
.style("stroke", "black")
.style("stroke-width", "2px")
.style("stroke-linecap","round")
.attr("x1", function(d) {
var bbox = this.parentNode.getBBox();
return bbox.width+5;
})
.attr("y1", height)
.attr("x2", function(d) {
var bbox = this.parentNode.getBBox();
return bbox.width;
})
.attr("y2", height + 50);
var margin = {
top: 20,
right: 20,
bottom: 60,
left: 40
},
width = 560 - margin.left - margin.right,
height = 360 - margin.top - margin.bottom;
var color = {
Mechanical: '#4A7B9D',
Electrical: '#54577C',
Hydraulic: '#ED6A5A'
};
var barPadding = 40;
var data = [{
key: 'Mechanical',
values: [{
key: 'Gear',
value: 11
}, {
key: 'Bearing',
value: 8
}, {
key: 'Motor',
value: 3
}]
}, {
key: 'Electrical',
values: [{
key: 'Switch',
value: 19
}, {
key: 'Plug',
value: 12
}, {
key: 'Cord',
value: 11
}, {
key: 'Fuse',
value: 3
}, {
key: 'Bulb',
value: 2
}]
}, {
key: 'Hydraulic',
values: [{
key: 'Pump',
value: 4
}, {
key: 'Leak',
value: 3
}, {
key: 'Seals',
value: 1
}]
}];
var rangeBands = [];
var cummulative = 0;
data.forEach(function(val, i) {
val.cummulative = cummulative;
cummulative += val.values.length;
val.values.forEach(function(values) {
values.parentKey = val.key;
rangeBands.push(i);
})
});
//console.log(data);
var x_category = d3.scale.linear()
.range([0, width]);
var x_defect = d3.scale.ordinal().domain(rangeBands).rangeRoundBands([0, width], .1);
var x_category_domain = x_defect.rangeBand() * rangeBands.length;
x_category.domain([0, x_category_domain]);
var y = d3.scale.linear()
.range([height, 0]);
y.domain([0, d3.max(data, function(cat) {
return d3.max(cat.values, function(def) {
return def.value;
});
})]);
var category_axis = d3.svg.axis()
.scale(x_category)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style('background-color', 'EFEFEF')
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
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("Value");
var category_g = svg.selectAll(".category")
.data(data)
.enter().append("g")
.attr("class", function(d) {
return 'category category-' + d.key;
})
.attr("transform", function(d) {
return "translate(" + x_category((d.cummulative * x_defect.rangeBand())) + ",0)";
})
.attr("fill", function(d) {
return color[d.key];
});
var category_label = category_g.selectAll(".category-label")
.data(function(d) {
return [d];
})
.enter().append("text")
.attr("class", function(d) {
//console.log(d)
return 'category-label category-label-' + d.key;
})
.attr("transform", function(d) {
var x_label = x_category((d.values.length * x_defect.rangeBand() + barPadding) / 2);
var y_label = height + 30;
return "translate(" + x_label + "," + y_label + ")";
})
.text(function(d) {
return d.key;
})
.attr('text-anchor', 'middle');
var defect_g = category_g.selectAll(".defect")
.data(function(d) {
return d.values;
})
.enter().append("g")
.attr("class", function(d) {
return 'defect defect-' + d.key;
})
.attr("transform", function(d, i) {
return "translate(" + x_category((i * x_defect.rangeBand())) + ",0)";
});
var defect_label = defect_g.selectAll(".defect-label")
.data(function(d) {
return [d];
})
.enter().append("text")
.attr("class", function(d) {
//console.log(d)
return 'defect-label defect-label-' + d.key;
})
.attr("transform", function(d) {
var x_label = x_category((x_defect.rangeBand() + barPadding) / 2);
var y_label = height + 10;
return "translate(" + x_label + "," + y_label + ")";
})
.text(function(d) {
return d.key;
})
.attr('text-anchor', 'middle');
var rects = defect_g.selectAll('.rect')
.data(function(d) {
return [d];
})
.enter().append("rect")
.attr("class", "rect")
.attr("width", x_category(x_defect.rangeBand() - barPadding))
.attr("x", function(d) {
return x_category(barPadding);
})
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return height - y(d.value);
});
category_g
.append("line")
.style("stroke", "black")
.style("stroke-width", "4px")
.style("stroke-linecap","round")
.attr("x1", function(d) {
var bbox = this.parentNode.getBBox();
return bbox.width+7;
})
.attr("y1", height)
.attr("x2", function(d) {
var bbox = this.parentNode.getBBox();
return bbox.width;
})
.attr("y2", height + 50);
/* Styles go here */
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.x.axis path {
display: none;
}
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h1>Hello Plunker!</h1>
</body>
</html>
I need to create a legend for the bubble/circle pack chart. I'm displaying the values inside the circle. I need the names as the legend. For an instance, in the below provided data, if the value is 60, i need that name "Petrol" in the legend. How could i achieve it?
Snippet:
var diameter = 200,
format = d3.format(",d"),
color = ["#7b6888", "#ccc", "#aaa", "#6b486b"];
var bubble = d3.layout.pack().size([diameter, diameter]);
var svg = d3.select("#bubbleCharts").append("svg")
.attr("width", diameter + 10)
.attr("height", diameter)
.attr("class", "bubble");
d3.json("flare.json", function(error, root) {
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(root))
.filter(function(d) { return !d.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + 20 + "," + d.y + ")"; });
node.append("circle").attr("r", function(d) { return d.r+ 7; })
.style("fill", function(d,i) { return color[i];} );
node.append("text").attr("dy", ".3em").style("text-anchor", "middle")
.text(function(d) { return d.value+"%"; });
});
function classes(root) {
var classes = [];
function recurse(name, node) {
if (node.children)
node.children.forEach(function(child){
recurse(node.name, child);
});
else
classes.push({packageName: name, value: node.value});
}
recurse(null, root);
return {children: classes};
}
var legend = d3.select("#bubbleChart").append("svg")
.selectAll("g").data(node.children).enter().append("g")
.attr("class","legend")
.attr("width", radius)
.attr("height", radius * 2)
.attr("transform", function(d, i) {return "translate(" + i *10 + "0" + ")"; });
legend.append("rect").attr("width", 18).attr("height", 10)
.style("fill", function(d, i) { return color[i];});
legend.append("text").attr("x", 24).attr("y", 5).attr("dy", ".35em")
.text(function(d) { return d; });
My data:
{
"name": "Spending Activity",
"children": [
{"name": "Petrol", "value": 10},
{"name": "Travel", "value": 60},
{"name": "Medical", "value": 25},
{"name": "Shopping", "value": 5}
]
}
How would i take the values from json and create a legend? Thanks.
You can simply iterate through your data set and add those values:
legend.selectAll("circle").data(data.children)
.enter()
.append("circle")
.attr("cy", function(d,i) { return (i+1) * 10; })
.attr("r", function(d) { return d.r+ 7; })
.style("fill", function(d,i) {
return color[i];
});
legend.selectAll("text").data(data.children)
.enter()
.append("text")
.attr("transform", function(d,i) {
return "translate(10," + ((i+1) * 10) + ")";
});