blob: 1910defc6ba2f1e517a9fc5e39819731de948af0 [file] [log] [blame]
/*
* 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 * as zrUtil from 'zrender/src/core/util';
import * as graphic from '../../util/graphic';
import {createTextStyle} from '../../label/labelStyle';
import Model from '../../model/Model';
import AxisView from './AxisView';
import AxisBuilder from './AxisBuilder';
import { AngleAxisModel } from '../../coord/polar/AxisModel';
import GlobalModel from '../../model/Global';
import Polar from '../../coord/polar/Polar';
import AngleAxis from '../../coord/polar/AngleAxis';
import { ZRTextAlign, ZRTextVerticalAlign, ColorString } from '../../util/types';
import { getECData } from '../../util/innerStore';
const elementList = [
'axisLine',
'axisLabel',
'axisTick',
'minorTick',
'splitLine',
'minorSplitLine',
'splitArea'
] as const;
function getAxisLineShape(polar: Polar, rExtent: number[], angle: number) {
rExtent[1] > rExtent[0] && (rExtent = rExtent.slice().reverse());
const start = polar.coordToPoint([rExtent[0], angle]);
const end = polar.coordToPoint([rExtent[1], angle]);
return {
x1: start[0],
y1: start[1],
x2: end[0],
y2: end[1]
};
}
function getRadiusIdx(polar: Polar) {
const radiusAxis = polar.getRadiusAxis();
return radiusAxis.inverse ? 0 : 1;
}
// Remove the last tick which will overlap the first tick
function fixAngleOverlap(list: TickCoord[]) {
const firstItem = list[0];
const lastItem = list[list.length - 1];
if (firstItem
&& lastItem
&& Math.abs(Math.abs(firstItem.coord - lastItem.coord) - 360) < 1e-4
) {
list.pop();
}
}
type TickCoord = ReturnType<AngleAxis['getTicksCoords']>[number];
type TickLabel = ReturnType<AngleAxis['getViewLabels']>[number] & {
coord: number
};
class AngleAxisView extends AxisView {
static readonly type = 'angleAxis';
readonly type = AngleAxisView.type;
axisPointerClass = 'PolarAxisPointer';
render(angleAxisModel: AngleAxisModel, ecModel: GlobalModel) {
this.group.removeAll();
if (!angleAxisModel.get('show')) {
return;
}
const angleAxis = angleAxisModel.axis;
const polar = angleAxis.polar;
const radiusExtent = polar.getRadiusAxis().getExtent();
const ticksAngles = angleAxis.getTicksCoords();
const minorTickAngles = angleAxis.getMinorTicksCoords();
const labels = zrUtil.map(angleAxis.getViewLabels(), function (labelItem: TickLabel) {
labelItem = zrUtil.clone(labelItem);
labelItem.coord = angleAxis.dataToCoord(labelItem.tickValue);
return labelItem;
});
fixAngleOverlap(labels);
fixAngleOverlap(ticksAngles);
zrUtil.each(elementList, function (name) {
if (angleAxisModel.get([name, 'show'])
&& (!angleAxis.scale.isBlank() || name === 'axisLine')
) {
angelAxisElementsBuilders[name](
this.group, angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent, labels
);
}
}, this);
}
}
interface AngleAxisElementBuilder {
(
group: graphic.Group,
angleAxisModel: AngleAxisModel,
polar: Polar,
ticksAngles: TickCoord[],
minorTickAngles: TickCoord[][],
radiusExtent: number[],
labels?: TickLabel[]
): void
}
const angelAxisElementsBuilders: Record<typeof elementList[number], AngleAxisElementBuilder> = {
axisLine(group, angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent) {
const lineStyleModel = angleAxisModel.getModel(['axisLine', 'lineStyle']);
// extent id of the axis radius (r0 and r)
const rId = getRadiusIdx(polar);
const r0Id = rId ? 0 : 1;
let shape;
if (radiusExtent[r0Id] === 0) {
shape = new graphic.Circle({
shape: {
cx: polar.cx,
cy: polar.cy,
r: radiusExtent[rId]
},
style: lineStyleModel.getLineStyle(),
z2: 1,
silent: true
});
}
else {
shape = new graphic.Ring({
shape: {
cx: polar.cx,
cy: polar.cy,
r: radiusExtent[rId],
r0: radiusExtent[r0Id]
},
style: lineStyleModel.getLineStyle(),
z2: 1,
silent: true
});
}
shape.style.fill = null;
group.add(shape);
},
axisTick(group, angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent) {
const tickModel = angleAxisModel.getModel('axisTick');
const tickLen = (tickModel.get('inside') ? -1 : 1) * tickModel.get('length');
const radius = radiusExtent[getRadiusIdx(polar)];
const lines = zrUtil.map(ticksAngles, function (tickAngleItem) {
return new graphic.Line({
shape: getAxisLineShape(polar, [radius, radius + tickLen], tickAngleItem.coord)
});
});
group.add(graphic.mergePath(
lines, {
style: zrUtil.defaults(
tickModel.getModel('lineStyle').getLineStyle(),
{
stroke: angleAxisModel.get(['axisLine', 'lineStyle', 'color'])
}
)
}
));
},
minorTick(group, angleAxisModel, polar, tickAngles, minorTickAngles, radiusExtent) {
if (!minorTickAngles.length) {
return;
}
const tickModel = angleAxisModel.getModel('axisTick');
const minorTickModel = angleAxisModel.getModel('minorTick');
const tickLen = (tickModel.get('inside') ? -1 : 1) * minorTickModel.get('length');
const radius = radiusExtent[getRadiusIdx(polar)];
const lines = [];
for (let i = 0; i < minorTickAngles.length; i++) {
for (let k = 0; k < minorTickAngles[i].length; k++) {
lines.push(new graphic.Line({
shape: getAxisLineShape(polar, [radius, radius + tickLen], minorTickAngles[i][k].coord)
}));
}
}
group.add(graphic.mergePath(
lines, {
style: zrUtil.defaults(
minorTickModel.getModel('lineStyle').getLineStyle(),
zrUtil.defaults(
tickModel.getLineStyle(), {
stroke: angleAxisModel.get(['axisLine', 'lineStyle', 'color'])
}
)
)
}
));
},
axisLabel(group, angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent, labels) {
const rawCategoryData = angleAxisModel.getCategories(true);
const commonLabelModel = angleAxisModel.getModel('axisLabel');
const labelMargin = commonLabelModel.get('margin');
const triggerEvent = angleAxisModel.get('triggerEvent');
// Use length of ticksAngles because it may remove the last tick to avoid overlapping
zrUtil.each(labels, function (labelItem, idx) {
let labelModel = commonLabelModel;
const tickValue = labelItem.tickValue;
const r = radiusExtent[getRadiusIdx(polar)];
const p = polar.coordToPoint([r + labelMargin, labelItem.coord]);
const cx = polar.cx;
const cy = polar.cy;
const labelTextAlign: ZRTextAlign = Math.abs(p[0] - cx) / r < 0.3
? 'center' : (p[0] > cx ? 'left' : 'right');
const labelTextVerticalAlign: ZRTextVerticalAlign = Math.abs(p[1] - cy) / r < 0.3
? 'middle' : (p[1] > cy ? 'top' : 'bottom');
if (rawCategoryData && rawCategoryData[tickValue]) {
const rawCategoryItem = rawCategoryData[tickValue];
if (zrUtil.isObject(rawCategoryItem) && rawCategoryItem.textStyle) {
labelModel = new Model(
rawCategoryItem.textStyle, commonLabelModel, commonLabelModel.ecModel
);
}
}
const textEl = new graphic.Text({
silent: AxisBuilder.isLabelSilent(angleAxisModel),
style: createTextStyle(labelModel, {
x: p[0],
y: p[1],
fill: labelModel.getTextColor()
|| angleAxisModel.get(['axisLine', 'lineStyle', 'color']) as ColorString,
text: labelItem.formattedLabel,
align: labelTextAlign,
verticalAlign: labelTextVerticalAlign
})
});
group.add(textEl);
// Pack data for mouse event
if (triggerEvent) {
const eventData = AxisBuilder.makeAxisEventDataBase(angleAxisModel);
eventData.targetType = 'axisLabel';
eventData.value = labelItem.rawLabel;
getECData(textEl).eventData = eventData;
}
}, this);
},
splitLine(group, angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent) {
const splitLineModel = angleAxisModel.getModel('splitLine');
const lineStyleModel = splitLineModel.getModel('lineStyle');
let lineColors = lineStyleModel.get('color');
let lineCount = 0;
lineColors = lineColors instanceof Array ? lineColors : [lineColors];
const splitLines: graphic.Line[][] = [];
for (let i = 0; i < ticksAngles.length; i++) {
const colorIndex = (lineCount++) % lineColors.length;
splitLines[colorIndex] = splitLines[colorIndex] || [];
splitLines[colorIndex].push(new graphic.Line({
shape: getAxisLineShape(polar, radiusExtent, ticksAngles[i].coord)
}));
}
// Simple optimization
// Batching the lines if color are the same
for (let i = 0; i < splitLines.length; i++) {
group.add(graphic.mergePath(splitLines[i], {
style: zrUtil.defaults({
stroke: lineColors[i % lineColors.length]
}, lineStyleModel.getLineStyle()),
silent: true,
z: angleAxisModel.get('z')
}));
}
},
minorSplitLine(group, angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent) {
if (!minorTickAngles.length) {
return;
}
const minorSplitLineModel = angleAxisModel.getModel('minorSplitLine');
const lineStyleModel = minorSplitLineModel.getModel('lineStyle');
const lines = [];
for (let i = 0; i < minorTickAngles.length; i++) {
for (let k = 0; k < minorTickAngles[i].length; k++) {
lines.push(new graphic.Line({
shape: getAxisLineShape(polar, radiusExtent, minorTickAngles[i][k].coord)
}));
}
}
group.add(graphic.mergePath(lines, {
style: lineStyleModel.getLineStyle(),
silent: true,
z: angleAxisModel.get('z')
}));
},
splitArea(group, angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent) {
if (!ticksAngles.length) {
return;
}
const splitAreaModel = angleAxisModel.getModel('splitArea');
const areaStyleModel = splitAreaModel.getModel('areaStyle');
let areaColors = areaStyleModel.get('color');
let lineCount = 0;
areaColors = areaColors instanceof Array ? areaColors : [areaColors];
const splitAreas: graphic.Sector[][] = [];
const RADIAN = Math.PI / 180;
let prevAngle = -ticksAngles[0].coord * RADIAN;
const r0 = Math.min(radiusExtent[0], radiusExtent[1]);
const r1 = Math.max(radiusExtent[0], radiusExtent[1]);
const clockwise = angleAxisModel.get('clockwise');
for (let i = 1, len = ticksAngles.length; i <= len; i++) {
const coord = i === len ? ticksAngles[0].coord : ticksAngles[i].coord;
const colorIndex = (lineCount++) % areaColors.length;
splitAreas[colorIndex] = splitAreas[colorIndex] || [];
splitAreas[colorIndex].push(new graphic.Sector({
shape: {
cx: polar.cx,
cy: polar.cy,
r0: r0,
r: r1,
startAngle: prevAngle,
endAngle: -coord * RADIAN,
clockwise: clockwise
},
silent: true
}));
prevAngle = -coord * RADIAN;
}
// Simple optimization
// Batching the lines if color are the same
for (let i = 0; i < splitAreas.length; i++) {
group.add(graphic.mergePath(splitAreas[i], {
style: zrUtil.defaults({
fill: areaColors[i % areaColors.length]
}, areaStyleModel.getAreaStyle()),
silent: true
}));
}
}
};
export default AngleAxisView;