/**
* @class Ext.chart.series.sprite.Bar
* @extends Ext.chart.series.sprite.StackedCartesian
*
* Draws a sprite used in the bar series.
*/
Ext.define('Ext.chart.series.sprite.Bar', {
alias: 'sprite.barSeries',
extend: 'Ext.chart.series.sprite.StackedCartesian',
inheritableStatics: {
def: {
processors: {
/**
* @cfg {Number} [minBarWidth=2] The minimum bar width.
*/
minBarWidth: 'number',
/**
* @cfg {Number} [maxBarWidth=100] The maximum bar width.
*/
maxBarWidth: 'number',
/**
* @cfg {Number} [minGapWidth=5] The minimum gap between bars.
*/
minGapWidth: 'number',
/**
* @cfg {Number} [radius=0] The degree of rounding for rounded bars.
*/
radius: 'number',
/**
* @cfg {Number} [inGroupGapWidth=3] The gap between grouped bars.
*/
inGroupGapWidth: 'number'
},
defaults: {
minBarWidth: 2,
maxBarWidth: 100,
minGapWidth: 5,
inGroupGapWidth: 3,
radius: 0
}
}
},
drawLabel: function(text, dataX, dataStartY, dataY, labelId, sClipRect) {
var me = this,
attr = me.attr,
label = me.getMarker('labels'),
labelTpl = label.getTemplate(),
labelCfg = me.labelCfg || (me.labelCfg = {}),
surfaceMatrix = me.surfaceMatrix,
labelOverflowPadding = attr.labelOverflowPadding,
labelDisplay = labelTpl.attr.display,
labelOrientation = labelTpl.attr.orientation,
isVerticalText = (labelOrientation === 'horizontal' && attr.flipXY) ||
(labelOrientation === 'vertical' && !attr.flipXY) ||
!labelOrientation,
calloutLine = labelTpl.getCalloutLine(),
labelY, halfText, labelBBox, calloutLineLength,
changes, hasPendingChanges, params, paddingOffset,
calloutInfo;
// The coordinates below (data point converted to surface coordinates)
// are just for the renderer to give it a notion of where the label will be positioned.
// The actual position of the label will be different
// (unless the renderer returns x/y coordinates in the changes object)
// and depend on several things including the size of the text,
// which has to be measured after the renderer call,
// since text can be modified by the renderer.
labelCfg.x = surfaceMatrix.x(dataX, dataY);
labelCfg.y = surfaceMatrix.y(dataX, dataY);
if (calloutLine) {
calloutLineLength = calloutLine.length;
}
else {
calloutLineLength = 0;
}
// Set defaults
if (!attr.flipXY) {
labelCfg.rotationRads = -Math.PI * 0.5;
}
else {
labelCfg.rotationRads = 0;
}
labelCfg.calloutVertical = !attr.flipXY;
// Check if we have a specific orientation specified, if so, set
// the appropriate values.
switch (labelOrientation) {
case 'horizontal':
labelCfg.rotationRads = 0;
labelCfg.calloutVertical = false;
break;
case 'vertical':
labelCfg.rotationRads = -Math.PI * 0.5;
labelCfg.calloutVertical = true;
break;
}
labelCfg.text = text;
if (labelTpl.attr.renderer) {
// The label instance won't exist on first render before the renderer is called,
// it's only created later by `me.putMarker` after the renderer call. To make
// sure the renderer always can access the label instance, we make this check here.
if (!label.get(labelId)) {
label.putMarkerFor('labels', {}, labelId);
}
params = [text, label, labelCfg, {
store: me.getStore(),
field: this.getField()
}, labelId];
changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
if (typeof changes === 'string') {
labelCfg.text = changes;
}
else if (typeof changes === 'object') {
if ('text' in changes) {
labelCfg.text = changes.text;
}
hasPendingChanges = true;
}
}
labelBBox = me.getMarkerBBox('labels', labelId, true);
if (!labelBBox) {
me.putMarker('labels', labelCfg, labelId);
labelBBox = me.getMarkerBBox('labels', labelId, true);
}
if (calloutLineLength > 0) {
halfText = calloutLineLength;
}
else if (calloutLineLength === 0) {
halfText = (isVerticalText ? labelBBox.width : labelBBox.height) / 2;
}
else {
halfText =
(isVerticalText ? labelBBox.width : labelBBox.height) / 2 + labelOverflowPadding;
}
paddingOffset = labelOverflowPadding * 2;
if (dataStartY > dataY) {
halfText = -halfText;
paddingOffset = -paddingOffset;
}
if (isVerticalText) {
labelY = (labelDisplay === 'insideStart')
? dataStartY + halfText
: dataY - halfText;
}
else {
labelY = (labelDisplay === 'insideStart')
? dataStartY + paddingOffset
: dataY - paddingOffset;
}
labelCfg.x = surfaceMatrix.x(dataX, labelY);
labelCfg.y = surfaceMatrix.y(dataX, labelY);
calloutInfo = this.getCalloutYPos(labelDisplay, dataStartY, dataY, halfText, sClipRect);
labelCfg.calloutStartX = surfaceMatrix.x(dataX, calloutInfo.start);
labelCfg.calloutStartY = surfaceMatrix.y(dataX, calloutInfo.start);
labelCfg.calloutPlaceX = surfaceMatrix.x(dataX, calloutInfo.place);
labelCfg.calloutPlaceY = surfaceMatrix.y(dataX, calloutInfo.place);
labelCfg.calloutColor = (calloutLine && calloutLine.color) || me.attr.fillStyle;
if (calloutLine) {
if (calloutLine.width) {
labelCfg.calloutWidth = calloutLine.width;
}
}
else {
labelCfg.calloutColor = 'none';
}
// Cast to a number
labelCfg.callout = +(Math.abs(dataY - dataStartY) <= Math.abs(halfText * 2) ||
labelDisplay === 'outside');
if (hasPendingChanges) {
Ext.apply(labelCfg, changes);
}
me.putMarker('labels', labelCfg, labelId);
},
// shared object to prevent allocating a new one all the time
callOutObj: {
start: 0,
place: 0
},
getCalloutYPos: function(display, startY, y, halfText, rect) {
var me = this,
o = me.callOutObj,
inside = display === 'insideStart',
start = inside ? startY : y,
place = inside ? startY - halfText : y + halfText,
textSize = halfText * 2,
placeText = place + textSize;
if (!me.fromSelf && !inside && (placeText <= rect[1] || placeText >= rect[3])) {
// On the unlikely chance this occurs, prevent recursive calls
me.fromSelf = true;
o = me.getCalloutYPos('insideStart', startY, y, halfText, rect);
delete me.fromSelf;
}
else {
o.start = start;
o.place = place;
}
return o;
},
drawBar: function(ctx, surface, rect, left, top, right, bottom, index) {
var me = this,
itemCfg = {},
renderer = me.attr.renderer,
changes;
itemCfg.x = left;
itemCfg.y = top;
itemCfg.width = right - left;
itemCfg.height = bottom - top;
itemCfg.radius = me.attr.radius;
if (renderer) {
changes = Ext.callback(renderer, null, [me, itemCfg, { store: me.getStore() }, index],
0, me.getSeries());
Ext.apply(itemCfg, changes);
}
me.putMarker('items', itemCfg, index, !renderer);
},
renderClipped: function(surface, ctx, dataClipRect, surfaceClipRect) {
if (this.cleanRedraw) {
return;
}
// eslint-disable-next-line vars-on-top
var me = this,
attr = me.attr,
dataX = attr.dataX,
dataY = attr.dataY,
dataText = attr.labels,
dataStartY = attr.dataStartY,
groupCount = attr.groupCount,
groupOffset = attr.groupOffset - (groupCount - 1) * 0.5,
inGroupGapWidth = attr.inGroupGapWidth,
lineWidth = ctx.lineWidth,
matrix = attr.matrix,
xx = matrix.elements[0],
yy = matrix.elements[3],
dx = matrix.elements[4],
dy = surface.roundPixel(matrix.elements[5]) - 1,
maxBarWidth = Math.abs(xx) - attr.minGapWidth,
minBarWidth = (Math.min(maxBarWidth, attr.maxBarWidth) -
inGroupGapWidth * (groupCount - 1)) / groupCount,
barWidth = surface.roundPixel(Math.max(attr.minBarWidth, minBarWidth)),
surfaceMatrix = me.surfaceMatrix,
left, right, bottom, top, i, center,
halfLineWidth = 0.5 * attr.lineWidth,
// Finding min/max so that bars render properly in both LTR and RTL modes.
min = Math.min(dataClipRect[0], dataClipRect[2]),
max = Math.max(dataClipRect[0], dataClipRect[2]),
// binarySearch will get the index of max and min in dataX
start = Math.max(0, me.binarySearch(min)),
end = Math.min(dataX.length - 1, me.binarySearch(max)),
isDrawLabels = dataText && me.getMarker('labels'),
yLow, yHi;
// The scaling (xx) and translation (dx) here will already be such that the midpoints
// of the first and last bars are not at the surface edges (which would mean that
// bars are half-clipped), but padded, so that those bars are fully visible
// (assuming no pan/zoom).
for (i = start; i <= end; i++) {
yLow = dataStartY ? dataStartY[i] : 0;
yHi = dataY[i];
center = dataX[i] * xx + dx + groupOffset * (barWidth + inGroupGapWidth);
left = surface.roundPixel(center - barWidth / 2) + halfLineWidth;
top = surface.roundPixel(yHi * yy + dy + lineWidth);
right = surface.roundPixel(center + barWidth / 2) - halfLineWidth;
bottom = surface.roundPixel(yLow * yy + dy + lineWidth);
me.drawBar(ctx, surface, dataClipRect, left, top - halfLineWidth, right,
bottom - halfLineWidth, i);
// We want 0 values to be passed to the renderer
if (isDrawLabels && dataText[i] != null) {
me.drawLabel(dataText[i], center, bottom, top, i, surfaceClipRect);
}
me.putMarker('markers', {
translationX: surfaceMatrix.x(center, top),
translationY: surfaceMatrix.y(center, top)
}, i, true);
}
}
});