mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1175686 - Pull line and bar graph widgets outside of Graphs.js into their own files, r=jsantell, a=Mossop
This commit is contained in:
parent
2da0a4a197
commit
16b2351d2f
@ -10,8 +10,8 @@
|
||||
const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
const { Heritage } = require("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
const { LineGraphWidget } = require("devtools/shared/widgets/Graphs");
|
||||
const { BarGraphWidget } = require("devtools/shared/widgets/Graphs");
|
||||
const LineGraphWidget = require("devtools/shared/widgets/LineGraphWidget");
|
||||
const BarGraphWidget = require("devtools/shared/widgets/BarGraphWidget");
|
||||
const { CanvasGraphUtils } = require("devtools/shared/widgets/Graphs");
|
||||
|
||||
loader.lazyRequireGetter(this, "promise");
|
||||
|
@ -48,12 +48,14 @@ EXTRA_JS_MODULES.devtools.shared += [
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.devtools.shared.widgets += [
|
||||
'widgets/BarGraphWidget.js',
|
||||
'widgets/CubicBezierPresets.js',
|
||||
'widgets/CubicBezierWidget.js',
|
||||
'widgets/FastListWidget.js',
|
||||
'widgets/FilterWidget.js',
|
||||
'widgets/FlameGraph.js',
|
||||
'widgets/Graphs.js',
|
||||
'widgets/LineGraphWidget.js',
|
||||
'widgets/MdnDocsWidget.js',
|
||||
'widgets/Spectrum.js',
|
||||
'widgets/TableWidget.js',
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
// Tests that graph widgets works properly.
|
||||
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
|
||||
const TEST_REGIONS = [{ start: 320, end: 460 }, { start: 780, end: 860 }];
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -4,7 +4,7 @@
|
||||
// Tests that graph widgets can handle clients getting/setting the
|
||||
// selection or cursor.
|
||||
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
// Tests that graph widgets can correctly compare selections and cursors.
|
||||
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
|
||||
const TEST_REGIONS = [{ start: 320, end: 460 }, { start: 780, end: 860 }];
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
|
||||
const TEST_REGIONS = [{ start: 320, end: 460 }, { start: 780, end: 860 }];
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -4,7 +4,7 @@
|
||||
// Tests if selecting, resizing, moving selections and zooming in/out works.
|
||||
|
||||
const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -4,7 +4,7 @@
|
||||
// Tests if selections can't be added via clicking, while not allowed.
|
||||
|
||||
const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -6,7 +6,7 @@
|
||||
// callbacks with textX / testY for convenience.
|
||||
|
||||
const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
|
||||
const TEST_REGIONS = [{ start: 320, end: 460 }, { start: 780, end: 860 }];
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -4,7 +4,7 @@
|
||||
// Tests that selections are drawn onto the canvas.
|
||||
|
||||
const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
let CURRENT_ZOOM = 1;
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
// Tests if a selection is dropped when clicking outside of it.
|
||||
|
||||
const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -4,7 +4,7 @@
|
||||
// Tests that line graphs properly create the gutter and tooltips.
|
||||
|
||||
const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -4,7 +4,7 @@
|
||||
// Tests that line graphs properly use the tooltips configuration properties.
|
||||
|
||||
const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -4,7 +4,7 @@
|
||||
// Tests that line graphs hide the tooltips when there's no data available.
|
||||
|
||||
const TEST_DATA = [];
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -5,7 +5,7 @@
|
||||
// the 'min' and 'max' tooltip is too small.
|
||||
|
||||
const TEST_DATA = [{ delta: 100, value: 60 }, { delta: 200, value: 59.9 }];
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -7,7 +7,7 @@
|
||||
const NO_DATA = [];
|
||||
const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
|
||||
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -5,7 +5,7 @@
|
||||
// gutter/tooltips and lines.
|
||||
|
||||
const TEST_DATA = [{ delta: 100, value: 60 }, { delta: 200, value: 1 }];
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -4,7 +4,7 @@
|
||||
// Tests that graphs properly handle resizing.
|
||||
|
||||
const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -5,7 +5,7 @@
|
||||
// the graph dimensions stay the same.
|
||||
|
||||
const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -2,7 +2,7 @@
|
||||
// Tests that graphs properly handle resizing.
|
||||
|
||||
const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
// Tests that bar graph create a legend as expected.
|
||||
|
||||
let {BarGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let BarGraphWidget = devtools.require("devtools/shared/widgets/BarGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
const CATEGORIES = [
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
// Tests that bar graph's legend items handle mouseover/mouseout.
|
||||
|
||||
let {BarGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let BarGraphWidget = devtools.require("devtools/shared/widgets/BarGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
const CATEGORIES = [
|
||||
|
@ -3,7 +3,9 @@
|
||||
|
||||
// Tests that canvas graphs can have their selection linked.
|
||||
|
||||
let {LineGraphWidget,BarGraphWidget,CanvasGraphUtils} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let BarGraphWidget = devtools.require("devtools/shared/widgets/BarGraphWidget");
|
||||
let {CanvasGraphUtils} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
// Tests that graph widgets may have a fixed width or height.
|
||||
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -4,7 +4,7 @@
|
||||
// Tests that graph widgets correctly emit mouse input events.
|
||||
|
||||
const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
const FAST_FPS = 60;
|
||||
const SLOW_FPS = 10;
|
||||
|
||||
// Each element represents a second
|
||||
const FRAMES= [FAST_FPS, FAST_FPS, FAST_FPS, SLOW_FPS, FAST_FPS];
|
||||
const TEST_DATA = [];
|
||||
@ -19,7 +20,7 @@ for (let frameRate of FRAMES) {
|
||||
}
|
||||
}
|
||||
|
||||
let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
|
||||
let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
|
476
browser/devtools/shared/widgets/BarGraphWidget.js
Normal file
476
browser/devtools/shared/widgets/BarGraphWidget.js
Normal file
@ -0,0 +1,476 @@
|
||||
"use strict";
|
||||
|
||||
const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
|
||||
const { Heritage, setNamedTimeout, clearNamedTimeout } = require("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
const { AbstractCanvasGraph, CanvasGraphUtils } = require("devtools/shared/widgets/Graphs");
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
// Bar graph constants.
|
||||
|
||||
const GRAPH_DAMPEN_VALUES_FACTOR = 0.75;
|
||||
const GRAPH_BARS_MARGIN_TOP = 1; // px
|
||||
const GRAPH_BARS_MARGIN_END = 1; // px
|
||||
const GRAPH_MIN_BARS_WIDTH = 5; // px
|
||||
const GRAPH_MIN_BLOCKS_HEIGHT = 1; // px
|
||||
|
||||
const GRAPH_BACKGROUND_GRADIENT_START = "rgba(0,136,204,0.0)";
|
||||
const GRAPH_BACKGROUND_GRADIENT_END = "rgba(255,255,255,0.25)";
|
||||
|
||||
const GRAPH_CLIPHEAD_LINE_COLOR = "#666";
|
||||
const GRAPH_SELECTION_LINE_COLOR = "#555";
|
||||
const GRAPH_SELECTION_BACKGROUND_COLOR = "rgba(0,136,204,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)";
|
||||
|
||||
const GRAPH_HIGHLIGHTS_MASK_BACKGROUND = "rgba(255,255,255,0.75)";
|
||||
const GRAPH_HIGHLIGHTS_MASK_STRIPES = "rgba(255,255,255,0.5)";
|
||||
|
||||
const GRAPH_LEGEND_MOUSEOVER_DEBOUNCE = 50; // ms
|
||||
|
||||
/**
|
||||
* A bar graph, plotting tuples of values as rectangles.
|
||||
*
|
||||
* @see AbstractCanvasGraph for emitted events and other options.
|
||||
*
|
||||
* Example usage:
|
||||
* let graph = new BarGraphWidget(node);
|
||||
* graph.format = ...;
|
||||
* graph.once("ready", () => {
|
||||
* graph.setData(src);
|
||||
* });
|
||||
*
|
||||
* The `graph.format` traits are mandatory and will determine how the values
|
||||
* are styled as "blocks" in every "bar":
|
||||
* [
|
||||
* { color: "#f00", label: "Foo" },
|
||||
* { color: "#0f0", label: "Bar" },
|
||||
* ...
|
||||
* { color: "#00f", label: "Baz" }
|
||||
* ]
|
||||
*
|
||||
* Data source format:
|
||||
* [
|
||||
* { delta: x1, values: [y11, y12, ... y1n] },
|
||||
* { delta: x2, values: [y21, y22, ... y2n] },
|
||||
* ...
|
||||
* { delta: xm, values: [ym1, ym2, ... ymn] }
|
||||
* ]
|
||||
* where each item in the array represents a "bar", for which every value
|
||||
* represents a "block" inside that "bar", plotted at the "delta" position.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the graph.
|
||||
*/
|
||||
this.BarGraphWidget = function(parent, ...args) {
|
||||
AbstractCanvasGraph.apply(this, [parent, "bar-graph", ...args]);
|
||||
|
||||
this.once("ready", () => {
|
||||
this._onLegendMouseOver = this._onLegendMouseOver.bind(this);
|
||||
this._onLegendMouseOut = this._onLegendMouseOut.bind(this);
|
||||
this._onLegendMouseDown = this._onLegendMouseDown.bind(this);
|
||||
this._onLegendMouseUp = this._onLegendMouseUp.bind(this);
|
||||
this._createLegend();
|
||||
});
|
||||
};
|
||||
|
||||
BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
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 colors used to fill each block inside every bar, also
|
||||
* corresponding to labels displayed in this graph's legend.
|
||||
* @see constructor
|
||||
*/
|
||||
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,
|
||||
|
||||
/**
|
||||
* Bars that are too close too each other in the graph will be combined.
|
||||
* This scalar specifies the required minimum width of each bar.
|
||||
*/
|
||||
minBarsWidth: GRAPH_MIN_BARS_WIDTH,
|
||||
|
||||
/**
|
||||
* Blocks in a bar that are too thin inside the bar will not be rendered.
|
||||
* This scalar specifies the required minimum height of each block.
|
||||
*/
|
||||
minBlocksHeight: GRAPH_MIN_BLOCKS_HEIGHT,
|
||||
|
||||
/**
|
||||
* Renders the graph's background.
|
||||
* @see AbstractCanvasGraph.prototype.buildBackgroundImage
|
||||
*/
|
||||
buildBackgroundImage: function() {
|
||||
let { canvas, ctx } = this._getNamedCanvas("bar-graph-background");
|
||||
let width = this._width;
|
||||
let height = this._height;
|
||||
|
||||
let gradient = ctx.createLinearGradient(0, 0, 0, height);
|
||||
gradient.addColorStop(0, GRAPH_BACKGROUND_GRADIENT_START);
|
||||
gradient.addColorStop(1, GRAPH_BACKGROUND_GRADIENT_END);
|
||||
ctx.fillStyle = gradient;
|
||||
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 "The graph format traits are mandatory to style the data source.";
|
||||
}
|
||||
let { canvas, ctx } = this._getNamedCanvas("bar-graph-data");
|
||||
let width = this._width;
|
||||
let height = this._height;
|
||||
|
||||
let totalTypes = this.format.length;
|
||||
let totalTicks = this._data.length;
|
||||
let lastTick = this._data[totalTicks - 1].delta;
|
||||
|
||||
let minBarsWidth = this.minBarsWidth * this._pixelRatio;
|
||||
let minBlocksHeight = this.minBlocksHeight * this._pixelRatio;
|
||||
|
||||
let duration = this.dataDuration || lastTick;
|
||||
let dataScaleX = this.dataScaleX = width / (duration - this.dataOffsetX);
|
||||
let dataScaleY = this.dataScaleY = height / this._calcMaxHeight({
|
||||
data: this._data,
|
||||
dataScaleX: dataScaleX,
|
||||
minBarsWidth: minBarsWidth
|
||||
}) * this.dampenValuesFactor;
|
||||
|
||||
// Draw the graph.
|
||||
|
||||
// Iterate over the blocks, then the bars, to draw all rectangles of
|
||||
// the same color in a single pass. See the @constructor for more
|
||||
// information about the data source, and how a "bar" contains "blocks".
|
||||
|
||||
this._blocksBoundingRects = [];
|
||||
let prevHeight = [];
|
||||
let scaledMarginEnd = GRAPH_BARS_MARGIN_END * this._pixelRatio;
|
||||
let scaledMarginTop = GRAPH_BARS_MARGIN_TOP * this._pixelRatio;
|
||||
|
||||
for (let type = 0; type < totalTypes; type++) {
|
||||
ctx.fillStyle = this.format[type].color || "#000";
|
||||
ctx.beginPath();
|
||||
|
||||
let prevRight = 0;
|
||||
let skippedCount = 0;
|
||||
let skippedHeight = 0;
|
||||
|
||||
for (let tick = 0; tick < totalTicks; tick++) {
|
||||
let delta = this._data[tick].delta;
|
||||
let value = this._data[tick].values[type] || 0;
|
||||
let blockRight = (delta - this.dataOffsetX) * dataScaleX;
|
||||
let blockHeight = value * dataScaleY;
|
||||
|
||||
let blockWidth = blockRight - prevRight;
|
||||
if (blockWidth < minBarsWidth) {
|
||||
skippedCount++;
|
||||
skippedHeight += blockHeight;
|
||||
continue;
|
||||
}
|
||||
|
||||
let averageHeight = (blockHeight + skippedHeight) / (skippedCount + 1);
|
||||
if (averageHeight >= minBlocksHeight) {
|
||||
let bottom = height - ~~prevHeight[tick];
|
||||
ctx.moveTo(prevRight, bottom);
|
||||
ctx.lineTo(prevRight, bottom - averageHeight);
|
||||
ctx.lineTo(blockRight, bottom - averageHeight);
|
||||
ctx.lineTo(blockRight, bottom);
|
||||
|
||||
// Remember this block's type and location.
|
||||
this._blocksBoundingRects.push({
|
||||
type: type,
|
||||
start: prevRight,
|
||||
end: blockRight,
|
||||
top: bottom - averageHeight,
|
||||
bottom: bottom
|
||||
});
|
||||
|
||||
if (prevHeight[tick] === undefined) {
|
||||
prevHeight[tick] = averageHeight + scaledMarginTop;
|
||||
} else {
|
||||
prevHeight[tick] += averageHeight + scaledMarginTop;
|
||||
}
|
||||
}
|
||||
|
||||
prevRight += blockWidth + scaledMarginEnd;
|
||||
skippedHeight = 0;
|
||||
skippedCount = 0;
|
||||
}
|
||||
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// The blocks bounding rects isn't guaranteed to be sorted ascending by
|
||||
// block location on the X axis. This should be the case, for better
|
||||
// cache cohesion and a faster `buildMaskImage`.
|
||||
this._blocksBoundingRects.sort((a, b) => a.start > b.start ? 1 : -1);
|
||||
|
||||
// Update the legend.
|
||||
|
||||
while (this._legendNode.hasChildNodes()) {
|
||||
this._legendNode.firstChild.remove();
|
||||
}
|
||||
for (let { color, label } of this.format) {
|
||||
this._createLegendItem(color, label);
|
||||
}
|
||||
|
||||
return canvas;
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the graph's mask.
|
||||
* Fades in only the parts of the graph that are inside the specified areas.
|
||||
*
|
||||
* @param array highlights
|
||||
* A list of { start, end } values. Optionally, each object
|
||||
* in the list may also specify { top, bottom } pixel values if the
|
||||
* highlighting shouldn't span across the full height of the graph.
|
||||
* @param boolean inPixels
|
||||
* Set this to true if the { start, end } values in the highlights
|
||||
* list are pixel values, and not values from the data source.
|
||||
* @param function unpack [optional]
|
||||
* @see AbstractCanvasGraph.prototype.getMappedSelection
|
||||
*/
|
||||
buildMaskImage: function(highlights, inPixels = false, unpack = e => e.delta) {
|
||||
// A null `highlights` array is used to clear the mask. An empty array
|
||||
// will mask the entire graph.
|
||||
if (!highlights) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get a render target for the highlights. It will be overlaid on top of
|
||||
// the existing graph, masking the areas that aren't highlighted.
|
||||
|
||||
let { canvas, ctx } = this._getNamedCanvas("graph-highlights");
|
||||
let width = this._width;
|
||||
let height = this._height;
|
||||
|
||||
// Draw the background mask.
|
||||
|
||||
let pattern = AbstractCanvasGraph.getStripePattern({
|
||||
ownerDocument: this._document,
|
||||
backgroundColor: GRAPH_HIGHLIGHTS_MASK_BACKGROUND,
|
||||
stripesColor: GRAPH_HIGHLIGHTS_MASK_STRIPES
|
||||
});
|
||||
ctx.fillStyle = pattern;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
// Clear highlighted areas.
|
||||
|
||||
let totalTicks = this._data.length;
|
||||
let firstTick = unpack(this._data[0]);
|
||||
let lastTick = unpack(this._data[totalTicks - 1]);
|
||||
|
||||
for (let { start, end, top, bottom } of highlights) {
|
||||
if (!inPixels) {
|
||||
start = CanvasGraphUtils.map(start, firstTick, lastTick, 0, width);
|
||||
end = CanvasGraphUtils.map(end, firstTick, lastTick, 0, width);
|
||||
}
|
||||
let firstSnap = findFirst(this._blocksBoundingRects, e => e.start >= start);
|
||||
let lastSnap = findLast(this._blocksBoundingRects, e => e.start >= start && e.end <= end);
|
||||
|
||||
let x1 = firstSnap ? firstSnap.start : start;
|
||||
let x2 = lastSnap ? lastSnap.end : firstSnap ? firstSnap.end : end;
|
||||
let y1 = top || 0;
|
||||
let y2 = bottom || height;
|
||||
ctx.clearRect(x1, y1, x2 - x1, y2 - y1);
|
||||
}
|
||||
|
||||
return canvas;
|
||||
},
|
||||
|
||||
/**
|
||||
* A list storing the bounding rectangle for each drawn block in the graph.
|
||||
* Created whenever `buildGraphImage` is invoked.
|
||||
*/
|
||||
_blocksBoundingRects: null,
|
||||
|
||||
/**
|
||||
* Calculates the height of the tallest bar that would eventially be rendered
|
||||
* in this graph.
|
||||
*
|
||||
* Bars that are too close too each other in the graph will be combined.
|
||||
* @see `minBarsWidth`
|
||||
*
|
||||
* @return number
|
||||
* The tallest bar height in this graph.
|
||||
*/
|
||||
_calcMaxHeight: function({ data, dataScaleX, minBarsWidth }) {
|
||||
let maxHeight = 0;
|
||||
let prevRight = 0;
|
||||
let skippedCount = 0;
|
||||
let skippedHeight = 0;
|
||||
let scaledMarginEnd = GRAPH_BARS_MARGIN_END * this._pixelRatio;
|
||||
|
||||
for (let { delta, values } of data) {
|
||||
let barRight = (delta - this.dataOffsetX) * dataScaleX;
|
||||
let barHeight = values.reduce((a, b) => a + b, 0);
|
||||
|
||||
let barWidth = barRight - prevRight;
|
||||
if (barWidth < minBarsWidth) {
|
||||
skippedCount++;
|
||||
skippedHeight += barHeight;
|
||||
continue;
|
||||
}
|
||||
|
||||
let averageHeight = (barHeight + skippedHeight) / (skippedCount + 1);
|
||||
maxHeight = Math.max(averageHeight, maxHeight);
|
||||
|
||||
prevRight += barWidth + scaledMarginEnd;
|
||||
skippedHeight = 0;
|
||||
skippedCount = 0;
|
||||
}
|
||||
|
||||
return maxHeight;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the legend container when constructing this graph.
|
||||
*/
|
||||
_createLegend: function() {
|
||||
let legendNode = this._legendNode = this._document.createElementNS(HTML_NS, "div");
|
||||
legendNode.className = "bar-graph-widget-legend";
|
||||
this._container.appendChild(legendNode);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a legend item when constructing this graph.
|
||||
*/
|
||||
_createLegendItem: function(color, label) {
|
||||
let itemNode = this._document.createElementNS(HTML_NS, "div");
|
||||
itemNode.className = "bar-graph-widget-legend-item";
|
||||
|
||||
let colorNode = this._document.createElementNS(HTML_NS, "span");
|
||||
colorNode.setAttribute("view", "color");
|
||||
colorNode.setAttribute("data-index", this._legendNode.childNodes.length);
|
||||
colorNode.style.backgroundColor = color;
|
||||
colorNode.addEventListener("mouseover", this._onLegendMouseOver);
|
||||
colorNode.addEventListener("mouseout", this._onLegendMouseOut);
|
||||
colorNode.addEventListener("mousedown", this._onLegendMouseDown);
|
||||
colorNode.addEventListener("mouseup", this._onLegendMouseUp);
|
||||
|
||||
let labelNode = this._document.createElementNS(HTML_NS, "span");
|
||||
labelNode.setAttribute("view", "label");
|
||||
labelNode.textContent = label;
|
||||
|
||||
itemNode.appendChild(colorNode);
|
||||
itemNode.appendChild(labelNode);
|
||||
this._legendNode.appendChild(itemNode);
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoked whenever a color node in the legend is hovered.
|
||||
*/
|
||||
_onLegendMouseOver: function(e) {
|
||||
setNamedTimeout("bar-graph-debounce", GRAPH_LEGEND_MOUSEOVER_DEBOUNCE, () => {
|
||||
let type = e.target.dataset.index;
|
||||
let rects = this._blocksBoundingRects.filter(e => e.type == type);
|
||||
|
||||
this._originalHighlights = this._mask;
|
||||
this._hasCustomHighlights = true;
|
||||
this.setMask(rects, true);
|
||||
|
||||
this.emit("legend-hover", [type, rects]);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoked whenever a color node in the legend is unhovered.
|
||||
*/
|
||||
_onLegendMouseOut: function() {
|
||||
clearNamedTimeout("bar-graph-debounce");
|
||||
|
||||
if (this._hasCustomHighlights) {
|
||||
this.setMask(this._originalHighlights);
|
||||
this._hasCustomHighlights = false;
|
||||
this._originalHighlights = null;
|
||||
}
|
||||
|
||||
this.emit("legend-unhover");
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoked whenever a color node in the legend is pressed.
|
||||
*/
|
||||
_onLegendMouseDown: function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
let type = e.target.dataset.index;
|
||||
let rects = this._blocksBoundingRects.filter(e => e.type == type);
|
||||
let leftmost = rects[0];
|
||||
let rightmost = rects[rects.length - 1];
|
||||
if (!leftmost || !rightmost) {
|
||||
this.dropSelection();
|
||||
} else {
|
||||
this.setSelection({ start: leftmost.start, end: rightmost.end });
|
||||
}
|
||||
|
||||
this.emit("legend-selection", [leftmost, rightmost]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoked whenever a color node in the legend is released.
|
||||
*/
|
||||
_onLegendMouseUp: function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Finds the first element in an array that validates a predicate.
|
||||
* @param array
|
||||
* @param function predicate
|
||||
* @return number
|
||||
*/
|
||||
function findFirst(array, predicate) {
|
||||
for (let i = 0, len = array.length; i < len; i++) {
|
||||
let element = array[i];
|
||||
if (predicate(element)) return element;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the last element in an array that validates a predicate.
|
||||
* @param array
|
||||
* @param function predicate
|
||||
* @return number
|
||||
*/
|
||||
function findLast(array, predicate) {
|
||||
for (let i = array.length - 1; i >= 0; i--) {
|
||||
let element = array[i];
|
||||
if (predicate(element)) return element;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BarGraphWidget;
|
@ -6,7 +6,6 @@
|
||||
const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
|
||||
const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
const { ViewHelpers } = require("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
const { Heritage, setNamedTimeout, clearNamedTimeout } = require("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
|
||||
loader.lazyRequireGetter(this, "promise");
|
||||
@ -22,8 +21,6 @@ const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const GRAPH_SRC = "chrome://browser/content/devtools/graphs-frame.xhtml";
|
||||
const WORKER_URL = "resource:///modules/devtools/GraphsWorker.js";
|
||||
|
||||
const L10N = new ViewHelpers.L10N();
|
||||
|
||||
// Generic constants.
|
||||
|
||||
const GRAPH_RESIZE_EVENTS_DRAIN = 100; // ms
|
||||
@ -44,53 +41,6 @@ const GRAPH_STRIPE_PATTERN_HEIGHT = 16; // px
|
||||
const GRAPH_STRIPE_PATTERN_LINE_WIDTH = 2; // px
|
||||
const GRAPH_STRIPE_PATTERN_LINE_SPACING = 4; // px
|
||||
|
||||
// Line graph constants.
|
||||
|
||||
const LINE_GRAPH_DAMPEN_VALUES = 0.85;
|
||||
const LINE_GRAPH_TOOLTIP_SAFE_BOUNDS = 8; // px
|
||||
const LINE_GRAPH_MIN_MAX_TOOLTIP_DISTANCE = 14; // px
|
||||
|
||||
const LINE_GRAPH_BACKGROUND_COLOR = "#0088cc";
|
||||
const LINE_GRAPH_STROKE_WIDTH = 1; // px
|
||||
const LINE_GRAPH_STROKE_COLOR = "rgba(255,255,255,0.9)";
|
||||
const LINE_GRAPH_HELPER_LINES_DASH = [5]; // px
|
||||
const LINE_GRAPH_HELPER_LINES_WIDTH = 1; // px
|
||||
const LINE_GRAPH_MAXIMUM_LINE_COLOR = "rgba(255,255,255,0.4)";
|
||||
const LINE_GRAPH_AVERAGE_LINE_COLOR = "rgba(255,255,255,0.7)";
|
||||
const LINE_GRAPH_MINIMUM_LINE_COLOR = "rgba(255,255,255,0.9)";
|
||||
const LINE_GRAPH_BACKGROUND_GRADIENT_START = "rgba(255,255,255,0.25)";
|
||||
const LINE_GRAPH_BACKGROUND_GRADIENT_END = "rgba(255,255,255,0.0)";
|
||||
|
||||
const LINE_GRAPH_CLIPHEAD_LINE_COLOR = "#fff";
|
||||
const LINE_GRAPH_SELECTION_LINE_COLOR = "#fff";
|
||||
const LINE_GRAPH_SELECTION_BACKGROUND_COLOR = "rgba(44,187,15,0.25)";
|
||||
const LINE_GRAPH_SELECTION_STRIPES_COLOR = "rgba(255,255,255,0.1)";
|
||||
const LINE_GRAPH_REGION_BACKGROUND_COLOR = "transparent";
|
||||
const LINE_GRAPH_REGION_STRIPES_COLOR = "rgba(237,38,85,0.2)";
|
||||
|
||||
// Bar graph constants.
|
||||
|
||||
const BAR_GRAPH_DAMPEN_VALUES = 0.75;
|
||||
const BAR_GRAPH_BARS_MARGIN_TOP = 1; // px
|
||||
const BAR_GRAPH_BARS_MARGIN_END = 1; // px
|
||||
const BAR_GRAPH_MIN_BARS_WIDTH = 5; // px
|
||||
const BAR_GRAPH_MIN_BLOCKS_HEIGHT = 1; // px
|
||||
|
||||
const BAR_GRAPH_BACKGROUND_GRADIENT_START = "rgba(0,136,204,0.0)";
|
||||
const BAR_GRAPH_BACKGROUND_GRADIENT_END = "rgba(255,255,255,0.25)";
|
||||
|
||||
const BAR_GRAPH_CLIPHEAD_LINE_COLOR = "#666";
|
||||
const BAR_GRAPH_SELECTION_LINE_COLOR = "#555";
|
||||
const BAR_GRAPH_SELECTION_BACKGROUND_COLOR = "rgba(0,136,204,0.25)";
|
||||
const BAR_GRAPH_SELECTION_STRIPES_COLOR = "rgba(255,255,255,0.1)";
|
||||
const BAR_GRAPH_REGION_BACKGROUND_COLOR = "transparent";
|
||||
const BAR_GRAPH_REGION_STRIPES_COLOR = "rgba(237,38,85,0.2)";
|
||||
|
||||
const BAR_GRAPH_HIGHLIGHTS_MASK_BACKGROUND = "rgba(255,255,255,0.75)";
|
||||
const BAR_GRAPH_HIGHLIGHTS_MASK_STRIPES = "rgba(255,255,255,0.5)";
|
||||
|
||||
const BAR_GRAPH_LEGEND_MOUSEOVER_DEBOUNCE = 50; // ms
|
||||
|
||||
/**
|
||||
* Small data primitives for all graphs.
|
||||
*/
|
||||
@ -1240,782 +1190,6 @@ AbstractCanvasGraph.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A basic line graph, plotting values on a curve and adding helper lines
|
||||
* and tooltips for maximum, average and minimum values.
|
||||
*
|
||||
* @see AbstractCanvasGraph for emitted events and other options.
|
||||
*
|
||||
* Example usage:
|
||||
* let graph = new LineGraphWidget(node, "units");
|
||||
* graph.once("ready", () => {
|
||||
* graph.setData(src);
|
||||
* });
|
||||
*
|
||||
* Data source format:
|
||||
* [
|
||||
* { delta: x1, value: y1 },
|
||||
* { delta: x2, value: y2 },
|
||||
* ...
|
||||
* { delta: xn, value: yn }
|
||||
* ]
|
||||
* where each item in the array represents a point in the graph.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the graph.
|
||||
* @param object options [optional]
|
||||
* `metric`: The metric displayed in the graph, e.g. "fps" or "bananas".
|
||||
* `min`: Boolean whether to show the min tooltip/gutter/line (default: true)
|
||||
* `max`: Boolean whether to show the max tooltip/gutter/line (default: true)
|
||||
* `avg`: Boolean whether to show the avg tooltip/gutter/line (default: true)
|
||||
*/
|
||||
this.LineGraphWidget = function(parent, options, ...args) {
|
||||
options = options || {};
|
||||
let metric = options.metric;
|
||||
|
||||
this._showMin = options.min !== false;
|
||||
this._showMax = options.max !== false;
|
||||
this._showAvg = options.avg !== false;
|
||||
AbstractCanvasGraph.apply(this, [parent, "line-graph", ...args]);
|
||||
|
||||
this.once("ready", () => {
|
||||
// Create all gutters and tooltips incase the showing of min/max/avg
|
||||
// are changed later
|
||||
this._gutter = this._createGutter();
|
||||
|
||||
this._maxGutterLine = this._createGutterLine("maximum");
|
||||
this._maxTooltip = this._createTooltip("maximum", "start", "max", metric);
|
||||
this._minGutterLine = this._createGutterLine("minimum");
|
||||
this._minTooltip = this._createTooltip("minimum", "start", "min", metric);
|
||||
this._avgGutterLine = this._createGutterLine("average");
|
||||
this._avgTooltip = this._createTooltip("average", "end", "avg", metric);
|
||||
});
|
||||
};
|
||||
|
||||
LineGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
backgroundColor: LINE_GRAPH_BACKGROUND_COLOR,
|
||||
backgroundGradientStart: LINE_GRAPH_BACKGROUND_GRADIENT_START,
|
||||
backgroundGradientEnd: LINE_GRAPH_BACKGROUND_GRADIENT_END,
|
||||
strokeColor: LINE_GRAPH_STROKE_COLOR,
|
||||
strokeWidth: LINE_GRAPH_STROKE_WIDTH,
|
||||
maximumLineColor: LINE_GRAPH_MAXIMUM_LINE_COLOR,
|
||||
averageLineColor: LINE_GRAPH_AVERAGE_LINE_COLOR,
|
||||
minimumLineColor: LINE_GRAPH_MINIMUM_LINE_COLOR,
|
||||
clipheadLineColor: LINE_GRAPH_CLIPHEAD_LINE_COLOR,
|
||||
selectionLineColor: LINE_GRAPH_SELECTION_LINE_COLOR,
|
||||
selectionBackgroundColor: LINE_GRAPH_SELECTION_BACKGROUND_COLOR,
|
||||
selectionStripesColor: LINE_GRAPH_SELECTION_STRIPES_COLOR,
|
||||
regionBackgroundColor: LINE_GRAPH_REGION_BACKGROUND_COLOR,
|
||||
regionStripesColor: LINE_GRAPH_REGION_STRIPES_COLOR,
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
dampenValuesFactor: LINE_GRAPH_DAMPEN_VALUES,
|
||||
|
||||
/**
|
||||
* Specifies if min/max/avg tooltips have arrow handlers on their sides.
|
||||
*/
|
||||
withTooltipArrows: true,
|
||||
|
||||
/**
|
||||
* Specifies if min/max/avg tooltips are positioned based on the actual
|
||||
* values, or just placed next to the graph corners.
|
||||
*/
|
||||
withFixedTooltipPositions: false,
|
||||
|
||||
/**
|
||||
* Takes a list of numbers and plots them on a line graph representing
|
||||
* the rate of occurences in a specified interval. Useful for drawing
|
||||
* framerate, for example, from a sequence of timestamps.
|
||||
*
|
||||
* @param array timestamps
|
||||
* A list of numbers representing time, ordered ascending. For example,
|
||||
* this can be the raw data received from the framerate actor, which
|
||||
* represents the elapsed time on each refresh driver tick.
|
||||
* @param number interval
|
||||
* The maximum amount of time to wait between calculations.
|
||||
* @param number duration
|
||||
* The duration of the recording in milliseconds.
|
||||
*/
|
||||
setDataFromTimestamps: Task.async(function*(timestamps, interval, duration) {
|
||||
let {
|
||||
plottedData,
|
||||
plottedMinMaxSum
|
||||
} = yield CanvasGraphUtils._performTaskInWorker("plotTimestampsGraph", {
|
||||
timestamps, interval, duration
|
||||
});
|
||||
|
||||
this._tempMinMaxSum = plottedMinMaxSum;
|
||||
this.setData(plottedData);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Renders the graph's data source.
|
||||
* @see AbstractCanvasGraph.prototype.buildGraphImage
|
||||
*/
|
||||
buildGraphImage: function() {
|
||||
let { canvas, ctx } = this._getNamedCanvas("line-graph-data");
|
||||
let width = this._width;
|
||||
let height = this._height;
|
||||
|
||||
let totalTicks = this._data.length;
|
||||
let firstTick = totalTicks ? this._data[0].delta : 0;
|
||||
let lastTick = totalTicks ? this._data[totalTicks - 1].delta : 0;
|
||||
let maxValue = Number.MIN_SAFE_INTEGER;
|
||||
let minValue = Number.MAX_SAFE_INTEGER;
|
||||
let avgValue = 0;
|
||||
|
||||
if (this._tempMinMaxSum) {
|
||||
maxValue = this._tempMinMaxSum.maxValue;
|
||||
minValue = this._tempMinMaxSum.minValue;
|
||||
avgValue = this._tempMinMaxSum.avgValue;
|
||||
} else {
|
||||
let sumValues = 0;
|
||||
for (let { delta, value } of this._data) {
|
||||
maxValue = Math.max(value, maxValue);
|
||||
minValue = Math.min(value, minValue);
|
||||
sumValues += value;
|
||||
}
|
||||
avgValue = sumValues / totalTicks;
|
||||
}
|
||||
|
||||
let duration = this.dataDuration || lastTick;
|
||||
let dataScaleX = this.dataScaleX = width / (duration - this.dataOffsetX);
|
||||
let dataScaleY = this.dataScaleY = height / maxValue * this.dampenValuesFactor;
|
||||
|
||||
// Draw the background.
|
||||
|
||||
ctx.fillStyle = this.backgroundColor;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
// Draw the graph.
|
||||
|
||||
let gradient = ctx.createLinearGradient(0, height / 2, 0, height);
|
||||
gradient.addColorStop(0, this.backgroundGradientStart);
|
||||
gradient.addColorStop(1, this.backgroundGradientEnd);
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.strokeStyle = this.strokeColor;
|
||||
ctx.lineWidth = this.strokeWidth * this._pixelRatio;
|
||||
ctx.beginPath();
|
||||
|
||||
for (let { delta, value } of this._data) {
|
||||
let currX = (delta - this.dataOffsetX) * dataScaleX;
|
||||
let currY = height - value * dataScaleY;
|
||||
|
||||
if (delta == firstTick) {
|
||||
ctx.moveTo(-LINE_GRAPH_STROKE_WIDTH, height);
|
||||
ctx.lineTo(-LINE_GRAPH_STROKE_WIDTH, currY);
|
||||
}
|
||||
|
||||
ctx.lineTo(currX, currY);
|
||||
|
||||
if (delta == lastTick) {
|
||||
ctx.lineTo(width + LINE_GRAPH_STROKE_WIDTH, currY);
|
||||
ctx.lineTo(width + LINE_GRAPH_STROKE_WIDTH, height);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
this._drawOverlays(ctx, minValue, maxValue, avgValue, dataScaleY);
|
||||
|
||||
return canvas;
|
||||
},
|
||||
|
||||
/**
|
||||
* Draws the min, max and average horizontal lines, along with their
|
||||
* repsective tooltips.
|
||||
*
|
||||
* @param CanvasRenderingContext2D ctx
|
||||
* @param number minValue
|
||||
* @param number maxValue
|
||||
* @param number avgValue
|
||||
* @param number dataScaleY
|
||||
*/
|
||||
_drawOverlays: function(ctx, minValue, maxValue, avgValue, dataScaleY) {
|
||||
let width = this._width;
|
||||
let height = this._height;
|
||||
let totalTicks = this._data.length;
|
||||
|
||||
// Draw the maximum value horizontal line.
|
||||
if (this._showMax) {
|
||||
ctx.strokeStyle = this.maximumLineColor;
|
||||
ctx.lineWidth = LINE_GRAPH_HELPER_LINES_WIDTH;
|
||||
ctx.setLineDash(LINE_GRAPH_HELPER_LINES_DASH);
|
||||
ctx.beginPath();
|
||||
let maximumY = height - maxValue * dataScaleY;
|
||||
ctx.moveTo(0, maximumY);
|
||||
ctx.lineTo(width, maximumY);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Draw the average value horizontal line.
|
||||
if (this._showAvg) {
|
||||
ctx.strokeStyle = this.averageLineColor;
|
||||
ctx.lineWidth = LINE_GRAPH_HELPER_LINES_WIDTH;
|
||||
ctx.setLineDash(LINE_GRAPH_HELPER_LINES_DASH);
|
||||
ctx.beginPath();
|
||||
let averageY = height - avgValue * dataScaleY;
|
||||
ctx.moveTo(0, averageY);
|
||||
ctx.lineTo(width, averageY);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Draw the minimum value horizontal line.
|
||||
if (this._showMin) {
|
||||
ctx.strokeStyle = this.minimumLineColor;
|
||||
ctx.lineWidth = LINE_GRAPH_HELPER_LINES_WIDTH;
|
||||
ctx.setLineDash(LINE_GRAPH_HELPER_LINES_DASH);
|
||||
ctx.beginPath();
|
||||
let minimumY = height - minValue * dataScaleY;
|
||||
ctx.moveTo(0, minimumY);
|
||||
ctx.lineTo(width, minimumY);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Update the tooltips text and gutter lines.
|
||||
|
||||
this._maxTooltip.querySelector("[text=value]").textContent =
|
||||
L10N.numberWithDecimals(maxValue, 2);
|
||||
this._avgTooltip.querySelector("[text=value]").textContent =
|
||||
L10N.numberWithDecimals(avgValue, 2);
|
||||
this._minTooltip.querySelector("[text=value]").textContent =
|
||||
L10N.numberWithDecimals(minValue, 2);
|
||||
|
||||
let bottom = height / this._pixelRatio;
|
||||
let maxPosY = map(maxValue * this.dampenValuesFactor, 0, maxValue, bottom, 0);
|
||||
let avgPosY = map(avgValue * this.dampenValuesFactor, 0, maxValue, bottom, 0);
|
||||
let minPosY = map(minValue * this.dampenValuesFactor, 0, maxValue, bottom, 0);
|
||||
|
||||
let safeTop = LINE_GRAPH_TOOLTIP_SAFE_BOUNDS;
|
||||
let safeBottom = bottom - LINE_GRAPH_TOOLTIP_SAFE_BOUNDS;
|
||||
|
||||
let maxTooltipTop = (this.withFixedTooltipPositions
|
||||
? safeTop : clamp(maxPosY, safeTop, safeBottom));
|
||||
let avgTooltipTop = (this.withFixedTooltipPositions
|
||||
? safeTop : clamp(avgPosY, safeTop, safeBottom));
|
||||
let minTooltipTop = (this.withFixedTooltipPositions
|
||||
? safeBottom : clamp(minPosY, safeTop, safeBottom));
|
||||
|
||||
this._maxTooltip.style.top = maxTooltipTop + "px";
|
||||
this._avgTooltip.style.top = avgTooltipTop + "px";
|
||||
this._minTooltip.style.top = minTooltipTop + "px";
|
||||
|
||||
this._maxGutterLine.style.top = maxPosY + "px";
|
||||
this._avgGutterLine.style.top = avgPosY + "px";
|
||||
this._minGutterLine.style.top = minPosY + "px";
|
||||
|
||||
this._maxTooltip.setAttribute("with-arrows", this.withTooltipArrows);
|
||||
this._avgTooltip.setAttribute("with-arrows", this.withTooltipArrows);
|
||||
this._minTooltip.setAttribute("with-arrows", this.withTooltipArrows);
|
||||
|
||||
let distanceMinMax = Math.abs(maxTooltipTop - minTooltipTop);
|
||||
this._maxTooltip.hidden = this._showMax === false || !totalTicks || distanceMinMax < LINE_GRAPH_MIN_MAX_TOOLTIP_DISTANCE;
|
||||
this._avgTooltip.hidden = this._showAvg === false || !totalTicks;
|
||||
this._minTooltip.hidden = this._showMin === false || !totalTicks;
|
||||
this._gutter.hidden = (this._showMin === false && this._showAvg === false && this._showMax === false) || !totalTicks;
|
||||
|
||||
this._maxGutterLine.hidden = this._showMax === false;
|
||||
this._avgGutterLine.hidden = this._showAvg === false;
|
||||
this._minGutterLine.hidden = this._showMin === false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the gutter node when constructing this graph.
|
||||
* @return nsIDOMNode
|
||||
*/
|
||||
_createGutter: function() {
|
||||
let gutter = this._document.createElementNS(HTML_NS, "div");
|
||||
gutter.className = "line-graph-widget-gutter";
|
||||
gutter.setAttribute("hidden", true);
|
||||
this._container.appendChild(gutter);
|
||||
|
||||
return gutter;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the gutter line nodes when constructing this graph.
|
||||
* @return nsIDOMNode
|
||||
*/
|
||||
_createGutterLine: function(type) {
|
||||
let line = this._document.createElementNS(HTML_NS, "div");
|
||||
line.className = "line-graph-widget-gutter-line";
|
||||
line.setAttribute("type", type);
|
||||
this._gutter.appendChild(line);
|
||||
|
||||
return line;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the tooltip nodes when constructing this graph.
|
||||
* @return nsIDOMNode
|
||||
*/
|
||||
_createTooltip: function(type, arrow, info, metric) {
|
||||
let tooltip = this._document.createElementNS(HTML_NS, "div");
|
||||
tooltip.className = "line-graph-widget-tooltip";
|
||||
tooltip.setAttribute("type", type);
|
||||
tooltip.setAttribute("arrow", arrow);
|
||||
tooltip.setAttribute("hidden", true);
|
||||
|
||||
let infoNode = this._document.createElementNS(HTML_NS, "span");
|
||||
infoNode.textContent = info;
|
||||
infoNode.setAttribute("text", "info");
|
||||
|
||||
let valueNode = this._document.createElementNS(HTML_NS, "span");
|
||||
valueNode.textContent = 0;
|
||||
valueNode.setAttribute("text", "value");
|
||||
|
||||
let metricNode = this._document.createElementNS(HTML_NS, "span");
|
||||
metricNode.textContent = metric;
|
||||
metricNode.setAttribute("text", "metric");
|
||||
|
||||
tooltip.appendChild(infoNode);
|
||||
tooltip.appendChild(valueNode);
|
||||
tooltip.appendChild(metricNode);
|
||||
this._container.appendChild(tooltip);
|
||||
|
||||
return tooltip;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* A bar graph, plotting tuples of values as rectangles.
|
||||
*
|
||||
* @see AbstractCanvasGraph for emitted events and other options.
|
||||
*
|
||||
* Example usage:
|
||||
* let graph = new BarGraphWidget(node);
|
||||
* graph.format = ...;
|
||||
* graph.once("ready", () => {
|
||||
* graph.setData(src);
|
||||
* });
|
||||
*
|
||||
* The `graph.format` traits are mandatory and will determine how the values
|
||||
* are styled as "blocks" in every "bar":
|
||||
* [
|
||||
* { color: "#f00", label: "Foo" },
|
||||
* { color: "#0f0", label: "Bar" },
|
||||
* ...
|
||||
* { color: "#00f", label: "Baz" }
|
||||
* ]
|
||||
*
|
||||
* Data source format:
|
||||
* [
|
||||
* { delta: x1, values: [y11, y12, ... y1n] },
|
||||
* { delta: x2, values: [y21, y22, ... y2n] },
|
||||
* ...
|
||||
* { delta: xm, values: [ym1, ym2, ... ymn] }
|
||||
* ]
|
||||
* where each item in the array represents a "bar", for which every value
|
||||
* represents a "block" inside that "bar", plotted at the "delta" position.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the graph.
|
||||
*/
|
||||
this.BarGraphWidget = function(parent, ...args) {
|
||||
AbstractCanvasGraph.apply(this, [parent, "bar-graph", ...args]);
|
||||
|
||||
// Populated with [node, event, listener] entries which need to be removed
|
||||
// when this graph is being destroyed.
|
||||
this.outstandingEventListeners = [];
|
||||
|
||||
this.once("ready", () => {
|
||||
this._onLegendMouseOver = this._onLegendMouseOver.bind(this);
|
||||
this._onLegendMouseOut = this._onLegendMouseOut.bind(this);
|
||||
this._onLegendMouseDown = this._onLegendMouseDown.bind(this);
|
||||
this._onLegendMouseUp = this._onLegendMouseUp.bind(this);
|
||||
this._createLegend();
|
||||
});
|
||||
|
||||
this.once("destroyed", () => {
|
||||
for (let [node, event, listener] of this.outstandingEventListeners) {
|
||||
node.removeEventListener(event, listener);
|
||||
}
|
||||
this.outstandingEventListeners = null;
|
||||
});
|
||||
};
|
||||
|
||||
BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
clipheadLineColor: BAR_GRAPH_CLIPHEAD_LINE_COLOR,
|
||||
selectionLineColor: BAR_GRAPH_SELECTION_LINE_COLOR,
|
||||
selectionBackgroundColor: BAR_GRAPH_SELECTION_BACKGROUND_COLOR,
|
||||
selectionStripesColor: BAR_GRAPH_SELECTION_STRIPES_COLOR,
|
||||
regionBackgroundColor: BAR_GRAPH_REGION_BACKGROUND_COLOR,
|
||||
regionStripesColor: BAR_GRAPH_REGION_STRIPES_COLOR,
|
||||
|
||||
/**
|
||||
* List of colors used to fill each block inside every bar, also
|
||||
* corresponding to labels displayed in this graph's legend.
|
||||
*/
|
||||
format: null,
|
||||
|
||||
/**
|
||||
* Optionally offsets the `delta` in the data source by this scalar.
|
||||
*/
|
||||
dataOffsetX: 0,
|
||||
|
||||
/**
|
||||
* The scalar used to multiply the graph values to leave some headroom
|
||||
* on the top.
|
||||
*/
|
||||
dampenValuesFactor: BAR_GRAPH_DAMPEN_VALUES,
|
||||
|
||||
/**
|
||||
* Bars that are too close too each other in the graph will be combined.
|
||||
* This scalar specifies the required minimum width of each bar.
|
||||
*/
|
||||
minBarsWidth: BAR_GRAPH_MIN_BARS_WIDTH,
|
||||
|
||||
/**
|
||||
* Blocks in a bar that are too thin inside the bar will not be rendered.
|
||||
* This scalar specifies the required minimum height of each block.
|
||||
*/
|
||||
minBlocksHeight: BAR_GRAPH_MIN_BLOCKS_HEIGHT,
|
||||
|
||||
/**
|
||||
* Renders the graph's background.
|
||||
* @see AbstractCanvasGraph.prototype.buildBackgroundImage
|
||||
*/
|
||||
buildBackgroundImage: function() {
|
||||
let { canvas, ctx } = this._getNamedCanvas("bar-graph-background");
|
||||
let width = this._width;
|
||||
let height = this._height;
|
||||
|
||||
let gradient = ctx.createLinearGradient(0, 0, 0, height);
|
||||
gradient.addColorStop(0, BAR_GRAPH_BACKGROUND_GRADIENT_START);
|
||||
gradient.addColorStop(1, BAR_GRAPH_BACKGROUND_GRADIENT_END);
|
||||
ctx.fillStyle = gradient;
|
||||
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 "The graph format traits are mandatory to style the data source.";
|
||||
}
|
||||
let { canvas, ctx } = this._getNamedCanvas("bar-graph-data");
|
||||
let width = this._width;
|
||||
let height = this._height;
|
||||
|
||||
let totalTypes = this.format.length;
|
||||
let totalTicks = this._data.length;
|
||||
let lastTick = this._data[totalTicks - 1].delta;
|
||||
|
||||
let minBarsWidth = this.minBarsWidth * this._pixelRatio;
|
||||
let minBlocksHeight = this.minBlocksHeight * this._pixelRatio;
|
||||
|
||||
let dataScaleX = this.dataScaleX = width / (lastTick - this.dataOffsetX);
|
||||
let dataScaleY = this.dataScaleY = height / this._calcMaxHeight({
|
||||
data: this._data,
|
||||
dataScaleX: dataScaleX,
|
||||
minBarsWidth: minBarsWidth
|
||||
}) * this.dampenValuesFactor;
|
||||
|
||||
// Draw the graph.
|
||||
|
||||
// Iterate over the blocks, then the bars, to draw all rectangles of
|
||||
// the same color in a single pass. See the @constructor for more
|
||||
// information about the data source, and how a "bar" contains "blocks".
|
||||
|
||||
this._blocksBoundingRects = [];
|
||||
let prevHeight = [];
|
||||
let scaledMarginEnd = BAR_GRAPH_BARS_MARGIN_END * this._pixelRatio;
|
||||
let unscaledMarginTop = BAR_GRAPH_BARS_MARGIN_TOP;
|
||||
|
||||
for (let type = 0; type < totalTypes; type++) {
|
||||
ctx.fillStyle = this.format[type].color || "#000";
|
||||
ctx.beginPath();
|
||||
|
||||
let prevRight = 0;
|
||||
let skippedCount = 0;
|
||||
let skippedHeight = 0;
|
||||
|
||||
for (let tick = 0; tick < totalTicks; tick++) {
|
||||
let delta = this._data[tick].delta;
|
||||
let value = this._data[tick].values[type] || 0;
|
||||
let blockRight = (delta - this.dataOffsetX) * dataScaleX;
|
||||
let blockHeight = value * dataScaleY;
|
||||
|
||||
let blockWidth = blockRight - prevRight;
|
||||
if (blockWidth < minBarsWidth) {
|
||||
skippedCount++;
|
||||
skippedHeight += blockHeight;
|
||||
continue;
|
||||
}
|
||||
|
||||
let averageHeight = (blockHeight + skippedHeight) / (skippedCount + 1);
|
||||
if (averageHeight >= minBlocksHeight) {
|
||||
let bottom = height - ~~prevHeight[tick];
|
||||
ctx.moveTo(prevRight, bottom);
|
||||
ctx.lineTo(prevRight, bottom - averageHeight);
|
||||
ctx.lineTo(blockRight, bottom - averageHeight);
|
||||
ctx.lineTo(blockRight, bottom);
|
||||
|
||||
// Remember this block's type and location.
|
||||
this._blocksBoundingRects.push({
|
||||
type: type,
|
||||
start: prevRight,
|
||||
end: blockRight,
|
||||
top: bottom - averageHeight,
|
||||
bottom: bottom
|
||||
});
|
||||
|
||||
if (prevHeight[tick] === undefined) {
|
||||
prevHeight[tick] = averageHeight + unscaledMarginTop;
|
||||
} else {
|
||||
prevHeight[tick] += averageHeight + unscaledMarginTop;
|
||||
}
|
||||
}
|
||||
|
||||
prevRight += blockWidth + scaledMarginEnd;
|
||||
skippedHeight = 0;
|
||||
skippedCount = 0;
|
||||
}
|
||||
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// The blocks bounding rects isn't guaranteed to be sorted ascending by
|
||||
// block location on the X axis. This should be the case, for better
|
||||
// cache cohesion and a faster `buildMaskImage`.
|
||||
this._blocksBoundingRects.sort((a, b) => a.start > b.start ? 1 : -1);
|
||||
|
||||
// Update the legend.
|
||||
|
||||
while (this._legendNode.hasChildNodes()) {
|
||||
this._legendNode.firstChild.remove();
|
||||
}
|
||||
for (let { color, label } of this.format) {
|
||||
this._createLegendItem(color, label);
|
||||
}
|
||||
|
||||
return canvas;
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the graph's mask.
|
||||
* Fades in only the parts of the graph that are inside the specified areas.
|
||||
*
|
||||
* @param array highlights
|
||||
* A list of { start, end } values. Optionally, each object
|
||||
* in the list may also specify { top, bottom } pixel values if the
|
||||
* highlighting shouldn't span across the full height of the graph.
|
||||
* @param boolean inPixels
|
||||
* Set this to true if the { start, end } values in the highlights
|
||||
* list are pixel values, and not values from the data source.
|
||||
* @param function unpack [optional]
|
||||
* @see AbstractCanvasGraph.prototype.getMappedSelection
|
||||
*/
|
||||
buildMaskImage: function(highlights, inPixels = false, unpack = e => e.delta) {
|
||||
// A null `highlights` array is used to clear the mask. An empty array
|
||||
// will mask the entire graph.
|
||||
if (!highlights) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get a render target for the highlights. It will be overlaid on top of
|
||||
// the existing graph, masking the areas that aren't highlighted.
|
||||
|
||||
let { canvas, ctx } = this._getNamedCanvas("graph-highlights");
|
||||
let width = this._width;
|
||||
let height = this._height;
|
||||
|
||||
// Draw the background mask.
|
||||
|
||||
let pattern = AbstractCanvasGraph.getStripePattern({
|
||||
ownerDocument: this._document,
|
||||
backgroundColor: BAR_GRAPH_HIGHLIGHTS_MASK_BACKGROUND,
|
||||
stripesColor: BAR_GRAPH_HIGHLIGHTS_MASK_STRIPES
|
||||
});
|
||||
ctx.fillStyle = pattern;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
// Clear highlighted areas.
|
||||
|
||||
let totalTicks = this._data.length;
|
||||
let firstTick = unpack(this._data[0]);
|
||||
let lastTick = unpack(this._data[totalTicks - 1]);
|
||||
|
||||
for (let { start, end, top, bottom } of highlights) {
|
||||
if (!inPixels) {
|
||||
start = map(start, firstTick, lastTick, 0, width);
|
||||
end = map(end, firstTick, lastTick, 0, width);
|
||||
}
|
||||
let firstSnap = findFirst(this._blocksBoundingRects, e => e.start >= start);
|
||||
let lastSnap = findLast(this._blocksBoundingRects, e => e.start >= start && e.end <= end);
|
||||
|
||||
let x1 = firstSnap ? firstSnap.start : start;
|
||||
let x2 = lastSnap ? lastSnap.end : firstSnap ? firstSnap.end : end;
|
||||
let y1 = top || 0;
|
||||
let y2 = bottom || height;
|
||||
ctx.clearRect(x1, y1, x2 - x1, y2 - y1);
|
||||
}
|
||||
|
||||
return canvas;
|
||||
},
|
||||
|
||||
/**
|
||||
* A list storing the bounding rectangle for each drawn block in the graph.
|
||||
* Created whenever `buildGraphImage` is invoked.
|
||||
*/
|
||||
_blocksBoundingRects: null,
|
||||
|
||||
/**
|
||||
* Calculates the height of the tallest bar that would eventially be rendered
|
||||
* in this graph.
|
||||
*
|
||||
* Bars that are too close too each other in the graph will be combined.
|
||||
* @see `minBarsWidth`
|
||||
*
|
||||
* @return number
|
||||
* The tallest bar height in this graph.
|
||||
*/
|
||||
_calcMaxHeight: function({ data, dataScaleX, minBarsWidth }) {
|
||||
let maxHeight = 0;
|
||||
let prevRight = 0;
|
||||
let skippedCount = 0;
|
||||
let skippedHeight = 0;
|
||||
let scaledMarginEnd = BAR_GRAPH_BARS_MARGIN_END * this._pixelRatio;
|
||||
|
||||
for (let { delta, values } of data) {
|
||||
let barRight = (delta - this.dataOffsetX) * dataScaleX;
|
||||
let barHeight = values.reduce((a, b) => a + b, 0);
|
||||
|
||||
let barWidth = barRight - prevRight;
|
||||
if (barWidth < minBarsWidth) {
|
||||
skippedCount++;
|
||||
skippedHeight += barHeight;
|
||||
continue;
|
||||
}
|
||||
|
||||
let averageHeight = (barHeight + skippedHeight) / (skippedCount + 1);
|
||||
maxHeight = Math.max(averageHeight, maxHeight);
|
||||
|
||||
prevRight += barWidth + scaledMarginEnd;
|
||||
skippedHeight = 0;
|
||||
skippedCount = 0;
|
||||
}
|
||||
|
||||
return maxHeight;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the legend container when constructing this graph.
|
||||
*/
|
||||
_createLegend: function() {
|
||||
let legendNode = this._legendNode = this._document.createElementNS(HTML_NS, "div");
|
||||
legendNode.className = "bar-graph-widget-legend";
|
||||
this._container.appendChild(legendNode);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a legend item when constructing this graph.
|
||||
*/
|
||||
_createLegendItem: function(color, label) {
|
||||
let itemNode = this._document.createElementNS(HTML_NS, "div");
|
||||
itemNode.className = "bar-graph-widget-legend-item";
|
||||
|
||||
let colorNode = this._document.createElementNS(HTML_NS, "span");
|
||||
colorNode.setAttribute("view", "color");
|
||||
colorNode.setAttribute("data-index", this._legendNode.childNodes.length);
|
||||
colorNode.style.backgroundColor = color;
|
||||
colorNode.addEventListener("mouseover", this._onLegendMouseOver);
|
||||
colorNode.addEventListener("mouseout", this._onLegendMouseOut);
|
||||
colorNode.addEventListener("mousedown", this._onLegendMouseDown);
|
||||
colorNode.addEventListener("mouseup", this._onLegendMouseUp);
|
||||
|
||||
this.outstandingEventListeners.push([colorNode, "mouseover", this._onLegendMouseOver]);
|
||||
this.outstandingEventListeners.push([colorNode, "mouseout", this._onLegendMouseOut]);
|
||||
this.outstandingEventListeners.push([colorNode, "mousedown", this._onLegendMouseDown]);
|
||||
this.outstandingEventListeners.push([colorNode, "mouseup", this._onLegendMouseUp]);
|
||||
|
||||
let labelNode = this._document.createElementNS(HTML_NS, "span");
|
||||
labelNode.setAttribute("view", "label");
|
||||
labelNode.textContent = label;
|
||||
|
||||
itemNode.appendChild(colorNode);
|
||||
itemNode.appendChild(labelNode);
|
||||
this._legendNode.appendChild(itemNode);
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoked whenever a color node in the legend is hovered.
|
||||
*/
|
||||
_onLegendMouseOver: function(e) {
|
||||
setNamedTimeout("bar-graph-debounce", BAR_GRAPH_LEGEND_MOUSEOVER_DEBOUNCE, () => {
|
||||
let type = e.target.dataset.index;
|
||||
let rects = this._blocksBoundingRects.filter(e => e.type == type);
|
||||
|
||||
this._originalHighlights = this._mask;
|
||||
this._hasCustomHighlights = true;
|
||||
this.setMask(rects, true);
|
||||
|
||||
this.emit("legend-hover", [type, rects]);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoked whenever a color node in the legend is unhovered.
|
||||
*/
|
||||
_onLegendMouseOut: function() {
|
||||
clearNamedTimeout("bar-graph-debounce");
|
||||
|
||||
if (this._hasCustomHighlights) {
|
||||
this.setMask(this._originalHighlights);
|
||||
this._hasCustomHighlights = false;
|
||||
this._originalHighlights = null;
|
||||
}
|
||||
|
||||
this.emit("legend-unhover");
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoked whenever a color node in the legend is pressed.
|
||||
*/
|
||||
_onLegendMouseDown: function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
let type = e.target.dataset.index;
|
||||
let rects = this._blocksBoundingRects.filter(e => e.type == type);
|
||||
let leftmost = rects[0];
|
||||
let rightmost = rects[rects.length - 1];
|
||||
if (!leftmost || !rightmost) {
|
||||
this.dropSelection();
|
||||
} else {
|
||||
this.setSelection({ start: leftmost.start, end: rightmost.end });
|
||||
}
|
||||
|
||||
this.emit("legend-selection", [leftmost, rightmost]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoked whenever a color node in the legend is released.
|
||||
*/
|
||||
_onLegendMouseUp: function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
// Helper functions.
|
||||
|
||||
/**
|
||||
@ -2199,46 +1373,11 @@ function clamp(value, min, max) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the squared distance between two 2D points.
|
||||
*/
|
||||
function distSquared(x0, y0, x1, y1) {
|
||||
let xs = x1 - x0;
|
||||
let ys = y1 - y0;
|
||||
return xs * xs + ys * ys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first element in an array that validates a predicate.
|
||||
* @param array
|
||||
* @param function predicate
|
||||
* @return number
|
||||
*/
|
||||
function findFirst(array, predicate) {
|
||||
for (let i = 0, len = array.length; i < len; i++) {
|
||||
let element = array[i];
|
||||
if (predicate(element)) return element;
|
||||
}
|
||||
}
|
||||
|
||||
exports.GraphCursor = GraphCursor;
|
||||
exports.GraphArea = GraphArea;
|
||||
exports.GraphAreaDragger = GraphAreaDragger;
|
||||
exports.GraphAreaResizer = GraphAreaResizer;
|
||||
exports.AbstractCanvasGraph = AbstractCanvasGraph;
|
||||
exports.LineGraphWidget = LineGraphWidget;
|
||||
exports.BarGraphWidget = BarGraphWidget;
|
||||
exports.CanvasGraphUtils = CanvasGraphUtils;
|
||||
|
||||
/**
|
||||
* Finds the last element in an array that validates a predicate.
|
||||
* @param array
|
||||
* @param function predicate
|
||||
* @return number
|
||||
*/
|
||||
function findLast(array, predicate) {
|
||||
for (let i = array.length - 1; i >= 0; i--) {
|
||||
let element = array[i];
|
||||
if (predicate(element)) return element;
|
||||
}
|
||||
}
|
||||
exports.CanvasGraphUtils.map = map;
|
||||
exports.CanvasGraphUtils.clamp = clamp;
|
||||
|
386
browser/devtools/shared/widgets/LineGraphWidget.js
Normal file
386
browser/devtools/shared/widgets/LineGraphWidget.js
Normal file
@ -0,0 +1,386 @@
|
||||
"use strict";
|
||||
|
||||
const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
|
||||
const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
const { ViewHelpers, Heritage } = require("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
const { AbstractCanvasGraph, CanvasGraphUtils } = require("devtools/shared/widgets/Graphs");
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const L10N = new ViewHelpers.L10N();
|
||||
|
||||
// Line graph constants.
|
||||
|
||||
const GRAPH_DAMPEN_VALUES_FACTOR = 0.85;
|
||||
const GRAPH_TOOLTIP_SAFE_BOUNDS = 8; // px
|
||||
const GRAPH_MIN_MAX_TOOLTIP_DISTANCE = 14; // px
|
||||
|
||||
const GRAPH_BACKGROUND_COLOR = "#0088cc";
|
||||
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_MAXIMUM_LINE_COLOR = "rgba(255,255,255,0.4)";
|
||||
const GRAPH_AVERAGE_LINE_COLOR = "rgba(255,255,255,0.7)";
|
||||
const GRAPH_MINIMUM_LINE_COLOR = "rgba(255,255,255,0.9)";
|
||||
const GRAPH_BACKGROUND_GRADIENT_START = "rgba(255,255,255,0.25)";
|
||||
const GRAPH_BACKGROUND_GRADIENT_END = "rgba(255,255,255,0.0)";
|
||||
|
||||
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 basic line graph, plotting values on a curve and adding helper lines
|
||||
* and tooltips for maximum, average and minimum values.
|
||||
*
|
||||
* @see AbstractCanvasGraph for emitted events and other options.
|
||||
*
|
||||
* Example usage:
|
||||
* let graph = new LineGraphWidget(node, "units");
|
||||
* graph.once("ready", () => {
|
||||
* graph.setData(src);
|
||||
* });
|
||||
*
|
||||
* Data source format:
|
||||
* [
|
||||
* { delta: x1, value: y1 },
|
||||
* { delta: x2, value: y2 },
|
||||
* ...
|
||||
* { delta: xn, value: yn }
|
||||
* ]
|
||||
* where each item in the array represents a point in the graph.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the graph.
|
||||
* @param object options [optional]
|
||||
* `metric`: The metric displayed in the graph, e.g. "fps" or "bananas".
|
||||
* `min`: Boolean whether to show the min tooltip/gutter/line (default: true)
|
||||
* `max`: Boolean whether to show the max tooltip/gutter/line (default: true)
|
||||
* `avg`: Boolean whether to show the avg tooltip/gutter/line (default: true)
|
||||
*/
|
||||
this.LineGraphWidget = function(parent, options = {}, ...args) {
|
||||
let { metric, min, max, avg } = options;
|
||||
|
||||
this._showMin = min !== false;
|
||||
this._showMax = max !== false;
|
||||
this._showAvg = avg !== false;
|
||||
|
||||
AbstractCanvasGraph.apply(this, [parent, "line-graph", ...args]);
|
||||
|
||||
this.once("ready", () => {
|
||||
// Create all gutters and tooltips incase the showing of min/max/avg
|
||||
// are changed later
|
||||
this._gutter = this._createGutter();
|
||||
this._maxGutterLine = this._createGutterLine("maximum");
|
||||
this._maxTooltip = this._createTooltip("maximum", "start", "max", metric);
|
||||
this._minGutterLine = this._createGutterLine("minimum");
|
||||
this._minTooltip = this._createTooltip("minimum", "start", "min", metric);
|
||||
this._avgGutterLine = this._createGutterLine("average");
|
||||
this._avgTooltip = this._createTooltip("average", "end", "avg", metric);
|
||||
});
|
||||
};
|
||||
|
||||
LineGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
backgroundColor: GRAPH_BACKGROUND_COLOR,
|
||||
backgroundGradientStart: GRAPH_BACKGROUND_GRADIENT_START,
|
||||
backgroundGradientEnd: GRAPH_BACKGROUND_GRADIENT_END,
|
||||
strokeColor: GRAPH_STROKE_COLOR,
|
||||
strokeWidth: GRAPH_STROKE_WIDTH,
|
||||
maximumLineColor: GRAPH_MAXIMUM_LINE_COLOR,
|
||||
averageLineColor: GRAPH_AVERAGE_LINE_COLOR,
|
||||
minimumLineColor: GRAPH_MINIMUM_LINE_COLOR,
|
||||
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,
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
dampenValuesFactor: GRAPH_DAMPEN_VALUES_FACTOR,
|
||||
|
||||
/**
|
||||
* Specifies if min/max/avg tooltips have arrow handlers on their sides.
|
||||
*/
|
||||
withTooltipArrows: true,
|
||||
|
||||
/**
|
||||
* Specifies if min/max/avg tooltips are positioned based on the actual
|
||||
* values, or just placed next to the graph corners.
|
||||
*/
|
||||
withFixedTooltipPositions: false,
|
||||
|
||||
/**
|
||||
* Takes a list of numbers and plots them on a line graph representing
|
||||
* the rate of occurences in a specified interval. Useful for drawing
|
||||
* framerate, for example, from a sequence of timestamps.
|
||||
*
|
||||
* @param array timestamps
|
||||
* A list of numbers representing time, ordered ascending. For example,
|
||||
* this can be the raw data received from the framerate actor, which
|
||||
* represents the elapsed time on each refresh driver tick.
|
||||
* @param number interval
|
||||
* The maximum amount of time to wait between calculations.
|
||||
* @param number duration
|
||||
* The duration of the recording in milliseconds.
|
||||
*/
|
||||
setDataFromTimestamps: Task.async(function*(timestamps, interval, duration) {
|
||||
let {
|
||||
plottedData,
|
||||
plottedMinMaxSum
|
||||
} = yield CanvasGraphUtils._performTaskInWorker("plotTimestampsGraph", {
|
||||
timestamps, interval, duration
|
||||
});
|
||||
|
||||
this._tempMinMaxSum = plottedMinMaxSum;
|
||||
this.setData(plottedData);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Renders the graph's data source.
|
||||
* @see AbstractCanvasGraph.prototype.buildGraphImage
|
||||
*/
|
||||
buildGraphImage: function() {
|
||||
let { canvas, ctx } = this._getNamedCanvas("line-graph-data");
|
||||
let width = this._width;
|
||||
let height = this._height;
|
||||
|
||||
let totalTicks = this._data.length;
|
||||
let firstTick = totalTicks ? this._data[0].delta : 0;
|
||||
let lastTick = totalTicks ? this._data[totalTicks - 1].delta : 0;
|
||||
let maxValue = Number.MIN_SAFE_INTEGER;
|
||||
let minValue = Number.MAX_SAFE_INTEGER;
|
||||
let avgValue = 0;
|
||||
|
||||
if (this._tempMinMaxSum) {
|
||||
maxValue = this._tempMinMaxSum.maxValue;
|
||||
minValue = this._tempMinMaxSum.minValue;
|
||||
avgValue = this._tempMinMaxSum.avgValue;
|
||||
} else {
|
||||
let sumValues = 0;
|
||||
for (let { delta, value } of this._data) {
|
||||
maxValue = Math.max(value, maxValue);
|
||||
minValue = Math.min(value, minValue);
|
||||
sumValues += value;
|
||||
}
|
||||
avgValue = sumValues / totalTicks;
|
||||
}
|
||||
|
||||
let duration = this.dataDuration || lastTick;
|
||||
let dataScaleX = this.dataScaleX = width / (duration - this.dataOffsetX);
|
||||
let dataScaleY = this.dataScaleY = height / maxValue * this.dampenValuesFactor;
|
||||
|
||||
// Draw the background.
|
||||
|
||||
ctx.fillStyle = this.backgroundColor;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
// Draw the graph.
|
||||
|
||||
let gradient = ctx.createLinearGradient(0, height / 2, 0, height);
|
||||
gradient.addColorStop(0, this.backgroundGradientStart);
|
||||
gradient.addColorStop(1, this.backgroundGradientEnd);
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.strokeStyle = this.strokeColor;
|
||||
ctx.lineWidth = this.strokeWidth * this._pixelRatio;
|
||||
ctx.beginPath();
|
||||
|
||||
for (let { delta, value } of this._data) {
|
||||
let currX = (delta - this.dataOffsetX) * dataScaleX;
|
||||
let currY = height - value * dataScaleY;
|
||||
|
||||
if (delta == firstTick) {
|
||||
ctx.moveTo(-GRAPH_STROKE_WIDTH, height);
|
||||
ctx.lineTo(-GRAPH_STROKE_WIDTH, currY);
|
||||
}
|
||||
|
||||
ctx.lineTo(currX, currY);
|
||||
|
||||
if (delta == lastTick) {
|
||||
ctx.lineTo(width + GRAPH_STROKE_WIDTH, currY);
|
||||
ctx.lineTo(width + GRAPH_STROKE_WIDTH, height);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
this._drawOverlays(ctx, minValue, maxValue, avgValue, dataScaleY);
|
||||
|
||||
return canvas;
|
||||
},
|
||||
|
||||
/**
|
||||
* Draws the min, max and average horizontal lines, along with their
|
||||
* repsective tooltips.
|
||||
*
|
||||
* @param CanvasRenderingContext2D ctx
|
||||
* @param number minValue
|
||||
* @param number maxValue
|
||||
* @param number avgValue
|
||||
* @param number dataScaleY
|
||||
*/
|
||||
_drawOverlays: function(ctx, minValue, maxValue, avgValue, dataScaleY) {
|
||||
let width = this._width;
|
||||
let height = this._height;
|
||||
let totalTicks = this._data.length;
|
||||
|
||||
// Draw the maximum value horizontal line.
|
||||
if (this._showMax) {
|
||||
ctx.strokeStyle = this.maximumLineColor;
|
||||
ctx.lineWidth = GRAPH_HELPER_LINES_WIDTH;
|
||||
ctx.setLineDash(GRAPH_HELPER_LINES_DASH);
|
||||
ctx.beginPath();
|
||||
let maximumY = height - maxValue * dataScaleY;
|
||||
ctx.moveTo(0, maximumY);
|
||||
ctx.lineTo(width, maximumY);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Draw the average value horizontal line.
|
||||
if (this._showAvg) {
|
||||
ctx.strokeStyle = this.averageLineColor;
|
||||
ctx.lineWidth = GRAPH_HELPER_LINES_WIDTH;
|
||||
ctx.setLineDash(GRAPH_HELPER_LINES_DASH);
|
||||
ctx.beginPath();
|
||||
let averageY = height - avgValue * dataScaleY;
|
||||
ctx.moveTo(0, averageY);
|
||||
ctx.lineTo(width, averageY);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Draw the minimum value horizontal line.
|
||||
if (this._showMin) {
|
||||
ctx.strokeStyle = this.minimumLineColor;
|
||||
ctx.lineWidth = GRAPH_HELPER_LINES_WIDTH;
|
||||
ctx.setLineDash(GRAPH_HELPER_LINES_DASH);
|
||||
ctx.beginPath();
|
||||
let minimumY = height - minValue * dataScaleY;
|
||||
ctx.moveTo(0, minimumY);
|
||||
ctx.lineTo(width, minimumY);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Update the tooltips text and gutter lines.
|
||||
|
||||
this._maxTooltip.querySelector("[text=value]").textContent =
|
||||
L10N.numberWithDecimals(maxValue, 2);
|
||||
this._avgTooltip.querySelector("[text=value]").textContent =
|
||||
L10N.numberWithDecimals(avgValue, 2);
|
||||
this._minTooltip.querySelector("[text=value]").textContent =
|
||||
L10N.numberWithDecimals(minValue, 2);
|
||||
|
||||
let bottom = height / this._pixelRatio;
|
||||
let maxPosY = CanvasGraphUtils.map(maxValue * this.dampenValuesFactor, 0, maxValue, bottom, 0);
|
||||
let avgPosY = CanvasGraphUtils.map(avgValue * this.dampenValuesFactor, 0, maxValue, bottom, 0);
|
||||
let minPosY = CanvasGraphUtils.map(minValue * this.dampenValuesFactor, 0, maxValue, bottom, 0);
|
||||
|
||||
let safeTop = GRAPH_TOOLTIP_SAFE_BOUNDS;
|
||||
let safeBottom = bottom - GRAPH_TOOLTIP_SAFE_BOUNDS;
|
||||
|
||||
let maxTooltipTop = (this.withFixedTooltipPositions
|
||||
? safeTop : CanvasGraphUtils.clamp(maxPosY, safeTop, safeBottom));
|
||||
let avgTooltipTop = (this.withFixedTooltipPositions
|
||||
? safeTop : CanvasGraphUtils.clamp(avgPosY, safeTop, safeBottom));
|
||||
let minTooltipTop = (this.withFixedTooltipPositions
|
||||
? safeBottom : CanvasGraphUtils.clamp(minPosY, safeTop, safeBottom));
|
||||
|
||||
this._maxTooltip.style.top = maxTooltipTop + "px";
|
||||
this._avgTooltip.style.top = avgTooltipTop + "px";
|
||||
this._minTooltip.style.top = minTooltipTop + "px";
|
||||
|
||||
this._maxGutterLine.style.top = maxPosY + "px";
|
||||
this._avgGutterLine.style.top = avgPosY + "px";
|
||||
this._minGutterLine.style.top = minPosY + "px";
|
||||
|
||||
this._maxTooltip.setAttribute("with-arrows", this.withTooltipArrows);
|
||||
this._avgTooltip.setAttribute("with-arrows", this.withTooltipArrows);
|
||||
this._minTooltip.setAttribute("with-arrows", this.withTooltipArrows);
|
||||
|
||||
let distanceMinMax = Math.abs(maxTooltipTop - minTooltipTop);
|
||||
this._maxTooltip.hidden = this._showMax === false || !totalTicks || distanceMinMax < GRAPH_MIN_MAX_TOOLTIP_DISTANCE;
|
||||
this._avgTooltip.hidden = this._showAvg === false || !totalTicks;
|
||||
this._minTooltip.hidden = this._showMin === false || !totalTicks;
|
||||
this._gutter.hidden = (this._showMin === false && this._showAvg === false && this._showMax === false) || !totalTicks;
|
||||
|
||||
this._maxGutterLine.hidden = this._showMax === false;
|
||||
this._avgGutterLine.hidden = this._showAvg === false;
|
||||
this._minGutterLine.hidden = this._showMin === false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the gutter node when constructing this graph.
|
||||
* @return nsIDOMNode
|
||||
*/
|
||||
_createGutter: function() {
|
||||
let gutter = this._document.createElementNS(HTML_NS, "div");
|
||||
gutter.className = "line-graph-widget-gutter";
|
||||
gutter.setAttribute("hidden", true);
|
||||
this._container.appendChild(gutter);
|
||||
|
||||
return gutter;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the gutter line nodes when constructing this graph.
|
||||
* @return nsIDOMNode
|
||||
*/
|
||||
_createGutterLine: function(type) {
|
||||
let line = this._document.createElementNS(HTML_NS, "div");
|
||||
line.className = "line-graph-widget-gutter-line";
|
||||
line.setAttribute("type", type);
|
||||
this._gutter.appendChild(line);
|
||||
|
||||
return line;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the tooltip nodes when constructing this graph.
|
||||
* @return nsIDOMNode
|
||||
*/
|
||||
_createTooltip: function(type, arrow, info, metric) {
|
||||
let tooltip = this._document.createElementNS(HTML_NS, "div");
|
||||
tooltip.className = "line-graph-widget-tooltip";
|
||||
tooltip.setAttribute("type", type);
|
||||
tooltip.setAttribute("arrow", arrow);
|
||||
tooltip.setAttribute("hidden", true);
|
||||
|
||||
let infoNode = this._document.createElementNS(HTML_NS, "span");
|
||||
infoNode.textContent = info;
|
||||
infoNode.setAttribute("text", "info");
|
||||
|
||||
let valueNode = this._document.createElementNS(HTML_NS, "span");
|
||||
valueNode.textContent = 0;
|
||||
valueNode.setAttribute("text", "value");
|
||||
|
||||
let metricNode = this._document.createElementNS(HTML_NS, "span");
|
||||
metricNode.textContent = metric;
|
||||
metricNode.setAttribute("text", "metric");
|
||||
|
||||
tooltip.appendChild(infoNode);
|
||||
tooltip.appendChild(valueNode);
|
||||
tooltip.appendChild(metricNode);
|
||||
this._container.appendChild(tooltip);
|
||||
|
||||
return tooltip;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = LineGraphWidget;
|
@ -25,7 +25,7 @@ const Telemetry = require("devtools/shared/telemetry");
|
||||
const telemetry = new Telemetry();
|
||||
|
||||
devtools.lazyRequireGetter(this, "LineGraphWidget",
|
||||
"devtools/shared/widgets/Graphs", true);
|
||||
"devtools/shared/widgets/LineGraphWidget");
|
||||
|
||||
// `AUDIO_NODE_DEFINITION` defined in the controller's initialization,
|
||||
// which describes all the properties of an AudioNode
|
||||
|
@ -1025,10 +1025,6 @@
|
||||
|
||||
/* Bar graph widget */
|
||||
|
||||
.bar-graph-widget-canvas {
|
||||
background: #f7f7f7;
|
||||
}
|
||||
|
||||
.bar-graph-widget-legend {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
|
Loading…
Reference in New Issue
Block a user