| /* | |
| * 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] | |
| */ | |
| 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 | |
| */ | |
| 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, { | |
| 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); |