BrushTargetManager.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. var _config = require("../../config");
  2. var __DEV__ = _config.__DEV__;
  3. var zrUtil = require("zrender/lib/core/util");
  4. var graphic = require("../../util/graphic");
  5. var modelUtil = require("../../util/model");
  6. var brushHelper = require("./brushHelper");
  7. var each = zrUtil.each;
  8. var indexOf = zrUtil.indexOf;
  9. var curry = zrUtil.curry;
  10. var COORD_CONVERTS = ['dataToPoint', 'pointToData']; // FIXME
  11. // how to genarialize to more coordinate systems.
  12. var INCLUDE_FINDER_MAIN_TYPES = ['grid', 'xAxis', 'yAxis', 'geo', 'graph', 'polar', 'radiusAxis', 'angleAxis', 'bmap'];
  13. /**
  14. * [option in constructor]:
  15. * {
  16. * Index/Id/Name of geo, xAxis, yAxis, grid: See util/model#parseFinder.
  17. * }
  18. *
  19. *
  20. * [targetInfo]:
  21. *
  22. * There can be multiple axes in a single targetInfo. Consider the case
  23. * of `grid` component, a targetInfo represents a grid which contains one or more
  24. * cartesian and one or more axes. And consider the case of parallel system,
  25. * which has multiple axes in a coordinate system.
  26. * Can be {
  27. * panelId: ...,
  28. * coordSys: <a representitive cartesian in grid (first cartesian by default)>,
  29. * coordSyses: all cartesians.
  30. * gridModel: <grid component>
  31. * xAxes: correspond to coordSyses on index
  32. * yAxes: correspond to coordSyses on index
  33. * }
  34. * or {
  35. * panelId: ...,
  36. * coordSys: <geo coord sys>
  37. * coordSyses: [<geo coord sys>]
  38. * geoModel: <geo component>
  39. * }
  40. *
  41. *
  42. * [panelOpt]:
  43. *
  44. * Make from targetInfo. Input to BrushController.
  45. * {
  46. * panelId: ...,
  47. * rect: ...
  48. * }
  49. *
  50. *
  51. * [area]:
  52. *
  53. * Generated by BrushController or user input.
  54. * {
  55. * panelId: Used to locate coordInfo directly. If user inpput, no panelId.
  56. * brushType: determine how to convert to/from coord('rect' or 'polygon' or 'lineX/Y').
  57. * Index/Id/Name of geo, xAxis, yAxis, grid: See util/model#parseFinder.
  58. * range: pixel range.
  59. * coordRange: representitive coord range (the first one of coordRanges).
  60. * coordRanges: <Array> coord ranges, used in multiple cartesian in one grid.
  61. * }
  62. */
  63. /**
  64. * @param {Object} option contains Index/Id/Name of xAxis/yAxis/geo/grid
  65. * Each can be {number|Array.<number>}. like: {xAxisIndex: [3, 4]}
  66. * @param {module:echarts/model/Global} ecModel
  67. * @param {Object} [opt]
  68. * @param {Array.<string>} [opt.include] include coordinate system types.
  69. */
  70. function BrushTargetManager(option, ecModel, opt) {
  71. /**
  72. * @private
  73. * @type {Array.<Object>}
  74. */
  75. var targetInfoList = this._targetInfoList = [];
  76. var info = {};
  77. var foundCpts = parseFinder(ecModel, option);
  78. each(targetInfoBuilders, function (builder, type) {
  79. if (!opt || !opt.include || indexOf(opt.include, type) >= 0) {
  80. builder(foundCpts, targetInfoList, info);
  81. }
  82. });
  83. }
  84. var proto = BrushTargetManager.prototype;
  85. proto.setOutputRanges = function (areas, ecModel) {
  86. this.matchOutputRanges(areas, ecModel, function (area, coordRange, coordSys) {
  87. (area.coordRanges || (area.coordRanges = [])).push(coordRange); // area.coordRange is the first of area.coordRanges
  88. if (!area.coordRange) {
  89. area.coordRange = coordRange; // In 'category' axis, coord to pixel is not reversible, so we can not
  90. // rebuild range by coordRange accrately, which may bring trouble when
  91. // brushing only one item. So we use __rangeOffset to rebuilding range
  92. // by coordRange. And this it only used in brush component so it is no
  93. // need to be adapted to coordRanges.
  94. var result = coordConvert[area.brushType](0, coordSys, coordRange);
  95. area.__rangeOffset = {
  96. offset: diffProcessor[area.brushType](result.values, area.range, [1, 1]),
  97. xyMinMax: result.xyMinMax
  98. };
  99. }
  100. });
  101. };
  102. proto.matchOutputRanges = function (areas, ecModel, cb) {
  103. each(areas, function (area) {
  104. var targetInfo = this.findTargetInfo(area, ecModel);
  105. if (targetInfo && targetInfo !== true) {
  106. zrUtil.each(targetInfo.coordSyses, function (coordSys) {
  107. var result = coordConvert[area.brushType](1, coordSys, area.range);
  108. cb(area, result.values, coordSys, ecModel);
  109. });
  110. }
  111. }, this);
  112. };
  113. proto.setInputRanges = function (areas, ecModel) {
  114. each(areas, function (area) {
  115. var targetInfo = this.findTargetInfo(area, ecModel);
  116. area.range = area.range || []; // convert coordRange to global range and set panelId.
  117. if (targetInfo && targetInfo !== true) {
  118. area.panelId = targetInfo.panelId; // (1) area.range shoule always be calculate from coordRange but does
  119. // not keep its original value, for the sake of the dataZoom scenario,
  120. // where area.coordRange remains unchanged but area.range may be changed.
  121. // (2) Only support converting one coordRange to pixel range in brush
  122. // component. So do not consider `coordRanges`.
  123. // (3) About __rangeOffset, see comment above.
  124. var result = coordConvert[area.brushType](0, targetInfo.coordSys, area.coordRange);
  125. var rangeOffset = area.__rangeOffset;
  126. area.range = rangeOffset ? diffProcessor[area.brushType](result.values, rangeOffset.offset, getScales(result.xyMinMax, rangeOffset.xyMinMax)) : result.values;
  127. }
  128. }, this);
  129. };
  130. proto.makePanelOpts = function (api, getDefaultBrushType) {
  131. return zrUtil.map(this._targetInfoList, function (targetInfo) {
  132. var rect = targetInfo.getPanelRect();
  133. return {
  134. panelId: targetInfo.panelId,
  135. defaultBrushType: getDefaultBrushType && getDefaultBrushType(targetInfo),
  136. clipPath: brushHelper.makeRectPanelClipPath(rect),
  137. isTargetByCursor: brushHelper.makeRectIsTargetByCursor(rect, api, targetInfo.coordSysModel),
  138. getLinearBrushOtherExtent: brushHelper.makeLinearBrushOtherExtent(rect)
  139. };
  140. });
  141. };
  142. proto.controlSeries = function (area, seriesModel, ecModel) {
  143. // Check whether area is bound in coord, and series do not belong to that coord.
  144. // If do not do this check, some brush (like lineX) will controll all axes.
  145. var targetInfo = this.findTargetInfo(area, ecModel);
  146. return targetInfo === true || targetInfo && indexOf(targetInfo.coordSyses, seriesModel.coordinateSystem) >= 0;
  147. };
  148. /**
  149. * If return Object, a coord found.
  150. * If reutrn true, global found.
  151. * Otherwise nothing found.
  152. *
  153. * @param {Object} area
  154. * @param {Array} targetInfoList
  155. * @return {Object|boolean}
  156. */
  157. proto.findTargetInfo = function (area, ecModel) {
  158. var targetInfoList = this._targetInfoList;
  159. var foundCpts = parseFinder(ecModel, area);
  160. for (var i = 0; i < targetInfoList.length; i++) {
  161. var targetInfo = targetInfoList[i];
  162. var areaPanelId = area.panelId;
  163. if (areaPanelId) {
  164. if (targetInfo.panelId === areaPanelId) {
  165. return targetInfo;
  166. }
  167. } else {
  168. for (var i = 0; i < targetInfoMatchers.length; i++) {
  169. if (targetInfoMatchers[i](foundCpts, targetInfo)) {
  170. return targetInfo;
  171. }
  172. }
  173. }
  174. }
  175. return true;
  176. };
  177. function formatMinMax(minMax) {
  178. minMax[0] > minMax[1] && minMax.reverse();
  179. return minMax;
  180. }
  181. function parseFinder(ecModel, option) {
  182. return modelUtil.parseFinder(ecModel, option, {
  183. includeMainTypes: INCLUDE_FINDER_MAIN_TYPES
  184. });
  185. }
  186. var targetInfoBuilders = {
  187. grid: function (foundCpts, targetInfoList) {
  188. var xAxisModels = foundCpts.xAxisModels;
  189. var yAxisModels = foundCpts.yAxisModels;
  190. var gridModels = foundCpts.gridModels; // Remove duplicated.
  191. var gridModelMap = zrUtil.createHashMap();
  192. var xAxesHas = {};
  193. var yAxesHas = {};
  194. if (!xAxisModels && !yAxisModels && !gridModels) {
  195. return;
  196. }
  197. each(xAxisModels, function (axisModel) {
  198. var gridModel = axisModel.axis.grid.model;
  199. gridModelMap.set(gridModel.id, gridModel);
  200. xAxesHas[gridModel.id] = true;
  201. });
  202. each(yAxisModels, function (axisModel) {
  203. var gridModel = axisModel.axis.grid.model;
  204. gridModelMap.set(gridModel.id, gridModel);
  205. yAxesHas[gridModel.id] = true;
  206. });
  207. each(gridModels, function (gridModel) {
  208. gridModelMap.set(gridModel.id, gridModel);
  209. xAxesHas[gridModel.id] = true;
  210. yAxesHas[gridModel.id] = true;
  211. });
  212. gridModelMap.each(function (gridModel) {
  213. var grid = gridModel.coordinateSystem;
  214. var cartesians = [];
  215. each(grid.getCartesians(), function (cartesian, index) {
  216. if (indexOf(xAxisModels, cartesian.getAxis('x').model) >= 0 || indexOf(yAxisModels, cartesian.getAxis('y').model) >= 0) {
  217. cartesians.push(cartesian);
  218. }
  219. });
  220. targetInfoList.push({
  221. panelId: 'grid--' + gridModel.id,
  222. gridModel: gridModel,
  223. coordSysModel: gridModel,
  224. // Use the first one as the representitive coordSys.
  225. coordSys: cartesians[0],
  226. coordSyses: cartesians,
  227. getPanelRect: panelRectBuilder.grid,
  228. xAxisDeclared: xAxesHas[gridModel.id],
  229. yAxisDeclared: yAxesHas[gridModel.id]
  230. });
  231. });
  232. },
  233. geo: function (foundCpts, targetInfoList) {
  234. each(foundCpts.geoModels, function (geoModel) {
  235. var coordSys = geoModel.coordinateSystem;
  236. targetInfoList.push({
  237. panelId: 'geo--' + geoModel.id,
  238. geoModel: geoModel,
  239. coordSysModel: geoModel,
  240. coordSys: coordSys,
  241. coordSyses: [coordSys],
  242. getPanelRect: panelRectBuilder.geo
  243. });
  244. });
  245. }
  246. };
  247. var targetInfoMatchers = [// grid
  248. function (foundCpts, targetInfo) {
  249. var xAxisModel = foundCpts.xAxisModel;
  250. var yAxisModel = foundCpts.yAxisModel;
  251. var gridModel = foundCpts.gridModel;
  252. !gridModel && xAxisModel && (gridModel = xAxisModel.axis.grid.model);
  253. !gridModel && yAxisModel && (gridModel = yAxisModel.axis.grid.model);
  254. return gridModel && gridModel === targetInfo.gridModel;
  255. }, // geo
  256. function (foundCpts, targetInfo) {
  257. var geoModel = foundCpts.geoModel;
  258. return geoModel && geoModel === targetInfo.geoModel;
  259. }];
  260. var panelRectBuilder = {
  261. grid: function () {
  262. // grid is not Transformable.
  263. return this.coordSys.grid.getRect().clone();
  264. },
  265. geo: function () {
  266. var coordSys = this.coordSys;
  267. var rect = coordSys.getBoundingRect().clone(); // geo roam and zoom transform
  268. rect.applyTransform(graphic.getTransform(coordSys));
  269. return rect;
  270. }
  271. };
  272. var coordConvert = {
  273. lineX: curry(axisConvert, 0),
  274. lineY: curry(axisConvert, 1),
  275. rect: function (to, coordSys, rangeOrCoordRange) {
  276. var xminymin = coordSys[COORD_CONVERTS[to]]([rangeOrCoordRange[0][0], rangeOrCoordRange[1][0]]);
  277. var xmaxymax = coordSys[COORD_CONVERTS[to]]([rangeOrCoordRange[0][1], rangeOrCoordRange[1][1]]);
  278. var values = [formatMinMax([xminymin[0], xmaxymax[0]]), formatMinMax([xminymin[1], xmaxymax[1]])];
  279. return {
  280. values: values,
  281. xyMinMax: values
  282. };
  283. },
  284. polygon: function (to, coordSys, rangeOrCoordRange) {
  285. var xyMinMax = [[Infinity, -Infinity], [Infinity, -Infinity]];
  286. var values = zrUtil.map(rangeOrCoordRange, function (item) {
  287. var p = coordSys[COORD_CONVERTS[to]](item);
  288. xyMinMax[0][0] = Math.min(xyMinMax[0][0], p[0]);
  289. xyMinMax[1][0] = Math.min(xyMinMax[1][0], p[1]);
  290. xyMinMax[0][1] = Math.max(xyMinMax[0][1], p[0]);
  291. xyMinMax[1][1] = Math.max(xyMinMax[1][1], p[1]);
  292. return p;
  293. });
  294. return {
  295. values: values,
  296. xyMinMax: xyMinMax
  297. };
  298. }
  299. };
  300. function axisConvert(axisNameIndex, to, coordSys, rangeOrCoordRange) {
  301. var axis = coordSys.getAxis(['x', 'y'][axisNameIndex]);
  302. var values = formatMinMax(zrUtil.map([0, 1], function (i) {
  303. return to ? axis.coordToData(axis.toLocalCoord(rangeOrCoordRange[i])) : axis.toGlobalCoord(axis.dataToCoord(rangeOrCoordRange[i]));
  304. }));
  305. var xyMinMax = [];
  306. xyMinMax[axisNameIndex] = values;
  307. xyMinMax[1 - axisNameIndex] = [NaN, NaN];
  308. return {
  309. values: values,
  310. xyMinMax: xyMinMax
  311. };
  312. }
  313. var diffProcessor = {
  314. lineX: curry(axisDiffProcessor, 0),
  315. lineY: curry(axisDiffProcessor, 1),
  316. rect: function (values, refer, scales) {
  317. return [[values[0][0] - scales[0] * refer[0][0], values[0][1] - scales[0] * refer[0][1]], [values[1][0] - scales[1] * refer[1][0], values[1][1] - scales[1] * refer[1][1]]];
  318. },
  319. polygon: function (values, refer, scales) {
  320. return zrUtil.map(values, function (item, idx) {
  321. return [item[0] - scales[0] * refer[idx][0], item[1] - scales[1] * refer[idx][1]];
  322. });
  323. }
  324. };
  325. function axisDiffProcessor(axisNameIndex, values, refer, scales) {
  326. return [values[0] - scales[axisNameIndex] * refer[0], values[1] - scales[axisNameIndex] * refer[1]];
  327. } // We have to process scale caused by dataZoom manually,
  328. // although it might be not accurate.
  329. function getScales(xyMinMaxCurr, xyMinMaxOrigin) {
  330. var sizeCurr = getSize(xyMinMaxCurr);
  331. var sizeOrigin = getSize(xyMinMaxOrigin);
  332. var scales = [sizeCurr[0] / sizeOrigin[0], sizeCurr[1] / sizeOrigin[1]];
  333. isNaN(scales[0]) && (scales[0] = 1);
  334. isNaN(scales[1]) && (scales[1] = 1);
  335. return scales;
  336. }
  337. function getSize(xyMinMax) {
  338. return xyMinMax ? [xyMinMax[0][1] - xyMinMax[0][0], xyMinMax[1][1] - xyMinMax[1][0]] : [NaN, NaN];
  339. }
  340. var _default = BrushTargetManager;
  341. module.exports = _default;