You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by wa...@apache.org on 2021/01/02 13:06:57 UTC
[incubator-echarts] 02/05: refactor(symbol): refact the symbol clip
logic.
This is an automated email from the ASF dual-hosted git repository.
wangzx pushed a commit to branch feat/symbol-clip
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git
commit 38da1b1617f65eee472db31c6c71bee595b01bd6
Author: plainheart <yh...@all-my-life.cn>
AuthorDate: Thu Dec 31 22:18:34 2020 +0800
refactor(symbol): refact the symbol clip logic.
---
src/chart/helper/Symbol.ts | 107 +++-------------
src/chart/helper/symbolClipHelper.ts | 240 +++++++++++++++++++++++++++++++++++
src/util/types.ts | 9 +-
3 files changed, 258 insertions(+), 98 deletions(-)
diff --git a/src/chart/helper/Symbol.ts b/src/chart/helper/Symbol.ts
index 3eff199..f2b8592 100644
--- a/src/chart/helper/Symbol.ts
+++ b/src/chart/helper/Symbol.ts
@@ -24,13 +24,14 @@ import { enterEmphasis, leaveEmphasis, enableHoverEmphasis } from '../../util/st
import {parsePercent} from '../../util/number';
import {getDefaultLabel} from './labelHelper';
import List from '../../data/List';
-import { ColorString, BlurScope, AnimationOption, SymbolClip } from '../../util/types';
+import { ColorString, BlurScope, AnimationOption } from '../../util/types';
import SeriesModel from '../../model/Series';
import { PathProps } from 'zrender/src/graphic/Path';
import { SymbolDrawSeriesScope, SymbolDrawItemModelOption } from './SymbolDraw';
-import { extend, isArray, isNumber, map } from 'zrender/src/core/util';
+import { extend } from 'zrender/src/core/util';
import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle';
import ZRImage from 'zrender/src/graphic/Image';
+import { makeSymbolClipPath } from './symbolClipHelper';
type ECSymbol = ReturnType<typeof createSymbol>;
@@ -332,20 +333,6 @@ class Symbol extends graphic.Group {
this._sizeX = symbolSize[0] / 2;
this._sizeY = symbolSize[1] / 2;
- const symbolClip = data.getItemVisual(idx, 'symbolClip');
- console.log(symbolClip)
-
- symbolPath.getClipPath() && symbolPath.removeClipPath();
-
- if (symbolClip) {
- const clipPath = makeClipPath(symbolClip, symbolSize);
- if (clipPath) {
- clipPath.scaleX = 1 / this._sizeX;
- clipPath.scaleY = 1 / this._sizeY;
- symbolPath.setClipPath(clipPath);
- }
- }
-
const emphasisState = symbolPath.ensureState('emphasis');
emphasisState.style = emphasisItemStyle;
@@ -359,6 +346,20 @@ class Symbol extends graphic.Group {
}
this.setSymbolScale(1);
+ const symbolClip = data.getItemVisual(idx, 'symbolClip');
+ console.log(symbolClip)
+
+ symbolPath.getClipPath() && symbolPath.removeClipPath();
+
+ if (symbolClip) {
+ const clipPath = makeSymbolClipPath(symbolClip, symbolSize);
+ if (clipPath) {
+ clipPath.scaleX = this.scaleX / this._sizeX;
+ clipPath.scaleY = this.scaleY / this._sizeY;
+ symbolPath.setClipPath(clipPath);
+ }
+ }
+
enableHoverEmphasis(this, focus, blurScope);
}
@@ -424,78 +425,4 @@ function driftSymbol(this: ECSymbol, dx: number, dy: number) {
this.parent.drift(dx, dy);
}
-function makeClipPath(clip: SymbolClip, symbolSize: number[]): graphic.Path {
- if (!clip) {
- return;
- }
- const size = getSymbolClipShapeSize(clip, symbolSize);
- let radius = clip.radius;
- switch (clip.type) {
- case 'circle':
- // consider circle radius in percentage
- isArray(radius) && (radius = radius[0]);
- radius = parsePercent(radius, size[0]);
- const maxRadius = size[0] / 2;
- // restrict the max radius
- if (radius > maxRadius) {
- radius = maxRadius;
- }
- return radius > 0 && new graphic.Circle({shape: {r: radius}});
- case 'rect':
- return size[0] > 0 && size[1] > 0 && new graphic.Rect({
- shape: {
- x: -size[0] / 2,
- y: -size[1] / 2,
- width: size[0],
- height: size[1],
- // not support ellipse yet
- r: radius && isArray(radius)
- ? map(radius, r => +r)
- : +(radius as number | string)
- }
- });
- case 'polygon':
- return clip.points && clip.points.length > 0 && new graphic.Polygon({
- shape: {
- points: clip.points
- }
- });
- case 'path':
- return size[0] > 0 && size[1] > 0
- ? clip.path instanceof graphic.Path
- ? clip.path
- : graphic.makePath(clip.path, {}, {
- x: -size[0] / 2,
- y: -size[1] / 2,
- width: size[0],
- height: size[1]
- }, 'center') as graphic.Path
- : void 0;
- }
-}
-
-function getSymbolClipShapeSize(clip: SymbolClip, symbolSize?: number[]): [number, number] {
- const size = [0, 0] as [number, number];
- let size0: number | string;
- let size1: number | string;
- const clipSize = clip.size;
-
- if (!clipSize) {
- size0 = symbolSize[0];
- size1 = symbolSize[1];
- }
- else if (isArray(clipSize)) {
- size0 = clipSize[0];
- size1 = clipSize[1];
- }
- else {
- size0 = size1 = clipSize;
- }
-
- size0 && (size[0] = parsePercent(size0, symbolSize[0]));
- size1 && (size[1] = parsePercent(size1, symbolSize[1]));
-
- return size;
-}
-
export default Symbol;
diff --git a/src/chart/helper/symbolClipHelper.ts b/src/chart/helper/symbolClipHelper.ts
new file mode 100644
index 0000000..1add7b4
--- /dev/null
+++ b/src/chart/helper/symbolClipHelper.ts
@@ -0,0 +1,240 @@
+/*
+* 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 { isString, isArray, map, normalizeCssArray } from 'zrender/src/core/util';
+import { parsePercent } from '../../util/number';
+import { SymbolClip } from '../../util/types';
+import * as graphic from '../../util/graphic';
+
+const REG_CSS_CLIP_SHAPE = /^([a-z]+).*\((.*)\)/;
+
+type SymbolClipPath = graphic.Path;
+
+type ParsedCSSClip = {
+ type: 'inset' | 'rect' | 'circle' | 'ellipse' | 'polygon' | 'path'
+ center?: number | string | (number | string)[]
+ radius?: number | string | (number | string)[]
+ points?: (number | string)[] | (number | string)[][],
+ path?: string
+};
+
+/**
+ * To make a symbol clip path
+ * @param clip the clip expression following the CSS `clip-path` rule
+ * @param symbolSize the size of symbol
+ * @return the symbol clip path
+ */
+export function makeSymbolClipPath(
+ clip: SymbolClip,
+ symbolSize: number[]
+): SymbolClipPath {
+
+ if (!clip) {
+ return;
+ }
+
+ if (clip instanceof graphic.Path) {
+ return clip;
+ }
+
+ if (isString(clip)) {
+ const parsedClip = parseCSSClipExp(clip);
+ return parsedClip && createSymbolClipPath(parsedClip, symbolSize);
+ }
+
+}
+
+function createSymbolClipPath(
+ clip: ParsedCSSClip,
+ symbolSize: number[]
+): SymbolClipPath {
+
+ let clipPath: SymbolClipPath;
+
+ const clipType = clip.type;
+ if (clipType === 'circle' || clipType === 'ellipse') {
+ let {radius, center} = clip;
+ let cx;
+ let cy;
+ if (!center || (center as (string | number)[]).length === 0) {
+ cx = 0;
+ cy = 0;
+ }
+ else {
+ if (!isArray(center)) {
+ center = [center, center];
+ }
+ center = [
+ parsePercent(center[0], symbolSize[0]) || 0,
+ parsePercent(center[1] == null ? center[0] : center[1], symbolSize[1]) || 0
+ ];
+ cx = -symbolSize[0] / 2 + (center as number[])[0];
+ cy = -symbolSize[1] / 2 + (center as number[])[1];
+ }
+ if (clipType === 'circle') {
+ // consider circle radius in percentage
+ isArray(radius) && (radius = radius[0]);
+ radius = parsePercent(radius, symbolSize[0]) || 0;
+ const maxRadius = symbolSize[0] / 2;
+ // restrict the max radius
+ if (radius > maxRadius) {
+ radius = maxRadius;
+ }
+ if (!(radius > 0)) {
+ return;
+ }
+ clipPath = new graphic.Circle({
+ shape: {
+ cx: cx,
+ cy: cy,
+ r: radius
+ }
+ });
+ }
+ else {
+ if (!isArray(radius)) {
+ radius = [radius, radius];
+ }
+ const rx = parsePercent(radius[0], symbolSize[0]) || 0;
+ const ry = parsePercent(radius[1] == null ? radius[0] : radius[1], symbolSize[1]) || 0;
+ clipPath = new graphic.Ellipse({
+ shape: {
+ cx: cx,
+ cy: cy,
+ rx: rx,
+ ry: ry
+ }
+ });
+ }
+ }
+ else if (clipType === 'rect') {
+ const {radius, points} = clip;
+ const shapeArgs = normalizeArray(
+ map(points as (string | number)[], (point, idx) => {
+ return parsePercent(point, symbolSize[idx % 2 ? 0 : 1]);
+ })
+ );
+ const r = normalizeArray(
+ map(radius as (string | number)[], (r, idx) => {
+ return parsePercent(r, symbolSize[idx % 2 ? 0 : 1]);
+ })
+ );
+ const width = symbolSize[0] - shapeArgs[1] - shapeArgs[3];
+ const height = symbolSize[1] - shapeArgs[0] - shapeArgs[2];
+ if (!(width > 0 && height > 0 && (width < symbolSize[0] || height < symbolSize[1]))) {
+ return;
+ }
+ clipPath = new graphic.Rect({
+ shape: {
+ x: -symbolSize[0] / 2 + shapeArgs[3],
+ y: -symbolSize[1] / 2 + shapeArgs[0],
+ width: width,
+ height: height,
+ r: r
+ }
+ });
+ }
+ else if (clipType === 'polygon') {
+ const points = clip.points;
+ if (points && points.length > 0) {
+ const normalizedPoints = map(points as (number | string)[][], point => {
+ return map(point, (val, dim) => {
+ return parsePercent(val, symbolSize[dim]) - symbolSize[dim] / 2;
+ });
+ });
+ clipPath = new graphic.Polygon({
+ shape: {
+ points: normalizedPoints
+ }
+ });
+ }
+ }
+ else if (clipType === 'path') {
+ if (clip.path) {
+ graphic.makePath(clip.path, {}, {
+ x: -symbolSize[0] / 2,
+ y: -symbolSize[1] / 2,
+ width: symbolSize[0],
+ height: symbolSize[1]
+ }, 'center');
+ }
+ }
+
+ return clipPath;
+}
+
+/**
+ * To parse clip expression value defined in CSS.
+ *
+ * Supported forms of value are as follows:
+ * `inset(22% 12% 10px 35px)`
+ * `rect(22% 12% 10px 35px)`
+ * `circle(20px at 50% 25%)`
+ * `ellipse(115px 55px at 50% 40%)`
+ * `polygon(50% 20%, 90% 80%, 10% 80%)`
+ * `path('M0.5,1 C0.5,1,0,0.7,0,0.3 A0.25,0.25,1,1,1,0.5,0.3 A0.25,0.25,1,1,1,1,0.3 C1,0.7,0.5,1,0.5,1 Z')`
+ *
+ * Note that only `px` and `%` are supported. `rem` and other units will be ignored.
+ *
+ * @param clipExp the raw clip expression in CSS
+ */
+function parseCSSClipExp(clipExp: string): ParsedCSSClip {
+ const match = clipExp && clipExp.trim().match(REG_CSS_CLIP_SHAPE);
+ if (!match) {
+ return;
+ }
+
+ const type = match[1] as any;
+ const value = match[2] && match[2].trim().replace(/(p(x|t))|(r?em)|(v(h|w))|(ch)/ig, '');
+ if (!type || !value) {
+ return;
+ }
+
+ const parsedClip: ParsedCSSClip = {type: type};
+
+ if (type === 'inset' || type === 'rect') {
+ parsedClip.type = 'rect';
+ const vals = value.split('round');
+ parsedClip.points = vals[0].trim().split(' ');
+ parsedClip.radius = vals[1] && vals[1].trim().split(' ');
+ }
+ else if (type === 'circle' || type === 'ellipse') {
+ const vals = value.split('at');
+ parsedClip.radius = vals[0].trim().split(' ');
+ parsedClip.center = vals[1] && vals[1].trim().split(' ');
+ }
+ else if (type === 'polygon') {
+ parsedClip.points = map(value.split(','), p => {
+ return p.trim().split(' ');
+ });
+ }
+ else if (type === 'path') {
+ parsedClip.path = value.replace(/[\'"]/g, '');
+ }
+
+ return parsedClip;
+}
+
+function normalizeArray(val: Parameters<typeof normalizeCssArray>[0]) {
+ const normalizedArr = normalizeCssArray(val);
+ if (normalizedArr.length === 1) {
+ normalizedArr[1] = normalizedArr[2] = normalizedArr[3] = normalizedArr[0];
+ }
+ return normalizedArr;
+}
diff --git a/src/util/types.ts b/src/util/types.ts
index 224e64e..00540be 100644
--- a/src/util/types.ts
+++ b/src/util/types.ts
@@ -47,7 +47,6 @@ import Path, { PathStyleProps } from 'zrender/src/graphic/Path';
import { ImageStyleProps } from 'zrender/src/graphic/Image';
import ZRText, { TextStyleProps } from 'zrender/src/graphic/Text';
import { Source } from '../data/Source';
-import { VectorArray } from 'zrender/src/core/vector';
// ---------------------------
@@ -837,13 +836,7 @@ export interface RoamOptionMixin {
}
// TODO: TYPE value type?
-export type SymbolClip = {
- type: 'circle' | 'rect' | 'polygon' | 'path'
- radius?: number | string | (number | string)[]
- size?: number | string | (number | string)[]
- points?: VectorArray[]
- path?: string | Path
-}
+export type SymbolClip = string | Path;
export type SymbolSizeCallback<T> = (rawValue: any, params: T) => number | number[];
export type SymbolCallback<T> = (rawValue: any, params: T) => string;
export type SymbolRotateCallback<T> = (rawValue: any, params: T) => number;
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org