1118 lines
37 KiB
JavaScript
Generated
1118 lines
37 KiB
JavaScript
Generated
|
|
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
(function (context) {
|
|
|
|
var DEFAULT_DATA_TABLE_LIMIT = 8;
|
|
|
|
var objToString = Object.prototype.toString;
|
|
var TYPED_ARRAY = {
|
|
'[object Int8Array]': 1,
|
|
'[object Uint8Array]': 1,
|
|
'[object Uint8ClampedArray]': 1,
|
|
'[object Int16Array]': 1,
|
|
'[object Uint16Array]': 1,
|
|
'[object Int32Array]': 1,
|
|
'[object Uint32Array]': 1,
|
|
'[object Float32Array]': 1,
|
|
'[object Float64Array]': 1
|
|
};
|
|
|
|
var params = {};
|
|
var parts = location.search.slice(1).split('&');
|
|
for (var i = 0; i < parts.length; ++i) {
|
|
var kv = parts[i].split('=');
|
|
params[kv[0]] = kv[1];
|
|
}
|
|
|
|
if ('__SEED_RANDOM__' in params) {
|
|
require(['../node_modules/seedrandom/seedrandom.js'], function (seedrandom) {
|
|
var myRandom = new seedrandom('echarts-random');
|
|
// Fixed random generator
|
|
Math.random = function () {
|
|
const val = myRandom();
|
|
return val;
|
|
};
|
|
});
|
|
}
|
|
|
|
var testHelper = {};
|
|
|
|
|
|
/**
|
|
* @param {Object} opt
|
|
* @param {string|Array.<string>} [opt.title] If array, each item is on a single line.
|
|
* Can use '**abc**', means <strong>abc</strong>.
|
|
* @param {Option} opt.option
|
|
* @param {Object} [opt.info] info object to display.
|
|
* @param {string} [opt.infoKey='option']
|
|
* @param {Object|Array} [opt.dataTable]
|
|
* @param {Array.<Object|Array>} [opt.dataTables] Multiple dataTables.
|
|
* @param {number} [opt.dataTableLimit=DEFAULT_DATA_TABLE_LIMIT]
|
|
* @param {number} [opt.width]
|
|
* @param {number} [opt.height]
|
|
* @param {boolean} [opt.draggable]
|
|
* @param {boolean} [opt.lazyUpdate]
|
|
* @param {boolean} [opt.notMerge]
|
|
* @param {boolean} [opt.autoResize=true]
|
|
* @param {Array.<Object>|Object} [opt.button] {text: ..., onClick: ...}, or an array of them.
|
|
* @param {Array.<Object>|Object} [opt.buttons] {text: ..., onClick: ...}, or an array of them.
|
|
* @param {boolean} [opt.recordCanvas] 'test/lib/canteen.js' is required.
|
|
* @param {boolean} [opt.recordVideo]
|
|
* @param {string} [opt.renderer] 'canvas' or 'svg'
|
|
*/
|
|
testHelper.create = function (echarts, domOrId, opt) {
|
|
var dom = getDom(domOrId);
|
|
|
|
if (!dom) {
|
|
return;
|
|
}
|
|
|
|
var title = document.createElement('div');
|
|
var left = document.createElement('div');
|
|
var chartContainer = document.createElement('div');
|
|
var buttonsContainer = document.createElement('div');
|
|
var dataTableContainer = document.createElement('div');
|
|
var infoContainer = document.createElement('div');
|
|
var recordCanvasContainer = document.createElement('div');
|
|
var recordVideoContainer = document.createElement('div');
|
|
|
|
title.setAttribute('title', dom.getAttribute('id'));
|
|
|
|
title.className = 'test-title';
|
|
dom.className = 'test-chart-block';
|
|
left.className = 'test-chart-block-left';
|
|
chartContainer.className = 'test-chart';
|
|
buttonsContainer.className = 'test-buttons';
|
|
dataTableContainer.className = 'test-data-table';
|
|
infoContainer.className = 'test-info';
|
|
recordCanvasContainer.className = 'record-canvas';
|
|
recordVideoContainer.className = 'record-video';
|
|
|
|
if (opt.info) {
|
|
dom.className += ' test-chart-block-has-right';
|
|
infoContainer.className += ' test-chart-block-right';
|
|
}
|
|
|
|
left.appendChild(recordCanvasContainer);
|
|
left.appendChild(recordVideoContainer);
|
|
left.appendChild(buttonsContainer);
|
|
left.appendChild(dataTableContainer);
|
|
left.appendChild(chartContainer);
|
|
dom.appendChild(infoContainer);
|
|
dom.appendChild(left);
|
|
dom.parentNode.insertBefore(title, dom);
|
|
|
|
var chart;
|
|
|
|
var optTitle = opt.title;
|
|
if (optTitle) {
|
|
if (optTitle instanceof Array) {
|
|
optTitle = optTitle.join('\n');
|
|
}
|
|
title.innerHTML = '<div class="test-title-inner">'
|
|
+ testHelper.encodeHTML(optTitle)
|
|
.replace(/\*\*([^*]+?)\*\*/g, '<strong>$1</strong>')
|
|
.replace(/\n/g, '<br>')
|
|
+ '</div>';
|
|
}
|
|
|
|
chart = testHelper.createChart(echarts, chartContainer, opt.option, opt, opt.setOptionOpts);
|
|
|
|
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('');
|
|
}
|
|
|
|
var buttons = opt.buttons || opt.button;
|
|
if (!(buttons instanceof Array)) {
|
|
buttons = buttons ? [buttons] : [];
|
|
}
|
|
if (buttons.length) {
|
|
for (var i = 0; i < buttons.length; i++) {
|
|
var btnDefine = buttons[i];
|
|
if (btnDefine) {
|
|
var btn = document.createElement('button');
|
|
btn.innerHTML = testHelper.encodeHTML(btnDefine.name || btnDefine.text || 'button');
|
|
btn.addEventListener('click', btnDefine.onClick || btnDefine.onclick);
|
|
buttonsContainer.appendChild(btn);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (opt.info) {
|
|
updateInfo(opt.info, opt.infoKey);
|
|
}
|
|
|
|
function updateInfo(info, infoKey) {
|
|
infoContainer.innerHTML = createObjectHTML(info, infoKey || 'option');
|
|
}
|
|
|
|
initRecordCanvas(opt, chart, recordCanvasContainer);
|
|
|
|
if (opt.recordVideo) {
|
|
testHelper.createRecordVideo(chart, recordVideoContainer);
|
|
}
|
|
|
|
chart.__testHelper = {
|
|
updateInfo: updateInfo
|
|
};
|
|
|
|
return chart;
|
|
};
|
|
|
|
function initRecordCanvas(opt, chart, recordCanvasContainer) {
|
|
if (!opt.recordCanvas) {
|
|
return;
|
|
}
|
|
recordCanvasContainer.innerHTML = ''
|
|
+ '<button>Show Canvas Record</button>'
|
|
+ '<button>Clear Canvas Record</button>'
|
|
+ '<div class="content-area"><textarea></textarea><br><button>Close</button></div>';
|
|
var buttons = recordCanvasContainer.getElementsByTagName('button');
|
|
var canvasRecordButton = buttons[0];
|
|
var clearButton = buttons[1];
|
|
var closeButton = buttons[2];
|
|
var recordArea = recordCanvasContainer.getElementsByTagName('textarea')[0];
|
|
var contentAraa = recordArea.parentNode;
|
|
canvasRecordButton.addEventListener('click', function () {
|
|
var content = [];
|
|
eachCtx(function (zlevel, ctx) {
|
|
content.push('\nLayer zlevel: ' + zlevel, '\n\n');
|
|
if (typeof ctx.stack !== 'function') {
|
|
alert('Missing: <script src="test/lib/canteen.js"></script>');
|
|
return;
|
|
}
|
|
var stack = ctx.stack();
|
|
for (var i = 0; i < stack.length; i++) {
|
|
var line = stack[i];
|
|
content.push(JSON.stringify(line), ',\n');
|
|
}
|
|
});
|
|
contentAraa.style.display = 'block';
|
|
recordArea.value = content.join('');
|
|
});
|
|
clearButton.addEventListener('click', function () {
|
|
eachCtx(function (zlevel, ctx) {
|
|
ctx.clear();
|
|
});
|
|
recordArea.value = 'Cleared.';
|
|
});
|
|
closeButton.addEventListener('click', function () {
|
|
contentAraa.style.display = 'none';
|
|
});
|
|
|
|
function eachCtx(cb) {
|
|
var layers = chart.getZr().painter.getLayers();
|
|
for (var zlevel in layers) {
|
|
if (layers.hasOwnProperty(zlevel)) {
|
|
var layer = layers[zlevel];
|
|
var canvas = layer.dom;
|
|
var ctx = canvas.getContext('2d');
|
|
cb(zlevel, ctx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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'
|
|
*/
|
|
testHelper.createChart = function (echarts, domOrId, option, opt) {
|
|
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 chart = echarts.init(dom, null, {
|
|
renderer: opt.renderer,
|
|
useCoarsePointer: opt.useCoarsePointer,
|
|
pointerSize: opt.pointerSize
|
|
});
|
|
|
|
if (opt.draggable) {
|
|
if (!window.draggable) {
|
|
throw new Error(
|
|
'Pleasse add the script in HTML: \n'
|
|
+ '<script src="lib/draggable.js"></script>'
|
|
);
|
|
}
|
|
window.draggable.init(dom, chart, {throttle: 70});
|
|
}
|
|
|
|
option && chart.setOption(option, {
|
|
lazyUpdate: opt.lazyUpdate,
|
|
notMerge: opt.notMerge
|
|
});
|
|
|
|
var isAutoResize = opt.autoResize == null ? true : opt.autoResize;
|
|
if (isAutoResize) {
|
|
testHelper.resizable(chart);
|
|
}
|
|
|
|
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) {
|
|
if (!cond) {
|
|
throw new 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;',
|
|
'font-size: ' + fontSize + 'px;',
|
|
'z-index: ' + (failErr ? 99999 : 88888) + ';',
|
|
'color: ' + (failErr ? 'red' : 'green') + ';',
|
|
].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;
|
|
|
|
/**
|
|
* 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) {
|
|
opt = opt || {};
|
|
var pauseAt = opt.pauseAt;
|
|
pauseAt == null && (pauseAt = 0);
|
|
|
|
var _running = true;
|
|
var _pendingCbList = [];
|
|
var _frameNumber = 0;
|
|
var _mounted = false;
|
|
|
|
function getRunBtnText() {
|
|
return _running ? 'pause' : 'run';
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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() {
|
|
_running = true;
|
|
nextFrame();
|
|
}
|
|
|
|
function pause() {
|
|
_running = false;
|
|
}
|
|
|
|
function nextFrame() {
|
|
opt.onFrame && opt.onFrame(_frameNumber);
|
|
|
|
if (pauseAt != null && _frameNumber === pauseAt) {
|
|
_running = 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++;
|
|
}
|
|
}
|
|
|
|
testHelper.resizable = function (chart) {
|
|
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();
|
|
width = newWidth;
|
|
height = newHeight;
|
|
}
|
|
}
|
|
if (window.attachEvent) {
|
|
// Use builtin resize in IE
|
|
window.attachEvent('onresize', chart.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;
|
|
};
|
|
|
|
testHelper.encodeHTML = function (source) {
|
|
return String(source)
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
};
|
|
|
|
/**
|
|
* @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 type === '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;
|
|
};
|
|
|
|
/**
|
|
* 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 {string} [opt.objectLineBreak=true]
|
|
* @param {string} [opt.arrayLineBreak=false]
|
|
* @param {string} [opt.indent=4]
|
|
* @param {string} [opt.lineBreak='\n']
|
|
* @param {string} [opt.quotationMark='\'']
|
|
*/
|
|
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 != null ? opt.quotationMark : '\'';
|
|
|
|
return doPrint(obj, opt.key, 0).str;
|
|
|
|
function doPrint(obj, key, depth) {
|
|
var codeIndent = (new Array(depth * indent + 1)).join(' ');
|
|
var subCodeIndent = (new Array((depth + 1) * indent + 1)).join(' ');
|
|
var hasLineBreak = false;
|
|
|
|
var preStr = key != null ? (key + ': ' ) : '';
|
|
var str;
|
|
|
|
var objType = getType(obj);
|
|
|
|
switch (objType) {
|
|
case 'function':
|
|
hasLineBreak = true;
|
|
str = preStr + quotationMark + obj + quotationMark;
|
|
break;
|
|
case 'regexp':
|
|
case 'date':
|
|
str = preStr + quotationMark + obj + quotationMark;
|
|
break;
|
|
case 'array':
|
|
case 'typedArray':
|
|
hasLineBreak = opt.arrayLineBreak != null ? opt.arrayLineBreak : false;
|
|
// If no break line in array, print in single line, like [12, 23, 34].
|
|
// else, each item takes a line.
|
|
var childBuilder = [];
|
|
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;
|
|
}
|
|
}
|
|
var tail = hasLineBreak ? lineBreak : '';
|
|
var delimiter = ',' + (hasLineBreak ? (lineBreak + subCodeIndent) : ' ');
|
|
var subPre = hasLineBreak ? subCodeIndent : '';
|
|
var endPre = hasLineBreak ? codeIndent : '';
|
|
str = ''
|
|
+ preStr + '[' + tail
|
|
+ subPre + childBuilder.join(delimiter) + tail
|
|
+ endPre + ']';
|
|
break;
|
|
case 'object':
|
|
hasLineBreak = opt.objectLineBreak != null ? opt.objectLineBreak : true;
|
|
var childBuilder = [];
|
|
for (var i in obj) {
|
|
if (obj.hasOwnProperty(i)) {
|
|
var subResult = doPrint(obj[i], i, depth + 1);
|
|
childBuilder.push(subResult.str);
|
|
if (subResult.hasLineBreak) {
|
|
hasLineBreak = true;
|
|
}
|
|
}
|
|
}
|
|
str = ''
|
|
+ preStr + '{' + (hasLineBreak ? lineBreak : '')
|
|
+ (childBuilder.length
|
|
? (hasLineBreak ? subCodeIndent : '') + childBuilder.join(',' + (hasLineBreak ? lineBreak + subCodeIndent: ' ')) + (hasLineBreak ? lineBreak: '')
|
|
: ''
|
|
)
|
|
+ (hasLineBreak ? codeIndent : '') + '}';
|
|
break;
|
|
case 'boolean':
|
|
case 'number':
|
|
str = preStr + obj + '';
|
|
break;
|
|
case 'string':
|
|
str = JSON.stringify(obj); // escapse \n\r or others.
|
|
str = preStr + quotationMark + str.slice(1, str.length - 1) + quotationMark;
|
|
break;
|
|
default:
|
|
str = preStr + obj + '';
|
|
}
|
|
|
|
return {
|
|
str: str,
|
|
hasLineBreak: hasLineBreak
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 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.<string>} [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
|
|
*/
|
|
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`.
|
|
*/
|
|
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.<Element>}
|
|
*/
|
|
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 createDataTableHTML(data, opt) {
|
|
var sourceFormat = detectSourceFormat(data);
|
|
var dataTableLimit = opt.dataTableLimit || DEFAULT_DATA_TABLE_LIMIT;
|
|
|
|
if (!sourceFormat) {
|
|
return '';
|
|
}
|
|
|
|
var html = ['<table><tbody>'];
|
|
|
|
if (sourceFormat === 'arrayRows') {
|
|
for (var i = 0; i < data.length && i <= dataTableLimit; i++) {
|
|
var line = data[i];
|
|
var htmlLine = ['<tr>'];
|
|
for (var j = 0; j < line.length; j++) {
|
|
var val = i === dataTableLimit ? '...' : line[j];
|
|
htmlLine.push('<td>' + testHelper.encodeHTML(val) + '</td>');
|
|
}
|
|
htmlLine.push('</tr>');
|
|
html.push(htmlLine.join(''));
|
|
}
|
|
}
|
|
else if (sourceFormat === 'objectRows') {
|
|
for (var i = 0; i < data.length && i <= dataTableLimit; i++) {
|
|
var line = data[i];
|
|
var htmlLine = ['<tr>'];
|
|
for (var key in line) {
|
|
if (line.hasOwnProperty(key)) {
|
|
var keyText = i === dataTableLimit ? '...' : key;
|
|
htmlLine.push('<td class="test-data-table-key">' + testHelper.encodeHTML(keyText) + '</td>');
|
|
var val = i === dataTableLimit ? '...' : line[key];
|
|
htmlLine.push('<td>' + testHelper.encodeHTML(val) + '</td>');
|
|
}
|
|
}
|
|
htmlLine.push('</tr>');
|
|
html.push(htmlLine.join(''));
|
|
}
|
|
}
|
|
else if (sourceFormat === 'keyedColumns') {
|
|
for (var key in data) {
|
|
var htmlLine = ['<tr>'];
|
|
htmlLine.push('<td class="test-data-table-key">' + testHelper.encodeHTML(key) + '</td>');
|
|
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('<td>' + testHelper.encodeHTML(val) + '</td>');
|
|
}
|
|
}
|
|
htmlLine.push('</tr>');
|
|
html.push(htmlLine.join(''));
|
|
}
|
|
}
|
|
|
|
html.push('</tbody></table>');
|
|
|
|
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)
|
|
? testHelper.encodeHTML(printObject(obj, key))
|
|
: obj
|
|
? obj.toString()
|
|
: '';
|
|
|
|
return [
|
|
'<pre class="test-print-object">',
|
|
html,
|
|
'</pre>'
|
|
].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 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();
|
|
}
|
|
}
|
|
}
|
|
|
|
context.testHelper = testHelper;
|
|
|
|
})(window); |