[Code Style] Use prototypes in Plot bundle

WTD-1482.
This commit is contained in:
Victor Woeltjen 2015-08-13 12:12:15 -07:00
parent aefad6fdd3
commit 820c15d74c
11 changed files with 844 additions and 852 deletions

View File

@ -31,116 +31,89 @@ define(
* *
* @memberof platform/features/plot * @memberof platform/features/plot
* @constructor * @constructor
* @implements {platform/features/plot.Chart}
* @param {CanvasElement} canvas the canvas object to render upon * @param {CanvasElement} canvas the canvas object to render upon
* @throws {Error} an error is thrown if Canvas's 2D API is unavailable. * @throws {Error} an error is thrown if Canvas's 2D API is unavailable.
*/ */
function Canvas2DChart(canvas) { function Canvas2DChart(canvas) {
var c2d = canvas.getContext('2d'), this.canvas = canvas;
width = canvas.width, this.c2d = canvas.getContext('2d');
height = canvas.height, this.width = canvas.width;
dimensions = [ width, height ], this.height = canvas.height;
origin = [ 0, 0 ]; this.dimensions = [ this.width, this.height ];
this.origin = [ 0, 0 ];
// Convert from logical to physical x coordinates if (!this.c2d) {
function x(v) {
return ((v - origin[0]) / dimensions[0]) * width;
}
// Convert from logical to physical y coordinates
function y(v) {
return height - ((v - origin[1]) / dimensions[1]) * height;
}
// Set the color to be used for drawing operations
function setColor(color) {
var mappedColor = color.map(function (c, i) {
return i < 3 ? Math.floor(c * 255) : (c);
}).join(',');
c2d.strokeStyle = "rgba(" + mappedColor + ")";
c2d.fillStyle = "rgba(" + mappedColor + ")";
}
if (!c2d) {
throw new Error("Canvas 2d API unavailable."); throw new Error("Canvas 2d API unavailable.");
} }
return {
/**
* Clear the chart.
* @memberof platform/features/plot.Canvas2DChart#
*/
clear: function () {
width = canvas.width;
height = canvas.height;
c2d.clearRect(0, 0, width, height);
},
/**
* Set the logical boundaries of the chart.
* @param {number[]} dimensions the horizontal and
* vertical dimensions of the chart
* @param {number[]} origin the horizontal/vertical
* origin of the chart
* @memberof platform/features/plot.Canvas2DChart#
*/
setDimensions: function (newDimensions, newOrigin) {
dimensions = newDimensions;
origin = newOrigin;
},
/**
* Draw the supplied buffer as a line strip (a sequence
* of line segments), in the chosen color.
* @param {Float32Array} buf the line strip to draw,
* in alternating x/y positions
* @param {number[]} color the color to use when drawing
* the line, as an RGBA color where each element
* is in the range of 0.0-1.0
* @param {number} points the number of points to draw
* @memberof platform/features/plot.Canvas2DChart#
*/
drawLine: function (buf, color, points) {
var i;
setColor(color);
// Configure context to draw two-pixel-thick lines
c2d.lineWidth = 2;
// Start a new path...
if (buf.length > 1) {
c2d.beginPath();
c2d.moveTo(x(buf[0]), y(buf[1]));
}
// ...and add points to it...
for (i = 2; i < points * 2; i = i + 2) {
c2d.lineTo(x(buf[i]), y(buf[i + 1]));
}
// ...before finally drawing it.
c2d.stroke();
},
/**
* Draw a rectangle extending from one corner to another,
* in the chosen color.
* @param {number[]} min the first corner of the rectangle
* @param {number[]} max the opposite corner
* @param {number[]} color the color to use when drawing
* the rectangle, as an RGBA color where each element
* is in the range of 0.0-1.0
* @memberof platform/features/plot.Canvas2DChart#
*/
drawSquare: function (min, max, color) {
var x1 = x(min[0]),
y1 = y(min[1]),
w = x(max[0]) - x1,
h = y(max[1]) - y1;
setColor(color);
c2d.fillRect(x1, y1, w, h);
}
};
} }
// Convert from logical to physical x coordinates
Canvas2DChart.prototype.x = function (v) {
return ((v - this.origin[0]) / this.dimensions[0]) * this.width;
};
// Convert from logical to physical y coordinates
Canvas2DChart.prototype.y = function (v) {
return this.height -
((v - this.origin[1]) / this.dimensions[1]) * this.height;
};
// Set the color to be used for drawing operations
Canvas2DChart.prototype.setColor = function (color) {
var mappedColor = color.map(function (c, i) {
return i < 3 ? Math.floor(c * 255) : (c);
}).join(',');
this.c2d.strokeStyle = "rgba(" + mappedColor + ")";
this.c2d.fillStyle = "rgba(" + mappedColor + ")";
};
Canvas2DChart.prototype.clear = function () {
var canvas = this.canvas;
this.width = canvas.width;
this.height = canvas.height;
this.c2d.clearRect(0, 0, this.width, this.height);
};
Canvas2DChart.prototype.setDimensions = function (newDimensions, newOrigin) {
this.dimensions = newDimensions;
this.origin = newOrigin;
};
Canvas2DChart.prototype.drawLine = function (buf, color, points) {
var i;
this.setColor(color);
// Configure context to draw two-pixel-thick lines
this.c2d.lineWidth = 2;
// Start a new path...
if (buf.length > 1) {
this.c2d.beginPath();
this.c2d.moveTo(this.x(buf[0]), this.y(buf[1]));
}
// ...and add points to it...
for (i = 2; i < points * 2; i = i + 2) {
this.c2d.lineTo(this.x(buf[i]), this.y(buf[i + 1]));
}
// ...before finally drawing it.
this.c2d.stroke();
};
Canvas2DChart.prototype.drawSquare = function (min, max, color) {
var x1 = this.x(min[0]),
y1 = this.y(min[1]),
w = this.x(max[0]) - x1,
h = this.y(max[1]) - y1;
this.setColor(color);
this.c2d.fillRect(x1, y1, w, h);
};
return Canvas2DChart; return Canvas2DChart;
} }
); );

View File

