Spaces:
Running
Running
| /* | |
| * 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<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); | |
| 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<typeof elementList[number], AngleAxisElementBuilder> = { | |
| axisLine(group, angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent) { | |
| const lineStyleModel = angleAxisModel.getModel(['axisLine', 'lineStyle']); | |
| const angleAxis = polar.getAngleAxis(); | |
| const RADIAN = Math.PI / 180; | |
| const angleExtent = angleAxis.getExtent(); | |
| // extent id of the axis radius (r0 and r) | |
| const rId = getRadiusIdx(polar); | |
| const r0Id = rId ? 0 : 1; | |
| let shape; | |
| const shapeType = Math.abs(angleExtent[1] - angleExtent[0]) === 360 ? 'Circle' : 'Arc'; | |
| if (radiusExtent[r0Id] === 0) { | |
| shape = new graphic[shapeType]({ | |
| shape: { | |
| cx: polar.cx, | |
| cy: polar.cy, | |
| r: radiusExtent[rId], | |
| startAngle: -angleExtent[0] * RADIAN, | |
| endAngle: -angleExtent[1] * RADIAN, | |
| clockwise: angleAxis.inverse | |
| }, | |
| 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; | |