MountainGraphWidget.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. "use strict";
  2. const { Heritage } = require("devtools/client/shared/widgets/view-helpers");
  3. const { AbstractCanvasGraph } = require("devtools/client/shared/widgets/Graphs");
  4. // Bar graph constants.
  5. const GRAPH_DAMPEN_VALUES_FACTOR = 0.9;
  6. const GRAPH_BACKGROUND_COLOR = "#ddd";
  7. // px
  8. const GRAPH_STROKE_WIDTH = 1;
  9. const GRAPH_STROKE_COLOR = "rgba(255,255,255,0.9)";
  10. // px
  11. const GRAPH_HELPER_LINES_DASH = [5];
  12. const GRAPH_HELPER_LINES_WIDTH = 1;
  13. const GRAPH_CLIPHEAD_LINE_COLOR = "#fff";
  14. const GRAPH_SELECTION_LINE_COLOR = "#fff";
  15. const GRAPH_SELECTION_BACKGROUND_COLOR = "rgba(44,187,15,0.25)";
  16. const GRAPH_SELECTION_STRIPES_COLOR = "rgba(255,255,255,0.1)";
  17. const GRAPH_REGION_BACKGROUND_COLOR = "transparent";
  18. const GRAPH_REGION_STRIPES_COLOR = "rgba(237,38,85,0.2)";
  19. /**
  20. * A mountain graph, plotting sets of values as line graphs.
  21. *
  22. * @see AbstractCanvasGraph for emitted events and other options.
  23. *
  24. * Example usage:
  25. * let graph = new MountainGraphWidget(node);
  26. * graph.format = ...;
  27. * graph.once("ready", () => {
  28. * graph.setData(src);
  29. * });
  30. *
  31. * The `graph.format` traits are mandatory and will determine how each
  32. * section of the moutain will be styled:
  33. * [
  34. * { color: "#f00", ... },
  35. * { color: "#0f0", ... },
  36. * ...
  37. * { color: "#00f", ... }
  38. * ]
  39. *
  40. * Data source format:
  41. * [
  42. * { delta: x1, values: [y11, y12, ... y1n] },
  43. * { delta: x2, values: [y21, y22, ... y2n] },
  44. * ...
  45. * { delta: xm, values: [ym1, ym2, ... ymn] }
  46. * ]
  47. * where the [ymn] values is assumed to aready be normalized from [0..1].
  48. *
  49. * @param nsIDOMNode parent
  50. * The parent node holding the graph.
  51. */
  52. this.MountainGraphWidget = function (parent, ...args) {
  53. AbstractCanvasGraph.apply(this, [parent, "mountain-graph", ...args]);
  54. };
  55. MountainGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
  56. backgroundColor: GRAPH_BACKGROUND_COLOR,
  57. strokeColor: GRAPH_STROKE_COLOR,
  58. strokeWidth: GRAPH_STROKE_WIDTH,
  59. clipheadLineColor: GRAPH_CLIPHEAD_LINE_COLOR,
  60. selectionLineColor: GRAPH_SELECTION_LINE_COLOR,
  61. selectionBackgroundColor: GRAPH_SELECTION_BACKGROUND_COLOR,
  62. selectionStripesColor: GRAPH_SELECTION_STRIPES_COLOR,
  63. regionBackgroundColor: GRAPH_REGION_BACKGROUND_COLOR,
  64. regionStripesColor: GRAPH_REGION_STRIPES_COLOR,
  65. /**
  66. * List of rules used to style each section of the mountain.
  67. * @see constructor
  68. * @type array
  69. */
  70. format: null,
  71. /**
  72. * Optionally offsets the `delta` in the data source by this scalar.
  73. */
  74. dataOffsetX: 0,
  75. /**
  76. * Optionally uses this value instead of the last tick in the data source
  77. * to compute the horizontal scaling.
  78. */
  79. dataDuration: 0,
  80. /**
  81. * The scalar used to multiply the graph values to leave some headroom
  82. * on the top.
  83. */
  84. dampenValuesFactor: GRAPH_DAMPEN_VALUES_FACTOR,
  85. /**
  86. * Renders the graph's background.
  87. * @see AbstractCanvasGraph.prototype.buildBackgroundImage
  88. */
  89. buildBackgroundImage: function () {
  90. let { canvas, ctx } = this._getNamedCanvas("mountain-graph-background");
  91. let width = this._width;
  92. let height = this._height;
  93. ctx.fillStyle = this.backgroundColor;
  94. ctx.fillRect(0, 0, width, height);
  95. return canvas;
  96. },
  97. /**
  98. * Renders the graph's data source.
  99. * @see AbstractCanvasGraph.prototype.buildGraphImage
  100. */
  101. buildGraphImage: function () {
  102. if (!this.format || !this.format.length) {
  103. throw new Error("The graph format traits are mandatory to style " +
  104. "the data source.");
  105. }
  106. let { canvas, ctx } = this._getNamedCanvas("mountain-graph-data");
  107. let width = this._width;
  108. let height = this._height;
  109. let totalSections = this.format.length;
  110. let totalTicks = this._data.length;
  111. let firstTick = totalTicks ? this._data[0].delta : 0;
  112. let lastTick = totalTicks ? this._data[totalTicks - 1].delta : 0;
  113. let duration = this.dataDuration || lastTick;
  114. let dataScaleX = this.dataScaleX = width / (duration - this.dataOffsetX);
  115. let dataScaleY = this.dataScaleY = height * this.dampenValuesFactor;
  116. // Draw the graph.
  117. let prevHeights = Array.from({ length: totalTicks }).fill(0);
  118. ctx.globalCompositeOperation = "destination-over";
  119. ctx.strokeStyle = this.strokeColor;
  120. ctx.lineWidth = this.strokeWidth * this._pixelRatio;
  121. for (let section = 0; section < totalSections; section++) {
  122. ctx.fillStyle = this.format[section].color || "#000";
  123. ctx.beginPath();
  124. for (let tick = 0; tick < totalTicks; tick++) {
  125. let { delta, values } = this._data[tick];
  126. let currX = (delta - this.dataOffsetX) * dataScaleX;
  127. let currY = values[section] * dataScaleY;
  128. let prevY = prevHeights[tick];
  129. if (delta == firstTick) {
  130. ctx.moveTo(-GRAPH_STROKE_WIDTH, height);
  131. ctx.lineTo(-GRAPH_STROKE_WIDTH, height - currY - prevY);
  132. }
  133. ctx.lineTo(currX, height - currY - prevY);
  134. if (delta == lastTick) {
  135. ctx.lineTo(width + GRAPH_STROKE_WIDTH, height - currY - prevY);
  136. ctx.lineTo(width + GRAPH_STROKE_WIDTH, height);
  137. }
  138. prevHeights[tick] += currY;
  139. }
  140. ctx.fill();
  141. ctx.stroke();
  142. }
  143. ctx.globalCompositeOperation = "source-over";
  144. ctx.lineWidth = GRAPH_HELPER_LINES_WIDTH;
  145. ctx.setLineDash(GRAPH_HELPER_LINES_DASH);
  146. // Draw the maximum value horizontal line.
  147. ctx.beginPath();
  148. let maximumY = height * this.dampenValuesFactor;
  149. ctx.moveTo(0, maximumY);
  150. ctx.lineTo(width, maximumY);
  151. ctx.stroke();
  152. // Draw the average value horizontal line.
  153. ctx.beginPath();
  154. let averageY = height / 2 * this.dampenValuesFactor;
  155. ctx.moveTo(0, averageY);
  156. ctx.lineTo(width, averageY);
  157. ctx.stroke();
  158. return canvas;
  159. }
  160. });
  161. module.exports = MountainGraphWidget;