| /* |
| * 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. |
| */ |
| import * as echarts from '../../../echarts'; |
| import * as zrUtil from 'zrender/src/core/util'; |
| import * as eventTool from 'zrender/src/core/event'; |
| import lang from '../../../lang'; |
| import * as featureManager from '../featureManager'; |
| var dataViewLang = lang.toolbox.dataView; |
| var BLOCK_SPLITER = new Array(60).join('-'); |
| var ITEM_SPLITER = '\t'; |
| /** |
| * Group series into two types |
| * 1. on category axis, like line, bar |
| * 2. others, like scatter, pie |
| * @param {module:echarts/model/Global} ecModel |
| * @return {Object} |
| * @inner |
| */ |
| |
| function groupSeries(ecModel) { |
| var seriesGroupByCategoryAxis = {}; |
| var otherSeries = []; |
| var meta = []; |
| ecModel.eachRawSeries(function (seriesModel) { |
| var coordSys = seriesModel.coordinateSystem; |
| |
| if (coordSys && (coordSys.type === 'cartesian2d' || coordSys.type === 'polar')) { |
| var baseAxis = coordSys.getBaseAxis(); |
| |
| if (baseAxis.type === 'category') { |
| var key = baseAxis.dim + '_' + baseAxis.index; |
| |
| if (!seriesGroupByCategoryAxis[key]) { |
| seriesGroupByCategoryAxis[key] = { |
| categoryAxis: baseAxis, |
| valueAxis: coordSys.getOtherAxis(baseAxis), |
| series: [] |
| }; |
| meta.push({ |
| axisDim: baseAxis.dim, |
| axisIndex: baseAxis.index |
| }); |
| } |
| |
| seriesGroupByCategoryAxis[key].series.push(seriesModel); |
| } else { |
| otherSeries.push(seriesModel); |
| } |
| } else { |
| otherSeries.push(seriesModel); |
| } |
| }); |
| return { |
| seriesGroupByCategoryAxis: seriesGroupByCategoryAxis, |
| other: otherSeries, |
| meta: meta |
| }; |
| } |
| /** |
| * Assemble content of series on cateogory axis |
| * @param {Array.<module:echarts/model/Series>} series |
| * @return {string} |
| * @inner |
| */ |
| |
| |
| function assembleSeriesWithCategoryAxis(series) { |
| var tables = []; |
| zrUtil.each(series, function (group, key) { |
| var categoryAxis = group.categoryAxis; |
| var valueAxis = group.valueAxis; |
| var valueAxisDim = valueAxis.dim; |
| var headers = [' '].concat(zrUtil.map(group.series, function (series) { |
| return series.name; |
| })); |
| var columns = [categoryAxis.model.getCategories()]; |
| zrUtil.each(group.series, function (series) { |
| var rawData = series.getRawData(); |
| columns.push(series.getRawData().mapArray(rawData.mapDimension(valueAxisDim), function (val) { |
| return val; |
| })); |
| }); // Assemble table content |
| |
| var lines = [headers.join(ITEM_SPLITER)]; |
| |
| for (var i = 0; i < columns[0].length; i++) { |
| var items = []; |
| |
| for (var j = 0; j < columns.length; j++) { |
| items.push(columns[j][i]); |
| } |
| |
| lines.push(items.join(ITEM_SPLITER)); |
| } |
| |
| tables.push(lines.join('\n')); |
| }); |
| return tables.join('\n\n' + BLOCK_SPLITER + '\n\n'); |
| } |
| /** |
| * Assemble content of other series |
| * @param {Array.<module:echarts/model/Series>} series |
| * @return {string} |
| * @inner |
| */ |
| |
| |
| function assembleOtherSeries(series) { |
| return zrUtil.map(series, function (series) { |
| var data = series.getRawData(); |
| var lines = [series.name]; |
| var vals = []; |
| data.each(data.dimensions, function () { |
| var argLen = arguments.length; |
| var dataIndex = arguments[argLen - 1]; |
| var name = data.getName(dataIndex); |
| |
| for (var i = 0; i < argLen - 1; i++) { |
| vals[i] = arguments[i]; |
| } |
| |
| lines.push((name ? name + ITEM_SPLITER : '') + vals.join(ITEM_SPLITER)); |
| }); |
| return lines.join('\n'); |
| }).join('\n\n' + BLOCK_SPLITER + '\n\n'); |
| } |
| /** |
| * @param {module:echarts/model/Global} |
| * @return {Object} |
| * @inner |
| */ |
| |
| |
| function getContentFromModel(ecModel) { |
| var result = groupSeries(ecModel); |
| return { |
| value: zrUtil.filter([assembleSeriesWithCategoryAxis(result.seriesGroupByCategoryAxis), assembleOtherSeries(result.other)], function (str) { |
| return str.replace(/[\n\t\s]/g, ''); |
| }).join('\n\n' + BLOCK_SPLITER + '\n\n'), |
| meta: result.meta |
| }; |
| } |
| |
| function trim(str) { |
| return str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); |
| } |
| /** |
| * If a block is tsv format |
| */ |
| |
| |
| function isTSVFormat(block) { |
| // Simple method to find out if a block is tsv format |
| var firstLine = block.slice(0, block.indexOf('\n')); |
| |
| if (firstLine.indexOf(ITEM_SPLITER) >= 0) { |
| return true; |
| } |
| } |
| |
| var itemSplitRegex = new RegExp('[' + ITEM_SPLITER + ']+', 'g'); |
| /** |
| * @param {string} tsv |
| * @return {Object} |
| */ |
| |
| function parseTSVContents(tsv) { |
| var tsvLines = tsv.split(/\n+/g); |
| var headers = trim(tsvLines.shift()).split(itemSplitRegex); |
| var categories = []; |
| var series = zrUtil.map(headers, function (header) { |
| return { |
| name: header, |
| data: [] |
| }; |
| }); |
| |
| for (var i = 0; i < tsvLines.length; i++) { |
| var items = trim(tsvLines[i]).split(itemSplitRegex); |
| categories.push(items.shift()); |
| |
| for (var j = 0; j < items.length; j++) { |
| series[j] && (series[j].data[i] = items[j]); |
| } |
| } |
| |
| return { |
| series: series, |
| categories: categories |
| }; |
| } |
| /** |
| * @param {string} str |
| * @return {Array.<Object>} |
| * @inner |
| */ |
| |
| |
| function parseListContents(str) { |
| var lines = str.split(/\n+/g); |
| var seriesName = trim(lines.shift()); |
| var data = []; |
| |
| for (var i = 0; i < lines.length; i++) { |
| // if line is empty, ignore it. |
| // there is a case that a user forgot to delete `\n`. |
| var line = trim(lines[i]); |
| |
| if (!line) { |
| continue; |
| } |
| |
| var items = line.split(itemSplitRegex); |
| var name = ''; |
| var value; |
| var hasName = false; |
| |
| if (isNaN(items[0])) { |
| // First item is name |
| hasName = true; |
| name = items[0]; |
| items = items.slice(1); |
| data[i] = { |
| name: name, |
| value: [] |
| }; |
| value = data[i].value; |
| } else { |
| value = data[i] = []; |
| } |
| |
| for (var j = 0; j < items.length; j++) { |
| value.push(+items[j]); |
| } |
| |
| if (value.length === 1) { |
| hasName ? data[i].value = value[0] : data[i] = value[0]; |
| } |
| } |
| |
| return { |
| name: seriesName, |
| data: data |
| }; |
| } |
| /** |
| * @param {string} str |
| * @param {Array.<Object>} blockMetaList |
| * @return {Object} |
| * @inner |
| */ |
| |
| |
| function parseContents(str, blockMetaList) { |
| var blocks = str.split(new RegExp('\n*' + BLOCK_SPLITER + '\n*', 'g')); |
| var newOption = { |
| series: [] |
| }; |
| zrUtil.each(blocks, function (block, idx) { |
| if (isTSVFormat(block)) { |
| var result = parseTSVContents(block); |
| var blockMeta = blockMetaList[idx]; |
| var axisKey = blockMeta.axisDim + 'Axis'; |
| |
| if (blockMeta) { |
| newOption[axisKey] = newOption[axisKey] || []; |
| newOption[axisKey][blockMeta.axisIndex] = { |
| data: result.categories |
| }; |
| newOption.series = newOption.series.concat(result.series); |
| } |
| } else { |
| var result = parseListContents(block); |
| newOption.series.push(result); |
| } |
| }); |
| return newOption; |
| } |
| /** |
| * @alias {module:echarts/component/toolbox/feature/DataView} |
| * @constructor |
| * @param {module:echarts/model/Model} model |
| */ |
| |
| |
| function DataView(model) { |
| this._dom = null; |
| this.model = model; |
| } |
| |
| DataView.defaultOption = { |
| show: true, |
| readOnly: false, |
| optionToContent: null, |
| contentToOption: null, |
| icon: 'M17.5,17.3H33 M17.5,17.3H33 M45.4,29.5h-28 M11.5,2v56H51V14.8L38.4,2H11.5z M38.4,2.2v12.7H51 M45.4,41.7h-28', |
| title: zrUtil.clone(dataViewLang.title), |
| lang: zrUtil.clone(dataViewLang.lang), |
| backgroundColor: '#fff', |
| textColor: '#000', |
| textareaColor: '#fff', |
| textareaBorderColor: '#333', |
| buttonColor: '#c23531', |
| buttonTextColor: '#fff' |
| }; |
| |
| DataView.prototype.onclick = function (ecModel, api) { |
| var container = api.getDom(); |
| var model = this.model; |
| |
| if (this._dom) { |
| container.removeChild(this._dom); |
| } |
| |
| var root = document.createElement('div'); |
| root.style.cssText = 'position:absolute;left:5px;top:5px;bottom:5px;right:5px;'; |
| root.style.backgroundColor = model.get('backgroundColor') || '#fff'; // Create elements |
| |
| var header = document.createElement('h4'); |
| var lang = model.get('lang') || []; |
| header.innerHTML = lang[0] || model.get('title'); |
| header.style.cssText = 'margin: 10px 20px;'; |
| header.style.color = model.get('textColor'); |
| var viewMain = document.createElement('div'); |
| var textarea = document.createElement('textarea'); |
| viewMain.style.cssText = 'display:block;width:100%;overflow:auto;'; |
| var optionToContent = model.get('optionToContent'); |
| var contentToOption = model.get('contentToOption'); |
| var result = getContentFromModel(ecModel); |
| |
| if (typeof optionToContent === 'function') { |
| var htmlOrDom = optionToContent(api.getOption()); |
| |
| if (typeof htmlOrDom === 'string') { |
| viewMain.innerHTML = htmlOrDom; |
| } else if (zrUtil.isDom(htmlOrDom)) { |
| viewMain.appendChild(htmlOrDom); |
| } |
| } else { |
| // Use default textarea |
| viewMain.appendChild(textarea); |
| textarea.readOnly = model.get('readOnly'); |
| textarea.style.cssText = 'width:100%;height:100%;font-family:monospace;font-size:14px;line-height:1.6rem;'; |
| textarea.style.color = model.get('textColor'); |
| textarea.style.borderColor = model.get('textareaBorderColor'); |
| textarea.style.backgroundColor = model.get('textareaColor'); |
| textarea.value = result.value; |
| } |
| |
| var blockMetaList = result.meta; |
| var buttonContainer = document.createElement('div'); |
| buttonContainer.style.cssText = 'position:absolute;bottom:0;left:0;right:0;'; |
| var buttonStyle = 'float:right;margin-right:20px;border:none;' + 'cursor:pointer;padding:2px 5px;font-size:12px;border-radius:3px'; |
| var closeButton = document.createElement('div'); |
| var refreshButton = document.createElement('div'); |
| buttonStyle += ';background-color:' + model.get('buttonColor'); |
| buttonStyle += ';color:' + model.get('buttonTextColor'); |
| var self = this; |
| |
| function close() { |
| container.removeChild(root); |
| self._dom = null; |
| } |
| |
| eventTool.addEventListener(closeButton, 'click', close); |
| eventTool.addEventListener(refreshButton, 'click', function () { |
| var newOption; |
| |
| try { |
| if (typeof contentToOption === 'function') { |
| newOption = contentToOption(viewMain, api.getOption()); |
| } else { |
| newOption = parseContents(textarea.value, blockMetaList); |
| } |
| } catch (e) { |
| close(); |
| throw new Error('Data view format error ' + e); |
| } |
| |
| if (newOption) { |
| api.dispatchAction({ |
| type: 'changeDataView', |
| newOption: newOption |
| }); |
| } |
| |
| close(); |
| }); |
| closeButton.innerHTML = lang[1]; |
| refreshButton.innerHTML = lang[2]; |
| refreshButton.style.cssText = buttonStyle; |
| closeButton.style.cssText = buttonStyle; |
| !model.get('readOnly') && buttonContainer.appendChild(refreshButton); |
| buttonContainer.appendChild(closeButton); |
| root.appendChild(header); |
| root.appendChild(viewMain); |
| root.appendChild(buttonContainer); |
| viewMain.style.height = container.clientHeight - 80 + 'px'; |
| container.appendChild(root); |
| this._dom = root; |
| }; |
| |
| DataView.prototype.remove = function (ecModel, api) { |
| this._dom && api.getDom().removeChild(this._dom); |
| }; |
| |
| DataView.prototype.dispose = function (ecModel, api) { |
| this.remove(ecModel, api); |
| }; |
| /** |
| * @inner |
| */ |
| |
| |
| function tryMergeDataOption(newData, originalData) { |
| return zrUtil.map(newData, function (newVal, idx) { |
| var original = originalData && originalData[idx]; |
| |
| if (zrUtil.isObject(original) && !zrUtil.isArray(original)) { |
| var newValIsObject = zrUtil.isObject(newVal) && !zrUtil.isArray(newVal); |
| |
| if (!newValIsObject) { |
| newVal = { |
| value: newVal |
| }; |
| } // original data has name but new data has no name |
| |
| |
| var shouldDeleteName = original.name != null && newVal.name == null; // Original data has option |
| |
| newVal = zrUtil.defaults(newVal, original); |
| shouldDeleteName && delete newVal.name; |
| return newVal; |
| } else { |
| return newVal; |
| } |
| }); |
| } |
| |
| featureManager.register('dataView', DataView); |
| echarts.registerAction({ |
| type: 'changeDataView', |
| event: 'dataViewChanged', |
| update: 'prepareAndUpdate' |
| }, function (payload, ecModel) { |
| var newSeriesOptList = []; |
| zrUtil.each(payload.newOption.series, function (seriesOpt) { |
| var seriesModel = ecModel.getSeriesByName(seriesOpt.name)[0]; |
| |
| if (!seriesModel) { |
| // New created series |
| // Geuss the series type |
| newSeriesOptList.push(zrUtil.extend({ |
| // Default is scatter |
| type: 'scatter' |
| }, seriesOpt)); |
| } else { |
| var originalData = seriesModel.get('data'); |
| newSeriesOptList.push({ |
| name: seriesOpt.name, |
| data: tryMergeDataOption(seriesOpt.data, originalData) |
| }); |
| } |
| }); |
| ecModel.mergeOption(zrUtil.defaults({ |
| series: newSeriesOptList |
| }, payload.newOption)); |
| }); |
| export default DataView; |