@ -51,6 +51,7 @@ define(
* *
* @memberof platform/features/plot * @memberof platform/features/plot
* @constructor * @constructor
* @implements {platform/features/plot.Chart}
* @param {CanvasElement} canvas the canvas object to render upon * @param {CanvasElement} canvas the canvas object to render upon
* @throws {Error} an error is thrown if WebGL is unavailable. * @throws {Error} an error is thrown if WebGL is unavailable.
*/ */
@ -62,8 +63,7 @@ define(
aVertexPosition, aVertexPosition,
uColor, uColor,
uDimensions, uDimensions,
uOrigin, uOrigin;
buffer;
// Ensure a context was actually available before proceeding // Ensure a context was actually available before proceeding
if (!gl) { if (!gl) {
@ -94,7 +94,7 @@ define(
gl.enableVertexAttribArray(aVertexPosition); gl.enableVertexAttribArray(aVertexPosition);
// Create a buffer to holds points which will be drawn // Create a buffer to holds points which will be drawn
buffer = gl.createBuffer(); this.buffer = gl.createBuffer();
// Use a line width of 2.0 for legibility // Use a line width of 2.0 for legibility
gl.lineWidth(2.0); gl.lineWidth(2.0);
@ -103,79 +103,59 @@ define(
gl.enable(gl.BLEND); gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
// Utility function to handle drawing of a buffer; this.gl = gl;
// drawType will determine whether this is a box, line, etc. this.aVertexPosition = aVertexPosition;
function doDraw(drawType, buf, color, points) { this.uColor = uColor;
gl.bindBuffer(gl.ARRAY_BUFFER, buffer); this.uDimensions = uDimensions;
gl.bufferData(gl.ARRAY_BUFFER, buf, gl.DYNAMIC_DRAW); this.uOrigin = uOrigin;
gl.vertexAttribPointer(aVertexPosition, 2, gl.FLOAT, false, 0, 0);
gl.uniform4fv(uColor, color);
gl.drawArrays(drawType, 0, points);
}
return {
/**
* Clear the chart.
* @memberof platform/features/plot.GLChart#
*/
clear: function () {
// Set the viewport size; note that we use the width/height
// that our WebGL context reports, which may be lower
// resolution than the canvas we requested.
gl.viewport(
0,
0,
gl.drawingBufferWidth,
gl.drawingBufferHeight
);
gl.clear(gl.COLOR_BUFFER_BIT + gl.DEPTH_BUFFER_BIT);
},
/**
* Set the logical boundaries of the chart.
* @param {number[]} dimensions the horizontal and
* vertical dimensions of the chart
* @param {number[]} origin the horizontal/vertical
* origin of the chart
* @memberof platform/features/plot.GLChart#
*/
setDimensions: function (dimensions, origin) {
if (dimensions && dimensions.length > 0 &&
origin && origin.length > 0) {
gl.uniform2fv(uDimensions, dimensions);
gl.uniform2fv(uOrigin, origin);
}
},
/**
* Draw the supplied buffer as a line strip (a sequence
* of line segments), in the chosen color.
* @param {Float32Array} buf the line strip to draw,
* in alternating x/y positions
* @param {number[]} color the color to use when drawing
* the line, as an RGBA color where each element
* is in the range of 0.0-1.0
* @param {number} points the number of points to draw
* @memberof platform/features/plot.GLChart#
*/
drawLine: function (buf, color, points) {
doDraw(gl.LINE_STRIP, buf, color, points);
},
/**
* Draw a rectangle extending from one corner to another,
* in the chosen color.
* @param {number[]} min the first corner of the rectangle
* @param {number[]} max the opposite corner
* @param {number[]} color the color to use when drawing
* the rectangle, as an RGBA color where each element
* is in the range of 0.0-1.0
* @memberof platform/features/plot.GLChart#
*/
drawSquare: function (min, max, color) {
doDraw(gl.TRIANGLE_FAN, new Float32Array(
min.concat([min[0], max[1]]).concat(max).concat([max[0], min[1]])
), color, 4);
}
};
} }
// Utility function to handle drawing of a buffer;
// drawType will determine whether this is a box, line, etc.
GLChart.prototype.doDraw = function (drawType, buf, color, points) {
var gl = this.gl;
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
gl.bufferData(gl.ARRAY_BUFFER, buf, gl.DYNAMIC_DRAW);
gl.vertexAttribPointer(this.aVertexPosition, 2, gl.FLOAT, false, 0, 0);
gl.uniform4fv(this.uColor, color);
gl.drawArrays(drawType, 0, points);
};
GLChart.prototype.clear = function () {
var gl = this.gl;
// Set the viewport size; note that we use the width/height
// that our WebGL context reports, which may be lower
// resolution than the canvas we requested.
gl.viewport(
0,
0,
gl.drawingBufferWidth,
gl.drawingBufferHeight
);
gl.clear(gl.COLOR_BUFFER_BIT + gl.DEPTH_BUFFER_BIT);
};
GLChart.prototype.setDimensions = function (dimensions, origin) {
var gl = this.gl;
if (dimensions && dimensions.length > 0 &&
origin && origin.length > 0) {
gl.uniform2fv(this.uDimensions, dimensions);
gl.uniform2fv(this.uOrigin, origin);
}
};
GLChart.prototype.drawLine = function (buf, color, points) {
this.doDraw(this.gl.LINE_STRIP, buf, color, points);
};
GLChart.prototype.drawSquare = function (min, max, color) {
this.doDraw(this.gl.TRIANGLE_FAN, new Float32Array(
min.concat([min[0], max[1]]).concat(max).concat([max[0], min[1]])
), color, 4);
};
return GLChart; return GLChart;
} }
); );

View File

@ -206,6 +206,45 @@ define(
}; };
} }
/**
* @interface platform/features/plot.Chart
* @private
*/
/**
* Clear the chart.
* @method platform/features/plot.Chart#clear
*/
/**
* Set the logical boundaries of the chart.
* @param {number[]} dimensions the horizontal and
* vertical dimensions of the chart
* @param {number[]} origin the horizontal/vertical
* origin of the chart
* @memberof platform/features/plot.Chart#setDimensions
*/
/**
* Draw the supplied buffer as a line strip (a sequence
* of line segments), in the chosen color.
* @param {Float32Array} buf the line strip to draw,
* in alternating x/y positions
* @param {number[]} color the color to use when drawing
* the line, as an RGBA color where each element
* is in the range of 0.0-1.0
* @param {number} points the number of points to draw
* @memberof platform/features/plot.Chart#drawLine
*/
/**
* Draw a rectangle extending from one corner to another,
* in the chosen color.
* @param {number[]} min the first corner of the rectangle
* @param {number[]} max the opposite corner
* @param {number[]} color the color to use when drawing
* the rectangle, as an RGBA color where each element
* is in the range of 0.0-1.0
* @memberof platform/features/plot.Chart#drawSquare
*/
return MCTChart; return MCTChart;
} }
); );

View File

