stacking axes horizontally on top of each other - d3.js

Thisis an example by Mike Bostock of a "simple" hive graph (as he refers to it in this article ). It has three "axis" created by this code
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]);
As you can see from the first link, the three "axes" form a circle, which seems to be accomplished by the rotation in the "transform" of the code above and use of these angle and degrees functions
var angle = d3.scale.ordinal().domain(d3.range(4)).rangePoints([0, 2 * Math.PI]),
function degrees(radians) {
return radians / Math.PI * 180 - 90;
}
Question: if there were only two "axes", how would it be possible (using a "translate") to stack the "axes" on top of each other (i.e. as two horizontal lines parallel to each other?
In my attempt to do this, I tried to remove the rotation of the "axis" and then to space them vertically. To stop the rotation,I removed the call to "degrees" like this
.attr("transform", function(d) { return "rotate(" + angle(d) + ")"; })
and I also set the range of the angles to be 0,0
d3.scale.ordinal().domain(["one", "two"]).range([0,0]);
then , to space the axes, I included a "translate" like this
.attr("transform", function(d) {return "translate(" + width /2 + "," + height/d + ")"});
The result is that there is one visible horizontal axis, and it seems the other one exists but is only detectable when I run the mouse over it ( and the nodes and lines haven't been repositioned)

Not sure if this is what you are after but two "axis" stacked vertically can be achieved with:
var angle = d3.scale.ordinal()
.domain(d3.range(3)) //<-- only calculate angles for 2 [-90, 90]
.rangePoints([0, 2 * Math.PI]),
...
svg.selectAll(".axis")
.data(d3.range(2)) //<-- 2 lines
EDITS
What are you are describing is not really a hive plot and attempting to re-purpose the layout is probably more trouble then it's worth. If you just want linked points on a line, here's an off-the-cuff implementation:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.link {
fill: none;
stroke-width: 1.5px;
}
.axis, .node {
stroke: #000;
stroke-width: 1.5px;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="d3.hive.min.js"></script>
<script>
var width = 500,
height = 500;
var lineSep = 200,
lineLen = 400,
color = d3.scale.category10().domain(d3.range(20)),
margin = [50,50];
var nodes = [
{x: 0, y: .1},
{x: 0, y: .9},
{x: 0, y: .2},
{x: 1, y: .3},
{x: 1, y: .1},
{x: 1, y: .8},
{x: 1, y: .4},
{x: 1, y: .6},
{x: 1, y: .2},
{x: 1, y: .7},
{x: 1, y: .8}
];
var links = [
{source: nodes[0], target: nodes[3]},
{source: nodes[1], target: nodes[3]},
{source: nodes[2], target: nodes[4]},
{source: nodes[2], target: nodes[9]},
{source: nodes[3], target: nodes[0]},
{source: nodes[4], target: nodes[0]},
{source: nodes[5], target: nodes[1]}
];
var nodeNest = d3.nest().key(function(d){ return d.x }).entries(nodes);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + margin[0] + "," + margin[1] + ")");
var axis = svg.selectAll(".axis")
.data(nodeNest);
var g = axis
.enter().append("g")
.attr("class", "axis")
.attr("transform", function(d,i) {
var t = "translate(0," + (i * lineSep) + ")";
return t;
})
.append("line")
.attr("x1", 0)
.attr("x2", lineLen);
axis.selectAll(".node")
.data(function(d){
d.values.forEach(function(q){
q.len = d.values.length;
})
return d.values;
})
.enter().append("circle")
.attr("class", "node")
.attr("cx", function(d, i, j) {
d.cx = ((i + 0.5) * (lineLen / d.len));
d.cy = (j * lineSep);
return d.cx;
})
.attr("r", 5)
.style("fill", function(d) { return color(d.x); });
svg.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", function(d){
console.log(d);
var p = "";
p += "M" + d.source.cx + "," + d.source.cy;
p += "Q" + "0," + ((margin[1] / 2) + (lineSep/2));
p += " " + d.target.cx + "," + d.target.cy;
return p;
})
.style("stroke", function(d) {
return color(d.source.x);
});
function degrees(radians) {
return radians / Math.PI * 180 - 90;
}
</script>

Related

Convert from hive style plot to draw axes paralelly

