[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
* @constructor
* @implements {platform/features/plot.Chart}
* @param {CanvasElement} canvas the canvas object to render upon
* @throws {Error} an error is thrown if Canvas's 2D API is unavailable.
*/
function Canvas2DChart(canvas) {
var c2d = canvas.getContext('2d'),
width = canvas.width,
height = canvas.height,
dimensions = [ width, height ],
origin = [ 0, 0 ];
this.canvas = canvas;
this.c2d = canvas.getContext('2d');
this.width = canvas.width;
this.height = canvas.height;
this.dimensions = [ this.width, this.height ];
this.origin = [ 0, 0 ];
// Convert from logical to physical x coordinates
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) {
if (!this.c2d) {
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;
}
);

View File

@ -51,6 +51,7 @@ define(
*
* @memberof platform/features/plot
* @constructor
* @implements {platform/features/plot.Chart}
* @param {CanvasElement} canvas the canvas object to render upon
* @throws {Error} an error is thrown if WebGL is unavailable.
*/
@ -62,8 +63,7 @@ define(
aVertexPosition,
uColor,
uDimensions,
uOrigin,
buffer;
uOrigin;
// Ensure a context was actually available before proceeding
if (!gl) {
@ -94,7 +94,7 @@ define(
gl.enableVertexAttribArray(aVertexPosition);
// 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
gl.lineWidth(2.0);
@ -103,79 +103,59 @@ define(
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
// Utility function to handle drawing of a buffer;
// drawType will determine whether this is a box, line, etc.
function doDraw(drawType, buf, color, points) {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, buf, gl.DYNAMIC_DRAW);
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);
}
};
this.gl = gl;
this.aVertexPosition = aVertexPosition;
this.uColor = uColor;
this.uDimensions = uDimensions;
this.uOrigin = uOrigin;
}
// 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;
}
);

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;
}
);

View File