@ -61,15 +61,11 @@ define(
throttle, throttle,
PLOT_FIXED_DURATION PLOT_FIXED_DURATION
) { ) {
var subPlotFactory = new SubPlotFactory(telemetryFormatter), var self = this,
modeOptions = new PlotModeOptions([], subPlotFactory), subPlotFactory = new SubPlotFactory(telemetryFormatter),
subplots = [],
cachedObjects = [], cachedObjects = [],
limitTracker,
updater, updater,
handle, handle;
scheduleUpdate,
domainOffset;
// Populate the scope with axis information (specifically, options // Populate the scope with axis information (specifically, options
// available for each axis.) // available for each axis.)
@ -91,18 +87,13 @@ define(
function setupModes(telemetryObjects) { function setupModes(telemetryObjects) {
if (cachedObjects !== telemetryObjects) { if (cachedObjects !== telemetryObjects) {
cachedObjects = telemetryObjects; cachedObjects = telemetryObjects;
modeOptions = new PlotModeOptions( self.modeOptions = new PlotModeOptions(
telemetryObjects || [], telemetryObjects || [],
subPlotFactory subPlotFactory
); );
} }
} }
// Update all sub-plots
function update() {
scheduleUpdate();
}
// Reinstantiate the plot updater (e.g. because we have a // Reinstantiate the plot updater (e.g. because we have a
// new subscription.) This will clear the plot. // new subscription.) This will clear the plot.
function recreateUpdater() { function recreateUpdater() {
@ -112,7 +103,7 @@ define(
($scope.axes[1].active || {}).key, ($scope.axes[1].active || {}).key,
PLOT_FIXED_DURATION PLOT_FIXED_DURATION
); );
limitTracker = new PlotLimitTracker( self.limitTracker = new PlotLimitTracker(
handle, handle,
($scope.axes[1].active || {}).key ($scope.axes[1].active || {}).key
); );
@ -125,19 +116,19 @@ define(
} }
if (updater) { if (updater) {
updater.update(); updater.update();
modeOptions.getModeHandler().plotTelemetry(updater); self.modeOptions.getModeHandler().plotTelemetry(updater);
} }
if (limitTracker) { if (self.limitTracker) {
limitTracker.update(); self.limitTracker.update();
} }
update(); self.update();
} }
// Display new historical data as it becomes available // Display new historical data as it becomes available
function addHistoricalData(domainObject, series) { function addHistoricalData(domainObject, series) {
updater.addHistorical(domainObject, series); updater.addHistorical(domainObject, series);
modeOptions.getModeHandler().plotTelemetry(updater); self.modeOptions.getModeHandler().plotTelemetry(updater);
update(); self.update();
} }
// Issue a new request for historical telemetry // Issue a new request for historical telemetry
@ -174,116 +165,119 @@ define(
} }
} }
this.modeOptions = new PlotModeOptions([], subPlotFactory);
this.updateValues = updateValues;
// Create a throttled update function
this.scheduleUpdate = throttle(function () {
self.modeOptions.getModeHandler().getSubPlots()
.forEach(updateSubplot);
});
// Subscribe to telemetry when a domain object becomes available // Subscribe to telemetry when a domain object becomes available
$scope.$watch('domainObject', subscribe); $scope.$watch('domainObject', subscribe);
// Unsubscribe when the plot is destroyed // Unsubscribe when the plot is destroyed
$scope.$on("$destroy", releaseSubscription); $scope.$on("$destroy", releaseSubscription);
// Create a throttled update function
scheduleUpdate = throttle(function () {
modeOptions.getModeHandler().getSubPlots()
.forEach(updateSubplot);
});
return {
/**
* Get the color (as a style-friendly string) to use
* for plotting the trace at the specified index.
* @param {number} index the index of the trace
* @returns {string} the color, in #RRGGBB form
* @memberof platform/features/plot.PlotController#
*/
getColor: function (index) {
return PlotPalette.getStringColor(index);
},
/**
* Check if the plot is zoomed or panned out
* of its default state (to determine whether back/unzoom
* controls should be shown)
* @returns {boolean} true if not in default state
* @memberof platform/features/plot.PlotController#
*/
isZoomed: function () {
return modeOptions.getModeHandler().isZoomed();
},
/**
* Undo the most recent pan/zoom change and restore
* the prior state.
* @memberof platform/features/plot.PlotController#
*/
stepBackPanZoom: function () {
return modeOptions.getModeHandler().stepBackPanZoom();
},
/**
* Undo all pan/zoom changes and restore the initial state.
* @memberof platform/features/plot.PlotController#
*/
unzoom: function () {
return modeOptions.getModeHandler().unzoom();
},
/**
* Get the mode options (Stacked/Overlaid) that are applicable
* for this plot.
* @memberof platform/features/plot.PlotController#
*/
getModeOptions: function () {
return modeOptions.getModeOptions();
},
/**
* Get the current mode that is applicable to this plot. This
* will include key, name, and glyph fields.
* @memberof platform/features/plot.PlotController#
*/
getMode: function () {
return modeOptions.getMode();
},
/**
* Set the mode which should be active in this plot.
* @param mode one of the mode options returned from
* getModeOptions()
* @memberof platform/features/plot.PlotController#
*/
setMode: function (mode) {
modeOptions.setMode(mode);
updateValues();
},
/**
* Get all individual plots contained within this Plot view.
* (Multiple may be contained when in Stacked mode).
* @returns {SubPlot[]} all subplots in this Plot view
* @memberof platform/features/plot.PlotController#
*/
getSubPlots: function () {
return modeOptions.getModeHandler().getSubPlots();
},
/**
* Get the CSS class to apply to the legend for this domain
* object; this will reflect limit state.
* @returns {string} the CSS class
* @memberof platform/features/plot.PlotController#
*/
getLegendClass: function (telemetryObject) {
return limitTracker &&
limitTracker.getLegendClass(telemetryObject);
},
/**
* Explicitly update all plots.
* @memberof platform/features/plot.PlotController#
*/
update: update,
/**
* Check if a request is pending (to show the wait spinner)
* @memberof platform/features/plot.PlotController#
*/
isRequestPending: function () {
// Placeholder; this should reflect request state
// when requesting historical telemetry
return false;
}
};
} }
/**
* Get the color (as a style-friendly string) to use
* for plotting the trace at the specified index.
* @param {number} index the index of the trace
* @returns {string} the color, in #RRGGBB form
*/
PlotController.prototype.getColor = function (index) {
return PlotPalette.getStringColor(index);
};
/**
* Check if the plot is zoomed or panned out
* of its default state (to determine whether back/unzoom
* controls should be shown)
* @returns {boolean} true if not in default state
*/
PlotController.prototype.isZoomed = function () {
return this.modeOptions.getModeHandler().isZoomed();
};
/**
* Undo the most recent pan/zoom change and restore
* the prior state.
*/
PlotController.prototype.stepBackPanZoom = function () {
return this.modeOptions.getModeHandler().stepBackPanZoom();
};
/**
* Undo all pan/zoom changes and restore the initial state.
*/
PlotController.prototype.unzoom = function () {
return this.modeOptions.getModeHandler().unzoom();
};
/**
* Get the mode options (Stacked/Overlaid) that are applicable
* for this plot.
*/
PlotController.prototype.getModeOptions = function () {
return this.modeOptions.getModeOptions();
};
/**
* Get the current mode that is applicable to this plot. This
* will include key, name, and glyph fields.
*/
PlotController.prototype.getMode = function () {
return this.modeOptions.getMode();
};
/**
* Set the mode which should be active in this plot.
* @param mode one of the mode options returned from
* getModeOptions()
*/
PlotController.prototype.setMode = function (mode) {
this.modeOptions.setMode(mode);
this.updateValues();
};
/**
* Get all individual plots contained within this Plot view.
* (Multiple may be contained when in Stacked mode).
* @returns {SubPlot[]} all subplots in this Plot view
*/
PlotController.prototype.getSubPlots = function () {
return this.modeOptions.getModeHandler().getSubPlots();
};
/**
* Get the CSS class to apply to the legend for this domain
* object; this will reflect limit state.
* @returns {string} the CSS class
*/
PlotController.prototype.getLegendClass = function (telemetryObject) {
return this.limitTracker &&
this.limitTracker.getLegendClass(telemetryObject);
};
/**
* Explicitly update all plots.
*/
PlotController.prototype.update = function () {
this.scheduleUpdate();
};
/**
* Check if a request is pending (to show the wait spinner)
*/
PlotController.prototype.isRequestPending = function () {
// Placeholder; this should reflect request state
// when requesting historical telemetry
return false;
};
return PlotController; return PlotController;
} }
); );

View File

