brushAxisHighlight.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. "use strict";
  2. var __rest = (this && this.__rest) || function (s, e) {
  3. var t = {};
  4. for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
  5. t[p] = s[p];
  6. if (s != null && typeof Object.getOwnPropertySymbols === "function")
  7. for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
  8. if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
  9. t[p[i]] = s[p[i]];
  10. }
  11. return t;
  12. };
  13. Object.defineProperty(exports, "__esModule", { value: true });
  14. exports.BrushAxisHighlight = exports.brushAxisHighlight = exports.AXIS_HOT_AREA_CLASS_NAME = exports.AXIS_MAIN_CLASS_NAME = exports.AXIS_LINE_CLASS_NAME = exports.AXIS_CLASS_NAME = void 0;
  15. const g_1 = require("@antv/g");
  16. const helper_1 = require("../utils/helper");
  17. const scale_1 = require("../utils/scale");
  18. const brushHighlight_1 = require("./brushHighlight");
  19. const brushXHighlight_1 = require("./brushXHighlight");
  20. const brushYHighlight_1 = require("./brushYHighlight");
  21. const utils_1 = require("./utils");
  22. exports.AXIS_CLASS_NAME = 'axis';
  23. exports.AXIS_LINE_CLASS_NAME = 'axis-line';
  24. exports.AXIS_MAIN_CLASS_NAME = 'axis-main-group';
  25. exports.AXIS_HOT_AREA_CLASS_NAME = 'axis-hot-area';
  26. function axesOf(container) {
  27. return container.getElementsByClassName(exports.AXIS_CLASS_NAME);
  28. }
  29. function lineOf(axis) {
  30. return axis.getElementsByClassName(exports.AXIS_LINE_CLASS_NAME)[0];
  31. }
  32. function mainGroupOf(axis) {
  33. return axis.getElementsByClassName(exports.AXIS_MAIN_CLASS_NAME)[0];
  34. }
  35. // Use the bounds of main group of axis as the bounds of axis,
  36. // get rid of grid and title.
  37. function boundsOfAxis(axis) {
  38. return mainGroupOf(axis).getLocalBounds();
  39. }
  40. // Brush for vertical axis.
  41. function verticalBrush(axis, _a) {
  42. var { cross, offsetX, offsetY } = _a, style = __rest(_a, ["cross", "offsetX", "offsetY"]);
  43. const bounds = boundsOfAxis(axis);
  44. const axisLine = lineOf(axis);
  45. const [lineX] = axisLine.getLocalBounds().min;
  46. const [minX, minY] = bounds.min;
  47. const [maxX, maxY] = bounds.max;
  48. const size = (maxX - minX) * 2;
  49. return {
  50. brushRegion: brushYHighlight_1.brushYRegion,
  51. hotZone: new g_1.Rect({
  52. className: exports.AXIS_HOT_AREA_CLASS_NAME,
  53. style: Object.assign({
  54. // If it is not cross, draw brush in both side of axisLine,
  55. // otherwise the draw brush within bounds area.
  56. x: cross ? minX : lineX - size / 2, width: cross ? size / 2 : size, y: minY, height: maxY - minY }, style),
  57. }),
  58. extent: cross
  59. ? // If it is cross, the x range is ignored.
  60. (x, y, x1, y1) => [-Infinity, y, Infinity, y1]
  61. : (x, y, x1, y1) => [
  62. Math.floor(minX - offsetX),
  63. y,
  64. Math.ceil(maxX - offsetX),
  65. y1,
  66. ],
  67. };
  68. }
  69. // Brush for horizontal axis.
  70. function horizontalBrush(axis, _a) {
  71. var { offsetY, offsetX, cross = false } = _a, style = __rest(_a, ["offsetY", "offsetX", "cross"]);
  72. const bounds = boundsOfAxis(axis);
  73. const axisLine = lineOf(axis);
  74. const [, lineY] = axisLine.getLocalBounds().min;
  75. const [minX, minY] = bounds.min;
  76. const [maxX, maxY] = bounds.max;
  77. const size = maxY - minY;
  78. return {
  79. brushRegion: brushXHighlight_1.brushXRegion,
  80. hotZone: new g_1.Rect({
  81. className: exports.AXIS_HOT_AREA_CLASS_NAME,
  82. style: Object.assign({ x: minX, width: maxX - minX,
  83. // If it is not cross, draw brush in both side of axisLine,
  84. // otherwise the draw brush within bounds area.
  85. y: cross ? minY : lineY - size, height: cross ? size : size * 2 }, style),
  86. }),
  87. extent: cross
  88. ? // If it is cross, the y range is ignored.
  89. (x, y, x1, y1) => [x, -Infinity, x1, Infinity]
  90. : (x, y, x1, y1) => [
  91. x,
  92. Math.floor(minY - offsetY),
  93. x1,
  94. Math.ceil(maxY - offsetY),
  95. ],
  96. };
  97. }
  98. function brushAxisHighlight(root, _a) {
  99. var { axes: axesOf, // given root, return axes
  100. elements: elementsOf, // given root, return elements
  101. points: pointsOf, // given shape, return control points
  102. horizontal: isHorizontal, // given axis, return direction
  103. datum, // given shape, return datum
  104. offsetY, // offsetY for shape area
  105. offsetX, // offsetX for shape area
  106. reverse = false, state = {}, emitter, coordinate } = _a, rest = __rest(_a, ["axes", "elements", "points", "horizontal", "datum", "offsetY", "offsetX", "reverse", "state", "emitter", "coordinate"]) // style
  107. ;
  108. const elements = elementsOf(root);
  109. const axes = axesOf(root);
  110. const valueof = (0, utils_1.createValueof)(elements, datum);
  111. const { setState, removeState } = (0, utils_1.useState)(state, valueof);
  112. const axisExtent = new Map();
  113. const brushStyle = (0, helper_1.subObject)(rest, 'mask');
  114. // Only some of shape's points in all mask, it is selected.
  115. const brushed = (points) => Array.from(axisExtent.values()).every(([x, y, x1, y1]) => points.some(([x0, y0]) => {
  116. return x0 >= x && x0 <= x1 && y0 >= y && y0 <= y1;
  117. }));
  118. const scales = axes.map((d) => d.attributes.scale);
  119. const extentOf = (D) => (D.length > 2 ? [D[0], D[D.length - 1]] : D);
  120. const indexDomain = new Map();
  121. const initIndexDomain = () => {
  122. indexDomain.clear();
  123. for (let i = 0; i < axes.length; i++) {
  124. const scale = scales[i];
  125. const { domain } = scale.getOptions();
  126. indexDomain.set(i, extentOf(domain));
  127. }
  128. };
  129. initIndexDomain();
  130. // Update element when brush changed.
  131. const updateElement = (i, emit) => {
  132. const selectedElements = [];
  133. for (const element of elements) {
  134. const points = pointsOf(element);
  135. if (brushed(points)) {
  136. setState(element, 'active');
  137. selectedElements.push(element);
  138. }
  139. else
  140. setState(element, 'inactive');
  141. }
  142. indexDomain.set(i, selectionOf(selectedElements, i));
  143. if (!emit)
  144. return;
  145. // Emit events.
  146. const selection = () => {
  147. if (!cross)
  148. return Array.from(indexDomain.values());
  149. const S = [];
  150. for (const [index, domain] of indexDomain) {
  151. const scale = scales[index];
  152. const { name } = scale.getOptions();
  153. if (name === 'x')
  154. S[0] = domain;
  155. else
  156. S[1] = domain;
  157. }
  158. return S;
  159. };
  160. emitter.emit('brushAxis:highlight', {
  161. nativeEvent: true,
  162. data: {
  163. selection: selection(),
  164. },
  165. });
  166. };
  167. const clearElement = (emit) => {
  168. for (const element of elements)
  169. removeState(element, 'active', 'inactive');
  170. initIndexDomain();
  171. if (!emit)
  172. return;
  173. emitter.emit('brushAxis:remove', { nativeEvent: true });
  174. };
  175. const selectionOf = (selected, i) => {
  176. const scale = scales[i];
  177. const { name } = scale.getOptions();
  178. const domain = selected.map((d) => {
  179. const data = d.__data__;
  180. return scale.invert(data[name]);
  181. });
  182. return extentOf((0, scale_1.domainOf)(scale, domain));
  183. };
  184. // Distinguish between parallel coordinates and normal charts.
  185. const cross = axes.some(isHorizontal) && axes.some((d) => !isHorizontal(d));
  186. const handlers = [];
  187. for (let i = 0; i < axes.length; i++) {
  188. const axis = axes[i];
  189. const createBrush = isHorizontal(axis) ? horizontalBrush : verticalBrush;
  190. const { hotZone, brushRegion, extent } = createBrush(axis, {
  191. offsetY,
  192. offsetX,
  193. cross,
  194. zIndex: 999,
  195. fill: 'transparent', // Make it interactive.
  196. });
  197. axis.parentNode.appendChild(hotZone);
  198. const brushHandler = (0, brushHighlight_1.brush)(hotZone, Object.assign(Object.assign({}, brushStyle), { reverse,
  199. brushRegion,
  200. brushended(emit) {
  201. axisExtent.delete(axis);
  202. if (Array.from(axisExtent.entries()).length === 0)
  203. clearElement(emit);
  204. else
  205. updateElement(i, emit);
  206. },
  207. brushed(x, y, x1, y1, emit) {
  208. axisExtent.set(axis, extent(x, y, x1, y1));
  209. updateElement(i, emit);
  210. } }));
  211. handlers.push(brushHandler);
  212. }
  213. const onRemove = (event = {}) => {
  214. const { nativeEvent } = event;
  215. if (nativeEvent)
  216. return;
  217. handlers.forEach((d) => d.remove());
  218. };
  219. const rangeOf = (domain, scale, axis) => {
  220. const [d0, d1] = domain;
  221. const maybeStep = (scale) => (scale.getStep ? scale.getStep() : 0);
  222. const x = abstractOf(d0, scale, axis);
  223. const x1 = abstractOf(d1, scale, axis) + maybeStep(scale);
  224. if (isHorizontal(axis))
  225. return [x, -Infinity, x1, Infinity];
  226. return [-Infinity, x, Infinity, x1];
  227. };
  228. const abstractOf = (x, scale, axis) => {
  229. const { height, width } = coordinate.getOptions();
  230. const scale1 = scale.clone();
  231. if (isHorizontal(axis))
  232. scale1.update({ range: [0, width] });
  233. else
  234. scale1.update({ range: [height, 0] });
  235. return scale1.map(x);
  236. };
  237. const onHighlight = (event) => {
  238. const { nativeEvent } = event;
  239. if (nativeEvent)
  240. return;
  241. const { selection } = event.data;
  242. for (let i = 0; i < handlers.length; i++) {
  243. const domain = selection[i];
  244. const handler = handlers[i];
  245. const axis = axes[i];
  246. if (domain) {
  247. const scale = scales[i];
  248. handler.move(...rangeOf(domain, scale, axis), false);
  249. }
  250. else {
  251. handler.remove();
  252. }
  253. }
  254. };
  255. emitter.on('brushAxis:remove', onRemove);
  256. emitter.on('brushAxis:highlight', onHighlight);
  257. return () => {
  258. handlers.forEach((d) => d.destroy());
  259. emitter.off('brushAxis:remove', onRemove);
  260. emitter.off('brushAxis:highlight', onHighlight);
  261. };
  262. }
  263. exports.brushAxisHighlight = brushAxisHighlight;
  264. /**
  265. * @todo Support mask size.
  266. */
  267. function BrushAxisHighlight(options) {
  268. return (target, _, emitter) => {
  269. const { container, view, options: viewOptions } = target;
  270. const plotArea = (0, utils_1.selectPlotArea)(container);
  271. const { x: x0, y: y0 } = plotArea.getBBox();
  272. const { coordinate } = view;
  273. return brushAxisHighlight(container, Object.assign({ elements: utils_1.selectG2Elements, axes: axesOf, offsetY: y0, offsetX: x0, points: (element) => element.__data__.points, horizontal: (axis) => {
  274. const { startPos: [sx, sy], endPos: [ex, ey], } = axis.attributes;
  275. // attention, non-horizontal does not mean vertical
  276. // it may has a specific degree angle
  277. return sx !== ex && sy === ey;
  278. }, datum: (0, utils_1.createDatumof)(view), state: (0, utils_1.mergeState)(viewOptions, [
  279. 'active',
  280. ['inactive', { opacity: 0.5 }],
  281. ]), coordinate,
  282. emitter }, options));
  283. };
  284. }
  285. exports.BrushAxisHighlight = BrushAxisHighlight;
  286. //# sourceMappingURL=brushAxisHighlight.js.map