Related
I am trying to "clip" this spinning wheel: https://bl.ocks.org/mpmckenna8/7f1f0adbf7d9ed7520b3950103e8094c
I want to only make the top-half of the wheel visible. When I try to do this with "clip-path" I end up having a half-wheel rotating. (see: https://codepen.io/icklerly/pen/JMBdGX)
svg.append("clipPath") // define a clip path
.attr("id", "ellipse-clip") // give the clipPath an ID
.append("rect")
.attr("x", -100)
.attr("y", 0)
.attr("width", 200)
.attr("height", 200);
But I want the wheel to rotate and the clip window always on the same position top.
Any suggestions?
The issue is that you are rotating the g element on where you applied the clip-path. Instead you can add another g on where you apply the clip-path and keep the rotation on another g inside.
So intead of this :
var hub = svg.append('g')
.attr('transform', function(){
return "translate(" + width/2 + "," + height/2 + ")"
})
.attr('class', 'hub')
.attr("clip-path", "url(#rect-clip)")
Do this :
var hub = svg.append('g').attr("clip-path", "url(#rect-clip)") /* append first g with clip-path */
.append('g') /* then create the inside g with the remaining properties*/
.attr('transform', function(){
return "translate(" + width/2 + "," + height/2 + ")"
})
.attr('class', 'hub')
You can also adjust the clip-path and simply make its size half the wheel to avoid using negative value for x/y.
Full Code:
var svg = d3.select('svg')
var margin = 20;
var width = 200, // margin,
height = 200 // margin;
svg.append("clipPath") // define a clip path
.attr("id", "rect-clip") // give the clipPath an ID
.append("rect")
.attr("x", 0) // position the x-centre
.attr("y", 0) // position the y-centre
.attr("width", 200) // set the x radius
.attr("height", 100);
var hub = svg.append('g').attr("clip-path", "url(#rect-clip)").append('g')
.attr('transform', function() {
return "translate(" + width / 2 + "," + height / 2 + ")"
})
.attr('class', 'hub')
hub.append('circle')
.attr('cx', 0)
.attr('cy', 0)
.attr('r', 10)
.attr('fill', 'pink')
hub.append('circle')
.attr('cx', 0)
.attr('cy', 0)
.attr('r', 90)
.attr('stroke', 'red')
.attr('stroke-width', 5)
.attr('fill', 'none')
var linelen = [0, 90];
var line = d3.line().x(function(d) {
return (0)
})
.y(function(d) {
return (d)
})
const numberSpokes = 10;
for (let i = 0; i < numberSpokes; i++) {
var rotation = (360 / numberSpokes) * i;
var spoke = hub
.append('path')
.datum(linelen)
.attr('d', line)
.attr('stroke', 'blue')
.attr('transform', 'rotate(' + rotation + ')')
.attr('class', 'spoke')
}
const alreadyTransformed = hub.attr('transform')
rotateIt(false)
function rotateIt(much) {
//console.log(alreadyTransformed)
hub.transition().duration(4000)
.attr('transform', alreadyTransformed + ' rotate(' + (much ? 0 : 180) + ')')
.on('end', function() {
rotateIt(!much)
})
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg id="svger" width="200px" height="200px"></svg>
I'm trying to animate a circle being drained, with is working well. However I want a "marker" to follow the drain level, and I'm having difficulties understanding how to do this.
I already made a example embedded in this post, where the circle fill animates, and the number animates.
The final state can be seen here:
The issue is; that I want to place the "XXX used" marker based on the drain percentage. But I have had little luck figuring out how to achieve this.
So it has to move up and down, but also left and right depending on the percentage.
My code is as follows:
const usedAmount = 200;
const totalAmount = 400;
const radius = 120;
const valuePercent = (usedAmount / totalAmount) * 100;
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
let grad = svg
.append("defs")
.append("linearGradient")
.attr("id", "grad")
.attr("x1", "0%")
.attr("x2", "0%")
.attr("y1", "0%")
.attr("y2", "0%");
grad.append("stop").attr("offset", "1%").style("stop-color", '#000');
grad.append("stop").attr("offset", "1%").style("stop-color", '#ccc');
let arc = d3.arc()
.innerRadius( radius - 40 )
.outerRadius( radius )
.endAngle(2 * Math.PI)
.startAngle(0 * Math.PI);
let cutout = svg.select('defs')
.append('clipPath')
.attr('clip-rule', 'evenodd')
.attr('id', 'cutout')
.append("path")
.attr('transform', 'translate(' + radius + ',' + radius + ')')
.attr('d', arc)
.attr('clip-rule', 'evenodd')
.attr('fill', '#ccc');
svg.append("circle")
.attr("cx", radius)
.attr("cy", radius)
.attr("r", radius)
.attr("clip-path", "url(#cutout)") // Apply the mask
.attr("fill", "url(#grad)");
grad
.transition()
.duration(3000)
.ease(d3.easeQuad)
.delay(300)
.attr("y1", valuePercent + 1 + '%');
var marker = svg.append('g')
.attr('class', 'gauge__fillup__follow')
.attr("transform", "translate(" + 200 + "," + 50 + ")");
marker.append("rect")
.attr("x", 10)
.attr("y", 6)
.attr("fill", "#000")
.attr("width", 35)
.attr("height", 3);
marker.append('svg:text')
.attr('class', 'label')
.attr('z-index', '4')
.attr('x', 50)
.attr('y', 0)
.attr('dy', 12)
.attr('text-anchor', 'left')
.datum({textContent: ''})
.text(200)
.transition()
.duration(3000)
.delay(300)
.ease(d3.easeQuad)
.tween("text", function(d) {
const i = d3.interpolate(0, this.textContent, d);
return (t) => {
d3.select(this).text(Math.round(i(t)));
};
});
marker.append('svg:text')
.attr('class', 'label')
.attr('color', 'white')
.attr('z-index', '4')
.attr('x', 50)
.attr('y', 16)
.attr('dy', 12)
.attr('text-anchor', 'left')
.text('used');
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<svg height="500" width="500"></svg>
Ended up with this result: https://codepen.io/Saturate/pen/BROzBe
You just have to translate the group down by the same amount:
marker.transition()
.duration(3000)
.ease(d3.easeQuad)
.delay(300)
.attr("transform", "translate(" + 220 + ","
+ ((usedAmount / totalAmount) * (radius * 2)) + ")");
Here is the demo:
const usedAmount = 200;
const totalAmount = 400;
const radius = 120;
const valuePercent = (usedAmount / totalAmount) * 100;
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
let grad = svg
.append("defs")
.append("linearGradient")
.attr("id", "grad")
.attr("x1", "0%")
.attr("x2", "0%")
.attr("y1", "0%")
.attr("y2", "0%");
grad.append("stop").attr("offset", "1%").style("stop-color", '#000');
grad.append("stop").attr("offset", "1%").style("stop-color", '#ccc');
let arc = d3.arc()
.innerRadius(radius - 40)
.outerRadius(radius)
.endAngle(2 * Math.PI)
.startAngle(0 * Math.PI);
let cutout = svg.select('defs')
.append('clipPath')
.attr('clip-rule', 'evenodd')
.attr('id', 'cutout')
.append("path")
.attr('transform', 'translate(' + radius + ',' + radius + ')')
.attr('d', arc)
.attr('clip-rule', 'evenodd')
.attr('fill', '#ccc');
svg.append("circle")
.attr("cx", radius)
.attr("cy", radius)
.attr("r", radius)
.attr("clip-path", "url(#cutout)") // Apply the mask
.attr("fill", "url(#grad)");
grad
.transition()
.duration(3000)
.ease(d3.easeQuad)
.delay(300)
.attr("y1", valuePercent + 1 + '%');
var marker = svg.append('g')
.attr('class', 'gauge__fillup__follow')
.attr("transform", "translate(" + 220 + "," + 0 + ")");
marker.append("rect")
.attr("x", 10)
.attr("y", 1)
.attr("fill", "#000")
.attr("width", 35)
.attr("height", 3);
marker.append('svg:text')
.attr('class', 'label')
.attr('z-index', '4')
.attr('x', 50)
.attr('y', -6)
.attr('dy', 12)
.attr('text-anchor', 'left')
.datum({
textContent: ''
})
.text(200)
.transition()
.duration(3000)
.delay(300)
.ease(d3.easeQuad)
.tween("text", function(d) {
const i = d3.interpolate(0, this.textContent, d);
return (t) => {
d3.select(this).text(Math.round(i(t)));
};
});
marker.append('svg:text')
.attr('class', 'label')
.attr('color', 'white')
.attr('z-index', '4')
.attr('x', 50)
.attr('y', 10)
.attr('dy', 12)
.attr('text-anchor', 'left')
.text('used');
marker.transition()
.duration(3000)
.ease(d3.easeQuad)
.delay(300)
.attr("transform", "translate(" + 220 + "," + ((usedAmount / totalAmount )*(radius*2)) + ")");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<svg height="500" width="500"></svg>
EDIT: Here is the translation with attrTween, going from up to down and left to right:
marker.transition()
.duration(3000)
.ease(d3.easeQuad)
.delay(300)
.attrTween("transform", function() {
return function(t) {
return "translate(" + (radius * (1 + (Math.sin(Math.PI / 2 * t)))) + ","
+ (((usedAmount / totalAmount) * (radius * 2)) * (1 - (Math.cos(Math.PI / 2 * t)))) + ")"
}
});
Here is the demo:
const usedAmount = 200;
const totalAmount = 400;
const radius = 120;
const valuePercent = (usedAmount / totalAmount) * 100;
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
let grad = svg
.append("defs")
.append("linearGradient")
.attr("id", "grad")
.attr("x1", "0%")
.attr("x2", "0%")
.attr("y1", "0%")
.attr("y2", "0%");
grad.append("stop").attr("offset", "1%").style("stop-color", '#000');
grad.append("stop").attr("offset", "1%").style("stop-color", '#ccc');
let arc = d3.arc()
.innerRadius(radius - 40)
.outerRadius(radius)
.endAngle(2 * Math.PI)
.startAngle(0 * Math.PI);
let cutout = svg.select('defs')
.append('clipPath')
.attr('clip-rule', 'evenodd')
.attr('id', 'cutout')
.append("path")
.attr('transform', 'translate(' + radius + ',' + radius + ')')
.attr('d', arc)
.attr('clip-rule', 'evenodd')
.attr('fill', '#ccc');
svg.append("circle")
.attr("cx", radius)
.attr("cy", radius)
.attr("r", radius)
.attr("clip-path", "url(#cutout)") // Apply the mask
.attr("fill", "url(#grad)");
grad
.transition()
.duration(3000)
.ease(d3.easeQuad)
.delay(300)
.attr("y1", valuePercent + 1 + '%');
var marker = svg.append('g')
.attr('class', 'gauge__fillup__follow')
.attr("transform", "translate(" + (radius) + "," + 0 + ")");
marker.append("rect")
.attr("x", 10)
.attr("y", 1)
.attr("fill", "#000")
.attr("width", 35)
.attr("height", 3);
marker.append('svg:text')
.attr('class', 'label')
.attr('z-index', '4')
.attr('x', 50)
.attr('y', -6)
.attr('dy', 12)
.attr('text-anchor', 'left')
.datum({
textContent: ''
})
.text(200)
.transition()
.duration(3000)
.delay(300)
.ease(d3.easeQuad)
.tween("text", function(d) {
const i = d3.interpolate(0, this.textContent, d);
return (t) => {
d3.select(this).text(Math.round(i(t)));
};
});
marker.append('svg:text')
.attr('class', 'label')
.attr('color', 'white')
.attr('z-index', '4')
.attr('x', 50)
.attr('y', 10)
.attr('dy', 12)
.attr('text-anchor', 'left')
.text('used');
marker.transition()
.duration(3000)
.ease(d3.easeQuad)
.delay(300)
.attrTween("transform", function() {
return function(t) {
return "translate(" + (radius * (1 + (Math.sin(Math.PI / 2 * t)))) + "," + (((usedAmount / totalAmount) * (radius * 2)) * (1 - (Math.cos(Math.PI / 2 * t)))) + ")"
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<svg height="500" width="500"></svg>
Your question made me realize that i didn't understand trigonometry as well as i would like, so i took a stab at it. Once i learned the fundamentals of pi and sine, the answer became quite clear.
Great article i read that led me to my answer: https://betterexplained.com/articles/intuitive-understanding-of-sine-waves/
https://jsfiddle.net/zk0wsq5a/2/
marker.transition()
.duration(3000)
.ease(d3.easeQuad)
.delay(300)
.attrTween("transform", function() {
return function(t) {
let distance = Math.PI * t * (usedAmount / totalAmount)
let x = radius + (radius * Math.sin(distance))
let y = radius * (1-Math.cos(distance))
return `translate(${x} ,${y})`
}
});
I am trying to alter the traditional zooming feature on a sunburst chart. Traditionally when you click on a partition, that partition grows to cover 100% of the base layer while all other partitions on the same layer disappear. The children of the selected partition all grow to fill the newly created space.
My current code does just what I stated above. I would like to alter my code to allow for the selected partition to only take up 75% of the base layer. The children elements will grow to cover this new space but the remaining 25% will still contain all other non-selected partitions.
I have tried altering the 't' value that is returned from d3.interpolate() but I have had unpredictable results.
I hope my description is clear.
Does anyone have any thoughts on this?
<script>
var width = 960,
height = 700,
radius = Math.min(width, height) / 2;
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.linear()
.range([0, radius]);
var color = d3.scale.category20c();
function percent(d) {
var percentage = (d.value / 956129) * 100;
return percentage.toFixed(2);
}
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>" + d.name + "</strong> <span style='color:red'>" + percent(d) + "%</span>";
})
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")");
svg.call(tip);
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, y(d.y)) })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)) });
d3.json("flare.json", function(error, root) {
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
var path = g.append("path")
.attr("d", arc)
// .attr("stroke", 'black')
// .style("fill", function(d) { return color((d.children ? d : d.parent).name); })
.style("fill", function(d, i) {
return color(i);
})
.on("click", click)
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
var text = g.append("text")
.attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
.attr("x", function(d) { return y(d.y); })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d) {
if (percent(d) > 1.35) {
return d.name;
}
})
.attr('font-size', function(d) {
if (d.value < 100000) {
return '10px'
} else {
return '20px';
}
})
.on("click", click)
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
function click(d) {
console.log(d)
// fade out all text elements
text.transition().attr("opacity", 0);
path
.transition()
.duration(750)
.attrTween("d", arcTween(d))
.each("end", function(e, i) {
// check if the animated element's data e lies within the visible angle span given in d
if (e.x >= d.x && e.x < (d.x + d.dx)) {
// get a selection of the associated text element
var arcText = d3.select(this.parentNode).select("text");
// fade in the text element and recalculate positions
arcText.transition().duration(750)
.attr("opacity", 1)
.attr("transform", function() { return "rotate(" + computeTextRotation(e) + ")" })
.attr("x", function(d) { return y(d.y); });
}
});
}
});
d3.select(self.frameElement).style("height", height + "px");
// Interpolate the scales!
function arcTween(d) {
console.log(d.name, x.domain())
console.log(d.name, y.domain())
console.log(d.name, y.range())
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) {
console.log(t)
x.domain(xd(t));
y.domain(yd(t)).range(yr(t));
return arc(d);
};
};
}
function computeTextRotation(d) {
return (x(d.x + d.dx / 2) - Math.PI / 2) / Math.PI * 180;
}
I found the solution here: https://bl.ocks.org/mbostock/1306365. This example manages the zoom without getting rid of the sibling nodes.
Lost on this especially since I've done it successfully before. The code below is within a function called on a click. This first example works just fine:
y.domain([1, get_max(data)]);
svg.select('.y.axis')
.call(yAxis);
svg.selectAll('.bars')
.attr('y', function (d) { return y(d); })
.attr('height', function (d) { return height - y(d); });
This second example doesn't do anything:
y.domain([1, get_max(data)]);
svg.select('.y.axis')
.transition()
.duration(80)
.call(yAxis);
svg.selectAll('.bars')
.transition()
.duration(80)
.attr('y', function (d) { return y(d); })
.attr('height', function (d) { return height - y(d); });
No javascript errors are produced. It simply doesn't do anything.
Any help would be greatly appreciated. Thank you!
Note: get_max(data) is a special function to get the max of some very oddly formatted data. When I replace it with a hard coded value of 10,000 the problem persists. Again it works fine until I add the transition.
EDIT:
function render(parent, data, brands){
var time_format = parent.attr('data-chart-size') > 3 ? '%b %e' : '%e',
margin = { top: 30, right: 20, bottom: 21, left: 45 },
width = parent.width() - margin.left - margin.right - 110; // -110 for the legends on the right side
height = 205 - margin.top - margin.bottom;
var x = d3.time.scale().domain([ML._dates.start(), ML._dates.end()]).range([0, width]);
y = d3.scale.log().clamp(true).range([height, 1]);
var xAxis = d3.svg.axis().scale(x).orient('bottom')
.tickFormat(d3.time.format(time_format)).tickSize(0).tickPadding(8);
yAxis = d3.svg.axis().scale(y).orient('left').ticks(5, 's').tickSize(0);
svg = d3.select(parent.get(0)).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 + ')');
var brands_length = brands.length,
values_length = data.posts[brands[0]].values.length,
bar_width_adjustment = 60 / values_length,
bar_width = ((width / values_length) / brands_length) - bar_width_adjustment;
range_band = width / values_length;
y.domain([1, get_max(data)]);
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(' + (bar_width * 2) + ',' + height + ')')
.call(xAxis);
svg.append('g')
.attr('class', 'y axis')
.attr('transform', 'translate(0, 0)')
.call(yAxis);
// BUILD THE BARS AND TOOLTIP HIGHLIGHT AREA
for(var brands_loop = 0; brands_loop < brands_length; ++brands_loop){
svg.selectAll('.chart-hover')
.data(data.posts[brands[brands_loop]].values)
.enter().append('rect')
.attr('width', width / values_length)
.attr('x', function (d, i) { return i * range_band - ((range_band - bar_width * 3) / 2); })
.attr('y', 1)
.attr('height', height - y(y.domain()[1]))
.attr('transform', 'translate(' + ((bar_width * (brands_loop + 1) - (bar_width / 2)) + ', 0)'))
.attr('data-index', function (d, i) { return i; })
.attr('class', 'chart-hover')
.style('opacity', 0.01);
svg.selectAll('.bar-' + brands_loop)
.data(data.posts[brands[brands_loop]].values)
.enter().append('rect')
.attr('data-legend-listener-brand', brands[brands_loop])
.attr('data-legend-listener-metric', 'posts')
.attr('data-hover-dispatcher-index', function (d, i) { return i; })
.attr('width', bar_width)
.attr('x', function (d, i) { return i * range_band; })
.attr('y', function (d) { return y(d); })
.attr('height', function (d) { return height - y(d); })
.attr('transform', 'translate(' + ((bar_width * (brands_loop + 1) - (bar_width / 2)) + ', 0)'))
.attr('class', 'posts bars bar-' + brands_loop);
// POPULATE LEGEND TEXTS FOR POSTS, EXPOSURE AND ENGAGEMENT
$('.brand-' + (brands_loop + 1))
.text(data.posts[brands[brands_loop]].label)
.attr('data-legend-brand', brands[brands_loop])
.attr('data-legend-listener-brand', brands[brands_loop])
.prev('i')
.attr('data-legend-brand', brands[brands_loop])
.attr('data-legend-listener-brand', brands[brands_loop]);
}
etc. etc.
I am trying to make my tree have a straight links between the parent node and the children nodes.
I have straight links now but the links are not connecting to the right places.
I think this may be because there is a transformation of rotation and translate to the nodes and the x and y didn't change somehow.
I have tried following the answer in this question but result is the same. D3: Substituting d3.svg.diagonal() with d3.svg.line()
var lines = svg.selectAll('line')
.data(links)
.enter()
.append('line')
.attr('stroke','#000')
lines.attr('x1',function(d){return d.source.x })
.attr('y1',function(d){return d.source.x})
.attr('x2',function(d){return d.target.x })
.attr('y2',function(d){return d.target.y })
Here is the full code:
var diameter = 1000;
var tree = d3.layout.tree()
.size([360, diameter / 2 - 100])
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
// var diagonal = d3.svg.diagonal.radial()
// .projection(function(d) {
// return [d.y, d.x ]; })
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter )
.append("g")
.attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
d3.json("flare.json", function(error, root) {
var nodes = tree.nodes(root),
links = tree.links(nodes);
var link = svg.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
var lines = svg.selectAll('line')
.data(links)
.enter()
.append('line')
.attr('stroke','#000')
lines.attr('x1',function(d){return d.source.x })
.attr('y1',function(d){return d.source.x})
.attr('x2',function(d){return d.target.x })
.attr('y2',function(d){return d.target.y })
var node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d) { return "rotate(" + (d.x - 90 ) + ")translate(" + d.y + ")"; })
node.append("circle")
.attr("r", 10);
node.append("text")
.attr("dy", ".81em")
.attr("text-anchor", function(d) {
return d.x < 180 ? "start" : "end"; })
.attr("transform", function(d) { return d.x < 180 ? "translate(20)" : "rotate(180)translate(-20)"; })
.text(function(d) { return d.name; });
});
d3.select(self.frameElement).style("height", diameter - 150 + "px");
screenshots
I finally got it to work.. The solution is quite bizarre.
There is no projection method for line as there is for diagonal.
So when resetting the positions of x1,x2,y1,y2 needs a little bit tuning just like the diagonal projection.
Also I have to apply the transformation like how the nodes are applied but without the translation.
var link = svg.selectAll("link")
.data(links)
.enter().append("path")
.attr("class", "link")
var lines = svg.selectAll('line')
.data(links)
.enter()
.append('line')
.attr('stroke','#000')
lines.attr('x1',function(d){return d.source.y})
.attr('y1',function(d){return d.source.x/180*Math.PI})
.attr('x2',function(d){return d.target.y })
.attr('y2',function(d){return d.target.x/180*Math.PI})
// lines.attr("transform", function(d) {
// return "rotate(" + (d.source.x - 90 ) + ")translate(" + d.source.y + ")"; })
lines.attr("transform", function(d) {
return "rotate(" + (d.target.x - 90 ) + ")"; })