Long story short. I need to display chart of target-source items and I found that d3.js can do the job. I spend already many hours to get something from there and I finished with using Hive Plot like this one:
https://bost.ocks.org/mike/hive/
But the problem is that I will have only one pair of items and I want to display nodes and corresponding to them items not in let's say circular manner but more conventional parallel (like in image below, left is actual result, right is desired one)[
I tried many things but unfortunately i get lost.
I tried also get jsfiddle up and running but for unknown reason for me is not displaying anything. Here's the jsfiddle:
https://jsfiddle.net/a7yrjfgc/2/
Code:
var width = 200,
height = 200,
innerRadius = 10,
outerRadius = 100,
majorAngle = 1 * Math.PI / 1,
minorAngle = 1 * Math.PI / 2;
var angle = d3.scaleOrdinal()
.domain(["source", "source-target", "target-source", "target"])
.range([0, majorAngle - minorAngle, majorAngle + minorAngle, 2 * majorAngle]);
radius = d3.scaleLinear()
.range([innerRadius, outerRadius]),
color = d3.scaleOrdinal(d3.schemeCategory10),
formatNumber = d3.format(",d");
var nodes = [
{x: 0, y: .4, name: "node1", color: "#0000FF"},
{x: 0, y: .2, name: "node2", color: "#FFA500"},
{x: 1, y: .2, name: "node3", color: "#008000"},
{x: 1, y: .3, name: "node4", color: "#A52A2A"},
];
var links = [
{source: nodes[0], target: nodes[2]},
{source: nodes[1], target: nodes[3]},
];
var info = d3.select("#info")
.text(defaultInfo = "Showing " + formatNumber(links.length) + " splices " + formatNumber(nodes.length) + " strands.");
console.log(info);
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(2))
.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]);
// draw links
svg.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", link()
.angle(function(d) { return angle(d.x); })
.radius(function(d) { return radius(d.y); }))
.style("stroke", function(d) { return color(d.color); })
.on("mouseover", linkMouseover)
.on("mouseout", mouseout);
// draw nodes
svg.selectAll(".node")
.data(nodes)
.enter().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 d3.color(d.color); })
.on("mouseover", nodeMouseover)
.on("mouseout", mouseout);
Many, many thanks for helping in this topic.
As i get to know more about how d3 works, I realized that I need to simply draw everything from scratch.
function DisplayGraph(){
//D3 Configuration
var width = 400,
height = 50,
color = d3.scaleOrdinal(d3.schemeCategory10),
formatNumber = d3.format(",d"),
defaultInfo ="";
var nodes = [
{x: 0, y: 0, name: "node1", color: "#0000FF"},
{x: 0, y: 15, name: "node2", color: "#FFA500"},
{x: 100, y: 15, name: "node3", color: "#008000"},
{x: 100, y: 0, name: "node4", color: "#A52A2A"},
];
var links = [
{source: nodes[0], target: nodes[2]},
{source: nodes[1], target: nodes[3]},
];
var info = d3.select("#info")
.text(defaultInfo = "Showing " + formatNumber(links.length) + " splices " + formatNumber(nodes.length) + " strands.");
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width/2 + "," + height/2 + ")");
var diagonal = function link(d) {
let sourceX = (d.source.x + 50);
let sourceY = (d.source.y + 5);
let targetX = (d.target.x);
let targetY = (d.target.y + 5);
var output = "M" + sourceX + "," + sourceY
+ "C" + (sourceX + targetX) / 2 + "," + sourceY
+ " " + (sourceX + targetX) / 2 + "," + targetY
+ " " + targetX + "," + targetY;
return output;
};
//draw squares
svg.selectAll(".node")
.data(nodes)
.enter().append("rect")
.attr("class", "node")
.attr("width", 50)
.attr("height", 10)
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.style("fill", function(d) { return d3.color(d.color); })
.on("mouseover", nodeMouseover)
.on("mouseout", mouseout);
// draw links
svg.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", diagonal)
.style("stroke", function(d) { return color(d.color); })
.on("mouseover", linkMouseover)
.on("mouseout", mouseout);
//Helpers
function linkMouseover(d) {
svg.selectAll(".link")
.classed("turnedOn", function(dl) {
return dl === d;
})
.classed("turnedOff", function(dl) {
return !(dl === d);
})
svg.selectAll(".node")
.classed("turnedOn", function(dl) {
return dl === d.source || dl === d.target;
})
info.text(d.source.name + " → " + d.target.name);
}
// Highlight the node and connected links on mouseover.
function nodeMouseover(d) {
svg.selectAll(".node")
.classed("turnedOn", function(dl) {
return dl.source === d || dl.target === d;
})
.classed("turnedOff", function(dl) {
return !(dl.source === d || dl.target === d)
});
d3.select(this)
.classed("turnedOn", true);
info.text(d.name);
}
// Clear any highlighted nodes or links.
function mouseout() {
svg.selectAll(".turnedOn").classed("turnedOn", false);
svg.selectAll(".turnedOff").classed("turnedOff", false);
info.text("Hover to node or link");
}
}