@ -50,141 +50,278 @@ define(
// We are used from a template often, so maintain // We are used from a template often, so maintain
// state in local variables to allow for fast look-up, // state in local variables to allow for fast look-up,
// as is normal for controllers. // as is normal for controllers.
var draw = {}, this.telemetryObjects = telemetryObjects;
rangeTicks = [], this.domainTicks = [];
domainTicks = [], this.rangeTicks = [];
formatter = telemetryFormatter, this.formatter = telemetryFormatter;
domainOffset, this.draw = {};
mousePosition, this.hovering = false;
marqueeStart, this.panZoomStack = panZoomStack;
panStart,
panStartBounds, // Start with the right initial drawing bounds,
subPlotBounds, // tick marks
hoverCoordinates, this.updateDrawingBounds();
isHovering = false; this.updateTicks();
}
// Utility function for filtering out empty strings.
function isNonEmpty(v) {
return typeof v === 'string' && v !== "";
}
// Converts from pixel coordinates to domain-range,
// to interpret mouse gestures.
SubPlot.prototype.mousePositionToDomainRange = function (mousePosition) {
return new PlotPosition(
mousePosition.x,
mousePosition.y,
mousePosition.width,
mousePosition.height,
this.panZoomStack
).getPosition();
};
// Utility function to get the mouse position (in x,y
// pixel coordinates in the canvas area) from a mouse
// event object.
SubPlot.prototype.toMousePosition = function ($event) {
var bounds = this.subPlotBounds;
return {
x: $event.clientX - bounds.left,
y: $event.clientY - bounds.top,
width: bounds.width,
height: bounds.height
};
};
// Convert a domain-range position to a displayable
// position. This will subtract the domain offset, which
// is used to bias domain values to minimize loss-of-precision
// associated with conversion to a 32-bit floating point
// format (which is needed in the chart area itself, by WebGL.)
SubPlot.prototype.toDisplayable = function (position) {
return [ position[0] - this.domainOffset, position[1] ];
};
// Update the current hover coordinates
SubPlot.prototype.updateHoverCoordinates = function () {
var formatter = this.formatter;
// Utility, for map/forEach loops. Index 0 is domain, // Utility, for map/forEach loops. Index 0 is domain,
// index 1 is range. // index 1 is range.
function formatValue(v, i) { function formatValue(v, i) {
return (i ? return (i ?
formatter.formatRangeValue : formatter.formatRangeValue :
formatter.formatDomainValue)(v); formatter.formatDomainValue)(v);
} }
// Utility function for filtering out empty strings. this.hoverCoordinates = this.mousePosition &&
function isNonEmpty(v) { this.mousePositionToDomainRange(this.mousePosition)
return typeof v === 'string' && v !== ""; .map(formatValue)
.filter(isNonEmpty)
.join(", ");
};
// Update the drawable marquee area to reflect current
// mouse position (or don't show it at all, if no marquee
// zoom is in progress)
SubPlot.prototype.updateMarqueeBox = function () {
// Express this as a box in the draw object, which
// is passed to an mct-chart in the template for rendering.
this.draw.boxes = this.marqueeStart ?
[{
start: this.toDisplayable(
this.mousePositionToDomainRange(this.marqueeStart)
),
end: this.toDisplayable(
this.mousePositionToDomainRange(this.mousePosition)
),
color: [1, 1, 1, 0.5 ]
}] : undefined;
};
// Update the bounds (origin and dimensions) of the drawing area.
SubPlot.prototype.updateDrawingBounds = function () {
var panZoom = this.panZoomStack.getPanZoom();
// Communicate pan-zoom state from stack to the draw object
// which is passed to mct-chart in the template.
this.draw.dimensions = panZoom.dimensions;
this.draw.origin = [
panZoom.origin[0] - this.domainOffset,
panZoom.origin[1]
];
};
// Update tick marks in scope.
SubPlot.prototype.updateTicks = function () {
var tickGenerator =
new PlotTickGenerator(this.panZoomStack, this.formatter);
this.domainTicks =
tickGenerator.generateDomainTicks(DOMAIN_TICKS);
this.rangeTicks =
tickGenerator.generateRangeTicks(RANGE_TICKS);
};
SubPlot.prototype.updatePan = function () {
var start, current, delta, nextOrigin;
// Clear the previous panning pan-zoom state
this.panZoomStack.popPanZoom();
// Calculate what the new resulting pan-zoom should be
start = this.mousePositionToDomainRange(
this.panStart,
this.panZoomStack
);
current = this.mousePositionToDomainRange(
this.mousePosition,
this.panZoomStack
);
delta = [ current[0] - start[0], current[1] - start[1] ];
nextOrigin = [
this.panStartBounds.origin[0] - delta[0],
this.panStartBounds.origin[1] - delta[1]
];
// ...and push a new one at the current mouse position
this.panZoomStack
.pushPanZoom(nextOrigin, this.panStartBounds.dimensions);
};
/**
* Get the set of domain objects which are being
* represented in this sub-plot.
* @returns {DomainObject[]} the domain objects which
* will have data plotted in this sub-plot
*/
SubPlot.prototype.getTelemetryObjects = function () {
return this.telemetryObjects;
};
/**
* Get ticks mark information appropriate for using in the
* template for this sub-plot's domain axis, as prepared
* by the PlotTickGenerator.
* @returns {Array} tick marks for the domain axis
*/
SubPlot.prototype.getDomainTicks = function () {
return this.domainTicks;
};
/**
* Get ticks mark information appropriate for using in the
* template for this sub-plot's range axis, as prepared
* by the PlotTickGenerator.
* @returns {Array} tick marks for the range axis
*/
SubPlot.prototype.getRangeTicks = function () {
return this.rangeTicks;
};
/**
* Get the drawing object associated with this sub-plot;
* this object will be passed to the mct-chart in which
* this sub-plot's lines will be plotted, as its "draw"
* attribute, and should have the same internal format
* expected by that directive.
* @return {object} the drawing object
*/
SubPlot.prototype.getDrawingObject = function () {
return this.draw;
};
/**
* Get the coordinates (as displayable text) for the
* current mouse position.
* @returns {string[]} the displayable domain and range
* coordinates over which the mouse is hovered
*/
SubPlot.prototype.getHoverCoordinates = function () {
return this.hoverCoordinates;
};
/**
* Handle mouse movement over the chart area.
* @param $event the mouse event
* @memberof platform/features/plot.SubPlot#
*/
SubPlot.prototype.hover = function ($event) {
this.hovering = true;
this.subPlotBounds = $event.target.getBoundingClientRect();
this.mousePosition = this.toMousePosition($event);
this.updateHoverCoordinates();
if (this.marqueeStart) {
this.updateMarqueeBox();
} }
if (this.panStart) {
// Converts from pixel coordinates to domain-range, this.updatePan();
// to interpret mouse gestures. this.updateDrawingBounds();
function mousePositionToDomainRange(mousePosition) { this.updateTicks();
return new PlotPosition(
mousePosition.x,
mousePosition.y,
mousePosition.width,
mousePosition.height,
panZoomStack
).getPosition();
} }
};
// Utility function to get the mouse position (in x,y /**
// pixel coordinates in the canvas area) from a mouse * Continue a previously-start pan or zoom gesture.
// event object. * @param $event the mouse event
function toMousePosition($event) { * @memberof platform/features/plot.SubPlot#
var bounds = subPlotBounds; */
SubPlot.prototype.continueDrag = function ($event) {
return { this.mousePosition = this.toMousePosition($event);
x: $event.clientX - bounds.left, if (this.marqueeStart) {
y: $event.clientY - bounds.top, this.updateMarqueeBox();
width: bounds.width,
height: bounds.height
};
} }
if (this.panStart) {
// Convert a domain-range position to a displayable this.updatePan();
// position. This will subtract the domain offset, which this.updateDrawingBounds();
// is used to bias domain values to minimize loss-of-precision this.updateTicks();
// associated with conversion to a 32-bit floating point
// format (which is needed in the chart area itself, by WebGL.)
function toDisplayable(position) {
return [ position[0] - domainOffset, position[1] ];
} }
};
/**
// Update the currnet hover coordinates * Initiate a marquee zoom action.
function updateHoverCoordinates() { * @param $event the mouse event
hoverCoordinates = mousePosition && */
mousePositionToDomainRange(mousePosition) SubPlot.prototype.startDrag = function ($event) {
.map(formatValue) this.subPlotBounds = $event.target.getBoundingClientRect();
.filter(isNonEmpty) this.mousePosition = this.toMousePosition($event);
.join(", "); // Treat any modifier key as a pan
} if ($event.altKey || $event.shiftKey || $event.ctrlKey) {
// Start panning
// Update the drawable marquee area to reflect current this.panStart = this.mousePosition;
// mouse position (or don't show it at all, if no marquee this.panStartBounds = this.panZoomStack.getPanZoom();
// zoom is in progress) // We're starting a pan, so add this back as a
function updateMarqueeBox() { // state on the stack; it will get replaced
// Express this as a box in the draw object, which // during the pan.
// is passed to an mct-chart in the template for rendering. this.panZoomStack.pushPanZoom(
draw.boxes = marqueeStart ? this.panStartBounds.origin,
[{ this.panStartBounds.dimensions
start: toDisplayable(mousePositionToDomainRange(marqueeStart)), );
end: toDisplayable(mousePositionToDomainRange(mousePosition)), $event.preventDefault();
color: [1, 1, 1, 0.5 ] } else {
}] : undefined; // Start marquee zooming
} this.marqueeStart = this.mousePosition;
this.updateMarqueeBox();
// Update the bounds (origin and dimensions) of the drawing area.
function updateDrawingBounds() {
var panZoom = panZoomStack.getPanZoom();
// Communicate pan-zoom state from stack to the draw object
// which is passed to mct-chart in the template.
draw.dimensions = panZoom.dimensions;
draw.origin = [
panZoom.origin[0] - domainOffset,
panZoom.origin[1]
];
}
// Update tick marks in scope.
function updateTicks() {
var tickGenerator = new PlotTickGenerator(panZoomStack, formatter);
domainTicks =
tickGenerator.generateDomainTicks(DOMAIN_TICKS);
rangeTicks =
tickGenerator.generateRangeTicks(RANGE_TICKS);
}
function updatePan() {
var start, current, delta, nextOrigin;
// Clear the previous panning pan-zoom state
panZoomStack.popPanZoom();
// Calculate what the new resulting pan-zoom should be
start = mousePositionToDomainRange(panStart);
current = mousePositionToDomainRange(mousePosition);
delta = [ current[0] - start[0], current[1] - start[1] ];
nextOrigin = [
panStartBounds.origin[0] - delta[0],
panStartBounds.origin[1] - delta[1]
];
// ...and push a new one at the current mouse position
panZoomStack.pushPanZoom(nextOrigin, panStartBounds.dimensions);
} }
};
/**
* Complete a marquee zoom action.
* @param $event the mouse event
*/
SubPlot.prototype.endDrag = function ($event) {
var self = this;
// Perform a marquee zoom. // Perform a marquee zoom.
function marqueeZoom(start, end) { function marqueeZoom(start, end) {
// Determine what boundary is described by the marquee, // Determine what boundary is described by the marquee,
// in domain-range values. Use the minima for origin, so that // in domain-range values. Use the minima for origin, so that
// it doesn't matter what direction the user marqueed in. // it doesn't matter what direction the user marqueed in.
var a = mousePositionToDomainRange(start), var a = self.mousePositionToDomainRange(start),
b = mousePositionToDomainRange(end), b = self.mousePositionToDomainRange(end),
origin = [ origin = [
Math.min(a[0], b[0]), Math.min(a[0], b[0]),
Math.min(a[1], b[1]) Math.min(a[1], b[1])
@ -197,196 +334,68 @@ define(
// Proceed with zoom if zoom dimensions are non zeros // Proceed with zoom if zoom dimensions are non zeros
if (!(dimensions[0] === 0 && dimensions[1] === 0)) { if (!(dimensions[0] === 0 && dimensions[1] === 0)) {
// Push the new state onto the pan-zoom stack // Push the new state onto the pan-zoom stack
panZoomStack.pushPanZoom(origin, dimensions); self.panZoomStack.pushPanZoom(origin, dimensions);
// Make sure tick marks reflect new bounds // Make sure tick marks reflect new bounds
updateTicks(); self.updateTicks();
} }
} }
// Start with the right initial drawing bounds, this.mousePosition = this.toMousePosition($event);
// tick marks this.subPlotBounds = undefined;
updateDrawingBounds(); if (this.marqueeStart) {
updateTicks(); marqueeZoom(this.marqueeStart, this.mousePosition);
this.marqueeStart = undefined;
this.updateMarqueeBox();
this.updateDrawingBounds();
this.updateTicks();
}
if (this.panStart) {
// End panning
this.panStart = undefined;
this.panStartBounds = undefined;
}
};
return { /**
/** * Update the drawing bounds, marquee box, and
* Get the set of domain objects which are being * tick marks for this subplot.
* represented in this sub-plot. */
* @returns {DomainObject[]} the domain objects which SubPlot.prototype.update = function () {
* will have data plotted in this sub-plot this.updateDrawingBounds();
* @memberof platform/features/plot.SubPlot# this.updateMarqueeBox();
*/ this.updateTicks();
getTelemetryObjects: function () { };
return telemetryObjects;
}, /**
/** * Set the domain offset associated with this sub-plot.
* Get ticks mark information appropriate for using in the * A domain offset is subtracted from all domain
* template for this sub-plot's domain axis, as prepared * before lines are drawn to avoid artifacts associated
* by the PlotTickGenerator. * with the use of 32-bit floats when domain values
* @returns {Array} tick marks for the domain axis * are often timestamps (due to insufficient precision.)
* @memberof platform/features/plot.SubPlot# * A SubPlot will be drawing boxes (for marquee zoom) in
*/ * the same offset coordinate space, so it needs to know
getDomainTicks: function () { * the value of this to position that marquee box
return domainTicks; * correctly.
}, * @param {number} value the domain offset
/** */
* Get ticks mark information appropriate for using in the SubPlot.prototype.setDomainOffset = function (value) {
* template for this sub-plot's range axis, as prepared this.domainOffset = value;
* by the PlotTickGenerator. };
* @returns {Array} tick marks for the range axis
* @memberof platform/features/plot.SubPlot# /**
*/ * When used with no argument, check whether or not the user
getRangeTicks: function () { * is currently hovering over this subplot. When used with
return rangeTicks; * an argument, set that state.
}, * @param {boolean} [state] the new hovering state
/** * @returns {boolean} the hovering state
* Get the drawing object associated with this sub-plot; */
* this object will be passed to the mct-chart in which SubPlot.prototype.isHovering = function (state) {
* this sub-plot's lines will be plotted, as its "draw" if (state !== undefined) {
* attribute, and should have the same internal format this.hovering = state;
* expected by that directive. }
* @return {object} the drawing object return this.hovering;
* @memberof platform/features/plot.SubPlot# };
*/
getDrawingObject: function () {
return draw;
},
/**
* Get the coordinates (as displayable text) for the
* current mouse position.
* @returns {string[]} the displayable domain and range
* coordinates over which the mouse is hovered
* @memberof platform/features/plot.SubPlot#
*/
getHoverCoordinates: function () {
return hoverCoordinates;
},
/**
* Handle mouse movement over the chart area.
* @param $event the mouse event
* @memberof platform/features/plot.SubPlot#
*/
hover: function ($event) {
isHovering = true;
subPlotBounds = $event.target.getBoundingClientRect();
mousePosition = toMousePosition($event);
updateHoverCoordinates();
if (marqueeStart) {
updateMarqueeBox();
}
if (panStart) {
updatePan();
updateDrawingBounds();
updateTicks();
}
},
/**
* Continue a previously-start pan or zoom gesture.
* @param $event the mouse event
* @memberof platform/features/plot.SubPlot#
*/
continueDrag: function ($event) {
mousePosition = toMousePosition($event);
if (marqueeStart) {
updateMarqueeBox();
}
if (panStart) {
updatePan();
updateDrawingBounds();
updateTicks();
}
},
/**
* Initiate a marquee zoom action.
* @param $event the mouse event
* @memberof platform/features/plot.SubPlot#
*/
startDrag: function ($event) {
subPlotBounds = $event.target.getBoundingClientRect();
mousePosition = toMousePosition($event);
// Treat any modifier key as a pan
if ($event.altKey || $event.shiftKey || $event.ctrlKey) {
// Start panning
panStart = mousePosition;
panStartBounds = panZoomStack.getPanZoom();
// We're starting a pan, so add this back as a
// state on the stack; it will get replaced
// during the pan.
panZoomStack.pushPanZoom(
panStartBounds.origin,
panStartBounds.dimensions
);
$event.preventDefault();
} else {
// Start marquee zooming
marqueeStart = mousePosition;
updateMarqueeBox();
}
},
/**
* Complete a marquee zoom action.
* @param $event the mouse event
* @memberof platform/features/plot.SubPlot#
*/
endDrag: function ($event) {
mousePosition = toMousePosition($event);
subPlotBounds = undefined;
if (marqueeStart) {
marqueeZoom(marqueeStart, mousePosition);
marqueeStart = undefined;
updateMarqueeBox();
updateDrawingBounds();
updateTicks();
}
if (panStart) {
// End panning
panStart = undefined;
panStartBounds = undefined;
}
},
/**
* Update the drawing bounds, marquee box, and
* tick marks for this subplot.
* @memberof platform/features/plot.SubPlot#
*/
update: function () {
updateDrawingBounds();
updateMarqueeBox();
updateTicks();
},
/**
* Set the domain offset associated with this sub-plot.
* A domain offset is subtracted from all domain
* before lines are drawn to avoid artifacts associated
* with the use of 32-bit floats when domain values
* are often timestamps (due to insufficient precision.)
* A SubPlot will be drawing boxes (for marquee zoom) in
* the same offset coordinate space, so it needs to know
* the value of this to position that marquee box
* correctly.
* @param {number} value the domain offset
* @memberof platform/features/plot.SubPlot#
*/
setDomainOffset: function (value) {
domainOffset = value;
},
/**
* When used with no argument, check whether or not the user
* is currently hovering over this subplot. When used with
* an argument, set that state.
* @param {boolean} [state] the new hovering state
* @returns {boolean} the hovering state
* @memberof platform/features/plot.SubPlot#
*/
isHovering: function (state) {
if (state !== undefined) {
isHovering = state;
}
return isHovering;
}
};
}
return SubPlot; return SubPlot;

View File

@ -35,28 +35,26 @@ define(
* @constructor * @constructor
*/ */
function SubPlotFactory(telemetryFormatter) { function SubPlotFactory(telemetryFormatter) {
return { this.telemetryFormatter = telemetryFormatter;
/**
* Instantiate a new sub-plot.
* @param {DomainObject[]} telemetryObjects the domain objects
* which will be plotted in this sub-plot
* @param {PlotPanZoomStack} panZoomStack the stack of pan-zoom
* states which is applicable to this sub-plot
* @returns {SubPlot} the instantiated sub-plot
* @method
* @memberof SubPlotFactory
* @memberof platform/features/plot.SubPlotFactory#
*/
createSubPlot: function (telemetryObjects, panZoomStack) {
return new SubPlot(
telemetryObjects,
panZoomStack,
telemetryFormatter
);
}
};
} }
/**
* Instantiate a new sub-plot.
* @param {DomainObject[]} telemetryObjects the domain objects
* which will be plotted in this sub-plot
* @param {PlotPanZoomStack} panZoomStack the stack of pan-zoom
* states which is applicable to this sub-plot
* @returns {SubPlot} the instantiated sub-plot
* @method
*/
SubPlotFactory.prototype.createSubPlot = function (telemetryObjects, panZoomStack) {
return new SubPlot(
telemetryObjects,
panZoomStack,
this.telemetryFormatter
);
};
return SubPlotFactory; return SubPlotFactory;
} }

View File

@ -30,15 +30,53 @@ define(
key: "stacked", key: "stacked",
name: "Stacked", name: "Stacked",
glyph: "m", glyph: "m",
factory: PlotStackMode Constructor: PlotStackMode
}, },
OVERLAID = { OVERLAID = {
key: "overlaid", key: "overlaid",
name: "Overlaid", name: "Overlaid",
glyph: "6", glyph: "6",
factory: PlotOverlayMode Constructor: PlotOverlayMode
}; };
/**
* Handles distinct behavior associated with different
* plot modes.
*
* @interface platform/features/plot.PlotModeHandler
* @private
*/
/**
* Plot telemetry to the sub-plot(s) managed by this mode.
* @param {platform/features/plot.PlotUpdater} updater a source
* of data that is ready to plot
* @method platform/features/plot.PlotModeHandler#plotTelemetry
*/
/**
* Get all sub-plots to be displayed in this mode; used
* to populate the plot template.
* @return {platform/features/plot.SubPlot[]} all sub-plots to
* display in this mode
* @method platform/features/plot.PlotModeHandler#getSubPlots
*/
/**
* Check if we are not in our base pan-zoom state (that is,
* there are some temporary user modifications to the
* current pan-zoom state.)
* @returns {boolean} true if not in the base pan-zoom state
* @method platform/features/plot.PlotModeHandler#isZoomed
*/
/**
* Undo the most recent pan/zoom change and restore
* the prior state.
* @method platform/features/plot.PlotModeHandler#stepBackPanZoom
*/
/**
* Undo all pan/zoom change and restore the base state.
* @method platform/features/plot.PlotModeHandler#unzoom
*/
/** /**
* Determines which plotting modes (stacked/overlaid) * Determines which plotting modes (stacked/overlaid)
* are applicable in a given plot view, maintains current * are applicable in a given plot view, maintains current
@ -46,73 +84,74 @@ define(
* different behaviors associated with these modes. * different behaviors associated with these modes.
* @memberof platform/features/plot * @memberof platform/features/plot
* @constructor * @constructor
* @param {DomainObject[]} the telemetry objects being * @param {DomainObject[]} telemetryObjects the telemetry objects being
* represented in this plot view * represented in this plot view
* @param {platform/features/plot.SubPlotFactory} subPlotFactory a
* factory for creating sub-plots
*/ */
function PlotModeOptions(telemetryObjects, subPlotFactory) { function PlotModeOptions(telemetryObjects, subPlotFactory) {
var options = telemetryObjects.length > 1 ? this.options = telemetryObjects.length > 1 ?
[ OVERLAID, STACKED ] : [ OVERLAID ], [ OVERLAID, STACKED ] : [ OVERLAID ];
mode = options[0], // Initial selection (overlaid) this.mode = this.options[0]; // Initial selection (overlaid)
modeHandler; this.telemetryObjects = telemetryObjects;
this.subPlotFactory = subPlotFactory;
return {
/**
* Get a handler for the current mode. This will handle
* plotting telemetry, providing subplots for the template,
* and view-level interactions with pan-zoom state.
* @returns {PlotOverlayMode|PlotStackMode} a handler
* for the current mode
* @memberof platform/features/plot.PlotModeOptions#
*/
getModeHandler: function () {
// Lazily initialize
if (!modeHandler) {
modeHandler = mode.factory(
telemetryObjects,
subPlotFactory
);
}
return modeHandler;
},
/**
* Get all mode options available for each plot. Each
* mode contains a `name` and `glyph` field suitable
* for display in a template.
* @return {Array} the available modes
* @memberof platform/features/plot.PlotModeOptions#
*/
getModeOptions: function () {
return options;
},
/**
* Get the plotting mode option currently in use.
* This will be one of the elements returned from
* `getModeOptions`.
* @return {object} the current mode
* @memberof platform/features/plot.PlotModeOptions#
*/
getMode: function () {
return mode;
},
/**
* Set the plotting mode option to use.
* The passed argument must be one of the options
* returned by `getModeOptions`.
* @param {object} option one of the plot mode options
* from `getModeOptions`
* @memberof platform/features/plot.PlotModeOptions#
*/
setMode: function (option) {
if (mode !== option) {
mode = option;
// Clear the existing mode handler, so it
// can be instantiated next time it's needed.
modeHandler = undefined;
}
}
};
} }
/**
* Get a handler for the current mode. This will handle
* plotting telemetry, providing subplots for the template,
* and view-level interactions with pan-zoom state.
* @returns {PlotOverlayMode|PlotStackMode} a handler
* for the current mode
*/
PlotModeOptions.prototype.getModeHandler = function () {
// Lazily initialize
if (!this.modeHandler) {
this.modeHandler = new this.mode.Constructor(
this.telemetryObjects,
this.subPlotFactory
);
}
return this.modeHandler;
};
/**
* Get all mode options available for each plot. Each
* mode contains a `name` and `glyph` field suitable
* for display in a template.
* @return {Array} the available modes
*/
PlotModeOptions.prototype.getModeOptions = function () {
return this.options;
};
/**
* Get the plotting mode option currently in use.
* This will be one of the elements returned from
* `getModeOptions`.
* @return {*} the current mode
*/
PlotModeOptions.prototype.getMode = function () {
return this.mode;
};
/**
* Set the plotting mode option to use.
* The passed argument must be one of the options
* returned by `getModeOptions`.
* @param {object} option one of the plot mode options
* from `getModeOptions`
*/
PlotModeOptions.prototype.setMode = function (option) {
if (this.mode !== option) {
this.mode = option;
// Clear the existing mode handler, so it
// can be instantiated next time it's needed.
this.modeHandler = undefined;
}
};
return PlotModeOptions; return PlotModeOptions;
} }
); );

