/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import * as polygonContain from 'zrender/src/contain/polygon'; import BoundingRect, { RectLike } from 'zrender/src/core/BoundingRect'; import {linePolygonIntersect} from '../../util/graphic'; import { BrushType, BrushDimensionMinMax } from '../helper/BrushController'; import { BrushAreaParamInternal } from './BrushModel'; export interface BrushSelectableArea extends BrushAreaParamInternal { boundingRect: BoundingRect; selectors: BrushCommonSelectorsForSeries } /** * Key of the first level is brushType: `line`, `rect`, `polygon`. * See moudule:echarts/component/helper/BrushController * function param: * {Object} itemLayout fetch from data.getItemLayout(dataIndex) * {Object} selectors {point: selector, rect: selector, ...} * {Object} area {range: [[], [], ..], boudingRect} * function return: * {boolean} Whether in the given brush. */ interface BrushSelectorOnBrushType { // For chart element type "point" point( // fetch from data.getItemLayout(dataIndex) itemLayout: number[], selectors: BrushCommonSelectorsForSeries, area: BrushSelectableArea ): boolean; // For chart element type "rect" rect( // fetch from data.getItemLayout(dataIndex) itemLayout: RectLike, selectors: BrushCommonSelectorsForSeries, area: BrushSelectableArea ): boolean; } /** * This methods are corresponding to `BrushSelectorOnBrushType`, * but `area: BrushSelectableArea` is binded to each method. */ export interface BrushCommonSelectorsForSeries { // For chart element type "point" point(itemLayout: number[]): boolean; // For chart element type "rect" rect(itemLayout: RectLike): boolean; } export function makeBrushCommonSelectorForSeries( area: BrushSelectableArea ): BrushCommonSelectorsForSeries { const brushType = area.brushType; // Do not use function binding or curry for performance. const selectors: BrushCommonSelectorsForSeries = { point(itemLayout: number[]) { return selector[brushType].point(itemLayout, selectors, area); }, rect(itemLayout: RectLike) { return selector[brushType].rect(itemLayout, selectors, area); } }; return selectors; } const selector: Record = { lineX: getLineSelectors(0), lineY: getLineSelectors(1), rect: { point: function (itemLayout, selectors, area) { return itemLayout && area.boundingRect.contain(itemLayout[0], itemLayout[1]); }, rect: function (itemLayout, selectors, area) { return itemLayout && area.boundingRect.intersect(itemLayout); } }, polygon: { point: function (itemLayout, selectors, area) { return itemLayout && area.boundingRect.contain( itemLayout[0], itemLayout[1] ) && polygonContain.contain( area.range as BrushDimensionMinMax[], itemLayout[0], itemLayout[1] ); }, rect: function (itemLayout, selectors, area) { const points = area.range as BrushDimensionMinMax[]; if (!itemLayout || points.length <= 1) { return false; } const x = itemLayout.x; const y = itemLayout.y; const width = itemLayout.width; const height = itemLayout.height; const p = points[0]; if (polygonContain.contain(points, x, y) || polygonContain.contain(points, x + width, y) || polygonContain.contain(points, x, y + height) || polygonContain.contain(points, x + width, y + height) || BoundingRect.create(itemLayout).contain(p[0], p[1]) || linePolygonIntersect(x, y, x + width, y, points) || linePolygonIntersect(x, y, x, y + height, points) || linePolygonIntersect(x + width, y, x + width, y + height, points) || linePolygonIntersect(x, y + height, x + width, y + height, points) ) { return true; } } } }; function getLineSelectors(xyIndex: 0 | 1): BrushSelectorOnBrushType { const xy = ['x', 'y'] as const; const wh = ['width', 'height'] as const; return { point: function (itemLayout, selectors, area) { if (itemLayout) { const range = area.range as BrushDimensionMinMax; const p = itemLayout[xyIndex]; return inLineRange(p, range); } }, rect: function (itemLayout, selectors, area) { if (itemLayout) { const range = area.range as BrushDimensionMinMax; const layoutRange = [ itemLayout[xy[xyIndex]], itemLayout[xy[xyIndex]] + itemLayout[wh[xyIndex]] ]; layoutRange[1] < layoutRange[0] && layoutRange.reverse(); return inLineRange(layoutRange[0], range) || inLineRange(layoutRange[1], range) || inLineRange(range[0], layoutRange) || inLineRange(range[1], layoutRange); } } }; } function inLineRange(p: number, range: BrushDimensionMinMax): boolean { return range[0] <= p && p <= range[1]; } export default selector;