| /* |
| * 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. |
| */ |
| |
| /** |
| * Simple view coordinate system |
| * Mapping given x, y to transformd view x, y |
| */ |
| |
| import * as vector from 'zrender/src/core/vector'; |
| import * as matrix from 'zrender/src/core/matrix'; |
| import BoundingRect from 'zrender/src/core/BoundingRect'; |
| import Transformable from 'zrender/src/core/Transformable'; |
| import { CoordinateSystemMaster, CoordinateSystem } from './CoordinateSystem'; |
| import GlobalModel from '../model/Global'; |
| import { ParsedModelFinder, ParsedModelFinderKnown } from '../util/model'; |
| |
| const v2ApplyTransform = vector.applyTransform; |
| |
| export type ViewCoordSysTransformInfoPart = Pick<Transformable, 'x' | 'y' | 'scaleX' | 'scaleY'>; |
| |
| class View extends Transformable implements CoordinateSystemMaster, CoordinateSystem { |
| |
| readonly type: string = 'view'; |
| |
| static dimensions = ['x', 'y']; |
| readonly dimensions = ['x', 'y']; |
| |
| readonly name: string; |
| |
| zoomLimit: { |
| max?: number; |
| min?: number; |
| }; |
| |
| /** |
| * Represents the transform brought by roam/zoom. |
| * If `View['_viewRect']` applies roam transform, |
| * we can get the final displayed rect. |
| */ |
| private _roamTransformable = new Transformable(); |
| /** |
| * Represents the transform from `View['_rect']` to `View['_viewRect']`. |
| */ |
| protected _rawTransformable = new Transformable(); |
| private _rawTransform: matrix.MatrixArray; |
| |
| /** |
| * This is a user specified point on the source, which will be |
| * located to the center of the `View['_viewRect']`. |
| * The unit this the same as `View['_rect']`. |
| */ |
| private _center: number[]; |
| private _zoom: number; |
| |
| /** |
| * The rect of the source, where the measure is used by "data" and "center". |
| * Has nothing to do with roam/zoom. |
| * The unit is defined by the source. For example, |
| * for geo source the unit is lat/lng, |
| * for SVG source the unit is the same as the width/height defined in SVG. |
| */ |
| private _rect: BoundingRect; |
| /** |
| * The visible rect on the canvas. Has nothing to do with roam/zoom. |
| * The unit of `View['_viewRect']` is pixel of the canvas. |
| */ |
| private _viewRect: BoundingRect; |
| |
| |
| constructor(name?: string) { |
| super(); |
| this.name = name; |
| } |
| |
| setBoundingRect(x: number, y: number, width: number, height: number): BoundingRect { |
| this._rect = new BoundingRect(x, y, width, height); |
| return this._rect; |
| } |
| |
| /** |
| * @return {module:zrender/core/BoundingRect} |
| */ |
| getBoundingRect(): BoundingRect { |
| return this._rect; |
| } |
| |
| setViewRect(x: number, y: number, width: number, height: number): void { |
| this._transformTo(x, y, width, height); |
| this._viewRect = new BoundingRect(x, y, width, height); |
| } |
| |
| /** |
| * Transformed to particular position and size |
| */ |
| protected _transformTo(x: number, y: number, width: number, height: number): void { |
| const rect = this.getBoundingRect(); |
| const rawTransform = this._rawTransformable; |
| |
| rawTransform.transform = rect.calculateTransform( |
| new BoundingRect(x, y, width, height) |
| ); |
| |
| const rawParent = rawTransform.parent; |
| rawTransform.parent = null; |
| rawTransform.decomposeTransform(); |
| rawTransform.parent = rawParent; |
| |
| this._updateTransform(); |
| } |
| |
| /** |
| * Set center of view |
| */ |
| setCenter(centerCoord?: number[]): void { |
| if (!centerCoord) { |
| return; |
| } |
| this._center = centerCoord; |
| |
| this._updateCenterAndZoom(); |
| } |
| |
| setZoom(zoom: number): void { |
| zoom = zoom || 1; |
| |
| const zoomLimit = this.zoomLimit; |
| if (zoomLimit) { |
| if (zoomLimit.max != null) { |
| zoom = Math.min(zoomLimit.max, zoom); |
| } |
| if (zoomLimit.min != null) { |
| zoom = Math.max(zoomLimit.min, zoom); |
| } |
| } |
| this._zoom = zoom; |
| |
| this._updateCenterAndZoom(); |
| } |
| |
| /** |
| * Get default center without roam |
| */ |
| getDefaultCenter(): number[] { |
| // Rect before any transform |
| const rawRect = this.getBoundingRect(); |
| const cx = rawRect.x + rawRect.width / 2; |
| const cy = rawRect.y + rawRect.height / 2; |
| |
| return [cx, cy]; |
| } |
| |
| getCenter(): number[] { |
| return this._center || this.getDefaultCenter(); |
| } |
| |
| getZoom(): number { |
| return this._zoom || 1; |
| } |
| |
| getRoamTransform(): matrix.MatrixArray { |
| return this._roamTransformable.getLocalTransform(); |
| } |
| |
| /** |
| * Remove roam |
| */ |
| private _updateCenterAndZoom(): void { |
| // Must update after view transform updated |
| const rawTransformMatrix = this._rawTransformable.getLocalTransform(); |
| const roamTransform = this._roamTransformable; |
| let defaultCenter = this.getDefaultCenter(); |
| let center = this.getCenter(); |
| const zoom = this.getZoom(); |
| |
| center = vector.applyTransform([], center, rawTransformMatrix); |
| defaultCenter = vector.applyTransform([], defaultCenter, rawTransformMatrix); |
| |
| roamTransform.originX = center[0]; |
| roamTransform.originY = center[1]; |
| roamTransform.x = defaultCenter[0] - center[0]; |
| roamTransform.y = defaultCenter[1] - center[1]; |
| roamTransform.scaleX = roamTransform.scaleY = zoom; |
| |
| this._updateTransform(); |
| } |
| |
| /** |
| * Update transform props on `this` based on the current |
| * `this._roamTransformable` and `this._rawTransformable`. |
| */ |
| protected _updateTransform(): void { |
| const roamTransformable = this._roamTransformable; |
| const rawTransformable = this._rawTransformable; |
| |
| rawTransformable.parent = roamTransformable; |
| roamTransformable.updateTransform(); |
| rawTransformable.updateTransform(); |
| |
| matrix.copy(this.transform || (this.transform = []), rawTransformable.transform || matrix.create()); |
| |
| this._rawTransform = rawTransformable.getLocalTransform(); |
| |
| this.invTransform = this.invTransform || []; |
| matrix.invert(this.invTransform, this.transform); |
| |
| this.decomposeTransform(); |
| } |
| |
| getTransformInfo(): { |
| roam: ViewCoordSysTransformInfoPart |
| raw: ViewCoordSysTransformInfoPart |
| } { |
| const rawTransformable = this._rawTransformable; |
| |
| const roamTransformable = this._roamTransformable; |
| // Becuase roamTransformabel has `originX/originY` modified, |
| // but the caller of `getTransformInfo` can not handle `originX/originY`, |
| // so need to recalcualte them. |
| const dummyTransformable = new Transformable(); |
| dummyTransformable.transform = roamTransformable.transform; |
| dummyTransformable.decomposeTransform(); |
| |
| return { |
| roam: { |
| x: dummyTransformable.x, |
| y: dummyTransformable.y, |
| scaleX: dummyTransformable.scaleX, |
| scaleY: dummyTransformable.scaleY |
| }, |
| raw: { |
| x: rawTransformable.x, |
| y: rawTransformable.y, |
| scaleX: rawTransformable.scaleX, |
| scaleY: rawTransformable.scaleY |
| } |
| }; |
| } |
| |
| getViewRect(): BoundingRect { |
| return this._viewRect; |
| } |
| |
| /** |
| * Get view rect after roam transform |
| */ |
| getViewRectAfterRoam(): BoundingRect { |
| const rect = this.getBoundingRect().clone(); |
| rect.applyTransform(this.transform); |
| return rect; |
| } |
| |
| /** |
| * Convert a single (lon, lat) data item to (x, y) point. |
| */ |
| dataToPoint(data: number[], noRoam?: boolean, out?: number[]): number[] { |
| const transform = noRoam ? this._rawTransform : this.transform; |
| out = out || []; |
| return transform |
| ? v2ApplyTransform(out, data, transform) |
| : vector.copy(out, data); |
| } |
| |
| /** |
| * Convert a (x, y) point to (lon, lat) data |
| */ |
| pointToData(point: number[]): number[] { |
| const invTransform = this.invTransform; |
| return invTransform |
| ? v2ApplyTransform([], point, invTransform) |
| : [point[0], point[1]]; |
| } |
| |
| convertToPixel(ecModel: GlobalModel, finder: ParsedModelFinder, value: number[]): number[] { |
| const coordSys = getCoordSys(finder); |
| return coordSys === this ? coordSys.dataToPoint(value) : null; |
| } |
| |
| convertFromPixel(ecModel: GlobalModel, finder: ParsedModelFinder, pixel: number[]): number[] { |
| const coordSys = getCoordSys(finder); |
| return coordSys === this ? coordSys.pointToData(pixel) : null; |
| } |
| |
| /** |
| * @implements |
| */ |
| containPoint(point: number[]): boolean { |
| return this.getViewRectAfterRoam().contain(point[0], point[1]); |
| } |
| |
| /** |
| * @return {number} |
| */ |
| // getScalarScale() { |
| // // Use determinant square root of transform to mutiply scalar |
| // let m = this.transform; |
| // let det = Math.sqrt(Math.abs(m[0] * m[3] - m[2] * m[1])); |
| // return det; |
| // } |
| } |
| |
| function getCoordSys(finder: ParsedModelFinderKnown): View { |
| const seriesModel = finder.seriesModel; |
| return seriesModel ? seriesModel.coordinateSystem as View : null; // e.g., graph. |
| } |
| |
| export default View; |