PiecewiseModel.js 16 KB

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