@progress/kendo-charts
Version:
Kendo UI platform-independent Charts library
573 lines (457 loc) • 20.5 kB
JavaScript
import ErrorRangeCalculator from './error-bars/error-range-calculator';
import CategoricalErrorBar from './error-bars/categorical-error-bar';
import { ERROR_LOW_FIELD, ERROR_HIGH_FIELD } from './constants';
import { evalOptions, categoriesCount } from './utils';
import { ChartElement, Box } from '../core';
import { VALUE, STRING, MIN_VALUE, MAX_VALUE, OBJECT } from '../common/constants';
import { convertableToNumber, deepExtend, defined, isNumber, last, setDefaultOptions, sparseArrayLimits } from '../common';
var CategoricalChart = (function (ChartElement) {
function CategoricalChart(plotArea, options) {
ChartElement.call(this, options);
this.plotArea = plotArea;
this.chartService = plotArea.chartService;
this.categoryAxis = plotArea.seriesCategoryAxis(options.series[0]);
// Value axis ranges grouped by axis name, e.g.:
// primary: { min: 0, max: 1 }
this.valueAxisRanges = {};
this.points = [];
this.categoryPoints = [];
this.seriesPoints = [];
this.seriesOptions = [];
this._evalSeries = [];
this.render();
}
if ( ChartElement ) CategoricalChart.__proto__ = ChartElement;
CategoricalChart.prototype = Object.create( ChartElement && ChartElement.prototype );
CategoricalChart.prototype.constructor = CategoricalChart;
CategoricalChart.prototype.render = function render () {
this.traverseDataPoints(this.addValue.bind(this));
};
CategoricalChart.prototype.pointOptions = function pointOptions (series, seriesIx) {
var options = this.seriesOptions[seriesIx];
if (!options) {
var defaults = this.pointType().prototype.defaults;
this.seriesOptions[seriesIx] = options = deepExtend({ }, defaults, {
vertical: !this.options.invertAxes
}, series);
}
return options;
};
CategoricalChart.prototype.plotValue = function plotValue (point) {
if (!point) {
return 0;
}
if (this.options.isStacked100 && isNumber(point.value)) {
var categoryIx = point.categoryIx;
var categoryPoints = this.categoryPoints[categoryIx];
var otherValues = [];
var categorySum = 0;
for (var i = 0; i < categoryPoints.length; i++) {
var other = categoryPoints[i];
if (other) {
var stack = point.series.stack;
var otherStack = other.series.stack;
if ((stack && otherStack) && stack.group !== otherStack.group) {
continue;
}
if (isNumber(other.value)) {
categorySum += Math.abs(other.value);
otherValues.push(Math.abs(other.value));
}
}
}
if (categorySum > 0) {
return point.value / categorySum;
}
}
return point.value;
};
CategoricalChart.prototype.plotRange = function plotRange (point, startValue) {
var this$1 = this;
if ( startValue === void 0 ) startValue = 0;
var categoryPoints = this.categoryPoints[point.categoryIx];
if (this.options.isStacked) {
var plotValue = this.plotValue(point);
var positive = plotValue >= 0;
var prevValue = startValue;
var isStackedBar = false;
var stack = defined(point.series.stack) ? point.series.stack : this.options.defaultStack;
var isNonGroupStack = function (stack) { return stack === true || typeof stack === OBJECT && !stack.group; };
if (stack) {
for (var i = 0; i < categoryPoints.length; i++) {
var other = categoryPoints[i];
if (point === other) {
break;
}
var otherStack = defined(other.series.stack) ? other.series.stack : this$1.options.defaultStack;
if (!otherStack) {
continue;
}
if (typeof stack === STRING && stack !== otherStack) {
continue;
}
if (isNonGroupStack(stack) && !isNonGroupStack(otherStack)) {
continue;
}
if (stack.group && stack.group !== otherStack.group) {
continue;
}
var otherValue = this$1.plotValue(other);
if ((otherValue >= 0 && positive) ||
(otherValue < 0 && !positive)) {
// zero values should be skipped for log axis (startValue !== 0)
if (startValue === 0 || otherValue !== 0) {
prevValue += otherValue;
plotValue += otherValue;
isStackedBar = true;
if (this$1.options.isStacked100) {
plotValue = Math.min(plotValue, 1);
}
}
}
}
}
if (isStackedBar) {
prevValue -= startValue;
}
return [ prevValue, plotValue ];
}
var series = point.series;
var valueAxis = this.seriesValueAxis(series);
var axisCrossingValue = this.categoryAxisCrossingValue(valueAxis);
return [ axisCrossingValue, convertableToNumber(point.value) ? point.value : axisCrossingValue ];
};
CategoricalChart.prototype.stackLimits = function stackLimits (axisName, stackName) {
var this$1 = this;
var min = MAX_VALUE;
var max = MIN_VALUE;
for (var i = 0; i < this.categoryPoints.length; i++) {
var categoryPoints = this$1.categoryPoints[i];
if (!categoryPoints) {
continue;
}
for (var pIx = 0; pIx < categoryPoints.length; pIx++) {
var point = categoryPoints[pIx];
if (point) {
if (point.series.stack === stackName || point.series.axis === axisName) {
var to = this$1.plotRange(point, 0)[1];
if (defined(to) && isFinite(to)) {
max = Math.max(max, to);
min = Math.min(min, to);
}
}
}
}
}
return { min: min, max: max };
};
CategoricalChart.prototype.updateStackRange = function updateStackRange () {
var this$1 = this;
var ref = this.options;
var isStacked = ref.isStacked;
var chartSeries = ref.series;
var limitsCache = {};
if (isStacked) {
for (var i = 0; i < chartSeries.length; i++) {
var series = chartSeries[i];
var axisName = series.axis;
var key = axisName + series.stack;
var limits = limitsCache[key];
if (!limits) {
limits = this$1.stackLimits(axisName, series.stack);
var errorTotals = this$1.errorTotals;
if (errorTotals) {
if (errorTotals.negative.length) {
limits.min = Math.min(limits.min, sparseArrayLimits(errorTotals.negative).min);
}
if (errorTotals.positive.length) {
limits.max = Math.max(limits.max, sparseArrayLimits(errorTotals.positive).max);
}
}
if (limits.min !== MAX_VALUE || limits.max !== MIN_VALUE) {
limitsCache[key] = limits;
} else {
limits = null;
}
}
if (limits) {
this$1.valueAxisRanges[axisName] = limits;
}
}
}
};
CategoricalChart.prototype.addErrorBar = function addErrorBar (point, data, categoryIx) {
var value = point.value;
var series = point.series;
var seriesIx = point.seriesIx;
var errorBars = point.options.errorBars;
var lowValue = data.fields[ERROR_LOW_FIELD];
var highValue = data.fields[ERROR_HIGH_FIELD];
var errorRange;
if (isNumber(lowValue) && isNumber(highValue)) {
errorRange = { low: lowValue, high: highValue };
} else if (errorBars && defined(errorBars.value)) {
this.seriesErrorRanges = this.seriesErrorRanges || [];
this.seriesErrorRanges[seriesIx] = this.seriesErrorRanges[seriesIx] ||
new ErrorRangeCalculator(errorBars.value, series, VALUE);
errorRange = this.seriesErrorRanges[seriesIx].getErrorRange(value, errorBars.value);
}
if (errorRange) {
point.low = errorRange.low;
point.high = errorRange.high;
this.addPointErrorBar(point, categoryIx);
}
};
CategoricalChart.prototype.addPointErrorBar = function addPointErrorBar (point, categoryIx) {
var isVertical = !this.options.invertAxes;
var options = point.options.errorBars;
var series = point.series;
var low = point.low;
var high = point.high;
if (this.options.isStacked) {
var stackedErrorRange = this.stackedErrorRange(point, categoryIx);
low = stackedErrorRange.low;
high = stackedErrorRange.high;
} else {
var fields = { categoryIx: categoryIx, series: series };
this.updateRange({ value: low }, fields);
this.updateRange({ value: high }, fields);
}
var errorBar = new CategoricalErrorBar(low, high, isVertical, this, series, options);
point.errorBars = [ errorBar ];
point.append(errorBar);
};
CategoricalChart.prototype.stackedErrorRange = function stackedErrorRange (point, categoryIx) {
var plotValue = this.plotRange(point, 0)[1] - point.value;
var low = point.low + plotValue;
var high = point.high + plotValue;
this.errorTotals = this.errorTotals || { positive: [], negative: [] };
if (low < 0) {
this.errorTotals.negative[categoryIx] = Math.min(this.errorTotals.negative[categoryIx] || 0, low);
}
if (high > 0) {
this.errorTotals.positive[categoryIx] = Math.max(this.errorTotals.positive[categoryIx] || 0, high);
}
return { low: low, high: high };
};
CategoricalChart.prototype.addValue = function addValue (data, fields) {
var categoryIx = fields.categoryIx;
var series = fields.series;
var seriesIx = fields.seriesIx;
var categoryPoints = this.categoryPoints[categoryIx];
if (!categoryPoints) {
this.categoryPoints[categoryIx] = categoryPoints = [];
}
var seriesPoints = this.seriesPoints[seriesIx];
if (!seriesPoints) {
this.seriesPoints[seriesIx] = seriesPoints = [];
}
var point = this.createPoint(data, fields);
if (point) {
Object.assign(point, fields);
point.owner = this;
point.noteText = data.fields.noteText;
if (!defined(point.dataItem)) {
point.dataItem = series.data[categoryIx];
}
this.addErrorBar(point, data, categoryIx);
}
this.points.push(point);
seriesPoints.push(point);
categoryPoints.push(point);
this.updateRange(data.valueFields, fields);
};
CategoricalChart.prototype.evalPointOptions = function evalPointOptions (options, value, fields) {
var categoryIx = fields.categoryIx;
var category = fields.category;
var series = fields.series;
var seriesIx = fields.seriesIx;
var state = {
defaults: series._defaults,
excluded: [
"data", "aggregate", "_events", "tooltip", "content", "template",
"visual", "toggle", "_outOfRangeMinPoint", "_outOfRangeMaxPoint",
"drilldownSeriesFactory", "ariaTemplate", "ariaContent"
]
};
var doEval = this._evalSeries[seriesIx];
if (!defined(doEval)) {
this._evalSeries[seriesIx] = doEval = evalOptions(options, {}, state, true);
}
var pointOptions = options;
if (doEval) {
pointOptions = deepExtend({}, pointOptions);
evalOptions(pointOptions, {
value: value,
category: category,
index: categoryIx,
series: series,
dataItem: series.data[categoryIx]
}, state);
}
return pointOptions;
};
CategoricalChart.prototype.updateRange = function updateRange (data, fields) {
var axisName = fields.series.axis;
var value = data.value;
var axisRange = this.valueAxisRanges[axisName];
if (isFinite(value) && value !== null) {
axisRange = this.valueAxisRanges[axisName] =
axisRange || { min: MAX_VALUE, max: MIN_VALUE };
axisRange.min = Math.min(axisRange.min, value);
axisRange.max = Math.max(axisRange.max, value);
}
};
CategoricalChart.prototype.seriesValueAxis = function seriesValueAxis (series) {
var plotArea = this.plotArea;
var axisName = series.axis;
var axis = axisName ? plotArea.namedValueAxes[axisName] : plotArea.valueAxis;
if (!axis) {
throw new Error("Unable to locate value axis with name " + axisName);
}
return axis;
};
CategoricalChart.prototype.reflow = function reflow (targetBox) {
var this$1 = this;
var categorySlots = this.categorySlots = [];
var chartPoints = this.points;
var categoryAxis = this.categoryAxis;
var pointIx = 0;
this.traverseDataPoints(function (data, fields) {
var categoryIx = fields.categoryIx;
var currentSeries = fields.series;
var valueAxis = this$1.seriesValueAxis(currentSeries);
var point = chartPoints[pointIx++];
var categorySlot = categorySlots[categoryIx];
if (!categorySlot) {
categorySlots[categoryIx] = categorySlot =
this$1.categorySlot(categoryAxis, categoryIx, valueAxis);
}
if (point) {
var plotRange = this$1.plotRange(point, valueAxis.startValue());
var valueSlot = this$1.valueSlot(valueAxis, plotRange);
if (valueSlot) {
var pointSlot = this$1.pointSlot(categorySlot, valueSlot);
point.aboveAxis = this$1.aboveAxis(point, valueAxis);
point.stackValue = plotRange[1];
if (this$1.options.isStacked100) {
point.percentage = this$1.plotValue(point);
}
this$1.reflowPoint(point, pointSlot);
} else {
point.visible = false;
}
}
});
this.reflowCategories(categorySlots);
if (!this.options.clip && this.options.limitPoints && this.points.length) {
this.limitPoints();
}
this.box = targetBox;
};
CategoricalChart.prototype.valueSlot = function valueSlot (valueAxis, plotRange) {
return valueAxis.getSlot(plotRange[0], plotRange[1], !this.options.clip);
};
CategoricalChart.prototype.limitPoints = function limitPoints () {
var this$1 = this;
var categoryPoints = this.categoryPoints;
var points = categoryPoints[0].concat(last(categoryPoints));
for (var idx = 0; idx < points.length; idx++) {
if (points[idx]) {
this$1.limitPoint(points[idx]);
}
}
};
CategoricalChart.prototype.limitPoint = function limitPoint (point) {
var limitedSlot = this.categoryAxis.limitSlot(point.box);
if (!limitedSlot.equals(point.box)) {
point.reflow(limitedSlot);
}
};
CategoricalChart.prototype.aboveAxis = function aboveAxis (point, valueAxis) {
var axisCrossingValue = this.categoryAxisCrossingValue(valueAxis);
var value = point.value;
return valueAxis.options.reverse ?
value < axisCrossingValue : value >= axisCrossingValue;
};
CategoricalChart.prototype.categoryAxisCrossingValue = function categoryAxisCrossingValue (valueAxis) {
var categoryAxis = this.categoryAxis;
var options = valueAxis.options;
var crossingValues = [].concat(
options.axisCrossingValues || options.axisCrossingValue
);
return crossingValues[categoryAxis.axisIndex || 0] || 0;
};
CategoricalChart.prototype.reflowPoint = function reflowPoint (point, pointSlot) {
point.reflow(pointSlot);
};
CategoricalChart.prototype.reflowCategories = function reflowCategories () { };
CategoricalChart.prototype.pointSlot = function pointSlot (categorySlot, valueSlot) {
var options = this.options;
var invertAxes = options.invertAxes;
var slotX = invertAxes ? valueSlot : categorySlot;
var slotY = invertAxes ? categorySlot : valueSlot;
return new Box(slotX.x1, slotY.y1, slotX.x2, slotY.y2);
};
CategoricalChart.prototype.categorySlot = function categorySlot (categoryAxis, categoryIx) {
return categoryAxis.getSlot(categoryIx);
};
CategoricalChart.prototype.traverseDataPoints = function traverseDataPoints (callback) {
var this$1 = this;
var series = this.options.series;
var count = categoriesCount(series);
var seriesCount = series.length;
for (var seriesIx = 0; seriesIx < seriesCount; seriesIx++) {
this$1._outOfRangeCallback(series[seriesIx], "_outOfRangeMinPoint", seriesIx, callback);
}
for (var categoryIx = 0; categoryIx < count; categoryIx++) {
for (var seriesIx$1 = 0; seriesIx$1 < seriesCount; seriesIx$1++) {
var currentSeries = series[seriesIx$1];
var currentCategory = this$1.categoryAxis.categoryAt(categoryIx);
var pointData = this$1.plotArea.bindPoint(currentSeries, categoryIx);
callback(pointData, {
category: currentCategory,
categoryIx: categoryIx,
categoriesCount: count,
series: currentSeries,
seriesIx: seriesIx$1
});
}
}
for (var seriesIx$2 = 0; seriesIx$2 < seriesCount; seriesIx$2++) {
this$1._outOfRangeCallback(series[seriesIx$2], "_outOfRangeMaxPoint", seriesIx$2, callback);
}
};
CategoricalChart.prototype._outOfRangeCallback = function _outOfRangeCallback (series, field, seriesIx, callback) {
var outOfRangePoint = series[field];
if (outOfRangePoint) {
var categoryIx = outOfRangePoint.categoryIx;
var pointData = this.plotArea.bindPoint(series, categoryIx, outOfRangePoint.item);
callback(pointData, {
category: outOfRangePoint.category,
categoryIx: categoryIx,
series: series,
seriesIx: seriesIx,
dataItem: outOfRangePoint.item
});
}
};
CategoricalChart.prototype.formatPointValue = function formatPointValue (point, format) {
if (point.value === null) {
return "";
}
return this.chartService.format.auto(format, point.value);
};
CategoricalChart.prototype.pointValue = function pointValue (data) {
return data.valueFields.value;
};
return CategoricalChart;
}(ChartElement));
setDefaultOptions(CategoricalChart, {
series: [],
invertAxes: false,
isStacked: false,
clip: true,
limitPoints: true
});
export default CategoricalChart;