@ -61,15 +61,11 @@ define(
throttle,
PLOT_FIXED_DURATION
) {
var subPlotFactory = new SubPlotFactory(telemetryFormatter),
modeOptions = new PlotModeOptions([], subPlotFactory),
subplots = [],
var self = this,
subPlotFactory = new SubPlotFactory(telemetryFormatter),
cachedObjects = [],
limitTracker,
updater,
handle,
scheduleUpdate,
domainOffset;
handle;
// Populate the scope with axis information (specifically, options
// available for each axis.)
@ -91,18 +87,13 @@ define(
function setupModes(telemetryObjects) {
if (cachedObjects !== telemetryObjects) {
cachedObjects = telemetryObjects;
modeOptions = new PlotModeOptions(
self.modeOptions = new PlotModeOptions(
telemetryObjects || [],
subPlotFactory
);
}
}
// Update all sub-plots
function update() {
scheduleUpdate();
}
// Reinstantiate the plot updater (e.g. because we have a
// new subscription.) This will clear the plot.
function recreateUpdater() {
@ -112,7 +103,7 @@ define(
($scope.axes[1].active || {}).key,
PLOT_FIXED_DURATION
);
limitTracker = new PlotLimitTracker(
self.limitTracker = new PlotLimitTracker(
handle,
($scope.axes[1].active || {}).key
);
@ -125,19 +116,19 @@ define(
}
if (updater) {
updater.update();
modeOptions.getModeHandler().plotTelemetry(updater);
self.modeOptions.getModeHandler().plotTelemetry(updater);
}
if (limitTracker) {
limitTracker.update();
if (self.limitTracker) {
self.limitTracker.update();
}
update();
self.update();
}
// Display new historical data as it becomes available
function addHistoricalData(domainObject, series) {
updater.addHistorical(domainObject, series);
modeOptions.getModeHandler().plotTelemetry(updater);
update();
self.modeOptions.getModeHandler().plotTelemetry(updater);
self.update();
}
// 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
$scope.$watch('domainObject', subscribe);
// Unsubscribe when the plot is destroyed
$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;
}
);

View File

@ -50,141 +50,278 @@ define(
// We are used from a template often, so maintain
// state in local variables to allow for fast look-up,
// as is normal for controllers.
var draw = {},
rangeTicks = [],
domainTicks = [],
formatter = telemetryFormatter,
domainOffset,
mousePosition,
marqueeStart,
panStart,
panStartBounds,
subPlotBounds,
hoverCoordinates,
isHovering = false;
this.telemetryObjects = telemetryObjects;
this.domainTicks = [];
this.rangeTicks = [];
this.formatter = telemetryFormatter;
this.draw = {};
this.hovering = false;
this.panZoomStack = panZoomStack;
// Start with the right initial drawing bounds,
// tick marks
this.updateDrawingBounds();
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,
// index 1 is range.
function formatValue(v, i) {
return (i ?
formatter.formatRangeValue :
formatter.formatDomainValue)(v);
formatter.formatRangeValue :
formatter.formatDomainValue)(v);
}
// Utility function for filtering out empty strings.
function isNonEmpty(v) {
return typeof v === 'string' && v !== "";
this.hoverCoordinates = this.mousePosition &&
this.mousePositionToDomainRange(this.mousePosition)
.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();
}
// Converts from pixel coordinates to domain-range,
// to interpret mouse gestures.
function mousePositionToDomainRange(mousePosition) {
return new PlotPosition(
mousePosition.x,
mousePosition.y,
mousePosition.width,
mousePosition.height,
panZoomStack
).getPosition();
if (this.panStart) {
this.updatePan();
this.updateDrawingBounds();
this.updateTicks();
}
};
// Utility function to get the mouse position (in x,y
// pixel coordinates in the canvas area) from a mouse
// event object.
function toMousePosition($event) {
var bounds = subPlotBounds;
return {
x: $event.clientX - bounds.left,
y: $event.clientY - bounds.top,
width: bounds.width,
height: bounds.height
};
/**
* Continue a previously-start pan or zoom gesture.
* @param $event the mouse event
* @memberof platform/features/plot.SubPlot#
*/
SubPlot.prototype.continueDrag = function ($event) {
this.mousePosition = this.toMousePosition($event);
if (this.marqueeStart) {
this.updateMarqueeBox();
}
// 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.)
function toDisplayable(position) {
return [ position[0] - domainOffset, position[1] ];
if (this.panStart) {
this.updatePan();
this.updateDrawingBounds();
this.updateTicks();
}
};
// Update the currnet hover coordinates
function updateHoverCoordinates() {
hoverCoordinates = mousePosition &&
mousePositionToDomainRange(mousePosition)
.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)
function updateMarqueeBox() {
// Express this as a box in the draw object, which
// is passed to an mct-chart in the template for rendering.
draw.boxes = marqueeStart ?
[{
start: toDisplayable(mousePositionToDomainRange(marqueeStart)),
end: toDisplayable(mousePositionToDomainRange(mousePosition)),
color: [1, 1, 1, 0.5 ]
}] : undefined;
}
// 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);
/**
* Initiate a marquee zoom action.
* @param $event the mouse event
*/
SubPlot.prototype.startDrag = function ($event) {
this.subPlotBounds = $event.target.getBoundingClientRect();
this.mousePosition = this.toMousePosition($event);
// Treat any modifier key as a pan
if ($event.altKey || $event.shiftKey || $event.ctrlKey) {
// Start panning
this.panStart = this.mousePosition;
this.panStartBounds = this.panZoomStack.getPanZoom();
// We're starting a pan, so add this back as a
// state on the stack; it will get replaced
// during the pan.
this.panZoomStack.pushPanZoom(
this.panStartBounds.origin,
this.panStartBounds.dimensions
);
$event.preventDefault();
} else {
// Start marquee zooming
this.marqueeStart = this.mousePosition;
this.updateMarqueeBox();
}
};
/**
* Complete a marquee zoom action.
* @param $event the mouse event
*/
SubPlot.prototype.endDrag = function ($event) {
var self = this;
// Perform a marquee zoom.
function marqueeZoom(start, end) {
// Determine what boundary is described by the marquee,
// in domain-range values. Use the minima for origin, so that
// it doesn't matter what direction the user marqueed in.
var a = mousePositionToDomainRange(start),
b = mousePositionToDomainRange(end),
var a = self.mousePositionToDomainRange(start),
b = self.mousePositionToDomainRange(end),
origin = [
Math.min(a[0], b[0]),
Math.min(a[1], b[1])
@ -197,196 +334,68 @@ define(
// Proceed with zoom if zoom dimensions are non zeros
if (!(dimensions[0] === 0 && dimensions[1] === 0)) {
// 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
updateTicks();
self.updateTicks();
}
}
// Start with the right initial drawing bounds,
// tick marks
updateDrawingBounds();
updateTicks();
this.mousePosition = this.toMousePosition($event);
this.subPlotBounds = undefined;
if (this.marqueeStart) {
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 {
/**
* 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
* @memberof platform/features/plot.SubPlot#
*/
getTelemetryObjects: function () {
return 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
* @memberof platform/features/plot.SubPlot#
*/
getDomainTicks: function () {
return 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
* @memberof platform/features/plot.SubPlot#
*/
getRangeTicks: function () {
return 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
* @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;
}
};
}
/**
* Update the drawing bounds, marquee box, and
* tick marks for this subplot.
*/
SubPlot.prototype.update = function () {
this.updateDrawingBounds();
this.updateMarqueeBox();
this.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
*/
SubPlot.prototype.setDomainOffset = function (value) {
this.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
*/
SubPlot.prototype.isHovering = function (state) {
if (state !== undefined) {
this.hovering = state;
}
return this.hovering;
};
return SubPlot;

View File

@ -35,28 +35,26 @@ define(
* @constructor
*/
function SubPlotFactory(telemetryFormatter) {
return {
/**
* 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
);
}
};
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
*/
SubPlotFactory.prototype.createSubPlot = function (telemetryObjects, panZoomStack) {
return new SubPlot(
telemetryObjects,
panZoomStack,
this.telemetryFormatter
);
};
return SubPlotFactory;
}

View File

@ -30,15 +30,53 @@ define(
key: "stacked",
name: "Stacked",
glyph: "m",
factory: PlotStackMode
Constructor: PlotStackMode
},
OVERLAID = {
key: "overlaid",
name: "Overlaid",
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)
* are applicable in a given plot view, maintains current
@ -46,73 +84,74 @@ define(
* different behaviors associated with these modes.
* @memberof platform/features/plot
* @constructor
* @param {DomainObject[]} the telemetry objects being
* @param {DomainObject[]} telemetryObjects the telemetry objects being
* represented in this plot view
* @param {platform/features/plot.SubPlotFactory} subPlotFactory a
* factory for creating sub-plots
*/
function PlotModeOptions(telemetryObjects, subPlotFactory) {
var options = telemetryObjects.length > 1 ?
[ OVERLAID, STACKED ] : [ OVERLAID ],
mode = options[0], // Initial selection (overlaid)
modeHandler;
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;
}
}
};
this.options = telemetryObjects.length > 1 ?
[ OVERLAID, STACKED ] : [ OVERLAID ];
this.mode = this.options[0]; // Initial selection (overlaid)
this.telemetryObjects = telemetryObjects;
this.subPlotFactory = subPlotFactory;
}
/**
* 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;
}
);

View File

@ -31,81 +31,59 @@ define(
* is one sub-plot which contains all plotted objects.
* @memberof platform/features/plot
* @constructor
* @implements {platform/features/plot.PlotModeHandler}
* @param {DomainObject[]} the domain objects to be plotted
*/
function PlotOverlayMode(telemetryObjects, subPlotFactory) {
var domainOffset,
panZoomStack = new PlotPanZoomStack([], []),
subplot = subPlotFactory.createSubPlot(
telemetryObjects,
panZoomStack
),
subplots = [ subplot ];
this.panZoomStack = new PlotPanZoomStack([], []);
this.subplot = subPlotFactory.createSubPlot(
telemetryObjects,
this.panZoomStack
);
this.subplots = [ this.subplot ];
}
function plotTelemetry(prepared) {
// Fit to the boundaries of the data, but don't
// override any user-initiated pan-zoom changes.
panZoomStack.setBasePanZoom(
prepared.getOrigin(),
prepared.getDimensions()
);
PlotOverlayMode.prototype.plotTelemetry = function (updater) {
// Fit to the boundaries of the data, but don't
// override any user-initiated pan-zoom changes.
this.panZoomStack.setBasePanZoom(
updater.getOrigin(),
updater.getDimensions()
);
// 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());
// Track the domain offset, used to bias domain values
// to minimize loss of precision when converted to 32-bit
// floating point values for display.
this.subplot.setDomainOffset(updater.getDomainOffset());
// Draw the buffers. Select color by index.
subplot.getDrawingObject().lines = prepared.getLineBuffers().map(function (buf, i) {
// Draw the buffers. Select color by index.
this.subplot.getDrawingObject().lines =
updater.getLineBuffers().map(function (buf, i) {
return {
buffer: buf.getBuffer(),
color: PlotPalette.getFloatColor(i),
points: buf.getLength()
};
});
}
};
return {
/**
* Plot telemetry to the sub-plot(s) managed by this mode.
* @param {PlotPreparer} prepared the prepared data to plot
* @memberof platform/features/plot.PlotOverlayMode#
*/
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.PlotOverlayMode#
*/
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.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();
}
};
}
PlotOverlayMode.prototype.getSubPlots = function () {
return this.subplots;
};
PlotOverlayMode.prototype.isZoomed = function () {
return this.panZoomStack.getDepth() > 1;
};
PlotOverlayMode.prototype.stepBackPanZoom = function () {
this.panZoomStack.popPanZoom();
this.subplot.update();
};
PlotOverlayMode.prototype.unzoom = function () {
this.panZoomStack.clearPanZoom();
this.subplot.update();
};
return PlotOverlayMode;
}

View File

@ -31,99 +31,76 @@ define(
* is one sub-plot for each plotted object.
* @memberof platform/features/plot
* @constructor
* @implements {platform/features/plot.PlotModeHandler}
* @param {DomainObject[]} the domain objects to be plotted
*/
function PlotStackMode(telemetryObjects, subPlotFactory) {
var domainOffset,
panZoomStackGroup =
new PlotPanZoomStackGroup(telemetryObjects.length),
subplots = telemetryObjects.map(function (telemetryObject, i) {
var self = this;
this.panZoomStackGroup =
new PlotPanZoomStackGroup(telemetryObjects.length);
this.subplots = telemetryObjects.map(function (telemetryObject, i) {
return subPlotFactory.createSubPlot(
[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;
}
);

View File

@ -28,39 +28,38 @@ define(
/**
* Policy preventing the Plot view from being made available for
* domain objects which have non-numeric telemetry.
* @implements {Policy}
* @implements {Policy.<View, DomainObject>}
* @constructor
* @memberof platform/features/plot
*/
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
// 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) {
function hasNumericTelemetry(domainObject) {
var telemetry = domainObject &&
domainObject.getCapability('telemetry'),
metadata = telemetry ? telemetry.getMetadata() : {},
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
// (numeric telemetry is the common case)
return range.format === undefined ||
range.format === 'number';
range.format === 'number';
});
}
PlotViewPolicy.prototype.allow = function (view, domainObject) {
if (view.key === 'plot') {
return hasNumericTelemetry(domainObject);
}
return {
allow: function (view, domainObject) {
if (view.key === 'plot') {
return hasImageTelemetry(domainObject);
}
return true;
}
};
}
return true;
};
return PlotViewPolicy;
}

View File

@ -39,6 +39,12 @@ define(
mockSeries,
controller;
function bind(method, thisObj) {
return function () {
return method.apply(thisObj, arguments);
};
}
beforeEach(function () {
mockScope = jasmine.createSpyObj(
@ -196,13 +202,13 @@ define(
});
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 () {
expect(controller.isZoomed).not.toThrow();
expect(controller.stepBackPanZoom).not.toThrow();
expect(controller.unzoom).not.toThrow();
expect(bind(controller.isZoomed, controller)).not.toThrow();
expect(bind(controller.stepBackPanZoom, controller)).not.toThrow();
expect(bind(controller.unzoom, controller)).not.toThrow();
});
it("indicates if a request is pending", function () {