/**
* @class Ext.chart.series.sprite.PieSlice
*
* Pie slice sprite.
*/
Ext.define('Ext.chart.series.sprite.PieSlice', {
extend: 'Ext.draw.sprite.Sector',
mixins: {
markerHolder: 'Ext.chart.MarkerHolder'
},
alias: 'sprite.pieslice',
inheritableStatics: {
def: {
processors: {
/**
* @cfg {Boolean} [doCallout=true]
* 'true' if the pie series uses label callouts.
*/
doCallout: 'bool',
/**
* @cfg {String} [label='']
* Label associated with the Pie sprite.
*/
label: 'string',
// @deprecated Use series.label.orientation config instead.
// @since 5.0.1
rotateLabels: 'bool',
/**
* @cfg {Number} [labelOverflowPadding=10]
* Padding around labels to determine overlap.
* Any negative number allows the labels to overlap.
*/
labelOverflowPadding: 'number',
renderer: 'default'
},
defaults: {
doCallout: true,
rotateLabels: true,
label: '',
labelOverflowPadding: 10,
renderer: null
}
}
},
config: {
/**
* @private
* @cfg {Object} rendererData The object that is passed to the renderer.
*
* For instance when the PieSlice sprite is used in a Gauge chart, the object
* contains the 'store' and 'angleField' properties, and the 'value' as well
* for that one PieSlice that is used to draw the needle of the Gauge.
*/
rendererData: null,
rendererIndex: 0,
series: null
},
setGradientBBox: function(ctx, rect) {
var me = this,
attr = me.attr,
hasGradients = (attr.fillStyle && attr.fillStyle.isGradient) ||
(attr.strokeStyle && attr.strokeStyle.isGradient);
if (hasGradients && !attr.constrainGradients) {
// eslint-disable-next-line vars-on-top, one-var
var midAngle = me.getMidAngle(),
margin = attr.margin,
cx = attr.centerX,
cy = attr.centerY,
r = attr.endRho,
matrix = attr.matrix,
scaleX = matrix.getScaleX(),
scaleY = matrix.getScaleY(),
w = scaleX * r,
h = scaleY * r,
bbox = {
width: w + w,
height: h + h
};
if (margin) {
cx += margin * Math.cos(midAngle);
cy += margin * Math.sin(midAngle);
}
bbox.x = matrix.x(cx, cy) - w;
bbox.y = matrix.y(cx, cy) - h;
ctx.setGradientBBox(bbox);
}
else {
me.callParent([ctx, rect]);
}
},
render: function(surface, ctx, rect) {
var me = this,
attr = me.attr,
itemCfg = {},
changes;
if (attr.renderer) {
itemCfg = {
type: 'sector',
centerX: attr.centerX,
centerY: attr.centerY,
margin: attr.margin,
startAngle: Math.min(attr.startAngle, attr.endAngle),
endAngle: Math.max(attr.startAngle, attr.endAngle),
startRho: Math.min(attr.startRho, attr.endRho),
endRho: Math.max(attr.startRho, attr.endRho)
};
changes = Ext.callback(attr.renderer, null,
[me, itemCfg, me.getRendererData(), me.getRendererIndex()],
0, me.getSeries());
me.setAttributes(changes);
me.useAttributes(ctx, rect);
}
// Draw the sector
me.callParent([surface, ctx, rect]);
// Draw the labels
if (attr.label && me.getMarker('labels')) {
me.placeLabel();
}
},
placeLabel: function() {
var me = this,
attr = me.attr,
attributeId = attr.attributeId,
startAngle = Math.min(attr.startAngle, attr.endAngle),
endAngle = Math.max(attr.startAngle, attr.endAngle),
midAngle = (startAngle + endAngle) * 0.5,
margin = attr.margin,
centerX = attr.centerX,
centerY = attr.centerY,
sinMidAngle = Math.sin(midAngle),
cosMidAngle = Math.cos(midAngle),
startRho = Math.min(attr.startRho, attr.endRho) + margin,
endRho = Math.max(attr.startRho, attr.endRho) + margin,
midRho = (startRho + endRho) * 0.5,
surfaceMatrix = me.surfaceMatrix,
labelCfg = me.labelCfg || (me.labelCfg = {}),
label = me.getMarker('labels'),
labelTpl = label.getTemplate(),
hideLessThan = labelTpl.getHideLessThan(),
calloutLine = labelTpl.getCalloutLine(),
labelBox, x, y, changes, params, calloutLineLength;
if (calloutLine) {
calloutLineLength = calloutLine.length || 40;
}
else {
calloutLineLength = 0;
}
surfaceMatrix.appendMatrix(attr.matrix);
labelCfg.text = attr.label;
x = centerX + cosMidAngle * midRho;
y = centerY + sinMidAngle * midRho;
labelCfg.x = surfaceMatrix.x(x, y);
labelCfg.y = surfaceMatrix.y(x, y);
x = centerX + cosMidAngle * endRho;
y = centerY + sinMidAngle * endRho;
labelCfg.calloutStartX = surfaceMatrix.x(x, y);
labelCfg.calloutStartY = surfaceMatrix.y(x, y);
x = centerX + cosMidAngle * (endRho + calloutLineLength);
y = centerY + sinMidAngle * (endRho + calloutLineLength);
labelCfg.calloutPlaceX = surfaceMatrix.x(x, y);
labelCfg.calloutPlaceY = surfaceMatrix.y(x, y);
if (!attr.rotateLabels) {
labelCfg.rotationRads = 0;
//<debug>
Ext.log.warn("'series.style.rotateLabels' config is deprecated. " +
"Use 'series.label.orientation' config instead.");
//</debug>
}
else {
switch (labelTpl.attr.orientation) {
case 'horizontal':
labelCfg.rotationRads = midAngle + Math.atan2(
surfaceMatrix.y(1, 0) - surfaceMatrix.y(0, 0),
surfaceMatrix.x(1, 0) - surfaceMatrix.x(0, 0)
) + Math.PI / 2;
break;
case 'vertical':
labelCfg.rotationRads = midAngle + Math.atan2(
surfaceMatrix.y(1, 0) - surfaceMatrix.y(0, 0),
surfaceMatrix.x(1, 0) - surfaceMatrix.x(0, 0)
);
break;
}
}
labelCfg.calloutColor = (calloutLine && calloutLine.color) || me.attr.fillStyle;
if (calloutLine) {
if (calloutLine.width) {
labelCfg.calloutWidth = calloutLine.width;
}
}
else {
labelCfg.calloutColor = 'none';
}
labelCfg.globalAlpha = attr.globalAlpha * attr.fillOpacity;
// If a slice is empty, don't display the label.
// This behavior can be overridden by a renderer.
if (labelTpl.display !== 'none') {
// eslint-disable-next-line eqeqeq
labelCfg.hidden = (attr.startAngle == attr.endAngle);
}
if (labelTpl.attr.renderer) {
// Note: the labels are 'put' by the Ext.chart.series.Pie.updateLabelData, so we can
// be sure the label sprite instances will exist and can be accessed from the label
// renderer on first render. For example, with 'bar' series this isn't the case,
// so we make a check and create a label instance if necessary.
params = [me.attr.label, label, labelCfg, me.getRendererData(), me.getRendererIndex()];
changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
if (typeof changes === 'string') {
labelCfg.text = changes;
}
else {
Ext.apply(labelCfg, changes);
}
}
me.putMarker('labels', labelCfg, attributeId);
labelBox = me.getMarkerBBox('labels', attributeId, true);
if (labelBox) {
if (attr.doCallout &&
((endAngle - startAngle) * endRho > hideLessThan || attr.highlighted)) {
if (labelTpl.attr.display === 'outside') {
me.putMarker('labels', {
callout: 1
}, attributeId);
}
else if (labelTpl.attr.display === 'inside') {
me.putMarker('labels', {
callout: 0
}, attributeId);
}
else {
me.putMarker('labels', {
callout: 1 - me.sliceContainsLabel(attr, labelBox)
}, attributeId);
}
}
else {
me.putMarker('labels', {
globalAlpha: me.sliceContainsLabel(attr, labelBox)
}, attributeId);
}
}
},
sliceContainsLabel: function(attr, bbox) {
var padding = attr.labelOverflowPadding,
middle = (attr.endRho + attr.startRho) / 2,
outer = middle + (bbox.width + padding) / 2,
inner = middle - (bbox.width + padding) / 2,
sliceAngle, l1, l2, l3;
if (padding < 0) {
return 1;
}
if (bbox.width + padding * 2 > (attr.endRho - attr.startRho)) {
return 0;
}
l1 = Math.sqrt(attr.endRho * attr.endRho - outer * outer);
l2 = Math.sqrt(attr.endRho * attr.endRho - inner * inner);
sliceAngle = Math.abs(attr.endAngle - attr.startAngle);
l3 = (sliceAngle > Math.PI / 2 ? inner : Math.abs(Math.tan(sliceAngle / 2)) * inner);
if (bbox.height + padding * 2 > Math.min(l1, l2, l3) * 2) {
return 0;
}
return 1;
}
});