Drawing a chess board with D3 is discussed in this question:
How to draw a chess board in D3?
Also, there is an incredible D3 chess board plugin by #jbkunst:
d3-chessboard plugin
However, I would like to animate chess moves, like this:
(but a lot smoother; with configurable duration etc.)
Do you have any advice how to do it, D3 style?
I would be happy with animation of just one move for now. I will build up the more general solution later.
Here's a quick implementation using chained transitions to make the pieces step across the board. I've tried to account for two different types of movement, "line" where the pieces move in a straight line (ie bishop, castle) and "step" where they move step-wise (ie knight). I based it off your work in the previous question.
// piece is the text element to move
// position is an object like { x: 4, y: 6 } of the board position to move to
// type is "step" or "line"
function movePiece(piece, position, type) {
var p = d3.select(piece),
d = p.datum();
(function repeat() {
if (type === "step"){
if (position.y === d.y) {
if (position.x === d.x) {
return;
} else if (position.x > d.x) {
d.x += 1;
} else {
d.x -= 1;
}
} else {
if (position.y > d.y) {
d.y += 1;
} else {
d.y -= 1;
}
}
} else {
if (position.x === d.x &&
position.y === d.y) {
return;
}
else {
if (position.x != d.x){
if (position.x > d.x) {
d.x += 1;
} else {
d.x -= 1;
}
}
if (position.y != d.y){
if (position.y > d.y) {
d.y += 1;
} else {
d.y -= 1;
}
}
}
}
p = p.transition()
.transition()
.attr("x", d.x * fieldSize)
.attr("y", d.y * fieldSize)
.each("end", repeat);
})();
}
Note, I didn't attempt to code whether it's a legal move.
Full example:
<!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" />
<script src="script.js"></script>
</head>
<body>
<script>
var pieces = {
NONE: {
name: "None",
code: " "
},
WHITE_KING: {
name: "White King",
code: "\u2654"
},
WHITE_QUEEN: {
name: "White Queen",
code: "\u2655"
},
WHITE_ROOK: {
name: "White Rook",
code: "\u2656"
},
WHITE_BISHOP: {
name: "White Bishop",
code: "\u2657"
},
WHITE_KNIGHT: {
name: "White Knight",
code: "\u2658"
},
WHITE_POWN: {
name: "White Pown",
code: "\u2659"
},
BLACK_KING: {
name: "Black King",
code: "\u265A"
},
BLACK_QUEEN: {
name: "Black Queen",
code: "\u265B"
},
BLACK_ROOK: {
name: "Black Rook",
code: "\u265C"
},
BLACK_BISHOP: {
name: "Black Bishop",
code: "\u265D"
},
BLACK_KNIGHT: {
name: "Black Knight",
code: "\u265E"
},
BLACK_POWN: {
name: "Black Pown",
code: "\u265F"
},
};
var board = [],
boardDimension = 8,
fieldSize = 40;
for (var i = 0; i < boardDimension * boardDimension; i++) {
board.push({
x: i % boardDimension,
y: Math.floor(i / boardDimension),
piece: pieces.NONE
});
};
board[0].piece = pieces.BLACK_ROOK
board[1].piece = pieces.BLACK_KNIGHT
board[2].piece = pieces.BLACK_BISHOP
board[3].piece = pieces.BLACK_QUEEN
board[4].piece = pieces.BLACK_KING
board[5].piece = pieces.BLACK_BISHOP
board[6].piece = pieces.BLACK_KNIGHT
board[7].piece = pieces.BLACK_ROOK
board[8].piece = pieces.BLACK_POWN
board[9].piece = pieces.BLACK_POWN
board[10].piece = pieces.BLACK_POWN
board[11].piece = pieces.BLACK_POWN
board[12].piece = pieces.BLACK_POWN
board[13].piece = pieces.BLACK_POWN
board[14].piece = pieces.BLACK_POWN
board[15].piece = pieces.BLACK_POWN
board[6 * 8 + 0].piece = pieces.WHITE_POWN
board[6 * 8 + 1].piece = pieces.WHITE_POWN
board[6 * 8 + 2].piece = pieces.WHITE_POWN
board[6 * 8 + 3].piece = pieces.WHITE_POWN
board[6 * 8 + 4].piece = pieces.WHITE_POWN
board[6 * 8 + 5].piece = pieces.WHITE_POWN
board[6 * 8 + 6].piece = pieces.WHITE_POWN
board[6 * 8 + 7].piece = pieces.WHITE_POWN
board[7 * 8 + 0].piece = pieces.WHITE_ROOK
board[7 * 8 + 1].piece = pieces.WHITE_KNIGHT
board[7 * 8 + 2].piece = pieces.WHITE_BISHOP
board[7 * 8 + 3].piece = pieces.WHITE_QUEEN
board[7 * 8 + 4].piece = pieces.WHITE_KING
board[7 * 8 + 5].piece = pieces.WHITE_BISHOP
board[7 * 8 + 6].piece = pieces.WHITE_KNIGHT
board[7 * 8 + 7].piece = pieces.WHITE_ROOK
var svg = d3.select('body')
.append('svg')
.attr('width', 500)
.attr('height', 500);
svg.selectAll("rect")
.data(board)
.enter()
.append("rect")
.style("class", "fields")
.style("class", "rects")
.attr("x", function(d) {
return d.x * fieldSize;
})
.attr("y", function(d) {
return d.y * fieldSize;
})
.attr("width", fieldSize + "px")
.attr("height", fieldSize + "px")
.style("fill", function(d) {
if (((d.x % 2 == 0) && (d.y % 2 == 0)) ||
((d.x % 2 == 1) && (d.y % 2 == 1)))
return "beige";
else
return "tan";
});
var pieces = svg.selectAll("text")
.data(board)
.enter().append("text")
.attr("x", function(d) {
d.piece.x = d.x;
return d.x * fieldSize;
})
.attr("y", function(d) {
d.piece.y = d.y;
return d.y * fieldSize;
})
.style("font-size", "40")
.attr("text-anchor", "middle")
.attr("dy", "35px")
.attr("dx", "20px")
.text(function(d) {
return d.piece.code;
})
pieces
.append("title")
.text(function(d) {
return d.piece.name;
});
movePiece(pieces[0][6], {
x: 5,
y: 2
}, "step");
movePiece(pieces[0][58], {
x: 5,
y: 4
}, "line");
function movePiece(piece, position, type) {
var p = d3.select(piece),
d = p.datum();
(function repeat() {
if (type === "step"){
if (position.y === d.y) {
if (position.x === d.x) {
return;
} else if (position.x > d.x) {
d.x += 1;
} else {
d.x -= 1;
}
} else {
if (position.y > d.y) {
d.y += 1;
} else {
d.y -= 1;
}
}
} else {
if (position.x === d.x &&
position.y === d.y) {
return;
}
else {
if (position.x != d.x){
if (position.x > d.x) {
d.x += 1;
} else {
d.x -= 1;
}
}
if (position.y != d.y){
if (position.y > d.y) {
d.y += 1;
} else {
d.y -= 1;
}
}
}
}
p = p.transition()
.transition()
.attr("x", d.x * fieldSize)
.attr("y", d.y * fieldSize)
.each("end", repeat);
})();
}
</script>
</body>
</html>
If you know the start position and the stop position, you can set a transition() for whatever process is moving the pieces. That will make it tween between the states in an animated fashion. This will be linear, though, so it will look nice for anyone who moves along the grid in a straight line, less good if you don't (e.g. a knight). For a knight, I would first transition along one axis, and then along the other.
Well, after some research, I found this: http://blog.visual.ly/creating-animations-and-transitions-with-d3-js/ which pretty much gives the same answer as nucleon: when you want to change attributes of an element (like position) in d3 and you do something like d3.select(selector).attr(attribute,value) and you have to use d3.select(selector).transition().attr(attribute,value)
However, the way the chessboard is drawn by the plugin, for example, you have g elements that represent squares, containing a rect and a text. the rect is the color of the square, while the text is the piece. If you change the transform of the text outside the range of the g element it disappears. Either way it feels a wrong model for moving pieces.
Supposing you are drawing your board and then, independently, draw your pieces, you might use the example in the link above to move the pieces where you want. Take care with the knight, you should move it with two transitions, though, and probably you should think a bit about the capturing animation.
That's all I got.
Related
I have text objects labeling points that are evenly spaced around a circle. Thanks to this article, I am able to correctly position both the points and text objects but the labels on the left hemisphere of the circle need to be rotated 180 degrees (flipped vertically) to be more legible.
I thought I could rotate the text object about its own origin before rotating it to the appropriate position around the circle but was unable to determine how to locate the center position of each text object.
How can I rotate text objects about their center for those on the left hemisphere of the circle (angle>= PI/2 && angle<=PI*1.5)? Or is there a better technique to use?
<style type="text/css">
* {
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
font-size: 13px;
}
circle {
fill: steelblue;
fill-opacity: .8;
}
circle:hover {
fill: orange;
fill-opacity: .8;
}
</style>
<div id="canvas"></div>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.v2.min.js"></script>
<script type="text/javascript">
(function () {
var paddding = 250;
var createNodes = function () {
var nodeData = [
{ id: 0, label: 'AAA' },
{ id: 1, label: 'BBB' },
{ id: 2, label: 'CCC' },
{ id: 3, label: 'DDD' },
{ id: 4, label: 'EEE' },
{ id: 5, label: 'FFF' },
{ id: 6, label: 'GGG' },
{ id: 7, label: 'HHH' }
];
var radius = 100;
var nodes = [],
width = (radius * 2) + paddding,
height = (radius * 2) + paddding,
angle,
x,
y,
i;
var numNodes = nodeData.length;
for (i = 0; i < numNodes; i++) {
angle = (i / (numNodes / 2)) * Math.PI;
x = (radius * Math.cos(angle)) + (width / 2);
y = (radius * Math.sin(angle)) + (width / 2);
nodes.push({ 'id': i, 'x': x, 'y': y, 'label': nodeData[i].label, 'angle': angle });
}
return nodes;
}
var createSvg = function (radius, callback) {
d3.selectAll('svg').remove();
var svg = d3.select('#canvas').append('svg:svg')
.attr('width', (radius * 2) + paddding)
.attr('height', (radius * 2) + paddding);
callback(svg);
}
var createElements = function (svg, nodes, elementRadius) {
element = svg.selectAll('circle')
.data(nodes)
.enter().append('svg:circle')
.attr('r', elementRadius)
.attr('cx', function (d, i) { return d.x; })
.attr('cy', function (d, i) { return d.y; });
element = svg.selectAll('text')
.data(nodes)
.enter().append('svg:text')
.text(function (d, i) { return d.label + " - " + d.angle.toFixed(2) + ", " + (d.angle*180/Math.PI); })
.attr('x', function (d, i) { return nodes[0].x + 15; }) // add 15 for spacing off point
.attr('y', function (d, i) { return nodes[0].y; })
.attr("dy", ".35em")
.style("alignment-baseline","middle")
.style("text-anchor", "start")
.attr("transform", function(d,i) {
return "rotate(" + (d.angle * 180) / Math.PI + ", 225, 225)";})
;
}
var draw = function () {
var radius = 100;
var nodes = createNodes();
createSvg(radius, function (svg) {
createElements(svg, nodes, 10);
});
}
$(document).ready(function () {
draw();
});
})();
</script>
If you want to reverse the labels for those on the left side of the circle. You can achieve different ways. One way is by modifying three attributes of the text as you append it:
.attr('x', function (d, i) { return nodes[0].x + 15; })
.style("text-anchor", "start")
.attr("transform", function(d,i) {
return "rotate(" + (d.angle * 180) / Math.PI + ", 225, 225)"
})
If you modify only some of these, you might not get the results you are looking for.
Modification of text-end
This is needed as your text will start away from the point you are defining, and as the text may have variable length, defining a start point will be more complex than necessary. For points you need to flip, you'll need to use:
.style("text-anchor", "end")
Modification of the transform and x
The text needs to be rotated 180 degrees so that it is right way up; however, if you modify this function to add 180 degrees to any text, then the text will appear on the wrong side of the display. So, you'll need to set x to a new value too, so that it appears on the correct side of the display:
.attr('x', function (d, i) { return nodes[0].x - 215; }) // radius * 2, add 15 for spacing off point
.attr("transform", function(d,i) {
return "rotate(" + ((d.angle * 180) / Math.PI - 180) + ", 225, 225)"
})
All together, that looks like:
(function () {
var paddding = 250;
var createNodes = function () {
var nodeData = [
{ id: 0, label: 'AAA' },
{ id: 1, label: 'BBB' },
{ id: 2, label: 'CCC' },
{ id: 3, label: 'DDD' },
{ id: 4, label: 'EEE' },
{ id: 5, label: 'FFF' },
{ id: 6, label: 'GGG' },
{ id: 7, label: 'HHH' }
];
var radius = 100;
var nodes = [],
width = (radius * 2) + paddding,
height = (radius * 2) + paddding,
angle,
x,
y,
i;
var numNodes = nodeData.length;
for (i = 0; i < numNodes; i++) {
angle = (i / (numNodes / 2)) * Math.PI;
x = (radius * Math.cos(angle)) + (width / 2);
y = (radius * Math.sin(angle)) + (width / 2);
nodes.push({ 'id': i, 'x': x, 'y': y, 'label': nodeData[i].label, 'angle': angle });
}
return nodes;
}
var createSvg = function (radius, callback) {
d3.selectAll('svg').remove();
var svg = d3.select('#canvas').append('svg:svg')
.attr('width', (radius * 2) + paddding)
.attr('height', (radius * 2) + paddding);
callback(svg);
}
var createElements = function (svg, nodes, elementRadius) {
element = svg.selectAll('circle')
.data(nodes)
.enter().append('svg:circle')
.attr('r', elementRadius)
.attr('cx', function (d, i) { return d.x; })
.attr('cy', function (d, i) { return d.y; });
element = svg.selectAll('text')
.data(nodes)
.enter().append('svg:text')
.text(function (d, i) { return d.label + " - " + d.angle.toFixed(2) + ", " + (d.angle*180/Math.PI); })
.attr('x', function (d, i) { return nodes[0].x - 215; }) // radius * 2, add 15 for spacing off point
.attr('y', function (d, i) { return nodes[0].y; })
.attr("dy", ".35em")
.style("alignment-baseline","middle")
.style("text-anchor", "end")
.attr("transform", function(d,i) {
return "rotate(" + ((d.angle * 180) / Math.PI - 180) + ", 225, 225)";})
;
}
var draw = function () {
var radius = 100;
var nodes = createNodes();
createSvg(radius, function (svg) {
createElements(svg, nodes, 10);
});
}
$(document).ready(function () {
draw();
});
})();
* {
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
font-size: 13px;
}
circle {
fill: steelblue;
fill-opacity: .8;
}
circle:hover {
fill: orange;
fill-opacity: .8;
}
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.v2.min.js"></script>
<div id="canvas"></div>
However, now the labels on the right are upside down. All that is left is to determine is whether a label falls on the right half or the left half and assign the appropriate attributes based on this.
Zero degrees points to the right, it is not the top of the diagram. Therefore, you need to ascertain if d.angle is less than 90 degrees (bottom right) or more than 270 degrees (top right), if so, your original code can be applied. If not, then you need to flip the label using the above code:
(function () {
var paddding = 250;
var createNodes = function () {
var nodeData = [
{ id: 0, label: 'AAA' },
{ id: 1, label: 'BBB' },
{ id: 2, label: 'CCC' },
{ id: 3, label: 'DDD' },
{ id: 4, label: 'EEE' },
{ id: 5, label: 'FFF' },
{ id: 6, label: 'GGG' },
{ id: 7, label: 'HHH' }
];
var radius = 100;
var nodes = [],
width = (radius * 2) + paddding,
height = (radius * 2) + paddding,
angle,
x,
y,
i;
var numNodes = nodeData.length;
for (i = 0; i < numNodes; i++) {
angle = (i / (numNodes / 2)) * Math.PI;
x = (radius * Math.cos(angle)) + (width / 2);
y = (radius * Math.sin(angle)) + (width / 2);
nodes.push({ 'id': i, 'x': x, 'y': y, 'label': nodeData[i].label, 'angle': angle });
}
return nodes;
}
var createSvg = function (radius, callback) {
d3.selectAll('svg').remove();
var svg = d3.select('#canvas').append('svg:svg')
.attr('width', (radius * 2) + paddding)
.attr('height', (radius * 2) + paddding);
callback(svg);
}
var createElements = function (svg, nodes, elementRadius) {
element = svg.selectAll('circle')
.data(nodes)
.enter().append('svg:circle')
.attr('r', elementRadius)
.attr('cx', function (d, i) { return d.x; })
.attr('cy', function (d, i) { return d.y; });
element = svg.selectAll('text')
.data(nodes)
.enter().append('svg:text')
.text(function (d, i) { return d.label + " - " + d.angle.toFixed(2) + ", " + (d.angle*180/Math.PI); })
.attr('x', function (d, i) {
if (d.angle > Math.PI/2 && d.angle < 1.5 * Math.PI) {
return nodes[0].x - 215 }
else {
return nodes[0].x + 15;
}
})
.attr('y', function (d, i) { return nodes[0].y; })
.attr("dy", ".35em")
.style("alignment-baseline","middle")
.style("text-anchor", function(d) {
if (d.angle > Math.PI/2 && d.angle < 1.5 * Math.PI) {
return "end"
}
else {
return "start";
}
})
.attr("transform", function(d,i) {
if (d.angle > Math.PI/2 && d.angle < 1.5 * Math.PI) {
return "rotate(" + ((d.angle * 180) / Math.PI - 180) + ", 225, 225)";
}
else {
return "rotate(" + ((d.angle * 180) / Math.PI) + ", 225, 225)"
}
})
;
}
var draw = function () {
var radius = 100;
var nodes = createNodes();
createSvg(radius, function (svg) {
createElements(svg, nodes, 10);
});
}
$(document).ready(function () {
draw();
});
})();
* {
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
font-size: 13px;
}
circle {
fill: steelblue;
fill-opacity: .8;
}
circle:hover {
fill: orange;
fill-opacity: .8;
}
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.v2.min.js"></script>
<div id="canvas"></div>
Suppose you want to have an html page where the entire background is an svg, such that there is some animation going on constantly. In this silly example, I have made a smiley face which randomly moves about. While some people might find brownian motion appealing to the eye, it would be nicer if the svg element could move with the appearance of momentum (both direction and rotation).
One might quickly realize that having an object move along a path would solve this issue and it would... for the first pass of the object. However if the element were to be bounded by the screen, then how could one get the transition to adjust for the deflection?
In short the question is as follows:
Using d3.js v4 how can I make an svg element (such as #Mr_Smiley in the demo below) appear to float* across the html page?
*let float mean smoothly move with constant velocity along a vector or arc within the svg space with recoil upon hitting borders and correct deflection
var mr_s = d3.select("svg").append("g").attr("id", "Mr_Smiley")
mr_s.append("circle").attr("cx", 30).attr("cy", 30).attr("r", 30).style("fill","yellow").style("stroke", "black")
mr_s.append("circle").attr("cx", 20).attr("cy", 20).attr("r", 5).style("fill","black")
mr_s.append("circle").attr("cx", 40).attr("cy", 20).attr("r", 5).style("fill","black")
mr_s.append("path").attr("d", "M20 40 A 10 10 0 0 0 40 40").style("fill","black")
mr_s.datum({"x": 30, "y": 30, "r": 1})
mr_s.attr("transform", function(d) {"translate(" + d.x + ", " + d.y + ") rotate(" + d.r + ")"})
dur = 100
step = 10
// Lets have Mr. S go for a trip
d3.select("#Mr_Smiley")
.transition()
.duration(dur)
.on("start", function repeat() {
d3.active(this)
.attr("transform",
function(d)
{
// update y
if (Math.random() >= .5) {
d.y += step
} else {
d.y -= step
// basic bounds
if (d.y < 0) {
d.y = 0
}
}
// update x
if (Math.random() >= .5) {
d.x += step
} else {
d.x -= step
if (d.x < 0) {
d.x = 0
}
}
// update r
if (Math.random() >= .5) {
d.r += step
} else {
d.r -= step
}
return "translate(" + d.x + ", " + d.y + ") rotate(" + d.r + ")"
})
.transition()
.attr("transform",
function(d)
{
// update y
if (Math.random() >= .5) {
d.y += step
} else {
d.y -= step
}
// update x
if (Math.random() >= .5) {
d.x += step
} else {
d.x -= step
}
// update r
if (Math.random() >= .5) {
d.r += step
} else {
d.r -= step
}
return "translate(" + d.x + ", " + d.y + ") rotate(" + d.r + ")"
})
.transition()
.on("start", repeat)
})
mr_s.on("mouseover", mouseover)
mr_s.on("mouseout", mouseout)
function mouseover(d, i) {
var svg = d3.select("svg")
svg.append("text").attr("id", "mouseover_text").text("god help me, this is so unsmooth").attr("x", d.x).attr("y", d.y)
}
function mouseout(d, i) {
d3.select("#mouseover_text").remove()
}
html, body {
width: 100%;
height: 100%;
}
svg {
position: absolute;
width: 100%;
height: 100%;
background-color: light-blue;
border: 1px black solid;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
<h1>
Mr. Smiley goes on a trip
</h1>
Here's the simplest Mr S. bounce around a room I can code using d3 conventions:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
var w = 250,
h = 250,
r = 30;
var svg = d3.select('body')
.append('svg')
.attr('width', w)
.attr('height', h)
.style('border', '1px solid steelblue');
var ix = Math.random() * ((Math.random() > 0.5) ? 5 : -5),
iy = Math.random() * ((Math.random() > 0.5) ? 5 : -5),
x = w / 2,
y = h / 2;
var mr_s = svg.append("g")
.attr("id", "Mr_Smiley")
mr_s.append("circle")
.attr("r", r)
.style("fill", "yellow")
.style("stroke", "black");
mr_s.append("circle")
.attr("cx", -10)
.attr("cy", -10)
.attr("r", 5)
.style("fill", "black");
mr_s.append("circle")
.attr("cx", 10)
.attr("cy", -10)
.attr("r", 5)
.style("fill", "black");
mr_s.append("path")
.attr("d", "M-10 10 A 10 10 0 0 0 10 10")
.style("fill", "black");
mr_s.attr('transform', 'translate(' + x + ',' + y + ')');
d3.interval(tick, 20);
function tick() {
x += ix;
y += iy;
if (x > (w - r) || x < r) {
ix = -ix;
}
if (y > (h - r) || y < r) {
iy = -iy;
}
mr_s.attr('transform', 'translate(' + x + ',' + y + ')');
}
</script>
</body>
</html>
I'm struggling to get my first Sankey made the data is pretty simple:
Discharge OBS ADM FULL ADM Total
Station 1 1725 610 708 3043
Station 2 1095 424 464 1983
Station 3 1652 24 27 1703
So, I was thinking of creating the nodes and links in the following fashion - which was unsuccessful:
{
"nodes":[
{"name":"All Patients"},
{"name":"Station 1"},
{"name":"Station 2"},
{"name":"Station 3"},
{"name":"Discharge"},
{"name":"Obs Admission"},
{"name":"Full Admission"}
],
"links":[
{"source":"All Patients","target":"Station 1","value":3.043},
{"source":"All Patients","target":"Station 2","value":1.983},
{"source":"All Patients","target":"Station 3","value":1.703},
{"source":"Station 1","target":"Discharge","value":1.725},
{"source":"Station 2","target":"Discharge","value":1.095},
{"source":"Station 3","target":"Discharge","value":1.652},
{"source":"Station 1","target":"Obs Admission","value":.610},
{"source":"Station 2","target":"Obs Admission","value":.424},
{"source":"Station 3","target":"Obs Admission","value":.024},
{"source":"Station 1","target":"Full Admission","value":.708},
{"source":"Station 2","target":"Full Admission","value":.464},
{"source":"Station 3","target":"Full Admission","value":.027},
]}
Using this index.html:
<!DOCTYPE html>
<meta charset="utf-8">
<title>SANKEY Experiment</title>
<style>
.node rect {
cursor: move;
fill-opacity: .9;
shape-rendering: crispEdges;
}
.node text {
pointer-events: none;
text-shadow: 0 1px 0 #fff;
}
.link {
fill: none;
stroke: #000;
stroke-opacity: .2;
}
.link:hover {
stroke-opacity: .5;
}
</style>
<body>
<p id="chart">
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="sankey.js"></script>
<script>
var units = "Widgets";
var margin = {top: 10, right: 10, bottom: 10, left: 10},
width = 700 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var formatNumber = d3.format(",.0f"), // zero decimal places
format = function(d) { return formatNumber(d) + " " + units; },
color = d3.scale.category20();
// append the svg canvas to the page
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Set the sankey diagram properties
var sankey = d3.sankey()
.nodeWidth(36)
.nodePadding(40)
.size([width, height]);
var path = sankey.link();
// load the data
d3.json("sankey-formatted-names.json", function(error, graph) {
var nodeMap = {};
graph.nodes.forEach(function(x) { nodeMap[x.name] = x; });
graph.links = graph.links.map(function(x) {
return {
source: nodeMap[x.source],
target: nodeMap[x.target],
value: x.value
};
});
sankey
.nodes(graph.nodes)
.links(graph.links)
.layout(32);
// add in the links
var link = svg.append("g").selectAll(".link")
.data(graph.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
.sort(function(a, b) { return b.dy - a.dy; });
// add the link titles
link.append("title")
.text(function(d) {
return d.source.name + " → " +
d.target.name + "\n" + format(d.value); });
// add in the nodes
var node = svg.append("g").selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; })
.call(d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", function() {
this.parentNode.appendChild(this); })
.on("drag", dragmove));
// add the rectangles for the nodes
node.append("rect")
.attr("height", function(d) { return d.dy; })
.attr("width", sankey.nodeWidth())
.style("fill", function(d) {
return d.color = color(d.name.replace(/ .*/, "")); })
.style("stroke", function(d) {
return d3.rgb(d.color).darker(2); })
.append("title")
.text(function(d) {
return d.name + "\n" + format(d.value); });
// add in the title for the nodes
node.append("text")
.attr("x", -6)
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("transform", null)
.text(function(d) { return d.name; })
.filter(function(d) { return d.x < width / 2; })
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
// the function for moving the nodes
function dragmove(d) {
d3.select(this).attr("transform",
"translate(" + d.x + "," + (
d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))
) + ")");
sankey.relayout();
link.attr("d", path);
}
});
</script>
</body>
</html>
And this sankey.js:
d3.sankey = function() {
var sankey = {},
nodeWidth = 24,
nodePadding = 8,
size = [1, 1],
nodes = [],
links = [];
sankey.nodeWidth = function(_) {
if (!arguments.length) return nodeWidth;
nodeWidth = +_;
return sankey;
};
sankey.nodePadding = function(_) {
if (!arguments.length) return nodePadding;
nodePadding = +_;
return sankey;
};
sankey.nodes = function(_) {
if (!arguments.length) return nodes;
nodes = _;
return sankey;
};
sankey.links = function(_) {
if (!arguments.length) return links;
links = _;
return sankey;
};
sankey.size = function(_) {
if (!arguments.length) return size;
size = _;
return sankey;
};
sankey.layout = function(iterations) {
computeNodeLinks();
computeNodeValues();
computeNodeBreadths();
computeNodeDepths(iterations);
computeLinkDepths();
return sankey;
};
sankey.relayout = function() {
computeLinkDepths();
return sankey;
};
sankey.link = function() {
var curvature = .5;
function link(d) {
var x0 = d.source.x + d.source.dx,
x1 = d.target.x,
xi = d3.interpolateNumber(x0, x1),
x2 = xi(curvature),
x3 = xi(1 - curvature),
y0 = d.source.y + d.sy + d.dy / 2,
y1 = d.target.y + d.ty + d.dy / 2;
return "M" + x0 + "," + y0
+ "C" + x2 + "," + y0
+ " " + x3 + "," + y1
+ " " + x1 + "," + y1;
}
link.curvature = function(_) {
if (!arguments.length) return curvature;
curvature = +_;
return link;
};
return link;
};
// Populate the sourceLinks and targetLinks for each node.
// Also, if the source and target are not objects, assume they are indices.
function computeNodeLinks() {
nodes.forEach(function(node) {
node.sourceLinks = [];
node.targetLinks = [];
});
links.forEach(function(link) {
var source = link.source,
target = link.target;
if (typeof source === "number") source = link.source = nodes[link.source];
if (typeof target === "number") target = link.target = nodes[link.target];
source.sourceLinks.push(link);
target.targetLinks.push(link);
});
}
// Compute the value (size) of each node by summing the associated links.
function computeNodeValues() {
nodes.forEach(function(node) {
node.value = Math.max(
d3.sum(node.sourceLinks, value),
d3.sum(node.targetLinks, value)
);
});
}
// Iteratively assign the breadth (x-position) for each node.
// Nodes are assigned the maximum breadth of incoming neighbors plus one;
// nodes with no incoming links are assigned breadth zero, while
// nodes with no outgoing links are assigned the maximum breadth.
function computeNodeBreadths() {
var remainingNodes = nodes,
nextNodes,
x = 0;
while (remainingNodes.length) {
nextNodes = [];
remainingNodes.forEach(function(node) {
node.x = x;
node.dx = nodeWidth;
node.sourceLinks.forEach(function(link) {
nextNodes.push(link.target);
});
});
remainingNodes = nextNodes;
++x;
}
//
moveSinksRight(x);
scaleNodeBreadths((width - nodeWidth) / (x - 1));
}
function moveSourcesRight() {
nodes.forEach(function(node) {
if (!node.targetLinks.length) {
node.x = d3.min(node.sourceLinks, function(d) { return d.target.x; }) - 1;
}
});
}
function moveSinksRight(x) {
nodes.forEach(function(node) {
if (!node.sourceLinks.length) {
node.x = x - 1;
}
});
}
function scaleNodeBreadths(kx) {
nodes.forEach(function(node) {
node.x *= kx;
});
}
function computeNodeDepths(iterations) {
var nodesByBreadth = d3.nest()
.key(function(d) { return d.x; })
.sortKeys(d3.ascending)
.entries(nodes)
.map(function(d) { return d.values; });
//
initializeNodeDepth();
resolveCollisions();
for (var alpha = 1; iterations > 0; --iterations) {
relaxRightToLeft(alpha *= .99);
resolveCollisions();
relaxLeftToRight(alpha);
resolveCollisions();
}
function initializeNodeDepth() {
var ky = d3.min(nodesByBreadth, function(nodes) {
return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value);
});
nodesByBreadth.forEach(function(nodes) {
nodes.forEach(function(node, i) {
node.y = i;
node.dy = node.value * ky;
});
});
links.forEach(function(link) {
link.dy = link.value * ky;
});
}
function relaxLeftToRight(alpha) {
nodesByBreadth.forEach(function(nodes, breadth) {
nodes.forEach(function(node) {
if (node.targetLinks.length) {
var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value);
node.y += (y - center(node)) * alpha;
}
});
});
function weightedSource(link) {
return center(link.source) * link.value;
}
}
function relaxRightToLeft(alpha) {
nodesByBreadth.slice().reverse().forEach(function(nodes) {
nodes.forEach(function(node) {
if (node.sourceLinks.length) {
var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value);
node.y += (y - center(node)) * alpha;
}
});
});
function weightedTarget(link) {
return center(link.target) * link.value;
}
}
function resolveCollisions() {
nodesByBreadth.forEach(function(nodes) {
var node,
dy,
y0 = 0,
n = nodes.length,
i;
// Push any overlapping nodes down.
nodes.sort(ascendingDepth);
for (i = 0; i < n; ++i) {
node = nodes[i];
dy = y0 - node.y;
if (dy > 0) node.y += dy;
y0 = node.y + node.dy + nodePadding;
}
// If the bottommost node goes outside the bounds, push it back up.
dy = y0 - nodePadding - size[1];
if (dy > 0) {
y0 = node.y -= dy;
// Push any overlapping nodes back up.
for (i = n - 2; i >= 0; --i) {
node = nodes[i];
dy = node.y + node.dy + nodePadding - y0;
if (dy > 0) node.y -= dy;
y0 = node.y;
}
}
});
}
function ascendingDepth(a, b) {
return a.y - b.y;
}
}
function computeLinkDepths() {
nodes.forEach(function(node) {
node.sourceLinks.sort(ascendingTargetDepth);
node.targetLinks.sort(ascendingSourceDepth);
});
nodes.forEach(function(node) {
var sy = 0, ty = 0;
node.sourceLinks.forEach(function(link) {
link.sy = sy;
sy += link.dy;
});
node.targetLinks.forEach(function(link) {
link.ty = ty;
ty += link.dy;
});
});
function ascendingSourceDepth(a, b) {
return a.source.y - b.source.y;
}
function ascendingTargetDepth(a, b) {
return a.target.y - b.target.y;
}
}
function center(node) {
return node.y + node.dy / 2;
}
function value(link) {
return link.value;
}
return sankey;
};
It didn't work... not sure why. Any advice is greatly appreciated.
Alfa
Unfortunately, looks like couple very minor mistakes.
1) You have an extra comma at the end of your links {"source":"Station 3","target":"Full Admission","value":.027}**,** so use updated sankey-formatted-names.json file without extra comma below. I like to use JSON validator https://jsonformatter.curiousconcept.com/ to confirm if valid JSON file.
2) Also, I looked at an example JSON file at http://bl.ocks.org/d3noob/5028304 and they have the values in double quotes. Since you have source and target as strings, then looks like value must also be a string. If you have source and target as numeric, then value can be numeric.
{
"nodes":[
{"name":"All Patients"},
{"name":"Station 1"},
{"name":"Station 2"},
{"name":"Station 3"},
{"name":"Discharge"},
{"name":"Obs Admission"},
{"name":"Full Admission"}
],
"links":[
{"source":"All Patients","target":"Station 1","value":"3.043"},
{"source":"All Patients","target":"Station 2","value":"1.983"},
{"source":"All Patients","target":"Station 3","value":"1.703"},
{"source":"Station 1","target":"Discharge","value":"1.725"},
{"source":"Station 2","target":"Discharge","value":"1.095"},
{"source":"Station 3","target":"Discharge","value":"1.652"},
{"source":"Station 1","target":"Obs Admission","value":"0.610"},
{"source":"Station 2","target":"Obs Admission","value":"0.424"},
{"source":"Station 3","target":"Obs Admission","value":"0.024"},
{"source":"Station 1","target":"Full Admission","value":"0.708"},
{"source":"Station 2","target":"Full Admission","value":"0.464"},
{"source":"Station 3","target":"Full Admission","value":"0.027"}
]}
I have my data formatted like flare.json that's used in this example :
I am just wondering what function the d3 zoomable chart uses to get the data in this format
In flare.json it's like this
{
name: "stuff",
children: [
....
]
}
and it's converted to this in the example. Which line does this?
{
children: Array[17]
depth: 1
dx: 0.6028744305756647
dy: 0.25
name: "A name would appear here"
parent: Object
value: 39850000.06
x: 0
y: 0.25
}
Chart
var total_revenue = json.total_revenue;
json = json.chart_data;
var width = 840,
height = width,
radius = width / 2,
x = d3.scale.linear().range([0, 2 * Math.PI]),
y = d3.scale.pow().exponent(1.3).domain([0, 1]).range([0, radius]),
padding = 5,
duration = 1000;
var div = d3.select("#chart_render");
div.select("img").remove();
var vis = div.append("svg")
.attr("width", width + padding * 2)
.attr("height", height + padding * 2)
.append("g")
.attr("transform", "translate(" + [radius + padding, radius + padding] + ")");
var partition = d3.layout.partition()
.value(function(d) { return d.size });
var arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, d.y ? y(d.y) : d.y); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
console.log(json);
var nodes = partition.nodes({children: json});
var path = vis.selectAll("path").data(nodes);
path.enter().append("path")
.attr("id", function(d, i) { return "path-" + i; })
.attr("d", arc)
.attr("fill-rule", "evenodd")
.style("fill", colour)
.on("click", click);
var text = vis.selectAll("text").data(nodes);
var textEnter = text.enter().append("text")
.style("fill-opacity", function(d) {
var relative_percent = 0;
var relative_total = 0;
//console.log(d);
if (d.depth != 0) {
for(var i = 0; i < d.parent.children.length; i++) {
relative_total += d.parent.children[i].value;
}
//console.log(relative_total);
relative_percent = d.value/total_revenue*100;
if (relative_percent > 1) {
return '1';
} else {
return '0';
}
}
})
.style("fill", function(d) {
return "#fff";
})
.attr("text-anchor", function(d) {
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
})
.attr("dy", ".2em")
.attr("transform", function(d) {
var multiline = (d.name || "").split(" ").length > 1,
angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
rotate = angle + (multiline ? -.5 : 0);
return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
})
.on("click", click);
textEnter.append("tspan")
.attr("x", 0)
.text(function(d) { return d.depth ? d.name.split(" ")[0] : ""; });
textEnter.append("tspan")
.attr("x", 0)
.attr("dy", "1em")
.text(function(d) { return d.depth ? d.name.split(" ")[1] || "" : ""; });
function click(d) {
path.transition()
.duration(duration)
.attrTween("d", arcTween(d));
// Somewhat of a hack as we rely on arcTween updating the scales.
text.style("visibility", function(e) {
return isParentOf(d, e) && e.value > 1500000 ? null : d3.select(this).style("visibility");
})
.transition()
.duration(duration)
.attrTween("text-anchor", function(d) {
return function() {
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
};
})
.attrTween("transform", function(d) {
var multiline = (d.name || "").split(" ").length > 1;
return function() {
var angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
rotate = angle + (multiline ? -.5 : 0);
return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
};
})
.style("fill-opacity", function(e) { return isParentOf(d, e) ? 1 : 1e-6; })
.each("end", function(e) {
d3.select(this).style("visibility", function (d) {
// var relative_total = 0;
// var relative_percent = 0;
// for(var i = 0; i < d.parent.children.length; i++) {
// relative_total += d.parent.children[i].value;
// }
// console.log(relative_total);
// relative_percent = d.value/relative_total*100;
// console.log(relative_percent);
return isParentOf(d, e) && e.value > 1500000 ? null : "hidden";
})
});
}
function isParentOf(p, c) {
if (p === c) return true;
if (p.children) {
return p.children.some(function(d) {
return isParentOf(d, c);
});
}
return false;
}
function colour(d) {
if (d.depth == 0) {
return "rgb(250, 250, 250)";
} else if (d.depth == 1) {
return 'rgb(86, 135, 209)';
} else if (d.depth == 2) {
return 'rgb(222, 120, 59)';
} else if (d.depth == 3) {
return 'rgb(106, 185, 117)';
}
// if (d.children) {
// // There is a maximum of two children!
// var colours = d.children.map(colour),
// a = d3.hsl(colours[0]),
// b = d3.hsl(colours[1]);
// // L*a*b* might be better here...
// return d3.hsl((a.h + b.h) / 2, a.s * 1.2, a.l / 1.2);
// }
// return d.colour || "#fff";
}
// Interpolate the scales!
function arcTween(d) {
var my = maxY(d),
xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, my]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d) {
return function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
};
}
function maxY(d) {
return d.children ? Math.max.apply(Math, d.children.map(maxY)) : d.y + d.dy;
}
// http://www.w3.org/WAI/ER/WD-AERT/#color-contrast
function brightness(rgb) {
return rgb.r * .299 + rgb.g * .587 + rgb.b * .114;
}
This line:
var nodes = partition.nodes({children: json});
Explanation of code that sets up sunburst diagram
In D3 parlance, sunburst diagram is based on D3 "partition layout". Actually, D3 "partition layout" is in a way more general term, since it can be used for displaying not only sunburst diagram, but also others based on the idea of "partitioning" parents (hence the name "partition"). This is also a useful example for noticing difference between "layout" and "diagram" (in D3 mindset), but this is another story.
Following 2 lines are first steps in initializing partition layout:
var partition = d3.layout.partition()
.value(function(d) { return d.size });
This line does all calculations:
var nodes = partition.nodes({children: json});
Then variable nodes can be used for defining actual visual appearance of svg elements (arcs and labels):
var path = vis.selectAll("path").data(nodes);
and
var text = vis.selectAll("text").data(nodes);
These two lines represent something which is called "data binding" often. They enable programmers to use data to drive visual elements, like in the following line:
.text(function(d) { return d.depth ? d.name.split(" ")[0] : ""; });
Here, d.name originates from data, and d.depth is added by partition layout. They are both actually part of nodes.
I tried to explain in simple terms, but probably there are some confusing points to you - don't worry, it will be crystal clear to you soon, if you read the right docs and tutorials. ;)
I'm using this model : http://www.jasondavies.com/coffee-wheel/:
I made a sunburst that works pretty well, except when I zoom in or out, sometimes it changes the current node.
When I look at my tooltip, I can see that the "zone" doesn't refer to the correct element and it sends me to this wrong element. I don't know where that could come from.
name should be "LDG", but there is a small zone in which there is a reference to another element.
loadSunburstTree: function() {
var width = 960,
height = width,
radius = Math.min(width, height) / 2,
padding = 5,
duration = 1000;
var x = d3.scale.linear().range([0, 2 * Math.PI]);
var y = d3.scale.sqrt().range([0, radius]);
var color = d3.scale.category20c();
var path = null;
var arc = null;
var text = null;
$.ajax({
url: $("#path").val() + ".json",
method: "GET",
success: function(result) {
var data = {
"name": "home",
"level" : "root",
"children": []
};
[...] // filling the variable data here (data are correct),
var svg = d3.select("#graph").append("svg")
.attr("width", width)
.attr("height", height + 50)
.datum(data)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")");
var partition = d3.layout.partition()
.value(function(d) { return d.size; });
arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, y(d.y)); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
path = svg.selectAll("path")
.data(partition.nodes)
.enter().append("path")
.attr("d", arc)
.style("fill", function(d) { return color((d.children ? d : d.parent).name); })
.on("click", click);
text = svg.selectAll("text").data(partition.nodes);
text.enter().append("text")
.style("full-opacity", 1)
.style("fill", function(d) {
return brightness(d3.rgb(colour(d))) < 125 ? "#eee" : "#000";
})
.attr("text-anchor", function(d) {
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
})
.attr("dy", ".2em")
.attr("transform", function(d) {
var angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
rotate = angle;
return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
})
.text(function(d) {
if( d.dx < .01){
d3.select(this).style("display","none");
}
return d.depth ? d.name : "";
})
.on("click", click);
}
});
function click(d) {
path.transition()
.duration(duration)
.attrTween("d", arcTween(d));
text.transition()
.duration(duration)
.attrTween("transform", function(d) {
return function() {
var angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90;
var rotate = angle;
return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
};
})
.attrTween("text-anchor", function(d) {
return function() {
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
};
})
.style("fill-opacity", function(e) { return isParentOf(d, e) ? 1 : 1e-6; })
.each("end", function(e) {
d3.select(this).style("display","");
var st = e;
if (st.dx / d.dx < 0.01) {
d3.select(this).style("display","none");
} else {
d3.select(this).style("display","");
}
});
}
d3.select(self.frameElement).style("height", height + "px");
// Interpolate the scales!
function arcTween(d) {
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d, i) {
return i
? function(t) { return arc(d); }
: function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
};
}
function isParentOf(p, c) {
if (p === c) return true;
if (p.children) {
return p.children.some(function(d) {
return isParentOf(d, c);
});
}
return false;
}
function colour(d) {
if (d.children) {
// There is a maximum of two children!
var colours = d.children.map(colour),
a = d3.hsl(colours[0]),
b = d3.hsl(colours[1]);
// L*a*b* might be better here...
return d3.hsl((a.h + b.h) / 2, a.s * 1.2, a.l / 1.2);
}
return d.colour || "#fff";
}
function brightness(rgb) {
return rgb.r * .299 + rgb.g * .587 + rgb.b * .114;
}
}
};
PS : Looks like the demo also have this problem, it's just harder to notice because there is no tooltip.
PPS : The clickable element is the text ! (in most of the case) The texts only have an opacity = 0 and are still on the view. That what makes the problem. They must not be correctly hidden. Still working on it.
That solved the problem. Texts tooltips are no longer visible nor clickable :
Just edit the text code of the function click(d) like follow :
function click(d) {
path.transition()
.duration(duration)
.attrTween("d", arcTween(d));
text.transition()
.duration(duration)
.attrTween("transform", function(d) {
return function() {
var angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90;
var rotate = angle;
return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
};
})
.attrTween("text-anchor", function(d) {
return function() {
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
};
})
//modified from here
.style("fill-opacity", function(e) {
if (isParentOf(d, e)) {
return 1;
} else {
return 0;
}
})
.each("end", function(e) {
if (e.dx / d.dx < 0.01 || $(this).css("fill-opacity") == 0) {
d3.select(this).style("display","none");
} else {
d3.select(this).style("display","");
}
});
//to here
}