/*
|
* 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';
|
|
interface Polar {
|
update(ecModel: GlobalModel, api: ExtensionAPI): void
|
}
|
class Polar implements CoordinateSystem, CoordinateSystemMaster {
|
|
readonly name: string;
|
|
readonly dimensions = ['radius', 'angle'];
|
|
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;
|
const d2 = dx * dx + dy * dy;
|
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;
|