View File

@ -31,81 +31,59 @@ define(
* is one sub-plot which contains all plotted objects. * is one sub-plot which contains all plotted objects.
* @memberof platform/features/plot * @memberof platform/features/plot
* @constructor * @constructor
* @implements {platform/features/plot.PlotModeHandler}
* @param {DomainObject[]} the domain objects to be plotted * @param {DomainObject[]} the domain objects to be plotted
*/ */
function PlotOverlayMode(telemetryObjects, subPlotFactory) { function PlotOverlayMode(telemetryObjects, subPlotFactory) {
var domainOffset, this.panZoomStack = new PlotPanZoomStack([], []);
panZoomStack = new PlotPanZoomStack([], []), this.subplot = subPlotFactory.createSubPlot(
subplot = subPlotFactory.createSubPlot( telemetryObjects,
telemetryObjects, this.panZoomStack
panZoomStack );
), this.subplots = [ this.subplot ];
subplots = [ subplot ]; }
function plotTelemetry(prepared) { PlotOverlayMode.prototype.plotTelemetry = function (updater) {
// Fit to the boundaries of the data, but don't // Fit to the boundaries of the data, but don't
// override any user-initiated pan-zoom changes. // override any user-initiated pan-zoom changes.
panZoomStack.setBasePanZoom( this.panZoomStack.setBasePanZoom(
prepared.getOrigin(), updater.getOrigin(),
prepared.getDimensions() updater.getDimensions()
); );
// Track the domain offset, used to bias domain values // Track the domain offset, used to bias domain values
// to minimize loss of precision when converted to 32-bit // to minimize loss of precision when converted to 32-bit
// floating point values for display. // floating point values for display.
subplot.setDomainOffset(prepared.getDomainOffset()); this.subplot.setDomainOffset(updater.getDomainOffset());
// Draw the buffers. Select color by index. // Draw the buffers. Select color by index.
subplot.getDrawingObject().lines = prepared.getLineBuffers().map(function (buf, i) { this.subplot.getDrawingObject().lines =
updater.getLineBuffers().map(function (buf, i) {
return { return {
buffer: buf.getBuffer(), buffer: buf.getBuffer(),
color: PlotPalette.getFloatColor(i), color: PlotPalette.getFloatColor(i),
points: buf.getLength() points: buf.getLength()
}; };
}); });
} };
return { PlotOverlayMode.prototype.getSubPlots = function () {
/** return this.subplots;
* Plot telemetry to the sub-plot(s) managed by this mode. };
* @param {PlotPreparer} prepared the prepared data to plot
* @memberof platform/features/plot.PlotOverlayMode# PlotOverlayMode.prototype.isZoomed = function () {
*/ return this.panZoomStack.getDepth() > 1;
plotTelemetry: plotTelemetry, };
/**
* Get all sub-plots to be displayed in this mode; used PlotOverlayMode.prototype.stepBackPanZoom = function () {
* to populate the plot template. this.panZoomStack.popPanZoom();
* @return {SubPlot[]} all sub-plots to display in this mode this.subplot.update();
* @memberof platform/features/plot.PlotOverlayMode# };
*/
getSubPlots: function () { PlotOverlayMode.prototype.unzoom = function () {
return subplots; this.panZoomStack.clearPanZoom();
}, this.subplot.update();
/** };
* Check if we are not in our base pan-zoom state (that is,
* there are some temporary user modifications to the
* current pan-zoom state.)
* @returns {boolean} true if not in the base pan-zoom state
* @memberof platform/features/plot.PlotOverlayMode#
*/
isZoomed: function () {
return panZoomStack.getDepth() > 1;
},
/**
* Undo the most recent pan/zoom change and restore
* the prior state.
* @memberof platform/features/plot.PlotOverlayMode#
*/
stepBackPanZoom: function () {
panZoomStack.popPanZoom();
subplot.update();
},
unzoom: function () {
panZoomStack.clearPanZoom();
subplot.update();
}
};
}
return PlotOverlayMode; return PlotOverlayMode;
} }

