@progress/kendo-charts
Version:
Kendo UI platform-independent Charts library
348 lines (277 loc) • 10.2 kB
JavaScript
import Axis from './axis';
import AxisLabel from './axis-label';
import Box from './box';
import { BLACK, DEFAULT_PRECISION, COORD_PRECISION } from '../common/constants';
import { deepExtend, defined, limitValue, round, setDefaultOptions, valueOrDefault } from '../common';
import autoMajorUnit from './utils/auto-major-unit';
import autoAxisMin from './utils/auto-axis-min';
import autoAxisMax from './utils/auto-axis-max';
import floor from './utils/floor';
import ceil from './utils/ceil';
import limitCoordinate from './utils/limit-coordinate';
const MIN_VALUE_RANGE = Math.pow(10, -DEFAULT_PRECISION + 1);
class NumericAxis extends Axis {
constructor(seriesMin, seriesMax, options, chartService) {
super(Object.assign({}, options, {
seriesMin: seriesMin,
seriesMax: seriesMax
}), chartService);
}
initUserOptions(options) {
const autoOptions = autoAxisOptions(options.seriesMin, options.seriesMax, options);
this.totalOptions = totalAxisOptions(autoOptions, options);
return axisOptions(autoOptions, options);
}
initFields() {
this.totalMin = this.totalOptions.min;
this.totalMax = this.totalOptions.max;
this.totalMajorUnit = this.totalOptions.majorUnit;
this.seriesMin = this.options.seriesMin;
this.seriesMax = this.options.seriesMax;
}
clone() {
return new NumericAxis(
this.seriesMin,
this.seriesMax,
Object.assign({}, this.options),
this.chartService
);
}
startValue() {
return 0;
}
range() {
const options = this.options;
return { min: options.min, max: options.max };
}
getDivisions(stepValue) {
if (stepValue === 0) {
return 1;
}
const options = this.options;
const range = options.max - options.min;
return Math.floor(round(range / stepValue, COORD_PRECISION)) + 1;
}
getTickPositions(unit, skipUnit) {
const options = this.options;
const { axisDir, axisOrigin, lineBox, lineSize } = this.lineInfo();
const range = options.max - options.min;
const scale = lineSize / range;
const step = unit * scale;
const divisions = this.getDivisions(unit);
const positions = [];
let pos = lineBox[axisOrigin];
let skipStep = 0;
if (skipUnit) {
skipStep = skipUnit / unit;
}
for (let idx = 0; idx < divisions; idx++) {
if (idx % skipStep !== 0) {
positions.push(round(pos, COORD_PRECISION));
}
pos = pos + step * axisDir;
}
return positions;
}
getMajorTickPositions() {
return this.getTickPositions(this.options.majorUnit);
}
getMinorTickPositions() {
return this.getTickPositions(this.options.minorUnit);
}
getSlot(a, b, limit = false) {
const options = this.options;
const { axis, axisDir, lineBox, lineSize, lineStart } = this.lineInfo();
const step = axisDir * (lineSize / (options.max - options.min));
let start = valueOrDefault(a, b || 0);
let end = valueOrDefault(b, a || 0);
if (limit) {
start = limitValue(start, options.min, options.max);
end = limitValue(end, options.min, options.max);
}
const p1 = Math.min(start, end) - options.min;
const p2 = Math.max(start, end) - options.min;
const slotBox = new Box(lineBox.x1, lineBox.y1, lineBox.x1, lineBox.y1);
slotBox[axis + 1] = limitCoordinate(lineStart + step * (axisDir > 0 ? p1 : p2));
slotBox[axis + 2] = limitCoordinate(lineStart + step * (axisDir > 0 ? p2 : p1));
return slotBox;
}
getValue(point) {
const options = this.options;
const max = Number(options.max);
const min = Number(options.min);
const offset = this.pointOffset(point);
const valueOffset = offset * (max - min);
if (offset < 0 || offset > 1) {
return null;
}
const value = min + valueOffset;
return round(value, DEFAULT_PRECISION);
}
translateRange(delta) {
const options = this.options;
const { vertical, reverse, max, min } = options;
const { lineSize } = this.lineInfo();
const range = max - min;
const scale = lineSize / range;
let offset = round(delta / scale, DEFAULT_PRECISION);
if ((vertical || reverse) && !(vertical && reverse )) {
offset = -offset;
}
return {
min: min + offset,
max: max + offset,
offset: offset
};
}
labelsCount() {
return this.getDivisions(this.options.majorUnit);
}
createAxisLabel(index, labelOptions, labelContext) {
const options = this.options;
const value = round(options.min + (index * options.majorUnit), DEFAULT_PRECISION);
const text = this.axisLabelText(value, labelOptions, labelContext);
return new AxisLabel(value, text, index, null, labelOptions);
}
shouldRenderNote(value) {
const range = this.range();
return range.min <= value && value <= range.max;
}
pan(delta) {
const range = this.translateRange(delta);
return this.limitRange(range.min, range.max, this.totalMin, this.totalMax, range.offset);
}
pointsRange(start, end) {
const startValue = this.getValue(start);
const endValue = this.getValue(end);
const min = Math.min(startValue, endValue);
const max = Math.max(startValue, endValue);
if (this.isValidRange(min, max)) {
return {
min: min,
max: max
};
}
}
scaleRange(scale, cursor) {
const position = Math.abs(this.pointOffset(cursor));
const range = this.options.max - this.options.min;
const delta = this.scaleToDelta(scale, range);
const minDelta = position * delta;
const maxDelta = (1 - position) * delta;
const min = round(this.options.min + minDelta, DEFAULT_PRECISION);
let max = round(this.options.max - maxDelta, DEFAULT_PRECISION);
if (max - min < MIN_VALUE_RANGE) {
max = min + MIN_VALUE_RANGE;
}
return {
min: min,
max: max
};
}
zoomRange(scale, cursor) {
const { totalMin, totalMax } = this;
const range = this.scaleRange(scale, cursor);
return {
min: limitValue(range.min, totalMin, totalMax),
max: limitValue(range.max, totalMin, totalMax),
narrowRange: false
};
}
isValidRange(min, max) {
return max - min > MIN_VALUE_RANGE;
}
}
function autoAxisOptions(seriesMin, seriesMax, options) {
const narrowRange = options.narrowRange;
let autoMin = autoAxisMin(seriesMin, seriesMax, narrowRange);
let autoMax = autoAxisMax(seriesMin, seriesMax, narrowRange);
const majorUnit = autoMajorUnit(autoMin, autoMax);
const autoOptions = {
majorUnit: majorUnit
};
if (options.roundToMajorUnit !== false) {
if (autoMin < 0 && remainderClose(autoMin, majorUnit, 1 / 3)) {
autoMin -= majorUnit;
}
if (autoMax > 0 && remainderClose(autoMax, majorUnit, 1 / 3)) {
autoMax += majorUnit;
}
}
autoOptions.min = floor(autoMin, majorUnit);
autoOptions.max = ceil(autoMax, majorUnit);
return autoOptions;
}
function totalAxisOptions(autoOptions, options) {
return {
min: defined(options.min) ? Math.min(autoOptions.min, options.min) : autoOptions.min,
max: defined(options.max) ? Math.max(autoOptions.max, options.max) : autoOptions.max,
majorUnit: autoOptions.majorUnit
};
}
function clearNullValues(options, fields) {
for (let idx = 0; idx < fields.length; idx++) {
const field = fields[idx];
if (options[field] === null) {
options[field] = undefined;
}
}
}
function axisOptions(autoOptions, userOptions) {
let options = userOptions;
let userSetMin, userSetMax;
if (userOptions) {
clearNullValues(userOptions, [ 'min', 'max' ]);
userSetMin = defined(userOptions.min);
userSetMax = defined(userOptions.max);
const userSetLimits = userSetMin || userSetMax;
if (userSetLimits) {
if (userOptions.min === userOptions.max) {
if (userOptions.min > 0) {
userOptions.min = 0;
} else {
userOptions.max = 1;
}
}
}
if (userOptions.majorUnit) {
autoOptions.min = floor(autoOptions.min, userOptions.majorUnit);
autoOptions.max = ceil(autoOptions.max, userOptions.majorUnit);
} else if (userSetLimits) {
options = deepExtend(autoOptions, userOptions);
// Determine an auto major unit after min/max have been set
autoOptions.majorUnit = autoMajorUnit(options.min, options.max);
}
}
autoOptions.minorUnit = (options.majorUnit || autoOptions.majorUnit) / 5;
const result = deepExtend(autoOptions, options);
if (result.min >= result.max) {
if (userSetMin && !userSetMax) {
result.max = result.min + result.majorUnit;
} else if (!userSetMin && userSetMax) {
result.min = result.max - result.majorUnit;
}
}
return result;
}
function remainderClose(value, divisor, ratio) {
const remainder = round(Math.abs(value % divisor), DEFAULT_PRECISION);
const threshold = divisor * (1 - ratio);
return remainder === 0 || remainder > threshold;
}
setDefaultOptions(NumericAxis, {
type: "numeric",
min: 0,
max: 1,
vertical: true,
majorGridLines: {
visible: true,
width: 1,
color: BLACK
},
labels: {
format: "#.####################"
},
zIndex: 1
});
export default NumericAxis;