VisualMapModel.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. var echarts = require("../../echarts");
  2. var zrUtil = require("zrender/lib/core/util");
  3. var env = require("zrender/lib/core/env");
  4. var visualDefault = require("../../visual/visualDefault");
  5. var VisualMapping = require("../../visual/VisualMapping");
  6. var visualSolution = require("../../visual/visualSolution");
  7. var modelUtil = require("../../util/model");
  8. var numberUtil = require("../../util/number");
  9. var mapVisual = VisualMapping.mapVisual;
  10. var eachVisual = VisualMapping.eachVisual;
  11. var isArray = zrUtil.isArray;
  12. var each = zrUtil.each;
  13. var asc = numberUtil.asc;
  14. var linearMap = numberUtil.linearMap;
  15. var noop = zrUtil.noop;
  16. var DEFAULT_COLOR = ['#f6efa6', '#d88273', '#bf444c'];
  17. var VisualMapModel = echarts.extendComponentModel({
  18. type: 'visualMap',
  19. dependencies: ['series'],
  20. /**
  21. * @readOnly
  22. * @type {Array.<string>}
  23. */
  24. stateList: ['inRange', 'outOfRange'],
  25. /**
  26. * @readOnly
  27. * @type {Array.<string>}
  28. */
  29. replacableOptionKeys: ['inRange', 'outOfRange', 'target', 'controller', 'color'],
  30. /**
  31. * [lowerBound, upperBound]
  32. *
  33. * @readOnly
  34. * @type {Array.<number>}
  35. */
  36. dataBound: [-Infinity, Infinity],
  37. /**
  38. * @readOnly
  39. * @type {string|Object}
  40. */
  41. layoutMode: {
  42. type: 'box',
  43. ignoreSize: true
  44. },
  45. /**
  46. * @protected
  47. */
  48. defaultOption: {
  49. show: true,
  50. zlevel: 0,
  51. z: 4,
  52. seriesIndex: 'all',
  53. // 'all' or null/undefined: all series.
  54. // A number or an array of number: the specified series.
  55. // set min: 0, max: 200, only for campatible with ec2.
  56. // In fact min max should not have default value.
  57. min: 0,
  58. // min value, must specified if pieces is not specified.
  59. max: 200,
  60. // max value, must specified if pieces is not specified.
  61. dimension: null,
  62. inRange: null,
  63. // 'color', 'colorHue', 'colorSaturation', 'colorLightness', 'colorAlpha',
  64. // 'symbol', 'symbolSize'
  65. outOfRange: null,
  66. // 'color', 'colorHue', 'colorSaturation',
  67. // 'colorLightness', 'colorAlpha',
  68. // 'symbol', 'symbolSize'
  69. left: 0,
  70. // 'center' ¦ 'left' ¦ 'right' ¦ {number} (px)
  71. right: null,
  72. // The same as left.
  73. top: null,
  74. // 'top' ¦ 'bottom' ¦ 'center' ¦ {number} (px)
  75. bottom: 0,
  76. // The same as top.
  77. itemWidth: null,
  78. itemHeight: null,
  79. inverse: false,
  80. orient: 'vertical',
  81. // 'horizontal' ¦ 'vertical'
  82. backgroundColor: 'rgba(0,0,0,0)',
  83. borderColor: '#ccc',
  84. // 值域边框颜色
  85. contentColor: '#5793f3',
  86. inactiveColor: '#aaa',
  87. borderWidth: 0,
  88. // 值域边框线宽,单位px,默认为0(无边框)
  89. padding: 5,
  90. // 值域内边距,单位px,默认各方向内边距为5,
  91. // 接受数组分别设定上右下左边距,同css
  92. textGap: 10,
  93. //
  94. precision: 0,
  95. // 小数精度,默认为0,无小数点
  96. color: null,
  97. //颜色(deprecated,兼容ec2,顺序同pieces,不同于inRange/outOfRange)
  98. formatter: null,
  99. text: null,
  100. // 文本,如['高', '低'],兼容ec2,text[0]对应高值,text[1]对应低值
  101. textStyle: {
  102. color: '#333' // 值域文字颜色
  103. }
  104. },
  105. /**
  106. * @protected
  107. */
  108. init: function (option, parentModel, ecModel) {
  109. /**
  110. * @private
  111. * @type {Array.<number>}
  112. */
  113. this._dataExtent;
  114. /**
  115. * @readOnly
  116. */
  117. this.targetVisuals = {};
  118. /**
  119. * @readOnly
  120. */
  121. this.controllerVisuals = {};
  122. /**
  123. * @readOnly
  124. */
  125. this.textStyleModel;
  126. /**
  127. * [width, height]
  128. * @readOnly
  129. * @type {Array.<number>}
  130. */
  131. this.itemSize;
  132. this.mergeDefaultAndTheme(option, ecModel);
  133. },
  134. /**
  135. * @protected
  136. */
  137. optionUpdated: function (newOption, isInit) {
  138. var thisOption = this.option; // FIXME
  139. // necessary?
  140. // Disable realtime view update if canvas is not supported.
  141. if (!env.canvasSupported) {
  142. thisOption.realtime = false;
  143. }
  144. !isInit && visualSolution.replaceVisualOption(thisOption, newOption, this.replacableOptionKeys);
  145. this.textStyleModel = this.getModel('textStyle');
  146. this.resetItemSize();
  147. this.completeVisualOption();
  148. },
  149. /**
  150. * @protected
  151. */
  152. resetVisual: function (supplementVisualOption) {
  153. var stateList = this.stateList;
  154. supplementVisualOption = zrUtil.bind(supplementVisualOption, this);
  155. this.controllerVisuals = visualSolution.createVisualMappings(this.option.controller, stateList, supplementVisualOption);
  156. this.targetVisuals = visualSolution.createVisualMappings(this.option.target, stateList, supplementVisualOption);
  157. },
  158. /**
  159. * @protected
  160. * @return {Array.<number>} An array of series indices.
  161. */
  162. getTargetSeriesIndices: function () {
  163. var optionSeriesIndex = this.option.seriesIndex;
  164. var seriesIndices = [];
  165. if (optionSeriesIndex == null || optionSeriesIndex === 'all') {
  166. this.ecModel.eachSeries(function (seriesModel, index) {
  167. seriesIndices.push(index);
  168. });
  169. } else {
  170. seriesIndices = modelUtil.normalizeToArray(optionSeriesIndex);
  171. }
  172. return seriesIndices;
  173. },
  174. /**
  175. * @public
  176. */
  177. eachTargetSeries: function (callback, context) {
  178. zrUtil.each(this.getTargetSeriesIndices(), function (seriesIndex) {
  179. callback.call(context, this.ecModel.getSeriesByIndex(seriesIndex));
  180. }, this);
  181. },
  182. /**
  183. * @pubilc
  184. */
  185. isTargetSeries: function (seriesModel) {
  186. var is = false;
  187. this.eachTargetSeries(function (model) {
  188. model === seriesModel && (is = true);
  189. });
  190. return is;
  191. },
  192. /**
  193. * @example
  194. * this.formatValueText(someVal); // format single numeric value to text.
  195. * this.formatValueText(someVal, true); // format single category value to text.
  196. * this.formatValueText([min, max]); // format numeric min-max to text.
  197. * this.formatValueText([this.dataBound[0], max]); // using data lower bound.
  198. * this.formatValueText([min, this.dataBound[1]]); // using data upper bound.
  199. *
  200. * @param {number|Array.<number>} value Real value, or this.dataBound[0 or 1].
  201. * @param {boolean} [isCategory=false] Only available when value is number.
  202. * @param {Array.<string>} edgeSymbols Open-close symbol when value is interval.
  203. * @return {string}
  204. * @protected
  205. */
  206. formatValueText: function (value, isCategory, edgeSymbols) {
  207. var option = this.option;
  208. var precision = option.precision;
  209. var dataBound = this.dataBound;
  210. var formatter = option.formatter;
  211. var isMinMax;
  212. var textValue;
  213. edgeSymbols = edgeSymbols || ['<', '>'];
  214. if (zrUtil.isArray(value)) {
  215. value = value.slice();
  216. isMinMax = true;
  217. }
  218. textValue = isCategory ? value : isMinMax ? [toFixed(value[0]), toFixed(value[1])] : toFixed(value);
  219. if (zrUtil.isString(formatter)) {
  220. return formatter.replace('{value}', isMinMax ? textValue[0] : textValue).replace('{value2}', isMinMax ? textValue[1] : textValue);
  221. } else if (zrUtil.isFunction(formatter)) {
  222. return isMinMax ? formatter(value[0], value[1]) : formatter(value);
  223. }
  224. if (isMinMax) {
  225. if (value[0] === dataBound[0]) {
  226. return edgeSymbols[0] + ' ' + textValue[1];
  227. } else if (value[1] === dataBound[1]) {
  228. return edgeSymbols[1] + ' ' + textValue[0];
  229. } else {
  230. return textValue[0] + ' - ' + textValue[1];
  231. }
  232. } else {
  233. // Format single value (includes category case).
  234. return textValue;
  235. }
  236. function toFixed(val) {
  237. return val === dataBound[0] ? 'min' : val === dataBound[1] ? 'max' : (+val).toFixed(Math.min(precision, 20));
  238. }
  239. },
  240. /**
  241. * @protected
  242. */
  243. resetExtent: function () {
  244. var thisOption = this.option; // Can not calculate data extent by data here.
  245. // Because series and data may be modified in processing stage.
  246. // So we do not support the feature "auto min/max".
  247. var extent = asc([thisOption.min, thisOption.max]);
  248. this._dataExtent = extent;
  249. },
  250. /**
  251. * @public
  252. * @param {module:echarts/data/List} list
  253. * @return {string} Concrete dimention. If return null/undefined,
  254. * no dimension used.
  255. */
  256. getDataDimension: function (list) {
  257. var optDim = this.option.dimension;
  258. return optDim != null ? optDim : list.dimensions.length - 1;
  259. },
  260. /**
  261. * @public
  262. * @override
  263. */
  264. getExtent: function () {
  265. return this._dataExtent.slice();
  266. },
  267. /**
  268. * @protected
  269. */
  270. completeVisualOption: function () {
  271. var thisOption = this.option;
  272. var base = {
  273. inRange: thisOption.inRange,
  274. outOfRange: thisOption.outOfRange
  275. };
  276. var target = thisOption.target || (thisOption.target = {});
  277. var controller = thisOption.controller || (thisOption.controller = {});
  278. zrUtil.merge(target, base); // Do not override
  279. zrUtil.merge(controller, base); // Do not override
  280. var isCategory = this.isCategory();
  281. completeSingle.call(this, target);
  282. completeSingle.call(this, controller);
  283. completeInactive.call(this, target, 'inRange', 'outOfRange'); // completeInactive.call(this, target, 'outOfRange', 'inRange');
  284. completeController.call(this, controller);
  285. function completeSingle(base) {
  286. // Compatible with ec2 dataRange.color.
  287. // The mapping order of dataRange.color is: [high value, ..., low value]
  288. // whereas inRange.color and outOfRange.color is [low value, ..., high value]
  289. // Notice: ec2 has no inverse.
  290. if (isArray(thisOption.color) // If there has been inRange: {symbol: ...}, adding color is a mistake.
  291. // So adding color only when no inRange defined.
  292. && !base.inRange) {
  293. base.inRange = {
  294. color: thisOption.color.slice().reverse()
  295. };
  296. } // Compatible with previous logic, always give a defautl color, otherwise
  297. // simple config with no inRange and outOfRange will not work.
  298. // Originally we use visualMap.color as the default color, but setOption at
  299. // the second time the default color will be erased. So we change to use
  300. // constant DEFAULT_COLOR.
  301. // If user do not want the defualt color, set inRange: {color: null}.
  302. base.inRange = base.inRange || {
  303. color: DEFAULT_COLOR
  304. }; // If using shortcut like: {inRange: 'symbol'}, complete default value.
  305. each(this.stateList, function (state) {
  306. var visualType = base[state];
  307. if (zrUtil.isString(visualType)) {
  308. var defa = visualDefault.get(visualType, 'active', isCategory);
  309. if (defa) {
  310. base[state] = {};
  311. base[state][visualType] = defa;
  312. } else {
  313. // Mark as not specified.
  314. delete base[state];
  315. }
  316. }
  317. }, this);
  318. }
  319. function completeInactive(base, stateExist, stateAbsent) {
  320. var optExist = base[stateExist];
  321. var optAbsent = base[stateAbsent];
  322. if (optExist && !optAbsent) {
  323. optAbsent = base[stateAbsent] = {};
  324. each(optExist, function (visualData, visualType) {
  325. if (!VisualMapping.isValidType(visualType)) {
  326. return;
  327. }
  328. var defa = visualDefault.get(visualType, 'inactive', isCategory);
  329. if (defa != null) {
  330. optAbsent[visualType] = defa; // Compatibable with ec2:
  331. // Only inactive color to rgba(0,0,0,0) can not
  332. // make label transparent, so use opacity also.
  333. if (visualType === 'color' && !optAbsent.hasOwnProperty('opacity') && !optAbsent.hasOwnProperty('colorAlpha')) {
  334. optAbsent.opacity = [0, 0];
  335. }
  336. }
  337. });
  338. }
  339. }
  340. function completeController(controller) {
  341. var symbolExists = (controller.inRange || {}).symbol || (controller.outOfRange || {}).symbol;
  342. var symbolSizeExists = (controller.inRange || {}).symbolSize || (controller.outOfRange || {}).symbolSize;
  343. var inactiveColor = this.get('inactiveColor');
  344. each(this.stateList, function (state) {
  345. var itemSize = this.itemSize;
  346. var visuals = controller[state]; // Set inactive color for controller if no other color
  347. // attr (like colorAlpha) specified.
  348. if (!visuals) {
  349. visuals = controller[state] = {
  350. color: isCategory ? inactiveColor : [inactiveColor]
  351. };
  352. } // Consistent symbol and symbolSize if not specified.
  353. if (visuals.symbol == null) {
  354. visuals.symbol = symbolExists && zrUtil.clone(symbolExists) || (isCategory ? 'roundRect' : ['roundRect']);
  355. }
  356. if (visuals.symbolSize == null) {
  357. visuals.symbolSize = symbolSizeExists && zrUtil.clone(symbolSizeExists) || (isCategory ? itemSize[0] : [itemSize[0], itemSize[0]]);
  358. } // Filter square and none.
  359. visuals.symbol = mapVisual(visuals.symbol, function (symbol) {
  360. return symbol === 'none' || symbol === 'square' ? 'roundRect' : symbol;
  361. }); // Normalize symbolSize
  362. var symbolSize = visuals.symbolSize;
  363. if (symbolSize != null) {
  364. var max = -Infinity; // symbolSize can be object when categories defined.
  365. eachVisual(symbolSize, function (value) {
  366. value > max && (max = value);
  367. });
  368. visuals.symbolSize = mapVisual(symbolSize, function (value) {
  369. return linearMap(value, [0, max], [0, itemSize[0]], true);
  370. });
  371. }
  372. }, this);
  373. }
  374. },
  375. /**
  376. * @protected
  377. */
  378. resetItemSize: function () {
  379. this.itemSize = [parseFloat(this.get('itemWidth')), parseFloat(this.get('itemHeight'))];
  380. },
  381. /**
  382. * @public
  383. */
  384. isCategory: function () {
  385. return !!this.option.categories;
  386. },
  387. /**
  388. * @public
  389. * @abstract
  390. */
  391. setSelected: noop,
  392. /**
  393. * @public
  394. * @abstract
  395. * @param {*|module:echarts/data/List} valueOrData
  396. * @param {number} dataIndex
  397. * @return {string} state See this.stateList
  398. */
  399. getValueState: noop,
  400. /**
  401. * FIXME
  402. * Do not publish to thirt-part-dev temporarily
  403. * util the interface is stable. (Should it return
  404. * a function but not visual meta?)
  405. *
  406. * @pubilc
  407. * @abstract
  408. * @param {Function} getColorVisual
  409. * params: value, valueState
  410. * return: color
  411. * @return {Object} visualMeta
  412. * should includes {stops, outerColors}
  413. * outerColor means [colorBeyondMinValue, colorBeyondMaxValue]
  414. */
  415. getVisualMeta: noop
  416. });
  417. var _default = VisualMapModel;
  418. module.exports = _default;