custom.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. var _config = require("../config");
  2. var __DEV__ = _config.__DEV__;
  3. var echarts = require("../echarts");
  4. var zrUtil = require("zrender/lib/core/util");
  5. var graphicUtil = require("../util/graphic");
  6. var _labelHelper = require("./helper/labelHelper");
  7. var findLabelValueDim = _labelHelper.findLabelValueDim;
  8. var createListFromArray = require("./helper/createListFromArray");
  9. var barGrid = require("../layout/barGrid");
  10. var DataDiffer = require("../data/DataDiffer");
  11. var prepareCartesian2d = require("../coord/cartesian/prepareCustom");
  12. var prepareGeo = require("../coord/geo/prepareCustom");
  13. var prepareSingleAxis = require("../coord/single/prepareCustom");
  14. var preparePolar = require("../coord/polar/prepareCustom");
  15. var prepareCalendar = require("../coord/calendar/prepareCustom");
  16. var ITEM_STYLE_NORMAL_PATH = ['itemStyle', 'normal'];
  17. var ITEM_STYLE_EMPHASIS_PATH = ['itemStyle', 'emphasis'];
  18. var LABEL_NORMAL = ['label', 'normal'];
  19. var LABEL_EMPHASIS = ['label', 'emphasis']; // Use prefix to avoid index to be the same as el.name,
  20. // which will cause weird udpate animation.
  21. var GROUP_DIFF_PREFIX = 'e\0\0';
  22. /**
  23. * To reduce total package size of each coordinate systems, the modules `prepareCustom`
  24. * of each coordinate systems are not required by each coordinate systems directly, but
  25. * required by the module `custom`.
  26. *
  27. * prepareInfoForCustomSeries {Function}: optional
  28. * @return {Object} {coordSys: {...}, api: {
  29. * coord: function (data, clamp) {}, // return point in global.
  30. * size: function (dataSize, dataItem) {} // return size of each axis in coordSys.
  31. * }}
  32. */
  33. var prepareCustoms = {
  34. cartesian2d: prepareCartesian2d,
  35. geo: prepareGeo,
  36. singleAxis: prepareSingleAxis,
  37. polar: preparePolar,
  38. calendar: prepareCalendar
  39. }; // ------
  40. // Model
  41. // ------
  42. echarts.extendSeriesModel({
  43. type: 'series.custom',
  44. dependencies: ['grid', 'polar', 'geo', 'singleAxis', 'calendar'],
  45. defaultOption: {
  46. coordinateSystem: 'cartesian2d',
  47. // Can be set as 'none'
  48. zlevel: 0,
  49. z: 2,
  50. legendHoverLink: true // Cartesian coordinate system
  51. // xAxisIndex: 0,
  52. // yAxisIndex: 0,
  53. // Polar coordinate system
  54. // polarIndex: 0,
  55. // Geo coordinate system
  56. // geoIndex: 0,
  57. // label: {}
  58. // itemStyle: {}
  59. },
  60. getInitialData: function (option, ecModel) {
  61. return createListFromArray(option.data, this, ecModel);
  62. }
  63. }); // -----
  64. // View
  65. // -----
  66. echarts.extendChartView({
  67. type: 'custom',
  68. /**
  69. * @private
  70. * @type {module:echarts/data/List}
  71. */
  72. _data: null,
  73. /**
  74. * @override
  75. */
  76. render: function (customSeries, ecModel, api) {
  77. var oldData = this._data;
  78. var data = customSeries.getData();
  79. var group = this.group;
  80. var renderItem = makeRenderItem(customSeries, data, ecModel, api);
  81. data.diff(oldData).add(function (newIdx) {
  82. data.hasValue(newIdx) && createOrUpdate(null, newIdx, renderItem(newIdx), customSeries, group, data);
  83. }).update(function (newIdx, oldIdx) {
  84. var el = oldData.getItemGraphicEl(oldIdx);
  85. data.hasValue(newIdx) ? createOrUpdate(el, newIdx, renderItem(newIdx), customSeries, group, data) : el && group.remove(el);
  86. }).remove(function (oldIdx) {
  87. var el = oldData.getItemGraphicEl(oldIdx);
  88. el && group.remove(el);
  89. }).execute();
  90. this._data = data;
  91. },
  92. /**
  93. * @override
  94. */
  95. dispose: zrUtil.noop
  96. });
  97. function createEl(elOption) {
  98. var graphicType = elOption.type;
  99. var el;
  100. if (graphicType === 'path') {
  101. var shape = elOption.shape;
  102. el = graphicUtil.makePath(shape.pathData, null, {
  103. x: shape.x || 0,
  104. y: shape.y || 0,
  105. width: shape.width || 0,
  106. height: shape.height || 0
  107. }, 'center');
  108. el.__customPathData = elOption.pathData;
  109. } else if (graphicType === 'image') {
  110. el = new graphicUtil.Image({});
  111. el.__customImagePath = elOption.style.image;
  112. } else if (graphicType === 'text') {
  113. el = new graphicUtil.Text({});
  114. el.__customText = elOption.style.text;
  115. } else {
  116. var Clz = graphicUtil[graphicType.charAt(0).toUpperCase() + graphicType.slice(1)];
  117. el = new Clz();
  118. }
  119. el.__customGraphicType = graphicType;
  120. el.name = elOption.name;
  121. return el;
  122. }
  123. function updateEl(el, dataIndex, elOption, animatableModel, data, isInit) {
  124. var targetProps = {};
  125. var elOptionStyle = elOption.style || {};
  126. elOption.shape && (targetProps.shape = zrUtil.clone(elOption.shape));
  127. elOption.position && (targetProps.position = elOption.position.slice());
  128. elOption.scale && (targetProps.scale = elOption.scale.slice());
  129. elOption.origin && (targetProps.origin = elOption.origin.slice());
  130. elOption.rotation && (targetProps.rotation = elOption.rotation);
  131. if (el.type === 'image' && elOption.style) {
  132. var targetStyle = targetProps.style = {};
  133. zrUtil.each(['x', 'y', 'width', 'height'], function (prop) {
  134. prepareStyleTransition(prop, targetStyle, elOptionStyle, el.style, isInit);
  135. });
  136. }
  137. if (el.type === 'text' && elOption.style) {
  138. var targetStyle = targetProps.style = {};
  139. zrUtil.each(['x', 'y'], function (prop) {
  140. prepareStyleTransition(prop, targetStyle, elOptionStyle, el.style, isInit);
  141. }); // Compatible with previous: both support
  142. // textFill and fill, textStroke and stroke in 'text' element.
  143. !elOptionStyle.hasOwnProperty('textFill') && elOptionStyle.fill && (elOptionStyle.textFill = elOptionStyle.fill);
  144. !elOptionStyle.hasOwnProperty('textStroke') && elOptionStyle.stroke && (elOptionStyle.textStroke = elOptionStyle.stroke);
  145. }
  146. if (el.type !== 'group') {
  147. el.useStyle(elOptionStyle); // Init animation.
  148. if (isInit) {
  149. el.style.opacity = 0;
  150. var targetOpacity = elOptionStyle.opacity;
  151. targetOpacity == null && (targetOpacity = 1);
  152. graphicUtil.initProps(el, {
  153. style: {
  154. opacity: targetOpacity
  155. }
  156. }, animatableModel, dataIndex);
  157. }
  158. }
  159. if (isInit) {
  160. el.attr(targetProps);
  161. } else {
  162. graphicUtil.updateProps(el, targetProps, animatableModel, dataIndex);
  163. } // z2 must not be null/undefined, otherwise sort error may occur.
  164. el.attr({
  165. z2: elOption.z2 || 0,
  166. silent: elOption.silent
  167. });
  168. elOption.styleEmphasis !== false && graphicUtil.setHoverStyle(el, elOption.styleEmphasis);
  169. }
  170. function prepareStyleTransition(prop, targetStyle, elOptionStyle, oldElStyle, isInit) {
  171. if (elOptionStyle[prop] != null && !isInit) {
  172. targetStyle[prop] = elOptionStyle[prop];
  173. elOptionStyle[prop] = oldElStyle[prop];
  174. }
  175. }
  176. function makeRenderItem(customSeries, data, ecModel, api) {
  177. var renderItem = customSeries.get('renderItem');
  178. var coordSys = customSeries.coordinateSystem;
  179. var prepareResult = {};
  180. if (coordSys) {
  181. prepareResult = coordSys.prepareCustoms ? coordSys.prepareCustoms() : prepareCustoms[coordSys.type](coordSys);
  182. }
  183. var userAPI = zrUtil.defaults({
  184. getWidth: api.getWidth,
  185. getHeight: api.getHeight,
  186. getZr: api.getZr,
  187. getDevicePixelRatio: api.getDevicePixelRatio,
  188. value: value,
  189. style: style,
  190. styleEmphasis: styleEmphasis,
  191. visual: visual,
  192. barLayout: barLayout,
  193. currentSeriesIndices: currentSeriesIndices,
  194. font: font
  195. }, prepareResult.api || {});
  196. var userParams = {
  197. context: {},
  198. seriesId: customSeries.id,
  199. seriesName: customSeries.name,
  200. seriesIndex: customSeries.seriesIndex,
  201. coordSys: prepareResult.coordSys,
  202. dataInsideLength: data.count(),
  203. encode: wrapEncodeDef(customSeries.getData())
  204. }; // Do not support call `api` asynchronously without dataIndexInside input.
  205. var currDataIndexInside;
  206. var currDirty = true;
  207. var currItemModel;
  208. var currLabelNormalModel;
  209. var currLabelEmphasisModel;
  210. var currLabelValueDim;
  211. var currVisualColor;
  212. return function (dataIndexInside) {
  213. currDataIndexInside = dataIndexInside;
  214. currDirty = true;
  215. return renderItem && renderItem(zrUtil.defaults({
  216. dataIndexInside: dataIndexInside,
  217. dataIndex: data.getRawIndex(dataIndexInside)
  218. }, userParams), userAPI) || {};
  219. }; // Do not update cache until api called.
  220. function updateCache(dataIndexInside) {
  221. dataIndexInside == null && (dataIndexInside = currDataIndexInside);
  222. if (currDirty) {
  223. currItemModel = data.getItemModel(dataIndexInside);
  224. currLabelNormalModel = currItemModel.getModel(LABEL_NORMAL);
  225. currLabelEmphasisModel = currItemModel.getModel(LABEL_EMPHASIS);
  226. currLabelValueDim = findLabelValueDim(data);
  227. currVisualColor = data.getItemVisual(dataIndexInside, 'color');
  228. currDirty = false;
  229. }
  230. }
  231. /**
  232. * @public
  233. * @param {number|string} dim
  234. * @param {number} [dataIndexInside=currDataIndexInside]
  235. * @return {number|string} value
  236. */
  237. function value(dim, dataIndexInside) {
  238. dataIndexInside == null && (dataIndexInside = currDataIndexInside);
  239. return data.get(data.getDimension(dim || 0), dataIndexInside);
  240. }
  241. /**
  242. * By default, `visual` is applied to style (to support visualMap).
  243. * `visual.color` is applied at `fill`. If user want apply visual.color on `stroke`,
  244. * it can be implemented as:
  245. * `api.style({stroke: api.visual('color'), fill: null})`;
  246. * @public
  247. * @param {Object} [extra]
  248. * @param {number} [dataIndexInside=currDataIndexInside]
  249. */
  250. function style(extra, dataIndexInside) {
  251. dataIndexInside == null && (dataIndexInside = currDataIndexInside);
  252. updateCache(dataIndexInside);
  253. var itemStyle = currItemModel.getModel(ITEM_STYLE_NORMAL_PATH).getItemStyle();
  254. currVisualColor != null && (itemStyle.fill = currVisualColor);
  255. var opacity = data.getItemVisual(dataIndexInside, 'opacity');
  256. opacity != null && (itemStyle.opacity = opacity);
  257. if (currLabelValueDim != null) {
  258. graphicUtil.setTextStyle(itemStyle, currLabelNormalModel, null, {
  259. autoColor: currVisualColor,
  260. isRectText: true
  261. });
  262. itemStyle.text = currLabelNormalModel.getShallow('show') ? zrUtil.retrieve2(customSeries.getFormattedLabel(dataIndexInside, 'normal'), data.get(currLabelValueDim, dataIndexInside)) : null;
  263. }
  264. extra && zrUtil.extend(itemStyle, extra);
  265. return itemStyle;
  266. }
  267. /**
  268. * @public
  269. * @param {Object} [extra]
  270. * @param {number} [dataIndexInside=currDataIndexInside]
  271. */
  272. function styleEmphasis(extra, dataIndexInside) {
  273. dataIndexInside == null && (dataIndexInside = currDataIndexInside);
  274. updateCache(dataIndexInside);
  275. var itemStyle = currItemModel.getModel(ITEM_STYLE_EMPHASIS_PATH).getItemStyle();
  276. if (currLabelValueDim != null) {
  277. graphicUtil.setTextStyle(itemStyle, currLabelEmphasisModel, null, {
  278. isRectText: true
  279. }, true);
  280. itemStyle.text = currLabelEmphasisModel.getShallow('show') ? zrUtil.retrieve3(customSeries.getFormattedLabel(dataIndexInside, 'emphasis'), customSeries.getFormattedLabel(dataIndexInside, 'normal'), data.get(currLabelValueDim, dataIndexInside)) : null;
  281. }
  282. extra && zrUtil.extend(itemStyle, extra);
  283. return itemStyle;
  284. }
  285. /**
  286. * @public
  287. * @param {string} visualType
  288. * @param {number} [dataIndexInside=currDataIndexInside]
  289. */
  290. function visual(visualType, dataIndexInside) {
  291. dataIndexInside == null && (dataIndexInside = currDataIndexInside);
  292. return data.getItemVisual(dataIndexInside, visualType);
  293. }
  294. /**
  295. * @public
  296. * @param {number} opt.count Positive interger.
  297. * @param {number} [opt.barWidth]
  298. * @param {number} [opt.barMaxWidth]
  299. * @param {number} [opt.barGap]
  300. * @param {number} [opt.barCategoryGap]
  301. * @return {Object} {width, offset, offsetCenter} is not support, return undefined.
  302. */
  303. function barLayout(opt) {
  304. if (coordSys.getBaseAxis) {
  305. var baseAxis = coordSys.getBaseAxis();
  306. return barGrid.getLayoutOnAxis(zrUtil.defaults({
  307. axis: baseAxis
  308. }, opt), api);
  309. }
  310. }
  311. /**
  312. * @public
  313. * @return {Array.<number>}
  314. */
  315. function currentSeriesIndices() {
  316. return ecModel.getCurrentSeriesIndices();
  317. }
  318. /**
  319. * @public
  320. * @param {Object} opt
  321. * @param {string} [opt.fontStyle]
  322. * @param {number} [opt.fontWeight]
  323. * @param {number} [opt.fontSize]
  324. * @param {string} [opt.fontFamily]
  325. * @return {string} font string
  326. */
  327. function font(opt) {
  328. return graphicUtil.getFont(opt, ecModel);
  329. }
  330. }
  331. function wrapEncodeDef(data) {
  332. var encodeDef = {};
  333. zrUtil.each(data.dimensions, function (dimName, dataDimIndex) {
  334. var dimInfo = data.getDimensionInfo(dimName);
  335. if (!dimInfo.isExtraCoord) {
  336. var coordDim = dimInfo.coordDim;
  337. var dataDims = encodeDef[coordDim] = encodeDef[coordDim] || [];
  338. dataDims[dimInfo.coordDimIndex] = dataDimIndex;
  339. }
  340. });
  341. return encodeDef;
  342. }
  343. function createOrUpdate(el, dataIndex, elOption, animatableModel, group, data) {
  344. el = doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data);
  345. el && data.setItemGraphicEl(dataIndex, el);
  346. }
  347. function doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data) {
  348. var elOptionType = elOption.type;
  349. if (el && elOptionType !== el.__customGraphicType && (elOptionType !== 'path' || elOption.pathData !== el.__customPathData) && (elOptionType !== 'image' || elOption.style.image !== el.__customImagePath) && (elOptionType !== 'text' || elOption.style.text !== el.__customText)) {
  350. group.remove(el);
  351. el = null;
  352. } // `elOption.type` is undefined when `renderItem` returns nothing.
  353. if (elOptionType == null) {
  354. return;
  355. }
  356. var isInit = !el;
  357. !el && (el = createEl(elOption));
  358. updateEl(el, dataIndex, elOption, animatableModel, data, isInit);
  359. if (elOptionType === 'group') {
  360. var oldChildren = el.children() || [];
  361. var newChildren = elOption.children || [];
  362. if (elOption.diffChildrenByName) {
  363. // lower performance.
  364. diffGroupChildren({
  365. oldChildren: oldChildren,
  366. newChildren: newChildren,
  367. dataIndex: dataIndex,
  368. animatableModel: animatableModel,
  369. group: el,
  370. data: data
  371. });
  372. } else {
  373. // better performance.
  374. var index = 0;
  375. for (; index < newChildren.length; index++) {
  376. doCreateOrUpdate(el.childAt(index), dataIndex, newChildren[index], animatableModel, el, data);
  377. }
  378. for (; index < oldChildren.length; index++) {
  379. oldChildren[index] && el.remove(oldChildren[index]);
  380. }
  381. }
  382. }
  383. group.add(el);
  384. return el;
  385. }
  386. function diffGroupChildren(context) {
  387. new DataDiffer(context.oldChildren, context.newChildren, getKey, getKey, context).add(processAddUpdate).update(processAddUpdate).remove(processRemove).execute();
  388. }
  389. function getKey(item, idx) {
  390. var name = item && item.name;
  391. return name != null ? name : GROUP_DIFF_PREFIX + idx;
  392. }
  393. function processAddUpdate(newIndex, oldIndex) {
  394. var context = this.context;
  395. var childOption = newIndex != null ? context.newChildren[newIndex] : null;
  396. var child = oldIndex != null ? context.oldChildren[oldIndex] : null;
  397. doCreateOrUpdate(child, context.dataIndex, childOption, context.animatableModel, context.group, context.data);
  398. }
  399. function processRemove(oldIndex) {
  400. var context = this.context;
  401. var child = context.oldChildren[oldIndex];
  402. child && context.group.remove(child);
  403. }