зеркало из https://github.com/mozilla/gecko-dev.git
Backout 175e66ebdb05 (bug 1077444) for dt2 orange
This commit is contained in:
Родитель
be4d564b24
Коммит
e28a549c00
|
@ -22,7 +22,6 @@ EXTRA_JS_MODULES.devtools += [
|
|||
'widgets/AbstractTreeItem.jsm',
|
||||
'widgets/BreadcrumbsWidget.jsm',
|
||||
'widgets/Chart.jsm',
|
||||
'widgets/FlameGraph.jsm',
|
||||
'widgets/Graphs.jsm',
|
||||
'widgets/GraphsWorker.js',
|
||||
'widgets/SideMenuWidget.jsm',
|
||||
|
|
|
@ -15,10 +15,6 @@ support-files =
|
|||
[browser_cubic-bezier-01.js]
|
||||
[browser_cubic-bezier-02.js]
|
||||
[browser_cubic-bezier-03.js]
|
||||
[browser_flame-graph-01.js]
|
||||
[browser_flame-graph-02.js]
|
||||
[browser_flame-graph-03.js]
|
||||
[browser_flame-graph-04.js]
|
||||
[browser_graphs-01.js]
|
||||
[browser_graphs-02.js]
|
||||
[browser_graphs-03.js]
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests that flame graph widget works properly.
|
||||
|
||||
let {FlameGraph} = Cu.import("resource:///modules/devtools/FlameGraph.jsm", {});
|
||||
let {DOMHelpers} = Cu.import("resource:///modules/devtools/DOMHelpers.jsm", {});
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
let {Hosts} = devtools.require("devtools/framework/toolbox-hosts");
|
||||
|
||||
let test = Task.async(function*() {
|
||||
yield promiseTab("about:blank");
|
||||
yield performTest();
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
});
|
||||
|
||||
function* performTest() {
|
||||
let [host, win, doc] = yield createHost();
|
||||
doc.body.setAttribute("style", "position: fixed; width: 100%; height: 100%; margin: 0;");
|
||||
|
||||
let graph = new FlameGraph(doc.body);
|
||||
|
||||
let readyEventEmitted;
|
||||
graph.once("ready", () => readyEventEmitted = true);
|
||||
|
||||
yield graph.ready();
|
||||
ok(readyEventEmitted, "The 'ready' event should have been emitted");
|
||||
|
||||
testGraph(host, graph);
|
||||
|
||||
graph.destroy();
|
||||
host.destroy();
|
||||
}
|
||||
|
||||
function testGraph(host, graph) {
|
||||
ok(graph._container.classList.contains("flame-graph-widget-container"),
|
||||
"The correct graph container was created.");
|
||||
ok(graph._canvas.classList.contains("flame-graph-widget-canvas"),
|
||||
"The correct graph container was created.");
|
||||
|
||||
let bounds = host.frame.getBoundingClientRect();
|
||||
|
||||
is(graph.width, bounds.width * window.devicePixelRatio,
|
||||
"The graph has the correct width.");
|
||||
is(graph.height, bounds.height * window.devicePixelRatio,
|
||||
"The graph has the correct height.");
|
||||
|
||||
ok(graph._selection.start === null,
|
||||
"The graph's selection start value is initially null.");
|
||||
ok(graph._selection.end === null,
|
||||
"The graph's selection end value is initially null.");
|
||||
|
||||
ok(graph._selectionDragger.origin === null,
|
||||
"The graph's dragger origin value is initially null.");
|
||||
ok(graph._selectionDragger.anchor.start === null,
|
||||
"The graph's dragger anchor start value is initially null.");
|
||||
ok(graph._selectionDragger.anchor.end === null,
|
||||
"The graph's dragger anchor end value is initially null.");
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests that flame graph widgets may have a fixed width or height.
|
||||
|
||||
let {FlameGraph} = Cu.import("resource:///modules/devtools/FlameGraph.jsm", {});
|
||||
let {DOMHelpers} = Cu.import("resource:///modules/devtools/DOMHelpers.jsm", {});
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
let {Hosts} = devtools.require("devtools/framework/toolbox-hosts");
|
||||
|
||||
let test = Task.async(function*() {
|
||||
yield promiseTab("about:blank");
|
||||
yield performTest();
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
});
|
||||
|
||||
function* performTest() {
|
||||
let [host, win, doc] = yield createHost();
|
||||
doc.body.setAttribute("style", "position: fixed; width: 100%; height: 100%; margin: 0;");
|
||||
|
||||
let graph = new FlameGraph(doc.body);
|
||||
graph.fixedWidth = 200;
|
||||
graph.fixedHeight = 100;
|
||||
|
||||
yield graph.ready();
|
||||
testGraph(host, graph);
|
||||
|
||||
graph.destroy();
|
||||
host.destroy();
|
||||
}
|
||||
|
||||
function testGraph(host, graph) {
|
||||
let bounds = host.frame.getBoundingClientRect();
|
||||
|
||||
isnot(graph.width, bounds.width * window.devicePixelRatio,
|
||||
"The graph should not span all the parent node's width.");
|
||||
isnot(graph.height, bounds.height * window.devicePixelRatio,
|
||||
"The graph should not span all the parent node's height.");
|
||||
|
||||
is(graph.width, graph.fixedWidth * window.devicePixelRatio,
|
||||
"The graph has the correct width.");
|
||||
is(graph.height, graph.fixedHeight * window.devicePixelRatio,
|
||||
"The graph has the correct height.");
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests that selections in the flame graph widget work properly.
|
||||
|
||||
let TEST_DATA = [{ color: "#f00", blocks: [{ x: 0, y: 0, width: 50, height: 20, text: "FOO" }, { x: 50, y: 0, width: 100, height: 20, text: "BAR" }] }, { color: "#00f", blocks: [{ x: 0, y: 30, width: 30, height: 20, text: "BAZ" }] }];
|
||||
let TEST_WIDTH = 200;
|
||||
let TEST_HEIGHT = 100;
|
||||
|
||||
let {FlameGraph} = Cu.import("resource:///modules/devtools/FlameGraph.jsm", {});
|
||||
let {DOMHelpers} = Cu.import("resource:///modules/devtools/DOMHelpers.jsm", {});
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
let {Hosts} = devtools.require("devtools/framework/toolbox-hosts");
|
||||
|
||||
let test = Task.async(function*() {
|
||||
yield promiseTab("about:blank");
|
||||
yield performTest();
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
});
|
||||
|
||||
function* performTest() {
|
||||
let [host, win, doc] = yield createHost();
|
||||
doc.body.setAttribute("style", "position: fixed; width: 100%; height: 100%; margin: 0;");
|
||||
|
||||
let graph = new FlameGraph(doc.body, 1);
|
||||
graph.fixedWidth = TEST_WIDTH;
|
||||
graph.fixedHeight = TEST_HEIGHT;
|
||||
|
||||
yield graph.ready();
|
||||
|
||||
testGraph(graph);
|
||||
|
||||
graph.destroy();
|
||||
host.destroy();
|
||||
}
|
||||
|
||||
function testGraph(graph) {
|
||||
graph.setData(TEST_DATA);
|
||||
|
||||
is(graph.getDataWindowStart(), 0,
|
||||
"The selection start boundary is correct (1).");
|
||||
is(graph.getDataWindowEnd(), TEST_WIDTH,
|
||||
"The selection end boundary is correct (1).");
|
||||
|
||||
scroll(graph, 200, HORIZONTAL_AXIS, 10);
|
||||
is(graph.getDataWindowStart() | 0, 100,
|
||||
"The selection start boundary is correct (2).");
|
||||
is(graph.getDataWindowEnd() | 0, 300,
|
||||
"The selection end boundary is correct (2).");
|
||||
|
||||
scroll(graph, -200, HORIZONTAL_AXIS, 10);
|
||||
is(graph.getDataWindowStart() | 0, 0,
|
||||
"The selection start boundary is correct (3).");
|
||||
is(graph.getDataWindowEnd() | 0, 200,
|
||||
"The selection end boundary is correct (3).");
|
||||
|
||||
scroll(graph, 200, VERTICAL_AXIS, TEST_WIDTH / 2);
|
||||
is(graph.getDataWindowStart() | 0, 0,
|
||||
"The selection start boundary is correct (4).");
|
||||
is(graph.getDataWindowEnd() | 0, 207,
|
||||
"The selection end boundary is correct (4).");
|
||||
|
||||
scroll(graph, -200, VERTICAL_AXIS, TEST_WIDTH / 2);
|
||||
is(graph.getDataWindowStart() | 0, 7,
|
||||
"The selection start boundary is correct (5).");
|
||||
is(graph.getDataWindowEnd() | 0, 199,
|
||||
"The selection end boundary is correct (5).");
|
||||
|
||||
dragStart(graph, TEST_WIDTH / 2);
|
||||
is(graph.getDataWindowStart() | 0, 7,
|
||||
"The selection start boundary is correct (6).");
|
||||
is(graph.getDataWindowEnd() | 0, 199,
|
||||
"The selection end boundary is correct (6).");
|
||||
|
||||
hover(graph, TEST_WIDTH / 2 - 10);
|
||||
is(graph.getDataWindowStart() | 0, 16,
|
||||
"The selection start boundary is correct (7).");
|
||||
is(graph.getDataWindowEnd() | 0, 209,
|
||||
"The selection end boundary is correct (7).");
|
||||
|
||||
dragStop(graph, 10);
|
||||
is(graph.getDataWindowStart() | 0, 93,
|
||||
"The selection start boundary is correct (8).");
|
||||
is(graph.getDataWindowEnd() | 0, 286,
|
||||
"The selection end boundary is correct (8).");
|
||||
}
|
||||
|
||||
// EventUtils just doesn't work!
|
||||
|
||||
function hover(graph, x, y = 1) {
|
||||
x /= window.devicePixelRatio;
|
||||
y /= window.devicePixelRatio;
|
||||
graph._onMouseMove({ clientX: x, clientY: y });
|
||||
}
|
||||
|
||||
function dragStart(graph, x, y = 1) {
|
||||
x /= window.devicePixelRatio;
|
||||
y /= window.devicePixelRatio;
|
||||
graph._onMouseMove({ clientX: x, clientY: y });
|
||||
graph._onMouseDown({ clientX: x, clientY: y });
|
||||
}
|
||||
|
||||
function dragStop(graph, x, y = 1) {
|
||||
x /= window.devicePixelRatio;
|
||||
y /= window.devicePixelRatio;
|
||||
graph._onMouseMove({ clientX: x, clientY: y });
|
||||
graph._onMouseUp({ clientX: x, clientY: y });
|
||||
}
|
||||
|
||||
let HORIZONTAL_AXIS = 1;
|
||||
let VERTICAL_AXIS = 2;
|
||||
|
||||
function scroll(graph, wheel, axis, x, y = 1) {
|
||||
x /= window.devicePixelRatio;
|
||||
y /= window.devicePixelRatio;
|
||||
graph._onMouseMove({ clientX: x, clientY: y });
|
||||
graph._onMouseWheel({ clientX: x, clientY: y, axis, detail: wheel, axis,
|
||||
HORIZONTAL_AXIS,
|
||||
VERTICAL_AXIS
|
||||
});
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests that text metrics in the flame graph widget work properly.
|
||||
|
||||
let HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
let FLAME_GRAPH_BLOCK_TEXT_FONT_SIZE = 9; // px
|
||||
let FLAME_GRAPH_BLOCK_TEXT_FONT_FAMILY = "sans-serif";
|
||||
let {ViewHelpers} = Cu.import("resource:///modules/devtools/ViewHelpers.jsm", {});
|
||||
let {FlameGraph} = Cu.import("resource:///modules/devtools/FlameGraph.jsm", {});
|
||||
let {DOMHelpers} = Cu.import("resource:///modules/devtools/DOMHelpers.jsm", {});
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
let {Hosts} = devtools.require("devtools/framework/toolbox-hosts");
|
||||
|
||||
let L10N = new ViewHelpers.L10N();
|
||||
|
||||
let test = Task.async(function*() {
|
||||
yield promiseTab("about:blank");
|
||||
yield performTest();
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
});
|
||||
|
||||
function* performTest() {
|
||||
let [host, win, doc] = yield createHost();
|
||||
let graph = new FlameGraph(doc.body, 1);
|
||||
yield graph.ready();
|
||||
|
||||
testGraph(graph);
|
||||
|
||||
graph.destroy();
|
||||
host.destroy();
|
||||
}
|
||||
|
||||
function testGraph(graph) {
|
||||
is(graph._averageCharWidth, getAverageCharWidth(),
|
||||
"The average char width was calculated correctly.");
|
||||
is(graph._overflowCharWidth, getCharWidth(L10N.ellipsis),
|
||||
"The ellipsis char width was calculated correctly.");
|
||||
|
||||
is(graph._getTextWidthApprox("This text is maybe overflowing"),
|
||||
getAverageCharWidth() * 30,
|
||||
"The approximate width was calculated correctly.");
|
||||
|
||||
is(graph._getFittedText("This text is maybe overflowing", 1000),
|
||||
"This text is maybe overflowing",
|
||||
"The fitted text for 1000px width is correct.");
|
||||
|
||||
isnot(graph._getFittedText("This text is maybe overflowing", 100),
|
||||
"This text is maybe overflowing",
|
||||
"The fitted text for 100px width is correct (1).");
|
||||
|
||||
ok(graph._getFittedText("This text is maybe overflowing", 100)
|
||||
.contains(L10N.ellipsis),
|
||||
"The fitted text for 100px width is correct (2).");
|
||||
|
||||
is(graph._getFittedText("This text is maybe overflowing", 10),
|
||||
L10N.ellipsis,
|
||||
"The fitted text for 10px width is correct.");
|
||||
|
||||
is(graph._getFittedText("This text is maybe overflowing", 1),
|
||||
"",
|
||||
"The fitted text for 1px width is correct.");
|
||||
}
|
||||
|
||||
function getAverageCharWidth() {
|
||||
let letterWidthsSum = 0;
|
||||
let start = 32; // space
|
||||
let end = 123; // "z"
|
||||
|
||||
for (let i = start; i < end; i++) {
|
||||
let char = String.fromCharCode(i);
|
||||
letterWidthsSum += getCharWidth(char);
|
||||
}
|
||||
|
||||
return letterWidthsSum / (end - start);
|
||||
}
|
||||
|
||||
function getCharWidth(char) {
|
||||
let canvas = document.createElementNS(HTML_NS, "canvas");
|
||||
let ctx = canvas.getContext("2d");
|
||||
|
||||
let fontSize = FLAME_GRAPH_BLOCK_TEXT_FONT_SIZE;
|
||||
let fontFamily = FLAME_GRAPH_BLOCK_TEXT_FONT_FAMILY;
|
||||
ctx.font = fontSize + "px " + fontFamily;
|
||||
|
||||
return ctx.measureText(char).width;
|
||||
}
|
|
@ -1,755 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
Cu.import("resource:///modules/devtools/Graphs.jsm");
|
||||
const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
|
||||
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"FlameGraph",
|
||||
"FlameGraphUtils"
|
||||
];
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const GRAPH_SRC = "chrome://browser/content/devtools/graphs-frame.xhtml";
|
||||
const L10N = new ViewHelpers.L10N();
|
||||
|
||||
const GRAPH_WHEEL_ZOOM_SENSITIVITY = 0.00035;
|
||||
const GRAPH_WHEEL_SCROLL_SENSITIVITY = 0.5;
|
||||
const GRAPH_MIN_SELECTION_WIDTH = 10; // ms
|
||||
|
||||
const TIMELINE_TICKS_MULTIPLE = 5; // ms
|
||||
const TIMELINE_TICKS_SPACING_MIN = 75; // px
|
||||
|
||||
const OVERVIEW_HEADER_HEIGHT = 18; // px
|
||||
const OVERVIEW_HEADER_SAFE_BOUNDS = 50; // px
|
||||
const OVERVIEW_HEADER_TEXT_COLOR = "#18191a";
|
||||
const OVERVIEW_HEADER_TEXT_FONT_SIZE = 9; // px
|
||||
const OVERVIEW_HEADER_TEXT_FONT_FAMILY = "sans-serif";
|
||||
const OVERVIEW_HEADER_TEXT_PADDING_LEFT = 6; // px
|
||||
const OVERVIEW_HEADER_TEXT_PADDING_TOP = 5; // px
|
||||
const OVERVIEW_TIMELINE_STROKES = "#ddd";
|
||||
|
||||
const FLAME_GRAPH_BLOCK_BORDER = 1; // px
|
||||
const FLAME_GRAPH_BLOCK_TEXT_COLOR = "#000";
|
||||
const FLAME_GRAPH_BLOCK_TEXT_FONT_SIZE = 9; // px
|
||||
const FLAME_GRAPH_BLOCK_TEXT_FONT_FAMILY = "sans-serif";
|
||||
const FLAME_GRAPH_BLOCK_TEXT_PADDING_TOP = 1; // px
|
||||
const FLAME_GRAPH_BLOCK_TEXT_PADDING_LEFT = 3; // px
|
||||
const FLAME_GRAPH_BLOCK_TEXT_PADDING_RIGHT = 3; // px
|
||||
|
||||
/**
|
||||
* A flamegraph visualization. This implementation is responsable only with
|
||||
* drawing the graph, using a data source consisting of rectangles and
|
||||
* their corresponding widths.
|
||||
*
|
||||
* Example usage:
|
||||
* let graph = new FlameGraph(node);
|
||||
* let src = FlameGraphUtils.createFlameGraphDataFromSamples(samples);
|
||||
* graph.once("ready", () => {
|
||||
* graph.setData(src);
|
||||
* });
|
||||
*
|
||||
* Data source format:
|
||||
* [
|
||||
* {
|
||||
* color: "string",
|
||||
* blocks: [
|
||||
* {
|
||||
* x: number,
|
||||
* y: number,
|
||||
* width: number,
|
||||
* height: number,
|
||||
* text: "string"
|
||||
* },
|
||||
* ...
|
||||
* ]
|
||||
* },
|
||||
* {
|
||||
* color: "string",
|
||||
* blocks: [...]
|
||||
* },
|
||||
* ...
|
||||
* {
|
||||
* color: "string",
|
||||
* blocks: [...]
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* Use `FlameGraphUtils` to convert profiler data (or any other data source)
|
||||
* into a drawable format.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the graph.
|
||||
* @param number sharpness [optional]
|
||||
* Defaults to the current device pixel ratio.
|
||||
*/
|
||||
function FlameGraph(parent, sharpness) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this._parent = parent;
|
||||
this._ready = promise.defer();
|
||||
|
||||
AbstractCanvasGraph.createIframe(GRAPH_SRC, parent, iframe => {
|
||||
this._iframe = iframe;
|
||||
this._window = iframe.contentWindow;
|
||||
this._document = iframe.contentDocument;
|
||||
this._pixelRatio = sharpness || this._window.devicePixelRatio;
|
||||
|
||||
let container = this._container = this._document.getElementById("graph-container");
|
||||
container.className = "flame-graph-widget-container graph-widget-container";
|
||||
|
||||
let canvas = this._canvas = this._document.getElementById("graph-canvas");
|
||||
canvas.className = "flame-graph-widget-canvas graph-widget-canvas";
|
||||
|
||||
let bounds = parent.getBoundingClientRect();
|
||||
bounds.width = this.fixedWidth || bounds.width;
|
||||
bounds.height = this.fixedHeight || bounds.height;
|
||||
iframe.setAttribute("width", bounds.width);
|
||||
iframe.setAttribute("height", bounds.height);
|
||||
|
||||
this._width = canvas.width = bounds.width * this._pixelRatio;
|
||||
this._height = canvas.height = bounds.height * this._pixelRatio;
|
||||
this._ctx = canvas.getContext("2d");
|
||||
|
||||
this._selection = new GraphSelection();
|
||||
this._selectionDragger = new GraphSelectionDragger();
|
||||
|
||||
// Calculating text widths is necessary to trim the text inside the blocks
|
||||
// while the scaling changes (e.g. via scrolling). This is very expensive,
|
||||
// so maintain a cache of string contents to text widths.
|
||||
this._textWidthsCache = {};
|
||||
|
||||
let fontSize = FLAME_GRAPH_BLOCK_TEXT_FONT_SIZE * this._pixelRatio;
|
||||
let fontFamily = FLAME_GRAPH_BLOCK_TEXT_FONT_FAMILY;
|
||||
this._ctx.font = fontSize + "px " + fontFamily;
|
||||
this._averageCharWidth = this._calcAverageCharWidth();
|
||||
this._overflowCharWidth = this._getTextWidth(this.overflowChar);
|
||||
|
||||
this._onMouseMove = this._onMouseMove.bind(this);
|
||||
this._onMouseDown = this._onMouseDown.bind(this);
|
||||
this._onMouseUp = this._onMouseUp.bind(this);
|
||||
this._onMouseWheel = this._onMouseWheel.bind(this);
|
||||
this._onAnimationFrame = this._onAnimationFrame.bind(this);
|
||||
|
||||
container.addEventListener("mousemove", this._onMouseMove);
|
||||
container.addEventListener("mousedown", this._onMouseDown);
|
||||
container.addEventListener("mouseup", this._onMouseUp);
|
||||
container.addEventListener("MozMousePixelScroll", this._onMouseWheel);
|
||||
|
||||
this._animationId = this._window.requestAnimationFrame(this._onAnimationFrame);
|
||||
|
||||
this._ready.resolve(this);
|
||||
this.emit("ready", this);
|
||||
});
|
||||
}
|
||||
|
||||
FlameGraph.prototype = {
|
||||
/**
|
||||
* Read-only width and height of the canvas.
|
||||
* @return number
|
||||
*/
|
||||
get width() {
|
||||
return this._width;
|
||||
},
|
||||
get height() {
|
||||
return this._height;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a promise resolved once this graph is ready to receive data.
|
||||
*/
|
||||
ready: function() {
|
||||
return this._ready.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroys this graph.
|
||||
*/
|
||||
destroy: function() {
|
||||
let container = this._container;
|
||||
container.removeEventListener("mousemove", this._onMouseMove);
|
||||
container.removeEventListener("mousedown", this._onMouseDown);
|
||||
container.removeEventListener("mouseup", this._onMouseUp);
|
||||
container.removeEventListener("MozMousePixelScroll", this._onMouseWheel);
|
||||
|
||||
this._window.cancelAnimationFrame(this._animationId);
|
||||
this._iframe.remove();
|
||||
|
||||
this._selection = null;
|
||||
this._selectionDragger = null;
|
||||
|
||||
this._data = null;
|
||||
|
||||
this.emit("destroyed");
|
||||
},
|
||||
|
||||
/**
|
||||
* Rendering options. Subclasses should override these.
|
||||
*/
|
||||
overviewHeaderTextColor: OVERVIEW_HEADER_TEXT_COLOR,
|
||||
overviewTimelineStrokes: OVERVIEW_TIMELINE_STROKES,
|
||||
blockTextColor: FLAME_GRAPH_BLOCK_TEXT_COLOR,
|
||||
|
||||
/**
|
||||
* Makes sure the canvas graph is of the specified width or height, and
|
||||
* doesn't flex to fit all the available space.
|
||||
*/
|
||||
fixedWidth: null,
|
||||
fixedHeight: null,
|
||||
|
||||
/**
|
||||
* The units used in the overhead ticks. Could be "ms", for example.
|
||||
* Overwrite this with your own localized format.
|
||||
*/
|
||||
timelineTickUnits: "",
|
||||
|
||||
/**
|
||||
* Character used when a block's text is overflowing.
|
||||
* Defaults to an ellipsis.
|
||||
*/
|
||||
overflowChar: L10N.ellipsis,
|
||||
|
||||
/**
|
||||
* Sets the data source for this graph.
|
||||
*
|
||||
* @param object data
|
||||
* The data source. See the constructor for more information.
|
||||
*/
|
||||
setData: function(data) {
|
||||
this._data = data;
|
||||
this._selection = { start: 0, end: this._width };
|
||||
this._shouldRedraw = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Same as `setData`, but waits for this graph to finish initializing first.
|
||||
*
|
||||
* @param object data
|
||||
* The data source. See the constructor for more information.
|
||||
* @return promise
|
||||
* A promise resolved once the data is set.
|
||||
*/
|
||||
setDataWhenReady: Task.async(function*(data) {
|
||||
yield this.ready();
|
||||
this.setData(data);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Gets the start or end of this graph's selection, i.e. the 'data window'.
|
||||
* @return number
|
||||
*/
|
||||
getDataWindowStart: function() {
|
||||
return this._selection.start;
|
||||
},
|
||||
getDataWindowEnd: function() {
|
||||
return this._selection.end;
|
||||
},
|
||||
|
||||
/**
|
||||
* The contents of this graph are redrawn only when something changed,
|
||||
* like the data source, or the selection bounds etc. This flag tracks
|
||||
* if the rendering is "dirty" and needs to be refreshed.
|
||||
*/
|
||||
_shouldRedraw: false,
|
||||
|
||||
/**
|
||||
* Animation frame callback, invoked on each tick of the refresh driver.
|
||||
*/
|
||||
_onAnimationFrame: function() {
|
||||
this._animationId = this._window.requestAnimationFrame(this._onAnimationFrame);
|
||||
this._drawWidget();
|
||||
},
|
||||
|
||||
/**
|
||||
* Redraws the widget when necessary. The actual graph is not refreshed
|
||||
* every time this function is called, only the cliphead, selection etc.
|
||||
*/
|
||||
_drawWidget: function() {
|
||||
if (!this._shouldRedraw) {
|
||||
return;
|
||||
}
|
||||
let ctx = this._ctx;
|
||||
let canvasWidth = this._width;
|
||||
let canvasHeight = this._height;
|
||||
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
||||
|
||||
let selection = this._selection;
|
||||
let selectionWidth = selection.end - selection.start;
|
||||
let selectionScale = canvasWidth / selectionWidth;
|
||||
this._drawTicks(selection.start, selectionScale);
|
||||
this._drawPyramid(this._data, selection.start, selectionScale);
|
||||
|
||||
this._shouldRedraw = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Draws the overhead ticks in this graph.
|
||||
*
|
||||
* @param number dataOffset, dataScale
|
||||
* Offsets and scales the data source by the specified amount.
|
||||
* This is used for scrolling the visualization.
|
||||
*/
|
||||
_drawTicks: function(dataOffset, dataScale) {
|
||||
let ctx = this._ctx;
|
||||
let canvasWidth = this._width;
|
||||
let canvasHeight = this._height;
|
||||
let scaledOffset = dataOffset * dataScale;
|
||||
|
||||
let safeBounds = OVERVIEW_HEADER_SAFE_BOUNDS * this._pixelRatio;
|
||||
let availableWidth = canvasWidth - safeBounds;
|
||||
|
||||
let fontSize = OVERVIEW_HEADER_TEXT_FONT_SIZE * this._pixelRatio;
|
||||
let fontFamily = OVERVIEW_HEADER_TEXT_FONT_FAMILY;
|
||||
let textPaddingLeft = OVERVIEW_HEADER_TEXT_PADDING_LEFT * this._pixelRatio;
|
||||
let textPaddingTop = OVERVIEW_HEADER_TEXT_PADDING_TOP * this._pixelRatio;
|
||||
let tickInterval = this._findOptimalTickInterval(dataScale);
|
||||
|
||||
ctx.textBaseline = "top";
|
||||
ctx.font = fontSize + "px " + fontFamily;
|
||||
ctx.fillStyle = this.overviewHeaderTextColor;
|
||||
ctx.strokeStyle = this.overviewTimelineStrokes;
|
||||
ctx.beginPath();
|
||||
|
||||
for (let x = 0; x < availableWidth + scaledOffset; x += tickInterval) {
|
||||
let lineLeft = x - scaledOffset;
|
||||
let textLeft = lineLeft + textPaddingLeft;
|
||||
let time = Math.round(x / dataScale / this._pixelRatio);
|
||||
let label = time + " " + this.timelineTickUnits;
|
||||
ctx.fillText(label, textLeft, textPaddingTop);
|
||||
ctx.moveTo(lineLeft, 0);
|
||||
ctx.lineTo(lineLeft, canvasHeight);
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
},
|
||||
|
||||
/**
|
||||
* Draws the blocks and text in this graph.
|
||||
*
|
||||
* @param object dataSource
|
||||
* The data source. See the constructor for more information.
|
||||
* @param number dataOffset, dataScale
|
||||
* Offsets and scales the data source by the specified amount.
|
||||
* This is used for scrolling the visualization.
|
||||
*/
|
||||
_drawPyramid: function(dataSource, dataOffset, dataScale) {
|
||||
let ctx = this._ctx;
|
||||
|
||||
let fontSize = FLAME_GRAPH_BLOCK_TEXT_FONT_SIZE * this._pixelRatio;
|
||||
let fontFamily = FLAME_GRAPH_BLOCK_TEXT_FONT_FAMILY;
|
||||
let visibleBlocks = this._drawPyramidFill(dataSource, dataOffset, dataScale);
|
||||
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.font = fontSize + "px " + fontFamily;
|
||||
ctx.fillStyle = this.blockTextColor;
|
||||
|
||||
this._drawPyramidText(visibleBlocks, dataOffset, dataScale);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fills all block inside this graph's pyramid.
|
||||
* @see FlameGraph.prototype._drawPyramid
|
||||
*/
|
||||
_drawPyramidFill: function(dataSource, dataOffset, dataScale) {
|
||||
let visibleBlocksStore = [];
|
||||
let minVisibleBlockWidth = this._overflowCharWidth;
|
||||
|
||||
for (let { color, blocks } of dataSource) {
|
||||
this._drawBlocksFill(
|
||||
color, blocks, dataOffset, dataScale,
|
||||
visibleBlocksStore, minVisibleBlockWidth);
|
||||
}
|
||||
|
||||
return visibleBlocksStore;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds the text for all block inside this graph's pyramid.
|
||||
* @see FlameGraph.prototype._drawPyramid
|
||||
*/
|
||||
_drawPyramidText: function(blocks, dataOffset, dataScale) {
|
||||
for (let block of blocks) {
|
||||
this._drawBlockText(block, dataOffset, dataScale);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fills a group of blocks sharing the same style.
|
||||
*
|
||||
* @param string color
|
||||
* The color used as the block's background.
|
||||
* @param array blocks
|
||||
* A list of { x, y, width, height } objects visually representing
|
||||
* all the blocks sharing this particular style.
|
||||
* @param number dataOffset, dataScale
|
||||
* Offsets and scales the data source by the specified amount.
|
||||
* This is used for scrolling the visualization.
|
||||
* @param array visibleBlocksStore
|
||||
* An array to store all the visible blocks into, after drawing them.
|
||||
* The provided array will be populated.
|
||||
* @param number minVisibleBlockWidth
|
||||
* The minimum width of the blocks that will be added into
|
||||
* the `visibleBlocksStore`.
|
||||
*/
|
||||
_drawBlocksFill: function(
|
||||
color, blocks, dataOffset, dataScale,
|
||||
visibleBlocksStore, minVisibleBlockWidth)
|
||||
{
|
||||
let ctx = this._ctx;
|
||||
let canvasWidth = this._width;
|
||||
let canvasHeight = this._height;
|
||||
let scaledOffset = dataOffset * dataScale;
|
||||
|
||||
ctx.fillStyle = color;
|
||||
ctx.beginPath();
|
||||
|
||||
for (let block of blocks) {
|
||||
let { x, y, width, height } = block;
|
||||
let rectLeft = x * this._pixelRatio * dataScale - scaledOffset;
|
||||
let rectTop = (y + OVERVIEW_HEADER_HEIGHT) * this._pixelRatio;
|
||||
let rectWidth = width * this._pixelRatio * dataScale;
|
||||
let rectHeight = height * this._pixelRatio;
|
||||
|
||||
if (rectLeft > canvasWidth || // Too far right.
|
||||
rectLeft < -rectWidth || // Too far left.
|
||||
rectTop > canvasHeight) { // Too far bottom.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Clamp the blocks position to start at 0. Avoid negative X coords,
|
||||
// to properly place the text inside the blocks.
|
||||
if (rectLeft < 0) {
|
||||
rectWidth += rectLeft;
|
||||
rectLeft = 0;
|
||||
}
|
||||
|
||||
// Avoid drawing blocks that are too narrow.
|
||||
if (rectWidth <= FLAME_GRAPH_BLOCK_BORDER ||
|
||||
rectHeight <= FLAME_GRAPH_BLOCK_BORDER) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ctx.rect(
|
||||
rectLeft, rectTop,
|
||||
rectWidth - FLAME_GRAPH_BLOCK_BORDER,
|
||||
rectHeight - FLAME_GRAPH_BLOCK_BORDER);
|
||||
|
||||
// Populate the visible blocks store with this block if the width
|
||||
// is longer than a given threshold.
|
||||
if (rectWidth > minVisibleBlockWidth) {
|
||||
visibleBlocksStore.push(block);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.fill();
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds text for a single block.
|
||||
*
|
||||
* @param object block
|
||||
* A single { x, y, width, height, text } object visually representing
|
||||
* the block containing the text.
|
||||
* @param number dataOffset, dataScale
|
||||
* Offsets and scales the data source by the specified amount.
|
||||
* This is used for scrolling the visualization.
|
||||
*/
|
||||
_drawBlockText: function(block, dataOffset, dataScale) {
|
||||
let ctx = this._ctx;
|
||||
let scaledOffset = dataOffset * dataScale;
|
||||
|
||||
let { x, y, width, height, text } = block;
|
||||
|
||||
let paddingTop = FLAME_GRAPH_BLOCK_TEXT_PADDING_TOP * this._pixelRatio;
|
||||
let paddingLeft = FLAME_GRAPH_BLOCK_TEXT_PADDING_LEFT * this._pixelRatio;
|
||||
let paddingRight = FLAME_GRAPH_BLOCK_TEXT_PADDING_RIGHT * this._pixelRatio;
|
||||
let totalHorizontalPadding = paddingLeft + paddingRight;
|
||||
|
||||
let rectLeft = x * this._pixelRatio * dataScale - scaledOffset;
|
||||
let rectWidth = width * this._pixelRatio * dataScale;
|
||||
|
||||
// Clamp the blocks position to start at 0. Avoid negative X coords,
|
||||
// to properly place the text inside the blocks.
|
||||
if (rectLeft < 0) {
|
||||
rectWidth += rectLeft;
|
||||
rectLeft = 0;
|
||||
}
|
||||
|
||||
let textLeft = rectLeft + paddingLeft;
|
||||
let textTop = (y + height / 2 + OVERVIEW_HEADER_HEIGHT) * this._pixelRatio + paddingTop;
|
||||
let textAvailableWidth = rectWidth - totalHorizontalPadding;
|
||||
|
||||
// Massage the text to fit inside a given width. This clamps the string
|
||||
// at the end to avoid overflowing.
|
||||
let fittedText = this._getFittedText(text, textAvailableWidth);
|
||||
if (fittedText.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.fillText(fittedText, textLeft, textTop);
|
||||
},
|
||||
|
||||
/**
|
||||
* Calculating text widths is necessary to trim the text inside the blocks
|
||||
* while the scaling changes (e.g. via scrolling). This is very expensive,
|
||||
* so maintain a cache of string contents to text widths.
|
||||
*/
|
||||
_textWidthsCache: null,
|
||||
_overflowCharWidth: null,
|
||||
_averageCharWidth: null,
|
||||
|
||||
/**
|
||||
* Gets the width of the specified text, for the current context state
|
||||
* (font size, family etc.).
|
||||
*
|
||||
* @param string text
|
||||
* The text to analyze.
|
||||
* @return number
|
||||
* The text width.
|
||||
*/
|
||||
_getTextWidth: function(text) {
|
||||
let cachedWidth = this._textWidthsCache[text];
|
||||
if (cachedWidth) {
|
||||
return cachedWidth;
|
||||
}
|
||||
let metrics = this._ctx.measureText(text);
|
||||
return (this._textWidthsCache[text] = metrics.width);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets an approximate width of the specified text. This is much faster
|
||||
* than `_getTextWidth`, but inexact.
|
||||
*
|
||||
* @param string text
|
||||
* The text to analyze.
|
||||
* @return number
|
||||
* The approximate text width.
|
||||
*/
|
||||
_getTextWidthApprox: function(text) {
|
||||
return text.length * this._averageCharWidth;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the average letter width in the English alphabet, for the current
|
||||
* context state (font size, family etc.). This provides a close enough
|
||||
* value to use in `_getTextWidthApprox`.
|
||||
*
|
||||
* @return number
|
||||
* The average letter width.
|
||||
*/
|
||||
_calcAverageCharWidth: function() {
|
||||
let letterWidthsSum = 0;
|
||||
let start = 32; // space
|
||||
let end = 123; // "z"
|
||||
|
||||
for (let i = start; i < end; i++) {
|
||||
let char = String.fromCharCode(i);
|
||||
letterWidthsSum += this._getTextWidth(char);
|
||||
}
|
||||
|
||||
return letterWidthsSum / (end - start);
|
||||
},
|
||||
|
||||
/**
|
||||
* Massage a text to fit inside a given width. This clamps the string
|
||||
* at the end to avoid overflowing.
|
||||
*
|
||||
* @param string text
|
||||
* The text to fit inside the given width.
|
||||
* @param number maxWidth
|
||||
* The available width for the given text.
|
||||
* @return string
|
||||
* The fitted text.
|
||||
*/
|
||||
_getFittedText: function(text, maxWidth) {
|
||||
let textWidth = this._getTextWidth(text);
|
||||
if (textWidth < maxWidth) {
|
||||
return text;
|
||||
}
|
||||
if (this._overflowCharWidth > maxWidth) {
|
||||
return "";
|
||||
}
|
||||
for (let i = 1, len = text.length; i <= len; i++) {
|
||||
let trimmedText = text.substring(0, len - i);
|
||||
let trimmedWidth = this._getTextWidthApprox(trimmedText) + this._overflowCharWidth;
|
||||
if (trimmedWidth < maxWidth) {
|
||||
return trimmedText + this.overflowChar;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener for the "mousemove" event on the graph's container.
|
||||
*/
|
||||
_onMouseMove: function(e) {
|
||||
let offset = this._getContainerOffset();
|
||||
let mouseX = (e.clientX - offset.left) * this._pixelRatio;
|
||||
|
||||
let canvasWidth = this._width;
|
||||
let canvasHeight = this._height;
|
||||
|
||||
let selection = this._selection;
|
||||
let selectionWidth = selection.end - selection.start;
|
||||
let selectionScale = canvasWidth / selectionWidth;
|
||||
|
||||
let dragger = this._selectionDragger;
|
||||
if (dragger.origin != null) {
|
||||
selection.start = dragger.anchor.start + (dragger.origin - mouseX) / selectionScale;
|
||||
selection.end = dragger.anchor.end + (dragger.origin - mouseX) / selectionScale;
|
||||
this._normalizeSelectionBounds();
|
||||
this._shouldRedraw = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener for the "mousedown" event on the graph's container.
|
||||
*/
|
||||
_onMouseDown: function(e) {
|
||||
let offset = this._getContainerOffset();
|
||||
let mouseX = (e.clientX - offset.left) * this._pixelRatio;
|
||||
|
||||
this._selectionDragger.origin = mouseX;
|
||||
this._selectionDragger.anchor.start = this._selection.start;
|
||||
this._selectionDragger.anchor.end = this._selection.end;
|
||||
this._canvas.setAttribute("input", "adjusting-selection-boundary");
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener for the "mouseup" event on the graph's container.
|
||||
*/
|
||||
_onMouseUp: function() {
|
||||
this._selectionDragger.origin = null;
|
||||
this._canvas.removeAttribute("input");
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener for the "wheel" event on the graph's container.
|
||||
*/
|
||||
_onMouseWheel: function(e) {
|
||||
let offset = this._getContainerOffset();
|
||||
let mouseX = (e.clientX - offset.left) * this._pixelRatio;
|
||||
|
||||
let canvasWidth = this._width;
|
||||
let canvasHeight = this._height;
|
||||
|
||||
let selection = this._selection;
|
||||
let selectionWidth = selection.end - selection.start;
|
||||
let selectionScale = canvasWidth / selectionWidth;
|
||||
|
||||
switch (e.axis) {
|
||||
case e.VERTICAL_AXIS: {
|
||||
let distFromStart = mouseX;
|
||||
let distFromEnd = canvasWidth - mouseX;
|
||||
let vector = e.detail * GRAPH_WHEEL_ZOOM_SENSITIVITY / selectionScale;
|
||||
selection.start -= distFromStart * vector;
|
||||
selection.end += distFromEnd * vector;
|
||||
break;
|
||||
}
|
||||
case e.HORIZONTAL_AXIS: {
|
||||
let vector = e.detail * GRAPH_WHEEL_SCROLL_SENSITIVITY / selectionScale;
|
||||
selection.start += vector;
|
||||
selection.end += vector;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this._normalizeSelectionBounds();
|
||||
this._shouldRedraw = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Makes sure the start and end points of the current selection
|
||||
* are withing the graph's visible bounds, and that they form a selection
|
||||
* wider than the allowed minimum width.
|
||||
*/
|
||||
_normalizeSelectionBounds: function() {
|
||||
let canvasWidth = this._width * 2;
|
||||
let canvasHeight = this._height;
|
||||
|
||||
let { start, end } = this._selection;
|
||||
let minSelectionWidth = GRAPH_MIN_SELECTION_WIDTH * this._pixelRatio;
|
||||
|
||||
if (start < 0) {
|
||||
start = 0;
|
||||
}
|
||||
if (end < 0) {
|
||||
start = 0;
|
||||
end = minSelectionWidth;
|
||||
}
|
||||
if (end > canvasWidth) {
|
||||
end = canvasWidth;
|
||||
}
|
||||
if (start > canvasWidth) {
|
||||
end = canvasWidth;
|
||||
start = canvasWidth - minSelectionWidth;
|
||||
}
|
||||
if (end - start < minSelectionWidth) {
|
||||
let midPoint = (start + end) / 2;
|
||||
start = midPoint - minSelectionWidth / 2;
|
||||
end = midPoint + minSelectionWidth / 2;
|
||||
}
|
||||
|
||||
this._selection.start = start;
|
||||
this._selection.end = end;
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* Finds the optimal tick interval between time markers in this graph.
|
||||
*
|
||||
* @param number dataScale
|
||||
* @return number
|
||||
*/
|
||||
_findOptimalTickInterval: function(dataScale) {
|
||||
let timingStep = TIMELINE_TICKS_MULTIPLE;
|
||||
let spacingMin = TIMELINE_TICKS_SPACING_MIN * this._pixelRatio;
|
||||
|
||||
if (dataScale > spacingMin) {
|
||||
return dataScale;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
let scaledStep = dataScale * timingStep;
|
||||
if (scaledStep < spacingMin) {
|
||||
timingStep <<= 1;
|
||||
continue;
|
||||
}
|
||||
return scaledStep;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the offset of this graph's container relative to the owner window.
|
||||
*
|
||||
* @return object
|
||||
* The { left, top } offset.
|
||||
*/
|
||||
_getContainerOffset: function() {
|
||||
let node = this._canvas;
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
|
||||
while ((node = node.offsetParent)) {
|
||||
x += node.offsetLeft;
|
||||
y += node.offsetTop;
|
||||
}
|
||||
|
||||
return { left: x, top: y };
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A collection of utility functions converting various data sources
|
||||
* into a format drawable by the FlameGraph.
|
||||
*/
|
||||
let FlameGraphUtils = {
|
||||
// TODO bug 1077459
|
||||
};
|
|
@ -7,14 +7,10 @@ const Cu = Components.utils;
|
|||
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
|
||||
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
|
||||
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"GraphCursor",
|
||||
"GraphSelection",
|
||||
"GraphSelectionDragger",
|
||||
"GraphSelectionResizer",
|
||||
"AbstractCanvasGraph",
|
||||
"LineGraphWidget",
|
||||
"BarGraphWidget",
|
||||
|
@ -97,23 +93,28 @@ const BAR_GRAPH_LEGEND_MOUSEOVER_DEBOUNCE = 50; // ms
|
|||
/**
|
||||
* Small data primitives for all graphs.
|
||||
*/
|
||||
this.GraphCursor = function() {
|
||||
this.x = null;
|
||||
this.y = null;
|
||||
this.GraphCursor = function() {};
|
||||
this.GraphSelection = function() {};
|
||||
this.GraphSelectionDragger = function() {};
|
||||
this.GraphSelectionResizer = function() {};
|
||||
|
||||
GraphCursor.prototype = {
|
||||
x: null,
|
||||
y: null
|
||||
};
|
||||
|
||||
this.GraphSelection = function() {
|
||||
this.start = null;
|
||||
this.end = null;
|
||||
GraphSelection.prototype = {
|
||||
start: null,
|
||||
end: null
|
||||
};
|
||||
|
||||
this.GraphSelectionDragger = function() {
|
||||
this.origin = null;
|
||||
this.anchor = new GraphSelection();
|
||||
GraphSelectionDragger.prototype = {
|
||||
origin: null,
|
||||
anchor: new GraphSelection()
|
||||
};
|
||||
|
||||
this.GraphSelectionResizer = function() {
|
||||
this.margin = null;
|
||||
GraphSelectionResizer.prototype = {
|
||||
margin: null
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -244,11 +245,6 @@ AbstractCanvasGraph.prototype = {
|
|||
this._window.cancelAnimationFrame(this._animationId);
|
||||
this._iframe.remove();
|
||||
|
||||
this._cursor = null;
|
||||
this._selection = null;
|
||||
this._selectionDragger = null;
|
||||
this._selectionResizer = null;
|
||||
|
||||
this._data = null;
|
||||
this._mask = null;
|
||||
this._maskArgs = null;
|
||||
|
@ -896,9 +892,6 @@ AbstractCanvasGraph.prototype = {
|
|||
|
||||
/**
|
||||
* Gets the offset of this graph's container relative to the owner window.
|
||||
*
|
||||
* @return object
|
||||
* The { left, top } offset.
|
||||
*/
|
||||
_getContainerOffset: function() {
|
||||
let node = this._canvas;
|
||||
|
|
Загрузка…
Ссылка в новой задаче