Series.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  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 _config = require("../config");
  20. var __DEV__ = _config.__DEV__;
  21. var zrUtil = require("zrender/lib/core/util");
  22. var env = require("zrender/lib/core/env");
  23. var _format = require("../util/format");
  24. var formatTime = _format.formatTime;
  25. var encodeHTML = _format.encodeHTML;
  26. var addCommas = _format.addCommas;
  27. var getTooltipMarker = _format.getTooltipMarker;
  28. var modelUtil = require("../util/model");
  29. var ComponentModel = require("./Component");
  30. var colorPaletteMixin = require("./mixin/colorPalette");
  31. var dataFormatMixin = require("../model/mixin/dataFormat");
  32. var _layout = require("../util/layout");
  33. var getLayoutParams = _layout.getLayoutParams;
  34. var mergeLayoutParam = _layout.mergeLayoutParam;
  35. var _task = require("../stream/task");
  36. var createTask = _task.createTask;
  37. var _sourceHelper = require("../data/helper/sourceHelper");
  38. var prepareSource = _sourceHelper.prepareSource;
  39. var getSource = _sourceHelper.getSource;
  40. var _dataProvider = require("../data/helper/dataProvider");
  41. var retrieveRawValue = _dataProvider.retrieveRawValue;
  42. /*
  43. * Licensed to the Apache Software Foundation (ASF) under one
  44. * or more contributor license agreements. See the NOTICE file
  45. * distributed with this work for additional information
  46. * regarding copyright ownership. The ASF licenses this file
  47. * to you under the Apache License, Version 2.0 (the
  48. * "License"); you may not use this file except in compliance
  49. * with the License. You may obtain a copy of the License at
  50. *
  51. * http://www.apache.org/licenses/LICENSE-2.0
  52. *
  53. * Unless required by applicable law or agreed to in writing,
  54. * software distributed under the License is distributed on an
  55. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  56. * KIND, either express or implied. See the License for the
  57. * specific language governing permissions and limitations
  58. * under the License.
  59. */
  60. var inner = modelUtil.makeInner();
  61. var SeriesModel = ComponentModel.extend({
  62. type: 'series.__base__',
  63. /**
  64. * @readOnly
  65. */
  66. seriesIndex: 0,
  67. // coodinateSystem will be injected in the echarts/CoordinateSystem
  68. coordinateSystem: null,
  69. /**
  70. * @type {Object}
  71. * @protected
  72. */
  73. defaultOption: null,
  74. /**
  75. * legend visual provider to the legend component
  76. * @type {Object}
  77. */
  78. // PENDING
  79. legendVisualProvider: null,
  80. /**
  81. * Access path of color for visual
  82. */
  83. visualColorAccessPath: 'itemStyle.color',
  84. /**
  85. * Access path of borderColor for visual
  86. */
  87. visualBorderColorAccessPath: 'itemStyle.borderColor',
  88. /**
  89. * Support merge layout params.
  90. * Only support 'box' now (left/right/top/bottom/width/height).
  91. * @type {string|Object} Object can be {ignoreSize: true}
  92. * @readOnly
  93. */
  94. layoutMode: null,
  95. init: function (option, parentModel, ecModel, extraOpt) {
  96. /**
  97. * @type {number}
  98. * @readOnly
  99. */
  100. this.seriesIndex = this.componentIndex;
  101. this.dataTask = createTask({
  102. count: dataTaskCount,
  103. reset: dataTaskReset
  104. });
  105. this.dataTask.context = {
  106. model: this
  107. };
  108. this.mergeDefaultAndTheme(option, ecModel);
  109. prepareSource(this);
  110. var data = this.getInitialData(option, ecModel);
  111. wrapData(data, this);
  112. this.dataTask.context.data = data;
  113. /**
  114. * @type {module:echarts/data/List|module:echarts/data/Tree|module:echarts/data/Graph}
  115. * @private
  116. */
  117. inner(this).dataBeforeProcessed = data; // If we reverse the order (make data firstly, and then make
  118. // dataBeforeProcessed by cloneShallow), cloneShallow will
  119. // cause data.graph.data !== data when using
  120. // module:echarts/data/Graph or module:echarts/data/Tree.
  121. // See module:echarts/data/helper/linkList
  122. // Theoretically, it is unreasonable to call `seriesModel.getData()` in the model
  123. // init or merge stage, because the data can be restored. So we do not `restoreData`
  124. // and `setData` here, which forbids calling `seriesModel.getData()` in this stage.
  125. // Call `seriesModel.getRawData()` instead.
  126. // this.restoreData();
  127. autoSeriesName(this);
  128. },
  129. /**
  130. * Util for merge default and theme to option
  131. * @param {Object} option
  132. * @param {module:echarts/model/Global} ecModel
  133. */
  134. mergeDefaultAndTheme: function (option, ecModel) {
  135. var layoutMode = this.layoutMode;
  136. var inputPositionParams = layoutMode ? getLayoutParams(option) : {}; // Backward compat: using subType on theme.
  137. // But if name duplicate between series subType
  138. // (for example: parallel) add component mainType,
  139. // add suffix 'Series'.
  140. var themeSubType = this.subType;
  141. if (ComponentModel.hasClass(themeSubType)) {
  142. themeSubType += 'Series';
  143. }
  144. zrUtil.merge(option, ecModel.getTheme().get(this.subType));
  145. zrUtil.merge(option, this.getDefaultOption()); // Default label emphasis `show`
  146. modelUtil.defaultEmphasis(option, 'label', ['show']);
  147. this.fillDataTextStyle(option.data);
  148. if (layoutMode) {
  149. mergeLayoutParam(option, inputPositionParams, layoutMode);
  150. }
  151. },
  152. mergeOption: function (newSeriesOption, ecModel) {
  153. // this.settingTask.dirty();
  154. newSeriesOption = zrUtil.merge(this.option, newSeriesOption, true);
  155. this.fillDataTextStyle(newSeriesOption.data);
  156. var layoutMode = this.layoutMode;
  157. if (layoutMode) {
  158. mergeLayoutParam(this.option, newSeriesOption, layoutMode);
  159. }
  160. prepareSource(this);
  161. var data = this.getInitialData(newSeriesOption, ecModel);
  162. wrapData(data, this);
  163. this.dataTask.dirty();
  164. this.dataTask.context.data = data;
  165. inner(this).dataBeforeProcessed = data;
  166. autoSeriesName(this);
  167. },
  168. fillDataTextStyle: function (data) {
  169. // Default data label emphasis `show`
  170. // FIXME Tree structure data ?
  171. // FIXME Performance ?
  172. if (data && !zrUtil.isTypedArray(data)) {
  173. var props = ['show'];
  174. for (var i = 0; i < data.length; i++) {
  175. if (data[i] && data[i].label) {
  176. modelUtil.defaultEmphasis(data[i], 'label', props);
  177. }
  178. }
  179. }
  180. },
  181. /**
  182. * Init a data structure from data related option in series
  183. * Must be overwritten
  184. */
  185. getInitialData: function () {},
  186. /**
  187. * Append data to list
  188. * @param {Object} params
  189. * @param {Array|TypedArray} params.data
  190. */
  191. appendData: function (params) {
  192. // FIXME ???
  193. // (1) If data from dataset, forbidden append.
  194. // (2) support append data of dataset.
  195. var data = this.getRawData();
  196. data.appendData(params.data);
  197. },
  198. /**
  199. * Consider some method like `filter`, `map` need make new data,
  200. * We should make sure that `seriesModel.getData()` get correct
  201. * data in the stream procedure. So we fetch data from upstream
  202. * each time `task.perform` called.
  203. * @param {string} [dataType]
  204. * @return {module:echarts/data/List}
  205. */
  206. getData: function (dataType) {
  207. var task = getCurrentTask(this);
  208. if (task) {
  209. var data = task.context.data;
  210. return dataType == null ? data : data.getLinkedData(dataType);
  211. } else {
  212. // When series is not alive (that may happen when click toolbox
  213. // restore or setOption with not merge mode), series data may
  214. // be still need to judge animation or something when graphic
  215. // elements want to know whether fade out.
  216. return inner(this).data;
  217. }
  218. },
  219. /**
  220. * @param {module:echarts/data/List} data
  221. */
  222. setData: function (data) {
  223. var task = getCurrentTask(this);
  224. if (task) {
  225. var context = task.context; // Consider case: filter, data sample.
  226. if (context.data !== data && task.modifyOutputEnd) {
  227. task.setOutputEnd(data.count());
  228. }
  229. context.outputData = data; // Caution: setData should update context.data,
  230. // Because getData may be called multiply in a
  231. // single stage and expect to get the data just
  232. // set. (For example, AxisProxy, x y both call
  233. // getData and setDate sequentially).
  234. // So the context.data should be fetched from
  235. // upstream each time when a stage starts to be
  236. // performed.
  237. if (task !== this.dataTask) {
  238. context.data = data;
  239. }
  240. }
  241. inner(this).data = data;
  242. },
  243. /**
  244. * @see {module:echarts/data/helper/sourceHelper#getSource}
  245. * @return {module:echarts/data/Source} source
  246. */
  247. getSource: function () {
  248. return getSource(this);
  249. },
  250. /**
  251. * Get data before processed
  252. * @return {module:echarts/data/List}
  253. */
  254. getRawData: function () {
  255. return inner(this).dataBeforeProcessed;
  256. },
  257. /**
  258. * Get base axis if has coordinate system and has axis.
  259. * By default use coordSys.getBaseAxis();
  260. * Can be overrided for some chart.
  261. * @return {type} description
  262. */
  263. getBaseAxis: function () {
  264. var coordSys = this.coordinateSystem;
  265. return coordSys && coordSys.getBaseAxis && coordSys.getBaseAxis();
  266. },
  267. // FIXME
  268. /**
  269. * Default tooltip formatter
  270. *
  271. * @param {number} dataIndex
  272. * @param {boolean} [multipleSeries=false]
  273. * @param {number} [dataType]
  274. * @param {string} [renderMode='html'] valid values: 'html' and 'richText'.
  275. * 'html' is used for rendering tooltip in extra DOM form, and the result
  276. * string is used as DOM HTML content.
  277. * 'richText' is used for rendering tooltip in rich text form, for those where
  278. * DOM operation is not supported.
  279. * @return {Object} formatted tooltip with `html` and `markers`
  280. */
  281. formatTooltip: function (dataIndex, multipleSeries, dataType, renderMode) {
  282. var series = this;
  283. renderMode = renderMode || 'html';
  284. var newLine = renderMode === 'html' ? '<br/>' : '\n';
  285. var isRichText = renderMode === 'richText';
  286. var markers = {};
  287. var markerId = 0;
  288. function formatArrayValue(value) {
  289. // ??? TODO refactor these logic.
  290. // check: category-no-encode-has-axis-data in dataset.html
  291. var vertially = zrUtil.reduce(value, function (vertially, val, idx) {
  292. var dimItem = data.getDimensionInfo(idx);
  293. return vertially |= dimItem && dimItem.tooltip !== false && dimItem.displayName != null;
  294. }, 0);
  295. var result = [];
  296. tooltipDims.length ? zrUtil.each(tooltipDims, function (dim) {
  297. setEachItem(retrieveRawValue(data, dataIndex, dim), dim);
  298. }) // By default, all dims is used on tooltip.
  299. : zrUtil.each(value, setEachItem);
  300. function setEachItem(val, dim) {
  301. var dimInfo = data.getDimensionInfo(dim); // If `dimInfo.tooltip` is not set, show tooltip.
  302. if (!dimInfo || dimInfo.otherDims.tooltip === false) {
  303. return;
  304. }
  305. var dimType = dimInfo.type;
  306. var markName = 'sub' + series.seriesIndex + 'at' + markerId;
  307. var dimHead = getTooltipMarker({
  308. color: color,
  309. type: 'subItem',
  310. renderMode: renderMode,
  311. markerId: markName
  312. });
  313. var dimHeadStr = typeof dimHead === 'string' ? dimHead : dimHead.content;
  314. var valStr = (vertially ? dimHeadStr + encodeHTML(dimInfo.displayName || '-') + ': ' : '') + // FIXME should not format time for raw data?
  315. encodeHTML(dimType === 'ordinal' ? val + '' : dimType === 'time' ? multipleSeries ? '' : formatTime('yyyy/MM/dd hh:mm:ss', val) : addCommas(val));
  316. valStr && result.push(valStr);
  317. if (isRichText) {
  318. markers[markName] = color;
  319. ++markerId;
  320. }
  321. }
  322. var newLine = vertially ? isRichText ? '\n' : '<br/>' : '';
  323. var content = newLine + result.join(newLine || ', ');
  324. return {
  325. renderMode: renderMode,
  326. content: content,
  327. style: markers
  328. };
  329. }
  330. function formatSingleValue(val) {
  331. // return encodeHTML(addCommas(val));
  332. return {
  333. renderMode: renderMode,
  334. content: encodeHTML(addCommas(val)),
  335. style: markers
  336. };
  337. }
  338. var data = this.getData();
  339. var tooltipDims = data.mapDimension('defaultedTooltip', true);
  340. var tooltipDimLen = tooltipDims.length;
  341. var value = this.getRawValue(dataIndex);
  342. var isValueArr = zrUtil.isArray(value);
  343. var color = data.getItemVisual(dataIndex, 'color');
  344. if (zrUtil.isObject(color) && color.colorStops) {
  345. color = (color.colorStops[0] || {}).color;
  346. }
  347. color = color || 'transparent'; // Complicated rule for pretty tooltip.
  348. var formattedValue = tooltipDimLen > 1 || isValueArr && !tooltipDimLen ? formatArrayValue(value) : tooltipDimLen ? formatSingleValue(retrieveRawValue(data, dataIndex, tooltipDims[0])) : formatSingleValue(isValueArr ? value[0] : value);
  349. var content = formattedValue.content;
  350. var markName = series.seriesIndex + 'at' + markerId;
  351. var colorEl = getTooltipMarker({
  352. color: color,
  353. type: 'item',
  354. renderMode: renderMode,
  355. markerId: markName
  356. });
  357. markers[markName] = color;
  358. ++markerId;
  359. var name = data.getName(dataIndex);
  360. var seriesName = this.name;
  361. if (!modelUtil.isNameSpecified(this)) {
  362. seriesName = '';
  363. }
  364. seriesName = seriesName ? encodeHTML(seriesName) + (!multipleSeries ? newLine : ': ') : '';
  365. var colorStr = typeof colorEl === 'string' ? colorEl : colorEl.content;
  366. var html = !multipleSeries ? seriesName + colorStr + (name ? encodeHTML(name) + ': ' + content : content) : colorStr + seriesName + content;
  367. return {
  368. html: html,
  369. markers: markers
  370. };
  371. },
  372. /**
  373. * @return {boolean}
  374. */
  375. isAnimationEnabled: function () {
  376. if (env.node) {
  377. return false;
  378. }
  379. var animationEnabled = this.getShallow('animation');
  380. if (animationEnabled) {
  381. if (this.getData().count() > this.getShallow('animationThreshold')) {
  382. animationEnabled = false;
  383. }
  384. }
  385. return animationEnabled;
  386. },
  387. restoreData: function () {
  388. this.dataTask.dirty();
  389. },
  390. getColorFromPalette: function (name, scope, requestColorNum) {
  391. var ecModel = this.ecModel; // PENDING
  392. var color = colorPaletteMixin.getColorFromPalette.call(this, name, scope, requestColorNum);
  393. if (!color) {
  394. color = ecModel.getColorFromPalette(name, scope, requestColorNum);
  395. }
  396. return color;
  397. },
  398. /**
  399. * Use `data.mapDimension(coordDim, true)` instead.
  400. * @deprecated
  401. */
  402. coordDimToDataDim: function (coordDim) {
  403. return this.getRawData().mapDimension(coordDim, true);
  404. },
  405. /**
  406. * Get progressive rendering count each step
  407. * @return {number}
  408. */
  409. getProgressive: function () {
  410. return this.get('progressive');
  411. },
  412. /**
  413. * Get progressive rendering count each step
  414. * @return {number}
  415. */
  416. getProgressiveThreshold: function () {
  417. return this.get('progressiveThreshold');
  418. },
  419. /**
  420. * Get data indices for show tooltip content. See tooltip.
  421. * @abstract
  422. * @param {Array.<string>|string} dim
  423. * @param {Array.<number>} value
  424. * @param {module:echarts/coord/single/SingleAxis} baseAxis
  425. * @return {Object} {dataIndices, nestestValue}.
  426. */
  427. getAxisTooltipData: null,
  428. /**
  429. * See tooltip.
  430. * @abstract
  431. * @param {number} dataIndex
  432. * @return {Array.<number>} Point of tooltip. null/undefined can be returned.
  433. */
  434. getTooltipPosition: null,
  435. /**
  436. * @see {module:echarts/stream/Scheduler}
  437. */
  438. pipeTask: null,
  439. /**
  440. * Convinient for override in extended class.
  441. * @protected
  442. * @type {Function}
  443. */
  444. preventIncremental: null,
  445. /**
  446. * @public
  447. * @readOnly
  448. * @type {Object}
  449. */
  450. pipelineContext: null
  451. });
  452. zrUtil.mixin(SeriesModel, dataFormatMixin);
  453. zrUtil.mixin(SeriesModel, colorPaletteMixin);
  454. /**
  455. * MUST be called after `prepareSource` called
  456. * Here we need to make auto series, especially for auto legend. But we
  457. * do not modify series.name in option to avoid side effects.
  458. */
  459. function autoSeriesName(seriesModel) {
  460. // User specified name has higher priority, otherwise it may cause
  461. // series can not be queried unexpectedly.
  462. var name = seriesModel.name;
  463. if (!modelUtil.isNameSpecified(seriesModel)) {
  464. seriesModel.name = getSeriesAutoName(seriesModel) || name;
  465. }
  466. }
  467. function getSeriesAutoName(seriesModel) {
  468. var data = seriesModel.getRawData();
  469. var dataDims = data.mapDimension('seriesName', true);
  470. var nameArr = [];
  471. zrUtil.each(dataDims, function (dataDim) {
  472. var dimInfo = data.getDimensionInfo(dataDim);
  473. dimInfo.displayName && nameArr.push(dimInfo.displayName);
  474. });
  475. return nameArr.join(' ');
  476. }
  477. function dataTaskCount(context) {
  478. return context.model.getRawData().count();
  479. }
  480. function dataTaskReset(context) {
  481. var seriesModel = context.model;
  482. seriesModel.setData(seriesModel.getRawData().cloneShallow());
  483. return dataTaskProgress;
  484. }
  485. function dataTaskProgress(param, context) {
  486. // Avoid repead cloneShallow when data just created in reset.
  487. if (context.outputData && param.end > context.outputData.count()) {
  488. context.model.getRawData().cloneShallow(context.outputData);
  489. }
  490. } // TODO refactor
  491. function wrapData(data, seriesModel) {
  492. zrUtil.each(data.CHANGABLE_METHODS, function (methodName) {
  493. data.wrapMethod(methodName, zrUtil.curry(onDataSelfChange, seriesModel));
  494. });
  495. }
  496. function onDataSelfChange(seriesModel) {
  497. var task = getCurrentTask(seriesModel);
  498. if (task) {
  499. // Consider case: filter, selectRange
  500. task.setOutputEnd(this.count());
  501. }
  502. }
  503. function getCurrentTask(seriesModel) {
  504. var scheduler = (seriesModel.ecModel || {}).scheduler;
  505. var pipeline = scheduler && scheduler.getPipeline(seriesModel.uid);
  506. if (pipeline) {
  507. // When pipline finished, the currrentTask keep the last
  508. // task (renderTask).
  509. var task = pipeline.currentTask;
  510. if (task) {
  511. var agentStubMap = task.agentStubMap;
  512. if (agentStubMap) {
  513. task = agentStubMap.get(seriesModel.uid);
  514. }
  515. }
  516. return task;
  517. }
  518. }
  519. var _default = SeriesModel;
  520. module.exports = _default;