Little Child Little Child - 5 months ago 36
Node.js Question

d3js with node, how do I add a lines to pie chart?

d3js newbie here. I am using d3js to render an svg on the server. After going over some tutorials I was able to come up with this:

var fs = require('fs');
var d3 = require('d3');
var jsdom = require('jsdom');
var _ = require("underscore");
var chartWidth = 500, chartHeight = 500;

var arc = d3.svg.arc()
.outerRadius(chartWidth/2 - 10)
.innerRadius(0);

var colours = ['#98abc5','#8a89a6','#7b6888','#6b486b','#a05d56',"#d0743c", "#ff8c00"];

var pieData = [
{
"category" : "Category A",
"amount" : 300
},
{
"category" : "Category B",
"amount" : 300
}
];

makePie( pieData );

function makePie( pieData, outputLocation ){
var amount = _.pluck( pieData, "amount" );
var category = _.pluck( pieData, "category" );

jsdom.env({
html:'',
features:{ QuerySelector:true }, //you need query selector for D3 to work
done:function(errors, window){
window.d3 = d3.select(window.document); //get d3 into the dom

//do yr normal d3 stuff
var svg = window.d3.select('body')
.append('div')
.attr('class','container') //make a container div to ease the saving process
.append('svg')
.attr({
xmlns:'http://www.w3.org/2000/svg',
width:chartWidth,
height:chartHeight
})
.append('g')
.attr('transform','translate(' + chartWidth/2 + ',' + chartWidth/2 + ')');

svg.selectAll('.arc')
.data( d3.layout.pie()(amount) )
.enter()
.append('path')
.attr({
'class':'arc',
'd':arc,
'fill':function(d,i){
return colours[i];
},
'stroke':'#fff'
});
//write out the children of the container div
fs.writeFileSync("test.svg", window.d3.select('.container').html()) //using sync to keep the code simple

}
});
}


Although this renders the SVG chart, I do not know how to add lines to the chart which will show which slice belongs to which category.

How do I do that?

Answer

Edit/Update (note: written by 'thudfactor') - this is how to add the labels/lines

var data = [{
    label: 'Star Wars',
    instances: 207
}, {
    label: 'Lost In Space',
    instances: 3
}, {
    label: 'the Boston Pops',
    instances: 20
}, {
    label: 'Indiana Jones',
    instances: 150
}, {
    label: 'Harry Potter',
    instances: 75
}, {
    label: 'Jaws',
    instances: 5
}, {
    label: 'Lincoln',
    instances: 1
}];

svg = d3.select("svg");
canvas = d3.select("#canvas");
art = d3.select("#art");
labels = d3.select("#labels");

// Create the pie layout function.
// This function will add convenience
// data to our existing data, like 
// the start angle and end angle
// for each data element.
jhw_pie = d3.layout.pie()
jhw_pie.value(function (d, i) {
    // Tells the layout function what
    // property of our data object to
    // use as the value.
    return d.instances;
});

// Store our chart dimensions
cDim = {
    height: 500,
    width: 500,
    innerRadius: 50,
    outerRadius: 150,
    labelRadius: 175
}

// Set the size of our SVG element
svg.attr({
    height: cDim.height,
    width: cDim.width
});

// This translate property moves the origin of the group's coordinate
// space to the center of the SVG element, saving us translating every
// coordinate individually. 
canvas.attr("transform", "translate(" + (cDim.width / 2) + "," + (cDim.width / 2) + ")");

pied_data = jhw_pie(data);

// The pied_arc function we make here will calculate the path
// information for each wedge based on the data set. This is 
// used in the "d" attribute.
pied_arc = d3.svg.arc()
    .innerRadius(50)
    .outerRadius(150);

// This is an ordinal scale that returns 10 predefined colors.
// It is part of d3 core.
pied_colors = d3.scale.category10();

// Let's start drawing the arcs.
enteringArcs = art.selectAll(".wedge").data(pied_data).enter();

enteringArcs.append("path")
    .attr("class", "wedge")
    .attr("d", pied_arc)
    .style("fill", function (d, i) {
    return pied_colors(i);
});

// Now we'll draw our label lines, etc.
enteringLabels = labels.selectAll(".label").data(pied_data).enter();
labelGroups = enteringLabels.append("g").attr("class", "label");
labelGroups.append("circle").attr({
    x: 0,
    y: 0,
    r: 2,
    fill: "#000",
    transform: function (d, i) {
        centroid = pied_arc.centroid(d);
        return "translate(" + pied_arc.centroid(d) + ")";
    },
        'class': "label-circle"
});

