in src/plugins/vis_type_vislib/public/vislib/visualizations/pie_chart.js [134:356]
addPath(width, height, svg, slices) {
const self = this;
const marginFactor = 0.95;
const isDonut = self._attr.isDonut;
const radius = (Math.min(width, height) / 2) * marginFactor;
const color = self.handler.data.getPieColorFunc();
const tooltip = self.tooltip;
const isTooltip = self._attr.addTooltip;
const arcs = svg.append('g').attr('class', 'arcs');
const labels = svg.append('g').attr('class', 'labels');
const showLabels = self._attr.labels.show;
const showValues = self._attr.labels.values;
const truncateLabelLength = self._attr.labels.truncate;
const showOnlyOnLastLevel = self._attr.labels.last_level;
const partition = d3.layout
.partition()
.sort(null)
.value(function (d) {
return d.percentOfParent * 100;
});
const x = d3.scale.linear().range([0, 2 * Math.PI]);
const y = d3.scale.sqrt().range([0, showLabels ? radius * 0.7 : radius]);
const startAngle = function (d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x)));
};
const endAngle = function (d) {
if (d.dx < 1e-8) return x(d.x);
return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
};
const arc = d3.svg
.arc()
.startAngle(startAngle)
.endAngle(endAngle)
.innerRadius(function (d) {
// option for a single layer, i.e pie chart
if (d.depth === 1 && !isDonut) {
// return no inner radius
return 0;
}
return Math.max(0, y(d.y));
})
.outerRadius(function (d) {
return Math.max(0, y(d.y + d.dy));
});
const outerArc = d3.svg
.arc()
.startAngle(startAngle)
.endAngle(endAngle)
.innerRadius(radius * 0.8)
.outerRadius(radius * 0.8);
let maxDepth = 0;
const path = arcs
.datum(slices)
.selectAll('path')
.data(partition.nodes)
.enter()
.append('path')
.attr('d', arc)
.attr('class', function (d) {
if (d.depth === 0) {
return;
}
if (d.depth > maxDepth) maxDepth = d.depth;
return 'slice';
})
.attr('data-test-subj', function (d) {
if (d.name) {
return `pieSlice-${d.name.split(' ').join('-')}`;
}
})
.call(self._addIdentifier, 'name')
.style('fill', function (d) {
if (d.depth === 0) {
return 'none';
}
return color(d.name);
});
// add labels
if (showLabels) {
const labelGroups = labels.datum(slices).selectAll('.label').data(partition.nodes);
// create an empty quadtree to hold label positions
const svgParentNode = svg.node().parentNode.parentNode;
const svgBBox = {
width: svgParentNode.clientWidth,
height: svgParentNode.clientHeight,
};
const labelLayout = d3.geom
.quadtree()
.extent([
[-svgBBox.width, -svgBBox.height],
[svgBBox.width, svgBBox.height],
])
.x(function (d) {
return d.position.x;
})
.y(function (d) {
return d.position.y;
})([]);
labelGroups
.enter()
.append('g')
.attr('class', 'label')
.append('text')
.text(function (d) {
if (d.depth === 0) {
d3.select(this.parentNode).remove();
return;
}
if (showValues) {
const value = numeral(d.value / 100).format('0.[00]%');
return `${d.name} (${value})`;
}
return d.name;
})
.text(function () {
return truncateLabel(this, truncateLabelLength);
})
.attr('text-anchor', function (d) {
const midAngle = startAngle(d) + (endAngle(d) - startAngle(d)) / 2;
return midAngle < Math.PI ? 'start' : 'end';
})
.attr('class', 'label-text')
.each(function resolveConflicts(d) {
if (d.depth === 0) return;
const parentNode = this.parentNode;
if (showOnlyOnLastLevel && maxDepth !== d.depth) {
d3.select(parentNode).remove();
return;
}
const bbox = this.getBBox();
const pos = outerArc.centroid(d);
const midAngle = startAngle(d) + (endAngle(d) - startAngle(d)) / 2;
pos[1] += 4;
pos[0] = (0.7 + d.depth / 10) * radius * (midAngle < Math.PI ? 1 : -1);
d.position = {
x: pos[0],
y: pos[1],
left: midAngle < Math.PI ? pos[0] : pos[0] - bbox.width,
right: midAngle > Math.PI ? pos[0] + bbox.width : pos[0],
bottom: pos[1] + 5,
top: pos[1] - bbox.height - 5,
};
const conflicts = [];
labelLayout.visit(function (node) {
if (!node.point) return;
if (conflicts.length) return true;
const point = node.point.position;
const current = d.position;
if (point) {
const horizontalConflict =
(point.left < 0 && current.left < 0) || (point.left > 0 && current.left > 0);
const verticalConflict =
(point.top >= current.top && point.top <= current.bottom) ||
(point.top <= current.top && point.bottom >= current.top);
if (horizontalConflict && verticalConflict) {
point.point = node.point;
conflicts.push(point);
}
return true;
}
});
if (conflicts.length) {
d3.select(parentNode).remove();
return;
}
labelLayout.add(d);
})
.attr('x', function (d) {
if (d.depth === 0 || !d.position) {
return;
}
return d.position.x;
})
.attr('y', function (d) {
if (d.depth === 0 || !d.position) {
return;
}
return d.position.y;
});
labelGroups
.append('polyline')
.attr('points', function (d) {
if (d.depth === 0 || !d.position) {
return;
}
const pos1 = outerArc.centroid(d);
const x2 = d.position.x > 0 ? d.position.x - 10 : d.position.x + 10;
const pos2 = [x2, d.position.y - 4];
pos1[1] = pos2[1];
return [arc.centroid(d), pos1, pos2];
})
.attr('class', 'label-line');
}
if (isTooltip) {
path.call(tooltip.render());
}
return path;
}