diff options
Diffstat (limited to 'devtools/client/shared/widgets/MountainGraphWidget.js')
-rw-r--r-- | devtools/client/shared/widgets/MountainGraphWidget.js | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/devtools/client/shared/widgets/MountainGraphWidget.js b/devtools/client/shared/widgets/MountainGraphWidget.js new file mode 100644 index 0000000000..728475114c --- /dev/null +++ b/devtools/client/shared/widgets/MountainGraphWidget.js @@ -0,0 +1,201 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { extend } = require("devtools/shared/extend"); +const { + AbstractCanvasGraph, +} = require("devtools/client/shared/widgets/Graphs"); + +// Bar graph constants. + +const GRAPH_DAMPEN_VALUES_FACTOR = 0.9; + +const GRAPH_BACKGROUND_COLOR = "#ddd"; +const GRAPH_STROKE_WIDTH = 1; // px +const GRAPH_STROKE_COLOR = "rgba(255,255,255,0.9)"; +const GRAPH_HELPER_LINES_DASH = [5]; // px +const GRAPH_HELPER_LINES_WIDTH = 1; // px + +const GRAPH_CLIPHEAD_LINE_COLOR = "#fff"; +const GRAPH_SELECTION_LINE_COLOR = "#fff"; +const GRAPH_SELECTION_BACKGROUND_COLOR = "rgba(44,187,15,0.25)"; +const GRAPH_SELECTION_STRIPES_COLOR = "rgba(255,255,255,0.1)"; +const GRAPH_REGION_BACKGROUND_COLOR = "transparent"; +const GRAPH_REGION_STRIPES_COLOR = "rgba(237,38,85,0.2)"; + +/** + * A mountain graph, plotting sets of values as line graphs. + * + * @see AbstractCanvasGraph for emitted events and other options. + * + * Example usage: + * let graph = new MountainGraphWidget(node); + * graph.format = ...; + * graph.once("ready", () => { + * graph.setData(src); + * }); + * + * The `graph.format` traits are mandatory and will determine how each + * section of the moutain will be styled: + * [ + * { color: "#f00", ... }, + * { color: "#0f0", ... }, + * ... + * { color: "#00f", ... } + * ] + * + * Data source format: + * [ + * { delta: x1, values: [y11, y12, ... y1n] }, + * { delta: x2, values: [y21, y22, ... y2n] }, + * ... + * { delta: xm, values: [ym1, ym2, ... ymn] } + * ] + * where the [ymn] values is assumed to aready be normalized from [0..1]. + * + * @param Node parent + * The parent node holding the graph. + */ +this.MountainGraphWidget = function(parent, ...args) { + AbstractCanvasGraph.apply(this, [parent, "mountain-graph", ...args]); +}; + +MountainGraphWidget.prototype = extend(AbstractCanvasGraph.prototype, { + backgroundColor: GRAPH_BACKGROUND_COLOR, + strokeColor: GRAPH_STROKE_COLOR, + strokeWidth: GRAPH_STROKE_WIDTH, + clipheadLineColor: GRAPH_CLIPHEAD_LINE_COLOR, + selectionLineColor: GRAPH_SELECTION_LINE_COLOR, + selectionBackgroundColor: GRAPH_SELECTION_BACKGROUND_COLOR, + selectionStripesColor: GRAPH_SELECTION_STRIPES_COLOR, + regionBackgroundColor: GRAPH_REGION_BACKGROUND_COLOR, + regionStripesColor: GRAPH_REGION_STRIPES_COLOR, + + /** + * List of rules used to style each section of the mountain. + * @see constructor + * @type array + */ + format: null, + + /** + * Optionally offsets the `delta` in the data source by this scalar. + */ + dataOffsetX: 0, + + /** + * Optionally uses this value instead of the last tick in the data source + * to compute the horizontal scaling. + */ + dataDuration: 0, + + /** + * The scalar used to multiply the graph values to leave some headroom + * on the top. + */ + dampenValuesFactor: GRAPH_DAMPEN_VALUES_FACTOR, + + /** + * Renders the graph's background. + * @see AbstractCanvasGraph.prototype.buildBackgroundImage + */ + buildBackgroundImage: function() { + const { canvas, ctx } = this._getNamedCanvas("mountain-graph-background"); + const width = this._width; + const height = this._height; + + ctx.fillStyle = this.backgroundColor; + ctx.fillRect(0, 0, width, height); + + return canvas; + }, + + /** + * Renders the graph's data source. + * @see AbstractCanvasGraph.prototype.buildGraphImage + */ + buildGraphImage: function() { + if (!this.format || !this.format.length) { + throw new Error( + "The graph format traits are mandatory to style " + "the data source." + ); + } + const { canvas, ctx } = this._getNamedCanvas("mountain-graph-data"); + const width = this._width; + const height = this._height; + + const totalSections = this.format.length; + const totalTicks = this._data.length; + const firstTick = totalTicks ? this._data[0].delta : 0; + const lastTick = totalTicks ? this._data[totalTicks - 1].delta : 0; + + const duration = this.dataDuration || lastTick; + const dataScaleX = (this.dataScaleX = + width / (duration - this.dataOffsetX)); + const dataScaleY = (this.dataScaleY = height * this.dampenValuesFactor); + + // Draw the graph. + + const prevHeights = Array.from({ length: totalTicks }).fill(0); + + ctx.globalCompositeOperation = "destination-over"; + ctx.strokeStyle = this.strokeColor; + ctx.lineWidth = this.strokeWidth * this._pixelRatio; + + for (let section = 0; section < totalSections; section++) { + ctx.fillStyle = this.format[section].color || "#000"; + ctx.beginPath(); + + for (let tick = 0; tick < totalTicks; tick++) { + const { delta, values } = this._data[tick]; + const currX = (delta - this.dataOffsetX) * dataScaleX; + const currY = values[section] * dataScaleY; + const prevY = prevHeights[tick]; + + if (delta == firstTick) { + ctx.moveTo(-GRAPH_STROKE_WIDTH, height); + ctx.lineTo(-GRAPH_STROKE_WIDTH, height - currY - prevY); + } + + ctx.lineTo(currX, height - currY - prevY); + + if (delta == lastTick) { + ctx.lineTo(width + GRAPH_STROKE_WIDTH, height - currY - prevY); + ctx.lineTo(width + GRAPH_STROKE_WIDTH, height); + } + + prevHeights[tick] += currY; + } + + ctx.fill(); + ctx.stroke(); + } + + ctx.globalCompositeOperation = "source-over"; + ctx.lineWidth = GRAPH_HELPER_LINES_WIDTH; + ctx.setLineDash(GRAPH_HELPER_LINES_DASH); + + // Draw the maximum value horizontal line. + + ctx.beginPath(); + const maximumY = height * this.dampenValuesFactor; + ctx.moveTo(0, maximumY); + ctx.lineTo(width, maximumY); + ctx.stroke(); + + // Draw the average value horizontal line. + + ctx.beginPath(); + const averageY = (height / 2) * this.dampenValuesFactor; + ctx.moveTo(0, averageY); + ctx.lineTo(width, averageY); + ctx.stroke(); + + return canvas; + }, +}); + +module.exports = MountainGraphWidget; |