recharts
Version:
React charts
184 lines (153 loc) • 5.15 kB
text/typescript
import _ from 'lodash';
import { getPercentValue } from './DataUtils';
import { parseScale, checkDomainOfScale, getTicksOfScale } from './ChartUtils';
import { Coordinate, ChartOffset, GeometrySector } from './types';
export const RADIAN = Math.PI / 180;
export const degreeToRadian = (angle: number) => (angle * Math.PI) / 180;
export const radianToDegree = (angleInRadian: number) => (angleInRadian * 180) / Math.PI;
export const polarToCartesian = (cx: number, cy: number, radius: number, angle: number): Coordinate => ({
x: cx + Math.cos(-RADIAN * angle) * radius,
y: cy + Math.sin(-RADIAN * angle) * radius,
});
export const getMaxRadius = (
width: number,
height: number,
offset: ChartOffset = {
top: 0,
right: 0,
bottom: 0,
left: 0,
},
) =>
Math.min(
Math.abs(width - (offset.left || 0) - (offset.right || 0)),
Math.abs(height - (offset.top || 0) - (offset.bottom || 0)),
) / 2;
/**
* Calculate the scale function, position, width, height of axes
* @param {Object} props Latest props
* @param {Object} axisMap The configuration of axes
* @param {Object} offset The offset of main part in the svg element
* @param {Object} axisType The type of axes, radius-axis or angle-axis
* @param {String} chartName The name of chart
* @return {Object} Configuration
*/
export const formatAxisMap = (
props: any,
axisMap: any,
offset: ChartOffset,
axisType: 'angleAxis' | 'radiusAxis',
chartName: string,
) => {
const { width, height } = props;
let { startAngle, endAngle } = props;
const cx = getPercentValue(props.cx, width, width / 2);
const cy = getPercentValue(props.cy, height, height / 2);
const maxRadius = getMaxRadius(width, height, offset);
const innerRadius = getPercentValue(props.innerRadius, maxRadius, 0);
const outerRadius = getPercentValue(props.outerRadius, maxRadius, maxRadius * 0.8);
const ids = Object.keys(axisMap);
return ids.reduce((result, id) => {
const axis = axisMap[id];
const { domain, reversed } = axis;
let range;
if (_.isNil(axis.range)) {
if (axisType === 'angleAxis') {
range = [startAngle, endAngle];
} else if (axisType === 'radiusAxis') {
range = [innerRadius, outerRadius];
}
if (reversed) {
range = [range[1], range[0]];
}
} else {
({ range } = axis);
[startAngle, endAngle] = range;
}
const { realScaleType, scale } = parseScale(axis, chartName);
scale.domain(domain).range(range);
checkDomainOfScale(scale);
const ticks = getTicksOfScale(scale, { ...axis, realScaleType });
const finalAxis = {
...axis,
...ticks,
range,
radius: outerRadius,
realScaleType,
scale,
cx,
cy,
innerRadius,
outerRadius,
startAngle,
endAngle,
};
return { ...result, [id]: finalAxis };
}, {});
};
export const distanceBetweenPoints = (point: Coordinate, anotherPoint: Coordinate) => {
const { x: x1, y: y1 } = point;
const { x: x2, y: y2 } = anotherPoint;
return Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2);
};
export const getAngleOfPoint = ({ x, y }: Coordinate, { cx, cy }: GeometrySector) => {
const radius = distanceBetweenPoints({ x, y }, { x: cx, y: cy });
if (radius <= 0) {
return { radius };
}
const cos = (x - cx) / radius;
let angleInRadian = Math.acos(cos);
if (y > cy) {
angleInRadian = 2 * Math.PI - angleInRadian;
}
return { radius, angle: radianToDegree(angleInRadian), angleInRadian };
};
export const formatAngleOfSector = ({ startAngle, endAngle }: GeometrySector) => {
const startCnt = Math.floor(startAngle / 360);
const endCnt = Math.floor(endAngle / 360);
const min = Math.min(startCnt, endCnt);
return {
startAngle: startAngle - min * 360,
endAngle: endAngle - min * 360,
};
};
const reverseFormatAngleOfSetor = (angle: number, { startAngle, endAngle }: GeometrySector) => {
const startCnt = Math.floor(startAngle / 360);
const endCnt = Math.floor(endAngle / 360);
const min = Math.min(startCnt, endCnt);
return angle + min * 360;
};
export const inRangeOfSector = ({ x, y }: Coordinate, sector: GeometrySector) => {
const { radius, angle } = getAngleOfPoint({ x, y }, sector);
const { innerRadius, outerRadius } = sector;
if (radius < innerRadius || radius > outerRadius) {
return false;
}
if (radius === 0) {
return true;
}
const { startAngle, endAngle } = formatAngleOfSector(sector);
let formatAngle = angle;
let inRange;
if (startAngle <= endAngle) {
while (formatAngle > endAngle) {
formatAngle -= 360;
}
while (formatAngle < startAngle) {
formatAngle += 360;
}
inRange = formatAngle >= startAngle && formatAngle <= endAngle;
} else {
while (formatAngle > startAngle) {
formatAngle -= 360;
}
while (formatAngle < endAngle) {
formatAngle += 360;
}
inRange = formatAngle >= endAngle && formatAngle <= startAngle;
}
if (inRange) {
return { ...sector, radius, angle: reverseFormatAngleOfSetor(formatAngle, sector) };
}
return null;
};