/**
* @private
* @class Ext.chart.axis.sprite.Axis
* @extends Ext.draw.sprite.Sprite
*
* The axis sprite. Currently all types of the axis will be rendered with this sprite.
*/
Ext.define('Ext.chart.axis.sprite.Axis', {
extend: 'Ext.draw.sprite.Sprite',
alias: 'sprite.axis',
type: 'axis',
mixins: {
markerHolder: 'Ext.chart.MarkerHolder'
},
requires: ['Ext.draw.sprite.Text'],
inheritableStatics: {
def: {
processors: {
/**
* @cfg {Boolean} grid 'true' if the axis has a grid.
*/
grid: 'bool',
/**
* @cfg {Boolean} axisLine 'true' if the main line of the axis is drawn.
*/
axisLine: 'bool',
/**
* @cfg {Boolean} minorTicks 'true' if the axis has sub ticks.
*/
minorTicks: 'bool',
/**
* @cfg {Number} minorTickSize The length of the minor ticks.
*/
minorTickSize: 'number',
/**
* @cfg {Boolean} majorTicks 'true' if the axis has major ticks.
*/
majorTicks: 'bool',
/**
* @cfg {Number} majorTickSize The length of the major ticks.
*/
majorTickSize: 'number',
/**
* @cfg {Number} length The total length of the axis.
*/
length: 'number',
/**
* @private
* @cfg {Number} startGap Axis start determined by the chart inset padding.
*/
startGap: 'number',
/**
* @private
* @cfg {Number} endGap Axis end determined by the chart inset padding.
*/
endGap: 'number',
/**
* @cfg {Number} dataMin The minimum value of the axis data.
*/
dataMin: 'number',
/**
* @cfg {Number} dataMax The maximum value of the axis data.
*/
dataMax: 'number',
/**
* @cfg {Number} visibleMin The minimum value that is displayed.
*/
visibleMin: 'number',
/**
* @cfg {Number} visibleMax The maximum value that is displayed.
*/
visibleMax: 'number',
/**
* @cfg {String} position The position of the axis on the chart.
*/
position: 'enums(left,right,top,bottom,angular,radial,gauge)',
/**
* @cfg {Number} minStepSize The minimum step size between ticks.
*/
minStepSize: 'number',
/**
* @private
* @cfg {Number} estStepSize The estimated step size between ticks.
*/
estStepSize: 'number',
/**
* @private
* Unused.
*/
titleOffset: 'number',
/**
* @cfg {Number} [textPadding=0]
* The padding around axis labels to determine collision.
* The default is 0 for all axes except horizontal axes of cartesian charts,
* where the default is 5 to prevent axis labels from blending one into another.
* This default is defined in the {@link Ext.chart.theme.Base#axis axis} config
* of the {@link Ext.chart.theme.Base Base} theme.
* You may want to change this default to a smaller number or 0, if you have
* horizontal axis labels rotated, which allows for more text to fit in.
*/
textPadding: 'number',
/**
* @cfg {Number} min The minimum value of the axis.
* `min` and {@link #max} attributes represent the effective range of the axis
* after segmentation, layout, and range reconciliation between axes.
*/
min: 'number',
/**
* @cfg {Number} max The maximum value of the axis.
* {@link #min} and `max` attributes represent the effective range of the axis
* after segmentation, layout, and range reconciliation between axes.
*/
max: 'number',
/**
* @cfg {Number} centerX The central point of the angular axis on the x-axis.
*/
centerX: 'number',
/**
* @cfg {Number} centerY The central point of the angular axis on the y-axis.
*/
centerY: 'number',
/**
* @private
* @cfg {Number} radius
* Unused.
*/
radius: 'number',
/**
* @private
*/
totalAngle: 'number',
/**
* @cfg {Number} baseRotation The starting rotation of the angular axis.
*/
baseRotation: 'number',
/**
* @private
* Unused.
*/
data: 'default',
/**
* @cfg {Boolean} 'true' if the estimated step size is adjusted by text size.
*/
enlargeEstStepSizeByText: 'bool'
},
defaults: {
grid: false,
axisLine: true,
minorTicks: false,
minorTickSize: 3,
majorTicks: true,
majorTickSize: 5,
length: 0,
startGap: 0,
endGap: 0,
visibleMin: 0,
visibleMax: 1,
dataMin: 0,
dataMax: 1,
position: '',
minStepSize: 0,
estStepSize: 20,
min: 0,
max: 1,
centerX: 0,
centerY: 0,
radius: 1,
baseRotation: 0,
data: null,
titleOffset: 0,
textPadding: 0,
scalingCenterY: 0,
scalingCenterX: 0,
// Override default
strokeStyle: 'black',
enlargeEstStepSizeByText: false
},
triggers: {
minorTickSize: 'bbox',
majorTickSize: 'bbox',
position: 'bbox,layout',
axisLine: 'bbox,layout',
minorTicks: 'layout',
min: 'layout',
max: 'layout',
length: 'layout',
minStepSize: 'layout',
estStepSize: 'layout',
data: 'layout',
dataMin: 'layout',
dataMax: 'layout',
visibleMin: 'layout',
visibleMax: 'layout',
enlargeEstStepSizeByText: 'layout'
},
updaters: {
layout: 'layoutUpdater'
}
}
},
config: {
/**
* @cfg {Object} label
*
* The label configuration object for the Axis. This object may include style attributes
* like `spacing`, `padding`, `font` that receives a string or number and
* returns a new string with the modified values.
*/
label: null,
/**
* @cfg {Number} labelOffset
* The distance between the label and the edge of a major tick.
* Only applicable for 'gauge' and 'angular' axes.
*/
labelOffset: 10,
/**
* @cfg {Object|Ext.chart.axis.layout.Layout} layout The layout configuration used by
* the axis.
*/
layout: null,
/**
* @cfg {Object|Ext.chart.axis.segmenter.Segmenter} segmenter The method of segmenter
* used by the axis.
*/
segmenter: null,
/**
* @cfg {Function} renderer Allows direct customisation of rendered axis sprites.
*/
renderer: null,
/**
* @private
* @cfg {Object} layoutContext Stores the context after calculating layout.
*/
layoutContext: null,
/**
* @cfg {Ext.chart.axis.Axis} axis The axis represented by this sprite.
*/
axis: null
},
thickness: 0,
stepSize: 0,
getBBox: function() {
return null;
},
defaultRenderer: function(v) {
// 'this' pointer in this case is a layoutContext
return this.segmenter.renderer(v, this);
},
layoutUpdater: function() {
var me = this,
chart = me.getAxis().getChart();
if (chart.isInitializing) {
return;
}
// eslint-disable-next-line vars-on-top, one-var
var attr = me.attr,
layout = me.getLayout(),
isRtl = chart.getInherited().rtl,
dataRange = attr.dataMax - attr.dataMin,
min = attr.dataMin + dataRange * attr.visibleMin,
max = attr.dataMin + dataRange * attr.visibleMax,
range = max - min,
position = attr.position,
context = {
attr: attr,
segmenter: me.getSegmenter(),
renderer: me.defaultRenderer
};
if (position === 'left' || position === 'right') {
attr.translationX = 0;
attr.translationY = max * attr.length / range;
attr.scalingX = 1;
attr.scalingY = -attr.length / range;
attr.scalingCenterY = 0;
attr.scalingCenterX = 0;
me.applyTransformations(true);
}
else if (position === 'top' || position === 'bottom') {
if (isRtl) {
attr.translationX = attr.length + min * attr.length / range + 1;
}
else {
attr.translationX = -min * attr.length / range;
}
attr.translationY = 0;
attr.scalingX = (isRtl ? -1 : 1) * attr.length / range;
attr.scalingY = 1;
attr.scalingCenterY = 0;
attr.scalingCenterX = 0;
me.applyTransformations(true);
}
if (layout) {
layout.calculateLayout(context);
me.setLayoutContext(context);
}
},
iterate: function(snaps, fn) {
var i, position,
id, axis, floatingAxes, floatingValues,
some = Ext.Array.some,
abs = Math.abs,
threshold, isTickVisible;
if (snaps.getLabel) { // Discrete layout.
if (snaps.min < snaps.from) {
fn.call(this, snaps.min, snaps.getLabel(snaps.min), -1, snaps);
}
for (i = 0; i <= snaps.steps; i++) {
fn.call(this, snaps.get(i), snaps.getLabel(i), i, snaps);
}
if (snaps.max > snaps.to) {
fn.call(this, snaps.max, snaps.getLabel(snaps.max), snaps.steps + 1, snaps);
}
}
else {
axis = this.getAxis();
floatingAxes = axis.floatingAxes;
floatingValues = [];
threshold = (snaps.to - snaps.from) / (snaps.steps + 1);
if (axis.getFloating()) {
for (id in floatingAxes) {
floatingValues.push(floatingAxes[id]);
}
}
// Don't render ticks in axes intersection points.
isTickVisible = function(position) {
return !floatingValues.length || some(floatingValues, function(value) {
return abs(value - position) > threshold;
});
};
if (snaps.min < snaps.from && isTickVisible(snaps.min)) {
fn.call(this, snaps.min, snaps.min, -1, snaps);
}
for (i = 0; i <= snaps.steps; i++) {
position = snaps.get(i);
if (isTickVisible(position)) {
fn.call(this, position, position, i, snaps);
}
}
if (snaps.max > snaps.to && isTickVisible(snaps.max)) {
fn.call(this, snaps.max, snaps.max, snaps.steps + 1, snaps);
}
}
},
renderTicks: function(surface, ctx, layout, clipRect) {
var me = this,
attr = me.attr,
docked = attr.position,
matrix = attr.matrix,
halfLineWidth = 0.5 * attr.lineWidth,
xx = matrix.getXX(),
dx = matrix.getDX(),
yy = matrix.getYY(),
dy = matrix.getDY(),
majorTicks = layout.majorTicks,
majorTickSize = attr.majorTickSize,
minorTicks = layout.minorTicks,
minorTickSize = attr.minorTickSize,
gaugeAngles;
/* eslint-disable no-inner-declarations, no-case-declarations */
if (majorTicks) {
switch (docked) {
case 'right':
function getRightTickFn(size) {
return function(position, labelText, i) {
position = surface.roundPixel(position * yy + dy) + halfLineWidth;
ctx.moveTo(0, position);
ctx.lineTo(size, position);
};
}
me.iterate(majorTicks, getRightTickFn(majorTickSize));
if (minorTicks) {
me.iterate(minorTicks, getRightTickFn(minorTickSize));
}
break;
case 'left':
function getLeftTickFn(size) {
return function(position, labelText, i) {
position = surface.roundPixel(position * yy + dy) + halfLineWidth;
ctx.moveTo(clipRect[2] - size, position);
ctx.lineTo(clipRect[2], position);
};
}
me.iterate(majorTicks, getLeftTickFn(majorTickSize));
if (minorTicks) {
me.iterate(minorTicks, getLeftTickFn(minorTickSize));
}
break;
case 'bottom':
function getBottomTickFn(size) {
return function(position, labelText, i) {
position = surface.roundPixel(position * xx + dx) - halfLineWidth;
ctx.moveTo(position, 0);
ctx.lineTo(position, size);
};
}
me.iterate(majorTicks, getBottomTickFn(majorTickSize));
if (minorTicks) {
me.iterate(minorTicks, getBottomTickFn(minorTickSize));
}
break;
case 'top':
function getTopTickFn(size) {
return function(position, labelText, i) {
position = surface.roundPixel(position * xx + dx) - halfLineWidth;
ctx.moveTo(position, clipRect[3]);
ctx.lineTo(position, clipRect[3] - size);
};
}
me.iterate(majorTicks, getTopTickFn(majorTickSize));
if (minorTicks) {
me.iterate(minorTicks, getTopTickFn(minorTickSize));
}
break;
case 'angular':
me.iterate(majorTicks, function(position, labelText, i) {
position = position / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
ctx.moveTo(
attr.centerX + (attr.length) * Math.cos(position),
attr.centerY + (attr.length) * Math.sin(position)
);
ctx.lineTo(
attr.centerX + (attr.length + majorTickSize) * Math.cos(position),
attr.centerY + (attr.length + majorTickSize) * Math.sin(position)
);
});
break;
case 'gauge':
gaugeAngles = me.getGaugeAngles();
me.iterate(majorTicks, function(position, labelText, i) {
position = (position - attr.min) / (attr.max - attr.min) *
attr.totalAngle - attr.totalAngle + gaugeAngles.start;
ctx.moveTo(
attr.centerX + (attr.length) * Math.cos(position),
attr.centerY + (attr.length) * Math.sin(position)
);
ctx.lineTo(
attr.centerX + (attr.length + majorTickSize) * Math.cos(position),
attr.centerY + (attr.length + majorTickSize) * Math.sin(position)
);
});
break;
}
}
/* eslint-enable no-inner-declarations, no-case-declarations */
},
renderLabels: function(surface, ctx, layoutContext, clipRect) {
var me = this,
attr = me.attr,
halfLineWidth = 0.5 * attr.lineWidth,
docked = attr.position,
matrix = attr.matrix,
textPadding = attr.textPadding,
xx = matrix.getXX(),
dx = matrix.getDX(),
yy = matrix.getYY(),
dy = matrix.getDY(),
thickness = 0,
majorTicks = layoutContext.majorTicks,
tickPadding = Math.max(attr.majorTickSize, attr.minorTickSize) + attr.lineWidth,
isBBoxIntersect = Ext.draw.Draw.isBBoxIntersect,
label = me.getLabel(),
font,
labelOffset = me.getLabelOffset(),
lastLabelText = null,
textSize = 0,
textCount = 0,
segmenter = layoutContext.segmenter,
renderer = me.getRenderer(),
axis = me.getAxis(),
title = axis.getTitle(),
titleBBox = title && title.attr.text !== '' && title.getBBox(),
labelInverseMatrix,
lastBBox = null,
bbox, fly, text, titlePadding,
translation, gaugeAngles, angle;
if (majorTicks && label && !label.attr.hidden) {
font = label.attr.font;
if (ctx.font !== font) {
ctx.font = font;
} // This can profoundly improve performance.
label.setAttributes({ translationX: 0, translationY: 0 }, true);
label.applyTransformations();
labelInverseMatrix = label.attr.inverseMatrix.elements.slice(0);
switch (docked) {
case 'left':
titlePadding = titleBBox ? titleBBox.x + titleBBox.width : 0;
switch (label.attr.textAlign) {
case 'start':
translation = surface.roundPixel(titlePadding + dx) - halfLineWidth;
break;
case 'end':
translation = surface.roundPixel(clipRect[2] - tickPadding + dx) -
halfLineWidth;
break;
default: // 'center'
translation =
surface.roundPixel(titlePadding + (clipRect[2] - titlePadding -
tickPadding) / 2 + dx) - halfLineWidth;
}
label.setAttributes({
translationX: translation
}, true);
break;
case 'right':
titlePadding = titleBBox ? clipRect[2] - titleBBox.x : 0;
switch (label.attr.textAlign) {
case 'start':
translation = surface.roundPixel(tickPadding + dx) + halfLineWidth;
break;
case 'end':
translation = surface.roundPixel(clipRect[2] - titlePadding + dx) +
halfLineWidth;
break;
default: // 'center'
translation =
surface.roundPixel(tickPadding + (clipRect[2] - tickPadding -
titlePadding) / 2 + dx) + halfLineWidth;
}
label.setAttributes({
translationX: translation
}, true);
break;
case 'top':
titlePadding = titleBBox ? titleBBox.y + titleBBox.height : 0;
label.setAttributes({
translationY: surface.roundPixel(titlePadding + (clipRect[3] -
titlePadding - tickPadding) / 2) -
halfLineWidth
}, true);
break;
case 'bottom':
titlePadding = titleBBox ? clipRect[3] - titleBBox.y : 0;
label.setAttributes({
translationY: surface.roundPixel(tickPadding + (clipRect[3] - tickPadding -
titlePadding) / 2) + halfLineWidth
}, true);
break;
case 'radial' :
label.setAttributes({
translationX: attr.centerX
}, true);
break;
case 'angular':
label.setAttributes({
translationY: attr.centerY
}, true);
break;
case 'gauge':
label.setAttributes({
translationY: attr.centerY
}, true);
break;
}
// TODO: there are better ways to detect collision.
if (docked === 'left' || docked === 'right') {
me.iterate(majorTicks, function(position, labelText, i) {
if (labelText === undefined) {
return;
}
if (renderer) {
text = Ext.callback(renderer, null,
[axis, labelText, layoutContext, lastLabelText],
0, axis);
}
else {
text = segmenter.renderer(labelText, layoutContext, lastLabelText);
}
lastLabelText = labelText;
label.setAttributes({
text: String(text),
translationY: surface.roundPixel(position * yy + dy)
}, true);
label.applyTransformations();
thickness = Math.max(thickness, label.getBBox().width + tickPadding);
fly = Ext.draw.Matrix.fly(label.attr.matrix.elements.slice(0));
bbox = fly.prepend.apply(fly, labelInverseMatrix).transformBBox(
label.getBBox(true)
);
if (lastBBox && !isBBoxIntersect(bbox, lastBBox, textPadding)) {
return;
}
surface.renderSprite(label);
lastBBox = bbox;
textSize += bbox.height;
textCount++;
});
}
else if (docked === 'top' || docked === 'bottom') {
me.iterate(majorTicks, function(position, labelText, i) {
if (labelText === undefined) {
return;
}
if (renderer) {
text = Ext.callback(renderer, null,
[axis, labelText, layoutContext, lastLabelText],
0, axis);
}
else {
text = segmenter.renderer(labelText, layoutContext, lastLabelText);
}
lastLabelText = labelText;
label.setAttributes({
text: String(text),
translationX: surface.roundPixel(position * xx + dx)
}, true);
label.applyTransformations();
thickness = Math.max(thickness, label.getBBox().height + tickPadding);
fly = Ext.draw.Matrix.fly(label.attr.matrix.elements.slice(0));
bbox = fly.prepend.apply(fly, labelInverseMatrix)
.transformBBox(label.getBBox(true));
if (lastBBox && !isBBoxIntersect(bbox, lastBBox, textPadding)) {
return;
}
surface.renderSprite(label);
lastBBox = bbox;
textSize += bbox.width;
textCount++;
});
}
else if (docked === 'radial') {
me.iterate(majorTicks, function(position, labelText, i) {
if (labelText === undefined) {
return;
}
if (renderer) {
text = Ext.callback(renderer, null,
[axis, labelText, layoutContext, lastLabelText],
0, axis);
}
else {
text = segmenter.renderer(labelText, layoutContext, lastLabelText);
}
lastLabelText = labelText;
if (typeof text !== 'undefined') {
label.setAttributes({
text: String(text),
translationX: attr.centerX - surface.roundPixel(position) /
attr.max * attr.length * Math.cos(attr.baseRotation + Math.PI / 2),
translationY: attr.centerY - surface.roundPixel(position) /
attr.max * attr.length * Math.sin(attr.baseRotation + Math.PI / 2)
}, true);
label.applyTransformations();
bbox = label.attr.matrix.transformBBox(label.getBBox(true));
if (lastBBox && !isBBoxIntersect(bbox, lastBBox)) {
return;
}
surface.renderSprite(label);
lastBBox = bbox;
textSize += bbox.width;
textCount++;
}
});
}
else if (docked === 'angular') {
labelOffset += attr.majorTickSize + attr.lineWidth * 0.5;
me.iterate(majorTicks, function(position, labelText, i) {
if (labelText === undefined) {
return;
}
if (renderer) {
text = Ext.callback(renderer, null,
[axis, labelText, layoutContext, lastLabelText],
0, axis);
}
else {
text = segmenter.renderer(labelText, layoutContext, lastLabelText);
}
lastLabelText = labelText;
thickness = Math.max(thickness,
Math.max(attr.majorTickSize, attr.minorTickSize) +
(attr.lineCap !== 'butt' ? attr.lineWidth * 0.5 : 0)
);
if (typeof text !== 'undefined') {
angle = position / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
label.setAttributes({
text: String(text),
translationX: attr.centerX + (attr.length + labelOffset) *
Math.cos(angle),
translationY: attr.centerY + (attr.length + labelOffset) *
Math.sin(angle)
}, true);
label.applyTransformations();
bbox = label.attr.matrix.transformBBox(label.getBBox(true));
if (lastBBox && !isBBoxIntersect(bbox, lastBBox)) {
return;
}
surface.renderSprite(label);
lastBBox = bbox;
textSize += bbox.width;
textCount++;
}
});
}
else if (docked === 'gauge') {
gaugeAngles = me.getGaugeAngles();
labelOffset += attr.majorTickSize + attr.lineWidth * 0.5;
me.iterate(majorTicks, function(position, labelText, i) {
if (labelText === undefined) {
return;
}
if (renderer) {
text = Ext.callback(renderer, null,
[axis, labelText, layoutContext, lastLabelText],
0, axis);
}
else {
text = segmenter.renderer(labelText, layoutContext, lastLabelText);
}
lastLabelText = labelText;
if (typeof text !== 'undefined') {
angle = (position - attr.min) / (attr.max - attr.min) * attr.totalAngle -
attr.totalAngle + gaugeAngles.start;
label.setAttributes({
text: String(text),
translationX: attr.centerX + (attr.length + labelOffset) *
Math.cos(angle),
translationY: attr.centerY + (attr.length + labelOffset) *
Math.sin(angle)
}, true);
label.applyTransformations();
bbox = label.attr.matrix.transformBBox(label.getBBox(true));
if (lastBBox && !isBBoxIntersect(bbox, lastBBox)) {
return;
}
surface.renderSprite(label);
lastBBox = bbox;
textSize += bbox.width;
textCount++;
}
});
}
if (attr.enlargeEstStepSizeByText && textCount) {
textSize /= textCount;
textSize += tickPadding;
textSize *= 2;
if (attr.estStepSize < textSize) {
attr.estStepSize = textSize;
}
}
if (Math.abs(me.thickness - thickness) > 1) {
me.thickness = thickness;
attr.bbox.plain.dirty = true;
attr.bbox.transform.dirty = true;
me.doThicknessChanged();
return false;
}
}
},
renderAxisLine: function(surface, ctx, layout, clipRect) {
var me = this,
attr = me.attr,
halfLineWidth = attr.lineWidth * 0.5,
docked = attr.position,
position, gaugeAngles;
if (attr.axisLine && attr.length) {
switch (docked) {
case 'left':
position = surface.roundPixel(clipRect[2]) - halfLineWidth;
ctx.moveTo(position, -attr.endGap);
ctx.lineTo(position, attr.length + attr.startGap + 1);
break;
case 'right':
ctx.moveTo(halfLineWidth, -attr.endGap);
ctx.lineTo(halfLineWidth, attr.length + attr.startGap + 1);
break;
case 'bottom':
ctx.moveTo(-attr.startGap, halfLineWidth);
ctx.lineTo(attr.length + attr.endGap, halfLineWidth);
break;
case 'top':
position = surface.roundPixel(clipRect[3]) - halfLineWidth;
ctx.moveTo(-attr.startGap, position);
ctx.lineTo(attr.length + attr.endGap, position);
break;
case 'angular':
ctx.moveTo(attr.centerX + attr.length, attr.centerY);
ctx.arc(attr.centerX, attr.centerY, attr.length, 0, Math.PI * 2, true);
break;
case 'gauge':
gaugeAngles = me.getGaugeAngles();
ctx.moveTo(attr.centerX + Math.cos(gaugeAngles.start) * attr.length,
attr.centerY + Math.sin(gaugeAngles.start) * attr.length);
ctx.arc(attr.centerX, attr.centerY, attr.length, gaugeAngles.start,
gaugeAngles.end, true);
break;
}
}
},
getGaugeAngles: function() {
var me = this,
angle = me.attr.totalAngle,
offset;
if (angle <= Math.PI) {
offset = (Math.PI - angle) * 0.5;
}
else {
offset = -(Math.PI * 2 - angle) * 0.5;
}
offset = Math.PI * 2 - offset;
return {
start: offset,
end: offset - angle
};
},
renderGridLines: function(surface, ctx, layout, clipRect) {
var me = this,
axis = me.getAxis(),
attr = me.attr,
matrix = attr.matrix,
startGap = attr.startGap,
endGap = attr.endGap,
xx = matrix.getXX(),
yy = matrix.getYY(),
dx = matrix.getDX(),
dy = matrix.getDY(),
position = attr.position,
alignment = axis.getGridAlignment(),
majorTicks = layout.majorTicks,
anchor, j, lastAnchor;
if (attr.grid) {
if (majorTicks) {
if (position === 'left' || position === 'right') {
lastAnchor = attr.min * yy + dy + endGap + startGap;
me.iterate(majorTicks, function(position, labelText, i) {
anchor = position * yy + dy + endGap;
me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
y: anchor,
height: lastAnchor - anchor
}, j = i, true);
lastAnchor = anchor;
});
j++;
anchor = 0;
me.putMarker(alignment + '-' + (j % 2 ? 'odd' : 'even'), {
y: anchor,
height: lastAnchor - anchor
}, j, true);
}
else if (position === 'top' || position === 'bottom') {
lastAnchor = attr.min * xx + dx + startGap;
if (startGap) {
me.putMarker(alignment + '-even', {
x: 0,
width: lastAnchor
}, -1, true);
}
me.iterate(majorTicks, function(position, labelText, i) {
anchor = position * xx + dx + startGap;
me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
x: anchor,
width: lastAnchor - anchor
}, j = i, true);
lastAnchor = anchor;
});
j++;
anchor = attr.length + attr.startGap + attr.endGap;
me.putMarker(alignment + '-' + (j % 2 ? 'odd' : 'even'), {
x: anchor,
width: lastAnchor - anchor
}, j, true);
}
else if (position === 'radial') {
me.iterate(majorTicks, function(position, labelText, i) {
if (!position) {
return;
}
anchor = position / attr.max * attr.length;
me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
scalingX: anchor,
scalingY: anchor
}, i, true);
lastAnchor = anchor;
});
}
else if (position === 'angular') {
me.iterate(majorTicks, function(position, labelText, i) {
if (!attr.length) {
return;
}
anchor = position / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
rotationRads: anchor,
rotationCenterX: 0,
rotationCenterY: 0,
scalingX: attr.length,
scalingY: attr.length
}, i, true);
lastAnchor = anchor;
});
}
}
}
},
renderLimits: function(clipRect) {
var me = this,
attr = me.attr,
axis = me.getAxis(),
limits = Ext.Array.from(axis.getLimits());
if (!limits.length || attr.dataMin === attr.dataMax) {
if (axis.limits) {
axis.limits.titles.attr.hidden = true;
}
return;
}
// eslint-disable-next-line vars-on-top, one-var
var chart = axis.getChart(),
innerPadding = chart.getInnerPadding(),
limitsRect = axis.limits.surface.getRect(),
matrix = attr.matrix,
position = attr.position,
chain = Ext.Object.chain,
titles = axis.limits.titles,
titleBBox, titlePosition, titleFlip,
limit, value,
i, ln, x, y;
titles.attr.hidden = false;
titles.instances = [];
titles.position = 0;
if (position === 'left' || position === 'right') {
for (i = 0, ln = limits.length; i < ln; i++) {
limit = chain(limits[i]);
if (!limit.line) {
limit.line = {};
}
value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
value = value * matrix.getYY() + matrix.getDY();
limit.line.y = value + innerPadding.top;
limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
me.putMarker('horizontal-limit-lines', limit.line, i, true);
if (limit.line.title) {
titles.add(limit.line.title);
titleBBox = titles.getBBoxFor(titles.position - 1);
titlePosition = limit.line.title.position ||
(position === 'left' ? 'start' : 'end');
switch (titlePosition) {
case 'start':
x = 10;
break;
case 'end':
x = limitsRect[2] - 10;
break;
case 'middle':
x = limitsRect[2] / 2;
break;
}
titles.setAttributesFor(titles.position - 1, {
x: x,
y: limit.line.y - titleBBox.height / 2,
textAlign: titlePosition,
fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle
});
}
}
}
else if (position === 'top' || position === 'bottom') {
for (i = 0, ln = limits.length; i < ln; i++) {
limit = chain(limits[i]);
if (!limit.line) {
limit.line = {};
}
value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
value = value * matrix.getXX() + matrix.getDX();
limit.line.x = value + innerPadding.left;
limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
me.putMarker('vertical-limit-lines', limit.line, i, true);
if (limit.line.title) {
titles.add(limit.line.title);
titleBBox = titles.getBBoxFor(titles.position - 1);
titlePosition = limit.line.title.position ||
(position === 'top' ? 'end' : 'start');
switch (titlePosition) {
case 'start':
y = limitsRect[3] - titleBBox.width / 2 - 10;
break;
case 'end':
y = titleBBox.width / 2 + 10;
break;
case 'middle':
y = limitsRect[3] / 2;
break;
}
titles.setAttributesFor(titles.position - 1, {
x: limit.line.x + titleBBox.height / 2,
y: y,
fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle,
rotationRads: Math.PI / 2
});
}
}
}
else if (position === 'radial') {
for (i = 0, ln = limits.length; i < ln; i++) {
limit = chain(limits[i]);
if (!limit.line) {
limit.line = {};
}
value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
if (value > attr.max) {
continue;
}
value = value / attr.max * attr.length;
limit.line.cx = attr.centerX;
limit.line.cy = attr.centerY;
limit.line.scalingX = value;
limit.line.scalingY = value;
limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
me.putMarker('circular-limit-lines', limit.line, i, true);
if (limit.line.title) {
titles.add(limit.line.title);
titleBBox = titles.getBBoxFor(titles.position - 1);
titles.setAttributesFor(titles.position - 1, {
x: attr.centerX,
y: attr.centerY - value - titleBBox.height / 2,
fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle
});
}
}
}
else if (position === 'angular') {
for (i = 0, ln = limits.length; i < ln; i++) {
limit = chain(limits[i]);
if (!limit.line) {
limit.line = {};
}
value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
value = value / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
limit.line.translationX = attr.centerX;
limit.line.translationY = attr.centerY;
limit.line.rotationRads = value;
limit.line.rotationCenterX = 0;
limit.line.rotationCenterY = 0;
limit.line.scalingX = attr.length;
limit.line.scalingY = attr.length;
limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
me.putMarker('radial-limit-lines', limit.line, i, true);
if (limit.line.title) {
titles.add(limit.line.title);
titleBBox = titles.getBBoxFor(titles.position - 1);
titleFlip = ((value > -0.5 * Math.PI && value < 0.5 * Math.PI) ||
(value > 1.5 * Math.PI && value < 2 * Math.PI))
? 1
: -1;
titles.setAttributesFor(titles.position - 1, {
x: attr.centerX + 0.5 * attr.length * Math.cos(value) +
titleFlip * titleBBox.height / 2 * Math.sin(value),
y: attr.centerY + 0.5 * attr.length * Math.sin(value) -
titleFlip * titleBBox.height / 2 * Math.cos(value),
rotationRads: titleFlip === 1 ? value : value - Math.PI,
fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle
});
}
}
}
else if (position === 'gauge') {
// TODO
}
},
doThicknessChanged: function() {
var axis = this.getAxis();
if (axis) {
axis.onThicknessChanged();
}
},
render: function(surface, ctx, rect) {
var me = this,
layoutContext = me.getLayoutContext();
if (layoutContext) {
if (me.renderLabels(surface, ctx, layoutContext, rect) === false) {
return false;
}
ctx.beginPath();
me.renderTicks(surface, ctx, layoutContext, rect);
me.renderAxisLine(surface, ctx, layoutContext, rect);
me.renderGridLines(surface, ctx, layoutContext, rect);
me.renderLimits(rect);
ctx.stroke();
}
}
});