DataView.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. var echarts = require("../../../echarts");
  20. var zrUtil = require("zrender/lib/core/util");
  21. var eventTool = require("zrender/lib/core/event");
  22. var lang = require("../../../lang");
  23. var featureManager = require("../featureManager");
  24. /*
  25. * Licensed to the Apache Software Foundation (ASF) under one
  26. * or more contributor license agreements. See the NOTICE file
  27. * distributed with this work for additional information
  28. * regarding copyright ownership. The ASF licenses this file
  29. * to you under the Apache License, Version 2.0 (the
  30. * "License"); you may not use this file except in compliance
  31. * with the License. You may obtain a copy of the License at
  32. *
  33. * http://www.apache.org/licenses/LICENSE-2.0
  34. *
  35. * Unless required by applicable law or agreed to in writing,
  36. * software distributed under the License is distributed on an
  37. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  38. * KIND, either express or implied. See the License for the
  39. * specific language governing permissions and limitations
  40. * under the License.
  41. */
  42. var dataViewLang = lang.toolbox.dataView;
  43. var BLOCK_SPLITER = new Array(60).join('-');
  44. var ITEM_SPLITER = '\t';
  45. /**
  46. * Group series into two types
  47. * 1. on category axis, like line, bar
  48. * 2. others, like scatter, pie
  49. * @param {module:echarts/model/Global} ecModel
  50. * @return {Object}
  51. * @inner
  52. */
  53. function groupSeries(ecModel) {
  54. var seriesGroupByCategoryAxis = {};
  55. var otherSeries = [];
  56. var meta = [];
  57. ecModel.eachRawSeries(function (seriesModel) {
  58. var coordSys = seriesModel.coordinateSystem;
  59. if (coordSys && (coordSys.type === 'cartesian2d' || coordSys.type === 'polar')) {
  60. var baseAxis = coordSys.getBaseAxis();
  61. if (baseAxis.type === 'category') {
  62. var key = baseAxis.dim + '_' + baseAxis.index;
  63. if (!seriesGroupByCategoryAxis[key]) {
  64. seriesGroupByCategoryAxis[key] = {
  65. categoryAxis: baseAxis,
  66. valueAxis: coordSys.getOtherAxis(baseAxis),
  67. series: []
  68. };
  69. meta.push({
  70. axisDim: baseAxis.dim,
  71. axisIndex: baseAxis.index
  72. });
  73. }
  74. seriesGroupByCategoryAxis[key].series.push(seriesModel);
  75. } else {
  76. otherSeries.push(seriesModel);
  77. }
  78. } else {
  79. otherSeries.push(seriesModel);
  80. }
  81. });
  82. return {
  83. seriesGroupByCategoryAxis: seriesGroupByCategoryAxis,
  84. other: otherSeries,
  85. meta: meta
  86. };
  87. }
  88. /**
  89. * Assemble content of series on cateogory axis
  90. * @param {Array.<module:echarts/model/Series>} series
  91. * @return {string}
  92. * @inner
  93. */
  94. function assembleSeriesWithCategoryAxis(series) {
  95. var tables = [];
  96. zrUtil.each(series, function (group, key) {
  97. var categoryAxis = group.categoryAxis;
  98. var valueAxis = group.valueAxis;
  99. var valueAxisDim = valueAxis.dim;
  100. var headers = [' '].concat(zrUtil.map(group.series, function (series) {
  101. return series.name;
  102. }));
  103. var columns = [categoryAxis.model.getCategories()];
  104. zrUtil.each(group.series, function (series) {
  105. var rawData = series.getRawData();
  106. columns.push(series.getRawData().mapArray(rawData.mapDimension(valueAxisDim), function (val) {
  107. return val;
  108. }));
  109. }); // Assemble table content
  110. var lines = [headers.join(ITEM_SPLITER)];
  111. for (var i = 0; i < columns[0].length; i++) {
  112. var items = [];
  113. for (var j = 0; j < columns.length; j++) {
  114. items.push(columns[j][i]);
  115. }
  116. lines.push(items.join(ITEM_SPLITER));
  117. }
  118. tables.push(lines.join('\n'));
  119. });
  120. return tables.join('\n\n' + BLOCK_SPLITER + '\n\n');
  121. }
  122. /**
  123. * Assemble content of other series
  124. * @param {Array.<module:echarts/model/Series>} series
  125. * @return {string}
  126. * @inner
  127. */
  128. function assembleOtherSeries(series) {
  129. return zrUtil.map(series, function (series) {
  130. var data = series.getRawData();
  131. var lines = [series.name];
  132. var vals = [];
  133. data.each(data.dimensions, function () {
  134. var argLen = arguments.length;
  135. var dataIndex = arguments[argLen - 1];
  136. var name = data.getName(dataIndex);
  137. for (var i = 0; i < argLen - 1; i++) {
  138. vals[i] = arguments[i];
  139. }
  140. lines.push((name ? name + ITEM_SPLITER : '') + vals.join(ITEM_SPLITER));
  141. });
  142. return lines.join('\n');
  143. }).join('\n\n' + BLOCK_SPLITER + '\n\n');
  144. }
  145. /**
  146. * @param {module:echarts/model/Global}
  147. * @return {Object}
  148. * @inner
  149. */
  150. function getContentFromModel(ecModel) {
  151. var result = groupSeries(ecModel);
  152. return {
  153. value: zrUtil.filter([assembleSeriesWithCategoryAxis(result.seriesGroupByCategoryAxis), assembleOtherSeries(result.other)], function (str) {
  154. return str.replace(/[\n\t\s]/g, '');
  155. }).join('\n\n' + BLOCK_SPLITER + '\n\n'),
  156. meta: result.meta
  157. };
  158. }
  159. function trim(str) {
  160. return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
  161. }
  162. /**
  163. * If a block is tsv format
  164. */
  165. function isTSVFormat(block) {
  166. // Simple method to find out if a block is tsv format
  167. var firstLine = block.slice(0, block.indexOf('\n'));
  168. if (firstLine.indexOf(ITEM_SPLITER) >= 0) {
  169. return true;
  170. }
  171. }
  172. var itemSplitRegex = new RegExp('[' + ITEM_SPLITER + ']+', 'g');
  173. /**
  174. * @param {string} tsv
  175. * @return {Object}
  176. */
  177. function parseTSVContents(tsv) {
  178. var tsvLines = tsv.split(/\n+/g);
  179. var headers = trim(tsvLines.shift()).split(itemSplitRegex);
  180. var categories = [];
  181. var series = zrUtil.map(headers, function (header) {
  182. return {
  183. name: header,
  184. data: []
  185. };
  186. });
  187. for (var i = 0; i < tsvLines.length; i++) {
  188. var items = trim(tsvLines[i]).split(itemSplitRegex);
  189. categories.push(items.shift());
  190. for (var j = 0; j < items.length; j++) {
  191. series[j] && (series[j].data[i] = items[j]);
  192. }
  193. }
  194. return {
  195. series: series,
  196. categories: categories
  197. };
  198. }
  199. /**
  200. * @param {string} str
  201. * @return {Array.<Object>}
  202. * @inner
  203. */
  204. function parseListContents(str) {
  205. var lines = str.split(/\n+/g);
  206. var seriesName = trim(lines.shift());
  207. var data = [];
  208. for (var i = 0; i < lines.length; i++) {
  209. // if line is empty, ignore it.
  210. // there is a case that a user forgot to delete `\n`.
  211. var line = trim(lines[i]);
  212. if (!line) {
  213. continue;
  214. }
  215. var items = line.split(itemSplitRegex);
  216. var name = '';
  217. var value;
  218. var hasName = false;
  219. if (isNaN(items[0])) {
  220. // First item is name
  221. hasName = true;
  222. name = items[0];
  223. items = items.slice(1);
  224. data[i] = {
  225. name: name,
  226. value: []
  227. };
  228. value = data[i].value;
  229. } else {
  230. value = data[i] = [];
  231. }
  232. for (var j = 0; j < items.length; j++) {
  233. value.push(+items[j]);
  234. }
  235. if (value.length === 1) {
  236. hasName ? data[i].value = value[0] : data[i] = value[0];
  237. }
  238. }
  239. return {
  240. name: seriesName,
  241. data: data
  242. };
  243. }
  244. /**
  245. * @param {string} str
  246. * @param {Array.<Object>} blockMetaList
  247. * @return {Object}
  248. * @inner
  249. */
  250. function parseContents(str, blockMetaList) {
  251. var blocks = str.split(new RegExp('\n*' + BLOCK_SPLITER + '\n*', 'g'));
  252. var newOption = {
  253. series: []
  254. };
  255. zrUtil.each(blocks, function (block, idx) {
  256. if (isTSVFormat(block)) {
  257. var result = parseTSVContents(block);
  258. var blockMeta = blockMetaList[idx];
  259. var axisKey = blockMeta.axisDim + 'Axis';
  260. if (blockMeta) {
  261. newOption[axisKey] = newOption[axisKey] || [];
  262. newOption[axisKey][blockMeta.axisIndex] = {
  263. data: result.categories
  264. };
  265. newOption.series = newOption.series.concat(result.series);
  266. }
  267. } else {
  268. var result = parseListContents(block);
  269. newOption.series.push(result);
  270. }
  271. });
  272. return newOption;
  273. }
  274. /**
  275. * @alias {module:echarts/component/toolbox/feature/DataView}
  276. * @constructor
  277. * @param {module:echarts/model/Model} model
  278. */
  279. function DataView(model) {
  280. this._dom = null;
  281. this.model = model;
  282. }
  283. DataView.defaultOption = {
  284. show: true,
  285. readOnly: false,
  286. optionToContent: null,
  287. contentToOption: null,
  288. 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',
  289. title: zrUtil.clone(dataViewLang.title),
  290. lang: zrUtil.clone(dataViewLang.lang),
  291. backgroundColor: '#fff',
  292. textColor: '#000',
  293. textareaColor: '#fff',
  294. textareaBorderColor: '#333',
  295. buttonColor: '#c23531',
  296. buttonTextColor: '#fff'
  297. };
  298. DataView.prototype.onclick = function (ecModel, api) {
  299. var container = api.getDom();
  300. var model = this.model;
  301. if (this._dom) {
  302. container.removeChild(this._dom);
  303. }
  304. var root = document.createElement('div');
  305. root.style.cssText = 'position:absolute;left:5px;top:5px;bottom:5px;right:5px;';
  306. root.style.backgroundColor = model.get('backgroundColor') || '#fff'; // Create elements
  307. var header = document.createElement('h4');
  308. var lang = model.get('lang') || [];
  309. header.innerHTML = lang[0] || model.get('title');
  310. header.style.cssText = 'margin: 10px 20px;';
  311. header.style.color = model.get('textColor');
  312. var viewMain = document.createElement('div');
  313. var textarea = document.createElement('textarea');
  314. viewMain.style.cssText = 'display:block;width:100%;overflow:auto;';
  315. var optionToContent = model.get('optionToContent');
  316. var contentToOption = model.get('contentToOption');
  317. var result = getContentFromModel(ecModel);
  318. if (typeof optionToContent === 'function') {
  319. var htmlOrDom = optionToContent(api.getOption());
  320. if (typeof htmlOrDom === 'string') {
  321. viewMain.innerHTML = htmlOrDom;
  322. } else if (zrUtil.isDom(htmlOrDom)) {
  323. viewMain.appendChild(htmlOrDom);
  324. }
  325. } else {
  326. // Use default textarea
  327. viewMain.appendChild(textarea);
  328. textarea.readOnly = model.get('readOnly');
  329. textarea.style.cssText = 'width:100%;height:100%;font-family:monospace;font-size:14px;line-height:1.6rem;';
  330. textarea.style.color = model.get('textColor');
  331. textarea.style.borderColor = model.get('textareaBorderColor');
  332. textarea.style.backgroundColor = model.get('textareaColor');
  333. textarea.value = result.value;
  334. }
  335. var blockMetaList = result.meta;
  336. var buttonContainer = document.createElement('div');
  337. buttonContainer.style.cssText = 'position:absolute;bottom:0;left:0;right:0;';
  338. var buttonStyle = 'float:right;margin-right:20px;border:none;' + 'cursor:pointer;padding:2px 5px;font-size:12px;border-radius:3px';
  339. var closeButton = document.createElement('div');
  340. var refreshButton = document.createElement('div');
  341. buttonStyle += ';background-color:' + model.get('buttonColor');
  342. buttonStyle += ';color:' + model.get('buttonTextColor');
  343. var self = this;
  344. function close() {
  345. container.removeChild(root);
  346. self._dom = null;
  347. }
  348. eventTool.addEventListener(closeButton, 'click', close);
  349. eventTool.addEventListener(refreshButton, 'click', function () {
  350. var newOption;
  351. try {
  352. if (typeof contentToOption === 'function') {
  353. newOption = contentToOption(viewMain, api.getOption());
  354. } else {
  355. newOption = parseContents(textarea.value, blockMetaList);
  356. }
  357. } catch (e) {
  358. close();
  359. throw new Error('Data view format error ' + e);
  360. }
  361. if (newOption) {
  362. api.dispatchAction({
  363. type: 'changeDataView',
  364. newOption: newOption
  365. });
  366. }
  367. close();
  368. });
  369. closeButton.innerHTML = lang[1];
  370. refreshButton.innerHTML = lang[2];
  371. refreshButton.style.cssText = buttonStyle;
  372. closeButton.style.cssText = buttonStyle;
  373. !model.get('readOnly') && buttonContainer.appendChild(refreshButton);
  374. buttonContainer.appendChild(closeButton);
  375. root.appendChild(header);
  376. root.appendChild(viewMain);
  377. root.appendChild(buttonContainer);
  378. viewMain.style.height = container.clientHeight - 80 + 'px';
  379. container.appendChild(root);
  380. this._dom = root;
  381. };
  382. DataView.prototype.remove = function (ecModel, api) {
  383. this._dom && api.getDom().removeChild(this._dom);
  384. };
  385. DataView.prototype.dispose = function (ecModel, api) {
  386. this.remove(ecModel, api);
  387. };
  388. /**
  389. * @inner
  390. */
  391. function tryMergeDataOption(newData, originalData) {
  392. return zrUtil.map(newData, function (newVal, idx) {
  393. var original = originalData && originalData[idx];
  394. if (zrUtil.isObject(original) && !zrUtil.isArray(original)) {
  395. var newValIsObject = zrUtil.isObject(newVal) && !zrUtil.isArray(newVal);
  396. if (!newValIsObject) {
  397. newVal = {
  398. value: newVal
  399. };
  400. } // original data has name but new data has no name
  401. var shouldDeleteName = original.name != null && newVal.name == null; // Original data has option
  402. newVal = zrUtil.defaults(newVal, original);
  403. shouldDeleteName && delete newVal.name;
  404. return newVal;
  405. } else {
  406. return newVal;
  407. }
  408. });
  409. }
  410. featureManager.register('dataView', DataView);
  411. echarts.registerAction({
  412. type: 'changeDataView',
  413. event: 'dataViewChanged',
  414. update: 'prepareAndUpdate'
  415. }, function (payload, ecModel) {
  416. var newSeriesOptList = [];
  417. zrUtil.each(payload.newOption.series, function (seriesOpt) {
  418. var seriesModel = ecModel.getSeriesByName(seriesOpt.name)[0];
  419. if (!seriesModel) {
  420. // New created series
  421. // Geuss the series type
  422. newSeriesOptList.push(zrUtil.extend({
  423. // Default is scatter
  424. type: 'scatter'
  425. }, seriesOpt));
  426. } else {
  427. var originalData = seriesModel.get('data');
  428. newSeriesOptList.push({
  429. name: seriesOpt.name,
  430. data: tryMergeDataOption(seriesOpt.data, originalData)
  431. });
  432. }
  433. });
  434. ecModel.mergeOption(zrUtil.defaults({
  435. series: newSeriesOptList
  436. }, payload.newOption));
  437. });
  438. var _default = DataView;
  439. module.exports = _default;