Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transform label in reverse order d3 radial chart

I have a d3 radial chart created using some community sample and stack-overflow posts.

Here the two bottom labels and numbers are in mirrored form (A13 and A14). Looking for some snippets to transform only this two in counter-clockwise with numbers top (next to the chart) then label so that it will be in better readable form.

JSFiddle

var data = [
{"name":"A11","value":217,"color":"#fad64b"},
{"name":"A12","value":86,"color":"#f15d5d"},
{"name":"A13","value":79,"color":"#f15d5d"},
{"name":"A14","value":82,"color":"#f15d5d"},
{"name":"A15","value":101,"color":"#fad64b"},
{"name":"A16","value":91,"color":"#fad64b"}
];

var width = 500;
var height = 300;
var barHeight = height / 2 - 15;
var formatNumber = d3.format('s');

var color = d3.scale.ordinal()
    .range(['#F15D5D', '#FAD64B']);
var svg = d3.select('#chart').append('svg')
  .attr('width', width)
  .attr('height', height)
  .attr('class', 'radial')
  .append('g')
  .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');

var extent = [0, d3.max(data, function(d) { return d.value; })];

var lastNum = extent[1];
var percentageOne = (lastNum*25)/100;
var percentageTwo = (lastNum*50)/100;
var percentageThree = (lastNum*75)/100;
var tickValues = [percentageOne, percentageTwo, percentageThree, lastNum];

var barScale = d3.scale.linear()
    .domain(extent)
    .range([0, barHeight]);

var keys = data.map(function(d, i) {
  return d.name;
});
var numBars = keys.length;

// X scale
var x = d3.scale.linear()
.domain(extent)
.range([0, -barHeight]);

// X axis
var xAxis = d3.svg.axis()
.scale(x).orient('left')
.tickFormat(formatNumber)
.tickValues(tickValues);

// Inner circles
var circles = svg.selectAll('circle')
.data(tickValues)
.enter().append('circle')
.attr('r', function(d) {
  return barScale(d);
})
.style('fill', 'none')
.style('stroke-width', '0.5px');

// Create arcs
var arc = d3.svg.arc()
.startAngle(function(d, i) {
  var a = (i * 2 * Math.PI) / numBars;
  var b = ((i + 1) * 2 * Math.PI) / numBars;
  var d = (b - a) / 4;
  var x = a + d;
  var y = b - d;
  return x; //(i * 2 * Math.PI) / numBars; 
})
.endAngle(function(d, i) {
  var a = (i * 2 * Math.PI) / numBars;
  var b = ((i + 1) * 2 * Math.PI) / numBars;
  var d = (b - a) / 4;
  var x = a + d;
  var y = b - d;
  return y; //((i + 1) * 2 * Math.PI) / numBars; 
})
.innerRadius(0);

// Render colored arcs
var segments = svg.selectAll('path')
.data(data)
.enter().append('path')
.each(function(d) {
  d.outerRadius = 0;
})
.attr('class', 'bar')
.style('fill', function(d) {
  return d.color;
})
.attr('d', arc);

// Animate segments
segments.transition().ease('elastic').duration(1000).delay(function(d, i) {
  return (25 - i) * 10;
})
  .attrTween('d', function(d, index) {
  var i = d3.interpolate(d.outerRadius, barScale(+d.value));
  return function(t) {
    d.outerRadius = i(t);
    return arc(d, index);
  };
});

// Outer circle
svg.append('circle')
  .attr('r', barHeight)
  .classed('outer', true)
  .style('fill', 'none')
  .style('stroke-width', '.5px');

// Apply x axis
svg.append('g')
  .attr('class', 'x axis')
  .call(xAxis);

// Labels
var labelRadius = barHeight * 1.025;

var labels = svg.selectAll('foo')
.data(data)
.enter()
.append('g')
.classed('labels', true);

labels.append('def')
  .append('path')
  .attr('id', function(d, i) { return 'label-path' + i; })
  .attr('d', function(d) { 
  return 'm0 ' + -(barScale(d.value) + 4) + ' a' + (barScale(d.value) + 4) + ' ' + (barScale(d.value) + 4) + ' 0 1,1 -0.01 0'; 
});

labels.append('def')
  .append('path')
  .attr('id', function(d, i) { return 'label-pathnum' + i; })
  .attr('d', function(d){  
  return 'm0 ' + -(barScale(d.value) + 14) + ' a' + (barScale(d.value) + 14) + ' ' + (barScale(d.value) + 14) + ' 0 1,1 -0.01 0'; 
});

labels.append('text')
  .style('text-anchor', 'middle')
  .style('fill', function(d, i) {
  return d.color;
})
  .append('textPath')
  .attr('xlink:href', function(d, i) { return '#label-path' + i; })
  .attr('startOffset', function(d, i) {
  return i * 100 / numBars + 50 / numBars + '%';
})
  .text(function(d) {
  return d.name.toUpperCase();
});

labels.append('text')
  .style('text-anchor', 'middle')
  .style('fill', function(d, i) {
  return d.color;
})
  .append('textPath')
  .attr('xlink:href', function(d, i) { return '#label-pathnum' + i; })
  .attr('startOffset', function(d, i) {
  return i * 100 / numBars + 50 / numBars + '%';
})
  .text(function(d) {
  return d.value;
});

enter image description here

like image 802
Anshad Vattapoyil Avatar asked Apr 25 '17 10:04

Anshad Vattapoyil


1 Answers

You need to modify the path for the specific elements that need to be flipped. To do this, I start by storing the angle in your data object:

.each(function(d,i) {
  d.outerRadius = 0;
  var angleStart = (i/numBars) * 360;
  var angleEnd = ((i+1)/numBars) * 360;
  d.angle = (angleStart + angleEnd) / 2;
})

Then I tested the angle when creating the paths for the text and I reverse the path for the flipped text case:

var len = barScale(d.value) + 4;
if(d.angle > 91 && d.angle < 269) {
  len += 8; // the flipped text is on the inside of the path so we need to draw it further out
  return 'M -0.01 ' + (-len) + ' A ' + len + ' ' + len + ' 0 1,0 0 ' + (-len);
}
else {
  return 'M 0 ' + (-len) + ' A' + len + ' ' + len + ' 0 1,1 -0.01 ' + (-len); 
}

Then, you need to flip your '% around the path' for the placement of the text along the reversed path:

.attr('startOffset', function(d, i) { 
  if(d.angle > 91 && d.angle < 269)
    return (100 - (i * 100 / numBars + 50 / numBars)) + '%'; 
  else      
    return i * 100 / numBars + 50 / numBars + '%'; 
})

Working fiddle can be found here: https://jsfiddle.net/FrancisMacDougall/mnrqokqL/

With this result:

enter image description here

like image 170
fmacdee Avatar answered Oct 14 '22 14:10

fmacdee