UNPKG

@antv/g2

Version:

the Grammar of Graphics in Javascript

286 lines 11.7 kB
"use strict"; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; Object.defineProperty(exports, "__esModule", { value: true }); 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; const g_1 = require("@antv/g"); const helper_1 = require("../utils/helper"); const scale_1 = require("../utils/scale"); const brushHighlight_1 = require("./brushHighlight"); const brushXHighlight_1 = require("./brushXHighlight"); const brushYHighlight_1 = require("./brushYHighlight"); const utils_1 = require("./utils"); exports.AXIS_CLASS_NAME = 'axis'; exports.AXIS_LINE_CLASS_NAME = 'axis-line'; exports.AXIS_MAIN_CLASS_NAME = 'axis-main-group'; exports.AXIS_HOT_AREA_CLASS_NAME = 'axis-hot-area'; function axesOf(container) { return container.getElementsByClassName(exports.AXIS_CLASS_NAME); } function lineOf(axis) { return axis.getElementsByClassName(exports.AXIS_LINE_CLASS_NAME)[0]; } function mainGroupOf(axis) { return axis.getElementsByClassName(exports.AXIS_MAIN_CLASS_NAME)[0]; } // Use the bounds of main group of axis as the bounds of axis, // get rid of grid and title. function boundsOfAxis(axis) { return mainGroupOf(axis).getLocalBounds(); } // Brush for vertical axis. function verticalBrush(axis, _a) { var { cross, offsetX, offsetY } = _a, style = __rest(_a, ["cross", "offsetX", "offsetY"]); const bounds = boundsOfAxis(axis); const axisLine = lineOf(axis); const [lineX] = axisLine.getLocalBounds().min; const [minX, minY] = bounds.min; const [maxX, maxY] = bounds.max; const size = (maxX - minX) * 2; return { brushRegion: brushYHighlight_1.brushYRegion, hotZone: new g_1.Rect({ className: exports.AXIS_HOT_AREA_CLASS_NAME, style: Object.assign({ // If it is not cross, draw brush in both side of axisLine, // otherwise the draw brush within bounds area. width: cross ? size / 2 : size, transform: `translate(${(cross ? minX : lineX - size / 2).toFixed(2)}, ${minY})`, height: maxY - minY }, style), }), extent: cross ? // If it is cross, the x range is ignored. (x, y, x1, y1) => [-Infinity, y, Infinity, y1] : (x, y, x1, y1) => [ Math.floor(minX - offsetX), y, Math.ceil(maxX - offsetX), y1, ], }; } // Brush for horizontal axis. function horizontalBrush(axis, _a) { var { offsetY, offsetX, cross = false } = _a, style = __rest(_a, ["offsetY", "offsetX", "cross"]); const bounds = boundsOfAxis(axis); const axisLine = lineOf(axis); const [, lineY] = axisLine.getLocalBounds().min; const [minX, minY] = bounds.min; const [maxX, maxY] = bounds.max; const size = maxY - minY; return { brushRegion: brushXHighlight_1.brushXRegion, hotZone: new g_1.Rect({ className: exports.AXIS_HOT_AREA_CLASS_NAME, style: Object.assign({ width: maxX - minX, // If it is not cross, draw brush in both side of axisLine, // otherwise the draw brush within bounds area. height: cross ? size : size * 2, transform: `translate(${minX}, ${cross ? minY : lineY - size})` }, style), }), extent: cross ? // If it is cross, the y range is ignored. (x, y, x1, y1) => [x, -Infinity, x1, Infinity] : (x, y, x1, y1) => [ x, Math.floor(minY - offsetY), x1, Math.ceil(maxY - offsetY), ], }; } function brushAxisHighlight(root, _a) { var { axes: axesOf, // given root, return axes elements: elementsOf, // given root, return elements points: pointsOf, // given shape, return control points horizontal: isHorizontal, // given axis, return direction datum, // given shape, return datum offsetY, // offsetY for shape area offsetX, // offsetX for shape area reverse = false, state = {}, emitter, coordinate } = _a, rest = __rest(_a, ["axes", "elements", "points", "horizontal", "datum", "offsetY", "offsetX", "reverse", "state", "emitter", "coordinate"]) // style ; const elements = elementsOf(root); const axes = axesOf(root); const valueof = (0, utils_1.createValueof)(elements, datum); const { setState, removeState } = (0, utils_1.useState)(state, valueof); const axisExtent = new Map(); const brushStyle = (0, helper_1.subObject)(rest, 'mask'); // Only some of shape's points in all mask, it is selected. const brushed = (points) => Array.from(axisExtent.values()).every(([x, y, x1, y1]) => points.some(([x0, y0]) => { return x0 >= x && x0 <= x1 && y0 >= y && y0 <= y1; })); const scales = axes.map((d) => d.attributes.scale); const extentOf = (D) => (D.length > 2 ? [D[0], D[D.length - 1]] : D); const indexDomain = new Map(); const initIndexDomain = () => { indexDomain.clear(); for (let i = 0; i < axes.length; i++) { const scale = scales[i]; const { domain } = scale.getOptions(); indexDomain.set(i, extentOf(domain)); } }; initIndexDomain(); // Update element when brush changed. const updateElement = (i, emit) => { const selectedElements = []; for (const element of elements) { const points = pointsOf(element); if (brushed(points)) { setState(element, 'active'); selectedElements.push(element); } else setState(element, 'inactive'); } indexDomain.set(i, selectionOf(selectedElements, i)); if (!emit) return; // Emit events. const selection = () => { if (!cross) return Array.from(indexDomain.values()); const S = []; for (const [index, domain] of indexDomain) { const scale = scales[index]; const { name } = scale.getOptions(); if (name === 'x') S[0] = domain; else S[1] = domain; } return S; }; emitter.emit('brushAxis:highlight', { nativeEvent: true, data: { selection: selection(), }, }); }; const clearElement = (emit) => { for (const element of elements) removeState(element, 'active', 'inactive'); initIndexDomain(); if (!emit) return; emitter.emit('brushAxis:remove', { nativeEvent: true }); }; const selectionOf = (selected, i) => { const scale = scales[i]; const { name } = scale.getOptions(); const domain = selected.map((d) => { const data = d.__data__; return scale.invert(data[name]); }); return extentOf((0, scale_1.domainOf)(scale, domain)); }; // Distinguish between parallel coordinates and normal charts. const cross = axes.some(isHorizontal) && axes.some((d) => !isHorizontal(d)); const handlers = []; for (let i = 0; i < axes.length; i++) { const axis = axes[i]; const createBrush = isHorizontal(axis) ? horizontalBrush : verticalBrush; const { hotZone, brushRegion, extent } = createBrush(axis, { offsetY, offsetX, cross, zIndex: 999, fill: 'transparent', // Make it interactive. }); axis.parentNode.appendChild(hotZone); const brushHandler = (0, brushHighlight_1.brush)(hotZone, Object.assign(Object.assign({}, brushStyle), { reverse, brushRegion, brushended(emit) { axisExtent.delete(axis); if (Array.from(axisExtent.entries()).length === 0) clearElement(emit); else updateElement(i, emit); }, brushed(x, y, x1, y1, emit) { axisExtent.set(axis, extent(x, y, x1, y1)); updateElement(i, emit); } })); handlers.push(brushHandler); } const onRemove = (event = {}) => { const { nativeEvent } = event; if (nativeEvent) return; handlers.forEach((d) => d.remove(false)); }; const rangeOf = (domain, scale, axis) => { const [d0, d1] = domain; const maybeStep = (scale) => (scale.getStep ? scale.getStep() : 0); const x = abstractOf(d0, scale, axis); const x1 = abstractOf(d1, scale, axis) + maybeStep(scale); if (isHorizontal(axis)) return [x, -Infinity, x1, Infinity]; return [-Infinity, x, Infinity, x1]; }; const abstractOf = (x, scale, axis) => { const { height, width } = coordinate.getOptions(); const scale1 = scale.clone(); if (isHorizontal(axis)) scale1.update({ range: [0, width] }); else scale1.update({ range: [height, 0] }); return scale1.map(x); }; const onHighlight = (event) => { const { nativeEvent } = event; if (nativeEvent) return; const { selection } = event.data; for (let i = 0; i < handlers.length; i++) { const domain = selection[i]; const handler = handlers[i]; const axis = axes[i]; if (domain) { const scale = scales[i]; handler.move(...rangeOf(domain, scale, axis), false); } else { handler.remove(false); } } }; emitter.on('brushAxis:remove', onRemove); emitter.on('brushAxis:highlight', onHighlight); return () => { handlers.forEach((d) => d.destroy()); emitter.off('brushAxis:remove', onRemove); emitter.off('brushAxis:highlight', onHighlight); }; } exports.brushAxisHighlight = brushAxisHighlight; /** * @todo Support mask size. */ function BrushAxisHighlight(options) { return (target, _, emitter) => { const { container, view, options: viewOptions } = target; const plotArea = (0, utils_1.selectPlotArea)(container); const { x: x0, y: y0 } = plotArea.getBBox(); const { coordinate } = view; return brushAxisHighlight(container, Object.assign({ elements: utils_1.selectG2Elements, axes: axesOf, offsetY: y0, offsetX: x0, points: (element) => element.__data__.points, horizontal: (axis) => { const { startPos: [sx, sy], endPos: [ex, ey], } = axis.attributes; // attention, non-horizontal does not mean vertical // it may has a specific degree angle return sx !== ex && sy === ey; }, datum: (0, utils_1.createDatumof)(view), state: (0, utils_1.mergeState)(viewOptions, [ 'active', ['inactive', { opacity: 0.5 }], ]), coordinate, emitter }, options)); }; } exports.BrushAxisHighlight = BrushAxisHighlight; //# sourceMappingURL=brushAxisHighlight.js.map