View File

@ -31,99 +31,76 @@ define(
* is one sub-plot for each plotted object. * is one sub-plot for each plotted object.
* @memberof platform/features/plot * @memberof platform/features/plot
* @constructor * @constructor
* @implements {platform/features/plot.PlotModeHandler}
* @param {DomainObject[]} the domain objects to be plotted * @param {DomainObject[]} the domain objects to be plotted
*/ */
function PlotStackMode(telemetryObjects, subPlotFactory) { function PlotStackMode(telemetryObjects, subPlotFactory) {
var domainOffset, var self = this;
panZoomStackGroup =
new PlotPanZoomStackGroup(telemetryObjects.length), this.panZoomStackGroup =
subplots = telemetryObjects.map(function (telemetryObject, i) { new PlotPanZoomStackGroup(telemetryObjects.length);
this.subplots = telemetryObjects.map(function (telemetryObject, i) {
return subPlotFactory.createSubPlot( return subPlotFactory.createSubPlot(
[telemetryObject], [telemetryObject],
panZoomStackGroup.getPanZoomStack(i) self.panZoomStackGroup.getPanZoomStack(i)
); );
}); });
function plotTelemetryTo(subplot, prepared, index) {
var buffer = prepared.getLineBuffers()[index];
// Track the domain offset, used to bias domain values
// to minimize loss of precision when converted to 32-bit
// floating point values for display.
subplot.setDomainOffset(prepared.getDomainOffset());
// Draw the buffers. Always use the 0th color, because there
// is one line per plot.
subplot.getDrawingObject().lines = [{
buffer: buffer.getBuffer(),
color: PlotPalette.getFloatColor(0),
points: buffer.getLength()
}];
}
function plotTelemetry(prepared) {
// Fit to the boundaries of the data, but don't
// override any user-initiated pan-zoom changes.
panZoomStackGroup.setBasePanZoom(
prepared.getOrigin(),
prepared.getDimensions()
);
subplots.forEach(function (subplot, index) {
plotTelemetryTo(subplot, prepared, index);
});
}
return {
/**
* Plot telemetry to the sub-plot(s) managed by this mode.
* @param {PlotPreparer} prepared the prepared data to plot
* @memberof platform/features/plot.PlotStackMode#
*/
plotTelemetry: plotTelemetry,
/**
* Get all sub-plots to be displayed in this mode; used
* to populate the plot template.
* @return {SubPlot[]} all sub-plots to display in this mode
* @memberof platform/features/plot.PlotStackMode#
*/
getSubPlots: function () {
return subplots;
},
/**
* Check if we are not in our base pan-zoom state (that is,
* there are some temporary user modifications to the
* current pan-zoom state.)
* @returns {boolean} true if not in the base pan-zoom state
* @memberof platform/features/plot.PlotStackMode#
*/
isZoomed: function () {
return panZoomStackGroup.getDepth() > 1;
},
/**
* Undo the most recent pan/zoom change and restore
* the prior state.
* @memberof platform/features/plot.PlotStackMode#
*/
stepBackPanZoom: function () {
panZoomStackGroup.popPanZoom();
subplots.forEach(function (subplot) {
subplot.update();
});
},
/**
* Undo all pan/zoom changes and restore the initial state.
* @memberof platform/features/plot.PlotStackMode#
*/
unzoom: function () {
panZoomStackGroup.clearPanZoom();
subplots.forEach(function (subplot) {
subplot.update();
});
}
};
} }
PlotStackMode.prototype.plotTelemetryTo = function (subplot, prepared, index) {
var buffer = prepared.getLineBuffers()[index];
// Track the domain offset, used to bias domain values
// to minimize loss of precision when converted to 32-bit
// floating point values for display.
subplot.setDomainOffset(prepared.getDomainOffset());
// Draw the buffers. Always use the 0th color, because there
// is one line per plot.
subplot.getDrawingObject().lines = [{
buffer: buffer.getBuffer(),
color: PlotPalette.getFloatColor(0),
points: buffer.getLength()
}];
};
PlotStackMode.prototype.plotTelemetry = function (prepared) {
var self = this;
// Fit to the boundaries of the data, but don't
// override any user-initiated pan-zoom changes.
this.panZoomStackGroup.setBasePanZoom(
prepared.getOrigin(),
prepared.getDimensions()
);
this.subplots.forEach(function (subplot, index) {
self.plotTelemetryTo(subplot, prepared, index);
});
};
PlotStackMode.prototype.getSubPlots = function () {
return this.subplots;
};
PlotStackMode.prototype.isZoomed = function () {
return this.panZoomStackGroup.getDepth() > 1;
};
PlotStackMode.prototype.stepBackPanZoom = function () {
this.panZoomStackGroup.popPanZoom();
this.subplots.forEach(function (subplot) {
subplot.update();
});
};
PlotStackMode.prototype.unzoom = function () {
this.panZoomStackGroup.clearPanZoom();
this.subplots.forEach(function (subplot) {
subplot.update();
});
};
return PlotStackMode; return PlotStackMode;
} }
); );