D3JS: Unable to place a group of text elements over several rectangles

I'm trying to create a set of text elements and place them above various rect elements so that it looks as if they were inside. the thing is that I haven't been able to accomplish this simple task.
The text elements I need inside the column of rect's are the elements of the array: var dataDnt4 = [42,31,16,4,3,2,1];
I'll leave a running snippet so that you can my progress so far.
Your help is very appreciated. thanks
var icon2 = '<g><path class="st0" d="M23.1,34.9c6.9,0,12.5-5.6,12.5-12.5c0-6.9-5.6-12.5-12.5-12.5c-6.9,0-12.5,5.6-12.5,12.5 C10.6,29.3,16.2,34.9,23.1,34.9L23.1,34.9z"/><path class="st0" d="M39.2,54.6c0.2,0,0.4,0,0.7,0c-3.7-3-6-7.5-6-12.6c0-1.2,0.1-2.4,0.4-3.6c-0.1,0-0.3,0-0.4,0H12.4 C5.5,38.5-0.1,44.1-0.1,51v17.9h23.3C24.1,60.8,30.9,54.6,39.2,54.6L39.2,54.6z"/><path class="st0" d="M76.8,34.9c6.9,0,12.5-5.6,12.5-12.5c0-6.9-5.6-12.5-12.5-12.5c-6.9,0-12.5,5.6-12.5,12.5 C64.2,29.3,69.9,34.9,76.8,34.9L76.8,34.9z"/><path class="st0" d="M87.5,38.5H66c-0.1,0-0.3,0-0.4,0c0.3,1.1,0.4,2.3,0.4,3.6c0,5.1-2.4,9.6-6,12.6c0.2,0,0.4,0,0.7,0 c8.3,0,15.1,6.3,16,14.3H100V51C100,44.1,94.4,38.5,87.5,38.5L87.5,38.5z"/><path class="st0" d="M49.9,54.6c6.9,0,12.5-5.6,12.5-12.5c0-6.9-5.6-12.5-12.5-12.5c-6.9,0-12.5,5.6-12.5,12.5 C37.4,49,43,54.6,49.9,54.6L49.9,54.6z"/><path class="st0" d="M60.7,58.1H39.2c-6.9,0-12.5,5.6-12.5,12.5v17.9h46.5V70.7C73.2,63.7,67.6,58.1,60.7,58.1L60.7,58.1z"/></g>'
var dataDnt4 = [42, 31, 16, 4, 3, 2, 1];
var distanciaRect = [25, 50, 75, 100, 125, 150, 175]
var width = 512,
height = 600
radius = (Math.min(width, height) / 2.5) - 60;
var sym = "%"
var legendTextArr = ["alpha", "beta", "Gamma", "vvv", "www", "xxx", "yyy", "zzz"]
var color_rect = ["#00338D", "#BC204B", "#0091DA", "#eaaa00", "#005eb8", "#f68d2e", "#009444", "#470a68"]
var pie = d3.pie()
.value(function(d) {
return d
})(dataDnt4);
var arc = d3.arc()
.outerRadius(radius - 10)
.innerRadius(radius - (radius / 2.4));
var labelArc = d3.arc()
.outerRadius(radius - 35)
.innerRadius(radius - 35);
var svg = d3.select("#chartdiv")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2.4 + ")");
var title = svg.append("text")
.attr("font-weight", "bold")
.attr("class", "title1")
.html("title 1")
.attr("transform", function() {
return "translate(" + (-184) + "," + (-180) + ")"
})
var title = svg.append("text")
.attr("font-weight", "bold")
.attr("class", "title2")
.html("title 2")
.attr("transform", function() {
return "translate(" + (-184) + "," + (-160) + ")"
})
var legendG = svg.append("g")
.attr("class", "legendG")
.attr("transform", function() {
return "translate(" + (-60) + "," + (155) + ")"
})
var legendG = svg.append("g")
.attr("class", "legendG")
.attr("transform", function() {
return "translate(" + (-60) + "," + (155) + ")"
})
var legendText = legendG.selectAll("text")
.data(distanciaRect)
.enter()
.append("text")
.attr("x", -80)
.attr("y", function(d, i) {
return d + 10
})
.data(legendTextArr)
.html(function(d) {
return d
})
var legends = legendG.selectAll(".rect")
.data(distanciaRect)
.enter()
.append("rect")
.attr("x", -120)
.attr("y", function(d, i) {
return d
})
.attr("width", 25)
.attr("height", 17)
.attr("class", "icon1")
.data(color_rect)
.attr("fill", function(d, i) {
return d
})
var g = svg.selectAll("arc")
.data(pie)
.enter().append("g")
.attr("class", "arc");
function easeInverse(ease) {
return function(e) {
var min = 0,
max = 1;
while (max - min > 1e-3) {
var mid = (max + min) * 0.5;
emid = ease(mid);
if (emid > e) {
max = mid;
} else {
min = mid;
}
}
return max;
}
}
var inverseCubic = easeInverse(d3.easeCubic);
var oneOver2Pi = 1.0 / (2 * Math.PI);
var total_msec = 2000;
g.append("path")
.attr("d", arc)
.attr("transform", function() {
return "translate(" + (-16) + "," + (0) + ")"
})
.style("fill", function(d, i) {
return color_rect[i];
})
.transition()
.ease(d3.easeLinear)
.delay(function(d) {
return total_msec * inverseCubic(d.startAngle * oneOver2Pi);
})
.duration(function(d) {
return total_msec * (inverseCubic(d.endAngle * oneOver2Pi) - inverseCubic(d.startAngle * oneOver2Pi));
})
.attrTween("d", arcTween);
function arcTween(d) {
var i = d3.interpolate(inverseCubic(d.startAngle * oneOver2Pi), inverseCubic(d.endAngle * oneOver2Pi));
return function(t) {
d.endAngle = 2 * Math.PI * d3.easeCubic(i(t));
return arc(d);
}
}
svg.append("g")
.attr("class", "icon2")
.html(icon2);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="chartdiv"></div>
There are numerous ways to do this, so here is one possible way. I would group together the three pieces of the legend--the rectangle, the key text, and the text over the rectangle--in a g element and bind dataDnt4 to each item. The rectangle colour and the legend text can be retrieved by position, i.e. the first dataDnt4 item corresponds to color_rect[0] and legendTextArr[0], the second to color_rect[1] and legendTextArr[1], etc.
I've cut out the code that is not relevant to the positioning of the legend items -- you can restore that in your script.
var width = 512,
height = 600,
radius = (Math.min(width, height) / 2.5) - 60;
var sym = "%"
var legendTextArr = ["alpha", "beta", "Gamma", "vvv", "www", "xxx", "yyy", "zzz"]
var dataDnt4 = [42, 31, 16, 4, 3, 2, 1];
var color_rect = ["#00338D", "#BC204B", "#0091DA", "#eaaa00", "#005eb8", "#f68d2e", "#009444", "#470a68"]
var svg = d3.select("#chartdiv")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2.4 + ")");
var title = svg.append("text")
.attr("font-weight", "bold")
.attr("class", "title1")
.html("scroll down!")
.attr("transform", function() {
return "translate(" + -184 + "," + -180 + ")"
})
var legendG = svg.append("g")
.attr("class", "legendG")
.attr("transform", function() {
// this moves the whole legend box
// you can change this to whatever transformation is appropriate for your chart
return "translate(" + -((width / 2)-40) + "," + 120 + ")"
})
// group each legend item in a `g` element
var legendText = legendG.selectAll("g")
.data(dataDnt4)
.enter()
.append('g')
.attr('transform', function(d, i) {
// instead of having a hard-coded list of multiples of 25, you can multiply
// the array index, `i`, by 25 to get the correct position
return 'translate(0,' + (i*25) + ')';
});
legendText.append("rect")
.attr("width", 25)
.attr("height", 17)
.attr("class", "icon1")
.attr("fill", function(d, i) {
return color_rect[i];
})
// the text "in" the rectangle
// use 'text-anchor: middle' and an x offset of 12.5 (rectangle width / 2)
// to centre the labels
// change the `y` attribute to alter the vertical positioning
legendText.append("text")
.attr("x", 12.5)
.attr("y", 13)
.attr('text-anchor', 'middle')
.attr('fill', 'white')
// d is the items in dataDnt4
.text(function(d) {
return d;
})
// legend text items
legendText.append("text")
.attr("x", 40)
.attr("y", 13)
// take legendTextArr item in position i
.text(function(d,i) {
return legendTextArr[i];
})
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="chartdiv"></div>
You have some errors in your code (e.g. you declare the variables legendG and title twice), and it would probably be helpful for you to run your code through a code linter so you can see the problems that you might not pick up by eye.

