@progress/kendo-charts
Version:
Kendo UI platform-independent Charts library
657 lines (532 loc) • 20.5 kB
JavaScript
import { DomEventsBuilder } from '../services';
import { DateCategoryAxis, Point } from '../core';
import { MOUSEWHEEL_DELAY, MOUSEWHEEL, SELECT_START, SELECT, SELECT_END } from './constants';
import { LEFT, RIGHT, MIN_VALUE, MAX_VALUE, X } from '../common/constants';
import { addClass, Class, removeClass, eventCoordinates, deepExtend, elementStyles, eventElement, setDefaultOptions, limitValue, round, bindEvents, unbindEvents, mousewheelDelta, hasClasses } from '../common';
import { parseDate } from '../date-utils';
var ZOOM_ACCELERATION = 3;
var SELECTOR_HEIGHT_ADJUST = 0.1;
function createDiv(classNames) {
var element = document.createElement("div");
if (classNames) {
element.className = classNames;
}
return element;
}
function closestHandle(element) {
var current = element;
while (current && !hasClasses(current, "k-handle")) {
current = current.parentNode;
}
return current;
}
var Selection = (function (Class) {
function Selection(chart, categoryAxis, options, observer) {
Class.call(this);
var chartElement = chart.element;
this.options = deepExtend({}, this.options, options);
this.chart = chart;
this.observer = observer;
this.chartElement = chartElement;
this.categoryAxis = categoryAxis;
this._dateAxis = this.categoryAxis instanceof DateCategoryAxis;
this.initOptions();
this.visible = this.options.visible && chartElement.offsetHeight;
if (this.visible) {
this.createElements();
this.set(this._index(this.options.from), this._index(this.options.to));
this.bindEvents();
}
}
if ( Class ) Selection.__proto__ = Class;
Selection.prototype = Object.create( Class && Class.prototype );
Selection.prototype.constructor = Selection;
Selection.prototype.onPane = function onPane (pane) {
return this.categoryAxis.pane === pane;
};
Selection.prototype.createElements = function createElements () {
var options = this.options;
var wrapper = this.wrapper = createDiv("k-selector k-pointer-events-none");
elementStyles(wrapper, {
top: options.offset.top,
left: options.offset.left,
width: options.width,
height: options.height,
direction: 'ltr'
});
var selection = this.selection = createDiv("k-selection k-pointer-events-none");
this.leftMask = createDiv("k-mask k-pointer-events-none");
this.rightMask = createDiv("k-mask k-pointer-events-none");
wrapper.appendChild(this.leftMask);
wrapper.appendChild(this.rightMask);
wrapper.appendChild(selection);
var body = this.body = createDiv("k-selection-bg k-pointer-events-none");
selection.appendChild(body);
var leftHandle = this.leftHandle = createDiv("k-handle k-left-handle k-pointer-events-auto");
var rightHandle = this.rightHandle = createDiv("k-handle k-right-handle k-pointer-events-auto");
leftHandle.appendChild(createDiv());
rightHandle.appendChild(createDiv());
selection.appendChild(leftHandle);
selection.appendChild(rightHandle);
this.chartElement.appendChild(wrapper);
var selectionStyles = elementStyles(selection, [ "borderLeftWidth", "borderRightWidth", "height" ]);
var leftHandleHeight = elementStyles(leftHandle, "height").height;
var rightHandleHeight = elementStyles(rightHandle, "height").height;
options.selection = {
border: {
left: selectionStyles.borderLeftWidth,
right: selectionStyles.borderRightWidth
}
};
elementStyles(leftHandle, {
top: (selectionStyles.height - leftHandleHeight) / 2
});
elementStyles(rightHandle, {
top: (selectionStyles.height - rightHandleHeight) / 2
});
/* eslint no-self-assign: "off" */
wrapper.style.cssText = wrapper.style.cssText;
};
Selection.prototype.bindEvents = function bindEvents$1 () {
var obj;
if (this.options.mousewheel !== false) {
this._mousewheelHandler = this._mousewheel.bind(this);
bindEvents(this.chartElement, ( obj = {}, obj[ MOUSEWHEEL ] = this._mousewheelHandler, obj ));
}
this._domEvents = DomEventsBuilder.create(this.chartElement, {
stopPropagation: true, // applicable for the jQuery UserEvents
start: this._start.bind(this),
move: this._move.bind(this),
end: this._end.bind(this),
tap: this._tap.bind(this),
press: this._press.bind(this),
gesturestart: this._gesturestart.bind(this),
gesturechange: this._gesturechange.bind(this),
gestureend: this._gestureend.bind(this)
});
};
Selection.prototype.initOptions = function initOptions () {
var ref = this;
var options = ref.options;
var categoryAxis = ref.categoryAxis;
var box = categoryAxis.pane.chartsBox();
var intlService = this.chart.chartService.intl;
if (this._dateAxis) {
deepExtend(options, {
min: parseDate(intlService, options.min),
max: parseDate(intlService, options.max),
from: parseDate(intlService, options.from),
to: parseDate(intlService, options.to)
});
}
var ref$1 = elementStyles(this.chartElement, [ "paddingLeft", "paddingTop" ]);
var paddingLeft = ref$1.paddingLeft;
var paddingTop = ref$1.paddingTop;
this.options = deepExtend({}, {
width: box.width(),
height: box.height() + SELECTOR_HEIGHT_ADJUST, //workaround for sub-pixel hover on the paths in chrome
padding: {
left: paddingLeft,
top: paddingTop
},
offset: {
left: box.x1 + paddingLeft,
top: box.y1 + paddingTop
},
from: options.min,
to: options.max
}, options);
};
Selection.prototype.destroy = function destroy () {
var obj;
if (this._domEvents) {
this._domEvents.destroy();
delete this._domEvents;
}
clearTimeout(this._mwTimeout);
this._state = null;
if (this.wrapper) {
if (this._mousewheelHandler) {
unbindEvents(this.chartElement, ( obj = {}, obj[ MOUSEWHEEL ] = this._mousewheelHandler, obj ));
this._mousewheelHandler = null;
}
this.chartElement.removeChild(this.wrapper);
this.wrapper = null;
}
};
Selection.prototype._rangeEventArgs = function _rangeEventArgs (range) {
return {
axis: this.categoryAxis.options,
from: this._value(range.from),
to: this._value(range.to)
};
};
Selection.prototype._pointInPane = function _pointInPane (x, y) {
var paneBox = this.categoryAxis.pane.box;
var modelCoords = this.chart._toModelCoordinates(x, y);
return paneBox.containsPoint(modelCoords);
};
Selection.prototype._start = function _start (e) {
var options = this.options;
var target = eventElement(e);
if (this._state || !target) {
return;
}
var coords = eventCoordinates(e);
var inPane = this._pointInPane(coords.x, coords.y);
if (!inPane) {
return;
}
var handle = closestHandle(target);
var bodyRect = this.body.getBoundingClientRect();
var inBody = !handle && coords.x >= bodyRect.x && coords.x <= bodyRect.x + bodyRect.width &&
coords.y >= bodyRect.y && coords.y <= bodyRect.y + bodyRect.height;
this.chart._unsetActivePoint();
this._state = {
moveTarget: handle,
startLocation: e.x ? e.x.location : 0,
inBody: inBody,
range: {
from: this._index(options.from),
to: this._index(options.to)
}
};
var args = this._rangeEventArgs({
from: this._index(options.from),
to: this._index(options.to)
});
if (this.trigger(SELECT_START, args)) {
this._state = null;
}
};
Selection.prototype._press = function _press (e) {
var handle;
if (this._state) {
handle = this._state.moveTarget;
} else {
handle = closestHandle(eventElement(e));
}
if (handle) {
addClass(handle, "k-handle-active");
}
};
Selection.prototype._move = function _move (e) {
if (!this._state) {
return;
}
var ref = this;
var state = ref._state;
var options = ref.options;
var categoryAxis = ref.categoryAxis;
var range = state.range;
var target = state.moveTarget;
var reverse = categoryAxis.options.reverse;
var from = this._index(options.from);
var to = this._index(options.to);
var min = this._index(options.min);
var max = this._index(options.max);
var delta = state.startLocation - e.x.location;
var oldRange = { from: range.from, to: range.to };
var span = range.to - range.from;
var scale = elementStyles(this.wrapper, "width").width / (categoryAxis.categoriesCount() - 1);
var offset = Math.round(delta / scale) * (reverse ? -1 : 1);
if (!target && !state.inBody) {
return;
}
var leftHandle = target && hasClasses(target, "k-left-handle");
var rightHandle = target && hasClasses(target, "k-right-handle");
if (state.inBody) {
range.from = Math.min(
Math.max(min, from - offset),
max - span
);
range.to = Math.min(
range.from + span,
max
);
} else if ((leftHandle && !reverse) || (rightHandle && reverse)) {
range.from = Math.min(
Math.max(min, from - offset),
max - 1
);
range.to = Math.max(range.from + 1, range.to);
} else if ((leftHandle && reverse) || (rightHandle && !reverse)) {
range.to = Math.min(
Math.max(min + 1, to - offset),
max
);
range.from = Math.min(range.to - 1, range.from);
}
if (range.from !== oldRange.from || range.to !== oldRange.to) {
this.move(range.from, range.to);
this.trigger(SELECT, this._rangeEventArgs(range));
}
};
Selection.prototype._end = function _end () {
if (this._state) {
var moveTarget = this._state.moveTarget;
if (moveTarget) {
removeClass(moveTarget, "k-handle-active");
}
var range = this._state.range;
this.set(range.from, range.to);
this.trigger(SELECT_END, this._rangeEventArgs(range));
delete this._state;
}
};
Selection.prototype._tap = function _tap (e) {
var ref = this;
var options = ref.options;
var categoryAxis = ref.categoryAxis;
var coords = this.chart._eventCoordinates(e);
var categoryIx = categoryAxis.pointCategoryIndex(new Point(coords.x, categoryAxis.box.y1));
var from = this._index(options.from);
var to = this._index(options.to);
var min = this._index(options.min);
var max = this._index(options.max);
var span = to - from;
var mid = from + span / 2;
var range = {};
var rightClick = e.event.which === 3;
var offset = Math.round(mid - categoryIx);
if (this._state || rightClick) {
return;
}
this.chart._unsetActivePoint();
if (!categoryAxis.options.justified) {
offset--;
}
range.from = Math.min(
Math.max(min, from - offset),
max - span
);
range.to = Math.min(range.from + span, max);
this._start(e);
if (this._state) {
this._state.range = range;
this.trigger(SELECT, this._rangeEventArgs(range));
this._end();
}
};
Selection.prototype._mousewheel = function _mousewheel (e) {
var this$1 = this;
var delta = mousewheelDelta(e);
this._start(e);
if (this._state) {
var range = this._state.range;
e.preventDefault();
e.stopPropagation();
if (Math.abs(delta) > 1) {
delta *= ZOOM_ACCELERATION;
}
if (this.options.mousewheel.reverse) {
delta *= -1;
}
if (this.expand(delta)) {
this.trigger(SELECT, {
axis: this.categoryAxis.options,
delta: delta,
originalEvent: e,
from: this._value(range.from),
to: this._value(range.to)
});
}
if (this._mwTimeout) {
clearTimeout(this._mwTimeout);
}
this._mwTimeout = setTimeout(function () {
this$1._end();
}, MOUSEWHEEL_DELAY);
}
};
Selection.prototype._gesturestart = function _gesturestart (e) {
var options = this.options;
var touch = e.touches[0];
var inPane = this._pointInPane(touch.pageX, touch.pageY);
if (!inPane) {
return;
}
this._state = {
range: {
from: this._index(options.from),
to: this._index(options.to)
}
};
var args = this._rangeEventArgs(this._state.range);
if (this.trigger(SELECT_START, args)) {
this._state = null;
} else {
e.preventDefault();
}
};
Selection.prototype._gestureend = function _gestureend () {
if (this._state) {
this.trigger(SELECT_END, this._rangeEventArgs(this._state.range));
delete this._state;
}
};
Selection.prototype._gesturechange = function _gesturechange (e) {
if (!this._state) {
return;
}
var ref = this;
var chart = ref.chart;
var state = ref._state;
var options = ref.options;
var categoryAxis = ref.categoryAxis;
var range = state.range;
var p0 = chart._toModelCoordinates(e.touches[0].x.location).x;
var p1 = chart._toModelCoordinates(e.touches[1].x.location).x;
var left = Math.min(p0, p1);
var right = Math.max(p0, p1);
e.preventDefault();
range.from = categoryAxis.pointCategoryIndex(new Point(left)) || options.min;
range.to = categoryAxis.pointCategoryIndex(new Point(right)) || options.max;
this.move(range.from, range.to);
this.trigger(SELECT, this._rangeEventArgs(range));
};
Selection.prototype._index = function _index (value) {
var index = value;
if (value instanceof Date) {
index = this.categoryAxis.categoryIndex(value);
}
return index;
};
Selection.prototype._value = function _value (index) {
var value = index;
if (this._dateAxis) {
value = this.categoryAxis.categoryAt(index);
if (value > this.options.max) {
value = this.options.max;
}
}
return value;
};
Selection.prototype._slot = function _slot (value) {
var categoryAxis = this.categoryAxis;
var index = this._index(value);
return categoryAxis.getSlot(index, index, true);
};
Selection.prototype.move = function move (from, to) {
var options = this.options;
var reverse = this.categoryAxis.options.reverse;
var offset = options.offset;
var padding = options.padding;
var border = options.selection.border;
var left = reverse ? to : from;
var right = reverse ? from : to;
var edge = 'x' + (reverse ? 2 : 1);
var box = this._slot(left);
var leftMaskWidth = round(box[edge] - offset.left + padding.left);
elementStyles(this.leftMask, {
width: leftMaskWidth
});
elementStyles(this.selection, {
left: leftMaskWidth
});
box = this._slot(right);
var rightMaskWidth = round(options.width - (box[edge] - offset.left + padding.left));
elementStyles(this.rightMask, {
width: rightMaskWidth
});
var distance = options.width - rightMaskWidth;
if (distance !== options.width) {
distance += border.right;
}
elementStyles(this.rightMask, {
left: distance
});
elementStyles(this.selection, {
width: Math.max(options.width - (leftMaskWidth + rightMaskWidth) - border.right, 0)
});
};
Selection.prototype.set = function set (from, to) {
var options = this.options;
var min = this._index(options.min);
var max = this._index(options.max);
var fromValue = limitValue(this._index(from), min, max);
var toValue = limitValue(this._index(to), fromValue + 1, max);
if (options.visible) {
this.move(fromValue, toValue);
}
options.from = this._value(fromValue);
options.to = this._value(toValue);
};
Selection.prototype.expand = function expand (delta) {
var options = this.options;
var min = this._index(options.min);
var max = this._index(options.max);
var zDir = options.mousewheel.zoom;
var from = this._index(options.from);
var to = this._index(options.to);
var range = { from: from, to: to };
var oldRange = deepExtend({}, range);
if (this._state) {
range = this._state.range;
}
if (zDir !== RIGHT) {
range.from = limitValue(
limitValue(from - delta, 0, to - 1),
min, max
);
}
if (zDir !== LEFT) {
range.to = limitValue(
limitValue(to + delta, range.from + 1, max),
min,
max
);
}
if (range.from !== oldRange.from || range.to !== oldRange.to) {
this.set(range.from, range.to);
return true;
}
};
Selection.prototype.zoom = function zoom (delta, coords) {
var options = this.options;
var min = this._index(options.min);
var max = this._index(options.max);
var from = this._index(options.from);
var to = this._index(options.to);
var range = { from: from, to: to };
var oldRange = deepExtend({}, range);
var ref = this.categoryAxis.options;
var reverse = ref.reverse;
var origin = X + (reverse ? '2' : '1');
var lineBox = this.categoryAxis.lineBox();
var relative = Math.abs(lineBox[origin] - coords[X]);
var size = lineBox.width();
var position = round(relative / size, 2);
var minDelta = round(position * delta);
var maxDelta = round((1 - position) * delta);
if (this._state) {
range = this._state.range;
}
range.from = limitValue(
limitValue(from - minDelta, 0, to - 1),
min, max
);
range.to = limitValue(
limitValue(to + maxDelta, range.from + 1, max),
min,
max
);
if (range.from !== oldRange.from || range.to !== oldRange.to) {
this.set(range.from, range.to);
return true;
}
};
Selection.prototype.trigger = function trigger (name, args) {
return (this.observer || this.chart).trigger(name, args);
};
return Selection;
}(Class));
setDefaultOptions(Selection, {
visible: true,
mousewheel: {
zoom: "both"
},
min: MIN_VALUE,
max: MAX_VALUE
});
export default Selection;