Shivam Dave 5be6e90388 [Mobile] Clean Up
Commented out console calls from gesture
pinch/plot classes. Also allowed pan with
single finger.
2015-09-01 11:53:41 -07:00

447 lines
19 KiB
JavaScript

/*global define,window*/
define(
[
'../lib/utils'
],
function (utils) {
"use strict";
var RANGE_TICK_COUNT = 7,
DOMAIN_TICK_COUNT = 5,
ZOOM_AMT = 0.02;
function MCTPlot() {
function link($scope, $element) {
// Now that we're here, let's handle some scope management that the controller would otherwise handle.
if (typeof $scope.rectangles === "undefined") {
$scope.rectangles = [];
}
if (typeof $scope.displayableRange === "undefined") {
$scope.displayableRange = function (x) { return x; };
}
if (typeof $scope.displayableDomain === "undefined") {
$scope.displayableDomain = function (x) { return x; };
}
if (typeof $scope.axes === "undefined") {
$scope.axes = {
domain: {
label: "Time",
tickCount: DOMAIN_TICK_COUNT,
ticks: []
},
range: {
label: "Value",
tickCount: RANGE_TICK_COUNT,
ticks: []
}
};
}
var dragStart,
marqueeBox = {},
marqueeRect, // Set when exists.
chartElementBounds,
firstTouches,
firstTouch,
firstTouchDistance,
lastTouchDistance,
$canvas = $element.find('canvas');
function updateAxesForCurrentViewport() {
// Update axes definitions for current viewport.
['domain', 'range'].forEach(function (axisName) {
var axis = $scope.axes[axisName],
firstTick = $scope.viewport.topLeft[axisName],
lastTick = $scope.viewport.bottomRight[axisName],
axisSize = firstTick - lastTick,
denominator = axis.tickCount - 1,
tickNumber,
tickIncrement,
tickValue;
// Yes, ticksize is negative for domain and positive for range.
// It's because ticks are generated/displayed top to bottom and left to right.
axis.ticks = [];
for (tickNumber = 0; tickNumber < axis.tickCount; tickNumber = tickNumber + 1) {
tickIncrement = (axisSize * (tickNumber / denominator));
tickValue = firstTick - tickIncrement;
axis.ticks.push(
tickValue
);
}
});
}
function drawMarquee() {
// Create rectangle for Marquee if it should be set.
if (marqueeBox && marqueeBox.start && marqueeBox.end) {
if (!marqueeRect) {
marqueeRect = {};
$scope.rectangles.push(marqueeRect);
}
marqueeRect.start = marqueeBox.start;
marqueeRect.end = marqueeBox.end;
marqueeRect.color = [1, 1, 1, 0.5];
marqueeRect.layer = 'top'; // TODO: implement this.
$scope.$broadcast('rectangle-change');
} else if (marqueeRect && $scope.rectangles.indexOf(marqueeRect) !== -1) {
$scope.rectangles.splice($scope.rectangles.indexOf(marqueeRect));
marqueeRect = undefined;
$scope.$broadcast('rectangle-change');
}
}
function untrackMousePosition() {
$scope.mouseCoordinates = undefined;
}
function updateMarquee() {
// Update the marquee box in progress.
marqueeBox.end = $scope.mouseCoordinates.positionAsPlotPoint;
drawMarquee();
}
function startMarquee() {
marqueeBox.start = $scope.mouseCoordinates.positionAsPlotPoint;
}
function endMarquee() {
// marqueeBox start/end are opposite corners but we need
// topLeft and bottomRight.
var boxPoints = utils.boxPointsFromOppositeCorners(marqueeBox.start, marqueeBox.end),
newViewport = utils.oppositeCornersFromBoxPoints(boxPoints);
marqueeBox = {};
drawMarquee();
$scope.$emit('user:viewport:change:end', newViewport);
$scope.viewport = newViewport;
}
function startDrag($event) {
$scope.$emit('user:viewport:change:start');
if (!$scope.mouseCoordinates) {
return;
}
$event.preventDefault();
// Track drag location relative to position over element
// not domain, as chart viewport will change as we drag.
dragStart = $scope.mouseCoordinates.positionAsPlotPoint;
// Tell controller that we're starting to navigate.
return false;
}
function updateDrag() {
// calculate offset between points. Apply that offset to viewport.
var newPosition = $scope.mouseCoordinates.positionAsPlotPoint,
dDomain = dragStart.domain - newPosition.domain,
dRange = dragStart.range - newPosition.range;
$scope.viewport = {
topLeft: {
domain: $scope.viewport.topLeft.domain + dDomain,
range: $scope.viewport.topLeft.range + dRange
},
bottomRight: {
domain: $scope.viewport.bottomRight.domain + dDomain,
range: $scope.viewport.bottomRight.range + dRange
}
};
}
function endDrag() {
dragStart = undefined;
$scope.$emit('user:viewport:change:end', $scope.viewport);
}
function trackTouchPosition(touchPosition, bounds) {
var positionOverElement,
positionAsPlotPoint,
position;
chartElementBounds = bounds;
positionOverElement = {
x: touchPosition.clientX - bounds.left,
y: touchPosition.clientY - bounds.top
};
positionAsPlotPoint = utils.elementPositionAsPlotPosition(
positionOverElement,
bounds,
$scope.viewport
);
position = {
positionOverElement: positionOverElement,
positionAsPlotPoint: positionAsPlotPoint
};
return position;
}
function trackMousePosition($event) {
// Calculate coordinates of mouse related to canvas and as
// domain, range value and make available in scope for display.
var bounds = $event.target.getBoundingClientRect(),
positionOverElement,
positionAsPlotPoint;
chartElementBounds = bounds;
positionOverElement = {
x: $event.clientX - bounds.left,
y: $event.clientY - bounds.top
};
positionAsPlotPoint = utils.elementPositionAsPlotPosition(
positionOverElement,
bounds,
$scope.viewport
);
$scope.mouseCoordinates = {
positionOverElement: positionOverElement,
positionAsPlotPoint: positionAsPlotPoint
};
if (marqueeBox && marqueeBox.start) {
updateMarquee();
}
if (dragStart) {
updateDrag();
}
}
function watchForMarquee() {
$canvas.removeClass('plot-drag');
$canvas.addClass('plot-marquee');
$canvas.on('mousedown', startMarquee);
$canvas.on('mouseup', endMarquee);
$canvas.off('mousedown', startDrag);
$canvas.off('mouseup', endDrag);
}
function watchForDrag() {
$canvas.addClass('plot-drag');
$canvas.removeClass('plot-marquee');
$canvas.on('mousedown', startDrag);
$canvas.on('mouseup', endDrag);
$canvas.off('mousedown', startMarquee);
$canvas.off('mouseup', endMarquee);
}
function toggleInteractionMode(event) {
if (event.keyCode === 16) { // shift key.
watchForDrag();
}
}
function resetInteractionMode(event) {
if (event.keyCode === 16) {
watchForMarquee();
}
}
function stopWatching() {
$canvas.off('mousedown', startDrag);
$canvas.off('mouseup', endDrag);
$canvas.off('mousedown', startMarquee);
$canvas.off('mouseup', endMarquee);
window.removeEventListener('keydown', toggleInteractionMode);
window.removeEventListener('keyup', resetInteractionMode);
}
$canvas.on('mousemove', trackMousePosition);
$canvas.on('mouseleave', untrackMousePosition);
watchForMarquee();
window.addEventListener('keydown', toggleInteractionMode);
window.addEventListener('keyup', resetInteractionMode);
function onViewportChange() {
if ($scope.mouseCoordinates && chartElementBounds) {
$scope.mouseCoordinates.positionAsPlotPoint =
utils.elementPositionAsPlotPosition(
$scope.mouseCoordinates.positionOverElement,
chartElementBounds,
$scope.viewport
);
}
// TODO: Discuss whether marqueeBox start should be fixed to data or fixed to canvas element, especially when "isLive is true".
updateAxesForCurrentViewport();
}
function calculateDistance(coordOne, coordTwo) {
return Math.sqrt(Math.pow(coordOne.clientX - coordTwo.clientX, 2) +
Math.pow(coordOne.clientY - coordTwo.clientY, 2));
}
function setDR(midpoint) {
return {
tl: {
domain: Math.abs(midpoint.domain - ($scope.viewport.topLeft.domain)),
range: Math.abs(midpoint.range - ($scope.viewport.topLeft.range))
},
br: {
domain: Math.abs(($scope.viewport.bottomRight.domain) - midpoint.domain),
range: Math.abs(($scope.viewport.bottomRight.range) - midpoint.range)
}
};
}
function calculateViewport(midpoint, touchPosition, ratio) {
var tl,
br,
drSet = setDR(midpoint);
if (ratio < 1) {
tl = {
domain: ZOOM_AMT * drSet.tl.domain,
range: ZOOM_AMT * drSet.tl.range
};
br = {
domain: ZOOM_AMT * drSet.br.domain,
range: ZOOM_AMT * drSet.br.range
};
} else if (ratio > 1) {
tl = {
domain: - ZOOM_AMT * drSet.tl.domain,
range: - ZOOM_AMT * drSet.tl.range
};
br = {
domain: - ZOOM_AMT * drSet.br.domain,
range: - ZOOM_AMT * drSet.br.range
};
}
return {
topLeft: {
domain: (($scope.viewport.topLeft.domain) + tl.domain),
range: (($scope.viewport.topLeft.range) - tl.range)
},
bottomRight: {
domain: (($scope.viewport.bottomRight.domain) - br.domain),
range: (($scope.viewport.bottomRight.range) + br.range)
}
};
}
function updateZoom(midpoint, bounds, touches, distance) {
// calculate offset between points. Apply that offset to viewport.
var midpointPosition = trackTouchPosition(midpoint, bounds),
newMidpointPosition = midpointPosition.positionAsPlotPoint,
newTouchPosition = [trackTouchPosition(touches[0], bounds).positionAsPlotPoint,
trackTouchPosition(touches[1], bounds).positionAsPlotPoint],
distanceRatio = lastTouchDistance / distance || firstTouchDistance / distance,
newViewport = calculateViewport(newMidpointPosition, newTouchPosition, distanceRatio);
$scope.viewport = newViewport;
}
function startZoom(midpoint, bounds, touches, distance) {
firstTouches = [trackTouchPosition(touches[0], bounds).positionAsPlotPoint,
trackTouchPosition(touches[1], bounds).positionAsPlotPoint];
firstTouchDistance = distance;
firstTouch = trackTouchPosition(midpoint, bounds).positionAsPlotPoint;
}
function updatePan(touch, bounds) {
// calculate offset between points. Apply that offset to viewport.
var panPosition = trackTouchPosition(touch, bounds),
newPanPosition = panPosition.positionAsPlotPoint,
dDomain = firstTouch.domain - newPanPosition.domain,
dRange = firstTouch.range - newPanPosition.range;
$scope.viewport = {
topLeft: {
domain: (($scope.viewport.topLeft.domain) + dDomain),
range: (($scope.viewport.topLeft.range) + dRange)
},
bottomRight: {
domain: (($scope.viewport.bottomRight.domain) + dDomain),
range: (($scope.viewport.bottomRight.range) + dRange)
}
};
}
function startPan(touch, bounds) {
$scope.$emit('user:viewport:change:start');
firstTouch = trackTouchPosition(touch, bounds).positionAsPlotPoint;
}
function endTouch() {
$scope.$emit('user:viewport:change:end', $scope.viewport);
}
function onPinchStart(event, touch) {
$scope.$emit('user:viewport:change:start');
startZoom(touch.midpoint, touch.bounds, touch.touches, touch.distance);
}
function comparePinchDrag(distance, firstDistance, lastDistance) {
var amt = 2;
return (((firstDistance += amt) >= distance) && ((firstDistance -= amt) <= distance))
|| (((lastDistance += amt) >= distance) && ((lastDistance -= amt) <= distance));
}
function onPinchChange(event, touch) {
//console.log(Math.round(touch.distance));
if(comparePinchDrag(Math.round(touch.distance), Math.round(firstTouchDistance),
Math.round(lastTouchDistance))) {
//console.log("# PINCH PAN");
updatePan(touch.midpoint, touch.bounds);
} else {
//console.log("# PINCH ZOOM");
updateZoom(touch.midpoint, touch.bounds, touch.touches, touch.distance);
}
lastTouchDistance = touch.distance;
}
function onPanStart(event, touch) {
startPan(touch.touch, touch.bounds);
}
function onPanChange(event, touch) {
updatePan(touch.touch, touch.bounds);
}
function onTouchEnd(event) {
endTouch();
}
$scope.$watchCollection('viewport', onViewportChange);
$scope.$on('mct:pan:start', onPanStart);
$scope.$on('mct:pan:change', onPanChange);
$scope.$on('mct:pinch:start', onPinchStart);
$scope.$on('mct:pinch:change', onPinchChange);
$scope.$on('mct:ptouch:end', onTouchEnd);
$scope.$on('$destroy', stopWatching);
}
return {
restrict: "E",
templateUrl: 'platform/features/plot-reborn/res/templates/mct-plot.html',
link: link,
scope: {
viewport: "=",
series: "=",
rectangles: "=?",
axes: "=?",
displayableRange: "=?",
displayableDomain: "=?"
}
};
}
return MCTPlot;
}
);