VisualMapModel.js 16 KB

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