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. | |
| */ | |
| // Symbol factory | |
| import { each, isArray, retrieve2 } from 'zrender/src/core/util'; | |
| import * as graphic from './graphic'; | |
| import BoundingRect from 'zrender/src/core/BoundingRect'; | |
| import { calculateTextPosition } from 'zrender/src/contain/text'; | |
| import { Dictionary } from 'zrender/src/core/types'; | |
| import { SymbolOptionMixin, ZRColor } from './types'; | |
| import { parsePercent } from './number'; | |
| export type ECSymbol = graphic.Path & { | |
| __isEmptyBrush?: boolean | |
| setColor: (color: ZRColor, innerColor?: ZRColor) => void | |
| getColor: () => ZRColor | |
| }; | |
| type SymbolCtor = { new(): ECSymbol }; | |
| type SymbolShapeMaker = (x: number, y: number, w: number, h: number, shape: Dictionary<any>) => void; | |
| /** | |
| * Triangle shape | |
| * @inner | |
| */ | |
| const Triangle = graphic.Path.extend({ | |
| type: 'triangle', | |
| shape: { | |
| cx: 0, | |
| cy: 0, | |
| width: 0, | |
| height: 0 | |
| }, | |
| buildPath: function (path, shape) { | |
| const cx = shape.cx; | |
| const cy = shape.cy; | |
| const width = shape.width / 2; | |
| const height = shape.height / 2; | |
| path.moveTo(cx, cy - height); | |
| path.lineTo(cx + width, cy + height); | |
| path.lineTo(cx - width, cy + height); | |
| path.closePath(); | |
| } | |
| }); | |
| /** | |
| * Diamond shape | |
| * @inner | |
| */ | |
| const Diamond = graphic.Path.extend({ | |
| type: 'diamond', | |
| shape: { | |
| cx: 0, | |
| cy: 0, | |
| width: 0, | |
| height: 0 | |
| }, | |
| buildPath: function (path, shape) { | |
| const cx = shape.cx; | |
| const cy = shape.cy; | |
| const width = shape.width / 2; | |
| const height = shape.height / 2; | |
| path.moveTo(cx, cy - height); | |
| path.lineTo(cx + width, cy); | |
| path.lineTo(cx, cy + height); | |
| path.lineTo(cx - width, cy); | |
| path.closePath(); | |
| } | |
| }); | |
| /** | |
| * Pin shape | |
| * @inner | |
| */ | |
| const Pin = graphic.Path.extend({ | |
| type: 'pin', | |
| shape: { | |
| // x, y on the cusp | |
| x: 0, | |
| y: 0, | |
| width: 0, | |
| height: 0 | |
| }, | |
| buildPath: function (path, shape) { | |
| const x = shape.x; | |
| const y = shape.y; | |
| const w = shape.width / 5 * 3; | |
| // Height must be larger than width | |
| const h = Math.max(w, shape.height); | |
| const r = w / 2; | |
| // Dist on y with tangent point and circle center | |
| const dy = r * r / (h - r); | |
| const cy = y - h + r + dy; | |
| const angle = Math.asin(dy / r); | |
| // Dist on x with tangent point and circle center | |
| const dx = Math.cos(angle) * r; | |
| const tanX = Math.sin(angle); | |
| const tanY = Math.cos(angle); | |
| const cpLen = r * 0.6; | |
| const cpLen2 = r * 0.7; | |
| path.moveTo(x - dx, cy + dy); | |
| path.arc( | |
| x, cy, r, | |
| Math.PI - angle, | |
| Math.PI * 2 + angle | |
| ); | |
| path.bezierCurveTo( | |
| x + dx - tanX * cpLen, cy + dy + tanY * cpLen, | |
| x, y - cpLen2, | |
| x, y | |
| ); | |
| path.bezierCurveTo( | |
| x, y - cpLen2, | |
| x - dx + tanX * cpLen, cy + dy + tanY * cpLen, | |
| x - dx, cy + dy | |
| ); | |
| path.closePath(); | |
| } | |
| }); | |
| /** | |
| * Arrow shape | |
| * @inner | |
| */ | |
| const Arrow = graphic.Path.extend({ | |
| type: 'arrow', | |
| shape: { | |
| x: 0, | |
| y: 0, | |
| width: 0, | |
| height: 0 | |
| }, | |
| buildPath: function (ctx, shape) { | |
| const height = shape.height; | |
| const width = shape.width; | |
| const x = shape.x; | |
| const y = shape.y; | |
| const dx = width / 3 * 2; | |
| ctx.moveTo(x, y); | |
| ctx.lineTo(x + dx, y + height); | |
| ctx.lineTo(x, y + height / 4 * 3); | |
| ctx.lineTo(x - dx, y + height); | |
| ctx.lineTo(x, y); | |
| ctx.closePath(); | |
| } | |
| }); | |
| /** | |
| * Map of path constructors | |
| */ | |
| // TODO Use function to build symbol path. | |
| const symbolCtors: Dictionary<SymbolCtor> = { | |
| line: graphic.Line as unknown as SymbolCtor, | |
| rect: graphic.Rect as unknown as SymbolCtor, | |
| roundRect: graphic.Rect as unknown as SymbolCtor, | |
| square: graphic.Rect as unknown as SymbolCtor, | |
| circle: graphic.Circle as unknown as SymbolCtor, | |
| diamond: Diamond as unknown as SymbolCtor, | |
| pin: Pin as unknown as SymbolCtor, | |
| arrow: Arrow as unknown as SymbolCtor, | |
| triangle: Triangle as unknown as SymbolCtor | |
| }; | |
| const symbolShapeMakers: Dictionary<SymbolShapeMaker> = { | |
| line: function (x, y, w, h, shape: graphic.Line['shape']) { | |
| shape.x1 = x; | |
| shape.y1 = y + h / 2; | |
| shape.x2 = x + w; | |
| shape.y2 = y + h / 2; | |
| }, | |
| rect: function (x, y, w, h, shape: graphic.Rect['shape']) { | |
| shape.x = x; | |
| shape.y = y; | |
| shape.width = w; | |
| shape.height = h; | |
| }, | |
| roundRect: function (x, y, w, h, shape: graphic.Rect['shape']) { | |
| shape.x = x; | |
| shape.y = y; | |
| shape.width = w; | |
| shape.height = h; | |
| shape.r = Math.min(w, h) / 4; | |
| }, | |
| square: function (x, y, w, h, shape: graphic.Rect['shape']) { | |
| const size = Math.min(w, h); | |
| shape.x = x; | |
| shape.y = y; | |
| shape.width = size; | |
| shape.height = size; | |
| }, | |
| circle: function (x, y, w, h, shape: graphic.Circle['shape']) { | |
| // Put circle in the center of square | |
| shape.cx = x + w / 2; | |
| shape.cy = y + h / 2; | |
| shape.r = Math.min(w, h) / 2; | |
| }, | |
| diamond: function (x, y, w, h, shape: InstanceType<typeof Diamond>['shape']) { | |
| shape.cx = x + w / 2; | |
| shape.cy = y + h / 2; | |
| shape.width = w; | |
| shape.height = h; | |
| }, | |
| pin: function (x, y, w, h, shape: InstanceType<typeof Pin>['shape']) { | |
| shape.x = x + w / 2; | |
| shape.y = y + h / 2; | |
| shape.width = w; | |
| shape.height = h; | |
| }, | |
| arrow: function (x, y, w, h, shape: InstanceType<typeof Arrow>['shape']) { | |
| shape.x = x + w / 2; | |
| shape.y = y + h / 2; | |
| shape.width = w; | |
| shape.height = h; | |
| }, | |
| triangle: function (x, y, w, h, shape: InstanceType<typeof Triangle>['shape']) { | |
| shape.cx = x + w / 2; | |
| shape.cy = y + h / 2; | |
| shape.width = w; | |
| shape.height = h; | |
| } | |
| }; | |
| export const symbolBuildProxies: Dictionary<ECSymbol> = {}; | |
| each(symbolCtors, function (Ctor, name) { | |
| symbolBuildProxies[name] = new Ctor(); | |
| }); | |
| const SymbolClz = graphic.Path.extend({ | |
| type: 'symbol', | |
| shape: { | |
| symbolType: '', | |
| x: 0, | |
| y: 0, | |
| width: 0, | |
| height: 0 | |
| }, | |
| calculateTextPosition(out, config, rect) { | |
| const res = calculateTextPosition(out, config, rect); | |
| const shape = this.shape; | |
| if (shape && shape.symbolType === 'pin' && config.position === 'inside') { | |
| res.y = rect.y + rect.height * 0.4; | |
| } | |
| return res; | |
| }, | |
| buildPath(ctx, shape, inBundle) { | |
| let symbolType = shape.symbolType; | |
| if (symbolType !== 'none') { | |
| let proxySymbol = symbolBuildProxies[symbolType]; | |
| if (!proxySymbol) { | |
| // Default rect | |
| symbolType = 'rect'; | |
| proxySymbol = symbolBuildProxies[symbolType]; | |
| } | |
| symbolShapeMakers[symbolType]( | |
| shape.x, shape.y, shape.width, shape.height, proxySymbol.shape | |
| ); | |
| proxySymbol.buildPath(ctx, proxySymbol.shape, inBundle); | |
| } | |
| } | |
| }); | |
| // Provide setColor helper method to avoid determine if set the fill or stroke outside | |
| function symbolPathSetColor(this: ECSymbol, color: ZRColor, innerColor?: ZRColor) { | |
| if (this.type !== 'image') { | |
| const symbolStyle = this.style; | |
| if (this.__isEmptyBrush) { | |
| symbolStyle.stroke = color; | |
| symbolStyle.fill = innerColor || '#fff'; | |
| // TODO Same width with lineStyle in LineView | |
| symbolStyle.lineWidth = 2; | |
| } | |
| else if (this.shape.symbolType === 'line') { | |
| symbolStyle.stroke = color; | |
| } | |
| else { | |
| symbolStyle.fill = color; | |
| } | |
| this.markRedraw(); | |
| } | |
| } | |
| /** | |
| * Create a symbol element with given symbol configuration: shape, x, y, width, height, color | |
| */ | |
| export function createSymbol( | |
| symbolType: string, | |
| x: number, | |
| y: number, | |
| w: number, | |
| h: number, | |
| color?: ZRColor, | |
| // whether to keep the ratio of w/h, | |
| keepAspect?: boolean | |
| ) { | |
| // TODO Support image object, DynamicImage. | |
| const isEmpty = symbolType.indexOf('empty') === 0; | |
| if (isEmpty) { | |
| symbolType = symbolType.substr(5, 1).toLowerCase() + symbolType.substr(6); | |
| } | |
| let symbolPath: ECSymbol | graphic.Image; | |
| if (symbolType.indexOf('image://') === 0) { | |
| symbolPath = graphic.makeImage( | |
| symbolType.slice(8), | |
| new BoundingRect(x, y, w, h), | |
| keepAspect ? 'center' : 'cover' | |
| ); | |
| } | |
| else if (symbolType.indexOf('path://') === 0) { | |
| symbolPath = graphic.makePath( | |
| symbolType.slice(7), | |
| {}, | |
| new BoundingRect(x, y, w, h), | |
| keepAspect ? 'center' : 'cover' | |
| ) as unknown as ECSymbol; | |
| } | |
| else { | |
| symbolPath = new SymbolClz({ | |
| shape: { | |
| symbolType: symbolType, | |
| x: x, | |
| y: y, | |
| width: w, | |
| height: h | |
| } | |
| }) as unknown as ECSymbol; | |
| } | |
| (symbolPath as ECSymbol).__isEmptyBrush = isEmpty; | |
| // TODO Should deprecate setColor | |
| (symbolPath as ECSymbol).setColor = symbolPathSetColor; | |
| if (color) { | |
| (symbolPath as ECSymbol).setColor(color); | |
| } | |
| return symbolPath as ECSymbol; | |
| } | |
| export function normalizeSymbolSize(symbolSize: number | number[]): [number, number] { | |
| if (!isArray(symbolSize)) { | |
| symbolSize = [+symbolSize, +symbolSize]; | |
| } | |
| return [symbolSize[0] || 0, symbolSize[1] || 0]; | |
| } | |
| export function normalizeSymbolOffset( | |
| symbolOffset: SymbolOptionMixin['symbolOffset'], | |
| symbolSize: number[] | |
| ): [number, number] { | |
| if (symbolOffset == null) { | |
| return; | |
| } | |
| if (!isArray(symbolOffset)) { | |
| symbolOffset = [symbolOffset, symbolOffset]; | |
| } | |
| return [ | |
| parsePercent(symbolOffset[0], symbolSize[0]) || 0, | |
| parsePercent(retrieve2(symbolOffset[1], symbolOffset[0]), symbolSize[1]) || 0 | |
| ]; | |
| } | |