DataView.js 13 KB

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