visualEncoding.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. var echarts = require("../../echarts");
  2. var zrUtil = require("zrender/lib/core/util");
  3. var BoundingRect = require("zrender/lib/core/BoundingRect");
  4. var visualSolution = require("../../visual/visualSolution");
  5. var selector = require("./selector");
  6. var throttleUtil = require("../../util/throttle");
  7. var BrushTargetManager = require("../helper/BrushTargetManager");
  8. var STATE_LIST = ['inBrush', 'outOfBrush'];
  9. var DISPATCH_METHOD = '__ecBrushSelect';
  10. var DISPATCH_FLAG = '__ecInBrushSelectEvent';
  11. var PRIORITY_BRUSH = echarts.PRIORITY.VISUAL.BRUSH;
  12. /**
  13. * Layout for visual, the priority higher than other layout, and before brush visual.
  14. */
  15. echarts.registerLayout(PRIORITY_BRUSH, function (ecModel, api, payload) {
  16. ecModel.eachComponent({
  17. mainType: 'brush'
  18. }, function (brushModel) {
  19. payload && payload.type === 'takeGlobalCursor' && brushModel.setBrushOption(payload.key === 'brush' ? payload.brushOption : {
  20. brushType: false
  21. });
  22. var brushTargetManager = brushModel.brushTargetManager = new BrushTargetManager(brushModel.option, ecModel);
  23. brushTargetManager.setInputRanges(brushModel.areas, ecModel);
  24. });
  25. });
  26. /**
  27. * Register the visual encoding if this modules required.
  28. */
  29. echarts.registerVisual(PRIORITY_BRUSH, function (ecModel, api, payload) {
  30. var brushSelected = [];
  31. var throttleType;
  32. var throttleDelay;
  33. ecModel.eachComponent({
  34. mainType: 'brush'
  35. }, function (brushModel, brushIndex) {
  36. var thisBrushSelected = {
  37. brushId: brushModel.id,
  38. brushIndex: brushIndex,
  39. brushName: brushModel.name,
  40. areas: zrUtil.clone(brushModel.areas),
  41. selected: []
  42. }; // Every brush component exists in event params, convenient
  43. // for user to find by index.
  44. brushSelected.push(thisBrushSelected);
  45. var brushOption = brushModel.option;
  46. var brushLink = brushOption.brushLink;
  47. var linkedSeriesMap = [];
  48. var selectedDataIndexForLink = [];
  49. var rangeInfoBySeries = [];
  50. var hasBrushExists = 0;
  51. if (!brushIndex) {
  52. // Only the first throttle setting works.
  53. throttleType = brushOption.throttleType;
  54. throttleDelay = brushOption.throttleDelay;
  55. } // Add boundingRect and selectors to range.
  56. var areas = zrUtil.map(brushModel.areas, function (area) {
  57. return bindSelector(zrUtil.defaults({
  58. boundingRect: boundingRectBuilders[area.brushType](area)
  59. }, area));
  60. });
  61. var visualMappings = visualSolution.createVisualMappings(brushModel.option, STATE_LIST, function (mappingOption) {
  62. mappingOption.mappingMethod = 'fixed';
  63. });
  64. zrUtil.isArray(brushLink) && zrUtil.each(brushLink, function (seriesIndex) {
  65. linkedSeriesMap[seriesIndex] = 1;
  66. });
  67. function linkOthers(seriesIndex) {
  68. return brushLink === 'all' || linkedSeriesMap[seriesIndex];
  69. } // If no supported brush or no brush on the series,
  70. // all visuals should be in original state.
  71. function brushed(rangeInfoList) {
  72. return !!rangeInfoList.length;
  73. }
  74. /**
  75. * Logic for each series: (If the logic has to be modified one day, do it carefully!)
  76. *
  77. * ( brushed ┬ && ┬hasBrushExist ┬ && linkOthers ) => StepA: ┬record, ┬ StepB: ┬visualByRecord.
  78. * !brushed┘ ├hasBrushExist ┤ └nothing,┘ ├visualByRecord.
  79. * └!hasBrushExist┘ └nothing.
  80. * ( !brushed && ┬hasBrushExist ┬ && linkOthers ) => StepA: nothing, StepB: ┬visualByRecord.
  81. * └!hasBrushExist┘ └nothing.
  82. * ( brushed ┬ && !linkOthers ) => StepA: nothing, StepB: ┬visualByCheck.
  83. * !brushed┘ └nothing.
  84. * ( !brushed && !linkOthers ) => StepA: nothing, StepB: nothing.
  85. */
  86. // Step A
  87. ecModel.eachSeries(function (seriesModel, seriesIndex) {
  88. var rangeInfoList = rangeInfoBySeries[seriesIndex] = [];
  89. seriesModel.subType === 'parallel' ? stepAParallel(seriesModel, seriesIndex, rangeInfoList) : stepAOthers(seriesModel, seriesIndex, rangeInfoList);
  90. });
  91. function stepAParallel(seriesModel, seriesIndex) {
  92. var coordSys = seriesModel.coordinateSystem;
  93. hasBrushExists |= coordSys.hasAxisBrushed();
  94. linkOthers(seriesIndex) && coordSys.eachActiveState(seriesModel.getData(), function (activeState, dataIndex) {
  95. activeState === 'active' && (selectedDataIndexForLink[dataIndex] = 1);
  96. });
  97. }
  98. function stepAOthers(seriesModel, seriesIndex, rangeInfoList) {
  99. var selectorsByBrushType = getSelectorsByBrushType(seriesModel);
  100. if (!selectorsByBrushType || brushModelNotControll(brushModel, seriesIndex)) {
  101. return;
  102. }
  103. zrUtil.each(areas, function (area) {
  104. selectorsByBrushType[area.brushType] && brushModel.brushTargetManager.controlSeries(area, seriesModel, ecModel) && rangeInfoList.push(area);
  105. hasBrushExists |= brushed(rangeInfoList);
  106. });
  107. if (linkOthers(seriesIndex) && brushed(rangeInfoList)) {
  108. var data = seriesModel.getData();
  109. data.each(function (dataIndex) {
  110. if (checkInRange(selectorsByBrushType, rangeInfoList, data, dataIndex)) {
  111. selectedDataIndexForLink[dataIndex] = 1;
  112. }
  113. });
  114. }
  115. } // Step B
  116. ecModel.eachSeries(function (seriesModel, seriesIndex) {
  117. var seriesBrushSelected = {
  118. seriesId: seriesModel.id,
  119. seriesIndex: seriesIndex,
  120. seriesName: seriesModel.name,
  121. dataIndex: []
  122. }; // Every series exists in event params, convenient
  123. // for user to find series by seriesIndex.
  124. thisBrushSelected.selected.push(seriesBrushSelected);
  125. var selectorsByBrushType = getSelectorsByBrushType(seriesModel);
  126. var rangeInfoList = rangeInfoBySeries[seriesIndex];
  127. var data = seriesModel.getData();
  128. var getValueState = linkOthers(seriesIndex) ? function (dataIndex) {
  129. return selectedDataIndexForLink[dataIndex] ? (seriesBrushSelected.dataIndex.push(data.getRawIndex(dataIndex)), 'inBrush') : 'outOfBrush';
  130. } : function (dataIndex) {
  131. return checkInRange(selectorsByBrushType, rangeInfoList, data, dataIndex) ? (seriesBrushSelected.dataIndex.push(data.getRawIndex(dataIndex)), 'inBrush') : 'outOfBrush';
  132. }; // If no supported brush or no brush, all visuals are in original state.
  133. (linkOthers(seriesIndex) ? hasBrushExists : brushed(rangeInfoList)) && visualSolution.applyVisual(STATE_LIST, visualMappings, data, getValueState);
  134. });
  135. });
  136. dispatchAction(api, throttleType, throttleDelay, brushSelected, payload);
  137. });
  138. function dispatchAction(api, throttleType, throttleDelay, brushSelected, payload) {
  139. // This event will not be triggered when `setOpion`, otherwise dead lock may
  140. // triggered when do `setOption` in event listener, which we do not find
  141. // satisfactory way to solve yet. Some considered resolutions:
  142. // (a) Diff with prevoius selected data ant only trigger event when changed.
  143. // But store previous data and diff precisely (i.e., not only by dataIndex, but
  144. // also detect value changes in selected data) might bring complexity or fragility.
  145. // (b) Use spectial param like `silent` to suppress event triggering.
  146. // But such kind of volatile param may be weird in `setOption`.
  147. if (!payload) {
  148. return;
  149. }
  150. var zr = api.getZr();
  151. if (zr[DISPATCH_FLAG]) {
  152. return;
  153. }
  154. if (!zr[DISPATCH_METHOD]) {
  155. zr[DISPATCH_METHOD] = doDispatch;
  156. }
  157. var fn = throttleUtil.createOrUpdate(zr, DISPATCH_METHOD, throttleDelay, throttleType);
  158. fn(api, brushSelected);
  159. }
  160. function doDispatch(api, brushSelected) {
  161. if (!api.isDisposed()) {
  162. var zr = api.getZr();
  163. zr[DISPATCH_FLAG] = true;
  164. api.dispatchAction({
  165. type: 'brushSelect',
  166. batch: brushSelected
  167. });
  168. zr[DISPATCH_FLAG] = false;
  169. }
  170. }
  171. function checkInRange(selectorsByBrushType, rangeInfoList, data, dataIndex) {
  172. for (var i = 0, len = rangeInfoList.length; i < len; i++) {
  173. var area = rangeInfoList[i];
  174. if (selectorsByBrushType[area.brushType](dataIndex, data, area.selectors, area)) {
  175. return true;
  176. }
  177. }
  178. }
  179. function getSelectorsByBrushType(seriesModel) {
  180. var brushSelector = seriesModel.brushSelector;
  181. if (zrUtil.isString(brushSelector)) {
  182. var sels = [];
  183. zrUtil.each(selector, function (selectorsByElementType, brushType) {
  184. sels[brushType] = function (dataIndex, data, selectors, area) {
  185. var itemLayout = data.getItemLayout(dataIndex);
  186. return selectorsByElementType[brushSelector](itemLayout, selectors, area);
  187. };
  188. });
  189. return sels;
  190. } else if (zrUtil.isFunction(brushSelector)) {
  191. var bSelector = {};
  192. zrUtil.each(selector, function (sel, brushType) {
  193. bSelector[brushType] = brushSelector;
  194. });
  195. return bSelector;
  196. }
  197. return brushSelector;
  198. }
  199. function brushModelNotControll(brushModel, seriesIndex) {
  200. var seriesIndices = brushModel.option.seriesIndex;
  201. return seriesIndices != null && seriesIndices !== 'all' && (zrUtil.isArray(seriesIndices) ? zrUtil.indexOf(seriesIndices, seriesIndex) < 0 : seriesIndex !== seriesIndices);
  202. }
  203. function bindSelector(area) {
  204. var selectors = area.selectors = {};
  205. zrUtil.each(selector[area.brushType], function (selFn, elType) {
  206. // Do not use function binding or curry for performance.
  207. selectors[elType] = function (itemLayout) {
  208. return selFn(itemLayout, selectors, area);
  209. };
  210. });
  211. return area;
  212. }
  213. var boundingRectBuilders = {
  214. lineX: zrUtil.noop,
  215. lineY: zrUtil.noop,
  216. rect: function (area) {
  217. return getBoundingRectFromMinMax(area.range);
  218. },
  219. polygon: function (area) {
  220. var minMax;
  221. var range = area.range;
  222. for (var i = 0, len = range.length; i < len; i++) {
  223. minMax = minMax || [[Infinity, -Infinity], [Infinity, -Infinity]];
  224. var rg = range[i];
  225. rg[0] < minMax[0][0] && (minMax[0][0] = rg[0]);
  226. rg[0] > minMax[0][1] && (minMax[0][1] = rg[0]);
  227. rg[1] < minMax[1][0] && (minMax[1][0] = rg[1]);
  228. rg[1] > minMax[1][1] && (minMax[1][1] = rg[1]);
  229. }
  230. return minMax && getBoundingRectFromMinMax(minMax);
  231. }
  232. };
  233. function getBoundingRectFromMinMax(minMax) {
  234. return new BoundingRect(minMax[0][0], minMax[1][0], minMax[0][1] - minMax[0][0], minMax[1][1] - minMax[1][0]);
  235. }