[Style] JSLint Compliance

This commit is contained in:
Pete Richards 2015-08-14 11:39:05 -07:00
parent a0dc3da8fb
commit 056b3f61ce
4 changed files with 228 additions and 210 deletions

View File

@ -7,15 +7,17 @@ define(
// TODO: Store this in more accessible locations / retrieve from // TODO: Store this in more accessible locations / retrieve from
// domainObject metadata. // domainObject metadata.
var DOMAIN_INTERVAL = 1 * 60 * 1000; // One minute. var DOMAIN_INTERVAL = 2 * 60 * 1000; // Two minutes.
function PlotController($scope, colorService) { function PlotController($scope, colorService) {
var plotHistory = []; var plotHistory = [],
var isLive = true; isLive = true,
var maxDomain = +new Date(); maxDomain = +new Date(),
var subscriptions = []; subscriptions = [],
var palette = new colorService.ColorPalette(); palette = new colorService.ColorPalette();
var setToDefaultViewport = function() {
function setToDefaultViewport() {
// TODO: We shouldn't set the viewport until we have received data or something has given us a reasonable viewport. // TODO: We shouldn't set the viewport until we have received data or something has given us a reasonable viewport.
$scope.viewport = { $scope.viewport = {
topLeft: { topLeft: {
@ -27,15 +29,15 @@ define(
range: -1 range: -1
} }
}; };
}; }
setToDefaultViewport(); setToDefaultViewport();
$scope.displayableRange = function(rangeValue) { $scope.displayableRange = function (rangeValue) {
// TODO: Call format function provided by domain object. // TODO: Call format function provided by domain object.
return rangeValue; return rangeValue;
}; };
$scope.displayableDomain = function(domainValue) { $scope.displayableDomain = function (domainValue) {
// TODO: Call format function provided by domain object. // TODO: Call format function provided by domain object.
return new Date(domainValue).toUTCString(); return new Date(domainValue).toUTCString();
}; };
@ -44,25 +46,33 @@ define(
$scope.rectangles = []; $scope.rectangles = [];
var updateSeriesFromTelemetry = function(series, seriesIndex, telemetry) { function updateSeriesFromTelemetry(series, seriesIndex, telemetry) {
var domainValue = telemetry.getDomainValue(telemetry.getPointCount() - 1); var domainValue = telemetry.getDomainValue(
var rangeValue = telemetry.getRangeValue(telemetry.getPointCount() - 1); telemetry.getPointCount() - 1
),
rangeValue = telemetry.getRangeValue(
telemetry.getPointCount() - 1
),
newTelemetry;
// Track the biggest domain we've seen for sticky-ness. // Track the biggest domain we've seen for sticky-ness.
maxDomain = Math.max(maxDomain, domainValue); maxDomain = Math.max(maxDomain, domainValue);
var newTelemetry = { newTelemetry = {
domain: domainValue, domain: domainValue,
range: rangeValue range: rangeValue
}; };
series.data.push(newTelemetry); series.data.push(newTelemetry);
$scope.$broadcast('series:data:add', seriesIndex, [newTelemetry]); $scope.$broadcast('series:data:add', seriesIndex, [newTelemetry]);
}; }
var subscribeToDomainObject = function(domainObject) { function subscribeToDomainObject(domainObject) {
var telemetryCapability = domainObject.getCapability('telemetry'); var telemetryCapability = domainObject.getCapability('telemetry'),
var model = domainObject.getModel(); model = domainObject.getModel(),
series,
seriesIndex,
updater;
var series = { series = {
name: model.name, name: model.name,
// TODO: Bring back PlotPalette. // TODO: Bring back PlotPalette.
color: palette.getColor($scope.series.length), color: palette.getColor($scope.series.length),
@ -70,17 +80,25 @@ define(
}; };
$scope.series.push(series); $scope.series.push(series);
var seriesIndex = $scope.series.indexOf(series); seriesIndex = $scope.series.indexOf(series);
var updater = updateSeriesFromTelemetry.bind( updater = updateSeriesFromTelemetry.bind(
null, null,
series, series,
seriesIndex seriesIndex
); );
subscriptions.push(telemetryCapability.subscribe(updater)); subscriptions.push(telemetryCapability.subscribe(updater));
}; }
var linkDomainObject = function(domainObject) { function unlinkDomainObject() {
subscriptions.forEach(function(subscription) {
subscription.unsubscribe();
});
subscriptions = [];
}
function linkDomainObject(domainObject) {
unlinkDomainObject(); unlinkDomainObject();
if (domainObject.hasCapability('telemetry')) { if (domainObject.hasCapability('telemetry')) {
subscribeToDomainObject(domainObject); subscribeToDomainObject(domainObject);
@ -98,23 +116,17 @@ define(
} else { } else {
throw new Error('Domain object type not supported.'); throw new Error('Domain object type not supported.');
} }
}; }
var unlinkDomainObject = function() {
subscriptions.forEach(function(subscription) {
subscription.unsubscribe();
});
subscriptions = [];
};
var onUserViewportChangeStart = function() { function onUserViewportChangeStart() {
// TODO: this is a great time to track a history entry. // TODO: this is a great time to track a history entry.
// Disable live mode so they have full control of viewport. // Disable live mode so they have full control of viewport.
plotHistory.push($scope.viewport); plotHistory.push($scope.viewport);
isLive = false; isLive = false;
}; }
var onUserViewportChangeEnd = function(event, viewport) { function onUserViewportChangeEnd(event, viewport) {
// If the new viewport is "close enough" to the maxDomain then // If the new viewport is "close enough" to the maxDomain then
// enable live mode. Set empirically to 10% of the domain // enable live mode. Set empirically to 10% of the domain
// interval. // interval.
@ -127,10 +139,10 @@ define(
isLive = false; isLive = false;
} }
plotHistory.push(viewport); plotHistory.push(viewport);
}; }
var viewportForMaxDomain = function() { function viewportForMaxDomain() {
var viewport = { return {
topLeft: { topLeft: {
range: $scope.viewport.topLeft.range, range: $scope.viewport.topLeft.range,
domain: maxDomain - DOMAIN_INTERVAL domain: maxDomain - DOMAIN_INTERVAL
@ -140,14 +152,13 @@ define(
domain: maxDomain domain: maxDomain
} }
}; };
return viewport; }
};
var followDataIfLive = function() { function followDataIfLive() {
if (isLive) { if (isLive) {
$scope.viewport = viewportForMaxDomain(); $scope.viewport = viewportForMaxDomain();
} }
}; }
$scope.$on('series:data:add', followDataIfLive); $scope.$on('series:data:add', followDataIfLive);
$scope.$on('user:viewport:change:end', onUserViewportChangeEnd); $scope.$on('user:viewport:change:end', onUserViewportChangeEnd);
@ -155,7 +166,7 @@ define(
$scope.$watch('domainObject', linkDomainObject); $scope.$watch('domainObject', linkDomainObject);
var controller = { return {
historyBack: function() { historyBack: function() {
// TODO: Step History Back. // TODO: Step History Back.
}, },
@ -166,8 +177,6 @@ define(
// TODO: Reset view to defaults. Keep history stack alive? // TODO: Reset view to defaults. Keep history stack alive?
} }
}; };
return controller;
} }
return PlotController; return PlotController;

View File

@ -51,24 +51,6 @@ define(
return; return;
} }
function redraw() {
if (isDestroyed) {
return;
}
requestAnimationFrame(redraw);
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
drawAPI.clear();
createOffset();
if (!offset) {
return;
}
updateViewport();
drawSeries();
drawRectangles();
}
function createOffset() { function createOffset() {
if (offset) { if (offset) {
return; return;
@ -84,18 +66,30 @@ define(
); );
} }
function drawIfResized() { function lineFromSeries(series) {
if (canvas.width !== canvas.offsetWidth || // TODO: handle when lines get longer than 10,000 points.
canvas.height !== canvas.offsetHeight) { // Each line allocates 10,000 points. This should be more
redraw(); // that we ever need, but we have to decide how to handle
} // this at the higher level. I imagine the plot controller
} // should watch it's series and when they get huge, slice
// them in half and delete the oldest half.
function destroyChart() { //
isDestroyed = true; // As long as the controller replaces $scope.series with a
if (activeInterval) { // new series object, then this directive will
$interval.cancel(activeInterval); // automatically generate new arrays for those lines.
// In practice, the overhead of regenerating these lines
// appears minimal.
var lineBuffer = new Float32Array(20000),
i = 0;
for (i = 0; i < series.data.length; i++) {
lineBuffer[2*i] = offset.domain(series.data[i].domain);
lineBuffer[2*i+1] = offset.range(series.data[i].range);
} }
return {
color: series.color,
buffer: lineBuffer,
pointCount: series.data.length
};
} }
function drawSeries() { function drawSeries() {
@ -132,7 +126,10 @@ define(
} }
function updateViewport() { function updateViewport() {
var dimensions = [ var dimensions,
origin;
dimensions = [
Math.abs( Math.abs(
offset.domain($scope.viewport.topLeft.domain) - offset.domain($scope.viewport.topLeft.domain) -
offset.domain($scope.viewport.bottomRight.domain) offset.domain($scope.viewport.bottomRight.domain)
@ -143,7 +140,7 @@ define(
) )
]; ];
var origin = [ origin = [
offset.domain( offset.domain(
$scope.viewport.topLeft.domain $scope.viewport.topLeft.domain
), ),
@ -158,31 +155,6 @@ define(
); );
} }
function lineFromSeries(series) {
// TODO: handle when lines get longer than 10,000 points.
// Each line allocates 10,000 points. This should be more
// that we ever need, but we have to decide how to handle
// this at the higher level. I imagine the plot controller
// should watch it's series and when they get huge, slice
// them in half and delete the oldest half.
//
// As long as the controller replaces $scope.series with a
// new series object, then this directive will
// automatically generate new arrays for those lines.
// In practice, the overhead of regenerating these lines
// appears minimal.
var lineBuffer = new Float32Array(20000);
for (var i = 0; i < series.data.length; i++) {
lineBuffer[2*i] = offset.domain(series.data[i].domain);
lineBuffer[2*i+1] = offset.range(series.data[i].range);
}
return {
color: series.color,
buffer: lineBuffer,
pointCount: series.data.length
};
}
function onSeriesDataAdd(event, seriesIndex, points) { function onSeriesDataAdd(event, seriesIndex, points) {
var line = lines[seriesIndex]; var line = lines[seriesIndex];
points.forEach(function (point) { points.forEach(function (point) {
@ -192,6 +164,41 @@ define(
}); });
} }
function redraw() {
if (isDestroyed) {
return;
}
requestAnimationFrame(redraw);
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
drawAPI.clear();
createOffset();
if (!offset) {
return;
}
updateViewport();
drawSeries();
drawRectangles();
}
function drawIfResized() {
if (canvas.width !== canvas.offsetWidth ||
canvas.height !== canvas.offsetHeight) {
redraw();
}
}
function destroyChart() {
isDestroyed = true;
if (activeInterval) {
$interval.cancel(activeInterval);
}
}
// Check for resize, on a timer // Check for resize, on a timer
activeInterval = $interval(drawIfResized, 1000); activeInterval = $interval(drawIfResized, 1000);

View File

@ -7,8 +7,8 @@ define(
function (utils) { function (utils) {
"use strict"; "use strict";
var RANGE_TICK_COUNT = 7; var RANGE_TICK_COUNT = 7,
var DOMAIN_TICK_COUNT = 5; DOMAIN_TICK_COUNT = 5;
function MCTPlot() { function MCTPlot() {
@ -40,34 +40,37 @@ define(
} }
var dragStart; var dragStart,
var marqueeBox = {}; marqueeBox = {},
var marqueeRect; // Set when exists. marqueeRect, // Set when exists.
var chartElementBounds; chartElementBounds,
var $canvas = $element.find('canvas'); $canvas = $element.find('canvas');
var updateAxesForCurrentViewport = function() { function updateAxesForCurrentViewport() {
// Update axes definitions for current viewport. // Update axes definitions for current viewport.
['domain', 'range'].forEach(function(axisName) { ['domain', 'range'].forEach(function(axisName) {
var axis = $scope.axes[axisName]; var axis = $scope.axes[axisName],
var firstTick = $scope.viewport.topLeft[axisName]; firstTick = $scope.viewport.topLeft[axisName],
var lastTick = $scope.viewport.bottomRight[axisName]; lastTick = $scope.viewport.bottomRight[axisName],
var axisSize = firstTick - lastTick; axisSize = firstTick - lastTick,
var denominator = axis.tickCount - 1; denominator = axis.tickCount - 1,
tickNumber,
tickIncrement,
tickValue;
// Yes, ticksize is negative for domain and positive for range. // Yes, ticksize is negative for domain and positive for range.
// It's because ticks are generated/displayed top to bottom and left to right. // It's because ticks are generated/displayed top to bottom and left to right.
axis.ticks = []; axis.ticks = [];
for (var tickNumber = 0; tickNumber < axis.tickCount; tickNumber++) { for (tickNumber = 0; tickNumber < axis.tickCount; tickNumber++) {
var tickIncrement = (axisSize * (tickNumber / denominator)); tickIncrement = (axisSize * (tickNumber / denominator));
var tickValue = firstTick - tickIncrement; tickValue = firstTick - tickIncrement;
axis.ticks.push( axis.ticks.push(
tickValue tickValue
); );
} }
}); });
}; }
var drawMarquee = function() { function drawMarquee() {
// Create rectangle for Marquee if it should be set. // Create rectangle for Marquee if it should be set.
if (marqueeBox && marqueeBox.start && marqueeBox.end) { if (marqueeBox && marqueeBox.start && marqueeBox.end) {
if (!marqueeRect) { if (!marqueeRect) {
@ -79,30 +82,88 @@ define(
marqueeRect.color = [1, 1, 1, 0.5]; marqueeRect.color = [1, 1, 1, 0.5];
marqueeRect.layer = 'top'; // TODO: implement this. marqueeRect.layer = 'top'; // TODO: implement this.
$scope.$broadcast('rectangle-change'); $scope.$broadcast('rectangle-change');
} else if (marqueeRect && $scope.rectangles.indexOf(marqueeRect) != -1) { } else if (marqueeRect && $scope.rectangles.indexOf(marqueeRect) !== -1) {
$scope.rectangles.splice($scope.rectangles.indexOf(marqueeRect)); $scope.rectangles.splice($scope.rectangles.indexOf(marqueeRect));
marqueeRect = undefined; marqueeRect = undefined;
$scope.$broadcast('rectangle-change'); $scope.$broadcast('rectangle-change');
} }
}; }
var untrackMousePosition = function() { function untrackMousePosition() {
$scope.mouseCoordinates = undefined; $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);
var trackMousePosition = function($event) { 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 trackMousePosition($event) {
// Calculate coordinates of mouse related to canvas and as // Calculate coordinates of mouse related to canvas and as
// domain, range value and make available in scope for display. // domain, range value and make available in scope for display.
var bounds = $event.target.getBoundingClientRect(); var bounds = $event.target.getBoundingClientRect(),
positionOverElement,
positionAsPlotPoint;
chartElementBounds = bounds; chartElementBounds = bounds;
var positionOverElement = { positionOverElement = {
x: $event.clientX - bounds.left, x: $event.clientX - bounds.left,
y: $event.clientY - bounds.top y: $event.clientY - bounds.top
}; };
var positionAsPlotPoint = utils.elementPositionAsPlotPosition( positionAsPlotPoint = utils.elementPositionAsPlotPosition(
positionOverElement, positionOverElement,
bounds, bounds,
$scope.viewport $scope.viewport
@ -120,104 +181,46 @@ define(
if (dragStart) { if (dragStart) {
updateDrag(); updateDrag();
} }
}; }
var startMarquee = function() { function watchForMarquee() {
marqueeBox.start = $scope.mouseCoordinates.positionAsPlotPoint;
};
var updateMarquee = function() {
// Update the marquee box in progress.
marqueeBox.end = $scope.mouseCoordinates.positionAsPlotPoint;
drawMarquee();
};
var endMarquee = function() {
// marqueeBox start/end are opposite corners but we need
// topLeft and bottomRight.
var boxPoints = utils.boxPointsFromOppositeCorners(marqueeBox.start, marqueeBox.end);
var newViewport = utils.oppositeCornersFromBoxPoints(boxPoints);
marqueeBox = {};
drawMarquee();
$scope.$emit('user:viewport:change:end', newViewport);
$scope.viewport = newViewport;
};
var startDrag = function($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;
};
var updateDrag = function() {
// calculate offset between points. Apply that offset to viewport.
var newPosition = $scope.mouseCoordinates.positionAsPlotPoint;
var dDomain = dragStart.domain - newPosition.domain;
var 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
}
};
};
var endDrag = function() {
dragStart = undefined;
$scope.$emit('user:viewport:change:end', $scope.viewport);
};
var watchForMarquee = function() {
$canvas.removeClass('plot-drag'); $canvas.removeClass('plot-drag');
$canvas.addClass('plot-marquee'); $canvas.addClass('plot-marquee');
$canvas.on('mousedown', startMarquee); $canvas.on('mousedown', startMarquee);
$canvas.on('mouseup', endMarquee); $canvas.on('mouseup', endMarquee);
$canvas.off('mousedown', startDrag); $canvas.off('mousedown', startDrag);
$canvas.off('mouseup', endDrag); $canvas.off('mouseup', endDrag);
}; }
var watchForDrag = function() { function watchForDrag() {
$canvas.addClass('plot-drag'); $canvas.addClass('plot-drag');
$canvas.removeClass('plot-marquee'); $canvas.removeClass('plot-marquee');
$canvas.on('mousedown', startDrag); $canvas.on('mousedown', startDrag);
$canvas.on('mouseup', endDrag); $canvas.on('mouseup', endDrag);
$canvas.off('mousedown', startMarquee); $canvas.off('mousedown', startMarquee);
$canvas.off('mouseup', endMarquee); $canvas.off('mouseup', endMarquee);
}; }
var stopWatching = function() { function toggleInteractionMode(event) {
if (event.keyCode === '18') { // control key.
watchForDrag();
}
}
function resetInteractionMode(event) {
if (event.keyCode === '18') {
watchForMarquee();
}
}
function stopWatching() {
$canvas.off('mousedown', startDrag); $canvas.off('mousedown', startDrag);
$canvas.off('mouseup', endDrag); $canvas.off('mouseup', endDrag);
$canvas.off('mousedown', startMarquee); $canvas.off('mousedown', startMarquee);
$canvas.off('mouseup', endMarquee); $canvas.off('mouseup', endMarquee);
window.removeEventListener('keydown', toggleInteractionMode); window.removeEventListener('keydown', toggleInteractionMode);
window.removeEventListener('keyup', resetInteractionMode); window.removeEventListener('keyup', resetInteractionMode);
}; }
var toggleInteractionMode = function(event) {
if (event.keyCode == '18') { // control key.
watchForDrag();
}
};
var resetInteractionMode = function(event) {
if (event.keyCode == '18') {
watchForMarquee();
}
};
$canvas.on('mousemove', trackMousePosition); $canvas.on('mousemove', trackMousePosition);
$canvas.on('mouseleave', untrackMousePosition); $canvas.on('mouseleave', untrackMousePosition);
@ -226,7 +229,7 @@ define(
window.addEventListener('keydown', toggleInteractionMode); window.addEventListener('keydown', toggleInteractionMode);
window.addEventListener('keyup', resetInteractionMode); window.addEventListener('keyup', resetInteractionMode);
var onViewportChange = function() { function onViewportChange() {
if ($scope.mouseCoordinates && chartElementBounds) { if ($scope.mouseCoordinates && chartElementBounds) {
$scope.mouseCoordinates.positionAsPlotPoint = $scope.mouseCoordinates.positionAsPlotPoint =
utils.elementPositionAsPlotPosition( utils.elementPositionAsPlotPosition(
@ -235,11 +238,9 @@ define(
$scope.viewport $scope.viewport
); );
} }
if (marqueeBox && marqueeBox.start) { // TODO: Discuss whether marqueeBox start should be fixed to data or fixed to canvas element, especially when "isLive is true".
// TODO: Discuss whether marqueeBox start should be fixed to data or fixed to canvas element, especially when "isLive is true".
}
updateAxesForCurrentViewport(); updateAxesForCurrentViewport();
}; }
$scope.$watchCollection('viewport', onViewportChange); $scope.$watchCollection('viewport', onViewportChange);

View File

@ -1,4 +1,4 @@
/*global define */ /*global define,$log */
define( define(
[ [
@ -25,7 +25,8 @@ define(
the draw API to. the draw API to.
*/ */
getDrawAPI: function (canvas) { getDrawAPI: function (canvas) {
for (var i = 0; i < CHARTS.length; i++) { var i;
for (i = 0; i < CHARTS.length; i++) {
try { try {
return new CHARTS[i](canvas); return new CHARTS[i](canvas);
} catch (e) { } catch (e) {
@ -42,4 +43,4 @@ define(
} }
}; };
} }
); );