DataView.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  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. columns.push(series.getRawData().mapArray(valueAxisDim, function (val) {
  106. return val;
  107. }));
  108. }); // Assemble table content
  109. var lines = [headers.join(ITEM_SPLITER)];
  110. for (var i = 0; i < columns[0].length; i++) {
  111. var items = [];
  112. for (var j = 0; j < columns.length; j++) {
  113. items.push(columns[j][i]);
  114. }
  115. lines.push(items.join(ITEM_SPLITER));
  116. }
  117. tables.push(lines.join('\n'));
  118. });
  119. return tables.join('\n\n' + BLOCK_SPLITER + '\n\n');
  120. }
  121. /**
  122. * Assemble content of other series
  123. * @param {Array.<module:echarts/model/Series>} series
  124. * @return {string}
  125. * @inner
  126. */
  127. function assembleOtherSeries(series) {
  128. return zrUtil.map(series, function (series) {
  129. var data = series.getRawData();
  130. var lines = [series.name];
  131. var vals = [];
  132. data.each(data.dimensions, function () {
  133. var argLen = arguments.length;
  134. var dataIndex = arguments[argLen - 1];
  135. var name = data.getName(dataIndex);
  136. for (var i = 0; i < argLen - 1; i++) {
  137. vals[i] = arguments[i];
  138. }
  139. lines.push((name ? name + ITEM_SPLITER : '') + vals.join(ITEM_SPLITER));
  140. });
  141. return lines.join('\n');
  142. }).join('\n\n' + BLOCK_SPLITER + '\n\n');
  143. }
  144. /**
  145. * @param {module:echarts/model/Global}
  146. * @return {Object}
  147. * @inner
  148. */
  149. function getContentFromModel(ecModel) {
  150. var result = groupSeries(ecModel);
  151. return {
  152. value: zrUtil.filter([assembleSeriesWithCategoryAxis(result.seriesGroupByCategoryAxis), assembleOtherSeries(result.other)], function (str) {
  153. return str.replace(/[\n\t\s]/g, '');
  154. }).join('\n\n' + BLOCK_SPLITER + '\n\n'),
  155. meta: result.meta
  156. };
  157. }
  158. function trim(str) {
  159. return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
  160. }
  161. /**
  162. * If a block is tsv format
  163. */
  164. function isTSVFormat(block) {
  165. // Simple method to find out if a block is tsv format
  166. var firstLine = block.slice(0, block.indexOf('\n'));
  167. if (firstLine.indexOf(ITEM_SPLITER) >= 0) {
  168. return true;
  169. }
  170. }
  171. var itemSplitRegex = new RegExp('[' + ITEM_SPLITER + ']+', 'g');
  172. /**
  173. * @param {string} tsv
  174. * @return {Object}
  175. */
  176. function parseTSVContents(tsv) {
  177. var tsvLines = tsv.split(/\n+/g);
  178. var headers = trim(tsvLines.shift()).split(itemSplitRegex);
  179. var categories = [];
  180. var series = zrUtil.map(headers, function (header) {
  181. return {
  182. name: header,
  183. data: []
  184. };
  185. });
  186. for (var i = 0; i < tsvLines.length; i++) {
  187. var items = trim(tsvLines[i]).split(itemSplitRegex);
  188. categories.push(items.shift());
  189. for (var j = 0; j < items.length; j++) {
  190. series[j] && (series[j].data[i] = items[j]);
  191. }
  192. }
  193. return {
  194. series: series,
  195. categories: categories
  196. };
  197. }
  198. /**
  199. * @param {string} str
  200. * @return {Array.<Object>}
  201. * @inner
  202. */
  203. function parseListContents(str) {
  204. var lines = str.split(/\n+/g);
  205. var seriesName = trim(lines.shift());
  206. var data = [];
  207. for (var i = 0; i < lines.length; i++) {
  208. var items = trim(lines[i]).split(itemSplitRegex);
  209. var name = '';
  210. var value;
  211. var hasName = false;
  212. if (isNaN(items[0])) {
  213. // First item is name
  214. hasName = true;
  215. name = items[0];
  216. items = items.slice(1);
  217. data[i] = {
  218. name: name,
  219. value: []
  220. };
  221. value = data[i].value;
  222. } else {
  223. value = data[i] = [];
  224. }
  225. for (var j = 0; j < items.length; j++) {
  226. value.push(+items[j]);
  227. }
  228. if (value.length === 1) {
  229. hasName ? data[i].value = value[0] : data[i] = value[0];
  230. }
  231. }
  232. return {
  233. name: seriesName,
  234. data: data
  235. };
  236. }
  237. /**
  238. * @param {string} str
  239. * @param {Array.<Object>} blockMetaList
  240. * @return {Object}
  241. * @inner
  242. */
  243. function parseContents(str, blockMetaList) {
  244. var blocks = str.split(new RegExp('\n*' + BLOCK_SPLITER + '\n*', 'g'));
  245. var newOption = {
  246. series: []
  247. };
  248. zrUtil.each(blocks, function (block, idx) {
  249. if (isTSVFormat(block)) {
  250. var result = parseTSVContents(block);
  251. var blockMeta = blockMetaList[idx];
  252. var axisKey = blockMeta.axisDim + 'Axis';
  253. if (blockMeta) {
  254. newOption[axisKey] = newOption[axisKey] || [];
  255. newOption[axisKey][blockMeta.axisIndex] = {
  256. data: result.categories
  257. };
  258. newOption.series = newOption.series.concat(result.series);
  259. }
  260. } else {
  261. var result = parseListContents(block);
  262. newOption.series.push(result);
  263. }
  264. });
  265. return newOption;
  266. }
  267. /**
  268. * @alias {module:echarts/component/toolbox/feature/DataView}
  269. * @constructor
  270. * @param {module:echarts/model/Model} model
  271. */
  272. function DataView(model) {
  273. this._dom = null;
  274. this.model = model;
  275. }
  276. DataView.defaultOption = {
  277. show: true,
  278. readOnly: false,
  279. optionToContent: null,
  280. contentToOption: null,
  281. 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',
  282. title: zrUtil.clone(dataViewLang.title),
  283. lang: zrUtil.clone(dataViewLang.lang),
  284. backgroundColor: '#fff',
  285. textColor: '#000',
  286. textareaColor: '#fff',
  287. textareaBorderColor: '#333',
  288. buttonColor: '#c23531',
  289. buttonTextColor: '#fff'
  290. };
  291. DataView.prototype.onclick = function (ecModel, api) {
  292. var container = api.getDom();
  293. var model = this.model;
  294. if (this._dom) {
  295. container.removeChild(this._dom);
  296. }
  297. var root = document.createElement('div');
  298. root.style.cssText = 'position:absolute;left:5px;top:5px;bottom:5px;right:5px;';
  299. root.style.backgroundColor = model.get('backgroundColor') || '#fff'; // Create elements
  300. var header = document.createElement('h4');
  301. var lang = model.get('lang') || [];
  302. header.innerHTML = lang[0] || model.get('title');
  303. header.style.cssText = 'margin: 10px 20px;';
  304. header.style.color = model.get('textColor');
  305. var viewMain = document.createElement('div');
  306. var textarea = document.createElement('textarea');
  307. viewMain.style.cssText = 'display:block;width:100%;overflow:auto;';
  308. var optionToContent = model.get('optionToContent');
  309. var contentToOption = model.get('contentToOption');
  310. var result = getContentFromModel(ecModel);
  311. if (typeof optionToContent === 'function') {
  312. var htmlOrDom = optionToContent(api.getOption());
  313. if (typeof htmlOrDom === 'string') {
  314. viewMain.innerHTML = htmlOrDom;
  315. } else if (zrUtil.isDom(htmlOrDom)) {
  316. viewMain.appendChild(htmlOrDom);
  317. }
  318. } else {
  319. // Use default textarea
  320. viewMain.appendChild(textarea);
  321. textarea.readOnly = model.get('readOnly');
  322. textarea.style.cssText = 'width:100%;height:100%;font-family:monospace;font-size:14px;line-height:1.6rem;';
  323. textarea.style.color = model.get('textColor');
  324. textarea.style.borderColor = model.get('textareaBorderColor');
  325. textarea.style.backgroundColor = model.get('textareaColor');
  326. textarea.value = result.value;
  327. }
  328. var blockMetaList = result.meta;
  329. var buttonContainer = document.createElement('div');
  330. buttonContainer.style.cssText = 'position:absolute;bottom:0;left:0;right:0;';
  331. var buttonStyle = 'float:right;margin-right:20px;border:none;' + 'cursor:pointer;padding:2px 5px;font-size:12px;border-radius:3px';
  332. var closeButton = document.createElement('div');
  333. var refreshButton = document.createElement('div');
  334. buttonStyle += ';background-color:' + model.get('buttonColor');
  335. buttonStyle += ';color:' + model.get('buttonTextColor');
  336. var self = this;
  337. function close() {
  338. container.removeChild(root);
  339. self._dom = null;
  340. }
  341. eventTool.addEventListener(closeButton, 'click', close);
  342. eventTool.addEventListener(refreshButton, 'click', function () {
  343. var newOption;
  344. try {
  345. if (typeof contentToOption === 'function') {
  346. newOption = contentToOption(viewMain, api.getOption());
  347. } else {
  348. newOption = parseContents(textarea.value, blockMetaList);
  349. }
  350. } catch (e) {
  351. close();
  352. throw new Error('Data view format error ' + e);
  353. }
  354. if (newOption) {
  355. api.dispatchAction({
  356. type: 'changeDataView',
  357. newOption: newOption
  358. });
  359. }
  360. close();
  361. });
  362. closeButton.innerHTML = lang[1];
  363. refreshButton.innerHTML = lang[2];
  364. refreshButton.style.cssText = buttonStyle;
  365. closeButton.style.cssText = buttonStyle;
  366. !model.get('readOnly') && buttonContainer.appendChild(refreshButton);
  367. buttonContainer.appendChild(closeButton);
  368. root.appendChild(header);
  369. root.appendChild(viewMain);
  370. root.appendChild(buttonContainer);
  371. viewMain.style.height = container.clientHeight - 80 + 'px';
  372. container.appendChild(root);
  373. this._dom = root;
  374. };
  375. DataView.prototype.remove = function (ecModel, api) {
  376. this._dom && api.getDom().removeChild(this._dom);
  377. };
  378. DataView.prototype.dispose = function (ecModel, api) {
  379. this.remove(ecModel, api);
  380. };
  381. /**
  382. * @inner
  383. */
  384. function tryMergeDataOption(newData, originalData) {
  385. return zrUtil.map(newData, function (newVal, idx) {
  386. var original = originalData && originalData[idx];
  387. if (zrUtil.isObject(original) && !zrUtil.isArray(original)) {
  388. if (zrUtil.isObject(newVal) && !zrUtil.isArray(newVal)) {
  389. newVal = newVal.value;
  390. } // Original data has option
  391. return zrUtil.defaults({
  392. value: newVal
  393. }, original);
  394. } else {
  395. return newVal;
  396. }
  397. });
  398. }
  399. featureManager.register('dataView', DataView);
  400. echarts.registerAction({
  401. type: 'changeDataView',
  402. event: 'dataViewChanged',
  403. update: 'prepareAndUpdate'
  404. }, function (payload, ecModel) {
  405. var newSeriesOptList = [];
  406. zrUtil.each(payload.newOption.series, function (seriesOpt) {
  407. var seriesModel = ecModel.getSeriesByName(seriesOpt.name)[0];
  408. if (!seriesModel) {
  409. // New created series
  410. // Geuss the series type
  411. newSeriesOptList.push(zrUtil.extend({
  412. // Default is scatter
  413. type: 'scatter'
  414. }, seriesOpt));
  415. } else {
  416. var originalData = seriesModel.get('data');
  417. newSeriesOptList.push({
  418. name: seriesOpt.name,
  419. data: tryMergeDataOption(seriesOpt.data, originalData)
  420. });
  421. }
  422. });
  423. ecModel.mergeOption(zrUtil.defaults({
  424. series: newSeriesOptList
  425. }, payload.newOption));
  426. });
  427. var _default = DataView;
  428. module.exports = _default;