/* * 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'; import OrdinalScale from '../../scale/Ordinal'; 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[number]; type TickLabel = ReturnType[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); const scale = angleAxis.scale; const tickValue = scale.type === 'ordinal' ? (scale as OrdinalScale).getRawOrdinalNumber(labelItem.tickValue) : labelItem.tickValue; labelItem.coord = angleAxis.dataToCoord(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 = { 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;