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. | |
| */ | |
| // Poly path support NaN point | |
| import Path, { PathProps } from 'zrender/src/graphic/Path'; | |
| import PathProxy from 'zrender/src/core/PathProxy'; | |
| import { cubicRootAt, cubicAt } from 'zrender/src/core/curve'; | |
| const mathMin = Math.min; | |
| const mathMax = Math.max; | |
| function isPointNull(x: number, y: number) { | |
| return isNaN(x) || isNaN(y); | |
| } | |
| /** | |
| * Draw smoothed line in non-monotone, in may cause undesired curve in extreme | |
| * situations. This should be used when points are non-monotone neither in x or | |
| * y dimension. | |
| */ | |
| function drawSegment( | |
| ctx: PathProxy, | |
| points: ArrayLike<number>, | |
| start: number, | |
| segLen: number, | |
| allLen: number, | |
| dir: number, | |
| smooth: number, | |
| smoothMonotone: 'x' | 'y' | 'none', | |
| connectNulls: boolean | |
| ) { | |
| let prevX: number; | |
| let prevY: number; | |
| let cpx0: number; | |
| let cpy0: number; | |
| let cpx1: number; | |
| let cpy1: number; | |
| let idx = start; | |
| let k = 0; | |
| for (; k < segLen; k++) { | |
| let x = points[idx * 2]; | |
| let y = points[idx * 2 + 1]; | |
| if (idx >= allLen || idx < 0) { | |
| break; | |
| } | |
| if (isPointNull(x, y)) { | |
| if (connectNulls) { | |
| idx += dir; | |
| continue; | |
| } | |
| break; | |
| } | |
| if (idx === start) { | |
| ctx[dir > 0 ? 'moveTo' : 'lineTo'](x, y); | |
| cpx0 = x; | |
| cpy0 = y; | |
| } | |
| else { | |
| let dx = x - prevX; | |
| let dy = y - prevY; | |
| // Ignore tiny segment. | |
| if ((dx * dx + dy * dy) < 0.5) { | |
| idx += dir; | |
| continue; | |
| } | |
| if (smooth > 0) { | |
| let nextIdx = idx + dir; | |
| let nextX = points[nextIdx * 2]; | |
| let nextY = points[nextIdx * 2 + 1]; | |
| // Ignore duplicate point | |
| while (nextX === x && nextY === y && k < segLen) { | |
| k++; | |
| nextIdx += dir; | |
| idx += dir; | |
| nextX = points[nextIdx * 2]; | |
| nextY = points[nextIdx * 2 + 1]; | |
| x = points[idx * 2]; | |
| y = points[idx * 2 + 1]; | |
| dx = x - prevX; | |
| dy = y - prevY; | |
| } | |
| let tmpK = k + 1; | |
| if (connectNulls) { | |
| // Find next point not null | |
| while (isPointNull(nextX, nextY) && tmpK < segLen) { | |
| tmpK++; | |
| nextIdx += dir; | |
| nextX = points[nextIdx * 2]; | |
| nextY = points[nextIdx * 2 + 1]; | |
| } | |
| } | |
| let ratioNextSeg = 0.5; | |
| let vx: number = 0; | |
| let vy: number = 0; | |
| let nextCpx0; | |
| let nextCpy0; | |
| // Is last point | |
| if (tmpK >= segLen || isPointNull(nextX, nextY)) { | |
| cpx1 = x; | |
| cpy1 = y; | |
| } | |
| else { | |
| vx = nextX - prevX; | |
| vy = nextY - prevY; | |
| const dx0 = x - prevX; | |
| const dx1 = nextX - x; | |
| const dy0 = y - prevY; | |
| const dy1 = nextY - y; | |
| let lenPrevSeg; | |
| let lenNextSeg; | |
| if (smoothMonotone === 'x') { | |
| lenPrevSeg = Math.abs(dx0); | |
| lenNextSeg = Math.abs(dx1); | |
| const dir = vx > 0 ? 1 : -1; | |
| cpx1 = x - dir * lenPrevSeg * smooth; | |
| cpy1 = y; | |
| nextCpx0 = x + dir * lenNextSeg * smooth; | |
| nextCpy0 = y; | |
| } | |
| else if (smoothMonotone === 'y') { | |
| lenPrevSeg = Math.abs(dy0); | |
| lenNextSeg = Math.abs(dy1); | |
| const dir = vy > 0 ? 1 : -1; | |
| cpx1 = x; | |
| cpy1 = y - dir * lenPrevSeg * smooth; | |
| nextCpx0 = x; | |
| nextCpy0 = y + dir * lenNextSeg * smooth; | |
| } | |
| else { | |
| lenPrevSeg = Math.sqrt(dx0 * dx0 + dy0 * dy0); | |
| lenNextSeg = Math.sqrt(dx1 * dx1 + dy1 * dy1); | |
| // Use ratio of seg length | |
| ratioNextSeg = lenNextSeg / (lenNextSeg + lenPrevSeg); | |
| cpx1 = x - vx * smooth * (1 - ratioNextSeg); | |
| cpy1 = y - vy * smooth * (1 - ratioNextSeg); | |
| // cp0 of next segment | |
| nextCpx0 = x + vx * smooth * ratioNextSeg; | |
| nextCpy0 = y + vy * smooth * ratioNextSeg; | |
| // Smooth constraint between point and next point. | |
| // Avoid exceeding extreme after smoothing. | |
| nextCpx0 = mathMin(nextCpx0, mathMax(nextX, x)); | |
| nextCpy0 = mathMin(nextCpy0, mathMax(nextY, y)); | |
| nextCpx0 = mathMax(nextCpx0, mathMin(nextX, x)); | |
| nextCpy0 = mathMax(nextCpy0, mathMin(nextY, y)); | |
| // Reclaculate cp1 based on the adjusted cp0 of next seg. | |
| vx = nextCpx0 - x; | |
| vy = nextCpy0 - y; | |
| cpx1 = x - vx * lenPrevSeg / lenNextSeg; | |
| cpy1 = y - vy * lenPrevSeg / lenNextSeg; | |
| // Smooth constraint between point and prev point. | |
| // Avoid exceeding extreme after smoothing. | |
| cpx1 = mathMin(cpx1, mathMax(prevX, x)); | |
| cpy1 = mathMin(cpy1, mathMax(prevY, y)); | |
| cpx1 = mathMax(cpx1, mathMin(prevX, x)); | |
| cpy1 = mathMax(cpy1, mathMin(prevY, y)); | |
| // Adjust next cp0 again. | |
| vx = x - cpx1; | |
| vy = y - cpy1; | |
| nextCpx0 = x + vx * lenNextSeg / lenPrevSeg; | |
| nextCpy0 = y + vy * lenNextSeg / lenPrevSeg; | |
| } | |
| } | |
| ctx.bezierCurveTo(cpx0, cpy0, cpx1, cpy1, x, y); | |
| cpx0 = nextCpx0; | |
| cpy0 = nextCpy0; | |
| } | |
| else { | |
| ctx.lineTo(x, y); | |
| } | |
| } | |
| prevX = x; | |
| prevY = y; | |
| idx += dir; | |
| } | |
| return k; | |
| } | |
| class ECPolylineShape { | |
| points: ArrayLike<number>; | |
| smooth = 0; | |
| smoothConstraint = true; | |
| smoothMonotone: 'x' | 'y' | 'none'; | |
| connectNulls: boolean; | |
| } | |
| interface ECPolylineProps extends PathProps { | |
| shape?: Partial<ECPolylineShape> | |
| } | |
| export class ECPolyline extends Path<ECPolylineProps> { | |
| readonly type = 'ec-polyline'; | |
| shape: ECPolylineShape; | |
| constructor(opts?: ECPolylineProps) { | |
| super(opts); | |
| } | |
| getDefaultStyle() { | |
| return { | |
| stroke: '#000', | |
| fill: null as string | |
| }; | |
| } | |
| getDefaultShape() { | |
| return new ECPolylineShape(); | |
| } | |
| buildPath(ctx: PathProxy, shape: ECPolylineShape) { | |
| const points = shape.points; | |
| let i = 0; | |
| let len = points.length / 2; | |
| // const result = getBoundingBox(points, shape.smoothConstraint); | |
| if (shape.connectNulls) { | |
| // Must remove first and last null values avoid draw error in polygon | |
| for (; len > 0; len--) { | |
| if (!isPointNull(points[len * 2 - 2], points[len * 2 - 1])) { | |
| break; | |
| } | |
| } | |
| for (; i < len; i++) { | |
| if (!isPointNull(points[i * 2], points[i * 2 + 1])) { | |
| break; | |
| } | |
| } | |
| } | |
| while (i < len) { | |
| i += drawSegment( | |
| ctx, points, i, len, len, | |
| 1, | |
| shape.smooth, | |
| shape.smoothMonotone, shape.connectNulls | |
| ) + 1; | |
| } | |
| } | |
| getPointOn(xOrY: number, dim: 'x' | 'y'): number[] { | |
| if (!this.path) { | |
| this.createPathProxy(); | |
| this.buildPath(this.path, this.shape); | |
| } | |
| const path = this.path; | |
| const data = path.data; | |
| const CMD = PathProxy.CMD; | |
| let x0; | |
| let y0; | |
| const isDimX = dim === 'x'; | |
| const roots: number[] = []; | |
| for (let i = 0; i < data.length;) { | |
| const cmd = data[i++]; | |
| let x; | |
| let y; | |
| let x2; | |
| let y2; | |
| let x3; | |
| let y3; | |
| let t; | |
| switch (cmd) { | |
| case CMD.M: | |
| x0 = data[i++]; | |
| y0 = data[i++]; | |
| break; | |
| case CMD.L: | |
| x = data[i++]; | |
| y = data[i++]; | |
| t = isDimX ? (xOrY - x0) / (x - x0) | |
| : (xOrY - y0) / (y - y0); | |
| if (t <= 1 && t >= 0) { | |
| const val = isDimX ? (y - y0) * t + y0 | |
| : (x - x0) * t + x0; | |
| return isDimX ? [xOrY, val] : [val, xOrY]; | |
| } | |
| x0 = x; | |
| y0 = y; | |
| break; | |
| case CMD.C: | |
| x = data[i++]; | |
| y = data[i++]; | |
| x2 = data[i++]; | |
| y2 = data[i++]; | |
| x3 = data[i++]; | |
| y3 = data[i++]; | |
| const nRoot = isDimX ? cubicRootAt(x0, x, x2, x3, xOrY, roots) | |
| : cubicRootAt(y0, y, y2, y3, xOrY, roots); | |
| if (nRoot > 0) { | |
| for (let i = 0; i < nRoot; i++) { | |
| const t = roots[i]; | |
| if (t <= 1 && t >= 0) { | |
| const val = isDimX ? cubicAt(y0, y, y2, y3, t) | |
| : cubicAt(x0, x, x2, x3, t); | |
| return isDimX ? [xOrY, val] : [val, xOrY]; | |
| } | |
| } | |
| } | |
| x0 = x3; | |
| y0 = y3; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| class ECPolygonShape extends ECPolylineShape { | |
| // Offset between stacked base points and points | |
| stackedOnPoints: ArrayLike<number>; | |
| stackedOnSmooth: number; | |
| } | |
| interface ECPolygonProps extends PathProps { | |
| shape?: Partial<ECPolygonShape> | |
| } | |
| export class ECPolygon extends Path { | |
| readonly type = 'ec-polygon'; | |
| shape: ECPolygonShape; | |
| constructor(opts?: ECPolygonProps) { | |
| super(opts); | |
| } | |
| getDefaultShape() { | |
| return new ECPolygonShape(); | |
| } | |
| buildPath(ctx: PathProxy, shape: ECPolygonShape) { | |
| const points = shape.points; | |
| const stackedOnPoints = shape.stackedOnPoints; | |
| let i = 0; | |
| let len = points.length / 2; | |
| const smoothMonotone = shape.smoothMonotone; | |
| if (shape.connectNulls) { | |
| // Must remove first and last null values avoid draw error in polygon | |
| for (; len > 0; len--) { | |
| if (!isPointNull(points[len * 2 - 2], points[len * 2 - 1])) { | |
| break; | |
| } | |
| } | |
| for (; i < len; i++) { | |
| if (!isPointNull(points[i * 2], points[i * 2 + 1])) { | |
| break; | |
| } | |
| } | |
| } | |
| while (i < len) { | |
| const k = drawSegment( | |
| ctx, points, i, len, len, | |
| 1, | |
| shape.smooth, | |
| smoothMonotone, shape.connectNulls | |
| ); | |
| drawSegment( | |
| ctx, stackedOnPoints, i + k - 1, k, len, | |
| -1, | |
| shape.stackedOnSmooth, | |
| smoothMonotone, shape.connectNulls | |
| ); | |
| i += k + 1; | |
| ctx.closePath(); | |
| } | |
| } | |
| } |