['boundingRect']
var _currBoundingRectOpt = false;
chart.__testHelper.updateBoundingRects
= chart.__testHelper.updateBoundingRect
= chart.__testHelper.boundingRect
= chart.__testHelper.boundingRects
= updateBoundingRects;
updateBoundingRects(opt.boundingRect);
return;
function updateBoundingRects(opt) {
if (arguments.length > 0) {
_currBoundingRectOpt = opt;
} // If no opt, keep the last one.
_currBoundingRectOpt
? buildBoundingRects(_currBoundingRectOpt)
: disableBoundingRects();
}
function ensureBoundingRectsFacilities() {
// zr requires size non-zero.
boundingRectsContainer.style.width = chart.getWidth() + 'px';
boundingRectsContainer.style.height = chart.getHeight() + 'px';
if (_bRectZr) {
_bRectZr.resize();
return;
}
_bRectGroup = new echarts.graphic.Group();
_bRectGroup.__testHelperBoundingRectsRoot = true;
_bRectGroup.on('click', function (event) {
var target = event.target;
if (!target || !target.__testHelperBoundingRectTarget) {
return;
}
var wrapper = {
boundingRect: target,
rawElement: target.__testHelperBoundingRectTarget
};
console.log('boundingRect:', wrapper.boundingRect);
console.log('rawElement:', wrapper.rawElement);
window.$0 = wrapper;
});
_bRectZr = echarts.zrender.init(boundingRectsContainer);
_bRectZr.add(_bRectGroup);
}
function disableBoundingRects() {
chart.off('finished', updateBoundingRects);
boundingRectsContainer.style.display = 'none';
if (_bRectGroup) {
_bRectGroup.removeAll();
}
}
function buildBoundingRects(boundingRectOpt) {
ensureBoundingRectsFacilities();
boundingRectOpt = isObject(boundingRectOpt) ? boundingRectOpt : {};
boundingRectsContainer.style.display = 'block';
_bRectGroup.removeAll();
var strokeColor = boundingRectOpt.color || 'rgba(0,0,255,0.5)';
var silent = boundingRectOpt.silent != null ? boundingRectOpt.silent : false;
boundingRectsContainer.style.pointerEvent = silent ? 'none' : 'auto';
var roots = chart.getZr().storage.getRoots();
for (var rootIdx = 0; rootIdx < roots.length; rootIdx++) {
travelGroupAndBuildRects(roots[rootIdx], _bRectGroup);
}
// Follow chart update and resize.
chart.on('finished', updateBoundingRects);
return;
function travelGroupAndBuildRects(el, visualRectGroupParent) {
if (el.childrenRef) { // group or text
var visualRectGroup = createVisualRectGroup(el, visualRectGroupParent)
var children = el.childrenRef();
for (var idx = 0; idx < children.length; idx++) {
var child = children[idx];
travelGroupAndBuildRects(child, visualRectGroup);
}
}
// Both display ZRText and TSpan bounding rect for debuging.
if (!el.isGroup) {
createVisualRectForEl(el, visualRectGroupParent);
}
function createVisualRectForEl(el, visualRectGroup) {
createRectForDisplayable(el, visualRectGroup);
var textContent = el.getTextContent();
var textGuildLine = el.getTextGuideLine();
var textConfig = el.textConfig;
if (textContent || textGuildLine) {
var isLocal = textConfig && textConfig.local;
var targetVisualGroup = isLocal ? visualRectGroup : _bRectGroup;
textContent && createRectForDisplayable(textContent, targetVisualGroup, true);
textGuildLine && createRectForDisplayable(textGuildLine, targetVisualGroup, true);
}
}
function createVisualRectGroup(fromEl, visualRectGroupParent) {
var visualRectGroup = new echarts.graphic.Group();
copyTransformAttrs(visualRectGroup, fromEl);
visualRectGroupParent.add(visualRectGroup);
return visualRectGroup;
}
function createRectForDisplayable(el, visualRectGroup, useInnerTransformable) {
var elRawRect = el.getBoundingRect();
var visualRect = new echarts.graphic.Rect({
shape: {x: elRawRect.x, y: elRawRect.y, width: elRawRect.width, height: elRawRect.height},
style: {fill: null, stroke: strokeColor, lineWidth: 1, strokeNoScale: true},
silent: silent,
z: Number.MAX_SAFE_INTEGER
});
visualRect.__testHelperBoundingRectTarget = el;
var transAttrSource = el;
if (useInnerTransformable && el.innerTransformable) {
transAttrSource = el.innerTransformable;
}
copyTransformAttrs(visualRect, transAttrSource);
visualRectGroup.add(visualRect);
}
}
function copyTransformAttrs(target, source) {
target.x = source.x;
target.y = source.y;
target.rotation = source.rotation;
target.scaleX = source.scaleX;
target.scaleY = source.scaleY;
target.originX = source.originX;
target.originY = source.originY;
target.skewX = source.skewX;
target.skewY = source.skewY;
target.anchorX = source.anchorX;
target.anchorY = source.anchorY;
}
}
}
/**
* Simply create inputs - all of them are 'select' or 'br'.
* This is the most commonly used case.
*
* Usage:
* ```js
* const _ctx = {
* xAxis_axisTick_interval: {
* text: 'xAxis.axisTick.interval:', // Any text
* value: 'NOT_SET', // Initial value of the "select" input.
* values: ['NOT_SET', 2, 0, 1] // Options of the "select" input.
* __extra: 123 // Pass to updateChart directly.
* },
* xAxis_axisLabel_interval: {
* text: 'xAxis.axisLabel.interval:',
* value: 'NOT_SET',
* values: ['NOT_SET', 2, 0, 1]
* },
* br0: {}, // Can create a 'br' input.
* // 'br'/'BR' is reserved keys.
* dataZoomOrMinMax: {
* text: 'use:', // Can omit.
* value: 'use_xAxis_min_max',
* values: ['use_xAxis_min_max', 'use_dataZoom'],
* },
* br1: {}, // Create another 'br' input.
* hr1: { // Create a 'hr' input.
* text: 'some label on hr'
* },
* };
* function createOption() {
* return {...};
* }
* function updateChart(currentInputOption) {
* chart.setOption(createOption(), {notMerge: true});
* console.log(currentInputOption.__extra); // 123 or null/undefined.
* }
* var chart = testHelper.create(echarts, 'chart4', {
* option: createOption(),
* inputsStyle: 'compact',
* inputs: testHelper.createInputsSimply(_ctx, updateChart)
* };
* ```
*/
testHelper.createInputsSimply = function (_ctx, updateChart) {
var inputs = [];
for (var key in _ctx) {
if (_ctx.hasOwnProperty(key)) {
inputs.push(createInput(key));
}
}
function createInput(key) {
if (/^br[0-9]*$/i.test(key)) {
// Can be typically `{br: {}, br0: {}, br1: {}, BR: {}, BR1: {}}`.
return {
type: 'br'
};
}
else if (/^hr[0-9]*$/i.test(key)) {
// Can be typically `{br: {}, br0: {}, br1: {}, BR: {}, BR1: {}}`.
return {
type: 'hr',
text: _ctx[key].text
};
}
else {
var input = {
type: 'select',
text: _ctx[key].text || (key + ':'),
onchange: function () {
var newValue = this.value;
if (_ctx[key].hasOwnProperty('valueIndex')) {
_ctx[key].valueIndex = findValueIndex();
}
else if (_ctx[key].hasOwnProperty('optionIndex')) {
_ctx[key].optionIndex = findValueIndex();
}
else {
_ctx[key].value = newValue;
}
function findValueIndex() {
if (_ctx[key].values) {
return arrayIndexOf(_ctx[key].values, newValue);
}
else if (_ctx[key].options) {
for (var i = 0; i < _ctx[key].options.length; i++) {
if (_ctx[key].options[i].value === newValue) {
return i;
}
}
return -1;
}
}
updateChart(_ctx[key]);
}
};
function assignIfExisting(prop) {
if (_ctx[key].hasOwnProperty(prop)) {
input[prop] = _ctx[key][prop];
}
}
assignIfExisting('value');
assignIfExisting('valueIndex');
assignIfExisting('optionIndex');
assignIfExisting('values');
assignIfExisting('options');
return input;
}
}
return inputs;
};
testHelper.createRecordVideo = function (chart, recordVideoContainer) {
var button = document.createElement('button');
button.innerHTML = 'Start Recording';
recordVideoContainer.appendChild(button);
var recorder = new VideoRecorder(chart);
var isRecording = false;
button.onclick = function () {
isRecording ? recorder.stop() : recorder.start();
button.innerHTML = (isRecording ? 'Start' : 'Stop') + ' Recording';
isRecording = !isRecording;
}
}
/**
* @param {ECharts} echarts
* @param {HTMLElement|string} domOrId
* @param {Object} option
* @param {boolean|number} opt If number, means height
* @param {boolean} opt.lazyUpdate
* @param {boolean} opt.notMerge
* @param {boolean} opt.useCoarsePointer
* @param {boolean} opt.pointerSize
* @param {number} opt.width
* @param {number} opt.height
* @param {boolean} opt.draggable
* @param {string} opt.renderer 'canvas' or 'svg'
* @param {string} errMsgPrefix
*/
testHelper.createChart = function (echarts, domOrId, option, opt, errMsgPrefix) {
if (typeof opt === 'number') {
opt = {height: opt};
}
else {
opt = opt || {};
}
var dom = getDom(domOrId);
if (dom) {
if (opt.width != null) {
dom.style.width = opt.width + 'px';
}
if (opt.height != null) {
dom.style.height = opt.height + 'px';
}
var theme = opt.theme && opt.theme !== 'none' ? opt.theme : null;
if (theme == null && window.__ECHARTS__DEFAULT__THEME__) {
theme = window.__ECHARTS__DEFAULT__THEME__;
}
if (typeof theme === 'string') {
require(['theme/' + theme]);
}
var chart = echarts.init(dom, theme, {
renderer: opt.renderer,
useCoarsePointer: opt.useCoarsePointer,
pointerSize: opt.pointerSize
});
if (opt.draggable) {
if (!window.draggable) {
throw new Error(
errMsgPrefix + ' Pleasse add the script in HTML: \n'
+ ''
);
}
window.draggable.init(dom, chart, {throttle: 70, onResize: opt.onResize});
}
option && chart.setOption(option, {
lazyUpdate: opt.lazyUpdate,
notMerge: opt.notMerge
});
var isAutoResize = opt.autoResize == null ? true : opt.autoResize;
if (isAutoResize) {
testHelper.resizable(chart, {onResize: opt.onResize});
}
return chart;
}
};
/**
* @usage
* ```js
* testHelper.printAssert(chart, function (assert) {
* // If any error thrown here, a "checked: Fail" will be printed on the chart;
* // Otherwise, "checked: Pass" will be printed on the chart.
* assert(condition1);
* assert(condition2);
* assert(condition3);
* });
* ```
* `testHelper.printAssert` can be called multiple times for one chart instance.
* For each call, one result (fail or pass) will be printed.
*
* @param chartOrDomId {EChartsInstance | string}
* @param checkFn {Function} param: a function `assert`.
*/
testHelper.printAssert = function (chartOrDomId, checkerFn) {
if (!chartOrDomId) {
return;
}
var hostDOMEl;
var chart;
if (typeof chartOrDomId === 'string') {
hostDOMEl = document.getElementById(chartOrDomId);
}
else {
chart = chartOrDomId;
hostDOMEl = chartOrDomId.getDom();
}
var failErr;
function assert(cond, msg) {
if (!cond) {
throw new Error(msg || 'printAssert error');
}
}
try {
checkerFn(assert);
}
catch (err) {
console.error(err);
failErr = err;
}
var printAssertRecord = hostDOMEl.__printAssertRecord || (hostDOMEl.__printAssertRecord = []);
var resultDom = document.createElement('div');
resultDom.innerHTML = failErr ? 'checked: Fail' : 'checked: Pass';
var fontSize = 40;
resultDom.style.cssText = [
'position: absolute;',
'left: 20px;',
'pointer-events: none;',
'font-size: ' + fontSize + 'px;',
'z-index: ' + (failErr ? 99999 : 88888) + ';',
'color: ' + (failErr ? 'rgba(150,0,0,0.8)' : 'rgba(0,150,0,0.8)') + ';',
].join('');
printAssertRecord.push(resultDom);
hostDOMEl.appendChild(resultDom);
relayoutResult();
function relayoutResult() {
var chartHeight = chart ? chart.getHeight() : hostDOMEl.offsetHeight;
var lineHeight = Math.min(fontSize + 10, (chartHeight - 20) / printAssertRecord.length);
for (var i = 0; i < printAssertRecord.length; i++) {
var record = printAssertRecord[i];
record.style.top = (10 + i * lineHeight) + 'px';
}
}
};
var _dummyRequestAnimationFrameMounted = false;
var _controlFrameSingleton = null;
/**
* Usage:
* ```js
* testHelper.controlFrame({pauseAt: 60});
* // Then load echarts.js (must after controlFrame called)
* ```
*
* @param {Object} [opt]
* @param {number} [opt.puaseAt] If specified `pauseAt`, auto pause at the frame.
* @param {Function} [opt.onFrame]
*/
testHelper.controlFrame = function (opt) {
if (_controlFrameSingleton) {
throw new Error('`testHelper.controlFrame` can only be called once.');
}
_controlFrameSingleton = {};
opt = opt || {};
var pauseAt = opt.pauseAt;
pauseAt == null && (pauseAt = 0);
var _running = true;
var _pendingCbList = [];
var _frameNumber = 0;
var _mounted = false;
var _mask = null;
function getRunBtnText() {
return _running ? 'Pause' : 'Resume';
}
var buttons = [{
text: getRunBtnText(),
onclick: function () {
buttons[0].el.innerHTML = getRunBtnText();
_running ? pause() : run();
}
}, {
text: 'Next Frame',
onclick: nextFrame
}];
var btnPanel = document.createElement('div');
btnPanel.className = 'control-frame-btn-panel'
var infoEl = document.createElement('div');
infoEl.className = 'control-frame-info';
btnPanel.appendChild(infoEl);
document.body.appendChild(btnPanel);
for (var i = 0; i < buttons.length; i++) {
var button = buttons[i];
var btnEl = button.el = document.createElement('button');
btnEl.innerHTML = button.text;
btnEl.addEventListener('click', button.onclick);
btnPanel.appendChild(btnEl);
}
_controlFrameSingleton.btnPanel = btnPanel;
if (_dummyRequestAnimationFrameMounted) {
throw new Error('Do not support `controlFrame` twice');
}
_dummyRequestAnimationFrameMounted = true;
var raf = window.requestAnimationFrame;
window.requestAnimationFrame = function (cb) {
_pendingCbList.push(cb);
if (_running && !_mounted) {
_mounted = true;
raf(nextFrame);
}
};
function run() {
setRunning(true);
nextFrame();
}
function pause() {
setRunning(false);
}
function setRunning(nextRunning) {
if (!_running && nextRunning) {
addOrRemoveMask(false);
}
else if (_running && !nextRunning) {
addOrRemoveMask(true);
}
_running = nextRunning;
}
function nextFrame() {
opt.onFrame && opt.onFrame(_frameNumber);
if (pauseAt != null && _frameNumber === pauseAt) {
setRunning(false);
pauseAt = null;
}
infoEl.innerHTML = 'Frame: ' + _frameNumber + ' ( ' + (_running ? 'Running' : 'Paused') + ' )';
buttons[0].el.innerHTML = getRunBtnText();
_mounted = false;
var pending = _pendingCbList;
_pendingCbList = [];
for (var i = 0; i < pending.length; i++) {
pending[i]();
}
_frameNumber++;
}
function addOrRemoveMask(addOrRemove) {
if (addOrRemove && !_mask) {
_mask = document.createElement('div');
_mask.className = 'control-frame-mask';
document.body.appendChild(_mask);
}
else if (!addOrRemove && _mask) {
document.body.removeChild(_mask);
_mask = null;
}
}
}; // End of `controlFrame`
/**
* Print zrender canvas layer draw operations on frame.
*
* @usage
* ```js
* var recordContainer = document.getElementById('record');
* testHelper.controlFrame({
* pauseAt: 30,
* onFrame: function (frameNumber) {
* testHelper.printCanvasLayerDrawOperationsOnFrame(chart, frameNumber, recordContainer);
* }
* });
* ```
*/
testHelper.printCanvasLayerDrawOperationsOnFrame = function (
chart, // Mandatory.
frameNumber, // Mandatory.
recordContainer, // Mandatory.
cellMax // Optional; max cell count to display
) {
if (!chart) {
return;
}
var _drawOpCtx = chart.__printCanvasLayerDrawOperationsOnFrame_ctx
|| (chart.__printCanvasLayerDrawOperationsOnFrame_ctx = initPrintDrawOpCtx(recordContainer));
// key: layer key; value: ops array.
var _lastOps = {};
doPrint();
function initPrintDrawOpCtx(recordContainer) {
if (window.Canteen) {
window.Canteen.globals.STACK_SIZE = 100000000;
}
else {
console.error && console.error('canteen.js is required but not imported');
}
recordContainer.innerHTML = [
'',
'NOTE: In "incremental layers" (layer zr_N.1), ',
'canvas instruction count per frame (red number) should be the same per frame;',
'
In "normal layers" (layer zr_N.0 or zr_N.2), should be no incremental canvas instructions per frame.',
'
'
].join('');
recordContainer.className = 'print-canvas-layer-draw-operations-on-frame-record';
return {
layersInfoMap: {},
recordContainer: recordContainer,
CELL_MAX: cellMax || 90,
};
}
function doPrint() {
_drawOpCtx.lastOps = {};
var painter = chart.getZr().painter;
if (painter.type !== 'canvas') {
return;
}
var layers = painter.getLayers();
for (var layerId in layers) {
if (layers.hasOwnProperty(layerId)) {
printSingleCanvasLayer(layerId, layers[layerId], frameNumber);
}
}
}
function printSingleCanvasLayer(layerId, layer, frameNumber) {
var layerInfo = _drawOpCtx.layersInfoMap[layerId];
if (!layerInfo) {
layerInfo = _drawOpCtx.layersInfoMap[layerId] = {
recordLineCellCount: 0,
recordLineTitle: document.createElement('span'),
recordLineAvg: document.createElement('span'),
recordLineContainer: document.createElement('div')
};
layerInfo.stackLengthRecord = [];
layerInfo.stackLengthSumInWindow = 0;
layerInfo.stackLengthMax = 0;
layerInfo.recordLineTitle.innerHTML = 'layer ' + layerId + ': ';
layerInfo.recordLineTitle.className = 'print-canvas-layer-draw-operations-on-frame-record-line-title';
layerInfo.recordLineAvg.className = 'print-canvas-layer-draw-operations-on-frame-record-line-title';
layerInfo.recordLineContainer.className = 'print-canvas-layer-draw-operations-on-frame-record-line';
var titleLineDom = document.createElement('div');
titleLineDom.appendChild(layerInfo.recordLineTitle);
titleLineDom.appendChild(layerInfo.recordLineAvg);
_drawOpCtx.recordContainer.appendChild(titleLineDom);
_drawOpCtx.recordContainer.appendChild(layerInfo.recordLineContainer);
}
var canvas = layer.dom;
var ctx = canvas.getContext('2d');
var stackLength = getStackLength(ctx);
var thisStackLength = stackLength;
if (thisStackLength > layerInfo.stackLengthMax) {
layerInfo.stackLengthMax = thisStackLength;
}
layerInfo.stackLengthRecord.push(thisStackLength);
layerInfo.stackLengthSumInWindow += thisStackLength;
if (layerInfo.stackLengthRecord.length > _drawOpCtx.CELL_MAX) {
layerInfo.stackLengthSumInWindow -= layerInfo.stackLengthRecord[0];
layerInfo.stackLengthRecord.shift();
}
var avgStackLength = (layerInfo.stackLengthSumInWindow / layerInfo.stackLengthRecord.length).toFixed(2);
layerInfo.recordLineAvg.innerHTML =
' (avg: ' + avgStackLength
+ ' = ' + layerInfo.stackLengthSumInWindow + ' / ' + layerInfo.stackLengthRecord.length
+ ' max: ' + layerInfo.stackLengthMax + ')';
var cell;
if (layerInfo.recordLineCellCount > _drawOpCtx.CELL_MAX) {
cell = layerInfo.recordLineContainer.firstChild;
}
else {
cell = document.createElement('span');
layerInfo.recordLineCellCount++;
}
cell.innerHTML = frameNumber + ':' + thisStackLength + ' ';
layerInfo.recordLineContainer.appendChild(cell);
if (ctx.stack) {
_lastOps[layerId] = ctx.stack().slice();
}
if (ctx.clear) {
ctx.clear();
}
}
function getStackLength(ctx) {
return ctx.stack ? ctx.stack().length : 0;
}
return _lastOps;
}; // End of `printCanvasLayerDrawOperationsOnFrame`
testHelper.resizable = function (chart, opt) {
opt = opt || {};
var dom = chart.getDom();
var width = dom.clientWidth;
var height = dom.clientHeight;
function resize() {
var newWidth = dom.clientWidth;
var newHeight = dom.clientHeight;
if (width !== newWidth || height !== newHeight) {
chart.resize();
if (chart.__testHelper && chart.__testHelper.updateBoundingRects) {
chart.__testHelper.updateBoundingRects();
}
width = newWidth;
height = newHeight;
if (opt.onResize) {
opt.onResize();
}
}
}
if (window.attachEvent) {
// Use builtin resize in IE
window.attachEvent('onresize', resize);
}
else if (window.addEventListener) {
window.addEventListener('resize', resize, false);
}
};
// Clean params specified by `cleanList` and seed a param specifid by `newVal` in URL.
testHelper.setURLParam = function (cleanList, newVal) {
var params = getParamListFromURL();
for (var i = params.length - 1; i >= 0; i--) {
for (var j = 0; j < cleanList.length; j++) {
if (params[i] === cleanList[j]) {
params.splice(i, 1);
}
}
}
newVal && params.push(newVal);
params.sort();
location.search = params.join('&');
};
// Whether has param `val` in URL.
testHelper.hasURLParam = function (val) {
var params = getParamListFromURL();
for (var i = params.length - 1; i >= 0; i--) {
if (params[i] === val) {
return true;
}
}
return false;
};
// Nodejs `path.resolve`.
testHelper.resolve = function () {
var resolvedPath = '';
var resolvedAbsolute;
for (var i = arguments.length - 1; i >= 0 && !resolvedAbsolute; i--) {
var path = arguments[i];
if (path) {
resolvedPath = path + '/' + resolvedPath;
resolvedAbsolute = path[0] === '/';
}
}
if (!resolvedAbsolute) {
throw new Error('At least one absolute path should be input.');
}
// Normalize the path
resolvedPath = normalizePathArray(resolvedPath.split('/'), false).join('/');
return '/' + resolvedPath;
};
var encodeHTML = testHelper.encodeHTML = function (source) {
return String(source)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
};
var encodeJSObjectKey = function (source, quotationMark) {
source = '' + source;
if (!/^[a-zA-Z$_][a-zA-Z0-9$_]*$/.test(source)) {
source = convertStringToJSLiteral(source, quotationMark);
}
return source;
};
var convertStringToJSLiteral = function (str, quotationMark) {
// assert(getType(str) === 'string');
// assert(quotationMark === '"' || quotationMark === "'");
str = JSON.stringify(str); // escapse \n\r or others.
if (quotationMark === "'") {
str = "'" + str.slice(1, str.length - 1).replace(/'/g, "\\'") + "'";
}
return str;
}
/**
* @usage
* var result = retrieveValue(val, defaultVal);
* var result = retrieveValue(val1, val2, defaultVal);
*/
var retrieveValue = testHelper.retrieveValue = function () {
for (var i = 0, len = arguments.length; i < len; i++) {
var val = arguments[i];
if (val != null) {
return val;
}
}
};
/**
* @public
* @return {string} Current url dir.
*/
testHelper.dir = function () {
return location.origin + testHelper.resolve(location.pathname, '..');
};
/**
* Not accurate.
* @param {*} type
* @return {string} 'function', 'array', 'typedArray', 'regexp',
* 'date', 'object', 'boolean', 'number', 'string'
*/
var getType = testHelper.getType = function (value) {
var type = typeof value;
var typeStr = objToString.call(value);
return !!TYPED_ARRAY[objToString.call(value)]
? 'typedArray'
: typeof value === 'function'
? 'function'
: typeStr === '[object Array]'
? 'array'
: typeStr === '[object Number]'
? 'number'
: typeStr === '[object Boolean]'
? 'boolean'
: typeStr === '[object String]'
? 'string'
: typeStr === '[object RegExp]'
? 'regexp'
: typeStr === '[object Date]'
? 'date'
: !!value && type === 'object'
? 'object'
: null;
};
function containsDOMElement(parent, child, includeSelf) {
if (!includeSelf && child) {
child = child.parentNode;
}
while (child) {
if (child === parent) {
return true;
}
child = child.parentNode;
}
return false;
}
function cssHasClass(el, className) {
return cssNormalizeClass(el.className).indexOf(className) >= 0;
}
function cssAddClass(el, className) {
if (!cssHasClass(el, className)) {
el.className = cssNormalizeClass(el.className + ' ' + className);
}
}
function cssRemoveClass(el, className) {
var classes = (' ' + el.className + ' ').replace(' ' + className + ' ', function () {
return ' '; // Use a cb to String.prototype.replace to avoid `$` issue.
});
el.className = cssNormalizeClass(classes);
}
function cssNormalizeClass(classes) {
return classes.replace(/^\s+|\s+$/g, '').replace(/\s+/g, ' ');
}
/**
* JSON.stringify(obj, null, 2) will vertically layout array, which takes too much space.
* Can print like:
* [
* {name: 'xxx', value: 123},
* {name: 'xxx', value: 123},
* {name: 'xxx', value: 123}
* ]
* {
* arr: [33, 44, 55],
* str: 'xxx'
* }
*
* @param {*} object
* @param {opt|string} [opt] If string, means key.
* @param {string} [opt.key=''] Top level key, if given, print like: 'someKey: [asdf]'
* @param {number} [opt.lineBreakMaxColumn=80] If the content in a single line is greater than
* `maxColumn` (indent is not included), line break.
* @param {boolean} [opt.objectLineBreak=undefined] Whether to line break. undefined/null means auto.
* @param {boolean} [opt.arrayLineBreak=undefined] Whether to line break. undefined/null means auto.
* @param {string} [opt.indent=4]
* @param {string} [opt.marginLeft=0] Spaces number for margin left of the entire text.
* @param {string} [opt.lineBreak='\n']
* @param {string} [opt.quotationMark="'"] "'" or '"'.
* @return {string}
*/
var printObject = testHelper.printObject = function (obj, opt) {
opt = typeof opt === 'string'
? {key: opt}
: (opt || {});
var indent = opt.indent != null ? opt.indent : 4;
var lineBreak = opt.lineBreak != null ? opt.lineBreak : '\n';
var quotationMark = ({'"': '"', "'": "'"})[opt.quotationMark] || "'";
var marginLeft = opt.marginLeft || 0;
var lineBreakMaxColumn = opt.lineBreakMaxColumn || 80;
var forceObjectLineBreak = opt.objectLineBreak === true || opt.objectLineBreak === false;
var forceArrayLineBreak = opt.arrayLineBreak === true || opt.arrayLineBreak === false;
return (new Array(marginLeft + 1)).join(' ') + doPrint(obj, opt.key, 0).str;
function doPrint(obj, key, depth) {
var codeIndent = (new Array(depth * indent + marginLeft + 1)).join(' ');
var subCodeIndent = (new Array((depth + 1) * indent + marginLeft + 1)).join(' ');
var hasLineBreak = false;
// [
// 11, 22, 33, 44, 55, 66, // This is a partial break.
// 77, 88, 99
// ]
var preventParentArrayPartiallyBreak = false;
var preStr = '';
if (key != null) {
preStr += encodeJSObjectKey(key, quotationMark) + ': ';
}
var str;
var objType = getType(obj);
switch (objType) {
case 'function':
hasLineBreak = true;
preventParentArrayPartiallyBreak = true;
var fnStr = obj.toString();
var isMethodShorthand = key != null && isMethodShorthandNotAccurate(fnStr, obj.name, key);
str = (isMethodShorthand ? '' : preStr) + fnStr;
break;
case 'regexp':
case 'date':
str = preStr + quotationMark + obj + quotationMark;
break;
case 'array':
case 'typedArray':
if (forceArrayLineBreak) {
hasLineBreak = !!opt.arrayLineBreak;
}
// If no break line in array, print in single line, like [12, 23, 34].
// else, each item takes a line.
var childBuilder = [];
var maxColumnWithoutLineBreak = preStr.length;
var canPartiallyBreak = true;
for (var i = 0, len = obj.length; i < len; i++) {
var subResult = doPrint(obj[i], null, depth + 1);
childBuilder.push(subResult.str);
if (subResult.hasLineBreak) {
hasLineBreak = true;
}
else {
maxColumnWithoutLineBreak += subResult.str.length + 2; // `2` is ', '.length
}
if (subResult.preventParentArrayPartiallyBreak) {
preventParentArrayPartiallyBreak = true;
canPartiallyBreak = false
}
}
if (obj.length > 3) {
// `3` is an arbitrary value, considering a path array:
// [
// [1,2], [3,4], [5,6],
// [7,8], [9,10]
// ]
preventParentArrayPartiallyBreak = true;
}
if (!forceObjectLineBreak && maxColumnWithoutLineBreak > lineBreakMaxColumn) {
hasLineBreak = true;
}
var tail = hasLineBreak ? lineBreak : '';
var subPre = hasLineBreak ? subCodeIndent : '';
var endPre = hasLineBreak ? codeIndent : '';
var delimiterInline = ', ';
var delimiterBreak = ',' + lineBreak + subCodeIndent;
if (!childBuilder.length) {
str = preStr + '[]';
}
else {
var subContentStr = '';
var subContentMaxColumn = 0;
if (canPartiallyBreak && hasLineBreak) {
for (var idx = 0; idx < childBuilder.length; idx++) {
var childStr = childBuilder[idx];
subContentMaxColumn += childStr.length + delimiterInline.length;
if (idx === childBuilder.length - 1) {
subContentStr += childStr;
}
else if (subContentMaxColumn > lineBreakMaxColumn) {
subContentStr += childStr + delimiterBreak;
subContentMaxColumn = 0;
}
else {
subContentStr += childStr + delimiterInline;
}
}
}
else {
subContentStr = childBuilder.join(hasLineBreak ? delimiterBreak : delimiterInline);
}
str = ''
+ preStr + '[' + tail
+ subPre + subContentStr + tail
+ endPre + ']';
}
break;
case 'object':
if (forceObjectLineBreak) {
hasLineBreak = !!opt.objectLineBreak;
}
var childBuilder = [];
var maxColumnWithoutLineBreak = preStr.length;
var keyCount = 0;
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
keyCount++;
var subResult = doPrint(obj[i], i, depth + 1);
childBuilder.push(subResult.str);
if (subResult.hasLineBreak) {
hasLineBreak = true;
}
else {
maxColumnWithoutLineBreak += subResult.str.length + 2; // `2` is ', '.length
}
if (subResult.preventParentArrayPartiallyBreak) {
preventParentArrayPartiallyBreak = true;
}
}
}
if (keyCount > 1) {
// `3` is an arbitrary value, considering case like:
// [
// {name: 'xx'}, {name: 'yy'}, {name: 'zz'},
// {name: 'aa'}, {name: 'bb'}
// ]
preventParentArrayPartiallyBreak = true;
}
if (!forceObjectLineBreak && maxColumnWithoutLineBreak > lineBreakMaxColumn) {
hasLineBreak = true;
}
if (!childBuilder.length) {
str = preStr + '{}';
}
else {
str = ''
+ preStr + '{' + (hasLineBreak ? lineBreak : '')
+ (hasLineBreak ? subCodeIndent : '')
+ childBuilder.join(',' + (hasLineBreak ? lineBreak + subCodeIndent: ' '))
+ (hasLineBreak ? lineBreak: '')
+ (hasLineBreak ? codeIndent : '') + '}';
}
break;
case 'boolean':
case 'number':
str = preStr + obj + '';
break;
case 'string':
str = preStr + convertStringToJSLiteral(obj, quotationMark);
break;
default:
str = preStr + obj + '';
preventParentArrayPartiallyBreak = true;
}
return {
str: str,
hasLineBreak: hasLineBreak,
isMethodShorthand: isMethodShorthand,
preventParentArrayPartiallyBreak: preventParentArrayPartiallyBreak
};
}
/**
* Simple implementation for detecting method shorthand, such as,
* ({abc() { return 1; }}).abc is a method shorthand and needs to
* be serialized as `{abc() { return 1; }}` rather than `{abc: abc() { return 1; }}`.
* Those cases can be detected:
* ({abc() { console.log('=>'); return 1; }}).abc expected: IS_SHORTHAND
* ({abc(x, y = 5) { return 1; }}).abc expected: IS_SHORTHAND
* ({$ab_c() { return 1; }}).$ab_c expected: IS_SHORTHAND
* ({*abc() { return 1; }}).abc expected: IS_SHORTHAND
* ({* abc() { return 1; }}).abc expected: IS_SHORTHAND
* ({async abc() { return 1; }}).abc expected: IS_SHORTHAND
* ({*abc() { yield 1; }}).abc expected: IS_SHORTHAND
* ({abc(x, y) { return x + y; }}).abc expected: IS_SHORTHAND
* ({abc: function abc() { return 1; }}).abc expected: NOT_SHORTHAND
* ({abc: function def() { return 1; }}).abc expected: NOT_SHORTHAND
* ({abc: function() { return 1; }}).abc expected: NOT_SHORTHAND
* ({abc: function* () { return 1; }}).abc expected: NOT_SHORTHAND
* ({abc: function (aa, bb) { return 1; }}).abc expected: NOT_SHORTHAND
* ({abc: function (aa, bb = 5) { return 1; }}).abc expected: NOT_SHORTHAND
* ({abc: async () => { return 1; }}).abc expected: NOT_SHORTHAND
* ({abc: () => { return 1; }}).abc expected: NOT_SHORTHAND
* ({abc: (aa, bb = 5) => { return 1; }}).abc expected: NOT_SHORTHAND
* FIXME: fail at some rare cases, such as:
* Literal string involved, like:
* ({"ab-() ' =>c"() { return 1; }})["ab-() ' =>c"] expected: IS_SHORTHAND
* ({async "ab-c"() { return 1; }})["ab-c"] expected: IS_SHORTHAND
* Computed property name involved, like:
* ({[some]() { return 1; }})[some] expected: IS_SHORTHAND
*/
function isMethodShorthandNotAccurate(fnStr, fnName, objKey) {
// Assert fnStr, fnName, objKey is a string.
if (fnName !== objKey) {
return false;
}
var matched = fnStr.match(/^\s*(async\s+)?(function\s*)?(\*\s*)?([a-zA-Z$_][a-zA-Z0-9$_]*)?\s*\(/);
if (!matched) {
return false;
}
if (matched[2]) { // match 'function'
return false;
}
// May enhanced by /(['"])(?:(?=(\\?))\2.)*?\1/; to match literal string,
// such as "ab-c", "a\nc". But this simple impl does not cover it.
if (!matched[4] || matched[4] !== objKey) { // match "maybe function name"
return false;
}
return true;
}
};
/**
* Usage:
* ```js
* // Print all elements that has `style.text`:
* var str = testHelper.stringifyElements(chart, {
* attr: ['z', 'z2', 'style.text', 'style.fill', 'style.stroke'],
* filter: el => el.style && el.style.text
* });
* ```
*
* @param {EChart} chart
* @param {Object} [opt]
* @param {string|Array.} [opt.attr] Only print the given attrName;
* For example: 'z2' or ['z2', 'style.fill', 'style.stroke']
* @param {function} [opt.filter] print a subtree only if any satisfied node exists.
* param: el, return: boolean
*/
var stringifyElements = testHelper.stringifyElements = function (chart, opt) {
if (!chart) {
return;
}
opt = opt || {};
var attrNameList = opt.attr;
if (getType(attrNameList) !== 'array') {
attrNameList = attrNameList ? [attrNameList] : [];
}
var zr = chart.getZr();
var roots = zr.storage.getRoots();
var plainRoots = [];
retrieve(roots, plainRoots);
var elsStr = printObject(plainRoots, {indent: 2});
return elsStr;
// Only retrieve the value of the given attrName.
function retrieve(elList, plainNodes) {
var anySatisfied = false;
for (var i = 0; i < elList.length; i++) {
var el = elList[i];
var thisElSatisfied = !opt.filter || opt.filter(el);
var plainNode = {};
copyElment(plainNode, el);
var textContent = el.getTextContent();
if (textContent) {
plainNode.textContent = {};
copyElment(plainNode.textContent, textContent);
}
var thisSubAnySatisfied = false;
if (el.isGroup) {
plainNode.children = [];
thisSubAnySatisfied = retrieve(el.childrenRef(), plainNode.children);
}
if (thisElSatisfied || thisSubAnySatisfied) {
plainNodes.push(plainNode);
anySatisfied = true;
}
}
return anySatisfied;
}
function copyElment(plainNode, el) {
for (var i = 0; i < attrNameList.length; i++) {
var attrName = attrNameList[i];
var attrParts = attrName.split('.');
var partsLen = attrParts.length;
if (!partsLen) {
continue;
}
var elInner = el;
var plainInner = plainNode;
for (var j = 0; j < partsLen - 1 && elInner; j++) {
var attr = attrParts[j];
elInner = el[attr];
if (elInner) {
plainInner = plainInner[attr] || (plainInner[attr] = {});
}
}
var attr = attrParts[partsLen - 1];
if (elInner && elInner.hasOwnProperty(attr)) {
plainInner[attr] = elInner[attr];
}
}
}
};
/**
* Usage:
* ```js
* // Print all elements that has `style.text`:
* testHelper.printElements(chart, {
* attr: ['z', 'z2', 'style.text', 'style.fill', 'style.stroke'],
* filter: el => el.style && el.style.text
* });
* ```
*
* @see `stringifyElements`.
*/
var printElements = testHelper.printElements = function (chart, opt) {
var elsStr = testHelper.stringifyElements(chart, opt);
console.log(elsStr);
};
/**
* Usage:
* ```js
* // Print all elements that has `style.text`:
* testHelper.retrieveElements(chart, {
* filter: el => el.style && el.style.text
* });
* ```
*
* @param {EChart} chart
* @param {Object} [opt]
* @param {function} [opt.filter] print a subtree only if any satisfied node exists.
* param: el, return: boolean
* @return {Array.}
*/
var retrieveElements = testHelper.retrieveElements = function (chart, opt) {
if (!chart) {
return;
}
opt = opt || {};
var attrNameList = opt.attr;
if (getType(attrNameList) !== 'array') {
attrNameList = attrNameList ? [attrNameList] : [];
}
var zr = chart.getZr();
var roots = zr.storage.getRoots();
var result = [];
retrieve(roots);
function retrieve(elList) {
for (var i = 0; i < elList.length; i++) {
var el = elList[i];
if (!opt.filter || opt.filter(el)) {
result.push(el);
}
if (el.isGroup) {
retrieve(el.childrenRef());
}
}
}
return result;
};
// opt: {record: JSON, width: number, height: number}
testHelper.reproduceCanteen = function (opt) {
var canvas = document.createElement('canvas');
canvas.style.width = opt.width + 'px';
canvas.style.height = opt.height + 'px';
var dpr = Math.max(window.devicePixelRatio || 1, 1);
canvas.width = opt.width * dpr;
canvas.height = opt.height * dpr;
var ctx = canvas.getContext('2d');
var record = opt.record;
for (var i = 0; i < record.length; i++) {
var line = record[i];
if (line.attr) {
if (!line.hasOwnProperty('val')) {
alertIllegal(line);
}
ctx[line.attr] = line.val;
}
else if (line.method) {
if (!line.hasOwnProperty('arguments')) {
alertIllegal(line);
}
ctx[line.method].apply(ctx, line.arguments);
}
else {
alertIllegal(line);
}
}
function alertIllegal(line) {
throw new Error('Illegal line: ' + JSON.stringify(line));
}
document.body.appendChild(canvas);
};
function initDataTables(opt, dataTableContainer) {
var dataTables = opt.dataTables;
if (!dataTables && opt.dataTable) {
dataTables = [opt.dataTable];
}
if (dataTables) {
var tableHTML = [];
for (var i = 0; i < dataTables.length; i++) {
tableHTML.push(createDataTableHTML(dataTables[i], opt));
}
dataTableContainer.innerHTML = tableHTML.join('');
}
}
function createDataTableHTML(data, opt) {
var sourceFormat = detectSourceFormat(data);
var dataTableLimit = opt.dataTableLimit || DEFAULT_DATA_TABLE_LIMIT;
if (!sourceFormat) {
return '';
}
var html = [''];
if (sourceFormat === 'arrayRows') {
for (var i = 0; i < data.length && i <= dataTableLimit; i++) {
var line = data[i];
var htmlLine = [''];
for (var j = 0; j < line.length; j++) {
var val = i === dataTableLimit ? '...' : line[j];
htmlLine.push('| ' + encodeHTML(val) + ' | ');
}
htmlLine.push('
');
html.push(htmlLine.join(''));
}
}
else if (sourceFormat === 'objectRows') {
for (var i = 0; i < data.length && i <= dataTableLimit; i++) {
var line = data[i];
var htmlLine = [''];
for (var key in line) {
if (line.hasOwnProperty(key)) {
var keyText = i === dataTableLimit ? '...' : key;
htmlLine.push('| ' + encodeHTML(keyText) + ' | ');
var val = i === dataTableLimit ? '...' : line[key];
htmlLine.push('' + encodeHTML(val) + ' | ');
}
}
htmlLine.push('
');
html.push(htmlLine.join(''));
}
}
else if (sourceFormat === 'keyedColumns') {
for (var key in data) {
var htmlLine = [''];
htmlLine.push('| ' + encodeHTML(key) + ' | ');
if (data.hasOwnProperty(key)) {
var col = data[key] || [];
for (var i = 0; i < col.length && i <= dataTableLimit; i++) {
var val = i === dataTableLimit ? '...' : col[i];
htmlLine.push('' + encodeHTML(val) + ' | ');
}
}
htmlLine.push('
');
html.push(htmlLine.join(''));
}
}
html.push('
');
return html.join('');
}
function detectSourceFormat(data) {
if (data.length) {
for (var i = 0, len = data.length; i < len; i++) {
var item = data[i];
if (item == null) {
continue;
}
else if (item.length) {
return 'arrayRows';
}
else if (typeof data === 'object') {
return 'objectRows';
}
}
}
else if (typeof data === 'object') {
return 'keyedColumns';
}
}
function createObjectHTML(obj, key) {
var html = isObject(obj)
? encodeHTML(printObject(obj, key))
: obj
? obj.toString()
: '';
return [
'',
html,
''
].join('');
}
var getDom = testHelper.getDom = function (domOrId) {
return getType(domOrId) === 'string' ? document.getElementById(domOrId) : domOrId;
}
// resolves . and .. elements in a path array with directory names there
// must be no slashes or device names (c:\) in the array
// (so also no leading and trailing slashes - it does not distinguish
// relative and absolute paths)
function normalizePathArray(parts, allowAboveRoot) {
var res = [];
for (var i = 0; i < parts.length; i++) {
var p = parts[i];
// ignore empty parts
if (!p || p === '.') {
continue;
}
if (p === '..') {
if (res.length && res[res.length - 1] !== '..') {
res.pop();
} else if (allowAboveRoot) {
res.push('..');
}
} else {
res.push(p);
}
}
return res;
}
function getParamListFromURL() {
var params = location.search.replace('?', '');
return params ? params.split('&') : [];
}
function isObject(value) {
// Avoid a V8 JIT bug in Chrome 19-20.
// See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
var type = typeof value;
return type === 'function' || (!!value && type === 'object');
}
function arrayIndexOf(arr, value) {
if (arr.indexOf) {
return arr.indexOf(value);
}
for (var i = 0; i < arr.length; i++) {
if (arr[i] === value) {
return i;
}
}
return -1;
}
var assert = testHelper.assert = function (cond, msg) {
if (!cond) {
throw new Error(msg || 'Assertion failed.');
}
}
function makeFlexibleNames(dashedNames) {
var nameMap = {};
for (var i = 0; i < dashedNames.length; i++) {
var name = dashedNames[i];
var tmpNames = [];
tmpNames.push(name);
tmpNames.push(name.replace(/-/g, ''));
tmpNames.push(name.replace(/-/g, '_'));
tmpNames.push(name.replace(/-([a-zA-Z0-9])/g, function (_, wf) {
return wf.toUpperCase();
}));
for (var j = 0; j < tmpNames.length; j++) {
nameMap[tmpNames[j]] = 1;
nameMap[tmpNames[j].toUpperCase()] = 1;
nameMap[tmpNames[j].toLowerCase()] = 1;
}
}
var names = [];
for (var name in nameMap) {
if (nameMap.hasOwnProperty(name)) {
names.push(name);
}
}
return names;
}
/**
* Copied from src/util/number.ts
*/
function getPrecision(val) {
val = +val;
if (isNaN(val)) {
return 0;
}
// It is much faster than methods converting number to string as follows
// let tmp = val.toString();
// return tmp.length - 1 - tmp.indexOf('.');
// especially when precision is low
// Notice:
// (1) If the loop count is over about 20, it is slower than `getPrecisionSafe`.
// (see https://jsbench.me/2vkpcekkvw/1)
// (2) If the val is less than for example 1e-15, the result may be incorrect.
// (see test/ut/spec/util/number.test.ts `getPrecision_equal_random`)
if (val > 1e-14) {
var e = 1;
for (var i = 0; i < 15; i++, e *= 10) {
if (Math.round(val * e) / e === val) {
return i;
}
}
}
return getPrecisionSafe(val);
}
/**
* Copied from src/util/number.ts
* Get precision with slow but safe method
*/
function getPrecisionSafe(val) {
// toLowerCase for: '3.4E-12'
var str = val.toString().toLowerCase();
// Consider scientific notation: '3.4e-12' '3.4e+12'
var eIndex = str.indexOf('e');
var exp = eIndex > 0 ? +str.slice(eIndex + 1) : 0;
var significandPartLen = eIndex > 0 ? eIndex : str.length;
var dotIndex = str.indexOf('.');
var decimalPartLen = dotIndex < 0 ? 0 : significandPartLen - 1 - dotIndex;
return Math.max(0, decimalPartLen - exp);
}
/**
* Copied from src/util/number.ts
*/
function round(x, precision, returnStr) {
if (precision == null) {
precision = 10;
}
// Avoid range error
precision = Math.min(Math.max(0, precision), ROUND_SUPPORTED_PRECISION_MAX);
// PENDING: 1.005.toFixed(2) is '1.00' rather than '1.01'
x = (+x).toFixed(precision);
return (returnStr ? x : +x);
}
// Although chrome already enlarge this number to 100 for `toFixed`, but
// we sill follow the spec for compatibility.
var ROUND_SUPPORTED_PRECISION_MAX = 20;
function objectNoOtherNotNullUndefinedPropExcept(obj, exceptProps) {
if (!obj) {
return false;
}
for (var key in obj) {
if (obj.hasOwnProperty(key) && arrayIndexOf(exceptProps, key) < 0 && obj[key] != null) {
return false;
}
}
return true;
}
var copyToClipboard = function (text) {
if (typeof navigator === 'undefined' || !navigator.clipboard || !navigator.clipboard.writeText) {
console.error('[clipboard] Can not copy to clipboard.');
return;
}
return navigator.clipboard.writeText(text).then(function () {
console.log('[clipboard] Text copied to clipboard.');
}).catch(function (err) {
console.error('[clipboard] Failed to copy text: ', err); // Just print for easy to use.
return err;
});
};
/**
* A shortcut for both stringify and copy to clipboard.
*
* @param {any} val Any val to stringify and copy to clipboard.
* @param {Object?} printObjectOpt Optional.
*/
testHelper.clipboard = function (val, printObjectOpt) {
var literal = testHelper.printObject(val, printObjectOpt);
if (document.hasFocus()) {
copyToClipboard(literal);
}
else {
// Handle the error:
// NotAllowedError: Failed to execute 'writeText' on 'Clipboard': Document is not focused.
ensureClipboardButton();
updateClipboardButton(literal)
console.log(
'⚠️ [clipboard] Please click the new button that appears on the top-left corner of the screen'
+ ' to copy to clipboard.'
);
}
function updateClipboardButton(text) {
var button = __tmpClipboardButttonWrapper.button;
button.innerHTML = 'Click me to copy to clipboard';
button.style.display = 'block';
__tmpClipboardButttonWrapper.text = text;
}
function ensureClipboardButton() {
var button = __tmpClipboardButttonWrapper.button;
if (button != null) {
return;
}
__tmpClipboardButttonWrapper.button = button = document.createElement('div');
button.style.cssText = [
'height: 80px;',
'line-height: 80px;',
'padding: 10px 20px;',
'margin: 5px;',
'text-align: center;',
'position: fixed;',
'top: 10px;',
'left: 10px;',
'z-index: 9999;',
'cursor: pointer;',
'color: #fff;',
'background-color: #333;',
'border: 2px solid #eee;',
'border-radius: 5px;',
'font-size: 18px;',
'font-weight: bold;',
'font-family: sans-serif;',
'box-shadow: 0 4px 10px rgba(0, 0, 0, 0.8);'
].join('');
document.body.appendChild(button);
button.addEventListener('click', function () {
copyToClipboard(__tmpClipboardButttonWrapper.text).then(function (err) {
if (!err) {
button.style.display = 'none';
}
else {
button.innerHTML = 'error, see console log.';
}
});
});
}
// Do not return the text, because it may be too long for a console.log.
};
var __tmpClipboardButttonWrapper = {};
// It may be changed by test case changing. Do not use it as a persistent id.
var _idBase = 1;
function generateNonPersistentId(prefix) {
return (prefix || '') + '' + (_idBase++);
}
function VideoRecorder(chart) {
this.start = startRecording;
this.stop = stopRecording;
var recorder = null;
var oldRefreshImmediately = chart.getZr().refreshImmediately;
function startRecording() {
// Normal resolution or high resolution?
var compositeCanvas = document.createElement('canvas');
var width = chart.getWidth();
var height = chart.getHeight();
compositeCanvas.width = width;
compositeCanvas.height = height;
var compositeCtx = compositeCanvas.getContext('2d');
chart.getZr().refreshImmediately = function () {
var ret = oldRefreshImmediately.apply(this, arguments);
var canvasList = chart.getDom().querySelectorAll('canvas');
compositeCtx.fillStyle = '#fff';
compositeCtx.fillRect(0, 0, width, height);
for (var i = 0; i < canvasList.length; i++) {
compositeCtx.drawImage(canvasList[i], 0, 0, width, height);
}
return ret;
}
var stream = compositeCanvas.captureStream(25);
recorder = new MediaRecorder(stream, { mimeType: 'video/webm' });
var videoData = [];
recorder.ondataavailable = function (event) {
if (event.data && event.data.size) {
videoData.push(event.data);
}
};
recorder.onstop = function () {
var url = URL.createObjectURL(new Blob(videoData, { type: 'video/webm' }));
var a = document.createElement('a');
a.href = url;
a.download = 'recording.webm';
a.click();
setTimeout(function () {
window.URL.revokeObjectURL(url);
}, 100);
};
recorder.start();
}
function stopRecording() {
if (recorder) {
chart.getZr().refreshImmediately = oldRefreshImmediately;
recorder.stop();
}
}
}
/**
* Print layer info to chart internal zrender. (For visual testing).
*/
// testHelper.printZRCanvasPainterLayersInfoForTest = function (echarts, chart) {
// var zr = chart.getZr();
// start();
// function retrieveLayerInfo() {
// // CAVEAT: Accessing internal data structure, may change.
// // See `CanvasPainterInternal`
// var painter = zr.painter;
// var painterInternal = painter._i;
// var layerStackInfo = echarts.util.clone(painterInternal.layerStack);
// // Respect to the original content of `painterInternal.layers`.
// var layersMapInfo = {};
// for (var ii in painterInternal.layers) {
// if (painterInternal.layers.hasOwnProperty(ii)) {
// layersMapInfo[ii] = {};
// var layersPerZLevel2 = painterInternal.layers[ii];
// for (var jj in layersPerZLevel2) {
// if (layersPerZLevel2.hasOwnProperty(jj)) {
// var layer = layersPerZLevel2[jj];
// // CAVEAT: Accessing internal data structure, may change.
// var cursorStack = layer.__cursorStack;
// var cursors = layer.__cursors;
// var cursorsInfo = {};
// cursors.each(function (cursor, key) {
// cursorsInfo[key] = echarts.util.clone(cursor);
// });
// layersMapInfo[ii][jj] = {
// layerId: layer.id,
// cursorStack: echarts.util.clone(cursorStack),
// cursors: cursorsInfo
// };
// }
// }
// }
// }
// var hoverLayerInfo = painterInternal.hoverLayer
// ? {layerId: painterInternal.hoverLayer.id}
// : null;
// return {
// painterInternal: painterInternal,
// layersMapInfo: layersMapInfo,
// hoverLayerInfo: hoverLayerInfo,
// layerStackInfo: layerStackInfo,
// };
// }
// function ensureResultDOM() {
// var hostDOMEl = chart.getDom();
// var resultDom = chart.__zrLayerInfo;
// if (!resultDom) {
// resultDom = chart.__zrLayerInfo = document.createElement('pre');
// hostDOMEl.appendChild(resultDom);
// resultDom.style.cssText = [
// 'position: absolute;',
// 'top: 10px;',
// 'left: 5px;',
// 'pointer-events: none;',
// 'font-size: 9px;',
// 'z-index: 9999;',
// 'color: #000;',
// 'width: 220px;',
// 'line-height: 1;',
// 'font-family: Arial;',
// 'background-color: rgba(255,255,255,0.5);',
// 'border: 1px solid #000;',
// 'padding: 2px;'
// ].join('');
// }
// return resultDom;
// }
// function print1(allInfo, resultDom) {
// console.log(allInfo.painterInternal); // For debug
// }
// function print2(allInfo, resultDom) {
// var htmlArr = [
// 'chart.on("finished")
',
// 'layerStack: ' + testHelper.printObject(allInfo.layerStackInfo) + '
',
// 'layersMap: ' + testHelper.printObject(allInfo.layersMapInfo)
// ];
// // console.log(allInfo.layersMapInfo);
// resultDom.innerHTML = htmlArr.join('');
// }
// function start() {
// var resultDom = ensureResultDOM();
// var lastRendered = [];
// console.log('You can visit window.__info');
// chart.on('rendered', function () {
// var allInfo = retrieveLayerInfo();
// // print1(allInfo, resultDom);
// lastRendered.push({
// layerStackInfo: allInfo.layerStackInfo,
// hoverLayerInfo: allInfo.hoverLayerInfo,
// layersMapInfo: allInfo.layersMapInfo
// });
// // console.log('rendered');
// });
// chart.on('finished', function () {
// // console.log('chart.on("finished")');
// var allInfo = retrieveLayerInfo();
// print2(allInfo, resultDom);
// // console.log(lastRendered);
// // console.log(allInfo.painterInternal);
// window.__info = lastRendered.slice();
// lastRendered.length = 0;
// });
// }
// };
testHelper.getCanvasLayerDom = function (chart, zlevel, zlevel2) {
var zr = chart.getZr();
var painter = zr.painter;
if (painter.type !== 'canvas') {
return;
}
// CAVEAT: Accessing internal data structure, may change.
// See `CanvasPainterInternal`
var painterInternal = painter._i;
var layersPerZLevel = painterInternal.layers[zlevel];
var layer = layersPerZLevel && layersPerZLevel[zlevel2];
return layer ? layer.dom : null;
};
context.testHelper = testHelper;
})(window);