/* * 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'; import { parsePercent } from '../util/number'; import type ExtensionAPI from '../core/ExtensionAPI'; const v2ApplyTransform = vector.applyTransform; export type ViewCoordSysTransformInfoPart = Pick; 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 | string)[], api: ExtensionAPI): void { if (!centerCoord) { return; } this._center = [ parsePercent(centerCoord[0], api.getWidth()), parsePercent(centerCoord[1], api.getHeight()) ]; 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; // Because roamTransformabel has `originX/originY` modified, // but the caller of `getTransformInfo` can not handle `originX/originY`, // so need to recalculate 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 multiply 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;