View File

@ -28,39 +28,38 @@ define(
/** /**
* Policy preventing the Plot view from being made available for * Policy preventing the Plot view from being made available for
* domain objects which have non-numeric telemetry. * domain objects which have non-numeric telemetry.
* @implements {Policy} * @implements {Policy.<View, DomainObject>}
* @constructor * @constructor
* @memberof platform/features/plot * @memberof platform/features/plot
*/ */
function PlotViewPolicy() { function PlotViewPolicy() {
function hasImageTelemetry(domainObject) { }
var telemetry = domainObject &&
domainObject.getCapability('telemetry'),
metadata = telemetry ? telemetry.getMetadata() : {},
ranges = metadata.ranges || [];
// Generally, we want to allow Plot for telemetry-providing function hasNumericTelemetry(domainObject) {
// objects (most telemetry is plottable.) We only want to var telemetry = domainObject &&
// suppress this for telemetry which only has explicitly domainObject.getCapability('telemetry'),
// non-numeric values. metadata = telemetry ? telemetry.getMetadata() : {},
return ranges.length === 0 || ranges.some(function (range) { ranges = metadata.ranges || [];
// Generally, we want to allow Plot for telemetry-providing
// objects (most telemetry is plottable.) We only want to
// suppress this for telemetry which only has explicitly
// non-numeric values.
return ranges.length === 0 || ranges.some(function (range) {
// Assume format is numeric if it is undefined // Assume format is numeric if it is undefined
// (numeric telemetry is the common case) // (numeric telemetry is the common case)
return range.format === undefined || return range.format === undefined ||
range.format === 'number'; range.format === 'number';
}); });
}
PlotViewPolicy.prototype.allow = function (view, domainObject) {
if (view.key === 'plot') {
return hasNumericTelemetry(domainObject);
} }
return { return true;
allow: function (view, domainObject) { };
if (view.key === 'plot') {
return hasImageTelemetry(domainObject);
}
return true;
}
};
}
return PlotViewPolicy; return PlotViewPolicy;
} }

View File

@ -39,6 +39,12 @@ define(
mockSeries, mockSeries,
controller; controller;
function bind(method, thisObj) {
return function () {
return method.apply(thisObj, arguments);
};
}
beforeEach(function () { beforeEach(function () {
mockScope = jasmine.createSpyObj( mockScope = jasmine.createSpyObj(
@ -196,13 +202,13 @@ define(
}); });
it("allows plots to be updated", function () { it("allows plots to be updated", function () {
expect(controller.update).not.toThrow(); expect(bind(controller.update, controller)).not.toThrow();
}); });
it("allows changing pan-zoom state", function () { it("allows changing pan-zoom state", function () {
expect(controller.isZoomed).not.toThrow(); expect(bind(controller.isZoomed, controller)).not.toThrow();
expect(controller.stepBackPanZoom).not.toThrow(); expect(bind(controller.stepBackPanZoom, controller)).not.toThrow();
expect(controller.unzoom).not.toThrow(); expect(bind(controller.unzoom, controller)).not.toThrow();
}); });
it("indicates if a request is pending", function () { it("indicates if a request is pending", function () {