BrushTargetManager.js 14 KB

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