diff --git a/index.d.ts b/index.d.ts
index 811a2c6cf..047e18390 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -1,2 +1,2 @@
-export * from './src/zrender';
-export * from './src/export';
\ No newline at end of file
+export * from './lib/zrender';
+export * from './lib/export';
diff --git a/index.js b/index.js
index 903454279..a95f37b0a 100644
--- a/index.js
+++ b/index.js
@@ -5,4 +5,4 @@ import {registerPainter} from './lib/zrender';
import CanvasPainter from './lib/canvas/Painter';
import SVGPainter from './lib/svg/Painter';
registerPainter('canvas', CanvasPainter);
-registerPainter('svg', SVGPainter);
\ No newline at end of file
+registerPainter('svg', SVGPainter);
diff --git a/package-lock.json b/package-lock.json
index 370ad6e64..609711f3b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -712,6 +712,32 @@
"integrity": "sha512-518yewjSga1jLdiLrcmpMFlaba5P+50b0TWNFUpC+SL9Yzf0kMi57qw+bMl+rQ08cGqH1vLx4eg9YFUbZXgZ0Q==",
"dev": true
},
+ "@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "requires": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ }
+ },
+ "@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true
+ },
+ "@nodelib/fs.walk": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz",
+ "integrity": "sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA==",
+ "dev": true,
+ "requires": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ }
+ },
"@sinonjs/commons": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.0.tgz",
@@ -774,12 +800,6 @@
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
"dev": true
},
- "@types/eslint-visitor-keys": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
- "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==",
- "dev": true
- },
"@types/estree": {
"version": "0.0.42",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.42.tgz",
@@ -822,9 +842,9 @@
}
},
"@types/json-schema": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz",
- "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==",
+ "version": "7.0.7",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
+ "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==",
"dev": true
},
"@types/node": {
@@ -855,99 +875,186 @@
"dev": true
},
"@typescript-eslint/eslint-plugin": {
- "version": "2.34.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz",
- "integrity": "sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ==",
+ "version": "4.28.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.0.tgz",
+ "integrity": "sha512-KcF6p3zWhf1f8xO84tuBailV5cN92vhS+VT7UJsPzGBm9VnQqfI9AsiMUFUCYHTYPg1uCCo+HyiDnpDuvkAMfQ==",
"dev": true,
"requires": {
- "@typescript-eslint/experimental-utils": "2.34.0",
+ "@typescript-eslint/experimental-utils": "4.28.0",
+ "@typescript-eslint/scope-manager": "4.28.0",
+ "debug": "^4.3.1",
"functional-red-black-tree": "^1.0.1",
- "regexpp": "^3.0.0",
- "tsutils": "^3.17.1"
+ "regexpp": "^3.1.0",
+ "semver": "^7.3.5",
+ "tsutils": "^3.21.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "semver": {
+ "version": "7.3.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+ "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^6.0.0"
+ }
+ }
}
},
"@typescript-eslint/experimental-utils": {
- "version": "2.34.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz",
- "integrity": "sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA==",
+ "version": "4.28.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.0.tgz",
+ "integrity": "sha512-9XD9s7mt3QWMk82GoyUpc/Ji03vz4T5AYlHF9DcoFNfJ/y3UAclRsfGiE2gLfXtyC+JRA3trR7cR296TEb1oiQ==",
"dev": true,
"requires": {
- "@types/json-schema": "^7.0.3",
- "@typescript-eslint/typescript-estree": "2.34.0",
- "eslint-scope": "^5.0.0",
- "eslint-utils": "^2.0.0"
+ "@types/json-schema": "^7.0.7",
+ "@typescript-eslint/scope-manager": "4.28.0",
+ "@typescript-eslint/types": "4.28.0",
+ "@typescript-eslint/typescript-estree": "4.28.0",
+ "eslint-scope": "^5.1.1",
+ "eslint-utils": "^3.0.0"
},
"dependencies": {
+ "eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ }
+ },
"eslint-utils": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
+ "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+ "dev": true,
+ "requires": {
+ "eslint-visitor-keys": "^2.0.0"
+ }
+ },
+ "eslint-visitor-keys": {
"version": "2.1.0",
- "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
- "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+ "dev": true
+ },
+ "esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"dev": true,
"requires": {
- "eslint-visitor-keys": "^1.1.0"
+ "estraverse": "^5.2.0"
+ },
+ "dependencies": {
+ "estraverse": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
+ "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
+ "dev": true
+ }
}
}
}
},
"@typescript-eslint/parser": {
- "version": "2.24.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.24.0.tgz",
- "integrity": "sha512-H2Y7uacwSSg8IbVxdYExSI3T7uM1DzmOn2COGtCahCC3g8YtM1xYAPi2MAHyfPs61VKxP/J/UiSctcRgw4G8aw==",
+ "version": "4.28.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.0.tgz",
+ "integrity": "sha512-7x4D22oPY8fDaOCvkuXtYYTQ6mTMmkivwEzS+7iml9F9VkHGbbZ3x4fHRwxAb5KeuSkLqfnYjs46tGx2Nour4A==",
"dev": true,
"requires": {
- "@types/eslint-visitor-keys": "^1.0.0",
- "@typescript-eslint/experimental-utils": "2.24.0",
- "@typescript-eslint/typescript-estree": "2.24.0",
- "eslint-visitor-keys": "^1.1.0"
+ "@typescript-eslint/scope-manager": "4.28.0",
+ "@typescript-eslint/types": "4.28.0",
+ "@typescript-eslint/typescript-estree": "4.28.0",
+ "debug": "^4.3.1"
},
"dependencies": {
- "@typescript-eslint/experimental-utils": {
- "version": "2.24.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.24.0.tgz",
- "integrity": "sha512-DXrwuXTdVh3ycNCMYmWhUzn/gfqu9N0VzNnahjiDJvcyhfBy4gb59ncVZVxdp5XzBC77dCncu0daQgOkbvPwBw==",
+ "debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ }
+ }
+ },
+ "@typescript-eslint/scope-manager": {
+ "version": "4.28.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.0.tgz",
+ "integrity": "sha512-eCALCeScs5P/EYjwo6se9bdjtrh8ByWjtHzOkC4Tia6QQWtQr3PHovxh3TdYTuFcurkYI4rmFsRFpucADIkseg==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "4.28.0",
+ "@typescript-eslint/visitor-keys": "4.28.0"
+ }
+ },
+ "@typescript-eslint/types": {
+ "version": "4.28.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.0.tgz",
+ "integrity": "sha512-p16xMNKKoiJCVZY5PW/AfILw2xe1LfruTcfAKBj3a+wgNYP5I9ZEKNDOItoRt53p4EiPV6iRSICy8EPanG9ZVA==",
+ "dev": true
+ },
+ "@typescript-eslint/typescript-estree": {
+ "version": "4.28.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.0.tgz",
+ "integrity": "sha512-m19UQTRtxMzKAm8QxfKpvh6OwQSXaW1CdZPoCaQuLwAq7VZMNuhJmZR4g5281s2ECt658sldnJfdpSZZaxUGMQ==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "4.28.0",
+ "@typescript-eslint/visitor-keys": "4.28.0",
+ "debug": "^4.3.1",
+ "globby": "^11.0.3",
+ "is-glob": "^4.0.1",
+ "semver": "^7.3.5",
+ "tsutils": "^3.21.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"dev": true,
"requires": {
- "@types/json-schema": "^7.0.3",
- "@typescript-eslint/typescript-estree": "2.24.0",
- "eslint-scope": "^5.0.0"
+ "ms": "2.1.2"
}
},
- "@typescript-eslint/typescript-estree": {
- "version": "2.24.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.24.0.tgz",
- "integrity": "sha512-RJ0yMe5owMSix55qX7Mi9V6z2FDuuDpN6eR5fzRJrp+8in9UF41IGNQHbg5aMK4/PjVaEQksLvz0IA8n+Mr/FA==",
+ "semver": {
+ "version": "7.3.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+ "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"dev": true,
"requires": {
- "debug": "^4.1.1",
- "eslint-visitor-keys": "^1.1.0",
- "glob": "^7.1.6",
- "is-glob": "^4.0.1",
- "lodash": "^4.17.15",
- "semver": "^6.3.0",
- "tsutils": "^3.17.1"
+ "lru-cache": "^6.0.0"
}
}
}
},
- "@typescript-eslint/typescript-estree": {
- "version": "2.34.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz",
- "integrity": "sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg==",
+ "@typescript-eslint/visitor-keys": {
+ "version": "4.28.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.0.tgz",
+ "integrity": "sha512-PjJyTWwrlrvM5jazxYF5ZPs/nl0kHDZMVbuIcbpawVXaDPelp3+S9zpOz5RmVUfS/fD5l5+ZXNKnWhNYjPzCvw==",
"dev": true,
"requires": {
- "debug": "^4.1.1",
- "eslint-visitor-keys": "^1.1.0",
- "glob": "^7.1.6",
- "is-glob": "^4.0.1",
- "lodash": "^4.17.15",
- "semver": "^7.3.2",
- "tsutils": "^3.17.1"
+ "@typescript-eslint/types": "4.28.0",
+ "eslint-visitor-keys": "^2.0.0"
},
"dependencies": {
- "semver": {
- "version": "7.3.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
- "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
+ "eslint-visitor-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
"dev": true
}
}
@@ -1070,6 +1177,12 @@
"integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=",
"dev": true
},
+ "array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true
+ },
"array-unique": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
@@ -1763,6 +1876,15 @@
"integrity": "sha512-nFIfVk5B/NStCsJ+zaPO4vYuLjlzQ6uFvPxzYyHlejNZ/UGa7G/n7peOXVrVNvRuyfstt+mZQYGpjxg9Z6N8Kw==",
"dev": true
},
+ "dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "requires": {
+ "path-type": "^4.0.0"
+ }
+ },
"doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -2247,6 +2369,20 @@
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
"dev": true
},
+ "fast-glob": {
+ "version": "3.2.5",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz",
+ "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==",
+ "dev": true,
+ "requires": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.0",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.2",
+ "picomatch": "^2.2.1"
+ }
+ },
"fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -2259,6 +2395,15 @@
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true
},
+ "fastq": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz",
+ "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==",
+ "dev": true,
+ "requires": {
+ "reusify": "^1.0.4"
+ }
+ },
"fb-watchman": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz",
@@ -2466,6 +2611,28 @@
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"dev": true
},
+ "globby": {
+ "version": "11.0.4",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz",
+ "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==",
+ "dev": true,
+ "requires": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.1.1",
+ "ignore": "^5.1.4",
+ "merge2": "^1.3.0",
+ "slash": "^3.0.0"
+ },
+ "dependencies": {
+ "ignore": {
+ "version": "5.1.8",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
+ "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
+ "dev": true
+ }
+ }
+ },
"graceful-fs": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
@@ -3851,6 +4018,15 @@
"@sinonjs/commons": "^1.7.0"
}
},
+ "lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ },
"make-dir": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz",
@@ -3896,6 +4072,12 @@
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true
},
+ "merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true
+ },
"micromatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
@@ -4270,6 +4452,12 @@
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
+ "path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true
+ },
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
@@ -4407,6 +4595,12 @@
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
"dev": true
},
+ "queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true
+ },
"react-is": {
"version": "16.12.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz",
@@ -4433,9 +4627,9 @@
}
},
"regexpp": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
- "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
+ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
"dev": true
},
"remove-trailing-separator": {
@@ -4600,6 +4794,12 @@
"integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
"dev": true
},
+ "reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true
+ },
"rimraf": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
@@ -4688,6 +4888,15 @@
"is-promise": "^2.1.0"
}
},
+ "run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "requires": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
"rxjs": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz",
@@ -5540,14 +5749,14 @@
}
},
"tslib": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
- "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
+ "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
},
"tsutils": {
- "version": "3.17.1",
- "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz",
- "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==",
+ "version": "3.21.0",
+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
+ "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
"dev": true,
"requires": {
"tslib": "^1.8.1"
@@ -5607,9 +5816,9 @@
}
},
"typescript": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz",
- "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==",
+ "version": "4.3.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
+ "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==",
"dev": true
},
"uglify-js": {
@@ -5964,6 +6173,12 @@
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
"dev": true
},
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
"yargs": {
"version": "15.1.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.1.0.tgz",
diff --git a/package.json b/package.json
index a25b582fa..d6b543639 100644
--- a/package.json
+++ b/package.json
@@ -26,7 +26,7 @@
"module": "index.js",
"main": "dist/zrender.js",
"dependencies": {
- "tslib": "2.0.3"
+ "tslib": "2.3.0"
},
"sideEffects": [
"lib/canvas/canvas.js",
@@ -35,8 +35,8 @@
"devDependencies": {
"@microsoft/api-extractor": "^7.7.2",
"@types/jest": "^25.1.2",
- "@typescript-eslint/eslint-plugin": "^2.24.0",
- "@typescript-eslint/parser": "^2.24.0",
+ "@typescript-eslint/eslint-plugin": "^4.9.1",
+ "@typescript-eslint/parser": "^4.9.1",
"chalk": "^3.0.0",
"commander": "2.11.0",
"eslint": "6.3.0",
@@ -47,7 +47,7 @@
"rollup-plugin-typescript2": "^0.25.3",
"rollup-plugin-uglify": "^6.0.4",
"ts-jest": "^25.2.0",
- "typescript": "^4.1.2",
+ "typescript": "4.3.5",
"uglify-js": "^3.10.0"
}
}
diff --git a/src/Element.ts b/src/Element.ts
index 1922a498f..fd5324e38 100644
--- a/src/Element.ts
+++ b/src/Element.ts
@@ -8,7 +8,7 @@ import {
} from './core/types';
import Path from './graphic/Path';
import BoundingRect, { RectLike } from './core/BoundingRect';
-import Eventful, {EventQuery, EventCallback} from './core/Eventful';
+import Eventful from './core/Eventful';
import ZRText, { DefaultTextStyle } from './graphic/Text';
import { calculateTextPosition, TextPositionCalculationResult, parsePercent } from './contain/text';
import {
@@ -28,7 +28,7 @@ import Point from './core/Point';
import { LIGHT_LABEL_COLOR, DARK_LABEL_COLOR } from './config';
import { parse, stringify } from './tool/color';
import env from './core/env';
-import { REDARAW_BIT, STYLE_CHANGED_BIT } from './graphic/constants';
+import { REDARAW_BIT } from './graphic/constants';
export interface ElementAnimateConfig {
duration?: number
@@ -284,16 +284,22 @@ export type ElementCommonState = {
hoverLayer?: boolean
}
+export type ElementCalculateTextPosition = (
+ out: TextPositionCalculationResult,
+ style: ElementTextConfig,
+ rect: RectLike
+) => TextPositionCalculationResult;
+
let tmpTextPosCalcRes = {} as TextPositionCalculationResult;
let tmpBoundingRect = new BoundingRect(0, 0, 0, 0);
-interface Element extends Transformable, Eventful, ElementEventHandlerProps {
- // Provide more typed event callback params for mouse events.
- on(event: ElementEventName, handler: ElementEventCallback, context?: Ctx): this
- on(event: string, handler: EventCallback, context?: Ctx): this
-
- on(event: ElementEventName, query: EventQuery, handler: ElementEventCallback, context?: Ctx): this
- on(event: string, query: EventQuery, handler: EventCallback, context?: Ctx): this
+interface Element extends Transformable,
+ Eventful<{
+ [key in ElementEventName]: (e: ElementEvent) => void | boolean
+ } & {
+ [key in string]: (...args: any) => void | boolean
+ }>,
+ ElementEventHandlerProps {
}
class Element {
@@ -487,6 +493,7 @@ class Element {
*/
update() {
this.updateTransform();
+
if (this.__dirty) {
this.updateInnerText();
}
@@ -501,35 +508,21 @@ class Element {
}
const textConfig = this.textConfig;
const isLocal = textConfig.local;
- const attachedTransform = textEl.attachedTransform;
+ const innerTransformable = textEl.innerTransformable;
let textAlign: TextAlign;
let textVerticalAlign: TextVerticalAlign;
let textStyleChanged = false;
- // TODO Restore the element after textConfig changed.
-
- // NOTE: Can't be used both as normal element and as textContent.
- if (isLocal) {
- // Apply host's transform.
- // TODO parent is always be group for developers. But can be displayble inside.
- attachedTransform.parent = this as unknown as Group;
- }
- else {
- attachedTransform.parent = null;
- }
+ // Apply host's transform.
+ innerTransformable.parent = isLocal ? this as unknown as Group : null;
let innerOrigin = false;
// Reset x/y/rotation
- attachedTransform.x = textEl.x;
- attachedTransform.y = textEl.y;
- attachedTransform.originX = textEl.originX;
- attachedTransform.originY = textEl.originY;
- attachedTransform.rotation = textEl.rotation;
- attachedTransform.scaleX = textEl.scaleX;
- attachedTransform.scaleY = textEl.scaleY;
+ innerTransformable.copyTransform(textEl);
+
// Force set attached text's position if `position` is in config.
if (textConfig.position != null) {
let layoutRect = tmpBoundingRect;
@@ -552,8 +545,8 @@ class Element {
// TODO Should modify back if textConfig.position is set to null again.
// Or textContent is detached.
- attachedTransform.x = tmpTextPosCalcRes.x;
- attachedTransform.y = tmpTextPosCalcRes.y;
+ innerTransformable.x = tmpTextPosCalcRes.x;
+ innerTransformable.y = tmpTextPosCalcRes.y;
// User specified align/verticalAlign has higher priority, which is
// useful in the case that attached text is rotated 90 degree.
@@ -574,26 +567,26 @@ class Element {
}
innerOrigin = true;
- attachedTransform.originX = -attachedTransform.x + relOriginX + (isLocal ? 0 : layoutRect.x);
- attachedTransform.originY = -attachedTransform.y + relOriginY + (isLocal ? 0 : layoutRect.y);
+ innerTransformable.originX = -innerTransformable.x + relOriginX + (isLocal ? 0 : layoutRect.x);
+ innerTransformable.originY = -innerTransformable.y + relOriginY + (isLocal ? 0 : layoutRect.y);
}
}
if (textConfig.rotation != null) {
- attachedTransform.rotation = textConfig.rotation;
+ innerTransformable.rotation = textConfig.rotation;
}
// TODO
const textOffset = textConfig.offset;
if (textOffset) {
- attachedTransform.x += textOffset[0];
- attachedTransform.y += textOffset[1];
+ innerTransformable.x += textOffset[0];
+ innerTransformable.y += textOffset[1];
// Not change the user set origin.
if (!innerOrigin) {
- attachedTransform.originX = -textOffset[0];
- attachedTransform.originY = -textOffset[1];
+ innerTransformable.originX = -textOffset[0];
+ innerTransformable.originY = -textOffset[1];
}
}
@@ -1288,7 +1281,7 @@ class Element {
throw new Error('Text element has been added to zrender.');
}
- textEl.attachedTransform = new Transformable();
+ textEl.innerTransformable = new Transformable();
this._attachComponent(textEl);
@@ -1323,7 +1316,7 @@ class Element {
removeTextContent() {
const textEl = this._textContent;
if (textEl) {
- textEl.attachedTransform = null;
+ textEl.innerTransformable = null;
this._detachComponent(textEl);
this._textContent = null;
this._innerTextDefaultStyle = null;
@@ -1402,6 +1395,10 @@ class Element {
* Not recursively because it will be invoked when element added to storage.
*/
addSelfToZr(zr: ZRenderType) {
+ if (this.__zr === zr) {
+ return;
+ }
+
this.__zr = zr;
// 添加动画
const animators = this.animators;
@@ -1427,6 +1424,10 @@ class Element {
* Not recursively because it will be invoked when element added to storage.
*/
removeSelfFromZr(zr: ZRenderType) {
+ if (!this.__zr) {
+ return;
+ }
+
this.__zr = null;
// Remove animation
const animators = this.animators;
@@ -1564,7 +1565,7 @@ class Element {
// Overload definitions
animateFrom(
- target: Props, cfg: Omit, animationProps?: MapToType
+ target: Props, cfg: ElementAnimateConfig, animationProps?: MapToType
) {
animateTo(this, target, cfg, animationProps, true);
}
@@ -1608,9 +1609,7 @@ class Element {
* verticalAlign: string. optional. use style.textVerticalAlign by default.
* }
*/
- calculateTextPosition: (
- out: TextPositionCalculationResult, style: ElementTextConfig, rect: RectLike
- ) => TextPositionCalculationResult
+ calculateTextPosition: ElementCalculateTextPosition;
protected static initDefaultProps = (function () {
const elProto = Element.prototype;
diff --git a/src/Storage.ts b/src/Storage.ts
index f7f9ff928..7c094f9fb 100644
--- a/src/Storage.ts
+++ b/src/Storage.ts
@@ -1,6 +1,6 @@
import * as util from './core/util';
import env from './core/env';
-import Group from './graphic/Group';
+import Group, { GroupLike } from './graphic/Group';
import Element from './Element';
// Use timsort because in most case elements are partially sorted
@@ -133,8 +133,8 @@ export default class Storage {
}
// ZRText and Group and combining morphing Path may use children
- if ((el as Group).childrenRef) {
- const children = (el as Group).childrenRef();
+ if ((el as GroupLike).childrenRef) {
+ const children = (el as GroupLike).childrenRef();
for (let i = 0; i < children.length; i++) {
const child = children[i];
diff --git a/src/animation/Animator.ts b/src/animation/Animator.ts
index 5e02c0044..1fd3a12e9 100644
--- a/src/animation/Animator.ts
+++ b/src/animation/Animator.ts
@@ -145,21 +145,6 @@ function is1DArraySame(arr0: NumberArray, arr1: NumberArray) {
return true;
}
-function is2DArraySame(arr0: NumberArray[], arr1: NumberArray[]) {
- const len = arr0.length;
- if (len !== arr1.length) {
- return false;
- }
- const len2 = arr0[0].length;
- for (let i = 0; i < len; i++) {
- for (let j = 0; j < len2; j++) {
- if (arr0[i][j] !== arr1[i][j]) {
- return false;
- }
- }
- }
- return true;
-}
/**
* Catmull Rom interpolate number
@@ -311,8 +296,10 @@ class Track {
}
needsAnimate() {
- // return this.keyframes.length >= 2;
- return !this._isAllValueEqual && this.keyframes.length >= 2 && this.interpolable;
+ return !this._isAllValueEqual
+ && this.keyframes.length >= 2
+ && this.interpolable
+ && this.maxTime > 0;
}
getAdditiveTrack() {
@@ -706,10 +693,10 @@ export default class Animator {
private _additiveAnimators: Animator[]
- private _doneList: DoneCallback[]
- private _onframeList: OnframeCallback[]
+ private _doneCbs: DoneCallback[]
+ private _onframeCbs: OnframeCallback[]
- private _abortedList: AbortCallback[]
+ private _abortedCbs: AbortCallback[]
private _clip: Clip = null
@@ -811,7 +798,7 @@ export default class Animator {
// Clear clip
this._clip = null;
- const doneList = this._doneList;
+ const doneList = this._doneCbs;
if (doneList) {
const len = doneList.length;
for (let i = 0; i < len; i++) {
@@ -823,7 +810,7 @@ export default class Animator {
this._setTracksFinished();
const animation = this.animation;
- const abortedList = this._abortedList;
+ const abortedList = this._abortedCbs;
if (animation) {
animation.removeClip(this._clip);
@@ -877,7 +864,7 @@ export default class Animator {
for (let i = 0; i < this._trackKeys.length; i++) {
const propName = this._trackKeys[i];
const track = this._tracks[propName];
- const additiveTrack = this._getAdditiveTrack(propName)
+ const additiveTrack = this._getAdditiveTrack(propName);
const kfs = track.keyframes;
track.prepare(additiveTrack);
if (track.needsAnimate()) {
@@ -920,7 +907,7 @@ export default class Animator {
// Because target may be changed.
tracks[i].step(self._target, percent);
}
- const onframeList = self._onframeList;
+ const onframeList = self._onframeCbs;
if (onframeList) {
for (let i = 0; i < onframeList.length; i++) {
onframeList[i](self._target, percent);
@@ -980,10 +967,10 @@ export default class Animator {
*/
during(cb: OnframeCallback) {
if (cb) {
- if (!this._onframeList) {
- this._onframeList = [];
+ if (!this._onframeCbs) {
+ this._onframeCbs = [];
}
- this._onframeList.push(cb);
+ this._onframeCbs.push(cb);
}
return this;
}
@@ -993,20 +980,20 @@ export default class Animator {
*/
done(cb: DoneCallback) {
if (cb) {
- if (!this._doneList) {
- this._doneList = [];
+ if (!this._doneCbs) {
+ this._doneCbs = [];
}
- this._doneList.push(cb);
+ this._doneCbs.push(cb);
}
return this;
}
aborted(cb: AbortCallback) {
if (cb) {
- if (!this._abortedList) {
- this._abortedList = [];
+ if (!this._abortedCbs) {
+ this._abortedCbs = [];
}
- this._abortedList.push(cb);
+ this._abortedCbs.push(cb);
}
return this;
}
diff --git a/src/canvas/graphic.ts b/src/canvas/graphic.ts
index 834725cef..b65f494f1 100644
--- a/src/canvas/graphic.ts
+++ b/src/canvas/graphic.ts
@@ -25,6 +25,14 @@ function styleHasStroke(style: PathStyleProps) {
return !(stroke == null || stroke === 'none' || !(style.lineWidth > 0));
}
+// ignore lineWidth and must be string
+// Expected color but found '[' when color is gradient
+function isValidStrokeFillStyle(
+ strokeOrFill: PathStyleProps['stroke'] | PathStyleProps['fill']
+): strokeOrFill is string {
+ return typeof strokeOrFill === 'string' && strokeOrFill !== 'none';
+}
+
function styleHasFill(style: PathStyleProps) {
const fill = style.fill;
return fill != null && fill !== 'none';
@@ -459,14 +467,14 @@ function bindPathAndTextCommonStyle(
flushPathDrawn(ctx, scope);
styleChanged = true;
}
- ctx.fillStyle = style.fill as string;
+ isValidStrokeFillStyle(style.fill) && (ctx.fillStyle = style.fill);
}
if (forceSetAll || style.stroke !== prevStyle.stroke) {
if (!styleChanged) {
flushPathDrawn(ctx, scope);
styleChanged = true;
}
- ctx.strokeStyle = style.stroke as string;
+ isValidStrokeFillStyle(style.stroke) && (ctx.strokeStyle = style.stroke);
}
if (forceSetAll || style.opacity !== prevStyle.opacity) {
if (!styleChanged) {
diff --git a/src/contain/path.ts b/src/contain/path.ts
index ef0d855eb..16a37aa1f 100644
--- a/src/contain/path.ts
+++ b/src/contain/path.ts
@@ -11,8 +11,6 @@ const PI2 = Math.PI * 2;
const EPSILON = 1e-4;
-type PathData = Float32Array | number[];
-
function isAroundEqual(a: number, b: number) {
return Math.abs(a - b) < EPSILON;
}
diff --git a/src/core/Eventful.ts b/src/core/Eventful.ts
index 700573dc9..d66ce7675 100644
--- a/src/core/Eventful.ts
+++ b/src/core/Eventful.ts
@@ -1,20 +1,26 @@
+import { Dictionary, WithThisType } from './types';
+
// Return true to cancel bubble
-export type EventCallback = (
- this: CbThis, eventParam: EvtParam, ...args: unknown[]
-) => boolean | void
+export type EventCallbackSingleParam = EvtParam extends any
+ ? (params: EvtParam) => boolean | void
+ : never
+
+export type EventCallback = EvtParams extends any[]
+ ? (...args: EvtParams) => boolean | void
+ : never
export type EventQuery = string | Object
type CbThis = unknown extends Ctx ? Impl : Ctx;
-type EventHandler = {
- h: EventCallback
+type EventHandler = {
+ h: EventCallback
ctx: CbThis
query: EventQuery
callAtLast: boolean
}
-type DefaultEventDefinition = {[eventName: string]: unknown};
+type DefaultEventDefinition = Dictionary>;
export interface EventProcessor {
normalizeQuery?: (query: EventQuery) => EventQuery
@@ -58,9 +64,9 @@ export interface EventProcessor {
* @param eventProcessor.afterTrigger Called after all handlers called.
* param: {string} eventType
*/
-export default class Eventful {
+export default class Eventful {
- private _$handlers: {[key: string]: EventHandler[]}
+ private _$handlers: Dictionary[]>
protected _$eventProcessor: EventProcessor
@@ -72,13 +78,13 @@ export default class Eventful {
on(
event: EvtNm,
- handler: EventCallback,
+ handler: WithThisType>,
context?: Ctx
): this
on(
event: EvtNm,
query: EventQuery,
- handler: EventCallback,
+ handler: WithThisType>,
context?: Ctx
): this
/**
@@ -91,8 +97,8 @@ export default class Eventful {
*/
on(
event: EvtNm,
- query: EventQuery | EventCallback,
- handler?: EventCallback | Ctx,
+ query: EventQuery | WithThisType, CbThis>,
+ handler?: WithThisType, CbThis> | Ctx,
context?: Ctx
): this {
if (!this._$handlers) {
@@ -103,7 +109,7 @@ export default class Eventful {
if (typeof query === 'function') {
context = handler as Ctx;
- handler = query as EventCallback;
+ handler = query as (...args: any) => any;
query = null;
}
@@ -126,8 +132,8 @@ export default class Eventful {
}
}
- const wrap: EventHandler = {
- h: handler as EventCallback,
+ const wrap: EventHandler = {
+ h: handler as EventCallback,
query: query,
ctx: (context || this) as CbThis,
// FIXME
@@ -199,8 +205,10 @@ export default class Eventful {
*
* @param {string} eventType The event name.
*/
- trigger(eventType: keyof EvtDef, eventParam?: EvtDef[keyof EvtDef], ...args: any[]): this;
- trigger(eventType: keyof EvtDef, ...args: any[]): this {
+ trigger(
+ eventType: EvtNm,
+ ...args: Parameters
+ ): this {
if (!this._$handlers) {
return this;
}
@@ -225,7 +233,6 @@ export default class Eventful {
// Optimize advise from backbone
switch (argLen) {
case 0:
- // @ts-ignore
hItem.h.call(hItem.ctx);
break;
case 1:
@@ -253,7 +260,7 @@ export default class Eventful {
*
* @param {string} type The event name.
*/
- triggerWithContext(type: keyof EvtDef) {
+ triggerWithContext(type: keyof EvtDef, ...args: any[]): this {
if (!this._$handlers) {
return this;
}
@@ -262,7 +269,6 @@ export default class Eventful {
const eventProcessor = this._$eventProcessor;
if (_h) {
- const args: any = arguments;
const argLen = args.length;
const ctx = args[argLen - 1];
@@ -280,7 +286,6 @@ export default class Eventful {
// Optimize advise from backbone
switch (argLen) {
case 0:
- // @ts-ignore
hItem.h.call(ctx);
break;
case 1:
diff --git a/src/core/PathProxy.ts b/src/core/PathProxy.ts
index dded51acf..b28e832de 100644
--- a/src/core/PathProxy.ts
+++ b/src/core/PathProxy.ts
@@ -281,6 +281,8 @@ export default class PathProxy {
}
bezierCurveTo(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number) {
+ this._drawPendingPt();
+
this.addData(CMD.C, x1, y1, x2, y2, x3, y3);
if (this._ctx) {
this._needsDash ? this._dashedBezierTo(x1, y1, x2, y2, x3, y3)
@@ -292,6 +294,8 @@ export default class PathProxy {
}
quadraticCurveTo(x1: number, y1: number, x2: number, y2: number) {
+ this._drawPendingPt();
+
this.addData(CMD.Q, x1, y1, x2, y2);
if (this._ctx) {
this._needsDash ? this._dashedQuadraticTo(x1, y1, x2, y2)
@@ -303,6 +307,8 @@ export default class PathProxy {
}
arc(cx: number, cy: number, r: number, startAngle: number, endAngle: number, anticlockwise?: boolean) {
+ this._drawPendingPt();
+
tmpAngles[0] = startAngle;
tmpAngles[1] = endAngle;
normalizeArcAngles(tmpAngles, anticlockwise);
@@ -312,10 +318,10 @@ export default class PathProxy {
let delta = endAngle - startAngle;
-
this.addData(
CMD.A, cx, cy, r, r, startAngle, delta, 0, anticlockwise ? 0 : 1
);
+
this._ctx && this._ctx.arc(cx, cy, r, startAngle, endAngle, anticlockwise);
this._xi = mathCos(endAngle) * r + cx;
@@ -325,6 +331,8 @@ export default class PathProxy {
// TODO
arcTo(x1: number, y1: number, x2: number, y2: number, radius: number) {
+ this._drawPendingPt();
+
if (this._ctx) {
this._ctx.arcTo(x1, y1, x2, y2, radius);
}
@@ -333,6 +341,8 @@ export default class PathProxy {
// TODO
rect(x: number, y: number, w: number, h: number) {
+ this._drawPendingPt();
+
this._ctx && this._ctx.rect(x, y, w, h);
this.addData(CMD.R, x, y, w, h);
return this;
@@ -947,12 +957,14 @@ export default class PathProxy {
x0 = xi;
y0 = yi;
}
+ // Only lineTo support ignoring small segments.
+ // Otherwise if the pending point should always been flushed.
+ if (cmd !== CMD.L && pendingPtDist > 0) {
+ ctx.lineTo(pendingPtX, pendingPtY);
+ pendingPtDist = 0;
+ }
switch (cmd) {
case CMD.M:
- if (pendingPtDist > 0) {
- ctx.lineTo(pendingPtX, pendingPtY);
- pendingPtDist = 0;
- }
x0 = xi = d[i++];
y0 = yi = d[i++];
ctx.moveTo(xi, yi);
@@ -1115,11 +1127,6 @@ export default class PathProxy {
ctx.rect(x, y, width, height);
break;
case CMD.Z:
- if (pendingPtDist > 0) {
- ctx.lineTo(pendingPtX, pendingPtY);
- pendingPtDist = 0;
- }
-
if (drawPart) {
const l = pathSegLen[segCount++];
if (accumLength + l > displayedLength) {
@@ -1137,6 +1144,15 @@ export default class PathProxy {
}
}
+ clone() {
+ const newProxy = new PathProxy();
+ const data = this.data;
+ newProxy.data = data.slice ? data.slice()
+ : Array.prototype.slice.call(data);
+ newProxy._len = this._len;
+ return newProxy;
+ }
+
private static initDefaultProps = (function () {
const proto = PathProxy.prototype;
proto._saveData = true;
diff --git a/src/core/Point.ts b/src/core/Point.ts
index 7d013f506..7dd767efa 100644
--- a/src/core/Point.ts
+++ b/src/core/Point.ts
@@ -1,4 +1,4 @@
-import { MatrixArray } from "./matrix";
+import { MatrixArray } from './matrix';
export interface PointLike {
x: number
diff --git a/src/core/Transformable.ts b/src/core/Transformable.ts
index 3fd74016a..b8d7b63ba 100644
--- a/src/core/Transformable.ts
+++ b/src/core/Transformable.ts
@@ -42,6 +42,13 @@ class Transformable {
transform: matrix.MatrixArray
invTransform: matrix.MatrixArray
+ /**
+ * Get computed local transform
+ */
+ getLocalTransform(m?: matrix.MatrixArray) {
+ return Transformable.getLocalTransform(this, m);
+ }
+
/**
* Set position from array
*/
@@ -88,12 +95,11 @@ class Transformable {
* Update global transform
*/
updateTransform() {
- const parent = this.parent;
- const parentHasTransform = parent && parent.transform;
+ const parentTransform = this.parent && this.parent.transform;
const needLocalTransform = this.needLocalTransform();
let m = this.transform;
- if (!(needLocalTransform || parentHasTransform)) {
+ if (!(needLocalTransform || parentTransform)) {
m && mIdentity(m);
return;
}
@@ -108,12 +114,12 @@ class Transformable {
}
// 应用父节点变换
- if (parentHasTransform) {
+ if (parentTransform) {
if (needLocalTransform) {
- matrix.mul(m, parent.transform, m);
+ matrix.mul(m, parentTransform, m);
}
else {
- matrix.copy(m, parent.transform);
+ matrix.copy(m, parentTransform);
}
}
// 保存这个变换矩阵
@@ -140,12 +146,6 @@ class Transformable {
this.invTransform = this.invTransform || matrix.create();
matrix.invert(this.invTransform, m);
}
- /**
- * Get computed local transform
- */
- getLocalTransform(m?: matrix.MatrixArray) {
- return Transformable.getLocalTransform(this, m);
- }
/**
* Get computed global transform
@@ -178,8 +178,6 @@ class Transformable {
const rotation = Math.atan2(m[1], m[0]);
-
-
const shearX = Math.PI / 2 + rotation - Math.atan2(m[3], m[2]);
sy = Math.sqrt(sy) * Math.cos(shearX);
sx = Math.sqrt(sx);
@@ -281,6 +279,15 @@ class Transformable {
: 1;
}
+ copyTransform(source: Transformable) {
+ const target = this;
+
+ for (let i = 0; i < TRANSFORMABLE_PROPS.length; i++) {
+ const propName = TRANSFORMABLE_PROPS[i];
+ target[propName] = source[propName];
+ }
+ }
+
static getLocalTransform(target: Transformable, m?: matrix.MatrixArray): matrix.MatrixArray {
m = m || [];
@@ -338,4 +345,8 @@ class Transformable {
})()
};
+export const TRANSFORMABLE_PROPS = [
+ 'x', 'y', 'originX', 'originY', 'rotation', 'scaleX', 'scaleY', 'skewX', 'skewY'
+] as const;
+
export default Transformable;
\ No newline at end of file
diff --git a/src/core/WeakMap.ts b/src/core/WeakMap.ts
index 0caa83765..d4d00b1ad 100644
--- a/src/core/WeakMap.ts
+++ b/src/core/WeakMap.ts
@@ -1,5 +1,7 @@
let wmUniqueIndex = Math.round(Math.random() * 9);
+const supportDefineProperty = typeof Object.defineProperty === 'function';
+
export default class WeakMap {
protected _id: string;
@@ -14,7 +16,7 @@ export default class WeakMap {
set(key: K, value: V): WeakMap {
const target = this._guard(key) as any;
- if (typeof Object.defineProperty === 'function') {
+ if (supportDefineProperty) {
Object.defineProperty(target, this._id, {
value: value,
enumerable: false,
diff --git a/src/core/curve.ts b/src/core/curve.ts
index 054e164b3..9e3580723 100644
--- a/src/core/curve.ts
+++ b/src/core/curve.ts
@@ -480,7 +480,7 @@ export function quadraticLength(
let d = 0;
- const step = 1 / iteration;
+ const step = 1 / iteration;
for (let i = 1; i <= iteration; i++) {
let t = i * step;
diff --git a/src/core/matrix.ts b/src/core/matrix.ts
index 248c892fb..78009f815 100644
--- a/src/core/matrix.ts
+++ b/src/core/matrix.ts
@@ -116,7 +116,7 @@ export function scale(out: MatrixArray, a: MatrixArray, v: VectorArray): MatrixA
/**
* 求逆矩阵
*/
-export function invert(out: MatrixArray, a: MatrixArray): MatrixArray {
+export function invert(out: MatrixArray, a: MatrixArray): MatrixArray | null {
const aa = a[0];
const ac = a[2];
@@ -147,4 +147,4 @@ export function clone(a: MatrixArray): MatrixArray {
const b = create();
copy(b, a);
return b;
-}
\ No newline at end of file
+}
diff --git a/src/core/types.ts b/src/core/types.ts
index fb2c6c7b2..41b035d65 100644
--- a/src/core/types.ts
+++ b/src/core/types.ts
@@ -64,7 +64,7 @@ export type ZRPinchEvent = ZRRawEvent & {
export type ElementEventName = 'click' | 'dblclick' | 'mousewheel' | 'mouseout' |
'mouseover' | 'mouseup' | 'mousedown' | 'mousemove' | 'contextmenu' |
- 'drag' | 'dragstart' | 'dragend' | 'dragenter' | 'dragleave' | 'dragover' | 'drop';
+ 'drag' | 'dragstart' | 'dragend' | 'dragenter' | 'dragleave' | 'dragover' | 'drop' | 'globalout';
export type ElementEventNameWithOn = 'onclick' | 'ondblclick' | 'onmousewheel' | 'onmouseout' |
'onmouseup' | 'onmousedown' | 'onmousemove' | 'oncontextmenu' |
@@ -90,3 +90,6 @@ export type MapToType, S> = {
// `keyof A | B` does not equals to `Keyof A | Keyof B`
// KeyOfDistributive equals to `KeyOfDistributive | KeyOfDistributive`
export type KeyOfDistributive = T extends unknown ? keyof T : never;
+
+export type WithThisType any, This> =
+ (this: This, ...args: Parameters) => ReturnType;
diff --git a/src/core/util.ts b/src/core/util.ts
index b2f77082a..7eae0db24 100644
--- a/src/core/util.ts
+++ b/src/core/util.ts
@@ -728,26 +728,6 @@ export function concatArray(a: ArrayLike, b: ArrayLike): ArrayLike(obj: T, proto: object): T {
-// if (Object.setPrototypeOf) {
-// Object.setPrototypeOf(obj, proto);
-// return obj;
-// }
-// else {
-// const StyleCtor = function () {};
-// StyleCtor.prototype = proto;
-// const newObj = new (StyleCtor as any)();
-// extend(newObj, obj);
-// return newObj;
-// }
-// }
-
-
export function createObject(proto?: object, properties?: T): T {
// Performance of Object.create
// https://jsperf.com/style-strategy-proto-or-others
diff --git a/src/export.ts b/src/export.ts
index 0e68eee69..1b7dc35ed 100644
--- a/src/export.ts
+++ b/src/export.ts
@@ -8,7 +8,8 @@ import * as vector from './core/vector';
import * as colorTool from './tool/color';
import * as pathTool from './tool/path';
import {parseSVG} from './tool/parseSVG';
-import {morphPath} from './tool/morphPath';
+
+import * as morphPathTool from './tool/morphPath';
export {default as Point, PointLike} from './core/Point';
@@ -65,7 +66,8 @@ export {colorTool as color};
export {pathTool as path};
export {zrUtil as util};
+export {morphPathTool as morph};
+
export {parseSVG};
-export {morphPath};
export {default as showDebugDirtyRect} from './debug/showDebugDirtyRect';
\ No newline at end of file
diff --git a/src/graphic/Group.ts b/src/graphic/Group.ts
index efd400cad..337a8822b 100644
--- a/src/graphic/Group.ts
+++ b/src/graphic/Group.ts
@@ -113,6 +113,14 @@ class Group extends Element {
return this;
}
+ replace(oldChild: Element, newChild: Element) {
+ const idx = zrUtil.indexOf(this._children, oldChild);
+ if (idx >= 0) {
+ this.replaceAt(newChild, idx);
+ }
+ return this;
+ }
+
replaceAt(child: Element, index: number) {
const children = this._children;
const old = children[index];
@@ -212,6 +220,7 @@ class Group extends Element {
* Visit all descendants.
* Return false in callback to stop visit descendants of current node
*/
+ // TODO Group itself should also invoke the callback.
traverse(
cb: (this: T, el: Element) => boolean | void,
context?: T
@@ -281,5 +290,9 @@ class Group extends Element {
}
Group.prototype.type = 'group';
+// Storage will use childrenRef to get children to render.
+export interface GroupLike extends Element {
+ childrenRef(): Element[]
+}
export default Group;
\ No newline at end of file
diff --git a/src/graphic/Path.ts b/src/graphic/Path.ts
index 7c2bfbbca..34885002d 100644
--- a/src/graphic/Path.ts
+++ b/src/graphic/Path.ts
@@ -99,7 +99,7 @@ export interface PathProps extends DisplayableProps {
buildPath?: (
ctx: PathProxy | CanvasRenderingContext2D,
shapeCfg: Dictionary,
- inBundle?: boolean
+ inBatch?: boolean
) => void
}
@@ -302,13 +302,21 @@ class Path extends Displayable {
buildPath(
ctx: PathProxy | CanvasRenderingContext2D,
shapeCfg: Dictionary,
- inBundle?: boolean
+ inBatch?: boolean
) {}
pathUpdated() {
this.__dirty &= ~SHAPE_CHANGED_BIT;
}
+ getUpdatedPathProxy(inBatch?: boolean) {
+ // Update path proxy data to latest.
+ !this.path && this.createPathProxy();
+ this.path.beginPath();
+ this.buildPath(this.path, this.shape, inBatch);
+ return this.path;
+ }
+
createPathProxy() {
this.path = new PathProxy(false);
}
@@ -614,7 +622,7 @@ class Path extends Displayable {
getBoundingRect?: Displayable['getBoundingRect']
calculateTextPosition?: Element['calculateTextPosition']
- buildPath(this: Path, ctx: CanvasRenderingContext2D | PathProxy, shape: Shape, inBundle?: boolean): void
+ buildPath(this: Path, ctx: CanvasRenderingContext2D | PathProxy, shape: Shape, inBatch?: boolean): void
init?(this: Path, opts: PathProps): void // TODO Should be SubPathOption
}): {
new(opts?: PathProps & {shape: Shape}): Path
diff --git a/src/graphic/Text.ts b/src/graphic/Text.ts
index 93ba480a6..eb4709e3d 100644
--- a/src/graphic/Text.ts
+++ b/src/graphic/Text.ts
@@ -10,12 +10,17 @@ import { DEFAULT_FONT, adjustTextX, adjustTextY } from '../contain/text';
import ZRImage from './Image';
import Rect from './shape/Rect';
import BoundingRect from '../core/BoundingRect';
-import { MatrixArray, copy } from '../core/matrix';
-import Displayable, { DisplayableStatePropNames, DisplayableProps, DEFAULT_COMMON_ANIMATION_PROPS } from './Displayable';
+import { MatrixArray } from '../core/matrix';
+import Displayable, {
+ DisplayableStatePropNames,
+ DisplayableProps,
+ DEFAULT_COMMON_ANIMATION_PROPS
+} from './Displayable';
import { ZRenderType } from '../zrender';
import Animator from '../animation/Animator';
import Transformable from '../core/Transformable';
import { ElementCommonState } from '../Element';
+import { GroupLike } from './Group';
type TextContentBlock = ReturnType
type TextLine = TextContentBlock['lines'][0]
@@ -248,7 +253,7 @@ interface ZRText {
stateProxy: (stateName: string) => TextState
}
-class ZRText extends Displayable {
+class ZRText extends Displayable implements GroupLike {
type = 'text'
@@ -262,10 +267,11 @@ class ZRText extends Displayable {
overlap: 'hidden' | 'show' | 'blur'
/**
- * Calculated transform after the text is attached on some element.
- * Will override the default transform.
+ * Will use this to calculate transform matrix
+ * instead of Element itseelf if it's give.
+ * Not exposed to developers
*/
- attachedTransform: Transformable
+ innerTransformable: Transformable
private _children: (ZRImage | Rect | TSpan)[] = []
@@ -283,12 +289,14 @@ class ZRText extends Displayable {
}
update() {
+
+ super.update();
+
// Update children
if (this.styleChanged()) {
this._updateSubTexts();
}
-
for (let i = 0; i < this._children.length; i++) {
const child = this._children[i];
// Set common properties.
@@ -299,25 +307,29 @@ class ZRText extends Displayable {
child.cursor = this.cursor;
child.invisible = this.invisible;
}
+ }
- const attachedTransform = this.attachedTransform;
- if (attachedTransform) {
- attachedTransform.updateTransform();
- const m = attachedTransform.transform;
- if (m) {
- this.transform = this.transform || [];
- // Copy to the transform will be actually used.
- copy(this.transform, m);
- }
- else {
- this.transform = null;
+ updateTransform() {
+ const innerTransformable = this.innerTransformable;
+ if (innerTransformable) {
+ innerTransformable.updateTransform();
+ if (innerTransformable.transform) {
+ this.transform = innerTransformable.transform;
}
}
else {
- super.update();
+ super.updateTransform();
}
}
+ getLocalTransform(m?: MatrixArray): MatrixArray {
+ const innerTransformable = this.innerTransformable;
+ return innerTransformable
+ ? innerTransformable.getLocalTransform(m)
+ : super.getLocalTransform(m);
+ }
+
+ // TODO override setLocalTransform?
getComputedTransform() {
if (this.__hostTarget) {
// Update host target transform
@@ -326,8 +338,7 @@ class ZRText extends Displayable {
this.__hostTarget.updateInnerText(true);
}
- return this.attachedTransform ? this.attachedTransform.getComputedTransform()
- : super.getComputedTransform();
+ return super.getComputedTransform();
}
private _updateSubTexts() {
@@ -734,7 +745,7 @@ class ZRText extends Displayable {
const defaultStyle = this._defaultStyle;
let useDefaultFill = false;
let defaultLineWidth = 0;
- const textFill = getStroke(
+ const textFill = getFill(
'fill' in tokenStyle ? tokenStyle.fill
: 'fill' in style ? style.fill
: (useDefaultFill = true, defaultStyle.fill)
@@ -810,7 +821,7 @@ class ZRText extends Displayable {
let rectEl: Rect;
let imgEl: ZRImage;
- if (isPlainOrGradientBg || (textBorderWidth && textBorderColor)) {
+ if (isPlainOrGradientBg || style.lineHeight || (textBorderWidth && textBorderColor)) {
// Background is color
rectEl = this._getOrCreateChild(Rect);
rectEl.useStyle(rectEl.createStyle()); // Create an empty style.
@@ -985,6 +996,7 @@ function getStyleText(style: TextStylePropsPart): string {
function needDrawBackground(style: TextStylePropsPart): boolean {
return !!(
style.backgroundColor
+ || style.lineHeight
|| (style.borderWidth && style.borderColor)
);
}
diff --git a/src/svg/Painter.ts b/src/svg/Painter.ts
index fe61daa63..8cb19150d 100644
--- a/src/svg/Painter.ts
+++ b/src/svg/Painter.ts
@@ -3,7 +3,7 @@
* @module zrender/svg/Painter
*/
-import {createElement} from './core';
+import {createElement, normalizeColor} from './core';
import * as util from '../core/util';
import Path from '../graphic/Path';
import ZRImage from '../graphic/Image';
@@ -194,7 +194,10 @@ class SVGPainter implements PainterBase {
bgNode.setAttribute('x', 0 as any);
bgNode.setAttribute('y', 0 as any);
bgNode.setAttribute('id', 0 as any);
- bgNode.style.fill = backgroundColor;
+ const { color, opacity } = normalizeColor(backgroundColor);
+ bgNode.setAttribute('fill', color);
+ bgNode.setAttribute('fill-opacity', opacity as any);
+
this._backgroundRoot.appendChild(bgNode);
this._backgroundNode = bgNode;
}
diff --git a/src/svg/core.ts b/src/svg/core.ts
index cef8e2093..7ddc66b0f 100644
--- a/src/svg/core.ts
+++ b/src/svg/core.ts
@@ -1,3 +1,23 @@
+import { parse } from '../tool/color';
+
export function createElement(name: string) {
return document.createElementNS('http://www.w3.org/2000/svg', name);
-}
\ No newline at end of file
+}
+
+export function normalizeColor(color: string): { color: string, opacity: number } {
+ let opacity;
+ if (!color || color === 'transparent') {
+ color = 'none';
+ }
+ else if (typeof color === 'string' && color.indexOf('rgba') > -1) {
+ const arr = parse(color);
+ if (arr) {
+ color = 'rgb(' + arr[0] + ',' + arr[1] + ',' + arr[2] + ')';
+ opacity = arr[3];
+ }
+ }
+ return {
+ color,
+ opacity: opacity == null ? 1 : opacity
+ };
+}
diff --git a/src/svg/graphic.ts b/src/svg/graphic.ts
index ed3921bb2..709b0a544 100644
--- a/src/svg/graphic.ts
+++ b/src/svg/graphic.ts
@@ -2,7 +2,7 @@
// 1. shadow
// 2. Image: sx, sy, sw, sh
-import {createElement} from './core';
+import {createElement, normalizeColor} from './core';
import { PathRebuilder } from '../core/PathProxy';
import * as matrix from '../core/matrix';
import Path, { PathStyleProps } from '../graphic/Path';
@@ -92,12 +92,14 @@ function bindStyle(svgEl: SVGElement, style: AllStyleOption, el?: Path | TSpan |
}
if (pathHasFill(style)) {
- let fill = style.fill;
- fill = fill === 'transparent' ? NONE : fill;
- attr(svgEl, 'fill', fill as string);
+ const fill = normalizeColor(style.fill as string);
+ attr(svgEl, 'fill', fill.color);
attr(svgEl,
'fill-opacity',
- (style.fillOpacity != null ? style.fillOpacity * opacity : opacity) + ''
+ (style.fillOpacity != null
+ ? style.fillOpacity * fill.opacity * opacity
+ : fill.opacity * opacity
+ ) + ''
);
}
else {
@@ -105,9 +107,8 @@ function bindStyle(svgEl: SVGElement, style: AllStyleOption, el?: Path | TSpan |
}
if (pathHasStroke(style)) {
- let stroke = style.stroke;
- stroke = stroke === 'transparent' ? NONE : stroke;
- attr(svgEl, 'stroke', stroke as string);
+ const stroke = normalizeColor(style.stroke as string);
+ attr(svgEl, 'stroke', stroke.color);
const strokeWidth = style.lineWidth;
const strokeScale = style.strokeNoScale
? (el as Path).getLineScale()
@@ -115,7 +116,11 @@ function bindStyle(svgEl: SVGElement, style: AllStyleOption, el?: Path | TSpan |
attr(svgEl, 'stroke-width', (strokeScale ? strokeWidth / strokeScale : 0) + '');
// stroke then fill for text; fill then stroke for others
attr(svgEl, 'paint-order', style.strokeFirst ? 'stroke' : 'fill');
- attr(svgEl, 'stroke-opacity', (style.strokeOpacity != null ? style.strokeOpacity * opacity : opacity) + '');
+ attr(svgEl, 'stroke-opacity', (
+ style.strokeOpacity != null
+ ? style.strokeOpacity * stroke.opacity * opacity
+ : stroke.opacity * opacity
+ ) + '');
let lineDash = style.lineDash && strokeWidth > 0 && normalizeLineDash(style.lineDash, strokeWidth);
if (lineDash) {
let lineDashOffset = style.lineDashOffset;
@@ -169,7 +174,14 @@ class SVGPathRebuilder implements PathRebuilder {
arc(cx: number, cy: number, r: number, startAngle: number, endAngle: number, anticlockwise: boolean) {
this.ellipse(cx, cy, r, r, 0, startAngle, endAngle, anticlockwise);
}
- ellipse(cx: number, cy: number, rx: number, ry: number, psi: number, startAngle: number, endAngle: number, anticlockwise: boolean) {
+ ellipse(
+ cx: number, cy: number,
+ rx: number, ry: number,
+ psi: number,
+ startAngle: number,
+ endAngle: number,
+ anticlockwise: boolean
+ ) {
const firstCmd = this._d.length === 0;
@@ -237,6 +249,7 @@ class SVGPathRebuilder implements PathRebuilder {
this._add('L', x + w, y + h);
this._add('L', x, y + h);
this._add('L', x, y);
+ this._add('Z');
}
closePath() {
// Not use Z as first command
diff --git a/src/svg/helper/ClippathManager.ts b/src/svg/helper/ClippathManager.ts
index 774878aeb..2e2fe3345 100644
--- a/src/svg/helper/ClippathManager.ts
+++ b/src/svg/helper/ClippathManager.ts
@@ -44,8 +44,11 @@ export default class ClippathManager extends Definable {
markAllUnused() {
super.markAllUnused();
- for (let key in this._refGroups) {
- this.markDomUnused(this._refGroups[key]);
+ const refGroups = this._refGroups;
+ for (let key in refGroups) {
+ if (refGroups.hasOwnProperty(key)) {
+ this.markDomUnused(refGroups[key]);
+ }
}
this._keyDuplicateCount = {};
}
@@ -163,13 +166,16 @@ export default class ClippathManager extends Definable {
super.removeUnused();
const newRefGroupsMap: Dictionary = {};
- for (let key in this._refGroups) {
- const group = this._refGroups[key];
- if (!this.isDomUnused(group)) {
- newRefGroupsMap[key] = group;
- }
- else if (group.parentNode) {
- group.parentNode.removeChild(group);
+ const refGroups = this._refGroups;
+ for (let key in refGroups) {
+ if (refGroups.hasOwnProperty(key)) {
+ const group = refGroups[key];
+ if (!this.isDomUnused(group)) {
+ newRefGroupsMap[key] = group;
+ }
+ else if (group.parentNode) {
+ group.parentNode.removeChild(group);
+ }
}
}
this._refGroups = newRefGroupsMap;
diff --git a/src/svg/helper/ShadowManager.ts b/src/svg/helper/ShadowManager.ts
index 3baf3345d..16814c0b6 100644
--- a/src/svg/helper/ShadowManager.ts
+++ b/src/svg/helper/ShadowManager.ts
@@ -7,6 +7,7 @@ import Definable from './Definable';
import Displayable from '../../graphic/Displayable';
import { PathStyleProps } from '../../graphic/Path';
import { Dictionary } from '../../core/types';
+import { normalizeColor } from '../core';
type DisplayableExtended = Displayable & {
_shadowDom: SVGElement
@@ -35,7 +36,7 @@ export default class ShadowManager extends Definable {
if (!shadowDom) {
shadowDom = this.createElement('filter') as SVGFilterElement;
shadowDom.setAttribute('id', 'zr' + this._zrId + '-shadow-' + this.nextId++);
- const domChild = this.createElement('feDropShadow')
+ const domChild = this.createElement('feDropShadow');
shadowDom.appendChild(domChild);
this.addDom(shadowDom);
}
@@ -98,11 +99,12 @@ export default class ShadowManager extends Definable {
let offsetX = style.shadowOffsetX || 0;
let offsetY = style.shadowOffsetY || 0;
let blur = style.shadowBlur;
- let color = style.shadowColor;
+ const normalizedColor = normalizeColor(style.shadowColor);
domChild.setAttribute('dx', offsetX / scaleX + '');
domChild.setAttribute('dy', offsetY / scaleY + '');
- domChild.setAttribute('flood-color', color);
+ domChild.setAttribute('flood-color', normalizedColor.color);
+ domChild.setAttribute('flood-opacity', normalizedColor.opacity + '');
// Divide by two here so that it looks the same as in canvas
// See: https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-shadowblur
@@ -134,9 +136,11 @@ export default class ShadowManager extends Definable {
let shadowDomsPool = this._shadowDomPool;
// let currentUsedShadow = 0;
- for (let key in this._shadowDomMap) {
- const dom = this._shadowDomMap[key];
- shadowDomsPool.push(dom);
+ const shadowDomMap = this._shadowDomMap;
+ for (let key in shadowDomMap) {
+ if (shadowDomMap.hasOwnProperty(key)) {
+ shadowDomsPool.push(shadowDomMap[key]);
+ }
// currentUsedShadow++;
}
diff --git a/src/tool/convertPath.ts b/src/tool/convertPath.ts
new file mode 100644
index 000000000..d0848de20
--- /dev/null
+++ b/src/tool/convertPath.ts
@@ -0,0 +1,295 @@
+import { cubicSubdivide } from '../core/curve';
+import PathProxy from '../core/PathProxy';
+
+const CMD = PathProxy.CMD;
+
+function aroundEqual(a: number, b: number) {
+ return Math.abs(a - b) < 1e-5;
+}
+
+export function pathToBezierCurves(path: PathProxy) {
+
+ const data = path.data;
+ const len = path.len();
+
+ const bezierArrayGroups: number[][] = [];
+ let currentSubpath: number[];
+
+ let xi = 0;
+ let yi = 0;
+ let x0 = 0;
+ let y0 = 0;
+
+ function createNewSubpath(x: number, y: number) {
+ // More than one M command
+ if (currentSubpath && currentSubpath.length > 2) {
+ bezierArrayGroups.push(currentSubpath);
+ }
+ currentSubpath = [x, y];
+ }
+
+ function addLine(x0: number, y0: number, x1: number, y1: number) {
+ if (!(aroundEqual(x0, x1) && aroundEqual(y0, y1))) {
+ currentSubpath.push(x0, y0, x1, y1, x1, y1);
+ }
+ }
+
+ function addArc(startAngle: number, endAngle: number, cx: number, cy: number, rx: number, ry: number) {
+ // https://stackoverflow.com/questions/1734745/how-to-create-circle-with-b%C3%A9zier-curves
+ const delta = Math.abs(endAngle - startAngle);
+ const len = Math.tan(delta / 4) * 4 / 3;
+ const dir = endAngle < startAngle ? -1 : 1;
+
+ const c1 = Math.cos(startAngle);
+ const s1 = Math.sin(startAngle);
+ const c2 = Math.cos(endAngle);
+ const s2 = Math.sin(endAngle);
+
+ const x1 = c1 * rx + cx;
+ const y1 = s1 * ry + cy;
+
+ const x4 = c2 * rx + cx;
+ const y4 = s2 * ry + cy;
+
+ const hx = rx * len * dir;
+ const hy = ry * len * dir;
+ currentSubpath.push(
+ // Move control points on tangent.
+ x1 - hx * s1, y1 + hy * c1,
+ x4 + hx * s2, y4 - hy * c2,
+ x4, y4
+ );
+ }
+
+ let x1;
+ let y1;
+ let x2;
+ let y2;
+
+ for (let i = 0; i < len;) {
+ const cmd = data[i++];
+ const isFirst = i === 1;
+
+ if (isFirst) {
+ // 如果第一个命令是 L, C, Q
+ // 则 previous point 同绘制命令的第一个 point
+ // 第一个命令为 Arc 的情况下会在后面特殊处理
+ xi = data[i];
+ yi = data[i + 1];
+
+ x0 = xi;
+ y0 = yi;
+
+ if (cmd === CMD.L || cmd === CMD.C || cmd === CMD.Q) {
+ // Start point
+ currentSubpath = [x0, y0];
+ }
+ }
+
+ switch (cmd) {
+ case CMD.M:
+ // moveTo 命令重新创建一个新的 subpath, 并且更新新的起点
+ // 在 closePath 的时候使用
+ xi = x0 = data[i++];
+ yi = y0 = data[i++];
+
+ createNewSubpath(x0, y0);
+ break;
+ case CMD.L:
+ x1 = data[i++];
+ y1 = data[i++];
+ addLine(xi, yi, x1, y1);
+ xi = x1;
+ yi = y1;
+ break;
+ case CMD.C:
+ currentSubpath.push(
+ data[i++], data[i++], data[i++], data[i++],
+ xi = data[i++], yi = data[i++]
+ );
+ break;
+ case CMD.Q:
+ x1 = data[i++];
+ y1 = data[i++];
+ x2 = data[i++];
+ y2 = data[i++];
+ currentSubpath.push(
+ // Convert quadratic to cubic
+ xi + 2 / 3 * (x1 - xi), yi + 2 / 3 * (y1 - yi),
+ x2 + 2 / 3 * (x1 - x2), y2 + 2 / 3 * (y1 - y2),
+ x2, y2
+ );
+ xi = x2;
+ yi = y2;
+ break;
+ case CMD.A:
+ const cx = data[i++];
+ const cy = data[i++];
+ const rx = data[i++];
+ const ry = data[i++];
+ const startAngle = data[i++];
+ const endAngle = data[i++] + startAngle;
+
+ // TODO Arc rotation
+ i += 1;
+ const anticlockwise = !data[i++];
+
+ x1 = Math.cos(startAngle) * rx + cx;
+ y1 = Math.sin(startAngle) * ry + cy;
+ if (isFirst) {
+ // 直接使用 arc 命令
+ // 第一个命令起点还未定义
+ x0 = x1;
+ y0 = y1;
+ createNewSubpath(x0, y0);
+ }
+ else {
+ // Connect a line between current point to arc start point.
+ addLine(xi, yi, x1, y1);
+ }
+
+ xi = Math.cos(endAngle) * rx + cx;
+ yi = Math.sin(endAngle) * ry + cy;
+
+ const step = (anticlockwise ? -1 : 1) * Math.PI / 2;
+
+ for (let angle = startAngle; anticlockwise ? angle > endAngle : angle < endAngle; angle += step) {
+ const nextAngle = anticlockwise ? Math.max(angle + step, endAngle)
+ : Math.min(angle + step, endAngle);
+ addArc(angle, nextAngle, cx, cy, rx, ry);
+ }
+
+ break;
+ case CMD.R:
+ x0 = xi = data[i++];
+ y0 = yi = data[i++];
+ x1 = x0 + data[i++];
+ y1 = y0 + data[i++];
+
+ // rect is an individual path.
+ createNewSubpath(x1, y0);
+ addLine(x1, y0, x1, y1);
+ addLine(x1, y1, x0, y1);
+ addLine(x0, y1, x0, y0);
+ addLine(x0, y0, x1, y0);
+ break;
+ case CMD.Z:
+ currentSubpath && addLine(xi, yi, x0, y0);
+ xi = x0;
+ yi = y0;
+ break;
+ }
+ }
+
+ if (currentSubpath && currentSubpath.length > 2) {
+ bezierArrayGroups.push(currentSubpath);
+ }
+
+ return bezierArrayGroups;
+}
+
+function adpativeBezier(
+ x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, x3: number, y3: number,
+ out: number[], scale: number
+) {
+ // This bezier is used to simulates a line when converting path to beziers.
+ if (aroundEqual(x0, x1) && aroundEqual(y0, y1) && aroundEqual(x2, x3) && aroundEqual(y2, y3)) {
+ out.push(x3, y3);
+ return;
+ }
+
+ const PIXEL_DISTANCE = 2 / scale;
+ const PIXEL_DISTANCE_SQR = PIXEL_DISTANCE * PIXEL_DISTANCE;
+
+ // Determine if curve is straight enough
+ let dx = x3 - x0;
+ let dy = y3 - y0;
+ const d = Math.sqrt(dx * dx + dy * dy);
+ dx /= d;
+ dy /= d;
+
+ const dx1 = x1 - x0;
+ const dy1 = y1 - y0;
+ const dx2 = x2 - x3;
+ const dy2 = y2 - y3;
+
+ const cp1LenSqr = dx1 * dx1 + dy1 * dy1;
+ const cp2LenSqr = dx2 * dx2 + dy2 * dy2;
+
+ if (cp1LenSqr < PIXEL_DISTANCE_SQR && cp2LenSqr < PIXEL_DISTANCE_SQR) {
+ // Add small segment
+ out.push(x3, y3);
+ return;
+ }
+
+ // Project length of cp1
+ const projLen1 = dx * dx1 + dy * dy1;
+ // Project length of cp2
+ const projLen2 = -dx * dx2 - dy * dy2;
+
+ // Distance from cp1 to start-end line.
+ const d1Sqr = cp1LenSqr - projLen1 * projLen1;
+ // Distance from cp2 to start-end line.
+ const d2Sqr = cp2LenSqr - projLen2 * projLen2;
+
+ // IF the cp1 and cp2 is near to the start-line enough
+ // We treat it straight enough
+ if (d1Sqr < PIXEL_DISTANCE_SQR && projLen1 >= 0
+ && d2Sqr < PIXEL_DISTANCE_SQR && projLen2 >= 0
+ ) {
+ out.push(x3, y3);
+ return;
+ }
+
+
+ const tmpSegX: number[] = [];
+ const tmpSegY: number[] = [];
+ // Subdivide
+ cubicSubdivide(x0, x1, x2, x3, 0.5, tmpSegX);
+ cubicSubdivide(y0, y1, y2, y3, 0.5, tmpSegY);
+
+ adpativeBezier(
+ tmpSegX[0], tmpSegY[0], tmpSegX[1], tmpSegY[1], tmpSegX[2], tmpSegY[2], tmpSegX[3], tmpSegY[3],
+ out, scale
+ );
+ adpativeBezier(
+ tmpSegX[4], tmpSegY[4], tmpSegX[5], tmpSegY[5], tmpSegX[6], tmpSegY[6], tmpSegX[7], tmpSegY[7],
+ out, scale
+ );
+}
+
+export function pathToPolygons(path: PathProxy, scale?: number) {
+ // TODO Optimize simple case like path is polygon and rect?
+ const bezierArrayGroups = pathToBezierCurves(path);
+
+ const polygons: number[][] = [];
+
+ scale = scale || 1;
+
+ for (let i = 0; i < bezierArrayGroups.length; i++) {
+ const beziers = bezierArrayGroups[i];
+ const polygon: number[] = [];
+ let x0 = beziers[0];
+ let y0 = beziers[1];
+
+ polygon.push(x0, y0);
+
+ for (let k = 2; k < beziers.length;) {
+
+ const x1 = beziers[k++];
+ const y1 = beziers[k++];
+ const x2 = beziers[k++];
+ const y2 = beziers[k++];
+ const x3 = beziers[k++];
+ const y3 = beziers[k++];
+
+ adpativeBezier(x0, y0, x1, y1, x2, y2, x3, y3, polygon, scale);
+
+ x0 = x3;
+ y0 = y3;
+ }
+
+ polygons.push(polygon);
+ }
+ return polygons;
+}
diff --git a/src/tool/dividePath.ts b/src/tool/dividePath.ts
new file mode 100644
index 000000000..a7fc8a38c
--- /dev/null
+++ b/src/tool/dividePath.ts
@@ -0,0 +1,406 @@
+import { fromPoints } from '../core/bbox';
+import BoundingRect from '../core/BoundingRect';
+import Point from '../core/Point';
+import { each, map } from '../core/util';
+import Path from '../graphic/Path';
+import Polygon from '../graphic/shape/Polygon';
+import Rect from '../graphic/shape/Rect';
+import Sector from '../graphic/shape/Sector';
+import { pathToPolygons } from './convertPath';
+import { clonePath } from './path';
+
+// Default shape dividers
+// TODO divide polygon by grids.
+interface BinaryDivide {
+ (shape: Path['shape']): Path['shape'][]
+}
+
+/**
+ * Calculating a grid to divide the shape.
+ */
+function getDividingGrids(dimSize: number[], rowDim: number, count: number) {
+ const rowSize = dimSize[rowDim];
+ const columnSize = dimSize[1 - rowDim];
+
+ const ratio = Math.abs(rowSize / columnSize);
+ let rowCount = Math.ceil(Math.sqrt(ratio * count));
+ let columnCount = Math.floor(count / rowCount);
+ if (columnCount === 0) {
+ columnCount = 1;
+ rowCount = count;
+ }
+
+ const grids: number[] = [];
+ for (let i = 0; i < rowCount; i++) {
+ grids.push(columnCount);
+ }
+ const currentCount = rowCount * columnCount;
+ // Distribute the remaind grid evenly on each row.
+ const remained = count - currentCount;
+ if (remained > 0) {
+ // const stride = Math.max(Math.floor(rowCount / remained), 1);
+ for (let i = 0; i < remained; i++) {
+ grids[i % rowCount] += 1;
+ }
+ }
+ return grids;
+}
+
+
+// TODO cornerRadius
+function divideSector(sectorShape: Sector['shape'], count: number, outShapes: Sector['shape'][]) {
+ const r0 = sectorShape.r0;
+ const r = sectorShape.r;
+ const startAngle = sectorShape.startAngle;
+ const endAngle = sectorShape.endAngle;
+ const angle = Math.abs(endAngle - startAngle);
+ const arcLen = angle * r;
+ const deltaR = r - r0;
+
+ const isAngleRow = arcLen > Math.abs(deltaR);
+ const grids = getDividingGrids([arcLen, deltaR], isAngleRow ? 0 : 1, count);
+
+ const rowSize = (isAngleRow ? angle : deltaR) / grids.length;
+
+ for (let row = 0; row < grids.length; row++) {
+ const columnSize = (isAngleRow ? deltaR : angle) / grids[row];
+ for (let column = 0; column < grids[row]; column++) {
+ const newShape = {} as Sector['shape'];
+
+ if (isAngleRow) {
+ newShape.startAngle = startAngle + rowSize * row;
+ newShape.endAngle = startAngle + rowSize * (row + 1);
+ newShape.r0 = r0 + columnSize * column;
+ newShape.r = r0 + columnSize * (column + 1);
+ }
+ else {
+ newShape.startAngle = startAngle + columnSize * column;
+ newShape.endAngle = startAngle + columnSize * (column + 1);
+ newShape.r0 = r0 + rowSize * row;
+ newShape.r = r0 + rowSize * (row + 1);
+ }
+
+ newShape.clockwise = sectorShape.clockwise;
+ newShape.cx = sectorShape.cx;
+ newShape.cy = sectorShape.cy;
+
+ outShapes.push(newShape);
+ }
+ }
+}
+
+function divideRect(rectShape: Rect['shape'], count: number, outShapes: Rect['shape'][]) {
+ const width = rectShape.width;
+ const height = rectShape.height;
+
+ const isHorizontalRow = width > height;
+ const grids = getDividingGrids([width, height], isHorizontalRow ? 0 : 1, count);
+ const rowSizeDim = isHorizontalRow ? 'width' : 'height';
+ const columnSizeDim = isHorizontalRow ? 'height' : 'width';
+ const rowDim = isHorizontalRow ? 'x' : 'y';
+ const columnDim = isHorizontalRow ? 'y' : 'x';
+ const rowSize = rectShape[rowSizeDim] / grids.length;
+
+ for (let row = 0; row < grids.length; row++) {
+ const columnSize = rectShape[columnSizeDim] / grids[row];
+ for (let column = 0; column < grids[row]; column++) {
+ const newShape = {} as Rect['shape'];
+ newShape[rowDim] = row * rowSize;
+ newShape[columnDim] = column * columnSize;
+ newShape[rowSizeDim] = rowSize;
+ newShape[columnSizeDim] = columnSize;
+
+ newShape.x += rectShape.x;
+ newShape.y += rectShape.y;
+
+ outShapes.push(newShape);
+ }
+ }
+}
+
+function crossProduct2d(x1: number, y1: number, x2: number, y2: number) {
+ return x1 * y2 - x2 * y1;
+}
+
+function lineLineIntersect(
+ a1x: number, a1y: number, a2x: number, a2y: number, // p1
+ b1x: number, b1y: number, b2x: number, b2y: number // p2
+): Point {
+ const mx = a2x - a1x;
+ const my = a2y - a1y;
+ const nx = b2x - b1x;
+ const ny = b2y - b1y;
+
+ const nmCrossProduct = crossProduct2d(nx, ny, mx, my);
+ if (Math.abs(nmCrossProduct) < 1e-6) {
+ return null;
+ }
+
+ const b1a1x = a1x - b1x;
+ const b1a1y = a1y - b1y;
+
+ const p = crossProduct2d(b1a1x, b1a1y, nx, ny) / nmCrossProduct;
+ if (p < 0 || p > 1) {
+ return null;
+ }
+ // p2 is an infinite line
+ return new Point(
+ p * mx + a1x,
+ p * my + a1y
+ );
+}
+
+function projPtOnLine(pt: Point, lineA: Point, lineB: Point): number {
+ const dir = new Point();
+ Point.sub(dir, lineB, lineA);
+ dir.normalize();
+ const dir2 = new Point();
+ Point.sub(dir2, pt, lineA);
+ const len = dir2.dot(dir);
+ return len;
+}
+
+function addToPoly(poly: number[][], pt: number[]) {
+ const last = poly[poly.length - 1];
+ if (last && last[0] === pt[0] && last[1] === pt[1]) {
+ return;
+ }
+ poly.push(pt);
+}
+
+function splitPolygonByLine(points: number[][], lineA: Point, lineB: Point) {
+ const len = points.length;
+ const intersections: {
+ projPt: number,
+ pt: Point
+ idx: number
+ }[] = [];
+ for (let i = 0; i < len; i++) {
+ const p0 = points[i];
+ const p1 = points[(i + 1) % len];
+ const intersectionPt = lineLineIntersect(
+ p0[0], p0[1], p1[0], p1[1],
+ lineA.x, lineA.y, lineB.x, lineB.y
+ );
+ if (intersectionPt) {
+ intersections.push({
+ projPt: projPtOnLine(intersectionPt, lineA, lineB),
+ pt: intersectionPt,
+ idx: i
+ });
+ }
+ }
+
+ // TODO No intersection?
+ if (intersections.length < 2) {
+ // Do clone
+ return [ { points}, {points} ];
+ }
+
+ // Find two farthest points.
+ intersections.sort((a, b) => {
+ return a.projPt - b.projPt;
+ });
+ let splitPt0 = intersections[0];
+ let splitPt1 = intersections[intersections.length - 1];
+ if (splitPt1.idx < splitPt0.idx) {
+ const tmp = splitPt0;
+ splitPt0 = splitPt1;
+ splitPt1 = tmp;
+ }
+
+ const splitPt0Arr = [splitPt0.pt.x, splitPt0.pt.y];
+ const splitPt1Arr = [splitPt1.pt.x, splitPt1.pt.y];
+
+ const newPolyA: number[][] = [splitPt0Arr];
+ const newPolyB: number[][] = [splitPt1Arr];
+
+ for (let i = splitPt0.idx + 1; i <= splitPt1.idx; i++) {
+ addToPoly(newPolyA, points[i].slice());
+ }
+ addToPoly(newPolyA, splitPt1Arr);
+ // Close the path
+ addToPoly(newPolyA, splitPt0Arr);
+
+ for (let i = splitPt1.idx + 1; i <= splitPt0.idx + len; i++) {
+ addToPoly(newPolyB, points[i % len].slice());
+ }
+ addToPoly(newPolyB, splitPt0Arr);
+ // Close the path
+ addToPoly(newPolyB, splitPt1Arr);
+
+ return [{
+ points: newPolyA
+ }, {
+ points: newPolyB
+ }];
+}
+
+function binaryDividePolygon(
+ polygonShape: Pick
+) {
+ const points = polygonShape.points;
+ const min: number[] = [];
+ const max: number[] = [];
+ fromPoints(points, min, max);
+ const boundingRect = new BoundingRect(
+ min[0], min[1], max[0] - min[0], max[1] - min[1]
+ );
+
+ const width = boundingRect.width;
+ const height = boundingRect.height;
+ const x = boundingRect.x;
+ const y = boundingRect.y;
+
+ const pt0 = new Point();
+ const pt1 = new Point();
+ if (width > height) {
+ pt0.x = pt1.x = x + width / 2;
+ pt0.y = y;
+ pt1.y = y + height;
+ }
+ else {
+ pt0.y = pt1.y = y + height / 2;
+ pt0.x = x;
+ pt1.x = x + width;
+ }
+ return splitPolygonByLine(points, pt0, pt1);
+}
+
+
+function binaryDivideRecursive(
+ divider: BinaryDivide, shape: T, count: number, out: T[]
+): T[] {
+ if (count === 1) {
+ out.push(shape);
+ }
+ else {
+ const mid = Math.floor(count / 2);
+ const sub = divider(shape);
+ binaryDivideRecursive(divider, sub[0], mid, out);
+ binaryDivideRecursive(divider, sub[1], count - mid, out);
+ }
+
+ return out;
+}
+
+export function clone(path: Path, count: number) {
+ const paths = [];
+ for (let i = 0; i < count; i++) {
+ paths.push(clonePath(path));
+ }
+ return paths;
+}
+
+function copyPathProps(source: Path, target: Path) {
+ target.setStyle(source.style);
+ target.z = source.z;
+ target.z2 = source.z2;
+ target.zlevel = source.zlevel;
+}
+
+function polygonConvert(points: number[]): number[][] {
+ const out = [];
+ for (let i = 0; i < points.length;) {
+ out.push([points[i++], points[i++]]);
+ }
+ return out;
+}
+
+export function split(
+ path: Path, count: number
+) {
+ const outShapes: Path['shape'][] = [];
+ const shape = path.shape;
+ let OutShapeCtor: new() => Path;
+ // TODO Use clone when shape size is small
+ switch (path.type) {
+ case 'rect':
+ divideRect(shape as Rect['shape'], count, outShapes as Rect['shape'][]);
+ OutShapeCtor = Rect;
+ break;
+ case 'sector':
+ divideSector(shape as Sector['shape'], count, outShapes as Sector['shape'][]);
+ OutShapeCtor = Sector;
+ break;
+ case 'circle':
+ divideSector({
+ r0: 0, r: shape.r, startAngle: 0, endAngle: Math.PI * 2,
+ cx: shape.cx, cy: shape.cy
+ } as Sector['shape'], count, outShapes as Sector['shape'][]);
+ OutShapeCtor = Sector;
+ break;
+ default:
+ const m = path.getComputedTransform();
+ const scale = m ? Math.sqrt(Math.max(m[0] * m[0] + m[1] * m[1], m[2] * m[2] + m[3] * m[3])) : 1;
+ const polygons = map(
+ pathToPolygons(path.getUpdatedPathProxy(), scale),
+ poly => polygonConvert(poly)
+ );
+ const polygonCount = polygons.length;
+ if (polygonCount === 0) {
+ binaryDivideRecursive(binaryDividePolygon, {
+ points: polygons[0]
+ }, count, outShapes);
+ }
+ else if (polygonCount === count) { // In case we only split batched paths to non-batched paths. No need to split.
+ for (let i = 0; i < polygonCount; i++) {
+ outShapes.push({
+ points: polygons[i]
+ } as Polygon['shape']);
+ }
+ }
+ else {
+ // Most complex case. Assign multiple subpath to each polygon based on it's area.
+ let totalArea = 0;
+ const items = map(polygons, poly => {
+ const min: number[] = [];
+ const max: number[] = [];
+ fromPoints(poly, min, max);
+ // TODO: polygon area?
+ const area = (max[1] - min[1]) * (max[0] - min[0]);
+ totalArea += area;
+ return { poly, area };
+ });
+ items.sort((a, b) => b.area - a.area);
+
+ let left = count;
+ for (let i = 0; i < polygonCount; i++) {
+ const item = items[i];
+ if (left <= 0) {
+ break;
+ }
+
+ const selfCount = i === polygonCount - 1
+ ? left // Use the last piece directly
+ : Math.ceil(item.area / totalArea * count);
+
+ if (selfCount < 0) {
+ continue;
+ }
+
+ binaryDivideRecursive(binaryDividePolygon, {
+ points: item.poly
+ }, selfCount, outShapes);
+ left -= selfCount;
+ };
+ }
+ OutShapeCtor = Polygon;
+ break;
+ }
+
+ if (!OutShapeCtor) {
+ // Unkown split algorithm. Use clone instead
+ return clone(path, count);
+ }
+ const out: Path[] = [];
+
+ for (let i = 0; i < outShapes.length; i++) {
+ const subPath = new OutShapeCtor();
+ subPath.setShape(outShapes[i]);
+ copyPathProps(path, subPath);
+ out.push(subPath);
+ }
+
+ return out;
+}
\ No newline at end of file
diff --git a/src/tool/morphPath.ts b/src/tool/morphPath.ts
index c1a9e2137..6c6fd205e 100644
--- a/src/tool/morphPath.ts
+++ b/src/tool/morphPath.ts
@@ -2,229 +2,15 @@ import PathProxy from '../core/PathProxy';
import { cubicSubdivide } from '../core/curve';
import Path from '../graphic/Path';
import Element, { ElementAnimateConfig } from '../Element';
-import { defaults, assert, noop, clone } from '../core/util';
+import { defaults, extend, map } from '../core/util';
import { lerp } from '../core/vector';
-import Rect from '../graphic/shape/Rect';
-import Sector from '../graphic/shape/Sector';
+import Group, { GroupLike } from '../graphic/Group';
+import { clonePath } from './path';
+import { MatrixArray } from '../core/matrix';
+import Transformable from '../core/Transformable';
import { ZRenderType } from '../zrender';
-import Group from '../graphic/Group';
-
-const CMD = PathProxy.CMD;
-const PI2 = Math.PI * 2;
-
-const PROP_XY = ['x', 'y'] as const;
-const PROP_WH = ['width', 'height'] as const;
-
-const tmpArr: number[] = [];
-
-
-interface CombiningPath extends Path {
- __combiningSubList: Path[];
- __oldAddSelfToZr: Element['addSelfToZr'];
- __oldRemoveSelfFromZr: Element['removeSelfFromZr'];
- __oldBuildPath: Path['buildPath'];
- // See `Stroage['_updateAndAddDisplayable']`
- childrenRef(): Path[];
-}
-
-export type MorphDividingMethod = 'split' | 'duplicate';
-
-export interface CombineSeparateConfig extends ElementAnimateConfig {
- dividingMethod?: MorphDividingMethod;
-}
-
-export interface CombineSeparateResult {
- // The length of `fromIndividuals`, `toIndividuals`
- // are the same as `count`.
- fromIndividuals: Path[];
- toIndividuals: Path[];
- count: number;
-}
-
-function aroundEqual(a: number, b: number) {
- return Math.abs(a - b) < 1e-5;
-}
-
-export function pathToBezierCurves(path: PathProxy) {
-
- const data = path.data;
- const len = path.len();
-
- const bezierArray: number[][] = [];
- let currentSubpath: number[];
-
- let xi = 0;
- let yi = 0;
- let x0 = 0;
- let y0 = 0;
-
- function createNewSubpath(x: number, y: number) {
- // More than one M command
- if (currentSubpath && currentSubpath.length > 2) {
- bezierArray.push(currentSubpath);
- }
- currentSubpath = [x, y];
- }
-
- function addLine(x0: number, y0: number, x1: number, y1: number) {
- if (!(aroundEqual(x0, x1) && aroundEqual(y0, y1))) {
- currentSubpath.push(x0, y0, x1, y1, x1, y1);
- }
- }
-
- function addArc(startAngle: number, endAngle: number, cx: number, cy: number, rx: number, ry: number) {
- // https://stackoverflow.com/questions/1734745/how-to-create-circle-with-b%C3%A9zier-curves
- const delta = Math.abs(endAngle - startAngle);
- const len = Math.tan(delta / 4) * 4 / 3;
- const dir = endAngle < startAngle ? -1 : 1;
-
- const c1 = Math.cos(startAngle);
- const s1 = Math.sin(startAngle);
- const c2 = Math.cos(endAngle);
- const s2 = Math.sin(endAngle);
-
- const x1 = c1 * rx + cx;
- const y1 = s1 * ry + cy;
-
- const x4 = c2 * rx + cx;
- const y4 = s2 * ry + cy;
-
- const hx = rx * len * dir;
- const hy = ry * len * dir;
- currentSubpath.push(
- // Move control points on tangent.
- x1 - hx * s1, y1 + hy * c1,
- x4 + hx * s2, y4 - hy * c2,
- x4, y4
- );
- }
-
- let x1;
- let y1;
- let x2;
- let y2;
-
- for (let i = 0; i < len;) {
- const cmd = data[i++];
- const isFirst = i === 1;
-
- if (isFirst) {
- // 如果第一个命令是 L, C, Q
- // 则 previous point 同绘制命令的第一个 point
- // 第一个命令为 Arc 的情况下会在后面特殊处理
- xi = data[i];
- yi = data[i + 1];
-
- x0 = xi;
- y0 = yi;
-
- if (cmd === CMD.L || cmd === CMD.C || cmd === CMD.Q) {
- // Start point
- currentSubpath = [x0, y0];
- }
- }
-
- switch (cmd) {
- case CMD.M:
- // moveTo 命令重新创建一个新的 subpath, 并且更新新的起点
- // 在 closePath 的时候使用
- xi = x0 = data[i++];
- yi = y0 = data[i++];
-
- createNewSubpath(x0, y0);
- break;
- case CMD.L:
- x1 = data[i++];
- y1 = data[i++];
- addLine(xi, yi, x1, y1);
- xi = x1;
- yi = y1;
- break;
- case CMD.C:
- currentSubpath.push(
- data[i++], data[i++], data[i++], data[i++],
- xi = data[i++], yi = data[i++]
- );
- break;
- case CMD.Q:
- x1 = data[i++];
- y1 = data[i++];
- x2 = data[i++];
- y2 = data[i++];
- currentSubpath.push(
- // Convert quadratic to cubic
- xi + 2 / 3 * (x1 - xi), yi + 2 / 3 * (y1 - yi),
- x2 + 2 / 3 * (x1 - x2), y2 + 2 / 3 * (y1 - y2),
- x2, y2
- );
- xi = x2;
- yi = y2;
- break;
- case CMD.A:
- const cx = data[i++];
- const cy = data[i++];
- const rx = data[i++];
- const ry = data[i++];
- const startAngle = data[i++];
- const endAngle = data[i++] + startAngle;
-
- // TODO Arc rotation
- i += 1;
- const anticlockwise = !data[i++];
-
- x1 = Math.cos(startAngle) * rx + cx;
- y1 = Math.sin(startAngle) * ry + cy;
- if (isFirst) {
- // 直接使用 arc 命令
- // 第一个命令起点还未定义
- x0 = x1;
- y0 = y1;
- createNewSubpath(x0, y0);
- }
- else {
- // Connect a line between current point to arc start point.
- addLine(xi, yi, x1, y1);
- }
-
- xi = Math.cos(endAngle) * rx + cx;
- yi = Math.sin(endAngle) * ry + cy;
-
- const step = (anticlockwise ? -1 : 1) * Math.PI / 2;
-
- for (let angle = startAngle; anticlockwise ? angle > endAngle : angle < endAngle; angle += step) {
- const nextAngle = anticlockwise ? Math.max(angle + step, endAngle)
- : Math.min(angle + step, endAngle);
- addArc(angle, nextAngle, cx, cy, rx, ry);
- }
-
- break;
- case CMD.R:
- x0 = xi = data[i++];
- y0 = yi = data[i++];
- x1 = x0 + data[i++];
- y1 = y0 + data[i++];
-
- // rect is an individual path.
- createNewSubpath(x1, y0);
- addLine(x1, y0, x1, y1);
- addLine(x1, y1, x0, y1);
- addLine(x0, y1, x0, y0);
- addLine(x0, y0, x1, y0);
- break;
- case CMD.Z:
- currentSubpath && addLine(xi, yi, x0, y0);
- xi = x0;
- yi = y0;
- break;
- }
- }
-
- if (currentSubpath && currentSubpath.length > 2) {
- bezierArray.push(currentSubpath);
- }
-
- return bezierArray;
-}
+import { split } from './dividePath';
+import { pathToBezierCurves } from './convertPath';
function alignSubpath(subpath1: number[], subpath2: number[]): [number[], number[]] {
const len1 = subpath1.length;
@@ -232,6 +18,8 @@ function alignSubpath(subpath1: number[], subpath2: number[]): [number[], number
if (len1 === len2) {
return [subpath1, subpath2];
}
+ const tmpSegX: number[] = [];
+ const tmpSegY: number[] = [];
const shorterPath = len1 < len2 ? subpath1 : subpath2;
const shorterLen = Math.min(len1, len2);
@@ -244,9 +32,6 @@ function alignSubpath(subpath1: number[], subpath2: number[]): [number[], number
const newSubpath = [shorterPath[0], shorterPath[1]];
let remained = diff;
- const tmpSegX: number[] = [];
- const tmpSegY: number[] = [];
-
for (let i = 2; i < shorterLen;) {
let x0 = shorterPath[i - 2];
let y0 = shorterPath[i - 1];
@@ -346,8 +131,11 @@ export function alignBezierCurves(array1: number[][], array2: number[][]) {
interface MorphingPath extends Path {
__morphT: number;
- __oldBuildPath: Path['buildPath'];
- __morphingData: MorphingData;
+}
+
+export interface CombineMorphingPath extends Path {
+ childrenRef(): (CombineMorphingPath | Path)[]
+ __isCombineMorphing: boolean;
}
export function centroid(array: number[]) {
@@ -543,59 +331,180 @@ function findBestMorphingRotation(
return result;
}
+export function isCombineMorphing(path: Element): path is CombineMorphingPath {
+ return (path as CombineMorphingPath).__isCombineMorphing;
+}
+
+export function isMorphing(el: Element) {
+ return (el as MorphingPath).__morphT >= 0;
+}
+
+const SAVED_METHOD_PREFIX = '__mOriginal_';
+function saveAndModifyMethod(
+ obj: T,
+ methodName: M,
+ modifiers: { replace?: T[M], after?: T[M], before?: T[M] }
+) {
+ const savedMethodName = SAVED_METHOD_PREFIX + methodName;
+ const originalMethod = (obj as any)[savedMethodName] || obj[methodName];
+ if (!(obj as any)[savedMethodName]) {
+ (obj as any)[savedMethodName] = obj[methodName];
+ }
+ const replace = modifiers.replace;
+ const after = modifiers.after;
+ const before = modifiers.before;
+
+ (obj as any)[methodName] = function () {
+ const args = arguments;
+ let res;
+ before && (before as unknown as Function).apply(this, args);
+ // Still call the original method if not replacement.
+ if (replace) {
+ res = (replace as unknown as Function).apply(this, args);
+ }
+ else {
+ res = originalMethod.apply(this, args);
+ }
+ after && (after as unknown as Function).apply(this, args);
+ return res;
+ };
+}
+function restoreMethod(
+ obj: T,
+ methodName: keyof T
+) {
+ const savedMethodName = SAVED_METHOD_PREFIX + methodName;
+ if ((obj as any)[savedMethodName]) {
+ obj[methodName] = (obj as any)[savedMethodName];
+ (obj as any)[savedMethodName] = null;
+ }
+}
+
+function applyTransformOnBeziers(bezierCurves: number[][], mm: MatrixArray) {
+ for (let i = 0; i < bezierCurves.length; i++) {
+ const subBeziers = bezierCurves[i];
+ for (let k = 0; k < subBeziers.length;) {
+ const x = subBeziers[k];
+ const y = subBeziers[k + 1];
+
+ subBeziers[k++] = mm[0] * x + mm[2] * y + mm[4];
+ subBeziers[k++] = mm[1] * x + mm[3] * y + mm[5];
+ }
+ }
+}
+
+function prepareMorphPath(
+ fromPath: Path,
+ toPath: Path
+) {
+ const fromPathProxy = fromPath.getUpdatedPathProxy();
+ const toPathProxy = toPath.getUpdatedPathProxy();
+
+ const [fromBezierCurves, toBezierCurves] =
+ alignBezierCurves(pathToBezierCurves(fromPathProxy), pathToBezierCurves(toPathProxy));
+
+ const fromPathTransform = fromPath.getComputedTransform();
+ const toPathTransform = toPath.getComputedTransform();
+ function updateIdentityTransform(this: Transformable) {
+ this.transform = null;
+ }
+ fromPathTransform && applyTransformOnBeziers(fromBezierCurves, fromPathTransform);
+ toPathTransform && applyTransformOnBeziers(toBezierCurves, toPathTransform);
+ // Just ignore transform
+ saveAndModifyMethod(toPath, 'updateTransform', { replace: updateIdentityTransform });
+ toPath.transform = null;
+
+ const morphingData = findBestMorphingRotation(fromBezierCurves, toBezierCurves, 10, Math.PI);
+
+ const tmpArr: number[] = [];
+
+ saveAndModifyMethod(toPath, 'buildPath', { replace(path: PathProxy) {
+ const t = (toPath as MorphingPath).__morphT;
+ const onet = 1 - t;
+
+ const newCp: number[] = [];
+
+ for (let i = 0; i < morphingData.length; i++) {
+ const item = morphingData[i];
+ const from = item.from;
+ const to = item.to;
+ const angle = item.rotation * t;
+ const fromCp = item.fromCp;
+ const toCp = item.toCp;
+ const sa = Math.sin(angle);
+ const ca = Math.cos(angle);
+
+ lerp(newCp, fromCp, toCp, t);
+
+ for (let m = 0; m < from.length; m += 2) {
+ const x0 = from[m];
+ const y0 = from[m + 1];
+ const x1 = to[m];
+ const y1 = to[m + 1];
+
+ const x = x0 * onet + x1 * t;
+ const y = y0 * onet + y1 * t;
+
+ tmpArr[m] = (x * ca - y * sa) + newCp[0];
+ tmpArr[m + 1] = (x * sa + y * ca) + newCp[1];
+ }
+
+ let x0 = tmpArr[0];
+ let y0 = tmpArr[1];
+
+ path.moveTo(x0, y0);
+
+ for (let m = 2; m < from.length;) {
+ const x1 = tmpArr[m++];
+ const y1 = tmpArr[m++];
+ const x2 = tmpArr[m++];
+ const y2 = tmpArr[m++];
+ const x3 = tmpArr[m++];
+ const y3 = tmpArr[m++];
+
+ // Is a line.
+ if (x0 === x1 && y0 === y1 && x2 === x3 && y2 === y3) {
+ path.lineTo(x3, y3);
+ }
+ else {
+ path.bezierCurveTo(x1, y1, x2, y2, x3, y3);
+ }
+ x0 = x3;
+ y0 = y3;
+ }
+ }
+ } });
+}
+
/**
* Morphing from old path to new path.
*/
export function morphPath(
- // `fromPath` only provides the current path state, which will
- // not be rendered or kept.
- // Note:
- // should be able to handle `isIndividualMorphingPath(fromPath)` is `ture`.
fromPath: Path,
- // `toPath` is the target path that will be rendered and kept.
- // Note:
- // (1) `toPath` and `fromPath` might be the same.
- // e.g., when triggering the same transition repeatly.
- // (2) should be able to handle `isIndividualMorphingPath(toPath)` is `ture`.
toPath: Path,
animationOpts: ElementAnimateConfig
): Path {
- let fromPathProxy: PathProxy;
- let toPathProxy: PathProxy;
-
if (!fromPath || !toPath) {
return toPath;
}
- // Calculate the current path into `fromPathProxy` from `fromPathInput`.
- !fromPath.path && fromPath.createPathProxy();
- fromPathProxy = fromPath.path;
- fromPathProxy.beginPath();
- fromPath.buildPath(fromPathProxy, fromPath.shape);
-
- // Calculate the target path into `toPathProxy` from `toPath`.
- !toPath.path && toPath.createPathProxy();
- toPathProxy = toPath.path;
- // From and to might be the same path.
- toPathProxy === fromPathProxy && (toPathProxy = new PathProxy(false));
- toPathProxy.beginPath();
- // toPath should always calculate the final state rather than morphing state.
- if (isIndividualMorphingPath(toPath)) {
- toPath.__oldBuildPath(toPathProxy, toPath.shape);
- }
- else {
- toPath.buildPath(toPathProxy, toPath.shape);
- }
+ const oldDone = animationOpts.done;
+ // const oldAborted = animationOpts.aborted;
+ const oldDuring = animationOpts.during;
- const [fromBezierCurves, toBezierCurves] =
- alignBezierCurves(pathToBezierCurves(fromPathProxy), pathToBezierCurves(toPathProxy));
+ prepareMorphPath(fromPath, toPath);
- const morphingData = findBestMorphingRotation(fromBezierCurves, toBezierCurves, 10, Math.PI);
- becomeIndividualMorphingPath(toPath, morphingData, 0);
+ (toPath as MorphingPath).__morphT = 0;
- const oldDone = animationOpts && animationOpts.done;
- const oldAborted = animationOpts && animationOpts.aborted;
- const oldDuring = animationOpts && animationOpts.during;
+ function restoreToPath() {
+ restoreMethod(toPath, 'buildPath');
+ restoreMethod(toPath, 'updateTransform');
+ // Mark as not in morphing
+ (toPath as MorphingPath).__morphT = -1;
+ // Cleanup.
+ toPath.createPathProxy();
+ toPath.dirtyShape();
+ }
toPath.animateTo({
__morphT: 1
@@ -605,458 +514,376 @@ export function morphPath(
oldDuring && oldDuring(p);
},
done() {
- restoreIndividualMorphingPath(toPath);
- // Cleanup.
- toPath.createPathProxy();
- toPath.dirtyShape();
+ restoreToPath();
oldDone && oldDone();
- },
- aborted() {
- oldAborted && oldAborted();
}
+ // NOTE: Don't do restore if aborted.
+ // Because all status was just set when animation started.
+ // aborted() {
+ // oldAborted && oldAborted();
+ // }
} as ElementAnimateConfig, animationOpts));
return toPath;
}
-function morphingPathBuildPath(
- this: Pick,
- path: PathProxy
-): void {
- const morphingData = this.__morphingData;
- const t = this.__morphT;
- const onet = 1 - t;
-
- const newCp: number[] = [];
- for (let i = 0; i < morphingData.length; i++) {
- const item = morphingData[i];
- const from = item.from;
- const to = item.to;
- const angle = item.rotation * t;
- const fromCp = item.fromCp;
- const toCp = item.toCp;
- const sa = Math.sin(angle);
- const ca = Math.cos(angle);
-
- lerp(newCp, fromCp, toCp, t);
-
- for (let m = 0; m < from.length; m += 2) {
- const x0 = from[m];
- const y0 = from[m + 1];
- const x1 = to[m];
- const y1 = to[m + 1];
-
- const x = x0 * onet + x1 * t;
- const y = y0 * onet + y1 * t;
-
- tmpArr[m] = (x * ca - y * sa) + newCp[0];
- tmpArr[m + 1] = (x * sa + y * ca) + newCp[1];
- }
-
- for (let m = 0; m < from.length;) {
- if (m === 0) {
- path.moveTo(tmpArr[m++], tmpArr[m++]);
+// https://github.com/mapbox/earcut/blob/master/src/earcut.js#L437
+// https://jsfiddle.net/pissang/2jk7x145/
+// function zOrder(x: number, y: number, minX: number, minY: number, maxX: number, maxY: number) {
+// // Normalize coords to 0 - 1
+// // The transformed into non-negative 15-bit integer range
+// x = (maxX === minX) ? 0 : Math.round(32767 * (x - minX) / (maxX - minX));
+// y = (maxY === minY) ? 0 : Math.round(32767 * (y - minY) / (maxY - minY));
+
+// x = (x | (x << 8)) & 0x00FF00FF;
+// x = (x | (x << 4)) & 0x0F0F0F0F;
+// x = (x | (x << 2)) & 0x33333333;
+// x = (x | (x << 1)) & 0x55555555;
+
+// y = (y | (y << 8)) & 0x00FF00FF;
+// y = (y | (y << 4)) & 0x0F0F0F0F;
+// y = (y | (y << 2)) & 0x33333333;
+// y = (y | (y << 1)) & 0x55555555;
+
+// return x | (y << 1);
+// }
+
+// https://github.com/w8r/hilbert/blob/master/hilbert.js#L30
+// https://jsfiddle.net/pissang/xdnbzg6v/
+function hilbert(x: number, y: number, minX: number, minY: number, maxX: number, maxY: number) {
+ const bits = 16;
+ x = (maxX === minX) ? 0 : Math.round(32767 * (x - minX) / (maxX - minX));
+ y = (maxY === minY) ? 0 : Math.round(32767 * (y - minY) / (maxY - minY));
+
+ let d = 0;
+ let tmp: number;
+ for (let s = (1 << bits) / 2; s > 0; s /= 2) {
+ let rx = 0, ry = 0;
+
+ if ((x & s) > 0) rx = 1;
+ if ((y & s) > 0) ry = 1;
+
+ d += s * s * ((3 * rx) ^ ry);
+
+ if (ry === 0) {
+ if (rx === 1) {
+ x = s - 1 - x;
+ y = s - 1 - y;
}
- path.bezierCurveTo(
- tmpArr[m++], tmpArr[m++],
- tmpArr[m++], tmpArr[m++],
- tmpArr[m++], tmpArr[m++]
- );
+ tmp = x;
+ x = y;
+ y = tmp;
}
}
-};
-
-function becomeIndividualMorphingPath(
- path: Path,
- morphingData: MorphingData,
- morphT: number
-): void {
- if (isIndividualMorphingPath(path)) {
- updateIndividualMorphingPath(path, morphingData, morphT);
- return;
- }
+ return d;
+}
+
+// Sort paths on hilbert. Not using z-order because it may still have large cross.
+// So the left most source can animate to the left most target, not right most target.
+// Hope in this way. We can make sure each element is animated to the proper target. Not the farthest.
+function sortPaths(pathList: Path[]): Path[] {
+ let xMin = Infinity;
+ let yMin = Infinity;
+ let xMax = -Infinity;
+ let yMax = -Infinity;
+ const cps = map(pathList, path => {
+ const rect = path.getBoundingRect();
+ const m = path.getComputedTransform();
+ const x = rect.x + rect.width / 2 + (m ? m[4] : 0);
+ const y = rect.y + rect.height / 2 + (m ? m[5] : 0);
+ xMin = Math.min(x, xMin);
+ yMin = Math.min(y, yMin);
+ xMax = Math.max(x, xMax);
+ yMax = Math.max(y, yMax);
+ return [x, y];
+ });
+
+ const items = map(cps, (cp, idx) => {
+ return {
+ cp,
+ z: hilbert(cp[0], cp[1], xMin, yMin, xMax, yMax),
+ path: pathList[idx]
+ }
+ });
- const morphingPath = path as MorphingPath;
- morphingPath.__oldBuildPath = morphingPath.buildPath;
- morphingPath.buildPath = morphingPathBuildPath;
- updateIndividualMorphingPath(morphingPath, morphingData, morphT);
+ return items.sort((a, b) => a.z - b.z).map(item => item.path);
}
-function updateIndividualMorphingPath(
- morphingPath: MorphingPath,
- morphingData: MorphingData,
- morphT: number
-): void {
- morphingPath.__morphingData = morphingData;
- morphingPath.__morphT = morphT;
+export interface DividePathParams {
+ path: Path,
+ count: number
+};
+export interface DividePath {
+ (params: DividePathParams): Path[]
}
-function restoreIndividualMorphingPath(path: Path): void {
- if (isIndividualMorphingPath(path)) {
- path.buildPath = path.__oldBuildPath;
- path.__oldBuildPath = path.__morphingData = null;
- }
+export interface IndividualDelay {
+ (index: number, count: number, fromPath: Path, toPath: Path): number
}
-function isIndividualMorphingPath(path: Path): path is MorphingPath {
- return (path as MorphingPath).__oldBuildPath != null;
+function defaultDividePath(param: DividePathParams) {
+ return split(param.path, param.count);
}
-
-export function isCombiningPath(path: Path): path is CombiningPath {
- return !!(path as CombiningPath).__combiningSubList;
+export interface CombineConfig extends ElementAnimateConfig {
+ /**
+ * Transform of returned will be ignored.
+ */
+ dividePath?: DividePath
+ /**
+ * delay of each individual.
+ * Because individual are sorted on z-order. The index is also sorted top-left / right-down.
+ */
+ individualDelay?: IndividualDelay
+ /**
+ * If sort splitted paths so the movement between them can be more natural
+ */
+ // sort?: boolean
}
-export function isInAnyMorphing(path: Path): boolean {
- return isIndividualMorphingPath(path) || isCombiningPath(path);
+function createEmptyReturn() {
+ return {
+ fromIndividuals: [] as Path[],
+ toIndividuals: [] as Path[],
+ count: 0
+ }
}
-
-
/**
- * Make combining morphing from many paths to one.
- * Make the MorphingKind of `toPath` become `'COMBINING'`.
+ * Make combine morphing from many paths to one.
+ * Will return a group to replace the original path.
*/
-export function combine(
- fromPathList: Path[],
+export function combineMorph(
+ fromList: (CombineMorphingPath | Path)[],
toPath: Path,
- animationOpts: CombineSeparateConfig,
- copyPropsIfDivided?: (srcPath: Path, tarPath: Path, needClone: boolean) => void
-): CombineSeparateResult {
-
- const fromIndividuals: Path[] = [];
- let separateCount = 0;
- for (let i = 0; i < fromPathList.length; i++) {
- const fromPath = fromPathList[i];
- if (isCombiningPath(fromPath)) {
- // If fromPath is combining, use the combineFromList as the from.
- const fromCombiningSubList = fromPath.__combiningSubList;
- for (let j = 0; j < fromCombiningSubList.length; j++) {
- fromIndividuals.push(fromCombiningSubList[j]);
- }
- separateCount += fromCombiningSubList.length;
- }
- else {
- fromIndividuals.push(fromPath);
- separateCount++;
- }
- }
-
- // fromPathList.length is 0.
- if (!separateCount) {
- return;
- }
-
- // PENDING: more separate strategies other than `divideShape`?
- const dividingMethod = animationOpts ? animationOpts.dividingMethod : null;
- const toPathSplittedList = divideShape(toPath, separateCount, dividingMethod);
- assert(toPathSplittedList.length === separateCount);
-
- const oldDone = animationOpts && animationOpts.done;
- const oldAborted = animationOpts && animationOpts.aborted;
- const oldDuring = animationOpts && animationOpts.during;
+ animationOpts: CombineConfig
+) {
+ let fromPathList: Path[] = [];
- let doneCount = 0;
- let abortedCalled = false;
- const morphAnimationOpts = defaults({
- during(p) {
- oldDuring && oldDuring(p);
- },
- done() {
- doneCount++;
- if (doneCount === toPathSplittedList.length) {
- restoreCombiningPath(toPath);
- oldDone && oldDone();
+ function addFromPath(fromList: Element[]) {
+ for (let i = 0; i < fromList.length; i++) {
+ const from = fromList[i];
+ if (isCombineMorphing(from)) {
+ addFromPath((from as GroupLike).childrenRef());
}
- },
- aborted() {
- // PENDING: is it logically correct?
- if (!abortedCalled) {
- abortedCalled = true;
- oldAborted && oldAborted();
+ else if (from instanceof Path) {
+ fromPathList.push(from);
}
}
- } as ElementAnimateConfig, animationOpts);
-
- for (let i = 0; i < separateCount; i++) {
- const from = fromIndividuals[i];
- const to = toPathSplittedList[i];
- copyPropsIfDivided && copyPropsIfDivided(toPath, to, true);
- morphPath(from, to, morphAnimationOpts);
}
+ addFromPath(fromList);
- becomeCombiningPath(toPath, toPathSplittedList);
-
- return {
- fromIndividuals: fromIndividuals,
- toIndividuals: toPathSplittedList,
- count: separateCount
- };
-}
-
+ const separateCount = fromPathList.length;
-// PENDING: This is NOT a good implementation to decorate path methods.
-// Potential flaw: when get path by `group.childAt(i)`,
-// it might return the `combiningSubList` group, which is not expected.
-// Probably this feature should be implemented same as the way of rich text?
-function becomeCombiningPath(path: Path, combiningSubList: Path[]): void {
- if (isCombiningPath(path)) {
- updateCombiningPathSubList(path, combiningSubList);
- return;
+ // fromPathList.length is 0.
+ if (!separateCount) {
+ return createEmptyReturn();
}
- const combiningPath = path as CombiningPath;
+ const dividePath = animationOpts.dividePath || defaultDividePath;
- updateCombiningPathSubList(combiningPath, combiningSubList);
+ let toSubPathList = dividePath({
+ path: toPath, count: separateCount
+ });
+ if (toSubPathList.length !== separateCount) {
+ console.error('Invalid morphing: unmatched splitted path');
+ return createEmptyReturn();
+ }
- // PENDING: Too tricky. error-prone.
- // Decorate methods. Do not do it repeatly.
- combiningPath.__oldAddSelfToZr = path.addSelfToZr;
- combiningPath.__oldRemoveSelfFromZr = path.removeSelfFromZr;
- combiningPath.addSelfToZr = combiningAddSelfToZr;
- combiningPath.removeSelfFromZr = combiningRemoveSelfFromZr;
- combiningPath.__oldBuildPath = combiningPath.buildPath;
- combiningPath.buildPath = noop;
- combiningPath.childrenRef = combiningChildrenRef;
+ fromPathList = sortPaths(fromPathList);
+ toSubPathList = sortPaths(toSubPathList);
- // PENDING: bounding rect?
-}
+ const oldDone = animationOpts.done;
+ // const oldAborted = animationOpts.aborted;
+ const oldDuring = animationOpts.during;
+ const individualDelay = animationOpts.individualDelay;
-function restoreCombiningPath(path: Path): void {
- if (!isCombiningPath(path)) {
- return;
- }
+ const identityTransform = new Transformable();
- const combiningPath = path as CombiningPath;
+ for (let i = 0; i < separateCount; i++) {
+ const from = fromPathList[i];
+ const to = toSubPathList[i];
- updateCombiningPathSubList(combiningPath, null);
+ to.parent = toPath as unknown as Group;
- combiningPath.addSelfToZr = combiningPath.__oldAddSelfToZr;
- combiningPath.removeSelfFromZr = combiningPath.__oldRemoveSelfFromZr;
- combiningPath.buildPath = combiningPath.__oldBuildPath;
- combiningPath.childrenRef =
- combiningPath.__combiningSubList =
- combiningPath.__oldAddSelfToZr =
- combiningPath.__oldRemoveSelfFromZr =
- combiningPath.__oldBuildPath = null;
-}
+ // Ignore transform in each subpath.
+ to.copyTransform(identityTransform);
-function updateCombiningPathSubList(
- combiningPath: CombiningPath,
- // Especially, `combiningSubList` is null/undefined means that remove sub list.
- combiningSubList: Path[]
-): void {
- if (combiningPath.__combiningSubList !== combiningSubList) {
- combiningPathSubListAddRemoveWithZr(combiningPath, 'removeSelfFromZr');
- combiningPath.__combiningSubList = combiningSubList;
- if (combiningSubList) {
- for (let i = 0; i < combiningSubList.length; i++) {
- // Tricky: make `updateTransform` work in `Transformable`. The parent can only be Group.
- combiningSubList[i].parent = combiningPath as unknown as Group;
- }
+ // Will do morphPath for each individual if individualDelay is set.
+ if (!individualDelay) {
+ prepareMorphPath(from, to);
}
- combiningPathSubListAddRemoveWithZr(combiningPath, 'addSelfToZr');
}
-}
-function combiningAddSelfToZr(this: CombiningPath, zr: ZRenderType): void {
- this.__oldAddSelfToZr(zr);
- combiningPathSubListAddRemoveWithZr(this, 'addSelfToZr');
-}
+ (toPath as CombineMorphingPath).__isCombineMorphing = true;
+ (toPath as CombineMorphingPath).childrenRef = function () {
+ return toSubPathList;
+ };
-function combiningPathSubListAddRemoveWithZr(
- path: CombiningPath,
- method: 'addSelfToZr' | 'removeSelfFromZr'
-): void {
- const combiningSubList = path.__combiningSubList;
- const zr = path.__zr;
- if (combiningSubList && zr) {
- for (let i = 0; i < combiningSubList.length; i++) {
- const child = combiningSubList[i];
- child[method](zr);
+ function addToSubPathListToZr(zr: ZRenderType) {
+ for (let i = 0; i < toSubPathList.length; i++) {
+ toSubPathList[i].addSelfToZr(zr);
}
}
-}
-
-function combiningRemoveSelfFromZr(this: CombiningPath, zr: ZRenderType): void {
- this.__oldRemoveSelfFromZr(zr);
- const combiningSubList = this.__combiningSubList;
- for (let i = 0; i < combiningSubList.length; i++) {
- const child = combiningSubList[i];
- child.removeSelfFromZr(zr);
- }
-}
+ saveAndModifyMethod(toPath, 'addSelfToZr', {
+ after(zr) {
+ addToSubPathListToZr(zr);
+ }
+ });
+ saveAndModifyMethod(toPath, 'removeSelfFromZr', {
+ after(zr) {
+ for (let i = 0; i < toSubPathList.length; i++) {
+ toSubPathList[i].removeSelfFromZr(zr);
+ }
+ }
+ });
-function combiningChildrenRef(this: CombiningPath): Path[] {
- return this.__combiningSubList;
-}
+ function restoreToPath() {
+ (toPath as CombineMorphingPath).__isCombineMorphing = false;
+ // Mark as not in morphing
+ (toPath as MorphingPath).__morphT = -1;
+ (toPath as CombineMorphingPath).childrenRef = null;
+ restoreMethod(toPath, 'addSelfToZr');
+ restoreMethod(toPath, 'removeSelfFromZr');
+ }
-/**
- * Make separate morphing from one path to many paths.
- * Make the MorphingKind of `toPath` become `'ONE_ONE'`.
- */
-export function separate(
- fromPath: Path,
- toPathList: Path[],
- animationOpts: CombineSeparateConfig,
- copyPropsIfDivided?: (srcPath: Path, tarPath: Path, needClone: boolean) => void
-): CombineSeparateResult {
- const toPathListLen = toPathList.length;
- let fromPathList: Path[];
- const dividingMethod = animationOpts ? animationOpts.dividingMethod : null;
- let copyProps = false;
+ const toLen = toSubPathList.length;
- // This case most happen when a combining path is called to reverse the animation
- // to its original separated state.
- if (isCombiningPath(fromPath)) {
- // [CATEAT]:
- // do not `restoreCombiningPath`, because it will cause the sub paths been removed
- // from its host, so that the original "global transform" can not be gotten any more.
-
- const fromCombiningSubList = fromPath.__combiningSubList;
- if (fromCombiningSubList.length === toPathListLen) {
- fromPathList = fromCombiningSubList;
+ if (individualDelay) {
+ let animating = toLen;
+ const eachDone = () => {
+ animating--;
+ if (animating === 0) {
+ restoreToPath();
+ oldDone && oldDone();
+ }
}
- // The fromPath is a `CombiningPath` and its combiningSubCount is different from toPathList.length
- // At present we do not make "continuous" animation for this case. It's might bring complicated logic.
- else {
- fromPathList = divideShape(fromPath, toPathListLen, dividingMethod);
- copyProps = true;
+ // Animate each element individually.
+ for (let i = 0; i < toLen; i++) {
+ // TODO only call during once?
+ const indivdualAnimationOpts = individualDelay ? defaults({
+ delay: (animationOpts.delay || 0) + individualDelay(i, toLen, fromPathList[i], toSubPathList[i]),
+ done: eachDone
+ } as ElementAnimateConfig, animationOpts) : animationOpts;
+ morphPath(fromPathList[i], toSubPathList[i], indivdualAnimationOpts);
}
}
else {
- fromPathList = divideShape(fromPath, toPathListLen, dividingMethod);
- copyProps = true;
+ (toPath as MorphingPath).__morphT = 0;
+ toPath.animateTo({
+ __morphT: 1
+ } as any, defaults({
+ during(p) {
+ for (let i = 0; i < toLen; i++) {
+ const child = toSubPathList[i] as MorphingPath;
+ child.__morphT = (toPath as MorphingPath).__morphT;
+ child.dirtyShape();
+ }
+ oldDuring && oldDuring(p);
+ },
+ done() {
+ restoreToPath();
+ for (let i = 0; i < fromList.length; i++) {
+ restoreMethod(fromList[i], 'updateTransform');
+ }
+ oldDone && oldDone();
+ }
+ } as ElementAnimateConfig, animationOpts));
}
- assert(fromPathList.length === toPathListLen);
- for (let i = 0; i < toPathListLen; i++) {
- if (copyProps && copyPropsIfDivided) {
- copyPropsIfDivided(fromPath, fromPathList[i], false);
- }
- morphPath(fromPathList[i], toPathList[i], animationOpts);
+ if (toPath.__zr) {
+ addToSubPathListToZr(toPath.__zr);
}
return {
fromIndividuals: fromPathList,
- toIndividuals: toPathList,
- count: toPathListLen
+ toIndividuals: toSubPathList,
+ count: toLen
};
}
-
-
-/**
- * TODO: triangulate separate
- *
- * @return Never be null/undefined, may empty [].
- */
-function divideShape(
- path: Path,
- separateCount: number,
- // By default 'split'.
- dividingMethod?: MorphDividingMethod
-): Path[] {
- return dividingMethod === 'duplicate'
- ? duplicateShape(path, separateCount)
- : splitShape(path, separateCount);
+export interface SeparateConfig extends ElementAnimateConfig {
+ dividePath?: DividePath
+ individualDelay?: IndividualDelay
+ /**
+ * If sort splitted paths so the movement between them can be more natural
+ */
+ // sort?: boolean
+ // // If the from path of separate animation is doing combine animation.
+ // // And the paths number is not same with toPathList. We need to do enter/leave animation
+ // // on the missing/spare paths.
+ // enter?: (el: Path) => void
+ // leave?: (el: Path) => void
}
/**
- * @return Never be null/undefined, may empty [].
+ * Make separate morphing from one path to many paths.
+ * Make the MorphingKind of `toPath` become `'ONE_ONE'`.
*/
-function splitShape(
- path: Path,
- separateCount: number
-): Path[] {
- const resultPaths: Path[] = [];
- if (separateCount <= 0) {
- return resultPaths;
- }
- if (separateCount === 1) {
- return duplicateShape(path, separateCount);
- }
+export function separateMorph(
+ fromPath: Path,
+ toPathList: Path[],
+ animationOpts: SeparateConfig
+) {
+ const toLen = toPathList.length;
+ let fromPathList: Path[] = [];
- if (path instanceof Rect) {
- const toPathShape = path.shape;
- const splitPropIdx = toPathShape.height > toPathShape.width ? 1 : 0;
- const propWH = PROP_WH[splitPropIdx];
- const propXY = PROP_XY[splitPropIdx];
- const subWH = toPathShape[propWH] / separateCount;
- let xyCurr = toPathShape[propXY];
-
- for (let i = 0; i < separateCount; i++, xyCurr += subWH) {
- const subShape = {
- x: toPathShape.x,
- y: toPathShape.y,
- width: toPathShape.width,
- height: toPathShape.height
- };
- subShape[propXY] = xyCurr;
- subShape[propWH] = i < separateCount - 1
- ? subWH
- : toPathShape[propXY] + toPathShape[propWH] - xyCurr;
- const splitted = new Rect({ shape: subShape });
- resultPaths.push(splitted);
+ const dividePath = animationOpts.dividePath || defaultDividePath;
+
+ function addFromPath(fromList: Element[]) {
+ for (let i = 0; i < fromList.length; i++) {
+ const from = fromList[i];
+ if (isCombineMorphing(from)) {
+ addFromPath((from as GroupLike).childrenRef());
+ }
+ else if (from instanceof Path) {
+ fromPathList.push(from);
+ }
}
}
- else if (path instanceof Sector) {
- const toPathShape = path.shape;
- const clockwise = toPathShape.clockwise;
- const startAngle = toPathShape.startAngle;
- const endAngle = toPathShape.endAngle;
- const endAngleNormalized = normalizeRadian(startAngle, toPathShape.endAngle, clockwise);
- const step = (endAngleNormalized - startAngle) / separateCount;
- let angleCurr = startAngle;
- for (let i = 0; i < separateCount; i++, angleCurr += step) {
- const splitted = new Sector({
- shape: {
- cx: toPathShape.cx,
- cy: toPathShape.cy,
- r: toPathShape.r,
- r0: toPathShape.r0,
- clockwise: clockwise,
- startAngle: angleCurr,
- endAngle: i === separateCount - 1 ? endAngle : angleCurr + step
- }
- });
- resultPaths.push(splitted);
+ // This case most happen when a combining path is called to reverse the animation
+ // to its original separated state.
+ if (isCombineMorphing(fromPath)) {
+ addFromPath(fromPath.childrenRef());
+
+ const fromLen = fromPathList.length;
+ if (fromLen < toLen) {
+ let k = 0;
+ for (let i = fromLen; i < toLen; i++) {
+ // Create a clone
+ fromPathList.push(clonePath(fromPathList[k++ % fromLen]));
+ }
}
+ // Else simply remove if fromLen > toLen
+ fromPathList.length = toLen;
}
- // TODO: triangulate path and split.
- // And should consider path is morphing.
else {
- return duplicateShape(path, separateCount);
+ fromPathList = dividePath({ path: fromPath, count: toLen });
+ const fromPathTransform = fromPath.getComputedTransform();
+ for (let i = 0; i < fromPathList.length; i++) {
+ // Force use transform of source path.
+ fromPathList[i].setLocalTransform(fromPathTransform);
+ }
+ if (fromPathList.length !== toLen) {
+ console.error('Invalid morphing: unmatched splitted path');
+ return createEmptyReturn();
+ }
}
- return resultPaths;
-}
+ fromPathList = sortPaths(fromPathList);
+ toPathList = sortPaths(toPathList);
-/**
- * @return Never be null/undefined, may empty [].
- */
-function duplicateShape(
- path: Path,
- separateCount: number
-): Path[] {
- const resultPaths: Path[] = [];
- if (separateCount <= 0) {
- return resultPaths;
+ const individualDelay = animationOpts.individualDelay;
+ for (let i = 0; i < toLen; i++) {
+ const indivdualAnimationOpts = individualDelay ? defaults({
+ delay: (animationOpts.delay || 0) + individualDelay(i, toLen, fromPathList[i], toPathList[i])
+ } as ElementAnimateConfig, animationOpts) : animationOpts;
+ morphPath(fromPathList[i], toPathList[i], indivdualAnimationOpts);
}
- const ctor = path.constructor;
- for (let i = 0; i < separateCount; i++) {
- const sub = new (ctor as any)({
- shape: clone(path.shape)
- });
- resultPaths.push(sub);
- }
- return resultPaths;
-}
-/**
- * If `clockwise`, normalize the `end` to the interval `[start, start + 2 * PI)` and return.
- * else, normalize the `end` to the interval `(start - 2 * PI, start]` and return.
- */
-function normalizeRadian(start: number, end: number, clockwise: boolean): number {
- return end + PI2 * (
- Math[clockwise ? 'ceil' : 'floor']((start - end) / PI2)
- );
+ return {
+ fromIndividuals: fromPathList,
+ toIndividuals: toPathList,
+ count: toPathList.length
+ };
}
+
+export { split as defaultDividePath }
\ No newline at end of file
diff --git a/src/tool/path.ts b/src/tool/path.ts
index fefb3dfd0..76c4d099b 100644
--- a/src/tool/path.ts
+++ b/src/tool/path.ts
@@ -448,13 +448,7 @@ export function mergePath(pathEls: Path[], opts: PathProps) {
const len = pathEls.length;
for (let i = 0; i < len; i++) {
const pathEl = pathEls[i];
- if (!pathEl.path) {
- pathEl.createPathProxy();
- }
- if (pathEl.shapeChanged()) {
- pathEl.buildPath(pathEl.path, pathEl.shape, true);
- }
- pathList.push(pathEl.path);
+ pathList.push(pathEl.getUpdatedPathProxy(true));
}
const pathBundle = new Path(opts);
@@ -473,4 +467,48 @@ export function mergePath(pathEls: Path[], opts: PathProps) {
};
return pathBundle;
-}
\ No newline at end of file
+}
+
+/**
+ * Clone a path.
+ */
+export function clonePath(sourcePath: Path, opts?: {
+ /**
+ * If bake global transform to path.
+ */
+ bakeTransform?: boolean
+ /**
+ * Convert global transform to local.
+ */
+ toLocal?: boolean
+}) {
+ opts = opts || {};
+ const path = new Path();
+ if (sourcePath.shape) {
+ path.setShape(sourcePath.shape);
+ }
+ path.setStyle(sourcePath.style);
+
+ if (opts.bakeTransform) {
+ transformPath(path.path, sourcePath.getComputedTransform());
+ }
+ else {
+ // TODO Copy getLocalTransform, updateTransform since they can be changed.
+ if (opts.toLocal) {
+ path.setLocalTransform(sourcePath.getComputedTransform());
+ }
+ else {
+ path.copyTransform(sourcePath);
+ }
+ }
+
+ // These methods may be overridden
+ path.buildPath = sourcePath.buildPath;
+ (path as SVGPath).applyTransform = (path as SVGPath).applyTransform;
+
+ path.z = sourcePath.z;
+ path.z2 = sourcePath.z2;
+ path.zlevel = sourcePath.zlevel;
+
+ return path;
+}
diff --git a/src/tool/transformPath.ts b/src/tool/transformPath.ts
index 3245e8f64..e6ff2f433 100644
--- a/src/tool/transformPath.ts
+++ b/src/tool/transformPath.ts
@@ -9,6 +9,10 @@ const mathSqrt = Math.sqrt;
const mathAtan2 = Math.atan2;
export default function transformPath(path: PathProxy, m: MatrixArray) {
+ if (!m) {
+ return;
+ }
+
let data = path.data;
const len = path.len();
let cmd;
diff --git a/src/zrender.ts b/src/zrender.ts
index 9ca502a8c..a1d02b6e9 100644
--- a/src/zrender.ts
+++ b/src/zrender.ts
@@ -16,7 +16,7 @@ import {PainterBase} from './PainterBase';
import Animation from './animation/Animation';
import HandlerProxy from './dom/HandlerProxy';
import Element, {ElementEventCallback, ElementEvent} from './Element';
-import { Dictionary, ElementEventName, RenderedEvent } from './core/types';
+import { Dictionary, ElementEventName, RenderedEvent, WithThisType } from './core/types';
import { LayerConfig } from './canvas/Layer';
import { GradientObject } from './graphic/Gradient';
import { PatternObject } from './graphic/Pattern';
@@ -406,10 +406,10 @@ class ZRender {
return this.handler.findHover(x, y);
}
- on(eventName: ElementEventName, eventHandler: ElementEventCallback, context?: Ctx): this
- on(eventName: string, eventHandler: EventCallback, context?: Ctx): this
+ on(eventName: ElementEventName, eventHandler: ElementEventCallback, context?: Ctx): this
+ on(eventName: string, eventHandler: WithThisType, unknown extends Ctx ? ZRenderType : Ctx>, context?: Ctx): this
// eslint-disable-next-line max-len
- on(eventName: string, eventHandler: EventCallback | EventCallback, context?: Ctx): this {
+ on(eventName: string, eventHandler: (...args: any) => any, context?: Ctx): this {
this.handler.on(eventName, eventHandler, context);
return this;
}
@@ -420,7 +420,7 @@ class ZRender {
* @param eventHandler Handler function
*/
// eslint-disable-next-line max-len
- off(eventName?: string, eventHandler?: EventCallback | EventCallback) {
+ off(eventName?: string, eventHandler?: EventCallback) {
this.handler.off(eventName, eventHandler);
}
diff --git a/test/boundingbox.html b/test/boundingbox.html
index a6a7f3f30..61e7b4298 100644
--- a/test/boundingbox.html
+++ b/test/boundingbox.html
@@ -12,13 +12,13 @@
+
+
+
+