d3js how to get rotated rect's corner coordinates?

I'm pretty new to d3js and feeling a little overwhelmed here. I'm trying to figure out how to query a rotated rectangle's corner coordinates so i can place a circle on that location (eventually I'm going to use that as a starting coordinate for a line to link to other nodes).
Here is an image showing what I'm trying to do:
Currently I'm getting the circle on the left of the svg boundary below, I'm trying to place it roughly where the x is below.
Here is my code for the circle:
let rx = node.attr("x");
let ry = node.attr("y");
g.append("circle")
.attr("cx",rx)
.attr("cy",ry)
.attr("r",5);
Here is my jsFiddle: jsFiddle and a Stack Overflow snippet
let d3Root = 'd3-cpm';
let w = document.documentElement.clientWidth;
let h = document.documentElement.clientHeight;
//TODO put type any
let eData = {
width: 180,
height: 180,
padding: 80,
fill: '#E0E0E0',
stroke: '#c3c5c5',
strokeWidth: 3,
hoverFill: '#1958b5',
hoverStroke: '#0046ad',
hoverTextColor: '#fff',
rx: 18,
ry: 18,
rotate: 45,
label: 'Decision Node',
textFill: 'black',
textHoverFill: 'white'
};
let cWidth;
let cHeight = h;
d3.select(d3Root)
.append("div")
.attr("id", "d3-root")
.html(function () {
let _txt = "Hello From D3! <br/>Frame Width: ";
let _div = d3.select(this);
let _w = _div.style("width");
cWidth = parseInt(_div.style("width"));
_txt += cWidth + "<br/> ViewPort Width: " + w;
return _txt;
});
let svg = d3.select(d3Root)
.append("svg")
.attr("width", cWidth)
.attr("height", cHeight)
.call(d3.zoom()
//.scaleExtent([1 / 2, 4])
.on("zoom", zoomed));
;
let g = svg.append("g")
.on("mouseover", function (d) {
d3.select(this)
.style("cursor", "pointer");
d3.select(this).select("rect")
.style("fill", eData.hoverFill)
.style("stroke", eData.hoverStroke);
d3.select(this).select("text")
.style("fill", eData.textHoverFill);
})
.on("mouseout", function (d) {
d3.select(this)
.style("cursor", "default");
d3.select(this).select("rect")
.style("fill", eData.fill)
.style("stroke", eData.stroke);
d3.select(this).select("text")
.style("fill", eData.textFill);
});
let node = g.append("rect")
.attr("width", eData.width)
.attr("height", eData.height)
.attr("fill", eData.fill)
.attr("stroke", eData.stroke)
.attr("stroke-width", eData.strokeWidth)
.attr("rx", eData.rx)
.attr("ry", eData.ry)
.attr("y", eData.padding)
.attr('transform', function () {
let _x = calcXLoc();
console.log(_x);
return "translate(" + _x + "," + "0) rotate(45)";
})
.on("click", ()=> {
console.log("rect clicked");
d3.event.stopPropagation();
//this.nodeClicked();
});
let nText = g.append('text')
.text(eData.label)
.style('fill', eData.textFill)
.attr('x', calcXLoc() - 50)
.attr('y', eData.width + 10)
.attr("text-anchor", "middle")
.on("click", ()=> {
console.log("text clicked");
d3.event.stopPropagation();
//this.nodeClicked();
});
let rx = node.attr("x");
let ry = node.attr("y");
g.append("circle")
.attr("cx",rx)
.attr("cy",ry)
.attr("r",5);
function calcXLoc() {
return (cWidth / 2 - eData.width / 2) + eData.width;
}
function zoomed() {
g.attr("transform", d3.event.transform);
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<d3-cpm></d3-cpm>
You're applying a transform to your rect to position and rotate it. It has no x attribute, so that comes back as undefined. This gets you slightly closer:
let rx = parseInt(node.attr("x"), 10) | 0;
let ry = parseInt(node.attr("y"), 10) | 0;
let height = parseInt(node.attr("height"), 10) | 0;
let transform = node.attr("transform");
g.append("circle")
.attr("cx",rx + height)
.attr("cy",ry + height)
.attr("transform", transform)
.attr("r",5);
But note that this is going to get kind of clunky and difficult to deal with - it'd be better if your data was modeled in such a way that the circular points were handled in there as well and could be somehow derived/transformed consistently....
Updated fiddle: https://jsfiddle.net/dcw48tk6/7/
Image:

Keeping text horizontal while it's position is rotating with d3.js

I'm trying to add labels to the planets around the sun into this example : http://bl.ocks.org/djvanderlaan/4953593.
So far, I've managed to add the labels, but the orientation of the labels is rotating with their position, while I want to keep them horizontal for the comfort of the readers.
I've been finding a beginning of a solution to my problem here : how to keep text orientation unchanged during rotation in SVG
but it's seems very complicated to me (I am a newbie and really not good at trigonometry) and plus, it's not using d3.js.
Here is the code that I'am using :
<div id="planetarium">
</div>
<script type="text/javascript">
var w = 800, h = 600;
var t0 = Date.now();
var planets = [
{ R: 300, r: 5, speed: 5, phi0: 90, name : 'Mercure'},
{ R: 150, r: 10, speed: 2, phi0: 190, name : 'Saturne'}
];
var svg = d3.select("#planetarium").insert("svg")
.attr("width", w).attr("height", h);
svg.append("circle").attr("r", 20).attr("cx", w/2)
.attr("cy", h/2).attr("class", "sun")
var container = svg.append("g")
.attr("transform", "translate(" + w/2 + "," + h/2 + ")")
container.selectAll("g.planet").data(planets).enter().append("g")
.attr("class", "planet").each(function(d, i) {
var orbit = d3.select(this).append("circle").attr("class", "orbit")
.attr("r", d.R);
var planet = d3.select(this).append("circle").attr("r", d.r).attr("cx",d.R)
.attr("cy", 0).attr("class", "planet");
var text = d3.select(this).append("text")
.attr("x", d.R)
.attr("y", ".31em")
.text(function(d) { return d.name; });
d3.timer(function() {
var delta = (Date.now() - t0);
planet.attr("transform", function(d) {
return "rotate(" + d.phi0 + delta * d.speed/200 + ")";
});
text.attr("transform", function(d) {
return "rotate(" + d.phi0 + delta * d.speed/200 + ")";
});
});
});
</script>
Here is my plunkr : http://plnkr.co/edit/dJEVXIeR7ly536tcMPWt?p=preview Thank you very much for your help !
I've finally found a solution inspired by this example : D3.js: rotate group, keep text the same orientation?
Instead of making two different variables for planets and text, I've gathered them in a same rotating group, and then added an inverse rotation on the text, but centered on the planet's center rather than the center of the container. Then I set both phi0 (the positions of the planets at the beginning of the animation) to 0, so that the text would be frozen horizontally. Here is my code :
var planets = [
{ R: 300, r: 5, speed: 5, phi0: 0, name : 'Mercure'},
{ R: 150, r: 10, speed: 2, phi0: 0, name : 'Saturne'}
];
//...
var container = svg.append("g")
.attr("transform", "translate(" + w/2 + "," + h/2 + ")")
var orbit = container.selectAll(".orbit")
.data(planets)
.enter()
.append("circle")
.attr("class", "orbit")
.attr("r", function(d) {return d.R;});
var planets = container.selectAll(".planet")
.data(planets)
.enter()
.append("g")
planets.append("circle")
.attr("class", "planet")
.attr("r", function(d) {return d.r;})
.attr("cx", function(d) {return d.R; })
.attr("cy", 0);
planets.append("text")
.attr("dx", function(d) {return d.R;})
.attr("dy", ".35em")
.text(function(d) {return d.name});
d3.timer(function() {
var delta = (Date.now() - t0);
planets.attr("transform", function(d) {
return "rotate(" + d.phi0 + delta * d.speed/200 + ")";
});
planets.selectAll("text").attr("transform", function(d) {
return "rotate(" + -1*(d.phi0 + delta * d.speed/200) + " " + d.R + " " + 0 + ")";
});
});
Not sure this is a very good solution, but for the moment it works. I will learn trigonometry though, I promise ;)

