PiecewiseModel.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. var _config = require("../../config");
  2. var __DEV__ = _config.__DEV__;
  3. var zrUtil = require("zrender/lib/core/util");
  4. var VisualMapModel = require("./VisualMapModel");
  5. var VisualMapping = require("../../visual/VisualMapping");
  6. var visualDefault = require("../../visual/visualDefault");
  7. var _number = require("../../util/number");
  8. var reformIntervals = _number.reformIntervals;
  9. var PiecewiseModel = VisualMapModel.extend({
  10. type: 'visualMap.piecewise',
  11. /**
  12. * Order Rule:
  13. *
  14. * option.categories / option.pieces / option.text / option.selected:
  15. * If !option.inverse,
  16. * Order when vertical: ['top', ..., 'bottom'].
  17. * Order when horizontal: ['left', ..., 'right'].
  18. * If option.inverse, the meaning of
  19. * the order should be reversed.
  20. *
  21. * this._pieceList:
  22. * The order is always [low, ..., high].
  23. *
  24. * Mapping from location to low-high:
  25. * If !option.inverse
  26. * When vertical, top is high.
  27. * When horizontal, right is high.
  28. * If option.inverse, reverse.
  29. */
  30. /**
  31. * @protected
  32. */
  33. defaultOption: {
  34. selected: null,
  35. // Object. If not specified, means selected.
  36. // When pieces and splitNumber: {'0': true, '5': true}
  37. // When categories: {'cate1': false, 'cate3': true}
  38. // When selected === false, means all unselected.
  39. minOpen: false,
  40. // Whether include values that smaller than `min`.
  41. maxOpen: false,
  42. // Whether include values that bigger than `max`.
  43. align: 'auto',
  44. // 'auto', 'left', 'right'
  45. itemWidth: 20,
  46. // When put the controller vertically, it is the length of
  47. // horizontal side of each item. Otherwise, vertical side.
  48. itemHeight: 14,
  49. // When put the controller vertically, it is the length of
  50. // vertical side of each item. Otherwise, horizontal side.
  51. itemSymbol: 'roundRect',
  52. pieceList: null,
  53. // Each item is Object, with some of those attrs:
  54. // {min, max, lt, gt, lte, gte, value,
  55. // color, colorSaturation, colorAlpha, opacity,
  56. // symbol, symbolSize}, which customize the range or visual
  57. // coding of the certain piece. Besides, see "Order Rule".
  58. categories: null,
  59. // category names, like: ['some1', 'some2', 'some3'].
  60. // Attr min/max are ignored when categories set. See "Order Rule"
  61. splitNumber: 5,
  62. // If set to 5, auto split five pieces equally.
  63. // If set to 0 and component type not set, component type will be
  64. // determined as "continuous". (It is less reasonable but for ec2
  65. // compatibility, see echarts/component/visualMap/typeDefaulter)
  66. selectedMode: 'multiple',
  67. // Can be 'multiple' or 'single'.
  68. itemGap: 10,
  69. // The gap between two items, in px.
  70. hoverLink: true,
  71. // Enable hover highlight.
  72. showLabel: null // By default, when text is used, label will hide (the logic
  73. // is remained for compatibility reason)
  74. },
  75. /**
  76. * @override
  77. */
  78. optionUpdated: function (newOption, isInit) {
  79. PiecewiseModel.superApply(this, 'optionUpdated', arguments);
  80. /**
  81. * The order is always [low, ..., high].
  82. * [{text: string, interval: Array.<number>}, ...]
  83. * @private
  84. * @type {Array.<Object>}
  85. */
  86. this._pieceList = [];
  87. this.resetExtent();
  88. /**
  89. * 'pieces', 'categories', 'splitNumber'
  90. * @type {string}
  91. */
  92. var mode = this._mode = this._determineMode();
  93. resetMethods[this._mode].call(this);
  94. this._resetSelected(newOption, isInit);
  95. var categories = this.option.categories;
  96. this.resetVisual(function (mappingOption, state) {
  97. if (mode === 'categories') {
  98. mappingOption.mappingMethod = 'category';
  99. mappingOption.categories = zrUtil.clone(categories);
  100. } else {
  101. mappingOption.dataExtent = this.getExtent();
  102. mappingOption.mappingMethod = 'piecewise';
  103. mappingOption.pieceList = zrUtil.map(this._pieceList, function (piece) {
  104. var piece = zrUtil.clone(piece);
  105. if (state !== 'inRange') {
  106. // FIXME
  107. // outOfRange do not support special visual in pieces.
  108. piece.visual = null;
  109. }
  110. return piece;
  111. });
  112. }
  113. });
  114. },
  115. /**
  116. * @protected
  117. * @override
  118. */
  119. completeVisualOption: function () {
  120. // Consider this case:
  121. // visualMap: {
  122. // pieces: [{symbol: 'circle', lt: 0}, {symbol: 'rect', gte: 0}]
  123. // }
  124. // where no inRange/outOfRange set but only pieces. So we should make
  125. // default inRange/outOfRange for this case, otherwise visuals that only
  126. // appear in `pieces` will not be taken into account in visual encoding.
  127. var option = this.option;
  128. var visualTypesInPieces = {};
  129. var visualTypes = VisualMapping.listVisualTypes();
  130. var isCategory = this.isCategory();
  131. zrUtil.each(option.pieces, function (piece) {
  132. zrUtil.each(visualTypes, function (visualType) {
  133. if (piece.hasOwnProperty(visualType)) {
  134. visualTypesInPieces[visualType] = 1;
  135. }
  136. });
  137. });
  138. zrUtil.each(visualTypesInPieces, function (v, visualType) {
  139. var exists = 0;
  140. zrUtil.each(this.stateList, function (state) {
  141. exists |= has(option, state, visualType) || has(option.target, state, visualType);
  142. }, this);
  143. !exists && zrUtil.each(this.stateList, function (state) {
  144. (option[state] || (option[state] = {}))[visualType] = visualDefault.get(visualType, state === 'inRange' ? 'active' : 'inactive', isCategory);
  145. });
  146. }, this);
  147. function has(obj, state, visualType) {
  148. return obj && obj[state] && (zrUtil.isObject(obj[state]) ? obj[state].hasOwnProperty(visualType) : obj[state] === visualType // e.g., inRange: 'symbol'
  149. );
  150. }
  151. VisualMapModel.prototype.completeVisualOption.apply(this, arguments);
  152. },
  153. _resetSelected: function (newOption, isInit) {
  154. var thisOption = this.option;
  155. var pieceList = this._pieceList; // Selected do not merge but all override.
  156. var selected = (isInit ? thisOption : newOption).selected || {};
  157. thisOption.selected = selected; // Consider 'not specified' means true.
  158. zrUtil.each(pieceList, function (piece, index) {
  159. var key = this.getSelectedMapKey(piece);
  160. if (!selected.hasOwnProperty(key)) {
  161. selected[key] = true;
  162. }
  163. }, this);
  164. if (thisOption.selectedMode === 'single') {
  165. // Ensure there is only one selected.
  166. var hasSel = false;
  167. zrUtil.each(pieceList, function (piece, index) {
  168. var key = this.getSelectedMapKey(piece);
  169. if (selected[key]) {
  170. hasSel ? selected[key] = false : hasSel = true;
  171. }
  172. }, this);
  173. } // thisOption.selectedMode === 'multiple', default: all selected.
  174. },
  175. /**
  176. * @public
  177. */
  178. getSelectedMapKey: function (piece) {
  179. return this._mode === 'categories' ? piece.value + '' : piece.index + '';
  180. },
  181. /**
  182. * @public
  183. */
  184. getPieceList: function () {
  185. return this._pieceList;
  186. },
  187. /**
  188. * @private
  189. * @return {string}
  190. */
  191. _determineMode: function () {
  192. var option = this.option;
  193. return option.pieces && option.pieces.length > 0 ? 'pieces' : this.option.categories ? 'categories' : 'splitNumber';
  194. },
  195. /**
  196. * @public
  197. * @override
  198. */
  199. setSelected: function (selected) {
  200. this.option.selected = zrUtil.clone(selected);
  201. },
  202. /**
  203. * @public
  204. * @override
  205. */
  206. getValueState: function (value) {
  207. var index = VisualMapping.findPieceIndex(value, this._pieceList);
  208. return index != null ? this.option.selected[this.getSelectedMapKey(this._pieceList[index])] ? 'inRange' : 'outOfRange' : 'outOfRange';
  209. },
  210. /**
  211. * @public
  212. * @params {number} pieceIndex piece index in visualMapModel.getPieceList()
  213. * @return {Array.<Object>} [{seriesId, dataIndices: <Array.<number>>}, ...]
  214. */
  215. findTargetDataIndices: function (pieceIndex) {
  216. var result = [];
  217. this.eachTargetSeries(function (seriesModel) {
  218. var dataIndices = [];
  219. var data = seriesModel.getData();
  220. data.each(this.getDataDimension(data), function (value, dataIndex) {
  221. // Should always base on model pieceList, because it is order sensitive.
  222. var pIdx = VisualMapping.findPieceIndex(value, this._pieceList);
  223. pIdx === pieceIndex && dataIndices.push(dataIndex);
  224. }, true, this);
  225. result.push({
  226. seriesId: seriesModel.id,
  227. dataIndex: dataIndices
  228. });
  229. }, this);
  230. return result;
  231. },
  232. /**
  233. * @private
  234. * @param {Object} piece piece.value or piece.interval is required.
  235. * @return {number} Can be Infinity or -Infinity
  236. */
  237. getRepresentValue: function (piece) {
  238. var representValue;
  239. if (this.isCategory()) {
  240. representValue = piece.value;
  241. } else {
  242. if (piece.value != null) {
  243. representValue = piece.value;
  244. } else {
  245. var pieceInterval = piece.interval || [];
  246. representValue = pieceInterval[0] === -Infinity && pieceInterval[1] === Infinity ? 0 : (pieceInterval[0] + pieceInterval[1]) / 2;
  247. }
  248. }
  249. return representValue;
  250. },
  251. getVisualMeta: function (getColorVisual) {
  252. // Do not support category. (category axis is ordinal, numerical)
  253. if (this.isCategory()) {
  254. return;
  255. }
  256. var stops = [];
  257. var outerColors = [];
  258. var visualMapModel = this;
  259. function setStop(interval, valueState) {
  260. var representValue = visualMapModel.getRepresentValue({
  261. interval: interval
  262. });
  263. if (!valueState) {
  264. valueState = visualMapModel.getValueState(representValue);
  265. }
  266. var color = getColorVisual(representValue, valueState);
  267. if (interval[0] === -Infinity) {
  268. outerColors[0] = color;
  269. } else if (interval[1] === Infinity) {
  270. outerColors[1] = color;
  271. } else {
  272. stops.push({
  273. value: interval[0],
  274. color: color
  275. }, {
  276. value: interval[1],
  277. color: color
  278. });
  279. }
  280. } // Suplement
  281. var pieceList = this._pieceList.slice();
  282. if (!pieceList.length) {
  283. pieceList.push({
  284. interval: [-Infinity, Infinity]
  285. });
  286. } else {
  287. var edge = pieceList[0].interval[0];
  288. edge !== -Infinity && pieceList.unshift({
  289. interval: [-Infinity, edge]
  290. });
  291. edge = pieceList[pieceList.length - 1].interval[1];
  292. edge !== Infinity && pieceList.push({
  293. interval: [edge, Infinity]
  294. });
  295. }
  296. var curr = -Infinity;
  297. zrUtil.each(pieceList, function (piece) {
  298. var interval = piece.interval;
  299. if (interval) {
  300. // Fulfill gap.
  301. interval[0] > curr && setStop([curr, interval[0]], 'outOfRange');
  302. setStop(interval.slice());
  303. curr = interval[1];
  304. }
  305. }, this);
  306. return {
  307. stops: stops,
  308. outerColors: outerColors
  309. };
  310. }
  311. });
  312. /**
  313. * Key is this._mode
  314. * @type {Object}
  315. * @this {module:echarts/component/viusalMap/PiecewiseMode}
  316. */
  317. var resetMethods = {
  318. splitNumber: function () {
  319. var thisOption = this.option;
  320. var pieceList = this._pieceList;
  321. var precision = Math.min(thisOption.precision, 20);
  322. var dataExtent = this.getExtent();
  323. var splitNumber = thisOption.splitNumber;
  324. splitNumber = Math.max(parseInt(splitNumber, 10), 1);
  325. thisOption.splitNumber = splitNumber;
  326. var splitStep = (dataExtent[1] - dataExtent[0]) / splitNumber; // Precision auto-adaption
  327. while (+splitStep.toFixed(precision) !== splitStep && precision < 5) {
  328. precision++;
  329. }
  330. thisOption.precision = precision;
  331. splitStep = +splitStep.toFixed(precision);
  332. var index = 0;
  333. if (thisOption.minOpen) {
  334. pieceList.push({
  335. index: index++,
  336. interval: [-Infinity, dataExtent[0]],
  337. close: [0, 0]
  338. });
  339. }
  340. for (var curr = dataExtent[0], len = index + splitNumber; index < len; curr += splitStep) {
  341. var max = index === splitNumber - 1 ? dataExtent[1] : curr + splitStep;
  342. pieceList.push({
  343. index: index++,
  344. interval: [curr, max],
  345. close: [1, 1]
  346. });
  347. }
  348. if (thisOption.maxOpen) {
  349. pieceList.push({
  350. index: index++,
  351. interval: [dataExtent[1], Infinity],
  352. close: [0, 0]
  353. });
  354. }
  355. reformIntervals(pieceList);
  356. zrUtil.each(pieceList, function (piece) {
  357. piece.text = this.formatValueText(piece.interval);
  358. }, this);
  359. },
  360. categories: function () {
  361. var thisOption = this.option;
  362. zrUtil.each(thisOption.categories, function (cate) {
  363. // FIXME category模式也使用pieceList,但在visualMapping中不是使用pieceList。
  364. // 是否改一致。
  365. this._pieceList.push({
  366. text: this.formatValueText(cate, true),
  367. value: cate
  368. });
  369. }, this); // See "Order Rule".
  370. normalizeReverse(thisOption, this._pieceList);
  371. },
  372. pieces: function () {
  373. var thisOption = this.option;
  374. var pieceList = this._pieceList;
  375. zrUtil.each(thisOption.pieces, function (pieceListItem, index) {
  376. if (!zrUtil.isObject(pieceListItem)) {
  377. pieceListItem = {
  378. value: pieceListItem
  379. };
  380. }
  381. var item = {
  382. text: '',
  383. index: index
  384. };
  385. if (pieceListItem.label != null) {
  386. item.text = pieceListItem.label;
  387. }
  388. if (pieceListItem.hasOwnProperty('value')) {
  389. var value = item.value = pieceListItem.value;
  390. item.interval = [value, value];
  391. item.close = [1, 1];
  392. } else {
  393. // `min` `max` is legacy option.
  394. // `lt` `gt` `lte` `gte` is recommanded.
  395. var interval = item.interval = [];
  396. var close = item.close = [0, 0];
  397. var closeList = [1, 0, 1];
  398. var infinityList = [-Infinity, Infinity];
  399. var useMinMax = [];
  400. for (var lg = 0; lg < 2; lg++) {
  401. var names = [['gte', 'gt', 'min'], ['lte', 'lt', 'max']][lg];
  402. for (var i = 0; i < 3 && interval[lg] == null; i++) {
  403. interval[lg] = pieceListItem[names[i]];
  404. close[lg] = closeList[i];
  405. useMinMax[lg] = i === 2;
  406. }
  407. interval[lg] == null && (interval[lg] = infinityList[lg]);
  408. }
  409. useMinMax[0] && interval[1] === Infinity && (close[0] = 0);
  410. useMinMax[1] && interval[0] === -Infinity && (close[1] = 0);
  411. if (interval[0] === interval[1] && close[0] && close[1]) {
  412. // Consider: [{min: 5, max: 5, visual: {...}}, {min: 0, max: 5}],
  413. // we use value to lift the priority when min === max
  414. item.value = interval[0];
  415. }
  416. }
  417. item.visual = VisualMapping.retrieveVisuals(pieceListItem);
  418. pieceList.push(item);
  419. }, this); // See "Order Rule".
  420. normalizeReverse(thisOption, pieceList); // Only pieces
  421. reformIntervals(pieceList);
  422. zrUtil.each(pieceList, function (piece) {
  423. var close = piece.close;
  424. var edgeSymbols = [['<', '≤'][close[1]], ['>', '≥'][close[0]]];
  425. piece.text = piece.text || this.formatValueText(piece.value != null ? piece.value : piece.interval, false, edgeSymbols);
  426. }, this);
  427. }
  428. };
  429. function normalizeReverse(thisOption, pieceList) {
  430. var inverse = thisOption.inverse;
  431. if (thisOption.orient === 'vertical' ? !inverse : inverse) {
  432. pieceList.reverse();
  433. }
  434. }
  435. var _default = PiecewiseModel;
  436. module.exports = _default;