|  | /* | 
|  | * 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 RadiusAxis from './RadiusAxis'; | 
|  | import AngleAxis from './AngleAxis'; | 
|  | import PolarModel from './PolarModel'; | 
|  | import { CoordinateSystem, CoordinateSystemMaster, CoordinateSystemClipArea } from '../CoordinateSystem'; | 
|  | import GlobalModel from '../../model/Global'; | 
|  | import { ParsedModelFinder, ParsedModelFinderKnown } from '../../util/model'; | 
|  | import { ScaleDataValue } from '../../util/types'; | 
|  | import ExtensionAPI from '../../core/ExtensionAPI'; | 
|  |  | 
|  | export const polarDimensions = ['radius', 'angle']; | 
|  |  | 
|  | interface Polar { | 
|  | update(ecModel: GlobalModel, api: ExtensionAPI): void | 
|  | } | 
|  | class Polar implements CoordinateSystem, CoordinateSystemMaster { | 
|  |  | 
|  | readonly name: string; | 
|  |  | 
|  | readonly dimensions = polarDimensions; | 
|  |  | 
|  | readonly type = 'polar'; | 
|  |  | 
|  | /** | 
|  | * x of polar center | 
|  | */ | 
|  | cx = 0; | 
|  |  | 
|  | /** | 
|  | * y of polar center | 
|  | */ | 
|  | cy = 0; | 
|  |  | 
|  | private _radiusAxis = new RadiusAxis(); | 
|  |  | 
|  | private _angleAxis = new AngleAxis(); | 
|  |  | 
|  | axisPointerEnabled = true; | 
|  |  | 
|  | model: PolarModel; | 
|  |  | 
|  | constructor(name: string) { | 
|  | this.name = name || ''; | 
|  |  | 
|  | this._radiusAxis.polar = this._angleAxis.polar = this; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * If contain coord | 
|  | */ | 
|  | containPoint(point: number[]) { | 
|  | const coord = this.pointToCoord(point); | 
|  | return this._radiusAxis.contain(coord[0]) | 
|  | && this._angleAxis.contain(coord[1]); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * If contain data | 
|  | */ | 
|  | containData(data: number[]) { | 
|  | return this._radiusAxis.containData(data[0]) | 
|  | && this._angleAxis.containData(data[1]); | 
|  | } | 
|  |  | 
|  | getAxis(dim: 'radius' | 'angle') { | 
|  | const key = ('_' + dim + 'Axis') as '_radiusAxis' | '_angleAxis'; | 
|  | return this[key]; | 
|  | } | 
|  |  | 
|  | getAxes() { | 
|  | return [this._radiusAxis, this._angleAxis]; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Get axes by type of scale | 
|  | */ | 
|  | getAxesByScale(scaleType: 'ordinal' | 'interval' | 'time' | 'log') { | 
|  | const axes = []; | 
|  | const angleAxis = this._angleAxis; | 
|  | const radiusAxis = this._radiusAxis; | 
|  | angleAxis.scale.type === scaleType && axes.push(angleAxis); | 
|  | radiusAxis.scale.type === scaleType && axes.push(radiusAxis); | 
|  |  | 
|  | return axes; | 
|  | } | 
|  |  | 
|  | getAngleAxis() { | 
|  | return this._angleAxis; | 
|  | } | 
|  |  | 
|  | getRadiusAxis() { | 
|  | return this._radiusAxis; | 
|  | } | 
|  |  | 
|  | getOtherAxis(axis: AngleAxis | RadiusAxis): AngleAxis | RadiusAxis { | 
|  | const angleAxis = this._angleAxis; | 
|  | return axis === angleAxis ? this._radiusAxis : angleAxis; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Base axis will be used on stacking. | 
|  | * | 
|  | */ | 
|  | getBaseAxis() { | 
|  | return this.getAxesByScale('ordinal')[0] | 
|  | || this.getAxesByScale('time')[0] | 
|  | || this.getAngleAxis(); | 
|  | } | 
|  |  | 
|  | getTooltipAxes(dim: 'radius' | 'angle' | 'auto') { | 
|  | const baseAxis = (dim != null && dim !== 'auto') | 
|  | ? this.getAxis(dim) : this.getBaseAxis(); | 
|  | return { | 
|  | baseAxes: [baseAxis], | 
|  | otherAxes: [this.getOtherAxis(baseAxis)] | 
|  | }; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Convert a single data item to (x, y) point. | 
|  | * Parameter data is an array which the first element is radius and the second is angle | 
|  | */ | 
|  | dataToPoint(data: ScaleDataValue[], clamp?: boolean) { | 
|  | return this.coordToPoint([ | 
|  | this._radiusAxis.dataToRadius(data[0], clamp), | 
|  | this._angleAxis.dataToAngle(data[1], clamp) | 
|  | ]); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Convert a (x, y) point to data | 
|  | */ | 
|  | pointToData(point: number[], clamp?: boolean) { | 
|  | const coord = this.pointToCoord(point); | 
|  | return [ | 
|  | this._radiusAxis.radiusToData(coord[0], clamp), | 
|  | this._angleAxis.angleToData(coord[1], clamp) | 
|  | ]; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Convert a (x, y) point to (radius, angle) coord | 
|  | */ | 
|  | pointToCoord(point: number[]) { | 
|  | let dx = point[0] - this.cx; | 
|  | let dy = point[1] - this.cy; | 
|  | const angleAxis = this.getAngleAxis(); | 
|  | const extent = angleAxis.getExtent(); | 
|  | let minAngle = Math.min(extent[0], extent[1]); | 
|  | let maxAngle = Math.max(extent[0], extent[1]); | 
|  | // Fix fixed extent in polarCreator | 
|  | // FIXME | 
|  | angleAxis.inverse | 
|  | ? (minAngle = maxAngle - 360) | 
|  | : (maxAngle = minAngle + 360); | 
|  |  | 
|  | const radius = Math.sqrt(dx * dx + dy * dy); | 
|  | dx /= radius; | 
|  | dy /= radius; | 
|  |  | 
|  | let radian = Math.atan2(-dy, dx) / Math.PI * 180; | 
|  |  | 
|  | // move to angleExtent | 
|  | const dir = radian < minAngle ? 1 : -1; | 
|  | while (radian < minAngle || radian > maxAngle) { | 
|  | radian += dir * 360; | 
|  | } | 
|  |  | 
|  | return [radius, radian]; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Convert a (radius, angle) coord to (x, y) point | 
|  | */ | 
|  | coordToPoint(coord: number[]) { | 
|  | const radius = coord[0]; | 
|  | const radian = coord[1] / 180 * Math.PI; | 
|  | const x = Math.cos(radian) * radius + this.cx; | 
|  | // Inverse the y | 
|  | const y = -Math.sin(radian) * radius + this.cy; | 
|  |  | 
|  | return [x, y]; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Get ring area of cartesian. | 
|  | * Area will have a contain function to determine if a point is in the coordinate system. | 
|  | */ | 
|  | getArea(): PolarArea { | 
|  |  | 
|  | const angleAxis = this.getAngleAxis(); | 
|  | const radiusAxis = this.getRadiusAxis(); | 
|  |  | 
|  | const radiusExtent = radiusAxis.getExtent().slice(); | 
|  | radiusExtent[0] > radiusExtent[1] && radiusExtent.reverse(); | 
|  | const angleExtent = angleAxis.getExtent(); | 
|  |  | 
|  | const RADIAN = Math.PI / 180; | 
|  |  | 
|  | return { | 
|  | cx: this.cx, | 
|  | cy: this.cy, | 
|  | r0: radiusExtent[0], | 
|  | r: radiusExtent[1], | 
|  | startAngle: -angleExtent[0] * RADIAN, | 
|  | endAngle: -angleExtent[1] * RADIAN, | 
|  | clockwise: angleAxis.inverse, | 
|  | contain(x: number, y: number) { | 
|  | // It's a ring shape. | 
|  | // Start angle and end angle don't matter | 
|  | const dx = x - this.cx; | 
|  | const dy = y - this.cy; | 
|  | // minus a tiny value 1e-4 to avoid being clipped unexpectedly | 
|  | const d2 = dx * dx + dy * dy - 1e-4; | 
|  | const r = this.r; | 
|  | const r0 = this.r0; | 
|  |  | 
|  | return d2 <= r * r && d2 >= r0 * r0; | 
|  | } | 
|  | }; | 
|  | } | 
|  |  | 
|  | convertToPixel(ecModel: GlobalModel, finder: ParsedModelFinder, value: ScaleDataValue[]) { | 
|  | const coordSys = getCoordSys(finder); | 
|  | return coordSys === this ? this.dataToPoint(value) : null; | 
|  | } | 
|  |  | 
|  | convertFromPixel(ecModel: GlobalModel, finder: ParsedModelFinder, pixel: number[]) { | 
|  | const coordSys = getCoordSys(finder); | 
|  | return coordSys === this ? this.pointToData(pixel) : null; | 
|  | } | 
|  | } | 
|  |  | 
|  | function getCoordSys(finder: ParsedModelFinderKnown) { | 
|  | const seriesModel = finder.seriesModel; | 
|  | const polarModel = finder.polarModel as PolarModel; | 
|  | return polarModel && polarModel.coordinateSystem | 
|  | || seriesModel && seriesModel.coordinateSystem as Polar; | 
|  | } | 
|  |  | 
|  | interface PolarArea extends CoordinateSystemClipArea { | 
|  | cx: number | 
|  | cy: number | 
|  | r0: number | 
|  | r: number | 
|  | startAngle: number | 
|  | endAngle: number | 
|  | clockwise: boolean | 
|  | } | 
|  |  | 
|  | export default Polar; |