// "When am I ever going to use this?" I said in 
// 10th grade trig.
textLines = labelGroups.append("line").attr({
    x1: function (d, i) {
        return pied_arc.centroid(d)[0];
    },
    y1: function (d, i) {
        return pied_arc.centroid(d)[1];
    },
    x2: function (d, i) {
        centroid = pied_arc.centroid(d);
        midAngle = Math.atan2(centroid[1], centroid[0]);
        x = Math.cos(midAngle) * cDim.labelRadius;
        return x;
    },
    y2: function (d, i) {
        centroid = pied_arc.centroid(d);
        midAngle = Math.atan2(centroid[1], centroid[0]);
        y = Math.sin(midAngle) * cDim.labelRadius;
        return y;
    },
        'class': "label-line"
});

textLabels = labelGroups.append("text").attr({
    x: function (d, i) {
        centroid = pied_arc.centroid(d);
        midAngle = Math.atan2(centroid[1], centroid[0]);
        x = Math.cos(midAngle) * cDim.labelRadius;
        sign = (x > 0) ? 1 : -1
        labelX = x + (5 * sign)
        return labelX;
    },
    y: function (d, i) {
        centroid = pied_arc.centroid(d);
        midAngle = Math.atan2(centroid[1], centroid[0]);
        y = Math.sin(midAngle) * cDim.labelRadius;
        return y;
    },
        'text-anchor': function (d, i) {
        centroid = pied_arc.centroid(d);
        midAngle = Math.atan2(centroid[1], centroid[0]);
        x = Math.cos(midAngle) * cDim.labelRadius;
        return (x > 0) ? "start" : "end";
    },
        'class': 'label-text'
}).text(function (d) {
    return d.data.label
});

alpha = 0.5;
spacing = 12;

function relax() {
    again = false;
    textLabels.each(function (d, i) {
        a = this;
        da = d3.select(a);
        y1 = da.attr("y");
        textLabels.each(function (d, j) {
            b = this;
            // a & b are the same element and don't collide.
            if (a == b) return;
            db = d3.select(b);
            // a & b are on opposite sides of the chart and
            // don't collide
            if (da.attr("text-anchor") != db.attr("text-anchor")) return;
            // Now let's calculate the distance between
            // these elements. 
            y2 = db.attr("y");
            deltaY = y1 - y2;
            
            // Our spacing is greater than our specified spacing,
            // so they don't collide.
            if (Math.abs(deltaY) > spacing) return;
            
            // If the labels collide, we'll push each 
            // of the two labels up and down a little bit.
            again = true;
            sign = deltaY > 0 ? 1 : -1;
            adjust = sign * alpha;
            da.attr("y",+y1 + adjust);
            db.attr("y",+y2 - adjust);
        });
    });
    // Adjust our line leaders here
    // so that they follow the labels. 
    if(again) {
        labelElements = textLabels[0];
        textLines.attr("y2",function(d,i) {
            labelForLine = d3.select(labelElements[i]);
            return labelForLine.attr("y");
        });
        setTimeout(relax,20)
    }
}

relax();
.label-text {
    alignment-baseline: middle;
    font-size: 12px;
    font-family: arial,helvetica,"sans-serif";
    fill: #393939;
}
.label-line {
    stroke-width: 1;
    stroke: #393939;
}
.label-circle {
    fill: #393939;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg>
    <g id="canvas">
        <g id="art" />
        <g id="labels" /></g>
</svg>

Do you mean something like this?

var w = 400;
var h = 400;
var r = h / 2;
var color = d3.scale.category20c();

var data = [{
  "label": "Category A",
  "value": 20
}, {
  "label": "Category B",
  "value": 50
}, {
  "label": "Category C",
  "value": 30
}];


var vis = d3.select('#pie').append("svg:svg").data([data]).attr("width", w).attr("height", h).append("svg:g").attr("transform", "translate(" + r + "," + r + ")");
var pie = d3.layout.pie().value(function(d) {
  return d.value;
});
var arc = d3.svg.arc().outerRadius(r);
var arcs = vis.selectAll("g.slice").data(pie).enter().append("svg:g").attr("class", "slice");
arcs.append("svg:path")
  .attr("fill", function(d, i) {
    return color(i);
  })
  .attr("d", function(d) {
    return arc(d);
  });

// add the text
arcs.append("svg:text").attr("transform", function(d) {
  d.innerRadius = 0;
  d.outerRadius = r;
  return "translate(" + arc.centroid(d) + ")";
}).attr("text-anchor", "middle").text(function(d, i) {
  return data[i].label;
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="pie">

</div>

Comments