@progress/kendo-charts
Version:
Kendo UI platform-independent Charts library
672 lines (546 loc) • 21.2 kB
JavaScript
import Axis from './axis';
import AxisLabel from './axis-label';
import { BLACK, COORD_PRECISION, DEFAULT_PRECISION, X, Y } from '../common/constants';
import { defined, isNumber, last, limitValue, round, setDefaultOptions, valueOrDefault, HashMap } from '../common';
import { dateEquals } from '../date-utils';
var MIN_CATEGORY_POINTS_RANGE = 0.01;
var MIN_CATEGORY_RANGE = 0.1;
function indexOf(value, arr) {
if (value instanceof Date) {
var length = arr.length;
for (var idx = 0; idx < length; idx++) {
if (dateEquals(arr[idx], value)) {
return idx;
}
}
return -1;
}
return arr.indexOf(value);
}
var CategoryAxis = (function (Axis) {
function CategoryAxis () {
Axis.apply(this, arguments);
}
if ( Axis ) CategoryAxis.__proto__ = Axis;
CategoryAxis.prototype = Object.create( Axis && Axis.prototype );
CategoryAxis.prototype.constructor = CategoryAxis;
CategoryAxis.prototype.initFields = function initFields () {
this._ticks = {};
};
CategoryAxis.prototype.categoriesHash = function categoriesHash () {
return "";
};
CategoryAxis.prototype.clone = function clone () {
var copy = new CategoryAxis(Object.assign({}, this.options, {
categories: this.options.srcCategories
}), this.chartService);
copy.createLabels();
return copy;
};
CategoryAxis.prototype.initUserOptions = function initUserOptions (options) {
var categories = options.categories || [];
var definedMin = defined(options.min);
var definedMax = defined(options.max);
options.srcCategories = options.categories = categories;
if ((definedMin || definedMax) && categories.length) {
var min = definedMin ? Math.floor(options.min) : 0;
var max;
if (definedMax) {
max = options.justified ? Math.floor(options.max) + 1 : Math.ceil(options.max);
} else {
max = categories.length;
}
options.categories = options.categories.slice(min, max);
}
return options;
};
CategoryAxis.prototype.rangeIndices = function rangeIndices () {
var options = this.options;
var length = options.categories.length || 1;
var min = isNumber(options.min) ? options.min % 1 : 0;
var max;
if (isNumber(options.max) && options.max % 1 !== 0 && options.max < this.totalRange().max) {
max = length - (1 - options.max % 1);
} else {
max = length - (options.justified ? 1 : 0);
}
return {
min: min,
max: max
};
};
CategoryAxis.prototype.range = function range () {
var options = this.options;
var min = isNumber(options.min) ? options.min : 0;
var max = isNumber(options.max) ? options.max : this.totalRange().max;
return {
min: min,
max: max
};
};
CategoryAxis.prototype.roundedRange = function roundedRange () {
return this.range();
};
CategoryAxis.prototype.totalRange = function totalRange () {
var options = this.options;
return { min: 0, max: Math.max(this._seriesMax || 0, options.srcCategories.length) - (options.justified ? 1 : 0) };
};
CategoryAxis.prototype.scaleOptions = function scaleOptions () {
var ref = this.rangeIndices();
var min = ref.min;
var max = ref.max;
var lineBox = this.lineBox();
var size = this.options.vertical ? lineBox.height() : lineBox.width();
var scale = size / ((max - min) || 1);
return {
scale: scale * (this.options.reverse ? -1 : 1),
box: lineBox,
min: min,
max: max
};
};
CategoryAxis.prototype.arrangeLabels = function arrangeLabels () {
Axis.prototype.arrangeLabels.call(this);
this.hideOutOfRangeLabels();
};
CategoryAxis.prototype.hideOutOfRangeLabels = function hideOutOfRangeLabels () {
var ref = this;
var box = ref.box;
var labels = ref.labels;
if (labels.length > 0) {
var valueAxis = this.options.vertical ? Y : X;
var start = box[valueAxis + 1];
var end = box[valueAxis + 2];
var firstLabel = labels[0];
var lastLabel = last(labels);
if (firstLabel.box[valueAxis + 1] > end || firstLabel.box[valueAxis + 2] < start) {
firstLabel.options.visible = false;
}
if (lastLabel.box[valueAxis + 1] > end || lastLabel.box[valueAxis + 2] < start) {
lastLabel.options.visible = false;
}
}
};
CategoryAxis.prototype.getMajorTickPositions = function getMajorTickPositions () {
return this.getTicks().majorTicks;
};
CategoryAxis.prototype.getMinorTickPositions = function getMinorTickPositions () {
return this.getTicks().minorTicks;
};
CategoryAxis.prototype.getLabelsTickPositions = function getLabelsTickPositions () {
return this.getTicks().labelTicks;
};
CategoryAxis.prototype.tickIndices = function tickIndices (stepSize) {
var ref = this.rangeIndices();
var min = ref.min;
var max = ref.max;
var limit = Math.ceil(max);
var current = Math.floor(min);
var indices = [];
while (current <= limit) {
indices.push(current);
current += stepSize;
}
return indices;
};
CategoryAxis.prototype.getTickPositions = function getTickPositions (stepSize) {
var ref = this.options;
var vertical = ref.vertical;
var reverse = ref.reverse;
var ref$1 = this.scaleOptions();
var scale = ref$1.scale;
var box = ref$1.box;
var min = ref$1.min;
var pos = box[(vertical ? Y : X) + (reverse ? 2 : 1)];
var indices = this.tickIndices(stepSize);
var positions = [];
for (var idx = 0; idx < indices.length; idx++) {
positions.push(pos + round(scale * (indices[idx] - min), COORD_PRECISION));
}
return positions;
};
CategoryAxis.prototype.getTicks = function getTicks () {
var options = this.options;
var cache = this._ticks;
var range = this.rangeIndices();
var lineBox = this.lineBox();
var hash = lineBox.getHash() + range.min + "," + range.max + options.reverse + options.justified;
if (cache._hash !== hash) {
var hasMinor = options.minorTicks.visible || options.minorGridLines.visible;
cache._hash = hash;
cache.labelTicks = this.getTickPositions(1);
cache.majorTicks = this.filterOutOfRangePositions(cache.labelTicks, lineBox);
cache.minorTicks = hasMinor ? this.filterOutOfRangePositions(this.getTickPositions(0.5), lineBox) : [];
}
return cache;
};
CategoryAxis.prototype.filterOutOfRangePositions = function filterOutOfRangePositions (positions, lineBox) {
if (!positions.length) {
return positions;
}
var axis = this.options.vertical ? Y : X;
var inRange = function (position) { return lineBox[axis + 1] <= position && position <= lineBox[axis + 2]; };
var end = positions.length - 1;
var startIndex = 0;
while (!inRange(positions[startIndex]) && startIndex <= end) {
startIndex++;
}
var endIndex = end;
while (!inRange(positions[endIndex]) && endIndex >= 0) {
endIndex--;
}
return positions.slice(startIndex, endIndex + 1);
};
CategoryAxis.prototype.lineInfo = function lineInfo () {
var ref = this.options;
var vertical = ref.vertical;
var reverse = ref.reverse;
var lineBox = this.lineBox();
var lineSize = vertical ? lineBox.height() : lineBox.width();
var axis = vertical ? Y : X;
var axisDir = reverse ? -1 : 1;
var startEdge = axisDir === 1 ? 1 : 2;
var axisOrigin = axis + startEdge.toString();
var lineStart = lineBox[axisOrigin];
return {
axis: axis,
axisOrigin: axisOrigin,
axisDir: axisDir,
lineBox: lineBox,
lineSize: lineSize,
lineStart: lineStart
};
};
CategoryAxis.prototype.lineDir = function lineDir () {
/*
* Category axis line direction:
* * Vertical: down.
* * Horizontal: right.
*/
var ref = this.options;
var reverse = ref.reverse;
return reverse ? -1 : 1;
};
// TODO: Rename to slotBox, valueSlot, slotByIndex?
CategoryAxis.prototype.getSlot = function getSlot (from, to, limit) {
var options = this.options;
var reverse = options.reverse;
var justified = options.justified;
var ref = this.scaleOptions();
var scale = ref.scale;
var box = ref.box;
var min = ref.min;
var ref$1 = this.lineInfo();
var valueAxis = ref$1.axis;
var lineStart = ref$1.lineStart;
var slotBox = box.clone();
var singleSlot = !defined(to);
var start = valueOrDefault(from, 0);
var end = valueOrDefault(to, start);
end = Math.max(end - 1, start);
// Fixes transient bug caused by iOS 6.0 JIT
// (one can never be too sure)
end = Math.max(start, end);
var p1 = lineStart + (start - min) * scale;
var p2 = lineStart + (end + 1 - min) * scale;
if (singleSlot && justified) {
p2 = p1;
}
if (limit) {
p1 = limitValue(p1, box[valueAxis + 1], box[valueAxis + 2]);
p2 = limitValue(p2, box[valueAxis + 1], box[valueAxis + 2]);
}
slotBox[valueAxis + 1] = reverse ? p2 : p1;
slotBox[valueAxis + 2] = reverse ? p1 : p2;
return slotBox;
};
CategoryAxis.prototype.limitSlot = function limitSlot (slot) {
var vertical = this.options.vertical;
var valueAxis = vertical ? Y : X;
var lineBox = this.lineBox();
var limittedSlot = slot.clone();
limittedSlot[valueAxis + 1] = limitValue(slot[valueAxis + 1], lineBox[valueAxis + 1], lineBox[valueAxis + 2]);
limittedSlot[valueAxis + 2] = limitValue(slot[valueAxis + 2], lineBox[valueAxis + 1], lineBox[valueAxis + 2]);
return limittedSlot;
};
CategoryAxis.prototype.slot = function slot (from, to, limit) {
var min = Math.floor(this.options.min || 0);
var start = from;
var end = to;
if (typeof start === "string") {
start = this.categoryIndex(start);
} else if (isNumber(start)) {
start -= min;
}
if (typeof end === "string") {
end = this.categoryIndex(end);
} else if (isNumber(end)) {
end -= min;
}
return Axis.prototype.slot.call(this, start, end, limit);
};
CategoryAxis.prototype.pointCategoryIndex = function pointCategoryIndex (point) {
var ref = this.options;
var reverse = ref.reverse;
var justified = ref.justified;
var vertical = ref.vertical;
var valueAxis = vertical ? Y : X;
var ref$1 = this.scaleOptions();
var scale = ref$1.scale;
var box = ref$1.box;
var min = ref$1.min;
var max = ref$1.max;
var startValue = reverse ? max : min;
var lineStart = box[valueAxis + 1];
var lineEnd = box[valueAxis + 2];
var pos = point[valueAxis];
if (pos < lineStart || pos > lineEnd) {
return null;
}
var value = startValue + (pos - lineStart) / scale;
var diff = value % 1;
if (justified) {
value = Math.round(value);
} else if (diff === 0 && value > 0) {
value--;
}
return Math.floor(value);
};
CategoryAxis.prototype.getCategory = function getCategory (point) {
var index = this.pointCategoryIndex(point);
if (index === null) {
return null;
}
return this.options.categories[index];
};
CategoryAxis.prototype.categoryIndex = function categoryIndex (value) {
return this.totalIndex(value) - Math.floor(this.options.min || 0);
};
CategoryAxis.prototype.categoryAt = function categoryAt (index, total) {
var options = this.options;
return (total ? options.srcCategories : options.categories)[index];
};
CategoryAxis.prototype.categoriesCount = function categoriesCount () {
return (this.options.categories || []).length;
};
CategoryAxis.prototype.translateRange = function translateRange (delta) {
var options = this.options;
var lineBox = this.lineBox();
var size = options.vertical ? lineBox.height() : lineBox.width();
var range = options.categories.length;
var scale = size / range;
var offset = round(delta / scale, DEFAULT_PRECISION);
return {
min: offset,
max: range + offset
};
};
CategoryAxis.prototype.scaleRange = function scaleRange (scale, cursor) {
var position = Math.abs(this.pointOffset(cursor));
var rangeIndices = this.limitedRangeIndices();
var range = rangeIndices.max - rangeIndices.min;
var delta = this.scaleToDelta(scale, range);
var minDelta = position * delta;
var maxDelta = (1 - position) * delta;
var min = rangeIndices.min + minDelta;
var max = rangeIndices.max - maxDelta;
if (max - min < MIN_CATEGORY_RANGE) {
max = min + MIN_CATEGORY_RANGE;
}
return {
min: min,
max: max
};
};
CategoryAxis.prototype.zoomRange = function zoomRange (scale, cursor) {
var ref = this.totalRange();
var totalMin = ref.min;
var totalMax = ref.max;
var range = this.scaleRange(scale, cursor);
return {
min: limitValue(range.min, totalMin, totalMax),
max: limitValue(range.max, totalMin, totalMax)
};
};
CategoryAxis.prototype.labelsCount = function labelsCount () {
var labelsRange = this.labelsRange();
return labelsRange.max - labelsRange.min;
};
CategoryAxis.prototype.labelsRange = function labelsRange () {
var options = this.options;
var justified = options.justified;
var labelOptions = options.labels;
var ref = this.limitedRangeIndices(true);
var min = ref.min;
var max = ref.max;
var start = Math.floor(min);
if (!justified) {
min = Math.floor(min);
max = Math.ceil(max);
} else {
min = Math.ceil(min);
max = Math.floor(max);
}
var skip;
if (min > labelOptions.skip) {
skip = labelOptions.skip + labelOptions.step * Math.ceil((min - labelOptions.skip) / labelOptions.step);
} else {
skip = labelOptions.skip;
}
return {
min: skip - start,
max: (options.categories.length ? max + (justified ? 1 : 0) : 0) - start
};
};
CategoryAxis.prototype.createAxisLabel = function createAxisLabel (index, labelOptions, labelContext) {
var options = this.options;
var dataItem = options.dataItems ? options.dataItems[index] : null;
var category = valueOrDefault(options.categories[index], "");
labelContext.dataItem = dataItem;
var text = this.axisLabelText(category, labelOptions, labelContext);
return new AxisLabel(category, text, index, dataItem, labelOptions);
};
CategoryAxis.prototype.shouldRenderNote = function shouldRenderNote (value) {
var range = this.limitedRangeIndices();
return Math.floor(range.min) <= value && value <= Math.ceil(range.max);
};
CategoryAxis.prototype.noteSlot = function noteSlot (value) {
var options = this.options;
var index = value - Math.floor(options.min || 0);
return this.getSlot(index);
};
CategoryAxis.prototype.arrangeNotes = function arrangeNotes () {
Axis.prototype.arrangeNotes.call(this);
this.hideOutOfRangeNotes();
};
CategoryAxis.prototype.hideOutOfRangeNotes = function hideOutOfRangeNotes () {
var ref = this;
var notes = ref.notes;
var box = ref.box;
if (notes && notes.length) {
var valueAxis = this.options.vertical ? Y : X;
var start = box[valueAxis + 1];
var end = box[valueAxis + 2];
for (var idx = 0; idx < notes.length; idx++) {
var note = notes[idx];
if (note.box && (end < note.box[valueAxis + 1] || note.box[valueAxis + 2] < start)) {
note.hide();
}
}
}
};
CategoryAxis.prototype.pan = function pan (delta) {
var range = this.limitedRangeIndices(true);
var ref = this.scaleOptions();
var scale = ref.scale;
var offset = round(delta / scale, DEFAULT_PRECISION);
var totalRange = this.totalRange();
var min = range.min + offset;
var max = range.max + offset;
return this.limitRange(min, max, 0, totalRange.max, offset);
};
CategoryAxis.prototype.pointsRange = function pointsRange (start, end) {
var ref = this.options;
var reverse = ref.reverse;
var vertical = ref.vertical;
var valueAxis = vertical ? Y : X;
var range = this.limitedRangeIndices(true);
var ref$1 = this.scaleOptions();
var scale = ref$1.scale;
var box = ref$1.box;
var lineStart = box[valueAxis + (reverse ? 2 : 1)];
var diffStart = start[valueAxis] - lineStart;
var diffEnd = end[valueAxis] - lineStart;
var min = range.min + diffStart / scale;
var max = range.min + diffEnd / scale;
var rangeMin = Math.min(min, max);
var rangeMax = Math.max(min, max);
if (rangeMax - rangeMin >= MIN_CATEGORY_POINTS_RANGE) {
return {
min: rangeMin,
max: rangeMax
};
}
};
CategoryAxis.prototype.valueRange = function valueRange () {
return this.range();
};
CategoryAxis.prototype.totalIndex = function totalIndex (value) {
var options = this.options;
var index = this._categoriesMap ?
this._categoriesMap.get(value) : indexOf(value, options.srcCategories);
return index;
};
CategoryAxis.prototype.currentRangeIndices = function currentRangeIndices () {
var options = this.options;
var min = 0;
if (isNumber(options.min)) {
min = Math.floor(options.min);
}
var max;
if (isNumber(options.max)) {
max = options.justified ? Math.floor(options.max) : Math.ceil(options.max) - 1;
} else {
max = this.totalCount() - 1;
}
return {
min: min,
max: max
};
};
CategoryAxis.prototype.limitedRangeIndices = function limitedRangeIndices (totalLimit) {
var options = this.options;
var min = isNumber(options.min) ? options.min : 0;
var max;
if (isNumber(options.max)) {
max = options.max;
} else if (isNumber(options.min)) {
max = min + options.categories.length;
} else {
max = this.totalRange().max || 1;
}
if (totalLimit) {
var totalRange = this.totalRange();
min = limitValue(min, 0, totalRange.max);
max = limitValue(max, 0, totalRange.max);
}
return {
min: min,
max: max
};
};
CategoryAxis.prototype.totalRangeIndices = function totalRangeIndices () {
return {
min: 0,
max: this.totalRange().max || 1
};
};
CategoryAxis.prototype.indexCategories = function indexCategories () {
if (!this._categoriesMap) {
var map = this._categoriesMap = new HashMap();
var srcCategories = this.options.srcCategories;
for (var idx = 0; idx < srcCategories.length; idx++) {
map.set(srcCategories[idx], idx);
}
}
};
CategoryAxis.prototype.totalCount = function totalCount () {
return Math.max(this.options.srcCategories.length, this._seriesMax || 0);
};
return CategoryAxis;
}(Axis));
setDefaultOptions(CategoryAxis, {
type: "category",
vertical: false,
majorGridLines: {
visible: false,
width: 1,
color: BLACK
},
labels: {
zIndex: 1
},
justified: false,
_deferLabels: true
});
export default CategoryAxis;