How to do wordwrap for chart labels using d3.js

I am trying to implement the horizontal bar chart using d3.js.Some of the chart labels are too long.
How to do word wrap for the chart labels on y aixs?
Source code:
var data = [{"Name": "Label 1", "Count": "428275" }, { "Name": "Label 2", "Count": "365005" }, { "Name": "Label 3", "Count": "327619" }];
var m = [30, 10, 10, 310],
w = 1000 - m[1] - m[3],
h = 550 - m[0] - m[2];
var format = d3.format(",.0f");
var x = d3.scale.linear().range([0, w + 10]),
y = d3.scale.ordinal().rangeRoundBands([0, h], .4);
var xAxis = d3.svg.axis().scale(x).orient("bottom").tickSize(h),
yAxis = d3.svg.axis().scale(y).orient("left").tickSize(0);
$("#chartrendering").empty();
var svg = d3.select("#chartrendering").append("svg")
.attr("width", w + m[1] + m[3])
.attr("height", h + m[0] + m[2])
.append("g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
// Set the scale domain.
x.domain([0, d3.max(data, function (d) { return d.Count; })]);
y.domain(data.map(function (d) { return d.Name; }));
var bar = svg.selectAll("g.bar")
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function (d) { return "translate(0," + y(d.Name) + ")"; });
bar.append("rect")
.attr("width", function (d) { return x(d.Count); })
.attr("height", y.rangeBand());
bar.append("text")
.attr("class", "value")
.attr("x", function (d) { return x(d.Count); })
.attr("y", y.rangeBand() / 2)
.attr("dx", +55)
.attr("dy", ".35em")
.attr("text-anchor", "end")
.text(function (d) { return format(d.Count); });
svg.append("g")
.attr("class", "x axis")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
Here is a working implementation I've written by pulling together various bits. As the other answer suggests, foreignObject is still the way to go. First the function:
var insertLinebreaks = function (t, d, width) {
var el = d3.select(t);
var p = d3.select(t.parentNode);
p.append("foreignObject")
.attr('x', -width/2)
.attr("width", width)
.attr("height", 200)
.append("xhtml:p")
.attr('style','word-wrap: break-word; text-align:center;')
.html(d);
el.remove();
};
This takes in a text element (t), the text content (d), and the width to wrap to. It then gets the parentNode of the text object, and attaches a foreignObject node to it into which an xhtml:p is added. The foreignObject is set to the desired width and offset -width/2 to center. Finally, the original text element is deleted.
This can then be applied to your axis elements as follows:
d3.select('#xaxis')
.selectAll('text')
.each(function(d,i){ insertLinebreaks(this, d, x1.rangeBand()*2 ); });
Here I've used rangeBand to get the width (with *2 for 2 bars on the graph).
I was looking for solutions to this problem, and found that Mike Bostock has published a working example using D3. The example is shown to work for the x-axis, but can easily be adapted for the y-axis.
Here's a function I wrote not only to solve the y-axis word wrap problem, but also wrap word that is more than 1 line in length, and also align the corresponding 'tick' in the center of the label:
Result is like this:
See this snippet:
var tempArray2 = [{date: "2017/3/11", ratio: 1}, {date: "2017/3/12", ratio: 0.5}, {date: "2017/3/13", ratio: 0.3}, {date: "2017/3/14", ratio: 0}, {date: "2017/3/15", ratio: 0.8}];
var margin = {
top: 20,
right: 20,
bottom: 40,
left: 80
},
width = 500 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
barHeight = 40;
labelWidth = 0;
tempArray2.sort(function(a, b) {
return new Date(a.date) - new Date(b.date);
})
dateRange = Math.round((new Date(tempArray2[tempArray2.length - 1].date) - new Date(tempArray2[0].date)) / 1000 / 3600 / 24);
svg = d3.select('body')
.append("svg")
.attr("style", "width: 500px\; height: 300px\;");
var x = d3.scaleUtc().range([0, width])
.domain([toUTCDate(tempArray2[0].date), calculateDays(toUTCDate(tempArray2[tempArray2.length - 1].date), 1)]);
var y = d3.scaleBand()
.range([height, 0])
.padding(0.1)
.domain(["Domain for testinginginging", "Another domain used for testing", "Horizontal bar"]);
passBar = svg.selectAll(".passBar")
.data(tempArray2)
.enter();
passBar.append("rect")
.attr("class", "passBar")
.attr("height", barHeight)
.attr("width", function(d) {
return x(calculateDays(toUTCDate(d.date), d.ratio)) - x(toUTCDate(d.date));
})
.attr("y", y("Horizontal bar") + (y.bandwidth() - barHeight) / 2)
.attr("transform", function(d) {
return "translate(" + (margin.left + x(toUTCDate(d.date))) + ", 0)";
});
failBar = svg.selectAll(".failBar")
.data(tempArray2)
.enter();
failBar.append("rect")
.attr("class", "failBar")
.attr("height", barHeight)
.attr("width", function(d) {
return x(calculateDays(toUTCDate(d.date), 1 - d.ratio)) - x(toUTCDate(d.date));
})
.attr("y", y("Horizontal bar") + (y.bandwidth() - barHeight) / 2)
.attr("transform", function(d) {
return "translate(" + (margin.left + x(toUTCDate(d.date)) + x(calculateDays(toUTCDate(d.date), d.ratio)) - x(toUTCDate(d.date))) + ", 0)";
});
//add grid lines
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(" + margin.left + "," + height + ")")
.call(make_x_gridlines(dateRange)
.tickSize(-height)
.tickFormat("")
)
// always draw axis at last
svg.append("g")
.attr("transform", "translate(" + margin.left + "," + height + ")")
.attr("class", "xAxis")
.call(d3.axisBottom(x).ticks(dateRange).tickFormat(d3.utcFormat("%m-%d")))
.selectAll("text")
.style("text-anchor", "middle");
svg.append("g")
.attr("transform", "translate(" + margin.left + ", 0)")
.attr("class", "yAxis")
.call(d3.axisLeft(y))
.selectAll("text")
.attr("class", "cateName")
.style("text-anchor", "start")
.call(wrapText, margin.left - 13);
function calculateDays(date, number) {
date.setUTCDate(date.getUTCDate() + number);
return date;
}
function make_x_gridlines(tickTime) {
return d3.axisBottom(x).ticks(tickTime);
}
function toUTCDate(input) {
var tempDate = new Date(input);
return new Date(Date.UTC(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate()));
}
function wrapText(text, width) {
text.each(function() {
var text = d3.select(this),
textContent = text.text(),
tempWord = addBreakSpace(textContent).split(/\s+/),
x = text.attr('x'),
y = text.attr('y'),
dy = parseFloat(text.attr('dy') || 0),
tspan = text.text(null).append('tspan').attr('x', x).attr('y', y).attr('dy', dy + 'em');
for (var i = 0; i < tempWord.length; i++) {
tempWord[i] = calHyphen(tempWord[i]);
}
textContent = tempWord.join(" ");
var words = textContent.split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
spanContent,
breakChars = ['/', '&', '-'];
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(' '));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
spanContent = line.join(' ');
breakChars.forEach(char => {
// Remove spaces trailing breakChars that were added above
spanContent = spanContent.replace(char + ' ', char);
});
tspan.text(spanContent);
line = [word];
tspan = text.append('tspan').attr('x', x).attr('y', y).attr('dy', lineHeight+'em').text(word);
}
}
var emToPxRatio = parseInt(window.getComputedStyle(text._groups[0][0]).fontSize.slice(0, -2));
text.attr("transform", "translate(-" + (margin.left - 13) + ", -" + lineHeight + ")");
function calHyphen(word) {
tspan.text(word);
if (tspan.node().getComputedTextLength() > width) {
var chars = word.split('');
var asword = "";
for (var i = 0; i < chars.length; i++) {
asword += chars[i];
tspan.text(asword);
if (tspan.node().getComputedTextLength() > width) {
if (chars[i - 1] !== "-") {
word = word.slice(0, i - 1) + "- " + calHyphen(word.slice(i - 1));
}
i = chars.length;
}
}
}
return word;
}
});
function addBreakSpace(inputString) {
var breakChars = ['/', '&', '-']
breakChars.forEach(char => {
// Add a space after each break char for the function to use to determine line breaks
inputString = inputString.replace(char, char + ' ');
});
return inputString;
}
}
svg {
width: 100%;
height: 100%;
position: center;
}
.passBar {
fill: #a6f3a6;
}
.failBar {
fill: #f8cbcb;
}
.grid line {
stroke: white;
stroke-width: 2px;
}
.grid path {
stroke-width: 0;
}
.xAxis {
font-size: 15px;
shape-rendering: crispEdges;
}
.yAxis {
font-size: 15px;
shape-rendering: crispEdges;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<body></body>
Main function about word wrap is this one: wrapText.
Modified from
https://bl.ocks.org/ericsoco/647db6ebadd4f4756cae
and
https://bl.ocks.org/mbostock/7555321
Hope it can help.
You can't do automatic word wrap in SVG. You could use foreignObject and HTML divs for that purpose, but that would require modifying the code that creates the axis labels. Alternatively, you can rotate the axis labels so that they have more space. See for example here for how to do that.
Here's some code to plumb Mike Bostock's chart() function into angular-nvd3. For background, see https://github.com/krispo/angular-nvd3/issues/36.
discretebar: {
dispatch: {
renderEnd: function(e){
d3.selectAll(".tick text").call(wrap,_chart.xAxis.rangeBand());
}
}
},
callback: function(chart){
_chart = chart; //global var
}
}

Resources